瀏覽代碼

support volume & volumeInstance color type in geo exporters

Sukolsak Sakshuwong 3 年之前
父節點
當前提交
c68306125e

+ 203 - 168
src/extensions/geo-export/glb-exporter.ts

@@ -4,7 +4,6 @@
  * @author Sukolsak Sakshuwong <sukolsak@stanford.edu>
  * @author Sukolsak Sakshuwong <sukolsak@stanford.edu>
  */
  */
 
 
-import { BaseValues } from '../../mol-gl/renderable/schema';
 import { asciiWrite } from '../../mol-io/common/ascii';
 import { asciiWrite } from '../../mol-io/common/ascii';
 import { IsNativeEndianLittle, flipByteOrder } from '../../mol-io/common/binary';
 import { IsNativeEndianLittle, flipByteOrder } from '../../mol-io/common/binary';
 import { Vec3, Mat3, Mat4 } from '../../mol-math/linear-algebra';
 import { Vec3, Mat3, Mat4 } from '../../mol-math/linear-algebra';
@@ -12,7 +11,7 @@ import { RuntimeContext } from '../../mol-task';
 import { Color } from '../../mol-util/color/color';
 import { Color } from '../../mol-util/color/color';
 import { arrayMinMax, fillSerial } from '../../mol-util/array';
 import { arrayMinMax, fillSerial } from '../../mol-util/array';
 import { NumberArray } from '../../mol-util/type-helpers';
 import { NumberArray } from '../../mol-util/type-helpers';
-import { MeshExporter } from './mesh-exporter';
+import { MeshExporter, AddMeshInput } from './mesh-exporter';
 
 
 // avoiding namespace lookup improved performance in Chrome (Aug 2020)
 // avoiding namespace lookup improved performance in Chrome (Aug 2020)
 const v3fromArray = Vec3.fromArray;
 const v3fromArray = Vec3.fromArray;
@@ -48,7 +47,9 @@ export class GlbExporter extends MeshExporter<GlbData> {
         return [ min, max ];
         return [ min, max ];
     }
     }
 
 
-    protected async addMeshWithColors(vertices: Float32Array, normals: Float32Array, indices: Uint32Array | undefined, groups: Float32Array | Uint8Array, vertexCount: number, drawCount: number, values: BaseValues, instanceIndex: number, isGeoTexture: boolean, ctx: RuntimeContext) {
+    protected async addMeshWithColors(input: AddMeshInput) {
+        const { mesh, meshes, values, isGeoTexture, webgl, ctx } = input;
+
         const t = Mat4();
         const t = Mat4();
         const n = Mat3();
         const n = Mat3();
         const tmpV = Vec3();
         const tmpV = Vec3();
@@ -61,185 +62,219 @@ export class GlbExporter extends MeshExporter<GlbData> {
         const dTransparency = values.dTransparency.ref.value;
         const dTransparency = values.dTransparency.ref.value;
         const tTransparency = values.tTransparency.ref.value;
         const tTransparency = values.tTransparency.ref.value;
         const aTransform = values.aTransform.ref.value;
         const aTransform = values.aTransform.ref.value;
+        const instanceCount = values.uInstanceCount.ref.value;
 
 
-        Mat4.fromArray(t, aTransform, instanceIndex * 16);
-        mat3directionTransform(n, t);
+        let interpolatedColors: Uint8Array;
+        if (colorType === 'volume' || colorType === 'volumeInstance') {
+            interpolatedColors = GlbExporter.getInterpolatedColors(mesh!.vertices, mesh!.vertexCount, values, stride, colorType, webgl!);
+        }
 
 
-        const currentProgress = (vertexCount * 3) * instanceIndex;
-        await ctx.update({ isIndeterminate: false, current: currentProgress, max: (vertexCount * 3) * values.uInstanceCount.ref.value });
+        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 = indices.length;
+            }
 
 
-        const vertexArray = new Float32Array(vertexCount * 3);
-        const normalArray = new Float32Array(vertexCount * 3);
-        const colorArray = new Float32Array(vertexCount * 4);
-        let indexArray: Uint32Array;
+            Mat4.fromArray(t, aTransform, instanceIndex * 16);
+            mat3directionTransform(n, t);
 
 
-        // position
-        for (let i = 0; i < vertexCount; ++i) {
-            if (i % 1000 === 0 && ctx.shouldUpdate) await ctx.update({ current: currentProgress + i });
-            v3transformMat4(tmpV, v3fromArray(tmpV, vertices, i * stride), t);
-            v3toArray(tmpV, vertexArray, i * 3);
-        }
+            const vertexArray = new Float32Array(vertexCount * 3);
+            const normalArray = new Float32Array(vertexCount * 3);
+            const colorArray = new Float32Array(vertexCount * 4);
+            let indexArray: Uint32Array;
 
 
-        // normal
-        for (let i = 0; i < vertexCount; ++i) {
-            if (i % 1000 === 0 && ctx.shouldUpdate) await ctx.update({ current: currentProgress + vertexCount + i });
-            v3fromArray(tmpV, normals, i * stride);
-            v3transformMat3(tmpV, v3normalize(tmpV, tmpV), n);
-            v3toArray(tmpV, normalArray, i * 3);
-        }
+            // position
+            for (let i = 0; i < vertexCount; ++i) {
+                v3transformMat4(tmpV, v3fromArray(tmpV, vertices, i * stride), t);
+                v3toArray(tmpV, vertexArray, i * 3);
+            }
 
 
-        // color
-        for (let i = 0; i < vertexCount; ++i) {
-            if (i % 1000 === 0 && ctx.shouldUpdate) await ctx.update({ current: currentProgress + vertexCount * 2 + 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;
+            // 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.');
                 }
                 }
-                case 'groupInstance': {
+
+                let alpha = uAlpha;
+                if (dTransparency) {
                     const group = isGeoTexture ? GlbExporter.getGroup(groups, i) : groups[i];
                     const group = isGeoTexture ? GlbExporter.getGroup(groups, i) : groups[i];
-                    color = Color.fromArray(tColor, (instanceIndex * groupCount + group) * 3);
-                    break;
+                    const transparency = tTransparency.array[instanceIndex * groupCount + group] / 255;
+                    alpha *= 1 - transparency;
                 }
                 }
-                case 'vertex':
-                    color = Color.fromArray(tColor, i * 3);
-                    break;
-                case 'vertexInstance':
-                    color = Color.fromArray(tColor, (instanceIndex * drawCount + 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.toArrayNormalized(color, colorArray, i * 4);
+                colorArray[i * 4 + 3] = alpha;
             }
             }
 
 
-            Color.toArrayNormalized(color, colorArray, i * 4);
-            colorArray[i * 4 + 3] = alpha;
-        }
-
-        // face
-        if (isGeoTexture) {
-            indexArray = new Uint32Array(drawCount);
-            fillSerial(indexArray);
-        } else {
-            indexArray = indices!.slice(0, drawCount);
-        }
+            // 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);
+            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
+            });
         }
         }
