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, }, }; }