diff --git a/packages/functions/src/quantize.ts b/packages/functions/src/quantize.ts index 1885d3ea..1de834f2 100644 --- a/packages/functions/src/quantize.ts +++ b/packages/functions/src/quantize.ts @@ -200,13 +200,13 @@ export function quantize(_options: QuantizeOptions = QUANTIZE_DEFAULTS): Transfo } function quantizePrimitive( - doc: Document, + document: Document, prim: Primitive | PrimitiveTarget, nodeTransform: VectorTransform, options: Required, ): void { const isTarget = prim instanceof PrimitiveTarget; - const logger = doc.getLogger(); + const logger = document.getLogger(); for (const semantic of prim.listSemantics()) { if (!isTarget && !options.pattern.test(semantic)) continue; @@ -280,7 +280,7 @@ function getNodeTransform(volume: bbox): VectorTransform { } /** Applies corrective scale and offset to nodes referencing a quantized Mesh. */ -function transformMeshParents(doc: Document, mesh: Mesh, nodeTransform: VectorTransform): void { +function transformMeshParents(document: Document, mesh: Mesh, nodeTransform: VectorTransform): void { const transformMatrix = fromTransform(nodeTransform); for (const parent of mesh.listParents()) { if (!(parent instanceof Node)) continue; @@ -297,13 +297,13 @@ function transformMeshParents(doc: Document, mesh: Mesh, nodeTransform: VectorTr const batch = parent.getExtension('EXT_mesh_gpu_instancing'); if (batch) { - parent.setExtension('EXT_mesh_gpu_instancing', transformBatch(batch, nodeTransform)); + parent.setExtension('EXT_mesh_gpu_instancing', transformBatch(document, batch, nodeTransform)); continue; } let targetNode: Node; if (isParentNode || isAnimated) { - targetNode = doc.createNode('').setMesh(mesh); + targetNode = document.createNode('').setMesh(mesh); parent.addChild(targetNode).setMesh(null); animChannels .filter((channel) => channel.getTargetPath() === WEIGHTS) @@ -333,21 +333,34 @@ function transformSkin(skin: Skin, nodeTransform: VectorTransform): Skin { } /** Applies corrective scale and offset to GPU instancing batches. */ -function transformBatch(batch: InstancedMesh, nodeTransform: VectorTransform): InstancedMesh { +function transformBatch(document: Document, batch: InstancedMesh, nodeTransform: VectorTransform): InstancedMesh { if (!batch.getAttribute('TRANSLATION') && !batch.getAttribute('ROTATION') && !batch.getAttribute('SCALE')) { return batch; } batch = batch.clone(); // quantize() does cleanup. - const instanceTranslation = batch.getAttribute('TRANSLATION')?.clone(); + + let instanceTranslation = batch.getAttribute('TRANSLATION')?.clone(); const instanceRotation = batch.getAttribute('ROTATION')?.clone(); - const instanceScale = batch.getAttribute('SCALE')?.clone(); + let instanceScale = batch.getAttribute('SCALE')?.clone(); + const tpl = (instanceTranslation || instanceRotation || instanceScale)!; const T_IDENTITY = [0, 0, 0] as vec3; const R_IDENTITY = [0, 0, 0, 1] as vec4; const S_IDENTITY = [1, 1, 1] as vec3; + // Transformed batch may now require instance translation or scale. + // See: https://github.com/donmccurdy/glTF-Transform/issues/1584 + + if (!instanceTranslation && nodeTransform.offset) { + instanceTranslation = document.createAccessor().setType('VEC3').setArray(makeArray(tpl.getCount(), T_IDENTITY)); + } + + if (!instanceScale && nodeTransform.scale) { + instanceScale = document.createAccessor().setType('VEC3').setArray(makeArray(tpl.getCount(), S_IDENTITY)); + } + const t = [0, 0, 0] as vec3; const r = [0, 0, 0, 1] as vec4; const s = [1, 1, 1] as vec3; @@ -608,3 +621,14 @@ function fromTransform(transform: VectorTransform): mat4 { function clamp(value: number, range: vec2): number { return Math.min(Math.max(value, range[0]), range[1]); } + +function makeArray(elementCount: number, initialElement: vec2 | vec3 | vec4) { + const elementSize = initialElement.length; + const array = new Float32Array(elementCount * elementSize); + + for (let i = 0; i < elementCount; i++) { + array.set(initialElement, i * elementSize); + } + + return array; +}