forked from phenom/healer-man
60 lines
2.9 KiB
TypeScript
60 lines
2.9 KiB
TypeScript
import { BufferGeometry, Float32BufferAttribute } from "three";
|
|
import { describe, expect, it } from "vitest";
|
|
import { bridgeDungeonStairNavigation, smoothDungeonStairGeometry } from "./dungeonTraversalRamps";
|
|
|
|
// The offline Node 24 pipeline uses native .ts specifiers; load it at runtime,
|
|
// without making those tool-only modules part of the browser TypeScript build.
|
|
const pipelineModule = "../../scripts/dungeon-pipeline/wow-stair-geometry.ts";
|
|
const { detectDungeonStairRamps, dungeonStairNavigationBridges, dungeonStairRisers } = await import(pipelineModule);
|
|
|
|
function staircase(steps: number, rotated = false) {
|
|
const vertices: number[] = [];
|
|
for (let step = 0; step < steps; step += 1) {
|
|
const x = step;
|
|
const y = step * 0.5;
|
|
const points = [[x, y, -2], [x, y + 0.5, -2], [x, y + 0.5, 2], [x, y, -2], [x, y + 0.5, 2], [x, y, 2]];
|
|
for (const point of points) vertices.push(...(rotated ? [-point[2], point[1], point[0]] : point));
|
|
}
|
|
const geometry = new BufferGeometry();
|
|
geometry.setAttribute("position", new Float32BufferAttribute(vertices, 3));
|
|
return geometry;
|
|
}
|
|
|
|
describe("WoW shipped stair detection", () => {
|
|
it.each([false, true])("detects repeated risers, including rotated stairs (%s)", (rotated) => {
|
|
const geometry = staircase(7, rotated);
|
|
const ramps = detectDungeonStairRamps(dungeonStairRisers(geometry));
|
|
expect(ramps).toHaveLength(1);
|
|
expect(ramps[0].width).toBeCloseTo(4.02);
|
|
expect(ramps[0].end[1] - ramps[0].start[1]).toBeCloseTo(3.5);
|
|
geometry.dispose();
|
|
});
|
|
|
|
it("does not interpret a single ledge or tall wall as a staircase", () => {
|
|
const ledge = staircase(1);
|
|
expect(detectDungeonStairRamps(dungeonStairRisers(ledge))).toEqual([]);
|
|
const wall = staircase(4);
|
|
const p = wall.getAttribute("position");
|
|
for (let index = 0; index < p.count; index += 1) p.setY(index, p.getY(index) * 10);
|
|
expect(detectDungeonStairRamps(dungeonStairRisers(wall))).toEqual([]);
|
|
ledge.dispose(); wall.dispose();
|
|
});
|
|
|
|
it("fills separate stair portals using existing source vertices, without bridging an unrelated floor", () => {
|
|
const geometry = new BufferGeometry();
|
|
geometry.setAttribute("position", new Float32BufferAttribute([
|
|
-1, 0, 0, 1, 0, 0, -1, 0.5, 2, 1, 0.5, 2,
|
|
-1, 0.75, 3, 1, 0.75, 3, -1, 1.5, 6, 1, 1.5, 6,
|
|
-1, 8, 3, 1, 8, 3, -1, 8, 4,
|
|
], 3));
|
|
geometry.setIndex([0, 1, 3, 0, 3, 2, 4, 5, 7, 4, 7, 6, 8, 9, 10]);
|
|
const ramps = dungeonStairNavigationBridges(geometry, [{ id: "flight", start: [0, 0, -0.01], end: [0, 1.5, 6.01], width: 3 }]);
|
|
expect(ramps[0].navigationBridges).toHaveLength(1);
|
|
smoothDungeonStairGeometry(geometry, ramps, 0.05);
|
|
expect(() => bridgeDungeonStairNavigation(geometry, ramps, 0.05)).not.toThrow();
|
|
expect(geometry.index!.count).toBe(21);
|
|
expect(geometry.getAttribute("position").getY(8)).toBe(8);
|
|
geometry.dispose();
|
|
});
|
|
});
|