@loaders.gl/tiles 4.4.5 → 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 (227) hide show
  1. package/LICENSE +14 -3
  2. package/README.md +110 -1
  3. package/dist/dist.dev.js +6588 -2057
  4. package/dist/dist.min.js +1 -1
  5. package/dist/image-set.d.ts +94 -0
  6. package/dist/image-set.d.ts.map +1 -0
  7. package/dist/image-set.js +243 -0
  8. package/dist/image-set.js.map +1 -0
  9. package/dist/index.cjs +6872 -2160
  10. package/dist/index.cjs.map +3 -4
  11. package/dist/index.d.ts +33 -9
  12. package/dist/index.d.ts.map +1 -1
  13. package/dist/index.js +18 -8
  14. package/dist/index.js.map +1 -1
  15. package/dist/point-cloud/point-cloud-tile.d.ts +47 -0
  16. package/dist/point-cloud/point-cloud-tile.d.ts.map +1 -0
  17. package/dist/point-cloud/point-cloud-tile.js +70 -0
  18. package/dist/point-cloud/point-cloud-tile.js.map +1 -0
  19. package/dist/point-cloud/point-cloud-tileset.d.ts +131 -0
  20. package/dist/point-cloud/point-cloud-tileset.d.ts.map +1 -0
  21. package/dist/point-cloud/point-cloud-tileset.js +510 -0
  22. package/dist/point-cloud/point-cloud-tileset.js.map +1 -0
  23. package/dist/point-cloud/types.d.ts +64 -0
  24. package/dist/point-cloud/types.d.ts.map +1 -0
  25. package/dist/point-cloud/types.js +5 -0
  26. package/dist/point-cloud/types.js.map +1 -0
  27. package/dist/raster-set.d.ts +113 -0
  28. package/dist/raster-set.d.ts.map +1 -0
  29. package/dist/raster-set.js +274 -0
  30. package/dist/raster-set.js.map +1 -0
  31. package/dist/tileset-2d/adapter.d.ts +38 -0
  32. package/dist/tileset-2d/adapter.d.ts.map +1 -0
  33. package/dist/tileset-2d/adapter.js +5 -0
  34. package/dist/tileset-2d/adapter.js.map +1 -0
  35. package/dist/tileset-2d/index.d.ts +7 -0
  36. package/dist/tileset-2d/index.d.ts.map +1 -0
  37. package/dist/tileset-2d/index.js +6 -0
  38. package/dist/tileset-2d/index.js.map +1 -0
  39. package/dist/tileset-2d/tile-2d-header.d.ts +68 -0
  40. package/dist/tileset-2d/tile-2d-header.d.ts.map +1 -0
  41. package/dist/tileset-2d/tile-2d-header.js +181 -0
  42. package/dist/tileset-2d/tile-2d-header.js.map +1 -0
  43. package/dist/tileset-2d/tileset-2d.d.ts +201 -0
  44. package/dist/tileset-2d/tileset-2d.d.ts.map +1 -0
  45. package/dist/tileset-2d/tileset-2d.js +532 -0
  46. package/dist/tileset-2d/tileset-2d.js.map +1 -0
  47. package/dist/tileset-2d/types.d.ts +44 -0
  48. package/dist/tileset-2d/types.d.ts.map +1 -0
  49. package/dist/tileset-2d/types.js +5 -0
  50. package/dist/tileset-2d/types.js.map +1 -0
  51. package/dist/tileset-2d.d.ts +2 -0
  52. package/dist/tileset-2d.d.ts.map +1 -0
  53. package/dist/tileset-2d.js +5 -0
  54. package/dist/tileset-2d.js.map +1 -0
  55. package/dist/tileset-3d/common/indexed-archive-tileset-source.d.ts +60 -0
  56. package/dist/tileset-3d/common/indexed-archive-tileset-source.d.ts.map +1 -0
  57. package/dist/tileset-3d/common/indexed-archive-tileset-source.js +125 -0
  58. package/dist/tileset-3d/common/indexed-archive-tileset-source.js.map +1 -0
  59. package/dist/{tileset → tileset-3d/common}/tile-3d.d.ts +133 -29
  60. package/dist/tileset-3d/common/tile-3d.d.ts.map +1 -0
  61. package/dist/{tileset → tileset-3d/common}/tile-3d.js +282 -77
  62. package/dist/tileset-3d/common/tile-3d.js.map +1 -0
  63. package/dist/tileset-3d/common/tileset-3d.d.ts +317 -0
  64. package/dist/tileset-3d/common/tileset-3d.d.ts.map +1 -0
  65. package/dist/tileset-3d/common/tileset-3d.js +807 -0
  66. package/dist/tileset-3d/common/tileset-3d.js.map +1 -0
  67. package/dist/tileset-3d/common/tileset-cache.d.ts +29 -0
  68. package/dist/tileset-3d/common/tileset-cache.d.ts.map +1 -0
  69. package/dist/{tileset → tileset-3d/common}/tileset-cache.js +12 -4
  70. package/dist/tileset-3d/common/tileset-cache.js.map +1 -0
  71. package/dist/tileset-3d/common/tileset-source.d.ts +204 -0
  72. package/dist/tileset-3d/common/tileset-source.d.ts.map +1 -0
  73. package/dist/tileset-3d/common/tileset-source.js +17 -0
  74. package/dist/tileset-3d/common/tileset-source.js.map +1 -0
  75. package/dist/{tileset → tileset-3d/common}/tileset-traverser.d.ts +15 -2
  76. package/dist/tileset-3d/common/tileset-traverser.d.ts.map +1 -0
  77. package/dist/{tileset → tileset-3d/common}/tileset-traverser.js +30 -3
  78. package/dist/tileset-3d/common/tileset-traverser.js.map +1 -0
  79. package/dist/tileset-3d/format-3d-tiles/implicit-tiling.d.ts +111 -0
  80. package/dist/tileset-3d/format-3d-tiles/implicit-tiling.d.ts.map +1 -0
  81. package/dist/tileset-3d/format-3d-tiles/implicit-tiling.js +340 -0
  82. package/dist/tileset-3d/format-3d-tiles/implicit-tiling.js.map +1 -0
  83. package/dist/tileset-3d/format-3d-tiles/tiles-3d-source.d.ts +189 -0
  84. package/dist/tileset-3d/format-3d-tiles/tiles-3d-source.d.ts.map +1 -0
  85. package/dist/tileset-3d/format-3d-tiles/tiles-3d-source.js +510 -0
  86. package/dist/tileset-3d/format-3d-tiles/tiles-3d-source.js.map +1 -0
  87. package/dist/tileset-3d/format-3d-tiles/tileset-3d-traverser.d.ts +43 -0
  88. package/dist/tileset-3d/format-3d-tiles/tileset-3d-traverser.d.ts.map +1 -0
  89. package/dist/tileset-3d/format-3d-tiles/tileset-3d-traverser.js +119 -0
  90. package/dist/tileset-3d/format-3d-tiles/tileset-3d-traverser.js.map +1 -0
  91. package/dist/tileset-3d/format-i3s/i3s-pending-tiles-register.d.ts.map +1 -0
  92. package/dist/{tileset → tileset-3d}/format-i3s/i3s-pending-tiles-register.js +3 -0
  93. package/dist/tileset-3d/format-i3s/i3s-pending-tiles-register.js.map +1 -0
  94. package/dist/tileset-3d/format-i3s/i3s-source.d.ts +99 -0
  95. package/dist/tileset-3d/format-i3s/i3s-source.d.ts.map +1 -0
  96. package/dist/tileset-3d/format-i3s/i3s-source.js +299 -0
  97. package/dist/tileset-3d/format-i3s/i3s-source.js.map +1 -0
  98. package/dist/tileset-3d/format-i3s/i3s-tile-manager.d.ts.map +1 -0
  99. package/dist/{tileset → tileset-3d}/format-i3s/i3s-tile-manager.js +5 -2
  100. package/dist/tileset-3d/format-i3s/i3s-tile-manager.js.map +1 -0
  101. package/dist/{tileset → tileset-3d}/format-i3s/i3s-tileset-traverser.d.ts +2 -2
  102. package/dist/tileset-3d/format-i3s/i3s-tileset-traverser.d.ts.map +1 -0
  103. package/dist/{tileset → tileset-3d}/format-i3s/i3s-tileset-traverser.js +17 -23
  104. package/dist/tileset-3d/format-i3s/i3s-tileset-traverser.js.map +1 -0
  105. package/dist/{tileset → tileset-3d}/helpers/3d-tiles-options.d.ts +1 -1
  106. package/dist/tileset-3d/helpers/3d-tiles-options.d.ts.map +1 -0
  107. package/dist/{tileset → tileset-3d}/helpers/3d-tiles-options.js +3 -0
  108. package/dist/tileset-3d/helpers/3d-tiles-options.js.map +1 -0
  109. package/dist/tileset-3d/helpers/bounding-volume.d.ts.map +1 -0
  110. package/dist/{tileset → tileset-3d}/helpers/bounding-volume.js +1 -0
  111. package/dist/tileset-3d/helpers/bounding-volume.js.map +1 -0
  112. package/dist/tileset-3d/helpers/frame-state.d.ts +86 -0
  113. package/dist/tileset-3d/helpers/frame-state.d.ts.map +1 -0
  114. package/dist/{tileset → tileset-3d}/helpers/frame-state.js +84 -9
  115. package/dist/tileset-3d/helpers/frame-state.js.map +1 -0
  116. package/dist/{tileset → tileset-3d}/helpers/i3s-lod.d.ts +1 -1
  117. package/dist/tileset-3d/helpers/i3s-lod.d.ts.map +1 -0
  118. package/dist/{tileset → tileset-3d}/helpers/i3s-lod.js +1 -1
  119. package/dist/tileset-3d/helpers/i3s-lod.js.map +1 -0
  120. package/dist/tileset-3d/helpers/tiles-3d-lod.d.ts +72 -0
  121. package/dist/tileset-3d/helpers/tiles-3d-lod.d.ts.map +1 -0
  122. package/dist/tileset-3d/helpers/tiles-3d-lod.js +257 -0
  123. package/dist/tileset-3d/helpers/tiles-3d-lod.js.map +1 -0
  124. package/dist/tileset-3d/helpers/tiles-3d-request-priority.d.ts +134 -0
  125. package/dist/tileset-3d/helpers/tiles-3d-request-priority.d.ts.map +1 -0
  126. package/dist/tileset-3d/helpers/tiles-3d-request-priority.js +179 -0
  127. package/dist/tileset-3d/helpers/tiles-3d-request-priority.js.map +1 -0
  128. package/dist/{tileset → tileset-3d}/helpers/transform-utils.d.ts +1 -1
  129. package/dist/tileset-3d/helpers/transform-utils.d.ts.map +1 -0
  130. package/dist/tileset-3d/helpers/transform-utils.js.map +1 -0
  131. package/dist/tileset-3d/helpers/zoom.d.ts.map +1 -0
  132. package/dist/tileset-3d/helpers/zoom.js.map +1 -0
  133. package/dist/types.d.ts +9 -0
  134. package/dist/types.d.ts.map +1 -1
  135. package/dist/types.js +3 -0
  136. package/dist/types.js.map +1 -1
  137. package/dist/utils/doubly-linked-list.d.ts.map +1 -1
  138. package/dist/utils/doubly-linked-list.js +1 -0
  139. package/dist/utils/doubly-linked-list.js.map +1 -1
  140. package/dist/utils/managed-array.d.ts.map +1 -1
  141. package/dist/utils/managed-array.js +1 -0
  142. package/dist/utils/managed-array.js.map +1 -1
  143. package/package.json +7 -6
  144. package/src/image-set.ts +328 -0
  145. package/src/index.ts +99 -9
  146. package/src/point-cloud/point-cloud-tile.ts +80 -0
  147. package/src/point-cloud/point-cloud-tileset.ts +713 -0
  148. package/src/point-cloud/types.ts +82 -0
  149. package/src/raster-set.ts +412 -0
  150. package/src/tileset-2d/adapter.ts +47 -0
  151. package/src/tileset-2d/index.ts +33 -0
  152. package/src/tileset-2d/tile-2d-header.ts +224 -0
  153. package/src/tileset-2d/tileset-2d.ts +716 -0
  154. package/src/tileset-2d/types.ts +39 -0
  155. package/src/tileset-2d.ts +5 -0
  156. package/src/tileset-3d/common/indexed-archive-tileset-source.ts +205 -0
  157. package/src/{tileset → tileset-3d/common}/tile-3d.ts +348 -93
  158. package/src/tileset-3d/common/tileset-3d.ts +1087 -0
  159. package/src/{tileset → tileset-3d/common}/tileset-cache.ts +16 -8
  160. package/src/tileset-3d/common/tileset-source.ts +255 -0
  161. package/src/{tileset → tileset-3d/common}/tileset-traverser.ts +32 -4
  162. package/src/tileset-3d/format-3d-tiles/implicit-tiling.ts +534 -0
  163. package/src/tileset-3d/format-3d-tiles/tiles-3d-source.ts +632 -0
  164. package/src/tileset-3d/format-3d-tiles/tileset-3d-traverser.ts +140 -0
  165. package/src/{tileset → tileset-3d}/format-i3s/i3s-pending-tiles-register.ts +4 -0
  166. package/src/tileset-3d/format-i3s/i3s-source.ts +366 -0
  167. package/src/{tileset → tileset-3d}/format-i3s/i3s-tile-manager.ts +6 -2
  168. package/src/{tileset → tileset-3d}/format-i3s/i3s-tileset-traverser.ts +19 -24
  169. package/src/{tileset → tileset-3d}/helpers/3d-tiles-options.ts +5 -1
  170. package/src/{tileset → tileset-3d}/helpers/bounding-volume.ts +2 -0
  171. package/src/tileset-3d/helpers/frame-state.ts +311 -0
  172. package/src/{tileset → tileset-3d}/helpers/i3s-lod.ts +2 -2
  173. package/src/tileset-3d/helpers/tiles-3d-lod.ts +370 -0
  174. package/src/tileset-3d/helpers/tiles-3d-request-priority.ts +301 -0
  175. package/src/{tileset → tileset-3d}/helpers/transform-utils.ts +1 -1
  176. package/src/types.ts +13 -0
  177. package/src/utils/doubly-linked-list.ts +2 -0
  178. package/src/utils/managed-array.ts +2 -0
  179. package/dist/tileset/format-3d-tiles/tileset-3d-traverser.d.ts +0 -7
  180. package/dist/tileset/format-3d-tiles/tileset-3d-traverser.d.ts.map +0 -1
  181. package/dist/tileset/format-3d-tiles/tileset-3d-traverser.js +0 -53
  182. package/dist/tileset/format-3d-tiles/tileset-3d-traverser.js.map +0 -1
  183. package/dist/tileset/format-i3s/i3s-pending-tiles-register.d.ts.map +0 -1
  184. package/dist/tileset/format-i3s/i3s-pending-tiles-register.js.map +0 -1
  185. package/dist/tileset/format-i3s/i3s-tile-manager.d.ts.map +0 -1
  186. package/dist/tileset/format-i3s/i3s-tile-manager.js.map +0 -1
  187. package/dist/tileset/format-i3s/i3s-tileset-traverser.d.ts.map +0 -1
  188. package/dist/tileset/format-i3s/i3s-tileset-traverser.js.map +0 -1
  189. package/dist/tileset/helpers/3d-tiles-options.d.ts.map +0 -1
  190. package/dist/tileset/helpers/3d-tiles-options.js.map +0 -1
  191. package/dist/tileset/helpers/bounding-volume.d.ts.map +0 -1
  192. package/dist/tileset/helpers/bounding-volume.js.map +0 -1
  193. package/dist/tileset/helpers/frame-state.d.ts +0 -28
  194. package/dist/tileset/helpers/frame-state.d.ts.map +0 -1
  195. package/dist/tileset/helpers/frame-state.js.map +0 -1
  196. package/dist/tileset/helpers/i3s-lod.d.ts.map +0 -1
  197. package/dist/tileset/helpers/i3s-lod.js.map +0 -1
  198. package/dist/tileset/helpers/tiles-3d-lod.d.ts +0 -8
  199. package/dist/tileset/helpers/tiles-3d-lod.d.ts.map +0 -1
  200. package/dist/tileset/helpers/tiles-3d-lod.js +0 -113
  201. package/dist/tileset/helpers/tiles-3d-lod.js.map +0 -1
  202. package/dist/tileset/helpers/transform-utils.d.ts.map +0 -1
  203. package/dist/tileset/helpers/transform-utils.js.map +0 -1
  204. package/dist/tileset/helpers/zoom.d.ts.map +0 -1
  205. package/dist/tileset/helpers/zoom.js.map +0 -1
  206. package/dist/tileset/tile-3d.d.ts.map +0 -1
  207. package/dist/tileset/tile-3d.js.map +0 -1
  208. package/dist/tileset/tileset-3d.d.ts +0 -284
  209. package/dist/tileset/tileset-3d.d.ts.map +0 -1
  210. package/dist/tileset/tileset-3d.js +0 -814
  211. package/dist/tileset/tileset-3d.js.map +0 -1
  212. package/dist/tileset/tileset-cache.d.ts +0 -19
  213. package/dist/tileset/tileset-cache.d.ts.map +0 -1
  214. package/dist/tileset/tileset-cache.js.map +0 -1
  215. package/dist/tileset/tileset-traverser.d.ts.map +0 -1
  216. package/dist/tileset/tileset-traverser.js.map +0 -1
  217. package/src/tileset/format-3d-tiles/tileset-3d-traverser.ts +0 -63
  218. package/src/tileset/helpers/frame-state.ts +0 -183
  219. package/src/tileset/helpers/tiles-3d-lod.ts +0 -149
  220. package/src/tileset/tileset-3d.ts +0 -1037
  221. /package/dist/{tileset → tileset-3d}/format-i3s/i3s-pending-tiles-register.d.ts +0 -0
  222. /package/dist/{tileset → tileset-3d}/format-i3s/i3s-tile-manager.d.ts +0 -0
  223. /package/dist/{tileset → tileset-3d}/helpers/bounding-volume.d.ts +0 -0
  224. /package/dist/{tileset → tileset-3d}/helpers/transform-utils.js +0 -0
  225. /package/dist/{tileset → tileset-3d}/helpers/zoom.d.ts +0 -0
  226. /package/dist/{tileset → tileset-3d}/helpers/zoom.js +0 -0
  227. /package/src/{tileset → tileset-3d}/helpers/zoom.ts +0 -0
