import { Vector3 } from './Vector3.js'; const _vector = /*@__PURE__*/ new Vector3(); const _segCenter = /*@__PURE__*/ new Vector3(); const _segDir = /*@__PURE__*/ new Vector3(); const _diff = /*@__PURE__*/ new Vector3(); const _edge1 = /*@__PURE__*/ new Vector3(); const _edge2 = /*@__PURE__*/ new Vector3(); const _normal = /*@__PURE__*/ new Vector3(); class Ray { constructor(origin = new Vector3(), direction = new Vector3(0, 0, -1)) { this.origin = origin; this.direction = direction; } set(origin, direction) { this.origin.copy(origin); this.direction.copy(direction); return this; } copy(ray) { this.origin.copy(ray.origin); this.direction.copy(ray.direction); return this; } at(t, target) { return target.copy(this.direction).multiplyScalar(t).add(this.origin); } lookAt(v) { this.direction.copy(v).sub(this.origin).normalize(); return this; } recast(t) { this.origin.copy(this.at(t, _vector)); return this; } 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.direction).multiplyScalar(directionDistance).add(this.origin); } distanceToPoint(point) { return Math.sqrt(this.distanceSqToPoint(point)); } 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.direction).multiplyScalar(directionDistance).add(this.origin); return _vector.distanceToSquared(point); } 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.direction).multiplyScalar(s0).add(this.origin); } if (optionalPointOnSegment) { optionalPointOnSegment.copy(_segDir).multiplyScalar(s1).add(_segCenter); } return sqrDist; } 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 both t0 and t1 are behind the ray - if so, return null if (t0 < 0 && 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) { return this.distanceSqToPoint(sphere.center) <= sphere.radius * sphere.radius; } 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, target) { const t = this.distanceToPlane(plane); if (t === null) { return null; } return this.at(t, target); } 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, 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; // These lines also handle the case where tmin or tmax is NaN // (result of 0 * Infinity). x !== x returns true if x is NaN if (tymin > tmin || tmin !== tmin) tmin = tymin; if (tymax < tmax || tmax !== 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) { return this.intersectBox(box, _vector) !== null; } 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); } applyMatrix4(matrix4) { this.origin.applyMatrix4(matrix4); this.direction.transformDirection(matrix4); return this; } equals(ray) { return ray.origin.equals(this.origin) && ray.direction.equals(this.direction); } clone() { return new this.constructor().copy(this); } } export { Ray };