Files
healer-man/scripts/manastorm-assets/inspect-navigation-components.mjs
T
2026-08-14 15:56:39 -04:00

115 lines
4.2 KiB
JavaScript

#!/usr/bin/env node
import { NodeIO } from "@gltf-transform/core";
import { ALL_EXTENSIONS } from "@gltf-transform/extensions";
import { MeshoptDecoder } from "meshoptimizer";
const file = process.argv[2];
if (!file) throw new Error("Usage: node inspect-navigation-components.mjs <navigation.glb>");
await MeshoptDecoder.ready;
const document = await new NodeIO()
.registerExtensions(ALL_EXTENSIONS)
.registerDependencies({ "meshopt.decoder": MeshoptDecoder })
.read(file);
const meshNode = document.getRoot().listNodes().find((node) => node.getMesh());
const primitive = meshNode?.getMesh()?.listPrimitives()[0];
const positionAccessor = primitive?.getAttribute("POSITION");
const sourcePositions = positionAccessor?.getArray();
const sourceIndices = primitive?.getIndices()?.getArray();
if (!sourcePositions || !meshNode || !positionAccessor) throw new Error(`${file}: no POSITION accessor.`);
const matrix = meshNode.getWorldMatrix();
const normalizer = positionAccessor.getNormalized()
? sourcePositions instanceof Int16Array
? (value) => Math.max(value / 32_767, -1)
: sourcePositions instanceof Uint16Array
? (value) => value / 65_535
: sourcePositions instanceof Int8Array
? (value) => Math.max(value / 127, -1)
: sourcePositions instanceof Uint8Array
? (value) => value / 255
: (value) => value
: (value) => value;
const positions = new Float64Array(sourcePositions.length);
for (let offset = 0; offset < sourcePositions.length; offset += 3) {
const x = normalizer(sourcePositions[offset]);
const y = normalizer(sourcePositions[offset + 1]);
const z = normalizer(sourcePositions[offset + 2]);
positions[offset] = matrix[0] * x + matrix[4] * y + matrix[8] * z + matrix[12];
positions[offset + 1] = matrix[1] * x + matrix[5] * y + matrix[9] * z + matrix[13];
positions[offset + 2] = matrix[2] * x + matrix[6] * y + matrix[10] * z + matrix[14];
}
const indices = sourceIndices
? Array.from(sourceIndices)
: Array.from({ length: positions.length / 3 }, (_, index) => index);
const triangleCount = Math.floor(indices.length / 3);
const parent = Array.from({ length: triangleCount }, (_, index) => index);
const vertexOwner = new Map();
const vertexIds = [];
const welded = new Map();
for (let offset = 0; offset < positions.length; offset += 3) {
const key = [
Math.round(positions[offset] * 1_000),
Math.round(positions[offset + 1] * 1_000),
Math.round(positions[offset + 2] * 1_000),
].join(":");
if (!welded.has(key)) welded.set(key, welded.size);
vertexIds.push(welded.get(key));
}
function root(start) {
let value = start;
while (parent[value] !== value) {
parent[value] = parent[parent[value]];
value = parent[value];
}
return value;
}
function union(left, right) {
const leftRoot = root(left);
const rightRoot = root(right);
if (leftRoot !== rightRoot) parent[rightRoot] = leftRoot;
}
for (let triangle = 0; triangle < triangleCount; triangle += 1) {
for (let corner = 0; corner < 3; corner += 1) {
const vertex = vertexIds[indices[triangle * 3 + corner]];
if (vertexOwner.has(vertex)) union(triangle, vertexOwner.get(vertex));
else vertexOwner.set(vertex, triangle);
}
}
const components = new Map();
for (let triangle = 0; triangle < triangleCount; triangle += 1) {
const component = root(triangle);
const entry = components.get(component) ?? {
triangles: 0,
minimum: [Infinity, Infinity, Infinity],
maximum: [-Infinity, -Infinity, -Infinity],
};
entry.triangles += 1;
for (let corner = 0; corner < 3; corner += 1) {
const pointIndex = indices[triangle * 3 + corner] * 3;
for (let axis = 0; axis < 3; axis += 1) {
entry.minimum[axis] = Math.min(entry.minimum[axis], positions[pointIndex + axis]);
entry.maximum[axis] = Math.max(entry.maximum[axis], positions[pointIndex + axis]);
}
}
components.set(component, entry);
}
const result = [...components.values()]
.sort((left, right) => right.triangles - left.triangles)
.map((component, index) => ({
rank: index + 1,
...component,
span: component.maximum.map((value, axis) => value - component.minimum[axis]),
}));
console.log(JSON.stringify({
file,
triangles: triangleCount,
components: result.length,
top: result.slice(0, 20),
}, null, 2));