OBB.js 10.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423
  1. import {
  2. Box3,
  3. MathUtils,
  4. Matrix4,
  5. Matrix3,
  6. Ray,
  7. Vector3
  8. } from 'three';
  9. // module scope helper variables
  10. const a = {
  11. c: null, // center
  12. u: [ new Vector3(), new Vector3(), new Vector3() ], // basis vectors
  13. e: [] // half width
  14. };
  15. const b = {
  16. c: null, // center
  17. u: [ new Vector3(), new Vector3(), new Vector3() ], // basis vectors
  18. e: [] // half width
  19. };
  20. const R = [[], [], []];
  21. const AbsR = [[], [], []];
  22. const t = [];
  23. const xAxis = new Vector3();
  24. const yAxis = new Vector3();
  25. const zAxis = new Vector3();
  26. const v1 = new Vector3();
  27. const size = new Vector3();
  28. const closestPoint = new Vector3();
  29. const rotationMatrix = new Matrix3();
  30. const aabb = new Box3();
  31. const matrix = new Matrix4();
  32. const inverse = new Matrix4();
  33. const localRay = new Ray();
  34. // OBB
  35. class OBB {
  36. constructor( center = new Vector3(), halfSize = new Vector3(), rotation = new Matrix3() ) {
  37. this.center = center;
  38. this.halfSize = halfSize;
  39. this.rotation = rotation;
  40. }
  41. set( center, halfSize, rotation ) {
  42. this.center = center;
  43. this.halfSize = halfSize;
  44. this.rotation = rotation;
  45. return this;
  46. }
  47. copy( obb ) {
  48. this.center.copy( obb.center );
  49. this.halfSize.copy( obb.halfSize );
  50. this.rotation.copy( obb.rotation );
  51. return this;
  52. }
  53. clone() {
  54. return new this.constructor().copy( this );
  55. }
  56. getSize( result ) {
  57. return result.copy( this.halfSize ).multiplyScalar( 2 );
  58. }
  59. /**
  60. * Reference: Closest Point on OBB to Point in Real-Time Collision Detection
  61. * by Christer Ericson (chapter 5.1.4)
  62. */
  63. clampPoint( point, result ) {
  64. const halfSize = this.halfSize;
  65. v1.subVectors( point, this.center );
  66. this.rotation.extractBasis( xAxis, yAxis, zAxis );
  67. // start at the center position of the OBB
  68. result.copy( this.center );
  69. // project the target onto the OBB axes and walk towards that point
  70. const x = MathUtils.clamp( v1.dot( xAxis ), - halfSize.x, halfSize.x );
  71. result.add( xAxis.multiplyScalar( x ) );
  72. const y = MathUtils.clamp( v1.dot( yAxis ), - halfSize.y, halfSize.y );
  73. result.add( yAxis.multiplyScalar( y ) );
  74. const z = MathUtils.clamp( v1.dot( zAxis ), - halfSize.z, halfSize.z );
  75. result.add( zAxis.multiplyScalar( z ) );
  76. return result;
  77. }
  78. containsPoint( point ) {
  79. v1.subVectors( point, this.center );
  80. this.rotation.extractBasis( xAxis, yAxis, zAxis );
  81. // project v1 onto each axis and check if these points lie inside the OBB
  82. return Math.abs( v1.dot( xAxis ) ) <= this.halfSize.x &&
  83. Math.abs( v1.dot( yAxis ) ) <= this.halfSize.y &&
  84. Math.abs( v1.dot( zAxis ) ) <= this.halfSize.z;
  85. }
  86. intersectsBox3( box3 ) {
  87. return this.intersectsOBB( obb.fromBox3( box3 ) );
  88. }
  89. intersectsSphere( sphere ) {
  90. // find the point on the OBB closest to the sphere center
  91. this.clampPoint( sphere.center, closestPoint );
  92. // if that point is inside the sphere, the OBB and sphere intersect
  93. return closestPoint.distanceToSquared( sphere.center ) <= ( sphere.radius * sphere.radius );
  94. }
  95. /**
  96. * Reference: OBB-OBB Intersection in Real-Time Collision Detection
  97. * by Christer Ericson (chapter 4.4.1)
  98. *
  99. */
  100. intersectsOBB( obb, epsilon = Number.EPSILON ) {
  101. // prepare data structures (the code uses the same nomenclature like the reference)
  102. a.c = this.center;
  103. a.e[ 0 ] = this.halfSize.x;
  104. a.e[ 1 ] = this.halfSize.y;
  105. a.e[ 2 ] = this.halfSize.z;
  106. this.rotation.extractBasis( a.u[ 0 ], a.u[ 1 ], a.u[ 2 ] );
  107. b.c = obb.center;
  108. b.e[ 0 ] = obb.halfSize.x;
  109. b.e[ 1 ] = obb.halfSize.y;
  110. b.e[ 2 ] = obb.halfSize.z;
  111. obb.rotation.extractBasis( b.u[ 0 ], b.u[ 1 ], b.u[ 2 ] );
  112. // compute rotation matrix expressing b in a's coordinate frame
  113. for ( let i = 0; i < 3; i ++ ) {
  114. for ( let j = 0; j < 3; j ++ ) {
  115. R[ i ][ j ] = a.u[ i ].dot( b.u[ j ] );
  116. }
  117. }
  118. // compute translation vector
  119. v1.subVectors( b.c, a.c );
  120. // bring translation into a's coordinate frame
  121. t[ 0 ] = v1.dot( a.u[ 0 ] );
  122. t[ 1 ] = v1.dot( a.u[ 1 ] );
  123. t[ 2 ] = v1.dot( a.u[ 2 ] );
  124. // compute common subexpressions. Add in an epsilon term to
  125. // counteract arithmetic errors when two edges are parallel and
  126. // their cross product is (near) null
  127. for ( let i = 0; i < 3; i ++ ) {
  128. for ( let j = 0; j < 3; j ++ ) {
  129. AbsR[ i ][ j ] = Math.abs( R[ i ][ j ] ) + epsilon;
  130. }
  131. }
  132. let ra, rb;
  133. // test axes L = A0, L = A1, L = A2
  134. for ( let i = 0; i < 3; i ++ ) {
  135. ra = a.e[ i ];
  136. rb = b.e[ 0 ] * AbsR[ i ][ 0 ] + b.e[ 1 ] * AbsR[ i ][ 1 ] + b.e[ 2 ] * AbsR[ i ][ 2 ];
  137. if ( Math.abs( t[ i ] ) > ra + rb ) return false;
  138. }
  139. // test axes L = B0, L = B1, L = B2
  140. for ( let i = 0; i < 3; i ++ ) {
  141. ra = a.e[ 0 ] * AbsR[ 0 ][ i ] + a.e[ 1 ] * AbsR[ 1 ][ i ] + a.e[ 2 ] * AbsR[ 2 ][ i ];
  142. rb = b.e[ i ];
  143. if ( Math.abs( t[ 0 ] * R[ 0 ][ i ] + t[ 1 ] * R[ 1 ][ i ] + t[ 2 ] * R[ 2 ][ i ] ) > ra + rb ) return false;
  144. }
  145. // test axis L = A0 x B0
  146. ra = a.e[ 1 ] * AbsR[ 2 ][ 0 ] + a.e[ 2 ] * AbsR[ 1 ][ 0 ];
  147. rb = b.e[ 1 ] * AbsR[ 0 ][ 2 ] + b.e[ 2 ] * AbsR[ 0 ][ 1 ];
  148. if ( Math.abs( t[ 2 ] * R[ 1 ][ 0 ] - t[ 1 ] * R[ 2 ][ 0 ] ) > ra + rb ) return false;
  149. // test axis L = A0 x B1
  150. ra = a.e[ 1 ] * AbsR[ 2 ][ 1 ] + a.e[ 2 ] * AbsR[ 1 ][ 1 ];
  151. rb = b.e[ 0 ] * AbsR[ 0 ][ 2 ] + b.e[ 2 ] * AbsR[ 0 ][ 0 ];
  152. if ( Math.abs( t[ 2 ] * R[ 1 ][ 1 ] - t[ 1 ] * R[ 2 ][ 1 ] ) > ra + rb ) return false;
  153. // test axis L = A0 x B2
  154. ra = a.e[ 1 ] * AbsR[ 2 ][ 2 ] + a.e[ 2 ] * AbsR[ 1 ][ 2 ];
  155. rb = b.e[ 0 ] * AbsR[ 0 ][ 1 ] + b.e[ 1 ] * AbsR[ 0 ][ 0 ];
  156. if ( Math.abs( t[ 2 ] * R[ 1 ][ 2 ] - t[ 1 ] * R[ 2 ][ 2 ] ) > ra + rb ) return false;
  157. // test axis L = A1 x B0
  158. ra = a.e[ 0 ] * AbsR[ 2 ][ 0 ] + a.e[ 2 ] * AbsR[ 0 ][ 0 ];
  159. rb = b.e[ 1 ] * AbsR[ 1 ][ 2 ] + b.e[ 2 ] * AbsR[ 1 ][ 1 ];
  160. if ( Math.abs( t[ 0 ] * R[ 2 ][ 0 ] - t[ 2 ] * R[ 0 ][ 0 ] ) > ra + rb ) return false;
  161. // test axis L = A1 x B1
  162. ra = a.e[ 0 ] * AbsR[ 2 ][ 1 ] + a.e[ 2 ] * AbsR[ 0 ][ 1 ];
  163. rb = b.e[ 0 ] * AbsR[ 1 ][ 2 ] + b.e[ 2 ] * AbsR[ 1 ][ 0 ];
  164. if ( Math.abs( t[ 0 ] * R[ 2 ][ 1 ] - t[ 2 ] * R[ 0 ][ 1 ] ) > ra + rb ) return false;
  165. // test axis L = A1 x B2
  166. ra = a.e[ 0 ] * AbsR[ 2 ][ 2 ] + a.e[ 2 ] * AbsR[ 0 ][ 2 ];
  167. rb = b.e[ 0 ] * AbsR[ 1 ][ 1 ] + b.e[ 1 ] * AbsR[ 1 ][ 0 ];
  168. if ( Math.abs( t[ 0 ] * R[ 2 ][ 2 ] - t[ 2 ] * R[ 0 ][ 2 ] ) > ra + rb ) return false;
  169. // test axis L = A2 x B0
  170. ra = a.e[ 0 ] * AbsR[ 1 ][ 0 ] + a.e[ 1 ] * AbsR[ 0 ][ 0 ];
  171. rb = b.e[ 1 ] * AbsR[ 2 ][ 2 ] + b.e[ 2 ] * AbsR[ 2 ][ 1 ];
  172. if ( Math.abs( t[ 1 ] * R[ 0 ][ 0 ] - t[ 0 ] * R[ 1 ][ 0 ] ) > ra + rb ) return false;
  173. // test axis L = A2 x B1
  174. ra = a.e[ 0 ] * AbsR[ 1 ][ 1 ] + a.e[ 1 ] * AbsR[ 0 ][ 1 ];
  175. rb = b.e[ 0 ] * AbsR[ 2 ][ 2 ] + b.e[ 2 ] * AbsR[ 2 ][ 0 ];
  176. if ( Math.abs( t[ 1 ] * R[ 0 ][ 1 ] - t[ 0 ] * R[ 1 ][ 1 ] ) > ra + rb ) return false;
  177. // test axis L = A2 x B2
  178. ra = a.e[ 0 ] * AbsR[ 1 ][ 2 ] + a.e[ 1 ] * AbsR[ 0 ][ 2 ];
  179. rb = b.e[ 0 ] * AbsR[ 2 ][ 1 ] + b.e[ 1 ] * AbsR[ 2 ][ 0 ];
  180. if ( Math.abs( t[ 1 ] * R[ 0 ][ 2 ] - t[ 0 ] * R[ 1 ][ 2 ] ) > ra + rb ) return false;
  181. // since no separating axis is found, the OBBs must be intersecting
  182. return true;
  183. }
  184. /**
  185. * Reference: Testing Box Against Plane in Real-Time Collision Detection
  186. * by Christer Ericson (chapter 5.2.3)
  187. */
  188. intersectsPlane( plane ) {
  189. this.rotation.extractBasis( xAxis, yAxis, zAxis );
  190. // compute the projection interval radius of this OBB onto L(t) = this->center + t * p.normal;
  191. const r = this.halfSize.x * Math.abs( plane.normal.dot( xAxis ) ) +
  192. this.halfSize.y * Math.abs( plane.normal.dot( yAxis ) ) +
  193. this.halfSize.z * Math.abs( plane.normal.dot( zAxis ) );
  194. // compute distance of the OBB's center from the plane
  195. const d = plane.normal.dot( this.center ) - plane.constant;
  196. // Intersection occurs when distance d falls within [-r,+r] interval
  197. return Math.abs( d ) <= r;
  198. }
  199. /**
  200. * Performs a ray/OBB intersection test and stores the intersection point
  201. * to the given 3D vector. If no intersection is detected, *null* is returned.
  202. */
  203. intersectRay( ray, result ) {
  204. // the idea is to perform the intersection test in the local space
  205. // of the OBB.
  206. this.getSize( size );
  207. aabb.setFromCenterAndSize( v1.set( 0, 0, 0 ), size );
  208. // create a 4x4 transformation matrix
  209. matrix.setFromMatrix3( this.rotation );
  210. matrix.setPosition( this.center );
  211. // transform ray to the local space of the OBB
  212. inverse.copy( matrix ).invert();
  213. localRay.copy( ray ).applyMatrix4( inverse );
  214. // perform ray <-> AABB intersection test
  215. if ( localRay.intersectBox( aabb, result ) ) {
  216. // transform the intersection point back to world space
  217. return result.applyMatrix4( matrix );
  218. } else {
  219. return null;
  220. }
  221. }
  222. /**
  223. * Performs a ray/OBB intersection test. Returns either true or false if
  224. * there is a intersection or not.
  225. */
  226. intersectsRay( ray ) {
  227. return this.intersectRay( ray, v1 ) !== null;
  228. }
  229. fromBox3( box3 ) {
  230. box3.getCenter( this.center );
  231. box3.getSize( this.halfSize ).multiplyScalar( 0.5 );
  232. this.rotation.identity();
  233. return this;
  234. }
  235. equals( obb ) {
  236. return obb.center.equals( this.center ) &&
  237. obb.halfSize.equals( this.halfSize ) &&
  238. obb.rotation.equals( this.rotation );
  239. }
  240. applyMatrix4( matrix ) {
  241. const e = matrix.elements;
  242. let sx = v1.set( e[ 0 ], e[ 1 ], e[ 2 ] ).length();
  243. const sy = v1.set( e[ 4 ], e[ 5 ], e[ 6 ] ).length();
  244. const sz = v1.set( e[ 8 ], e[ 9 ], e[ 10 ] ).length();
  245. const det = matrix.determinant();
  246. if ( det < 0 ) sx = - sx;
  247. rotationMatrix.setFromMatrix4( matrix );
  248. const invSX = 1 / sx;
  249. const invSY = 1 / sy;
  250. const invSZ = 1 / sz;
  251. rotationMatrix.elements[ 0 ] *= invSX;
  252. rotationMatrix.elements[ 1 ] *= invSX;
  253. rotationMatrix.elements[ 2 ] *= invSX;
  254. rotationMatrix.elements[ 3 ] *= invSY;
  255. rotationMatrix.elements[ 4 ] *= invSY;
  256. rotationMatrix.elements[ 5 ] *= invSY;
  257. rotationMatrix.elements[ 6 ] *= invSZ;
  258. rotationMatrix.elements[ 7 ] *= invSZ;
  259. rotationMatrix.elements[ 8 ] *= invSZ;
  260. this.rotation.multiply( rotationMatrix );
  261. this.halfSize.x *= sx;
  262. this.halfSize.y *= sy;
  263. this.halfSize.z *= sz;
  264. v1.setFromMatrixPosition( matrix );
  265. this.center.add( v1 );
  266. return this;
  267. }
  268. }
  269. const obb = new OBB();
  270. export { OBB };