| 两侧同时换到之前的修订记录
前一修订版
后一修订版
|
前一修订版
|
en:stpc-exxx-c [2026/11/14 00:10] wjj Fix Developer options path to Settings → System → Advanced → Developer options |
en:stpc-exxx-c [2026/11/14 03:52] (当前版本) wjj 3D 透视配色与透明度终值:蓝灰鬼影 + 内部零件加重 |
| #bodyContent .sml-doc p.sml-note{min-width:min(100%,240px)!important;} | #bodyContent .sml-doc p.sml-note{min-width:min(100%,240px)!important;} |
| } | } |
| | .sml-3d{position:absolute;inset:0;display:none;z-index:4;background:#f3f6fa;}.sml-g-stage.on3d{height:430px;}.sml-g-stage.on3d>*:not(.sml-3d){visibility:hidden;}.sml-g-stage.on3d .sml-3d{display:block;}.sml-g-side.on3d{justify-content:flex-start;}.sml-g-side.on3d button.sml-g-thumb{flex:0 0 auto;min-height:38px;}button.sml-g-thumb[data-3d] svg{display:block;width:100%;height:100%;}@media (max-width:900px){.sml-g-stage.on3d{height:330px;}.sml-g-side{flex-wrap:wrap!important;gap:4px!important;}button.sml-g-thumb{flex:1 1 calc(33.333% - 3px)!important;min-width:0;}.sml-g-side.on3d button.sml-g-thumb{min-height:44px;}.sml-g-cap{max-width:calc(100% - 78px);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;}} |
| | .sml-g-side{flex:0 0 168px;} |
| </style></html> | </style></html> |
| <html><script> | <html><script> |
| <div class="sml-gallery"> | <div class="sml-gallery"> |
| <div class="sml-g-head"><div><div class="sml-g-kicker">PRODUCT GALLERY / Product Gallery</div><div class="sml-g-title">sTPC-EXXX-C Series · Unit Appearance</div></div><div class="sml-g-note">Using the E156 model as an example · Click a view on the right to switch</div></div> | <div class="sml-g-head"><div><div class="sml-g-kicker">PRODUCT GALLERY / Product Gallery</div><div class="sml-g-title">sTPC-EXXX-C Series · Unit Appearance</div></div><div class="sml-g-note">Using the E156 model as an example · Click a view on the right to switch</div></div> |
| <div class="sml-g-body"><div class="sml-g-stage"><img id="sml-g-main" class="sml-g-main" src="/dokuwiki/lib/exe/fetch.php?media=wiki:stpc_e156c_front_v.jpg" alt="Front View · Multi-Point Capacitive Touch Screen"><div class="sml-g-badge">INDUSTRIAL PANEL PC</div><a id="sml-g-zoom" class="sml-g-zoom" href="/dokuwiki/lib/exe/fetch.php?media=wiki:stpc_e156c_front.jpg" target="_blank" rel="noopener">View Full-Resolution Image ↗</a><div class="sml-g-cap" id="sml-g-cap">Front View · Multi-Point Capacitive Touch Screen</div><div class="sml-g-count" id="sml-g-count">01 / 04</div></div><div class="sml-g-side"><div class="sml-g-side-hd">Unit Views / 04 VIEWS</div><button type="button" class="sml-g-thumb on" data-view="/dokuwiki/lib/exe/fetch.php?media=wiki:stpc_e156c_front_v.jpg" data-full="/dokuwiki/lib/exe/fetch.php?media=wiki:stpc_e156c_front.jpg" data-idx="01" data-cap="Front View · Multi-Point Capacitive Touch Screen"><span class="sml-g-tn"><img src="/dokuwiki/lib/exe/fetch.php?media=wiki:stpc_e156c_thumb_01.jpg" alt="Front View"></span><span class="sml-g-tx"><b>VIEW 01</b>Front View</span></button><button type="button" class="sml-g-thumb" data-view="/dokuwiki/lib/exe/fetch.php?media=wiki:stpc_e156c_persp_v.jpg" data-full="/dokuwiki/lib/exe/fetch.php?media=wiki:stpc_e156c_persp.jpg" data-idx="02" data-cap="Perspective View · Unit Structure and Rear Main Bay"><span class="sml-g-tn"><img src="/dokuwiki/lib/exe/fetch.php?media=wiki:stpc_e156c_thumb_02.jpg" alt="Perspective View"></span><span class="sml-g-tx"><b>VIEW 02</b>Perspective View</span></button><button type="button" class="sml-g-thumb" data-view="/dokuwiki/lib/exe/fetch.php?media=wiki:stpc_e156c_back_v.jpg" data-full="/dokuwiki/lib/exe/fetch.php?media=wiki:stpc_e156c_back.jpg" data-idx="03" data-cap="Rear View · Fully Sealed Rear Cover and VESA Holes"><span class="sml-g-tn"><img src="/dokuwiki/lib/exe/fetch.php?media=wiki:stpc_e156c_thumb_03.jpg" alt="Rear View"></span><span class="sml-g-tx"><b>VIEW 03</b>Rear View</span></button><button type="button" class="sml-g-thumb" data-view="/dokuwiki/lib/exe/fetch.php?media=wiki:stpc_e156c_io_v.jpg" data-full="/dokuwiki/lib/exe/fetch.php?media=wiki:stpc_e156c_io.jpg" data-idx="04" data-cap="Interface View · External I/O Concentrated on the Lower Rear"><span class="sml-g-tn"><img src="/dokuwiki/lib/exe/fetch.php?media=wiki:stpc_e156c_thumb_04.jpg" alt="Interface View"></span><span class="sml-g-tx"><b>VIEW 04</b>Interface View</span></button></div></div> | <div class="sml-g-body"><div class="sml-g-stage"><img id="sml-g-main" class="sml-g-main" src="/dokuwiki/lib/exe/fetch.php?media=wiki:stpc_e156c_front_v.jpg" alt="Front View · Multi-Point Capacitive Touch Screen"><div class="sml-g-badge">INDUSTRIAL PANEL PC</div><a id="sml-g-zoom" class="sml-g-zoom" href="/dokuwiki/lib/exe/fetch.php?media=wiki:stpc_e156c_front.jpg" target="_blank" rel="noopener">View Full-Resolution Image ↗</a><div class="sml-3d" id="sml-3d"></div><div class="sml-g-cap" id="sml-g-cap">Front View · Capacitive Touch Panel</div><div class="sml-g-count" id="sml-g-count">01 / 05</div></div><div class="sml-g-side"><div class="sml-g-side-hd">Unit Views / 05 VIEWS</div><button type="button" class="sml-g-thumb on" data-view="/dokuwiki/lib/exe/fetch.php?media=wiki:stpc_e156c_front_v.jpg" data-full="/dokuwiki/lib/exe/fetch.php?media=wiki:stpc_e156c_front.jpg" data-idx="01" data-cap="Front View · Capacitive Touch Panel"><span class="sml-g-tn"><img src="/dokuwiki/lib/exe/fetch.php?media=wiki:stpc_e156c_thumb_01.jpg" alt="Front View"></span><span class="sml-g-tx"><b>SLC</b>Front View</span></button><button type="button" class="sml-g-thumb" data-view="/dokuwiki/lib/exe/fetch.php?media=wiki:stpc_e156c_persp_v.jpg" data-full="/dokuwiki/lib/exe/fetch.php?media=wiki:stpc_e156c_persp.jpg" data-idx="02" data-cap="Perspective View · Main Module"><span class="sml-g-tn"><img src="/dokuwiki/lib/exe/fetch.php?media=wiki:stpc_e156c_thumb_02.jpg" alt="Perspective View"></span><span class="sml-g-tx"><b>SLC</b>Perspective View</span></button><button type="button" class="sml-g-thumb" data-view="/dokuwiki/lib/exe/fetch.php?media=wiki:stpc_e156c_back_v.jpg" data-full="/dokuwiki/lib/exe/fetch.php?media=wiki:stpc_e156c_back.jpg" data-idx="03" data-cap="Rear View · Mounting Back Plate"><span class="sml-g-tn"><img src="/dokuwiki/lib/exe/fetch.php?media=wiki:stpc_e156c_thumb_03.jpg" alt="Rear View"></span><span class="sml-g-tx"><b>SLC</b>Rear View</span></button><button type="button" class="sml-g-thumb" data-view="/dokuwiki/lib/exe/fetch.php?media=wiki:stpc_e156c_io_v.jpg" data-full="/dokuwiki/lib/exe/fetch.php?media=wiki:stpc_e156c_io.jpg" data-idx="04" data-cap="Interface View · Power and Network"><span class="sml-g-tn"><img src="/dokuwiki/lib/exe/fetch.php?media=wiki:stpc_e156c_thumb_04.jpg" alt="Interface View"></span><span class="sml-g-tx"><b>SLC</b>Interface View</span></button> |
| | <button type="button" class="sml-g-thumb" data-3d="1" data-idx="05" data-zip="/dokuwiki/lib/exe/fetch.php?media=wiki:stpc_e156c_3d.zip" data-lang="en" data-size="about 3.4 MB" data-rear="0" data-parts="[["后壳","Rear Shell","#eef2f5",1.25,-1],["前框","Front Bezel","#e6ebef",1.55,1],["显示模组","Display Module","#0b1015",1.0,1],["角部盖板","Corner Plate","#e2e8ed",1.7,1],["触摸屏","Touch Panel","#151d26",1.8,1],["前视窗玻璃","Front Glass","#0a0d11",1.95,1],["主板组件","Main Board","#2f6f59",0.7,-1],["连接器","Connector","#a45a52",0.35,-1],["接口座","I/O Socket","#a45a52",0.4,-1],["接口板","I/O Board","#2f6f59",0.5,-1],["屏蔽板","Shield Plate","#dfe5ea",0.5,-1],["按键座","Button Seat","#a45a52",0.35,-1],["导光件","Light Guide","#c9a227",0.3,-1],["紧固件","Fastener","#9aa4b0",0.2,-1],["显示面板","Display Panel","#0a0e12",2.0,1]]" data-cap="3D Teardown · Part Structure"><span class="sml-g-tn"><svg viewBox="0 0 44 30" xmlns="http://www.w3.org/2000/svg" aria-hidden="true"><g fill="none" stroke="#0b5394" stroke-width="1.3" stroke-linejoin="round"><path d="M12 8 L22 3 L32 8 L22 13 Z"/><path d="M12 8 L12 20 L22 25 L22 13"/><path d="M32 8 L32 20 L22 25"/><path d="M17 5.5 L27 10.5" stroke="#8fb2d6" stroke-width="1"/></g></svg></span><span class="sml-g-tx"><b>SLC</b>3D Teardown</span></button></div></div> |
| <div class="sml-g-specs"><div class="sml-g-spec"><b>RK3568 / 3576 / 3588</b><em>Three Platforms · up to 6 TOPS NPU</em></div><div class="sml-g-spec"><b>10.1″–21.5″</b><em>Six Sizes · Capacitive Touch</em></div><div class="sml-g-spec"><b>Fanless Fully Sealed</b><em>VESA / Embedded Mounting</em></div><div class="sml-g-spec"><b>Wide-Range Power</b><em>DC 12~24V ±10%</em></div></div> | <div class="sml-g-specs"><div class="sml-g-spec"><b>RK3568 / 3576 / 3588</b><em>Three Platforms · up to 6 TOPS NPU</em></div><div class="sml-g-spec"><b>10.1″–21.5″</b><em>Six Sizes · Capacitive Touch</em></div><div class="sml-g-spec"><b>Fanless Fully Sealed</b><em>VESA / Embedded Mounting</em></div><div class="sml-g-spec"><b>Wide-Range Power</b><em>DC 12~24V ±10%</em></div></div> |
| <div class="sml-g-apps"><span class="t">Typical Applications</span><span class="chip">Production Line Dashboard</span><span class="chip">Equipment HMI</span><span class="chip">Self-Service Query Terminal</span><span>Long-Term Stable Operation in Industrial Sites</span></div> | <div class="sml-g-apps"><span class="t">Typical Applications</span><span class="chip">Production Line Dashboard</span><span class="chip">Equipment HMI</span><span class="chip">Self-Service Query Terminal</span><span>Long-Term Stable Operation in Industrial Sites</span></div> |
| |
| <code bash> | <code bash> |
| export PATH=$PATH:/gcc-arm-10.3-2021.07-x86_64-aarch64-none-linux-gnu/bin/ | export PATH=$PATH:/gcc-arm-9.2-2019.12-x86_64-aarch64-none-linux-gnu/bin/ |
| aarch64-none-linux-gnu-gcc --version | aarch64-none-linux-gnu-gcc --version |
| </code> | </code> |
| |
| Use a ready-made build image; the host does not need Qt installed, isolation is good, and it suits batch compilation. | Use a ready-made build image; the host does not need Qt installed, isolation is good, and it suits batch compilation. |
| | |
| | **Download:** [[ftp://drv.nodka.com/arm_download/nodka_docker_qt_build_20230223.tar|nodka_docker_qt_build_20230223.tar]] — company FTP (drv.nodka.com) |
| |
| - Import the image | - Import the image |
| </code> | </code> |
| |
| **Note:** The image is large and is provided separately by us; the container ID (09a37c1b2fc3) is taken from the actual docker images output. | **Note:** The image is large; after import the image and the original tar each take a copy, so reserve at least 10 GB of free disk. Chrome / Edge no longer open ftp links directly — use Windows Explorer, FileZilla or a download manager instead. The container ID (09a37c1b2fc3) is taken from the actual docker images output. |
| |
| === 5.12.2 Set Up the Qt Build Environment on the Board === | === 5.12.2 Set Up the Qt Build Environment on the Board === |
| } | } |
| </style></html> | </style></html> |
| | <html><script> |
| | /* sTPC 3D 拆解查看器(嵌入版,无外部依赖) |
| | 用法:SML3D(hostElement, {url:'...zip', lang:'zh', ...}) */ |
| | window.SML3D = (function(){ |
| | "use strict"; |
| | |
| | /* ===== 默认零件表(仅 sTPC-S156-3568B;其它机型由 cfg.parts 传入) ===== */ |
| | var PARTS_S156 = [ |
| | /* 0 */ {n:'机身中框', e:'Mid Frame', col:'#b8c2cd', k:0.00, d:[0,0,0]}, |
| | /* 1 */ {n:'视窗玻璃', e:'Front Glass', col:'#1e2a36', k:1.55, d:[0,0,1]}, |
| | /* 2 */ {n:'显示模组', e:'Display Module', col:'#3f4d5c', k:1.05, d:[0,0,1]}, |
| | /* 3 */ {n:'后盖', e:'Rear Cover', col:'#d4dae1', k:1.45, d:[0,0,-1]}, |
| | /* 4 */ {n:'主板载板', e:'Carrier Board', col:'#2f6f59', k:0.55, d:[0,0,1]}, |
| | /* 5 */ {n:'屏线组件', e:'Panel Cable', col:'#c9a227', k:0.55, d:[0,0,1]}, |
| | /* 6 */ {n:'天线/顶条', e:'Antenna Bar', col:'#7b5ea7', k:0.60, d:[0,0,-1]}, |
| | /* 7 */ {n:'固定卡扣', e:'Clip', col:'#8e99a6', k:0.30, d:[0,0,-1]}, |
| | /* 8 */ {n:'固定卡扣', e:'Clip', col:'#8e99a6', k:0.30, d:[0,0,-1]}, |
| | /* 9 */ {n:'紧固件', e:'Fastener', col:'#8e99a6', k:0.30, d:[0,0,-1]}, |
| | /*10 */ {n:'下侧接口座', e:'I/O Block', col:'#a45a52', k:0.45, d:[0,0,-1]}, |
| | /*11 */ {n:'接地弹片', e:'Ground Spring', col:'#9aa4b0', k:0.20, d:[0,0,-1]}, |
| | /*12 */ {n:'竖向排线', e:'Vertical FPC', col:'#c9a227', k:0.45, d:[0,0,-1]}, |
| | /*13 */ {n:'接地弹片', e:'Ground Spring', col:'#9aa4b0', k:0.20, d:[0,0,-1]}, |
| | /*14 */ {n:'喇叭', e:'Speaker', col:'#a45a52', k:0.45, d:[0,0,-1]}, |
| | /*15 */ {n:'主板组件', e:'Main Board', col:'#2f6f5c', k:0.75, d:[0,0,-1]}, |
| | /*16 */ {n:'前面框', e:'Front Bezel', col:'#cbd3dc', k:1.75, d:[0,0,1]} |
| | ]; |
| | var REAR_S156 = 3; /* 后盖索引 */ |
| | |
| | var UI = { |
| | zh:{open:'打开后盖', close:'装回后盖', xray:'透视', parts:'零件', front:'前视', back:'后视', |
| | side:'侧视', iso:'等轴', reset:'重置', exp:'拆解', hint:'拖拽旋转 · 滚轮缩放 · 点击零件查看', |
| | load:'加载 3D 模型', loading:'正在载入 3D 模型…', size:'约 1.3 MB', all:'全部显示', |
| | faces:'面数', dim:'尺寸', err:'模型载入失败', sorry:'当前浏览器不支持 WebGL,请用 Chrome / Edge 打开'}, |
| | en:{open:'Open cover', close:'Close cover', xray:'X-ray', parts:'Parts', front:'Front', back:'Rear', |
| | side:'Side', iso:'Iso', reset:'Reset', exp:'Explode', hint:'Drag to orbit · Scroll to zoom · Click a part', |
| | load:'Load 3D model', loading:'Loading 3D model…', size:'about 1.3 MB', all:'Show all', |
| | faces:'Faces', dim:'Size', err:'Failed to load model', sorry:'WebGL is not available; please use Chrome or Edge'} |
| | }; |
| | |
| | var CSS = '' |
| | + '.v3d{position:absolute;inset:0;overflow:hidden;background:radial-gradient(120% 105% at 50% 0%,#fdfefe 0%,#eaeef3 52%,#d9dfe7 100%);font:400 12px/1.45 "Segoe UI","Microsoft YaHei",sans-serif;}' |
| | + '.v3d canvas{display:block;width:100%;height:100%;cursor:grab;touch-action:none;}' |
| | + '.v3d canvas:active{cursor:grabbing;}' |
| | + '.v3d .tbar{position:absolute;left:9px;top:9px;display:flex;gap:5px;flex-wrap:wrap;max-width:calc(100% - 18px);}' |
| | + '.v3d .tb{font:inherit;font-size:11.5px;padding:3px 9px;border:1px solid #c6d0da;background:rgba(255,255,255,.94);color:#26313c;border-radius:3px;cursor:pointer;white-space:nowrap;}' |
| | + '.v3d .tb:hover{background:#eef6fd;border-color:#84aede;}' |
| | + '.v3d .tb.on{background:#0b5394;border-color:#0b5394;color:#fff;}' |
| | + '.v3d .tb.hot{border-color:#0b7a75;color:#0b7a75;}' |
| | + '.v3d .tb.hot.on{background:#0b7a75;color:#fff;}' |
| | + '.v3d .ebar{position:absolute;left:9px;right:9px;bottom:8px;display:flex;align-items:center;gap:8px;padding:5px 9px;background:rgba(255,255,255,.9);border:1px solid #dbe2ea;border-radius:3px;}' |
| | + '.v3d .ebar input{flex:1;height:3px;-webkit-appearance:none;appearance:none;background:#cfd8e2;border-radius:2px;outline:none;}' |
| | + '.v3d .ebar input::-webkit-slider-thumb{-webkit-appearance:none;width:13px;height:13px;border-radius:50%;background:#0b5394;cursor:pointer;box-shadow:0 1px 3px rgba(0,0,0,.25);}' |
| | + '.v3d .ebar .lb{font-size:11px;color:#4a5765;white-space:nowrap;}' |
| | + '.v3d .ebar .pv{font-size:11px;color:#0b5394;font-variant-numeric:tabular-nums;min-width:30px;text-align:right;}' |
| | + '.v3d .hint{position:absolute;right:9px;bottom:44px;font-size:10.5px;color:#6b7681;background:rgba(255,255,255,.82);padding:2px 7px;border-radius:2px;}' |
| | + '.v3d .plist{position:absolute;right:9px;top:44px;width:150px;max-height:calc(100% - 96px);overflow:auto;background:rgba(255,255,255,.96);border:1px solid #cbd4de;border-radius:4px;padding:5px;display:none;box-shadow:0 6px 18px rgba(20,40,60,.14);}' |
| | + '.v3d .plist.on{display:block;}' |
| | + '.v3d .pi{display:flex;align-items:center;gap:6px;padding:3px 5px;border-radius:3px;cursor:pointer;font-size:11.5px;color:#26313c;}' |
| | + '.v3d .pi:hover{background:#eef6fd;}' |
| | + '.v3d .pi.sel{background:#dbeafb;box-shadow:inset 2px 0 0 #0b5394;}' |
| | + '.v3d .pi.off{opacity:.36;}' |
| | + '.v3d .pi i{width:9px;height:9px;border-radius:2px;flex:0 0 9px;border:1px solid rgba(0,0,0,.2);}' |
| | + '.v3d .pi span{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}' |
| | + '.v3d .tip{position:absolute;padding:5px 9px;background:rgba(23,32,42,.92);color:#fff;font-size:11px;border-radius:3px;pointer-events:none;display:none;max-width:190px;line-height:1.5;}' |
| | + '.v3d .tip.on{display:block;}' |
| | + '.v3d .tip b{color:#8fc2ff;font-weight:600;}' |
| | + '.v3d .cover{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;flex-direction:column;gap:9px;background:rgba(255,255,255,.92);}' |
| | + '.v3d .cover .b{font:inherit;font-size:12.5px;padding:7px 16px;border:1px solid #0b5394;background:#0b5394;color:#fff;border-radius:3px;cursor:pointer;}' |
| | + '.v3d .cover .b:hover{background:#0a4880;}' |
| | + '.v3d .cover .s{font-size:11px;color:#78838f;}' |
| | + '.v3d .bar{width:150px;height:3px;background:#dde4ec;border-radius:2px;overflow:hidden;}' |
| | + '.v3d .bar>i{display:block;height:100%;width:6%;background:#0b5394;transition:width .18s;}'; |
| | |
| | /* ===== 数学 ===== */ |
| | function persp(f,a,n,r){var t=1/Math.tan(f/2),d=1/(n-r);return new Float32Array([t/a,0,0,0,0,t,0,0,0,0,(r+n)*d,-1,0,0,2*r*n*d,0]);} |
| | function look(e,c,u){ |
| | var zx=e[0]-c[0],zy=e[1]-c[1],zz=e[2]-c[2],zl=Math.hypot(zx,zy,zz)||1;zx/=zl;zy/=zl;zz/=zl; |
| | var xx=u[1]*zz-u[2]*zy,xy=u[2]*zx-u[0]*zz,xz=u[0]*zy-u[1]*zx,xl=Math.hypot(xx,xy,xz)||1;xx/=xl;xy/=xl;xz/=xl; |
| | var yx=zy*xz-zz*xy,yy=zz*xx-zx*xz,yz=zx*xy-zy*xx; |
| | return new Float32Array([xx,yx,zx,0,xy,yy,zy,0,xz,yz,zz,0, |
| | -(xx*e[0]+xy*e[1]+xz*e[2]),-(yx*e[0]+yy*e[1]+yz*e[2]),-(zx*e[0]+zy*e[1]+zz*e[2]),1]); |
| | } |
| | function mmul(a,b){var o=new Float32Array(16);for(var c=0;c<4;c++)for(var r=0;r<4;r++){var s=0;for(var k=0;k<4;k++)s+=a[k*4+r]*b[c*4+k];o[c*4+r]=s;}return o;} |
| | function nmat(n){ |
| | if(n.matrix)return new Float32Array(n.matrix); |
| | var t=n.translation||[0,0,0],q=n.rotation||[0,0,0,1],s=n.scale||[1,1,1]; |
| | var x=q[0],y=q[1],z=q[2],w=q[3],x2=x+x,y2=y+y,z2=z+z,xx=x*x2,xy=x*y2,xz=x*z2,yy=y*y2,yz=y*z2,zz=z*z2,wx=w*x2,wy=w*y2,wz=w*z2; |
| | return new Float32Array([(1-(yy+zz))*s[0],(xy+wz)*s[0],(xz-wy)*s[0],0,(xy-wz)*s[1],(1-(xx+zz))*s[1],(yz+wx)*s[1],0,(xz+wy)*s[2],(yz-wx)*s[2],(1-(xx+yy))*s[2],0,t[0],t[1],t[2],1]); |
| | } |
| | function ap(m,v){return [m[0]*v[0]+m[4]*v[1]+m[8]*v[2]+m[12],m[1]*v[0]+m[5]*v[1]+m[9]*v[2]+m[13],m[2]*v[0]+m[6]*v[1]+m[10]*v[2]+m[14]];} |
| | function hex(h){return [parseInt(h.slice(1,3),16)/255,parseInt(h.slice(3,5),16)/255,parseInt(h.slice(5,7),16)/255];} |
| | |
| | /* ===== 载入 ===== */ |
| | function unzip(ab){ |
| | var d=new DataView(ab); |
| | if(d.getUint32(0,true)!==0x04034b50)throw new Error('zip'); |
| | if(d.getUint16(8,true)!==0)throw new Error('zip mode'); |
| | var cs=d.getUint32(18,true),nl=d.getUint16(26,true),el=d.getUint16(28,true),st=30+nl+el; |
| | return ab.slice(st,st+cs); |
| | } |
| | function parseGLB(ab){ |
| | var d=new DataView(ab); |
| | if(d.getUint32(0,true)!==0x46546C67)throw new Error('glb'); |
| | var len=d.getUint32(8,true),o=12,j=null,b=null; |
| | while(o<len){ |
| | var cl=d.getUint32(o,true),ct=d.getUint32(o+4,true);o+=8; |
| | var c=ab.slice(o,o+cl); |
| | if(ct===0x4E4F534A)j=JSON.parse(new TextDecoder().decode(c)); |
| | else if(ct===0x004E4942)b=c; |
| | o+=cl; |
| | } |
| | return {json:j,bin:b}; |
| | } |
| | var CT={5120:Int8Array,5121:Uint8Array,5122:Int16Array,5123:Uint16Array,5125:Uint32Array,5126:Float32Array}; |
| | function acc(g,i){ |
| | var a=g.json.accessors[i],v=g.json.bufferViews[a.bufferView],T=CT[a.componentType]; |
| | var it=a.type==='VEC3'?3:a.type==='VEC2'?2:a.type==='SCALAR'?1:4; |
| | var off=(v.byteOffset||0)+(a.byteOffset||0),st=v.byteStride||0; |
| | if(st&&st!==it*T.BYTES_PER_ELEMENT){ |
| | var o=new T(a.count*it); |
| | for(var k=0;k<a.count;k++)o.set(new T(g.bin,off+k*st,it),k*it); |
| | return o; |
| | } |
| | return new T(g.bin,off,a.count*it); |
| | } |
| | function build(g){ |
| | var ps=[],pos=[],nrm=[],idx=[],vb=0,ib=0; |
| | var sc=g.json.scenes[g.json.scene||0],stack=[]; |
| | (sc.nodes||[]).forEach(function(n){stack.push([n,new Float32Array([1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1])]);}); |
| | while(stack.length){ |
| | var cur=stack.pop(),ni=g.json.nodes[cur[0]],M=mmul(cur[1],nmat(ni)); |
| | if(ni.mesh!==undefined){ |
| | g.json.meshes[ni.mesh].primitives.forEach(function(p){ |
| | var P=acc(g,p.attributes.POSITION),N=p.attributes.NORMAL!==undefined?acc(g,p.attributes.NORMAL):null, |
| | I=p.indices!==undefined?acc(g,p.indices):null; |
| | var start=ib,vc=P.length/3,i,mn=[1e9,1e9,1e9],mx=[-1e9,-1e9,-1e9]; |
| | for(i=0;i<P.length;i+=3){ |
| | var v=ap(M,[P[i],P[i+1],P[i+2]]); |
| | pos.push(v[0],v[1],v[2]); |
| | for(var a=0;a<3;a++){if(v[a]<mn[a])mn[a]=v[a];if(v[a]>mx[a])mx[a]=v[a];} |
| | } |
| | for(i=0;i<vc;i++){ |
| | if(N){var w=ap(M,[N[i*3],N[i*3+1],N[i*3+2]]),l=Math.hypot(w[0],w[1],w[2])||1;nrm.push(w[0]/l,w[1]/l,w[2]/l);} |
| | else nrm.push(0,1,0); |
| | } |
| | if(I)for(i=0;i<I.length;i++)idx.push(I[i]+vb); |
| | else for(i=0;i<vc;i++)idx.push(i+vb); |
| | ps.push({pi:ps.length,start:start,count:idx.length-start,mn:mn,mx:mx, |
| | c:[(mn[0]+mx[0])/2,(mn[1]+mx[1])/2,(mn[2]+mx[2])/2], |
| | size:[mx[0]-mn[0],mx[1]-mn[1],mx[2]-mn[2]],vis:true}); |
| | vb+=vc;ib=idx.length; |
| | }); |
| | } |
| | (ni.children||[]).forEach(function(ch){stack.push([ch,M]);}); |
| | } |
| | var box=[1e9,1e9,1e9,-1e9,-1e9,-1e9]; |
| | ps.forEach(function(p){for(var a=0;a<3;a++){box[a]=Math.min(box[a],p.mn[a]);box[a+3]=Math.max(box[a+3],p.mx[a]);}}); |
| | return {parts:ps,pos:new Float32Array(pos),nrm:new Float32Array(nrm),idx:new Uint32Array(idx),box:box, |
| | center:[(box[0]+box[3])/2,(box[1]+box[4])/2,(box[2]+box[5])/2], |
| | diag:Math.hypot(box[3]-box[0],box[4]-box[1],box[5]-box[2])}; |
| | } |
| | |
| | /* ===== 主函数 ===== */ |
| | return function(host,cfg){ |
| | cfg=cfg||{}; |
| | var L=UI[cfg.lang==='en'?'en':'zh']; |
| | var PARTS=cfg.parts||PARTS_S156, REAR=cfg.rear!==undefined?cfg.rear:REAR_S156; |
| | if(!document.getElementById('sml3d-css')){ |
| | var st=document.createElement('style');st.id='sml3d-css';st.textContent=CSS;document.head.appendChild(st); |
| | } |
| | var root=document.createElement('div');root.className='v3d'; |
| | root.innerHTML='' |
| | +'<canvas></canvas>' |
| | +'<div class="tbar">' |
| | +'<button class="tb" data-v="front">'+L.front+'</button>' |
| | +'<button class="tb" data-v="back">'+L.back+'</button>' |
| | +'<button class="tb" data-v="side">'+L.side+'</button>' |
| | +'<button class="tb on" data-v="iso">'+L.iso+'</button>' |
| | +'<button class="tb hot" data-a="cover">'+L.open+'</button>' |
| | +'<button class="tb" data-a="xray">'+L.xray+'</button>' |
| | +'<button class="tb" data-a="parts">'+L.parts+'</button>' |
| | +'</div>' |
| | +'<div class="plist"></div>' |
| | +'<div class="hint">'+L.hint+'</div>' |
| | +'<div class="ebar"><span class="lb">'+L.exp+'</span><input type="range" min="0" max="100" value="0"><span class="pv">0%</span></div>' |
| | +'<div class="tip"></div>' |
| | +'<div class="cover"><button class="b">'+L.load+'</button><span class="s">'+(cfg.size||L.size)+'</span></div>'; |
| | host.appendChild(root); |
| | |
| | var cv=root.querySelector('canvas'); |
| | var gl=cv.getContext('webgl2',{antialias:true,alpha:true,premultipliedAlpha:false}); |
| | if(!gl){root.querySelector('.cover').innerHTML='<span class="s">'+L.sorry+'</span>';return {};} |
| | |
| | var VS=['#version 300 es','in vec3 aP;in vec3 aN;uniform mat4 uP,uV;uniform vec3 uO;out vec3 vP;out vec3 vN;', |
| | 'void main(){vec3 p=aP+uO;vP=p;vN=aN;gl_Position=uP*uV*vec4(p,1.0);}'].join('\n'); |
| | var FS=['#version 300 es','precision highp float;in vec3 vP;in vec3 vN;uniform vec3 uC,uE;uniform float uS,uD,uX,uA;uniform mat4 uV;out vec4 f;', |
| | 'void main(){vec3 N=normalize(vN),V=normalize(uE-vP);if(dot(N,V)<0.0)N=-N;', |
| | 'mat3 R=mat3(uV);vec3 Nv=normalize(R*N),Vv=normalize(R*V);vec3 L1=normalize(vec3(-0.42,0.58,0.70)),L2=normalize(vec3(0.60,-0.30,0.74));', |
| | 'float k1=max(dot(Nv,L1),0.0),k2=max(dot(Nv,L2),0.0);vec3 H=normalize(L1+Vv);', |
| | 'float sp=pow(max(dot(Nv,H),0.0),46.0)*0.30;float rm=pow(1.0-max(dot(Nv,Vv),0.0),3.0)*0.20;', |
| | 'vec3 c=uC*(0.34+0.52*k1+0.20*k2)+vec3(sp)+vec3(rm)*0.55;', |
| | 'if(uS>0.5)c=mix(c,vec3(0.12,0.40,0.85),0.34);if(uX>0.5)c=mix(c,vec3(0.40,0.48,0.58),0.55);else if(uD>0.5)c=mix(c,vec3(0.72,0.76,0.81),0.62);', |
| | 'float a=1.0;if(uD>0.5)a*=mix(0.40,1.0,uX);f=vec4(c,a*uA);}'].join('\n'); |
| | function sh(t,s){var o=gl.createShader(t);gl.shaderSource(o,s);gl.compileShader(o); |
| | if(!gl.getShaderParameter(o,gl.COMPILE_STATUS))throw new Error(gl.getShaderInfoLog(o));return o;} |
| | var pr=gl.createProgram(); |
| | gl.attachShader(pr,sh(gl.VERTEX_SHADER,VS));gl.attachShader(pr,sh(gl.FRAGMENT_SHADER,FS)); |
| | gl.bindAttribLocation(pr,0,'aP');gl.bindAttribLocation(pr,1,'aN');gl.linkProgram(pr);gl.useProgram(pr); |
| | var U={};['uP','uV','uO','uC','uE','uS','uD','uX','uA'].forEach(function(n){U[n]=gl.getUniformLocation(pr,n);}); |
| | |
| | var cam={th:0.60,ph:0.19,dist:3,tgt:[0,0,0],fov:34*Math.PI/180}; |
| | var model=null,explode=0,sel=-1,xray=false,coverHidden=false; |
| | var buf={}; |
| | |
| | function eye(){var cp=Math.cos(cam.ph);return [cam.tgt[0]+cam.dist*cp*Math.sin(cam.th),cam.tgt[1]+cam.dist*Math.sin(cam.ph),cam.tgt[2]+cam.dist*cp*Math.cos(cam.th)];} |
| | function fitDist(){ |
| | var w=cv.clientWidth||1,h=cv.clientHeight||1,as=w/h; |
| | var fv=cam.fov,fh=2*Math.atan(Math.tan(fv/2)*as),R=model.diag/2*1.06; |
| | return R/Math.sin(Math.min(fv,fh)/2); |
| | } |
| | /* 视角平滑过渡 */ |
| | var tw=null; |
| | function tween(to){ |
| | var from={th:cam.th,ph:cam.ph,dist:cam.dist}; |
| | var t0=performance.now(); |
| | if(to.th!==undefined&&Math.abs(to.th-cam.th)>Math.PI)cam.th+=(cam.th>to.th?1:-1)*2*Math.PI; |
| | from.th=cam.th; |
| | tw={from:from,to:to,t0:t0,dur:420}; |
| | if(!tw.run){tw.run=true;(function step(){ if(!tw){return;} |
| | var k=Math.min(1,(performance.now()-tw.t0)/tw.dur),e=k<0.5?2*k*k:1-Math.pow(-2*k+2,2)/2; |
| | cam.th=tw.from.th+(tw.to.th-tw.from.th)*e; |
| | cam.ph=tw.from.ph+(tw.to.ph-tw.from.ph)*e; |
| | cam.dist=tw.from.dist+(tw.to.dist-tw.from.dist)*e; |
| | draw(); |
| | if(k<1)requestAnimationFrame(step);else tw=null; |
| | })();} |
| | } |
| | function off(p){ |
| | var m=PARTS[p.pi]||{k:0.5,d:[0,0,1]}; |
| | var g=explode*model.diag*0.20*(m.k===undefined?0.5:m.k); |
| | return [m.d[0]*g,m.d[1]*g,m.d[2]*g]; |
| | } |
| | function col(p){return (PARTS[p.pi]&&PARTS[p.pi].col)||'#9aa4b0';} |
| | |
| | function draw(){ |
| | var dpr=Math.min(window.devicePixelRatio||1,2),w=cv.clientWidth||1,h=cv.clientHeight||1; |
| | if(cv.width!==w*dpr||cv.height!==h*dpr){cv.width=w*dpr;cv.height=h*dpr;} |
| | gl.viewport(0,0,cv.width,cv.height); |
| | gl.clearColor(0,0,0,0);gl.clear(gl.COLOR_BUFFER_BIT|gl.DEPTH_BUFFER_BIT); |
| | if(xray){gl.disable(gl.DEPTH_TEST);}else{gl.enable(gl.DEPTH_TEST);}gl.disable(gl.CULL_FACE);gl.enable(gl.BLEND); |
| | gl.blendFunc(gl.SRC_ALPHA,gl.ONE_MINUS_SRC_ALPHA); |
| | var e=eye(); |
| | gl.uniformMatrix4fv(U.uP,false,persp(cam.fov,w/h,model.diag*0.01,model.diag*12)); |
| | gl.uniformMatrix4fv(U.uV,false,look(e,cam.tgt,[0,1,0])); |
| | gl.uniform3fv(U.uE,e); |
| | gl.bindBuffer(gl.ARRAY_BUFFER,buf.p);gl.enableVertexAttribArray(0);gl.vertexAttribPointer(0,3,gl.FLOAT,false,0,0); |
| | gl.bindBuffer(gl.ARRAY_BUFFER,buf.n);gl.enableVertexAttribArray(1);gl.vertexAttribPointer(1,3,gl.FLOAT,false,0,0); |
| | gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER,buf.i); |
| | var any=sel>=0; |
| | model.parts.forEach(function(p){ |
| | if(!p.vis)return; |
| | var c=hex(col(p)); |
| | gl.uniform3f(U.uC,c[0],c[1],c[2]); |
| | gl.uniform3fv(U.uO,off(p)); |
| | gl.uniform1f(U.uS,p.pi===sel?1:0); |
| | gl.uniform1f(U.uD,(any&&p.pi!==sel)?1:0); |
| | gl.uniform1f(U.uA, xray ? (p.pi===sel ? 1.0 : (Math.max(p.size[0],p.size[1],p.size[2])>0.075?0.20:0.62)) : 1.0); |
| | gl.uniform1f(U.uX,xray&&p.pi!==sel?1:0); |
| | gl.drawElements(gl.TRIANGLES,p.count,gl.UNSIGNED_INT,p.start*4); |
| | }); |
| | } |
| | |
| | /* 交互 */ |
| | var drag=null; |
| | cv.addEventListener('pointerdown',function(e){cv.setPointerCapture(e.pointerId);drag={x:e.clientX,y:e.clientY,m:0,b:e.button};}); |
| | cv.addEventListener('pointermove',function(e){ |
| | if(!drag)return; |
| | var dx=e.clientX-drag.x,dy=e.clientY-drag.y;drag.x=e.clientX;drag.y=e.clientY;drag.m+=Math.abs(dx)+Math.abs(dy); |
| | if(drag.b===2||e.shiftKey){ |
| | var k=cam.dist*0.0016; |
| | cam.tgt[0]-=Math.cos(cam.th)*dx*k;cam.tgt[2]+=Math.sin(cam.th)*dx*k;cam.tgt[1]+=dy*k; |
| | }else{ |
| | cam.th-=dx*0.008;cam.ph=Math.max(-1.45,Math.min(1.45,cam.ph+dy*0.006)); |
| | } |
| | draw(); |
| | }); |
| | cv.addEventListener('pointerup',function(e){if(drag&&drag.m<6&&drag.b===0)pick(e);drag=null;}); |
| | cv.addEventListener('contextmenu',function(e){e.preventDefault();}); |
| | cv.addEventListener('wheel',function(e){ |
| | e.preventDefault(); |
| | cam.dist*=1+Math.sign(e.deltaY)*0.09; |
| | cam.dist=Math.max(model.diag*0.3,Math.min(model.diag*3.2,cam.dist)); |
| | draw(); |
| | },{passive:false}); |
| | |
| | function pick(e){ |
| | var r=cv.getBoundingClientRect(); |
| | var nx=((e.clientX-r.left)/r.width)*2-1,ny=1-((e.clientY-r.top)/r.height)*2; |
| | var o=eye(),f=[cam.tgt[0]-o[0],cam.tgt[1]-o[1],cam.tgt[2]-o[2]],fl=Math.hypot(f[0],f[1],f[2]); |
| | f=[f[0]/fl,f[1]/fl,f[2]/fl]; |
| | var rt=[f[1]*0-f[2]*1,f[2]*0-f[0]*0,f[0]*1-f[1]*0],rl=Math.hypot(rt[0],rt[1],rt[2])||1; |
| | rt=[rt[0]/rl,rt[1]/rl,rt[2]/rl]; |
| | var up=[rt[1]*f[2]-rt[2]*f[1],rt[2]*f[0]-rt[0]*f[2],rt[0]*f[1]-rt[1]*f[0]]; |
| | var hh=Math.tan(cam.fov/2),hw=hh*(r.width/r.height); |
| | var d=[f[0]+rt[0]*nx*hw+up[0]*ny*hh,f[1]+rt[1]*nx*hw+up[1]*ny*hh,f[2]+rt[2]*nx*hw+up[2]*ny*hh]; |
| | var dl=Math.hypot(d[0],d[1],d[2]);d=[d[0]/dl,d[1]/dl,d[2]/dl]; |
| | var best=-1,bt=1e12; |
| | model.parts.forEach(function(p){ |
| | if(!p.vis)return; |
| | var of=off(p),cx=p.c[0]+of[0],cy=p.c[1]+of[1],cz=p.c[2]+of[2]; |
| | var oc=[cx-o[0],cy-o[1],cz-o[2]],tca=oc[0]*d[0]+oc[1]*d[1]+oc[2]*d[2]; |
| | var rad=Math.hypot(p.size[0],p.size[1],p.size[2])/2; |
| | if(tca<0)return; |
| | if((oc[0]*oc[0]+oc[1]*oc[1]+oc[2]*oc[2])-tca*tca>rad*rad)return; |
| | for(var i=p.start;i<p.start+p.count;i+=3){ |
| | var a=model.idx[i]*3,b=model.idx[i+1]*3,c=model.idx[i+2]*3; |
| | var t=rt3(o,d, |
| | [model.pos[a]+of[0],model.pos[a+1]+of[1],model.pos[a+2]+of[2]], |
| | [model.pos[b]+of[0],model.pos[b+1]+of[1],model.pos[b+2]+of[2]], |
| | [model.pos[c]+of[0],model.pos[c+1]+of[1],model.pos[c+2]+of[2]]); |
| | if(t>0&&t<bt){bt=t;best=p.pi;} |
| | } |
| | }); |
| | select(best,e); |
| | } |
| | function rt3(o,d,a,b,c){ |
| | var e1=[b[0]-a[0],b[1]-a[1],b[2]-a[2]],e2=[c[0]-a[0],c[1]-a[1],c[2]-a[2]]; |
| | var p=[d[1]*e2[2]-d[2]*e2[1],d[2]*e2[0]-d[0]*e2[2],d[0]*e2[1]-d[1]*e2[0]]; |
| | var det=e1[0]*p[0]+e1[1]*p[1]+e1[2]*p[2]; |
| | if(Math.abs(det)<1e-9)return -1; |
| | var iv=1/det,t=[o[0]-a[0],o[1]-a[1],o[2]-a[2]]; |
| | var u=(t[0]*p[0]+t[1]*p[1]+t[2]*p[2])*iv; |
| | if(u<0||u>1)return -1; |
| | var q=[t[1]*e1[2]-t[2]*e1[1],t[2]*e1[0]-t[0]*e1[2],t[0]*e1[1]-t[1]*e1[0]]; |
| | var v=(d[0]*q[0]+d[1]*q[1]+d[2]*q[2])*iv; |
| | if(v<0||u+v>1)return -1; |
| | return (e2[0]*q[0]+e2[1]*q[1]+e2[2]*q[2])*iv; |
| | } |
| | |
| | var plist=root.querySelector('.plist'),tip=root.querySelector('.tip'); |
| | function nm(p){var m=PARTS[p.pi];if(!m)return (cfg.lang==='en'?'Part ':'零件 ')+(p.pi+1);return cfg.lang==='en'?m.e:m.n;} |
| | function buildList(){ |
| | plist.innerHTML=''; |
| | model.parts.slice().sort(function(a,b){return b.size[0]*b.size[1]-a.size[0]*a.size[1];}).forEach(function(p){ |
| | var el=document.createElement('div'); |
| | el.className='pi';el.dataset.pi=p.pi; |
| | el.innerHTML='<i style="background:'+col(p)+'"></i><span>'+nm(p)+'</span>'; |
| | el.addEventListener('click',function(ev){ |
| | if(ev.altKey){p.vis=!p.vis;sync();draw();return;} |
| | select(p.pi===sel?-1:p.pi); |
| | }); |
| | el.addEventListener('dblclick',function(){p.vis=!p.vis;sync();draw();}); |
| | plist.appendChild(el); |
| | }); |
| | sync(); |
| | } |
| | function sync(){ |
| | plist.querySelectorAll('.pi').forEach(function(el){ |
| | var p=model.parts[+el.dataset.pi]; |
| | el.classList.toggle('off',!p.vis);el.classList.toggle('sel',p.pi===sel); |
| | }); |
| | } |
| | function select(pi,e){ |
| | sel=pi;sync();draw(); |
| | if(pi<0||!e){tip.classList.remove('on');return;} |
| | var p=model.parts[pi],s=p.size.map(function(v){return Math.round(v*1000);}); |
| | tip.innerHTML='<b>'+nm(p)+'</b><br>'+L.dim+' '+s[0]+' × '+s[1]+' × '+s[2]+' mm<br>'+L.faces+' '+(p.count/3|0); |
| | var r=host.getBoundingClientRect(); |
| | tip.style.left=Math.min(r.width-200,e.clientX-r.left+12)+'px'; |
| | tip.style.top=Math.min(r.height-70,e.clientY-r.top+12)+'px'; |
| | tip.classList.add('on'); |
| | } |
| | |
| | /* 工具栏 */ |
| | root.querySelectorAll('.tb').forEach(function(b){ |
| | b.addEventListener('click',function(){ |
| | var v=b.dataset.v,a=b.dataset.a; |
| | if(v){ |
| | root.querySelectorAll('.tb[data-v]').forEach(function(x){x.classList.remove('on');}); |
| | b.classList.add('on'); |
| | var to={ph:v==='iso'?0.28:0.02,dist:fitDist()}; |
| | if(v==='front')to.th=0; |
| | if(v==='back')to.th=Math.PI; |
| | if(v==='side')to.th=-Math.PI/2; |
| | if(v==='iso')to.th=0.60; |
| | cam.tgt=[model.center[0],model.center[1],model.center[2]]; |
| | tween(to);return; |
| | } |
| | if(a==='parts'){plist.classList.toggle('on');b.classList.toggle('on');return;} |
| | if(a==='xray'){xray=!xray;b.classList.toggle('on',xray);draw();return;} |
| | if(a==='cover'){ |
| | coverHidden=!coverHidden; |
| | model.parts[REAR].vis=!coverHidden; |
| | b.classList.toggle('on',coverHidden); |
| | b.textContent=coverHidden?L.close:L.open; |
| | if(coverHidden){ |
| | explode=0.5;root.querySelector('.ebar input').value=50;root.querySelector('.pv').textContent='50%'; |
| | tween({th:Math.PI*0.80,ph:0.24,dist:fitDist()*1.02}); |
| | }else{ |
| | explode=0;root.querySelector('.ebar input').value=0;root.querySelector('.pv').textContent='0%'; |
| | tween({th:0.60,ph:0.28,dist:fitDist()}); |
| | } |
| | sync();draw();return; |
| | } |
| | }); |
| | }); |
| | var sld=root.querySelector('.ebar input'); |
| | sld.addEventListener('input',function(){ |
| | explode=+sld.value/100;root.querySelector('.pv').textContent=sld.value+'%'; |
| | if(coverHidden&&explode<0.15){coverHidden=false;model.parts[REAR].vis=true; |
| | var cb=root.querySelector('.tb[data-a=cover]');cb.classList.remove('on');cb.textContent=L.open;} |
| | draw(); |
| | }); |
| | |
| | /* 加载 */ |
| | var started=false; |
| | function start(){ |
| | if(started)return;started=true; |
| | var cover=root.querySelector('.cover'); |
| | cover.innerHTML=L.loading+'<div class="bar"><i></i></div>'; |
| | var bar=cover.querySelector('.bar>i'); |
| | fetch(cfg.url).then(function(r){ |
| | if(!r.ok)throw new Error('HTTP '+r.status); |
| | var tot=+r.headers.get('content-length')||0,got=0,ch=[],rd=r.body.getReader(); |
| | return (function pump(){ |
| | return rd.read().then(function(x){ |
| | if(x.done)return; |
| | ch.push(x.value);got+=x.value.length; |
| | if(tot)bar.style.width=Math.min(94,6+90*got/tot)+'%'; |
| | return pump(); |
| | }); |
| | })().then(function(){ |
| | var b=new Uint8Array(got),o=0;ch.forEach(function(c){b.set(c,o);o+=c.length;}); |
| | return b.buffer; |
| | }); |
| | }).then(function(ab){ |
| | model=build(parseGLB(unzip(ab))); |
| | buf.p=gl.createBuffer();gl.bindBuffer(gl.ARRAY_BUFFER,buf.p);gl.bufferData(gl.ARRAY_BUFFER,model.pos,gl.STATIC_DRAW); |
| | buf.n=gl.createBuffer();gl.bindBuffer(gl.ARRAY_BUFFER,buf.n);gl.bufferData(gl.ARRAY_BUFFER,model.nrm,gl.STATIC_DRAW); |
| | buf.i=gl.createBuffer();gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER,buf.i);gl.bufferData(gl.ELEMENT_ARRAY_BUFFER,model.idx,gl.STATIC_DRAW); |
| | bar.style.width='100%'; |
| | cover.style.display='none'; |
| | cam.tgt=[model.center[0],model.center[1],model.center[2]]; |
| | cam.dist=fitDist(); |
| | window.__parts=model.parts; |
| | if(cfg.only!==undefined){model.parts.forEach(function(q){q.vis=q.pi===cfg.only;});} |
| | buildList();draw(); |
| | if(cfg.boom!==undefined){ |
| | explode=cfg.boom; |
| | root.querySelector('.ebar input').value=Math.round(explode*100); |
| | root.querySelector('.pv').textContent=Math.round(explode*100)+'%'; |
| | } |
| | if(cfg.hideCover){ |
| | coverHidden=true;model.parts[REAR].vis=false; |
| | var cb=root.querySelector('.tb[data-a=cover]'); |
| | cb.classList.add('on');cb.textContent=L.close; |
| | sync(); |
| | } |
| | if(cfg.view){cam.th=cfg.view==='back'?Math.PI:cfg.view==='side'?-Math.PI/2:0.60; |
| | cam.ph=cfg.view==='iso'?0.28:0.02;} |
| | draw(); |
| | }).catch(function(err){ |
| | cover.innerHTML='<span class="s">'+L.err+':'+err.message+'</span>'; |
| | }); |
| | } |
| | root.querySelector('.cover .b').addEventListener('click',start); |
| | host.__sml3dStart=start; |
| | window.addEventListener('resize',function(){if(model)draw();}); |
| | return {start:start,ondraw:draw}; |
| | }; |
| | })(); |
| | |
| | </script></html> |
| | <html><script> |
| | /* 3D 拆解视角:捕获阶段接管图库按钮,不改动本页原有的切换脚本 */ |
| | (function () { |
| | var host = document.getElementById('sml-3d'); |
| | if (!host) return; |
| | var stage = host.parentNode, side = document.querySelector('.sml-g-side'); |
| | var v3 = null, on = false; |
| | function parseParts(btn) { |
| | try { |
| | var a = JSON.parse(btn.getAttribute('data-parts') || '[]'); |
| | return a.map(function (r) { return {n: r[0], e: r[1], col: r[2], k: r[3], d: [0, 0, r[4]]}; }); |
| | } catch (e) { return null; } |
| | } |
| | function enter(btn) { |
| | on = true; |
| | stage.className = 'sml-g-stage on3d'; |
| | if (side) side.className = 'sml-g-side on3d'; |
| | if (!v3) { |
| | if (!window.SML3D) return; |
| | v3 = window.SML3D(host, { |
| | url: btn.getAttribute('data-zip'), |
| | lang: btn.getAttribute('data-lang'), |
| | size: btn.getAttribute('data-size'), |
| | parts: parseParts(btn), |
| | rear: +btn.getAttribute('data-rear') |
| | }); |
| | v3.start(); |
| | } |
| | } |
| | function leave() { |
| | if (!on) return; |
| | on = false; |
| | stage.className = 'sml-g-stage'; |
| | if (side) side.className = 'sml-g-side'; |
| | } |
| | document.addEventListener('click', function (e) { |
| | var t = e.target; |
| | while (t && t !== document && !(t.tagName === 'BUTTON' && /sml-g-thumb/.test(t.className))) t = t.parentNode; |
| | if (!t || t === document) return; |
| | if (t.hasAttribute('data-3d')) { |
| | e.preventDefault(); |
| | e.stopPropagation(); |
| | var bs = document.querySelectorAll('button.sml-g-thumb'); |
| | for (var i = 0; i < bs.length; i++) bs[i].className = (bs[i] === t ? 'sml-g-thumb on' : 'sml-g-thumb'); |
| | var cap = document.getElementById('sml-g-cap'), cnt = document.getElementById('sml-g-count'); |
| | if (cnt) cnt.textContent = t.getAttribute('data-idx') + ' / ' + (bs.length < 10 ? '0' + bs.length : bs.length); |
| | if (cap) cap.textContent = t.getAttribute('data-cap'); |
| | enter(t); |
| | } else { |
| | leave(); |
| | } |
| | }, true); |
| | })(); |
| | </script></html> |
| |