Files

47 lines
5.3 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="interior">仅内部结构</option><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></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);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,mode==='frosted'?0:1);
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?1.125:zoom>1?.43:.12,zoom>2?.605:zoom>1?-.30:.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='已载入 · 两侧内构的几何、法线及原始材质一致。右侧装配模式含已修好的外壳;网格仅用于观察层次。';
}
try{
await Promise.all(scenes.map((s,i)=>s.load(i?'/assets/archive-cassette.glb':'/reference/internal-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/internal-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()})};
$('#display').oninput=queueDraw;$('#display').onchange=queueDraw;$('#zoom').onchange=queueDraw;addEventListener('resize',queueDraw);
</script></html>