@kitware/vtk.js
Version:
Visualization Toolkit for the Web
641 lines (517 loc) • 24.8 kB
JavaScript
import macro from '../../macros.js';
import { e as distance2BetweenPoints } from '../Core/Math/index.js';
var vtkErrorMacro = macro.vtkErrorMacro;
var OCTREENODE_INSERTPOINT = [function (points, pointIdx, coords) {
return pointIdx;
}, function (points, pointIdx, coords) {
points.setTuple(pointIdx, coords);
return pointIdx;
}, function (points, pointIdx, coords) {
return points.insertNextTuple(coords);
}]; // Given the index (0 ~ 7) of a child node, the spatial bounding axis (0 ~ 2
// for x, y, and z), and the value (0 ~ 1 for min and max) to access, this LUT
// allows for rapid assignment of its spatial bounding box --- MinBounds[3]
// and MaxBounds[3], with each specific value or entry of this LUT pointing to
// MinBounds[3] for 0, center point for 1, or MaxBounds[3] for 2.
var OCTREE_CHILD_BOUNDS_LUT = [[[0, 1], [0, 1], [0, 1]], [[1, 2], [0, 1], [0, 1]], [[0, 1], [1, 2], [0, 1]], [[1, 2], [1, 2], [0, 1]], [[0, 1], [0, 1], [1, 2]], [[1, 2], [0, 1], [1, 2]], [[0, 1], [1, 2], [1, 2]], [[1, 2], [1, 2], [1, 2]]];
function vtkIncrementalOctreeNode(publicAPI, model) {
// Set our className
model.classHierarchy.push('vtkIncrementalOctreeNode'); //------------------------------------------------------------------------------
publicAPI.createPointIdSet = function (initSize, growSize) {
if (model.pointIdSet == null) {
model.pointIdSet = []; // TODO: use initSize and growSize.
// model.pointIdSet.allocate(initSize, growSize);
}
}; //------------------------------------------------------------------------------
publicAPI.setBounds = function (x1, x2, y1, y2, z1, z2) {
if (model.minBounds == null) model.minBounds = [];
if (model.maxBounds == null) model.maxBounds = [];
if (model.minDataBounds == null) model.minDataBounds = [];
if (model.maxDataBounds == null) model.maxDataBounds = [];
model.minBounds[0] = x1;
model.maxBounds[0] = x2;
model.minBounds[1] = y1;
model.maxBounds[1] = y2;
model.minBounds[2] = z1;
model.maxBounds[2] = z2;
model.minDataBounds[0] = x2;
model.maxDataBounds[0] = x1;
model.minDataBounds[1] = y2;
model.maxDataBounds[1] = y1;
model.minDataBounds[2] = z2;
model.maxDataBounds[2] = z1;
}; //------------------------------------------------------------------------------
publicAPI.getBounds = function (bounds) {
bounds[0] = model.minBounds[0];
bounds[1] = model.maxBounds[0];
bounds[2] = model.minBounds[1];
bounds[3] = model.maxBounds[1];
bounds[4] = model.minBounds[2];
bounds[5] = model.maxBounds[2];
};
publicAPI.getChildIndex = function (point) {
return Number(point[0] > model.children[0].getMaxBoundsByReference()[0]) + ( // eslint-disable-next-line no-bitwise
Number(point[1] > model.children[0].getMaxBoundsByReference()[1]) << 1) + ( // eslint-disable-next-line no-bitwise
Number(point[2] > model.children[0].getMaxBoundsByReference()[2]) << 2);
};
publicAPI.containsPoint = function (pnt) {
return model.minBounds[0] < pnt[0] && pnt[0] <= model.maxBounds[0] && model.minBounds[1] < pnt[1] && pnt[1] <= model.maxBounds[1] && model.minBounds[2] < pnt[2] && pnt[2] <= model.maxBounds[2] ? 1 : 0;
};
publicAPI.containsPointByData = function (pnt) {
return model.minDataBounds[0] <= pnt[0] && pnt[0] <= model.maxDataBounds[0] && model.minDataBounds[1] <= pnt[1] && pnt[1] <= model.maxDataBounds[1] && model.minDataBounds[2] <= pnt[2] && pnt[2] <= model.maxDataBounds[2] ? 1 : 0;
}; //------------------------------------------------------------------------------
publicAPI.updateCounterAndDataBounds = function (point, nHits, updateData) {
model.numberOfPoints += nHits;
if (!updateData) return false;
var updated = false;
if (point[0] < model.minDataBounds[0]) {
updated = true;
model.minDataBounds[0] = point[0];
}
if (point[0] > model.maxDataBounds[0]) {
updated = true;
model.maxDataBounds[0] = point[0];
}
if (point[1] < model.minDataBounds[1]) {
updated = true;
model.minDataBounds[1] = point[1];
}
if (point[1] > model.maxDataBounds[1]) {
updated = true;
model.maxDataBounds[1] = point[1];
}
if (point[2] < model.minDataBounds[2]) {
updated = true;
model.minDataBounds[2] = point[2];
}
if (point[2] > model.maxDataBounds[2]) {
updated = true;
model.maxDataBounds[2] = point[2];
}
return updated;
}; //------------------------------------------------------------------------------
publicAPI.updateCounterAndDataBoundsRecursively = function (point, nHits, updateData, endNode) {
var updated = publicAPI.updateCounterAndDataBounds(point, nHits, updateData);
return model.parent === endNode ? updated : model.parent.updateCounterAndDataBoundsRecursively(point, nHits, updated, endNode);
}; //------------------------------------------------------------------------------
publicAPI.containsDuplicatePointsOnly = function (point) {
return model.minDataBounds[0] === point[0] && point[0] === model.maxDataBounds[0] && model.minDataBounds[1] === point[1] && point[1] === model.maxDataBounds[1] && model.minDataBounds[2] === point[2] && point[2] === model.maxDataBounds[2];
}; //------------------------------------------------------------------------------
publicAPI.isLeaf = function () {
return model.children == null;
}; //------------------------------------------------------------------------------
publicAPI.getChild = function (i) {
return model.children[i];
}; //------------------------------------------------------------------------------
/* eslint-disable no-use-before-define */
publicAPI.separateExactlyDuplicatePointsFromNewInsertion = function (points, pntIds, newPnt, pntIdx, maxPts, ptMode) {
// the number of points already maintained in this leaf node
// >= maxPts AND all of them are exactly duplicate with one another
// BUT the new point is not a duplicate of them any more
var pointIdx = pntIdx;
var i;
var dupPnt = [0.0, 0.0, 0.0];
var octMin = [0.0, 0.0, 0.0];
var octMid = [0.0, 0.0, 0.0];
var octMax = [0.0, 0.0, 0.0];
var boxPtr = [null, null, null];
var ocNode = null;
var duplic = publicAPI;
var single = publicAPI; // the coordinate of the duplicate points: note pntIds == model.pointIdSet
points.getPoint(pntIds[0], dupPnt);
while (duplic === single) {
// as long as separation has not been achieved
// update the current (in recursion) node and access the bounding box info
ocNode = duplic;
octMid[0] = (ocNode.minBounds[0] + ocNode.maxBounds[0]) * 0.5;
octMid[1] = (ocNode.minBounds[1] + ocNode.maxBounds[1]) * 0.5;
octMid[2] = (ocNode.minBounds[2] + ocNode.maxBounds[2]) * 0.5;
boxPtr[0] = ocNode.minBounds;
boxPtr[1] = octMid;
boxPtr[2] = ocNode.maxBounds; // create eight child nodes
// FIXME: May be too slow to use vtk newInstance()
ocNode.children = [newInstance(), newInstance(), newInstance(), newInstance(), newInstance(), newInstance(), newInstance(), newInstance()];
for (i = 0; i < 8; i++) {
// x-bound: axis 0
octMin[0] = boxPtr[OCTREE_CHILD_BOUNDS_LUT[i][0][0]][0];
octMax[0] = boxPtr[OCTREE_CHILD_BOUNDS_LUT[i][0][1]][0]; // y-bound: axis 1
octMin[1] = boxPtr[OCTREE_CHILD_BOUNDS_LUT[i][1][0]][1];
octMax[1] = boxPtr[OCTREE_CHILD_BOUNDS_LUT[i][1][1]][1]; // z-bound: axis 2
octMin[2] = boxPtr[OCTREE_CHILD_BOUNDS_LUT[i][2][0]][2];
octMax[2] = boxPtr[OCTREE_CHILD_BOUNDS_LUT[i][2][1]][2];
ocNode.children[i] = newInstance();
ocNode.children[i].setParent(ocNode);
ocNode.children[i].setBounds(octMin[0], octMax[0], octMin[1], octMax[1], octMin[2], octMax[2]);
} // determine the leaf node of the duplicate points & that of the new point
duplic = ocNode.children[ocNode.getChildIndex(dupPnt)];
single = ocNode.children[ocNode.getChildIndex(newPnt)];
} // Now the duplicate points have been separated from the new point //
// create a vtkIdList object for the new point
// update the counter and the data bounding box until the root node
// (including the root node)
pointIdx = OCTREENODE_INSERTPOINT[ptMode](points, pointIdx, newPnt); // eslint-disable-next-line no-bitwise
single.createPointIdSet(maxPts >> 2, maxPts >> 1);
single.getPointIdSet().push(pointIdx);
single.updateCounterAndDataBoundsRecursively(newPnt, 1, 1, null); // We just need to reference pntIds while un-registering it from 'this'.
// This avoids deep-copying point ids from pntIds to duplic's PointIdSet.
// update the counter and the data bounding box, but until 'this' node
// (excluding 'this' node)
duplic.setPointIdSet(pntIds);
duplic.updateCounterAndDataBoundsRecursively(dupPnt, pntIds.length, 1, publicAPI);
return pointIdx;
};
/* eslint-enable no-use-before-define */
//------------------------------------------------------------------------------
publicAPI.createChildNodes = function (points, pntIds, newPnt, pntIdx, maxPts, ptMode, numberOfNodes) {
// There are two scenarios for which this function is invoked.
//
// (1) the number of points already maintained in this leaf node
// == maxPts AND not all of them are exactly duplicate
// AND the new point is not a duplicate of them all
// (2) the number of points already maintained in this leaf node
// >= maxPts AND all of them are exactly duplicate with one another
// BUT the new point is not a duplicate of them any more
// address case (2) first if necessary
var nbNodes = numberOfNodes;
var pointIdx = pntIdx;
var sample = [];
points.getPoint(pntIds[0], sample);
if (publicAPI.containsDuplicatePointsOnly(sample)) {
pointIdx = publicAPI.separateExactlyDuplicatePointsFromNewInsertion(points, pntIds, newPnt, pointIdx, maxPts, ptMode);
return {
success: false,
nbNodes: nbNodes,
pointIdx: pointIdx
};
} // then address case (1) below
var i;
var target;
var dvidId = -1; // index of the sub-dividing octant, if any
var fullId = -1; // index of the full octant, if any
var numIds = [0, 0, 0, 0, 0, 0, 0, 0];
var octMin = [];
var octMax = [];
var tempPt = [];
var tempId;
var octMid = [(model.minBounds[0] + model.maxBounds[0]) * 0.5, (model.minBounds[1] + model.maxBounds[1]) * 0.5, (model.minBounds[2] + model.maxBounds[2]) * 0.5];
var boxPtr = [model.minBounds, octMid, model.maxBounds]; // create eight child nodes
model.children = [];
for (i = 0; i < 8; i++) {
// x-bound: axis 0
octMin[0] = boxPtr[OCTREE_CHILD_BOUNDS_LUT[i][0][0]][0];
octMax[0] = boxPtr[OCTREE_CHILD_BOUNDS_LUT[i][0][1]][0]; // y-bound: axis 1
octMin[1] = boxPtr[OCTREE_CHILD_BOUNDS_LUT[i][1][0]][1];
octMax[1] = boxPtr[OCTREE_CHILD_BOUNDS_LUT[i][1][1]][1]; // z-bound: axis 2
octMin[2] = boxPtr[OCTREE_CHILD_BOUNDS_LUT[i][2][0]][2];
octMax[2] = boxPtr[OCTREE_CHILD_BOUNDS_LUT[i][2][1]][2]; // This call internally sets the cener and default data bounding box, too.
// eslint-disable-next-line no-use-before-define
model.children[i] = newInstance(); // model.children[i].iD = nbNodes++;
model.children[i].setParent(publicAPI);
model.children[i].setBounds(octMin[0], octMax[0], octMin[1], octMax[1], octMin[2], octMax[2]); // allocate a list of point-indices (size = 2^n) for index registration
// eslint-disable-next-line no-bitwise
model.children[i].createPointIdSet(maxPts >> 2, maxPts >> 1);
}
boxPtr[0] = null;
boxPtr[1] = null;
boxPtr[2] = null; // distribute the available point-indices to the eight child nodes
for (i = 0; i < maxPts; i++) {
tempId = pntIds[i];
points.getPoint(tempId, tempPt);
target = publicAPI.getChildIndex(tempPt);
model.children[target].getPointIdSet().push(tempId);
model.children[target].updateCounterAndDataBounds(tempPt);
numIds[target]++;
} // locate the full child, just if any
for (i = 0; i < 8; i++) {
if (numIds[i] === maxPts) {
fullId = i;
break;
}
}
target = publicAPI.getChildIndex(newPnt);
if (fullId === target) {
// The fact is that we are going to insert the new point to an already
// full octant (child node). Thus we need to further divide this child
// to avoid the overflow problem.
var _model$children$targe = model.children[target].createChildNodes(points, pntIds, newPnt, pointIdx, maxPts, ptMode, nbNodes);
nbNodes = _model$children$targe.numberOfNodes;
pointIdx = _model$children$targe.pointIdx;
dvidId = fullId;
} else {
// the initial division is a success
pointIdx = OCTREENODE_INSERTPOINT[ptMode](points, pointIdx, newPnt);
model.children[target].getPointIdSet().push(pointIdx);
model.children[target].updateCounterAndDataBoundsRecursively(newPnt, 1, 1, null); // NOTE: The counter below might reach the threshold, though we delay the
// sub-division of this child node until the next point insertion occurs.
numIds[target]++;
} // Now it is time to reclaim those un-used vtkIdList objects, of which each
// either is empty or still needs to be deleted due to further division of
// the child node. This post-deallocation of the un-used vtkIdList objects
// (of some child nodes) is based on the assumption that retrieving the
// 'maxPts' points from vtkPoints and the associated 'maxPts' point-indices
// from vtkIdList is more expensive than reclaiming at most 8 vtkIdList
// objects at hand.
for (i = 0; i < 8; i++) {
if (numIds[i] === 0 || i === dvidId) {
model.children[i].getPointIdSet().length = 0;
}
} // notify vtkIncrementalOctreeNode::InsertPoint() to destroy pntIds
return {
success: true,
numberOfNodes: nbNodes,
pointIdx: pointIdx
};
}; //------------------------------------------------------------------------------
publicAPI.insertPoint = function (points, newPnt, maxPts, pntId, ptMode, numberOfNodes) {
var nbNodes = 0;
var pointIdx = pntId;
if (model.pointIdSet) {
// there has been at least one point index
if (model.pointIdSet.length < maxPts || publicAPI.containsDuplicatePointsOnly(newPnt)) {
// this leaf node is not full or
// this leaf node is full, but of all exactly duplicate points
// and the point under check is another duplicate of these points
pointIdx = OCTREENODE_INSERTPOINT[ptMode](points, pointIdx, newPnt);
model.pointIdSet.push(pointIdx);
publicAPI.updateCounterAndDataBoundsRecursively(newPnt, 1, 1, null);
} else {
// overflow: divide this node and delete the list of point-indices.
// Note that the number of exactly duplicate points might be greater
// than or equal to maxPts.
var _publicAPI$createChil = publicAPI.createChildNodes(points, model.pointIdSet, newPnt, pointIdx, maxPts, ptMode, numberOfNodes);
nbNodes = _publicAPI$createChil.numberOfNodes;
pointIdx = _publicAPI$createChil.pointIdx;
model.pointIdSet = null;
}
} else {
// There has been no any point index registered in this leaf node
pointIdx = OCTREENODE_INSERTPOINT[ptMode](points, pointIdx, newPnt);
model.pointIdSet = [];
model.pointIdSet.push(pointIdx);
publicAPI.updateCounterAndDataBoundsRecursively(newPnt, 1, 1, null);
}
return {
numberOfNodes: numberOfNodes + nbNodes,
pointIdx: pointIdx
};
}; //------------------------------------------------------------------------------
publicAPI.getDistance2ToBoundary = function (point, closest, innerOnly, rootNode, checkData) {
// It is mandatory that GetMinDataBounds() and GetMaxDataBounds() be used.
// Direct access to MinDataBounds and MaxDataBounds might incur problems.
var thisMin = null;
var thisMax = null;
var rootMin = null;
var rootMax = null; // TODO: Check
// let minDist = VTK_DOUBLE_MAX;
var minDist = Number.MAX_VALUE; // minimum distance to the boundaries
if (checkData) {
thisMin = publicAPI.getMinDataBounds();
thisMax = publicAPI.getMaxDataBounds();
rootMin = rootNode.getMinDataBounds();
rootMax = rootNode.getMaxDataBounds();
} else {
thisMin = model.minBounds;
thisMax = model.maxBounds;
rootMin = rootNode.getMinBounds();
rootMax = rootNode.getMaxBounds();
}
var minFace = 0; // index of the face with min distance to the point
var beXless = Number(point[0] < thisMin[0]);
var beXmore = Number(point[0] > thisMax[0]);
var beYless = Number(point[1] < thisMin[1]);
var beYmore = Number(point[1] > thisMax[1]);
var beZless = Number(point[2] < thisMin[2]);
var beZmore = Number(point[2] > thisMax[2]);
var withinX = Number(!beXless && !beXmore);
var withinY = Number(!beYless && !beYmore);
var withinZ = Number(!beZless && !beZmore); // eslint-disable-next-line no-bitwise
var xyzFlag = (withinZ << 2) + (withinY << 1) + withinX;
switch (xyzFlag) {
case 0:
{
// withinZ = 0; withinY = 0; withinX = 0
// closest to a corner
closest[0] = beXless ? thisMin[0] : thisMax[0];
closest[1] = beYless ? thisMin[1] : thisMax[1];
closest[2] = beZless ? thisMin[2] : thisMax[2];
minDist = distance2BetweenPoints(point, closest);
break;
}
case 1:
{
// withinZ = 0; withinY = 0; withinX = 1
// closest to an x-aligned edge
closest[0] = point[0];
closest[1] = beYless ? thisMin[1] : thisMax[1];
closest[2] = beZless ? thisMin[2] : thisMax[2];
minDist = distance2BetweenPoints(point, closest);
break;
}
case 2:
{
// withinZ = 0; withinY = 1; withinX = 0
// closest to a y-aligned edge
closest[0] = beXless ? thisMin[0] : thisMax[0];
closest[1] = point[1];
closest[2] = beZless ? thisMin[2] : thisMax[2];
minDist = distance2BetweenPoints(point, closest);
break;
}
case 3:
{
// withinZ = 0; withinY = 1; withinX = 1
// closest to a z-face
if (beZless) {
minDist = thisMin[2] - point[2];
closest[2] = thisMin[2];
} else {
minDist = point[2] - thisMax[2];
closest[2] = thisMax[2];
}
minDist *= minDist;
closest[0] = point[0];
closest[1] = point[1];
break;
}
case 4:
{
// withinZ = 1; withinY = 0; withinX = 0
// cloest to a z-aligned edge
closest[0] = beXless ? thisMin[0] : thisMax[0];
closest[1] = beYless ? thisMin[1] : thisMax[1];
closest[2] = point[2];
minDist = distance2BetweenPoints(point, closest);
break;
}
case 5:
{
// withinZ = 1; withinY = 0; withinX = 1
// closest to a y-face
if (beYless) {
minDist = thisMin[1] - point[1];
closest[1] = thisMin[1];
} else {
minDist = point[1] - thisMax[1];
closest[1] = thisMax[1];
}
minDist *= minDist;
closest[0] = point[0];
closest[2] = point[2];
break;
}
case 6:
{
// withinZ = 1; withinY = 1; withinX = 0
// closest to an x-face
if (beXless) {
minDist = thisMin[0] - point[0];
closest[0] = thisMin[0];
} else {
minDist = point[0] - thisMax[0];
closest[0] = thisMax[0];
}
minDist *= minDist;
closest[1] = point[1];
closest[2] = point[2];
break;
}
case 7:
{
// withinZ = 1; withinY = 1; withinZ = 1
// point is inside the box
if (innerOnly) {
// check only inner boundaries
var faceDst;
faceDst = point[0] - thisMin[0]; // x-min face
if (thisMin[0] !== rootMin[0] && faceDst < minDist) {
minFace = 0;
minDist = faceDst;
}
faceDst = thisMax[0] - point[0]; // x-max face
if (thisMax[0] !== rootMax[0] && faceDst < minDist) {
minFace = 1;
minDist = faceDst;
}
faceDst = point[1] - thisMin[1]; // y-min face
if (thisMin[1] !== rootMin[1] && faceDst < minDist) {
minFace = 2;
minDist = faceDst;
}
faceDst = thisMax[1] - point[1]; // y-max face
if (thisMax[1] !== rootMax[1] && faceDst < minDist) {
minFace = 3;
minDist = faceDst;
}
faceDst = point[2] - thisMin[2]; // z-min face
if (thisMin[2] !== rootMin[2] && faceDst < minDist) {
minFace = 4;
minDist = faceDst;
}
faceDst = thisMax[2] - point[2]; // z-max face
if (thisMax[2] !== rootMax[2] && faceDst < minDist) {
minFace = 5;
minDist = faceDst;
}
} else {
// check all boundaries
var tmpDist = [point[0] - thisMin[0], thisMax[0] - point[0], point[1] - thisMin[1], thisMax[1] - point[1], point[2] - thisMin[2], thisMax[2] - point[2]];
for (var i = 0; i < 6; i++) {
if (tmpDist[i] < minDist) {
minFace = i;
minDist = tmpDist[i];
}
}
} // no square operation if no any inner boundary
if (minDist !== Number.MAX_VALUE) {
minDist *= minDist;
}
closest[0] = point[0];
closest[1] = point[1];
closest[2] = point[2]; // minFace: the quad with the min distance to the point
// 0: x-min face ===> xyzIndx = 0: x and minFace & 1 = 0: thisMin
// 1: x-max face ===> xyzIndx = 0: x and minFace & 1 = 1: thisMax
// 2: y-min face ===> xyzIndx = 1: y and minFace & 1 = 0: thisMin
// 3: y-max face ===> xyzIndx = 1: y and minFace & 1 = 1: thisMax
// 4: z-min face ===> xyzIndx = 2: z and minFace & 1 = 0: thisMin
// 5: z-max face ===> xyzIndx = 2: z and minFace & 1 = 1: thisMax
var pMinMax = [thisMin, thisMax]; // eslint-disable-next-line no-bitwise
var xyzIndx = minFace >> 1; // eslint-disable-next-line no-bitwise
closest[xyzIndx] = pMinMax[minFace & 1][xyzIndx];
break;
}
default:
vtkErrorMacro('unexpected case in getDistance2ToBoundary');
}
return minDist;
}; //------------------------------------------------------------------------------
publicAPI.getDistance2ToInnerBoundary = function (point, rootNode) {
var dummy = [];
return publicAPI.getDistance2ToBoundary(point, dummy, 0, rootNode, 0);
};
} // ----------------------------------------------------------------------------
// Object factory
// ----------------------------------------------------------------------------
var DEFAULT_VALUES = {
pointIdSet: null,
minBounds: null,
maxBounds: null,
minDataBounds: null,
maxDataBounds: null,
parent: null,
children: null
}; // ----------------------------------------------------------------------------
function extend(publicAPI, model) {
var initialValues = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
Object.assign(model, DEFAULT_VALUES, initialValues); // Make this a VTK object
macro.obj(publicAPI, model);
macro.setGetArray(publicAPI, model, ['minBounds', 'maxBounds', 'minDataBounds', 'maxDataBounds'], 6);
macro.get(publicAPI, model, ['pointIdSet', 'numberOfPoints']); // TODO: No get?
macro.set(publicAPI, model, ['parent']); // Object specific methods
vtkIncrementalOctreeNode(publicAPI, model);
} // ----------------------------------------------------------------------------
var newInstance = macro.newInstance(extend, 'vtkIncrementalOctreeNode'); // ----------------------------------------------------------------------------
var vtkIncrementalOctreeNode$1 = {
newInstance: newInstance,
extend: extend
};
export { vtkIncrementalOctreeNode$1 as default, extend, newInstance };