@kitware/vtk.js
Version:
Visualization Toolkit for the Web
309 lines (279 loc) • 11 kB
JavaScript
import { m as macro } from '../../macros2.js';
import vtkDataArray from '../../Common/Core/DataArray.js';
import vtkEdgeLocator from '../../Common/DataModel/EdgeLocator.js';
import vtkPolyData from '../../Common/DataModel/PolyData.js';
import { l as normalize } from '../../Common/Core/Math/index.js';
import vtkCaseTable from './ImageMarchingCubes/caseTable.js';
const {
vtkErrorMacro,
vtkDebugMacro
} = macro;
// ----------------------------------------------------------------------------
// vtkImageMarchingCubes methods
// ----------------------------------------------------------------------------
function vtkImageMarchingCubes(publicAPI, model) {
// Set our className
model.classHierarchy.push('vtkImageMarchingCubes');
const ids = [];
const voxelScalars = [];
const voxelGradients = [];
const voxelPts = [];
const edgeLocator = vtkEdgeLocator.newInstance();
// Retrieve scalars and voxel coordinates. i-j-k is origin of voxel.
publicAPI.getVoxelScalars = (i, j, k, slice, dims, origin, spacing, s) => {
// First get the indices for the voxel
ids[0] = k * slice + j * dims[0] + i; // i, j, k
ids[1] = ids[0] + 1; // i+1, j, k
ids[2] = ids[0] + dims[0]; // i, j+1, k
ids[3] = ids[2] + 1; // i+1, j+1, k
ids[4] = ids[0] + slice; // i, j, k+1
ids[5] = ids[4] + 1; // i+1, j, k+1
ids[6] = ids[4] + dims[0]; // i, j+1, k+1
ids[7] = ids[6] + 1; // i+1, j+1, k+1
// Now retrieve the scalars
for (let ii = 0; ii < 8; ++ii) {
voxelScalars[ii] = s[ids[ii]];
}
};
// Retrieve voxel coordinates. i-j-k is origin of voxel.
publicAPI.getVoxelPoints = (i, j, k, origin, spacing) => {
// (i,i+1),(j,j+1),(k,k+1) - i varies fastest; then j; then k
voxelPts[0] = origin[0] + i * spacing[0]; // 0
voxelPts[1] = origin[1] + j * spacing[1];
voxelPts[2] = origin[2] + k * spacing[2];
voxelPts[3] = voxelPts[0] + spacing[0]; // 1
voxelPts[4] = voxelPts[1];
voxelPts[5] = voxelPts[2];
voxelPts[6] = voxelPts[0]; // 2
voxelPts[7] = voxelPts[1] + spacing[1];
voxelPts[8] = voxelPts[2];
voxelPts[9] = voxelPts[3]; // 3
voxelPts[10] = voxelPts[7];
voxelPts[11] = voxelPts[2];
voxelPts[12] = voxelPts[0]; // 4
voxelPts[13] = voxelPts[1];
voxelPts[14] = voxelPts[2] + spacing[2];
voxelPts[15] = voxelPts[3]; // 5
voxelPts[16] = voxelPts[1];
voxelPts[17] = voxelPts[14];
voxelPts[18] = voxelPts[0]; // 6
voxelPts[19] = voxelPts[7];
voxelPts[20] = voxelPts[14];
voxelPts[21] = voxelPts[3]; // 7
voxelPts[22] = voxelPts[7];
voxelPts[23] = voxelPts[14];
};
// Compute point gradient at i-j-k location
publicAPI.getPointGradient = (i, j, k, dims, slice, spacing, s, g) => {
let sp;
let sm;
// x-direction
if (i === 0) {
sp = s[i + 1 + j * dims[0] + k * slice];
sm = s[i + j * dims[0] + k * slice];
g[0] = (sm - sp) / spacing[0];
} else if (i === dims[0] - 1) {
sp = s[i + j * dims[0] + k * slice];
sm = s[i - 1 + j * dims[0] + k * slice];
g[0] = (sm - sp) / spacing[0];
} else {
sp = s[i + 1 + j * dims[0] + k * slice];
sm = s[i - 1 + j * dims[0] + k * slice];
g[0] = 0.5 * (sm - sp) / spacing[0];
}
// y-direction
if (j === 0) {
sp = s[i + (j + 1) * dims[0] + k * slice];
sm = s[i + j * dims[0] + k * slice];
g[1] = (sm - sp) / spacing[1];
} else if (j === dims[1] - 1) {
sp = s[i + j * dims[0] + k * slice];
sm = s[i + (j - 1) * dims[0] + k * slice];
g[1] = (sm - sp) / spacing[1];
} else {
sp = s[i + (j + 1) * dims[0] + k * slice];
sm = s[i + (j - 1) * dims[0] + k * slice];
g[1] = 0.5 * (sm - sp) / spacing[1];
}
// z-direction
if (k === 0) {
sp = s[i + j * dims[0] + (k + 1) * slice];
sm = s[i + j * dims[0] + k * slice];
g[2] = (sm - sp) / spacing[2];
} else if (k === dims[2] - 1) {
sp = s[i + j * dims[0] + k * slice];
sm = s[i + j * dims[0] + (k - 1) * slice];
g[2] = (sm - sp) / spacing[2];
} else {
sp = s[i + j * dims[0] + (k + 1) * slice];
sm = s[i + j * dims[0] + (k - 1) * slice];
g[2] = 0.5 * (sm - sp) / spacing[2];
}
};
// Compute voxel gradient values. I-j-k is origin point of voxel.
publicAPI.getVoxelGradients = (i, j, k, dims, slice, spacing, scalars) => {
const g = [];
publicAPI.getPointGradient(i, j, k, dims, slice, spacing, scalars, g);
voxelGradients[0] = g[0];
voxelGradients[1] = g[1];
voxelGradients[2] = g[2];
publicAPI.getPointGradient(i + 1, j, k, dims, slice, spacing, scalars, g);
voxelGradients[3] = g[0];
voxelGradients[4] = g[1];
voxelGradients[5] = g[2];
publicAPI.getPointGradient(i, j + 1, k, dims, slice, spacing, scalars, g);
voxelGradients[6] = g[0];
voxelGradients[7] = g[1];
voxelGradients[8] = g[2];
publicAPI.getPointGradient(i + 1, j + 1, k, dims, slice, spacing, scalars, g);
voxelGradients[9] = g[0];
voxelGradients[10] = g[1];
voxelGradients[11] = g[2];
publicAPI.getPointGradient(i, j, k + 1, dims, slice, spacing, scalars, g);
voxelGradients[12] = g[0];
voxelGradients[13] = g[1];
voxelGradients[14] = g[2];
publicAPI.getPointGradient(i + 1, j, k + 1, dims, slice, spacing, scalars, g);
voxelGradients[15] = g[0];
voxelGradients[16] = g[1];
voxelGradients[17] = g[2];
publicAPI.getPointGradient(i, j + 1, k + 1, dims, slice, spacing, scalars, g);
voxelGradients[18] = g[0];
voxelGradients[19] = g[1];
voxelGradients[20] = g[2];
publicAPI.getPointGradient(i + 1, j + 1, k + 1, dims, slice, spacing, scalars, g);
voxelGradients[21] = g[0];
voxelGradients[22] = g[1];
voxelGradients[23] = g[2];
};
publicAPI.produceTriangles = (cVal, i, j, k, extent, slice, dims, origin, spacing, scalars, points, tris, normals) => {
const CASE_MASK = [1, 2, 4, 8, 16, 32, 64, 128];
const VERT_MAP = [0, 1, 3, 2, 4, 5, 7, 6];
const xyz = [];
const n = [];
let pId;
publicAPI.getVoxelScalars(i, j, k, slice, dims, origin, spacing, scalars);
let index = 0;
for (let idx = 0; idx < 8; idx++) {
if (voxelScalars[VERT_MAP[idx]] >= cVal) {
index |= CASE_MASK[idx]; // eslint-disable-line no-bitwise
}
}
const voxelTris = vtkCaseTable.getCase(index);
if (voxelTris[0] < 0) {
return; // don't get the voxel coordinates, nothing to do
}
publicAPI.getVoxelPoints(i + extent[0], j + extent[2], k + extent[4], origin, spacing);
if (model.computeNormals) {
publicAPI.getVoxelGradients(i, j, k, dims, slice, spacing, scalars);
}
for (let idx = 0; voxelTris[idx] >= 0; idx += 3) {
tris.push(3);
for (let eid = 0; eid < 3; eid++) {
const edgeVerts = vtkCaseTable.getEdge(voxelTris[idx + eid]);
pId = undefined;
if (model.mergePoints) {
pId = edgeLocator.isInsertedEdge(ids[edgeVerts[0]], ids[edgeVerts[1]])?.value;
}
if (pId === undefined) {
const t = (cVal - voxelScalars[edgeVerts[0]]) / (voxelScalars[edgeVerts[1]] - voxelScalars[edgeVerts[0]]);
const x0 = voxelPts.slice(edgeVerts[0] * 3, (edgeVerts[0] + 1) * 3);
const x1 = voxelPts.slice(edgeVerts[1] * 3, (edgeVerts[1] + 1) * 3);
xyz[0] = x0[0] + t * (x1[0] - x0[0]);
xyz[1] = x0[1] + t * (x1[1] - x0[1]);
xyz[2] = x0[2] + t * (x1[2] - x0[2]);
pId = points.length / 3;
points.push(xyz[0], xyz[1], xyz[2]);
if (model.computeNormals) {
const n0 = voxelGradients.slice(edgeVerts[0] * 3, (edgeVerts[0] + 1) * 3);
const n1 = voxelGradients.slice(edgeVerts[1] * 3, (edgeVerts[1] + 1) * 3);
n[0] = n0[0] + t * (n1[0] - n0[0]);
n[1] = n0[1] + t * (n1[1] - n0[1]);
n[2] = n0[2] + t * (n1[2] - n0[2]);
normalize(n);
normals.push(n[0], n[1], n[2]);
}
if (model.mergePoints) {
edgeLocator.insertEdge(ids[edgeVerts[0]], ids[edgeVerts[1]], pId);
}
}
tris.push(pId);
}
}
};
publicAPI.requestData = (inData, outData) => {
// implement requestData
const input = inData[0];
if (!input) {
vtkErrorMacro('Invalid or missing input');
return;
}
console.time('mcubes');
// Retrieve output and volume data
const origin = input.getOrigin();
const spacing = input.getSpacing();
const dims = input.getDimensions();
const s = input.getPointData().getScalars().getData();
// Points - dynamic array
const pBuffer = [];
// Cells - dynamic array
const tBuffer = [];
// Normals
const nBuffer = [];
// Loop over all voxels, determine case and process
const extent = input.getExtent();
const slice = dims[0] * dims[1];
for (let k = 0; k < dims[2] - 1; ++k) {
for (let j = 0; j < dims[1] - 1; ++j) {
for (let i = 0; i < dims[0] - 1; ++i) {
publicAPI.produceTriangles(model.contourValue, i, j, k, extent, slice, dims, origin, spacing, s, pBuffer, tBuffer, nBuffer);
}
}
}
edgeLocator.initialize();
// Update output
const polydata = outData[0]?.initialize() || vtkPolyData.newInstance();
polydata.getPoints().setData(new Float32Array(pBuffer), 3);
polydata.getPolys().setData(new Uint32Array(tBuffer));
if (model.computeNormals) {
const nData = new Float32Array(nBuffer);
const normals = vtkDataArray.newInstance({
numberOfComponents: 3,
values: nData,
name: 'Normals'
});
polydata.getPointData().setNormals(normals);
}
outData[0] = polydata;
vtkDebugMacro('Produced output');
console.timeEnd('mcubes');
};
}
// ----------------------------------------------------------------------------
// Object factory
// ----------------------------------------------------------------------------
const DEFAULT_VALUES = {
contourValue: 0,
computeNormals: false,
mergePoints: false
};
// ----------------------------------------------------------------------------
function extend(publicAPI, model, initialValues = {}) {
Object.assign(model, DEFAULT_VALUES, initialValues);
// Make this a VTK object
macro.obj(publicAPI, model);
// Also make it an algorithm with one input and one output
macro.algo(publicAPI, model, 1, 1);
macro.setGet(publicAPI, model, ['contourValue', 'computeNormals', 'mergePoints']);
// Object specific methods
macro.algo(publicAPI, model, 1, 1);
vtkImageMarchingCubes(publicAPI, model);
}
// ----------------------------------------------------------------------------
const newInstance = macro.newInstance(extend, 'vtkImageMarchingCubes');
// ----------------------------------------------------------------------------
var vtkImageMarchingCubes$1 = {
newInstance,
extend
};
export { vtkImageMarchingCubes$1 as default, extend, newInstance };