Kaynağa Gözat

Merge commit 'e13d4dbc7a98dfe5c465bcc1b6cfd056c2e10119' into gl-geo

# Conflicts:
#	package-lock.json
#	package.json
#	src/apps/render-test/state.ts
Alexander Rose 7 yıl önce
ebeveyn
işleme
ebf704e45d

+ 57 - 0
src/apps/render-test/mcubes.ts

@@ -0,0 +1,57 @@
+/**
+ * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author David Sehnal <david.sehnal@gmail.com>
+ */
+
+import { Run } from 'mol-task'
+import { compute } from 'mol-geo/util/marching-cubes/algorithm'
+import { Surface } from 'mol-geo/shape/surface'
+import { Tensor, Mat4, Vec3 } from 'mol-math/linear-algebra'
+
+function fillField(tensor: Tensor, f: (x: number, y: number, z: number) => number, min: number[], max: number[]): Tensor {
+    const { space: { set, dimensions: [ii, jj, kk] }, data } = tensor;
+
+    const dx = (max[0] - min[0]) / (ii - 1);
+    const dy = (max[1] - min[1]) / (jj - 1);
+    const dz = (max[2] - min[2]) / (kk - 1);
+
+    for (let i = 0, x = min[0]; i < ii; i++, x += dx) {
+        for (let j = 0, y = min[1]; j < jj; j++, y += dy) {
+            for (let k = 0, z = min[2]; k < kk; k++, z += dz) {
+                set(data, i, j, k, f(x, y, z));
+            }
+        }
+    }
+
+    return tensor
+}
+
+export default async function computeSurface(f: (x: number, y: number, z: number) => number, data?: { field: Tensor, surface: Surface }) {
+    let field: Tensor;
+    if (data) field = data.field;
+    else {
+        const space = Tensor.Space([30, 30, 30], [0, 1, 2]);
+        field = Tensor.create(space, space.create(Float32Array));
+    }
+
+    const min = Vec3.create(-1.1, -1.1, -1.1), max = Vec3.create(1.1, 1.1, 1.1);
+
+    fillField(field, f, min, max);
+    const surface = await Run(compute({
+        scalarField: field,
+        isoLevel: 0,
+        oldSurface: data ? data.surface : void 0
+    }));
+
+    const translation = Mat4.fromTranslation(Mat4.zero(), min);
+    const grid = Vec3.zero();
+    Vec3.fromArray(grid, field.space.dimensions as any, 0);
+    const size = Vec3.sub(Vec3.zero(), max, min);
+    const scale = Mat4.fromScaling(Mat4.zero(), Vec3.create(size[0] / (grid[0] - 1), size[1] / (grid[1] - 1), size[2] / (grid[2] - 1)));
+
+    const transform = Mat4.mul(Mat4.zero(), translation, scale);
+    Surface.transformImmediate(surface, transform);
+    Surface.computeNormalsImmediate(surface);
+    return { surface, field };
+}

+ 61 - 22
src/apps/render-test/state.ts

@@ -5,13 +5,12 @@
  */
 
 import REGL = require('regl');
+import { ValueBox } from 'mol-util/value-cell'
 import * as glContext from 'mol-gl/context'
 import { Camera } from 'mol-gl/camera'
 import { Vec3, Mat4 } from 'mol-math/linear-algebra'
 import { PointRenderable, MeshRenderable } from 'mol-gl/renderable'
-import Attribute from 'mol-gl/attribute';
 import Model from 'mol-gl/model';
-import { createTransformAttributes } from 'mol-gl/renderable/util';
 import { calculateTextureInfo } from 'mol-gl/util';
 import Icosahedron from 'mol-geo/primitive/icosahedron'
 import Box from 'mol-geo/primitive/box'
@@ -37,6 +36,8 @@ async function getPdb(pdb: string) {
     return structure
 }
 
