Files
healer-man/scripts/arena-pipeline/inspect-navigation.mjs
T
2026-09-05 20:42:38 -04:00

85 lines
3.9 KiB
JavaScript

import { readFile } from "node:fs/promises";
import path from "node:path";
import { NodeIO } from "@gltf-transform/core";
const projectRoot = path.resolve(import.meta.dirname, "../..");
const catalog = JSON.parse(await readFile(path.join(projectRoot, "shared/arena-stages.json"), "utf8"));
const io = new NodeIO();
const requested = new Set(process.argv.slice(2).filter((value) => !value.startsWith("--")));
const detail = process.argv.includes("--all");
class UnionFind {
constructor(size) { this.parent = Array.from({ length: size }, (_, index) => index); }
find(value) { if (this.parent[value] !== value) this.parent[value] = this.find(this.parent[value]); return this.parent[value]; }
union(left, right) { const a = this.find(left); const b = this.find(right); if (a !== b) this.parent[b] = a; }
}
function round(values) { return values.map((value) => Math.round(value * 100) / 100); }
for (const stage of catalog.stages) {
if (requested.size && !requested.has(stage.id)) continue;
const base = path.join(projectRoot, "public", ...stage.assetBaseUrl.split("/").filter(Boolean));
let document;
try { document = await io.read(path.join(base, "navigation.glb")); } catch { continue; }
const vertices = [];
const triangles = [];
const byPosition = new Map();
for (const mesh of document.getRoot().listMeshes()) {
for (const primitive of mesh.listPrimitives()) {
const positions = primitive.getAttribute("POSITION")?.getArray();
if (!positions) continue;
const indices = primitive.getIndices()?.getArray() ?? Uint32Array.from({ length: positions.length / 3 }, (_, index) => index);
for (let offset = 0; offset < indices.length; offset += 3) {
const triangle = [];
for (let corner = 0; corner < 3; corner += 1) {
const index = indices[offset + corner];
const vertex = [positions[index * 3], positions[index * 3 + 1], positions[index * 3 + 2]];
const key = vertex.map((value) => Math.round(value * 1000)).join(":");
if (!byPosition.has(key)) {
byPosition.set(key, vertices.length);
vertices.push(vertex);
}
triangle.push(byPosition.get(key));
}
triangles.push(triangle);
}
}
}
const union = new UnionFind(vertices.length);
for (const triangle of triangles) {
union.union(triangle[0], triangle[1]);
union.union(triangle[0], triangle[2]);
}
const grouped = new Map();
for (let index = 0; index < vertices.length; index += 1) {
const root = union.find(index);
const component = grouped.get(root) ?? [];
component.push(vertices[index]);
grouped.set(root, component);
}
const components = [...grouped.values()];
components.sort((left, right) => right.length - left.length);
const main = components[0] ?? [];
const min = [0, 1, 2].map((axis) => Math.min(...main.map((vertex) => vertex[axis])));
const max = [0, 1, 2].map((axis) => Math.max(...main.map((vertex) => vertex[axis])));
const center = min.map((value, axis) => (value + max[axis]) / 2);
const axis = max[0] - min[0] >= max[2] - min[2] ? 0 : 2;
const sorted = [...main].sort((left, right) => left[axis] - right[axis]);
const inset = Math.max(1, Math.floor(sorted.length * 0.08));
const teamSpawns = [sorted[inset] ?? sorted[0], sorted[sorted.length - inset - 1] ?? sorted.at(-1)];
console.log(JSON.stringify({
id: stage.id,
componentCount: components.length,
mainVertices: main.length,
nextVertices: components[1]?.length ?? 0,
mainBounds: { min: round(min), max: round(max) },
center: round(center),
suggestedTeamSpawns: teamSpawns.map(round),
topComponents: components.slice(0, detail ? components.length : 8).map((component) => ({
vertices: component.length,
min: round([0, 1, 2].map((componentAxis) => Math.min(...component.map((vertex) => vertex[componentAxis])))),
max: round([0, 1, 2].map((componentAxis) => Math.max(...component.map((vertex) => vertex[componentAxis])))),
})),
}));
}