text: 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 ) {
  
  	// Don't intersect the box3 helpers because those are used for debugging
  	scene.traverse( c => {
  
  		// We set the default raycast function to empty so three.js doesn't automatically cast against it
  		Object.getPrototypeOf( c ).raycast.call( c, raycaster, intersects );
  
  	} );
  
  }
  
  // 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
  				let invScale;
  				_vec2.setFromMatrixScale( _mat );
  				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
  				let 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 );
  
  	}
  
  }
  