UNPKG

@acransac/vtk.js

Version:

Visualization Toolkit for the Web

546 lines (469 loc) 13.7 kB
import * as vtkMath from 'vtk.js/Sources/Common/Core/Math'; import macro from 'vtk.js/Sources/macro'; import vtkPlane from 'vtk.js/Sources/Common/DataModel/Plane'; const INIT_BOUNDS = [ Number.MAX_VALUE, -Number.MAX_VALUE, // X Number.MAX_VALUE, -Number.MAX_VALUE, // Y Number.MAX_VALUE, -Number.MAX_VALUE, // Z ]; // ---------------------------------------------------------------------------- // Global methods // ---------------------------------------------------------------------------- function isValid(bounds) { return ( bounds[0] <= bounds[1] && bounds[2] <= bounds[3] && bounds[4] <= bounds[5] ); } function getCenter(bounds) { return [ 0.5 * (bounds[0] + bounds[1]), 0.5 * (bounds[2] + bounds[3]), 0.5 * (bounds[4] + bounds[5]), ]; } function getLength(bounds, index) { return bounds[index * 2 + 1] - bounds[index * 2]; } function getLengths(bounds) { return [getLength(bounds, 0), getLength(bounds, 1), getLength(bounds, 2)]; } function getXRange(bounds) { return bounds.slice(0, 2); } function getYRange(bounds) { return bounds.slice(2, 4); } function getZRange(bounds) { return bounds.slice(4, 6); } function getMaxLength(bounds) { const l = getLengths(bounds); if (l[0] > l[1]) { if (l[0] > l[2]) { return l[0]; } return l[2]; } if (l[1] > l[2]) { return l[1]; } return l[2]; } function getDiagonalLength(bounds) { if (isValid(bounds)) { const l = getLengths(bounds); return Math.sqrt(l[0] * l[0] + l[1] * l[1] + l[2] * l[2]); } return null; } function oppositeSign(a, b) { return (a <= 0 && b >= 0) || (a >= 0 && b <= 0); } function getCorners(bounds, corners) { let count = 0; for (let ix = 0; ix < 2; ix++) { for (let iy = 2; iy < 4; iy++) { for (let iz = 4; iz < 6; iz++) { corners[count] = [bounds[ix], bounds[iy], bounds[iz]]; count++; } } } } // Computes the two corners with minimal and miximal coordinates function computeCornerPoints(point1, point2, bounds) { point1[0] = bounds[0]; point1[1] = bounds[2]; point1[2] = bounds[4]; point2[0] = bounds[1]; point2[1] = bounds[3]; point2[2] = bounds[5]; } function computeScale3(bounds, scale3 = []) { const center = getCenter(bounds); scale3[0] = bounds[1] - center[0]; scale3[1] = bounds[3] - center[1]; scale3[2] = bounds[5] - center[2]; return scale3; } /** * Compute local bounds. * Not as fast as vtkPoints.getBounds() if u, v, w form a natural basis. * @param {vtkPoints} points * @param {array} u first vector * @param {array} v second vector * @param {array} w third vector */ function computeLocalBounds(points, u, v, w) { const bounds = [].concat(INIT_BOUNDS); const pointsData = points.getData(); for (let i = 0; i < pointsData.length; i += 3) { const point = [pointsData[i], pointsData[i + 1], pointsData[i + 2]]; const du = vtkMath.dot(point, u); bounds[0] = Math.min(du, bounds[0]); bounds[1] = Math.max(du, bounds[1]); const dv = vtkMath.dot(point, v); bounds[2] = Math.min(dv, bounds[2]); bounds[3] = Math.max(dv, bounds[3]); const dw = vtkMath.dot(point, w); bounds[4] = Math.min(dw, bounds[4]); bounds[5] = Math.max(dw, bounds[5]); } return bounds; } // ---------------------------------------------------------------------------- // Static API // ---------------------------------------------------------------------------- export const STATIC = { isValid, getCenter, getLength, getLengths, getMaxLength, getDiagonalLength, getXRange, getYRange, getZRange, getCorners, computeCornerPoints, computeLocalBounds, computeScale3, INIT_BOUNDS, }; // ---------------------------------------------------------------------------- // vtkBoundingBox methods // ---------------------------------------------------------------------------- function vtkBoundingBox(publicAPI, model) { // Set our className model.classHierarchy.push('vtkBoundingBox'); publicAPI.clone = () => { const bounds = [].concat(model.bounds); /* eslint-disable no-use-before-define */ return newInstance({ bounds }); /* eslint-enable no-use-before-define */ }; publicAPI.equals = (other) => { const a = model.bounds; const b = other.getBounds(); return ( a[0] === b[0] && a[1] === b[1] && a[2] === b[2] && a[3] === b[3] && a[4] === b[4] && a[5] === b[5] ); }; publicAPI.setMinPoint = (x, y, z) => { const [xMin, xMax, yMin, yMax, zMin, zMax] = model.bounds; model.bounds = [ x, x > xMax ? x : xMax, y, y > yMax ? y : yMax, z, z > zMax ? z : zMax, ]; return xMin !== x || yMin !== y || zMin !== z; }; publicAPI.setMaxPoint = (x, y, z) => { const [xMin, xMax, yMin, yMax, zMin, zMax] = model.bounds; model.bounds = [ x < xMin ? x : xMin, x, y < yMin ? y : yMin, y, z < zMin ? z : zMin, z, ]; return xMax !== x || yMax !== y || zMax !== z; }; publicAPI.addPoint = (...xyz) => { model.bounds = model.bounds.map((value, index) => { if (index % 2 === 0) { const idx = index / 2; return value < xyz[idx] ? value : xyz[idx]; } const idx = (index - 1) / 2; return value > xyz[idx] ? value : xyz[idx]; }); }; publicAPI.addBounds = (xMin, xMax, yMin, yMax, zMin, zMax) => { const [_xMin, _xMax, _yMin, _yMax, _zMin, _zMax] = model.bounds; if (zMax === undefined) { model.bounds = [ Math.min(xMin[0], _xMin), Math.max(xMin[1], _xMax), Math.min(xMin[2], _yMin), Math.max(xMin[3], _yMax), Math.min(xMin[4], _zMin), Math.max(xMin[5], _zMax), ]; } else { model.bounds = [ Math.min(xMin, _xMin), Math.max(xMax, _xMax), Math.min(yMin, _yMin), Math.max(yMax, _yMax), Math.min(zMin, _zMin), Math.max(zMax, _zMax), ]; } }; publicAPI.addBox = (other) => { publicAPI.addBounds(other.getBounds()); }; publicAPI.isValid = () => isValid(model.bounds); publicAPI.intersect = (bbox) => { if (!(publicAPI.isValid() && bbox.isValid())) { return false; } const newBounds = [0, 0, 0, 0, 0, 0]; const bBounds = bbox.getBounds(); let intersects; for (let i = 0; i < 3; i++) { intersects = false; if ( bBounds[i * 2] >= model.bounds[i * 2] && bBounds[i * 2] <= model.bounds[i * 2 + 1] ) { intersects = true; newBounds[i * 2] = bBounds[i * 2]; } else if ( model.bounds[i * 2] >= bBounds[i * 2] && model.bounds[i * 2] <= bBounds[i * 2 + 1] ) { intersects = true; newBounds[i * 2] = model.bounds[i * 2]; } if ( bBounds[i * 2 + 1] >= model.bounds[i * 2] && bBounds[i * 2 + 1] <= model.bounds[i * 2 + 1] ) { intersects = true; newBounds[i * 2 + 1] = bbox.MaxPnt[i]; } else if ( model.bounds[i * 2 + 1] >= bbox.MinPnt[i * 2] && model.bounds[i * 2 + 1] <= bbox.MaxPnt[i * 2 + 1] ) { intersects = true; newBounds[i * 2 + 1] = model.bounds[i * 2 + 1]; } if (!intersects) { return false; } } // OK they did intersect - set the box to be the result model.bounds = newBounds; return true; }; publicAPI.intersects = (bbox) => { if (!(publicAPI.isValid() && bbox.isValid())) { return false; } const bBounds = bbox.getBounds(); /* eslint-disable no-continue */ for (let i = 0; i < 3; i++) { if ( bBounds[i * 2] >= model.bounds[i * 2] && bBounds[i * 2] <= model.bounds[i * 2 + 1] ) { continue; } else if ( model.bounds[i * 2] >= bBounds[i * 2] && model.bounds[i * 2] <= bBounds[i * 2 + 1] ) { continue; } if ( bBounds[i * 2 + 1] >= model.bounds[i * 2] && bBounds[i * 2 + 1] <= model.bounds[i * 2 + 1] ) { continue; } else if ( model.bounds[i * 2 + 1] >= bbox.MinPnt[i * 2] && model.bounds[i * 2 + 1] <= bbox.MaxPnt[i * 2 + 1] ) { continue; } return false; } /* eslint-enable no-continue */ return true; }; /** * Returns true if plane intersects bounding box. * If so, the box is cut by the plane * @param {array} origin * @param {array} normal */ publicAPI.intersectPlane = (origin, normal) => { // Index[0..2] represents the order of traversing the corners of a cube // in (x,y,z), (y,x,z) and (z,x,y) ordering, respectively const index = [ [0, 1, 2, 3, 4, 5, 6, 7], [0, 1, 4, 5, 2, 3, 6, 7], [0, 2, 4, 6, 1, 3, 5, 7], ]; // stores the signed distance to a plane const d = [0, 0, 0, 0, 0, 0, 0, 0]; let idx = 0; for (let ix = 0; ix < 2; ix++) { for (let iy = 2; iy < 4; iy++) { for (let iz = 4; iz < 6; iz++) { const x = [model.bounds[ix], model.bounds[iy], model.bounds[iz]]; d[idx++] = vtkPlane.evaluate(normal, origin, x); } } } let dir = 2; while (dir--) { // in each direction, we test if the vertices of two orthogonal faces // are on either side of the plane if ( oppositeSign(d[index[dir][0]], d[index[dir][4]]) && oppositeSign(d[index[dir][1]], d[index[dir][5]]) && oppositeSign(d[index[dir][2]], d[index[dir][6]]) && oppositeSign(d[index[dir][3]], d[index[dir][7]]) ) { break; } } if (dir < 0) { return false; } const sign = Math.sign(normal[dir]); const size = Math.abs( (model.bounds[dir * 2 + 1] - model.bounds[dir * 2]) * normal[dir] ); let t = sign > 0 ? 1 : 0; /* eslint-disable no-continue */ for (let i = 0; i < 4; i++) { if (size === 0) { continue; // shouldn't happen } const ti = Math.abs(d[index[dir][i]]) / size; if (sign > 0 && ti < t) { t = ti; } if (sign < 0 && ti > t) { t = ti; } } /* eslint-enable no-continue */ const bound = (1.0 - t) * model.bounds[dir * 2] + t * model.bounds[dir * 2 + 1]; if (sign > 0) { model.bounds[dir * 2] = bound; } else { model.bounds[dir * 2 + 1] = bound; } return true; }; publicAPI.containsPoint = (x, y, z) => { if (x < model.bounds[0] || x > model.bounds[1]) { return false; } if (y < model.bounds[2] || y > model.bounds[3]) { return false; } if (z < model.bounds[4] || z > model.bounds[5]) { return false; } return true; }; publicAPI.getMinPoint = () => [ model.bounds[0], model.bounds[2], model.bounds[4], ]; publicAPI.getMaxPoint = () => [ model.bounds[1], model.bounds[3], model.bounds[5], ]; publicAPI.getBound = (index) => model.bound[index]; publicAPI.contains = (bbox) => { // if either box is not valid or they don't intersect if (!publicAPI.intersects(bbox)) { return false; } if (!publicAPI.containsPoint(...bbox.getMinPoint())) { return false; } if (!publicAPI.containsPoint(...bbox.getMaxPoint())) { return 0; } return true; }; publicAPI.getCenter = () => getCenter(model.bounds); publicAPI.getLength = (index) => getLength(model.bounds, index); publicAPI.getLengths = () => getLengths(model.bounds); publicAPI.getMaxLength = () => getMaxLength(model.bounds); publicAPI.getDiagonalLength = () => getDiagonalLength(model.bounds); publicAPI.reset = () => publicAPI.setBounds([].concat(INIT_BOUNDS)); publicAPI.inflate = (delta) => { model.bounds = model.bounds.map((value, index) => { if (index % 2 === 0) { return value - delta; } return value + delta; }); }; publicAPI.getCorners = () => { getCorners(model.bounds, model.corners); return model.corners; }; publicAPI.scale = (sx, sy, sz) => { if (publicAPI.isValid()) { const newBounds = [].concat(model.bounds); if (sx >= 0.0) { newBounds[0] *= sx; newBounds[1] *= sx; } else { newBounds[0] = sx * model.bounds[1]; newBounds[1] = sx * model.bounds[0]; } if (sy >= 0.0) { newBounds[2] *= sy; newBounds[3] *= sy; } else { newBounds[2] = sy * model.bounds[3]; newBounds[3] = sy * model.bounds[2]; } if (sz >= 0.0) { newBounds[4] *= sz; newBounds[5] *= sz; } else { newBounds[4] = sz * model.bounds[5]; newBounds[5] = sz * model.bounds[4]; } model.bounds = newBounds; return true; } return false; }; } // ---------------------------------------------------------------------------- // Object factory // ---------------------------------------------------------------------------- const DEFAULT_VALUES = { type: 'vtkBoundingBox', bounds: [].concat(INIT_BOUNDS), corners: [], }; // ---------------------------------------------------------------------------- export function extend(publicAPI, model, initialValues = {}) { Object.assign(model, DEFAULT_VALUES, initialValues); // Object methods macro.obj(publicAPI, model); macro.setGet(publicAPI, model, ['bounds']); vtkBoundingBox(publicAPI, model); } // ---------------------------------------------------------------------------- export const newInstance = macro.newInstance(extend, 'vtkBoundingBox'); // ---------------------------------------------------------------------------- export default { newInstance, extend, ...STATIC };