raycastTraverse.js 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253
  1. import { Matrix4, Sphere, Ray, Vector3, Box3Helper } from 'three';
  2. const _sphere = new Sphere();
  3. const _mat = new Matrix4();
  4. const _vec = new Vector3();
  5. const _vec2 = new Vector3();
  6. const _ray = new Ray();
  7. const _hitArray = [];
  8. function distanceSort( a, b ) {
  9. return a.distance - b.distance;
  10. }
  11. function intersectTileScene( scene, raycaster, intersects ) {
  12. // Don't intersect the box3 helpers because those are used for debugging
  13. scene.traverse( c => {
  14. if ( ! ( c instanceof Box3Helper ) ) {
  15. Object.getPrototypeOf( c ).raycast.call( c, raycaster, intersects );
  16. }
  17. } );
  18. }
  19. // Returns the closest hit when traversing the tree
  20. export function raycastTraverseFirstHit( root, group, activeSet, raycaster ) {
  21. // If the root is active make sure we've checked it
  22. if ( activeSet.has( root.cached.scene ) ) {
  23. intersectTileScene( root.cached.scene, raycaster, _hitArray );
  24. if ( _hitArray.length > 0 ) {
  25. if ( _hitArray.length > 1 ) {
  26. _hitArray.sort( distanceSort );
  27. }
  28. _hitArray.length = 0;
  29. return _hitArray[ 0 ];
  30. } else {
  31. return null;
  32. }
  33. }
  34. // TODO: see if we can avoid creating a new array here every time to save on memory
  35. const array = [];
  36. const children = root.children;
  37. for ( let i = 0, l = children.length; i < l; i ++ ) {
  38. const tile = children[ i ];
  39. const cached = tile.cached;
  40. const groupMatrixWorld = group.matrixWorld;
  41. const transformMat = cached.transform;
  42. _mat.copy( groupMatrixWorld );
  43. _mat.multiply( transformMat );
  44. // if we don't hit the sphere then early out
  45. const sphere = cached.sphere;
  46. if ( sphere ) {
  47. _sphere.copy( sphere );
  48. _sphere.applyMatrix4( _mat );
  49. if ( ! raycaster.ray.intersectsSphere( _sphere ) ) {
  50. continue;
  51. }
  52. }
  53. // TODO: check region
  54. const boundingBox = cached.box;
  55. const obbMat = cached.boxTransform;
  56. if ( boundingBox ) {
  57. _mat.multiply( obbMat );
  58. _mat.getInverse( _mat );
  59. _ray.copy( raycaster.ray ).applyMatrix4( _mat );
  60. if ( _ray.intersectBox( boundingBox, _vec ) ) {
  61. // account for tile scale
  62. let invScale;
  63. _vec2.setFromMatrixScale( _mat );
  64. invScale = _vec2.x;
  65. if ( Math.abs( Math.max( _vec2.x - _vec2.y, _vec2.x - _vec2.z ) ) > 1e-6 ) {
  66. console.warn( 'ThreeTilesRenderer : Non uniform scale used for tile which may cause issues when raycasting.' );
  67. }
  68. // if we intersect the box save the distance to the tile bounds
  69. let data = {
  70. distance: Infinity,
  71. tile: null
  72. };
  73. array.push( data );
  74. data.distance = _vec.distanceToSquared( _ray.origin ) * invScale * invScale;
  75. data.tile = tile;
  76. } else {
  77. continue;
  78. }
  79. }
  80. }
  81. // sort them by ascending distance
  82. array.sort( distanceSort );
  83. // traverse until we find the best hit and early out if a tile bounds
  84. // couldn't possible include a best hit
  85. let bestDistanceSquared = Infinity;
  86. let bestHit = null;
  87. for ( let i = 0, l = array.length; i < l; i ++ ) {
  88. const data = array[ i ];
  89. const distanceSquared = data.distance;
  90. if ( distanceSquared > bestDistanceSquared ) {
  91. break;
  92. } else {
  93. const tile = data.tile;
  94. const scene = tile.cached.scene;
  95. let hit = null;
  96. if ( activeSet.has( scene ) ) {
  97. // save the hit if it's closer
  98. intersectTileScene( scene, raycaster, _hitArray );
  99. if ( _hitArray.length > 0 ) {
  100. if ( _hitArray.length > 1 ) {
  101. _hitArray.sort( distanceSort );
  102. }
  103. hit = _hitArray[ 0 ];
  104. }
  105. } else {
  106. hit = raycastTraverseFirstHit( tile, group, activeSet, raycaster );
  107. }
  108. if ( hit ) {
  109. const hitDistanceSquared = hit.distance * hit.distance;
  110. if ( hitDistanceSquared < bestDistanceSquared ) {
  111. bestDistanceSquared = hitDistanceSquared;
  112. bestHit = hit;
  113. }
  114. _hitArray.length = 0;
  115. }
  116. }
  117. }
  118. return bestHit;
  119. }
  120. export function raycastTraverse( tile, group, activeSet, raycaster, intersects ) {
  121. const cached = tile.cached;
  122. const groupMatrixWorld = group.matrixWorld;
  123. const transformMat = cached.transform;
  124. _mat.copy( groupMatrixWorld );
  125. _mat.multiply( transformMat );
  126. const sphere = cached.sphere;
  127. if ( sphere ) {
  128. _sphere.copy( sphere );
  129. _sphere.applyMatrix4( _mat );
  130. if ( ! raycaster.ray.intersectsSphere( _sphere ) ) {
  131. return;
  132. }
  133. }
  134. const boundingBox = cached.box;
  135. const obbMat = cached.boxTransform;
  136. if ( boundingBox ) {
  137. _mat.multiply( obbMat );
  138. _mat.getInverse( _mat );
  139. _ray.copy( raycaster.ray ).applyMatrix4( _mat );
  140. if ( ! _ray.intersectsBox( boundingBox ) ) {
  141. return;
  142. }
  143. }
  144. // TODO: check region
  145. const scene = cached.scene;
  146. if ( activeSet.has( scene ) ) {
  147. scene.traverse( c => {
  148. if ( ! ( c instanceof Box3Helper ) ) {
  149. Object.getPrototypeOf( c ).raycast.call( c, raycaster, intersects );
  150. }
  151. } );
  152. return;
  153. }
  154. const children = tile.children;
  155. for ( let i = 0, l = children.length; i < l; i ++ ) {
  156. raycastTraverse( children[ i ], group, activeSet, raycaster, intersects );
  157. }
  158. }