src/Box3.js
import { Vector3 } from "./Vector3.js";
/**
* A vector.
*
* @type {Vector3}
* @private
*/
const v = new Vector3();
/**
* A list of points.
*
* @type {Vector3[]}
* @private
*/
const points = [
new Vector3(),
new Vector3(),
new Vector3(),
new Vector3(),
new Vector3(),
new Vector3(),
new Vector3(),
new Vector3()
];
/**
* A 3D box.
*/
export class Box3 {
/**
* Constructs a new box.
*
* @param {Vector3} [min] - The lower bounds.
* @param {Vector3} [max] - The upper bounds.
*/
constructor(
min = new Vector3(Infinity, Infinity, Infinity),
max = new Vector3(-Infinity, -Infinity, -Infinity)
) {
/**
* The lower bounds.
*
* @type {Vector3}
*/
this.min = min;
/**
* The upper bounds.
*
* @type {Vector3}
*/
this.max = max;
}
/**
* Sets the values of this box.
*
* @param {Vector3} min - The lower bounds.
* @param {Vector3} max - The upper bounds.
* @return {Box3} This box.
*/
set(min, max) {
this.min.copy(min);
this.max.copy(max);
return this;
}
/**
* Copies the values of a given box.
*
* @param {Box3} b - A box.
* @return {Box3} This box.
*/
copy(b) {
this.min.copy(b.min);
this.max.copy(b.max);
return this;
}
/**
* Clones this box.
*
* @return {Box3} A clone of this box.
*/
clone() {
return new this.constructor().copy(this);
}
/**
* Makes this box empty.
*
* The lower bounds are set to infinity and the upper bounds to negative
* infinity to create an infinitely small box.
*
* @return {Box3} This box.
*/
makeEmpty() {
this.min.x = this.min.y = this.min.z = Infinity;
this.max.x = this.max.y = this.max.z = -Infinity;
return this;
}
/**
* Indicates whether this box is truly empty.
*
* This is a more robust check for emptiness since the volume can get positive
* with two negative axes.
*
* @return {Box3} This box.
*/
isEmpty() {
return (
this.max.x < this.min.x ||
this.max.y < this.min.y ||
this.max.z < this.min.z
);
}
/**
* Computes the center of this box.
*
* @param {Vector3} [target] - A target vector. If none is provided, a new one will be created.
* @return {Vector3} A vector that describes the center of this box.
*/
getCenter(target = new Vector3()) {
return !this.isEmpty() ?
target.addVectors(this.min, this.max).multiplyScalar(0.5) :
target.set(0, 0, 0);
}
/**
* Computes the size of this box.
*
* @param {Vector3} [target] - A target vector. If none is provided, a new one will be created.
* @return {Vector3} A vector that describes the size of this box.
*/
getSize(target = new Vector3()) {
return !this.isEmpty() ?
target.subVectors(this.max, this.min) :
target.set(0, 0, 0);
}
/**
* Computes the bounding box of the given sphere.
*
* @param {Sphere} sphere - A sphere.
* @return {Box3} This box.
*/
setFromSphere(sphere) {
this.set(sphere.center, sphere.center);
this.expandByScalar(sphere.radius);
return this;
}
/**
* Expands this box by the given point.
*
* @param {Vector3} p - A point.
* @return {Box3} This box.
*/
expandByPoint(p) {
this.min.min(p);
this.max.max(p);
return this;
}
/**
* Expands this box by the given vector.
*
* @param {Vector3} v - A vector.
* @return {Box3} This box.
*/
expandByVector(v) {
this.min.sub(v);
this.max.add(v);
return this;
}
/**
* Expands this box by the given scalar.
*
* @param {Number} s - A scalar.
* @return {Box3} This box.
*/
expandByScalar(s) {
this.min.addScalar(-s);
this.max.addScalar(s);
return this;
}
/**
* Defines this box by the given points.
*
* @param {Vector3[]} points - The points.
* @return {Box3} This box.
*/
setFromPoints(points) {
let i, l;
this.min.set(0, 0, 0);
this.max.set(0, 0, 0);
for(i = 0, l = points.length; i < l; ++i) {
this.expandByPoint(points[i]);
}
return this;
}
/**
* Defines this box by the given center and size.
*
* @param {Vector3} center - The center.
* @param {Number} size - The size.
* @return {Box3} This box.
*/
setFromCenterAndSize(center, size) {
const halfSize = v.copy(size).multiplyScalar(0.5);
this.min.copy(center).sub(halfSize);
this.max.copy(center).add(halfSize);
return this;
}
/**
* Clamps the given point to the boundaries of this box.
*
* @param {Vector3} point - A point.
* @param {Vector3} [target] - A target vector. If none is provided, a new one will be created.
* @return {Vector3} The clamped point.
*/
clampPoint(point, target = new Vector3()) {
return target.copy(point).clamp(this.min, this.max);
}
/**
* Calculates the distance from this box to the given point.
*
* @param {Vector3} p - A point.
* @return {Number} The distance.
*/
distanceToPoint(p) {
const clampedPoint = v.copy(p).clamp(this.min, this.max);
return clampedPoint.sub(p).length();
}
/**
* Applies the given matrix to this box.
*
* @param {Matrix4} m - The matrix.
* @return {Box3} This box.
*/
applyMatrix4(m) {
const min = this.min;
const max = this.max;
if(!this.isEmpty()) {
points[0].set(min.x, min.y, min.z).applyMatrix4(m);
points[1].set(min.x, min.y, max.z).applyMatrix4(m);
points[2].set(min.x, max.y, min.z).applyMatrix4(m);
points[3].set(min.x, max.y, max.z).applyMatrix4(m);
points[4].set(max.x, min.y, min.z).applyMatrix4(m);
points[5].set(max.x, min.y, max.z).applyMatrix4(m);
points[6].set(max.x, max.y, min.z).applyMatrix4(m);
points[7].set(max.x, max.y, max.z).applyMatrix4(m);
this.setFromPoints(points);
}
return this;
}
/**
* Translates this box.
*
* @param {Vector3} offset - The offset.
* @return {Box3} This box.
*/
translate(offset) {
this.min.add(offset);
this.max.add(offset);
return this;
}
/**
* Intersects this box with the given one.
*
* @param {Box3} b - A box.
* @return {Box3} This box.
*/
intersect(b) {
this.min.max(b.min);
this.max.min(b.max);
/* Ensure that if there is no overlap, the result is fully empty to prevent
subsequent intersections to erroneously return valid values. */
if(this.isEmpty()) {
this.makeEmpty();
}
return this;
}
/**
* Expands this box by combining it with the given one.
*
* @param {Box3} b - A box.
* @return {Box3} This box.
*/
union(b) {
this.min.min(b.min);
this.max.max(b.max);
return this;
}
/**
* Checks if the given point lies inside this box.
*
* @param {Vector3} p - A point.
* @return {Boolean} Whether this box contains the point.
*/
containsPoint(p) {
const min = this.min;
const max = this.max;
return (
p.x >= min.x &&
p.y >= min.y &&
p.z >= min.z &&
p.x <= max.x &&
p.y <= max.y &&
p.z <= max.z
);
}
/**
* Checks if the given box lies inside this box.
*
* @param {Box3} b - A box.
* @return {Boolean} Whether this box contains the given one.
*/
containsBox(b) {
const tMin = this.min;
const tMax = this.max;
const bMin = b.min;
const bMax = b.max;
return (
tMin.x <= bMin.x && bMax.x <= tMax.x &&
tMin.y <= bMin.y && bMax.y <= tMax.y &&
tMin.z <= bMin.z && bMax.z <= tMax.z
);
}
/**
* Checks if this box intersects the given one.
*
* @param {Box3} b - A box.
* @return {Boolean} Whether the boxes intersect.
*/
intersectsBox(b) {
const tMin = this.min;
const tMax = this.max;
const bMin = b.min;
const bMax = b.max;
return (
bMax.x >= tMin.x &&
bMax.y >= tMin.y &&
bMax.z >= tMin.z &&
bMin.x <= tMax.x &&
bMin.y <= tMax.y &&
bMin.z <= tMax.z
);
}
/**
* Checks if this box intersects the given sphere.
*
* @param {Sphere} s - A sphere.
* @return {Boolean} Whether the box intersects the sphere.
*/
intersectsSphere(s) {
// Find the point in this box that is closest to the sphere's center.
const closestPoint = this.clampPoint(s.center, v);
// If that point is inside the sphere, it intersects this box.
return (closestPoint.distanceToSquared(s.center) <= (s.radius * s.radius));
}
/**
* Checks if this box intersects the given plane.
*
* Computes the minimum and maximum dot product values. If those values are on
* the same side (back or front) of the plane, then there is no intersection.
*
* @param {Plane} p - A plane.
* @return {Boolean} Whether the box intersects the plane.
*/
intersectsPlane(p) {
let min, max;
if(p.normal.x > 0) {
min = p.normal.x * this.min.x;
max = p.normal.x * this.max.x;
} else {
min = p.normal.x * this.max.x;
max = p.normal.x * this.min.x;
}
if(p.normal.y > 0) {
min += p.normal.y * this.min.y;
max += p.normal.y * this.max.y;
} else {
min += p.normal.y * this.max.y;
max += p.normal.y * this.min.y;
}
if(p.normal.z > 0) {
min += p.normal.z * this.min.z;
max += p.normal.z * this.max.z;
} else {
min += p.normal.z * this.max.z;
max += p.normal.z * this.min.z;
}
return (min <= -p.constant && max >= -p.constant);
}
/**
* Checks if this box equals the given one.
*
* @param {Box3} b - A box.
* @return {Boolean} Whether this box equals the given one.
*/
equals(b) {
return (b.min.equals(this.min) && b.max.equals(this.max));
}
}