(_options: InstanceOptions = INSTANCE_DEFAULTS)
| 43 | * @category Transforms |
| 44 | */ |
| 45 | export function instance(_options: InstanceOptions = INSTANCE_DEFAULTS): Transform { |
| 46 | const options = assignDefaults(INSTANCE_DEFAULTS, _options); |
| 47 | |
| 48 | return createTransform(NAME, (doc: Document): void => { |
| 49 | const logger = doc.getLogger(); |
| 50 | const root = doc.getRoot(); |
| 51 | |
| 52 | if (root.listAnimations().length) { |
| 53 | logger.warn(`${NAME}: Instancing is not currently supported for animated models.`); |
| 54 | logger.debug(`${NAME}: Complete.`); |
| 55 | return; |
| 56 | } |
| 57 | |
| 58 | const batchExtension = doc.createExtension(EXTMeshGPUInstancing); |
| 59 | |
| 60 | let numBatches = 0; |
| 61 | let numInstances = 0; |
| 62 | |
| 63 | for (const scene of root.listScenes()) { |
| 64 | // Gather a one-to-many Mesh/Node mapping, identifying what we can instance. |
| 65 | const meshInstances = new Map<Mesh, Set<Node>>(); |
| 66 | scene.traverse((node) => { |
| 67 | const mesh = node.getMesh(); |
| 68 | if (!mesh) return; |
| 69 | if (node.getExtension('EXT_mesh_gpu_instancing')) return; |
| 70 | meshInstances.set(mesh, (meshInstances.get(mesh) || new Set<Node>()).add(node)); |
| 71 | }); |
| 72 | |
| 73 | // For each Mesh, create an InstancedMesh and collect transforms. |
| 74 | const modifiedNodes = []; |
| 75 | for (const mesh of Array.from(meshInstances.keys())) { |
| 76 | const nodes = Array.from(meshInstances.get(mesh)!); |
| 77 | if (nodes.length < options.min) continue; |
| 78 | if (nodes.some((node) => node.getSkin())) continue; |
| 79 | |
| 80 | // Cannot preserve volumetric effects when instancing with varying scale. |
| 81 | // See: https://github.com/KhronosGroup/glTF-Sample-Models/tree/master/2.0/AttenuationTest |
| 82 | if (mesh.listPrimitives().some(hasVolume) && nodes.some(hasScale)) continue; |
| 83 | |
| 84 | const batch = createBatch(doc, batchExtension, mesh, nodes.length); |
| 85 | const batchTranslation = batch.getAttribute('TRANSLATION')!; |
| 86 | const batchRotation = batch.getAttribute('ROTATION')!; |
| 87 | const batchScale = batch.getAttribute('SCALE')!; |
| 88 | |
| 89 | const batchNode = doc.createNode().setMesh(mesh).setExtension('EXT_mesh_gpu_instancing', batch); |
| 90 | scene.addChild(batchNode); |
| 91 | |
| 92 | let needsTranslation = false; |
| 93 | let needsRotation = false; |
| 94 | let needsScale = false; |
| 95 | |
| 96 | // For each Node, write TRS properties into instance attributes. |
| 97 | for (let i = 0; i < nodes.length; i++) { |
| 98 | let t: vec3, r: vec4, s: vec3; |
| 99 | const node = nodes[i]; |
| 100 | |
| 101 | batchTranslation.setElement(i, (t = node.getWorldTranslation())); |
| 102 | batchRotation.setElement(i, (r = node.getWorldRotation())); |
no test coverage detected