import assert from 'node:assert/strict' import { readFile } from 'node:fs/promises' import { createRequire } from 'node:module' import { pathToFileURL } from 'node:url' import test from 'node:test' import ts from 'typescript' const requireWeb = createRequire(new URL('../../unreal_tran_web/package.json', import.meta.url)) const threeUrl = pathToFileURL(requireWeb.resolve('three')).href const THREE = await import(threeUrl) const source = await readFile(new URL('../../unreal_tran_web/src/features/scene-legacy/modelPlacement.ts', import.meta.url), 'utf8') const output = ts.transpileModule(source, { compilerOptions: { module: ts.ModuleKind.ESNext, target: ts.ScriptTarget.ES2022 }, reportDiagnostics: true, }) assert.deepEqual(output.diagnostics, []) const compiled = output.outputText.replace(/from 'three'/g, `from '${threeUrl}'`) const { fitSceneModel, refreshSceneModelBounds, parseSceneScale, formatSceneScale } = await import( `data:text/javascript;base64,${Buffer.from(compiled).toString('base64')}`, ) function close(actual, expected, tolerance = 1e-8) { assert.ok(Math.abs(actual - expected) <= tolerance, `${actual} != ${expected}`) } function skinFixture() { const root = new THREE.Group() const bone = new THREE.Bone() const geometry = new THREE.BoxGeometry(2, 4, 10) geometry.translate(0, 2, 0) const count = geometry.getAttribute('position').count const indices = new Uint16Array(count * 4) const weights = new Float32Array(count * 4) for (let i = 0; i < count; i += 1) weights[i * 4] = 1 geometry.setAttribute('skinIndex', new THREE.Uint16BufferAttribute(indices, 4)) geometry.setAttribute('skinWeight', new THREE.Float32BufferAttribute(weights, 4)) const mesh = new THREE.SkinnedMesh(geometry, new THREE.MeshBasicMaterial()) root.add(bone, mesh) root.updateMatrixWorld(true) mesh.bind(new THREE.Skeleton([bone])) // The published model has already been normalized and may be saved at an // animation pose. This is the same stale matrix/cache state as loaded GLTF. root.scale.setScalar(0.1) root.position.set(1, 2, 3) bone.position.y = 5 return { root, bone, mesh } } test('fresh skinned placement reaches the requested size and floor after authored scale/pose replay', () => { const { root, mesh } = skinFixture() // Populate the wrong cached skin box just as the previous import path did. const stale = new THREE.Box3().setFromObject(root).getSize(new THREE.Vector3()) assert.ok(stale.z < 1) const box = fitSceneModel(root, 6) const size = box.getSize(new THREE.Vector3()) close(size.x, 1.2) close(size.y, 2.4) close(size.z, 6) close(root.scale.x, 0.6) close(box.min.y, 0) close(box.getCenter(new THREE.Vector3()).x, 0) close(box.getCenter(new THREE.Vector3()).z, 0) const vertex = new THREE.Vector3() for (let i = 0; i < mesh.geometry.getAttribute('position').count; i += 1) { mesh.getVertexPosition(i, vertex) assert.ok(mesh.boundingSphere.containsPoint(vertex)) } }) test('fresh static placement preserves authored non-uniform proportions', () => { const root = new THREE.Group() root.add(new THREE.Mesh(new THREE.BoxGeometry(2, 4, 10), new THREE.MeshBasicMaterial())) root.scale.set(2, 0.5, 3) root.position.set(-4, 8, 6) const box = fitSceneModel(root, 6) root.scale.toArray().forEach((value, i) => close(value, [0.4, 0.1, 0.6][i])) close(box.getSize(new THREE.Vector3()).z, 6) close(box.min.y, 0) }) test('restoring skin caches keeps existing scene transforms and authored bone poses unchanged', () => { const { root, bone, mesh } = skinFixture() refreshSceneModelBounds(root) root.position.set(-7.5, 1.06, -0.45) root.rotation.set(0.1, 0.4, -0.2) root.scale.set(6, 4, 2) bone.position.set(1, 17, 3) const before = { position: root.position.toArray(), rotation: root.rotation.toArray(), scale: root.scale.toArray(), bone: bone.position.toArray() } const staleBox = mesh.boundingBox.clone() const box = refreshSceneModelBounds(root) assert.deepEqual({ position: root.position.toArray(), rotation: root.rotation.toArray(), scale: root.scale.toArray(), bone: bone.position.toArray() }, before) assert.notDeepEqual(mesh.boundingBox, staleBox) const vertex = new THREE.Vector3() const generousBox = box.clone().expandByScalar(1e-8) for (let i = 0; i < mesh.geometry.getAttribute('position').count; i += 1) { mesh.getVertexPosition(i, vertex) assert.ok(vertex.distanceTo(mesh.boundingSphere.center) <= mesh.boundingSphere.radius + 1e-8) assert.ok(generousBox.containsPoint(vertex.applyMatrix4(mesh.matrixWorld))) } }) test('declared terrain ground datum stays on the scene floor', () => { const root = new THREE.Group() root.add(new THREE.Mesh(new THREE.BoxGeometry(8, 4, 10), new THREE.MeshBasicMaterial())) fitSceneModel(root, 20, -1.5) close(-1.5 * root.scale.y + root.position.y, 0) }) test('scale input accepts real GLTF normalization factors below 0.05', () => { close(parseSceneScale('0.0361637593257', 6), 0.0361637593257) close(parseSceneScale('0.000001', 6), 0.000001) close(parseSceneScale('1e-9', 6), 0.000001) for (const value of ['', ' ', 'invalid', 'NaN', 'Infinity', '0', '-2']) { close(parseSceneScale(value, 0.036164), 0.036164) } }) test('scale display preserves small factors and trims unnecessary zeros', () => { assert.equal(formatSceneScale(0.0361637593257), '0.03616376') assert.equal(formatSceneScale(0.000001), '0.000001') assert.equal(formatSceneScale(1), '1') assert.equal(formatSceneScale(0.6), '0.6') })