Files
healer-man/scripts/dungeon-pipeline/recast-mesh-adapter.mjs
T
2026-10-07 13:05:04 -04:00

76 lines
3.0 KiB
JavaScript

import { BufferAttribute, BufferGeometry, Mesh } from "three";
/**
* @recast-navigation/three currently assumes position/index attributes are
* tightly-packed Float32/Uint arrays. glTFLoader may return interleaved or
* normalized BufferAttributes, so copy decoded accessor values into a plain
* geometry before handing meshes to its generator helpers.
*/
export function unpackMeshesForRecast(meshes) {
let interleavedPositionAttributeCount = 0;
let normalizedPositionAttributeCount = 0;
let nonFloatPositionAttributeCount = 0;
let unpackedPositionAccessorCount = 0;
let decodedIndexAccessorCount = 0;
const preparedMeshes = meshes.map((sourceMesh) => {
const sourceGeometry = sourceMesh.geometry;
const sourcePositions = sourceGeometry?.getAttribute("position");
if (!sourcePositions || sourcePositions.itemSize !== 3) {
throw new Error(`${sourceMesh.name || "collision mesh"}: POSITION must be a VEC3 attribute.`);
}
const positions = new Float32Array(sourcePositions.count * 3);
for (let index = 0; index < sourcePositions.count; index += 1) {
const offset = index * 3;
positions[offset] = sourcePositions.getX(index);
positions[offset + 1] = sourcePositions.getY(index);
positions[offset + 2] = sourcePositions.getZ(index);
}
const geometry = new BufferGeometry();
geometry.setAttribute("position", new BufferAttribute(positions, 3));
if (sourcePositions.isInterleavedBufferAttribute) interleavedPositionAttributeCount += 1;
if (sourcePositions.normalized) normalizedPositionAttributeCount += 1;
if (!(sourcePositions.array instanceof Float32Array)) nonFloatPositionAttributeCount += 1;
unpackedPositionAccessorCount += 1;
const sourceIndices = sourceGeometry.getIndex();
if (sourceIndices) {
const indices = new Uint32Array(sourceIndices.count);
for (let index = 0; index < sourceIndices.count; index += 1) {
const value = sourceIndices.getX(index);
if (!Number.isInteger(value) || value < 0 || value >= sourcePositions.count) {
throw new Error(
`${sourceMesh.name || "collision mesh"}: index ${index} references invalid position ${value}.`,
);
}
indices[index] = value;
}
geometry.setIndex(new BufferAttribute(indices, 1));
decodedIndexAccessorCount += 1;
}
sourceMesh.updateWorldMatrix(true, false);
const preparedMesh = new Mesh(geometry);
preparedMesh.name = sourceMesh.name;
preparedMesh.matrixAutoUpdate = false;
preparedMesh.matrix.copy(sourceMesh.matrixWorld);
preparedMesh.matrixWorld.copy(sourceMesh.matrixWorld);
return preparedMesh;
});
return {
meshes: preparedMeshes,
report: {
adapter: "decoded-three-buffer-attributes-v1",
sourceMeshCount: meshes.length,
unpackedPositionAccessorCount,
interleavedPositionAttributeCount,
normalizedPositionAttributeCount,
nonFloatPositionAttributeCount,
decodedIndexAccessorCount,
},
};
}