TilesRenderer.js 19 KB

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