-
Notifications
You must be signed in to change notification settings - Fork 53
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(scene-composer): enable accelerated raycasting for 3D Tiles
- Loading branch information
1 parent
1d797b1
commit 84d2ce4
Showing
4 changed files
with
249 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
237 changes: 237 additions & 0 deletions
237
packages/scene-composer/src/three/tiles3d/raycastTraverse.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,237 @@ | ||
import { Matrix4, Sphere, Ray, Vector3 } from 'three'; | ||
|
||
const _sphere = new Sphere(); | ||
const _mat = new Matrix4(); | ||
const _vec = new Vector3(); | ||
const _vec2 = new Vector3(); | ||
const _ray = new Ray(); | ||
|
||
const _hitArray = []; | ||
|
||
function distanceSort( a, b ) { | ||
|
||
return a.distance - b.distance; | ||
|
||
} | ||
|
||
function intersectTileScene( scene, raycaster, intersects ) { | ||
|
||
// + Amazon -------- | ||
// Call the object's overridden raycast method | ||
scene.traverse(c => c.raycast.call(c, raycaster, intersects)); | ||
// - Amazon -------- | ||
|
||
} | ||
|
||
// Returns the closest hit when traversing the tree | ||
export function raycastTraverseFirstHit( root, group, activeTiles, raycaster ) { | ||
|
||
// If the root is active make sure we've checked it | ||
if ( activeTiles.has( root ) ) { | ||
|
||
intersectTileScene( root.cached.scene, raycaster, _hitArray ); | ||
|
||
if ( _hitArray.length > 0 ) { | ||
|
||
if ( _hitArray.length > 1 ) { | ||
|
||
_hitArray.sort( distanceSort ); | ||
|
||
} | ||
|
||
const res = _hitArray[ 0 ]; | ||
_hitArray.length = 0; | ||
return res; | ||
|
||
} else { | ||
|
||
return null; | ||
|
||
} | ||
|
||
} | ||
|
||
// TODO: can we avoid creating a new array here every time to save on memory? | ||
const array = []; | ||
const children = root.children; | ||
for ( let i = 0, l = children.length; i < l; i ++ ) { | ||
|
||
const tile = children[ i ]; | ||
const cached = tile.cached; | ||
const groupMatrixWorld = group.matrixWorld; | ||
|
||
_mat.copy( groupMatrixWorld ); | ||
|
||
// if we don't hit the sphere then early out | ||
const sphere = cached.sphere; | ||
if ( sphere ) { | ||
|
||
_sphere.copy( sphere ); | ||
_sphere.applyMatrix4( _mat ); | ||
if ( ! raycaster.ray.intersectsSphere( _sphere ) ) { | ||
|
||
continue; | ||
|
||
} | ||
|
||
} | ||
|
||
// TODO: check region? | ||
|
||
const boundingBox = cached.box; | ||
const obbMat = cached.boxTransform; | ||
if ( boundingBox ) { | ||
|
||
_mat.multiply( obbMat ).invert(); | ||
_ray.copy( raycaster.ray ); | ||
_ray.applyMatrix4( _mat ); | ||
if ( _ray.intersectBox( boundingBox, _vec ) ) { | ||
|
||
// account for tile scale | ||
_vec2.setFromMatrixScale( _mat ); | ||
const invScale = _vec2.x; | ||
|
||
if ( Math.abs( Math.max( _vec2.x - _vec2.y, _vec2.x - _vec2.z ) ) > 1e-6 ) { | ||
|
||
console.warn( 'ThreeTilesRenderer : Non uniform scale used for tile which may cause issues when raycasting.' ); | ||
|
||
} | ||
|
||
// if we intersect the box save the distance to the tile bounds | ||
const data = { | ||
distance: Infinity, | ||
tile: null | ||
}; | ||
array.push( data ); | ||
|
||
data.distance = _vec.distanceToSquared( _ray.origin ) * invScale * invScale; | ||
data.tile = tile; | ||
|
||
} else { | ||
|
||
continue; | ||
|
||
} | ||
|
||
} | ||
|
||
} | ||
|
||
// sort them by ascending distance | ||
array.sort( distanceSort ); | ||
|
||
// traverse until we find the best hit and early out if a tile bounds | ||
// couldn't possible include a best hit | ||
let bestDistanceSquared = Infinity; | ||
let bestHit = null; | ||
for ( let i = 0, l = array.length; i < l; i ++ ) { | ||
|
||
const data = array[ i ]; | ||
const distanceSquared = data.distance; | ||
if ( distanceSquared > bestDistanceSquared ) { | ||
|
||
break; | ||
|
||
} else { | ||
|
||
const tile = data.tile; | ||
const scene = tile.cached.scene; | ||
|
||
let hit = null; | ||
if ( activeTiles.has( tile ) ) { | ||
|
||
// save the hit if it's closer | ||
intersectTileScene( scene, raycaster, _hitArray ); | ||
if ( _hitArray.length > 0 ) { | ||
|
||
if ( _hitArray.length > 1 ) { | ||
|
||
_hitArray.sort( distanceSort ); | ||
|
||
} | ||
|
||
hit = _hitArray[ 0 ]; | ||
|
||
} | ||
|
||
} else { | ||
|
||
hit = raycastTraverseFirstHit( tile, group, activeTiles, raycaster ); | ||
|
||
} | ||
|
||
if ( hit ) { | ||
|
||
const hitDistanceSquared = hit.distance * hit.distance; | ||
if ( hitDistanceSquared < bestDistanceSquared ) { | ||
|
||
bestDistanceSquared = hitDistanceSquared; | ||
bestHit = hit; | ||
|
||
} | ||
_hitArray.length = 0; | ||
|
||
} | ||
|
||
} | ||
|
||
} | ||
|
||
return bestHit; | ||
|
||
} | ||
|
||
export function raycastTraverse( tile, group, activeTiles, raycaster, intersects ) { | ||
|
||
const cached = tile.cached; | ||
const groupMatrixWorld = group.matrixWorld; | ||
|
||
_mat.copy( groupMatrixWorld ); | ||
|
||
// Early out if we don't hit this tile sphere | ||
const sphere = cached.sphere; | ||
if ( sphere ) { | ||
|
||
_sphere.copy( sphere ); | ||
_sphere.applyMatrix4( _mat ); | ||
if ( ! raycaster.ray.intersectsSphere( _sphere ) ) { | ||
|
||
return; | ||
|
||
} | ||
|
||
} | ||
|
||
// Early out if we don't this this tile box | ||
const boundingBox = cached.box; | ||
const obbMat = cached.boxTransform; | ||
if ( boundingBox ) { | ||
|
||
_mat.multiply( obbMat ).invert(); | ||
_ray.copy( raycaster.ray ).applyMatrix4( _mat ); | ||
if ( ! _ray.intersectsBox( boundingBox ) ) { | ||
|
||
return; | ||
|
||
} | ||
|
||
} | ||
|
||
// TODO: check region | ||
|
||
const scene = cached.scene; | ||
if ( activeTiles.has( tile ) ) { | ||
|
||
intersectTileScene( scene, raycaster, intersects ); | ||
return; | ||
|
||
} | ||
|
||
const children = tile.children; | ||
for ( let i = 0, l = children.length; i < l; i ++ ) { | ||
|
||
raycastTraverse( children[ i ], group, activeTiles, raycaster, intersects ); | ||
|
||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters