Add Chromium-only Blender WebEngine parity work
This commit is contained in:
113
tools/web/check-pose-constraint-goldens.mjs
Normal file
113
tools/web/check-pose-constraint-goldens.mjs
Normal file
@@ -0,0 +1,113 @@
|
||||
import assert from "node:assert/strict";
|
||||
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/W-080/pose-constraint-depsgraph.json", root), "utf8"));
|
||||
const blend = fs.readFileSync(new URL(`tests/files/web/${golden.fixture}`, root));
|
||||
const wasmBinary = fs.readFileSync(new URL("web/app/src/vendor/blender/web_engine.wasm", root));
|
||||
const tolerance = golden.tolerance;
|
||||
|
||||
function readJson(engine, dataOut, lengthOut) {
|
||||
const pointer = engine.HEAPU32[dataOut >>> 2];
|
||||
const length = engine.HEAPU32[lengthOut >>> 2];
|
||||
assert.ok(pointer && length, "native response is empty");
|
||||
return JSON.parse(new TextDecoder().decode(engine.HEAPU8.slice(pointer, pointer + length)));
|
||||
}
|
||||
|
||||
function command(engine, handle, payload) {
|
||||
const encoded = new TextEncoder().encode(JSON.stringify(payload));
|
||||
const pointer = engine._malloc(encoded.byteLength);
|
||||
try {
|
||||
engine.HEAPU8.set(encoded, pointer);
|
||||
assert.equal(engine._web_engine_apply_command(handle, pointer, encoded.byteLength), 0, engine.UTF8ToString(engine._web_engine_last_error_message()));
|
||||
}
|
||||
finally {
|
||||
engine._free(pointer);
|
||||
}
|
||||
}
|
||||
|
||||
function evaluate(engine, handle) {
|
||||
const dataOut = engine._malloc(4);
|
||||
const lengthOut = engine._malloc(4);
|
||||
try {
|
||||
assert.equal(engine._web_engine_evaluate_depsgraph(handle, dataOut, lengthOut), 0, engine.UTF8ToString(engine._web_engine_last_error_message()));
|
||||
return readJson(engine, dataOut, lengthOut);
|
||||
}
|
||||
finally {
|
||||
engine._free(dataOut);
|
||||
engine._free(lengthOut);
|
||||
}
|
||||
}
|
||||
|
||||
const engine = await factory({ wasmBinary });
|
||||
const handle = engine._web_engine_create();
|
||||
assert.ok(handle > 0, "WebEngine handle creation failed");
|
||||
try {
|
||||
const input = engine._malloc(blend.byteLength);
|
||||
try {
|
||||
engine.HEAPU8.set(blend, input);
|
||||
assert.equal(engine._web_engine_open_blend(handle, input, blend.byteLength), 0, engine.UTF8ToString(engine._web_engine_last_error_message()));
|
||||
}
|
||||
finally {
|
||||
engine._free(input);
|
||||
}
|
||||
|
||||
const expectedByFrame = new Map(golden.samples.map((sample) => [sample.frame, sample]));
|
||||
const snapshotDataOut = engine._malloc(4);
|
||||
const snapshotLengthOut = engine._malloc(4);
|
||||
try {
|
||||
assert.equal(engine._web_engine_get_scene_snapshot(handle, snapshotDataOut, snapshotLengthOut), 0);
|
||||
const snapshot = readJson(engine, snapshotDataOut, snapshotLengthOut);
|
||||
const mesh = snapshot.meshes.find((candidate) => candidate.id === `mesh:${golden.mesh}`);
|
||||
assert.ok(mesh?.skinWeights, "pose fixture skin weights are missing");
|
||||
assert.equal(mesh.skinWeights.boneNames.length, 4, "pose fixture must cover four bones");
|
||||
const bindErrors = mesh.skinWeights.bindMatrix.map((value, index) => value - golden.meshBindMatrix[index]);
|
||||
assert.ok(Math.max(...bindErrors.map(Math.abs)) <= tolerance.maxMatrixError, "non-unit bind matrix mismatch");
|
||||
assert.ok(bindErrors.some((value, index) => Math.abs(golden.meshBindMatrix[index] - (index % 5 === 0 ? 1 : 0)) > 1e-3), "fixture bind matrix must be non-unit");
|
||||
}
|
||||
finally {
|
||||
engine._free(snapshotDataOut);
|
||||
engine._free(snapshotLengthOut);
|
||||
}
|
||||
for (const frame of golden.frames) {
|
||||
command(engine, handle, { type: "setFrame", frame });
|
||||
const report = evaluate(engine, handle);
|
||||
assert.equal(report.status, "EVALUATED");
|
||||
assert.equal(report.frame, frame);
|
||||
const expected = expectedByFrame.get(frame);
|
||||
assert.ok(expected, `missing desktop sample for frame ${frame}`);
|
||||
const armature = report.armatures?.find((candidate) => candidate.id === `armature:${golden.armature}`);
|
||||
assert.ok(armature, `armature report missing at frame ${frame}`);
|
||||
assert.equal(armature.objectId, `object:${golden.armatureObject}`);
|
||||
for (const [boneName, expectedMatrix] of Object.entries(expected.bones)) {
|
||||
const bone = armature.bones.find((candidate) => candidate.name === boneName);
|
||||
assert.ok(bone, `${boneName} missing at frame ${frame}`);
|
||||
const matrixErrors = bone.poseMatrix.map((value, index) => value - expectedMatrix[index]);
|
||||
const maxMatrixError = Math.max(0, ...matrixErrors.map((value) => Math.abs(value)));
|
||||
assert.ok(maxMatrixError <= tolerance.maxMatrixError, `${boneName} frame ${frame} matrix error ${maxMatrixError}`);
|
||||
const metadata = golden.constraintMetadata[boneName] ?? [];
|
||||
assert.equal(bone.constraints.length, metadata.length, `${boneName} constraint count at frame ${frame}`);
|
||||
for (const [index, expectedConstraint] of metadata.entries()) {
|
||||
const actual = bone.constraints[index];
|
||||
assert.equal(actual.name, expectedConstraint.name);
|
||||
assert.equal(actual.typeCode, expectedConstraint.typeCode);
|
||||
assert.ok(Math.abs(actual.influence - expectedConstraint.influence) <= tolerance.maxMatrixError);
|
||||
if (expectedConstraint.targetObject) assert.equal(actual.targetObjectId, `object:${expectedConstraint.targetObject}`);
|
||||
if (expectedConstraint.poleTargetObject) assert.equal(actual.poleTargetObjectId, `object:${expectedConstraint.poleTargetObject}`);
|
||||
}
|
||||
}
|
||||
const mesh = report.meshes.find((candidate) => candidate.sourceMeshId === `mesh:${golden.mesh}`);
|
||||
assert.ok(mesh, `mesh report missing at frame ${frame}`);
|
||||
const positionErrors = mesh.positions.map((value, index) => value - expected.positions[index]);
|
||||
const maxPositionError = Math.max(0, ...positionErrors.map((value) => Math.abs(value)));
|
||||
const rmsPositionError = Math.sqrt(positionErrors.reduce((sum, value) => sum + value * value, 0) / positionErrors.length);
|
||||
assert.ok(maxPositionError <= tolerance.maxPositionError, `frame ${frame} max position error ${maxPositionError}`);
|
||||
assert.ok(rmsPositionError <= tolerance.rmsPositionError, `frame ${frame} RMS position error ${rmsPositionError}`);
|
||||
}
|
||||
}
|
||||
finally {
|
||||
engine._web_engine_destroy(handle);
|
||||
}
|
||||
|
||||
process.stdout.write(`pose-constraint-goldens-ok fixture=${golden.fixture} frames=${golden.frames.join(",")}\n`);
|
||||
Reference in New Issue
Block a user