Files
healer-man/scripts/dungeon-pipeline/recast-monotone-fallback.mjs
T
2026-08-14 15:56:39 -04:00

89 lines
3.3 KiB
JavaScript

import { createHash } from "node:crypto";
import { mkdir, readFile, writeFile } from "node:fs/promises";
import path from "node:path";
import { fileURLToPath, pathToFileURL } from "node:url";
import { getPositionsAndIndices } from "@recast-navigation/three";
const EXPECTED_GENERATOR_VERSION = "0.43.1";
const TILED_WATERSHED_CALL =
"buildRegions(buildContext, compactHeightfield, tileConfig.borderSize, tileConfig.minRegionArea, tileConfig.mergeRegionArea)";
const TILED_MONOTONE_CALL =
"buildRegionsMonotone(buildContext, compactHeightfield, tileConfig.borderSize, tileConfig.minRegionArea, tileConfig.mergeRegionArea)";
let generatorPromise;
function sha256(value) {
return createHash("sha256").update(value).digest("hex");
}
async function loadPatchedGenerator() {
const packageFile = fileURLToPath(import.meta.resolve("@recast-navigation/generators/package.json"));
const packageJson = JSON.parse(await readFile(packageFile, "utf8"));
if (packageJson.version !== EXPECTED_GENERATOR_VERSION) {
throw new Error(
`Monotone Recast fallback expects @recast-navigation/generators ${EXPECTED_GENERATOR_VERSION}, `
+ `but found ${packageJson.version}.`,
);
}
const entryFile = fileURLToPath(import.meta.resolve("@recast-navigation/generators"));
const upstream = await readFile(entryFile, "utf8");
if (!upstream.includes("buildRegions, allocContourSet")) {
throw new Error("Monotone Recast fallback could not locate the pinned generator import.");
}
const watershedCallCount = upstream.split(TILED_WATERSHED_CALL).length - 1;
if (watershedCallCount !== 1) {
throw new Error(
`Monotone Recast fallback expected one tiled watershed call, found ${watershedCallCount}.`,
);
}
const patched = upstream
.replace("buildRegions, allocContourSet", "buildRegions, buildRegionsMonotone, allocContourSet")
.replace(TILED_WATERSHED_CALL, TILED_MONOTONE_CALL)
.replace(/\n\/\/# sourceMappingURL=.*$/u, "");
const sourceChecksum = sha256(upstream);
const generatedDirectory = path.resolve(
"dungeon-pipeline/work/.generated-recast",
);
const generatedFile = path.join(
generatedDirectory,
`generators-${EXPECTED_GENERATOR_VERSION}-monotone-${sourceChecksum.slice(0, 12)}.mjs`,
);
await mkdir(generatedDirectory, { recursive: true });
await writeFile(generatedFile, patched, "utf8");
const module = await import(`${pathToFileURL(generatedFile).href}?source=${sourceChecksum}`);
return {
generateTiledNavMesh: module.generateTiledNavMesh,
provenance: {
package: `@recast-navigation/generators@${EXPECTED_GENERATOR_VERSION}`,
upstreamEntry: path.relative(process.cwd(), entryFile).replace(/\\/g, "/"),
upstreamChecksum: sourceChecksum,
operation: "replace tiled buildRegions watershed call with buildRegionsMonotone",
},
};
}
async function monotoneGenerator() {
generatorPromise ??= loadPatchedGenerator();
return generatorPromise;
}
export async function threeToMonotoneTiledNavMesh(
meshes,
navMeshGeneratorConfig = {},
keepIntermediates = false,
) {
const [positions, indices] = getPositionsAndIndices(meshes);
const generator = await monotoneGenerator();
return {
result: generator.generateTiledNavMesh(
positions,
indices,
navMeshGeneratorConfig,
keepIntermediates,
),
provenance: generator.provenance,
};
}