California gets roads, traffic, and a car to follow
This commit is contained in:
@@ -0,0 +1,80 @@
|
||||
import assert from "node:assert/strict";
|
||||
import { describe, it } from "node:test";
|
||||
import * as THREE from "three";
|
||||
import {
|
||||
MODEL_X_METRICS,
|
||||
advanceModelXWheels,
|
||||
buildModelX,
|
||||
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("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);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user