OBB.js 11 KB

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