@loaders.gl/tiles 5.0.0-alpha.1 → 5.0.0-alpha.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (115) hide show
  1. package/LICENSE +14 -3
  2. package/dist/dist.dev.js +2396 -274
  3. package/dist/dist.min.js +1 -1
  4. package/dist/index.cjs +2763 -337
  5. package/dist/index.cjs.map +3 -4
  6. package/dist/index.d.ts +7 -1
  7. package/dist/index.d.ts.map +1 -1
  8. package/dist/index.js +1 -0
  9. package/dist/index.js.map +1 -1
  10. package/dist/point-cloud/point-cloud-tile.d.ts.map +1 -1
  11. package/dist/point-cloud/point-cloud-tile.js +3 -0
  12. package/dist/point-cloud/point-cloud-tile.js.map +1 -1
  13. package/dist/point-cloud/point-cloud-tileset.d.ts.map +1 -1
  14. package/dist/point-cloud/point-cloud-tileset.js +3 -0
  15. package/dist/point-cloud/point-cloud-tileset.js.map +1 -1
  16. package/dist/point-cloud/types.d.ts.map +1 -1
  17. package/dist/point-cloud/types.js +3 -0
  18. package/dist/point-cloud/types.js.map +1 -1
  19. package/dist/raster-set.d.ts +35 -29
  20. package/dist/raster-set.d.ts.map +1 -1
  21. package/dist/raster-set.js +7 -1
  22. package/dist/raster-set.js.map +1 -1
  23. package/dist/tileset-3d/common/tile-3d.d.ts +98 -3
  24. package/dist/tileset-3d/common/tile-3d.d.ts.map +1 -1
  25. package/dist/tileset-3d/common/tile-3d.js +229 -15
  26. package/dist/tileset-3d/common/tile-3d.js.map +1 -1
  27. package/dist/tileset-3d/common/tileset-3d.d.ts +143 -3
  28. package/dist/tileset-3d/common/tileset-3d.d.ts.map +1 -1
  29. package/dist/tileset-3d/common/tileset-3d.js +257 -16
  30. package/dist/tileset-3d/common/tileset-3d.js.map +1 -1
  31. package/dist/tileset-3d/common/tileset-cache.d.ts +11 -1
  32. package/dist/tileset-3d/common/tileset-cache.d.ts.map +1 -1
  33. package/dist/tileset-3d/common/tileset-cache.js +11 -3
  34. package/dist/tileset-3d/common/tileset-cache.js.map +1 -1
  35. package/dist/tileset-3d/common/tileset-source.d.ts +18 -0
  36. package/dist/tileset-3d/common/tileset-source.d.ts.map +1 -1
  37. package/dist/tileset-3d/common/tileset-source.js.map +1 -1
  38. package/dist/tileset-3d/common/tileset-traverser.d.ts +13 -0
  39. package/dist/tileset-3d/common/tileset-traverser.d.ts.map +1 -1
  40. package/dist/tileset-3d/common/tileset-traverser.js +28 -1
  41. package/dist/tileset-3d/common/tileset-traverser.js.map +1 -1
  42. package/dist/tileset-3d/format-3d-tiles/implicit-tiling.d.ts +111 -0
  43. package/dist/tileset-3d/format-3d-tiles/implicit-tiling.d.ts.map +1 -0
  44. package/dist/tileset-3d/format-3d-tiles/implicit-tiling.js +340 -0
  45. package/dist/tileset-3d/format-3d-tiles/implicit-tiling.js.map +1 -0
  46. package/dist/tileset-3d/format-3d-tiles/tiles-3d-source.d.ts +92 -3
  47. package/dist/tileset-3d/format-3d-tiles/tiles-3d-source.d.ts.map +1 -1
  48. package/dist/tileset-3d/format-3d-tiles/tiles-3d-source.js +211 -13
  49. package/dist/tileset-3d/format-3d-tiles/tiles-3d-source.js.map +1 -1
  50. package/dist/tileset-3d/format-3d-tiles/tileset-3d-traverser.d.ts +36 -0
  51. package/dist/tileset-3d/format-3d-tiles/tileset-3d-traverser.d.ts.map +1 -1
  52. package/dist/tileset-3d/format-3d-tiles/tileset-3d-traverser.js +66 -0
  53. package/dist/tileset-3d/format-3d-tiles/tileset-3d-traverser.js.map +1 -1
  54. package/dist/tileset-3d/format-i3s/i3s-pending-tiles-register.d.ts.map +1 -1
  55. package/dist/tileset-3d/format-i3s/i3s-pending-tiles-register.js +3 -0
  56. package/dist/tileset-3d/format-i3s/i3s-pending-tiles-register.js.map +1 -1
  57. package/dist/tileset-3d/format-i3s/i3s-tile-manager.d.ts.map +1 -1
  58. package/dist/tileset-3d/format-i3s/i3s-tile-manager.js +3 -0
  59. package/dist/tileset-3d/format-i3s/i3s-tile-manager.js.map +1 -1
  60. package/dist/tileset-3d/format-i3s/i3s-tileset-traverser.d.ts.map +1 -1
  61. package/dist/tileset-3d/format-i3s/i3s-tileset-traverser.js +3 -0
  62. package/dist/tileset-3d/format-i3s/i3s-tileset-traverser.js.map +1 -1
  63. package/dist/tileset-3d/helpers/3d-tiles-options.d.ts.map +1 -1
  64. package/dist/tileset-3d/helpers/3d-tiles-options.js +3 -0
  65. package/dist/tileset-3d/helpers/3d-tiles-options.js.map +1 -1
  66. package/dist/tileset-3d/helpers/bounding-volume.d.ts.map +1 -1
  67. package/dist/tileset-3d/helpers/bounding-volume.js +1 -0
  68. package/dist/tileset-3d/helpers/bounding-volume.js.map +1 -1
  69. package/dist/tileset-3d/helpers/frame-state.d.ts +59 -1
  70. package/dist/tileset-3d/helpers/frame-state.d.ts.map +1 -1
  71. package/dist/tileset-3d/helpers/frame-state.js +84 -9
  72. package/dist/tileset-3d/helpers/frame-state.js.map +1 -1
  73. package/dist/tileset-3d/helpers/tiles-3d-lod.d.ts +61 -9
  74. package/dist/tileset-3d/helpers/tiles-3d-lod.d.ts.map +1 -1
  75. package/dist/tileset-3d/helpers/tiles-3d-lod.js +198 -93
  76. package/dist/tileset-3d/helpers/tiles-3d-lod.js.map +1 -1
  77. package/dist/tileset-3d/helpers/tiles-3d-request-priority.d.ts +134 -0
  78. package/dist/tileset-3d/helpers/tiles-3d-request-priority.d.ts.map +1 -0
  79. package/dist/tileset-3d/helpers/tiles-3d-request-priority.js +179 -0
  80. package/dist/tileset-3d/helpers/tiles-3d-request-priority.js.map +1 -0
  81. package/dist/types.d.ts +2 -0
  82. package/dist/types.d.ts.map +1 -1
  83. package/dist/types.js +3 -0
  84. package/dist/types.js.map +1 -1
  85. package/dist/utils/doubly-linked-list.d.ts.map +1 -1
  86. package/dist/utils/doubly-linked-list.js +1 -0
  87. package/dist/utils/doubly-linked-list.js.map +1 -1
  88. package/dist/utils/managed-array.d.ts.map +1 -1
  89. package/dist/utils/managed-array.js +1 -0
  90. package/dist/utils/managed-array.js.map +1 -1
  91. package/package.json +5 -5
  92. package/src/index.ts +19 -0
  93. package/src/point-cloud/point-cloud-tile.ts +4 -0
  94. package/src/point-cloud/point-cloud-tileset.ts +4 -0
  95. package/src/point-cloud/types.ts +4 -0
  96. package/src/raster-set.ts +81 -43
  97. package/src/tileset-3d/common/tile-3d.ts +285 -21
  98. package/src/tileset-3d/common/tileset-3d.ts +368 -17
  99. package/src/tileset-3d/common/tileset-cache.ts +15 -7
  100. package/src/tileset-3d/common/tileset-source.ts +21 -0
  101. package/src/tileset-3d/common/tileset-traverser.ts +29 -1
  102. package/src/tileset-3d/format-3d-tiles/implicit-tiling.ts +534 -0
  103. package/src/tileset-3d/format-3d-tiles/tiles-3d-source.ts +266 -13
  104. package/src/tileset-3d/format-3d-tiles/tileset-3d-traverser.ts +77 -0
  105. package/src/tileset-3d/format-i3s/i3s-pending-tiles-register.ts +4 -0
  106. package/src/tileset-3d/format-i3s/i3s-tile-manager.ts +4 -0
  107. package/src/tileset-3d/format-i3s/i3s-tileset-traverser.ts +4 -0
  108. package/src/tileset-3d/helpers/3d-tiles-options.ts +4 -0
  109. package/src/tileset-3d/helpers/bounding-volume.ts +2 -0
  110. package/src/tileset-3d/helpers/frame-state.ts +139 -11
  111. package/src/tileset-3d/helpers/tiles-3d-lod.ts +291 -112
  112. package/src/tileset-3d/helpers/tiles-3d-request-priority.ts +301 -0
  113. package/src/types.ts +6 -0
  114. package/src/utils/doubly-linked-list.ts +2 -0
  115. package/src/utils/managed-array.ts +2 -0
