Spaces:
Running
Running
| 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 }; | |