-        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
-        });
     }
     }
 
 
     getData() {
     getData() {

+ 64 - 32
src/extensions/geo-export/mesh-exporter.ts

@@ -14,6 +14,8 @@ import { TextureMeshValues } from '../../mol-gl/renderable/texture-mesh';
 import { BaseValues, SizeValues } from '../../mol-gl/renderable/schema';
 import { BaseValues, SizeValues } from '../../mol-gl/renderable/schema';
 import { TextureImage } from '../../mol-gl/renderable/util';
 import { TextureImage } from '../../mol-gl/renderable/util';
 import { WebGLContext } from '../../mol-gl/webgl/context';
 import { WebGLContext } from '../../mol-gl/webgl/context';
+import { getTrilinearlyInterpolated } from '../../mol-geo/geometry/mesh/color-smoothing';
+import { Mesh } from '../../mol-geo/geometry/mesh/mesh';
 import { MeshBuilder } from '../../mol-geo/geometry/mesh/mesh-builder';
 import { MeshBuilder } from '../../mol-geo/geometry/mesh/mesh-builder';
 import { addSphere } from '../../mol-geo/geometry/mesh/builder/sphere';
 import { addSphere } from '../../mol-geo/geometry/mesh/builder/sphere';
 import { addCylinder } from '../../mol-geo/geometry/mesh/builder/cylinder';
 import { addCylinder } from '../../mol-geo/geometry/mesh/builder/cylinder';
@@ -23,9 +25,27 @@ import { RuntimeContext } from '../../mol-task';
 import { decodeFloatRGB } from '../../mol-util/float-packing';
 import { decodeFloatRGB } from '../../mol-util/float-packing';
 import { RenderObjectExporter, RenderObjectExportData } from './render-object-exporter';
 import { RenderObjectExporter, RenderObjectExportData } from './render-object-exporter';
 
 
+const GeoExportName = 'geo-export';
+
 // avoiding namespace lookup improved performance in Chrome (Aug 2020)
 // avoiding namespace lookup improved performance in Chrome (Aug 2020)
 const v3fromArray = Vec3.fromArray;
 const v3fromArray = Vec3.fromArray;
 
 
+export interface AddMeshInput {
+    mesh: {
+        vertices: Float32Array
+        normals: Float32Array
+        indices: Uint32Array | undefined
+        groups: Float32Array | Uint8Array
+        vertexCount: number
+        drawCount: number
+    } | undefined
+    meshes: Mesh[] | undefined
+    values: BaseValues
+    isGeoTexture: boolean
+    webgl: WebGLContext | undefined
+    ctx: RuntimeContext
+}
+
 export abstract class MeshExporter<D extends RenderObjectExportData> implements RenderObjectExporter<D> {
 export abstract class MeshExporter<D extends RenderObjectExportData> implements RenderObjectExporter<D> {
     abstract readonly fileExtension: string;
     abstract readonly fileExtension: string;
 
 
@@ -68,37 +88,58 @@ export abstract class MeshExporter<D extends RenderObjectExportData> implements
         return decodeFloatRGB(r, g, b);
         return decodeFloatRGB(r, g, b);
     }
     }
 
 
-    protected abstract addMeshWithColors(vertices: Float32Array, normals: Float32Array, indices: Uint32Array | undefined, groups: Float32Array | Uint8Array, vertexCount: number, drawCount: number, values: BaseValues, instanceIndex: number, isGeoTexture: boolean, ctx: RuntimeContext): void;
+    protected static getInterpolatedColors(vertices: Float32Array, vertexCount: number, values: BaseValues, stride: number, colorType: 'volume' | 'volumeInstance', webgl: WebGLContext) {
+        const colorGridTransform = values.uColorGridTransform.ref.value;
+        const colorGridDim = values.uColorGridDim.ref.value;
+        const colorTexDim = values.uColorTexDim.ref.value;
+        const aTransform = values.aTransform.ref.value;
+        const instanceCount = values.uInstanceCount.ref.value;
 
 
-    private async addMesh(values: MeshValues, ctx: RuntimeContext) {
+        if (!webgl.namedFramebuffers[GeoExportName]) {
+            webgl.namedFramebuffers[GeoExportName] = webgl.resources.framebuffer();
+        }
+        const framebuffer = webgl.namedFramebuffers[GeoExportName];
+
+        const [ width, height ] = values.uColorTexDim.ref.value;
+        const colorGrid = new Uint8Array(width * height * 4);
+
+        framebuffer.bind();
+        values.tColorGrid.ref.value.attachFramebuffer(framebuffer, 0);
+        webgl.readPixels(0, 0, width, height, colorGrid);
+
+        const interpolated = getTrilinearlyInterpolated({ vertexCount, instanceCount, transformBuffer: aTransform, positionBuffer: vertices, colorType, grid: colorGrid, gridDim: colorGridDim, gridTexDim: colorTexDim, gridTransform: colorGridTransform, vertexStride: stride, colorStride: 4 });
+        return interpolated.array;
+    }
+
+    protected abstract addMeshWithColors(inpit: AddMeshInput): void;
+
+    private async addMesh(values: MeshValues, webgl: WebGLContext, ctx: RuntimeContext) {
         const aPosition = values.aPosition.ref.value;
         const aPosition = values.aPosition.ref.value;
         const aNormal = values.aNormal.ref.value;
         const aNormal = values.aNormal.ref.value;
         const elements = values.elements.ref.value;
         const elements = values.elements.ref.value;
         const aGroup = values.aGroup.ref.value;
         const aGroup = values.aGroup.ref.value;
-        const instanceCount = values.instanceCount.ref.value;
         const vertexCount = values.uVertexCount.ref.value;
         const vertexCount = values.uVertexCount.ref.value;
         const drawCount = values.drawCount.ref.value;
         const drawCount = values.drawCount.ref.value;
 
 
-        for (let instanceIndex = 0; instanceIndex < instanceCount; ++instanceIndex) {
-            await this.addMeshWithColors(aPosition, aNormal, elements, aGroup, vertexCount, drawCount, values, instanceIndex, false, ctx);
-        }
+        await this.addMeshWithColors({ mesh: { vertices: aPosition, normals: aNormal, indices: elements, groups: aGroup, vertexCount, drawCount }, meshes: undefined, values, isGeoTexture: false, webgl, ctx });
     }
     }
 
 
-    private async addLines(values: LinesValues, ctx: RuntimeContext) {
+    private async addLines(values: LinesValues, webgl: WebGLContext, ctx: RuntimeContext) {
         // TODO
         // TODO
     }
     }
 
 
-    private async addPoints(values: PointsValues, ctx: RuntimeContext) {
+    private async addPoints(values: PointsValues, webgl: WebGLContext, ctx: RuntimeContext) {
         // TODO
         // TODO
     }
     }
 
 
-    private async addSpheres(values: SpheresValues, ctx: RuntimeContext) {
+    private async addSpheres(values: SpheresValues, webgl: WebGLContext, ctx: RuntimeContext) {
         const center = Vec3();
         const center = Vec3();
 
 
         const aPosition = values.aPosition.ref.value;
         const aPosition = values.aPosition.ref.value;
         const aGroup = values.aGroup.ref.value;
         const aGroup = values.aGroup.ref.value;
         const instanceCount = values.instanceCount.ref.value;
         const instanceCount = values.instanceCount.ref.value;
         const vertexCount = values.uVertexCount.ref.value;
         const vertexCount = values.uVertexCount.ref.value;
+        const meshes: Mesh[] = [];
 
 
         for (let instanceIndex = 0; instanceIndex < instanceCount; ++instanceIndex) {
         for (let instanceIndex = 0; instanceIndex < instanceCount; ++instanceIndex) {
             const state = MeshBuilder.createState(512, 256);
             const state = MeshBuilder.createState(512, 256);
@@ -112,16 +153,13 @@ export abstract class MeshExporter<D extends RenderObjectExportData> implements
                 addSphere(state, center, radius, 2);
                 addSphere(state, center, radius, 2);
             }
             }
 
 
-            const mesh = MeshBuilder.getMesh(state);
-            const vertices = mesh.vertexBuffer.ref.value;
-            const normals = mesh.normalBuffer.ref.value;
-            const indices = mesh.indexBuffer.ref.value;
-            const groups = mesh.groupBuffer.ref.value;
-            await this.addMeshWithColors(vertices, normals, indices, groups, mesh.vertexCount, indices.length, values, instanceIndex, false, ctx);
+            meshes.push(MeshBuilder.getMesh(state));
         }
         }
+
+        await this.addMeshWithColors({ mesh: undefined, meshes, values, isGeoTexture: false, webgl, ctx });
     }
     }
 
 
