Browse Source

gltf instancing

Sukolsak Sakshuwong 3 years ago
parent
commit
ea54209414

+ 246 - 211
src/extensions/geo-export/glb-exporter.ts

@@ -4,9 +4,10 @@
  * @author Sukolsak Sakshuwong <sukolsak@stanford.edu>
  */
 
+import { BaseValues } from '../../mol-gl/renderable/schema';
 import { asciiWrite } from '../../mol-io/common/ascii';
 import { IsNativeEndianLittle, flipByteOrder } from '../../mol-io/common/binary';
-import { Vec3, Mat3, Mat4 } from '../../mol-math/linear-algebra';
+import { Vec3, Mat4 } from '../../mol-math/linear-algebra';
 import { RuntimeContext } from '../../mol-task';
 import { Color } from '../../mol-util/color/color';
 import { arrayMinMax, fillSerial } from '../../mol-util/array';
@@ -15,11 +16,8 @@ import { MeshExporter, AddMeshInput } from './mesh-exporter';
 
 // avoiding namespace lookup improved performance in Chrome (Aug 2020)
 const v3fromArray = Vec3.fromArray;
-const v3transformMat4 = Vec3.transformMat4;
-const v3transformMat3 = Vec3.transformMat3;
 const v3normalize = Vec3.normalize;
 const v3toArray = Vec3.toArray;
-const mat3directionTransform = Mat3.directionTransform;
 
 // https://github.com/KhronosGroup/glTF/tree/master/specification/2.0
 
