Bladeren bron

support points & lines in glTF export (#810)

Alexander Rose 1 jaar geleden
bovenliggende
commit
430348a3cd

+ 1 - 0
CHANGELOG.md

@@ -7,6 +7,7 @@ Note that since we don't clearly distinguish between a public and private interf
 ## [Unreleased]
 
 - Add a uniform color theme for NtC tube that still paints residue and segment dividers in a different color
+- Support points & lines in glTF export
 - Fix bond assignments `struct_conn` records referencing waters
 - Add StructConn extension providing functions for inspecting struct_conns
 - Fix `PluginState.setSnapshot` triggering unnecessary state updates

+ 38 - 25
src/extensions/geo-export/glb-exporter.ts

@@ -1,5 +1,5 @@
 /**
- * Copyright (c) 2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2021-2023 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
  * @author Sukolsak Sakshuwong <sukolsak@stanford.edu>
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
@@ -13,7 +13,7 @@ import { PLUGIN_VERSION } from '../../mol-plugin/version';
 import { RuntimeContext } from '../../mol-task';
 import { Color } from '../../mol-util/color/color';
 import { fillSerial } from '../../mol-util/array';
-import { NumberArray } from '../../mol-util/type-helpers';
+import { NumberArray, assertUnreachable } from '../../mol-util/type-helpers';
 import { MeshExporter, AddMeshInput, MeshGeoData } from './mesh-exporter';
 
 // avoiding namespace lookup improved performance in Chrome (Aug 2020)
@@ -35,6 +35,15 @@ const BIN_CHUNK_TYPE = 0x004E4942;
 const JSON_PAD_CHAR = 0x20;
 const BIN_PAD_CHAR = 0x00;
 
+function getPrimitiveMode(mode: 'points' | 'lines' | 'triangles'): number {
+    switch (mode) {
+        case 'points': return 0;
+        case 'lines': return 1;
+        case 'triangles': return 4;
+        default: assertUnreachable(mode);
+    }
+}
+
 export type GlbData = {
     glb: Uint8Array
 }
@@ -89,12 +98,12 @@ export class GlbExporter extends MeshExporter<GlbData> {
         return accessorOffset;
     }
 
-    private addGeometryBuffers(vertices: Float32Array, normals: Float32Array, indices: Uint32Array | undefined, vertexCount: number, drawCount: number, isGeoTexture: boolean) {
+    private addGeometryBuffers(vertices: Float32Array, normals: Float32Array | undefined, 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 normalArray: Float32Array | undefined;
         let indexArray: Uint32Array | undefined;
 
         // position
@@ -104,32 +113,35 @@ export class GlbExporter extends MeshExporter<GlbData> {
         }
 
         // normal
-        for (let i = 0; i < vertexCount; ++i) {
-            v3fromArray(tmpV, normals, i * stride);
-            v3normalize(tmpV, tmpV);
-            v3toArray(tmpV, normalArray, i * 3);
+        if (normals) {
+            normalArray = new Float32Array(vertexCount * 3);
+            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);
+        if (!isGeoTexture && indices) {
+            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;
+        let normalBuffer = normalArray?.buffer;
+        let indexBuffer = (isGeoTexture || !indexArray) ? undefined : indexArray.buffer;
         if (!IsNativeEndianLittle) {
             vertexBuffer = flipByteOrder(new Uint8Array(vertexBuffer), 4);
-            normalBuffer = flipByteOrder(new Uint8Array(normalBuffer), 4);
+            if (normalBuffer) 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)
+            normalAccessorIndex: normalBuffer ? this.addBuffer(normalBuffer, FLOAT, 'VEC3', vertexCount, ARRAY_BUFFER) : undefined,
+            indexAccessorIndex: (isGeoTexture || !indexBuffer) ? undefined : this.addBuffer(indexBuffer, UNSIGNED_INT, 'SCALAR', drawCount, ELEMENT_ARRAY_BUFFER)
         };
     }
 
@@ -174,7 +186,7 @@ export class GlbExporter extends MeshExporter<GlbData> {
     }
 
     protected async addMeshWithColors(input: AddMeshInput) {
-        const { mesh, values, isGeoTexture, webgl, ctx } = input;
+        const { mesh, values, isGeoTexture, mode, webgl, ctx } = input;
 
         const t = Mat4();
 
@@ -190,28 +202,28 @@ export class GlbExporter extends MeshExporter<GlbData> {
         const material = this.addMaterial(metalness, roughness);
 
         let interpolatedColors: Uint8Array | undefined;
-        if (colorType === 'volume' || colorType === 'volumeInstance') {
+        if (webgl && mesh && (colorType === 'volume' || colorType === 'volumeInstance')) {
             const stride = isGeoTexture ? 4 : 3;
-            interpolatedColors = GlbExporter.getInterpolatedColors(webgl!, { vertices: mesh!.vertices, vertexCount: mesh!.vertexCount, values, stride, colorType });
+            interpolatedColors = GlbExporter.getInterpolatedColors(webgl, { vertices: mesh.vertices, vertexCount: mesh.vertexCount, values, stride, colorType });
         }
 
         let interpolatedOverpaint: Uint8Array | undefined;
-        if (overpaintType === 'volumeInstance') {
+        if (webgl && mesh && overpaintType === 'volumeInstance') {
             const stride = isGeoTexture ? 4 : 3;
-            interpolatedOverpaint = GlbExporter.getInterpolatedOverpaint(webgl!, { vertices: mesh!.vertices, vertexCount: mesh!.vertexCount, values, stride, colorType: overpaintType });
+            interpolatedOverpaint = GlbExporter.getInterpolatedOverpaint(webgl, { vertices: mesh.vertices, vertexCount: mesh.vertexCount, values, stride, colorType: overpaintType });
         }
 
         let interpolatedTransparency: Uint8Array | undefined;
-        if (transparencyType === 'volumeInstance') {
+        if (webgl && mesh && transparencyType === 'volumeInstance') {
             const stride = isGeoTexture ? 4 : 3;
-            interpolatedTransparency = GlbExporter.getInterpolatedTransparency(webgl!, { vertices: mesh!.vertices, vertexCount: mesh!.vertexCount, values, stride, colorType: transparencyType });
+            interpolatedTransparency = GlbExporter.getInterpolatedTransparency(webgl, { vertices: mesh.vertices, vertexCount: mesh.vertexCount, values, stride, colorType: transparencyType });
         }
 
         // instancing
         const sameGeometryBuffers = mesh !== undefined;
         const sameColorBuffer = sameGeometryBuffers && colorType !== 'instance' && !colorType.endsWith('Instance') && !dTransparency;
         let vertexAccessorIndex: number;
-        let normalAccessorIndex: number;
+        let normalAccessorIndex: number | undefined;
         let indexAccessorIndex: number | undefined;
         let colorAccessorIndex: number;
         let meshIndex: number;
@@ -235,7 +247,7 @@ export class GlbExporter extends MeshExporter<GlbData> {
 
                 // create a color buffer if needed
                 if (instanceIndex === 0 || !sameColorBuffer) {
-                    colorAccessorIndex = this.addColorBuffer({ values, groups, vertexCount, instanceIndex, isGeoTexture }, interpolatedColors, interpolatedOverpaint, interpolatedTransparency);
+                    colorAccessorIndex = this.addColorBuffer({ values, groups, vertexCount, instanceIndex, isGeoTexture, mode }, interpolatedColors, interpolatedOverpaint, interpolatedTransparency);
                 }
 
                 // glTF mesh
@@ -248,7 +260,8 @@ export class GlbExporter extends MeshExporter<GlbData> {
                             COLOR_0: colorAccessorIndex!
                         },
                         indices: indexAccessorIndex,
-                        material
+                        material,
+                        mode: getPrimitiveMode(mode),
                     }]
                 });
             }

+ 53 - 12
src/extensions/geo-export/mesh-exporter.ts

@@ -1,5 +1,5 @@
 /**
- * Copyright (c) 2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2021-2023 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
  * @author Sukolsak Sakshuwong <sukolsak@stanford.edu>
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
@@ -34,10 +34,12 @@ const GeoExportName = 'geo-export';
 // avoiding namespace lookup improved performance in Chrome (Aug 2020)
 const v3fromArray = Vec3.fromArray;
 
+type MeshMode = 'points' | 'lines' | 'triangles'
+
 export interface AddMeshInput {
     mesh: {
         vertices: Float32Array
-        normals: Float32Array
+        normals: Float32Array | undefined
         indices: Uint32Array | undefined
         groups: Float32Array | Uint8Array
         vertexCount: number
@@ -46,6 +48,7 @@ export interface AddMeshInput {
     meshes: Mesh[] | undefined
     values: BaseValues
     isGeoTexture: boolean
+    mode: MeshMode
     webgl: WebGLContext | undefined
     ctx: RuntimeContext
 }
@@ -55,7 +58,8 @@ export type MeshGeoData = {
     groups: Float32Array | Uint8Array,
     vertexCount: number,
     instanceIndex: number,
-    isGeoTexture: boolean
+    isGeoTexture: boolean,
+    mode: MeshMode
 }
 
 export abstract class MeshExporter<D extends RenderObjectExportData> implements RenderObjectExporter<D> {
@@ -222,7 +226,7 @@ export abstract class MeshExporter<D extends RenderObjectExportData> implements
     }
 
     protected static getColor(vertexIndex: number, geoData: MeshGeoData, interpolatedColors?: Uint8Array, interpolatedOverpaint?: Uint8Array): Color {
-        const { values, instanceIndex, isGeoTexture, groups, vertexCount } = geoData;
+        const { values, instanceIndex, isGeoTexture, mode, groups } = geoData;
         const groupCount = values.uGroupCount.ref.value;
         const colorType = values.dColorType.ref.value;
         const uColor = values.uColor.ref.value;
@@ -231,6 +235,12 @@ export abstract class MeshExporter<D extends RenderObjectExportData> implements
         const dOverpaint = values.dOverpaint.ref.value;
         const tOverpaint = values.tOverpaint.ref.value.array;
 
+        let vertexCount = geoData.vertexCount;
+        if (mode === 'lines') {
+            vertexIndex *= 2;
+            vertexCount *= 2;
+        }
+
         let color: Color;
         switch (colorType) {
             case 'uniform':
@@ -298,12 +308,18 @@ export abstract class MeshExporter<D extends RenderObjectExportData> implements
     }
 
     protected static getTransparency(vertexIndex: number, geoData: MeshGeoData, interpolatedTransparency?: Uint8Array): number {
-        const { values, instanceIndex, isGeoTexture, groups, vertexCount } = geoData;
+        const { values, instanceIndex, isGeoTexture, mode, groups } = geoData;
         const groupCount = values.uGroupCount.ref.value;
         const dTransparency = values.dTransparency.ref.value;
         const tTransparency = values.tTransparency.ref.value.array;
         const transparencyType = values.dTransparencyType.ref.value;
 
+        let vertexCount = geoData.vertexCount;
+        if (mode === 'lines') {
+            vertexIndex *= 2;
+            vertexCount *= 2;
+        }
+
         let transparency: number = 0;
         if (dTransparency) {
             switch (transparencyType) {
@@ -329,7 +345,7 @@ export abstract class MeshExporter<D extends RenderObjectExportData> implements
         return transparency;
     }
 
-    protected abstract addMeshWithColors(input: AddMeshInput): void;
+    protected abstract addMeshWithColors(input: AddMeshInput): Promise<void>;
 
     private async addMesh(values: MeshValues, webgl: WebGLContext, ctx: RuntimeContext) {
         const aPosition = values.aPosition.ref.value;
@@ -349,15 +365,38 @@ export abstract class MeshExporter<D extends RenderObjectExportData> implements
             drawCount = values.drawCount.ref.value;
         }
 
-        await this.addMeshWithColors({ mesh: { vertices: aPosition, normals: aNormal, indices, groups: aGroup, vertexCount, drawCount }, meshes: undefined, values, isGeoTexture: false, webgl, ctx });
+        await this.addMeshWithColors({ mesh: { vertices: aPosition, normals: aNormal, indices, groups: aGroup, vertexCount, drawCount }, meshes: undefined, values, isGeoTexture: false, mode: 'triangles', webgl, ctx });
     }
 
     private async addLines(values: LinesValues, webgl: WebGLContext, ctx: RuntimeContext) {
-        // TODO
+        const aStart = values.aStart.ref.value;
+        const aEnd = values.aEnd.ref.value;
+        const aGroup = values.aGroup.ref.value;
+        const vertexCount = (values.uVertexCount.ref.value / 4) * 2;
+        const drawCount = values.drawCount.ref.value / (2 * 3);
+
+        const n = (vertexCount / 2);
+        const vertices = new Float32Array(n * 2 * 3);
+        for (let i = 0; i < n; ++i) {
+            vertices[i * 6] = aStart[i * 4 * 3];
+            vertices[i * 6 + 1] = aStart[i * 4 * 3 + 1];
+            vertices[i * 6 + 2] = aStart[i * 4 * 3 + 2];
+
+            vertices[i * 6 + 3] = aEnd[i * 4 * 3];
+            vertices[i * 6 + 4] = aEnd[i * 4 * 3 + 1];
+            vertices[i * 6 + 5] = aEnd[i * 4 * 3 + 2];
+        }
+
+        await this.addMeshWithColors({ mesh: { vertices, normals: undefined, indices: undefined, groups: aGroup, vertexCount, drawCount }, meshes: undefined, values, isGeoTexture: false, mode: 'lines', webgl, ctx });
     }
 
     private async addPoints(values: PointsValues, webgl: WebGLContext, ctx: RuntimeContext) {
-        // TODO
+        const aPosition = values.aPosition.ref.value;
+        const aGroup = values.aGroup.ref.value;
+        const vertexCount = values.uVertexCount.ref.value;
+        const drawCount = values.drawCount.ref.value;
+
+        await this.addMeshWithColors({ mesh: { vertices: aPosition, normals: undefined, indices: undefined, groups: aGroup, vertexCount, drawCount }, meshes: undefined, values, isGeoTexture: false, mode: 'points', webgl, ctx });
     }
 
     private async addSpheres(values: SpheresValues, webgl: WebGLContext, ctx: RuntimeContext) {
@@ -390,7 +429,7 @@ export abstract class MeshExporter<D extends RenderObjectExportData> implements
             meshes.push(MeshBuilder.getMesh(state));
         }
 
-        await this.addMeshWithColors({ mesh: undefined, meshes, values, isGeoTexture: false, webgl, ctx });
+        await this.addMeshWithColors({ mesh: undefined, meshes, values, isGeoTexture: false, mode: 'triangles', webgl, ctx });
     }
 
     private async addCylinders(values: CylindersValues, webgl: WebGLContext, ctx: RuntimeContext) {
@@ -432,7 +471,7 @@ export abstract class MeshExporter<D extends RenderObjectExportData> implements
             meshes.push(MeshBuilder.getMesh(state));
         }
 
-        await this.addMeshWithColors({ mesh: undefined, meshes, values, isGeoTexture: false, webgl, ctx });
+        await this.addMeshWithColors({ mesh: undefined, meshes, values, isGeoTexture: false, mode: 'triangles', webgl, ctx });
     }
 
     private async addTextureMesh(values: TextureMeshValues, webgl: WebGLContext, ctx: RuntimeContext) {
@@ -457,11 +496,13 @@ export abstract class MeshExporter<D extends RenderObjectExportData> implements
         const vertexCount = values.uVertexCount.ref.value;
         const drawCount = values.drawCount.ref.value;
 
-        await this.addMeshWithColors({ mesh: { vertices, normals, indices: undefined, groups, vertexCount, drawCount }, meshes: undefined, values, isGeoTexture: true, webgl, ctx });
+        await this.addMeshWithColors({ mesh: { vertices, normals, indices: undefined, groups, vertexCount, drawCount }, meshes: undefined, values, isGeoTexture: true, mode: 'triangles', webgl, ctx });
     }
 
     add(renderObject: GraphicsRenderObject, webgl: WebGLContext, ctx: RuntimeContext) {
         if (!renderObject.state.visible) return;
+        if (renderObject.values.drawCount.ref.value === 0) return;
+        if (renderObject.values.instanceCount.ref.value === 0) return;
 
         switch (renderObject.type) {
             case 'mesh':

+ 11 - 10
src/extensions/geo-export/obj-exporter.ts

@@ -1,5 +1,5 @@
 /**
- * Copyright (c) 2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2021-2023 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
  * @author Sukolsak Sakshuwong <sukolsak@stanford.edu>
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
@@ -70,7 +70,8 @@ export class ObjExporter extends MeshExporter<ObjData> {
     }
 
     protected async addMeshWithColors(input: AddMeshInput) {
-        const { mesh, values, isGeoTexture, webgl, ctx } = input;
+        const { mesh, values, isGeoTexture, mode, webgl, ctx } = input;
+        if (mode !== 'triangles') return;
 
         const obj = this.obj;
         const t = Mat4();
@@ -86,19 +87,19 @@ export class ObjExporter extends MeshExporter<ObjData> {
         const instanceCount = values.uInstanceCount.ref.value;
 
         let interpolatedColors: Uint8Array | undefined;
-        if (colorType === 'volume' || colorType === 'volumeInstance') {
-            interpolatedColors = ObjExporter.getInterpolatedColors(webgl!, { vertices: mesh!.vertices, vertexCount: mesh!.vertexCount, values, stride, colorType });
+        if (webgl && mesh && (colorType === 'volume' || colorType === 'volumeInstance')) {
+            interpolatedColors = ObjExporter.getInterpolatedColors(webgl, { vertices: mesh.vertices, vertexCount: mesh.vertexCount, values, stride, colorType });
         }
 
         let interpolatedOverpaint: Uint8Array | undefined;
-        if (overpaintType === 'volumeInstance') {
-            interpolatedOverpaint = ObjExporter.getInterpolatedOverpaint(webgl!, { vertices: mesh!.vertices, vertexCount: mesh!.vertexCount, values, stride, colorType: overpaintType });
+        if (webgl && mesh && overpaintType === 'volumeInstance') {
+            interpolatedOverpaint = ObjExporter.getInterpolatedOverpaint(webgl, { vertices: mesh.vertices, vertexCount: mesh.vertexCount, values, stride, colorType: overpaintType });
         }
 
         let interpolatedTransparency: Uint8Array | undefined;
-        if (transparencyType === 'volumeInstance') {
+        if (webgl && mesh && transparencyType === 'volumeInstance') {
             const stride = isGeoTexture ? 4 : 3;
-            interpolatedTransparency = ObjExporter.getInterpolatedTransparency(webgl!, { vertices: mesh!.vertices, vertexCount: mesh!.vertexCount, values, stride, colorType: transparencyType });
+            interpolatedTransparency = ObjExporter.getInterpolatedTransparency(webgl, { vertices: mesh.vertices, vertexCount: mesh.vertexCount, values, stride, colorType: transparencyType });
         }
 
         await ctx.update({ isIndeterminate: false, current: 0, max: instanceCount });
@@ -126,7 +127,7 @@ export class ObjExporter extends MeshExporter<ObjData> {
 
             // normal
             for (let i = 0; i < vertexCount; ++i) {
-                v3transformMat3(tmpV, v3fromArray(tmpV, normals, i * stride), n);
+                v3transformMat3(tmpV, v3fromArray(tmpV, normals!, i * stride), n);
                 StringBuilder.writeSafe(obj, 'vn ');
                 StringBuilder.writeFloat(obj, tmpV[0], 100);
                 StringBuilder.whitespace1(obj);
@@ -136,7 +137,7 @@ export class ObjExporter extends MeshExporter<ObjData> {
                 StringBuilder.newline(obj);
             }
 
-            const geoData = { values, groups, vertexCount, instanceIndex, isGeoTexture };
+            const geoData = { values, groups, vertexCount, instanceIndex, isGeoTexture, mode };
 
             // color
             const quantizedColors = new Uint8Array(drawCount * 3);

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

@@ -1,5 +1,5 @@
 /**
- * Copyright (c) 2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2021-2023 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
  * @author Sukolsak Sakshuwong <sukolsak@stanford.edu>
  */
@@ -30,7 +30,8 @@ export class StlExporter extends MeshExporter<StlData> {
     private centerTransform: Mat4;
 
     protected async addMeshWithColors(input: AddMeshInput) {
-        const { values, isGeoTexture, ctx } = input;
+        const { values, isGeoTexture, mode, ctx } = input;
+        if (mode !== 'triangles') return;
 
         const t = Mat4();
         const tmpV = Vec3();

+ 11 - 10
src/extensions/geo-export/usdz-exporter.ts

@@ -1,5 +1,5 @@
 /**
- * Copyright (c) 2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2021-2023 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
  * @author Sukolsak Sakshuwong <sukolsak@stanford.edu>
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
@@ -61,7 +61,8 @@ def Material "material${materialKey}"
     }
 
     protected async addMeshWithColors(input: AddMeshInput) {
-        const { mesh, values, isGeoTexture, webgl, ctx } = input;
+        const { mesh, values, isGeoTexture, mode, webgl, ctx } = input;
+        if (mode !== 'triangles') return;
 
         const t = Mat4();
         const n = Mat3();
@@ -78,20 +79,20 @@ def Material "material${materialKey}"
         const roughness = values.uRoughness.ref.value;
 
         let interpolatedColors: Uint8Array | undefined;
-        if (colorType === 'volume' || colorType === 'volumeInstance') {
-            interpolatedColors = UsdzExporter.getInterpolatedColors(webgl!, { vertices: mesh!.vertices, vertexCount: mesh!.vertexCount, values, stride, colorType });
+        if (webgl && mesh && (colorType === 'volume' || colorType === 'volumeInstance')) {
+            interpolatedColors = UsdzExporter.getInterpolatedColors(webgl, { vertices: mesh.vertices, vertexCount: mesh.vertexCount, values, stride, colorType });
         }
 
         let interpolatedOverpaint: Uint8Array | undefined;
-        if (overpaintType === 'volumeInstance') {
+        if (webgl && mesh && overpaintType === 'volumeInstance') {
             const stride = isGeoTexture ? 4 : 3;
-            interpolatedOverpaint = UsdzExporter.getInterpolatedOverpaint(webgl!, { vertices: mesh!.vertices, vertexCount: mesh!.vertexCount, values, stride, colorType: overpaintType });
+            interpolatedOverpaint = UsdzExporter.getInterpolatedOverpaint(webgl, { vertices: mesh.vertices, vertexCount: mesh.vertexCount, values, stride, colorType: overpaintType });
         }
 
         let interpolatedTransparency: Uint8Array | undefined;
-        if (transparencyType === 'volumeInstance') {
+        if (webgl && mesh && transparencyType === 'volumeInstance') {
             const stride = isGeoTexture ? 4 : 3;
-            interpolatedTransparency = UsdzExporter.getInterpolatedTransparency(webgl!, { vertices: mesh!.vertices, vertexCount: mesh!.vertexCount, values, stride, colorType: transparencyType });
+            interpolatedTransparency = UsdzExporter.getInterpolatedTransparency(webgl, { vertices: mesh.vertices, vertexCount: mesh.vertexCount, values, stride, colorType: transparencyType });
         }
 
         await ctx.update({ isIndeterminate: false, current: 0, max: instanceCount });
@@ -123,7 +124,7 @@ def Material "material${materialKey}"
 
             // normal
             for (let i = 0; i < vertexCount; ++i) {
-                v3transformMat3(tmpV, v3fromArray(tmpV, normals, i * stride), n);
+                v3transformMat3(tmpV, v3fromArray(tmpV, normals!, i * stride), n);
                 StringBuilder.writeSafe(normalBuilder, (i === 0) ? '(' : ',(');
                 StringBuilder.writeFloat(normalBuilder, tmpV[0], 100);
                 StringBuilder.writeSafe(normalBuilder, ',');
@@ -133,7 +134,7 @@ def Material "material${materialKey}"
                 StringBuilder.writeSafe(normalBuilder, ')');
             }
 
-            const geoData = { values, groups, vertexCount, instanceIndex, isGeoTexture };
+            const geoData = { values, groups, vertexCount, instanceIndex, isGeoTexture, mode };
 
             // face
             for (let i = 0; i < drawCount; ++i) {