UNPKG

three

Version:

JavaScript 3D library

393 lines (275 loc) 8.29 kB
import { Box3 } from './Box3.js'; import { Vector3 } from './Vector3.js'; const _box = /*@__PURE__*/ new Box3(); const _v1 = /*@__PURE__*/ new Vector3(); const _v2 = /*@__PURE__*/ new Vector3(); /** * An analytical 3D sphere defined by a center and radius. This class is mainly * used as a Bounding Sphere for 3D objects. */ class Sphere { /** * Constructs a new sphere. * * @param {Vector3} [center=(0,0,0)] - The center of the sphere * @param {number} [radius=-1] - The radius of the sphere. */ constructor( center = new Vector3(), radius = - 1 ) { /** * This flag can be used for type testing. * * @type {boolean} * @readonly * @default true */ this.isSphere = true; /** * The center of the sphere * * @type {Vector3} */ this.center = center; /** * The radius of the sphere. * * @type {number} */ this.radius = radius; } /** * Sets the sphere's components by copying the given values. * * @param {Vector3} center - The center. * @param {number} radius - The radius. * @return {Sphere} A reference to this sphere. */ set( center, radius ) { this.center.copy( center ); this.radius = radius; return this; } /** * Computes the minimum bounding sphere for list of points. * If the optional center point is given, it is used as the sphere's * center. Otherwise, the center of the axis-aligned bounding box * encompassing the points is calculated. * * @param {Array<Vector3>} points - A list of points in 3D space. * @param {Vector3} [optionalCenter] - The center of the sphere. * @return {Sphere} A reference to this sphere. */ setFromPoints( points, optionalCenter ) { const center = this.center; if ( optionalCenter !== undefined ) { center.copy( optionalCenter ); } else { _box.setFromPoints( points ).getCenter( center ); } let maxRadiusSq = 0; for ( let i = 0, il = points.length; i < il; i ++ ) { maxRadiusSq = Math.max( maxRadiusSq, center.distanceToSquared( points[ i ] ) ); } this.radius = Math.sqrt( maxRadiusSq ); return this; } /** * Copies the values of the given sphere to this instance. * * @param {Sphere} sphere - The sphere to copy. * @return {Sphere} A reference to this sphere. */ copy( sphere ) { this.center.copy( sphere.center ); this.radius = sphere.radius; return this; } /** * Returns `true` if the sphere is empty (the radius set to a negative number). * * Spheres with a radius of `0` contain only their center point and are not * considered to be empty. * * @return {boolean} Whether this sphere is empty or not. */ isEmpty() { return ( this.radius < 0 ); } /** * Makes this sphere empty which means in encloses a zero space in 3D. * * @return {Sphere} A reference to this sphere. */ makeEmpty() { this.center.set( 0, 0, 0 ); this.radius = - 1; return this; } /** * Returns `true` if this sphere contains the given point inclusive of * the surface of the sphere. * * @param {Vector3} point - The point to check. * @return {boolean} Whether this sphere contains the given point or not. */ containsPoint( point ) { return ( point.distanceToSquared( this.center ) <= ( this.radius * this.radius ) ); } /** * Returns the closest distance from the boundary of the sphere to the * given point. If the sphere contains the point, the distance will * be negative. * * @param {Vector3} point - The point to compute the distance to. * @return {number} The distance to the point. */ distanceToPoint( point ) { return ( point.distanceTo( this.center ) - this.radius ); } /** * Returns `true` if this sphere intersects with the given one. * * @param {Sphere} sphere - The sphere to test. * @return {boolean} Whether this sphere intersects with the given one or not. */ intersectsSphere( sphere ) { const radiusSum = this.radius + sphere.radius; return sphere.center.distanceToSquared( this.center ) <= ( radiusSum * radiusSum ); } /** * Returns `true` if this sphere intersects with the given box. * * @param {Box3} box - The box to test. * @return {boolean} Whether this sphere intersects with the given box or not. */ intersectsBox( box ) { return box.intersectsSphere( this ); } /** * Returns `true` if this sphere intersects with the given plane. * * @param {Plane} plane - The plane to test. * @return {boolean} Whether this sphere intersects with the given plane or not. */ intersectsPlane( plane ) { return Math.abs( plane.distanceToPoint( this.center ) ) <= this.radius; } /** * Clamps a point within the sphere. If the point is outside the sphere, it * will clamp it to the closest point on the edge of the sphere. Points * already inside the sphere will not be affected. * * @param {Vector3} point - The plane to clamp. * @param {Vector3} target - The target vector that is used to store the method's result. * @return {Vector3} The clamped point. */ clampPoint( point, target ) { const deltaLengthSq = this.center.distanceToSquared( point ); target.copy( point ); if ( deltaLengthSq > ( this.radius * this.radius ) ) { target.sub( this.center ).normalize(); target.multiplyScalar( this.radius ).add( this.center ); } return target; } /** * Returns a bounding box that encloses this sphere. * * @param {Box3} target - The target box that is used to store the method's result. * @return {Box3} The bounding box that encloses this sphere. */ getBoundingBox( target ) { if ( this.isEmpty() ) { // Empty sphere produces empty bounding box target.makeEmpty(); return target; } target.set( this.center, this.center ); target.expandByScalar( this.radius ); return target; } /** * Transforms this sphere with the given 4x4 transformation matrix. * * @param {Matrix4} matrix - The transformation matrix. * @return {Sphere} A reference to this sphere. */ applyMatrix4( matrix ) { this.center.applyMatrix4( matrix ); this.radius = this.radius * matrix.getMaxScaleOnAxis(); return this; } /** * Translates the sphere's center by the given offset. * * @param {Vector3} offset - The offset. * @return {Sphere} A reference to this sphere. */ translate( offset ) { this.center.add( offset ); return this; } /** * Expands the boundaries of this sphere to include the given point. * * @param {Vector3} point - The point to include. * @return {Sphere} A reference to this sphere. */ expandByPoint( point ) { if ( this.isEmpty() ) { this.center.copy( point ); this.radius = 0; return this; } _v1.subVectors( point, this.center ); const lengthSq = _v1.lengthSq(); if ( lengthSq > ( this.radius * this.radius ) ) { // calculate the minimal sphere const length = Math.sqrt( lengthSq ); const delta = ( length - this.radius ) * 0.5; this.center.addScaledVector( _v1, delta / length ); this.radius += delta; } return this; } /** * Expands this sphere to enclose both the original sphere and the given sphere. * * @param {Sphere} sphere - The sphere to include. * @return {Sphere} A reference to this sphere. */ union( sphere ) { if ( sphere.isEmpty() ) { return this; } if ( this.isEmpty() ) { this.copy( sphere ); return this; } if ( this.center.equals( sphere.center ) === true ) { this.radius = Math.max( this.radius, sphere.radius ); } else { _v2.subVectors( sphere.center, this.center ).setLength( sphere.radius ); this.expandByPoint( _v1.copy( sphere.center ).add( _v2 ) ); this.expandByPoint( _v1.copy( sphere.center ).sub( _v2 ) ); } return this; } /** * Returns `true` if this sphere is equal with the given one. * * @param {Sphere} sphere - The sphere to test for equality. * @return {boolean} Whether this bounding sphere is equal with the given one. */ equals( sphere ) { return sphere.center.equals( this.center ) && ( sphere.radius === this.radius ); } /** * Returns a new sphere with copied values from this instance. * * @return {Sphere} A clone of this instance. */ clone() { return new this.constructor().copy( this ); } } export { Sphere };