TilesRendererBase.js 8.3 KB

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