Files

47 lines
5.8 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!doctype html><html lang="zh-CN"><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
<title>外壳 · 刻线与解密过渡</title>
<style>
*{box-sizing:border-box}body{margin:0;background:#eae5e1;color:#292c28;font:14px Arial,"Microsoft YaHei",sans-serif}main{max-width:1700px;margin:auto;padding:28px}h1{font-size:23px;margin:0 0 12px}p{color:#676962;line-height:1.7}nav{display:flex;gap:12px;align-items:center;flex-wrap:wrap;margin:24px 0}select,button,a{padding:9px 12px;border:1px solid #b7b9b0;background:transparent;color:inherit;font:inherit}a{text-decoration:none}label{display:flex;gap:10px;align-items:center}.views{display:grid;grid-template-columns:1fr 1fr;gap:20px}.view{height:440px;position:relative;background:#e1e0d9}canvas{width:100%!important;height:100%!important}h2{font-size:15px;font-weight:500}pre{font:12px monospace;white-space:pre-wrap;color:#666}button[aria-pressed=true]{background:#353b30;color:#f1f0e8}@media(max-width:800px){.views{grid-template-columns:1fr}.view{height:330px}}</style>
<main><h1>外壳 · 刻线与解密过渡</h1><p>检查顶边嵌片、两处螺丝及盖板内刻线。播放或拖动进度,观察清晰区域从顶部向下推进。</p>
<nav><label>显示<select id="display"><option value="clear">装配 · 清晰盖板</option><option value="frosted">装配 · 磨砂盖板</option></select></label><button data-angle="oblique" aria-pressed="true">斜向观察</button><button data-angle="front">正面</button><button data-angle="edge">低角度</button><label>缩放<select id="zoom"><option value="1">完整外壳</option><option value="1.6">顶部 1.6×</option><option value="2.2">右上螺丝 2.2×</option></select></label><a href="/?scene=detail" target="_blank">打开详情体验 ↗</a><label>清晰进度<input id="progress" type="range" min="0" max="1" step="0.01" value="1"></label><button id="play">播放变清晰</button></nav>
<section class="views"><article><h2>打磨前</h2><div class="view" id="before"></div></article><article><h2>外壳打磨版</h2><div class="view" id="after"></div></article></section><pre id="status">正在加载…</pre></main>
<script type="module">
import * as THREE from 'three';
import {GLTFLoader} from 'three/addons/loaders/GLTFLoader.js';
import {ArchiveScene} from '/src/scene.ts';
const $=q=>document.querySelector(q),scenes=[new ArchiveScene($('#before')),new ArchiveScene($('#after'))],groups=[];
let angle='oblique',ready=false;const grids=[];
scenes.forEach(s=>s.composer.render=()=>{});
async function asset(s,path){
const raw=(await new GLTFLoader().loadAsync(path)).scene;raw.updateMatrixWorld(true);const group=new THREE.Group();
raw.traverse(o=>{if(!o.isMesh)return;const m=new THREE.Mesh(o.geometry.clone().applyMatrix4(o.matrixWorld),o.material);m.userData={assemblyPart:o.userData.assemblyPart,surface:o.material.name.replace(/\.\d+$/,'')};group.add(m)});
s.appearance.prepare(group);s.appearance.apply(group,1);s.appearance.setClarity(group,1);const label=s.model.children.find(o=>o.isMesh&&!o.userData.surface)?.clone();if(label){label.material=label.material.clone();label.material.opacity=1;group.add(label)}group.position.y=-1.85;s.scene.add(group);return group;
}
function draw(){
if(!ready)return;const mode=$('#display').value,zoom=Number($('#zoom').value);
scenes.forEach((s,i)=>{
grids[i].visible=mode==='interior';const g=groups[i];for(const mesh of g.children)mesh.visible=mode!=='interior'||['optical-core','optical-lenses'].includes(mesh.userData.assemblyPart);
s.appearance.setClarity(g,Number($('#progress').value));
const points={front:[.2,.15,7.6],oblique:[2.6,4.5,6.6],edge:[3.8,7.5,3.0]};
const target=new THREE.Vector3(zoom>2?2.1:zoom>1?0:.12,zoom>1?1.4:.1,0);
const dir=new THREE.Vector3(...points[angle]);s.camera.position.copy(target).add(dir.multiplyScalar((mode==='interior'?.66:1.03)/zoom));s.camera.near=.1;s.camera.far=120;s.camera.fov=34;
s.camera.aspect=s.container.clientWidth/s.container.clientHeight;s.camera.lookAt(target);s.camera.updateProjectionMatrix();s.camera.updateMatrixWorld(true);
s.renderer.setPixelRatio(Math.min(devicePixelRatio,2));s.renderer.setSize(s.container.clientWidth,s.container.clientHeight,false);
s.renderer.render(s.scene,s.camera);
});
$('#status').textContent='清晰进度 '+Math.round(Number($('#progress').value)*100)+'% · 两份模型使用相同的新版盖板过渡';
}
try{
await Promise.all(scenes.map((s,i)=>s.load(i?'/assets/archive-cassette.glb':'/reference/shell-study/assembly-before.glb')));
for(const s of scenes){s.scene.fog=null;for(const child of s.scene.children)if(child.isMesh||child.isGroup)child.visible=false;}
groups.push(...await Promise.all(scenes.map((s,i)=>asset(s,i?'/assets/archive-assembly.glb':'/reference/shell-study/assembly-before.glb'))));
for(const s of scenes){
const grid=new THREE.GridHelper(8,32,0xaaa99d,0xbab9ae);grid.rotation.x=Math.PI/2;grid.position.z=-.065;s.scene.add(grid);grids.push(grid);
}
ready=true;draw();
}catch(e){$('#status').textContent=String(e);console.error(e)}
document.querySelectorAll('[data-angle]').forEach(b=>b.onclick=()=>{angle=b.dataset.angle;document.querySelectorAll('[data-angle]').forEach(x=>x.setAttribute('aria-pressed',String(x===b)));draw()});
let queued=false;const queueDraw=()=>{if(queued)return;queued=true;requestAnimationFrame(()=>{queued=false;draw()})};
$('#progress').oninput=()=>{$('#display').value=Number($('#progress').value)===0?'frosted':'clear';queueDraw()};$('#display').onchange=()=>{$('#progress').value=$('#display').value==='frosted'?'0':'1';queueDraw()};let playId=0;$('#play').onclick=()=>{cancelAnimationFrame(playId);const start=performance.now();const tick=now=>{$('#progress').value=Math.min(1,(now-start)/2200);draw();if(now-start<2200)playId=requestAnimationFrame(tick)};playId=requestAnimationFrame(tick)};$('#zoom').onchange=queueDraw;addEventListener('resize',queueDraw);
</script></html>