import fs from "node:fs"; import factory from "../../web/app/src/vendor/blender/web_engine.js"; const wasmBinary = fs.readFileSync(new URL("../../web/app/src/vendor/blender/web_engine.wasm", import.meta.url)); const blend = fs.readFileSync(new URL("../../tests/files/web/basic_scene.blend", import.meta.url)); const expected = JSON.parse(fs.readFileSync(new URL("../../tests/golden/W-079/basic-scene-decimate.json", import.meta.url), "utf8")); function readSnapshot(engine, handle) { const dataOut = engine._malloc(4); const lengthOut = engine._malloc(4); try { if (engine._web_engine_get_scene_snapshot(handle, dataOut, lengthOut) !== 0) throw new Error("golden snapshot request failed"); 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 open(engine, handle) { const pointer = engine._malloc(blend.byteLength); engine.HEAPU8.set(blend, pointer); const result = engine._web_engine_open_blend(handle, pointer, blend.byteLength); engine._free(pointer); if (result !== 0) throw new Error(`golden fixture open failed: ${engine.UTF8ToString(engine._web_engine_last_error_message())}`); } function apply(engine, handle, command) { const bytes = new TextEncoder().encode(JSON.stringify(command)); const pointer = engine._malloc(bytes.byteLength); engine.HEAPU8.set(bytes, pointer); const result = engine._web_engine_apply_command(handle, pointer, bytes.byteLength); engine._free(pointer); if (result !== 0) throw new Error(`golden command failed: ${engine.UTF8ToString(engine._web_engine_last_error_message())}`); } function sourceSummary(snapshot) { const meshById = new Map(snapshot.meshes.map((mesh) => [mesh.id, mesh])); return { frame: [snapshot.frame.start, snapshot.frame.end], objects: snapshot.nodes.map((node) => { const mesh = node.dataId ? meshById.get(node.dataId) : undefined; return { name: node.name, type: node.type, vertices: mesh?.vertexCount ?? 0, polygons: mesh?.faceCount ?? 0, materials: mesh?.materialSlotIds?.length ?? 0 }; }).sort((left, right) => left.name.localeCompare(right.name)), }; } const sourceEngine = await factory({ wasmBinary }); const sourceHandle = sourceEngine._web_engine_create(); open(sourceEngine, sourceHandle); const source = sourceSummary(readSnapshot(sourceEngine, sourceHandle)); sourceEngine._web_engine_destroy(sourceHandle); const collapse = {}; for (const ratio of [1, 0.9, 0.8, 0.75, 0.7, 0.65, 0.5, 0.25]) { const engine = await factory({ wasmBinary }); const handle = engine._web_engine_create(); open(engine, handle); const snapshot = readSnapshot(engine, handle); const mesh = snapshot.meshes.find((candidate) => candidate.id === "mesh:Cube.001"); apply(engine, handle, { type: "decimateMesh", meshId: mesh.id, profile: { schemaVersion: 1, sourceMeshRevision: snapshot.revision, attributePolicy: "PRESERVE", mode: "COLLAPSE", ratio, triangulate: false, useSymmetry: false } }); const output = readSnapshot(engine, handle).meshes.find((candidate) => candidate.id === mesh.id); collapse[String(ratio)] = { vertices: output.vertexCount, triangles: output.triangleCount ?? Math.floor((output.indices?.length ?? 0) / 3) }; engine._web_engine_destroy(handle); } const actual = { fixture: expected.fixture, source, collapse }; if (JSON.stringify(actual) !== JSON.stringify(expected)) { throw new Error(`Blender golden mismatch\nexpected=${JSON.stringify(expected)}\nactual=${JSON.stringify(actual)}`); } console.log(`blender-golden-ok fixture=${expected.fixture} ratios=${Object.keys(collapse).join(",")}`);