TilesRendererBase.js 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430
  1. import path from 'path';
  2. import { LRUCache } from '../utilities/LRUCache.js';
  3. import { PriorityQueue } from '../utilities/PriorityQueue.js';
  4. import { determineFrustumSet, toggleTiles, skipTraversal, markUsedSetLeaves, traverseSet } from './traverseFunctions.js';
  5. import { UNLOADED, LOADING, PARSING, LOADED, FAILED } from './constants.js';
  6. // TODO: Address the issue of too many promises, garbage collection
  7. // TODO: See if using classes improves performance
  8. // TODO: See if declaring function inline improves performance
  9. // TODO: Make sure active state works as expected
  10. // Function for sorting the evicted LRU items. We should evict the shallowest depth first.
  11. const lruSort = ( a, b ) => a.__depth - b.__depth;
  12. export class TilesRendererBase {
  13. get rootTileSet() {
  14. const tileSet = this.tileSets[ this.rootURL ];
  15. if ( ! tileSet || tileSet instanceof Promise ) {
  16. return null;
  17. } else {
  18. return tileSet;
  19. }
  20. }
  21. get root() {
  22. const tileSet = this.rootTileSet;
  23. return tileSet ? tileSet.root : null;
  24. }
  25. constructor( url ) {
  26. // state
  27. this.tileSets = {};
  28. this.rootURL = url;
  29. this.fetchOptions = {};
  30. this.lruCache = new LRUCache();
  31. this.downloadQueue = new PriorityQueue( 4 );
  32. this.parseQueue = new PriorityQueue( 1 );
  33. this.stats = {
  34. parsing: 0,
  35. downloading: 0,
  36. inFrustum: 0,
  37. used: 0,
  38. active: 0,
  39. visible: 0,
  40. };
  41. this.frameCount = 0;
  42. // options
  43. this.errorTarget = 6.0;
  44. this.errorThreshold = Infinity;
  45. this.loadSiblings = true;
  46. this.displayActiveTiles = false;
  47. this.maxDepth = Infinity;
  48. }
  49. traverse( beforecb, aftercb ) {
  50. const tileSets = this.tileSets;
  51. const rootTileSet = tileSets[ this.rootURL ];
  52. if ( ! rootTileSet || ! rootTileSet.root ) return;
  53. traverseSet( rootTileSet.root, beforecb, aftercb );
  54. }
  55. // Public API
  56. update() {
  57. const stats = this.stats;
  58. const lruCache = this.lruCache;
  59. const tileSets = this.tileSets;
  60. const rootTileSet = tileSets[ this.rootURL ];
  61. if ( ! ( this.rootURL in tileSets ) ) {
  62. this.loadTileSet( this.rootURL );
  63. return;
  64. } else if ( ! rootTileSet || ! rootTileSet.root ) {
  65. return;
  66. }
  67. const root = rootTileSet.root;
  68. stats.inFrustum = 0,
  69. stats.used = 0,
  70. stats.active = 0,
  71. stats.visible = 0,
  72. this.frameCount ++;
  73. determineFrustumSet( root, this );
  74. markUsedSetLeaves( root, this );
  75. skipTraversal( root, this );
  76. toggleTiles( root, this );
  77. // TODO: We may want to add this function in the requestTileContents function
  78. lruCache.scheduleUnload( lruSort );
  79. }
  80. // Overrideable
  81. parseTile( buffer, tile ) {
  82. return null;
  83. }
  84. disposeTile( tile ) {
  85. }
  86. preprocessNode( tile, parentTile, tileSetDir ) {
  87. if ( tile.content ) {
  88. // Fix old file formats
  89. if ( ! ( 'uri' in tile.content ) && 'url' in tile.content ) {
  90. tile.content.uri = tile.content.url;
  91. delete tile.content.url;
  92. }
  93. if ( tile.content.uri ) {
  94. tile.content.uri = path.join( tileSetDir, tile.content.uri );
  95. }
  96. // TODO: fix for some cases where tilesets provide the bounding volume
  97. // but volumes are not present.
  98. if (
  99. tile.content.boundingVolume &&
  100. ! (
  101. 'box' in tile.content.boundingVolume ||
  102. 'sphere' in tile.content.boundingVolume ||
  103. 'region' in tile.content.boundingVolume
  104. )
  105. ) {
  106. delete tile.content.boundingVolume;
  107. }
  108. }
  109. tile.parent = parentTile;
  110. tile.children = tile.children || [];
  111. tile.__contentEmpty = ! tile.content || ! tile.content.uri;
  112. tile.__error = 0.0;
  113. tile.__inFrustum = false;
  114. tile.__isLeaf = false;
  115. tile.__usedLastFrame = false;
  116. tile.__used = false;
  117. tile.__wasSetVisible = false;
  118. tile.__visible = false;
  119. tile.__childrenWereVisible = false;
  120. tile.__wasSetActive = false;
  121. tile.__active = false;
  122. tile.__loadingState = UNLOADED;
  123. tile.__loadIndex = 0;
  124. tile.__loadAbort = null;
  125. if ( parentTile === null ) {
  126. tile.__depth = 0;
  127. } else {
  128. tile.__depth = parentTile.__depth + 1;
  129. }
  130. }
  131. setTileActive( tile, state ) {
  132. }
  133. setTileVisible( tile, state ) {
  134. }
  135. calculateError( tile ) {
  136. return 0;
  137. }
  138. tileInView( tile ) {
  139. return true;
  140. }
  141. // Private Functions
  142. loadTileSet( url ) {
  143. const tileSets = this.tileSets;
  144. if ( ! ( url in tileSets ) ) {
  145. const pr =
  146. fetch( url, this.fetchOptions )
  147. .then( res => {
  148. if ( res.ok ) {
  149. return res.json();
  150. } else {
  151. throw new Error( `Status ${ res.status } (${ res.statusText })` );
  152. }
  153. } )
  154. .then( json => {
  155. // TODO: Add version query?
  156. const version = json.asset.version;
  157. console.assert( version === '1.0' || version === '0.0' );
  158. const basePath = path.dirname( url );
  159. traverseSet( json.root, ( node, parent ) => this.preprocessNode( node, parent, basePath ) );
  160. tileSets[ url ] = json;
  161. } );
  162. pr.catch( e => {
  163. console.error( `TilesLoader: Failed to load tile set json "${ url }"` );
  164. console.error( e );
  165. delete tileSets[ url ];
  166. } );
  167. tileSets[ url ] = pr;
  168. }
  169. return Promise.resolve( tileSets[ url ] );
  170. }
  171. requestTileContents( tile ) {
  172. // If the tile is already being loaded then don't
  173. // start it again.
  174. if ( tile.__loadingState !== UNLOADED ) {
  175. return;
  176. }
  177. // TODO: reuse the functions created here?
  178. const lruCache = this.lruCache;
  179. const downloadQueue = this.downloadQueue;
  180. const parseQueue = this.parseQueue;
  181. lruCache.add( tile, t => {
  182. if ( t.__loadingState === LOADING ) {
  183. t.__loadAbort.abort();
  184. t.__loadAbort = null;
  185. } else {
  186. this.disposeTile( t );
  187. }
  188. if ( t.__loadingState === LOADING ) {
  189. stats.downloading --;
  190. } else if ( t.__loadingState === PARSING ) {
  191. stats.parsing --;
  192. }
  193. t.__loadingState = UNLOADED;
  194. t.__loadIndex ++;
  195. // TODO: Removing from the queues here is slow?
  196. parseQueue.remove( t );
  197. downloadQueue.remove( t );
  198. } );
  199. tile.__loadIndex ++;
  200. const loadIndex = tile.__loadIndex;
  201. const priority = 1 / ( tile.__depth + 1 );
  202. const stats = this.stats;
  203. const controller = new AbortController();
  204. const signal = controller.signal;
  205. stats.downloading ++;
  206. tile.__loadAbort = controller;
  207. tile.__loadingState = LOADING;
  208. downloadQueue.add( tile, priority, tile => {
  209. if ( tile.__loadIndex !== loadIndex ) {
  210. return Promise.resolve();
  211. }
  212. return fetch( tile.content.uri, Object.assign( { signal }, this.fetchOptions ) );
  213. } )
  214. .then( res => {
  215. if ( tile.__loadIndex !== loadIndex ) {
  216. return;
  217. }
  218. if ( res.ok ) {
  219. return res.arrayBuffer();
  220. } else {
  221. throw new Error( `Failed to load model with error code ${res.status}` );
  222. }
  223. } )
  224. .then( buffer => {
  225. // if it has been unloaded then the tile has been disposed
  226. if ( tile.__loadIndex !== loadIndex ) {
  227. return;
  228. }
  229. stats.downloading --;
  230. stats.parsing ++;
  231. tile.__loadAbort = null;
  232. tile.__loadingState = PARSING;
  233. return parseQueue.add( buffer, priority, buffer => {
  234. // if it has been unloaded then the tile has been disposed
  235. if ( tile.__loadIndex !== loadIndex ) {
  236. return Promise.resolve();
  237. }
  238. return this.parseTile( buffer, tile );
  239. } );
  240. } )
  241. .then( () => {
  242. // if it has been unloaded then the tile has been disposed
  243. if ( tile.__loadIndex !== loadIndex ) {
  244. return;
  245. }
  246. stats.parsing --;
  247. tile.__loadingState = LOADED;
  248. if ( tile.__wasSetVisible ) {
  249. this.setTileVisible( tile, true );
  250. }
  251. if ( tile.__wasSetActive ) {
  252. this.setTileActive( tile, true );
  253. }
  254. } )
  255. .catch( e => {
  256. // if it has been unloaded then the tile has been disposed
  257. if ( tile.__loadIndex !== loadIndex ) {
  258. return;
  259. }
  260. if ( e.name !== 'AbortError' ) {
  261. console.error( 'TilesRenderer : Failed to load tile.' );
  262. console.error( e );
  263. tile.__loadingState = FAILED;
  264. } else {
  265. lruCache.remove( tile );
  266. }
  267. } );
  268. }
  269. }