state / harness / camera / pick / ui / sheets / render / urlparams。 经典脚本的顶层 const/let 落在同一个全局词法作用域,所以切片本身不改变任何行为—— 这一步先让"文件边界"与"模块语义"两件事分离:翻 ESM 时若出问题,责任清楚在 import/export,而不是切片切错了行。 切片踩到并修掉的:urlparams.js 被切掉了 IIFE 的收尾 })();(我按 len-2 收边界), node --check 当场报 Unexpected end of input。 截图基线分级也在这一步收敛:工程图/装配向导视图带 feGaussianBlur 发光滤镜, 同一构建两轮就有 ±1 抖动(先前扫描那两轮碰巧逐字节相同,才漏进"稳定"清单, 于是随机假 FAIL)。把 8 张工程图/向导视图连同 3 张线束 x-ray、boss_p07、 p03_mount_loose 一起转像素容差(存参考像素,阈值 mean<=0.02、单像素<=32, 约实测 20x 余量),其余 92 张仍要求逐字节相同。 关卡:92 张逐字节一致 + 13 张在噪声阈内;数据哈希与 IK exact 段一致。
116 lines
5.9 KiB
JavaScript
116 lines
5.9 KiB
JavaScript
/* 共享状态根:渲染器/场景/相机/灯光/栅格 + 关节链 + 网格构建 + 按需渲染标志 */
|
||
const TL = {}; for (const t of TRAVEL) TL[t.axis] = t;
|
||
const V3 = THREE.Vector3;
|
||
const VC = p => [p[0], p[2], -p[1]]; // CAD (x,y,z) -> viewer (x,z,-y); must precede use
|
||
const cv = document.getElementById('cv');
|
||
const rend = new THREE.WebGLRenderer({canvas:cv, antialias:true, preserveDrawingBuffer:true});
|
||
rend.setPixelRatio(Math.min(window.devicePixelRatio || 1, 1.5));
|
||
const scene = new THREE.Scene();
|
||
scene.background = new THREE.Color(0x0d0f13);
|
||
const cam = new THREE.PerspectiveCamera(38, 1, 1, 6000);
|
||
scene.add(new THREE.HemisphereLight(0xcfe4ff, 0x22262e, 1.15));
|
||
const key = new THREE.DirectionalLight(0xffffff, 1.5); key.position.set(420, 620, 520); scene.add(key);
|
||
const key2 = new THREE.DirectionalLight(0x9dc4ff, .7); key2.position.set(-520, 300, -420); scene.add(key2);
|
||
const grid = new THREE.GridHelper(600, 30, 0x2b323e, 0x1b2028); grid.position.y = -0.5; scene.add(grid);
|
||
|
||
let needRender = true;
|
||
function requestRender() { needRender = true; }
|
||
|
||
/* ---- kinematic groups: nested pivot matrices, serial chain ---- */
|
||
const ROOT = new THREE.Object3D(); scene.add(ROOT);
|
||
const JG = {root: ROOT};
|
||
const JO = DATA.joints;
|
||
const parentOf = {J1:'root', J2:'J1', J3:'J2', J4:'J3', J5:'J4', J6:'J5'};
|
||
const ang = {J1:0, J2:0, J3:0, J4:0, J5:0, J6:0};
|
||
for (const j of JO) {
|
||
const g = new THREE.Object3D(); g.matrixAutoUpdate = false;
|
||
JG[j.id] = g; JG[parentOf[j.id]].add(g);
|
||
}
|
||
/* 轴心 P / 轴向 D 只与 CAD 参数有关:预计算一次;矩阵合成改用 3 个共享实例。
|
||
旧版每次调用新建 2 个 Vector3 + 3 个 Matrix4(一次关节写入 = 6 次调用 = 30 个临时对象),
|
||
动画与实时追踪高频触发时纯属垃圾。数学与原式逐位相同:T(P)·R(D,θ)·T(−P)。 */
|
||
const JP = {}, JD = {};
|
||
for (const j of JO) { JP[j.id] = new V3(...VC(j.rear)); JD[j.id] = new V3(...VC(j.d)).normalize(); }
|
||
const _jmT = new THREE.Matrix4(), _jmR = new THREE.Matrix4(), _jmTi = new THREE.Matrix4();
|
||
function applyJoint(id) {
|
||
// geometry vertices were built in the viewer frame (x, z, -y), so the pivot point and the
|
||
// axis must go through the same swap - using raw CAD coords rotates about the wrong line
|
||
const P = JP[id], D = JD[id]; if (!P) return;
|
||
const a = (ang[id] || 0) * Math.PI / 180;
|
||
const m = _jmT.makeTranslation(P.x, P.y, P.z)
|
||
.multiply(_jmR.makeRotationAxis(D, a))
|
||
.multiply(_jmTi.makeTranslation(-P.x, -P.y, -P.z));
|
||
JG[id].matrix.copy(m);
|
||
JG[id].matrixWorldNeedsUpdate = true;
|
||
if (window.updateHarnessBridges && window.isHarnessVisible && window.isHarnessVisible()) {
|
||
updateHarnessBridges();
|
||
}
|
||
requestRender();
|
||
}
|
||
|
||
/* ---- meshes ---- */
|
||
const meshes = [], byName = {};
|
||
let expl = 0;
|
||
for (const p of DATA.parts) {
|
||
const geo = new THREE.BufferGeometry();
|
||
const pos = new Float32Array(p.v.length * 3);
|
||
for (let i = 0; i < p.v.length; i++) { pos[i*3] = p.v[i][0]; pos[i*3+1] = p.v[i][2]; pos[i*3+2] = -p.v[i][1]; }
|
||
p.triCount = p.t ? p.t.length : 0;
|
||
p.vertCount = p.v.length;
|
||
/* 外形三围:模型坐标系 X×Y×Z 包围盒(装配零位、打印朝向 Z=高),悬停面板显示用 */
|
||
{ let x0=1e9,y0=1e9,z0=1e9,x1=-1e9,y1=-1e9,z1=-1e9;
|
||
for (let i = 0; i < p.v.length; i++) { const a = p.v[i];
|
||
if (a[0]<x0)x0=a[0]; if (a[0]>x1)x1=a[0];
|
||
if (a[1]<y0)y0=a[1]; if (a[1]>y1)y1=a[1];
|
||
if (a[2]<z0)z0=a[2]; if (a[2]>z1)z1=a[2]; }
|
||
p.dims = [x1-x0, y1-y0, z1-z0]; }
|
||
const isEnv = p.name.slice(0, 4) === 'ENV_'; // 可达包络:运动学边界,不随关节动
|
||
/* 索引按顶点数降位:单件 ≤65535 顶点时 Uint16 合法(92 件实测最大 47483),
|
||
索引内存与上传带宽减半;超大件保留 Uint32 兜底。 */
|
||
const IDX16 = p.v.length <= 65535;
|
||
if (p.kind === 'line') { // 经纬线:LineSegments,无三角面
|
||
const sIdx = IDX16 ? new Uint16Array(p.s) : new Uint32Array(p.s);
|
||
geo.setAttribute('position', new THREE.BufferAttribute(pos, 3));
|
||
geo.setIndex(new THREE.BufferAttribute(sIdx, 1));
|
||
geo.computeBoundingSphere(); geo.computeBoundingBox();
|
||
const lm = new THREE.LineBasicMaterial({color: p.color, transparent: true, opacity: 0.8});
|
||
const ls = new THREE.LineSegments(geo, lm);
|
||
ls.userData = p;
|
||
ls.position.set(p.ex[0], p.ex[2], -p.ex[1]);
|
||
ls.base = ls.position.clone();
|
||
ls.visible = !isEnv; // 包络默认隐藏,走开关显示
|
||
JG[p.grp || 'root'].add(ls);
|
||
meshes.push(ls); byName[p.name] = ls;
|
||
delete p.v; delete p.s;
|
||
continue;
|
||
}
|
||
const idx = IDX16 ? new Uint16Array(p.t.length * 3) : new Uint32Array(p.t.length * 3);
|
||
// the vertex map (x,y,z)->(x,z,-y) has determinant +1, so triangle winding must be kept
|
||
// as-is; swapping two indices here inverted every face and made the normals point inward
|
||
for (let i = 0; i < p.t.length; i++) { idx[i*3] = p.t[i][0]; idx[i*3+1] = p.t[i][1]; idx[i*3+2] = p.t[i][2]; }
|
||
geo.setAttribute('position', new THREE.BufferAttribute(pos, 3));
|
||
geo.setIndex(new THREE.BufferAttribute(idx, 1));
|
||
geo.computeVertexNormals();
|
||
geo.computeBoundingSphere();
|
||
geo.computeBoundingBox();
|
||
delete p.v;
|
||
delete p.t;
|
||
const isMo = !p.printed && p.name[0] === 'M';
|
||
const isScr = !!p.screw; // 紧固件(螺钉):金属亮色,可被 BOM 行高亮
|
||
const mat = new THREE.MeshStandardMaterial({
|
||
color: p.color, metalness: isScr ? .78 : (isMo ? .55 : .05),
|
||
roughness: isScr ? .30 : (isMo ? .42 : .78),
|
||
transparent: isEnv, opacity: isEnv ? .06 : 1,
|
||
depthWrite: !isEnv,
|
||
flatShading: false, side: isEnv ? THREE.DoubleSide : THREE.FrontSide});
|
||
const ms = new THREE.Mesh(geo, mat);
|
||
ms.userData = p;
|
||
ms.position.set(p.ex[0], p.ex[2], -p.ex[1]);
|
||
ms.base = ms.position.clone();
|
||
ms.zero = [0,0,0];
|
||
if (isEnv) ms.visible = false; // 包络默认隐藏,走开关显示
|
||
JG[p.grp || 'root'].add(ms);
|
||
meshes.push(ms); byName[p.name] = ms;
|
||
}
|
||
|