Browse Source

Merge pull request #192 from sukolsak/gltf-instancing

Geometry export: use instancing in GLB
Alexander Rose 3 years ago
parent
commit
1e35ea15eb

+ 196 - 217
src/extensions/geo-export/glb-exporter.ts

@@ -4,42 +4,48 @@
  * @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 { PLUGIN_VERSION } from '../../mol-plugin/version';
 import { RuntimeContext } from '../../mol-task';
 import { Color } from '../../mol-util/color/color';
-import { arrayMinMax, fillSerial } from '../../mol-util/array';
+import { fillSerial } from '../../mol-util/array';
 import { NumberArray } from '../../mol-util/type-helpers';
 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
 
+const UNSIGNED_BYTE = 5121;
+const UNSIGNED_INT = 5125;
+const FLOAT = 5126;
+const ARRAY_BUFFER = 34962;
+const ELEMENT_ARRAY_BUFFER = 34963;
+
 export type GlbData = {
     glb: Uint8Array
 }
 
 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[] = [];
     private byteOffset = 0;
 
-    private static vecMinMax(a: NumberArray, length: number) {
-        const min: number[] = (new Array(length)).fill(Infinity);
-        const max: number[] = (new Array(length)).fill(-Infinity);
-        for (let i = 0, il = a.length; i < il; i += length) {
-            for (let j = 0; j < length; ++j) {
+    private static vec3MinMax(a: NumberArray) {
+        const min: number[] = [Infinity, Infinity, Infinity];
+        const max: number[] = [-Infinity, -Infinity, -Infinity];
+        for (let i = 0, il = a.length; i < il; i += 3) {
+            for (let j = 0; j < 3; ++j) {
                 min[j] = Math.min(a[i + j], min[j]);
                 max[j] = Math.max(a[i + j], max[j]);
             }
@@ -47,234 +53,210 @@ export class GlbExporter extends MeshExporter<GlbData> {
         return [ min, max ];
     }
 
-    protected async addMeshWithColors(input: AddMeshInput) {
-        const { mesh, meshes, values, isGeoTexture, webgl, ctx } = input;
+    private addBuffer(buffer: ArrayBuffer, componentType: number, type: string, count: number, target: number, min?: any, max?: any, normalized?: boolean) {
+        this.binaryBuffer.push(buffer);
+
+        const bufferViewOffset = this.bufferViews.length;
+        this.bufferViews.push({
+            buffer: 0,
+            byteOffset: this.byteOffset,
+            byteLength: buffer.byteLength,
+            target
+        });
+        this.byteOffset += buffer.byteLength;
+
+        const accessorOffset = this.accessors.length;
+        this.accessors.push({
+            bufferView: bufferViewOffset,
+            byteOffset: 0,
+            componentType,
+            count,
+            type,
+            min,
+            max,
+            normalized
+        });
+        return accessorOffset;
+    }
 
-        const t = Mat4();
-        const n = Mat3();
+    private addGeometryBuffers(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 | undefined;
+
+        // 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 = indices!.slice(0, drawCount);
+        }
+
+        const [ vertexMin, vertexMax ] = GlbExporter.vec3MinMax(vertexArray);
+
+        let vertexBuffer = vertexArray.buffer;
+        let normalBuffer = normalArray.buffer;
+        let indexBuffer = isGeoTexture ? undefined : indexArray!.buffer;
+        if (!IsNativeEndianLittle) {
+            vertexBuffer = flipByteOrder(new Uint8Array(vertexBuffer), 4);
+            normalBuffer = flipByteOrder(new Uint8Array(normalBuffer), 4);
+            if (!isGeoTexture) indexBuffer = flipByteOrder(new Uint8Array(indexBuffer!), 4);
+        }
+
+        return {
+            vertexAccessorIndex: this.addBuffer(vertexBuffer, FLOAT, 'VEC3', vertexCount, ARRAY_BUFFER, vertexMin, vertexMax),
+            normalAccessorIndex: this.addBuffer(normalBuffer, FLOAT, 'VEC3', vertexCount, ARRAY_BUFFER),
+            indexAccessorIndex: isGeoTexture ? undefined : this.addBuffer(indexBuffer!, UNSIGNED_INT, 'SCALAR', drawCount, ELEMENT_ARRAY_BUFFER)
+        };
+    }
+
+    private addColorBuffer(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 Uint8Array(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.toArray(color, colorArray, i * 4);
+            colorArray[i * 4 + 3] = Math.round(alpha * 255);
+        }
+
+        let colorBuffer = colorArray.buffer;
+        if (!IsNativeEndianLittle) {
+            colorBuffer = flipByteOrder(new Uint8Array(colorBuffer), 4);
+        }
+
+        return this.addBuffer(colorBuffer, UNSIGNED_BYTE, 'VEC4', vertexCount, ARRAY_BUFFER, undefined, undefined, true);
+    }
+
+    protected async addMeshWithColors(input: AddMeshInput) {
+        const { mesh, values, isGeoTexture, webgl, ctx } = input;
+
+        const t = Mat4();
+
+        const colorType = values.dColorType.ref.value;
+        const dTransparency = values.dTransparency.ref.value;
         const aTransform = values.aTransform.ref.value;
         const instanceCount = values.uInstanceCount.ref.value;
 
         let interpolatedColors: Uint8Array;
         if (colorType === 'volume' || colorType === 'volumeInstance') {
+            const stride = isGeoTexture ? 4 : 3;
             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 | undefined;
+        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;
-            }
+            // create a glTF mesh if needed
+            if (instanceIndex === 0 || !sameGeometryBuffers || !sameColorBuffer) {
+                const { vertices, normals, indices, groups, vertexCount, drawCount } = GlbExporter.getInstance(input, instanceIndex);
 
-            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);
-            }
-
-            // 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.addGeometryBuffers(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.addColorBuffer(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);
         }
     }
 
@@ -283,17 +265,14 @@ export class GlbExporter extends MeshExporter<GlbData> {
 
         const gltf = {
             asset: {
-                version: '2.0'
+                version: '2.0',
+                generator: `Mol* ${PLUGIN_VERSION}`
             },
             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,
             }],

+ 19 - 2
src/extensions/geo-export/mesh-exporter.ts

@@ -100,7 +100,7 @@ export abstract class MeshExporter<D extends RenderObjectExportData> implements
         }
         const framebuffer = webgl.namedFramebuffers[GeoExportName];
 
-        const [ width, height ] = values.uColorTexDim.ref.value;
+        const [ width, height ] = colorTexDim;
         const colorGrid = new Uint8Array(width * height * 4);
 
         framebuffer.bind();
@@ -111,7 +111,24 @@ 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;
+        if (mesh !== undefined) {
+            return mesh;
+        } else {
+            const mesh = meshes![instanceIndex];
+            return {
+                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
+            };
+        }
+    }
+
+    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

+ 2 - 2
src/mol-repr/structure/visual/gaussian-surface-mesh.ts

@@ -96,7 +96,7 @@ async function createGaussianSurfaceMesh(ctx: VisualContext, unit: Unit, structu
     (surface.meta as GaussianSurfaceMeta) = { resolution };
 
     Mesh.transform(surface, transform);
-    if (ctx.webgl && !ctx.webgl.isWebGL2) Mesh.uniformTriangleGroup(surface);
+    if (ctx.webgl && !ctx.webgl.isWebGL2) Mesh.uniformTriangleGroup(surface, false);
 
     const sphere = Sphere3D.expand(Sphere3D(), unit.boundary.sphere, props.radiusOffset + getUnitExtraRadius(unit));
     surface.setBoundingSphere(sphere);
@@ -157,7 +157,7 @@ async function createStructureGaussianSurfaceMesh(ctx: VisualContext, structure:
     (surface.meta as GaussianSurfaceMeta) = { resolution };
 
     Mesh.transform(surface, transform);
-    if (ctx.webgl && !ctx.webgl.isWebGL2) Mesh.uniformTriangleGroup(surface);
+    if (ctx.webgl && !ctx.webgl.isWebGL2) Mesh.uniformTriangleGroup(surface, false);
 
     const sphere = Sphere3D.expand(Sphere3D(), structure.boundary.sphere, props.radiusOffset + getStructureExtraRadius(structure));
     surface.setBoundingSphere(sphere);

+ 1 - 1
src/mol-repr/structure/visual/molecular-surface-mesh.ts

@@ -48,7 +48,7 @@ async function createMolecularSurfaceMesh(ctx: VisualContext, unit: Unit, struct
     const surface = await computeMarchingCubesMesh(params, mesh).runAsChild(ctx.runtime);
 
     Mesh.transform(surface, transform);
-    if (ctx.webgl && !ctx.webgl.isWebGL2) Mesh.uniformTriangleGroup(surface);
+    if (ctx.webgl && !ctx.webgl.isWebGL2) Mesh.uniformTriangleGroup(surface, false);
 
     const sphere = Sphere3D.expand(Sphere3D(), unit.boundary.sphere, props.probeRadius + getUnitExtraRadius(unit));
     surface.setBoundingSphere(sphere);