📄 Ray.js
¶
📊 Analysis Summary¶
Metric | Count |
---|---|
🔧 Functions | 20 |
🧱 Classes | 1 |
📦 Imports | 1 |
📊 Variables & Constants | 34 |
📚 Table of Contents¶
🛠️ File Location:¶
📂 src/math/Ray.js
📦 Imports¶
Name | Source |
---|---|
Vector3 |
./Vector3.js |
Variables & Constants¶
Name | Type | Kind | Value | Exported |
---|---|---|---|---|
_vector |
Vector3 |
let/var | new Vector3() |
✗ |
_segCenter |
Vector3 |
let/var | new Vector3() |
✗ |
_segDir |
Vector3 |
let/var | new Vector3() |
✗ |
_diff |
Vector3 |
let/var | new Vector3() |
✗ |
_edge1 |
Vector3 |
let/var | new Vector3() |
✗ |
_edge2 |
Vector3 |
let/var | new Vector3() |
✗ |
_normal |
Vector3 |
let/var | new Vector3() |
✗ |
segExtent |
number |
let/var | v0.distanceTo( v1 ) * 0.5 |
✗ |
a01 |
number |
let/var | - this.direction.dot( _segDir ) |
✗ |
b1 |
number |
let/var | - _diff.dot( _segDir ) |
✗ |
s0 |
any |
let/var | *not shown* |
✗ |
s1 |
any |
let/var | *not shown* |
✗ |
sqrDist |
any |
let/var | *not shown* |
✗ |
extDet |
any |
let/var | *not shown* |
✗ |
invDet |
number |
let/var | 1 / det |
✗ |
d2 |
number |
let/var | _vector.dot( _vector ) - tca * tca |
✗ |
radius2 |
number |
let/var | sphere.radius * sphere.radius |
✗ |
t0 |
number |
let/var | tca - thc |
✗ |
t1 |
number |
let/var | tca + thc |
✗ |
t |
number |
let/var | - ( this.origin.dot( plane.normal ) + plane.constant ) / denominator |
✗ |
tmin |
any |
let/var | *not shown* |
✗ |
tmax |
any |
let/var | *not shown* |
✗ |
tymin |
any |
let/var | *not shown* |
✗ |
tymax |
any |
let/var | *not shown* |
✗ |
tzmin |
any |
let/var | *not shown* |
✗ |
tzmax |
any |
let/var | *not shown* |
✗ |
invdirx |
number |
let/var | 1 / this.direction.x |
✗ |
invdiry |
number |
let/var | 1 / this.direction.y |
✗ |
invdirz |
number |
let/var | 1 / this.direction.z |
✗ |
origin |
Vector3 |
let/var | this.origin |
✗ |
sign |
any |
let/var | *not shown* |
✗ |
DdQxE2 |
number |
let/var | sign * this.direction.dot( _edge2.crossVectors( _diff, _edge2 ) ) |
✗ |
DdE1xQ |
number |
let/var | sign * this.direction.dot( _edge1.cross( _diff ) ) |
✗ |
QdN |
number |
let/var | - sign * _diff.dot( _normal ) |
✗ |
Functions¶
Ray.set(origin: Vector3, direction: Vector3): Ray
¶
JSDoc:
/**
* Sets the ray's components by copying the given values.
*
* @param {Vector3} origin - The origin.
* @param {Vector3} direction - The direction.
* @return {Ray} A reference to this ray.
*/
Parameters:
origin
Vector3
direction
Vector3
Returns: Ray
Calls:
this.origin.copy
this.direction.copy
Code
Ray.copy(ray: Ray): Ray
¶
JSDoc:
/**
* Copies the values of the given ray to this instance.
*
* @param {Ray} ray - The ray to copy.
* @return {Ray} A reference to this ray.
*/
Parameters:
ray
Ray
Returns: Ray
Calls:
this.origin.copy
this.direction.copy
Code
Ray.at(t: number, target: Vector3): Vector3
¶
JSDoc:
/**
* Returns a vector that is located at a given distance along this ray.
*
* @param {number} t - The distance along the ray to retrieve a position for.
* @param {Vector3} target - The target vector that is used to store the method's result.
* @return {Vector3} A position on the ray.
*/
Parameters:
t
number
target
Vector3
Returns: Vector3
Calls:
target.copy( this.origin ).addScaledVector
Ray.lookAt(v: Vector3): Ray
¶
JSDoc:
/**
* Adjusts the direction of the ray to point at the given vector in world space.
*
* @param {Vector3} v - The target position.
* @return {Ray} A reference to this ray.
*/
Parameters:
v
Vector3
Returns: Ray
Calls:
this.direction.copy( v ).sub( this.origin ).normalize
Ray.recast(t: number): Ray
¶
JSDoc:
/**
* Shift the origin of this ray along its direction by the given distance.
*
* @param {number} t - The distance along the ray to interpolate.
* @return {Ray} A reference to this ray.
*/
Parameters:
t
number
Returns: Ray
Calls:
this.origin.copy
this.at
Ray.closestPointToPoint(point: Vector3, target: Vector3): Vector3
¶
JSDoc:
/**
* Returns the point along this ray that is closest to the given point.
*
* @param {Vector3} point - A point in 3D space to get the closet location on the ray for.
* @param {Vector3} target - The target vector that is used to store the method's result.
* @return {Vector3} The closest point on this ray.
*/
Parameters:
point
Vector3
target
Vector3
Returns: Vector3
Calls:
target.subVectors
target.dot
target.copy
target.copy( this.origin ).addScaledVector
Code
Ray.distanceToPoint(point: Vector3): number
¶
JSDoc:
/**
* Returns the distance of the closest approach between this ray and the given point.
*
* @param {Vector3} point - A point in 3D space to compute the distance to.
* @return {number} The distance.
*/
Parameters:
point
Vector3
Returns: number
Calls:
Math.sqrt
this.distanceSqToPoint
Ray.distanceSqToPoint(point: Vector3): number
¶
JSDoc:
/**
* Returns the squared distance of the closest approach between this ray and the given point.
*
* @param {Vector3} point - A point in 3D space to compute the distance to.
* @return {number} The squared distance.
*/
Parameters:
point
Vector3
Returns: number
Calls:
_vector.subVectors( point, this.origin ).dot
this.origin.distanceToSquared
_vector.copy( this.origin ).addScaledVector
_vector.distanceToSquared
Internal Comments:
Code
distanceSqToPoint( point ) {
const directionDistance = _vector.subVectors( point, this.origin ).dot( this.direction );
// point behind the ray
if ( directionDistance < 0 ) {
return this.origin.distanceToSquared( point );
}
_vector.copy( this.origin ).addScaledVector( this.direction, directionDistance );
return _vector.distanceToSquared( point );
}
Ray.distanceSqToSegment(v0: Vector3, v1: Vector3, optionalPointOnRay: Vector3, optionalPointOnSegment: Vector3): number
¶
JSDoc:
/**
* Returns the squared distance between this ray and the given line segment.
*
* @param {Vector3} v0 - The start point of the line segment.
* @param {Vector3} v1 - The end point of the line segment.
* @param {Vector3} [optionalPointOnRay] - When provided, it receives the point on this ray that is closest to the segment.
* @param {Vector3} [optionalPointOnSegment] - When provided, it receives the point on the line segment that is closest to this ray.
* @return {number} The squared distance.
*/
Parameters:
v0
Vector3
v1
Vector3
optionalPointOnRay
Vector3
optionalPointOnSegment
Vector3
Returns: number
Calls:
_segCenter.copy( v0 ).add( v1 ).multiplyScalar
_segDir.copy( v1 ).sub( v0 ).normalize
_diff.copy( this.origin ).sub
v0.distanceTo
this.direction.dot
_diff.dot
_diff.lengthSq
Math.abs
Math.max
Math.min
optionalPointOnRay.copy( this.origin ).addScaledVector
optionalPointOnSegment.copy( _segCenter ).addScaledVector
Internal Comments:
// from https://github.com/pmjoniak/GeometricTools/blob/master/GTEngine/Include/Mathematics/GteDistRaySegment.h (x8)
// It returns the min distance between the ray and the segment (x8)
// defined by v0 and v1 (x8)
// It can also set two optional targets : (x8)
// - The closest point on the ray (x8)
// - The closest point on the segment (x8)
// The ray and segment are not parallel. (x3)
// region 0 (x2)
// Minimum at interior points of ray and segment. (x2)
// region 1 (x3)
// region 5 (x3)
// region 4 (x3)
// region 3 (x3)
// region 2 (x3)
// Ray and segment are parallel. (x3)
Code
distanceSqToSegment( v0, v1, optionalPointOnRay, optionalPointOnSegment ) {
// from https://github.com/pmjoniak/GeometricTools/blob/master/GTEngine/Include/Mathematics/GteDistRaySegment.h
// It returns the min distance between the ray and the segment
// defined by v0 and v1
// It can also set two optional targets :
// - The closest point on the ray
// - The closest point on the segment
_segCenter.copy( v0 ).add( v1 ).multiplyScalar( 0.5 );
_segDir.copy( v1 ).sub( v0 ).normalize();
_diff.copy( this.origin ).sub( _segCenter );
const segExtent = v0.distanceTo( v1 ) * 0.5;
const a01 = - this.direction.dot( _segDir );
const b0 = _diff.dot( this.direction );
const b1 = - _diff.dot( _segDir );
const c = _diff.lengthSq();
const det = Math.abs( 1 - a01 * a01 );
let s0, s1, sqrDist, extDet;
if ( det > 0 ) {
// The ray and segment are not parallel.
s0 = a01 * b1 - b0;
s1 = a01 * b0 - b1;
extDet = segExtent * det;
if ( s0 >= 0 ) {
if ( s1 >= - extDet ) {
if ( s1 <= extDet ) {
// region 0
// Minimum at interior points of ray and segment.
const invDet = 1 / det;
s0 *= invDet;
s1 *= invDet;
sqrDist = s0 * ( s0 + a01 * s1 + 2 * b0 ) + s1 * ( a01 * s0 + s1 + 2 * b1 ) + c;
} else {
// region 1
s1 = segExtent;
s0 = Math.max( 0, - ( a01 * s1 + b0 ) );
sqrDist = - s0 * s0 + s1 * ( s1 + 2 * b1 ) + c;
}
} else {
// region 5
s1 = - segExtent;
s0 = Math.max( 0, - ( a01 * s1 + b0 ) );
sqrDist = - s0 * s0 + s1 * ( s1 + 2 * b1 ) + c;
}
} else {
if ( s1 <= - extDet ) {
// region 4
s0 = Math.max( 0, - ( - a01 * segExtent + b0 ) );
s1 = ( s0 > 0 ) ? - segExtent : Math.min( Math.max( - segExtent, - b1 ), segExtent );
sqrDist = - s0 * s0 + s1 * ( s1 + 2 * b1 ) + c;
} else if ( s1 <= extDet ) {
// region 3
s0 = 0;
s1 = Math.min( Math.max( - segExtent, - b1 ), segExtent );
sqrDist = s1 * ( s1 + 2 * b1 ) + c;
} else {
// region 2
s0 = Math.max( 0, - ( a01 * segExtent + b0 ) );
s1 = ( s0 > 0 ) ? segExtent : Math.min( Math.max( - segExtent, - b1 ), segExtent );
sqrDist = - s0 * s0 + s1 * ( s1 + 2 * b1 ) + c;
}
}
} else {
// Ray and segment are parallel.
s1 = ( a01 > 0 ) ? - segExtent : segExtent;
s0 = Math.max( 0, - ( a01 * s1 + b0 ) );
sqrDist = - s0 * s0 + s1 * ( s1 + 2 * b1 ) + c;
}
if ( optionalPointOnRay ) {
optionalPointOnRay.copy( this.origin ).addScaledVector( this.direction, s0 );
}
if ( optionalPointOnSegment ) {
optionalPointOnSegment.copy( _segCenter ).addScaledVector( _segDir, s1 );
}
return sqrDist;
}
Ray.intersectSphere(sphere: Sphere, target: Vector3): Vector3
¶
JSDoc:
/**
* Intersects this ray with the given sphere, returning the intersection
* point or `null` if there is no intersection.
*
* @param {Sphere} sphere - The sphere to intersect.
* @param {Vector3} target - The target vector that is used to store the method's result.
* @return {?Vector3} The intersection point.
*/
Parameters:
sphere
Sphere
target
Vector3
Returns: Vector3
Calls:
_vector.subVectors
_vector.dot
Math.sqrt
this.at
Internal Comments:
// t0 = first intersect point - entrance on front of sphere (x2)
// t1 = second intersect point - exit point on back of sphere (x2)
// test to see if t1 is behind the ray - if so, return null
// test to see if t0 is behind the ray:
// if it is, the ray is inside the sphere, so return the second exit point scaled by t1,
// in order to always return an intersect point that is in front of the ray.
// else t0 is in front of the ray, so return the first collision point scaled by t0
Code
intersectSphere( sphere, target ) {
_vector.subVectors( sphere.center, this.origin );
const tca = _vector.dot( this.direction );
const d2 = _vector.dot( _vector ) - tca * tca;
const radius2 = sphere.radius * sphere.radius;
if ( d2 > radius2 ) return null;
const thc = Math.sqrt( radius2 - d2 );
// t0 = first intersect point - entrance on front of sphere
const t0 = tca - thc;
// t1 = second intersect point - exit point on back of sphere
const t1 = tca + thc;
// test to see if t1 is behind the ray - if so, return null
if ( t1 < 0 ) return null;
// test to see if t0 is behind the ray:
// if it is, the ray is inside the sphere, so return the second exit point scaled by t1,
// in order to always return an intersect point that is in front of the ray.
if ( t0 < 0 ) return this.at( t1, target );
// else t0 is in front of the ray, so return the first collision point scaled by t0
return this.at( t0, target );
}
Ray.intersectsSphere(sphere: Sphere): boolean
¶
JSDoc:
/**
* Returns `true` if this ray intersects with the given sphere.
*
* @param {Sphere} sphere - The sphere to intersect.
* @return {boolean} Whether this ray intersects with the given sphere or not.
*/
Parameters:
sphere
Sphere
Returns: boolean
Calls:
this.distanceSqToPoint
Code
Ray.distanceToPlane(plane: Plane): number
¶
JSDoc:
/**
* Computes the distance from the ray's origin to the given plane. Returns `null` if the ray
* does not intersect with the plane.
*
* @param {Plane} plane - The plane to compute the distance to.
* @return {?number} Whether this ray intersects with the given sphere or not.
*/
Parameters:
plane
Plane
Returns: number
Calls:
plane.normal.dot
plane.distanceToPoint
this.origin.dot
Internal Comments:
// line is coplanar, return origin
// Null is preferable to undefined since undefined means.... it is undefined
// Return if the ray never intersects the plane
Code
distanceToPlane( plane ) {
const denominator = plane.normal.dot( this.direction );
if ( denominator === 0 ) {
// line is coplanar, return origin
if ( plane.distanceToPoint( this.origin ) === 0 ) {
return 0;
}
// Null is preferable to undefined since undefined means.... it is undefined
return null;
}
const t = - ( this.origin.dot( plane.normal ) + plane.constant ) / denominator;
// Return if the ray never intersects the plane
return t >= 0 ? t : null;
}
Ray.intersectPlane(plane: Plane, target: Vector3): Vector3
¶
JSDoc:
/**
* Intersects this ray with the given plane, returning the intersection
* point or `null` if there is no intersection.
*
* @param {Plane} plane - The plane to intersect.
* @param {Vector3} target - The target vector that is used to store the method's result.
* @return {?Vector3} The intersection point.
*/
Parameters:
plane
Plane
target
Vector3
Returns: Vector3
Calls:
this.distanceToPlane
this.at
Code
Ray.intersectsPlane(plane: Plane): boolean
¶
JSDoc:
/**
* Returns `true` if this ray intersects with the given plane.
*
* @param {Plane} plane - The plane to intersect.
* @return {boolean} Whether this ray intersects with the given plane or not.
*/
Parameters:
plane
Plane
Returns: boolean
Calls:
plane.distanceToPoint
plane.normal.dot
Internal Comments:
// check if the ray lies on the plane first (x2)
// ray origin is behind the plane (and is pointing behind it)
Code
intersectsPlane( plane ) {
// check if the ray lies on the plane first
const distToPoint = plane.distanceToPoint( this.origin );
if ( distToPoint === 0 ) {
return true;
}
const denominator = plane.normal.dot( this.direction );
if ( denominator * distToPoint < 0 ) {
return true;
}
// ray origin is behind the plane (and is pointing behind it)
return false;
}
Ray.intersectBox(box: Box3, target: Vector3): Vector3
¶
JSDoc:
/**
* Intersects this ray with the given bounding box, returning the intersection
* point or `null` if there is no intersection.
*
* @param {Box3} box - The box to intersect.
* @param {Vector3} target - The target vector that is used to store the method's result.
* @return {?Vector3} The intersection point.
*/
Parameters:
box
Box3
target
Vector3
Returns: Vector3
Calls:
isNaN
this.at
Internal Comments:
Code
intersectBox( box, target ) {
let tmin, tmax, tymin, tymax, tzmin, tzmax;
const invdirx = 1 / this.direction.x,
invdiry = 1 / this.direction.y,
invdirz = 1 / this.direction.z;
const origin = this.origin;
if ( invdirx >= 0 ) {
tmin = ( box.min.x - origin.x ) * invdirx;
tmax = ( box.max.x - origin.x ) * invdirx;
} else {
tmin = ( box.max.x - origin.x ) * invdirx;
tmax = ( box.min.x - origin.x ) * invdirx;
}
if ( invdiry >= 0 ) {
tymin = ( box.min.y - origin.y ) * invdiry;
tymax = ( box.max.y - origin.y ) * invdiry;
} else {
tymin = ( box.max.y - origin.y ) * invdiry;
tymax = ( box.min.y - origin.y ) * invdiry;
}
if ( ( tmin > tymax ) || ( tymin > tmax ) ) return null;
if ( tymin > tmin || isNaN( tmin ) ) tmin = tymin;
if ( tymax < tmax || isNaN( tmax ) ) tmax = tymax;
if ( invdirz >= 0 ) {
tzmin = ( box.min.z - origin.z ) * invdirz;
tzmax = ( box.max.z - origin.z ) * invdirz;
} else {
tzmin = ( box.max.z - origin.z ) * invdirz;
tzmax = ( box.min.z - origin.z ) * invdirz;
}
if ( ( tmin > tzmax ) || ( tzmin > tmax ) ) return null;
if ( tzmin > tmin || tmin !== tmin ) tmin = tzmin;
if ( tzmax < tmax || tmax !== tmax ) tmax = tzmax;
//return point closest to the ray (positive side)
if ( tmax < 0 ) return null;
return this.at( tmin >= 0 ? tmin : tmax, target );
}
Ray.intersectsBox(box: Box3): boolean
¶
JSDoc:
/**
* Returns `true` if this ray intersects with the given box.
*
* @param {Box3} box - The box to intersect.
* @return {boolean} Whether this ray intersects with the given box or not.
*/
Parameters:
box
Box3
Returns: boolean
Calls:
this.intersectBox
Ray.intersectTriangle(a: Vector3, b: Vector3, c: Vector3, backfaceCulling: boolean, target: Vector3): Vector3
¶
JSDoc:
/**
* Intersects this ray with the given triangle, returning the intersection
* point or `null` if there is no intersection.
*
* @param {Vector3} a - The first vertex of the triangle.
* @param {Vector3} b - The second vertex of the triangle.
* @param {Vector3} c - The third vertex of the triangle.
* @param {boolean} backfaceCulling - Whether to use backface culling or not.
* @param {Vector3} target - The target vector that is used to store the method's result.
* @return {?Vector3} The intersection point.
*/
Parameters:
a
Vector3
b
Vector3
c
Vector3
backfaceCulling
boolean
target
Vector3
Returns: Vector3
Calls:
_edge1.subVectors
_edge2.subVectors
_normal.crossVectors
this.direction.dot
_diff.subVectors
_edge2.crossVectors
_edge1.cross
_diff.dot
this.at
Internal Comments:
// Compute the offset origin, edges, and normal. (x4)
// from https://github.com/pmjoniak/GeometricTools/blob/master/GTEngine/Include/Mathematics/GteIntrRay3Triangle3.h (x4)
// Solve Q + t*D = b1*E1 + b2*E2 (Q = kDiff, D = ray direction, (x2)
// E1 = kEdge1, E2 = kEdge2, N = Cross(E1,E2)) by (x2)
// |Dot(D,N)|*b1 = sign(Dot(D,N))*Dot(D,Cross(Q,E2)) (x2)
// |Dot(D,N)|*b2 = sign(Dot(D,N))*Dot(D,Cross(E1,Q)) (x2)
// |Dot(D,N)|*t = -sign(Dot(D,N))*Dot(Q,N) (x2)
// b1 < 0, no intersection
// b2 < 0, no intersection
// b1+b2 > 1, no intersection
// Line intersects triangle, check if ray does. (x2)
// t < 0, no intersection
// Ray intersects triangle.
Code
intersectTriangle( a, b, c, backfaceCulling, target ) {
// Compute the offset origin, edges, and normal.
// from https://github.com/pmjoniak/GeometricTools/blob/master/GTEngine/Include/Mathematics/GteIntrRay3Triangle3.h
_edge1.subVectors( b, a );
_edge2.subVectors( c, a );
_normal.crossVectors( _edge1, _edge2 );
// Solve Q + t*D = b1*E1 + b2*E2 (Q = kDiff, D = ray direction,
// E1 = kEdge1, E2 = kEdge2, N = Cross(E1,E2)) by
// |Dot(D,N)|*b1 = sign(Dot(D,N))*Dot(D,Cross(Q,E2))
// |Dot(D,N)|*b2 = sign(Dot(D,N))*Dot(D,Cross(E1,Q))
// |Dot(D,N)|*t = -sign(Dot(D,N))*Dot(Q,N)
let DdN = this.direction.dot( _normal );
let sign;
if ( DdN > 0 ) {
if ( backfaceCulling ) return null;
sign = 1;
} else if ( DdN < 0 ) {
sign = - 1;
DdN = - DdN;
} else {
return null;
}
_diff.subVectors( this.origin, a );
const DdQxE2 = sign * this.direction.dot( _edge2.crossVectors( _diff, _edge2 ) );
// b1 < 0, no intersection
if ( DdQxE2 < 0 ) {
return null;
}
const DdE1xQ = sign * this.direction.dot( _edge1.cross( _diff ) );
// b2 < 0, no intersection
if ( DdE1xQ < 0 ) {
return null;
}
// b1+b2 > 1, no intersection
if ( DdQxE2 + DdE1xQ > DdN ) {
return null;
}
// Line intersects triangle, check if ray does.
const QdN = - sign * _diff.dot( _normal );
// t < 0, no intersection
if ( QdN < 0 ) {
return null;
}
// Ray intersects triangle.
return this.at( QdN / DdN, target );
}
Ray.applyMatrix4(matrix4: Matrix4): Ray
¶
JSDoc:
/**
* Transforms this ray with the given 4x4 transformation matrix.
*
* @param {Matrix4} matrix4 - The transformation matrix.
* @return {Ray} A reference to this ray.
*/
Parameters:
matrix4
Matrix4
Returns: Ray
Calls:
this.origin.applyMatrix4
this.direction.transformDirection
Code
Ray.equals(ray: Ray): boolean
¶
JSDoc:
/**
* Returns `true` if this ray is equal with the given one.
*
* @param {Ray} ray - The ray to test for equality.
* @return {boolean} Whether this ray is equal with the given one.
*/
Parameters:
ray
Ray
Returns: boolean
Calls:
ray.origin.equals
ray.direction.equals
Code
Ray.clone(): Ray
¶
JSDoc:
/**
* Returns a new ray with copied values from this instance.
*
* @return {Ray} A clone of this instance.
*/
Returns: Ray
Calls:
new this.constructor().copy
Classes¶
Ray
¶
Class Code
class Ray {
/**
* Constructs a new ray.
*
* @param {Vector3} [origin=(0,0,0)] - The origin of the ray.
* @param {Vector3} [direction=(0,0,-1)] - The (normalized) direction of the ray.
*/
constructor( origin = new Vector3(), direction = new Vector3( 0, 0, - 1 ) ) {
/**
* The origin of the ray.
*
* @type {Vector3}
*/
this.origin = origin;
/**
* The (normalized) direction of the ray.
*
* @type {Vector3}
*/
this.direction = direction;
}
/**
* Sets the ray's components by copying the given values.
*
* @param {Vector3} origin - The origin.
* @param {Vector3} direction - The direction.
* @return {Ray} A reference to this ray.
*/
set( origin, direction ) {
this.origin.copy( origin );
this.direction.copy( direction );
return this;
}
/**
* Copies the values of the given ray to this instance.
*
* @param {Ray} ray - The ray to copy.
* @return {Ray} A reference to this ray.
*/
copy( ray ) {
this.origin.copy( ray.origin );
this.direction.copy( ray.direction );
return this;
}
/**
* Returns a vector that is located at a given distance along this ray.
*
* @param {number} t - The distance along the ray to retrieve a position for.
* @param {Vector3} target - The target vector that is used to store the method's result.
* @return {Vector3} A position on the ray.
*/
at( t, target ) {
return target.copy( this.origin ).addScaledVector( this.direction, t );
}
/**
* Adjusts the direction of the ray to point at the given vector in world space.
*
* @param {Vector3} v - The target position.
* @return {Ray} A reference to this ray.
*/
lookAt( v ) {
this.direction.copy( v ).sub( this.origin ).normalize();
return this;
}
/**
* Shift the origin of this ray along its direction by the given distance.
*
* @param {number} t - The distance along the ray to interpolate.
* @return {Ray} A reference to this ray.
*/
recast( t ) {
this.origin.copy( this.at( t, _vector ) );
return this;
}
/**
* Returns the point along this ray that is closest to the given point.
*
* @param {Vector3} point - A point in 3D space to get the closet location on the ray for.
* @param {Vector3} target - The target vector that is used to store the method's result.
* @return {Vector3} The closest point on this ray.
*/
closestPointToPoint( point, target ) {
target.subVectors( point, this.origin );
const directionDistance = target.dot( this.direction );
if ( directionDistance < 0 ) {
return target.copy( this.origin );
}
return target.copy( this.origin ).addScaledVector( this.direction, directionDistance );
}
/**
* Returns the distance of the closest approach between this ray and the given point.
*
* @param {Vector3} point - A point in 3D space to compute the distance to.
* @return {number} The distance.
*/
distanceToPoint( point ) {
return Math.sqrt( this.distanceSqToPoint( point ) );
}
/**
* Returns the squared distance of the closest approach between this ray and the given point.
*
* @param {Vector3} point - A point in 3D space to compute the distance to.
* @return {number} The squared distance.
*/
distanceSqToPoint( point ) {
const directionDistance = _vector.subVectors( point, this.origin ).dot( this.direction );
// point behind the ray
if ( directionDistance < 0 ) {
return this.origin.distanceToSquared( point );
}
_vector.copy( this.origin ).addScaledVector( this.direction, directionDistance );
return _vector.distanceToSquared( point );
}
/**
* Returns the squared distance between this ray and the given line segment.
*
* @param {Vector3} v0 - The start point of the line segment.
* @param {Vector3} v1 - The end point of the line segment.
* @param {Vector3} [optionalPointOnRay] - When provided, it receives the point on this ray that is closest to the segment.
* @param {Vector3} [optionalPointOnSegment] - When provided, it receives the point on the line segment that is closest to this ray.
* @return {number} The squared distance.
*/
distanceSqToSegment( v0, v1, optionalPointOnRay, optionalPointOnSegment ) {
// from https://github.com/pmjoniak/GeometricTools/blob/master/GTEngine/Include/Mathematics/GteDistRaySegment.h
// It returns the min distance between the ray and the segment
// defined by v0 and v1
// It can also set two optional targets :
// - The closest point on the ray
// - The closest point on the segment
_segCenter.copy( v0 ).add( v1 ).multiplyScalar( 0.5 );
_segDir.copy( v1 ).sub( v0 ).normalize();
_diff.copy( this.origin ).sub( _segCenter );
const segExtent = v0.distanceTo( v1 ) * 0.5;
const a01 = - this.direction.dot( _segDir );
const b0 = _diff.dot( this.direction );
const b1 = - _diff.dot( _segDir );
const c = _diff.lengthSq();
const det = Math.abs( 1 - a01 * a01 );
let s0, s1, sqrDist, extDet;
if ( det > 0 ) {
// The ray and segment are not parallel.
s0 = a01 * b1 - b0;
s1 = a01 * b0 - b1;
extDet = segExtent * det;
if ( s0 >= 0 ) {
if ( s1 >= - extDet ) {
if ( s1 <= extDet ) {
// region 0
// Minimum at interior points of ray and segment.
const invDet = 1 / det;
s0 *= invDet;
s1 *= invDet;
sqrDist = s0 * ( s0 + a01 * s1 + 2 * b0 ) + s1 * ( a01 * s0 + s1 + 2 * b1 ) + c;
} else {
// region 1
s1 = segExtent;
s0 = Math.max( 0, - ( a01 * s1 + b0 ) );
sqrDist = - s0 * s0 + s1 * ( s1 + 2 * b1 ) + c;
}
} else {
// region 5
s1 = - segExtent;
s0 = Math.max( 0, - ( a01 * s1 + b0 ) );
sqrDist = - s0 * s0 + s1 * ( s1 + 2 * b1 ) + c;
}
} else {
if ( s1 <= - extDet ) {
// region 4
s0 = Math.max( 0, - ( - a01 * segExtent + b0 ) );
s1 = ( s0 > 0 ) ? - segExtent : Math.min( Math.max( - segExtent, - b1 ), segExtent );
sqrDist = - s0 * s0 + s1 * ( s1 + 2 * b1 ) + c;
} else if ( s1 <= extDet ) {
// region 3
s0 = 0;
s1 = Math.min( Math.max( - segExtent, - b1 ), segExtent );
sqrDist = s1 * ( s1 + 2 * b1 ) + c;
} else {
// region 2
s0 = Math.max( 0, - ( a01 * segExtent + b0 ) );
s1 = ( s0 > 0 ) ? segExtent : Math.min( Math.max( - segExtent, - b1 ), segExtent );
sqrDist = - s0 * s0 + s1 * ( s1 + 2 * b1 ) + c;
}
}
} else {
// Ray and segment are parallel.
s1 = ( a01 > 0 ) ? - segExtent : segExtent;
s0 = Math.max( 0, - ( a01 * s1 + b0 ) );
sqrDist = - s0 * s0 + s1 * ( s1 + 2 * b1 ) + c;
}
if ( optionalPointOnRay ) {
optionalPointOnRay.copy( this.origin ).addScaledVector( this.direction, s0 );
}
if ( optionalPointOnSegment ) {
optionalPointOnSegment.copy( _segCenter ).addScaledVector( _segDir, s1 );
}
return sqrDist;
}
/**
* Intersects this ray with the given sphere, returning the intersection
* point or `null` if there is no intersection.
*
* @param {Sphere} sphere - The sphere to intersect.
* @param {Vector3} target - The target vector that is used to store the method's result.
* @return {?Vector3} The intersection point.
*/
intersectSphere( sphere, target ) {
_vector.subVectors( sphere.center, this.origin );
const tca = _vector.dot( this.direction );
const d2 = _vector.dot( _vector ) - tca * tca;
const radius2 = sphere.radius * sphere.radius;
if ( d2 > radius2 ) return null;
const thc = Math.sqrt( radius2 - d2 );
// t0 = first intersect point - entrance on front of sphere
const t0 = tca - thc;
// t1 = second intersect point - exit point on back of sphere
const t1 = tca + thc;
// test to see if t1 is behind the ray - if so, return null
if ( t1 < 0 ) return null;
// test to see if t0 is behind the ray:
// if it is, the ray is inside the sphere, so return the second exit point scaled by t1,
// in order to always return an intersect point that is in front of the ray.
if ( t0 < 0 ) return this.at( t1, target );
// else t0 is in front of the ray, so return the first collision point scaled by t0
return this.at( t0, target );
}
/**
* Returns `true` if this ray intersects with the given sphere.
*
* @param {Sphere} sphere - The sphere to intersect.
* @return {boolean} Whether this ray intersects with the given sphere or not.
*/
intersectsSphere( sphere ) {
if ( sphere.radius < 0 ) return false; // handle empty spheres, see #31187
return this.distanceSqToPoint( sphere.center ) <= ( sphere.radius * sphere.radius );
}
/**
* Computes the distance from the ray's origin to the given plane. Returns `null` if the ray
* does not intersect with the plane.
*
* @param {Plane} plane - The plane to compute the distance to.
* @return {?number} Whether this ray intersects with the given sphere or not.
*/
distanceToPlane( plane ) {
const denominator = plane.normal.dot( this.direction );
if ( denominator === 0 ) {
// line is coplanar, return origin
if ( plane.distanceToPoint( this.origin ) === 0 ) {
return 0;
}
// Null is preferable to undefined since undefined means.... it is undefined
return null;
}
const t = - ( this.origin.dot( plane.normal ) + plane.constant ) / denominator;
// Return if the ray never intersects the plane
return t >= 0 ? t : null;
}
/**
* Intersects this ray with the given plane, returning the intersection
* point or `null` if there is no intersection.
*
* @param {Plane} plane - The plane to intersect.
* @param {Vector3} target - The target vector that is used to store the method's result.
* @return {?Vector3} The intersection point.
*/
intersectPlane( plane, target ) {
const t = this.distanceToPlane( plane );
if ( t === null ) {
return null;
}
return this.at( t, target );
}
/**
* Returns `true` if this ray intersects with the given plane.
*
* @param {Plane} plane - The plane to intersect.
* @return {boolean} Whether this ray intersects with the given plane or not.
*/
intersectsPlane( plane ) {
// check if the ray lies on the plane first
const distToPoint = plane.distanceToPoint( this.origin );
if ( distToPoint === 0 ) {
return true;
}
const denominator = plane.normal.dot( this.direction );
if ( denominator * distToPoint < 0 ) {
return true;
}
// ray origin is behind the plane (and is pointing behind it)
return false;
}
/**
* Intersects this ray with the given bounding box, returning the intersection
* point or `null` if there is no intersection.
*
* @param {Box3} box - The box to intersect.
* @param {Vector3} target - The target vector that is used to store the method's result.
* @return {?Vector3} The intersection point.
*/
intersectBox( box, target ) {
let tmin, tmax, tymin, tymax, tzmin, tzmax;
const invdirx = 1 / this.direction.x,
invdiry = 1 / this.direction.y,
invdirz = 1 / this.direction.z;
const origin = this.origin;
if ( invdirx >= 0 ) {
tmin = ( box.min.x - origin.x ) * invdirx;
tmax = ( box.max.x - origin.x ) * invdirx;
} else {
tmin = ( box.max.x - origin.x ) * invdirx;
tmax = ( box.min.x - origin.x ) * invdirx;
}
if ( invdiry >= 0 ) {
tymin = ( box.min.y - origin.y ) * invdiry;
tymax = ( box.max.y - origin.y ) * invdiry;
} else {
tymin = ( box.max.y - origin.y ) * invdiry;
tymax = ( box.min.y - origin.y ) * invdiry;
}
if ( ( tmin > tymax ) || ( tymin > tmax ) ) return null;
if ( tymin > tmin || isNaN( tmin ) ) tmin = tymin;
if ( tymax < tmax || isNaN( tmax ) ) tmax = tymax;
if ( invdirz >= 0 ) {
tzmin = ( box.min.z - origin.z ) * invdirz;
tzmax = ( box.max.z - origin.z ) * invdirz;
} else {
tzmin = ( box.max.z - origin.z ) * invdirz;
tzmax = ( box.min.z - origin.z ) * invdirz;
}
if ( ( tmin > tzmax ) || ( tzmin > tmax ) ) return null;
if ( tzmin > tmin || tmin !== tmin ) tmin = tzmin;
if ( tzmax < tmax || tmax !== tmax ) tmax = tzmax;
//return point closest to the ray (positive side)
if ( tmax < 0 ) return null;
return this.at( tmin >= 0 ? tmin : tmax, target );
}
/**
* Returns `true` if this ray intersects with the given box.
*
* @param {Box3} box - The box to intersect.
* @return {boolean} Whether this ray intersects with the given box or not.
*/
intersectsBox( box ) {
return this.intersectBox( box, _vector ) !== null;
}
/**
* Intersects this ray with the given triangle, returning the intersection
* point or `null` if there is no intersection.
*
* @param {Vector3} a - The first vertex of the triangle.
* @param {Vector3} b - The second vertex of the triangle.
* @param {Vector3} c - The third vertex of the triangle.
* @param {boolean} backfaceCulling - Whether to use backface culling or not.
* @param {Vector3} target - The target vector that is used to store the method's result.
* @return {?Vector3} The intersection point.
*/
intersectTriangle( a, b, c, backfaceCulling, target ) {
// Compute the offset origin, edges, and normal.
// from https://github.com/pmjoniak/GeometricTools/blob/master/GTEngine/Include/Mathematics/GteIntrRay3Triangle3.h
_edge1.subVectors( b, a );
_edge2.subVectors( c, a );
_normal.crossVectors( _edge1, _edge2 );
// Solve Q + t*D = b1*E1 + b2*E2 (Q = kDiff, D = ray direction,
// E1 = kEdge1, E2 = kEdge2, N = Cross(E1,E2)) by
// |Dot(D,N)|*b1 = sign(Dot(D,N))*Dot(D,Cross(Q,E2))
// |Dot(D,N)|*b2 = sign(Dot(D,N))*Dot(D,Cross(E1,Q))
// |Dot(D,N)|*t = -sign(Dot(D,N))*Dot(Q,N)
let DdN = this.direction.dot( _normal );
let sign;
if ( DdN > 0 ) {
if ( backfaceCulling ) return null;
sign = 1;
} else if ( DdN < 0 ) {
sign = - 1;
DdN = - DdN;
} else {
return null;
}
_diff.subVectors( this.origin, a );
const DdQxE2 = sign * this.direction.dot( _edge2.crossVectors( _diff, _edge2 ) );
// b1 < 0, no intersection
if ( DdQxE2 < 0 ) {
return null;
}
const DdE1xQ = sign * this.direction.dot( _edge1.cross( _diff ) );
// b2 < 0, no intersection
if ( DdE1xQ < 0 ) {
return null;
}
// b1+b2 > 1, no intersection
if ( DdQxE2 + DdE1xQ > DdN ) {
return null;
}
// Line intersects triangle, check if ray does.
const QdN = - sign * _diff.dot( _normal );
// t < 0, no intersection
if ( QdN < 0 ) {
return null;
}
// Ray intersects triangle.
return this.at( QdN / DdN, target );
}
/**
* Transforms this ray with the given 4x4 transformation matrix.
*
* @param {Matrix4} matrix4 - The transformation matrix.
* @return {Ray} A reference to this ray.
*/
applyMatrix4( matrix4 ) {
this.origin.applyMatrix4( matrix4 );
this.direction.transformDirection( matrix4 );
return this;
}
/**
* Returns `true` if this ray is equal with the given one.
*
* @param {Ray} ray - The ray to test for equality.
* @return {boolean} Whether this ray is equal with the given one.
*/
equals( ray ) {
return ray.origin.equals( this.origin ) && ray.direction.equals( this.direction );
}
/**
* Returns a new ray with copied values from this instance.
*
* @return {Ray} A clone of this instance.
*/
clone() {
return new this.constructor().copy( this );
}
}
Methods¶
set(origin: Vector3, direction: Vector3): Ray
¶
Code
copy(ray: Ray): Ray
¶
Code
at(t: number, target: Vector3): Vector3
¶
lookAt(v: Vector3): Ray
¶
recast(t: number): Ray
¶
closestPointToPoint(point: Vector3, target: Vector3): Vector3
¶
Code
distanceToPoint(point: Vector3): number
¶
distanceSqToPoint(point: Vector3): number
¶
Code
distanceSqToPoint( point ) {
const directionDistance = _vector.subVectors( point, this.origin ).dot( this.direction );
// point behind the ray
if ( directionDistance < 0 ) {
return this.origin.distanceToSquared( point );
}
_vector.copy( this.origin ).addScaledVector( this.direction, directionDistance );
return _vector.distanceToSquared( point );
}
distanceSqToSegment(v0: Vector3, v1: Vector3, optionalPointOnRay: Vector3, optionalPointOnSegment: Vector3): number
¶
Code
distanceSqToSegment( v0, v1, optionalPointOnRay, optionalPointOnSegment ) {
// from https://github.com/pmjoniak/GeometricTools/blob/master/GTEngine/Include/Mathematics/GteDistRaySegment.h
// It returns the min distance between the ray and the segment
// defined by v0 and v1
// It can also set two optional targets :
// - The closest point on the ray
// - The closest point on the segment
_segCenter.copy( v0 ).add( v1 ).multiplyScalar( 0.5 );
_segDir.copy( v1 ).sub( v0 ).normalize();
_diff.copy( this.origin ).sub( _segCenter );
const segExtent = v0.distanceTo( v1 ) * 0.5;
const a01 = - this.direction.dot( _segDir );
const b0 = _diff.dot( this.direction );
const b1 = - _diff.dot( _segDir );
const c = _diff.lengthSq();
const det = Math.abs( 1 - a01 * a01 );
let s0, s1, sqrDist, extDet;
if ( det > 0 ) {
// The ray and segment are not parallel.
s0 = a01 * b1 - b0;
s1 = a01 * b0 - b1;
extDet = segExtent * det;
if ( s0 >= 0 ) {
if ( s1 >= - extDet ) {
if ( s1 <= extDet ) {
// region 0
// Minimum at interior points of ray and segment.
const invDet = 1 / det;
s0 *= invDet;
s1 *= invDet;
sqrDist = s0 * ( s0 + a01 * s1 + 2 * b0 ) + s1 * ( a01 * s0 + s1 + 2 * b1 ) + c;
} else {
// region 1
s1 = segExtent;
s0 = Math.max( 0, - ( a01 * s1 + b0 ) );
sqrDist = - s0 * s0 + s1 * ( s1 + 2 * b1 ) + c;
}
} else {
// region 5
s1 = - segExtent;
s0 = Math.max( 0, - ( a01 * s1 + b0 ) );
sqrDist = - s0 * s0 + s1 * ( s1 + 2 * b1 ) + c;
}
} else {
if ( s1 <= - extDet ) {
// region 4
s0 = Math.max( 0, - ( - a01 * segExtent + b0 ) );
s1 = ( s0 > 0 ) ? - segExtent : Math.min( Math.max( - segExtent, - b1 ), segExtent );
sqrDist = - s0 * s0 + s1 * ( s1 + 2 * b1 ) + c;
} else if ( s1 <= extDet ) {
// region 3
s0 = 0;
s1 = Math.min( Math.max( - segExtent, - b1 ), segExtent );
sqrDist = s1 * ( s1 + 2 * b1 ) + c;
} else {
// region 2
s0 = Math.max( 0, - ( a01 * segExtent + b0 ) );
s1 = ( s0 > 0 ) ? segExtent : Math.min( Math.max( - segExtent, - b1 ), segExtent );
sqrDist = - s0 * s0 + s1 * ( s1 + 2 * b1 ) + c;
}
}
} else {
// Ray and segment are parallel.
s1 = ( a01 > 0 ) ? - segExtent : segExtent;
s0 = Math.max( 0, - ( a01 * s1 + b0 ) );
sqrDist = - s0 * s0 + s1 * ( s1 + 2 * b1 ) + c;
}
if ( optionalPointOnRay ) {
optionalPointOnRay.copy( this.origin ).addScaledVector( this.direction, s0 );
}
if ( optionalPointOnSegment ) {
optionalPointOnSegment.copy( _segCenter ).addScaledVector( _segDir, s1 );
}
return sqrDist;
}
intersectSphere(sphere: Sphere, target: Vector3): Vector3
¶
Code
intersectSphere( sphere, target ) {
_vector.subVectors( sphere.center, this.origin );
const tca = _vector.dot( this.direction );
const d2 = _vector.dot( _vector ) - tca * tca;
const radius2 = sphere.radius * sphere.radius;
if ( d2 > radius2 ) return null;
const thc = Math.sqrt( radius2 - d2 );
// t0 = first intersect point - entrance on front of sphere
const t0 = tca - thc;
// t1 = second intersect point - exit point on back of sphere
const t1 = tca + thc;
// test to see if t1 is behind the ray - if so, return null
if ( t1 < 0 ) return null;
// test to see if t0 is behind the ray:
// if it is, the ray is inside the sphere, so return the second exit point scaled by t1,
// in order to always return an intersect point that is in front of the ray.
if ( t0 < 0 ) return this.at( t1, target );
// else t0 is in front of the ray, so return the first collision point scaled by t0
return this.at( t0, target );
}
intersectsSphere(sphere: Sphere): boolean
¶
Code
distanceToPlane(plane: Plane): number
¶
Code
distanceToPlane( plane ) {
const denominator = plane.normal.dot( this.direction );
if ( denominator === 0 ) {
// line is coplanar, return origin
if ( plane.distanceToPoint( this.origin ) === 0 ) {
return 0;
}
// Null is preferable to undefined since undefined means.... it is undefined
return null;
}
const t = - ( this.origin.dot( plane.normal ) + plane.constant ) / denominator;
// Return if the ray never intersects the plane
return t >= 0 ? t : null;
}
intersectPlane(plane: Plane, target: Vector3): Vector3
¶
Code
intersectsPlane(plane: Plane): boolean
¶
Code
intersectsPlane( plane ) {
// check if the ray lies on the plane first
const distToPoint = plane.distanceToPoint( this.origin );
if ( distToPoint === 0 ) {
return true;
}
const denominator = plane.normal.dot( this.direction );
if ( denominator * distToPoint < 0 ) {
return true;
}
// ray origin is behind the plane (and is pointing behind it)
return false;
}
intersectBox(box: Box3, target: Vector3): Vector3
¶
Code
intersectBox( box, target ) {
let tmin, tmax, tymin, tymax, tzmin, tzmax;
const invdirx = 1 / this.direction.x,
invdiry = 1 / this.direction.y,
invdirz = 1 / this.direction.z;
const origin = this.origin;
if ( invdirx >= 0 ) {
tmin = ( box.min.x - origin.x ) * invdirx;
tmax = ( box.max.x - origin.x ) * invdirx;
} else {
tmin = ( box.max.x - origin.x ) * invdirx;
tmax = ( box.min.x - origin.x ) * invdirx;
}
if ( invdiry >= 0 ) {
tymin = ( box.min.y - origin.y ) * invdiry;
tymax = ( box.max.y - origin.y ) * invdiry;
} else {
tymin = ( box.max.y - origin.y ) * invdiry;
tymax = ( box.min.y - origin.y ) * invdiry;
}
if ( ( tmin > tymax ) || ( tymin > tmax ) ) return null;
if ( tymin > tmin || isNaN( tmin ) ) tmin = tymin;
if ( tymax < tmax || isNaN( tmax ) ) tmax = tymax;
if ( invdirz >= 0 ) {
tzmin = ( box.min.z - origin.z ) * invdirz;
tzmax = ( box.max.z - origin.z ) * invdirz;
} else {
tzmin = ( box.max.z - origin.z ) * invdirz;
tzmax = ( box.min.z - origin.z ) * invdirz;
}
if ( ( tmin > tzmax ) || ( tzmin > tmax ) ) return null;
if ( tzmin > tmin || tmin !== tmin ) tmin = tzmin;
if ( tzmax < tmax || tmax !== tmax ) tmax = tzmax;
//return point closest to the ray (positive side)
if ( tmax < 0 ) return null;
return this.at( tmin >= 0 ? tmin : tmax, target );
}
intersectsBox(box: Box3): boolean
¶
intersectTriangle(a: Vector3, b: Vector3, c: Vector3, backfaceCulling: boolean, target: Vector3): Vector3
¶
Code
intersectTriangle( a, b, c, backfaceCulling, target ) {
// Compute the offset origin, edges, and normal.
// from https://github.com/pmjoniak/GeometricTools/blob/master/GTEngine/Include/Mathematics/GteIntrRay3Triangle3.h
_edge1.subVectors( b, a );
_edge2.subVectors( c, a );
_normal.crossVectors( _edge1, _edge2 );
// Solve Q + t*D = b1*E1 + b2*E2 (Q = kDiff, D = ray direction,
// E1 = kEdge1, E2 = kEdge2, N = Cross(E1,E2)) by
// |Dot(D,N)|*b1 = sign(Dot(D,N))*Dot(D,Cross(Q,E2))
// |Dot(D,N)|*b2 = sign(Dot(D,N))*Dot(D,Cross(E1,Q))
// |Dot(D,N)|*t = -sign(Dot(D,N))*Dot(Q,N)
let DdN = this.direction.dot( _normal );
let sign;
if ( DdN > 0 ) {
if ( backfaceCulling ) return null;
sign = 1;
} else if ( DdN < 0 ) {
sign = - 1;
DdN = - DdN;
} else {
return null;
}
_diff.subVectors( this.origin, a );
const DdQxE2 = sign * this.direction.dot( _edge2.crossVectors( _diff, _edge2 ) );
// b1 < 0, no intersection
if ( DdQxE2 < 0 ) {
return null;
}
const DdE1xQ = sign * this.direction.dot( _edge1.cross( _diff ) );
// b2 < 0, no intersection
if ( DdE1xQ < 0 ) {
return null;
}
// b1+b2 > 1, no intersection
if ( DdQxE2 + DdE1xQ > DdN ) {
return null;
}
// Line intersects triangle, check if ray does.
const QdN = - sign * _diff.dot( _normal );
// t < 0, no intersection
if ( QdN < 0 ) {
return null;
}
// Ray intersects triangle.
return this.at( QdN / DdN, target );
}