OBB.js 10 KB

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