120 lines
5.1 KiB
JavaScript
120 lines
5.1 KiB
JavaScript
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/M9-12/weight-paint.json", root), "utf8"));
|
|
const fixture = 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));
|
|
|
|
function call(engine, handle, name, payload) {
|
|
const bytes = new TextEncoder().encode(JSON.stringify(payload));
|
|
const pointer = engine._malloc(bytes.byteLength);
|
|
try {
|
|
engine.HEAPU8.set(bytes, pointer);
|
|
assert.equal(engine._web_engine_apply_command(handle, pointer, bytes.byteLength), 0,
|
|
`${name}: ${engine.UTF8ToString(engine._web_engine_last_error_message())}`);
|
|
}
|
|
finally {
|
|
engine._free(pointer);
|
|
}
|
|
}
|
|
|
|
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 snapshot(engine, handle) {
|
|
const dataOut = engine._malloc(4);
|
|
const lengthOut = engine._malloc(4);
|
|
try {
|
|
assert.equal(engine._web_engine_get_scene_snapshot(handle, dataOut, lengthOut), 0);
|
|
const pointer = engine.HEAPU32[dataOut >>> 2];
|
|
const length = engine.HEAPU32[lengthOut >>> 2];
|
|
return JSON.parse(new TextDecoder().decode(engine.HEAPU8.slice(pointer, pointer + length)));
|
|
}
|
|
finally {
|
|
engine._free(dataOut);
|
|
engine._free(lengthOut);
|
|
}
|
|
}
|
|
|
|
function save(engine, handle) {
|
|
const dataOut = engine._malloc(4);
|
|
const lengthOut = engine._malloc(4);
|
|
try {
|
|
assert.equal(engine._web_engine_save_blend(handle, dataOut, lengthOut), 0);
|
|
const pointer = engine.HEAPU32[dataOut >>> 2];
|
|
const length = engine.HEAPU32[lengthOut >>> 2];
|
|
const bytes = engine.HEAPU8.slice(pointer, pointer + length);
|
|
engine._web_engine_free_buffer(pointer);
|
|
return bytes;
|
|
}
|
|
finally {
|
|
engine._free(dataOut);
|
|
engine._free(lengthOut);
|
|
}
|
|
}
|
|
|
|
function weightsByVertex(snapshotValue) {
|
|
const mesh = snapshotValue.meshes.find((item) => item.id === `mesh:${golden.mesh}`);
|
|
assert.ok(mesh?.skinWeights, "weight golden mesh has no skinWeights");
|
|
return golden.steps[0].vertices.map((expectedVertex) => {
|
|
const result = {};
|
|
for (const [groupIndex, groupName] of mesh.skinWeights.boneNames.entries()) {
|
|
const offset = expectedVertex.index * 4;
|
|
const slot = mesh.skinWeights.indices.slice(offset, offset + 4).findIndex((value) => value === groupIndex);
|
|
result[groupName] = slot < 0 ? 0 : mesh.skinWeights.weights[offset + slot];
|
|
}
|
|
return result;
|
|
});
|
|
}
|
|
|
|
function compareStep(actualSnapshot, expected, label) {
|
|
const actual = weightsByVertex(actualSnapshot);
|
|
assert.deepEqual(actual.map((weights) => Object.keys(weights).sort()), expected.vertices.map(() => expected.groups.slice().sort()), `${label} group schema`);
|
|
expected.vertices.forEach((vertex, index) => {
|
|
for (const group of expected.groups) {
|
|
const error = Math.abs((actual[index][group] ?? 0) - (vertex.weights[group] ?? 0));
|
|
assert.ok(error <= golden.tolerance, `${label} vertex=${vertex.index} group=${group} error=${error}`);
|
|
}
|
|
});
|
|
}
|
|
|
|
const engine = await factory({ wasmBinary: wasmBinary.slice() });
|
|
const handle = engine._web_engine_create();
|
|
assert.ok(handle > 0);
|
|
try {
|
|
open(engine, handle, fixture);
|
|
call(engine, handle, "initial", { type: "setVertexWeights", objectId: `object:${golden.object}`, vertexGroup: "WebPaintGroup", indices: [0, 1], values: [0.75, 0.25] });
|
|
compareStep(snapshot(engine, handle), golden.steps[0], "initial");
|
|
call(engine, handle, "normalize", { type: "setVertexWeights", objectId: `object:${golden.object}`, vertexGroup: "WebPaintGroup", indices: [2], values: [0.5], normalize: true });
|
|
compareStep(snapshot(engine, handle), golden.steps[1], "normalize");
|
|
call(engine, handle, "limit-normalize", { type: "setVertexWeights", objectId: `object:${golden.object}`, vertexGroup: "WebPaintGroup", indices: [3], values: [0.4], limit: 2, normalize: true });
|
|
compareStep(snapshot(engine, handle), golden.steps[2], "limit-normalize");
|
|
call(engine, handle, "mirror", { type: "setVertexWeights", objectId: `object:${golden.object}`, vertexGroup: "WebPaintGroup", indices: [0], values: [0.9], mirror: true, mirrorAxis: 0, mirrorTolerance: 1e-4 });
|
|
compareStep(snapshot(engine, handle), golden.steps[3], "mirror");
|
|
const saved = save(engine, handle);
|
|
const reopened = engine._web_engine_create();
|
|
try {
|
|
open(engine, reopened, saved);
|
|
compareStep(snapshot(engine, reopened), golden.steps[3], "save-reopen");
|
|
}
|
|
finally {
|
|
engine._web_engine_destroy(reopened);
|
|
}
|
|
}
|
|
finally {
|
|
engine._web_engine_destroy(handle);
|
|
}
|
|
|
|
process.stdout.write(`weight-paint-golden-ok fixture=${golden.fixture} steps=${golden.steps.map((step) => step.name).join(",")} normalize=passed limit=passed mirror=passed save-reopen=passed\n`);
|