@@ -10,6 +10,7 @@ import type {Tile3D} from '../common/tile-3d';
10
10
  import {Tileset3DTraverser} from './tileset-3d-traverser';
11
11
  import type {Tileset3D} from '../common/tileset-3d';
12
12
  import type {
13
+ TileChildrenLoadResult,
13
14
  TileContentLoadResult,
14
15
  TilesetContentFormats,
15
16
  TilesetJSON,
@@ -24,6 +25,12 @@ import {Tile3D as Tile3DNode} from '../common/tile-3d';
24
25
  import {getZoomFromBoundingVolume} from '../helpers/zoom';
25
26
  import {TILESET_TYPE} from '../../constants';
26
27
  import type {TilesetTraverser, TilesetTraverserProps} from '../common/tileset-traverser';
28
+ import type {FrameState} from '../helpers/frame-state';
29
+ import {
30
+ materializeImplicitSubtree,
31
+ type ImplicitSubtreeReference,
32
+ type ParsedImplicitSubtree
33
+ } from './implicit-tiling';
27
34
 
28
35
  const EMPTY_CONTENT_FORMATS: TilesetContentFormats = {
29
36
  draco: false,
@@ -32,6 +39,24 @@ const EMPTY_CONTENT_FORMATS: TilesetContentFormats = {
32
39
  ktx2: false
33
40
  };
34
41
 
42
+ const DEFAULT_MAXIMUM_CACHED_SUBTREES = 32;
43
+
44
+ /** Diagnostics for source-managed implicit subtree loading. */
45
+ export type ImplicitTilingStats = {
46
+ /** Subtree resources requested from the resolver or core API. */
47
+ requestedSubtrees: number;
48
+ /** Successful subtree materializations, including parsed-cache hits. */
49
+ loadedSubtrees: number;
50
+ /** Requests served by the source's parsed-subtree cache. */
51
+ cacheHits: number;
52
+ /** Parsed subtree resources currently retained for reuse. */
53
+ cachedSubtrees: number;
54
+ /** Subtree resource requests currently in flight. */
55
+ pendingSubtrees: number;
56
+ /** Runtime headers created below already-existing subtree-root placeholders. */
57
+ materializedTiles: number;
58
+ };
59
+
35
60
  /**
36
61
  * {@link Tileset3DSource} implementation for 3D Tiles datasets.
37
62
  */
@@ -65,6 +90,26 @@ export class Tiles3DSource implements Tileset3DSource {
65
90
  metadata?: TilesetSourceMetadata;
66
91
 
67
92
  private readonly queryParams: Record<string, string> = {};
93
+ /** Final request URLs cached by unmodified tile content URL. */
94
+ private readonly tileUrlCache: Map<string, string> = new Map();
95
+ /** Parsed subtree promises keyed by final source URL for request deduplication and LRU reuse. */
96
+ private readonly implicitSubtreeCache: Map<string, Promise<ParsedImplicitSubtree>> = new Map();
97
+ /** URLs whose subtree resource requests have not settled. */
98
+ private readonly pendingImplicitSubtreeUrls: Set<string> = new Set();
99
+ /** Maximum number of settled parsed subtrees retained by this source. */
100
+ private readonly maximumCachedSubtrees: number;
101
+ /** Mutable counters exposed as a defensive snapshot through {@link getImplicitTilingStats}. */
102
+ private readonly implicitTilingStats: Omit<
103
+ ImplicitTilingStats,
104
+ 'cachedSubtrees' | 'pendingSubtrees'
105
+ > = {
106
+ requestedSubtrees: 0,
107
+ loadedSubtrees: 0,
108
+ cacheHits: 0,
109
+ materializedTiles: 0
110
+ };
111
+ /** Whether the owning tileset has released this source. */
112
+ private destroyed = false;
68
113
  private readonly extensionsUsed: string[] = [];
69
114
  private readonly resolver?: TilesetSourceResolver;
70
115
  private rootTileset: TilesetJSON;
@@ -84,6 +129,25 @@ export class Tiles3DSource implements Tileset3DSource {
84
129
  this.resolver = request.resolver;
85
130
  this.coreApi = request.coreApi;
86
131
  this.loadOptions = loadOptions;
132
+ const maximumCachedSubtrees = Number(
133
+ (loadOptions['3d-tiles'] as Record<string, unknown> | undefined)?.maximumCachedSubtrees
134
+ );
135
+ this.maximumCachedSubtrees = Number.isFinite(maximumCachedSubtrees)
136
+ ? Math.max(0, Math.floor(maximumCachedSubtrees))
137
+ : DEFAULT_MAXIMUM_CACHED_SUBTREES;
138
+ }
139
+
140
+ /**
141
+ * Releases URL and parsed-subtree caches and blocks late subtree installation.
142
+ *
143
+ * The injected resolver or core API remains responsible for transport-level abort signals. A
144
+ * request that cannot be aborted may finish parsing, but it will not mutate destroyed tiles.
145
+ */
146
+ destroy(): void {
147
+ this.destroyed = true;
148
+ this.tileUrlCache.clear();
149
+ this.implicitSubtreeCache.clear();
150
+ this.pendingImplicitSubtreeUrls.clear();
87
151
  }
88
152
 
89
153
  /**
@@ -104,7 +168,9 @@ export class Tiles3DSource implements Tileset3DSource {
104
168
 
105
169
  if (this.rootTileset.queryString) {
106
170
  const searchParams = new URLSearchParams(this.rootTileset.queryString);
107
- Object.assign(this.queryParams, Object.fromEntries(searchParams.entries()));
171
+ for (const [parameterName, parameterValue] of searchParams.entries()) {
172
+ this.setQueryParameter(parameterName, parameterValue);
173
+ }
108
174
  }
109
175
 
110
176
  this.asset = this.rootTileset.asset;
@@ -120,7 +186,7 @@ export class Tiles3DSource implements Tileset3DSource {
120
186
  }
121
187
 
122
188
  if ('tilesetVersion' in this.asset) {
123
- this.queryParams.v = this.asset.tilesetVersion;
189
+ this.setQueryParameter('v', this.asset.tilesetVersion);
124
190
  }
125
191
 
126
192
  this.properties = this.rootTileset.properties;
@@ -161,7 +227,7 @@ export class Tiles3DSource implements Tileset3DSource {
161
227
  }
162
228
 
163
229
  /**
164
- * Builds the eager runtime tile tree for a 3D Tiles subtree.
230
+ * Builds explicit runtime headers while leaving implicit subtree references lazy.
165
231
  */
166
232
  initializeTileHeaders(
167
233
  tileset: Tileset3D,
@@ -186,7 +252,7 @@ export class Tiles3DSource implements Tileset3DSource {
186
252
  const url = new URL(childTile.contentUrl);
187
253
  const session = url.searchParams.get('session');
188
254
  if (session) {
189
- this.queryParams.session = session;
255
+ this.setQueryParameter('session', session);
190
256
  }
191
257
  }
192
258
  tile.children.push(childTile);
@@ -216,7 +282,9 @@ export class Tiles3DSource implements Tileset3DSource {
216
282
  ...this.loadOptions,
217
283
  [this.loader.id]: {
218
284
  ...tilesetLoaderOptions,
219
- isTileset: tile.type === 'json',
285
+ // Content bytes, rather than URL suffixes, distinguish external tilesets from renderable
286
+ // payloads. This is required for signed and extensionless resources.
287
+ isTileset: 'auto',
220
288
  assetGltfUpAxis: (this.asset && this.asset.gltfUpAxis) || 'Y'
221
289
  }
222
290
  };
@@ -226,19 +294,89 @@ export class Tiles3DSource implements Tileset3DSource {
226
294
 
227
295
  return {
228
296
  loaded: true,
229
- nestedTileset: tile.contentUrl.includes('.json') ? content : undefined
297
+ nestedTileset: content?.shape === 'tileset3d' ? content : undefined
298
+ };
299
+ }
300
+
301
+ /**
302
+ * Loads, materializes, and installs exactly one implicit subtree.
303
+ *
304
+ * Final URLs pass through the same query inheritance and archive/custom resolver path as render
305
+ * content. Parsed resources are deduplicated by final URL, while the pure materializer creates
306
+ * lazy placeholders for every available child subtree instead of recursively fetching them.
307
+ *
308
+ * @param tile - Existing contentless subtree-root placeholder.
309
+ * @param frameState - View state that made this request eligible; priority is consumed earlier.
310
+ * @returns Counts describing the installed subtree.
311
+ */
312
+ async loadTileChildren(tile: Tile3D, frameState: FrameState): Promise<TileChildrenLoadResult> {
313
+ void frameState;
314
+ const reference = tile.header.implicitSubtree as ImplicitSubtreeReference | undefined;
315
+ if (!reference) {
316
+ return {loaded: false, tileCount: 0, childSubtreeCount: 0};
317
+ }
318
+
319
+ const subtreeUrl = this.getTileUrl(reference.subtreeUrl);
320
+ const subtree = await this.loadImplicitSubtreeResource(subtreeUrl);
321
+ if (this.destroyed || tile.isDestroyed()) {
322
+ return {loaded: false, tileCount: 0, childSubtreeCount: 0};
323
+ }
324
+ const materializedSubtree = materializeImplicitSubtree(subtree, {
325
+ ...reference,
326
+ subtreeUrl
327
+ });
328
+
329
+ tile.applyImplicitSubtreeHeader(materializedSubtree.root);
330
+ const materializedTileCount = this.initializeMaterializedChildren(
331
+ tile.tileset,
332
+ tile,
333
+ materializedSubtree.root.children
334
+ );
335
+ this.implicitTilingStats.loadedSubtrees++;
336
+ this.implicitTilingStats.materializedTiles += materializedTileCount;
337
+
338
+ return {
339
+ loaded: true,
340
+ tileCount: materializedSubtree.tileCount,
341
+ childSubtreeCount: materializedSubtree.childSubtreeCount
342
+ };
343
+ }
344
+
345
+ /**
346
+ * Returns a snapshot of implicit-subtree request, cache, and materialization counters.
347
+ *
348
+ * @returns Immutable-by-convention diagnostic values for runtime inspection.
349
+ */
350
+ getImplicitTilingStats(): ImplicitTilingStats {
351
+ return {
352
+ ...this.implicitTilingStats,
353
+ cachedSubtrees: this.implicitSubtreeCache.size - this.pendingImplicitSubtreeUrls.size,
354
+ pendingSubtrees: this.pendingImplicitSubtreeUrls.size
230
355
  };
231
356
  }
232
357
 
233
358
  /**
234
359
  * Resolves a tile content URL with source-managed query parameters.
360
+ *
361
+ * Existing per-resource parameters take precedence over inherited root, version, and session
362
+ * values. Completed URLs are cached by the original tile path; {@link setQueryParameter}
363
+ * invalidates the cache before changed source state can be observed.
364
+ *
365
+ * @param tilePath - Unmodified absolute content URL or data URL from the tile header.
366
+ * @returns Content URL with any missing source parameters appended.
235
367
  */
236
368
  getTileUrl(tilePath: string): string {
237
369
  if (tilePath.startsWith('data:')) {
238
370
  return tilePath;
239
371
  }
240
372
 
373
+ const cachedTileUrl = this.tileUrlCache.get(tilePath);
374
+ if (cachedTileUrl) {
375
+ return cachedTileUrl;
376
+ }
377
+
241
378
  if (!Object.keys(this.queryParams).length) {
379
+ this.tileUrlCache.set(tilePath, tilePath);
242
380
  return tilePath;
243
381
  }
244
382
 
@@ -252,7 +390,27 @@ export class Tiles3DSource implements Tileset3DSource {
252
390
  }
253
391
 
254
392
  const queryParams = mergedQueryParams.toString();
255
- return queryParams ? `${pathWithoutQuery}?${queryParams}` : pathWithoutQuery;
393
+ const tileUrl = queryParams ? `${pathWithoutQuery}?${queryParams}` : pathWithoutQuery;
394
+ this.tileUrlCache.set(tilePath, tileUrl);
395
+ return tileUrl;
396
+ }
397
+
398
+ /**
399
+ * Updates an inherited source query parameter and invalidates derived request URLs.
400
+ *
401
+ * Root tokens and tileset versions normally settle during initialization. Some providers expose
402
+ * a session parameter on a child URL, so invalidation is required to prevent URLs cached earlier
403
+ * in header construction from retaining stale authentication state.
404
+ *
405
+ * @param parameterName - Query parameter name.
406
+ * @param parameterValue - Query parameter value.
407
+ */
408
+ private setQueryParameter(parameterName: string, parameterValue: string): void {
409
+ if (this.queryParams[parameterName] === parameterValue) {
410
+ return;
411
+ }
412
+ this.queryParams[parameterName] = parameterValue;
413
+ this.tileUrlCache.clear();
256
414
  }
257
415
 
258
416
  /**
@@ -319,12 +477,16 @@ export class Tiles3DSource implements Tileset3DSource {
319
477
  /**
320
478
  * Loads data through injected core APIs so this module stays independent from `@loaders.gl/core`.
321
479
  */
322
- private async loadWithCoreApi(url: string, options: LoaderOptions): Promise<any> {
480
+ private async loadWithCoreApi(
481
+ url: string,
482
+ options: LoaderOptions,
483
+ loader: LoaderWithParser = this.loader
484
+ ): Promise<any> {
323
485
  if (!this.coreApi) {
324
486
  throw new Error('Tiles3DSource requires an injected coreApi to load tileset data');
325
487
  }
326
488
 
327
- return await this.coreApi.load(url, this.loader, options);
489
+ return await this.coreApi.load(url, loader, options);
328
490
  }
329
491
 
330
492
  /**
@@ -339,14 +501,105 @@ export class Tiles3DSource implements Tileset3DSource {
339
501
  }
340
502
 
341
503
  /**
342
- * Loads tile content through an injected resolver when present, otherwise through the injected core API.
504
+ * Loads an arbitrary source resource through an injected resolver or core API.
343
505
  */
344
- private async loadResourceData(url: string, options: LoaderOptions): Promise<any> {
506
+ private async loadResourceData(
507
+ url: string,
508
+ options: LoaderOptions,
509
+ loader: LoaderWithParser = this.loader
510
+ ): Promise<any> {
345
511
  if (this.resolver) {
346
- return await this.resolver.loadResource(url, this.loader, options);
512
+ return await this.resolver.loadResource(url, loader, options);
347
513
  }
348
514
 
349
- return await this.loadWithCoreApi(url, options);
515
+ return await this.loadWithCoreApi(url, options, loader);
516
+ }
517
+
518
+ /**
519
+ * Returns a parsed subtree from the LRU cache or starts one source-managed request.
520
+ *
521
+ * @param subtreeUrl - Final subtree URL after query inheritance.
522
+ * @returns Parsed subtree availability data.
523
+ */
524
+ private async loadImplicitSubtreeResource(subtreeUrl: string): Promise<ParsedImplicitSubtree> {
525
+ const cachedSubtree = this.implicitSubtreeCache.get(subtreeUrl);
526
+ if (cachedSubtree) {
527
+ this.implicitTilingStats.cacheHits++;
528
+ this.implicitSubtreeCache.delete(subtreeUrl);
529
+ this.implicitSubtreeCache.set(subtreeUrl, cachedSubtree);
530
+ return await cachedSubtree;
531
+ }
532
+
533
+ this.implicitTilingStats.requestedSubtrees++;
534
+ this.pendingImplicitSubtreeUrls.add(subtreeUrl);
535
+ const loaderOptions = (this.loadOptions[this.loader.id] as Record<string, unknown>) || {};
536
+ const subtreePromise = this.loadResourceData(subtreeUrl, {
537
+ ...this.loadOptions,
538
+ [this.loader.id]: {
539
+ ...loaderOptions,
540
+ isTileset: false,
541
+ isSubtree: true
542
+ }
543
+ }) as Promise<ParsedImplicitSubtree>;
544
+ this.implicitSubtreeCache.set(subtreeUrl, subtreePromise);
545
+
546
+ try {
547
+ return await subtreePromise;
548
+ } catch (error) {
549
+ this.implicitSubtreeCache.delete(subtreeUrl);
550
+ throw error;
551
+ } finally {
552
+ this.pendingImplicitSubtreeUrls.delete(subtreeUrl);
553
+ this.trimImplicitSubtreeCache();
554
+ }
555
+ }
556
+
557
+ /**
558
+ * Instantiates every header represented by one materialized subtree.
559
+ *
560
+ * @param tileset - Owning runtime tileset.
561
+ * @param parentTile - Existing materialized parent.
562
+ * @param childHeaders - Headers to install below the parent.
563
+ * @returns Number of newly allocated runtime tile nodes.
564
+ */
565
+ private initializeMaterializedChildren(
566
+ tileset: Tileset3D,
567
+ parentTile: Tile3D,
568
+ childHeaders: Record<string, any>[]
569
+ ): number {
570
+ let materializedTileCount = 0;
571
+ const stack: Array<{parentTile: Tile3D; childHeaders: Record<string, any>[]}> = [
572
+ {parentTile, childHeaders}
573
+ ];
574
+ while (stack.length > 0) {
575
+ const entry = stack.pop()!;
576
+ for (const childHeader of entry.childHeaders) {
577
+ const childTile = new Tile3DNode(tileset, childHeader, entry.parentTile);
578
+ entry.parentTile.children.push(childTile);
579
+ childTile.depth = entry.parentTile.depth + 1;
580
+ tileset.stats.get('Tiles In Tileset(s)').incrementCount();
581
+ materializedTileCount++;
582
+ if (childHeader.children?.length) {
583
+ stack.push({parentTile: childTile, childHeaders: childHeader.children});
584
+ }
585
+ }
586
+ }
587
+ return materializedTileCount;
588
+ }
589
+
590
+ /** Evicts least-recently-used settled subtree entries until the configured bound is met. */
591
+ private trimImplicitSubtreeCache(): void {
592
+ if (this.implicitSubtreeCache.size <= this.maximumCachedSubtrees) {
593
+ return;
594
+ }
595
+ for (const subtreeUrl of this.implicitSubtreeCache.keys()) {
596
+ if (!this.pendingImplicitSubtreeUrls.has(subtreeUrl)) {
597
+ this.implicitSubtreeCache.delete(subtreeUrl);
598
+ }
599
+ if (this.implicitSubtreeCache.size <= this.maximumCachedSubtrees) {
600
+ break;
601
+ }
602
+ }
350
603
  }
351
604
  }
352
605
 
@@ -7,8 +7,29 @@
7
7
 
8
8
  import {TILE3D_OPTIMIZATION_HINT, TILE_REFINEMENT} from '../../constants';
9
9
  import {TilesetTraverser} from '../common/tileset-traverser';
10
+ import type {Tile3D} from '../common/tile-3d';
11
+ import type {FrameState} from '../helpers/frame-state';
10
12
 
11
13
  export class Tileset3DTraverser extends TilesetTraverser {
14
+ /** Pending implicit subtree requests keyed by viewport and tile identity. */
15
+ private readonly pendingImplicitSubtrees: Map<string, number> = new Map();
16
+
17
+ /**
18
+ * Reports whether every implicit subtree request started by this viewport traversal has settled.
19
+ *
20
+ * @param frameState - Traversal frame whose completion callback is being considered.
21
+ * @returns `true` when no matching subtree metadata request remains pending.
22
+ */
23
+ protected traversalFinished(frameState: FrameState): boolean {
24
+ const viewportPrefix = `${frameState.viewport.id}:`;
25
+ for (const [requestKey, frameNumber] of this.pendingImplicitSubtrees) {
26
+ if (requestKey.startsWith(viewportPrefix) && frameNumber === frameState.frameNumber) {
27
+ return false;
28
+ }
29
+ }
30
+ return true;
31
+ }
32
+
12
33
  compareDistanceToCamera(a, b) {
13
34
  // Sort by farthest child first since this is going on a stack
14
35
  return b._distanceToCamera === 0 && a._distanceToCamera === 0
@@ -16,6 +37,62 @@ export class Tileset3DTraverser extends TilesetTraverser {
16
37
  : b._distanceToCamera - a._distanceToCamera;
17
38
  }
18
39
 
40
+ /**
41
+ * Starts an implicit subtree request only after culling, request-volume, and SSE eligibility.
42
+ *
43
+ * The current parent remains the traversal boundary while metadata is loading, which preserves
44
+ * REPLACE coverage and ADD accumulation. Request scheduling reads the tile's existing
45
+ * progressive-resolution and foveated metrics, so subtree metadata follows the same priority
46
+ * policy as render content. A completion notification lets the owning tileset publish the newly
47
+ * materialized traversal result on the next update.
48
+ *
49
+ * @param tile - Tile whose existing or lazy children should be updated.
50
+ * @param frameState - Current culling and LOD state.
51
+ */
52
+ updateChildTiles(tile: Tile3D, frameState: FrameState): void {
53
+ if (
54
+ tile.hasUnloadedChildren &&
55
+ tile.isVisibleAndInRequestVolume &&
56
+ this.shouldRefine(tile, frameState)
57
+ ) {
58
+ const requestKey = `${frameState.viewport.id}:${tile.id}`;
59
+ if (!this.pendingImplicitSubtrees.has(requestKey)) {
60
+ this.pendingImplicitSubtrees.set(requestKey, frameState.frameNumber);
61
+ void tile.tileset._loadTileChildren(tile, frameState).finally(() => {
62
+ this.handleImplicitSubtreeLoad(tile, frameState, requestKey);
63
+ });
64
+ }
65
+ }
66
+ super.updateChildTiles(tile, frameState);
67
+ }
68
+
69
+ /**
70
+ * Resumes the same traversal frame after one lazy subtree request settles.
71
+ *
72
+ * Successful materialization traverses the new headers immediately. Failed or scheduler-cancelled
73
+ * requests finish the current traversal without retrying in a tight loop; a later application
74
+ * update may retry them.
75
+ *
76
+ * @param tile - Subtree-root tile whose request settled.
77
+ * @param frameState - Frame that started the request.
78
+ * @param requestKey - Viewport-scoped pending-request identity.
79
+ */
80
+ private handleImplicitSubtreeLoad(
81
+ tile: Tile3D,
82
+ frameState: FrameState,
83
+ requestKey: string
84
+ ): void {
85
+ this.pendingImplicitSubtrees.delete(requestKey);
86
+ if (this._frameNumber !== frameState.frameNumber) {
87
+ return;
88
+ }
89
+ if (tile.childrenState === 'ready') {
90
+ this.executeTraversal(tile, frameState);
91
+ } else if (this.traversalFinished(frameState)) {
92
+ this.options.onTraversalEnd(frameState);
93
+ }
94
+ }
95
+
19
96
  updateTileVisibility(tile, frameState) {
20
97
  super.updateTileVisibility(tile, frameState);
21
98
 
@@ -1,3 +1,7 @@
1
+ // loaders.gl
2
+ // SPDX-License-Identifier: MIT
3
+ // Copyright (c) vis.gl contributors
4
+
1
5
  /**
2
6
  * Counter to register pending tile headers for the particular frameNumber
3
7
  * Until all tiles are loaded we won't call `onTraversalEnd` callback
@@ -1,3 +1,7 @@
1
+ // loaders.gl
2
+ // SPDX-License-Identifier: MIT
3
+ // Copyright (c) vis.gl contributors
4
+
1
5
  import {FrameState} from '../helpers/frame-state';
2
6
  import {I3SPendingTilesRegister} from './i3s-pending-tiles-register';
3
7
 
@@ -1,3 +1,7 @@
1
+ // loaders.gl
2
+ // SPDX-License-Identifier: MIT
3
+ // Copyright (c) vis.gl contributors
4
+
1
5
  import {TilesetTraverser} from '../common/tileset-traverser';
2
6
 
3
7
  import {getLodStatus} from '../helpers/i3s-lod';
@@ -1,3 +1,7 @@
1
+ // loaders.gl
2
+ // SPDX-License-Identifier: MIT
3
+ // Copyright (c) vis.gl contributors
4
+
1
5
  import type {Tileset3D} from '../common/tileset-3d';
2
6
 
3
7
  export function get3dTilesOptions(tileset: Tileset3D): {assetGltfUpAxis: 'X' | 'Y' | 'Z'} {
@@ -1,3 +1,5 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+
1
3
  // This file is derived from the Cesium code base under Apache 2 license
2
4
  // See LICENSE.md and https://github.com/AnalyticalGraphicsInc/cesium/blob/master/LICENSE.md
3
5