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,219 @@
|
||||
import { createHash } from "node:crypto";
|
||||
|
||||
const NULL_LINK_VALUES = new Set([-1, 0xffffffff]);
|
||||
|
||||
function canonicalize(value) {
|
||||
if (Array.isArray(value)) return value.map(canonicalize);
|
||||
if (!value || typeof value !== "object") return value;
|
||||
return Object.fromEntries(
|
||||
Object.keys(value).sort().map((key) => [key, canonicalize(value[key])]),
|
||||
);
|
||||
}
|
||||
|
||||
export function stableSha256(value) {
|
||||
const bytes = typeof value === "string" ? value : JSON.stringify(canonicalize(value));
|
||||
return createHash("sha256").update(bytes).digest("hex");
|
||||
}
|
||||
|
||||
export function buildDetourGeometryRanges(polygonCount, trianglePolygonIds) {
|
||||
const starts = new Int32Array(polygonCount).fill(-1);
|
||||
const counts = new Uint32Array(polygonCount);
|
||||
let previousPolygonId = -1;
|
||||
for (let triangleIndex = 0; triangleIndex < trianglePolygonIds.length; triangleIndex += 1) {
|
||||
const polygonId = trianglePolygonIds[triangleIndex];
|
||||
if (!Number.isInteger(polygonId) || polygonId < 0 || polygonId >= polygonCount) {
|
||||
throw new Error(`Exported triangle ${triangleIndex} references invalid Detour polygon ${polygonId}.`);
|
||||
}
|
||||
if (polygonId < previousPolygonId) {
|
||||
throw new Error("Exported Detour triangles are not in stable polygon order.");
|
||||
}
|
||||
previousPolygonId = polygonId;
|
||||
if (counts[polygonId] === 0) starts[polygonId] = triangleIndex;
|
||||
counts[polygonId] += 1;
|
||||
}
|
||||
return Array.from({ length: polygonCount }, (_, polygonId) => [
|
||||
starts[polygonId],
|
||||
counts[polygonId],
|
||||
]);
|
||||
}
|
||||
|
||||
function findComponents(nodeCount, links) {
|
||||
const parents = new Int32Array(nodeCount);
|
||||
const sizes = new Int32Array(nodeCount);
|
||||
for (let id = 0; id < nodeCount; id += 1) {
|
||||
parents[id] = id;
|
||||
sizes[id] = 1;
|
||||
}
|
||||
const rootOf = (node) => {
|
||||
let root = node;
|
||||
while (parents[root] !== root) root = parents[root];
|
||||
while (parents[node] !== node) {
|
||||
const next = parents[node];
|
||||
parents[node] = root;
|
||||
node = next;
|
||||
}
|
||||
return root;
|
||||
};
|
||||
|
||||
for (const [from, to] of links) {
|
||||
const left = rootOf(from);
|
||||
const right = rootOf(to);
|
||||
if (left === right) continue;
|
||||
if (sizes[left] < sizes[right]) {
|
||||
parents[left] = right;
|
||||
sizes[right] += sizes[left];
|
||||
} else {
|
||||
parents[right] = left;
|
||||
sizes[left] += sizes[right];
|
||||
}
|
||||
}
|
||||
|
||||
const components = new Map();
|
||||
for (let id = 0; id < nodeCount; id += 1) {
|
||||
const root = rootOf(id);
|
||||
components.set(root, (components.get(root) ?? 0) + 1);
|
||||
}
|
||||
const sizesByDescending = [...components.values()].sort((left, right) => right - left);
|
||||
return {
|
||||
count: sizesByDescending.length,
|
||||
largestPolygonCount: sizesByDescending[0] ?? 0,
|
||||
polygonCounts: sizesByDescending,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Copies the source Detour surface-polygon order and directed link chain into
|
||||
* compact, stable-ID records. Triangle ranges follow NavMeshHelper's emitted
|
||||
* tile/polygon/detail-triangle iteration order; links are only actual Detour
|
||||
* links and never inferred from near-coincident geometry.
|
||||
*/
|
||||
export function extractDetourTopology(navMesh, {
|
||||
sourceCollisionSha256,
|
||||
settingsSha256,
|
||||
} = {}) {
|
||||
if (!navMesh || typeof navMesh.getMaxTiles !== "function") {
|
||||
throw new Error("Detour topology requires a populated NavMesh.");
|
||||
}
|
||||
|
||||
const polygons = [];
|
||||
const polygonIdByReference = new Map();
|
||||
let triangleCount = 0;
|
||||
let polygonFanTriangleCount = 0;
|
||||
let tileCount = 0;
|
||||
let offMeshPolygonCount = 0;
|
||||
|
||||
for (let tileIndex = 0; tileIndex < navMesh.getMaxTiles(); tileIndex += 1) {
|
||||
const tile = navMesh.getTile(tileIndex);
|
||||
const header = tile.header();
|
||||
if (!header || header.polyCount() === 0) continue;
|
||||
tileCount += 1;
|
||||
const polyRefBase = navMesh.getPolyRefBase(tile);
|
||||
|
||||
for (let polygonIndex = 0; polygonIndex < header.polyCount(); polygonIndex += 1) {
|
||||
const polygon = tile.polys(polygonIndex);
|
||||
if (polygon.getType() !== 0) {
|
||||
offMeshPolygonCount += 1;
|
||||
continue;
|
||||
}
|
||||
const detail = tile.detailMeshes(polygonIndex);
|
||||
const triangleCountForPolygon = detail.triCount();
|
||||
const polygonVertexCount = polygon.vertCount();
|
||||
const id = polygons.length;
|
||||
polygons.push([
|
||||
id,
|
||||
tileIndex,
|
||||
header.x(),
|
||||
header.y(),
|
||||
header.layer(),
|
||||
polygonIndex,
|
||||
triangleCount,
|
||||
triangleCountForPolygon,
|
||||
polygonVertexCount,
|
||||
]);
|
||||
triangleCount += triangleCountForPolygon;
|
||||
polygonFanTriangleCount += Math.max(0, polygonVertexCount - 2);
|
||||
polygonIdByReference.set(polyRefBase + polygonIndex, id);
|
||||
}
|
||||
}
|
||||
|
||||
const links = [];
|
||||
let unrepresentedLinkCount = 0;
|
||||
for (const polygonRecord of polygons) {
|
||||
const [fromId, tileIndex, , , , polygonIndex] = polygonRecord;
|
||||
const tile = navMesh.getTile(tileIndex);
|
||||
const polygon = tile.polys(polygonIndex);
|
||||
const header = tile.header();
|
||||
const maxLinkCount = header.maxLinkCount();
|
||||
let linkIndex = polygon.firstLink();
|
||||
let traversed = 0;
|
||||
while (!NULL_LINK_VALUES.has(linkIndex)) {
|
||||
if (!Number.isInteger(linkIndex) || linkIndex < 0 || linkIndex >= maxLinkCount) {
|
||||
throw new Error(
|
||||
`Detour polygon ${tileIndex}:${polygonIndex} has invalid link index ${linkIndex}.`,
|
||||
);
|
||||
}
|
||||
if (traversed >= maxLinkCount) {
|
||||
throw new Error(`Detour polygon ${tileIndex}:${polygonIndex} contains a link cycle.`);
|
||||
}
|
||||
traversed += 1;
|
||||
const link = tile.links(linkIndex);
|
||||
const toId = polygonIdByReference.get(link.ref());
|
||||
if (toId === undefined) {
|
||||
unrepresentedLinkCount += 1;
|
||||
} else {
|
||||
const edgeIndex = link.edge();
|
||||
const polygonVertexCount = polygon.vertCount();
|
||||
if (edgeIndex < 0 || edgeIndex >= polygonVertexCount) {
|
||||
throw new Error(
|
||||
`Detour polygon ${tileIndex}:${polygonIndex} has invalid linked edge ${edgeIndex}.`,
|
||||
);
|
||||
}
|
||||
const edgeStartVertex = polygon.verts(edgeIndex);
|
||||
const edgeEndVertex = polygon.verts((edgeIndex + 1) % polygonVertexCount);
|
||||
const edgeStartOffset = edgeStartVertex * 3;
|
||||
const edgeEndOffset = edgeEndVertex * 3;
|
||||
links.push([
|
||||
fromId,
|
||||
toId,
|
||||
edgeIndex,
|
||||
link.side(),
|
||||
link.bmin(),
|
||||
link.bmax(),
|
||||
edgeStartVertex,
|
||||
edgeEndVertex,
|
||||
tile.verts(edgeStartOffset),
|
||||
tile.verts(edgeStartOffset + 1),
|
||||
tile.verts(edgeStartOffset + 2),
|
||||
tile.verts(edgeEndOffset),
|
||||
tile.verts(edgeEndOffset + 1),
|
||||
tile.verts(edgeEndOffset + 2),
|
||||
]);
|
||||
}
|
||||
linkIndex = link.next();
|
||||
}
|
||||
}
|
||||
|
||||
const weakComponents = findComponents(polygons.length, links.map(([from, to]) => [from, to]));
|
||||
return {
|
||||
schemaVersion: 1,
|
||||
kind: "recast-detour-surface-links",
|
||||
coordinateSpace: "GLB navigation POSITION coordinates (meters, Three.js Y-up)",
|
||||
encoding: {
|
||||
polygons: "[id,tileIndex,tileX,tileY,tileLayer,tilePolygonIndex,firstGeometryTriangle,geometryTriangleCount,vertexCount]",
|
||||
links: "[fromPolygonId,toPolygonId,fromEdge,side,bmin,bmax,fromEdgeStartVertex,fromEdgeEndVertex,startX,startY,startZ,endX,endY,endZ] (directed Detour link records)",
|
||||
},
|
||||
sourceCollisionSha256: sourceCollisionSha256 ?? null,
|
||||
settingsSha256: settingsSha256 ?? null,
|
||||
tileCount,
|
||||
polygonCount: polygons.length,
|
||||
detailTriangleCount: triangleCount,
|
||||
polygonFanTriangleCount,
|
||||
geometryTriangleCount: null,
|
||||
directedLinkCount: links.length,
|
||||
unrepresentedLinkCount,
|
||||
offMeshPolygonCount,
|
||||
weakComponents,
|
||||
polygons,
|
||||
links,
|
||||
};
|
||||
}
|
||||
Reference in new issue
Block a user