UNPKG

@kitware/vtk.js

Version:

Visualization Toolkit for the Web

641 lines (517 loc) 24.8 kB
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 };