Skip to content
Snippets Groups Projects
Commit fe185001 authored by David Sehnal's avatar David Sehnal
Browse files

Tensor tweaks, marching cubes

parent 1057caaa
No related branches found
No related tags found
No related merge requests found
......@@ -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'>
......
......@@ -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;
......
/**
* 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
/**
* 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
......@@ -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];
......
......@@ -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,
......
......@@ -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[]) {
......
......@@ -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% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment