@loaders.gl/tiles 4.4.0-alpha.9 → 4.4.0

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.
@@ -130,6 +130,14 @@ export class Tile3D {
130
130
  _inRequestVolume: boolean = false;
131
131
  _lodJudge: any = null; // TODO i3s specific, needs to remove
132
132
 
133
+ /**
134
+ * Indicates whether the tile has been drawn by the renderer.
135
+ * Defaults to true for backwards compatibility — renderers that support
136
+ * transition hold (e.g. deck.gl 9.3+) should set this to false on tile load,
137
+ * then back to true after first draw to avoid flashes (see deck.gl #7914).
138
+ */
139
+ tileDrawn: boolean = true;
140
+
133
141
  /**
134
142
  * @constructs
135
143
  * Create a Tile3D instance
@@ -426,6 +434,7 @@ export class Tile3D {
426
434
  }
427
435
  this.header.content = null;
428
436
  this.contentState = TILE_CONTENT_STATE.UNLOADED;
437
+ this.tileDrawn = true;
429
438
  return true;
430
439
  }
431
440
 
@@ -77,6 +77,7 @@ export type Tileset3DProps = {
77
77
  onTileError?: (tile: Tile3D, message: string, url: string) => any;
78
78
  contentLoader?: (tile: Tile3D) => Promise<void>;
79
79
  onTraversalComplete?: (selectedTiles: Tile3D[]) => Tile3D[];
80
+ onUpdate?: () => void;
80
81
  };
81
82
 
82
83
  type Props = {
@@ -104,6 +105,8 @@ type Props = {
104
105
  onTileError: (tile: Tile3D, message: string, url: string) => void;
105
106
  /** Callback. Allows post-process selectedTiles right after traversal. */
106
107
  onTraversalComplete: (selectedTiles: Tile3D[]) => Tile3D[];
108
+ /** Callback. Called when the set of selected tiles changes. */
109
+ onUpdate: () => void;
107
110
  /** The maximum screen space error used to drive level of detail refinement. */
108
111
  maximumScreenSpaceError: number;
109
112
  /** Whether to adjust the maximum screen space error to comply with the maximum memory limitation */
@@ -137,6 +140,7 @@ const DEFAULT_PROPS: Props = {
137
140
  onTileUnload: () => {},
138
141
  onTileError: () => {},
139
142
  onTraversalComplete: (selectedTiles: Tile3D[]) => selectedTiles,
143
+ onUpdate: () => {},
140
144
  contentLoader: undefined,
141
145
  viewDistanceScale: 1.0,
142
146
  maximumScreenSpaceError: 8,
@@ -284,6 +288,9 @@ export class Tileset3D {
284
288
  _cache = new TilesetCache();
285
289
  _requestScheduler: RequestScheduler;
286
290
 
291
+ /** Tile IDs held visible during transitions until replacements have drawn */
292
+ private _heldTiles: Set<string> = new Set();
293
+
287
294
  // Promise tracking
288
295
  private updatePromise: Promise<number> | null = null;
289
296
  tilesetInitializationPromise: Promise<void>;
@@ -538,6 +545,7 @@ export class Tileset3D {
538
545
  * Update tiles relying on data from all traversers
539
546
  */
540
547
  _updateTiles(): void {
548
+ const previousSelectedTiles = this.selectedTiles;
541
549
  this.selectedTiles = [];
542
550
  this._requestedTiles = [];
543
551
  this._emptyTiles = [];
@@ -551,6 +559,45 @@ export class Tileset3D {
551
559
 
552
560
  this.selectedTiles = this.options.onTraversalComplete(this.selectedTiles);
553
561
 
562
+ // Transition hold: keep recently-deselected tiles visible until all their
563
+ // replacements have been drawn by the renderer (e.g. deck.gl).
564
+ // Without this, there are single-frame flashes during REPLACE refinement transitions.
565
+ const selectedIds = new Set(this.selectedTiles.map((t) => t.id));
566
+ const hasUndrawnTiles = this.selectedTiles.some((t) => !t.tileDrawn);
567
+
568
+ // Keep recently-deselected tiles visible while new tiles haven't drawn yet
569
+ let heldBackCount = 0;
570
+ if (hasUndrawnTiles) {
571
+ for (const tileId of selectedIds) {
572
+ this._heldTiles.add(tileId);
573
+ }
574
+ for (const tileId of this._heldTiles) {
575
+ // Skip tiles that are already selected
576
+ if (selectedIds.has(tileId)) continue; // eslint-disable-line no-continue
577
+
578
+ // Keep tiles previously drawn selected
579
+ const tile = this._tiles[tileId];
580
+ if (tile && tile.contentAvailable) {
581
+ tile._selectedFrame = this._frameNumber;
582
+ this.selectedTiles.push(tile);
583
+ heldBackCount++;
584
+ } else {
585
+ this._heldTiles.delete(tileId);
586
+ }
587
+ }
588
+ } else {
589
+ // Store current selected ids for next frame
590
+ this._heldTiles = selectedIds;
591
+ }
592
+
593
+ if (heldBackCount > 0) {
594
+ // Schedule another update so that once all replacement tiles
595
+ // have drawn, the held tiles get released
596
+ setTimeout(() => {
597
+ this.selectTiles();
598
+ }, 0);
599
+ }
600
+
554
601
  for (const tile of this.selectedTiles) {
555
602
  this._tiles[tile.id] = tile;
556
603
  }
@@ -558,6 +605,10 @@ export class Tileset3D {
558
605
  this._loadTiles();
559
606
  this._unloadTiles();
560
607
  this._updateStats();
608
+
609
+ if (this._tilesChanged(previousSelectedTiles, this.selectedTiles)) {
610
+ this.options.onUpdate();
611
+ }
561
612
  }
562
613
 
563
614
  _tilesChanged(oldSelectedTiles: Tile3D[], selectedTiles: Tile3D[]): boolean {
@@ -574,7 +625,7 @@ export class Tileset3D {
574
625
  _loadTiles(): void {
575
626
  // Sort requests by priority before making any requests.
576
627
  // This makes it less likely this requests will be cancelled after being issued.
577
- // requestedTiles.sort((a, b) => a._priority - b._priority);
628
+ this._requestedTiles.sort((a, b) => a._priority - b._priority);
578
629
  for (const tile of this._requestedTiles) {
579
630
  if (tile.contentUnloaded) {
580
631
  // eslint-disable-next-line @typescript-eslint/no-floating-promises
@@ -353,6 +353,6 @@ export class TilesetTraverser {
353
353
  }
354
354
  }
355
355
  }
356
- return allDescendantsLoaded;
356
+ return root.hasEmptyContent || allDescendantsLoaded;
357
357
  }
358
358
  }