forked from phenom/healer-man
Update Healer Man server
This commit is contained in:
1 parent
6114a2295f
commit
69809f7228
593 files changed
+903643
-40774
No files matched your search
@@ -0,0 +1,190 @@
|
||||
import { createHash } from "node:crypto";
|
||||
import { readFile, writeFile } from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { NodeIO } from "@gltf-transform/core";
|
||||
import { ALL_EXTENSIONS } from "@gltf-transform/extensions";
|
||||
import { Matrix4, Vector3 } from "three";
|
||||
import { sourceAnchorSupportGeometry } from "./lib/source-anchor-support.mjs";
|
||||
|
||||
const projectRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../..");
|
||||
const workRoot = path.join(projectRoot, "runewaker-export-work");
|
||||
const slug = process.argv[2] ?? "aeternal-circle";
|
||||
const stageDungeon = path.join(workRoot, "source-anchor-support-correction-staged-v2", slug);
|
||||
const supportName = "runewaker_source_anchor_support";
|
||||
const io = new NodeIO().registerExtensions(ALL_EXTENSIONS);
|
||||
const sha256 = (bytes) => createHash("sha256").update(bytes).digest("hex");
|
||||
const relative = (file) => path.relative(projectRoot, file).replaceAll("\\", "/");
|
||||
|
||||
function include(bounds, point) {
|
||||
for (let axis = 0; axis < 3; axis += 1) {
|
||||
bounds.minimum[axis] = Math.min(bounds.minimum[axis], point[axis]);
|
||||
bounds.maximum[axis] = Math.max(bounds.maximum[axis], point[axis]);
|
||||
}
|
||||
}
|
||||
|
||||
function emptyBounds() {
|
||||
return { minimum: [Infinity, Infinity, Infinity], maximum: [-Infinity, -Infinity, -Infinity] };
|
||||
}
|
||||
|
||||
const recipe = JSON.parse(await readFile(path.join(projectRoot, "scripts/runewaker-pipeline/recipes", `${slug}.json`), "utf8"));
|
||||
const stageReport = JSON.parse(await readFile(path.join(stageDungeon, "staging-report.json"), "utf8"));
|
||||
const conversion = JSON.parse(await readFile(path.join(workRoot, slug, "converted/conversion-report.json"), "utf8"));
|
||||
const sourceReportPath = path.join(stageDungeon, "native-primary-collision-report.json");
|
||||
const sourceReport = JSON.parse(await readFile(sourceReportPath, "utf8"));
|
||||
const meters = Number(recipe.coordinateSystem.metersPerSourceUnit);
|
||||
const sourceBounds = sourceReport.nativeCollisionBounds;
|
||||
const expectedNativeBounds = {
|
||||
minimum: [
|
||||
Number(sourceBounds.min[0]) * meters,
|
||||
Number(sourceBounds.min[1]) * meters,
|
||||
-Number(sourceBounds.max[2]) * meters,
|
||||
],
|
||||
maximum: [
|
||||
Number(sourceBounds.max[0]) * meters,
|
||||
Number(sourceBounds.max[1]) * meters,
|
||||
-Number(sourceBounds.min[2]) * meters,
|
||||
],
|
||||
};
|
||||
const convertedNativeBounds = emptyBounds();
|
||||
let convertedNativeTriangles = 0;
|
||||
for (const record of conversion.collision ?? []) {
|
||||
const stagedRecord = stageReport.staged.collision.find((item) => item.source === record.file);
|
||||
const file = path.join(stageDungeon, stagedRecord?.staged ?? record.file);
|
||||
const document = await io.read(file);
|
||||
for (const node of document.getRoot().listNodes()) {
|
||||
if (!node.getMesh() || node.getExtras()?.sourceChunk === supportName) continue;
|
||||
const matrix = new Matrix4().fromArray(Array.from(node.getWorldMatrix()));
|
||||
for (const primitive of node.getMesh().listPrimitives()) {
|
||||
const position = primitive.getAttribute("POSITION");
|
||||
const indices = primitive.getIndices();
|
||||
convertedNativeTriangles += (indices?.getCount() ?? position?.getCount() ?? 0) / 3;
|
||||
if (!position) continue;
|
||||
const values = position.getArray();
|
||||
for (let offset = 0; offset < values.length; offset += 3) {
|
||||
include(convertedNativeBounds, new Vector3().fromArray(values, offset).applyMatrix4(matrix).toArray());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const placement = stageReport.source.primaryPlacement;
|
||||
const sourcePoints = recipe.source.sourceAnchorSupport.points.map((point) => point.map(Number));
|
||||
const omitted = new Set(stageReport.source.omittedNativeGroundedPointIndices);
|
||||
const nativeXZ = {
|
||||
minX: convertedNativeBounds.minimum[0], maxX: convertedNativeBounds.maximum[0],
|
||||
minZ: convertedNativeBounds.minimum[2], maxZ: convertedNativeBounds.maximum[2],
|
||||
};
|
||||
const padRows = [];
|
||||
const supportBounds = emptyBounds();
|
||||
const coreSupportBounds = emptyBounds();
|
||||
for (const [sourcePointIndex, sourcePoint] of sourcePoints.entries()) {
|
||||
if (omitted.has(sourcePointIndex)) continue;
|
||||
const geometry = sourceAnchorSupportGeometry([sourcePoint], placement, {
|
||||
halfExtent: Number(stageReport.source.halfExtentSourceUnits),
|
||||
thickness: Number(stageReport.source.thicknessSourceUnits),
|
||||
metersPerSourceUnit: meters,
|
||||
});
|
||||
const top = [];
|
||||
for (let vertex = 0; vertex < 4; vertex += 1) {
|
||||
top.push(geometry.positions.slice(vertex * 3, vertex * 3 + 3));
|
||||
}
|
||||
const box = emptyBounds();
|
||||
for (const point of geometry.positions.reduce((all, value, index) => {
|
||||
if (index % 3 === 0) all.push(geometry.positions.slice(index, index + 3));
|
||||
return all;
|
||||
}, [])) {
|
||||
include(box, point);
|
||||
include(supportBounds, point);
|
||||
}
|
||||
const overlapsNativeXZ = box.maximum[0] >= nativeXZ.minX && box.minimum[0] <= nativeXZ.maxX
|
||||
&& box.maximum[2] >= nativeXZ.minZ && box.minimum[2] <= nativeXZ.maxZ;
|
||||
const yGapM = box.minimum[1] > convertedNativeBounds.maximum[1]
|
||||
? box.minimum[1] - convertedNativeBounds.maximum[1]
|
||||
: box.maximum[1] < convertedNativeBounds.minimum[1]
|
||||
? convertedNativeBounds.minimum[1] - box.maximum[1]
|
||||
: 0;
|
||||
if (overlapsNativeXZ) {
|
||||
for (const point of geometry.positions.reduce((all, value, index) => {
|
||||
if (index % 3 === 0) all.push(geometry.positions.slice(index, index + 3));
|
||||
return all;
|
||||
}, [])) include(coreSupportBounds, point);
|
||||
}
|
||||
padRows.push({ sourcePointIndex, sourcePoint, center: top.reduce((center, point) => center.map((value, axis) => value + point[axis] / top.length), [0, 0, 0]),
|
||||
bounds: box, overlapsNativeXZ, verticalGapToNativeBoundsM: yGapM });
|
||||
}
|
||||
|
||||
const coreBounds = emptyBounds();
|
||||
for (const point of [
|
||||
[convertedNativeBounds.minimum[0], convertedNativeBounds.minimum[1], convertedNativeBounds.minimum[2]],
|
||||
[convertedNativeBounds.maximum[0], convertedNativeBounds.maximum[1], convertedNativeBounds.maximum[2]],
|
||||
[coreSupportBounds.minimum[0], coreSupportBounds.minimum[1], coreSupportBounds.minimum[2]],
|
||||
[coreSupportBounds.maximum[0], coreSupportBounds.maximum[1], coreSupportBounds.maximum[2]],
|
||||
]) {
|
||||
if (point.every(Number.isFinite)) include(coreBounds, point);
|
||||
}
|
||||
const gridWidth = Math.ceil((coreBounds.maximum[0] - coreBounds.minimum[0]) / Number(recipe.navigation.cellSize));
|
||||
const gridDepth = Math.ceil((coreBounds.maximum[2] - coreBounds.minimum[2]) / Number(recipe.navigation.cellSize));
|
||||
const overlapRows = padRows.filter((row) => row.overlapsNativeXZ);
|
||||
const remoteRows = padRows.filter((row) => !row.overlapsNativeXZ);
|
||||
const report = {
|
||||
schemaVersion: 1,
|
||||
slug,
|
||||
source: {
|
||||
primaryModel: recipe.source.model,
|
||||
primaryModelSha256: stageReport.source.sourceModelSha256,
|
||||
nativeCollisionReport: relative(sourceReportPath),
|
||||
nativeCollisionReportSha256: sha256(await readFile(sourceReportPath)),
|
||||
nativeCollisionTriangles: sourceReport.nativeCollisionTriangleCount,
|
||||
visualFallbackTriangles: sourceReport.renderMeshFallbackTriangleCount,
|
||||
nativeTriangleFlags: sourceReport.nativeTriangleFlagCounts,
|
||||
pathCollisionExclusionMask: sourceReport.pathCollisionPolicy.exclusionMask,
|
||||
convertedCollisionChunks: conversion.collision.map((record) => record.file),
|
||||
},
|
||||
boundsProof: {
|
||||
convertedCollisionExcludingSupport: convertedNativeBounds,
|
||||
expectedNativeCollisionBoundsAfterUnitsAndZBridge: expectedNativeBounds,
|
||||
comparisonMaximumErrorM: convertedNativeBounds.minimum.map((value, axis) => Math.max(
|
||||
Math.abs(value - expectedNativeBounds.minimum[axis]),
|
||||
Math.abs(convertedNativeBounds.maximum[axis] - expectedNativeBounds.maximum[axis]),
|
||||
)).reduce((max, value) => Math.max(max, value), 0),
|
||||
convertedNativeCollisionTriangles: convertedNativeTriangles,
|
||||
sourceQueryNativeTriangles: sourceReport.nativeCollisionTriangleCount,
|
||||
exactBoundsMatchToleranceM: 0.01,
|
||||
nativeCoreBounds: convertedNativeBounds,
|
||||
supportUnionBounds: supportBounds,
|
||||
nativeCorePlusXZOverlappingSupportBounds: coreBounds,
|
||||
},
|
||||
selection: {
|
||||
sourceAnchorPointCount: sourcePoints.length,
|
||||
omittedNativeGroundedPointIndices: [...omitted],
|
||||
retainedSupportCount: padRows.length,
|
||||
coreXzOverlapCount: overlapRows.length,
|
||||
isolatedRemoteCount: remoteRows.length,
|
||||
coreSupportSourcePointIndices: overlapRows.map((row) => row.sourcePointIndex),
|
||||
remoteSupportSourcePointIndices: remoteRows.map((row) => row.sourcePointIndex),
|
||||
remoteSupportClearanceM: Number(recipe.navigation.agentRadius ?? 0.36),
|
||||
remoteSupportTopInsetHalfExtentM: Number(stageReport.source.halfExtentSourceUnits) * meters - Number(recipe.navigation.agentRadius ?? 0.36),
|
||||
},
|
||||
predictedCoreRecastGrid: {
|
||||
cellSize: Number(recipe.navigation.cellSize),
|
||||
widthCells: gridWidth,
|
||||
depthCells: gridDepth,
|
||||
cellCount: gridWidth * gridDepth,
|
||||
recipeTileSize: recipe.navigation.tileSize ?? null,
|
||||
},
|
||||
padRows,
|
||||
conclusion: "This report only classifies source-grounded collision and support selection. It does not merge/bake any nav surfaces. Before staging, verify the bounds comparison and keep remote supports as separate explicit components with agent-radius inset; no route connectivity is inferred.",
|
||||
};
|
||||
const output = path.join(stageDungeon, "native-core-support-overlap.json");
|
||||
await writeFile(output, `${JSON.stringify(report, null, 2)}\n`, "utf8");
|
||||
console.log(JSON.stringify({
|
||||
report: relative(output),
|
||||
nativeCollisionTriangles: sourceReport.nativeCollisionTriangleCount,
|
||||
convertedNativeTriangles,
|
||||
boundsErrorM: report.boundsProof.comparisonMaximumErrorM,
|
||||
coreXzOverlapCount: overlapRows.length,
|
||||
isolatedRemoteCount: remoteRows.length,
|
||||
coreBounds,
|
||||
grid: report.predictedCoreRecastGrid,
|
||||
}, null, 2));
|
||||
Reference in new issue
Block a user