OBB.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535
  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. /**
  35. * Represents an oriented bounding box (OBB) in 3D space.
  36. *
  37. * @three_import import { OBB } from 'three/addons/math/OBB.js';
  38. */
  39. class OBB {
  40. /**
  41. * Constructs a new OBB.
  42. *
  43. * @param {Vector3} [center] - The center of the OBB.
  44. * @param {Vector3} [halfSize] - Positive halfwidth extents of the OBB along each axis.
  45. * @param {Matrix3} [rotation] - The rotation of the OBB.
  46. */
  47. constructor( center = new Vector3(), halfSize = new Vector3(), rotation = new Matrix3() ) {
  48. /**
  49. * The center of the OBB.
  50. *
  51. * @type {Vector3}
  52. */
  53. this.center = center;
  54. /**
  55. * Positive halfwidth extents of the OBB along each axis.
  56. *
  57. * @type {Vector3}
  58. */
  59. this.halfSize = halfSize;
  60. /**
  61. * The rotation of the OBB.
  62. *
  63. * @type {Matrix3}
  64. */
  65. this.rotation = rotation;
  66. }
  67. /**
  68. * Sets the OBBs components to the given values.
  69. *
  70. * @param {Vector3} [center] - The center of the OBB.
  71. * @param {Vector3} [halfSize] - Positive halfwidth extents of the OBB along each axis.
  72. * @param {Matrix3} [rotation] - The rotation of the OBB.
  73. * @return {OBB} A reference to this OBB.
  74. */
  75. set( center, halfSize, rotation ) {
  76. this.center = center;
  77. this.halfSize = halfSize;
  78. this.rotation = rotation;
  79. return this;
  80. }
  81. /**
  82. * Copies the values of the given OBB to this instance.
  83. *
  84. * @param {OBB} obb - The OBB to copy.
  85. * @return {OBB} A reference to this OBB.
  86. */
  87. copy( obb ) {
  88. this.center.copy( obb.center );
  89. this.halfSize.copy( obb.halfSize );
  90. this.rotation.copy( obb.rotation );
  91. return this;
  92. }
  93. /**
  94. * Returns a new OBB with copied values from this instance.
  95. *
  96. * @return {OBB} A clone of this instance.
  97. */
  98. clone() {
  99. return new this.constructor().copy( this );
  100. }
  101. /**
  102. * Returns the size of this OBB.
  103. *
  104. * @param {Vector3} target - The target vector that is used to store the method's result.
  105. * @return {Vector3} The size.
  106. */
  107. getSize( target ) {
  108. return target.copy( this.halfSize ).multiplyScalar( 2 );
  109. }
  110. /**
  111. * Clamps the given point within the bounds of this OBB.
  112. *
  113. * @param {Vector3} point - The point that should be clamped within the bounds of this OBB.
  114. * @param {Vector3} target - The target vector that is used to store the method's result.
  115. * @returns {Vector3} - The clamped point.
  116. */
  117. clampPoint( point, target ) {
  118. // Reference: Closest Point on OBB to Point in Real-Time Collision Detection
  119. // by Christer Ericson (chapter 5.1.4)
  120. const halfSize = this.halfSize;
  121. v1.subVectors( point, this.center );
  122. this.rotation.extractBasis( xAxis, yAxis, zAxis );
  123. // start at the center position of the OBB
  124. target.copy( this.center );
  125. // project the target onto the OBB axes and walk towards that point
  126. const x = MathUtils.clamp( v1.dot( xAxis ), - halfSize.x, halfSize.x );
  127. target.add( xAxis.multiplyScalar( x ) );
  128. const y = MathUtils.clamp( v1.dot( yAxis ), - halfSize.y, halfSize.y );
  129. target.add( yAxis.multiplyScalar( y ) );
  130. const z = MathUtils.clamp( v1.dot( zAxis ), - halfSize.z, halfSize.z );
  131. target.add( zAxis.multiplyScalar( z ) );
  132. return target;
  133. }
  134. /**
  135. * Returns `true` if the given point lies within this OBB.
  136. *
  137. * @param {Vector3} point - The point to test.
  138. * @returns {boolean} - Whether the given point lies within this OBB or not.
  139. */
  140. containsPoint( point ) {
  141. v1.subVectors( point, this.center );
  142. this.rotation.extractBasis( xAxis, yAxis, zAxis );
  143. // project v1 onto each axis and check if these points lie inside the OBB
  144. return Math.abs( v1.dot( xAxis ) ) <= this.halfSize.x &&
  145. Math.abs( v1.dot( yAxis ) ) <= this.halfSize.y &&
  146. Math.abs( v1.dot( zAxis ) ) <= this.halfSize.z;
  147. }
  148. /**
  149. * Returns `true` if the given AABB intersects this OBB.
  150. *
  151. * @param {Box3} box3 - The AABB to test.
  152. * @returns {boolean} - Whether the given AABB intersects this OBB or not.
  153. */
  154. intersectsBox3( box3 ) {
  155. return this.intersectsOBB( obb.fromBox3( box3 ) );
  156. }
  157. /**
  158. * Returns `true` if the given bounding sphere intersects this OBB.
  159. *
  160. * @param {Sphere} sphere - The bounding sphere to test.
  161. * @returns {boolean} - Whether the given bounding sphere intersects this OBB or not.
  162. */
  163. intersectsSphere( sphere ) {
  164. // find the point on the OBB closest to the sphere center
  165. this.clampPoint( sphere.center, closestPoint );
  166. // if that point is inside the sphere, the OBB and sphere intersect
  167. return closestPoint.distanceToSquared( sphere.center ) <= ( sphere.radius * sphere.radius );
  168. }
  169. /**
  170. * Returns `true` if the given OBB intersects this OBB.
  171. *
  172. * @param {OBB} obb - The OBB to test.
  173. * @param {number} [epsilon=Number.EPSILON] - A small value to prevent arithmetic errors.
  174. * @returns {boolean} - Whether the given OBB intersects this OBB or not.
  175. */
  176. intersectsOBB( obb, epsilon = Number.EPSILON ) {
  177. // Reference: OBB-OBB Intersection in Real-Time Collision Detection
  178. // by Christer Ericson (chapter 4.4.1)
  179. // prepare data structures (the code uses the same nomenclature like the reference)
  180. a.c = this.center;
  181. a.e[ 0 ] = this.halfSize.x;
  182. a.e[ 1 ] = this.halfSize.y;
  183. a.e[ 2 ] = this.halfSize.z;
  184. this.rotation.extractBasis( a.u[ 0 ], a.u[ 1 ], a.u[ 2 ] );
  185. b.c = obb.center;
  186. b.e[ 0 ] = obb.halfSize.x;
  187. b.e[ 1 ] = obb.halfSize.y;
  188. b.e[ 2 ] = obb.halfSize.z;
  189. obb.rotation.extractBasis( b.u[ 0 ], b.u[ 1 ], b.u[ 2 ] );
  190. // compute rotation matrix expressing b in a's coordinate frame
  191. for ( let i = 0; i < 3; i ++ ) {
  192. for ( let j = 0; j < 3; j ++ ) {
  193. R[ i ][ j ] = a.u[ i ].dot( b.u[ j ] );
  194. }
  195. }
  196. // compute translation vector
  197. v1.subVectors( b.c, a.c );
  198. // bring translation into a's coordinate frame
  199. t[ 0 ] = v1.dot( a.u[ 0 ] );
  200. t[ 1 ] = v1.dot( a.u[ 1 ] );
  201. t[ 2 ] = v1.dot( a.u[ 2 ] );
  202. // compute common subexpressions. Add in an epsilon term to
  203. // counteract arithmetic errors when two edges are parallel and
  204. // their cross product is (near) null
  205. for ( let i = 0; i < 3; i ++ ) {
  206. for ( let j = 0; j < 3; j ++ ) {
  207. AbsR[ i ][ j ] = Math.abs( R[ i ][ j ] ) + epsilon;
  208. }
  209. }
  210. let ra, rb;
  211. // test axes L = A0, L = A1, L = A2
  212. for ( let i = 0; i < 3; i ++ ) {
  213. ra = a.e[ i ];
  214. rb = b.e[ 0 ] * AbsR[ i ][ 0 ] + b.e[ 1 ] * AbsR[ i ][ 1 ] + b.e[ 2 ] * AbsR[ i ][ 2 ];
  215. if ( Math.abs( t[ i ] ) > ra + rb ) return false;
  216. }
  217. // test axes L = B0, L = B1, L = B2
  218. for ( let i = 0; i < 3; i ++ ) {
  219. ra = a.e[ 0 ] * AbsR[ 0 ][ i ] + a.e[ 1 ] * AbsR[ 1 ][ i ] + a.e[ 2 ] * AbsR[ 2 ][ i ];
  220. rb = b.e[ i ];
  221. if ( Math.abs( t[ 0 ] * R[ 0 ][ i ] + t[ 1 ] * R[ 1 ][ i ] + t[ 2 ] * R[ 2 ][ i ] ) > ra + rb ) return false;
  222. }
  223. // test axis L = A0 x B0
  224. ra = a.e[ 1 ] * AbsR[ 2 ][ 0 ] + a.e[ 2 ] * AbsR[ 1 ][ 0 ];
  225. rb = b.e[ 1 ] * AbsR[ 0 ][ 2 ] + b.e[ 2 ] * AbsR[ 0 ][ 1 ];
  226. if ( Math.abs( t[ 2 ] * R[ 1 ][ 0 ] - t[ 1 ] * R[ 2 ][ 0 ] ) > ra + rb ) return false;
  227. // test axis L = A0 x B1
  228. ra = a.e[ 1 ] * AbsR[ 2 ][ 1 ] + a.e[ 2 ] * AbsR[ 1 ][ 1 ];
  229. rb = b.e[ 0 ] * AbsR[ 0 ][ 2 ] + b.e[ 2 ] * AbsR[ 0 ][ 0 ];
  230. if ( Math.abs( t[ 2 ] * R[ 1 ][ 1 ] - t[ 1 ] * R[ 2 ][ 1 ] ) > ra + rb ) return false;
  231. // test axis L = A0 x B2
  232. ra = a.e[ 1 ] * AbsR[ 2 ][ 2 ] + a.e[ 2 ] * AbsR[ 1 ][ 2 ];
  233. rb = b.e[ 0 ] * AbsR[ 0 ][ 1 ] + b.e[ 1 ] * AbsR[ 0 ][ 0 ];
  234. if ( Math.abs( t[ 2 ] * R[ 1 ][ 2 ] - t[ 1 ] * R[ 2 ][ 2 ] ) > ra + rb ) return false;
  235. // test axis L = A1 x B0
  236. ra = a.e[ 0 ] * AbsR[ 2 ][ 0 ] + a.e[ 2 ] * AbsR[ 0 ][ 0 ];
  237. rb = b.e[ 1 ] * AbsR[ 1 ][ 2 ] + b.e[ 2 ] * AbsR[ 1 ][ 1 ];
  238. if ( Math.abs( t[ 0 ] * R[ 2 ][ 0 ] - t[ 2 ] * R[ 0 ][ 0 ] ) > ra + rb ) return false;
  239. // test axis L = A1 x B1
  240. ra = a.e[ 0 ] * AbsR[ 2 ][ 1 ] + a.e[ 2 ] * AbsR[ 0 ][ 1 ];
  241. rb = b.e[ 0 ] * AbsR[ 1 ][ 2 ] + b.e[ 2 ] * AbsR[ 1 ][ 0 ];
  242. if ( Math.abs( t[ 0 ] * R[ 2 ][ 1 ] - t[ 2 ] * R[ 0 ][ 1 ] ) > ra + rb ) return false;
  243. // test axis L = A1 x B2
  244. ra = a.e[ 0 ] * AbsR[ 2 ][ 2 ] + a.e[ 2 ] * AbsR[ 0 ][ 2 ];
  245. rb = b.e[ 0 ] * AbsR[ 1 ][ 1 ] + b.e[ 1 ] * AbsR[ 1 ][ 0 ];
  246. if ( Math.abs( t[ 0 ] * R[ 2 ][ 2 ] - t[ 2 ] * R[ 0 ][ 2 ] ) > ra + rb ) return false;
  247. // test axis L = A2 x B0
  248. ra = a.e[ 0 ] * AbsR[ 1 ][ 0 ] + a.e[ 1 ] * AbsR[ 0 ][ 0 ];
  249. rb = b.e[ 1 ] * AbsR[ 2 ][ 2 ] + b.e[ 2 ] * AbsR[ 2 ][ 1 ];
  250. if ( Math.abs( t[ 1 ] * R[ 0 ][ 0 ] - t[ 0 ] * R[ 1 ][ 0 ] ) > ra + rb ) return false;
  251. // test axis L = A2 x B1
  252. ra = a.e[ 0 ] * AbsR[ 1 ][ 1 ] + a.e[ 1 ] * AbsR[ 0 ][ 1 ];
  253. rb = b.e[ 0 ] * AbsR[ 2 ][ 2 ] + b.e[ 2 ] * AbsR[ 2 ][ 0 ];
  254. if ( Math.abs( t[ 1 ] * R[ 0 ][ 1 ] - t[ 0 ] * R[ 1 ][ 1 ] ) > ra + rb ) return false;
  255. // test axis L = A2 x B2
  256. ra = a.e[ 0 ] * AbsR[ 1 ][ 2 ] + a.e[ 1 ] * AbsR[ 0 ][ 2 ];
  257. rb = b.e[ 0 ] * AbsR[ 2 ][ 1 ] + b.e[ 1 ] * AbsR[ 2 ][ 0 ];
  258. if ( Math.abs( t[ 1 ] * R[ 0 ][ 2 ] - t[ 0 ] * R[ 1 ][ 2 ] ) > ra + rb ) return false;
  259. // since no separating axis is found, the OBBs must be intersecting
  260. return true;
  261. }
  262. /**
  263. * Returns `true` if the given plane intersects this OBB.
  264. *
  265. * @param {Plane} plane - The plane to test.
  266. * @returns {boolean} Whether the given plane intersects this OBB or not.
  267. */
  268. intersectsPlane( plane ) {
  269. // Reference: Testing Box Against Plane in Real-Time Collision Detection
  270. // by Christer Ericson (chapter 5.2.3)
  271. this.rotation.extractBasis( xAxis, yAxis, zAxis );
  272. // compute the projection interval radius of this OBB onto L(t) = this->center + t * p.normal;
  273. const r = this.halfSize.x * Math.abs( plane.normal.dot( xAxis ) ) +
  274. this.halfSize.y * Math.abs( plane.normal.dot( yAxis ) ) +
  275. this.halfSize.z * Math.abs( plane.normal.dot( zAxis ) );
  276. // compute distance of the OBB's center from the plane
  277. const d = plane.normal.dot( this.center ) - plane.constant;
  278. // Intersection occurs when distance d falls within [-r,+r] interval
  279. return Math.abs( d ) <= r;
  280. }
  281. /**
  282. * Performs a ray/OBB intersection test and stores the intersection point
  283. * in the given 3D vector.
  284. *
  285. * @param {Ray} ray - The ray to test.
  286. * @param {Vector3} target - The target vector that is used to store the method's result.
  287. * @return {?Vector3} The intersection point. If no intersection is detected, `null` is returned.
  288. */
  289. intersectRay( ray, target ) {
  290. // the idea is to perform the intersection test in the local space
  291. // of the OBB.
  292. this.getSize( size );
  293. aabb.setFromCenterAndSize( v1.set( 0, 0, 0 ), size );
  294. // create a 4x4 transformation matrix
  295. matrix.setFromMatrix3( this.rotation );
  296. matrix.setPosition( this.center );
  297. // transform ray to the local space of the OBB
  298. inverse.copy( matrix ).invert();
  299. localRay.copy( ray ).applyMatrix4( inverse );
  300. // perform ray <-> AABB intersection test
  301. if ( localRay.intersectBox( aabb, target ) ) {
  302. // transform the intersection point back to world space
  303. return target.applyMatrix4( matrix );
  304. } else {
  305. return null;
  306. }
  307. }
  308. /**
  309. * Returns `true` if the given ray intersects this OBB.
  310. *
  311. * @param {Ray} ray - The ray to test.
  312. * @returns {boolean} Whether the given ray intersects this OBB or not.
  313. */
  314. intersectsRay( ray ) {
  315. return this.intersectRay( ray, v1 ) !== null;
  316. }
  317. /**
  318. * Defines an OBB based on the given AABB.
  319. *
  320. * @param {Box3} box3 - The AABB to setup the OBB from.
  321. * @return {OBB} A reference of this OBB.
  322. */
  323. fromBox3( box3 ) {
  324. box3.getCenter( this.center );
  325. box3.getSize( this.halfSize ).multiplyScalar( 0.5 );
  326. this.rotation.identity();
  327. return this;
  328. }
  329. /**
  330. * Returns `true` if the given OBB is equal to this OBB.
  331. *
  332. * @param {OBB} obb - The OBB to test.
  333. * @returns {boolean} Whether the given OBB is equal to this OBB or not.
  334. */
  335. equals( obb ) {
  336. return obb.center.equals( this.center ) &&
  337. obb.halfSize.equals( this.halfSize ) &&
  338. obb.rotation.equals( this.rotation );
  339. }
  340. /**
  341. * Applies the given transformation matrix to this OBB. This method can be
  342. * used to transform the bounding volume with the world matrix of a 3D object
  343. * in order to keep both entities in sync.
  344. *
  345. * @param {Matrix4} matrix - The matrix to apply.
  346. * @return {OBB} A reference of this OBB.
  347. */
  348. applyMatrix4( matrix ) {
  349. const e = matrix.elements;
  350. let sx = v1.set( e[ 0 ], e[ 1 ], e[ 2 ] ).length();
  351. const sy = v1.set( e[ 4 ], e[ 5 ], e[ 6 ] ).length();
  352. const sz = v1.set( e[ 8 ], e[ 9 ], e[ 10 ] ).length();
  353. const det = matrix.determinant();
  354. if ( det < 0 ) sx = - sx;
  355. rotationMatrix.setFromMatrix4( matrix );
  356. const invSX = 1 / sx;
  357. const invSY = 1 / sy;
  358. const invSZ = 1 / sz;
  359. rotationMatrix.elements[ 0 ] *= invSX;
  360. rotationMatrix.elements[ 1 ] *= invSX;
  361. rotationMatrix.elements[ 2 ] *= invSX;
  362. rotationMatrix.elements[ 3 ] *= invSY;
  363. rotationMatrix.elements[ 4 ] *= invSY;
  364. rotationMatrix.elements[ 5 ] *= invSY;
  365. rotationMatrix.elements[ 6 ] *= invSZ;
  366. rotationMatrix.elements[ 7 ] *= invSZ;
  367. rotationMatrix.elements[ 8 ] *= invSZ;
  368. this.rotation.multiply( rotationMatrix );
  369. this.halfSize.x *= sx;
  370. this.halfSize.y *= sy;
  371. this.halfSize.z *= sz;
  372. v1.setFromMatrixPosition( matrix );
  373. this.center.add( v1 );
  374. return this;
  375. }
  376. }
  377. const obb = new OBB();
  378. export { OBB };