@@ -8,23 +8,28 @@
8
8
  import {Vector3, Matrix4} from '@math.gl/core';
9
9
  import {CullingVolume} from '@math.gl/culling';
10
10
 
11
- import {load} from '@loaders.gl/core';
12
-
13
11
  // Note: circular dependency
14
12
  import type {Tileset3D} from './tileset-3d';
15
- import type {DoublyLinkedListNode} from '../utils/doubly-linked-list-node';
16
- import {TILE_REFINEMENT, TILE_CONTENT_STATE, TILESET_TYPE} from '../constants';
13
+ import type {DoublyLinkedListNode} from '../../utils/doubly-linked-list-node';
14
+ import {LOD_METRIC_TYPE, TILE_REFINEMENT, TILE_CONTENT_STATE, TILESET_TYPE} from '../../constants';
15
+ import type {TileChildrenLoadResult, TileContentLoadResult} from './tileset-source';
17
16
 
18
- import {FrameState} from './helpers/frame-state';
17
+ import {FrameState} from '../helpers/frame-state';
19
18
  import {
20
19
  createBoundingVolume,
21
20
  getCartographicBounds,
22
21
  CartographicBounds
23
- } from './helpers/bounding-volume';
24
- import {getTiles3DScreenSpaceError} from './helpers/tiles-3d-lod';
25
- import {getProjectedRadius} from './helpers/i3s-lod';
26
- import {get3dTilesOptions} from './helpers/3d-tiles-options';
22
+ } from '../helpers/bounding-volume';
23
+ import {getTiles3DScreenSpaceError} from '../helpers/tiles-3d-lod';
24
+ import {getProjectedRadius} from '../helpers/i3s-lod';
27
25
  import {TilesetTraverser} from './tileset-traverser';
