import assert from "node:assert/strict"; import test from "node:test"; import { Document } from "@gltf-transform/core"; import { CREATURE_MESH_CONSOLIDATION_POLICY, consolidateCreatureSkinnedMeshes, } from "./creature-mesh-consolidation.mjs"; function createFixture({ animateRenderNode = false, distinctAttributes = false } = {}) { const document = new Document(); const buffer = document.createBuffer(); const scene = document.createScene("Creature_Scene"); const root = document.createNode("CreatureRoot"); const skeleton = document.createNode("SkeletonRoot"); const joint = document.createNode("bone_root"); const skin = document.createSkin("CreatureSkin").setSkeleton(skeleton).addJoint(joint); const material = document.createMaterial("BodyMaterial"); const position = document.createAccessor("positions", buffer) .setType("VEC3") .setArray(new Float32Array([ 0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, ])); scene.addChild(root); root.addChild(skeleton); skeleton.addChild(joint); const renderNodes = []; for (let index = 0; index < 2; index += 1) { const attributes = distinctAttributes && index === 1 ? document.createAccessor("positions-copy", buffer) .setType("VEC3") .setArray(new Float32Array(position.getArray())) : position; const indices = document.createAccessor(`indices-${index}`, buffer) .setType("SCALAR") .setArray(new Uint16Array(index === 0 ? [0, 1, 2] : [0, 2, 3])); const primitive = document.createPrimitive() .setMaterial(material) .setAttribute("POSITION", attributes) .setIndices(indices); const mesh = document.createMesh(`GeosetMesh${index}`).addPrimitive(primitive); const node = document.createNode(`Geoset${index}`).setMesh(mesh).setSkin(skin); root.addChild(node); renderNodes.push(node); } const animation = document.createAnimation("Stand"); const sampler = document.createAnimationSampler() .setInput(document.createAccessor("time", buffer).setType("SCALAR").setArray(new Float32Array([0, 1]))) .setOutput(document.createAccessor("rotation", buffer).setType("VEC4").setArray(new Float32Array([ 0, 0, 0, 1, 0, 0, 0, 1, ]))); animation.addSampler(sampler).addChannel( document.createAnimationChannel() .setSampler(sampler) .setTargetNode(animateRenderNode ? renderNodes[0] : joint) .setTargetPath("rotation"), ); return { document, root, joint }; } test("consolidates compatible skinned geosets without changing skeleton animation", async () => { const { document, root, joint } = createFixture(); const result = await consolidateCreatureSkinnedMeshes(document); assert.equal(result.policy, CREATURE_MESH_CONSOLIDATION_POLICY); assert.equal(result.groups, 1); assert.equal(result.sourceDrawCalls, 2); assert.equal(result.runtimeDrawCalls, 1); assert.equal(result.before.triangles, 2); assert.equal(result.after.triangles, 2); const renderNodes = document.getRoot().listNodes().filter((node) => node.getMesh()); assert.equal(renderNodes.length, 1); assert.equal(renderNodes[0].getSkin(), document.getRoot().listSkins()[0]); assert.deepEqual( Array.from(renderNodes[0].getMesh().listPrimitives()[0].getIndices().getArray()), [0, 1, 2, 0, 2, 3], ); assert.equal(document.getRoot().listAnimations()[0].listChannels()[0].getTargetNode(), joint); assert.equal(root.listChildren().some((node) => node.getName() === "CreatureRoot_MergedSkinned_01"), true); }); test("skips render nodes targeted by animation channels", async () => { const { document } = createFixture({ animateRenderNode: true }); const result = await consolidateCreatureSkinnedMeshes(document); assert.equal(result.groups, 0); assert.equal(result.before.drawCalls, 2); assert.equal(result.after.drawCalls, 2); }); test("does not join geosets backed by different vertex attributes", async () => { const { document } = createFixture({ distinctAttributes: true }); const result = await consolidateCreatureSkinnedMeshes(document); assert.equal(result.groups, 0); assert.equal(result.before.drawCalls, 2); assert.equal(result.after.drawCalls, 2); });