diff --git a/packages/view/src/subjects/NodeSubject.ts b/packages/view/src/subjects/NodeSubject.ts index 6b1e1718..885979e2 100644 --- a/packages/view/src/subjects/NodeSubject.ts +++ b/packages/view/src/subjects/NodeSubject.ts @@ -123,6 +123,12 @@ export class NodeSubject extends Subject { this.skin.update(def.getSkin()); this.light.update(def.getExtension('KHR_lights_punctual')); this.instancedMesh.update(def.getExtension('EXT_mesh_gpu_instancing')); + + const meshValue = this.mesh.value as Group; + + if (meshValue && !(this.value as Mesh).morphTargetInfluences) { + (this.value as Mesh).morphTargetInfluences = (meshValue.children?.[0] as Mesh)?.morphTargetInfluences; + } } dispose() { diff --git a/packages/view/src/subjects/PrimitiveSubject.ts b/packages/view/src/subjects/PrimitiveSubject.ts index dc40d710..13663f3b 100644 --- a/packages/view/src/subjects/PrimitiveSubject.ts +++ b/packages/view/src/subjects/PrimitiveSubject.ts @@ -8,6 +8,7 @@ import { Mesh, Points, SkinnedMesh, + TypedArray, } from 'three'; import { Accessor as AccessorDef, Material as MaterialDef, Primitive as PrimitiveDef } from '@gltf-transform/core'; import type { DocumentViewSubjectAPI } from '../DocumentViewImpl.js'; @@ -88,6 +89,28 @@ export class PrimitiveSubject extends Subject { material: Material, pool: ValuePool, ): MeshLike { + // Add Morph targets + const morphTargets = def.listTargets(); + if (morphTargets.length) { + geometry.morphAttributes = {}; + + for (const [index, morphTarget] of morphTargets.entries()) { + for (const semantic of morphTarget.listSemantics()) { + const attributeName = semanticToAttributeName(semantic); + if (!geometry.morphAttributes[attributeName]) { + geometry.morphAttributes[attributeName] = []; + } + const accessor = morphTarget.getAttribute(semantic) as AccessorDef; + const array = accessor.getArray() as TypedArray; + geometry.morphAttributes[attributeName][index] = new BufferAttribute( + array, + accessor.getElementSize(), + ); + } + } + geometry.morphTargetsRelative = true; + } + switch (def.getMode()) { case PrimitiveDef.Mode.TRIANGLES: case PrimitiveDef.Mode.TRIANGLE_FAN: diff --git a/packages/view/test/NodeSubject.test.ts b/packages/view/test/NodeSubject.test.ts index 653dc71b..0fdc1f96 100644 --- a/packages/view/test/NodeSubject.test.ts +++ b/packages/view/test/NodeSubject.test.ts @@ -1,7 +1,8 @@ import test from 'ava'; import { JSDOM } from 'jsdom'; -import { Document } from '@gltf-transform/core'; +import { Document, Accessor } from '@gltf-transform/core'; import { DocumentView, NullImageProvider } from '@gltf-transform/view'; +import { Mesh } from 'three'; global.document = new JSDOM().window.document; const imageProvider = new NullImageProvider(); @@ -87,3 +88,35 @@ test('NodeSubject | update in place', async (t) => { 'node2.mesh.name', ); }); + +test.only('NodeSubject | check morphTargetInfluences', async (t) => { + const document = new Document(); + + const AccessorDef = document + .createAccessor('myData') + .setArray(new Float32Array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12])) + .setType(Accessor.Type.VEC3) + .setBuffer(document.getRoot().listBuffers()[0]); + + const primitiveTarget = document.createPrimitiveTarget('myTarget').setAttribute('POSITION', AccessorDef); + + const primitiveDef = document.createPrimitive().setAttribute('POSITION', AccessorDef).addTarget(primitiveTarget); + + const meshDef = document.createMesh().setName('Mesh.v1').addPrimitive(primitiveDef).setWeights([0.5]); + const nodeDef1 = document + .createNode('Node1') + .setTranslation([0, 2, 0]) + .setRotation([0, 0, 0.707, 0.707]) + .setScale([0.5, 0.5, 0.5]) + .setMesh(meshDef); + + const documentView = new DocumentView(document, { imageProvider }); + const node1 = documentView.view(nodeDef1); + + t.is(node1.name, 'Node1', 'node1 → name'); + t.is(node1.children.length, 1, 'node1 → children'); + t.deepEqual(node1.position.toArray(), [0, 2, 0], 'node1 → position'); + t.deepEqual(node1.quaternion.toArray(), [0, 0, 0.707, 0.707], 'node1 → quaternion'); + t.deepEqual(node1.scale.toArray(), [0.5, 0.5, 0.5], 'node1 → scale'); + t.deepEqual((node1 as Mesh).morphTargetInfluences, [0], 'node1 → morphTargetInfluences'); +});