@kitware/vtk.js
Version:
Visualization Toolkit for the Web
360 lines (323 loc) • 13.6 kB
JavaScript
import { m as macro } from '../../macros2.js';
import { b as vtkMath } from '../Core/Math/index.js';
import vtkBoundingBox from './BoundingBox.js';
import vtkIncrementalOctreeNode from './IncrementalOctreeNode.js';
import vtkAbstractPointLocator from './AbstractPointLocator.js';
import { VtkDataTypes } from '../Core/DataArray/Constants.js';
const {
vtkErrorMacro
} = macro;
function vtkIncrementalOctreePointLocator(publicAPI, model) {
// Set our className
model.classHierarchy.push('vtkIncrementalOctreePointLocator');
function getLeafContainer(node, pnt) {
return node.isLeaf() ? node : getLeafContainer(node.getChild(node.getChildIndex(pnt)), pnt);
}
//------------------------------------------------------------------------------
publicAPI.freeSearchStructure = () => {
model.octreeRootNode = null;
model.numberOfNodes = 0;
model.locatorPoints = null;
};
//------------------------------------------------------------------------------
publicAPI.findClosestPointInLeafNode = (leafNode, point) => {
// NOTE: dist2 MUST be initiated with a very huge value below, but instead of
// model.octreeMaxDimSize * model.octreeMaxDimSize * 4.0, because the point
// under check may be outside the octree and hence the squared distance can
// be greater than the latter or other similar octree-based specific values.
let dist2 = Number.MAX_VALUE;
if (leafNode.getPointIdSet() == null) {
return [-1, dist2];
}
let numPts = 0;
let tmpDst = 0.0;
const tmpPnt = [];
let tmpIdx = -1;
let pntIdx = -1;
let idList = leafNode.getPointIdSet();
numPts = idList.length;
for (let i = 0; i < numPts; i++) {
tmpIdx = idList[i];
model.locatorPoints.getPoint(tmpIdx, tmpPnt);
tmpDst = vtkMath.distance2BetweenPoints(tmpPnt, point);
if (tmpDst < dist2) {
dist2 = tmpDst;
pntIdx = tmpIdx;
}
if (dist2 === 0.0) {
break;
}
}
idList = null;
return [pntIdx, dist2];
};
publicAPI.findClosestPointInSphere = (point, radius2, maskNode, refDist2) => {
let pointIndx = -1;
let minDist2 = Number.MAX_VALUE;
const nodesBase = [];
nodesBase.push(model.octreeRootNode);
let checkNode;
let childNode;
let distToData;
let tempDist2;
let tempPntId;
while (!nodesBase.length === 0 && minDist2 > 0.0) {
checkNode = nodesBase.top();
nodesBase.pop();
if (!checkNode.isLeaf()) {
for (let i = 0; i < 8; i++) {
childNode = checkNode.getChild(i);
// use ( radius2 + radius2 ) to skip empty nodes
distToData = childNode.getNumberOfPoints() ? childNode.getDistance2ToBoundary(point, model.octreeRootNode, 1) : radius2 + radius2;
// If a child node is not the mask node AND its distance, specifically
// the data bounding box (determined by the points inside or under) to
// the point, is less than the threshold radius (one exception is the
// point's container nodes), it is pushed to the stack as a suspect.
if (childNode !== maskNode && (distToData <= refDist2 || childNode.containsPoint(point) === 1)) {
nodesBase.push(childNode);
}
childNode = null;
}
} else {
// now that the node under check is a leaf, let's find the closest
// point therein and the minimum distance
[tempPntId, tempDist2] = publicAPI.findClosestPointInLeafNode(checkNode, point);
if (tempDist2 < minDist2) {
minDist2 = tempDist2;
pointIndx = tempPntId;
}
}
checkNode = null;
}
return [minDist2 <= radius2 ? pointIndx : -1, minDist2];
};
//------------------------------------------------------------------------------
publicAPI.initPointInsertion = (points, bounds, estNumPts = 0) => {
let i = 0;
let bbIndex = 0;
if (points == null) {
vtkErrorMacro('a valid vtkPoints object required for point insertion');
return false;
}
// destroy the existing octree, if any
publicAPI.freeSearchStructure();
model.locatorPoints = points;
// obtain the threshold squared distance
model.insertTolerance2 = model.tolerance * model.tolerance;
// Fix bounds
// (1) push out a little bit if the original volume is too flat --- a slab
// (2) pull back the x, y, and z's lower bounds a little bit such that
// points are clearly "inside" the spatial region. Point p is taken as
// "inside" range r = [r1, r2] if and only if r1 < p <= r2.
model.octreeMaxDimSize = 0.0;
const tmpBbox = [...bounds];
const dimDiff = vtkBoundingBox.getLengths(bounds);
model.octreeMaxDimSize = Math.max(...dimDiff);
if (model.buildCubicOctree) {
// make the bounding box a cube and hence descendant octants cubes too
for (i = 0; i < 3; i++) {
if (dimDiff[i] !== model.octreeMaxDimSize) {
const delta = model.octreeMaxDimSize - dimDiff[i];
tmpBbox[2 * i] -= 0.5 * delta;
tmpBbox[2 * i + 1] += 0.5 * delta;
dimDiff[i] = model.octreeMaxDimSize;
}
}
}
model.fudgeFactor = model.octreeMaxDimSize * 10e-6;
const minSideSize = model.octreeMaxDimSize * 10e-2;
for (i = 0; i < 3; i++) {
if (dimDiff[i] < minSideSize) {
// case (1) above
bbIndex = 2 * i;
const tempVal = tmpBbox[bbIndex];
tmpBbox[bbIndex] = tmpBbox[bbIndex + 1] - minSideSize;
tmpBbox[bbIndex + 1] = tempVal + minSideSize;
} else {
// case (2) above
tmpBbox[2 * i] -= model.fudgeFactor;
}
}
// init the octree with an empty leaf node
model.octreeRootNode = vtkIncrementalOctreeNode.newInstance();
++model.numberOfNodes;
// this call internally inits the middle (center) and data range, too
model.octreeRootNode.setBounds(...tmpBbox);
return true;
};
publicAPI.findClosestPointInSphereWithTolerance = (point, radius2, maskNode) => publicAPI.findClosestPointInSphere(point, radius2, maskNode, model.octreeMaxDimSize * model.octreeMaxDimSize * 4.0, radius2);
//------------------------------------------------------------------------------
publicAPI.findDuplicateFloatTypePointInVisitedLeafNode = (leafNode, point) => {
let tmpPnt;
let tmpIdx = -1;
let pntIdx = -1;
// float thePnt[3]; // TODO
// thePnt[0] = static_cast<float>(point[0]);
// thePnt[1] = static_cast<float>(point[1]);
// thePnt[2] = static_cast<float>(point[2]);
const idList = leafNode.getPointIdSet();
// float* pFloat = (static_cast<vtkFloatArray*>(model.locatorPoints.getData())).getPointer(0);
const values = model.locatorPoints.getData();
for (let i = 0; i < idList.length; i++) {
tmpIdx = idList[i];
// eslint-disable-next-line no-bitwise
tmpPnt = (tmpIdx << 1) + tmpIdx;
if (point[0] === values[tmpPnt] && point[1] === values[tmpPnt + 1] && point[2] === values[tmpPnt + 2]) {
pntIdx = tmpIdx;
break;
}
}
return pntIdx;
};
//------------------------------------------------------------------------------
publicAPI.findDuplicateDoubleTypePointInVisitedLeafNode = (leafNode, point) => {
let tmpPnt;
let tmpIdx = -1;
let pntIdx = -1;
const idList = leafNode.getPointIdSet();
const values = model.locatorPoints.getData();
for (let i = 0; i < idList.length; i++) {
tmpIdx = idList[i];
// eslint-disable-next-line no-bitwise
tmpPnt = (tmpIdx << 1) + tmpIdx;
if (point[0] === values[tmpPnt] && point[1] === values[tmpPnt + 1] && point[2] === values[tmpPnt + 2]) {
pntIdx = tmpIdx;
break;
}
}
return pntIdx;
};
//------------------------------------------------------------------------------
publicAPI.findDuplicatePointInLeafNode = (leafNode, point) => {
if (leafNode.getPointIdSet() == null) {
return -1;
}
return model.locatorPoints.getDataType() === VtkDataTypes.FLOAT ? publicAPI.findDuplicateFloatTypePointInVisitedLeafNode(leafNode, point) : publicAPI.findDuplicateDoubleTypePointInVisitedLeafNode(leafNode, point);
};
//------------------------------------------------------------------------------
publicAPI.insertPoint = (ptId, x) => {
const leafcontainer = getLeafContainer(model.octreeRootNode, x);
({
numberOfNodes: model.numberOfNodes
} = leafcontainer.insertPoint(model.locatorPoints, x, model.maxPointsPerLeaf, ptId, 1, model.numberOfNodes));
};
//------------------------------------------------------------------------------
publicAPI.insertUniquePoint = point => {
// TODO: We have a mix of let and const here.
// eslint-disable-next-line prefer-const
let {
pointIdx,
leafContainer
} = publicAPI.isInsertedPoint(point);
if (pointIdx > -1) {
return {
success: false,
idx: pointIdx
};
}
// TODO: pointIdx
let numberOfNodes;
// eslint-disable-next-line prefer-const
({
numberOfNodes,
pointIdx
} = leafContainer.insertPoint(model.locatorPoints, point, model.maxPointsPerLeaf, pointIdx, 2, model.numberOfNodes));
model.numberOfNodes = numberOfNodes;
return {
success: true,
idx: pointIdx
};
};
//------------------------------------------------------------------------------
publicAPI.insertNextPoint = x => {
const leafContainer = getLeafContainer(model.octreeRootNode, x);
const {
numberOfNodes,
pointIdx
} = leafContainer.insertPoint(model.locatorPoints, x, model.maxPointsPerLeaf, -1, 2, model.numberOfNodes);
model.numberOfNodes = numberOfNodes;
return pointIdx;
};
//------------------------------------------------------------------------------
publicAPI.isInsertedPointForZeroTolerance = x => {
// the target leaf node always exists there since the root node of the
// octree has been initialized to cover all possible points to be inserted
// and therefore we do not need to check it here
const leafContainer = getLeafContainer(model.octreeRootNode, x);
const pointIdx = publicAPI.findDuplicatePointInLeafNode(leafContainer, x);
return {
pointIdx,
leafContainer
};
};
//------------------------------------------------------------------------------
publicAPI.isInsertedPointForNonZeroTolerance = x => {
// minDist2 // min distance to ALL existing points
// elseDst2 // min distance to other nodes (inner boundaries)
let dist2Ext; // min distance to an EXTended set of nodes
let pntIdExt;
// the target leaf node always exists there since the root node of the
// octree has been initialized to cover all possible points to be inserted
// and therefore we do not need to check it here
const leafContainer = getLeafContainer(model.octreeRootNode, x);
let [pointIdx, minDist2] = publicAPI.findClosestPointInLeafNode(leafContainer, x);
if (minDist2 === 0.0) {
return {
pointIdx,
leafContainer
};
}
// As no any 'duplicate' point exists in this leaf node, we need to expand
// the search scope to capture possible closer points in other nodes.
const elseDst2 = leafContainer.getDistance2ToInnerBoundary(x, model.octreeRootNode);
if (elseDst2 < model.insertTolerance2) {
// one or multiple closer points might exist in the neighboring nodes
// TODO: dist2Ext
pntIdExt = publicAPI.findClosestPointInSphereWithTolerance(x, model.insertTolerance2, leafContainer, dist2Ext);
if (dist2Ext < minDist2) {
minDist2 = dist2Ext;
pointIdx = pntIdExt;
}
}
pointIdx = minDist2 <= model.insertTolerance2 ? pointIdx : -1;
return {
pointIdx,
leafContainer
};
};
//------------------------------------------------------------------------------
publicAPI.isInsertedPoint = (x, leafContainer) => model.insertTolerance2 === 0.0 ? publicAPI.isInsertedPointForZeroTolerance(x, leafContainer) : publicAPI.isInsertedPointForNonZeroTolerance(x, leafContainer);
}
// ----------------------------------------------------------------------------
// Object factory
// ----------------------------------------------------------------------------
function defaultValues(initialValues) {
return {
fudgeFactor: 0,
octreeMaxDimSize: 0,
buildCubicOctree: false,
maxPointsPerLeaf: 128,
insertTolerance2: 0.000001,
locatorPoints: null,
octreeRootNode: null,
numberOfNodes: 0,
...initialValues
};
}
// ----------------------------------------------------------------------------
function extend(publicAPI, model, initialValues = {}) {
vtkAbstractPointLocator.extend(publicAPI, model, defaultValues(initialValues));
// Make this a VTK object
macro.obj(publicAPI, model);
macro.setGet(publicAPI, model, ['fudgeFactor', 'octreeMaxDimSize', 'buildCubicOctree', 'maxPointsPerLeaf', 'insertTolerance2', 'locatorPoints', 'octreeRootNode', 'numberOfNodes']);
// Object specific methods
vtkIncrementalOctreePointLocator(publicAPI, model);
}
// ----------------------------------------------------------------------------
const newInstance = macro.newInstance(extend, 'vtkIncrementalOctreePointLocator');
// ----------------------------------------------------------------------------
var vtkIncrementalOctreePointLocator$1 = {
newInstance,
extend
};
export { vtkIncrementalOctreePointLocator$1 as default, extend, newInstance };