Release Healer Man 0.1.21
This commit is contained in:
1 parent
699fc858c9
commit
f6f2375145
210 files changed
+976039
-6247
No files matched your search
@@ -0,0 +1,186 @@
|
||||
import assert from "node:assert/strict";
|
||||
import { mkdtemp, readFile, rm } from "node:fs/promises";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import test from "node:test";
|
||||
import { Document, NodeIO } from "@gltf-transform/core";
|
||||
import { Matrix4, Vector3 } from "three";
|
||||
import {
|
||||
assembleInstancedGlb,
|
||||
buildWdbPlacementPlan,
|
||||
normalizeWdbResourcePath,
|
||||
placementMatrixInPrimaryThree,
|
||||
resourceCacheKey,
|
||||
transformSourcePointToPrimaryThree,
|
||||
} from "./lib/environment-assembly.mjs";
|
||||
|
||||
const identityPlacement = (overrides = {}) => ({
|
||||
guid: "PRIMARY",
|
||||
resource: "model\\dungeon\\fixture\\room.ros",
|
||||
scale: [1, 1, 1],
|
||||
rotation: [0, 0, 0, 1],
|
||||
translation: [10, 20, 30],
|
||||
localBounds: { center: [0, 0, 0], radius: 1 },
|
||||
worldBounds: { center: [10, 20, 30], radius: 1 },
|
||||
...overrides,
|
||||
});
|
||||
|
||||
const fixtureRecipe = (source = {}) => ({
|
||||
slug: "fixture",
|
||||
coordinateSystem: {
|
||||
source: "left-handed Y-up",
|
||||
nativeBridge: "three(x,y,z)=(runewaker.x,runewaker.y,-runewaker.z)",
|
||||
metersPerSourceUnit: 0.1,
|
||||
},
|
||||
source: {
|
||||
model: "model/dungeon/fixture/room.ros",
|
||||
primaryPlacementTranslation: [10, 20, 30],
|
||||
...source,
|
||||
},
|
||||
});
|
||||
|
||||
test("normalizes resource paths and makes case-insensitive stable cache keys", () => {
|
||||
assert.equal(
|
||||
normalizeWdbResourcePath("/Model/Dungeon/Fixture/Room.ROS"),
|
||||
"model\\dungeon\\fixture\\room.ros",
|
||||
);
|
||||
assert.equal(
|
||||
resourceCacheKey("Model/Dungeon/Fixture/Room.ROS"),
|
||||
resourceCacheKey("model\\dungeon\\fixture\\room.ros"),
|
||||
);
|
||||
});
|
||||
|
||||
test("converts full placement translation, quaternion, and nonuniform scale", () => {
|
||||
const primary = identityPlacement();
|
||||
const placed = identityPlacement({
|
||||
guid: "PLACED",
|
||||
resource: "model\\dungeon\\fixture\\bridge.ros",
|
||||
translation: [20, 22, 40],
|
||||
rotation: [0, Math.SQRT1_2, 0, Math.SQRT1_2],
|
||||
scale: [2, 3, 4],
|
||||
});
|
||||
const matrix = placementMatrixInPrimaryThree(placed, primary, 0.1);
|
||||
const sourcePoint = [3, 4, 5];
|
||||
const viaMatrix = new Vector3(sourcePoint[0] * 0.1, sourcePoint[1] * 0.1, -sourcePoint[2] * 0.1)
|
||||
.applyMatrix4(new Matrix4().fromArray(matrix.toArray()))
|
||||
.toArray();
|
||||
const direct = transformSourcePointToPrimaryThree(sourcePoint, placed, primary, 0.1);
|
||||
assert.deepEqual(viaMatrix.map((value) => Number(value.toFixed(9))), direct.map((value) => Number(value.toFixed(9))));
|
||||
assert.notDeepEqual(matrix.toArray(), new Matrix4().identity().toArray());
|
||||
});
|
||||
|
||||
test("builds deterministic static, dynamic, review, and unsupported placement groups", () => {
|
||||
const primary = identityPlacement();
|
||||
const door = identityPlacement({
|
||||
guid: "DOOR",
|
||||
resource: "model\\dungeon\\fixture\\boss_door.ros",
|
||||
translation: [15, 20, 30],
|
||||
});
|
||||
const hintedBridge = identityPlacement({
|
||||
guid: "BRIDGE",
|
||||
resource: "model\\dungeon\\fixture\\drawbridge.ros",
|
||||
translation: [20, 20, 30],
|
||||
});
|
||||
const unsupported = identityPlacement({
|
||||
guid: "FX",
|
||||
resource: "effect\\fixture\\mist.eft",
|
||||
});
|
||||
const recipe = fixtureRecipe({
|
||||
wdbAssembly: {
|
||||
mode: "all-ros",
|
||||
dynamicPlacements: [{
|
||||
id: "boss-door",
|
||||
guid: "DOOR",
|
||||
kind: "door",
|
||||
initialState: "closed",
|
||||
collision: "stateful",
|
||||
evidence: ["fixture.lua:10"],
|
||||
}],
|
||||
},
|
||||
});
|
||||
const first = buildWdbPlacementPlan({
|
||||
sourceWdb: "fixture.wdb",
|
||||
descriptors: [unsupported, hintedBridge, door, primary],
|
||||
}, recipe);
|
||||
const second = buildWdbPlacementPlan({
|
||||
sourceWdb: "fixture.wdb",
|
||||
descriptors: [unsupported, hintedBridge, door, primary],
|
||||
}, recipe);
|
||||
assert.deepEqual(first, second);
|
||||
assert.equal(first.staticPlacements.length, 2);
|
||||
assert.equal(first.dynamicPlacements.length, 1);
|
||||
assert.equal(first.dynamicPlacements[0].dynamic.kind, "door");
|
||||
assert.equal(first.reviewRequiredPlacements.length, 1);
|
||||
assert.equal(first.reviewRequiredPlacements[0].guid, "BRIDGE");
|
||||
assert.equal(first.unsupportedPlacements.length, 1);
|
||||
});
|
||||
|
||||
test("assembles repeated cached GLBs as transformed resource instances", async () => {
|
||||
const temporary = await mkdtemp(path.join(os.tmpdir(), "healer-man-wdb-assembly-"));
|
||||
try {
|
||||
const sourceFile = path.join(temporary, "source.glb");
|
||||
const outputFile = path.join(temporary, "assembled.glb");
|
||||
const source = new Document();
|
||||
const buffer = source.createBuffer("fixture-buffer");
|
||||
const position = source.createAccessor("fixture-position", buffer)
|
||||
.setType("VEC3")
|
||||
.setArray(new Float32Array([0, 0, 0, 1, 0, 0, 0, 1, 0]));
|
||||
const primitive = source.createPrimitive().setAttribute("POSITION", position);
|
||||
const mesh = source.createMesh("fixture-mesh").addPrimitive(primitive);
|
||||
source.createScene("fixture-scene").addChild(source.createNode("fixture-node").setMesh(mesh));
|
||||
const io = new NodeIO();
|
||||
await io.write(sourceFile, source);
|
||||
const makePlacement = (id, x) => ({
|
||||
id,
|
||||
guid: id.toUpperCase(),
|
||||
resource: "model/dungeon/fixture/room.ros",
|
||||
resourceCacheKey: resourceCacheKey("model/dungeon/fixture/room.ros"),
|
||||
transform: {
|
||||
primaryLocalThreeMatrix: new Matrix4().makeTranslation(x, 0, 0).toArray(),
|
||||
},
|
||||
});
|
||||
const result = await assembleInstancedGlb([
|
||||
{ assetFile: sourceFile, placement: makePlacement("first", 0) },
|
||||
{ assetFile: sourceFile, placement: makePlacement("second", 5) },
|
||||
], outputFile);
|
||||
const assembled = await io.read(outputFile);
|
||||
const scene = assembled.getRoot().getDefaultScene() ?? assembled.getRoot().listScenes()[0];
|
||||
assert.deepEqual(scene.listChildren().map((node) => node.getName()), ["first", "second"]);
|
||||
assert.deepEqual(result.bounds, { min: [0, 0, 0], max: [6, 1, 0] });
|
||||
assert.equal(assembled.getRoot().listMeshes().length, 1, "repeated placements share cached mesh data");
|
||||
} finally {
|
||||
await rm(temporary, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
test("rejects dynamic rules that would remove the primary coordinate anchor", () => {
|
||||
const primary = identityPlacement();
|
||||
assert.throws(() => buildWdbPlacementPlan({ descriptors: [primary] }, fixtureRecipe({
|
||||
wdbAssembly: {
|
||||
dynamicPlacements: [{
|
||||
id: "invalid-primary-door",
|
||||
guid: "PRIMARY",
|
||||
kind: "door",
|
||||
evidence: ["fixture.lua:1"],
|
||||
}],
|
||||
},
|
||||
})), /primary coordinate-anchor placement cannot be dynamic/);
|
||||
});
|
||||
|
||||
test("current modular WDB reports account for every descriptor without guessed dynamics", async () => {
|
||||
for (const slug of ["raksha-temple", "the-origin", "treasure-trove"]) {
|
||||
const [recipe, report] = await Promise.all([
|
||||
readFile(new URL("./recipes/" + slug + ".json", import.meta.url), "utf8").then(JSON.parse),
|
||||
readFile(new URL("../../src/assets/game/dungeons/" + slug + "/wdb-report.json", import.meta.url), "utf8").then(JSON.parse),
|
||||
]);
|
||||
const plan = buildWdbPlacementPlan(report, recipe);
|
||||
assert.equal(
|
||||
plan.staticPlacements.length + plan.dynamicPlacements.length + plan.unsupportedPlacements.length,
|
||||
report.descriptorCount,
|
||||
slug,
|
||||
);
|
||||
assert.equal(plan.dynamicPlacements.length, 0, slug + " must not infer stateful behavior from names");
|
||||
assert.ok(plan.resources.length > 1, slug + " should expose arbitrary resource assembly");
|
||||
assert.equal(plan.primaryPlacement.resource.toLowerCase(), recipe.source.model.toLowerCase());
|
||||
}
|
||||
});
|
||||
Reference in new issue
Block a user