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,182 @@
|
||||
import { createHash } from "node:crypto";
|
||||
import { mkdir, readFile } from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import { NodeIO } from "@gltf-transform/core";
|
||||
import { ALL_EXTENSIONS } from "@gltf-transform/extensions";
|
||||
import { simplifyPrimitive, weldPrimitive } from "@gltf-transform/functions";
|
||||
import { MeshoptDecoder, MeshoptSimplifier } from "meshoptimizer";
|
||||
import { nodeInstanceTransforms } from "../runewaker-pipeline/audit-environment-quality.mjs";
|
||||
import { loadRecipe, projectRoot, workRoot } from "./lib.mjs";
|
||||
|
||||
const PINNED_SHADOWFANG_SOURCE_SHA256 = "d78aaa98b1367cc0dda4b7fce81f143b0753a96917cb2aff5f530fb9ef513c74";
|
||||
|
||||
function sha256(bytes) {
|
||||
return createHash("sha256").update(bytes).digest("hex");
|
||||
}
|
||||
|
||||
function hashAccessor(accessor) {
|
||||
const array = accessor?.getArray();
|
||||
if (!array) return null;
|
||||
return sha256(Buffer.from(array.buffer, array.byteOffset, array.byteLength));
|
||||
}
|
||||
|
||||
function primitiveTriangles(primitive) {
|
||||
const count = primitive.getIndices()?.getCount() ?? primitive.getAttribute("POSITION")?.getCount() ?? 0;
|
||||
if (primitive.getMode() !== 4 || count % 3 !== 0) return 0;
|
||||
return count / 3;
|
||||
}
|
||||
|
||||
function sourceHashSnapshot(document) {
|
||||
const hashes = new Map();
|
||||
for (const [meshIndex, mesh] of document.getRoot().listMeshes().entries()) {
|
||||
for (const [primitiveIndex, primitive] of mesh.listPrimitives().entries()) {
|
||||
if (!primitive.getMaterial()?.getBaseColorTexture()) continue;
|
||||
const key = `${meshIndex}:${primitiveIndex}:${primitive.getMaterial()?.getName() ?? ""}`;
|
||||
const attributes = Object.fromEntries(primitive.listSemantics()
|
||||
.sort()
|
||||
.map((semantic) => [semantic, hashAccessor(primitive.getAttribute(semantic))]));
|
||||
hashes.set(key, { attributes, indices: hashAccessor(primitive.getIndices()) });
|
||||
}
|
||||
}
|
||||
return hashes;
|
||||
}
|
||||
|
||||
function textureHashSnapshot(document) {
|
||||
return document.getRoot().listTextures().map((texture, index) => ({
|
||||
index,
|
||||
name: texture.getName() ?? "",
|
||||
hash: texture.getImage() ? sha256(texture.getImage()) : null,
|
||||
}));
|
||||
}
|
||||
|
||||
function meshInstanceCounts(document) {
|
||||
const counts = new Map(document.getRoot().listMeshes().map((mesh) => [mesh, 0]));
|
||||
const visit = (node) => {
|
||||
const mesh = node.getMesh();
|
||||
if (mesh) counts.set(mesh, (counts.get(mesh) ?? 0) + nodeInstanceTransforms(node).length);
|
||||
for (const child of node.listChildren()) visit(child);
|
||||
};
|
||||
for (const scene of document.getRoot().listScenes()) {
|
||||
for (const node of scene.listChildren()) visit(node);
|
||||
}
|
||||
return counts;
|
||||
}
|
||||
|
||||
export function eligibleFallbackMaterial(material, profile) {
|
||||
if (!profile?.materialNamePattern) return null;
|
||||
const match = material?.getName()?.match(new RegExp(profile.materialNamePattern, "u"));
|
||||
if (!match || material.getBaseColorTexture()) return null;
|
||||
const category = `ADT_${match[1]}`;
|
||||
return profile.targetExpandedTriangles?.[category] ? category : null;
|
||||
}
|
||||
|
||||
export function simplifyEligibleFallbackPrimitive(primitive, profile) {
|
||||
const category = eligibleFallbackMaterial(primitive.getMaterial(), profile);
|
||||
if (!category || primitive.getMode() !== 4 || primitiveTriangles(primitive) === 0) return false;
|
||||
const target = profile.targetExpandedTriangles[category];
|
||||
const source = profile.sourceExpandedTriangles?.[category];
|
||||
if (!Number.isFinite(source) || source <= 0 || target <= 0 || target > source) {
|
||||
throw new Error(`${category} needs a valid source and target triangle count in the recipe.`);
|
||||
}
|
||||
weldPrimitive(primitive, { overwrite: false });
|
||||
simplifyPrimitive(primitive, {
|
||||
simplifier: MeshoptSimplifier,
|
||||
ratio: target / source,
|
||||
error: profile.simplifyError,
|
||||
lockBorder: Boolean(profile.lockBorder),
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
export async function preprocessShadowfangVisual(sourceFile, outputFile, { simplifyError } = {}) {
|
||||
const canonicalSource = path.join(workRoot, "shadowfang-keep/staging/environment/shadowfang-keep-visual.glb");
|
||||
if (path.resolve(sourceFile) !== path.resolve(canonicalSource)) {
|
||||
throw new Error("Selective fallback preprocessing only accepts the canonical Shadowfang source GLB.");
|
||||
}
|
||||
const recipe = await loadRecipe("shadowfang-keep");
|
||||
const profile = recipe.visualOptimization?.selectiveFallbackSimplification;
|
||||
if (!profile || profile.sourceSha256 !== PINNED_SHADOWFANG_SOURCE_SHA256) {
|
||||
throw new Error("Shadowfang recipe must opt in with the pinned selective fallback source checksum.");
|
||||
}
|
||||
const sourceBytes = await readFile(canonicalSource);
|
||||
const sourceChecksum = sha256(sourceBytes);
|
||||
if (sourceChecksum !== profile.sourceSha256) {
|
||||
throw new Error(`Pinned Shadowfang source checksum changed: expected ${profile.sourceSha256}, got ${sourceChecksum}.`);
|
||||
}
|
||||
const effectiveError = simplifyError ?? profile.simplifyError;
|
||||
if (!Number.isFinite(effectiveError) || effectiveError < 0) {
|
||||
throw new Error("Selective fallback simplification error must be a non-negative number.");
|
||||
}
|
||||
const effectiveProfile = { ...profile, simplifyError: effectiveError };
|
||||
|
||||
const io = new NodeIO()
|
||||
.registerExtensions(ALL_EXTENSIONS)
|
||||
.registerDependencies({ "meshopt.decoder": MeshoptDecoder });
|
||||
const document = await io.read(canonicalSource);
|
||||
const texturedBefore = sourceHashSnapshot(document);
|
||||
const sourceTextures = textureHashSnapshot(document);
|
||||
const instanceCounts = meshInstanceCounts(document);
|
||||
const selected = Object.fromEntries(Object.keys(profile.targetExpandedTriangles).map((name) => [name, {
|
||||
primitiveCount: 0,
|
||||
trianglesBefore: 0,
|
||||
trianglesAfter: 0,
|
||||
expandedTrianglesBefore: 0,
|
||||
expandedTrianglesAfter: 0,
|
||||
}]));
|
||||
await MeshoptSimplifier.ready;
|
||||
|
||||
for (const mesh of document.getRoot().listMeshes()) {
|
||||
for (const primitive of mesh.listPrimitives()) {
|
||||
const category = eligibleFallbackMaterial(primitive.getMaterial(), effectiveProfile);
|
||||
const counters = selected[category];
|
||||
if (!category || !counters) continue;
|
||||
const trianglesBefore = primitiveTriangles(primitive);
|
||||
if (trianglesBefore === 0) continue;
|
||||
const instances = instanceCounts.get(mesh) ?? 0;
|
||||
simplifyEligibleFallbackPrimitive(primitive, effectiveProfile);
|
||||
const trianglesAfter = primitiveTriangles(primitive);
|
||||
counters.primitiveCount += 1;
|
||||
counters.trianglesBefore += trianglesBefore;
|
||||
counters.trianglesAfter += trianglesAfter;
|
||||
counters.expandedTrianglesBefore += trianglesBefore * instances;
|
||||
counters.expandedTrianglesAfter += trianglesAfter * instances;
|
||||
}
|
||||
}
|
||||
|
||||
const selectedPrimitiveCount = Object.values(selected).reduce((sum, row) => sum + row.primitiveCount, 0);
|
||||
if (selectedPrimitiveCount === 0) throw new Error("Pinned Shadowfang source has no eligible fallback primitives.");
|
||||
const expandedFallbackTriangles = Object.values(selected)
|
||||
.reduce((sum, row) => sum + row.expandedTrianglesAfter, 0);
|
||||
const overBudget = Object.entries(selected)
|
||||
.filter(([material, row]) => row.expandedTrianglesAfter > profile.perMaterialMaximumExpandedTriangles?.[material])
|
||||
.map(([material, row]) => `${material} ${row.expandedTrianglesAfter} > ${profile.perMaterialMaximumExpandedTriangles?.[material]}`);
|
||||
if (expandedFallbackTriangles > profile.maximumExpandedTriangles || overBudget.length) {
|
||||
throw new Error(`Selective Shadowfang fallback geometry exceeds its enforced triangle budget (${expandedFallbackTriangles} > ${profile.maximumExpandedTriangles}; ${overBudget.join(", ")}).`);
|
||||
}
|
||||
if (JSON.stringify([...sourceHashSnapshot(document)]) !== JSON.stringify([...texturedBefore])) {
|
||||
throw new Error("Selective simplification changed a textured primitive accessor hash.");
|
||||
}
|
||||
if (JSON.stringify(textureHashSnapshot(document)) !== JSON.stringify(sourceTextures)) {
|
||||
throw new Error("Selective simplification changed source texture bytes.");
|
||||
}
|
||||
|
||||
await mkdir(path.dirname(outputFile), { recursive: true });
|
||||
await io.write(outputFile, document);
|
||||
return {
|
||||
source: { file: path.relative(projectRoot, canonicalSource).replaceAll("\\", "/"), sha256: sourceChecksum, byteLength: sourceBytes.length },
|
||||
preprocessedFile: outputFile,
|
||||
profile: {
|
||||
materialNamePattern: profile.materialNamePattern,
|
||||
sourceExpandedTriangles: profile.sourceExpandedTriangles,
|
||||
targetExpandedTriangles: profile.targetExpandedTriangles,
|
||||
maximumExpandedTriangles: profile.maximumExpandedTriangles,
|
||||
perMaterialMaximumExpandedTriangles: profile.perMaterialMaximumExpandedTriangles,
|
||||
expandedFallbackTriangles,
|
||||
simplifyError: effectiveError,
|
||||
lockBorder: Boolean(profile.lockBorder),
|
||||
},
|
||||
selectedByMaterial: selected,
|
||||
texturedPrimitiveAccessorHashesUnchanged: true,
|
||||
sourceTextureBytesUnchanged: true,
|
||||
};
|
||||
}
|
||||
Reference in new issue
Block a user