@openhps/core
Version:
Open Hybrid Positioning System - Core component
299 lines (272 loc) • 9.24 kB
JavaScript
import { Matrix3 } from './Matrix3.js';
import { Vector3 } from './Vector3.js';
const _vector1 = /*@__PURE__*/new Vector3();
const _vector2 = /*@__PURE__*/new Vector3();
const _normalMatrix = /*@__PURE__*/new Matrix3();
/**
* A two dimensional surface that extends infinitely in 3D space, represented
* in [Hessian normal form]{@link http://mathworld.wolfram.com/HessianNormalForm.html}
* by a unit length normal vector and a constant.
*/
class Plane {
/**
* Constructs a new plane.
*
* @param {Vector3} [normal=(1,0,0)] - A unit length vector defining the normal of the plane.
* @param {number} [constant=0] - The signed distance from the origin to the plane.
*/
constructor(normal = new Vector3(1, 0, 0), constant = 0) {
/**
* This flag can be used for type testing.
*
* @type {boolean}
* @readonly
* @default true
*/
this.isPlane = true;
/**
* A unit length vector defining the normal of the plane.
*
* @type {Vector3}
*/
this.normal = normal;
/**
* The signed distance from the origin to the plane.
*
* @type {number}
* @default 0
*/
this.constant = constant;
}
/**
* Sets the plane components by copying the given values.
*
* @param {Vector3} normal - The normal.
* @param {number} constant - The constant.
* @return {Plane} A reference to this plane.
*/
set(normal, constant) {
this.normal.copy(normal);
this.constant = constant;
return this;
}
/**
* Sets the plane components by defining `x`, `y`, `z` as the
* plane normal and `w` as the constant.
*
* @param {number} x - The value for the normal's x component.
* @param {number} y - The value for the normal's y component.
* @param {number} z - The value for the normal's z component.
* @param {number} w - The constant value.
* @return {Plane} A reference to this plane.
*/
setComponents(x, y, z, w) {
this.normal.set(x, y, z);
this.constant = w;
return this;
}
/**
* Sets the plane from the given normal and coplanar point (that is a point
* that lies onto the plane).
*
* @param {Vector3} normal - The normal.
* @param {Vector3} point - A coplanar point.
* @return {Plane} A reference to this plane.
*/
setFromNormalAndCoplanarPoint(normal, point) {
this.normal.copy(normal);
this.constant = -point.dot(this.normal);
return this;
}
/**
* Sets the plane from three coplanar points. The winding order is
* assumed to be counter-clockwise, and determines the direction of
* the plane normal.
*
* @param {Vector3} a - The first coplanar point.
* @param {Vector3} b - The second coplanar point.
* @param {Vector3} c - The third coplanar point.
* @return {Plane} A reference to this plane.
*/
setFromCoplanarPoints(a, b, c) {
const normal = _vector1.subVectors(c, b).cross(_vector2.subVectors(a, b)).normalize();
// Q: should an error be thrown if normal is zero (e.g. degenerate plane)?
this.setFromNormalAndCoplanarPoint(normal, a);
return this;
}
/**
* Copies the values of the given plane to this instance.
*
* @param {Plane} plane - The plane to copy.
* @return {Plane} A reference to this plane.
*/
copy(plane) {
this.normal.copy(plane.normal);
this.constant = plane.constant;
return this;
}
/**
* Normalizes the plane normal and adjusts the constant accordingly.
*
* @return {Plane} A reference to this plane.
*/
normalize() {
// Note: will lead to a divide by zero if the plane is invalid.
const inverseNormalLength = 1.0 / this.normal.length();
this.normal.multiplyScalar(inverseNormalLength);
this.constant *= inverseNormalLength;
return this;
}
/**
* Negates both the plane normal and the constant.
*
* @return {Plane} A reference to this plane.
*/
negate() {
this.constant *= -1;
this.normal.negate();
return this;
}
/**
* Returns the signed distance from the given point to this plane.
*
* @param {Vector3} point - The point to compute the distance for.
* @return {number} The signed distance.
*/
distanceToPoint(point) {
return this.normal.dot(point) + this.constant;
}
/**
* Returns the signed distance from the given sphere to this plane.
*
* @param {Sphere} sphere - The sphere to compute the distance for.
* @return {number} The signed distance.
*/
distanceToSphere(sphere) {
return this.distanceToPoint(sphere.center) - sphere.radius;
}
/**
* Projects a the given point onto the plane.
*
* @param {Vector3} point - The point to project.
* @param {Vector3} target - The target vector that is used to store the method's result.
* @return {Vector3} The projected point on the plane.
*/
projectPoint(point, target) {
return target.copy(point).addScaledVector(this.normal, -this.distanceToPoint(point));
}
/**
* Returns the intersection point of the passed line and the plane. Returns
* `null` if the line does not intersect. Returns the line's starting point if
* the line is coplanar with the plane.
*
* @param {Line3} line - The line to compute the intersection for.
* @param {Vector3} target - The target vector that is used to store the method's result.
* @return {?Vector3} The intersection point.
*/
intersectLine(line, target) {
const direction = line.delta(_vector1);
const denominator = this.normal.dot(direction);
if (denominator === 0) {
// line is coplanar, return origin
if (this.distanceToPoint(line.start) === 0) {
return target.copy(line.start);
}
// Unsure if this is the correct method to handle this case.
return null;
}
const t = -(line.start.dot(this.normal) + this.constant) / denominator;
if (t < 0 || t > 1) {
return null;
}
return target.copy(line.start).addScaledVector(direction, t);
}
/**
* Returns `true` if the given line segment intersects with (passes through) the plane.
*
* @param {Line3} line - The line to test.
* @return {boolean} Whether the given line segment intersects with the plane or not.
*/
intersectsLine(line) {
// Note: this tests if a line intersects the plane, not whether it (or its end-points) are coplanar with it.
const startSign = this.distanceToPoint(line.start);
const endSign = this.distanceToPoint(line.end);
return startSign < 0 && endSign > 0 || endSign < 0 && startSign > 0;
}
/**
* Returns `true` if the given bounding box intersects with the plane.
*
* @param {Box3} box - The bounding box to test.
* @return {boolean} Whether the given bounding box intersects with the plane or not.
*/
intersectsBox(box) {
return box.intersectsPlane(this);
}
/**
* Returns `true` if the given bounding sphere intersects with the plane.
*
* @param {Sphere} sphere - The bounding sphere to test.
* @return {boolean} Whether the given bounding sphere intersects with the plane or not.
*/
intersectsSphere(sphere) {
return sphere.intersectsPlane(this);
}
/**
* Returns a coplanar vector to the plane, by calculating the
* projection of the normal at the origin onto the plane.
*
* @param {Vector3} target - The target vector that is used to store the method's result.
* @return {Vector3} The coplanar point.
*/
coplanarPoint(target) {
return target.copy(this.normal).multiplyScalar(-this.constant);
}
/**
* Apply a 4x4 matrix to the plane. The matrix must be an affine, homogeneous transform.
*
* The optional normal matrix can be pre-computed like so:
* ```js
* const optionalNormalMatrix = new THREE.Matrix3().getNormalMatrix( matrix );
* ```
*
* @param {Matrix4} matrix - The transformation matrix.
* @param {Matrix4} [optionalNormalMatrix] - A pre-computed normal matrix.
* @return {Plane} A reference to this plane.
*/
applyMatrix4(matrix, optionalNormalMatrix) {
const normalMatrix = optionalNormalMatrix || _normalMatrix.getNormalMatrix(matrix);
const referencePoint = this.coplanarPoint(_vector1).applyMatrix4(matrix);
const normal = this.normal.applyMatrix3(normalMatrix).normalize();
this.constant = -referencePoint.dot(normal);
return this;
}
/**
* Translates the plane by the distance defined by the given offset vector.
* Note that this only affects the plane constant and will not affect the normal vector.
*
* @param {Vector3} offset - The offset vector.
* @return {Plane} A reference to this plane.
*/
translate(offset) {
this.constant -= offset.dot(this.normal);
return this;
}
/**
* Returns `true` if this plane is equal with the given one.
*
* @param {Plane} plane - The plane to test for equality.
* @return {boolean} Whether this plane is equal with the given one.
*/
equals(plane) {
return plane.normal.equals(this.normal) && plane.constant === this.constant;
}
/**
* Returns a new plane with copied values from this instance.
*
* @return {Plane} A clone of this instance.
*/
clone() {
return new this.constructor().copy(this);
}
}
export { Plane };