TilesRenderer.js 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962
  1. import { TilesRendererBase } from '../base/TilesRendererBase.js';
  2. import { B3DMLoader } from './B3DMLoader.js';
  3. import { PNTSLoader } from './PNTSLoader.js';
  4. import { I3DMLoader } from './I3DMLoader.js';
  5. import { CMPTLoader } from './CMPTLoader.js';
  6. import { GLTFExtensionLoader } from './GLTFExtensionLoader.js';
  7. import { TilesGroup } from './TilesGroup.js';
  8. import {
  9. Matrix4,
  10. Box3,
  11. Sphere,
  12. Vector3,
  13. Vector2,
  14. Frustum,
  15. LoadingManager
  16. } from 'three';
  17. import { raycastTraverse, raycastTraverseFirstHit } from './raycastTraverse.js';
  18. const INITIAL_FRUSTUM_CULLED = Symbol( 'INITIAL_FRUSTUM_CULLED' );
  19. const tempMat = new Matrix4();
  20. const tempMat2 = new Matrix4();
  21. const tempVector = new Vector3();
  22. const vecX = new Vector3();
  23. const vecY = new Vector3();
  24. const vecZ = new Vector3();
  25. const X_AXIS = new Vector3( 1, 0, 0 );
  26. const Y_AXIS = new Vector3( 0, 1, 0 );
  27. function updateFrustumCulled( object, toInitialValue ) {
  28. object.traverse( c => {
  29. c.frustumCulled = c[ INITIAL_FRUSTUM_CULLED ] && toInitialValue;
  30. } );
  31. }
  32. export class TilesRenderer extends TilesRendererBase {
  33. get autoDisableRendererCulling() {
  34. return this._autoDisableRendererCulling;
  35. }
  36. set autoDisableRendererCulling( value ) {
  37. if ( this._autoDisableRendererCulling !== value ) {
  38. super._autoDisableRendererCulling = value;
  39. this.forEachLoadedModel( ( scene ) => {
  40. updateFrustumCulled( scene, ! value );
  41. } );
  42. }
  43. }
  44. constructor( ...args ) {
  45. super( ...args );
  46. this.group = new TilesGroup( this );
  47. this.cameras = [];
  48. this.cameraMap = new Map();
  49. this.cameraInfo = [];
  50. this.activeTiles = new Set();
  51. this.visibleTiles = new Set();
  52. this._autoDisableRendererCulling = true;
  53. this.optimizeRaycast = true;
  54. this.onLoadTileSet = null;
  55. this.onLoadModel = null;
  56. this.onDisposeModel = null;
  57. const manager = new LoadingManager();
  58. manager.setURLModifier( url => {
  59. if ( this.preprocessURL ) {
  60. return this.preprocessURL( url );
  61. } else {
  62. return url;
  63. }
  64. } );
  65. this.manager = manager;
  66. // Setting up the override raycasting function to be used by
  67. // 3D objects created by this renderer
  68. const tilesRenderer = this;
  69. this._overridenRaycast = function ( raycaster, intersects ) {
  70. if ( ! tilesRenderer.optimizeRaycast ) {
  71. Object.getPrototypeOf( this ).raycast.call( this, raycaster, intersects );
  72. }
  73. };
  74. }
  75. /* Public API */
  76. getBounds( box ) {
  77. if ( ! this.root ) {
  78. return false;
  79. }
  80. const cached = this.root.cached;
  81. const boundingBox = cached.box;
  82. const obbMat = cached.boxTransform;
  83. if ( boundingBox ) {
  84. box.copy( boundingBox );
  85. box.applyMatrix4( obbMat );
  86. return true;
  87. } else {
  88. return false;
  89. }
  90. }
  91. getOrientedBounds( box, matrix ) {
  92. if ( ! this.root ) {
  93. return false;
  94. }
  95. const cached = this.root.cached;
  96. const boundingBox = cached.box;
  97. const obbMat = cached.boxTransform;
  98. if ( boundingBox ) {
  99. box.copy( boundingBox );
  100. matrix.copy( obbMat );
  101. return true;
  102. } else {
  103. return false;
  104. }
  105. }
  106. getBoundingSphere( sphere ) {
  107. if ( ! this.root ) {
  108. return false;
  109. }
  110. const boundingSphere = this.root.cached.sphere;
  111. if ( boundingSphere ) {
  112. sphere.copy( boundingSphere );
  113. return true;
  114. } else {
  115. return false;
  116. }
  117. }
  118. forEachLoadedModel( callback ) {
  119. this.traverse( tile => {
  120. const scene = tile.cached.scene;
  121. if ( scene ) {
  122. callback( scene, tile );
  123. }
  124. } );
  125. }
  126. raycast( raycaster, intersects ) {
  127. if ( ! this.root ) {
  128. return;
  129. }
  130. if ( raycaster.firstHitOnly ) {
  131. const hit = raycastTraverseFirstHit( this.root, this.group, this.activeTiles, raycaster );
  132. if ( hit ) {
  133. intersects.push( hit );
  134. }
  135. } else {
  136. raycastTraverse( this.root, this.group, this.activeTiles, raycaster, intersects );
  137. }
  138. }
  139. hasCamera( camera ) {
  140. return this.cameraMap.has( camera );
  141. }
  142. setCamera( camera ) {
  143. const cameras = this.cameras;
  144. const cameraMap = this.cameraMap;
  145. if ( ! cameraMap.has( camera ) ) {
  146. cameraMap.set( camera, new Vector2() );
  147. cameras.push( camera );
  148. return true;
  149. }
  150. return false;
  151. }
  152. setResolution( camera, xOrVec, y ) {
  153. const cameraMap = this.cameraMap;
  154. if ( ! cameraMap.has( camera ) ) {
  155. return false;
  156. }
  157. if ( xOrVec instanceof Vector2 ) {
  158. cameraMap.get( camera ).copy( xOrVec );
  159. } else {
  160. cameraMap.get( camera ).set( xOrVec, y );
  161. }
  162. return true;
  163. }
  164. setResolutionFromRenderer( camera, renderer ) {
  165. const cameraMap = this.cameraMap;
  166. if ( ! cameraMap.has( camera ) ) {
  167. return false;
  168. }
  169. const resolution = cameraMap.get( camera );
  170. renderer.getSize( resolution );
  171. resolution.multiplyScalar( renderer.getPixelRatio() );
  172. return true;
  173. }
  174. deleteCamera( camera ) {
  175. const cameras = this.cameras;
  176. const cameraMap = this.cameraMap;
  177. if ( cameraMap.has( camera ) ) {
  178. const index = cameras.indexOf( camera );
  179. cameras.splice( index, 1 );
  180. cameraMap.delete( camera );
  181. return true;
  182. }
  183. return false;
  184. }
  185. /* Overriden */
  186. fetchTileSet( url, ...rest ) {
  187. const pr = super.fetchTileSet( url, ...rest );
  188. pr.then( json => {
  189. if ( this.onLoadTileSet ) {
  190. // Push this onto the end of the event stack to ensure this runs
  191. // after the base renderer has placed the provided json where it
  192. // needs to be placed and is ready for an update.
  193. Promise.resolve().then( () => {
  194. this.onLoadTileSet( json, url );
  195. } );
  196. }
  197. } );
  198. return pr;
  199. }
  200. update() {
  201. const group = this.group;
  202. const cameras = this.cameras;
  203. const cameraMap = this.cameraMap;
  204. const cameraInfo = this.cameraInfo;
  205. if ( cameras.length === 0 ) {
  206. console.warn( 'TilesRenderer: no cameras defined. Cannot update 3d tiles.' );
  207. return;
  208. }
  209. // automatically scale the array of cameraInfo to match the cameras
  210. while ( cameraInfo.length > cameras.length ) {
  211. cameraInfo.pop();
  212. }
  213. while ( cameraInfo.length < cameras.length ) {
  214. cameraInfo.push( {
  215. frustum: new Frustum(),
  216. isOrthographic: false,
  217. sseDenominator: - 1, // used if isOrthographic:false
  218. position: new Vector3(),
  219. invScale: - 1,
  220. pixelSize: 0, // used if isOrthographic:true
  221. } );
  222. }
  223. // extract scale of group container
  224. tempMat2.copy( group.matrixWorld ).invert();
  225. let invScale;
  226. tempVector.setFromMatrixScale( tempMat2 );
  227. invScale = tempVector.x;
  228. if ( Math.abs( Math.max( tempVector.x - tempVector.y, tempVector.x - tempVector.z ) ) > 1e-6 ) {
  229. console.warn( 'ThreeTilesRenderer : Non uniform scale used for tile which may cause issues when calculating screen space error.' );
  230. }
  231. // store the camera cameraInfo in the 3d tiles root frame
  232. for ( let i = 0, l = cameraInfo.length; i < l; i ++ ) {
  233. const camera = cameras[ i ];
  234. const info = cameraInfo[ i ];
  235. const frustum = info.frustum;
  236. const position = info.position;
  237. const resolution = cameraMap.get( camera );
  238. if ( resolution.width === 0 || resolution.height === 0 ) {
  239. console.warn( 'TilesRenderer: resolution for camera error calculation is not set.' );
  240. }
  241. // Read the calculated projection matrix directly to support custom Camera implementations
  242. const projection = camera.projectionMatrix.elements;
  243. // The last element of the projection matrix is 1 for orthographic, 0 for perspective
  244. info.isOrthographic = projection[ 15 ] === 1;
  245. if ( info.isOrthographic ) {
  246. // See OrthographicCamera.updateProjectionMatrix and Matrix4.makeOrthographic:
  247. // the view width and height are used to populate matrix elements 0 and 5.
  248. const w = 2 / projection[ 0 ];
  249. const h = 2 / projection[ 5 ];
  250. info.pixelSize = Math.max( h / resolution.height, w / resolution.width );
  251. } else {
  252. // See PerspectiveCamera.updateProjectionMatrix and Matrix4.makePerspective:
  253. // the vertical FOV is used to populate matrix element 5.
  254. info.sseDenominator = ( 2 / projection[ 5 ] ) / resolution.height;
  255. }
  256. info.invScale = invScale;
  257. // get frustum in group root frame
  258. tempMat.copy( group.matrixWorld );
  259. tempMat.premultiply( camera.matrixWorldInverse );
  260. tempMat.premultiply( camera.projectionMatrix );
  261. frustum.setFromProjectionMatrix( tempMat );
  262. // get transform position in group root frame
  263. position.set( 0, 0, 0 );
  264. position.applyMatrix4( camera.matrixWorld );
  265. position.applyMatrix4( tempMat2 );
  266. }
  267. super.update();
  268. }
  269. preprocessNode( tile, parentTile, tileSetDir ) {
  270. super.preprocessNode( tile, parentTile, tileSetDir );
  271. const transform = new Matrix4();
  272. if ( tile.transform ) {
  273. const transformArr = tile.transform;
  274. for ( let i = 0; i < 16; i ++ ) {
  275. transform.elements[ i ] = transformArr[ i ];
  276. }
  277. } else {
  278. transform.identity();
  279. }
  280. if ( parentTile ) {
  281. transform.premultiply( parentTile.cached.transform );
  282. }
  283. const transformInverse = new Matrix4().copy( transform ).invert();
  284. let box = null;
  285. let boxTransform = null;
  286. let boxTransformInverse = null;
  287. if ( 'box' in tile.boundingVolume ) {
  288. const data = tile.boundingVolume.box;
  289. box = new Box3();
  290. boxTransform = new Matrix4();
  291. boxTransformInverse = new Matrix4();
  292. // get the extents of the bounds in each axis
  293. vecX.set( data[ 3 ], data[ 4 ], data[ 5 ] );
  294. vecY.set( data[ 6 ], data[ 7 ], data[ 8 ] );
  295. vecZ.set( data[ 9 ], data[ 10 ], data[ 11 ] );
  296. const scaleX = vecX.length();
  297. const scaleY = vecY.length();
  298. const scaleZ = vecZ.length();
  299. vecX.normalize();
  300. vecY.normalize();
  301. vecZ.normalize();
  302. // handle the case where the box has a dimension of 0 in one axis
  303. if ( scaleX === 0 ) {
  304. vecX.crossVectors( vecY, vecZ );
  305. }
  306. if ( scaleY === 0 ) {
  307. vecY.crossVectors( vecX, vecZ );
  308. }
  309. if ( scaleZ === 0 ) {
  310. vecZ.crossVectors( vecX, vecY );
  311. }
  312. // create the oriented frame that the box exists in
  313. boxTransform.set(
  314. vecX.x, vecY.x, vecZ.x, data[ 0 ],
  315. vecX.y, vecY.y, vecZ.y, data[ 1 ],
  316. vecX.z, vecY.z, vecZ.z, data[ 2 ],
  317. 0, 0, 0, 1
  318. );
  319. boxTransform.premultiply( transform );
  320. boxTransformInverse.copy( boxTransform ).invert();
  321. // scale the box by the extents
  322. box.min.set( - scaleX, - scaleY, - scaleZ );
  323. box.max.set( scaleX, scaleY, scaleZ );
  324. }
  325. let sphere = null;
  326. if ( 'sphere' in tile.boundingVolume ) {
  327. const data = tile.boundingVolume.sphere;
  328. sphere = new Sphere();
  329. sphere.center.set( data[ 0 ], data[ 1 ], data[ 2 ] );
  330. sphere.radius = data[ 3 ];
  331. sphere.applyMatrix4( transform );
  332. } else if ( 'box' in tile.boundingVolume ) {
  333. const data = tile.boundingVolume.box;
  334. sphere = new Sphere();
  335. box.getBoundingSphere( sphere );
  336. sphere.center.set( data[ 0 ], data[ 1 ], data[ 2 ] );
  337. sphere.applyMatrix4( transform );
  338. }
  339. let region = null;
  340. if ( 'region' in tile.boundingVolume ) {
  341. console.warn( 'ThreeTilesRenderer: region bounding volume not supported.' );
  342. }
  343. tile.cached = {
  344. loadIndex: 0,
  345. transform,
  346. transformInverse,
  347. active: false,
  348. inFrustum: [],
  349. box,
  350. boxTransform,
  351. boxTransformInverse,
  352. sphere,
  353. region,
  354. scene: null,
  355. geometry: null,
  356. material: null,
  357. };
  358. }
  359. parseTile( buffer, tile, extension ) {
  360. tile._loadIndex = tile._loadIndex || 0;
  361. tile._loadIndex ++;
  362. const uri = tile.content.uri;
  363. const uriSplits = uri.split( /[\\\/]/g );
  364. uriSplits.pop();
  365. const workingPath = uriSplits.join( '/' );
  366. const fetchOptions = this.fetchOptions;
  367. const manager = this.manager;
  368. const loadIndex = tile._loadIndex;
  369. let promise = null;
  370. switch ( extension ) {
  371. case 'b3dm': {
  372. const loader = new B3DMLoader( manager );
  373. loader.workingPath = workingPath;
  374. loader.fetchOptions = fetchOptions;
  375. promise = loader
  376. .parse( buffer )
  377. .then( res => res.scene );
  378. break;
  379. }
  380. case 'pnts': {
  381. const loader = new PNTSLoader( manager );
  382. loader.workingPath = workingPath;
  383. loader.fetchOptions = fetchOptions;
  384. promise = loader
  385. .parse( buffer )
  386. .then( res => res.scene );
  387. break;
  388. }
  389. case 'i3dm': {
  390. const loader = new I3DMLoader( manager );
  391. loader.workingPath = workingPath;
  392. loader.fetchOptions = fetchOptions;
  393. promise = loader
  394. .parse( buffer )
  395. .then( res => res.scene );
  396. break;
  397. }
  398. case 'cmpt': {
  399. const loader = new CMPTLoader( manager );
  400. loader.workingPath = workingPath;
  401. loader.fetchOptions = fetchOptions;
  402. promise = loader
  403. .parse( buffer )
  404. .then( res => res.scene );
  405. break;
  406. }
  407. // 3DTILES_content_gltf
  408. case 'gltf':
  409. case 'glb':
  410. const loader = new GLTFExtensionLoader( manager );
  411. loader.workingPath = workingPath;
  412. loader.fetchOptions = fetchOptions;
  413. promise = loader
  414. .parse( buffer )
  415. .then( res => res.scene );
  416. break;
  417. default:
  418. console.warn( `TilesRenderer: Content type "${ extension }" not supported.` );
  419. promise = Promise.resolve( null );
  420. break;
  421. }
  422. return promise.then( scene => {
  423. if ( tile._loadIndex !== loadIndex ) {
  424. return;
  425. }
  426. const upAxis = this.rootTileSet.asset && this.rootTileSet.asset.gltfUpAxis || 'y';
  427. const cached = tile.cached;
  428. const cachedTransform = cached.transform;
  429. switch ( upAxis.toLowerCase() ) {
  430. case 'x':
  431. tempMat.makeRotationAxis( Y_AXIS, - Math.PI / 2 );
  432. break;
  433. case 'y':
  434. tempMat.makeRotationAxis( X_AXIS, Math.PI / 2 );
  435. break;
  436. case 'z':
  437. tempMat.identity();
  438. break;
  439. }
  440. // ensure the matrix is up to date in case the scene has a transform applied
  441. scene.updateMatrix();
  442. // apply the local up-axis correction rotation
  443. // GLTFLoader seems to never set a transformation on the root scene object so
  444. // any transformations applied to it can be assumed to be applied after load
  445. // (such as applying RTC_CENTER) meaning they should happen _after_ the z-up
  446. // rotation fix which is why "multiply" happens here.
  447. if ( extension !== 'pnts' ) {
  448. scene.matrix.multiply( tempMat );
  449. }
  450. scene.matrix.premultiply( cachedTransform );
  451. scene.matrix.decompose( scene.position, scene.quaternion, scene.scale );
  452. scene.traverse( c => {
  453. c[ INITIAL_FRUSTUM_CULLED ] = c.frustumCulled;
  454. } );
  455. updateFrustumCulled( scene, ! this.autoDisableRendererCulling );
  456. cached.scene = scene;
  457. // We handle raycasting in a custom way so remove it from here
  458. scene.traverse( c => {
  459. c.raycast = this._overridenRaycast;
  460. } );
  461. const materials = [];
  462. const geometry = [];
  463. const textures = [];
  464. scene.traverse( c => {
  465. if ( c.geometry ) {
  466. geometry.push( c.geometry );
  467. }
  468. if ( c.material ) {
  469. const material = c.material;
  470. materials.push( c.material );
  471. for ( const key in material ) {
  472. const value = material[ key ];
  473. if ( value && value.isTexture ) {
  474. textures.push( value );
  475. }
  476. }
  477. }
  478. } );
  479. cached.materials = materials;
  480. cached.geometry = geometry;
  481. cached.textures = textures;
  482. if ( this.onLoadModel ) {
  483. this.onLoadModel( scene, tile );
  484. }
  485. } );
  486. }
  487. disposeTile( tile ) {
  488. // This could get called before the tile has finished downloading
  489. const cached = tile.cached;
  490. if ( cached.scene ) {
  491. const materials = cached.materials;
  492. const geometry = cached.geometry;
  493. const textures = cached.textures;
  494. for ( let i = 0, l = geometry.length; i < l; i ++ ) {
  495. geometry[ i ].dispose();
  496. }
  497. for ( let i = 0, l = materials.length; i < l; i ++ ) {
  498. materials[ i ].dispose();
  499. }
  500. for ( let i = 0, l = textures.length; i < l; i ++ ) {
  501. const texture = textures[ i ];
  502. texture.dispose();
  503. }
  504. if ( this.onDisposeModel ) {
  505. this.onDisposeModel( cached.scene, tile );
  506. }
  507. cached.scene = null;
  508. cached.materials = null;
  509. cached.textures = null;
  510. cached.geometry = null;
  511. }
  512. tile._loadIndex ++;
  513. }
  514. setTileVisible( tile, visible ) {
  515. const scene = tile.cached.scene;
  516. const visibleTiles = this.visibleTiles;
  517. const group = this.group;
  518. if ( visible ) {
  519. group.add( scene );
  520. visibleTiles.add( tile );
  521. scene.updateMatrixWorld( true );
  522. } else {
  523. group.remove( scene );
  524. visibleTiles.delete( tile );
  525. }
  526. }
  527. setTileActive( tile, active ) {
  528. const activeTiles = this.activeTiles;
  529. if ( active ) {
  530. activeTiles.add( tile );
  531. } else {
  532. activeTiles.delete( tile );
  533. }
  534. }
  535. calculateError( tile ) {
  536. const cached = tile.cached;
  537. const inFrustum = cached.inFrustum;
  538. const cameras = this.cameras;
  539. const cameraInfo = this.cameraInfo;
  540. // TODO: Use the content bounding volume here?
  541. // TODO: We should use the largest distance to the tile between
  542. // all available bounding volume types.
  543. const boundingVolume = tile.boundingVolume;
  544. if ( 'box' in boundingVolume || 'sphere' in boundingVolume ) {
  545. const boundingSphere = cached.sphere;
  546. const boundingBox = cached.box;
  547. const boxTransformInverse = cached.boxTransformInverse;
  548. const transformInverse = cached.transformInverse;
  549. const useBox = boundingBox && boxTransformInverse;
  550. let maxError = - Infinity;
  551. let minDistance = Infinity;
  552. for ( let i = 0, l = cameras.length; i < l; i ++ ) {
  553. if ( ! inFrustum[ i ] ) {
  554. continue;
  555. }
  556. // transform camera position into local frame of the tile bounding box
  557. const info = cameraInfo[ i ];
  558. const invScale = info.invScale;
  559. let error;
  560. if ( info.isOrthographic ) {
  561. const pixelSize = info.pixelSize;
  562. error = tile.geometricError / ( pixelSize * invScale );
  563. } else {
  564. tempVector.copy( info.position );
  565. let distance;
  566. if ( useBox ) {
  567. tempVector.applyMatrix4( boxTransformInverse );
  568. distance = boundingBox.distanceToPoint( tempVector );
  569. } else {
  570. tempVector.applyMatrix4( transformInverse );
  571. // Sphere#distanceToPoint is negative inside the sphere, whereas Box3#distanceToPoint is
  572. // zero inside the box. Clipping the distance to a minimum of zero ensures that both
  573. // types of bounding volume behave the same way.
  574. distance = Math.max( boundingSphere.distanceToPoint( tempVector ), 0 );
  575. }
  576. const scaledDistance = distance * invScale;
  577. const sseDenominator = info.sseDenominator;
  578. error = tile.geometricError / ( scaledDistance * sseDenominator );
  579. minDistance = Math.min( minDistance, scaledDistance );
  580. }
  581. maxError = Math.max( maxError, error );
  582. }
  583. tile.__distanceFromCamera = minDistance;
  584. tile.__error = maxError;
  585. } else if ( 'region' in boundingVolume ) {
  586. // unsupported
  587. console.warn( 'ThreeTilesRenderer : Region bounds not supported.' );
  588. }
  589. }
  590. tileInView( tile ) {
  591. // TODO: we should use the more precise bounding volumes here if possible
  592. // cache the root-space planes
  593. // Use separating axis theorem for frustum and obb
  594. const cached = tile.cached;
  595. const sphere = cached.sphere;
  596. const inFrustum = cached.inFrustum;
  597. if ( sphere ) {
  598. const cameraInfo = this.cameraInfo;
  599. let inView = false;
  600. for ( let i = 0, l = cameraInfo.length; i < l; i ++ ) {
  601. // Track which camera frustums this tile is in so we can use it
  602. // to ignore the error calculations for cameras that can't see it
  603. const frustum = cameraInfo[ i ].frustum;
  604. if ( frustum.intersectsSphere( sphere ) ) {
  605. inView = true;
  606. inFrustum[ i ] = true;
  607. } else {
  608. inFrustum[ i ] = false;
  609. }
  610. }
  611. return inView;
  612. }
  613. return true;
  614. }
  615. }