26
+ import {
27
+ calculateFoveatedFactor,
28
+ calculateTileRequestPriority,
29
+ isFoveatedRequestDelayActive,
30
+ isFoveatedRequestDeferred,
31
+ isProgressiveResolutionPriority
32
+ } from '../helpers/tiles-3d-request-priority';
28
33
 
29
34
  const scratchVector = new Vector3();
30
35
 
@@ -46,6 +51,9 @@ export type Tile3DProps = {
46
51
  extendedId: string;
47
52
  };
48
53
 
54
+ /** Lifecycle of a source-managed lazy child-header group. */
55
+ export type TileChildrenState = 'unloaded' | 'loading' | 'ready' | 'failed';
56
+
49
57
  /**
50
58
  * A Tile3DHeader represents a tile as Tileset3D. When a tile is first created, its content is not loaded;
51
59
  * the content is loaded on-demand when needed based on the view.
@@ -62,10 +70,19 @@ export class Tile3D {
62
70
  type: string;
63
71
  contentUrl: string;
64
72
  /** Different refinement algorithms used by I3S and 3D tiles */
65
- lodMetricType: 'geometricError' | 'maxScreenThreshold' = 'geometricError';
66
- /** The error, in meters, introduced if this tile is rendered and its children are not. */
73
+ lodMetricType = 'geometricError';
74
+ /**
75
+ * The world-space geometric error, in meters, introduced if this tile is rendered and its
76
+ * children are not. For I3S this stores the format's unscaled screen-threshold metric instead.
77
+ */
67
78
  lodMetricValue: number = 0;