-    private async addCylinders(values: CylindersValues, ctx: RuntimeContext) {
+    private async addCylinders(values: CylindersValues, webgl: WebGLContext, ctx: RuntimeContext) {
         const start = Vec3();
         const start = Vec3();
         const end = Vec3();
         const end = Vec3();
 
 
@@ -132,6 +170,7 @@ export abstract class MeshExporter<D extends RenderObjectExportData> implements
         const aGroup = values.aGroup.ref.value;
         const aGroup = values.aGroup.ref.value;
         const instanceCount = values.instanceCount.ref.value;
         const instanceCount = values.instanceCount.ref.value;
         const vertexCount = values.uVertexCount.ref.value;
         const vertexCount = values.uVertexCount.ref.value;
+        const meshes: Mesh[] = [];
 
 
         for (let instanceIndex = 0; instanceIndex < instanceCount; ++instanceIndex) {
         for (let instanceIndex = 0; instanceIndex < instanceCount; ++instanceIndex) {
             const state = MeshBuilder.createState(512, 256);
             const state = MeshBuilder.createState(512, 256);
@@ -150,17 +189,13 @@ export abstract class MeshExporter<D extends RenderObjectExportData> implements
                 addCylinder(state, start, end, 1, cylinderProps);
                 addCylinder(state, start, end, 1, cylinderProps);
             }
             }
 
 
-            const mesh = MeshBuilder.getMesh(state);
-            const vertices = mesh.vertexBuffer.ref.value;
-            const normals = mesh.normalBuffer.ref.value;
-            const indices = mesh.indexBuffer.ref.value;
-            const groups = mesh.groupBuffer.ref.value;
-            await this.addMeshWithColors(vertices, normals, indices, groups, mesh.vertexCount, indices.length, values, instanceIndex, false, ctx);
+            meshes.push(MeshBuilder.getMesh(state));
         }
         }
+
+        await this.addMeshWithColors({ mesh: undefined, meshes, values, isGeoTexture: false, webgl, ctx });
     }
     }
 
 
     private async addTextureMesh(values: TextureMeshValues, webgl: WebGLContext, ctx: RuntimeContext) {
     private async addTextureMesh(values: TextureMeshValues, webgl: WebGLContext, ctx: RuntimeContext) {
-        const GeoExportName = 'geo-export';
         if (!webgl.namedFramebuffers[GeoExportName]) {
         if (!webgl.namedFramebuffers[GeoExportName]) {
             webgl.namedFramebuffers[GeoExportName] = webgl.resources.framebuffer();
             webgl.namedFramebuffers[GeoExportName] = webgl.resources.framebuffer();
         }
         }
@@ -180,12 +215,9 @@ export abstract class MeshExporter<D extends RenderObjectExportData> implements
         webgl.readPixels(0, 0, width, height, groups);
         webgl.readPixels(0, 0, width, height, groups);
 
 
         const vertexCount = values.uVertexCount.ref.value;
         const vertexCount = values.uVertexCount.ref.value;
-        const instanceCount = values.instanceCount.ref.value;
         const drawCount = values.drawCount.ref.value;
         const drawCount = values.drawCount.ref.value;
 
 
-        for (let instanceIndex = 0; instanceIndex < instanceCount; ++instanceIndex) {
-            await this.addMeshWithColors(vertices, normals, undefined, groups, vertexCount, drawCount, values, instanceIndex, true, ctx);
-        }
+        await this.addMeshWithColors({ mesh: { vertices, normals, indices: undefined, groups, vertexCount, drawCount }, meshes: undefined, values, isGeoTexture: true, webgl, ctx });
     }
     }
 
 
     add(renderObject: GraphicsRenderObject, webgl: WebGLContext, ctx: RuntimeContext) {
     add(renderObject: GraphicsRenderObject, webgl: WebGLContext, ctx: RuntimeContext) {
@@ -193,15 +225,15 @@ export abstract class MeshExporter<D extends RenderObjectExportData> implements
 
 
         switch (renderObject.type) {
         switch (renderObject.type) {
             case 'mesh':
             case 'mesh':
-                return this.addMesh(renderObject.values as MeshValues, ctx);
+                return this.addMesh(renderObject.values as MeshValues, webgl, ctx);
             case 'lines':
             case 'lines':
-                return this.addLines(renderObject.values as LinesValues, ctx);
+                return this.addLines(renderObject.values as LinesValues, webgl, ctx);
             case 'points':
             case 'points':
-                return this.addPoints(renderObject.values as PointsValues, ctx);
+                return this.addPoints(renderObject.values as PointsValues, webgl, ctx);
             case 'spheres':
             case 'spheres':
-                return this.addSpheres(renderObject.values as SpheresValues, ctx);
+                return this.addSpheres(renderObject.values as SpheresValues, webgl, ctx);
             case 'cylinders':
             case 'cylinders':
-                return this.addCylinders(renderObject.values as CylindersValues, ctx);
+                return this.addCylinders(renderObject.values as CylindersValues, webgl, ctx);
             case 'texture-mesh':
             case 'texture-mesh':
                 return this.addTextureMesh(renderObject.values as TextureMeshValues, webgl, ctx);
                 return this.addTextureMesh(renderObject.values as TextureMeshValues, webgl, ctx);
         }
         }

+ 117 - 81
src/extensions/geo-export/obj-exporter.ts

@@ -4,14 +4,13 @@
  * @author Sukolsak Sakshuwong <sukolsak@stanford.edu>
  * @author Sukolsak Sakshuwong <sukolsak@stanford.edu>
  */
  */
 
 
-import { BaseValues } from '../../mol-gl/renderable/schema';
 import { asciiWrite } from '../../mol-io/common/ascii';
 import { asciiWrite } from '../../mol-io/common/ascii';
 import { Vec3, Mat3, Mat4 } from '../../mol-math/linear-algebra';
 import { Vec3, Mat3, Mat4 } from '../../mol-math/linear-algebra';
 import { RuntimeContext } from '../../mol-task';
 import { RuntimeContext } from '../../mol-task';
 import { StringBuilder } from '../../mol-util';
 import { StringBuilder } from '../../mol-util';
 import { Color } from '../../mol-util/color/color';
 import { Color } from '../../mol-util/color/color';
 import { zip } from '../../mol-util/zip/zip';
 import { zip } from '../../mol-util/zip/zip';
-import { MeshExporter } from './mesh-exporter';
+import { MeshExporter, AddMeshInput } from './mesh-exporter';
 
 
 // avoiding namespace lookup improved performance in Chrome (Aug 2020)
 // avoiding namespace lookup improved performance in Chrome (Aug 2020)
 const v3fromArray = Vec3.fromArray;
 const v3fromArray = Vec3.fromArray;
@@ -67,7 +66,9 @@ export class ObjExporter extends MeshExporter<ObjData> {
         }
         }
     }
     }
 
 
