Initial commit: RHINE LAB · ANALYSIS OS:三维界面与动效实验(本仓库不含个人归档索引数据)
This commit is contained in:
@@ -0,0 +1,55 @@
|
||||
<!doctype html><html lang="zh-CN"><meta charset="utf-8"><title>360° 导航过渡检查</title>
|
||||
<style>body{font:15px system-ui;background:#e8e5e1;padding:20px}iframe{width:720px;height:405px;border:0}pre{white-space:pre-wrap}</style>
|
||||
<h1>360° 导航过渡检查</h1><pre id="result">正在检查…</pre><iframe id="app" src="/?scene=detail"></iframe>
|
||||
<script type="module">
|
||||
import * as THREE from '/node_modules/three/build/three.module.js';
|
||||
import {ViewerCameraMotion} from '/src/viewer-camera.ts';
|
||||
const result=document.querySelector('#result'),app=document.querySelector('#app'),checks=[];
|
||||
const check=(name,value)=>{checks.push({name,passed:!!value});if(!value)throw Error(name)};
|
||||
const wait=async predicate=>{const start=performance.now();while(!predicate()){if(performance.now()-start>30000)throw Error('等待页面超时');await new Promise(r=>setTimeout(r,15))}};
|
||||
const close=(a,b,epsilon=1e-5)=>Math.abs(a-b)<epsilon;
|
||||
function rig(){const camera=new THREE.PerspectiveCamera(),goal=new THREE.PerspectiveCamera(),target=new THREE.Vector3();goal.position.set(7.2,3.8,12);const motion=new ViewerCameraMotion(camera);motion.snap(goal,target);return{camera,goal,target,motion}}
|
||||
try {
|
||||
const a=rig(),b=rig();for(const r of[a,b]){r.goal.position.multiplyScalar(.6);r.target.set(1,.5,0)}
|
||||
for(let i=0;i<30;i++)a.motion.update(a.goal,a.target,1/30);for(let i=0;i<120;i++)b.motion.update(b.goal,b.target,1/120);
|
||||
check('30 fps 与 120 fps 的相机过渡一致',a.camera.position.distanceTo(b.camera.position)<1e-7&&a.motion.focus.distanceTo(b.motion.focus)<1e-7);
|
||||
const c=rig();const initial=c.camera.position.clone();c.goal.position.set(-6,2,-9);c.target.set(2,1,0);c.motion.update(c.goal,c.target,.1);c.goal.position.copy(initial);c.target.set(0,0,0);const before=c.camera.position.clone();c.motion.reset();
|
||||
check('复位请求不会瞬间移动显示相机',c.camera.position.distanceTo(before)===0);
|
||||
let minimum=Infinity;for(let i=0;i<34;i++){c.motion.update(c.goal,c.target,1/60);minimum=Math.min(minimum,c.camera.position.distanceTo(c.motion.focus))}
|
||||
check('复位沿模型外侧过渡并精确回到默认视角',minimum>5&&c.camera.position.distanceTo(initial)<1e-7&&!c.motion.resetting);
|
||||
c.goal.position.set(-6,3,8);c.motion.update(c.goal,c.target,.1);c.goal.position.copy(initial);c.motion.reset();c.motion.update(c.goal,c.target,.1);const interrupted=c.camera.position.clone();c.motion.interruptReset(c.goal,c.target);
|
||||
check('新输入从当前画面打断复位',!c.motion.resetting&&c.goal.position.distanceTo(interrupted)<1e-7&&c.camera.position.distanceTo(interrupted)===0);
|
||||
c.goal.position.set(0,1,6);c.target.set(.5,0,0);c.motion.update(c.goal,c.target,1/60,true);
|
||||
check('减少动态效果时相机立即到位',c.camera.position.distanceTo(c.goal.position)<1e-7&&c.motion.focus.distanceTo(c.target)===0);
|
||||
const seam=rig();seam.goal.position.setFromSpherical(new THREE.Spherical(10,1.2,Math.PI-.05));seam.motion.snap(seam.goal,seam.target);const seamStart=seam.camera.position.clone();seam.goal.position.setFromSpherical(new THREE.Spherical(10,1.2,-Math.PI+.05));seam.motion.update(seam.goal,seam.target,.05);
|
||||
check('旋转越过正负 180° 时走短路径',seam.camera.position.distanceTo(seamStart)<1);
|
||||
await wait(()=>app.contentWindow?.rhine&&app.contentDocument.querySelector('#stage')?.dataset.mode==='detail'&&!app.contentDocument.querySelector('#loading'));
|
||||
const doc=app.contentDocument,win=app.contentWindow;
|
||||
doc.querySelector('.viewer-open').click();const viewer=doc.querySelector('.model-viewer');
|
||||
const stats=()=>JSON.parse(viewer.dataset.stats||'{}');await wait(()=>stats().ready);
|
||||
const canvas=viewer.querySelector('canvas');
|
||||
const key=key=>canvas.dispatchEvent(new win.KeyboardEvent('keydown',{key,bubbles:true,cancelable:true}));
|
||||
const wheel=deltaY=>canvas.dispatchEvent(new win.WheelEvent('wheel',{deltaY,deltaMode:0,bubbles:true,cancelable:true}));
|
||||
const origin=stats();key('ArrowRight');await wait(()=>stats().requestedTarget?.some(v=>Math.abs(v)>.01));
|
||||
let s=stats();check('方向键平移有中间位置',new THREE.Vector3(...s.targetPosition).length()>0&&new THREE.Vector3(...s.targetPosition).distanceTo(new THREE.Vector3(...s.requestedTarget))>.001);
|
||||
await wait(()=>new THREE.Vector3(...stats().targetPosition).distanceTo(new THREE.Vector3(...stats().requestedTarget))<.001);
|
||||
const beforeWheel=stats().distance;wheel(-120);wheel(-120);await wait(()=>stats().requestedDistance<beforeWheel-.01);s=stats();
|
||||
check('滚轮连续缩放累计目标并平滑追随',s.distance<beforeWheel&&s.distance>s.requestedDistance+.001);
|
||||
await wait(()=>close(stats().distance,stats().requestedDistance,.001));
|
||||
const beforeKey=stats().distance;key('+');await wait(()=>stats().requestedDistance<beforeKey-.01);s=stats();
|
||||
check('加减键缩放也有中间距离',s.distance>s.requestedDistance+.001&&s.distance<beforeKey);
|
||||
viewer.querySelector('[data-viewer="reset"]').click();await wait(()=>stats().resetting);
|
||||
check('复位按钮进入过渡而非瞬移',!close(stats().distance,origin.distance,.001));
|
||||
key('ArrowUp');await wait(()=>!stats().resetting);
|
||||
check('实际方向键能中断正在复位的视角',stats().requestedTarget.some(v=>Math.abs(v)>.01));
|
||||
key('Home');await wait(()=>!stats().resetting&&close(stats().distance,origin.distance,.0001));
|
||||
check('Home 复位精确恢复相机和观察中心',new THREE.Vector3(...stats().cameraPosition).distanceTo(new THREE.Vector3(...origin.cameraPosition))<.0001&&new THREE.Vector3(...stats().targetPosition).length()<.0001);
|
||||
for(let i=0;i<80;i++)key('ArrowRight');for(let i=0;i<80;i++)key('+');await wait(()=>close(stats().requestedDistance,5));
|
||||
check('连续输入仍遵守平移与缩放边界',new THREE.Vector3(...stats().requestedTarget).length()<=5.00001&&stats().requestedDistance>=4.99999);
|
||||
key('Home');await wait(()=>!stats().resetting&&close(stats().distance,origin.distance,.0001));
|
||||
viewer.querySelector('[data-viewer="close"]').click();await wait(()=>viewer.hidden);
|
||||
doc.querySelector('.viewer-open').click();await wait(()=>!viewer.hidden&&stats().ready);
|
||||
check('关闭后重新进入没有残留镜头位移',new THREE.Vector3(...stats().targetPosition).length()<.0001&&close(stats().distance,origin.distance,.0001));
|
||||
result.textContent=JSON.stringify({passed:true,checks},null,2);
|
||||
}catch(error){result.textContent=JSON.stringify({passed:false,error:String(error),checks},null,2)}
|
||||
</script></html>
|
||||
Reference in New Issue
Block a user