+import mcubes from './mcubes'
+
 export default class State {
     regl: REGL.Regl
 
@@ -67,18 +68,18 @@ export default class State {
         const model2 = Model(regl, { position: p1 })
         const model3 = Model(regl, { position: p2 })
 
-        const position = Attribute.create(regl, new Float32Array([0, -1, 0, -1, 0, 0, 1, 1, 0]), { size: 3 })
-        const normal = Attribute.create(regl, new Float32Array([0, 0, 0, 0, 0, 0, 0, 0, 0]), { size: 3 })
+        const position = ValueBox(new Float32Array([0, -1, 0, -1, 0, 0, 1, 1, 0]))
+        const normal = ValueBox(new Float32Array([0, 0, 0, 0, 0, 0, 0, 0, 0]))
 
-        const transformArray1 = new Float32Array(16)
-        const transformArray2 = new Float32Array(16 * 3)
+        const transformArray1 = ValueBox(new Float32Array(16))
+        const transformArray2 = ValueBox(new Float32Array(16 * 3))
         const m4 = Mat4.identity()
-        Mat4.toArray(m4, transformArray1, 0)
-        Mat4.toArray(m4, transformArray2, 0)
+        Mat4.toArray(m4, transformArray1.value, 0)
+        Mat4.toArray(m4, transformArray2.value, 0)
         Mat4.setTranslation(m4, p1)
-        Mat4.toArray(m4, transformArray2, 16)
+        Mat4.toArray(m4, transformArray2.value, 16)
         Mat4.setTranslation(m4, p2)
-        Mat4.toArray(m4, transformArray2, 32)
+        Mat4.toArray(m4, transformArray2.value, 32)
 
         const colorTexInfo = calculateTextureInfo(3, 3)
         const color = new Uint8Array(colorTexInfo.length)
@@ -104,13 +105,13 @@ export default class State {
 
         const points = PointRenderable.create(regl, {
             position,
-            ...createTransformAttributes(regl, transformArray1)
+            transform: transformArray1
         })
         const mesh = MeshRenderable.create(regl,
             {
                 position,
                 normal,
-                ...createTransformAttributes(regl, transformArray2)
+                transform: transformArray2
             },
             {
                 colorTex,
@@ -125,15 +126,25 @@ export default class State {
         console.log(box)
 
         const points2 = PointRenderable.create(regl, {
-            position: Attribute.create(regl, new Float32Array(box.vertices), { size: 3 }),
-            ...createTransformAttributes(regl, transformArray1)
+            position: ValueBox(new Float32Array(box.vertices)),
+            transform: transformArray1
         })
 
-        const mesh2 = MeshRenderable.create(regl,
+        let rr = 1;
+        function cubesF(x: number, y: number, z: number) {
+            return x * x + y * y + z * z - rr * rr;
+            // const a = ca;
+            // const t = (x + y + z + a);
+            // return x * x * x + y * y * y + z * z * z + a * a * a - t * t * t;
+        }
+
+        let cubes = await mcubes(cubesF);
+
+        const makeCubesMesh = () => MeshRenderable.create(regl,
             {
-                position: Attribute.create(regl, new Float32Array(sphere.vertices), { size: 3 }),
-                normal: Attribute.create(regl, new Float32Array(sphere.normals), { size: 3 }),
-                ...createTransformAttributes(regl, transformArray2)
+                position: cubes.surface.vertexBuffer,
+                normal: cubes.surface.normalBuffer as any,
+                transform: transformArray1
             },
             {
                 colorTex,
@@ -144,8 +155,36 @@ export default class State {
                 'light.falloff': 0,
                 'light.radius': 500
             },
-            // box.indices
-        )
+            cubes.surface.indexBuffer.value
+        );
+
+        let mesh2 = makeCubesMesh();
+
+        // const makeCubes = async () => {
+        //     rr = Math.random();
+        //     cubes = await mcubes(cubesF, cubes);
+        //     mesh2 = makeCubesMesh();
+        //     setTimeout(makeCubes, 1000 / 15);
+        // };
+        // makeCubes();
+
+        // const mesh2 = MeshRenderable.create(regl,
+        //     {
+        //         position: Attribute.create(regl, new Float32Array(box.vertices), { size: 3 }),
+        //         normal: Attribute.create(regl, new Float32Array(box.normals), { size: 3 }),
+        //         ...createTransformAttributes(regl, transformArray1)
+        //     },
+        //     {
+        //         colorTex,
+        //         colorTexSize: [ colorTexInfo.width, colorTexInfo.height ],
+        //         'light.position': Vec3.create(0, 0, -20),
+        //         'light.color': Vec3.create(1.0, 1.0, 1.0),
+        //         'light.ambient': Vec3.create(0.5, 0.5, 0.5),
+        //         'light.falloff': 0,
+        //         'light.radius': 500
+        //     },
+        //     box.indices
+        // )
 
         function createSpacefills (structure: Structure) {
             const spacefills: UnitRepresentation[] = []
@@ -187,12 +226,12 @@ export default class State {
                     // console.log(ctx)
                     regl.clear({color: [0, 0, 0, 1]})
                     spacefills.forEach(r => r.draw())
-                    position.update(array => { array[0] = Math.random() })
+                    // position.update(array => { array[0] = Math.random() })
                     // points.update(a => { a.position[0] = Math.random() })
                     // mesh.draw()
                     // points.draw()
                     mesh2.draw()
-                    points2.draw()
+                    // points2.draw()
                     // model1({}, ({ transform }) => {
                     //     points.draw()
                     // })

+ 1 - 1
src/mol-data/db/column.ts

@@ -34,7 +34,7 @@ namespace Column {
         export type Float = { '@type': 'float', T: number } & Base<'float'>
         export type Coordinate = { '@type': 'coord', T: number } & Base<'float'>
 
-        export type Tensor = { '@type': 'tensor', T: Tensors, space: Tensors.Space } & Base<'tensor'>
+        export type Tensor = { '@type': 'tensor', T: Tensors.Data, space: Tensors.Space } & Base<'tensor'>
         export type Aliased<T> = { '@type': 'aliased', T: T } & Base<'str' | 'int'>
         export type List<T extends number|string> = { '@type': 'list', T: T[], separator: string, itemParse: (x: string) => T } & Base<'list'>
 

+ 24 - 0
src/mol-data/util/_spec/chunked-array.spec.ts

@@ -0,0 +1,24 @@
+/**
+ * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author David Sehnal <david.sehnal@gmail.com>
+ */
+
+import { ChunkedArray } from '../chunked-array'
+
+describe('Chunked Array', () => {
+    it('creation', () => {
+        const arr  = ChunkedArray.create<number>(_ => [], 2, 2);
+        ChunkedArray.add2(arr, 1, 2);
+        ChunkedArray.add2(arr, 3, 4);
+        expect(ChunkedArray.compact(arr)).toEqual([1, 2, 3, 4]);
+    });
+
+    it('initial', () => {
+        const arr  = ChunkedArray.create<number>(s => new Int32Array(s), 2, 6, new Int32Array([1, 2, 3, 4]));
+        ChunkedArray.add2(arr, 4, 3);
+        ChunkedArray.add2(arr, 2, 1);
+        ChunkedArray.add2(arr, 5, 6);
+        expect(ChunkedArray.compact(arr)).toEqual(new Int32Array([4, 3, 2, 1, 5, 6]));
+    });
+});

+ 30 - 18
src/mol-data/util/chunked-array.ts

@@ -16,9 +16,7 @@ interface ChunkedArray<T> {
     ctor: (size: number) => any,
     elementSize: number,
 
-    linearGrowth: boolean,
-
-    initialSize: number,
+    growBy: number,
     allocatedSize: number,
     elementCount: number,
 
@@ -26,20 +24,16 @@ interface ChunkedArray<T> {
     currentChunk: any,
     currentIndex: number,
 
-    chunks: any[]
+    chunks: any[][]
 }
 
-// TODO: better api, write tests
 namespace ChunkedArray {
     export function is(x: any): x is ChunkedArray<any> {
         return x.creator && x.chunkSize;
     }
 
     function allocateNext(array: ChunkedArray<any>) {
-        let nextSize = !array.allocatedSize || array.linearGrowth
-            ? array.initialSize * array.elementSize
-            : Math.max(Math.ceil(0.61 * array.allocatedSize), 1);
-        if (nextSize % array.elementSize !== 0) nextSize += nextSize % array.elementSize;
+        let nextSize = array.growBy * array.elementSize;
         array.currentSize = nextSize;
         array.currentIndex = 0;
         array.currentChunk = array.ctor(nextSize);
@@ -80,16 +74,25 @@ namespace ChunkedArray {
         return array.elementCount++;
     }
 
+    export function compact<T>(array: ChunkedArray<T>, doNotResizeSingleton = false): ArrayLike<T> {
+        return _compact(array, doNotResizeSingleton);
+    }
 
-    export function compact<T>(array: ChunkedArray<T>): ArrayLike<T> {
+    export function _compact<T>(array: ChunkedArray<T>, doNotResizeSingleton: boolean): ArrayLike<T> {
         const { ctor, chunks, currentIndex } = array;
 
         if (!chunks.length) return ctor(0);
-        if (chunks.length === 1 && currentIndex === array.allocatedSize) {
-            return chunks[0];
+        if (chunks.length === 1) {
+            if (doNotResizeSingleton || currentIndex === array.allocatedSize) {
+                return chunks[0];
+            }
         }
 
-        const ret = ctor(array.elementSize * array.elementCount);
+        let size = 0;
+        for (let i = 0, _i = chunks.length - 1; i < _i; i++) size += chunks[i].length;
+        size += array.currentIndex;
+
+        const ret = ctor(size);
         let offset = 0;
 
         if (ret.buffer) {
@@ -115,13 +118,12 @@ namespace ChunkedArray {
         return ret;
     }
 
-    export function create<T>(ctor: (size: number) => any, elementSize: number, initialSize: number, linearGrowth: boolean): ChunkedArray<T> {
-        return {
+    export function create<T>(ctor: (size: number) => any, elementSize: number, growBy: number, initialChunk?: ArrayLike<T>): ChunkedArray<T> {
+        const ret: ChunkedArray<T> = {
             ctor,
             elementSize,
-            linearGrowth,
 
-            initialSize,
+            growBy,
             allocatedSize: 0,
             elementCount: 0,
 
@@ -130,7 +132,17 @@ namespace ChunkedArray {
             currentIndex: 0,
 
             chunks: []
-        } as ChunkedArray<T>;
+        };
+
+        if (!initialChunk) return ret;
+
+        if (initialChunk.length % elementSize !== 0) throw new Error('initialChunk length must be a multiple of the element size.');
+        ret.currentChunk = initialChunk;
+        ret.allocatedSize = initialChunk.length;
+        ret.currentSize = initialChunk.length;
+        ret.chunks[0] = initialChunk as any;
+
+        return ret;
     }
 }
 

+ 5 - 5
src/mol-geo/representation/structure/spacefill.ts

@@ -5,9 +5,9 @@
  */
 
 import REGL = require('regl');
+import { ValueBox } from 'mol-util/value-cell'
+
 import { MeshRenderable, Renderable } from 'mol-gl/renderable'
-import { createTransformAttributes } from 'mol-gl/renderable/util';
-import Attribute from 'mol-gl/attribute';
 import { calculateTextureInfo } from 'mol-gl/util';
 import Icosahedron from 'mol-geo/primitive/icosahedron'
 import { Vec3, Mat4 } from 'mol-math/linear-algebra'
@@ -79,9 +79,9 @@ export default function Spacefill(regl: REGL.Regl): UnitRepresentation {
 
             const spheres = MeshRenderable.create(regl,
                 {
-                    position: Attribute.create(regl, new Float32Array(vertices), { size: 3 }),
-                    normal: Attribute.create(regl, new Float32Array(normals), { size: 3 }),
-                    ...createTransformAttributes(regl, transformArray)
+                    position: ValueBox(new Float32Array(vertices)),
+                    normal: ValueBox(new Float32Array(normals)),
+                    transform: ValueBox(transformArray)
                 },
                 {
                     colorTex,

+ 211 - 0
src/mol-geo/shape/surface.ts

@@ -0,0 +1,211 @@
+/**
+ * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author David Sehnal <david.sehnal@gmail.com>
+ */
+
+import { Task } from 'mol-task'
+import { ValueBox } from 'mol-util'
+import { Vec3, Mat4 } from 'mol-math/linear-algebra'
+
+export interface Surface {
+    vertexCount: number,
+    triangleCount: number,
+    vertexBuffer: ValueBox<Float32Array>,
+    indexBuffer: ValueBox<Uint32Array>,
+    normalBuffer: ValueBox<Float32Array | undefined>,
+    normalsComputed: boolean,
+
+    vertexAnnotation?: ValueBox<ArrayLike<number>>
+    //boundingSphere?: { center: Geometry.LinearAlgebra.Vector3, radius: number };
+}
+
+export namespace Surface {
+    export function computeNormalsImmediate(surface: Surface) {
+        if (surface.normalsComputed) return;
+
+        const normals = surface.normalBuffer.value && surface.normalBuffer.value!.length >= surface.vertexCount * 3
+            ? surface.normalBuffer.value : new Float32Array(surface.vertexBuffer.value!.length);
+
+        const v = surface.vertexBuffer.value, triangles = surface.indexBuffer.value;
+
+        const x = Vec3.zero(), y = Vec3.zero(), z = Vec3.zero(), d1 = Vec3.zero(), d2 = Vec3.zero(), n = Vec3.zero();
+        for (let i = 0, ii = 3 * surface.triangleCount; i < ii; i += 3) {
+            const a = 3 * triangles[i], b = 3 * triangles[i + 1], c = 3 * triangles[i + 2];
+
+            Vec3.fromArray(x, v, a);
+            Vec3.fromArray(y, v, b);
+            Vec3.fromArray(z, v, c);
+            Vec3.sub(d1, z, y);
+            Vec3.sub(d2, y, x);
+            Vec3.cross(n, d1, d2);
+
+            normals[a] += n[0]; normals[a + 1] += n[1]; normals[a + 2] += n[2];
+            normals[b] += n[0]; normals[b + 1] += n[1]; normals[b + 2] += n[2];
+            normals[c] += n[0]; normals[c + 1] += n[1]; normals[c + 2] += n[2];
+        }
+
+        for (let i = 0, ii = 3 * surface.vertexCount; i < ii; i += 3) {
+            const nx = normals[i];
+            const ny = normals[i + 1];
+            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]])
+        }
+        surface.normalBuffer = ValueBox(surface.normalBuffer, normals);
+        surface.normalsComputed = true;
+    }
+
+    export function computeNormals(surface: Surface): Task<Surface> {
+        return Task.create<Surface>('Surface (Compute Normals)', async ctx => {
+            if (surface.normalsComputed) return surface;
+
+            await ctx.update('Computing normals...');
+            computeNormalsImmediate(surface);
+            return surface;
+        });
+    }
+
+    export function transformImmediate(surface: Surface, t: Mat4) {
+        const p = Vec3.zero();
+        const vertices = surface.vertexBuffer.value;
+        for (let i = 0, _c = surface.vertexCount * 3; i < _c; i += 3) {
+            p[0] = vertices[i];
+            p[1] = vertices[i + 1];
+            p[2] = vertices[i + 2];
+            Vec3.transformMat4(p, p, t);
+            vertices[i] = p[0];
+            vertices[i + 1] = p[1];
+            vertices[i + 2] = p[2];
+        }
+        surface.normalsComputed = false;
+        //surface.boundingSphere = void 0;
+    }
+}
+
+//     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));
+//     }
+
+//     export function computeBoundingSphere(surface: Surface): Computation<Surface> {
+//         return computation<Surface>(async ctx => {
+//             if (surface.boundingSphere) {
+//                 return surface;
+//             }
+//             await ctx.updateProgress('Computing bounding sphere...');
+
+//             const vertices = surface.vertices;
+//             let x = 0, y = 0, z = 0;
+//             for (let i = 0, _c = surface.vertices.length; i < _c; i += 3) {
+//                 x += vertices[i];
+//                 y += vertices[i + 1];
+//                 z += vertices[i + 2];
+//             }
+//             x /= surface.vertexCount;
+//             y /= surface.vertexCount;
+//             z /= surface.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);
+//             }
+//             surface.boundingSphere = {
+//                 center: LinearAlgebra.Vector3.fromValues(x, y, z),
+//                 radius: Math.sqrt(r)
+//             }
+//             return surface;
+//         });
+//     }
+
+
+//     export function transform(surface: Surface, t: number[]): Computation<Surface> {
+//         return computation<Surface>(async ctx => {
+//             ctx.updateProgress('Updating surface...');
+//             transformImmediate(surface, t);
+//             return surface;
+//         });
+//     }
+// }

+ 252 - 0
src/mol-geo/util/marching-cubes/algorithm.ts

@@ -0,0 +1,252 @@
+/**
+ * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author David Sehnal <david.sehnal@gmail.com>
+ */
+
+import { Task, RuntimeContext } from 'mol-task'
+import { ChunkedArray } from 'mol-data/util'
+import { Tensor } from 'mol-math/linear-algebra'
+import { Surface } from '../../shape/surface'
+import { Index, EdgeIdInfo, CubeEdges, EdgeTable, TriTable } from './tables'
+import { ValueBox } from 'mol-util'
+
+/**
+ * The parameters required by the algorithm.
+ */
+export interface MarchingCubesParameters {
+    isoLevel: number,
+    scalarField: Tensor,
+
+    bottomLeft?: ArrayLike<number>,
+    topRight?: ArrayLike<number>,
+
+    annotationField?: Tensor,
+
+    oldSurface?: Surface
+}
+
+export function compute(parameters: MarchingCubesParameters) {
+    return Task.create('Marching Cubes', async ctx => {
+        let comp = new MarchingCubesComputation(parameters, ctx);
+        return await comp.run();
+    });
+}
+
+class MarchingCubesComputation {
+    private size: number;
+    private sliceSize: number;
+    private parameters: MarchingCubesParameters;
+
+    private minX = 0; private minY = 0; private minZ = 0;
+    private maxX = 0; private maxY = 0; private maxZ = 0;
+    private state: MarchingCubesState;
+
+    private async doSlices() {
+        let done = 0;
+
+        for (let k = this.minZ; k < this.maxZ; k++) {
+            this.slice(k);
+
+            done += this.sliceSize;
+            if (this.ctx.shouldUpdate) {
+                await this.ctx.update({ message: 'Computing surface...', current: done, max: this.size });
+            }
+        }
+    }
+
+    private slice(k: number) {
+        for (let j = this.minY; j < this.maxY; j++) {
+            for (let i = this.minX; i < this.maxX; i++) {
+                this.state.processCell(i, j, k);
+            }
+        }
+        this.state.clearEdgeVertexIndexSlice(k);
+    }
+
+    private finish() {
+        const vb = ChunkedArray.compact(this.state.vertexBuffer, true) as Float32Array;
+        const ib = ChunkedArray.compact(this.state.triangleBuffer, true) as Uint32Array;
+
+        this.state.vertexBuffer = <any>void 0;
+        this.state.verticesOnEdges = <any>void 0;
+
+        let ret: Surface = {
+            vertexCount:  this.state.vertexCount,
+            triangleCount: this.state.triangleCount,
+            vertexBuffer: this.parameters.oldSurface ? ValueBox(this.parameters.oldSurface.vertexBuffer, vb) : ValueBox(vb),
+            indexBuffer: this.parameters.oldSurface ? ValueBox(this.parameters.oldSurface.indexBuffer, ib) : ValueBox(ib),
+            normalBuffer: this.parameters.oldSurface ? this.parameters.oldSurface.normalBuffer : ValueBox(void 0),
+            vertexAnnotation: this.state.annotate
+                ? this.parameters.oldSurface && this.parameters.oldSurface.vertexAnnotation
+                    ? ValueBox(this.parameters.oldSurface.vertexAnnotation, ChunkedArray.compact(this.state.annotationBuffer))
+                    : ValueBox(ChunkedArray.compact(this.state.annotationBuffer))
+                : void 0,
+            normalsComputed: false
+        }
+
+        return ret;
+    }
+
+    async run() {
+        await this.ctx.update({ message: 'Computing surface...', current: 0, max: this.size });
+        await this.doSlices();
+        await this.ctx.update('Finalizing...');
+        return this.finish();
+    }
+
+    constructor(
+        parameters: MarchingCubesParameters,
+        private ctx: RuntimeContext) {
+
+        let params = { ...parameters };
+        this.parameters = params;
+
+        if (!params.bottomLeft) params.bottomLeft = [0, 0, 0];
+        if (!params.topRight) params.topRight = params.scalarField.space.dimensions;
+
+        this.state = new MarchingCubesState(params),
+            this.minX = params.bottomLeft[0]; this.minY = params.bottomLeft[1]; this.minZ = params.bottomLeft[2];
+        this.maxX = params.topRight[0] - 1; this.maxY = params.topRight[1] - 1; this.maxZ = params.topRight[2] - 1;
+
+        this.size = (this.maxX - this.minX) * (this.maxY - this.minY) * (this.maxZ - this.minZ);
+        this.sliceSize = (this.maxX - this.minX) * (this.maxY - this.minY);
+    }
+}
+
+class MarchingCubesState {
+    nX: number; nY: number; nZ: number;
+    isoLevel: number;
+    scalarFieldGet: Tensor.Space['get'];
+    scalarField: Tensor.Data;
+    annotationFieldGet?: Tensor.Space['get'];
+    annotationField?: Tensor.Data;
+    annotate: boolean;
+
+    // two layers of vertex indices. Each vertex has 3 edges associated.
+    verticesOnEdges: Int32Array;
+    vertList: number[] = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
+    i: number = 0; j: number = 0; k: number = 0;
+
+    vertexBuffer: ChunkedArray<number>;
+    annotationBuffer: ChunkedArray<number>;
+    triangleBuffer: ChunkedArray<number>;
+    vertexCount = 0;
+    triangleCount = 0;
+
+    private get3dOffsetFromEdgeInfo(index: Index) {
+        return (this.nX * (((this.k + index.k) % 2) * this.nY + this.j + index.j) + this.i + index.i);
+    }
+
+    /**
+     * This clears the "vertex index buffer" for the slice that will not be accessed anymore.
+     */
+    clearEdgeVertexIndexSlice(k: number) {
+        // clear either the top or bottom half of the buffer...
+        const start = k % 2 === 0 ? 0 : 3 * this.nX * this.nY;
+        const end = k % 2 === 0 ? 3 * this.nX * this.nY : this.verticesOnEdges.length;
+        for (let i = start; i < end; i++) this.verticesOnEdges[i] = 0;
+    }
+
+    private interpolate(edgeNum: number) {
+        const info = EdgeIdInfo[edgeNum],
+            edgeId = 3 * this.get3dOffsetFromEdgeInfo(info) + info.e;
+
+        const ret = this.verticesOnEdges[edgeId];
+        if (ret > 0) return (ret - 1) | 0;
+
+        const edge = CubeEdges[edgeNum];
+        const a = edge.a, b = edge.b;
+        const li = a.i + this.i, lj = a.j + this.j, lk = a.k + this.k;
+        const hi = b.i + this.i, hj = b.j + this.j, hk = b.k + this.k;
+        const v0 = this.scalarFieldGet(this.scalarField, li, lj, lk), v1 = this.scalarFieldGet(this.scalarField, hi, hj, hk);
+        const t = (this.isoLevel - v0) / (v0 - v1);
+
+        const id = ChunkedArray.add3(
+            this.vertexBuffer,
+            li + t * (li - hi),
+            lj + t * (lj - hj),
+            lk + t * (lk - hk)) | 0;
+
+        this.verticesOnEdges[edgeId] = id + 1;
+
+        if (this.annotate) {
+            const u = this.annotationFieldGet!(this.annotationField!, li, lj, lk);
+            const v = this.annotationFieldGet!(this.annotationField!, hi, hj, hk)
+            let a = t < 0.5 ? u : v;
+            if (a < 0) a = t < 0.5 ? v : u;
+            ChunkedArray.add(this.annotationBuffer, a);
+        }
+
+        this.vertexCount++;
+
+        return id;
+    }
+
+    constructor(params: MarchingCubesParameters) {
+        const dims = params.scalarField.space.dimensions;
+        this.nX = dims[0]; this.nY = dims[1]; this.nZ = dims[2];
+        this.isoLevel = params.isoLevel;
+        this.scalarFieldGet = params.scalarField.space.get;
+        this.scalarField = params.scalarField.data;
+        if (params.annotationField) {
+            this.annotationField = params.annotationField.data;
+            this.annotationFieldGet = params.annotationField.space.get;
+        }
+
+        let dX = params.topRight![0] - params.bottomLeft![0], dY = params.topRight![1] - params.bottomLeft![1], dZ = params.topRight![2] - params.bottomLeft![2],
+            vertexBufferSize = Math.min(262144, Math.max(dX * dY * dZ / 16, 1024) | 0),
+            triangleBufferSize = Math.min(1 << 16, vertexBufferSize * 4);
+
+        this.vertexBuffer = ChunkedArray.create<number>(s => new Float32Array(s), 3, vertexBufferSize,
+            params.oldSurface && params.oldSurface.vertexBuffer.value);
+        this.triangleBuffer = ChunkedArray.create<number>(s => new Uint32Array(s), 3, triangleBufferSize,
+            params.oldSurface && params.oldSurface.indexBuffer.value);
+
+        this.annotate = !!params.annotationField;
+        if (this.annotate) this.annotationBuffer = ChunkedArray.create(s => new Int32Array(s), 1, vertexBufferSize);
+
+        // two layers of vertex indices. Each vertex has 3 edges associated.
+        this.verticesOnEdges = new Int32Array(3 * this.nX * this.nY * 2);
+    }
+
+    get(i: number, j: number, k: number) {
+        return this.scalarFieldGet(this.scalarField, i, j, k);
+    }
+
+    processCell(i: number, j: number, k: number) {
+        let tableIndex = 0;
+
+        if (this.get(i, j, k) < this.isoLevel) tableIndex |= 1;
+        if (this.get(i + 1, j, k) < this.isoLevel) tableIndex |= 2;
+        if (this.get(i + 1, j + 1, k) < this.isoLevel) tableIndex |= 4;
+        if (this.get(i, j + 1, k) < this.isoLevel) tableIndex |= 8;
+        if (this.get(i, j, k + 1) < this.isoLevel) tableIndex |= 16;
+        if (this.get(i + 1, j, k + 1) < this.isoLevel) tableIndex |= 32;
+        if (this.get(i + 1, j + 1, k + 1) < this.isoLevel) tableIndex |= 64;
+        if (this.get(i, j + 1, k + 1) < this.isoLevel) tableIndex |= 128;
+
+        if (tableIndex === 0 || tableIndex === 255) return;
+
+        this.i = i; this.j = j; this.k = k;
+        let edgeInfo = EdgeTable[tableIndex];
+        if ((edgeInfo & 1) > 0) this.vertList[0] = this.interpolate(0); // 0 1
+        if ((edgeInfo & 2) > 0) this.vertList[1] = this.interpolate(1); // 1 2
+        if ((edgeInfo & 4) > 0) this.vertList[2] = this.interpolate(2); // 2 3
+        if ((edgeInfo & 8) > 0) this.vertList[3] = this.interpolate(3); // 0 3
+        if ((edgeInfo & 16) > 0) this.vertList[4] = this.interpolate(4); // 4 5
+        if ((edgeInfo & 32) > 0) this.vertList[5] = this.interpolate(5); // 5 6
+        if ((edgeInfo & 64) > 0) this.vertList[6] = this.interpolate(6); // 6 7
+        if ((edgeInfo & 128) > 0) this.vertList[7] = this.interpolate(7); // 4 7
+        if ((edgeInfo & 256) > 0) this.vertList[8] = this.interpolate(8); // 0 4
+        if ((edgeInfo & 512) > 0) this.vertList[9] = this.interpolate(9); // 1 5
+        if ((edgeInfo & 1024) > 0) this.vertList[10] = this.interpolate(10); // 2 6
+        if ((edgeInfo & 2048) > 0) this.vertList[11] = this.interpolate(11); // 3 7
+
+        let triInfo = TriTable[tableIndex];
+        for (let t = 0; t < triInfo.length; t += 3) {
+            this.triangleCount++;
+            ChunkedArray.add3(this.triangleBuffer, this.vertList[triInfo[t]], this.vertList[triInfo[t + 1]], this.vertList[triInfo[t + 2]]);
+        }
+    }
+}

+ 420 - 0
src/mol-geo/util/marching-cubes/tables.ts

@@ -0,0 +1,420 @@
+/**
+ * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author David Sehnal <david.sehnal@gmail.com>
+ */
+
+export interface Index { i: number, j: number, k: number }
+export function Index(i: number, j: number, k: number): Index { return { i, j, k }; }
+
+export interface IndexPair { a: Index, b: Index }
+export function IndexPair(a: Index, b: Index): IndexPair { return { a, b }; }
+
+export const EdgesXY = [
+    [],
+    [0, 3],
+    [0, 1],
+    [1, 3],
+    [1, 2],
+
+    [0, 1, 1, 2, 2, 3, 0, 3],
+
+    [0, 2],
+    [2, 3],
+    [2, 3],
+    [0, 2],
+
+    [0, 1, 1, 2, 2, 3, 0, 3],
+
+    [1, 2],
+    [1, 3],
+    [0, 1],
+    [0, 3],
+    []
+];
+
+export const EdgesXZ = [
+    [],
+    [0, 8],
+    [0, 9],
+    [9, 8],
+    [9, 4],
+
+    [0, 9, 9, 4, 4, 8, 0, 8],
+
+    [0, 4],
+    [4, 8],
+    [4, 8],
+    [0, 4],
+
+    [0, 9, 9, 4, 4, 8, 0, 8],
+
+    [9, 4],
+    [9, 8],
+    [0, 9],
+    [0, 8],
+    []
+];
+
+export const EdgesYZ = [
+    [],
+    [3, 8],
+    [3, 11],
+    [11, 8],
+    [11, 7],
+
+    [3, 11, 11, 7, 7, 8, 3, 8],
+
+    [3, 7],
+    [7, 8],
+    [7, 8],
+    [3, 7],
+
+    [3, 11, 11, 7, 7, 8, 3, 8],
+
+    [11, 7],
+    [11, 8],
+    [3, 11],
+    [3, 8],
+    []
+];
+
+export const CubeVertices = [
+    Index(0, 0, 0), // a
+    Index(1, 0, 0), // b
+    Index(1, 1, 0), // c
+    Index(0, 1, 0), // d
+    Index(0, 0, 1), // e
+    Index(1, 0, 1), // f
+    Index(1, 1, 1), // g
+    Index(0, 1, 1), // h
+];
+
+export const CubeEdges = [
+    IndexPair(CubeVertices[0], CubeVertices[1]),
+    IndexPair(CubeVertices[1], CubeVertices[2]),
+    IndexPair(CubeVertices[2], CubeVertices[3]),
+    IndexPair(CubeVertices[3], CubeVertices[0]),
+
+    IndexPair(CubeVertices[4], CubeVertices[5]),
+    IndexPair(CubeVertices[5], CubeVertices[6]),
+    IndexPair(CubeVertices[6], CubeVertices[7]),
+    IndexPair(CubeVertices[7], CubeVertices[4]),
+
+    IndexPair(CubeVertices[0], CubeVertices[4]),
+    IndexPair(CubeVertices[1], CubeVertices[5]),
+    IndexPair(CubeVertices[2], CubeVertices[6]),
+    IndexPair(CubeVertices[3], CubeVertices[7]),
+];
+
+export const EdgeIdInfo = [
+    { i: 0, j: 0, k: 0, e: 0 },
+    { i: 1, j: 0, k: 0, e: 1 },
+    { i: 0, j: 1, k: 0, e: 0 },
+    { i: 0, j: 0, k: 0, e: 1 },
+
+    { i: 0, j: 0, k: 1, e: 0 },
+    { i: 1, j: 0, k: 1, e: 1 },
+    { i: 0, j: 1, k: 1, e: 0 },
+    { i: 0, j: 0, k: 1, e: 1 },
+
+    { i: 0, j: 0, k: 0, e: 2 },
+    { i: 1, j: 0, k: 0, e: 2 },
+    { i: 1, j: 1, k: 0, e: 2 },
+    { i: 0, j: 1, k: 0, e: 2 }
+];
+
+// Tables EdgeTable and TriTable taken from http://paulbourke.net/geometry/polygonise/
+export const EdgeTable = [
+    0x0, 0x109, 0x203, 0x30a, 0x406, 0x50f, 0x605, 0x70c,
+    0x80c, 0x905, 0xa0f, 0xb06, 0xc0a, 0xd03, 0xe09, 0xf00,
+    0x190, 0x99, 0x393, 0x29a, 0x596, 0x49f, 0x795, 0x69c,
+    0x99c, 0x895, 0xb9f, 0xa96, 0xd9a, 0xc93, 0xf99, 0xe90,
+    0x230, 0x339, 0x33, 0x13a, 0x636, 0x73f, 0x435, 0x53c,
+    0xa3c, 0xb35, 0x83f, 0x936, 0xe3a, 0xf33, 0xc39, 0xd30,
+    0x3a0, 0x2a9, 0x1a3, 0xaa, 0x7a6, 0x6af, 0x5a5, 0x4ac,
+    0xbac, 0xaa5, 0x9af, 0x8a6, 0xfaa, 0xea3, 0xda9, 0xca0,
+    0x460, 0x569, 0x663, 0x76a, 0x66, 0x16f, 0x265, 0x36c,
+    0xc6c, 0xd65, 0xe6f, 0xf66, 0x86a, 0x963, 0xa69, 0xb60,
+    0x5f0, 0x4f9, 0x7f3, 0x6fa, 0x1f6, 0xff, 0x3f5, 0x2fc,
+    0xdfc, 0xcf5, 0xfff, 0xef6, 0x9fa, 0x8f3, 0xbf9, 0xaf0,
+    0x650, 0x759, 0x453, 0x55a, 0x256, 0x35f, 0x55, 0x15c,
+    0xe5c, 0xf55, 0xc5f, 0xd56, 0xa5a, 0xb53, 0x859, 0x950,
+    0x7c0, 0x6c9, 0x5c3, 0x4ca, 0x3c6, 0x2cf, 0x1c5, 0xcc,
+    0xfcc, 0xec5, 0xdcf, 0xcc6, 0xbca, 0xac3, 0x9c9, 0x8c0,
+    0x8c0, 0x9c9, 0xac3, 0xbca, 0xcc6, 0xdcf, 0xec5, 0xfcc,
+    0xcc, 0x1c5, 0x2cf, 0x3c6, 0x4ca, 0x5c3, 0x6c9, 0x7c0,
+    0x950, 0x859, 0xb53, 0xa5a, 0xd56, 0xc5f, 0xf55, 0xe5c,
+    0x15c, 0x55, 0x35f, 0x256, 0x55a, 0x453, 0x759, 0x650,
+    0xaf0, 0xbf9, 0x8f3, 0x9fa, 0xef6, 0xfff, 0xcf5, 0xdfc,
+    0x2fc, 0x3f5, 0xff, 0x1f6, 0x6fa, 0x7f3, 0x4f9, 0x5f0,
+    0xb60, 0xa69, 0x963, 0x86a, 0xf66, 0xe6f, 0xd65, 0xc6c,
+    0x36c, 0x265, 0x16f, 0x66, 0x76a, 0x663, 0x569, 0x460,
+    0xca0, 0xda9, 0xea3, 0xfaa, 0x8a6, 0x9af, 0xaa5, 0xbac,
+    0x4ac, 0x5a5, 0x6af, 0x7a6, 0xaa, 0x1a3, 0x2a9, 0x3a0,
+    0xd30, 0xc39, 0xf33, 0xe3a, 0x936, 0x83f, 0xb35, 0xa3c,
+    0x53c, 0x435, 0x73f, 0x636, 0x13a, 0x33, 0x339, 0x230,
+    0xe90, 0xf99, 0xc93, 0xd9a, 0xa96, 0xb9f, 0x895, 0x99c,
+    0x69c, 0x795, 0x49f, 0x596, 0x29a, 0x393, 0x99, 0x190,
+    0xf00, 0xe09, 0xd03, 0xc0a, 0xb06, 0xa0f, 0x905, 0x80c,
+    0x70c, 0x605, 0x50f, 0x406, 0x30a, 0x203, 0x109, 0x0
+];
+
+export const TriTable = [
+    [],
+    [0, 8, 3],
+    [0, 1, 9],
+    [1, 8, 3, 9, 8, 1],
+    [1, 2, 10],
+    [0, 8, 3, 1, 2, 10],
+    [9, 2, 10, 0, 2, 9],
+    [2, 8, 3, 2, 10, 8, 10, 9, 8],
+    [3, 11, 2],
+    [0, 11, 2, 8, 11, 0],
+    [1, 9, 0, 2, 3, 11],
+    [1, 11, 2, 1, 9, 11, 9, 8, 11],
+    [3, 10, 1, 11, 10, 3],
+    [0, 10, 1, 0, 8, 10, 8, 11, 10],
+    [3, 9, 0, 3, 11, 9, 11, 10, 9],
+    [9, 8, 10, 10, 8, 11],
+    [4, 7, 8],
+    [4, 3, 0, 7, 3, 4],
+    [0, 1, 9, 8, 4, 7],
+    [4, 1, 9, 4, 7, 1, 7, 3, 1],
+    [1, 2, 10, 8, 4, 7],
+    [3, 4, 7, 3, 0, 4, 1, 2, 10],
+    [9, 2, 10, 9, 0, 2, 8, 4, 7],
+    [2, 10, 9, 2, 9, 7, 2, 7, 3, 7, 9, 4],
+    [8, 4, 7, 3, 11, 2],
+    [11, 4, 7, 11, 2, 4, 2, 0, 4],
+    [9, 0, 1, 8, 4, 7, 2, 3, 11],
+    [4, 7, 11, 9, 4, 11, 9, 11, 2, 9, 2, 1],
+    [3, 10, 1, 3, 11, 10, 7, 8, 4],
+    [1, 11, 10, 1, 4, 11, 1, 0, 4, 7, 11, 4],
+    [4, 7, 8, 9, 0, 11, 9, 11, 10, 11, 0, 3],
+    [4, 7, 11, 4, 11, 9, 9, 11, 10],
+    [9, 5, 4],
+    [9, 5, 4, 0, 8, 3],
+    [0, 5, 4, 1, 5, 0],
+    [8, 5, 4, 8, 3, 5, 3, 1, 5],
+    [1, 2, 10, 9, 5, 4],
+    [3, 0, 8, 1, 2, 10, 4, 9, 5],
+    [5, 2, 10, 5, 4, 2, 4, 0, 2],
+    [2, 10, 5, 3, 2, 5, 3, 5, 4, 3, 4, 8],
+    [9, 5, 4, 2, 3, 11],
+    [0, 11, 2, 0, 8, 11, 4, 9, 5],
+    [0, 5, 4, 0, 1, 5, 2, 3, 11],
+    [2, 1, 5, 2, 5, 8, 2, 8, 11, 4, 8, 5],
+    [10, 3, 11, 10, 1, 3, 9, 5, 4],
+    [4, 9, 5, 0, 8, 1, 8, 10, 1, 8, 11, 10],
+    [5, 4, 0, 5, 0, 11, 5, 11, 10, 11, 0, 3],
+    [5, 4, 8, 5, 8, 10, 10, 8, 11],
+    [9, 7, 8, 5, 7, 9],
+    [9, 3, 0, 9, 5, 3, 5, 7, 3],
+    [0, 7, 8, 0, 1, 7, 1, 5, 7],
+    [1, 5, 3, 3, 5, 7],
+    [9, 7, 8, 9, 5, 7, 10, 1, 2],
+    [10, 1, 2, 9, 5, 0, 5, 3, 0, 5, 7, 3],
+    [8, 0, 2, 8, 2, 5, 8, 5, 7, 10, 5, 2],
+    [2, 10, 5, 2, 5, 3, 3, 5, 7],
+    [7, 9, 5, 7, 8, 9, 3, 11, 2],
+    [9, 5, 7, 9, 7, 2, 9, 2, 0, 2, 7, 11],
+    [2, 3, 11, 0, 1, 8, 1, 7, 8, 1, 5, 7],
+    [11, 2, 1, 11, 1, 7, 7, 1, 5],
+    [9, 5, 8, 8, 5, 7, 10, 1, 3, 10, 3, 11],
+    [5, 7, 0, 5, 0, 9, 7, 11, 0, 1, 0, 10, 11, 10, 0],
+    [11, 10, 0, 11, 0, 3, 10, 5, 0, 8, 0, 7, 5, 7, 0],
+    [11, 10, 5, 7, 11, 5],
+    [10, 6, 5],
+    [0, 8, 3, 5, 10, 6],
+    [9, 0, 1, 5, 10, 6],
+    [1, 8, 3, 1, 9, 8, 5, 10, 6],
+    [1, 6, 5, 2, 6, 1],
+    [1, 6, 5, 1, 2, 6, 3, 0, 8],
+    [9, 6, 5, 9, 0, 6, 0, 2, 6],
+    [5, 9, 8, 5, 8, 2, 5, 2, 6, 3, 2, 8],
+    [2, 3, 11, 10, 6, 5],
+    [11, 0, 8, 11, 2, 0, 10, 6, 5],
+    [0, 1, 9, 2, 3, 11, 5, 10, 6],
+    [5, 10, 6, 1, 9, 2, 9, 11, 2, 9, 8, 11],
+    [6, 3, 11, 6, 5, 3, 5, 1, 3],
+    [0, 8, 11, 0, 11, 5, 0, 5, 1, 5, 11, 6],
+    [3, 11, 6, 0, 3, 6, 0, 6, 5, 0, 5, 9],
+    [6, 5, 9, 6, 9, 11, 11, 9, 8],
+    [5, 10, 6, 4, 7, 8],
+    [4, 3, 0, 4, 7, 3, 6, 5, 10],
+    [1, 9, 0, 5, 10, 6, 8, 4, 7],
+    [10, 6, 5, 1, 9, 7, 1, 7, 3, 7, 9, 4],
+    [6, 1, 2, 6, 5, 1, 4, 7, 8],
+    [1, 2, 5, 5, 2, 6, 3, 0, 4, 3, 4, 7],
+    [8, 4, 7, 9, 0, 5, 0, 6, 5, 0, 2, 6],
+    [7, 3, 9, 7, 9, 4, 3, 2, 9, 5, 9, 6, 2, 6, 9],
+    [3, 11, 2, 7, 8, 4, 10, 6, 5],
+    [5, 10, 6, 4, 7, 2, 4, 2, 0, 2, 7, 11],
+    [0, 1, 9, 4, 7, 8, 2, 3, 11, 5, 10, 6],
+    [9, 2, 1, 9, 11, 2, 9, 4, 11, 7, 11, 4, 5, 10, 6],
+    [8, 4, 7, 3, 11, 5, 3, 5, 1, 5, 11, 6],
+    [5, 1, 11, 5, 11, 6, 1, 0, 11, 7, 11, 4, 0, 4, 11],
+    [0, 5, 9, 0, 6, 5, 0, 3, 6, 11, 6, 3, 8, 4, 7],
+    [6, 5, 9, 6, 9, 11, 4, 7, 9, 7, 11, 9],
+    [10, 4, 9, 6, 4, 10],
+    [4, 10, 6, 4, 9, 10, 0, 8, 3],
+    [10, 0, 1, 10, 6, 0, 6, 4, 0],
+    [8, 3, 1, 8, 1, 6, 8, 6, 4, 6, 1, 10],
+    [1, 4, 9, 1, 2, 4, 2, 6, 4],
+    [3, 0, 8, 1, 2, 9, 2, 4, 9, 2, 6, 4],
+    [0, 2, 4, 4, 2, 6],
+    [8, 3, 2, 8, 2, 4, 4, 2, 6],
+    [10, 4, 9, 10, 6, 4, 11, 2, 3],
+    [0, 8, 2, 2, 8, 11, 4, 9, 10, 4, 10, 6],
+    [3, 11, 2, 0, 1, 6, 0, 6, 4, 6, 1, 10],
+    [6, 4, 1, 6, 1, 10, 4, 8, 1, 2, 1, 11, 8, 11, 1],
+    [9, 6, 4, 9, 3, 6, 9, 1, 3, 11, 6, 3],
+    [8, 11, 1, 8, 1, 0, 11, 6, 1, 9, 1, 4, 6, 4, 1],
+    [3, 11, 6, 3, 6, 0, 0, 6, 4],
+    [6, 4, 8, 11, 6, 8],
+    [7, 10, 6, 7, 8, 10, 8, 9, 10],
+    [0, 7, 3, 0, 10, 7, 0, 9, 10, 6, 7, 10],
+    [10, 6, 7, 1, 10, 7, 1, 7, 8, 1, 8, 0],
+    [10, 6, 7, 10, 7, 1, 1, 7, 3],
+    [1, 2, 6, 1, 6, 8, 1, 8, 9, 8, 6, 7],
+    [2, 6, 9, 2, 9, 1, 6, 7, 9, 0, 9, 3, 7, 3, 9],
+    [7, 8, 0, 7, 0, 6, 6, 0, 2],
+    [7, 3, 2, 6, 7, 2],
+    [2, 3, 11, 10, 6, 8, 10, 8, 9, 8, 6, 7],
+    [2, 0, 7, 2, 7, 11, 0, 9, 7, 6, 7, 10, 9, 10, 7],
+    [1, 8, 0, 1, 7, 8, 1, 10, 7, 6, 7, 10, 2, 3, 11],
+    [11, 2, 1, 11, 1, 7, 10, 6, 1, 6, 7, 1],
+    [8, 9, 6, 8, 6, 7, 9, 1, 6, 11, 6, 3, 1, 3, 6],
+    [0, 9, 1, 11, 6, 7],
+    [7, 8, 0, 7, 0, 6, 3, 11, 0, 11, 6, 0],
+    [7, 11, 6],
+    [7, 6, 11],
+    [3, 0, 8, 11, 7, 6],
+    [0, 1, 9, 11, 7, 6],
+    [8, 1, 9, 8, 3, 1, 11, 7, 6],
+    [10, 1, 2, 6, 11, 7],
+    [1, 2, 10, 3, 0, 8, 6, 11, 7],
+    [2, 9, 0, 2, 10, 9, 6, 11, 7],
+    [6, 11, 7, 2, 10, 3, 10, 8, 3, 10, 9, 8],
+    [7, 2, 3, 6, 2, 7],
+    [7, 0, 8, 7, 6, 0, 6, 2, 0],
+    [2, 7, 6, 2, 3, 7, 0, 1, 9],
+    [1, 6, 2, 1, 8, 6, 1, 9, 8, 8, 7, 6],
+    [10, 7, 6, 10, 1, 7, 1, 3, 7],
+    [10, 7, 6, 1, 7, 10, 1, 8, 7, 1, 0, 8],
+    [0, 3, 7, 0, 7, 10, 0, 10, 9, 6, 10, 7],
+    [7, 6, 10, 7, 10, 8, 8, 10, 9],
+    [6, 8, 4, 11, 8, 6],
+    [3, 6, 11, 3, 0, 6, 0, 4, 6],
+    [8, 6, 11, 8, 4, 6, 9, 0, 1],
+    [9, 4, 6, 9, 6, 3, 9, 3, 1, 11, 3, 6],
+    [6, 8, 4, 6, 11, 8, 2, 10, 1],
+    [1, 2, 10, 3, 0, 11, 0, 6, 11, 0, 4, 6],
+    [4, 11, 8, 4, 6, 11, 0, 2, 9, 2, 10, 9],
+    [10, 9, 3, 10, 3, 2, 9, 4, 3, 11, 3, 6, 4, 6, 3],
+    [8, 2, 3, 8, 4, 2, 4, 6, 2],
+    [0, 4, 2, 4, 6, 2],
+    [1, 9, 0, 2, 3, 4, 2, 4, 6, 4, 3, 8],
+    [1, 9, 4, 1, 4, 2, 2, 4, 6],
+    [8, 1, 3, 8, 6, 1, 8, 4, 6, 6, 10, 1],
+    [10, 1, 0, 10, 0, 6, 6, 0, 4],
+    [4, 6, 3, 4, 3, 8, 6, 10, 3, 0, 3, 9, 10, 9, 3],
+    [10, 9, 4, 6, 10, 4],
+    [4, 9, 5, 7, 6, 11],
+    [0, 8, 3, 4, 9, 5, 11, 7, 6],
+    [5, 0, 1, 5, 4, 0, 7, 6, 11],
+    [11, 7, 6, 8, 3, 4, 3, 5, 4, 3, 1, 5],
+    [9, 5, 4, 10, 1, 2, 7, 6, 11],
+    [6, 11, 7, 1, 2, 10, 0, 8, 3, 4, 9, 5],
+    [7, 6, 11, 5, 4, 10, 4, 2, 10, 4, 0, 2],
+    [3, 4, 8, 3, 5, 4, 3, 2, 5, 10, 5, 2, 11, 7, 6],
+    [7, 2, 3, 7, 6, 2, 5, 4, 9],
+    [9, 5, 4, 0, 8, 6, 0, 6, 2, 6, 8, 7],
+    [3, 6, 2, 3, 7, 6, 1, 5, 0, 5, 4, 0],
+    [6, 2, 8, 6, 8, 7, 2, 1, 8, 4, 8, 5, 1, 5, 8],
+    [9, 5, 4, 10, 1, 6, 1, 7, 6, 1, 3, 7],
+    [1, 6, 10, 1, 7, 6, 1, 0, 7, 8, 7, 0, 9, 5, 4],
+    [4, 0, 10, 4, 10, 5, 0, 3, 10, 6, 10, 7, 3, 7, 10],
+    [7, 6, 10, 7, 10, 8, 5, 4, 10, 4, 8, 10],
+    [6, 9, 5, 6, 11, 9, 11, 8, 9],
+    [3, 6, 11, 0, 6, 3, 0, 5, 6, 0, 9, 5],
+    [0, 11, 8, 0, 5, 11, 0, 1, 5, 5, 6, 11],
+    [6, 11, 3, 6, 3, 5, 5, 3, 1],
+    [1, 2, 10, 9, 5, 11, 9, 11, 8, 11, 5, 6],
+    [0, 11, 3, 0, 6, 11, 0, 9, 6, 5, 6, 9, 1, 2, 10],
+    [11, 8, 5, 11, 5, 6, 8, 0, 5, 10, 5, 2, 0, 2, 5],
+    [6, 11, 3, 6, 3, 5, 2, 10, 3, 10, 5, 3],
+    [5, 8, 9, 5, 2, 8, 5, 6, 2, 3, 8, 2],
+    [9, 5, 6, 9, 6, 0, 0, 6, 2],
+    [1, 5, 8, 1, 8, 0, 5, 6, 8, 3, 8, 2, 6, 2, 8],
+    [1, 5, 6, 2, 1, 6],
+    [1, 3, 6, 1, 6, 10, 3, 8, 6, 5, 6, 9, 8, 9, 6],
+    [10, 1, 0, 10, 0, 6, 9, 5, 0, 5, 6, 0],
+    [0, 3, 8, 5, 6, 10],
+    [10, 5, 6],
+    [11, 5, 10, 7, 5, 11],
+    [11, 5, 10, 11, 7, 5, 8, 3, 0],
+    [5, 11, 7, 5, 10, 11, 1, 9, 0],
+    [10, 7, 5, 10, 11, 7, 9, 8, 1, 8, 3, 1],
+    [11, 1, 2, 11, 7, 1, 7, 5, 1],
+    [0, 8, 3, 1, 2, 7, 1, 7, 5, 7, 2, 11],
+    [9, 7, 5, 9, 2, 7, 9, 0, 2, 2, 11, 7],
+    [7, 5, 2, 7, 2, 11, 5, 9, 2, 3, 2, 8, 9, 8, 2],
+    [2, 5, 10, 2, 3, 5, 3, 7, 5],
+    [8, 2, 0, 8, 5, 2, 8, 7, 5, 10, 2, 5],
+    [9, 0, 1, 5, 10, 3, 5, 3, 7, 3, 10, 2],
+    [9, 8, 2, 9, 2, 1, 8, 7, 2, 10, 2, 5, 7, 5, 2],
+    [1, 3, 5, 3, 7, 5],
+    [0, 8, 7, 0, 7, 1, 1, 7, 5],
+    [9, 0, 3, 9, 3, 5, 5, 3, 7],
+    [9, 8, 7, 5, 9, 7],
+    [5, 8, 4, 5, 10, 8, 10, 11, 8],
+    [5, 0, 4, 5, 11, 0, 5, 10, 11, 11, 3, 0],
+    [0, 1, 9, 8, 4, 10, 8, 10, 11, 10, 4, 5],
+    [10, 11, 4, 10, 4, 5, 11, 3, 4, 9, 4, 1, 3, 1, 4],
+    [2, 5, 1, 2, 8, 5, 2, 11, 8, 4, 5, 8],
+    [0, 4, 11, 0, 11, 3, 4, 5, 11, 2, 11, 1, 5, 1, 11],
+    [0, 2, 5, 0, 5, 9, 2, 11, 5, 4, 5, 8, 11, 8, 5],
+    [9, 4, 5, 2, 11, 3],
+    [2, 5, 10, 3, 5, 2, 3, 4, 5, 3, 8, 4],
+    [5, 10, 2, 5, 2, 4, 4, 2, 0],
+    [3, 10, 2, 3, 5, 10, 3, 8, 5, 4, 5, 8, 0, 1, 9],
+    [5, 10, 2, 5, 2, 4, 1, 9, 2, 9, 4, 2],
+    [8, 4, 5, 8, 5, 3, 3, 5, 1],
+    [0, 4, 5, 1, 0, 5],
+    [8, 4, 5, 8, 5, 3, 9, 0, 5, 0, 3, 5],
+    [9, 4, 5],
+    [4, 11, 7, 4, 9, 11, 9, 10, 11],
+    [0, 8, 3, 4, 9, 7, 9, 11, 7, 9, 10, 11],
+    [1, 10, 11, 1, 11, 4, 1, 4, 0, 7, 4, 11],
+    [3, 1, 4, 3, 4, 8, 1, 10, 4, 7, 4, 11, 10, 11, 4],
+    [4, 11, 7, 9, 11, 4, 9, 2, 11, 9, 1, 2],
+    [9, 7, 4, 9, 11, 7, 9, 1, 11, 2, 11, 1, 0, 8, 3],
+    [11, 7, 4, 11, 4, 2, 2, 4, 0],
+    [11, 7, 4, 11, 4, 2, 8, 3, 4, 3, 2, 4],
+    [2, 9, 10, 2, 7, 9, 2, 3, 7, 7, 4, 9],
+    [9, 10, 7, 9, 7, 4, 10, 2, 7, 8, 7, 0, 2, 0, 7],
+    [3, 7, 10, 3, 10, 2, 7, 4, 10, 1, 10, 0, 4, 0, 10],
+    [1, 10, 2, 8, 7, 4],
+    [4, 9, 1, 4, 1, 7, 7, 1, 3],
+    [4, 9, 1, 4, 1, 7, 0, 8, 1, 8, 7, 1],
+    [4, 0, 3, 7, 4, 3],
+    [4, 8, 7],
+    [9, 10, 8, 10, 11, 8],
+    [3, 0, 9, 3, 9, 11, 11, 9, 10],
+    [0, 1, 10, 0, 10, 8, 8, 10, 11],
+    [3, 1, 10, 11, 3, 10],
+    [1, 2, 11, 1, 11, 9, 9, 11, 8],
+    [3, 0, 9, 3, 9, 11, 1, 2, 9, 2, 11, 9],
+    [0, 2, 11, 8, 0, 11],
+    [3, 2, 11],
+    [2, 3, 8, 2, 8, 10, 10, 8, 9],
+    [9, 10, 2, 0, 9, 2],
+    [2, 3, 8, 2, 8, 10, 0, 1, 8, 1, 10, 8],
+    [1, 10, 2],
+    [1, 3, 8, 9, 1, 8],
+    [0, 9, 1],
+    [0, 3, 8],
+    []
+];

+ 3 - 2
src/mol-gl/attribute.ts

@@ -5,6 +5,7 @@
  */
 
 import REGL = require('regl');
+import { ValueBox } from 'mol-util/value-cell'
 
 // export type AttributeGroupMutator<T extends AttributesData> = (data: T) => (boolean | void)
 // export type AttributeGroupData = { [k: string]: Helpers.TypedArray }
@@ -62,9 +63,9 @@ namespace Attribute {
     export type ArrayCell<T> = { array: ReferenceCell<T> }
     export type ReferenceCell<T> = { readonly version: number, readonly value: T }
 
-    export function create<T extends Helpers.TypedArray>(regl: REGL.Regl, array: T, props: AttributeProps): Attribute<T> {
+    export function create<T extends Helpers.TypedArray>(regl: REGL.Regl, array: ValueBox<T>, props: AttributeProps): Attribute<T> {
         const itemSize = props.size
-        let _array = array
+        let _array = array.value
         let _count = _array.length / itemSize
         if (props.stride) _count = _array.length / (props.stride / _array.BYTES_PER_ELEMENT)
         console.log(_array.length, props.stride)

+ 12 - 18
src/mol-gl/renderable/mesh.ts

@@ -5,43 +5,35 @@
  */
 
 import REGL = require('regl');
+import { ValueBox } from 'mol-util/value-cell'
+
 import { Renderable } from '../renderable'
-import { getBuffers } from './util'
+import { getBuffers, createTransformAttributes, fillSerial } from './util'
 import Attribute from '../attribute';
-
 import { MeshShaders } from '../shaders'
 
 type Mesh = 'mesh'
 
 type Uniforms = { [k: string]: REGL.Uniform | REGL.Texture }
 
-export function fillSerial<T extends Helpers.NumberArray> (array: T) {
-    const n = array.length
-    for (let i = 0; i < n; ++i) array[ i ] = i
-    return array
-}
-
 namespace Mesh {
     export type DataType = {
         position: { type: Float32Array, itemSize: 3 }
         normal: { type: Float32Array, itemSize: 3 }
-        transformColumn0: { type: Float32Array, itemSize: 4 }
-        transformColumn1: { type: Float32Array, itemSize: 4 }
-        transformColumn2: { type: Float32Array, itemSize: 4 }
-        transformColumn3: { type: Float32Array, itemSize: 4 }
+        transform: { type: Float32Array, itemSize: 16 }
     }
     export type Data = { [K in keyof DataType]: DataType[K]['type'] }
-    export type Attributes = { [K in keyof Data]: Attribute<Data[K]> }
+    export type BoxedData = { [K in keyof Data]: ValueBox<Data[K]> }
 
-    export function create(regl: REGL.Regl, attributes: Attributes, uniforms: Uniforms, elements?: Helpers.UintArray): Renderable<Data> {
+    export function create(regl: REGL.Regl, data: BoxedData, uniforms: Uniforms, elements?: Helpers.UintArray): Renderable<Data> {
         // console.log('mesh', {
         //     count: attributes.position.getCount(),
         //     instances: attributes.transformColumn0.getCount(),
         //     attributes,
         //     uniforms
         // })
-        const instanceCount = attributes.transformColumn0.getCount()
-        const instanceId = fillSerial(new Float32Array(instanceCount))
+        const instanceCount = data.transform.value.length / 16
+        const instanceId = ValueBox(fillSerial(new Float32Array(instanceCount)))
         // console.log(instanceId)
         const command = regl({
             ...MeshShaders,
@@ -52,7 +44,9 @@ namespace Mesh {
             },
             attributes: getBuffers({
                 instanceId: Attribute.create(regl, instanceId, { size: 1, divisor: 1 }),
-                ...attributes
+                position: Attribute.create(regl, data.position, { size: 3 }),
+                normal: Attribute.create(regl, data.normal, { size: 3 }),
+                ...createTransformAttributes(regl, data.transform)
             }),
             elements: elements && regl.elements({
                 data: new Uint16Array(elements),
@@ -61,7 +55,7 @@ namespace Mesh {
                 // count: elements.length / 3,
                 // length: elements.length * 2
             }),
-            count: elements ? elements.length : attributes.position.getCount(),
+            count: elements ? elements.length : data.position.value.length / 3,
             instances: instanceCount,
             primitive: 'triangles'
         })

+ 15 - 14
src/mol-gl/renderable/point.ts

@@ -5,8 +5,10 @@
  */
 
 import REGL = require('regl');
+import { ValueBox } from 'mol-util/value-cell'
+
 import { Renderable } from '../renderable'
-import { getBuffers } from './util'
+import { getBuffers, createTransformAttributes, fillSerial } from './util'
 import Attribute from '../attribute';
 import { PointShaders } from '../shaders'
 
@@ -15,24 +17,23 @@ type Point = 'point'
 namespace Point {
     export type DataType = {
         position: { type: Float32Array, itemSize: 3 }
-        transformColumn0: { type: Float32Array, itemSize: 4 }
-        transformColumn1: { type: Float32Array, itemSize: 4 }
-        transformColumn2: { type: Float32Array, itemSize: 4 }
-        transformColumn3: { type: Float32Array, itemSize: 4 }
+        transform: { type: Float32Array, itemSize: 16 }
     }
     export type Data = { [K in keyof DataType]: DataType[K]['type'] }
-    export type Attributes = { [K in keyof Data]: Attribute<Data[K]> }
+    export type BoxedData = { [K in keyof Data]: ValueBox<Data[K]> }
 
-    export function create(regl: REGL.Regl, attributes: Attributes): Renderable<Data> {
-        console.log('point', {
-            count: attributes.position.getCount(),
-            instances: attributes.transformColumn0.getCount(),
-        }, attributes)
+    export function create(regl: REGL.Regl, data: BoxedData): Renderable<Data> {
+        const instanceCount = data.transform.value.length / 16
+        const instanceId = ValueBox(fillSerial(new Float32Array(instanceCount)))
         const command = regl({
             ...PointShaders,
-            attributes: getBuffers(attributes),
-            count: attributes.position.getCount(),
-            instances: attributes.transformColumn0.getCount(),
+            attributes: getBuffers({
+                instanceId: Attribute.create(regl, instanceId, { size: 1, divisor: 1 }),
+                position: Attribute.create(regl, data.position, { size: 3 }),
+                ...createTransformAttributes(regl, data.transform)
+            }),
+            count: data.position.value.length / 3,
+            instances: instanceCount,
             primitive: 'points'
         })
         return {

+ 10 - 2
src/mol-gl/renderable/util.ts

@@ -5,13 +5,15 @@
  */
 
 import REGL = require('regl');
+import { ValueBox } from 'mol-util/value-cell'
+
 import { Attributes, AttributesData, AttributesBuffers } from '../renderable'
 import Attribute from '../attribute'
 
-export function createTransformAttributes (regl: REGL.Regl, transform: Float32Array) {
+export function createTransformAttributes (regl: REGL.Regl, transform: ValueBox<Float32Array>) {
     const size = 4
     const divisor = 1
-    const bpe = transform.BYTES_PER_ELEMENT
+    const bpe = transform.value.BYTES_PER_ELEMENT
     const stride = 16 * bpe
     return {
         transformColumn0: Attribute.create(regl, transform, { size, divisor, offset: 0, stride }),
@@ -28,3 +30,9 @@ export function getBuffers<T extends AttributesData>(attributes: Attributes<T>):
     }
     return buffers as AttributesBuffers<T>
 }
+
+export function fillSerial<T extends Helpers.NumberArray> (array: T) {
+    const n = array.length
+    for (let i = 0; i < n; ++i) array[ i ] = i
+    return array
+}

+ 1 - 1
src/mol-io/common/binary-cif/array-encoder.ts

@@ -357,7 +357,7 @@ export namespace ArrayEncoding {
         let map: any = Object.create(null);
         let strings: string[] = [];
         let accLength = 0;
-        let offsets = ChunkedArray.create<number>(s => new Int32Array(s), 1, 1024, true)
+        let offsets = ChunkedArray.create<number>(s => new Int32Array(s), 1, 1024)
         let output = new Int32Array(data.length);
 
         ChunkedArray.add(offsets, 0);

+ 1 - 1
src/mol-io/reader/cif/data-model.ts

@@ -78,7 +78,7 @@ export interface Field {
     toFloatArray(params?: Column.ToArrayParams<number>): ReadonlyArray<number>
 }
 
-export function getTensor(category: Category, field: string, space: Tensor.Space, row: number): Tensor {
+export function getTensor(category: Category, field: string, space: Tensor.Space, row: number): Tensor.Data {
     const ret = space.create();
     if (space.rank === 1) {
         const rows = space.dimensions[0];

+ 2 - 2
src/mol-io/reader/cif/schema.ts

@@ -71,7 +71,7 @@ function createListColumn<T extends number|string>(schema: Column.Schema.List<T>
     };
 }
 
-function createTensorColumn(schema: Column.Schema.Tensor, category: Data.Category, key: string): Column<Tensor> {
+function createTensorColumn(schema: Column.Schema.Tensor, category: Data.Category, key: string): Column<Tensor.Data> {
     const space = schema.space;
     let firstFieldName: string;
     switch (space.rank) {
@@ -82,7 +82,7 @@ function createTensorColumn(schema: Column.Schema.Tensor, category: Data.Categor
     }
     const first = category.getField(firstFieldName) || Column.Undefined(category.rowCount, schema);
     const value = (row: number) => Data.getTensor(category, key, space, row);
-    const toArray: Column<Tensor>['toArray'] = params => ColumnHelpers.createAndFillArray(category.rowCount, value, params)
+    const toArray: Column<Tensor.Data>['toArray'] = params => ColumnHelpers.createAndFillArray(category.rowCount, value, params)
 
     return {
         schema,

+ 13 - 9
src/mol-math/linear-algebra/tensor.ts

@@ -6,18 +6,20 @@
 
 import { Mat4, Vec3, Vec4 } from './3d'
 
-export interface Tensor extends Array<number> { '@type': 'tensor' }
+export interface Tensor { data: Tensor.Data, space: Tensor.Space }
 
 export namespace Tensor {
     export type ArrayCtor = { new (size: number): ArrayLike<number> }
 
+    export interface Data extends Array<number> { '@type': 'tensor' }
+
     export interface Space {
         readonly rank: number,
         readonly dimensions: ReadonlyArray<number>,
         readonly axisOrderSlowToFast: ReadonlyArray<number>,
-        create(array?: ArrayCtor): Tensor,
-        get(data: Tensor, ...coords: number[]): number
-        set(data: Tensor, ...coordsAndValue: number[]): number
+        create(array?: ArrayCtor): Tensor.Data,
+        get(data: Tensor.Data, ...coords: number[]): number
+        set(data: Tensor.Data, ...coordsAndValue: number[]): number
     }
 
     interface Layout {
@@ -39,6 +41,8 @@ export namespace Tensor {
         return { dimensions, axisOrderFastToSlow, axisOrderSlowToFast, accessDimensions, defaultCtor: ctor || Float64Array }
     }
 
+    export function create(space: Space, data: Data): Tensor { return { space, data }; }
+
     export function Space(dimensions: number[], axisOrderSlowToFast: number[], ctor?: ArrayCtor): Space {
         const layout = Layout(dimensions, axisOrderSlowToFast, ctor);
         const { get, set } = accessors(layout);
@@ -49,7 +53,7 @@ export namespace Tensor {
     export function ColumnMajorMatrix(rows: number, cols: number, ctor?: ArrayCtor) { return Space([rows, cols], [1, 0], ctor); }
     export function RowMajorMatrix(rows: number, cols: number, ctor?: ArrayCtor) { return Space([rows, cols], [0, 1], ctor); }
 
-    export function toMat4(space: Space, data: Tensor): Mat4 {
+    export function toMat4(space: Space, data: Tensor.Data): Mat4 {
         if (space.rank !== 2) throw new Error('Invalid tensor rank');
         const mat = Mat4.zero();
         const d0 = Math.min(4, space.dimensions[0]), d1 = Math.min(4, space.dimensions[1]);
@@ -59,7 +63,7 @@ export namespace Tensor {
         return mat;
     }
 
-    export function toVec3(space: Space, data: Tensor): Vec3 {
+    export function toVec3(space: Space, data: Tensor.Data): Vec3 {
         if (space.rank !== 1) throw new Error('Invalid tensor rank');
         const vec = Vec3.zero();
         const d0 = Math.min(3, space.dimensions[0]);
@@ -67,7 +71,7 @@ export namespace Tensor {
         return vec;
     }
 
-    export function toVec4(space: Space, data: Tensor): Vec4 {
+    export function toVec4(space: Space, data: Tensor.Data): Vec4 {
         if (space.rank !== 1) throw new Error('Invalid tensor rank');
         const vec = Vec4.zero();
         const d0 = Math.min(4, space.dimensions[0]);
@@ -75,7 +79,7 @@ export namespace Tensor {
         return vec;
     }
 
-    export function areEqualExact(a: Tensor, b: Tensor) {
+    export function areEqualExact(a: Tensor.Data, b: Tensor.Data) {
         const len = a.length;
         if (len !== b.length) return false;
         for (let i = 0; i < len; i++) if (a[i] !== b[i]) return false;
@@ -136,7 +140,7 @@ export namespace Tensor {
         const { dimensions: ds } = layout;
         let size = 1;
         for (let i = 0, _i = ds.length; i < _i; i++) size *= ds[i];
-        return ctor => new (ctor || layout.defaultCtor)(size) as Tensor;
+        return ctor => new (ctor || layout.defaultCtor)(size) as Tensor.Data;
     }
 
     function dataOffset(layout: Layout, coord: number[]) {

+ 2 - 1
src/mol-task/execution/observable.ts

@@ -159,10 +159,11 @@ class ObservableRuntimeContext implements RuntimeContext {
             progress.message = update;
         } else {
             if (typeof update.canAbort !== 'undefined') progress.canAbort = update.canAbort;
+            if (typeof update.message !== 'undefined') progress.message = update.message;
             if (typeof update.current !== 'undefined') progress.current = update.current;
             if (typeof update.max !== 'undefined') progress.max = update.max;
-            if (typeof update.message !== 'undefined') progress.message = update.message;
             progress.isIndeterminate = typeof progress.current === 'undefined' || typeof progress.max === 'undefined';
+            if (typeof update.isIndeterminate !== 'undefined') progress.isIndeterminate = update.isIndeterminate;
         }
     }
 

+ 0 - 1
src/mol-util/value-cell.ts

@@ -31,4 +31,3 @@ function ValueBox<T>(boxOrValue: T | ValueBox<T>, value?: T): ValueBox<T> {
 }
 
 export { ValueCell, ValueBox };
-

+ 2 - 2
src/perf-tests/chunked-array-vs-native.ts

@@ -8,13 +8,13 @@ function testNative(size: number) {
 }
 
 function testChunkedTyped(size: number, chunk: number, linear: boolean) {
-    const xs = ChunkedArray.create(s => new Int32Array(s), 1, chunk, linear);
+    const xs = ChunkedArray.create(s => new Int32Array(s), 1, chunk);
     for (let i = 0; i < size; i++) ChunkedArray.add(xs, i * i);
     return ChunkedArray.compact(xs);
 }
 
 function testChunkedNative(size: number, chunk: number) {
-    const xs = ChunkedArray.create(s => [], 1, chunk, false);
+    const xs = ChunkedArray.create(s => [], 1, chunk);
     for (let i = 0; i < size; i++) ChunkedArray.add(xs, i * i);
     return ChunkedArray.compact(xs);
 }