UNPKG

@openhps/core

Version:

Open Hybrid Positioning System - Core component

297 lines (275 loc) 8.4 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 };