BoundingBox

vtkBoundingBox maintains a 3D axis aligned bounding box.
It is very lite weight and many of the member functions are in-lined so its
very fast. It is not derived from vtkObject so it can be allocated on the stack

newInstance({ bounds: [xMin, xMax, yMin, yMax, zMin, zMax]})

Construct a bounding box with the min point set to Number.MAX_VALUE and the max point set to -Number.MAX_VALUE unless { bounds } are provided.

clone() : newInstance

Return new instance with the same bounds.

equals(otherInstance) : Boolean

bounds

Set/Get the bounds explicitly of the box (vtk Style)

setMinPoint(x, y, z)

Set the minimum point of the bounding box - if the min point
is greater than the max point then the max point will also be changed

setMaxPoint(x, y, z)

Set the maximum point of the bounding box - if the max point
is less than the min point then the min point will also be changed

addPoint(x, y, z)

Change bounding box so it includes the point p
Note that the bounding box may have 0 volume if its bounds
were just initialized.

addBox(otherInstance)

Change the bouding box to be the union of itself and bbox

addBounds(xMin, xMax, yMin, yMax, zMin, zMax)

Change the bounding box so it includes bounds (defined by vtk standard)

intersect(otherInstance) : Boolean

Intersect this box with bbox. The method returns 1 if
both boxes are valid and they do have overlap else it will return false.
If false is returned the box has not been modified.

intersects(otherInstance) : Boolean

Returns true if the boxes intersect else returns false.

intersectPlane(origin[3], normal[3]) : Boolean

Intersect this box with the half space defined by plane.
Returns true if there is intersection—which implies that the box has been modified
Returns false otherwise

contains(otherInstance) : Boolean

Returns true if the min and max points of bbox are contained
within the bounds of this box, else returns false.

getBound(index) : Number

Return the ith bounds of the box (defined by vtk style)

getMinPoint() : [xMin, yMin, zMin]

Get the minimum point of the bounding box.

getMaxPoint() : [xMax, yMax, zMax]

Get the maximum point of the bounding box;

containsPoint(x, y, z) : Boolean

Returns true if the point is contained in the box else false.

getCenter() : [x, y, z]

Get the center of the bounding box

getLengths() : [with, height, depth]

Get the lengths of the box.

getLength(index) : Number

Return the length in the ith direction.

getMaxLength() : Number

Return the Max Length of the box

getDiagonalLength() : Number

Return the length of the diagonal or null if not valid.

inflate(delta)

Expand the Box by delta on each side, the box will grow by
2*delta in x, y and z

isValid()

Returns true if the bounds have been set and false if the box is in its
initialized state which is an inverted state.

reset()

Returns the box to its initialized state.

scale(x, y, z)

Scale each dimension of the box by some given factor.
If the box is not valid, it stays unchanged.
If the scalar factor is negative, bounds are flipped: for example,
if (xMin,xMax)=(-2,4) and sx=-3, (xMin,xMax) becomes (-12,6).

Source

index.js
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;
}

// ----------------------------------------------------------------------------
// Static API
// ----------------------------------------------------------------------------

export const STATIC = {
isValid,
getCenter,
getLength,
getLengths,
getMaxLength,
getDiagonalLength,
getXRange,
getYRange,
getZRange,
getCorners,
computeCornerPoints,
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;
};

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 Object.assign({ newInstance, extend }, STATIC);