three
Version:
JavaScript 3D library
368 lines (272 loc) • 9.11 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 };