Przeglądaj źródła

wip, geometry refactoring

- create helper function
- bounding-sphere handling
- cleanup
Alexander Rose 5 lat temu
rodzic
commit
870d2da8ed

+ 63 - 25
src/mol-geo/geometry/direct-volume/direct-volume.ts

@@ -18,7 +18,7 @@ import { createColors } from '../color-data';
 import { createMarkers } from '../marker-data';
 import { GeometryUtils } from '../geometry';
 import { transformPositionArray } from '../../../mol-geo/util';
-import { calculateBoundingSphere } from '../../../mol-gl/renderable/util';
+import { calculateInvariantBoundingSphere, calculateTransformBoundingSphere } from '../../../mol-gl/renderable/util';
 import { Theme } from '../../../mol-theme/theme';
 import { RenderableState } from '../../../mol-gl/renderable';
 import { ColorListOptions, ColorListName } from '../../../mol-util/color/lists';
@@ -26,12 +26,14 @@ import { Color } from '../../../mol-util/color';
 import { BaseGeometry } from '../base';
 import { createEmptyOverpaint } from '../overpaint-data';
 import { createEmptyTransparency } from '../transparency-data';
+import { hashFnv32a } from '../../../mol-data/util';
 
 const VolumeBox = Box()
 const RenderModeOptions = [['isosurface', 'Isosurface'], ['volume', 'Volume']] as [string, string][]
 
 export interface DirectVolume {
     readonly kind: 'direct-volume',
+
     readonly gridTexture: ValueCell<Texture>,
     readonly gridTextureDim: ValueCell<Vec3>,
     readonly gridDimension: ValueCell<Vec3>,
@@ -41,34 +43,66 @@ export interface DirectVolume {
     readonly transform: ValueCell<Mat4>
 
     /** Bounding sphere of the volume */
-    boundingSphere?: Sphere3D
+    boundingSphere: Sphere3D
 }
 
 export namespace DirectVolume {
     export function create(bbox: Box3D, gridDimension: Vec3, transform: Mat4, texture: Texture, directVolume?: DirectVolume): DirectVolume {
+        return directVolume ?
+            update(bbox, gridDimension, transform, texture, directVolume) :
+            fromData(bbox, gridDimension, transform, texture)
+    }
+
+    function hashCode(directVolume: DirectVolume) {
+        return hashFnv32a([
+            directVolume.bboxSize.ref.version, directVolume.gridDimension.ref.version,
+            directVolume.gridTexture.ref.version, directVolume.transform.ref.version,
+        ])
+    }
+
+    function fromData(bbox: Box3D, gridDimension: Vec3, transform: Mat4, texture: Texture): DirectVolume {
+        const boundingSphere = Sphere3D()
+        let currentHash = -1
+
         const width = texture.getWidth()
         const height = texture.getHeight()
         const depth = texture.getDepth()
-        if (directVolume) {
-            ValueCell.update(directVolume.gridDimension, gridDimension)
-            ValueCell.update(directVolume.gridTextureDim, Vec3.set(directVolume.gridTextureDim.ref.value, width, height, depth))
-            ValueCell.update(directVolume.bboxMin, bbox.min)
-            ValueCell.update(directVolume.bboxMax, bbox.max)
-            ValueCell.update(directVolume.bboxSize, Vec3.sub(directVolume.bboxSize.ref.value, bbox.max, bbox.min))
-            ValueCell.update(directVolume.transform, transform)
-            return directVolume
-        } else {
-            return {
-                kind: 'direct-volume',
-                gridDimension: ValueCell.create(gridDimension),
-                gridTexture: ValueCell.create(texture),
-                gridTextureDim: ValueCell.create(Vec3.create(width, height, depth)),
-                bboxMin: ValueCell.create(bbox.min),
-                bboxMax: ValueCell.create(bbox.max),
-                bboxSize: ValueCell.create(Vec3.sub(Vec3.zero(), bbox.max, bbox.min)),
-                transform: ValueCell.create(transform),
-            }
+
+        const directVolume = {
+            kind: 'direct-volume' as const,
+            gridDimension: ValueCell.create(gridDimension),
+            gridTexture: ValueCell.create(texture),
+            gridTextureDim: ValueCell.create(Vec3.create(width, height, depth)),
+            bboxMin: ValueCell.create(bbox.min),
+            bboxMax: ValueCell.create(bbox.max),
+            bboxSize: ValueCell.create(Vec3.sub(Vec3.zero(), bbox.max, bbox.min)),
+            transform: ValueCell.create(transform),
+            get boundingSphere() {
+                const newHash = hashCode(directVolume)
+                if (newHash !== currentHash) {
+                    const b = getBoundingSphere(directVolume.gridDimension.ref.value, directVolume.transform.ref.value)
+                    Sphere3D.copy(boundingSphere, b)
+                    currentHash = newHash
+                }
+                return boundingSphere
+            },
         }
+        return directVolume
+    }
+
+    function update(bbox: Box3D, gridDimension: Vec3, transform: Mat4, texture: Texture, directVolume: DirectVolume): DirectVolume {
+        const width = texture.getWidth()
+        const height = texture.getHeight()
+        const depth = texture.getDepth()
+
+        ValueCell.update(directVolume.gridDimension, gridDimension)
+        ValueCell.update(directVolume.gridTexture, texture)
+        ValueCell.update(directVolume.gridTextureDim, Vec3.set(directVolume.gridTextureDim.ref.value, width, height, depth))
+        ValueCell.update(directVolume.bboxMin, bbox.min)
+        ValueCell.update(directVolume.bboxMax, bbox.max)
+        ValueCell.update(directVolume.bboxSize, Vec3.sub(directVolume.bboxSize.ref.value, bbox.max, bbox.min))
+        ValueCell.update(directVolume.transform, transform)
+        return directVolume
     }
 
     export function createEmpty(directVolume?: DirectVolume): DirectVolume {
@@ -110,7 +144,8 @@ export namespace DirectVolume {
 
         const counts = { drawCount: VolumeBox.indices.length, groupCount, instanceCount }
 
-        const { boundingSphere, invariantBoundingSphere } = getBoundingSphere(gridDimension.ref.value, gridTransform.ref.value, transform.aTransform.ref.value, transform.instanceCount.ref.value)
+        const invariantBoundingSphere = Sphere3D.clone(directVolume.boundingSphere)
+        const boundingSphere = calculateTransformBoundingSphere(invariantBoundingSphere, transform.aTransform.ref.value, instanceCount)
 
         const controlPoints = getControlPointsFromVec2Array(props.controlPoints)
         const transferTex = createTransferFunctionTexture(controlPoints, props.list)
@@ -162,7 +197,9 @@ export namespace DirectVolume {
     }
 
     function updateBoundingSphere(values: DirectVolumeValues, directVolume: DirectVolume) {
-        const { boundingSphere, invariantBoundingSphere } = getBoundingSphere(values.uGridDim.ref.value, values.uTransform.ref.value, values.aTransform.ref.value, values.instanceCount.ref.value)
+        const invariantBoundingSphere = Sphere3D.clone(directVolume.boundingSphere)
+        const boundingSphere = calculateTransformBoundingSphere(invariantBoundingSphere, values.aTransform.ref.value, values.instanceCount.ref.value)
+
         if (!Sphere3D.equals(boundingSphere, values.boundingSphere.ref.value)) {
             ValueCell.update(values.boundingSphere, boundingSphere)
         }
@@ -189,11 +226,12 @@ const mTmp = Mat4.identity()
 const mTmp2 = Mat4.identity()
 const vHalfUnit = Vec3.create(0.5, 0.5, 0.5)
 const tmpVertices = new Float32Array(VolumeBox.vertices.length)
-function getBoundingSphere(gridDimension: Vec3, gridTransform: Mat4, transform: Float32Array, transformCount: number) {
+function getBoundingSphere(gridDimension: Vec3, gridTransform: Mat4) {
     tmpVertices.set(VolumeBox.vertices)
     Mat4.fromTranslation(mTmp, vHalfUnit)
     Mat4.mul(mTmp, Mat4.fromScaling(mTmp2, gridDimension), mTmp)
     Mat4.mul(mTmp, gridTransform, mTmp)
     transformPositionArray(mTmp, tmpVertices, 0, tmpVertices.length / 3)
-    return calculateBoundingSphere(tmpVertices, tmpVertices.length / 3, transform, transformCount)
+    return calculateInvariantBoundingSphere(tmpVertices, tmpVertices.length / 3, 1)
+    // return calculateBoundingSphere(tmpVertices, tmpVertices.length / 3, transform, transformCount)
 }

+ 2 - 2
src/mol-geo/geometry/geometry.ts

@@ -64,7 +64,7 @@ export namespace Geometry {
             case 'text': return geometry.charCount * 2 * 3
             case 'lines': return geometry.lineCount * 2 * 3
             case 'direct-volume': return 12 * 3
-            case 'texture-mesh': return geometry.vertexCount.ref.value
+            case 'texture-mesh': return geometry.vertexCount
         }
     }
 
@@ -79,7 +79,7 @@ export namespace Geometry {
             case 'direct-volume':
                 return 1
             case 'texture-mesh':
-                return geometry.groupCount.ref.value
+                return geometry.groupCount
         }
     }
 

+ 2 - 11
src/mol-geo/geometry/lines/lines-builder.ts

@@ -1,10 +1,9 @@
 /**
- * Copyright (c) 2018-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
 
-import { ValueCell } from '../../../mol-util/value-cell'
 import { ChunkedArray } from '../../../mol-data/util';
 import { Lines } from './lines';
 import { Mat4, Vec3 } from '../../../mol-math/linear-algebra';
@@ -84,15 +83,7 @@ export namespace LinesBuilder {
                 const gb = ChunkedArray.compact(groups, true) as Float32Array
                 const sb = ChunkedArray.compact(starts, true) as Float32Array
                 const eb = ChunkedArray.compact(ends, true) as Float32Array
-                return {
-                    kind: 'lines',
-                    lineCount: indices.elementCount / 2,
-                    mappingBuffer: lines ? ValueCell.update(lines.mappingBuffer, mb) : ValueCell.create(mb),
-                    indexBuffer: lines ? ValueCell.update(lines.indexBuffer, ib) : ValueCell.create(ib),
-                    groupBuffer: lines ? ValueCell.update(lines.groupBuffer, gb) : ValueCell.create(gb),
-                    startBuffer: lines ? ValueCell.update(lines.startBuffer, sb) : ValueCell.create(sb),
-                    endBuffer: lines ? ValueCell.update(lines.endBuffer, eb) : ValueCell.create(eb),
-                }
+                return Lines.create(mb, ib, gb, sb, eb, indices.elementCount / 2)
             }
         }
     }

+ 66 - 31
src/mol-geo/geometry/lines/lines.ts

@@ -1,5 +1,5 @@
 /**
- * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
@@ -17,19 +17,22 @@ import { LinesValues } from '../../../mol-gl/renderable/lines';
 import { Mesh } from '../mesh/mesh';
 import { LinesBuilder } from './lines-builder';
 import { ParamDefinition as PD } from '../../../mol-util/param-definition';
-import { calculateBoundingSphere } from '../../../mol-gl/renderable/util';
+import { calculateInvariantBoundingSphere, calculateTransformBoundingSphere } from '../../../mol-gl/renderable/util';
 import { Sphere3D } from '../../../mol-math/geometry';
 import { Theme } from '../../../mol-theme/theme';
 import { Color } from '../../../mol-util/color';
 import { BaseGeometry } from '../base';
 import { createEmptyOverpaint } from '../overpaint-data';
 import { createEmptyTransparency } from '../transparency-data';
+import { hashFnv32a } from '../../../mol-data/util';
 
 /** Wide line */
 export interface Lines {
     readonly kind: 'lines',
+
     /** Number of lines */
     lineCount: number,
+
     /** Mapping buffer as array of xy values wrapped in a value cell */
     readonly mappingBuffer: ValueCell<Float32Array>,
     /** Index buffer as array of vertex index triplets wrapped in a value cell */
@@ -40,24 +43,25 @@ export interface Lines {
     readonly startBuffer: ValueCell<Float32Array>,
     /** Line end buffer as array of xyz values wrapped in a value cell */
     readonly endBuffer: ValueCell<Float32Array>,
+
+    /** Bounding sphere of the lines */
+    readonly boundingSphere: Sphere3D
 }
 
 export namespace Lines {
+    export function create(mappings: Float32Array, indices: Uint32Array, groups: Float32Array, starts: Float32Array, ends: Float32Array, lineCount: number, lines?: Lines): Lines {
+        return lines ?
+            update(mappings, indices, groups, starts, ends, lineCount, lines) :
+            fromArrays(mappings, indices, groups, starts, ends, lineCount)
+    }
+
     export function createEmpty(lines?: Lines): Lines {
         const mb = lines ? lines.mappingBuffer.ref.value : new Float32Array(0)
         const ib = lines ? lines.indexBuffer.ref.value : new Uint32Array(0)
         const gb = lines ? lines.groupBuffer.ref.value : new Float32Array(0)
         const sb = lines ? lines.startBuffer.ref.value : new Float32Array(0)
         const eb = lines ? lines.endBuffer.ref.value : new Float32Array(0)
-        return {
-            kind: 'lines',
-            lineCount: 0,
-            mappingBuffer: lines ? ValueCell.update(lines.mappingBuffer, mb) : ValueCell.create(mb),
-            indexBuffer: lines ? ValueCell.update(lines.indexBuffer, ib) : ValueCell.create(ib),
-            groupBuffer: lines ? ValueCell.update(lines.groupBuffer, gb) : ValueCell.create(gb),
-            startBuffer: lines ? ValueCell.update(lines.startBuffer, sb) : ValueCell.create(sb),
-            endBuffer: lines ? ValueCell.update(lines.endBuffer, eb) : ValueCell.create(eb),
-        }
+        return create(mb, ib, gb, sb, eb, 0, lines)
     }
 
     export function fromMesh(mesh: Mesh, lines?: Lines) {
@@ -81,16 +85,57 @@ export namespace Lines {
         return builder.getLines();
     }
 
-    export function transformImmediate(line: Lines, t: Mat4) {
-        transformRangeImmediate(line, t, 0, line.lineCount)
+    function hashCode(lines: Lines) {
+        return hashFnv32a([
+            lines.lineCount, lines.mappingBuffer.ref.version, lines.indexBuffer.ref.version,
+            lines.groupBuffer.ref.version, lines.startBuffer.ref.version, lines.startBuffer.ref.version
+        ])
+    }
+
+    function fromArrays(mappings: Float32Array, indices: Uint32Array, groups: Float32Array, starts: Float32Array, ends: Float32Array, lineCount: number): Lines {
+
+        const boundingSphere = Sphere3D()
+        let currentHash = -1
+
+        const lines = {
+            kind: 'lines' as const,
+            lineCount,
+            mappingBuffer: ValueCell.create(mappings),
+            indexBuffer: ValueCell.create(indices),
+            groupBuffer: ValueCell.create(groups),
+            startBuffer: ValueCell.create(starts),
+            endBuffer: ValueCell.create(ends),
+            get boundingSphere() {
+                const newHash = hashCode(lines)
+                if (newHash !== currentHash) {
+                    const s = calculateInvariantBoundingSphere(lines.startBuffer.ref.value, lines.lineCount * 4, 4)
+                    const e = calculateInvariantBoundingSphere(lines.endBuffer.ref.value, lines.lineCount * 4, 4)
+
+                    Sphere3D.copy(boundingSphere, Sphere3D.expandBySphere(s, e))
+                    currentHash = newHash
+                }
+                return boundingSphere
+            },
+        }
+        return lines
+    }
+
+    function update(mappings: Float32Array, indices: Uint32Array, groups: Float32Array, starts: Float32Array, ends: Float32Array, lineCount: number, lines: Lines) {
+        lines.lineCount = lineCount
+        ValueCell.update(lines.mappingBuffer, mappings)
+        ValueCell.update(lines.indexBuffer, indices)
+        ValueCell.update(lines.groupBuffer, groups)
+        ValueCell.update(lines.startBuffer, starts)
+        ValueCell.update(lines.endBuffer, ends)
+        return lines
     }
 
-    export function transformRangeImmediate(lines: Lines, t: Mat4, offset: number, count: number) {
+    export function transform(lines: Lines, t: Mat4) {
         const start = lines.startBuffer.ref.value
-        transformPositionArray(t, start, offset, count * 4)
+        transformPositionArray(t, start, 0, lines.lineCount * 4)
         ValueCell.update(lines.startBuffer, start);
         const end = lines.endBuffer.ref.value
-        transformPositionArray(t, end, offset, count * 4)
+        transformPositionArray(t, end, 0, lines.lineCount * 4)
         ValueCell.update(lines.endBuffer, end);
     }
 
@@ -124,8 +169,8 @@ export namespace Lines {
 
         const counts = { drawCount: lines.lineCount * 2 * 3, groupCount, instanceCount }
 
-        const { boundingSphere, invariantBoundingSphere } = getBoundingSphere(lines.startBuffer.ref.value, lines.endBuffer.ref.value, lines.lineCount,
-            transform.aTransform.ref.value, transform.instanceCount.ref.value)
+        const invariantBoundingSphere = Sphere3D.clone(lines.boundingSphere)
+        const boundingSphere = calculateTransformBoundingSphere(invariantBoundingSphere, transform.aTransform.ref.value, instanceCount)
 
         return {
             aMapping: lines.mappingBuffer,
@@ -163,10 +208,9 @@ export namespace Lines {
     }
 
     function updateBoundingSphere(values: LinesValues, lines: Lines) {
-        const { boundingSphere, invariantBoundingSphere } = getBoundingSphere(
-            values.aStart.ref.value, values.aEnd.ref.value, lines.lineCount,
-            values.aTransform.ref.value, values.instanceCount.ref.value
-        )
+        const invariantBoundingSphere = Sphere3D.clone(lines.boundingSphere)
+        const boundingSphere = calculateTransformBoundingSphere(invariantBoundingSphere, values.aTransform.ref.value, values.instanceCount.ref.value)
+
         if (!Sphere3D.equals(boundingSphere, values.boundingSphere.ref.value)) {
             ValueCell.update(values.boundingSphere, boundingSphere)
         }
@@ -174,13 +218,4 @@ export namespace Lines {
             ValueCell.update(values.invariantBoundingSphere, invariantBoundingSphere)
         }
     }
-}
-
-function getBoundingSphere(lineStart: Float32Array, lineEnd: Float32Array, lineCount: number, transform: Float32Array, transformCount: number) {
-    const start = calculateBoundingSphere(lineStart, lineCount * 4, transform, transformCount, 0, 4)
-    const end = calculateBoundingSphere(lineEnd, lineCount * 4, transform, transformCount, 0, 4)
-    return {
-        boundingSphere: Sphere3D.expandBySphere(start.boundingSphere, end.boundingSphere),
-        invariantBoundingSphere: Sphere3D.expandBySphere(start.invariantBoundingSphere, end.invariantBoundingSphere)
-    }
 }

+ 90 - 0
src/mol-geo/geometry/mesh/laplacian-smoothing.ts

@@ -0,0 +1,90 @@
+/**
+ * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author David Sehnal <david.sehnal@gmail.com>
+ */
+
+// TODO
+
+//     function addVertex(src: Float32Array, i: number, dst: Float32Array, j: number) {
+//         dst[3 * j] += src[3 * i];
+//         dst[3 * j + 1] += src[3 * i + 1];
+//         dst[3 * j + 2] += src[3 * i + 2];
+//     }
+
+//     function laplacianSmoothIter(surface: Surface, vertexCounts: Int32Array, vs: Float32Array, vertexWeight: number) {
+//         const triCount = surface.triangleIndices.length,
+//             src = surface.vertices;
+
+//         const triangleIndices = surface.triangleIndices;
+
+//         for (let i = 0; i < triCount; i += 3) {
+//             const a = triangleIndices[i],
+//                 b = triangleIndices[i + 1],
+//                 c = triangleIndices[i + 2];
+
+//             addVertex(src, b, vs, a);
+//             addVertex(src, c, vs, a);
+
+//             addVertex(src, a, vs, b);
+//             addVertex(src, c, vs, b);
+
+//             addVertex(src, a, vs, c);
+//             addVertex(src, b, vs, c);
+//         }
+
+//         const vw = 2 * vertexWeight;
+//         for (let i = 0, _b = surface.vertexCount; i < _b; i++) {
+//             const n = vertexCounts[i] + vw;
+//             vs[3 * i] = (vs[3 * i] + vw * src[3 * i]) / n;
+//             vs[3 * i + 1] = (vs[3 * i + 1] + vw * src[3 * i + 1]) / n;
+//             vs[3 * i + 2] = (vs[3 * i + 2] + vw * src[3 * i + 2]) / n;
+//         }
+//     }
+
+//     async function laplacianSmoothComputation(ctx: Computation.Context, surface: Surface, iterCount: number, vertexWeight: number) {
+//         await ctx.updateProgress('Smoothing surface...', true);
+
+//         const vertexCounts = new Int32Array(surface.vertexCount),
+//             triCount = surface.triangleIndices.length;
+
+//         const tris = surface.triangleIndices;
+//         for (let i = 0; i < triCount; i++) {
+//             // in a triangle 2 edges touch each vertex, hence the constant.
+//             vertexCounts[tris[i]] += 2;
+//         }
+
+//         let vs = new Float32Array(surface.vertices.length);
+//         let started = Utils.PerformanceMonitor.currentTime();
+//         await ctx.updateProgress('Smoothing surface...', true);
+//         for (let i = 0; i < iterCount; i++) {
+//             if (i > 0) {
+//                 for (let j = 0, _b = vs.length; j < _b; j++) vs[j] = 0;
+//             }
+//             surface.normals = void 0;
+//             laplacianSmoothIter(surface, vertexCounts, vs, vertexWeight);
+//             const t = surface.vertices;
+//             surface.vertices = <any>vs;
+//             vs = <any>t;
+
+//             const time = Utils.PerformanceMonitor.currentTime();
+//             if (time - started > Computation.UpdateProgressDelta) {
+//                 started = time;
+//                 await ctx.updateProgress('Smoothing surface...', true, i + 1, iterCount);
+//             }
+//         }
+//         return surface;
+//     }
+
+//     /*
+//      * Smooths the vertices by averaging the neighborhood.
+//      *
+//      * Resets normals. Might replace vertex array.
+//      */
+//     export function laplacianSmooth(surface: Surface, iterCount: number = 1, vertexWeight: number = 1): Computation<Surface> {
+
+//         if (iterCount < 1) iterCount = 0;
+//         if (iterCount === 0) return Computation.resolve(surface);
+
+//         return computation(async ctx => await laplacianSmoothComputation(ctx, surface, iterCount, (1.1 * vertexWeight) / 1.1));
+//     }

+ 2 - 12
src/mol-geo/geometry/mesh/mesh-builder.ts

@@ -1,10 +1,9 @@
 /**
- * Copyright (c) 2018-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
 
-import { ValueCell } from '../../../mol-util/value-cell'
 import { Vec3, Mat4, Mat3 } from '../../../mol-math/linear-algebra';
 import { ChunkedArray } from '../../../mol-data/util';
 import { Mesh } from './mesh';
@@ -141,15 +140,6 @@ export namespace MeshBuilder {
         const ib = ChunkedArray.compact(indices, true) as Uint32Array
         const nb = ChunkedArray.compact(normals, true) as Float32Array
         const gb = ChunkedArray.compact(groups, true) as Float32Array
-        return {
-            kind: 'mesh',
-            vertexCount: state.vertices.elementCount,
-            triangleCount: state.indices.elementCount,
-            vertexBuffer: mesh ? ValueCell.update(mesh.vertexBuffer, vb) : ValueCell.create(vb),
-            indexBuffer: mesh ? ValueCell.update(mesh.indexBuffer, ib) : ValueCell.create(ib),
-            normalBuffer: mesh ? ValueCell.update(mesh.normalBuffer, nb) : ValueCell.create(nb),
-            groupBuffer: mesh ? ValueCell.update(mesh.groupBuffer, gb) : ValueCell.create(gb),
-            normalsComputed: true,
-        }
+        return Mesh.create(vb, ib, nb, gb, state.vertices.elementCount, state.indices.elementCount, mesh)
     }
 }

+ 62 - 175
src/mol-geo/geometry/mesh/mesh.ts

@@ -1,22 +1,23 @@
 /**
- * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
  * @author David Sehnal <david.sehnal@gmail.com>
  */
 
-import { Task } from '../../../mol-task'
 import { ValueCell } from '../../../mol-util'
-import { Vec3, Mat4 } from '../../../mol-math/linear-algebra'
+import { Vec3, Mat4, Mat3 } from '../../../mol-math/linear-algebra'
 import { Sphere3D } from '../../../mol-math/geometry'
-import { transformPositionArray/* , transformDirectionArray, getNormalMatrix */ } from '../../util';
+import { transformPositionArray,/* , transformDirectionArray, getNormalMatrix */
+transformDirectionArray} from '../../util';
 import { GeometryUtils } from '../geometry';
 import { createMarkers } from '../marker-data';
 import { TransformData } from '../transform-data';
 import { LocationIterator } from '../../util/location-iterator';
 import { createColors } from '../color-data';
-import { ChunkedArray } from '../../../mol-data/util';
+import { ChunkedArray, hashFnv32a } from '../../../mol-data/util';
 import { ParamDefinition as PD } from '../../../mol-util/param-definition';
-import { calculateBoundingSphere } from '../../../mol-gl/renderable/util';
+import { calculateInvariantBoundingSphere, calculateTransformBoundingSphere } from '../../../mol-gl/renderable/util';
 import { Theme } from '../../../mol-theme/theme';
 import { MeshValues } from '../../../mol-gl/renderable/mesh';
 import { Color } from '../../../mol-util/color';
@@ -41,47 +42,70 @@ export interface Mesh {
     /** Group buffer as array of group ids for each vertex wrapped in a value cell */
     readonly groupBuffer: ValueCell<Float32Array>,
 
-    /** Flag indicating if normals are computed for the current set of vertices */
-    normalsComputed: boolean,
-
     /** Bounding sphere of the mesh */
-    boundingSphere?: Sphere3D
+    readonly boundingSphere: Sphere3D
 }
 
 export namespace Mesh {
+    export function create(vertices: Float32Array, indices: Uint32Array, normals: Float32Array, groups: Float32Array, vertexCount: number, triangleCount: number, mesh?: Mesh): Mesh {
+        return mesh ?
+            update(vertices, indices, normals, groups, vertexCount, triangleCount, mesh) :
+            fromArrays(vertices, indices, normals, groups, vertexCount, triangleCount)
+    }
+
     export function createEmpty(mesh?: Mesh): Mesh {
         const vb = mesh ? mesh.vertexBuffer.ref.value : new Float32Array(0)
         const ib = mesh ? mesh.indexBuffer.ref.value : new Uint32Array(0)
         const nb = mesh ? mesh.normalBuffer.ref.value : new Float32Array(0)
         const gb = mesh ? mesh.groupBuffer.ref.value : new Float32Array(0)
-        return {
-            kind: 'mesh',
-            vertexCount: 0,
-            triangleCount: 0,
-            vertexBuffer: mesh ? ValueCell.update(mesh.vertexBuffer, vb) : ValueCell.create(vb),
-            indexBuffer: mesh ? ValueCell.update(mesh.indexBuffer, ib) : ValueCell.create(ib),
-            normalBuffer: mesh ? ValueCell.update(mesh.normalBuffer, nb) : ValueCell.create(nb),
-            groupBuffer: mesh ? ValueCell.update(mesh.groupBuffer, gb) : ValueCell.create(gb),
-            normalsComputed: true,
-        }
+        return create(vb, ib, nb, gb, 0, 0, mesh)
     }
 
-    export function fromArrays(vertices: Float32Array, indices: Uint32Array, normals: Float32Array, groups: Float32Array, vertexCount: number, triangleCount: number, normalsComputed: boolean): Mesh {
-        return {
-            kind: 'mesh',
+    function hashCode(mesh: Mesh) {
+        return hashFnv32a([
+            mesh.vertexCount, mesh.triangleCount,
+            mesh.vertexBuffer.ref.version, mesh.indexBuffer.ref.version,
+            mesh.normalBuffer.ref.version, mesh.groupBuffer.ref.version
+        ])
+    }
+
+    function fromArrays(vertices: Float32Array, indices: Uint32Array, normals: Float32Array, groups: Float32Array, vertexCount: number, triangleCount: number): Mesh {
+
+        const boundingSphere = Sphere3D()
+        let currentHash = -1
+
+        const mesh = {
+            kind: 'mesh' as const,
             vertexCount,
             triangleCount,
             vertexBuffer: ValueCell.create(vertices),
             indexBuffer: ValueCell.create(indices),
             normalBuffer: ValueCell.create(normals),
             groupBuffer: ValueCell.create(groups),
-            normalsComputed,
+            get boundingSphere() {
+                const newHash = hashCode(mesh)
+                if (newHash !== currentHash) {
+                    const b = calculateInvariantBoundingSphere(mesh.vertexBuffer.ref.value, mesh.vertexCount, 1)
+                    Sphere3D.copy(boundingSphere, b)
+                    currentHash = newHash
+                }
+                return boundingSphere
+            },
         }
+        return mesh
     }
 
-    export function computeNormalsImmediate(mesh: Mesh) {
-        if (mesh.normalsComputed) return;
+    function update(vertices: Float32Array, indices: Uint32Array, normals: Float32Array, groups: Float32Array, vertexCount: number, triangleCount: number, mesh: Mesh) {
+        mesh.vertexCount = vertexCount
+        mesh.triangleCount = triangleCount
+        ValueCell.update(mesh.vertexBuffer, vertices)
+        ValueCell.update(mesh.indexBuffer, indices)
+        ValueCell.update(mesh.normalBuffer, normals)
+        ValueCell.update(mesh.groupBuffer, groups)
+        return mesh
+    }
 
+    export function computeNormals(mesh: Mesh) {
         const normals = mesh.normalBuffer.ref.value.length >= mesh.vertexCount * 3
             ? mesh.normalBuffer.ref.value : new Float32Array(mesh.vertexBuffer.ref.value.length);
 
@@ -115,11 +139,8 @@ export namespace Mesh {
             const nz = normals[i + 2];
             const f = 1.0 / Math.sqrt(nx * nx + ny * ny + nz * nz);
             normals[i] *= f; normals[i + 1] *= f; normals[i + 2] *= f;
-
-            // console.log([normals[i], normals[i + 1], normals[i + 2]], [v[i], v[i + 1], v[i + 2]])
         }
         ValueCell.update(mesh.normalBuffer, normals);
-        mesh.normalsComputed = true;
     }
 
     export function checkForDuplicateVertices(mesh: Mesh, fractionDigits = 3) {
@@ -144,63 +165,15 @@ export namespace Mesh {
         return duplicates
     }
 
-    export function computeNormals(surface: Mesh): Task<Mesh> {
-        return Task.create<Mesh>('Surface (Compute Normals)', async ctx => {
-            if (surface.normalsComputed) return surface;
-
-            await ctx.update('Computing normals...');
-            computeNormalsImmediate(surface);
-            return surface;
-        });
-    }
-
-    export function transformImmediate(mesh: Mesh, t: Mat4) {
-        transformRangeImmediate(mesh, t, 0, mesh.vertexCount)
-    }
-
-    export function transformRangeImmediate(mesh: Mesh, t: Mat4, offset: number, count: number) {
+    const tmpMat3 = Mat3.zero()
+    export function transform(mesh: Mesh, t: Mat4) {
         const v = mesh.vertexBuffer.ref.value
-        transformPositionArray(t, v, offset, count)
-        // TODO normals transformation does not work for an unknown reason, ASR
-        // if (mesh.normalBuffer.ref.value) {
-        //     const n = getNormalMatrix(Mat3.zero(), t)
-        //     transformDirectionArray(n, mesh.normalBuffer.ref.value, offset, count)
-        //     mesh.normalsComputed = true;
-        // }
+        transformPositionArray(t, v, 0, mesh.vertexCount)
+        if (!Mat4.isTranslationAndUniformScaling(t)) {
+            const n = Mat3.directionTransform(tmpMat3, t)
+            transformDirectionArray(n, mesh.normalBuffer.ref.value, 0, mesh.vertexCount)
+        }
         ValueCell.update(mesh.vertexBuffer, v);
-        mesh.normalsComputed = false;
-    }
-
-    export function computeBoundingSphere(mesh: Mesh): Task<Mesh> {
-        return Task.create<Mesh>('Mesh (Compute Bounding Sphere)', async ctx => {
-            if (mesh.boundingSphere) {
-                return mesh;
-            }
-            await ctx.update('Computing bounding sphere...');
-
-            const vertices = mesh.vertexBuffer.ref.value;
-            let x = 0, y = 0, z = 0;
-            for (let i = 0, _c = vertices.length; i < _c; i += 3) {
-                x += vertices[i];
-                y += vertices[i + 1];
-                z += vertices[i + 2];
-            }
-            x /= mesh.vertexCount;
-            y /= mesh.vertexCount;
-            z /= mesh.vertexCount;
-            let r = 0;
-            for (let i = 0, _c = vertices.length; i < _c; i += 3) {
-                const dx = x - vertices[i];
-                const dy = y - vertices[i + 1];
-                const dz = z - vertices[i + 2];
-                r = Math.max(r, dx * dx + dy * dy + dz * dz);
-            }
-            mesh.boundingSphere = {
-                center: Vec3.create(x, y, z),
-                radius: Math.sqrt(r)
-            }
-            return mesh;
-        });
     }
 
     /**
@@ -389,10 +362,8 @@ export namespace Mesh {
 
         const counts = { drawCount: mesh.triangleCount * 3, groupCount, instanceCount }
 
-        const { boundingSphere, invariantBoundingSphere } = calculateBoundingSphere(
-            mesh.vertexBuffer.ref.value, mesh.vertexCount,
-            transform.aTransform.ref.value, instanceCount
-        )
+        const invariantBoundingSphere = Sphere3D.clone(mesh.boundingSphere)
+        const boundingSphere = calculateTransformBoundingSphere(invariantBoundingSphere, transform.aTransform.ref.value, instanceCount)
 
         return {
             aPosition: mesh.vertexBuffer,
@@ -430,10 +401,9 @@ export namespace Mesh {
     }
 
     function updateBoundingSphere(values: MeshValues, mesh: Mesh) {
-        const { boundingSphere, invariantBoundingSphere } = calculateBoundingSphere(
-            values.aPosition.ref.value, mesh.vertexCount,
-            values.aTransform.ref.value, values.instanceCount.ref.value
-        )
+        const invariantBoundingSphere = Sphere3D.clone(mesh.boundingSphere)
+        const boundingSphere = calculateTransformBoundingSphere(invariantBoundingSphere, values.aTransform.ref.value, values.instanceCount.ref.value)
+
         if (!Sphere3D.equals(boundingSphere, values.boundingSphere.ref.value)) {
             ValueCell.update(values.boundingSphere, boundingSphere)
         }
@@ -442,86 +412,3 @@ export namespace Mesh {
         }
     }
 }
-
-//     function addVertex(src: Float32Array, i: number, dst: Float32Array, j: number) {
-//         dst[3 * j] += src[3 * i];
-//         dst[3 * j + 1] += src[3 * i + 1];
-//         dst[3 * j + 2] += src[3 * i + 2];
-//     }
-
-//     function laplacianSmoothIter(surface: Surface, vertexCounts: Int32Array, vs: Float32Array, vertexWeight: number) {
-//         const triCount = surface.triangleIndices.length,
-//             src = surface.vertices;
-
-//         const triangleIndices = surface.triangleIndices;
-
-//         for (let i = 0; i < triCount; i += 3) {
-//             const a = triangleIndices[i],
-//                 b = triangleIndices[i + 1],
-//                 c = triangleIndices[i + 2];
-
-//             addVertex(src, b, vs, a);
-//             addVertex(src, c, vs, a);
-
-//             addVertex(src, a, vs, b);
-//             addVertex(src, c, vs, b);
-
-//             addVertex(src, a, vs, c);
-//             addVertex(src, b, vs, c);
-//         }
-
-//         const vw = 2 * vertexWeight;
-//         for (let i = 0, _b = surface.vertexCount; i < _b; i++) {
-//             const n = vertexCounts[i] + vw;
-//             vs[3 * i] = (vs[3 * i] + vw * src[3 * i]) / n;
-//             vs[3 * i + 1] = (vs[3 * i + 1] + vw * src[3 * i + 1]) / n;
-//             vs[3 * i + 2] = (vs[3 * i + 2] + vw * src[3 * i + 2]) / n;
-//         }
-//     }
-
-//     async function laplacianSmoothComputation(ctx: Computation.Context, surface: Surface, iterCount: number, vertexWeight: number) {
-//         await ctx.updateProgress('Smoothing surface...', true);
-
-//         const vertexCounts = new Int32Array(surface.vertexCount),
-//             triCount = surface.triangleIndices.length;
-
-//         const tris = surface.triangleIndices;
-//         for (let i = 0; i < triCount; i++) {
-//             // in a triangle 2 edges touch each vertex, hence the constant.
-//             vertexCounts[tris[i]] += 2;
-//         }
-
-//         let vs = new Float32Array(surface.vertices.length);
-//         let started = Utils.PerformanceMonitor.currentTime();
-//         await ctx.updateProgress('Smoothing surface...', true);
-//         for (let i = 0; i < iterCount; i++) {
-//             if (i > 0) {
-//                 for (let j = 0, _b = vs.length; j < _b; j++) vs[j] = 0;
-//             }
-//             surface.normals = void 0;
-//             laplacianSmoothIter(surface, vertexCounts, vs, vertexWeight);
-//             const t = surface.vertices;
-//             surface.vertices = <any>vs;
-//             vs = <any>t;
-
-//             const time = Utils.PerformanceMonitor.currentTime();
-//             if (time - started > Computation.UpdateProgressDelta) {
-//                 started = time;
-//                 await ctx.updateProgress('Smoothing surface...', true, i + 1, iterCount);
-//             }
-//         }
-//         return surface;
-//     }
-
-//     /*
-//      * Smooths the vertices by averaging the neighborhood.
-//      *
-//      * Resets normals. Might replace vertex array.
-//      */
-//     export function laplacianSmooth(surface: Surface, iterCount: number = 1, vertexWeight: number = 1): Computation<Surface> {
-
-//         if (iterCount < 1) iterCount = 0;
-//         if (iterCount === 0) return Computation.resolve(surface);
-
-//         return computation(async ctx => await laplacianSmoothComputation(ctx, surface, iterCount, (1.1 * vertexWeight) / 1.1));
-//     }

+ 2 - 8
src/mol-geo/geometry/points/points-builder.ts

@@ -1,10 +1,9 @@
 /**
- * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
 
-import { ValueCell } from '../../../mol-util/value-cell'
 import { ChunkedArray } from '../../../mol-data/util';
 import { Points } from './points';
 
@@ -26,12 +25,7 @@ export namespace PointsBuilder {
             getPoints: () => {
                 const cb = ChunkedArray.compact(centers, true) as Float32Array
                 const gb = ChunkedArray.compact(groups, true) as Float32Array
-                return {
-                    kind: 'points',
-                    pointCount: centers.elementCount,
-                    centerBuffer: points ? ValueCell.update(points.centerBuffer, cb) : ValueCell.create(cb),
-                    groupBuffer: points ? ValueCell.update(points.groupBuffer, gb) : ValueCell.create(gb),
-                }
+                return Points.create(cb, gb, centers.elementCount, points)
             }
         }
     }

+ 55 - 19
src/mol-geo/geometry/points/points.ts

@@ -1,5 +1,5 @@
 /**
- * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
@@ -14,7 +14,7 @@ import { createSizes } from '../size-data';
 import { TransformData } from '../transform-data';
 import { LocationIterator } from '../../util/location-iterator';
 import { ParamDefinition as PD } from '../../../mol-util/param-definition';
-import { calculateBoundingSphere } from '../../../mol-gl/renderable/util';
+import { calculateInvariantBoundingSphere, calculateTransformBoundingSphere } from '../../../mol-gl/renderable/util';
 import { Sphere3D } from '../../../mol-math/geometry';
 import { Theme } from '../../../mol-theme/theme';
 import { PointsValues } from '../../../mol-gl/renderable/points';
@@ -23,37 +23,76 @@ import { Color } from '../../../mol-util/color';
 import { BaseGeometry } from '../base';
 import { createEmptyOverpaint } from '../overpaint-data';
 import { createEmptyTransparency } from '../transparency-data';
+import { hashFnv32a } from '../../../mol-data/util';
 
 /** Point cloud */
 export interface Points {
     readonly kind: 'points',
+
     /** Number of vertices in the point cloud */
     pointCount: number,
+
     /** Center buffer as array of xyz values wrapped in a value cell */
     readonly centerBuffer: ValueCell<Float32Array>,
     /** Group buffer as array of group ids for each vertex wrapped in a value cell */
     readonly groupBuffer: ValueCell<Float32Array>,
+
+    /** Bounding sphere of the points */
+    readonly boundingSphere: Sphere3D
 }
 
 export namespace Points {
+    export function create(centers: Float32Array, groups: Float32Array, pointCount: number, points?: Points): Points {
+        return points ?
+            update(centers, groups, pointCount, points) :
+            fromArrays(centers, groups, pointCount)
+    }
+
     export function createEmpty(points?: Points): Points {
         const cb = points ? points.centerBuffer.ref.value : new Float32Array(0)
         const gb = points ? points.groupBuffer.ref.value : new Float32Array(0)
-        return {
-            kind: 'points',
-            pointCount: 0,
-            centerBuffer: points ? ValueCell.update(points.centerBuffer, cb) : ValueCell.create(cb),
-            groupBuffer: points ? ValueCell.update(points.groupBuffer, gb) : ValueCell.create(gb),
+        return create(cb, gb, 0, points)
+    }
+
+    function hashCode(points: Points) {
+        return hashFnv32a([
+            points.pointCount, points.centerBuffer.ref.version, points.groupBuffer.ref.version,
+        ])
+    }
+
+    function fromArrays(centers: Float32Array, groups: Float32Array, pointCount: number): Points {
+
+        const boundingSphere = Sphere3D()
+        let currentHash = -1
+
+        const points = {
+            kind: 'points' as const,
+            pointCount,
+            centerBuffer: ValueCell.create(centers),
+            groupBuffer: ValueCell.create(groups),
+            get boundingSphere() {
+                const newHash = hashCode(points)
+                if (newHash !== currentHash) {
+                    const b = calculateInvariantBoundingSphere(points.centerBuffer.ref.value, points.pointCount, 1)
+                    Sphere3D.copy(boundingSphere, b)
+                    currentHash = newHash
+                }
+                return boundingSphere
+            },
         }
+        return points
     }
 
-    export function transformImmediate(points: Points, t: Mat4) {
-        transformRangeImmediate(points, t, 0, points.pointCount)
+    function update(centers: Float32Array, groups: Float32Array, pointCount: number, points: Points) {
+        points.pointCount = pointCount
+        ValueCell.update(points.centerBuffer, centers)
+        ValueCell.update(points.groupBuffer, groups)
+        return points
     }
 
-    export function transformRangeImmediate(points: Points, t: Mat4, offset: number, count: number) {
+    export function transform(points: Points, t: Mat4) {
         const c = points.centerBuffer.ref.value
-        transformPositionArray(t, c, offset, count)
+        transformPositionArray(t, c, 0, points.pointCount)
         ValueCell.update(points.centerBuffer, c);
     }
 
@@ -89,10 +128,8 @@ export namespace Points {
 
         const counts = { drawCount: points.pointCount, groupCount, instanceCount }
 
-        const { boundingSphere, invariantBoundingSphere } = calculateBoundingSphere(
-            points.centerBuffer.ref.value, points.pointCount,
-            transform.aTransform.ref.value, transform.instanceCount.ref.value
-        )
+        const invariantBoundingSphere = Sphere3D.clone(points.boundingSphere)
+        const boundingSphere = calculateTransformBoundingSphere(invariantBoundingSphere, transform.aTransform.ref.value, instanceCount)
 
         return {
             aPosition: points.centerBuffer,
@@ -129,10 +166,9 @@ export namespace Points {
     }
 
     function updateBoundingSphere(values: PointsValues, points: Points) {
-        const { boundingSphere, invariantBoundingSphere } = calculateBoundingSphere(
-            values.aPosition.ref.value, points.pointCount,
-            values.aTransform.ref.value, values.instanceCount.ref.value
-        )
+        const invariantBoundingSphere = Sphere3D.clone(points.boundingSphere)
+        const boundingSphere = calculateTransformBoundingSphere(invariantBoundingSphere, values.aTransform.ref.value, values.instanceCount.ref.value)
+
         if (!Sphere3D.equals(boundingSphere, values.boundingSphere.ref.value)) {
             ValueCell.update(values.boundingSphere, boundingSphere)
         }

+ 2 - 10
src/mol-geo/geometry/spheres/spheres-builder.ts

@@ -1,10 +1,9 @@
 /**
- * Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2019-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
 
-import { ValueCell } from '../../../mol-util/value-cell'
 import { ChunkedArray } from '../../../mol-data/util';
 import { Spheres } from './spheres';
 
@@ -50,14 +49,7 @@ export namespace SpheresBuilder {
                 const mb = ChunkedArray.compact(mappings, true) as Float32Array
                 const ib = ChunkedArray.compact(indices, true) as Uint32Array
                 const gb = ChunkedArray.compact(groups, true) as Float32Array
-                return {
-                    kind: 'spheres',
-                    sphereCount: centers.elementCount / 4,
-                    centerBuffer: spheres ? ValueCell.update(spheres.centerBuffer, cb) : ValueCell.create(cb),
-                    mappingBuffer: spheres ? ValueCell.update(spheres.mappingBuffer, mb) : ValueCell.create(mb),
-                    indexBuffer: spheres ? ValueCell.update(spheres.indexBuffer, ib) : ValueCell.create(ib),
-                    groupBuffer: spheres ? ValueCell.update(spheres.groupBuffer, gb) : ValueCell.create(gb),
-                }
+                return Spheres.create(cb, mb, ib, gb, centers.elementCount / 4, spheres)
             }
         }
     }

+ 59 - 18
src/mol-geo/geometry/spheres/spheres.ts

@@ -1,5 +1,5 @@
 /**
- * Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2019-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
@@ -13,15 +13,15 @@ import { Theme } from '../../../mol-theme/theme';
 import { SpheresValues } from '../../../mol-gl/renderable/spheres';
 import { createColors } from '../color-data';
 import { createMarkers } from '../marker-data';
-import { calculateBoundingSphere } from '../../../mol-gl/renderable/util';
+import { calculateInvariantBoundingSphere, calculateTransformBoundingSphere } from '../../../mol-gl/renderable/util';
 import { Sphere3D } from '../../../mol-math/geometry';
 import { createSizes, getMaxSize } from '../size-data';
 import { Color } from '../../../mol-util/color';
 import { BaseGeometry } from '../base';
 import { createEmptyOverpaint } from '../overpaint-data';
 import { createEmptyTransparency } from '../transparency-data';
+import { hashFnv32a } from '../../../mol-data/util';
 
-/** Spheres */
 export interface Spheres {
     readonly kind: 'spheres',
 
@@ -36,22 +36,66 @@ export interface Spheres {
     readonly indexBuffer: ValueCell<Uint32Array>,
     /** Group buffer as array of group ids for each vertex wrapped in a value cell */
     readonly groupBuffer: ValueCell<Float32Array>,
+
+    /** Bounding sphere of the spheres */
+    readonly boundingSphere: Sphere3D
 }
 
 export namespace Spheres {
+    export function create(centers: Float32Array, mappings: Float32Array, indices: Uint32Array, groups: Float32Array, sphereCount: number, spheres?: Spheres): Spheres {
+        return spheres ?
+            update(centers, mappings, indices, groups, sphereCount, spheres) :
+            fromArrays(centers, mappings, indices, groups, sphereCount)
+    }
+
     export function createEmpty(spheres?: Spheres): Spheres {
         const cb = spheres ? spheres.centerBuffer.ref.value : new Float32Array(0)
         const mb = spheres ? spheres.mappingBuffer.ref.value : new Float32Array(0)
         const ib = spheres ? spheres.indexBuffer.ref.value : new Uint32Array(0)
         const gb = spheres ? spheres.groupBuffer.ref.value : new Float32Array(0)
-        return {
-            kind: 'spheres',
-            sphereCount: 0,
-            centerBuffer: spheres ? ValueCell.update(spheres.centerBuffer, cb) : ValueCell.create(cb),
-            mappingBuffer: spheres ? ValueCell.update(spheres.mappingBuffer, mb) : ValueCell.create(mb),
-            indexBuffer: spheres ? ValueCell.update(spheres.indexBuffer, ib) : ValueCell.create(ib),
-            groupBuffer: spheres ? ValueCell.update(spheres.groupBuffer, gb) : ValueCell.create(gb)
+        return create(cb, mb, ib, gb, 0, spheres)
+    }
+
+    function hashCode(spheres: Spheres) {
+        return hashFnv32a([
+            spheres.sphereCount,
+            spheres.centerBuffer.ref.version, spheres.mappingBuffer.ref.version,
+            spheres.indexBuffer.ref.version, spheres.groupBuffer.ref.version
+        ])
+    }
+
+    function fromArrays(centers: Float32Array, mappings: Float32Array, indices: Uint32Array, groups: Float32Array, sphereCount: number): Spheres {
+
+        const boundingSphere = Sphere3D()
+        let currentHash = -1
+
+        const spheres = {
+            kind: 'spheres' as const,
+            sphereCount,
+            centerBuffer: ValueCell.create(centers),
+            mappingBuffer: ValueCell.create(mappings),
+            indexBuffer: ValueCell.create(indices),
+            groupBuffer: ValueCell.create(groups),
+            get boundingSphere() {
+                const newHash = hashCode(spheres)
+                if (newHash !== currentHash) {
+                    const b = calculateInvariantBoundingSphere(spheres.centerBuffer.ref.value, spheres.sphereCount * 4, 4)
+                    Sphere3D.copy(boundingSphere, b)
+                    currentHash = newHash
+                }
+                return boundingSphere
+            },
         }
+        return spheres
+    }
+
+    function update(centers: Float32Array, mappings: Float32Array, indices: Uint32Array, groups: Float32Array, sphereCount: number, spheres: Spheres) {
+        spheres.sphereCount = sphereCount
+        ValueCell.update(spheres.centerBuffer, centers)
+        ValueCell.update(spheres.mappingBuffer, mappings)
+        ValueCell.update(spheres.indexBuffer, indices)
+        ValueCell.update(spheres.groupBuffer, groups)
+        return spheres
     }
 
     export const Params = {
@@ -88,10 +132,8 @@ export namespace Spheres {
         const counts = { drawCount: spheres.sphereCount * 2 * 3, groupCount, instanceCount }
 
         const padding = getMaxSize(size)
-        const { boundingSphere, invariantBoundingSphere } = calculateBoundingSphere(
-            spheres.centerBuffer.ref.value, spheres.sphereCount * 4,
-            transform.aTransform.ref.value, instanceCount, padding, 4
-        )
+        const invariantBoundingSphere = Sphere3D.expand(Sphere3D(), spheres.boundingSphere, padding)
+        const boundingSphere = calculateTransformBoundingSphere(invariantBoundingSphere, transform.aTransform.ref.value, instanceCount)
 
         return {
             aPosition: spheres.centerBuffer,
@@ -131,10 +173,9 @@ export namespace Spheres {
 
     function updateBoundingSphere(values: SpheresValues, spheres: Spheres) {
         const padding = getMaxSize(values)
-        const { boundingSphere, invariantBoundingSphere } = calculateBoundingSphere(
-            values.aPosition.ref.value, spheres.sphereCount * 4,
-            values.aTransform.ref.value, values.instanceCount.ref.value, padding, 4
-        )
+        const invariantBoundingSphere = Sphere3D.expand(Sphere3D(), spheres.boundingSphere, padding)
+        const boundingSphere = calculateTransformBoundingSphere(invariantBoundingSphere, values.aTransform.ref.value, values.instanceCount.ref.value)
+
         if (!Sphere3D.equals(boundingSphere, values.boundingSphere.ref.value)) {
             ValueCell.update(values.boundingSphere, boundingSphere)
         }

+ 2 - 13
src/mol-geo/geometry/text/text-builder.ts

@@ -1,11 +1,10 @@
 /**
- * Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2019-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
 
 import { ParamDefinition as PD } from '../../../mol-util/param-definition';
-import { ValueCell } from '../../../mol-util/value-cell'
 import { ChunkedArray } from '../../../mol-data/util';
 import { Text } from './text';
 import { getFontAtlas } from './font-atlas';
@@ -290,17 +289,7 @@ export namespace TextBuilder {
                 const ib = ChunkedArray.compact(indices, true) as Uint32Array
                 const gb = ChunkedArray.compact(groups, true) as Float32Array
                 const tb = ChunkedArray.compact(tcoords, true) as Float32Array
-                return {
-                    kind: 'text',
-                    charCount: indices.elementCount / 2,
-                    fontTexture: text ? ValueCell.update(text.fontTexture, ft) : ValueCell.create(ft),
-                    centerBuffer: text ? ValueCell.update(text.centerBuffer, cb) : ValueCell.create(cb),
-                    mappingBuffer: text ? ValueCell.update(text.mappingBuffer, mb) : ValueCell.create(mb),
-                    depthBuffer: text ? ValueCell.update(text.depthBuffer, db) : ValueCell.create(db),
-                    indexBuffer: text ? ValueCell.update(text.indexBuffer, ib) : ValueCell.create(ib),
-                    groupBuffer: text ? ValueCell.update(text.groupBuffer, gb) : ValueCell.create(gb),
-                    tcoordBuffer: text ? ValueCell.update(text.tcoordBuffer, tb) : ValueCell.create(tb),
-                }
+                return Text.create(ft, cb,mb, db, ib, gb, tb, indices.elementCount / 2, text)
             }
         }
     }

+ 68 - 21
src/mol-geo/geometry/text/text.ts

@@ -1,5 +1,5 @@
 /**
- * Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2019-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
@@ -15,7 +15,7 @@ import { createSizes, getMaxSize } from '../size-data';
 import { createMarkers } from '../marker-data';
 import { ColorNames } from '../../../mol-util/color/names';
 import { Sphere3D } from '../../../mol-math/geometry';
-import { calculateBoundingSphere, TextureImage, createTextureImage } from '../../../mol-gl/renderable/util';
+import { TextureImage, createTextureImage, calculateInvariantBoundingSphere, calculateTransformBoundingSphere } from '../../../mol-gl/renderable/util';
 import { TextValues } from '../../../mol-gl/renderable/text';
 import { Color } from '../../../mol-util/color';
 import { Vec3 } from '../../../mol-math/linear-algebra';
@@ -26,6 +26,7 @@ import { createRenderObject as _createRenderObject } from '../../../mol-gl/rende
 import { BaseGeometry } from '../base';
 import { createEmptyOverpaint } from '../overpaint-data';
 import { createEmptyTransparency } from '../transparency-data';
+import { hashFnv32a } from '../../../mol-data/util';
 
 type TextAttachment = (
     'bottom-left' | 'bottom-center' | 'bottom-right' |
@@ -38,7 +39,8 @@ export interface Text {
     readonly kind: 'text',
 
     /** Number of characters in the text */
-    readonly charCount: number,
+    charCount: number,
+
     /** Font Atlas */
     readonly fontTexture: ValueCell<TextureImage<Uint8Array>>,
 
@@ -54,9 +56,18 @@ export interface Text {
     readonly groupBuffer: ValueCell<Float32Array>,
     /** Texture coordinates buffer as array of uv values wrapped in a value cell */
     readonly tcoordBuffer: ValueCell<Float32Array>,
+
+    /** Bounding sphere of the text */
+    readonly boundingSphere: Sphere3D
 }
 
 export namespace Text {
+    export function create(fontTexture: TextureImage<Uint8Array>, centers: Float32Array, mappings: Float32Array, depths: Float32Array, indices: Uint32Array, groups: Float32Array, tcoords: Float32Array, charCount: number, text?: Text): Text {
+        return text ?
+            update(fontTexture, centers, mappings, depths, indices, groups, tcoords, charCount, text) :
+            fromData(fontTexture, centers, mappings, depths, indices, groups, tcoords, charCount)
+    }
+
     export function createEmpty(text?: Text): Text {
         const ft = text ? text.fontTexture.ref.value : createTextureImage(0, 1, Uint8Array)
         const cb = text ? text.centerBuffer.ref.value : new Float32Array(0)
@@ -65,17 +76,56 @@ export namespace Text {
         const ib = text ? text.indexBuffer.ref.value : new Uint32Array(0)
         const gb = text ? text.groupBuffer.ref.value : new Float32Array(0)
         const tb = text ? text.tcoordBuffer.ref.value : new Float32Array(0)
-        return {
-            kind: 'text',
-            charCount: 0,
-            fontTexture: text ? ValueCell.update(text.fontTexture, ft) : ValueCell.create(ft),
-            centerBuffer: text ? ValueCell.update(text.centerBuffer, cb) : ValueCell.create(cb),
-            mappingBuffer: text ? ValueCell.update(text.mappingBuffer, mb) : ValueCell.create(mb),
-            depthBuffer: text ? ValueCell.update(text.depthBuffer, db) : ValueCell.create(db),
-            indexBuffer: text ? ValueCell.update(text.indexBuffer, ib) : ValueCell.create(ib),
-            groupBuffer: text ? ValueCell.update(text.groupBuffer, gb) : ValueCell.create(gb),
-            tcoordBuffer: text ? ValueCell.update(text.tcoordBuffer, tb) : ValueCell.create(tb)
+        return create(ft, cb, mb, db, ib, gb, tb, 0, text)
+    }
+
+    function hashCode(text: Text) {
+        return hashFnv32a([
+            text.charCount, text.fontTexture.ref.version,
+            text.centerBuffer.ref.version, text.mappingBuffer.ref.version,
+            text.depthBuffer.ref.version, text.indexBuffer.ref.version,
+            text.groupBuffer.ref.version, text.tcoordBuffer.ref.version
+        ])
+    }
+
+    function fromData(fontTexture: TextureImage<Uint8Array>, centers: Float32Array, mappings: Float32Array, depths: Float32Array, indices: Uint32Array, groups: Float32Array, tcoords: Float32Array, charCount: number): Text {
+
+        const boundingSphere = Sphere3D()
+        let currentHash = -1
+
+        const text = {
+            kind: 'text' as const,
+            charCount,
+            fontTexture: ValueCell.create(fontTexture),
+            centerBuffer: ValueCell.create(centers),
+            mappingBuffer: ValueCell.create(mappings),
+            depthBuffer: ValueCell.create(depths),
+            indexBuffer: ValueCell.create(indices),
+            groupBuffer: ValueCell.create(groups),
+            tcoordBuffer: ValueCell.create(tcoords),
+            get boundingSphere() {
+                const newHash = hashCode(text)
+                if (newHash !== currentHash) {
+                    const b = calculateInvariantBoundingSphere(text.centerBuffer.ref.value, text.charCount * 4, 4)
+                    Sphere3D.copy(boundingSphere, b)
+                    currentHash = newHash
+                }
+                return boundingSphere
+            },
         }
+        return text
+    }
+
+    function update(fontTexture: TextureImage<Uint8Array>, centers: Float32Array, mappings: Float32Array, depths: Float32Array, indices: Uint32Array, groups: Float32Array, tcoords: Float32Array, charCount: number, text: Text) {
+        text.charCount = charCount
+        ValueCell.update(text.fontTexture, fontTexture)
+        ValueCell.update(text.centerBuffer, centers)
+        ValueCell.update(text.mappingBuffer, mappings)
+        ValueCell.update(text.depthBuffer, depths)
+        ValueCell.update(text.indexBuffer, indices)
+        ValueCell.update(text.groupBuffer, groups)
+        ValueCell.update(text.tcoordBuffer, tcoords)
+        return text
     }
 
     export const Params = {
@@ -130,10 +180,8 @@ export namespace Text {
         const counts = { drawCount: text.charCount * 2 * 3, groupCount, instanceCount }
 
         const padding = getPadding(text.mappingBuffer.ref.value, text.depthBuffer.ref.value, text.charCount, getMaxSize(size))
-        const { boundingSphere, invariantBoundingSphere } = calculateBoundingSphere(
-            text.centerBuffer.ref.value, text.charCount * 4,
-            transform.aTransform.ref.value, instanceCount, padding
-        )
+        const invariantBoundingSphere = Sphere3D.expand(Sphere3D(), text.boundingSphere, padding)
+        const boundingSphere = calculateTransformBoundingSphere(invariantBoundingSphere, transform.aTransform.ref.value, instanceCount)
 
         return {
             aPosition: text.centerBuffer,
@@ -194,10 +242,9 @@ export namespace Text {
 
     function updateBoundingSphere(values: TextValues, text: Text) {
         const padding = getPadding(values.aMapping.ref.value, values.aDepth.ref.value, text.charCount, getMaxSize(values))
-        const { boundingSphere, invariantBoundingSphere } = calculateBoundingSphere(
-            values.aPosition.ref.value, text.charCount * 4,
-            values.aTransform.ref.value, values.instanceCount.ref.value, padding
-        )
+        const invariantBoundingSphere = Sphere3D.expand(Sphere3D(), text.boundingSphere, padding)
+        const boundingSphere = calculateTransformBoundingSphere(invariantBoundingSphere, values.aTransform.ref.value, values.instanceCount.ref.value)
+
         if (!Sphere3D.equals(boundingSphere, values.boundingSphere.ref.value)) {
             ValueCell.update(values.boundingSphere, boundingSphere)
         }

+ 14 - 14
src/mol-geo/geometry/texture-mesh/texture-mesh.ts

@@ -27,16 +27,16 @@ export interface TextureMesh {
     readonly kind: 'texture-mesh',
 
     /** Number of vertices in the texture-mesh */
-    readonly vertexCount: ValueCell<number>,
+    vertexCount: number,
     /** Number of groups in the texture-mesh */
-    readonly groupCount: ValueCell<number>,
+    groupCount: number,
 
     readonly geoTextureDim: ValueCell<Vec2>,
     /** texture has vertex positions in XYZ and group id in W */
     readonly vertexGroupTexture: ValueCell<Texture>,
     readonly normalTexture: ValueCell<Texture>,
 
-    readonly boundingSphere: ValueCell<Sphere3D>,
+    readonly boundingSphere: Sphere3D
 }
 
 export namespace TextureMesh {
@@ -44,22 +44,22 @@ export namespace TextureMesh {
         const width = vertexGroupTexture.getWidth()
         const height = vertexGroupTexture.getHeight()
         if (textureMesh) {
-            ValueCell.update(textureMesh.vertexCount, vertexCount)
-            ValueCell.update(textureMesh.groupCount, groupCount)
+            textureMesh.vertexCount = vertexCount
+            textureMesh.groupCount = groupCount
             ValueCell.update(textureMesh.geoTextureDim, Vec2.set(textureMesh.geoTextureDim.ref.value, width, height))
             ValueCell.update(textureMesh.vertexGroupTexture, vertexGroupTexture)
             ValueCell.update(textureMesh.normalTexture, normalTexture)
-            ValueCell.update(textureMesh.boundingSphere, boundingSphere)
+            Sphere3D.copy(textureMesh.boundingSphere, boundingSphere)
             return textureMesh
         } else {
             return {
                 kind: 'texture-mesh',
-                vertexCount: ValueCell.create(vertexCount),
-                groupCount: ValueCell.create(groupCount),
+                vertexCount,
+                groupCount,
                 geoTextureDim: ValueCell.create(Vec2.create(width, height)),
                 vertexGroupTexture: ValueCell.create(vertexGroupTexture),
                 normalTexture: ValueCell.create(normalTexture),
-                boundingSphere: ValueCell.create(boundingSphere),
+                boundingSphere: Sphere3D.clone(boundingSphere),
             }
         }
     }
@@ -94,9 +94,9 @@ export namespace TextureMesh {
         const overpaint = createEmptyOverpaint()
         const transparency = createEmptyTransparency()
 
-        const counts = { drawCount: textureMesh.vertexCount.ref.value, groupCount, instanceCount }
+        const counts = { drawCount: textureMesh.vertexCount, groupCount, instanceCount }
 
-        const transformBoundingSphere = calculateTransformBoundingSphere(textureMesh.boundingSphere.ref.value, transform.aTransform.ref.value, transform.instanceCount.ref.value)
+        const transformBoundingSphere = calculateTransformBoundingSphere(textureMesh.boundingSphere, transform.aTransform.ref.value, transform.instanceCount.ref.value)
 
         return {
             uGeoTexDim: textureMesh.geoTextureDim,
@@ -104,9 +104,9 @@ export namespace TextureMesh {
             tNormal: textureMesh.normalTexture,
 
             // aGroup is used as a vertex index here and the group id is retirieved from tPositionGroup
-            aGroup: ValueCell.create(fillSerial(new Float32Array(textureMesh.vertexCount.ref.value))),
+            aGroup: ValueCell.create(fillSerial(new Float32Array(textureMesh.vertexCount))),
             boundingSphere: ValueCell.create(transformBoundingSphere),
-            invariantBoundingSphere: textureMesh.boundingSphere,
+            invariantBoundingSphere: ValueCell.create(Sphere3D.clone(textureMesh.boundingSphere)),
 
             ...color,
             ...marker,
@@ -142,7 +142,7 @@ export namespace TextureMesh {
     }
 
     function updateBoundingSphere(values: TextureMeshValues, textureMesh: TextureMesh) {
-        const invariantBoundingSphere = textureMesh.boundingSphere.ref.value
+        const invariantBoundingSphere = textureMesh.boundingSphere
         const boundingSphere = calculateTransformBoundingSphere(invariantBoundingSphere, values.aTransform.ref.value, values.instanceCount.ref.value)
         if (!Sphere3D.equals(boundingSphere, values.boundingSphere.ref.value)) {
             ValueCell.update(values.boundingSphere, boundingSphere)

+ 3 - 13
src/mol-geo/util/marching-cubes/builder.ts

@@ -1,5 +1,5 @@
 /**
- * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  * @author David Sehnal <david.sehnal@gmail.com>
@@ -7,7 +7,7 @@
  */
 
 import { ChunkedArray } from '../../../mol-data/util';
-import { ValueCell, noop } from '../../../mol-util';
+import { noop } from '../../../mol-util';
 import { Mesh } from '../../geometry/mesh/mesh';
 import { AllowedContours } from './tables';
 import { LinesBuilder } from '../../geometry/lines/lines-builder';
@@ -52,17 +52,7 @@ export function MarchinCubesMeshBuilder(vertexChunkSize: number, mesh?: Mesh): M
             const nb = ChunkedArray.compact(normals, true) as Float32Array;
             const ib = ChunkedArray.compact(indices, true) as Uint32Array;
             const gb = ChunkedArray.compact(groups, true) as Float32Array;
-
-            return {
-                kind: 'mesh',
-                vertexCount,
-                triangleCount,
-                vertexBuffer: mesh ? ValueCell.update(mesh.vertexBuffer, vb) : ValueCell.create(vb),
-                groupBuffer: mesh ? ValueCell.update(mesh.groupBuffer, gb) : ValueCell.create(gb),
-                indexBuffer: mesh ? ValueCell.update(mesh.indexBuffer, ib) : ValueCell.create(ib),
-                normalBuffer: mesh ? ValueCell.update(mesh.normalBuffer, nb) : ValueCell.create(nb),
-                normalsComputed: true
-            }
+            return Mesh.create(vb, ib, nb, gb, vertexCount, triangleCount, mesh)
         }
     }
 }

+ 2 - 3
src/mol-model/loci.ts

@@ -161,11 +161,10 @@ namespace Loci {
                 sphereHelper.radiusStep(tmpPos);
             }
         } else if (loci.kind === 'shape-loci') {
-            // TODO
-            return void 0;
+            return Sphere3D.copy(boundingSphere, loci.shape.geometry.boundingSphere)
         } else if (loci.kind === 'group-loci') {
             // TODO
-            return void 0;
+            return Sphere3D.copy(boundingSphere, loci.shape.geometry.boundingSphere)
         } else if (loci.kind === 'data-loci') {
             // TODO maybe add loci.getBoundingSphere()???
             return void 0;

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

@@ -47,7 +47,7 @@ async function createGaussianSurfaceMesh(ctx: VisualContext, unit: Unit, structu
     }
     const surface = await computeMarchingCubesMesh(params, mesh).runAsChild(ctx.runtime)
 
-    Mesh.transformImmediate(surface, transform)
+    Mesh.transform(surface, transform)
     if (ctx.webgl && !ctx.webgl.isWebGL2) Mesh.uniformTriangleGroup(surface)
 
     return surface
@@ -90,7 +90,7 @@ async function createStructureGaussianSurfaceMesh(ctx: VisualContext, structure:
     }
     const surface = await computeMarchingCubesMesh(params, mesh).runAsChild(ctx.runtime)
 
-    Mesh.transformImmediate(surface, transform)
+    Mesh.transform(surface, transform)
     if (ctx.webgl && !ctx.webgl.isWebGL2) Mesh.uniformTriangleGroup(surface)
 
     return surface

+ 1 - 1
src/mol-repr/structure/visual/gaussian-surface-wireframe.ts

@@ -26,7 +26,7 @@ async function createGaussianWireframe(ctx: VisualContext, unit: Unit, structure
     }
     const wireframe = await computeMarchingCubesLines(params, lines).runAsChild(ctx.runtime)
 
-    Lines.transformImmediate(wireframe, transform)
+    Lines.transform(wireframe, transform)
 
     return wireframe
 }

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

@@ -35,7 +35,7 @@ async function createMolecularSurfaceMesh(ctx: VisualContext, unit: Unit, struct
     }
     const surface = await computeMarchingCubesMesh(params, mesh).runAsChild(ctx.runtime)
 
-    Mesh.transformImmediate(surface, transform)
+    Mesh.transform(surface, transform)
     if (ctx.webgl && !ctx.webgl.isWebGL2) Mesh.uniformTriangleGroup(surface)
 
     return surface

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

@@ -36,7 +36,7 @@ async function createMolecularSurfaceWireframe(ctx: VisualContext, unit: Unit, s
     }
     const wireframe = await computeMarchingCubesLines(params, lines).runAsChild(ctx.runtime)
 
-    Lines.transformImmediate(wireframe, transform)
+    Lines.transform(wireframe, transform)
 
     return wireframe
 }

+ 2 - 2
src/mol-repr/volume/isosurface.ts

@@ -78,7 +78,7 @@ export async function createVolumeIsosurfaceMesh(ctx: VisualContext, volume: Vol
 
     const transform = VolumeData.getGridToCartesianTransform(volume);
     ctx.runtime.update({ message: 'Transforming mesh...' });
-    Mesh.transformImmediate(surface, transform);
+    Mesh.transform(surface, transform);
 
     return surface;
 }
@@ -114,7 +114,7 @@ export async function createVolumeIsosurfaceWireframe(ctx: VisualContext, volume
     }, lines).runAsChild(ctx.runtime)
 
     const transform = VolumeData.getGridToCartesianTransform(volume);
-    Lines.transformImmediate(wireframe, transform)
+    Lines.transform(wireframe, transform)
 
     return wireframe;
 }

+ 1 - 1
src/tests/browser/marching-cubes.ts

@@ -133,7 +133,7 @@ async function init() {
     const surface = await computeMarchingCubesMesh(params).run()
     console.timeEnd('cpu mc')
     console.log('surface', surface)
-    Mesh.transformImmediate(surface, densityData.transform)
+    Mesh.transform(surface, densityData.transform)
     const meshProps = { doubleSided: true, flatShaded: false, alpha: 1.0 }
     const meshValues = Mesh.Utils.createValuesSimple(surface, meshProps, Color(0x995511), 1)
     const meshState = Mesh.Utils.createRenderableState(meshProps)