import test from "node:test"; import assert from "node:assert/strict"; import { extrude, transformed } from "../engine/geometry.ts"; import { validateGeometry } from "../engine/schema.ts"; function volume(g: any) { let sum = 0; const p = g.positions, ix = g.indices; for (let i = 0; i < ix.length; i += 3) { const a = ix[i] * 3, b = ix[i + 1] * 3, c = ix[i + 2] * 3; sum += (p[a] * (p[b + 1] * p[c + 2] - p[b + 2] * p[c + 1]) + p[a + 1] * (p[b + 2] * p[c] - p[b] * p[c + 2]) + p[a + 2] * (p[b] * p[c + 1] - p[b + 1] * p[c])) / 6; } return sum; } const near = (actual: number, expected: number) => assert.ok( Math.abs(actual - expected) < 1e-5, "expected " + actual + " to equal " + expected, ); test("a rectangular extrusion produces a closed solid with correct volume", () => { const g = extrude( [ [0, 0], [3, 0], [3, 2], [0, 2], ], 2.5, ); validateGeometry(g); near(Math.abs(volume(g)), 15); }); test("concave profile preserves missing corner for both winding orders", () => { const profile: [[number, number], ...[number, number][]] = [ [0, 0], [3, 0], [3, 1], [1, 1], [1, 3], [0, 3], ]; for (const points of [profile, [...profile].reverse()]) { const g = extrude(points, 2); validateGeometry(g); near(Math.abs(volume(g)), 10); const p = g.positions; for (let i = 0; i < g.indices.length; i += 3) { const ids = g.indices.slice(i, i + 3).map((v: number) => v * 3); const ys = ids.map((v: number) => p[v + 1]); if (Math.max(...ys) - Math.min(...ys) > 1e-6) continue; const x = ids.reduce((s: number, j: number) => s + p[j], 0) / 3, z = ids.reduce((s: number, j: number) => s + p[j + 2], 0) / 3; assert.ok( !(x > 1 + 1e-6 && z > 1 + 1e-6), "a cap triangle fills the concave cutout", ); } } }); test("mirroring geometry preserves outward triangle orientation", () => { const g = extrude( [ [0, 0], [2, 0], [2, 1], [0, 1], ], 1, ); const mirrored = transformed(g, [7, 2, -3], [-2, 3, 4]); validateGeometry(mirrored); near(volume(mirrored), volume(g) * 24); }); test("mesh validator rejects corrupt or non-finite input before runtime upload", () => { const good = { positions: [0, 0, 0, 1, 0, 0, 0, 1, 0], indices: [0, 1, 2] }; validateGeometry(good); for (const bad of [ { ...good, positions: [0, 0, 0, 1, 0, 0, 0, 1, Infinity] }, { ...good, positions: [0, 0, 0, 1] }, { ...good, indices: [0, 1, 3] }, { ...good, indices: [0, -1, 2] }, { ...good, indices: [0, 0.5, 2] }, { ...good, indices: [0, 1] }, { ...good, normals: [0, 1, 0] }, { ...good, uvs: [0, 0] }, ]) assert.throws(() => validateGeometry(bad as any)); }); test("extrusion rejects degenerate profiles and nonpositive height", () => { for (const [profile, depth] of [ [ [ [0, 0], [1, 0], ], 1, ], [ [ [0, 0], [1, 0], [2, 0], ], 1, ], [ [ [0, 0], [1, 0], [0, 1], ], 0, ], [ [ [0, 0], [1, 0], [0, 1], ], -1, ], [ [ [0, 0], [1, 0], [0, NaN], ], 1, ], ] as any[]) assert.throws(() => extrude(profile, depth)); });