68
79
 
80
+ /**
81
+ * The source LOD metric before applying the tile's computed transform. Keeping the source value
82
+ * separate ensures transform updates never compound an already-scaled geometric error.
83
+ */
84
+ private _unscaledLodMetricValue: number = 0;
85
+
69
86
  /** @todo math.gl is not exporting BoundingVolume base type? */
70
87
  boundingVolume: any = null;
71
88
 
@@ -79,6 +96,8 @@ export class Tile3D {
79
96
 
80
97
  /** The tile's children - an array of Tile3D objects. */
81
98
  children: Tile3D[] = [];
99
+ /** Current lifecycle state for a lazy implicit subtree rooted at this tile. */
100
+ childrenState: TileChildrenState = 'ready';
82
101
  depth: number = 0;
83
102
  viewportIds: any[] = [];
84
103
  transform = new Matrix4();
@@ -104,11 +123,22 @@ export class Tile3D {
104
123
  private _expireDate: any = null;
105
124
  private _expiredContent: any = null;
106
125
 
126
+ /** In-flight source request shared by repeated traversal frames. */
127
+ private _childrenPromise: Promise<TileChildrenLoadResult> | null = null;
128
+
107
129
  private _boundingBox?: CartographicBounds = undefined;
108
130
 
109
131
  /** updated every frame for tree traversal and rendering optimizations: */
110
132
  public _distanceToCamera: number = 0;
111
133
  _screenSpaceError: number = 0;
134
+ /** SSE calculated at the reduced viewport height used to prioritize coarse coverage. */
135
+ _screenSpaceErrorProgressiveResolution: number = 0;
136
+ /** Whether this tile should be promoted into the progressive-resolution request pass. */
137
+ _priorityProgressiveResolution: boolean = false;
138
+ /** Angular distance from the camera view axis used to prioritize center tiles. */
139
+ _foveatedFactor: number = 0;
140
+ /** Whether this peripheral request is currently inside the camera-motion deferral window. */
141
+ priorityDeferred: boolean = false;
112
142
  private _visibilityPlaneMask: any;
113
143
  private _visible: boolean | undefined = undefined;
114
144
 
@@ -169,9 +199,11 @@ export class Tile3D {
169
199
  this.refine = this._getRefine(header.refine);
170
200
  this.type = header.type;
171
201
  this.contentUrl = header.contentUrl;
202
+ this.childrenState = header.implicitSubtree ? 'unloaded' : 'ready';
172
203
 
173
204
  this._initializeLodMetric(header);
174
205
  this._initializeTransforms(header);
206
+ this._updateLodMetricScale();
175
207
  this._initializeBoundingVolumes(header);
176
208
  this._initializeContent(header);
177
209
  this._initializeRenderingState(header);
@@ -206,7 +238,24 @@ export class Tile3D {
206
238
 
207
239
  /** Returns true if tile has children */
208
240
  get hasChildren() {
209
- return this.children.length > 0 || (this.header.children && this.header.children.length > 0);
241
+ return (
242
+ this.children.length > 0 ||
243
+ (this.header.children && this.header.children.length > 0) ||
244
+ Boolean(this.header.implicitSubtree && this.childrenState !== 'ready')
245
+ );
246
+ }
247
+
248
+ /** Returns whether traversal may request an unresolved lazy child-header group. */
249
+ get hasUnloadedChildren(): boolean {
250
+ return Boolean(
251
+ this.header.implicitSubtree &&
252
+ (this.childrenState === 'unloaded' || this.childrenState === 'failed')
253
+ );
254
+ }
255
+
256
+ /** Returns whether a lazy child-header request is currently in flight. */
257
+ get childrenLoading(): boolean {
258
+ return this.childrenState === 'loading';
210
259
  }
211
260
 
212
261
  /**
@@ -279,13 +328,28 @@ export class Tile3D {
279
328
  return this._boundingBox;
280
329
  }
281
330
 
282
- /** Get the tile's screen space error. */
283
- getScreenSpaceError(frameState, useParentLodMetric) {
331
+ /**
332
+ * Gets the tile's screen-space error for normal refinement or a reduced-height priority pass.
333
+ *
334
+ * I3S already supplies a screen-oriented LOD metric and intentionally ignores the optional 3D
335
+ * Tiles progressive-resolution height fraction.
336
+ *
337
+ * @param frameState - Current camera and viewport measurements.
338
+ * @param useParentLodMetric - Whether to substitute the parent LOD metric.
339
+ * @param viewportHeightFraction - Fraction of viewport height represented by the priority pass.
340
+ * @returns Screen-space LOD error for the current tileset format.
341
+ */
342
+ getScreenSpaceError(frameState, useParentLodMetric, viewportHeightFraction = 1) {
284
343
  switch (this.tileset.type) {
285
344
  case TILESET_TYPE.I3S:
286
345
  return getProjectedRadius(this, frameState);
287
346
  case TILESET_TYPE.TILES3D:
288
- return getTiles3DScreenSpaceError(this, frameState, useParentLodMetric);
347
+ return getTiles3DScreenSpaceError(
348
+ this,
349
+ frameState,
350
+ useParentLodMetric,
351
+ viewportHeightFraction
352
+ );
289
353
  default:
290
354
  // eslint-disable-next-line
291
355
  throw new Error('Unsupported tileset type');
@@ -307,12 +371,36 @@ export class Tile3D {
307
371
  return this.content.gpuMemoryUsageInBytes || this.content.byteLength || 0;
308
372
  }
309
373
 
310
- /*
311
- * If skipLevelOfDetail is off try to load child tiles as soon as possible so that their parent can refine sooner.
312
- * Tiles are prioritized by screen space error.
374
+ /**
375
+ * Returns scheduler priority for render-content requests.
376
+ *
377
+ * @returns Numeric priority, or a negative value when the queued request should be cancelled.
378
+ */
379
+ _getPriority(): number {
380
+ return this._calculateRequestPriority(false);
381
+ }
382
+
383
+ /**
384
+ * Returns scheduler priority for lazy child-header requests.
385
+ *
386
+ * Contentless implicit placeholders intentionally retain `UNLOADED` content state, so metadata
387
+ * scheduling skips only that render-content cancellation check and preserves every visibility,
388
+ * recency, progressive, foveated, and motion-deferral rule.
389
+ *
390
+ * @returns Numeric priority, or a negative value when the queued request should be cancelled.
391
+ */
392
+ private _getChildrenPriority(): number {
393
+ return this._calculateRequestPriority(true);
394
+ }
395
+
396
+ /**
397
+ * Calculates the common scheduler priority for content or hierarchy metadata.
398
+ *
399
+ * @param allowUnloadedContent - Whether an empty placeholder may request child metadata.
400
+ * @returns Numeric priority, or a negative value when the queued request should be cancelled.
313
401
  */
314
402
  // eslint-disable-next-line complexity
315
- _getPriority() {
403
+ private _calculateRequestPriority(allowUnloadedContent: boolean): number {
316
404
  const traverser = this.tileset._traverser;
317
405
  const {skipLevelOfDetail} = traverser.options;
318
406
 
@@ -332,7 +420,13 @@ export class Tile3D {
332
420
  if (this.tileset._frameNumber - this._touchedFrame >= 1) {
333
421
  return -1;
334
422
  }
335
- if (this.contentState === TILE_CONTENT_STATE.UNLOADED) {
423
+ // RequestScheduler re-evaluates this callback before a queued request receives a slot. A
424
+ // negative priority cancels work that became deferred after it was queued; the scheduled
425
+ // follow-up traversal submits it again after the motion delay expires.
426
+ if (this.priorityDeferred) {
427
+ return -1;
428
+ }
429
+ if (!allowUnloadedContent && this.contentState === TILE_CONTENT_STATE.UNLOADED) {
336
430
  return -1;
337
431
  }
338
432
 
@@ -346,8 +440,15 @@ export class Tile3D {
346
440
 
347
441
  const rootScreenSpaceError = traverser.root ? traverser.root._screenSpaceError : 0.0;
348
442
 
349
- // Map higher SSE to lower values (e.g. root tile is highest priority)
350
- return Math.max(rootScreenSpaceError - screenSpaceError, 0);
443
+ // Map higher SSE to lower values (e.g. root tile is highest priority), then combine the
444
+ // existing order with independent progressive and foveated priority bands.
445
+ const reverseScreenSpaceError = Math.max(rootScreenSpaceError - screenSpaceError, 0);
446
+ return calculateTileRequestPriority({
447
+ priorityProgressiveResolution: this._priorityProgressiveResolution,
448
+ foveatedFactor: this._foveatedFactor,
449
+ reverseScreenSpaceError,
450
+ rootScreenSpaceError
451
+ });
351
452
  }
352
453
 
353
454
  /**
@@ -355,13 +456,13 @@ export class Tile3D {
355
456
  * The request may not be made if the Request Scheduler can't prioritize it.
356
457
  */
357
458
  // eslint-disable-next-line max-statements, complexity
358
- async loadContent(): Promise<boolean> {
459
+ async loadContent(): Promise<TileContentLoadResult> {
359
460
  if (this.hasEmptyContent) {
360
- return false;
461
+ return {loaded: false};
361
462
  }
362
463
 
363
464
  if (this.content) {
364
- return true;
465
+ return {loaded: true};
365
466
  }
366
467
 
367
468
  const expired = this.contentExpired;
@@ -380,40 +481,19 @@ export class Tile3D {
380
481
  if (!requestToken) {
381
482
  // cancelled
382
483
  this.contentState = TILE_CONTENT_STATE.UNLOADED;
383
- return false;
484
+ return {loaded: false};
384
485
  }
385
486
 
386
487
  try {
387
- const contentUrl = this.tileset.getTileUrl(this.contentUrl);
388
- // The content can be a binary tile ot a JSON tileset
389
- const loader = this.tileset.loader;
390
- const tilesetLoaderOptions =
391
- (this.tileset.loadOptions[loader.id] as Record<string, unknown>) || {};
392
- const options = {
393
- ...this.tileset.loadOptions,
394
- [loader.id]: {
395
- ...tilesetLoaderOptions,
396
- isTileset: this.type === 'json',
397
- ...this._getLoaderSpecificOptions(loader.id)
398
- } // TODO add typecheck - as const satisfies ...
399
- };
400
-
401
- this.content = await load(contentUrl, loader, options);
488
+ const loadResult = await this.tileset.source.loadTileContent(this);
402
489
 
403
490
  if (this.tileset.options.contentLoader) {
404
491
  await this.tileset.options.contentLoader(this);
405
492
  }
406
493
 
407
- if (this._isTileset()) {
408
- // Add tile headers for the nested tilset's subtree
409
- // Async update of the tree should be fine since there would never be edits to the same node
410
- // TODO - we need to capture the child tileset's URL
411
- this.tileset._initializeTileHeaders(this.content, this);
412
- }
413
-
414
494
  this.contentState = TILE_CONTENT_STATE.READY;
415
495
  this._onContentLoaded();
416
- return true;
496
+ return loadResult;
417
497
  } catch (error) {
418
498
  // Tile is unloaded before the content finishes loading
419
499
  this.contentState = TILE_CONTENT_STATE.FAILED;
@@ -423,6 +503,96 @@ export class Tile3D {
423
503
  }
424
504
  }
425
505
 
506
+ /**
507
+ * Requests and installs this tile's lazy child-header group through its source.
508
+ *
509
+ * The same request scheduler and SSE/foveated priority value used for render content are reused
510
+ * for subtree metadata. Repeated traversal frames share one promise, and scheduler cancellation
511
+ * restores the unloaded state so a later frame can retry.
512
+ *
513
+ * @param frameState - View state that made the subtree eligible for refinement.
514
+ * @returns Source load result, or an unloaded result if scheduling was cancelled.
515
+ */
516
+ async loadChildren(frameState: FrameState): Promise<TileChildrenLoadResult> {
517
+ if (!this.header.implicitSubtree || this.childrenState === 'ready') {
518
+ return {loaded: false, tileCount: 0, childSubtreeCount: 0};
519
+ }
520
+ if (this._childrenPromise) {
521
+ return await this._childrenPromise;
522
+ }
523
+ if (!this.tileset.source.loadTileChildren) {
524
+ throw new Error('Tileset source does not support lazy tile children');
525
+ }
526
+
527
+ this.childrenState = 'loading';
528
+ this._childrenPromise = this._loadChildrenWithScheduler(frameState);
529
+ try {
530
+ return await this._childrenPromise;
531
+ } finally {
532
+ this._childrenPromise = null;
533
+ }
534
+ }
535
+
536
+ /**
537
+ * Runs one lazy child-header load after obtaining a scheduler token.
538
+ *
539
+ * @param frameState - View state forwarded to the source implementation.
540
+ * @returns Source load result.
541
+ */
542
+ private async _loadChildrenWithScheduler(
543
+ frameState: FrameState
544
+ ): Promise<TileChildrenLoadResult> {
545
+ const requestToken = await this.tileset._requestScheduler.scheduleRequest(
546
+ `${this.id}:implicit-subtree`,
547
+ this._getChildrenPriority.bind(this)
548
+ );
549
+ if (!requestToken) {
550
+ this.childrenState = 'unloaded';
551
+ return {loaded: false, tileCount: 0, childSubtreeCount: 0};
552
+ }
553
+
554
+ try {
555
+ const loadResult = await this.tileset.source.loadTileChildren!(this, frameState);
556
+ this.childrenState = loadResult.loaded ? 'ready' : 'unloaded';
557
+ return loadResult;
558
+ } catch (error) {
559
+ this.childrenState = 'failed';
560
+ throw error;
561
+ } finally {
562
+ requestToken.done();
563
+ }
564
+ }
565
+
566
+ /**
567
+ * Replaces a contentless implicit placeholder with its materialized subtree-root header.
568
+ *
569
+ * Transform state is intentionally preserved: the placeholder already owns the composed
570
+ * transform, and reapplying the root transform here would double-transform bounds and geometric
571
+ * error. Content and raw geometric error are reinitialized from the newly available header.
572
+ *
573
+ * @param materializedHeader - Available root header produced from one subtree resource.
574
+ */
575
+ applyImplicitSubtreeHeader(materializedHeader: Record<string, any>): void {
576
+ const existingTransform = this.header.transform;
577
+ const existingTransformMatrix = this.header.transformMatrix;
578
+ this.header = {
579
+ ...this.header,
580
+ ...materializedHeader,
581
+ transform: existingTransform,
582
+ transformMatrix: existingTransformMatrix,
583
+ implicitSubtree: undefined
584
+ };
585
+ this.refine = this._getRefine(materializedHeader.refine);
586
+ this.type = materializedHeader.type;
587
+ this.contentUrl = materializedHeader.contentUrl;
588
+ this._initializeLodMetric(materializedHeader);
589
+ this._updateLodMetricScale();
590
+ this._initializeBoundingVolumes(this.header);
591
+ // Use the merged header so inherited viewer-request-volume metadata remains available when a
592
+ // materialized implicit tile has no render content of its own.
593
+ this._initializeContent(this.header);
594
+ }
595
+
426
596
  // Unloads the tile's content.
427
597
  unloadContent() {
428
598
  if (this.content && this.content.destroy) {
@@ -463,6 +633,7 @@ export class Tile3D {
463
633
 
464
634
  this._distanceToCamera = this.distanceToTile(frameState);
465
635
  this._screenSpaceError = this.getScreenSpaceError(frameState, false);
636
+ this._updateRequestPriorityMetrics(frameState);
466
637
  this._visibilityPlaneMask = this.visibility(frameState, parentVisibilityPlaneMask); // Use parent's plane mask to speed up visibility test
467
638
  this._visible = this._visibilityPlaneMask !== CullingVolume.MASK_OUTSIDE;
468
639
  this._inRequestVolume = this.insideViewerRequestVolume(frameState);
@@ -471,6 +642,71 @@ export class Tile3D {
471
642
  this.viewportIds = viewportIds;
472
643
  }
473
644
 
645
+ /**
646
+ * Updates progressive-resolution and foveated request metrics for a 3D Tiles traversal frame.
647
+ *
648
+ * I3S retains its format-specific screen-threshold ordering and is explicitly isolated from
649
+ * geometric-error scheduling. Orthographic viewports still receive progressive coverage, but
650
+ * angular foveation is disabled because an orthographic frustum has no perspective field of view.
651
+ *
652
+ * @param frameState - Current camera and viewport measurements.
653
+ */
654
+ _updateRequestPriorityMetrics(frameState: FrameState): void {
655
+ if (this.tileset.type !== TILESET_TYPE.TILES3D) {
656
+ this._screenSpaceErrorProgressiveResolution = this._screenSpaceError;
657
+ this._priorityProgressiveResolution = false;
658
+ this._foveatedFactor = 0;
659
+ this.priorityDeferred = false;
660
+ return;
661
+ }
662
+
663
+ const options = this.tileset.options;
664
+ const progressiveResolutionHeightFraction = options.progressiveResolutionHeightFraction;
665
+ this._screenSpaceErrorProgressiveResolution = this.getScreenSpaceError(
666
+ frameState,
667
+ false,
668
+ progressiveResolutionHeightFraction
669
+ );
670
+ this._priorityProgressiveResolution = isProgressiveResolutionPriority(
671
+ this._screenSpaceErrorProgressiveResolution,
672
+ this.parent?._screenSpaceErrorProgressiveResolution,
673
+ this.tileset.memoryAdjustedScreenSpaceError,
674
+ progressiveResolutionHeightFraction
675
+ );
676
+
677
+ if (
678
+ !options.foveatedScreenSpaceError ||
679
+ frameState.viewport?.orthographic ||
680
+ !frameState.camera?.position ||
681
+ !frameState.camera?.direction
682
+ ) {
683
+ this._foveatedFactor = 0;
684
+ this.priorityDeferred = false;
685
+ return;
686
+ }
687
+
688
+ this._foveatedFactor = calculateFoveatedFactor(this.boundingVolume, frameState.camera);
689
+ const deferralEligible = isFoveatedRequestDeferred({
690
+ refinement: this.refine,
691
+ skipLevelOfDetail: this.tileset._traverser.options.skipLevelOfDetail,
692
+ foveatedScreenSpaceError: options.foveatedScreenSpaceError,
693
+ foveatedConeSize: options.foveatedConeSize,
694
+ minimumScreenSpaceErrorRelaxation: options.foveatedMinimumScreenSpaceErrorRelaxation,
695
+ interpolationCallback: options.foveatedInterpolationCallback,
696
+ foveatedFactor: this._foveatedFactor,
697
+ verticalFieldOfView: frameState.camera.verticalFieldOfView,
698
+ screenSpaceError: this._screenSpaceError,
699
+ parentScreenSpaceError: this.parent?._screenSpaceError,
700
+ maximumScreenSpaceError: this.tileset.memoryAdjustedScreenSpaceError,
701
+ priorityProgressiveResolution: this._priorityProgressiveResolution
702
+ });
703
+ this.priorityDeferred = isFoveatedRequestDelayActive(
704
+ deferralEligible,
705
+ frameState.camera.timeSinceMovement,
706
+ options.foveatedTimeDelay
707
+ );
708
+ }
709
+
474
710
  // Determines whether the tile's bounding volume intersects the culling volume.
475
711
  // @param {FrameState} frameState The frame state.
476
712
  // @param {Number} parentVisibilityPlaneMask The parent's plane mask to speed up the visibility check.
@@ -592,7 +828,8 @@ export class Tile3D {
592
828
 
593
829
  // INTERNAL METHODS
594
830
 
595
- _initializeLodMetric(header) {
831
+ /** Initializes the tile's LOD metric from its header or its nearest available ancestor. */
832
+ _initializeLodMetric(header: {[key: string]: any}): void {
596
833
  if ('lodMetricType' in header) {
597
834
  this.lodMetricType = header.lodMetricType;
598
835
  } else {
@@ -603,15 +840,20 @@ export class Tile3D {
603
840
 
604
841
  // This is used to compute screen space error, i.e., the error measured in pixels.
605
842
  if ('lodMetricValue' in header) {
606
- this.lodMetricValue = header.lodMetricValue;
843
+ this._unscaledLodMetricValue = header.lodMetricValue;
607
844
  } else {
608
- this.lodMetricValue =
609
- (this.parent && this.parent.lodMetricValue) || this.tileset.lodMetricValue;
845
+ // Inherit the source value, not the parent's world-space value. The child's complete
846
+ // transform will be applied after transform initialization below.
847
+ this._unscaledLodMetricValue = this.parent
848
+ ? this.parent._unscaledLodMetricValue
849
+ : this.tileset.lodMetricValue;
610
850
  // eslint-disable-next-line
611
851
  console.warn(
612
852
  '3D Tile: Required prop lodMetricValue is undefined. Using parent lodMetricValue'
613
853
  );
614
854
  }
855
+
856
+ this.lodMetricValue = this._unscaledLodMetricValue;
615
857
  }
616
858
 
617
859
  _initializeTransforms(tileHeader) {
@@ -632,6 +874,28 @@ export class Tile3D {
632
874
  this._initialTransform = new Matrix4(parentInitialTransform).multiplyRight(this.transform);
633
875
  }
634
876
 
877
+ /**
878
+ * Updates the world-space LOD metric from the source metric and the complete tile transform.
879
+ *
880
+ * 3D Tiles geometric error is a distance in the tile's local meters. Screen-space error (SSE)
881
+ * compares that distance with a world-space camera distance, so both quantities must use the
882
+ * same scale. For non-uniform transforms the largest axis scale is deliberately used: this is
883
+ * conservative and prevents refinement from stopping while any transformed dimension can still
884
+ * exceed the requested visual error. Rotation and translation do not affect the scale.
885
+ *
886
+ * I3S `maxScreenThreshold` is already a screen-space metric and must not be transform-scaled.
887
+ */
888
+ _updateLodMetricScale(): void {
889
+ if (this.lodMetricType !== LOD_METRIC_TYPE.GEOMETRIC_ERROR) {
890
+ this.lodMetricValue = this._unscaledLodMetricValue;
891
+ return;
892
+ }
893
+
894
+ const transformScale = this.computedTransform.getScale(scratchVector);
895
+ const maximumScale = Math.max(transformScale[0], transformScale[1], transformScale[2]);
896
+ this.lodMetricValue = this._unscaledLodMetricValue * maximumScale;
897
+ }
898
+
635
899
  _initializeBoundingVolumes(tileHeader) {
636
900
  this._contentBoundingVolume = null;
637
901
  this._viewerRequestVolume = null;
@@ -664,6 +928,10 @@ export class Tile3D {
664
928
  this._distanceToCamera = 0;
665
929
  this._centerZDepth = 0;
666
930
  this._screenSpaceError = 0;
931
+ this._screenSpaceErrorProgressiveResolution = 0;
932
+ this._priorityProgressiveResolution = false;
933
+ this._foveatedFactor = 0;
934
+ this.priorityDeferred = false;
667
935
  this._visibilityPlaneMask = CullingVolume.MASK_INDETERMINATE;
668
936
  this._visible = undefined;
669
937
  this._inRequestVolume = false;
@@ -685,8 +953,15 @@ export class Tile3D {
685
953
  return refine || (this.parent && this.parent.refine) || TILE_REFINEMENT.REPLACE;
686
954
  }
687
955
 
688
- _isTileset() {
689
- return this.contentUrl.indexOf('.json') !== -1;
956
+ /**
957
+ * Returns whether loaded content is an external tileset.
958
+ *
959
+ * Parsed shape is authoritative because valid external resources may be extensionless, signed,
960
+ * or use a misleading filename. This method is called only after source loading has populated
961
+ * `content`.
962
+ */
963
+ _isTileset(): boolean {
964
+ return this.content?.shape === 'tileset3d';
690
965
  }
691
966
 
692
967
  _onContentLoaded() {
@@ -716,6 +991,17 @@ export class Tile3D {
716
991
  this.boundingVolume
717
992
  );
718
993
 
994
+ // Viewer request volumes constrain traversal, not just render content. A contentless implicit
995
+ // connector can still own an inherited request volume, so initialize it before the
996
+ // content-specific early return below.
997
+ if (header.viewerRequestVolume) {
998
+ this._viewerRequestVolume = createBoundingVolume(
999
+ header.viewerRequestVolume,
1000
+ this.computedTransform,
1001
+ this._viewerRequestVolume
1002
+ );
1003
+ }
1004
+
719
1005
  const content = header.content;
720
1006
  if (!content) {
721
1007
  return;
@@ -734,17 +1020,13 @@ export class Tile3D {
734
1020
  this._contentBoundingVolume
735
1021
  );
736
1022
  }
737
- if (header.viewerRequestVolume) {
738
- this._viewerRequestVolume = createBoundingVolume(
739
- header.viewerRequestVolume,
740
- this.computedTransform,
741
- this._viewerRequestVolume
742
- );
743
- }
744
1023
  }
745
1024
 
746
- // Update the tile's transform. The transform is applied to the tile's bounding volumes.
747
- _updateTransform(parentTransform = new Matrix4()) {
1025
+ /**
1026
+ * Updates the tile's computed transform, world-space geometric error, and bounding volumes.
1027
+ * @param parentTransform - Current computed transform of the parent tile or tileset.
1028
+ */
1029
+ _updateTransform(parentTransform = new Matrix4()): void {
748
1030
  const computedTransform = parentTransform.clone().multiplyRight(this.transform);
749
1031
  const didTransformChange = !computedTransform.equals(this.computedTransform);
750
1032
 
@@ -754,35 +1036,8 @@ export class Tile3D {
754
1036
 
755
1037
  this.computedTransform = computedTransform;
756
1038
 
1039
+ // Recalculate from the unscaled source metric so repeated model-matrix changes never compound.
1040
+ this._updateLodMetricScale();
757
1041
  this._updateBoundingVolume(this.header);
758
1042
  }
759
-
760
- // Get options which are applicable only for the particular loader
761
- _getLoaderSpecificOptions(loaderId) {
762
- switch (loaderId) {
763
- case 'i3s':
764
- return {
765
- ...this.tileset.options.i3s,
766
- _tileOptions: {
767
- attributeUrls: this.header.attributeUrls,
768
- textureUrl: this.header.textureUrl,
769
- textureFormat: this.header.textureFormat,
770
- textureLoaderOptions: this.header.textureLoaderOptions,
771
- materialDefinition: this.header.materialDefinition,
772
- isDracoGeometry: this.header.isDracoGeometry,
773
- mbs: this.header.mbs
774
- },
775
- _tilesetOptions: {
776
- store: this.tileset.tileset.store,
777
- attributeStorageInfo: this.tileset.tileset.attributeStorageInfo,
778
- fields: this.tileset.tileset.fields
779
- },
780
- isTileHeader: false
781
- };
782
- case '3d-tiles':
783
- case 'cesium-ion':
784
- default:
785
- return get3dTilesOptions(this.tileset.tileset);
786
- }
787
- }
788
1043
  }