115 lines
4.2 KiB
JavaScript
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));
|