// math.gl // SPDX-License-Identifier: MIT and Apache-2.0 // Copyright (c) vis.gl contributors import {BoundingVolume} from './bounding-volume'; import {Vector3} from '@math.gl/core'; import {Plane} from '../plane'; import {INTERSECTION} from '../../constants'; const scratchVector = new Vector3(); const scratchNormal = new Vector3(); /** * An axis aligned bounding box - aligned with coordinate axes * @see BoundingVolume * @see BoundingRectangle * @see OrientedBoundingBox */ export class AxisAlignedBoundingBox implements BoundingVolume { /** The center point of the bounding box. */ readonly center: Vector3; /** The positive half diagonal of the bounding box. */ readonly halfDiagonal: Vector3; /** The minimum point defining the bounding box. [0, 0, 0] for empty box */ readonly minimum: Vector3; /** The maximum point defining the bounding box. [0, 0, 0] for empty box */ readonly maximum: Vector3; /** * Creates an instance of an AxisAlignedBoundingBox from the minimum and maximum points along the x, y, and z axes. * @param minimum=[0, 0, 0] The minimum point along the x, y, and z axes. * @param maximum=[0, 0, 0] The maximum point along the x, y, and z axes. * @param center The center of the box; automatically computed if not supplied. */ constructor( minimum: readonly number[] = [0, 0, 0], maximum: readonly number[] = [0, 0, 0], center?: readonly number[] ) { // If center was not defined, compute it. center = center || scratchVector.copy(minimum).add(maximum).scale(0.5); this.center = new Vector3(center); this.halfDiagonal = new Vector3(maximum).subtract(this.center); /** * The minimum point defining the bounding box. * @type {Vector3} * @default {@link 0, 0, 0} */ this.minimum = new Vector3(minimum); /** * The maximum point defining the bounding box. * @type {Vector3} * @default {@link 0, 0, 0} */ this.maximum = new Vector3(maximum); } /** * Duplicates a AxisAlignedBoundingBox instance. * * @returns {AxisAlignedBoundingBox} A new AxisAlignedBoundingBox instance. */ clone(): AxisAlignedBoundingBox { return new AxisAlignedBoundingBox(this.minimum, this.maximum, this.center); } /** * Compares the provided AxisAlignedBoundingBox componentwise and returns * true if they are equal, false otherwise. * * @param {AxisAlignedBoundingBox} [right] The second AxisAlignedBoundingBox to compare with. * @returns {Boolean} true if left and right are equal, false otherwise. */ equals(right: AxisAlignedBoundingBox): boolean { return ( this === right || (Boolean(right) && this.minimum.equals(right.minimum) && this.maximum.equals(right.maximum)) ); } /** * Applies a 4x4 affine transformation matrix to a bounding sphere. * @param transform The transformation matrix to apply to the bounding sphere. * @returns itself, i.e. the modified BoundingVolume. */ transform(transform: readonly number[]): this { this.center.transformAsPoint(transform); // TODO - this.halfDiagonal.transformAsVector(transform); this.halfDiagonal.transform(transform); this.minimum.transform(transform); this.maximum.transform(transform); return this; } /** * Determines which side of a plane a box is located. */ intersectPlane(plane: Plane): number { const {halfDiagonal} = this; const normal = scratchNormal.from(plane.normal); const e = halfDiagonal.x * Math.abs(normal.x) + halfDiagonal.y * Math.abs(normal.y) + halfDiagonal.z * Math.abs(normal.z); const s = this.center.dot(normal) + plane.distance; // signed distance from center if (s - e > 0) { return INTERSECTION.INSIDE; } if (s + e < 0) { // Not in front because normals point inward return INTERSECTION.OUTSIDE; } return INTERSECTION.INTERSECTING; } /** Computes the estimated distance from the closest point on a bounding box to a point. */ distanceTo(point: readonly number[]): number { return Math.sqrt(this.distanceSquaredTo(point)); } /** Computes the estimated distance squared from the closest point on a bounding box to a point. */ distanceSquaredTo(point: readonly number[]): number { const offset = scratchVector.from(point).subtract(this.center); const {halfDiagonal} = this; let distanceSquared = 0.0; let d; d = Math.abs(offset.x) - halfDiagonal.x; if (d > 0) { distanceSquared += d * d; } d = Math.abs(offset.y) - halfDiagonal.y; if (d > 0) { distanceSquared += d * d; } d = Math.abs(offset.z) - halfDiagonal.z; if (d > 0) { distanceSquared += d * d; } return distanceSquared; } }