106 lines
4.1 KiB
JavaScript
106 lines
4.1 KiB
JavaScript
import assert from "node:assert/strict";
|
|
import { test } from "node:test";
|
|
import { getPositionsAndIndices } from "@recast-navigation/three";
|
|
import {
|
|
BufferAttribute,
|
|
BufferGeometry,
|
|
InterleavedBuffer,
|
|
InterleavedBufferAttribute,
|
|
Mesh,
|
|
Object3D,
|
|
Vector3,
|
|
} from "three";
|
|
import { unpackMeshesForRecast } from "./recast-mesh-adapter.mjs";
|
|
|
|
test("Recast adapter decodes interleaved positions, indices, normalized positions, and parent transforms", () => {
|
|
const parent = new Object3D();
|
|
parent.position.set(10, 2, -4);
|
|
parent.rotation.y = Math.PI / 2;
|
|
parent.scale.set(2, 3, 4);
|
|
|
|
const interleavedGeometry = new BufferGeometry();
|
|
const interleavedData = new InterleavedBuffer(new Float32Array([
|
|
0, 0, 0, 91, 1,
|
|
1, 0, 0, 92, 2,
|
|
0, 0, 1, 93, 3,
|
|
]), 5);
|
|
interleavedGeometry.setAttribute("position", new InterleavedBufferAttribute(interleavedData, 3, 0));
|
|
interleavedGeometry.setIndex(new BufferAttribute(new Uint16Array([0, 1, 2]), 1));
|
|
const interleavedMesh = new Mesh(interleavedGeometry);
|
|
interleavedMesh.name = "interleaved";
|
|
interleavedMesh.position.set(1, 0, 2);
|
|
parent.add(interleavedMesh);
|
|
|
|
const normalizedGeometry = new BufferGeometry();
|
|
normalizedGeometry.setAttribute("position", new BufferAttribute(new Uint16Array([
|
|
0, 0, 0,
|
|
65535, 0, 0,
|
|
0, 65535, 0,
|
|
]), 3, true));
|
|
const normalizedMesh = new Mesh(normalizedGeometry);
|
|
normalizedMesh.name = "normalized";
|
|
normalizedMesh.position.set(-3, 5, 7);
|
|
parent.add(normalizedMesh);
|
|
parent.updateMatrixWorld(true);
|
|
|
|
const originalInterleaved = Array.from(interleavedData.array);
|
|
const originalNormalized = Array.from(normalizedGeometry.getAttribute("position").array);
|
|
const result = unpackMeshesForRecast([interleavedMesh, normalizedMesh]);
|
|
assert.deepEqual(result.report, {
|
|
adapter: "decoded-three-buffer-attributes-v1",
|
|
sourceMeshCount: 2,
|
|
unpackedPositionAccessorCount: 2,
|
|
interleavedPositionAttributeCount: 1,
|
|
normalizedPositionAttributeCount: 1,
|
|
nonFloatPositionAttributeCount: 1,
|
|
decodedIndexAccessorCount: 1,
|
|
});
|
|
for (const [index, sourceMesh] of [interleavedMesh, normalizedMesh].entries()) {
|
|
assert.deepEqual(
|
|
result.meshes[index].matrixWorld.toArray(),
|
|
sourceMesh.matrixWorld.toArray(),
|
|
"inspection and generator transforms must agree before generator mutation",
|
|
);
|
|
}
|
|
|
|
const [positions, indices] = getPositionsAndIndices(result.meshes);
|
|
const expectedWorld = [];
|
|
const expectedIndices = [];
|
|
for (const [sourceMeshIndex, sourceMesh] of [interleavedMesh, normalizedMesh].entries()) {
|
|
const attr = sourceMesh.geometry.getAttribute("position");
|
|
for (let i = 0; i < attr.count; i += 1) {
|
|
const point = new Vector3(attr.getX(i), attr.getY(i), attr.getZ(i))
|
|
.applyMatrix4(sourceMesh.matrixWorld);
|
|
expectedWorld.push(point.x, point.y, point.z);
|
|
if (sourceMeshIndex === 1) expectedIndices.push(3 + i);
|
|
}
|
|
if (sourceMeshIndex === 0) expectedIndices.push(0, 1, 2);
|
|
}
|
|
|
|
assert.equal(positions.length, expectedWorld.length);
|
|
for (let i = 0; i < expectedWorld.length; i += 1) {
|
|
assert.ok(Math.abs(positions[i] - expectedWorld[i]) < 1e-5, `world position component ${i}`);
|
|
}
|
|
assert.deepEqual(Array.from(indices), expectedIndices);
|
|
assert.deepEqual(Array.from(interleavedData.array), originalInterleaved);
|
|
assert.deepEqual(Array.from(normalizedGeometry.getAttribute("position").array), originalNormalized);
|
|
assert.equal(result.meshes[0].geometry.getAttribute("position").array.length, 9);
|
|
assert.equal(result.meshes[1].geometry.getAttribute("position").array.length, 9);
|
|
});
|
|
|
|
test("Recast adapter rejects malformed indices before Uint32 conversion can truncate them", () => {
|
|
const geometry = new BufferGeometry();
|
|
geometry.setAttribute("position", new BufferAttribute(new Float32Array([
|
|
0, 0, 0,
|
|
1, 0, 0,
|
|
0, 0, 1,
|
|
]), 3));
|
|
geometry.setIndex(new BufferAttribute(new Float32Array([0, 1.5, 2]), 1));
|
|
const mesh = new Mesh(geometry);
|
|
mesh.name = "malformed-index-fixture";
|
|
assert.throws(
|
|
() => unpackMeshesForRecast([mesh]),
|
|
/malformed-index-fixture: index 1 references invalid position 1\.5/,
|
|
);
|
|
});
|