diff --git a/src/mol-data/db/column.ts b/src/mol-data/db/column.ts index 5e53c04d88be1349c003ad77d412055d4c19d296..03394b28d5329a8b101a0ecba41d0c407a775c8c 100644 --- a/src/mol-data/db/column.ts +++ b/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'> diff --git a/src/mol-data/util/chunked-array.ts b/src/mol-data/util/chunked-array.ts index 8ac4ba715d1399d4588385ccad25eb3f1b6f82e2..5e54f5ed82ee07a90b82b3f9c8d80fa8a8b07a30 100644 --- a/src/mol-data/util/chunked-array.ts +++ b/src/mol-data/util/chunked-array.ts @@ -74,13 +74,14 @@ namespace ChunkedArray { return array.elementCount++; } - - export function compact<T>(array: ChunkedArray<T>): ArrayLike<T> { + export function compact<T>(array: ChunkedArray<T>, doNotResizeSingleton = false): 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]; + } } let size = 0; diff --git a/src/mol-geo/shape/surface.ts b/src/mol-geo/shape/surface.ts new file mode 100644 index 0000000000000000000000000000000000000000..9f31177f9b585710e61ff7f66afa3a417626c948 --- /dev/null +++ b/src/mol-geo/shape/surface.ts @@ -0,0 +1,200 @@ +/** + * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author David Sehnal <david.sehnal@gmail.com> + */ + +export interface Surface { + vertexCount: number, + triangleCount: number, + vertexBuffer: Float32Array, + indexBuffer: Uint32Array, + normalBuffer?: Float32Array, + normalsComputed: boolean, + + vertexAnnotation?: ArrayLike<number>[] + //boundingSphere?: { center: Geometry.LinearAlgebra.Vector3, radius: number }; +} + +// export namespace Surface { +// export function computeNormalsImmediate(surface: Surface) { +// if (surface.normals) return; + +// const normals = new Float32Array(surface.vertices.length), +// v = surface.vertices, triangles = surface.triangleIndices; +// for (let i = 0; i < triangles.length; i += 3) { +// const a = 3 * triangles[i], +// b = 3 * triangles[i + 1], +// c = 3 * triangles[i + 2]; + +// const nx = v[a + 2] * (v[b + 1] - v[c + 1]) + v[b + 2] * v[c + 1] - v[b + 1] * v[c + 2] + v[a + 1] * (-v[b + 2] + v[c + 2]), +// ny = -(v[b + 2] * v[c]) + v[a + 2] * (-v[b] + v[c]) + v[a] * (v[b + 2] - v[c + 2]) + v[b] * v[c + 2], +// nz = v[a + 1] * (v[b] - v[c]) + v[b + 1] * v[c] - v[b] * v[c + 1] + v[a] * (-v[b + 1] + v[b + 1]); + +// normals[a] += nx; normals[a + 1] += ny; normals[a + 2] += nz; +// normals[b] += nx; normals[b + 1] += ny; normals[b + 2] += nz; +// normals[c] += nx; normals[c + 1] += ny; normals[c + 2] += nz; +// } + +// for (let i = 0; i < normals.length; 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; +// } +// surface.normals = normals; +// } + +// export function computeNormals(surface: Surface): Computation<Surface> { +// return computation<Surface>(async ctx => { +// if (surface.normals) { +// return surface; +// }; + +// await ctx.updateProgress('Computing normals...'); +// computeNormalsImmediate(surface); +// return surface; +// }); +// } + +// 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 transformImmediate(surface: Surface, t: number[]) { +// const p = LinearAlgebra.Vector3.zero(); +// const m = LinearAlgebra.Vector3.transformMat4; +// const vertices = surface.vertices; +// for (let i = 0, _c = surface.vertices.length; i < _c; i += 3) { +// p[0] = vertices[i]; +// p[1] = vertices[i + 1]; +// p[2] = vertices[i + 2]; +// m(p, p, t); +// vertices[i] = p[0]; +// vertices[i + 1] = p[1]; +// vertices[i + 2] = p[2]; +// } +// surface.normals = void 0; +// surface.boundingSphere = void 0; +// } + +// export function transform(surface: Surface, t: number[]): Computation<Surface> { +// return computation<Surface>(async ctx => { +// ctx.updateProgress('Updating surface...'); +// transformImmediate(surface, t); +// return surface; +// }); +// } +// } \ No newline at end of file diff --git a/src/mol-geo/util/marching-cubes/algorithm.ts b/src/mol-geo/util/marching-cubes/algorithm.ts index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..2a534fba89d2f38dbf18d70e8404ace9329aed09 100644 --- a/src/mol-geo/util/marching-cubes/algorithm.ts +++ b/src/mol-geo/util/marching-cubes/algorithm.ts @@ -0,0 +1,242 @@ +/** + * 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' + +/** + * The parameters required by the algorithm. + */ +export interface MarchingCubesParameters { + isoLevel: number, + scalarField: Tensor, + + bottomLeft?: ArrayLike<number>, + topRight?: ArrayLike<number>, + + annotationField?: Tensor, + + buffers?: { + vertex?: Float32Array, + index?: Uint32Array, + normal?: Float32Array, + annotation?: ArrayLike<number> + } +} + +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 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 vertexBuffer = ChunkedArray.compact(this.state.vertexBuffer) as Float32Array; + const indexBuffer = ChunkedArray.compact(this.state.triangleBuffer) as Uint32Array; + + this.state.vertexBuffer = <any>void 0; + this.state.verticesOnEdges = <any>void 0; + + let ret: Surface = { + triangleCount: 0, + vertexCount: 0, + vertexBuffer, + indexBuffer, + // vertexAnnotation: this.state.annotate ? 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 }; + + 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>; + + 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); + } + + 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); + this.triangleBuffer = ChunkedArray.create<number>(s => new Uint32Array(s), 3, triangleBufferSize); + + 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) { + ChunkedArray.add3(this.triangleBuffer, this.vertList[triInfo[t]], this.vertList[triInfo[t + 1]], this.vertList[triInfo[t + 2]]); + } + } +} \ No newline at end of file diff --git a/src/mol-io/reader/cif/data-model.ts b/src/mol-io/reader/cif/data-model.ts index feb812fa368a40520afcb76825642cd756705ef5..8182d0fca61359721a06f525aa672b7faa404717 100644 --- a/src/mol-io/reader/cif/data-model.ts +++ b/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]; diff --git a/src/mol-io/reader/cif/schema.ts b/src/mol-io/reader/cif/schema.ts index d71b6cc898f65b770c6883bdfcab57f3d1e96375..c0f7e1d20c8ef614911b68cff79680662bab1d63 100644 --- a/src/mol-io/reader/cif/schema.ts +++ b/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, diff --git a/src/mol-math/linear-algebra/tensor.ts b/src/mol-math/linear-algebra/tensor.ts index 3efd39d60ebb057788b8e4bda0e12ff292ea971c..5952080321b59a03c44c637dbc16cf3209dfbfbb 100644 --- a/src/mol-math/linear-algebra/tensor.ts +++ b/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 { @@ -49,7 +51,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 +61,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 +69,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 +77,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 +138,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[]) { diff --git a/src/mol-task/execution/observable.ts b/src/mol-task/execution/observable.ts index 60d6c4e291d4e3c6fd5d37de1312604fb6652c1b..82d4e05a43917365d625115cdd1b13c072346d88 100644 --- a/src/mol-task/execution/observable.ts +++ b/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; } }