Advance M8-M11 parity workflows
This commit is contained in:
119
tools/web/check-nla-evaluation-golden.mjs
Normal file
119
tools/web/check-nla-evaluation-golden.mjs
Normal file
@@ -0,0 +1,119 @@
|
||||
import assert from "node:assert/strict";
|
||||
import crypto from "node:crypto";
|
||||
import fs from "node:fs";
|
||||
import factory from "../../web/app/src/vendor/blender/web_engine.js";
|
||||
|
||||
const root = new URL("../../", import.meta.url);
|
||||
const golden = JSON.parse(fs.readFileSync(new URL("tests/golden/M10-11/nla-evaluation.json", root), "utf8"));
|
||||
const fixture = fs.readFileSync(new URL(golden.fixture, root));
|
||||
const wasmBinary = fs.readFileSync(new URL("web/app/src/vendor/blender/web_engine.wasm", root));
|
||||
|
||||
assert.equal(crypto.createHash("sha256").update(fixture).digest("hex"), golden.fixtureSha256);
|
||||
assert.equal(golden.schemaVersion, 1);
|
||||
|
||||
function open(engine, handle, bytes) {
|
||||
const pointer = engine._malloc(bytes.byteLength);
|
||||
try {
|
||||
engine.HEAPU8.set(bytes, pointer);
|
||||
assert.equal(engine._web_engine_open_blend(handle, pointer, bytes.byteLength), 0,
|
||||
engine.UTF8ToString(engine._web_engine_last_error_message()));
|
||||
}
|
||||
finally { engine._free(pointer); }
|
||||
}
|
||||
|
||||
function output(engine, handle, fn) {
|
||||
const dataOut = engine._malloc(4);
|
||||
const lengthOut = engine._malloc(4);
|
||||
try {
|
||||
assert.equal(fn(handle, dataOut, lengthOut), 0,
|
||||
engine.UTF8ToString(engine._web_engine_last_error_message()));
|
||||
const pointer = engine.HEAPU32[dataOut >>> 2];
|
||||
const length = engine.HEAPU32[lengthOut >>> 2];
|
||||
assert.ok(pointer > 0 && length > 0);
|
||||
return JSON.parse(new TextDecoder().decode(engine.HEAPU8.slice(pointer, pointer + length)));
|
||||
}
|
||||
finally {
|
||||
engine._free(dataOut);
|
||||
engine._free(lengthOut);
|
||||
}
|
||||
}
|
||||
|
||||
function setFrame(engine, handle, frame) {
|
||||
const command = new TextEncoder().encode(JSON.stringify({ type: "setFrame", frame }));
|
||||
const pointer = engine._malloc(command.byteLength);
|
||||
try {
|
||||
engine.HEAPU8.set(command, pointer);
|
||||
assert.equal(engine._web_engine_apply_command(handle, pointer, command.byteLength), 0,
|
||||
engine.UTF8ToString(engine._web_engine_last_error_message()));
|
||||
}
|
||||
finally { engine._free(pointer); }
|
||||
}
|
||||
|
||||
function assertClose(expected, actual, label) {
|
||||
assert.equal(actual.length, expected.length, `${label} length`);
|
||||
const maximum = Math.max(0, ...expected.map((value, index) => Math.abs(actual[index] - value)));
|
||||
assert.ok(maximum <= golden.tolerance.maxMatrixError, `${label} max error ${maximum}`);
|
||||
}
|
||||
|
||||
function assertNlaSnapshot(snapshot) {
|
||||
assert.equal(snapshot.nlaTracks?.length, golden.tracks.length, "NLA track count");
|
||||
const track = snapshot.nlaTracks[0];
|
||||
const expectedTrack = golden.tracks[0];
|
||||
assert.equal(track.schemaVersion, 1);
|
||||
assert.equal(track.ownerId, `object:${golden.object}`);
|
||||
assert.equal(track.name, expectedTrack.name);
|
||||
assert.equal(track.muted, expectedTrack.muted);
|
||||
assert.equal(track.solo, expectedTrack.solo);
|
||||
assert.equal(track.strips.length, expectedTrack.strips.length);
|
||||
expectedTrack.strips.forEach((expected, index) => {
|
||||
const actual = track.strips[index];
|
||||
assert.equal(actual.id, expected.id);
|
||||
assert.equal(actual.actionId, `action:${expected.action}:object:${golden.object}`);
|
||||
for (const field of ["frameStart", "frameEnd", "actionFrameStart", "actionFrameEnd", "scale", "repeat", "blendIn", "blendOut", "influence"]) {
|
||||
assert.equal(actual[field], expected[field], `${expected.id}.${field}`);
|
||||
}
|
||||
for (const field of ["blendMode", "extrapolation", "muted", "reverse"]) {
|
||||
assert.equal(actual[field], expected[field], `${expected.id}.${field}`);
|
||||
}
|
||||
assert.equal(actual.stripType, "CLIP");
|
||||
assert.equal(actual.useTimeWarp, false);
|
||||
});
|
||||
for (const expected of expectedTrack.strips) {
|
||||
const action = snapshot.animations.find((candidate) => candidate.id === `action:${expected.action}:object:${golden.object}`);
|
||||
assert.ok(action, `${expected.action} animation`);
|
||||
assert.equal(action.targetId, `object:${golden.object}`);
|
||||
assert.equal(action.frameStart, 1);
|
||||
assert.equal(action.frameEnd, 11);
|
||||
assert.ok(action.channels.some((channel) => channel.path.startsWith("location[")), `${expected.action} location channel`);
|
||||
}
|
||||
}
|
||||
|
||||
const engine = await factory({ wasmBinary: wasmBinary.slice() });
|
||||
const handle = engine._web_engine_create();
|
||||
assert.ok(handle > 0);
|
||||
try {
|
||||
open(engine, handle, fixture);
|
||||
const initial = output(engine, handle, engine._web_engine_get_scene_snapshot);
|
||||
assertNlaSnapshot(initial);
|
||||
const initialNla = JSON.stringify({ nlaTracks: initial.nlaTracks, animations: initial.animations });
|
||||
let maximumMatrixError = 0;
|
||||
for (const expected of golden.frames) {
|
||||
setFrame(engine, handle, expected.frame);
|
||||
const report = output(engine, handle, engine._web_engine_evaluate_depsgraph);
|
||||
assert.equal(report.engine, "BlenderDepsgraph");
|
||||
assert.equal(report.status, "EVALUATED");
|
||||
assert.equal(report.frame, expected.frame);
|
||||
const mesh = report.meshes.find((candidate) => candidate.objectId === `object:${golden.object}`);
|
||||
assert.ok(mesh, `evaluated ${golden.object} at frame ${expected.frame}`);
|
||||
const errors = mesh.worldMatrix.map((value, index) => Math.abs(value - expected.worldMatrix[index]));
|
||||
maximumMatrixError = Math.max(maximumMatrixError, ...errors);
|
||||
assertClose(expected.worldMatrix, mesh.worldMatrix, `frame ${expected.frame}`);
|
||||
const after = output(engine, handle, engine._web_engine_get_scene_snapshot);
|
||||
assert.equal(JSON.stringify({ nlaTracks: after.nlaTracks, animations: after.animations }), initialNla,
|
||||
`NLA read-only identity changed at frame ${expected.frame}`);
|
||||
}
|
||||
process.stdout.write(`nla-evaluation-ok tracks=${golden.tracks.length} strips=${golden.tracks[0].strips.length} frames=${golden.frames.length} max-matrix-error=${maximumMatrixError} read-only=passed\n`);
|
||||
}
|
||||
finally {
|
||||
engine._web_engine_destroy(handle);
|
||||
}
|
||||
Reference in New Issue
Block a user