import assert from "node:assert/strict"; import { describe, it } from "node:test"; import * as THREE from "three"; import { MODEL_X_METRICS, LUMBRIDGE_EV_METRICS, advanceModelXWheels, buildModelX, buildLumbridgeEV, cloneModelX, disposeModelX, modelXInstanceParts, setModelXSteering, setModelXWheelRotation, } from "../assets/vehicles/index.ts"; function meshes(root: THREE.Object3D): THREE.Mesh[] { const found: THREE.Mesh[] = []; root.traverse((object) => { if (object instanceof THREE.Mesh) found.push(object); }); return found; } describe("procedural Model X vehicle asset", () => { it("publishes the original unbranded Lumbridge EV identity at both LODs", () => { const follow = buildLumbridgeEV({ detail: "follow" }); const corridor = buildLumbridgeEV({ detail: "corridor" }); assert.equal(LUMBRIDGE_EV_METRICS, MODEL_X_METRICS); assert.equal(follow.root.userData.design, "lumbridge-ev-01"); assert.equal(follow.root.userData.unbranded, true); assert.equal(follow.root.userData.lod, "follow"); assert.equal(corridor.root.userData.lod, "corridor"); const followVertices = meshes(follow.root).reduce((sum, mesh) => sum + mesh.geometry.getAttribute("position").count, 0); const corridorVertices = meshes(corridor.root).reduce((sum, mesh) => sum + mesh.geometry.getAttribute("position").count, 0); assert.ok(followVertices > corridorVertices, "follow LOD includes a readable interior and denser wheels"); disposeModelX(follow); disposeModelX(corridor); }); it("has a metre-scale crossover silhouette and faces -Z", () => { const rig = buildModelX({ detail: "corridor" }); const size = new THREE.Box3().setFromObject(rig.root).getSize(new THREE.Vector3()); assert.ok(Math.abs(size.x - MODEL_X_METRICS.width) < 0.12, `width ${size.x}`); assert.ok(Math.abs(size.y - MODEL_X_METRICS.height) < 0.08, `height ${size.y}`); assert.ok(Math.abs(size.z - MODEL_X_METRICS.length) < 0.08, `length ${size.z}`); assert.equal(rig.root.userData.forwardAxis, "-Z"); assert.ok(rig.wheels.frontLeft.steering.position.z < 0); assert.ok(rig.wheels.rearLeft.steering.position.z > 0); disposeModelX(rig); }); it("exposes independent steering and rolling joints", () => { const rig = buildModelX(); setModelXWheelRotation(rig, 1.25); for (const wheel of Object.values(rig.wheels)) assert.equal(wheel.spin.rotation.x, 1.25); setModelXSteering(rig, 99); assert.equal(rig.wheels.frontLeft.steering.rotation.y, MODEL_X_METRICS.maxSteeringAngle); assert.equal(rig.wheels.frontRight.steering.rotation.y, MODEL_X_METRICS.maxSteeringAngle); assert.equal(rig.wheels.rearLeft.steering.rotation.y, 0); advanceModelXWheels(rig, MODEL_X_METRICS.wheelRadius); assert.equal(rig.wheels.frontLeft.spin.rotation.x, 0.25); disposeModelX(rig); }); it("clones cheaply while keeping its pose independent", () => { const original = buildModelX(); const clone = cloneModelX(original); const sourceMeshes = meshes(original.root); const clonedMeshes = meshes(clone.root); assert.equal(clonedMeshes.length, sourceMeshes.length); for (let i = 0; i < sourceMeshes.length; i++) { assert.equal(clonedMeshes[i]!.geometry, sourceMeshes[i]!.geometry); assert.equal(clonedMeshes[i]!.material, sourceMeshes[i]!.material); } setModelXSteering(clone, -0.3); assert.equal(clone.wheels.frontLeft.steering.rotation.y, -0.3); assert.equal(original.wheels.frontLeft.steering.rotation.y, 0); assert.equal(clone.ownsMaterials, false); disposeModelX(original); }); it("publishes stable neutral-pose pieces for instanced traffic", () => { const rig = buildModelX({ detail: "corridor" }); const parts = modelXInstanceParts(rig); assert.equal(parts.length, meshes(rig.root).length); assert.ok(parts.some((part) => part.name === "frontLeft.tire")); assert.ok(parts.some((part) => part.name.startsWith("model-x.body:"))); assert.ok(parts.every((part) => Number.isFinite(part.matrix.determinant()))); disposeModelX(rig); }); });