@@ -29,7 +27,8 @@ export type GlbData = {
 
 export class GlbExporter extends MeshExporter<GlbData> {
     readonly fileExtension = 'glb';
-    private primitives: Record<string, any>[] = [];
+    private nodes: Record<string, any>[] = [];
+    private meshes: Record<string, any>[] = [];
     private accessors: Record<string, any>[] = [];
     private bufferViews: Record<string, any>[] = [];
     private binaryBuffer: ArrayBuffer[] = [];
@@ -47,20 +46,213 @@ export class GlbExporter extends MeshExporter<GlbData> {
         return [ min, max ];
     }
 
-    protected async addMeshWithColors(input: AddMeshInput) {
-        const { mesh, meshes, values, isGeoTexture, webgl, ctx } = input;
-
-        const t = Mat4();
-        const n = Mat3();
+    private createGeometryBuffers(vertices: Float32Array, normals: Float32Array, indices: Uint32Array | undefined, vertexCount: number, drawCount: number, isGeoTexture: boolean) {
         const tmpV = Vec3();
         const stride = isGeoTexture ? 4 : 3;
 
+        const vertexArray = new Float32Array(vertexCount * 3);
+        const normalArray = new Float32Array(vertexCount * 3);
+        let indexArray: Uint32Array;
+
+        // position
+        for (let i = 0; i < vertexCount; ++i) {
+            v3fromArray(tmpV, vertices, i * stride);
+            v3toArray(tmpV, vertexArray, i * 3);
+        }
+
+        // normal
+        for (let i = 0; i < vertexCount; ++i) {
+            v3fromArray(tmpV, normals, i * stride);
+            v3normalize(tmpV, tmpV);
+            v3toArray(tmpV, normalArray, i * 3);
+        }
+
+        // face
+        if (isGeoTexture) {
+            indexArray = new Uint32Array(drawCount);
+            fillSerial(indexArray);
+        } else {
+            indexArray = indices!.slice(0, drawCount);
+        }
+
+        const [ vertexMin, vertexMax ] = GlbExporter.vecMinMax(vertexArray, 3);
+        const [ normalMin, normalMax ] = GlbExporter.vecMinMax(normalArray, 3);
+        const [ indexMin, indexMax ] = arrayMinMax(indexArray);
+
+        // binary buffer
+        let vertexBuffer = vertexArray.buffer;
+        let normalBuffer = normalArray.buffer;
+        let indexBuffer = indexArray.buffer;
+        if (!IsNativeEndianLittle) {
+            vertexBuffer = flipByteOrder(new Uint8Array(vertexBuffer), 4);
+            normalBuffer = flipByteOrder(new Uint8Array(normalBuffer), 4);
+            indexBuffer = flipByteOrder(new Uint8Array(indexBuffer), 4);
+        }
+        this.binaryBuffer.push(vertexBuffer, normalBuffer, indexBuffer);
+
+        // buffer views
+        const bufferViewOffset = this.bufferViews.length;
+
+        this.bufferViews.push({
+            buffer: 0,
+            byteOffset: this.byteOffset,
+            byteLength: vertexBuffer.byteLength,
+            target: 34962 // ARRAY_BUFFER
+        });
+        this.byteOffset += vertexBuffer.byteLength;
+
+        this.bufferViews.push({
+            buffer: 0,
+            byteOffset: this.byteOffset,
+            byteLength: normalBuffer.byteLength,
+            target: 34962 // ARRAY_BUFFER
+        });
+        this.byteOffset += normalBuffer.byteLength;
+
+        this.bufferViews.push({
+            buffer: 0,
+            byteOffset: this.byteOffset,
+            byteLength: indexBuffer.byteLength,
+            target: 34963 // ELEMENT_ARRAY_BUFFER
+        });
+        this.byteOffset += indexBuffer.byteLength;
+
+        // accessors
+        const accessorOffset = this.accessors.length;
+        this.accessors.push({
+            bufferView: bufferViewOffset,
+            byteOffset: 0,
+            componentType: 5126, // FLOAT
+            count: vertexCount,
+            type: 'VEC3',
+            max: vertexMax,
+            min: vertexMin
+        });
+        this.accessors.push({
+            bufferView: bufferViewOffset + 1,
+            byteOffset: 0,
+            componentType: 5126, // FLOAT
+            count: vertexCount,
+            type: 'VEC3',
+            max: normalMax,
+            min: normalMin
+        });
+        this.accessors.push({
+            bufferView: bufferViewOffset + 2,
+            byteOffset: 0,
+            componentType: 5125, // UNSIGNED_INT
+            count: drawCount,
+            type: 'SCALAR',
+            max: [ indexMax ],
+            min: [ indexMin ]
+        });
+
+        return {
+            vertexAccessorIndex: accessorOffset,
+            normalAccessorIndex: accessorOffset + 1,
+            indexAccessorIndex: accessorOffset + 2
+        };
+    }
+
+    private createColorBuffer(values: BaseValues, groups: Float32Array | Uint8Array, vertexCount: number, instanceIndex: number, isGeoTexture: boolean, interpolatedColors: Uint8Array) {
         const groupCount = values.uGroupCount.ref.value;
         const colorType = values.dColorType.ref.value;
+        const uColor = values.uColor.ref.value;
         const tColor = values.tColor.ref.value.array;
         const uAlpha = values.uAlpha.ref.value;
         const dTransparency = values.dTransparency.ref.value;
         const tTransparency = values.tTransparency.ref.value;
+
+        const colorArray = new Float32Array(vertexCount * 4);
+
+        for (let i = 0; i < vertexCount; ++i) {
+            let color: Color;
+            switch (colorType) {
+                case 'uniform':
+                    color = Color.fromNormalizedArray(uColor, 0);
+                    break;
+                case 'instance':
+                    color = Color.fromArray(tColor, instanceIndex * 3);
+                    break;
+                case 'group': {
+                    const group = isGeoTexture ? GlbExporter.getGroup(groups, i) : groups[i];
+                    color = Color.fromArray(tColor, group * 3);
+                    break;
+                }
+                case 'groupInstance': {
+                    const group = isGeoTexture ? GlbExporter.getGroup(groups, i) : groups[i];
+                    color = Color.fromArray(tColor, (instanceIndex * groupCount + group) * 3);
+                    break;
+                }
+                case 'vertex':
+                    color = Color.fromArray(tColor, i * 3);
+                    break;
+                case 'vertexInstance':
+                    color = Color.fromArray(tColor, (instanceIndex * vertexCount + i) * 3);
+                    break;
+                case 'volume':
+                    color = Color.fromArray(interpolatedColors!, i * 3);
+                    break;
+                case 'volumeInstance':
+                    color = Color.fromArray(interpolatedColors!, (instanceIndex * vertexCount + i) * 3);
+                    break;
+                default: throw new Error('Unsupported color type.');
+            }
+
+            let alpha = uAlpha;
+            if (dTransparency) {
+                const group = isGeoTexture ? GlbExporter.getGroup(groups, i) : groups[i];
+                const transparency = tTransparency.array[instanceIndex * groupCount + group] / 255;
+                alpha *= 1 - transparency;
+            }
+
+            color = Color.sRGBToLinear(color);
+            Color.toArrayNormalized(color, colorArray, i * 4);
+            colorArray[i * 4 + 3] = alpha;
+        }
+
+        const [ colorMin, colorMax ] = GlbExporter.vecMinMax(colorArray, 4);
+
+        // binary buffer
+        let colorBuffer = colorArray.buffer;
+        if (!IsNativeEndianLittle) {
+            colorBuffer = flipByteOrder(new Uint8Array(colorBuffer), 4);
+        }
+        this.binaryBuffer.push(colorBuffer);
+
+        // buffer view
+        const bufferViewOffset = this.bufferViews.length;
+        this.bufferViews.push({
+            buffer: 0,
+            byteOffset: this.byteOffset,
+            byteLength: colorBuffer.byteLength,
+            target: 34962 // ARRAY_BUFFER
+        });
+        this.byteOffset += colorBuffer.byteLength;
+
+        // accessor
+        const accessorOffset = this.accessors.length;
+        this.accessors.push({
+            bufferView: bufferViewOffset,
+            byteOffset: 0,
+            componentType: 5126, // FLOAT
+            count: vertexCount,
+            type: 'VEC4',
+            max: colorMax,
+            min: colorMin
+        });
+
+        return accessorOffset;
+    }
+
+    protected async addMeshWithColors(input: AddMeshInput) {
+        const { mesh, values, isGeoTexture, webgl, ctx } = input;
+
+        const t = Mat4();
+        const stride = isGeoTexture ? 4 : 3;
+
+        const colorType = values.dColorType.ref.value;
+        const dTransparency = values.dTransparency.ref.value;
         const aTransform = values.aTransform.ref.value;
         const instanceCount = values.uInstanceCount.ref.value;
 
@@ -69,212 +261,59 @@ export class GlbExporter extends MeshExporter<GlbData> {
             interpolatedColors = GlbExporter.getInterpolatedColors(mesh!.vertices, mesh!.vertexCount, values, stride, colorType, webgl!);
         }
 
+        // instancing
+        const sameGeometryBuffers = mesh !== undefined;
+        const sameColorBuffer = sameGeometryBuffers && colorType !== 'instance' && !colorType.endsWith('Instance') && !dTransparency;
+        let vertexAccessorIndex: number;
+        let normalAccessorIndex: number;
+        let indexAccessorIndex: number;
+        let colorAccessorIndex: number;
+        let meshIndex: number;
+
         await ctx.update({ isIndeterminate: false, current: 0, max: instanceCount });
 
         for (let instanceIndex = 0; instanceIndex < instanceCount; ++instanceIndex) {
             if (ctx.shouldUpdate) await ctx.update({ current: instanceIndex + 1 });
 
-            let vertices: Float32Array;
-            let normals: Float32Array;
-            let indices: Uint32Array | undefined;
-            let groups: Float32Array | Uint8Array;
-            let vertexCount: number;
-            let drawCount: number;
-            if (mesh !== undefined) {
-                vertices = mesh.vertices;
-                normals = mesh.normals;
-                indices = mesh.indices;
-                groups = mesh.groups;
-                vertexCount = mesh.vertexCount;
-                drawCount = mesh.drawCount;
-            } else {
-                const mesh = meshes![instanceIndex];
-                vertices = mesh.vertexBuffer.ref.value;
-                normals = mesh.normalBuffer.ref.value;
-                indices = mesh.indexBuffer.ref.value;
-                groups = mesh.groupBuffer.ref.value;
-                vertexCount = mesh.vertexCount;
-                drawCount = mesh.triangleCount * 3;
-            }
-
-            Mat4.fromArray(t, aTransform, instanceIndex * 16);
-            mat3directionTransform(n, t);
-
-            const vertexArray = new Float32Array(vertexCount * 3);
-            const normalArray = new Float32Array(vertexCount * 3);
-            const colorArray = new Float32Array(vertexCount * 4);
-            let indexArray: Uint32Array;
-
-            // position
-            for (let i = 0; i < vertexCount; ++i) {
-                v3transformMat4(tmpV, v3fromArray(tmpV, vertices, i * stride), t);
-                v3toArray(tmpV, vertexArray, i * 3);
-            }
+            // create a glTF mesh if needed
+            if (instanceIndex === 0 || !sameGeometryBuffers || !sameColorBuffer) {
+                const { vertices, normals, indices, groups, vertexCount, drawCount } = GlbExporter.getInstance(input, instanceIndex);
 
-            // normal
-            for (let i = 0; i < vertexCount; ++i) {
-                v3fromArray(tmpV, normals, i * stride);
-                v3transformMat3(tmpV, v3normalize(tmpV, tmpV), n);
-                v3toArray(tmpV, normalArray, i * 3);
-            }
-
-            // color
-            for (let i = 0; i < vertexCount; ++i) {
-                let color: Color;
-                switch (colorType) {
-                    case 'uniform':
-                        color = Color.fromNormalizedArray(values.uColor.ref.value, 0);
-                        break;
-                    case 'instance':
-                        color = Color.fromArray(tColor, instanceIndex * 3);
-                        break;
-                    case 'group': {
-                        const group = isGeoTexture ? GlbExporter.getGroup(groups, i) : groups[i];
-                        color = Color.fromArray(tColor, group * 3);
-                        break;
-                    }
-                    case 'groupInstance': {
-                        const group = isGeoTexture ? GlbExporter.getGroup(groups, i) : groups[i];
-                        color = Color.fromArray(tColor, (instanceIndex * groupCount + group) * 3);
-                        break;
-                    }
-                    case 'vertex':
-                        color = Color.fromArray(tColor, i * 3);
-                        break;
-                    case 'vertexInstance':
-                        color = Color.fromArray(tColor, (instanceIndex * drawCount + i) * 3);
-                        break;
-                    case 'volume':
-                        color = Color.fromArray(interpolatedColors!, i * 3);
-                        break;
-                    case 'volumeInstance':
-                        color = Color.fromArray(interpolatedColors!, (instanceIndex * vertexCount + i) * 3);
-                        break;
-                    default: throw new Error('Unsupported color type.');
+                // create geometry buffers if needed
+                if (instanceIndex === 0 || !sameGeometryBuffers) {
+                    const accessorIndices = this.createGeometryBuffers(vertices, normals, indices, vertexCount, drawCount, isGeoTexture);
+                    vertexAccessorIndex = accessorIndices.vertexAccessorIndex;
+                    normalAccessorIndex = accessorIndices.normalAccessorIndex;
+                    indexAccessorIndex = accessorIndices.indexAccessorIndex;
                 }
 
-                let alpha = uAlpha;
-                if (dTransparency) {
-                    const group = isGeoTexture ? GlbExporter.getGroup(groups, i) : groups[i];
-                    const transparency = tTransparency.array[instanceIndex * groupCount + group] / 255;
-                    alpha *= 1 - transparency;
+                // create a color buffer if needed
+                if (instanceIndex === 0 || !sameColorBuffer) {
+                    colorAccessorIndex = this.createColorBuffer(values, groups, vertexCount, instanceIndex, isGeoTexture, interpolatedColors!);
                 }
 
-                color = Color.sRGBToLinear(color);
-                Color.toArrayNormalized(color, colorArray, i * 4);
-                colorArray[i * 4 + 3] = alpha;
+                // glTF mesh
+                meshIndex = this.meshes.length;
+                this.meshes.push({
+                    primitives: [{
+                        attributes: {
+                            POSITION: vertexAccessorIndex!,
+                            NORMAL: normalAccessorIndex!,
+                            COLOR_0: colorAccessorIndex!
+                        },
+                        indices: indexAccessorIndex!,
+                        material: 0
+                    }]
+                });
             }
 
-            // face
-            if (isGeoTexture) {
-                indexArray = new Uint32Array(drawCount);
-                fillSerial(indexArray);
-            } else {
-                indexArray = indices!.slice(0, drawCount);
-            }
-
-            const [ vertexMin, vertexMax ] = GlbExporter.vecMinMax(vertexArray, 3);
-            const [ normalMin, normalMax ] = GlbExporter.vecMinMax(normalArray, 3);
-            const [ colorMin, colorMax ] = GlbExporter.vecMinMax(colorArray, 4);
-            const [ indexMin, indexMax ] = arrayMinMax(indexArray);
-
-            // binary buffer
-            let vertexBuffer = vertexArray.buffer;
-            let normalBuffer = normalArray.buffer;
-            let colorBuffer = colorArray.buffer;
-            let indexBuffer = indexArray.buffer;
-            if (!IsNativeEndianLittle) {
-                vertexBuffer = flipByteOrder(new Uint8Array(vertexBuffer), 4);
-                normalBuffer = flipByteOrder(new Uint8Array(normalBuffer), 4);
-                colorBuffer = flipByteOrder(new Uint8Array(colorBuffer), 4);
-                indexBuffer = flipByteOrder(new Uint8Array(indexBuffer), 4);
-            }
-            this.binaryBuffer.push(vertexBuffer, normalBuffer, colorBuffer, indexBuffer);
-
-            // buffer views
-            const bufferViewOffset = this.bufferViews.length;
-
-            this.bufferViews.push({
-                buffer: 0,
-                byteOffset: this.byteOffset,
-                byteLength: vertexBuffer.byteLength,
-                target: 34962 // ARRAY_BUFFER
-            });
-            this.byteOffset += vertexBuffer.byteLength;
-
-            this.bufferViews.push({
-                buffer: 0,
-                byteOffset: this.byteOffset,
-                byteLength: normalBuffer.byteLength,
-                target: 34962 // ARRAY_BUFFER
-            });
-            this.byteOffset += normalBuffer.byteLength;
-
-            this.bufferViews.push({
-                buffer: 0,
-                byteOffset: this.byteOffset,
-                byteLength: colorBuffer.byteLength,
-                target: 34962 // ARRAY_BUFFER
-            });
-            this.byteOffset += colorBuffer.byteLength;
-
-            this.bufferViews.push({
-                buffer: 0,
-                byteOffset: this.byteOffset,
-                byteLength: indexBuffer.byteLength,
-                target: 34963 // ELEMENT_ARRAY_BUFFER
-            });
-            this.byteOffset += indexBuffer.byteLength;
-
-            // accessors
-            const accessorOffset = this.accessors.length;
-            this.accessors.push({
-                bufferView: bufferViewOffset,
-                byteOffset: 0,
-                componentType: 5126, // FLOAT
-                count: vertexCount,
-                type: 'VEC3',
-                max: vertexMax,
-                min: vertexMin
-            });
-            this.accessors.push({
-                bufferView: bufferViewOffset + 1,
-                byteOffset: 0,
-                componentType: 5126, // FLOAT
-                count: vertexCount,
-                type: 'VEC3',
-                max: normalMax,
-                min: normalMin
-            });
-            this.accessors.push({
-                bufferView: bufferViewOffset + 2,
-                byteOffset: 0,
-                componentType: 5126, // FLOAT
-                count: vertexCount,
-                type: 'VEC4',
-                max: colorMax,
-                min: colorMin
-            });
-            this.accessors.push({
-                bufferView: bufferViewOffset + 3,
-                byteOffset: 0,
-                componentType: 5125, // UNSIGNED_INT
-                count: drawCount,
-                type: 'SCALAR',
-                max: [ indexMax ],
-                min: [ indexMin ]
-            });
-
-            // primitive
-            this.primitives.push({
-                attributes: {
-                    POSITION: accessorOffset,
-                    NORMAL: accessorOffset + 1,
-                    COLOR_0: accessorOffset + 2,
-                },
-                indices: accessorOffset + 3,
-                material: 0
-            });
+            // node
+            const node: Record<string, any> = {
+                mesh: meshIndex!
+            };
+            Mat4.fromArray(t, aTransform, instanceIndex * 16);
+            if (!Mat4.isIdentity(t)) node.matrix = t.slice();
+            this.nodes.push(node);
         }
     }
 
@@ -286,14 +325,10 @@ export class GlbExporter extends MeshExporter<GlbData> {
                 version: '2.0'
             },
             scenes: [{
-                nodes: [ 0 ]
-            }],
-            nodes: [{
-                mesh: 0
-            }],
-            meshes: [{
-                primitives: this.primitives
+                nodes: fillSerial(new Array(this.nodes.length) as number[])
             }],
+            nodes: this.nodes,
+            meshes: this.meshes,
             buffers: [{
                 byteLength: binaryBufferLength,
             }],

+ 28 - 1
src/extensions/geo-export/mesh-exporter.ts

@@ -111,7 +111,34 @@ export abstract class MeshExporter<D extends RenderObjectExportData> implements
         return interpolated.array;
     }
 
-    protected abstract addMeshWithColors(inpit: AddMeshInput): void;
+    protected static getInstance(input: AddMeshInput, instanceIndex: number) {
+        const { mesh, meshes } = input;
+        let vertices: Float32Array;
+        let normals: Float32Array;
+        let indices: Uint32Array | undefined;
+        let groups: Float32Array | Uint8Array;
+        let vertexCount: number;
+        let drawCount: number;
+        if (mesh !== undefined) {
+            vertices = mesh.vertices;
+            normals = mesh.normals;
+            indices = mesh.indices;
+            groups = mesh.groups;
+            vertexCount = mesh.vertexCount;
+            drawCount = mesh.drawCount;
+        } else {
+            const mesh = meshes![instanceIndex];
+            vertices = mesh.vertexBuffer.ref.value;
+            normals = mesh.normalBuffer.ref.value;
+            indices = mesh.indexBuffer.ref.value;
+            groups = mesh.groupBuffer.ref.value;
+            vertexCount = mesh.vertexCount;
+            drawCount = mesh.triangleCount * 3;
+        }
+        return { vertices, normals, indices, groups, vertexCount, drawCount };
+    }
+
+    protected abstract addMeshWithColors(input: AddMeshInput): void;
 
     private async addMesh(values: MeshValues, webgl: WebGLContext, ctx: RuntimeContext) {
         const aPosition = values.aPosition.ref.value;

+ 3 - 24
src/extensions/geo-export/obj-exporter.ts

@@ -132,7 +132,7 @@ export class ObjExporter extends MeshExporter<ObjData> {
     }
 
     protected async addMeshWithColors(input: AddMeshInput) {
-        const { mesh, meshes, values, isGeoTexture, webgl, ctx } = input;
+        const { mesh, values, isGeoTexture, webgl, ctx } = input;
 
         const obj = this.obj;
         const t = Mat4();
@@ -160,28 +160,7 @@ export class ObjExporter extends MeshExporter<ObjData> {
         for (let instanceIndex = 0; instanceIndex < instanceCount; ++instanceIndex) {
             if (ctx.shouldUpdate) await ctx.update({ current: instanceIndex + 1 });
 
-            let vertices: Float32Array;
-            let normals: Float32Array;
-            let indices: Uint32Array | undefined;
-            let groups: Float32Array | Uint8Array;
-            let vertexCount: number;
-            let drawCount: number;
-            if (mesh !== undefined) {
-                vertices = mesh.vertices;
-                normals = mesh.normals;
-                indices = mesh.indices;
-                groups = mesh.groups;
-                vertexCount = mesh.vertexCount;
-                drawCount = mesh.drawCount;
-            } else {
-                const mesh = meshes![instanceIndex];
-                vertices = mesh.vertexBuffer.ref.value;
-                normals = mesh.normalBuffer.ref.value;
-                indices = mesh.indexBuffer.ref.value;
-                groups = mesh.groupBuffer.ref.value;
-                vertexCount = mesh.vertexCount;
-                drawCount = mesh.triangleCount * 3;
-            }
+            const { vertices, normals, indices, groups, vertexCount, drawCount } = ObjExporter.getInstance(input, instanceIndex);
 
             Mat4.fromArray(t, aTransform, instanceIndex * 16);
             mat3directionTransform(n, t);
@@ -234,7 +213,7 @@ export class ObjExporter extends MeshExporter<ObjData> {
                         color = Color.fromArray(tColor, indices![i] * 3);
                         break;
                     case 'vertexInstance':
-                        color = Color.fromArray(tColor, (instanceIndex * drawCount + indices![i]) * 3);
+                        color = Color.fromArray(tColor, (instanceIndex * vertexCount + indices![i]) * 3);
                         break;
                     case 'volume':
                         color = Color.fromArray(interpolatedColors!, (isGeoTexture ? i : indices![i]) * 3);

+ 3 - 18
src/extensions/geo-export/stl-exporter.ts

@@ -28,7 +28,7 @@ export class StlExporter extends MeshExporter<StlData> {
     private triangleCount = 0;
 
     protected async addMeshWithColors(input: AddMeshInput) {
-        const { mesh, meshes, values, isGeoTexture, ctx } = input;
+        const { values, isGeoTexture, ctx } = input;
 
         const t = Mat4();
         const tmpV = Vec3();
@@ -37,29 +37,14 @@ export class StlExporter extends MeshExporter<StlData> {
         const v3 = Vec3();
         const stride = isGeoTexture ? 4 : 3;
 
+        const aTransform = values.aTransform.ref.value;
         const instanceCount = values.uInstanceCount.ref.value;
 
         for (let instanceIndex = 0; instanceIndex < instanceCount; ++instanceIndex) {
             if (ctx.shouldUpdate) await ctx.update({ current: instanceIndex + 1 });
 
-            let vertices: Float32Array;
-            let indices: Uint32Array | undefined;
-            let vertexCount: number;
-            let drawCount: number;
-            if (mesh !== undefined) {
-                vertices = mesh.vertices;
-                indices = mesh.indices;
-                vertexCount = mesh.vertexCount;
-                drawCount = mesh.drawCount;
-            } else {
-                const mesh = meshes![instanceIndex];
-                vertices = mesh.vertexBuffer.ref.value;
-                indices = mesh.indexBuffer.ref.value;
-                vertexCount = mesh.vertexCount;
-                drawCount = mesh.triangleCount * 3;
-            }
+            const { vertices, indices, vertexCount, drawCount } = StlExporter.getInstance(input, instanceIndex);
 
-            const aTransform = values.aTransform.ref.value;
             Mat4.fromArray(t, aTransform, instanceIndex * 16);
 
             // position