-    protected async addMeshWithColors(vertices: Float32Array, normals: Float32Array, indices: Uint32Array | undefined, groups: Float32Array | Uint8Array, vertexCount: number, drawCount: number, values: BaseValues, instanceIndex: number, isGeoTexture: boolean, ctx: RuntimeContext) {
+    protected async addMeshWithColors(input: AddMeshInput) {
+        const { mesh, meshes, values, isGeoTexture, webgl, ctx } = input;
+
         const obj = this.obj;
         const obj = this.obj;
         const t = Mat4();
         const t = Mat4();
         const n = Mat3();
         const n = Mat3();
@@ -81,95 +82,130 @@ export class ObjExporter extends MeshExporter<ObjData> {
         const dTransparency = values.dTransparency.ref.value;
         const dTransparency = values.dTransparency.ref.value;
         const tTransparency = values.tTransparency.ref.value;
         const tTransparency = values.tTransparency.ref.value;
         const aTransform = values.aTransform.ref.value;
         const aTransform = values.aTransform.ref.value;
+        const instanceCount = values.uInstanceCount.ref.value;
 
 
-        Mat4.fromArray(t, aTransform, instanceIndex * 16);
-        mat3directionTransform(n, t);
-
-        const currentProgress = (vertexCount * 2 + drawCount) * instanceIndex;
-        await ctx.update({ isIndeterminate: false, current: currentProgress, max: (vertexCount * 2 + drawCount) * values.uInstanceCount.ref.value });
-
-        // position
-        for (let i = 0; i < vertexCount; ++i) {
-            if (i % 1000 === 0 && ctx.shouldUpdate) await ctx.update({ current: currentProgress + i });
-            v3transformMat4(tmpV, v3fromArray(tmpV, vertices, i * stride), t);
-            StringBuilder.writeSafe(obj, 'v ');
-            StringBuilder.writeFloat(obj, tmpV[0], 1000);
-            StringBuilder.whitespace1(obj);
-            StringBuilder.writeFloat(obj, tmpV[1], 1000);
-            StringBuilder.whitespace1(obj);
-            StringBuilder.writeFloat(obj, tmpV[2], 1000);
-            StringBuilder.newline(obj);
+        let interpolatedColors: Uint8Array;
+        if (colorType === 'volume' || colorType === 'volumeInstance') {
+            interpolatedColors = ObjExporter.getInterpolatedColors(mesh!.vertices, mesh!.vertexCount, values, stride, colorType, webgl!);
         }
         }
 
 
-        // normal
-        for (let i = 0; i < vertexCount; ++i) {
-            if (i % 1000 === 0 && ctx.shouldUpdate) await ctx.update({ current: currentProgress + vertexCount + i });
-            v3transformMat3(tmpV, v3fromArray(tmpV, normals, i * stride), n);
-            StringBuilder.writeSafe(obj, 'vn ');
-            StringBuilder.writeFloat(obj, tmpV[0], 100);
-            StringBuilder.whitespace1(obj);
-            StringBuilder.writeFloat(obj, tmpV[1], 100);
-            StringBuilder.whitespace1(obj);
-            StringBuilder.writeFloat(obj, tmpV[2], 100);
-            StringBuilder.newline(obj);
-        }
+        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 = indices.length;
+            }
 
 
-        // face
-        for (let i = 0; i < drawCount; i += 3) {
-            if (i % 3000 === 0 && ctx.shouldUpdate) await ctx.update({ current: currentProgress + vertexCount * 2 + 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 ? ObjExporter.getGroup(groups, i) : groups[indices![i]];
-                    color = Color.fromArray(tColor, group * 3);
-                    break;
+            Mat4.fromArray(t, aTransform, instanceIndex * 16);
+            mat3directionTransform(n, t);
+
+            // position
+            for (let i = 0; i < vertexCount; ++i) {
+                v3transformMat4(tmpV, v3fromArray(tmpV, vertices, i * stride), t);
+                StringBuilder.writeSafe(obj, 'v ');
+                StringBuilder.writeFloat(obj, tmpV[0], 1000);
+                StringBuilder.whitespace1(obj);
+                StringBuilder.writeFloat(obj, tmpV[1], 1000);
+                StringBuilder.whitespace1(obj);
+                StringBuilder.writeFloat(obj, tmpV[2], 1000);
+                StringBuilder.newline(obj);
+            }
+
+            // normal
+            for (let i = 0; i < vertexCount; ++i) {
+                v3transformMat3(tmpV, v3fromArray(tmpV, normals, i * stride), n);
+                StringBuilder.writeSafe(obj, 'vn ');
+                StringBuilder.writeFloat(obj, tmpV[0], 100);
+                StringBuilder.whitespace1(obj);
+                StringBuilder.writeFloat(obj, tmpV[1], 100);
+                StringBuilder.whitespace1(obj);
+                StringBuilder.writeFloat(obj, tmpV[2], 100);
+                StringBuilder.newline(obj);
+            }
+
+            // face
+            for (let i = 0; i < drawCount; i += 3) {
+                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 ? ObjExporter.getGroup(groups, i) : groups[indices![i]];
+                        color = Color.fromArray(tColor, group * 3);
+                        break;
+                    }
+                    case 'groupInstance': {
+                        const group = isGeoTexture ? ObjExporter.getGroup(groups, i) : groups[indices![i]];
+                        color = Color.fromArray(tColor, (instanceIndex * groupCount + group) * 3);
+                        break;
+                    }
+                    case 'vertex':
+                        color = Color.fromArray(tColor, indices![i] * 3);
+                        break;
+                    case 'vertexInstance':
+                        color = Color.fromArray(tColor, (instanceIndex * drawCount + indices![i]) * 3);
+                        break;
+                    case 'volume':
+                        color = Color.fromArray(interpolatedColors!, (isGeoTexture ? i : indices![i]) * 3);
+                        break;
+                    case 'volumeInstance':
+                        color = Color.fromArray(interpolatedColors!, (instanceIndex * vertexCount + (isGeoTexture ? i : indices![i])) * 3);
+                        break;
+                    default: throw new Error('Unsupported color type.');
                 }
                 }
-                case 'groupInstance': {
+
+                let alpha = uAlpha;
+                if (dTransparency) {
                     const group = isGeoTexture ? ObjExporter.getGroup(groups, i) : groups[indices![i]];
                     const group = isGeoTexture ? ObjExporter.getGroup(groups, i) : groups[indices![i]];
-                    color = Color.fromArray(tColor, (instanceIndex * groupCount + group) * 3);
-                    break;
+                    const transparency = tTransparency.array[instanceIndex * groupCount + group] / 255;
+                    alpha *= 1 - transparency;
                 }
                 }
-                case 'vertex':
-                    color = Color.fromArray(tColor, indices![i] * 3);
-                    break;
-                case 'vertexInstance':
-                    color = Color.fromArray(tColor, (instanceIndex * drawCount + indices![i]) * 3);
-                    break;
-                default: throw new Error('Unsupported color type.');
-            }
 
 
-            let alpha = uAlpha;
-            if (dTransparency) {
-                const group = isGeoTexture ? ObjExporter.getGroup(groups, i) : groups[indices![i]];
-                const transparency = tTransparency.array[instanceIndex * groupCount + group] / 255;
-                alpha *= 1 - transparency;
+                this.updateMaterial(color, alpha);
+
+                const v1 = this.vertexOffset + (isGeoTexture ? i : indices![i]) + 1;
+                const v2 = this.vertexOffset + (isGeoTexture ? i + 1 : indices![i + 1]) + 1;
+                const v3 = this.vertexOffset + (isGeoTexture ? i + 2 : indices![i + 2]) + 1;
+                StringBuilder.writeSafe(obj, 'f ');
+                StringBuilder.writeInteger(obj, v1);
+                StringBuilder.writeSafe(obj, '//');
+                StringBuilder.writeIntegerAndSpace(obj, v1);
+                StringBuilder.writeInteger(obj, v2);
+                StringBuilder.writeSafe(obj, '//');
+                StringBuilder.writeIntegerAndSpace(obj, v2);
+                StringBuilder.writeInteger(obj, v3);
+                StringBuilder.writeSafe(obj, '//');
+                StringBuilder.writeInteger(obj, v3);
+                StringBuilder.newline(obj);
             }
             }
 
 
-            this.updateMaterial(color, alpha);
-
-            const v1 = this.vertexOffset + (isGeoTexture ? i : indices![i]) + 1;
-            const v2 = this.vertexOffset + (isGeoTexture ? i + 1 : indices![i + 1]) + 1;
-            const v3 = this.vertexOffset + (isGeoTexture ? i + 2 : indices![i + 2]) + 1;
-            StringBuilder.writeSafe(obj, 'f ');
-            StringBuilder.writeInteger(obj, v1);
-            StringBuilder.writeSafe(obj, '//');
-            StringBuilder.writeIntegerAndSpace(obj, v1);
-            StringBuilder.writeInteger(obj, v2);
-            StringBuilder.writeSafe(obj, '//');
-            StringBuilder.writeIntegerAndSpace(obj, v2);
-            StringBuilder.writeInteger(obj, v3);
-            StringBuilder.writeSafe(obj, '//');
-            StringBuilder.writeInteger(obj, v3);
-            StringBuilder.newline(obj);
+            this.vertexOffset += vertexCount;
         }
         }
