/* 轨道相机与指针交互(旋转/平移/缩放/自适应取景) */ /* ---- camera orbit ---- */ let az = -0.9, po = 1.15, rd = 620, tx = 120, ty = 190, tz = -30; // lookAt with the world up-axis degenerate-rolls the image when looking near-vertically, // so top view pins the screen-up to -Z: model X runs right, depth runs down-screen const UP_Y = new V3(0, 1, 0), UP_TOP = new V3(0, 0, -1); let camUp = UP_Y.clone(); function fitTo(b) { const c = [(b[0][0]+b[1][0])/2, (b[0][1]+b[1][1])/2, (b[0][2]+b[1][2])/2]; tx = c[0]; ty = c[1]; tz = c[2]; const s = Math.max(b[1][0]-b[0][0], b[1][1]-b[0][1], b[1][2]-b[0][2]); rd = s * 1.78; requestRender(); } let BB = [[1e9,1e9,1e9],[-1e9,-1e9,-1e9]]; /* robust global bounds in the viewer frame (x, z, -y) */ (function(){ let lo=[1e9,1e9,1e9], hi=[-1e9,-1e9,-1e9]; for (const m of meshes) { if (m.userData.name.slice(0,4) === 'ENV_') continue; // 取景只按手臂本体,包络不入包围盒 const b=m.geometry.boundingBox; const a=[b.min.x+m.base.x,b.min.y+m.base.y,b.min.z+m.base.z]; const c=[b.max.x+m.base.x,b.max.y+m.base.y,b.max.z+m.base.z]; for(let i=0;i<3;i++){lo[i]=Math.min(lo[i],a[i]);hi[i]=Math.max(hi[i],c[i]);} } BB=[lo,hi]; })(); fitTo(BB); function updCam() { cam.up.copy(camUp); cam.position.set(tx + rd*Math.sin(po)*Math.cos(az), ty + rd*Math.cos(po), tz + rd*Math.sin(po)*Math.sin(az)); cam.lookAt(tx, ty, tz); } let drag = null, panDist = 0; cv.addEventListener('pointerdown', e => { drag = {x:e.offsetX, y:e.offsetY, b:e.button}; panDist = 0; cv.setPointerCapture(e.pointerId); }); /* 悬浮拾取限流:射线要对 34 万三角面全量求交(实测 ≈0.5~1.5 ms/次), 旧版每个 pointermove 都算(rAF 合帧后仍≈显示器刷新率次数); 这里加 60 ms 时间配额 + 位置去重,指针静止时一次都不算,尾随仍会补最新位置, tooltip 观感不变。点击 / 右键拾取不经过此函数,仍即时求交。 */ let hoverPending = false, lastHoverEv = null, hoverT = -1e9, hoverX = -1, hoverY = -1; const HOVER_MS = 60; function schedHover(e) { if (e.clientX === hoverX && e.clientY === hoverY) return; // 没移动:结果不可能变 hoverX = e.clientX; hoverY = e.clientY; lastHoverEv = e; if (hoverPending) return; hoverPending = true; setTimeout(() => { hoverPending = false; if (!lastHoverEv || drag) return; hoverT = performance.now(); hover(lastHoverEv); }, Math.max(0, HOVER_MS - (performance.now() - hoverT))); } cv.addEventListener('pointermove', e => { if (!drag) { schedHover(e); return; } const dx = e.offsetX - drag.x, dy = e.offsetY - drag.y; panDist += Math.abs(dx) + Math.abs(dy); drag = {x:e.offsetX, y:e.offsetY, b:drag.b}; if (drag.b === 0) { az -= dx*0.008; po = Math.min(3.04, Math.max(.05, po - dy*0.008)); } else { const r = rd*0.0016; const f = new V3(Math.sin(az),0,Math.cos(az)), ri = new V3().crossVectors(new V3(0,1,0), f).normalize(); tx -= ri.x*dx*r + f.x*dy*r*0.0; tz -= ri.z*dx*r + f.z*dy*r*0.0; ty += dy*r; } requestRender(); }); addEventListener('pointerup', () => drag = null); cv.addEventListener('wheel', e => { e.preventDefault(); rd *= (1 + Math.sign(e.deltaY)*0.09); requestRender(); }, {passive:false});