uglymol
Version:
Macromolecular Viewer for Crystallographers
688 lines (669 loc) • 21.3 kB
text/typescript
type Num3 = [number, number, number];
export class Block {
_points: Num3[] | null;
_values: number[] | null;
_size: Num3
constructor() {
this._points = null;
this._values = null;
this._size = [0, 0, 0];
}
set(points: Num3[], values: number[], size: Num3) {
if (size[0] <= 0 || size[1] <= 0 || size[2] <= 0) {
throw Error('Grid dimensions are zero along at least one edge');
}
const len = size[0] * size[1] * size[2];
if (values.length !== len || points.length !== len) {
throw Error('isosurface: array size mismatch');
}
this._points = points;
this._values = values;
this._size = size;
}
clear() {
this._points = null;
this._values = null;
}
empty() : boolean {
return this._values === null;
}
isosurface(isolevel: number, method: string) {
//if (method === 'marching tetrahedra') {
// return marchingTetrahedra(block, isolevel);
//}
return marchingCubes(this._size, this._values, this._points,
isolevel, method);
}
}
/* eslint comma-spacing: 0, no-multi-spaces: 0 */
const edgeTable = new Int32Array([
0x0 , 0x0 , 0x202, 0x302, 0x406, 0x406, 0x604, 0x704,
0x804, 0x805, 0xa06, 0xa06, 0xc0a, 0xd03, 0xe08, 0xf00,
0x90 , 0x98 , 0x292, 0x292, 0x496, 0x49e, 0x694, 0x694,
0x894, 0x894, 0xa96, 0xa96, 0xc9a, 0xc92, 0xe91, 0xe90,
0x230, 0x230, 0x33 , 0x13a, 0x636, 0x636, 0x434, 0x43c,
0xa34, 0xa35, 0x837, 0x936, 0xe3a, 0xf32, 0xc31, 0xd30,
0x2a0, 0x2a8, 0xa3 , 0xaa , 0x6a6, 0x6af, 0x5a4, 0x4ac,
0xaa4, 0xaa4, 0x9a6, 0x8a6, 0xfaa, 0xea3, 0xca1, 0xca0,
0x460, 0x460, 0x662, 0x762, 0x66 , 0x66 , 0x265, 0x364,
0xc64, 0xc65, 0xe66, 0xe66, 0x86a, 0x863, 0xa69, 0xa60,
0x4f0, 0x4f8, 0x6f2, 0x6f2, 0xf6 , 0xfe , 0x2f5, 0x2fc,
0xcf4, 0xcf4, 0xef6, 0xef6, 0x8fa, 0x8f3, 0xaf9, 0xaf0,
0x650, 0x650, 0x453, 0x552, 0x256, 0x256, 0x54 , 0x154,
0xe54, 0xf54, 0xc57, 0xd56, 0xa5a, 0xb52, 0x859, 0x950,
0x7c0, 0x6c1, 0x5c2, 0x4c2, 0x3c6, 0x2ce, 0xc5 , 0xc4 ,
0xfc4, 0xec5, 0xdc6, 0xcc6, 0xbca, 0xac2, 0x8c1, 0x8c0,
0x8c0, 0x8c0, 0xac2, 0xbc2, 0xcc6, 0xcc6, 0xec4, 0xfcc,
0xc4 , 0xc5 , 0x2c6, 0x3c6, 0x4c2, 0x5c2, 0x6c1, 0x7c0,
0x950, 0x859, 0xb52, 0xa5a, 0xd56, 0xc57, 0xe54, 0xe5c,
0x154, 0x54 , 0x25e, 0x256, 0x552, 0x453, 0x658, 0x650,
0xaf0, 0xaf0, 0x8f3, 0x8fa, 0xef6, 0xef6, 0xcf4, 0xcfc,
0x2f4, 0x3f5, 0xff , 0x1f6, 0x6f2, 0x6f3, 0x4f9, 0x5f0,
0xa60, 0xa69, 0x863, 0x86a, 0xe66, 0xe67, 0xd65, 0xc6c,
0x364, 0x265, 0x166, 0x66 , 0x76a, 0x663, 0x460, 0x460,
0xca0, 0xca0, 0xea2, 0xfa2, 0x8a6, 0x8a6, 0xaa4, 0xba4,
0x4ac, 0x5a4, 0x6ae, 0x7a6, 0xaa , 0xa3 , 0x2a8, 0x2a0,
0xd30, 0xc31, 0xf32, 0xe3a, 0x936, 0x837, 0xb35, 0xa34,
0x43c, 0x434, 0x73e, 0x636, 0x13a, 0x33 , 0x339, 0x230,
0xe90, 0xe90, 0xc92, 0xc9a, 0xa96, 0xa96, 0x894, 0x89c,
0x694, 0x695, 0x49f, 0x496, 0x292, 0x392, 0x98 , 0x90 ,
0xf00, 0xe08, 0xd03, 0xc0a, 0xa06, 0xa0e, 0x805, 0x804,
0x704, 0x604, 0x506, 0x406, 0x302, 0x202, 0x0 , 0x0]);
// generated from classical triTable by tools/isolut.py
const segTable = [
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[]];
const segTable2 = [
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[]];
const cubeVerts = [[0,0,0], [1,0,0], [1,1,0], [0,1,0],
[], [1,0,1], [1,1,1], [0,1,1]];
const edgeIndex = [[0,1], [1,2], [2,3], [3,0], [4,5], [5,6],
[], [7,4], [0,4], [1,5], [2,6], [3,7]];
// edge directions: [x, y, -x, -y, x, y, -x, -y, z, z, z, z]
// return offsets relative to vertex [0,0,0]
function calculateVertOffsets(dims: Num3) {
const vert_offsets = [];
for (let i = 0; i < 8; ++i) {
const v = cubeVerts[i];
vert_offsets.push(v[0] + dims[2] * (v[1] + dims[1] * v[2]));
}
return vert_offsets;
}
function marchingCubes(dims: Num3,
values: number[] | null,
points: Num3[] | null,
isolevel: number,
method: string) {
const snap = (method === 'snapped MC');
const seg_table = (method === 'squarish' ? segTable2 : segTable);
const vlist = new Array(12);
const vert_offsets = calculateVertOffsets(dims);
const vertex_values = new Float32Array(8);
const vertex_points: Num3[] = new Array(8);
const size_x = dims[0];
const size_y = dims[1];
const size_z = dims[2];
if (values == null || points == null) return;
const vertices = [];
const segments = [];
let vertex_count = 0;
for (let x = 0; x < size_x - 1; x++) {
for (let y = 0; y < size_y - 1; y++) {
for (let z = 0; z < size_z - 1; z++) {
const offset0 = z + size_z * (y + size_y * x);
let cubeindex = 0;
let i;
let j;
for (i = 0; i < 8; ++i) {
j = offset0 + vert_offsets[i];
cubeindex |= (values[j] < isolevel) ? 1 << i : 0;
}
if (cubeindex === 0 || cubeindex === 255) continue;
for (i = 0; i < 8; ++i) {
j = offset0 + vert_offsets[i];
vertex_values[i] = values[j];
vertex_points[i] = points[j];
}
// 12 bit number, indicates which edges are crossed by the isosurface
const edge_mask = edgeTable[cubeindex];
// check which edges are crossed, and estimate the point location
// using a weighted average of scalar values at edge endpoints.
for (i = 0; i < 12; ++i) {
if ((edge_mask & (1 << i)) !== 0) {
const e = edgeIndex[i];
let mu = (isolevel - vertex_values[e[0]]) /
(vertex_values[e[1]] - vertex_values[e[0]]);
if (snap === true) {
if (mu > 0.85) mu = 1;
else if (mu < 0.15) mu = 0;
}
const p1 = vertex_points[e[0]];
const p2 = vertex_points[e[1]];
// The number of added vertices could be roughly halved
// if we avoided duplicates between neighbouring cells.
// Using a map for lookups is too slow, perhaps a big
// array would do?
vertices.push(p1[0] + (p2[0] - p1[0]) * mu,
p1[1] + (p2[1] - p1[1]) * mu,
p1[2] + (p2[2] - p1[2]) * mu);
vlist[i] = vertex_count++;
}
}
const t = seg_table[cubeindex];
for (i = 0; i < t.length; i++) {
segments.push(vlist[t[i]]);
}
}
}
}
return { vertices: vertices, segments: segments };
}