Ray.js 9.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493
  1. import { Vector3 } from './Vector3.js';
  2. const _vector = /*@__PURE__*/ new Vector3();
  3. const _segCenter = /*@__PURE__*/ new Vector3();
  4. const _segDir = /*@__PURE__*/ new Vector3();
  5. const _diff = /*@__PURE__*/ new Vector3();
  6. const _edge1 = /*@__PURE__*/ new Vector3();
  7. const _edge2 = /*@__PURE__*/ new Vector3();
  8. const _normal = /*@__PURE__*/ new Vector3();
  9. class Ray {
  10. constructor( origin = new Vector3(), direction = new Vector3( 0, 0, - 1 ) ) {
  11. this.origin = origin;
  12. this.direction = direction;
  13. }
  14. set( origin, direction ) {
  15. this.origin.copy( origin );
  16. this.direction.copy( direction );
  17. return this;
  18. }
  19. copy( ray ) {
  20. this.origin.copy( ray.origin );
  21. this.direction.copy( ray.direction );
  22. return this;
  23. }
  24. at( t, target ) {
  25. return target.copy( this.direction ).multiplyScalar( t ).add( this.origin );
  26. }
  27. lookAt( v ) {
  28. this.direction.copy( v ).sub( this.origin ).normalize();
  29. return this;
  30. }
  31. recast( t ) {
  32. this.origin.copy( this.at( t, _vector ) );
  33. return this;
  34. }
  35. closestPointToPoint( point, target ) {
  36. target.subVectors( point, this.origin );
  37. const directionDistance = target.dot( this.direction );
  38. if ( directionDistance < 0 ) {
  39. return target.copy( this.origin );
  40. }
  41. return target.copy( this.direction ).multiplyScalar( directionDistance ).add( this.origin );
  42. }
  43. distanceToPoint( point ) {
  44. return Math.sqrt( this.distanceSqToPoint( point ) );
  45. }
  46. distanceSqToPoint( point ) {
  47. const directionDistance = _vector.subVectors( point, this.origin ).dot( this.direction );
  48. // point behind the ray
  49. if ( directionDistance < 0 ) {
  50. return this.origin.distanceToSquared( point );
  51. }
  52. _vector.copy( this.direction ).multiplyScalar( directionDistance ).add( this.origin );
  53. return _vector.distanceToSquared( point );
  54. }
  55. distanceSqToSegment( v0, v1, optionalPointOnRay, optionalPointOnSegment ) {
  56. // from https://github.com/pmjoniak/GeometricTools/blob/master/GTEngine/Include/Mathematics/GteDistRaySegment.h
  57. // It returns the min distance between the ray and the segment
  58. // defined by v0 and v1
  59. // It can also set two optional targets :
  60. // - The closest point on the ray
  61. // - The closest point on the segment
  62. _segCenter.copy( v0 ).add( v1 ).multiplyScalar( 0.5 );
  63. _segDir.copy( v1 ).sub( v0 ).normalize();
  64. _diff.copy( this.origin ).sub( _segCenter );
  65. const segExtent = v0.distanceTo( v1 ) * 0.5;
  66. const a01 = - this.direction.dot( _segDir );
  67. const b0 = _diff.dot( this.direction );
  68. const b1 = - _diff.dot( _segDir );
  69. const c = _diff.lengthSq();
  70. const det = Math.abs( 1 - a01 * a01 );
  71. let s0, s1, sqrDist, extDet;
  72. if ( det > 0 ) {
  73. // The ray and segment are not parallel.
  74. s0 = a01 * b1 - b0;
  75. s1 = a01 * b0 - b1;
  76. extDet = segExtent * det;
  77. if ( s0 >= 0 ) {
  78. if ( s1 >= - extDet ) {
  79. if ( s1 <= extDet ) {
  80. // region 0
  81. // Minimum at interior points of ray and segment.
  82. const invDet = 1 / det;
  83. s0 *= invDet;
  84. s1 *= invDet;
  85. sqrDist = s0 * ( s0 + a01 * s1 + 2 * b0 ) + s1 * ( a01 * s0 + s1 + 2 * b1 ) + c;
  86. } else {
  87. // region 1
  88. s1 = segExtent;
  89. s0 = Math.max( 0, - ( a01 * s1 + b0 ) );
  90. sqrDist = - s0 * s0 + s1 * ( s1 + 2 * b1 ) + c;
  91. }
  92. } else {
  93. // region 5
  94. s1 = - segExtent;
  95. s0 = Math.max( 0, - ( a01 * s1 + b0 ) );
  96. sqrDist = - s0 * s0 + s1 * ( s1 + 2 * b1 ) + c;
  97. }
  98. } else {
  99. if ( s1 <= - extDet ) {
  100. // region 4
  101. s0 = Math.max( 0, - ( - a01 * segExtent + b0 ) );
  102. s1 = ( s0 > 0 ) ? - segExtent : Math.min( Math.max( - segExtent, - b1 ), segExtent );
  103. sqrDist = - s0 * s0 + s1 * ( s1 + 2 * b1 ) + c;
  104. } else if ( s1 <= extDet ) {
  105. // region 3
  106. s0 = 0;
  107. s1 = Math.min( Math.max( - segExtent, - b1 ), segExtent );
  108. sqrDist = s1 * ( s1 + 2 * b1 ) + c;
  109. } else {
  110. // region 2
  111. s0 = Math.max( 0, - ( a01 * segExtent + b0 ) );
  112. s1 = ( s0 > 0 ) ? segExtent : Math.min( Math.max( - segExtent, - b1 ), segExtent );
  113. sqrDist = - s0 * s0 + s1 * ( s1 + 2 * b1 ) + c;
  114. }
  115. }
  116. } else {
  117. // Ray and segment are parallel.
  118. s1 = ( a01 > 0 ) ? - segExtent : segExtent;
  119. s0 = Math.max( 0, - ( a01 * s1 + b0 ) );
  120. sqrDist = - s0 * s0 + s1 * ( s1 + 2 * b1 ) + c;
  121. }
  122. if ( optionalPointOnRay ) {
  123. optionalPointOnRay.copy( this.direction ).multiplyScalar( s0 ).add( this.origin );
  124. }
  125. if ( optionalPointOnSegment ) {
  126. optionalPointOnSegment.copy( _segDir ).multiplyScalar( s1 ).add( _segCenter );
  127. }
  128. return sqrDist;
  129. }
  130. intersectSphere( sphere, target ) {
  131. _vector.subVectors( sphere.center, this.origin );
  132. const tca = _vector.dot( this.direction );
  133. const d2 = _vector.dot( _vector ) - tca * tca;
  134. const radius2 = sphere.radius * sphere.radius;
  135. if ( d2 > radius2 ) return null;
  136. const thc = Math.sqrt( radius2 - d2 );
  137. // t0 = first intersect point - entrance on front of sphere
  138. const t0 = tca - thc;
  139. // t1 = second intersect point - exit point on back of sphere
  140. const t1 = tca + thc;
  141. // test to see if t1 is behind the ray - if so, return null
  142. if ( t1 < 0 ) return null;
  143. // test to see if t0 is behind the ray:
  144. // if it is, the ray is inside the sphere, so return the second exit point scaled by t1,
  145. // in order to always return an intersect point that is in front of the ray.
  146. if ( t0 < 0 ) return this.at( t1, target );
  147. // else t0 is in front of the ray, so return the first collision point scaled by t0
  148. return this.at( t0, target );
  149. }
  150. intersectsSphere( sphere ) {
  151. return this.distanceSqToPoint( sphere.center ) <= ( sphere.radius * sphere.radius );
  152. }
  153. distanceToPlane( plane ) {
  154. const denominator = plane.normal.dot( this.direction );
  155. if ( denominator === 0 ) {
  156. // line is coplanar, return origin
  157. if ( plane.distanceToPoint( this.origin ) === 0 ) {
  158. return 0;
  159. }
  160. // Null is preferable to undefined since undefined means.... it is undefined
  161. return null;
  162. }
  163. const t = - ( this.origin.dot( plane.normal ) + plane.constant ) / denominator;
  164. // Return if the ray never intersects the plane
  165. return t >= 0 ? t : null;
  166. }
  167. intersectPlane( plane, target ) {
  168. const t = this.distanceToPlane( plane );
  169. if ( t === null ) {
  170. return null;
  171. }
  172. return this.at( t, target );
  173. }
  174. intersectsPlane( plane ) {
  175. // check if the ray lies on the plane first
  176. const distToPoint = plane.distanceToPoint( this.origin );
  177. if ( distToPoint === 0 ) {
  178. return true;
  179. }
  180. const denominator = plane.normal.dot( this.direction );
  181. if ( denominator * distToPoint < 0 ) {
  182. return true;
  183. }
  184. // ray origin is behind the plane (and is pointing behind it)
  185. return false;
  186. }
  187. intersectBox( box, target ) {
  188. let tmin, tmax, tymin, tymax, tzmin, tzmax;
  189. const invdirx = 1 / this.direction.x,
  190. invdiry = 1 / this.direction.y,
  191. invdirz = 1 / this.direction.z;
  192. const origin = this.origin;
  193. if ( invdirx >= 0 ) {
  194. tmin = ( box.min.x - origin.x ) * invdirx;
  195. tmax = ( box.max.x - origin.x ) * invdirx;
  196. } else {
  197. tmin = ( box.max.x - origin.x ) * invdirx;
  198. tmax = ( box.min.x - origin.x ) * invdirx;
  199. }
  200. if ( invdiry >= 0 ) {
  201. tymin = ( box.min.y - origin.y ) * invdiry;
  202. tymax = ( box.max.y - origin.y ) * invdiry;
  203. } else {
  204. tymin = ( box.max.y - origin.y ) * invdiry;
  205. tymax = ( box.min.y - origin.y ) * invdiry;
  206. }
  207. if ( ( tmin > tymax ) || ( tymin > tmax ) ) return null;
  208. if ( tymin > tmin || isNaN( tmin ) ) tmin = tymin;
  209. if ( tymax < tmax || isNaN( tmax ) ) tmax = tymax;
  210. if ( invdirz >= 0 ) {
  211. tzmin = ( box.min.z - origin.z ) * invdirz;
  212. tzmax = ( box.max.z - origin.z ) * invdirz;
  213. } else {
  214. tzmin = ( box.max.z - origin.z ) * invdirz;
  215. tzmax = ( box.min.z - origin.z ) * invdirz;
  216. }
  217. if ( ( tmin > tzmax ) || ( tzmin > tmax ) ) return null;
  218. if ( tzmin > tmin || tmin !== tmin ) tmin = tzmin;
  219. if ( tzmax < tmax || tmax !== tmax ) tmax = tzmax;
  220. //return point closest to the ray (positive side)
  221. if ( tmax < 0 ) return null;
  222. return this.at( tmin >= 0 ? tmin : tmax, target );
  223. }
  224. intersectsBox( box ) {
  225. return this.intersectBox( box, _vector ) !== null;
  226. }
  227. intersectTriangle( a, b, c, backfaceCulling, target ) {
  228. // Compute the offset origin, edges, and normal.
  229. // from https://github.com/pmjoniak/GeometricTools/blob/master/GTEngine/Include/Mathematics/GteIntrRay3Triangle3.h
  230. _edge1.subVectors( b, a );
  231. _edge2.subVectors( c, a );
  232. _normal.crossVectors( _edge1, _edge2 );
  233. // Solve Q + t*D = b1*E1 + b2*E2 (Q = kDiff, D = ray direction,
  234. // E1 = kEdge1, E2 = kEdge2, N = Cross(E1,E2)) by
  235. // |Dot(D,N)|*b1 = sign(Dot(D,N))*Dot(D,Cross(Q,E2))
  236. // |Dot(D,N)|*b2 = sign(Dot(D,N))*Dot(D,Cross(E1,Q))
  237. // |Dot(D,N)|*t = -sign(Dot(D,N))*Dot(Q,N)
  238. let DdN = this.direction.dot( _normal );
  239. let sign;
  240. if ( DdN > 0 ) {
  241. if ( backfaceCulling ) return null;
  242. sign = 1;
  243. } else if ( DdN < 0 ) {
  244. sign = - 1;
  245. DdN = - DdN;
  246. } else {
  247. return null;
  248. }
  249. _diff.subVectors( this.origin, a );
  250. const DdQxE2 = sign * this.direction.dot( _edge2.crossVectors( _diff, _edge2 ) );
  251. // b1 < 0, no intersection
  252. if ( DdQxE2 < 0 ) {
  253. return null;
  254. }
  255. const DdE1xQ = sign * this.direction.dot( _edge1.cross( _diff ) );
  256. // b2 < 0, no intersection
  257. if ( DdE1xQ < 0 ) {
  258. return null;
  259. }
  260. // b1+b2 > 1, no intersection
  261. if ( DdQxE2 + DdE1xQ > DdN ) {
  262. return null;
  263. }
  264. // Line intersects triangle, check if ray does.
  265. const QdN = - sign * _diff.dot( _normal );
  266. // t < 0, no intersection
  267. if ( QdN < 0 ) {
  268. return null;
  269. }
  270. // Ray intersects triangle.
  271. return this.at( QdN / DdN, target );
  272. }
  273. applyMatrix4( matrix4 ) {
  274. this.origin.applyMatrix4( matrix4 );
  275. this.direction.transformDirection( matrix4 );
  276. return this;
  277. }
  278. equals( ray ) {
  279. return ray.origin.equals( this.origin ) && ray.direction.equals( this.direction );
  280. }
  281. clone() {
  282. return new this.constructor().copy( this );
  283. }
  284. }
  285. export { Ray };