-
-        this.vertexOffset += vertexCount;
     }
     }
 
 
     getData() {
     getData() {

+ 65 - 47
src/extensions/geo-export/stl-exporter.ts

@@ -4,12 +4,11 @@
  * @author Sukolsak Sakshuwong <sukolsak@stanford.edu>
  * @author Sukolsak Sakshuwong <sukolsak@stanford.edu>
  */
  */
 
 
-import { BaseValues } from '../../mol-gl/renderable/schema';
 import { asciiWrite } from '../../mol-io/common/ascii';
 import { asciiWrite } from '../../mol-io/common/ascii';
 import { Vec3, Mat4 } from '../../mol-math/linear-algebra';
 import { Vec3, Mat4 } from '../../mol-math/linear-algebra';
 import { PLUGIN_VERSION } from '../../mol-plugin/version';
 import { PLUGIN_VERSION } from '../../mol-plugin/version';
 import { RuntimeContext } from '../../mol-task';
 import { RuntimeContext } from '../../mol-task';
-import { MeshExporter } from './mesh-exporter';
+import { MeshExporter, AddMeshInput } from './mesh-exporter';
 
 
 // avoiding namespace lookup improved performance in Chrome (Aug 2020)
 // avoiding namespace lookup improved performance in Chrome (Aug 2020)
 const v3fromArray = Vec3.fromArray;
 const v3fromArray = Vec3.fromArray;
@@ -28,7 +27,9 @@ export class StlExporter extends MeshExporter<StlData> {
     private triangleBuffers: ArrayBuffer[] = [];
     private triangleBuffers: ArrayBuffer[] = [];
     private triangleCount = 0;
     private triangleCount = 0;
 
 
-    protected async addMeshWithColors(vertices: Float32Array, normals: Float32Array, indices: Uint32Array | undefined, groups: Float32Array | Uint8Array, vertexCount: number, drawCount: number, values: BaseValues, instanceIndex: number, isGeoTexture: boolean, ctx: RuntimeContext) {
+    protected async addMeshWithColors(input: AddMeshInput) {
+        const { mesh, meshes, values, isGeoTexture, ctx } = input;
+
         const t = Mat4();
         const t = Mat4();
         const tmpV = Vec3();
         const tmpV = Vec3();
         const v1 = Vec3();
         const v1 = Vec3();
@@ -36,51 +37,68 @@ export class StlExporter extends MeshExporter<StlData> {
         const v3 = Vec3();
         const v3 = Vec3();
         const stride = isGeoTexture ? 4 : 3;
         const stride = isGeoTexture ? 4 : 3;
 
 
-        const aTransform = values.aTransform.ref.value;
-        Mat4.fromArray(t, aTransform, instanceIndex * 16);
-
-        const currentProgress = (vertexCount + drawCount) * instanceIndex;
-        await ctx.update({ isIndeterminate: false, current: currentProgress, max: (vertexCount + drawCount) * values.uInstanceCount.ref.value });
-
-        // position
-        const vertexArray = new Float32Array(vertexCount * 3);
-        for (let i = 0; i < vertexCount; ++i) {
-            if (i % 1000 === 0 && ctx.shouldUpdate) await ctx.update({ current: currentProgress + i });
-            v3transformMat4(tmpV, v3fromArray(tmpV, vertices, i * stride), t);
-            v3toArray(tmpV, vertexArray, i * 3);
+        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 = indices.length;
+            }
+
+            const aTransform = values.aTransform.ref.value;
+            Mat4.fromArray(t, aTransform, instanceIndex * 16);
+
+            // position
+            const vertexArray = new Float32Array(vertexCount * 3);
+            for (let i = 0; i < vertexCount; ++i) {
+                v3transformMat4(tmpV, v3fromArray(tmpV, vertices, i * stride), t);
+                v3toArray(tmpV, vertexArray, i * 3);
+            }
+
+            // face
+            const triangleBuffer = new ArrayBuffer(50 * drawCount);
+            const dataView = new DataView(triangleBuffer);
+            for (let i = 0; i < drawCount; i += 3) {
+                v3fromArray(v1, vertexArray, (isGeoTexture ? i : indices![i]) * 3);
+                v3fromArray(v2, vertexArray, (isGeoTexture ? i + 1 : indices![i + 1]) * 3);
+                v3fromArray(v3, vertexArray, (isGeoTexture ? i + 2 : indices![i + 2]) * 3);
+                v3triangleNormal(tmpV, v1, v2, v3);
+
+                const byteOffset = 50 * i;
+                dataView.setFloat32(byteOffset, tmpV[0], true);
+                dataView.setFloat32(byteOffset + 4, tmpV[1], true);
+                dataView.setFloat32(byteOffset + 8, tmpV[2], true);
+
+                dataView.setFloat32(byteOffset + 12, v1[0], true);
+                dataView.setFloat32(byteOffset + 16, v1[1], true);
+                dataView.setFloat32(byteOffset + 20, v1[2], true);
+
+                dataView.setFloat32(byteOffset + 24, v2[0], true);
+                dataView.setFloat32(byteOffset + 28, v2[1], true);
+                dataView.setFloat32(byteOffset + 32, v2[2], true);
+
+                dataView.setFloat32(byteOffset + 36, v3[0], true);
+                dataView.setFloat32(byteOffset + 40, v3[1], true);
+                dataView.setFloat32(byteOffset + 44, v3[2], true);
+            }
+
+            this.triangleBuffers.push(triangleBuffer);
+            this.triangleCount += drawCount;
         }
         }
-
-        // face
-        const triangleBuffer = new ArrayBuffer(50 * drawCount);
-        const dataView = new DataView(triangleBuffer);
-        for (let i = 0; i < drawCount; i += 3) {
-            if (i % 3000 === 0 && ctx.shouldUpdate) await ctx.update({ current: currentProgress + vertexCount + i });
-
-            v3fromArray(v1, vertexArray, (isGeoTexture ? i : indices![i]) * 3);
-            v3fromArray(v2, vertexArray, (isGeoTexture ? i + 1 : indices![i + 1]) * 3);
-            v3fromArray(v3, vertexArray, (isGeoTexture ? i + 2 : indices![i + 2]) * 3);
-            v3triangleNormal(tmpV, v1, v2, v3);
-
-            const byteOffset = 50 * i;
-            dataView.setFloat32(byteOffset, tmpV[0], true);
-            dataView.setFloat32(byteOffset + 4, tmpV[1], true);
-            dataView.setFloat32(byteOffset + 8, tmpV[2], true);
-
-            dataView.setFloat32(byteOffset + 12, v1[0], true);
-            dataView.setFloat32(byteOffset + 16, v1[1], true);
-            dataView.setFloat32(byteOffset + 20, v1[2], true);
-
-            dataView.setFloat32(byteOffset + 24, v2[0], true);
-            dataView.setFloat32(byteOffset + 28, v2[1], true);
-            dataView.setFloat32(byteOffset + 32, v2[2], true);
-
-            dataView.setFloat32(byteOffset + 36, v3[0], true);
-            dataView.setFloat32(byteOffset + 40, v3[1], true);
-            dataView.setFloat32(byteOffset + 44, v3[2], true);
-        }
-
-        this.triangleBuffers.push(triangleBuffer);
-        this.triangleCount += drawCount;
     }
     }
 
 
     getData() {
     getData() {

+ 6 - 4
src/mol-geo/geometry/mesh/color-smoothing.ts

@@ -134,7 +134,7 @@ export function calcMeshColorSmoothing(input: ColorSmoothingInput, resolution: n
 
 
         return { kind: 'volume' as const, texture, gridTexDim, gridDim, gridTransform, type };
         return { kind: 'volume' as const, texture, gridTexDim, gridDim, gridTransform, type };
     } else {
     } else {
-        const interpolated = getTrilinearlyInterpolated({ vertexCount, instanceCount, transformBuffer, positionBuffer, colorType: type, grid, gridDim, gridTexDim, gridTransform });
+        const interpolated = getTrilinearlyInterpolated({ vertexCount, instanceCount, transformBuffer, positionBuffer, colorType: type, grid, gridDim, gridTexDim, gridTransform, vertexStride: 3, colorStride: 3 });
 
 
         return {
         return {
             kind: 'vertex' as const,
             kind: 'vertex' as const,
@@ -157,10 +157,12 @@ interface ColorInterpolationInput {
     gridTexDim: Vec2
     gridTexDim: Vec2
     gridDim: Vec3
     gridDim: Vec3
     gridTransform: Vec4
     gridTransform: Vec4
+    vertexStride: number
+    colorStride: number
 }
 }
 
 
 export function getTrilinearlyInterpolated(input: ColorInterpolationInput): TextureImage<Uint8Array> {
 export function getTrilinearlyInterpolated(input: ColorInterpolationInput): TextureImage<Uint8Array> {
-    const { vertexCount, positionBuffer, transformBuffer, grid, gridDim, gridTexDim, gridTransform } = input;
+    const { vertexCount, positionBuffer, transformBuffer, grid, gridDim, gridTexDim, gridTransform, vertexStride, colorStride } = input;
 
 
     const isInstanceType = input.colorType.endsWith('Instance');
     const isInstanceType = input.colorType.endsWith('Instance');
     const instanceCount = isInstanceType ? input.instanceCount : 1;
     const instanceCount = isInstanceType ? input.instanceCount : 1;
@@ -176,7 +178,7 @@ export function getTrilinearlyInterpolated(input: ColorInterpolationInput): Text
         const column = Math.floor(((z * xn) % width) / xn);
         const column = Math.floor(((z * xn) % width) / xn);
         const row = Math.floor((z * xn) / width);
         const row = Math.floor((z * xn) / width);
         const px = column * xn + x;
         const px = column * xn + x;
-        return 3 * ((row * yn * width) + (y * width) + px);
+        return colorStride * ((row * yn * width) + (y * width) + px);
     }
     }
 
 
     const v = Vec3();
     const v = Vec3();
@@ -186,7 +188,7 @@ export function getTrilinearlyInterpolated(input: ColorInterpolationInput): Text
 
 
     for (let i = 0; i < instanceCount; ++i) {
     for (let i = 0; i < instanceCount; ++i) {
         for (let j = 0; j < vertexCount; ++j) {
         for (let j = 0; j < vertexCount; ++j) {
-            Vec3.fromArray(v, positionBuffer, j * 3);
+            Vec3.fromArray(v, positionBuffer, j * vertexStride);
             if (isInstanceType) Vec3.transformMat4Offset(v, v, transformBuffer, 0, 0, i * 16);
             if (isInstanceType) Vec3.transformMat4Offset(v, v, transformBuffer, 0, 0, i * 16);
             Vec3.sub(v, v, min);
             Vec3.sub(v, v, min);
             Vec3.scale(v, v, scaleFactor);
             Vec3.scale(v, v, scaleFactor);