TilesRenderer.js 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984
  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. this.onTileVisibilityChange = null;
  58. const manager = new LoadingManager();
  59. manager.setURLModifier( url => {
  60. if ( this.preprocessURL ) {
  61. return this.preprocessURL( url );
  62. } else {
  63. return url;
  64. }
  65. } );
  66. this.manager = manager;
  67. // Setting up the override raycasting function to be used by
  68. // 3D objects created by this renderer
  69. const tilesRenderer = this;
  70. this._overridenRaycast = function ( raycaster, intersects ) {
  71. if ( ! tilesRenderer.optimizeRaycast ) {
  72. Object.getPrototypeOf( this ).raycast.call( this, raycaster, intersects );
  73. }
  74. };
  75. }
  76. /* Public API */
  77. getBounds( box ) {
  78. if ( ! this.root ) {
  79. return false;
  80. }
  81. const cached = this.root.cached;
  82. const boundingBox = cached.box;
  83. const obbMat = cached.boxTransform;
  84. if ( boundingBox ) {
  85. box.copy( boundingBox );
  86. box.applyMatrix4( obbMat );
  87. return true;
  88. } else {
  89. return false;
  90. }
  91. }
  92. getOrientedBounds( box, matrix ) {
  93. if ( ! this.root ) {
  94. return false;
  95. }
  96. const cached = this.root.cached;
  97. const boundingBox = cached.box;
  98. const obbMat = cached.boxTransform;
  99. if ( boundingBox ) {
  100. box.copy( boundingBox );
  101. matrix.copy( obbMat );
  102. return true;
  103. } else {
  104. return false;
  105. }
  106. }
  107. getBoundingSphere( sphere ) {
  108. if ( ! this.root ) {
  109. return false;
  110. }
  111. const boundingSphere = this.root.cached.sphere;
  112. if ( boundingSphere ) {
  113. sphere.copy( boundingSphere );
  114. return true;
  115. } else {
  116. return false;
  117. }
  118. }
  119. forEachLoadedModel( callback ) {
  120. this.traverse( tile => {
  121. const scene = tile.cached.scene;
  122. if ( scene ) {
  123. callback( scene, tile );
  124. }
  125. } );
  126. }
  127. raycast( raycaster, intersects ) {
  128. if ( ! this.root ) {
  129. return;
  130. }
  131. if ( raycaster.firstHitOnly ) {
  132. const hit = raycastTraverseFirstHit( this.root, this.group, this.activeTiles, raycaster );
  133. if ( hit ) {
  134. intersects.push( hit );
  135. }
  136. } else {
  137. raycastTraverse( this.root, this.group, this.activeTiles, raycaster, intersects );
  138. }
  139. }
  140. hasCamera( camera ) {
  141. return this.cameraMap.has( camera );
  142. }
  143. setCamera( camera ) {
  144. const cameras = this.cameras;
  145. const cameraMap = this.cameraMap;
  146. if ( ! cameraMap.has( camera ) ) {
  147. cameraMap.set( camera, new Vector2() );
  148. cameras.push( camera );
  149. return true;
  150. }
  151. return false;
  152. }
  153. setResolution( camera, xOrVec, y ) {
  154. const cameraMap = this.cameraMap;
  155. if ( ! cameraMap.has( camera ) ) {
  156. return false;
  157. }
  158. if ( xOrVec instanceof Vector2 ) {
  159. cameraMap.get( camera ).copy( xOrVec );
  160. } else {
  161. cameraMap.get( camera ).set( xOrVec, y );
  162. }
  163. return true;
  164. }
  165. setResolutionFromRenderer( camera, renderer ) {
  166. const cameraMap = this.cameraMap;
  167. if ( ! cameraMap.has( camera ) ) {
  168. return false;
  169. }
  170. const resolution = cameraMap.get( camera );
  171. renderer.getSize( resolution );
  172. resolution.multiplyScalar( renderer.getPixelRatio() );
  173. return true;
  174. }
  175. deleteCamera( camera ) {
  176. const cameras = this.cameras;
  177. const cameraMap = this.cameraMap;
  178. if ( cameraMap.has( camera ) ) {
  179. const index = cameras.indexOf( camera );
  180. cameras.splice( index, 1 );
  181. cameraMap.delete( camera );
  182. return true;
  183. }
  184. return false;
  185. }
  186. /* Overriden */
  187. fetchTileSet( url, ...rest ) {
  188. const pr = super.fetchTileSet( url, ...rest );
  189. pr.then( json => {
  190. if ( this.onLoadTileSet ) {
  191. // Push this onto the end of the event stack to ensure this runs
  192. // after the base renderer has placed the provided json where it
  193. // needs to be placed and is ready for an update.
  194. Promise.resolve().then( () => {
  195. this.onLoadTileSet( json, url );
  196. } );
  197. }
  198. } );
  199. return pr;
  200. }
  201. update() {
  202. const group = this.group;
  203. const cameras = this.cameras;
  204. const cameraMap = this.cameraMap;
  205. const cameraInfo = this.cameraInfo;
  206. if ( cameras.length === 0 ) {
  207. console.warn( 'TilesRenderer: no cameras defined. Cannot update 3d tiles.' );
  208. return;
  209. }
  210. // automatically scale the array of cameraInfo to match the cameras
  211. while ( cameraInfo.length > cameras.length ) {
  212. cameraInfo.pop();
  213. }
  214. while ( cameraInfo.length < cameras.length ) {
  215. cameraInfo.push( {
  216. frustum: new Frustum(),
  217. isOrthographic: false,
  218. sseDenominator: - 1, // used if isOrthographic:false
  219. position: new Vector3(),
  220. invScale: - 1,
  221. pixelSize: 0, // used if isOrthographic:true
  222. } );
  223. }
  224. // extract scale of group container
  225. tempMat2.copy( group.matrixWorld ).invert();
  226. tempVector.setFromMatrixScale( tempMat2 );
  227. const 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. const 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. const upAxis = this.rootTileSet.asset && this.rootTileSet.asset.gltfUpAxis || 'y';
  371. const cached = tile.cached;
  372. const cachedTransform = cached.transform;
  373. switch ( upAxis.toLowerCase() ) {
  374. case 'x':
  375. tempMat.makeRotationAxis( Y_AXIS, - Math.PI / 2 );
  376. break;
  377. case 'y':
  378. tempMat.makeRotationAxis( X_AXIS, Math.PI / 2 );
  379. break;
  380. case 'z':
  381. tempMat.identity();
  382. break;
  383. }
  384. switch ( extension ) {
  385. case 'b3dm': {
  386. const loader = new B3DMLoader( manager );
  387. loader.workingPath = workingPath;
  388. loader.fetchOptions = fetchOptions;
  389. loader.adjustmentTransform.copy( tempMat );
  390. promise = loader
  391. .parse( buffer )
  392. .then( res => res.scene );
  393. break;
  394. }
  395. case 'pnts': {
  396. const loader = new PNTSLoader( manager );
  397. loader.workingPath = workingPath;
  398. loader.fetchOptions = fetchOptions;
  399. promise = loader
  400. .parse( buffer )
  401. .then( res => res.scene );
  402. break;
  403. }
  404. case 'i3dm': {
  405. const loader = new I3DMLoader( manager );
  406. loader.workingPath = workingPath;
  407. loader.fetchOptions = fetchOptions;
  408. loader.adjustmentTransform.copy( tempMat );
  409. promise = loader
  410. .parse( buffer )
  411. .then( res => res.scene );
  412. break;
  413. }
  414. case 'cmpt': {
  415. const loader = new CMPTLoader( manager );
  416. loader.workingPath = workingPath;
  417. loader.fetchOptions = fetchOptions;
  418. loader.adjustmentTransform.copy( tempMat );
  419. promise = loader
  420. .parse( buffer )
  421. .then( res => res.scene );
  422. break;
  423. }
  424. // 3DTILES_content_gltf
  425. case 'gltf':
  426. case 'glb':
  427. const loader = new GLTFExtensionLoader( manager );
  428. loader.workingPath = workingPath;
  429. loader.fetchOptions = fetchOptions;
  430. promise = loader
  431. .parse( buffer )
  432. .then( res => res.scene );
  433. break;
  434. default:
  435. console.warn( `TilesRenderer: Content type "${ extension }" not supported.` );
  436. promise = Promise.resolve( null );
  437. break;
  438. }
  439. return promise.then( scene => {
  440. if ( tile._loadIndex !== loadIndex ) {
  441. return;
  442. }
  443. // ensure the matrix is up to date in case the scene has a transform applied
  444. scene.updateMatrix();
  445. // apply the local up-axis correction rotation
  446. // GLTFLoader seems to never set a transformation on the root scene object so
  447. // any transformations applied to it can be assumed to be applied after load
  448. // (such as applying RTC_CENTER) meaning they should happen _after_ the z-up
  449. // rotation fix which is why "multiply" happens here.
  450. if ( extension === 'glb' || extension === 'gltf' ) {
  451. scene.matrix.multiply( tempMat );
  452. }
  453. scene.matrix.premultiply( cachedTransform );
  454. scene.matrix.decompose( scene.position, scene.quaternion, scene.scale );
  455. scene.traverse( c => {
  456. c[ INITIAL_FRUSTUM_CULLED ] = c.frustumCulled;
  457. } );
  458. updateFrustumCulled( scene, ! this.autoDisableRendererCulling );
  459. cached.scene = scene;
  460. // We handle raycasting in a custom way so remove it from here
  461. scene.traverse( c => {
  462. c.raycast = this._overridenRaycast;
  463. } );
  464. const materials = [];
  465. const geometry = [];
  466. const textures = [];
  467. scene.traverse( c => {
  468. if ( c.geometry ) {
  469. geometry.push( c.geometry );
  470. }
  471. if ( c.material ) {
  472. const material = c.material;
  473. materials.push( c.material );
  474. for ( const key in material ) {
  475. const value = material[ key ];
  476. if ( value && value.isTexture ) {
  477. textures.push( value );
  478. }
  479. }
  480. }
  481. } );
  482. cached.materials = materials;
  483. cached.geometry = geometry;
  484. cached.textures = textures;
  485. if ( this.onLoadModel ) {
  486. this.onLoadModel( scene, tile );
  487. }
  488. } );
  489. }
  490. disposeTile( tile ) {
  491. // This could get called before the tile has finished downloading
  492. const cached = tile.cached;
  493. if ( cached.scene ) {
  494. const materials = cached.materials;
  495. const geometry = cached.geometry;
  496. const textures = cached.textures;
  497. const parent = cached.scene.parent;
  498. for ( let i = 0, l = geometry.length; i < l; i ++ ) {
  499. geometry[ i ].dispose();
  500. }
  501. for ( let i = 0, l = materials.length; i < l; i ++ ) {
  502. materials[ i ].dispose();
  503. }
  504. for ( let i = 0, l = textures.length; i < l; i ++ ) {
  505. const texture = textures[ i ];
  506. texture.dispose();
  507. }
  508. if ( parent ) {
  509. parent.remove( cached.scene );
  510. }
  511. if ( this.onDisposeModel ) {
  512. this.onDisposeModel( cached.scene, tile );
  513. }
  514. cached.scene = null;
  515. cached.materials = null;
  516. cached.textures = null;
  517. cached.geometry = null;
  518. }
  519. this.activeTiles.delete( tile );
  520. this.visibleTiles.delete( tile );
  521. tile._loadIndex ++;
  522. }
  523. setTileVisible( tile, visible ) {
  524. const scene = tile.cached.scene;
  525. const visibleTiles = this.visibleTiles;
  526. const group = this.group;
  527. if ( visible ) {
  528. group.add( scene );
  529. visibleTiles.add( tile );
  530. scene.updateMatrixWorld( true );
  531. } else {
  532. group.remove( scene );
  533. visibleTiles.delete( tile );
  534. }
  535. if ( this.onTileVisibilityChange ) {
  536. this.onTileVisibilityChange( scene, tile, visible );
  537. }
  538. }
  539. setTileActive( tile, active ) {
  540. const activeTiles = this.activeTiles;
  541. if ( active ) {
  542. activeTiles.add( tile );
  543. } else {
  544. activeTiles.delete( tile );
  545. }
  546. }
  547. calculateError( tile ) {
  548. const cached = tile.cached;
  549. const inFrustum = cached.inFrustum;
  550. const cameras = this.cameras;
  551. const cameraInfo = this.cameraInfo;
  552. // TODO: Use the content bounding volume here?
  553. // TODO: We should use the largest distance to the tile between
  554. // all available bounding volume types.
  555. const boundingVolume = tile.boundingVolume;
  556. if ( 'box' in boundingVolume || 'sphere' in boundingVolume ) {
  557. const boundingSphere = cached.sphere;
  558. const boundingBox = cached.box;
  559. const boxTransformInverse = cached.boxTransformInverse;
  560. const transformInverse = cached.transformInverse;
  561. const useBox = boundingBox && boxTransformInverse;
  562. let maxError = - Infinity;
  563. let minDistance = Infinity;
  564. for ( let i = 0, l = cameras.length; i < l; i ++ ) {
  565. if ( ! inFrustum[ i ] ) {
  566. continue;
  567. }
  568. // transform camera position into local frame of the tile bounding box
  569. const info = cameraInfo[ i ];
  570. const invScale = info.invScale;
  571. let error;
  572. if ( info.isOrthographic ) {
  573. const pixelSize = info.pixelSize;
  574. error = tile.geometricError / ( pixelSize * invScale );
  575. } else {
  576. tempVector.copy( info.position );
  577. let distance;
  578. if ( useBox ) {
  579. tempVector.applyMatrix4( boxTransformInverse );
  580. distance = boundingBox.distanceToPoint( tempVector );
  581. } else {
  582. tempVector.applyMatrix4( transformInverse );
  583. // Sphere#distanceToPoint is negative inside the sphere, whereas Box3#distanceToPoint is
  584. // zero inside the box. Clipping the distance to a minimum of zero ensures that both
  585. // types of bounding volume behave the same way.
  586. distance = Math.max( boundingSphere.distanceToPoint( tempVector ), 0 );
  587. }
  588. const scaledDistance = distance * invScale;
  589. const sseDenominator = info.sseDenominator;
  590. error = tile.geometricError / ( scaledDistance * sseDenominator );
  591. minDistance = Math.min( minDistance, scaledDistance );
  592. }
  593. maxError = Math.max( maxError, error );
  594. }
  595. tile.__distanceFromCamera = minDistance;
  596. tile.__error = maxError;
  597. } else if ( 'region' in boundingVolume ) {
  598. // unsupported
  599. console.warn( 'ThreeTilesRenderer : Region bounds not supported.' );
  600. }
  601. }
  602. tileInView( tile ) {
  603. // TODO: we should use the more precise bounding volumes here if possible
  604. // cache the root-space planes
  605. // Use separating axis theorem for frustum and obb
  606. const cached = tile.cached;
  607. const sphere = cached.sphere;
  608. const inFrustum = cached.inFrustum;
  609. if ( sphere ) {
  610. const cameraInfo = this.cameraInfo;
  611. let inView = false;
  612. for ( let i = 0, l = cameraInfo.length; i < l; i ++ ) {
  613. // Track which camera frustums this tile is in so we can use it
  614. // to ignore the error calculations for cameras that can't see it
  615. const frustum = cameraInfo[ i ].frustum;
  616. if ( frustum.intersectsSphere( sphere ) ) {
  617. inView = true;
  618. inFrustum[ i ] = true;
  619. } else {
  620. inFrustum[ i ] = false;
  621. }
  622. }
  623. return inView;
  624. }
  625. return true;
  626. }
  627. }