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

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 (161) hide show
  1. package/LICENSE +14 -3
  2. package/dist/dist.dev.js +17482 -3685
  3. package/dist/dist.min.js +4 -1
  4. package/dist/index.cjs +4594 -422
  5. package/dist/index.cjs.map +3 -4
  6. package/dist/index.d.ts +16 -2
  7. package/dist/index.d.ts.map +1 -1
  8. package/dist/index.js +7 -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 +4 -0
  14. package/dist/point-cloud/point-cloud-tileset.d.ts.map +1 -1
  15. package/dist/point-cloud/point-cloud-tileset.js +54 -12
  16. package/dist/point-cloud/point-cloud-tileset.js.map +1 -1
  17. package/dist/point-cloud/types.d.ts +17 -0
  18. package/dist/point-cloud/types.d.ts.map +1 -1
  19. package/dist/point-cloud/types.js +3 -0
  20. package/dist/point-cloud/types.js.map +1 -1
  21. package/dist/raster-set.d.ts +37 -29
  22. package/dist/raster-set.d.ts.map +1 -1
  23. package/dist/raster-set.js +25 -2
  24. package/dist/raster-set.js.map +1 -1
  25. package/dist/spatial/format-spatial-reference.d.ts +67 -0
  26. package/dist/spatial/format-spatial-reference.d.ts.map +1 -0
  27. package/dist/spatial/format-spatial-reference.js +233 -0
  28. package/dist/spatial/format-spatial-reference.js.map +1 -0
  29. package/dist/spatial/get-spatial-coordinate-frame.d.ts +9 -0
  30. package/dist/spatial/get-spatial-coordinate-frame.d.ts.map +1 -0
  31. package/dist/spatial/get-spatial-coordinate-frame.js +58 -0
  32. package/dist/spatial/get-spatial-coordinate-frame.js.map +1 -0
  33. package/dist/spatial/i3s-elevation.d.ts +20 -0
  34. package/dist/spatial/i3s-elevation.d.ts.map +1 -0
  35. package/dist/spatial/i3s-elevation.js +90 -0
  36. package/dist/spatial/i3s-elevation.js.map +1 -0
  37. package/dist/spatial/i3s-spatial-transformer.d.ts +189 -0
  38. package/dist/spatial/i3s-spatial-transformer.d.ts.map +1 -0
  39. package/dist/spatial/i3s-spatial-transformer.js +686 -0
  40. package/dist/spatial/i3s-spatial-transformer.js.map +1 -0
  41. package/dist/spatial/spatial-coordinate-transformer.d.ts +48 -0
  42. package/dist/spatial/spatial-coordinate-transformer.d.ts.map +1 -0
  43. package/dist/spatial/spatial-coordinate-transformer.js +211 -0
  44. package/dist/spatial/spatial-coordinate-transformer.js.map +1 -0
  45. package/dist/spatial/spatial-resource-registry.d.ts +44 -0
  46. package/dist/spatial/spatial-resource-registry.d.ts.map +1 -0
  47. package/dist/spatial/spatial-resource-registry.js +67 -0
  48. package/dist/spatial/spatial-resource-registry.js.map +1 -0
  49. package/dist/spatial/spatial-types.d.ts +190 -0
  50. package/dist/spatial/spatial-types.d.ts.map +1 -0
  51. package/dist/spatial/spatial-types.js +192 -0
  52. package/dist/spatial/spatial-types.js.map +1 -0
  53. package/dist/tileset-3d/common/tile-3d.d.ts +147 -4
  54. package/dist/tileset-3d/common/tile-3d.d.ts.map +1 -1
  55. package/dist/tileset-3d/common/tile-3d.js +358 -76
  56. package/dist/tileset-3d/common/tile-3d.js.map +1 -1
  57. package/dist/tileset-3d/common/tileset-3d.d.ts +168 -3
  58. package/dist/tileset-3d/common/tileset-3d.d.ts.map +1 -1
  59. package/dist/tileset-3d/common/tileset-3d.js +306 -23
  60. package/dist/tileset-3d/common/tileset-3d.js.map +1 -1
  61. package/dist/tileset-3d/common/tileset-cache.d.ts +11 -1
  62. package/dist/tileset-3d/common/tileset-cache.d.ts.map +1 -1
  63. package/dist/tileset-3d/common/tileset-cache.js +11 -3
  64. package/dist/tileset-3d/common/tileset-cache.js.map +1 -1
  65. package/dist/tileset-3d/common/tileset-source.d.ts +32 -0
  66. package/dist/tileset-3d/common/tileset-source.d.ts.map +1 -1
  67. package/dist/tileset-3d/common/tileset-source.js.map +1 -1
  68. package/dist/tileset-3d/common/tileset-traverser.d.ts +19 -1
  69. package/dist/tileset-3d/common/tileset-traverser.d.ts.map +1 -1
  70. package/dist/tileset-3d/common/tileset-traverser.js +39 -7
  71. package/dist/tileset-3d/common/tileset-traverser.js.map +1 -1
  72. package/dist/tileset-3d/format-3d-tiles/implicit-tiling.d.ts +136 -0
  73. package/dist/tileset-3d/format-3d-tiles/implicit-tiling.d.ts.map +1 -0
  74. package/dist/tileset-3d/format-3d-tiles/implicit-tiling.js +384 -0
  75. package/dist/tileset-3d/format-3d-tiles/implicit-tiling.js.map +1 -0
  76. package/dist/tileset-3d/format-3d-tiles/tiles-3d-source.d.ts +86 -3
  77. package/dist/tileset-3d/format-3d-tiles/tiles-3d-source.d.ts.map +1 -1
  78. package/dist/tileset-3d/format-3d-tiles/tiles-3d-source.js +196 -21
  79. package/dist/tileset-3d/format-3d-tiles/tiles-3d-source.js.map +1 -1
  80. package/dist/tileset-3d/format-3d-tiles/tileset-3d-traverser.d.ts +36 -0
  81. package/dist/tileset-3d/format-3d-tiles/tileset-3d-traverser.d.ts.map +1 -1
  82. package/dist/tileset-3d/format-3d-tiles/tileset-3d-traverser.js +66 -0
  83. package/dist/tileset-3d/format-3d-tiles/tileset-3d-traverser.js.map +1 -1
  84. package/dist/tileset-3d/format-i3s/i3s-pending-tiles-register.d.ts.map +1 -1
  85. package/dist/tileset-3d/format-i3s/i3s-pending-tiles-register.js +3 -0
  86. package/dist/tileset-3d/format-i3s/i3s-pending-tiles-register.js.map +1 -1
  87. package/dist/tileset-3d/format-i3s/i3s-source.d.ts +14 -1
  88. package/dist/tileset-3d/format-i3s/i3s-source.d.ts.map +1 -1
  89. package/dist/tileset-3d/format-i3s/i3s-source.js +107 -17
  90. package/dist/tileset-3d/format-i3s/i3s-source.js.map +1 -1
  91. package/dist/tileset-3d/format-i3s/i3s-tile-manager.d.ts.map +1 -1
  92. package/dist/tileset-3d/format-i3s/i3s-tile-manager.js +3 -0
  93. package/dist/tileset-3d/format-i3s/i3s-tile-manager.js.map +1 -1
  94. package/dist/tileset-3d/format-i3s/i3s-tileset-traverser.d.ts.map +1 -1
  95. package/dist/tileset-3d/format-i3s/i3s-tileset-traverser.js +3 -0
  96. package/dist/tileset-3d/format-i3s/i3s-tileset-traverser.js.map +1 -1
  97. package/dist/tileset-3d/helpers/3d-tiles-options.d.ts.map +1 -1
  98. package/dist/tileset-3d/helpers/3d-tiles-options.js +3 -0
  99. package/dist/tileset-3d/helpers/3d-tiles-options.js.map +1 -1
  100. package/dist/tileset-3d/helpers/bounding-volume.d.ts.map +1 -1
  101. package/dist/tileset-3d/helpers/bounding-volume.js +4 -33
  102. package/dist/tileset-3d/helpers/bounding-volume.js.map +1 -1
  103. package/dist/tileset-3d/helpers/frame-state.d.ts +62 -2
  104. package/dist/tileset-3d/helpers/frame-state.d.ts.map +1 -1
  105. package/dist/tileset-3d/helpers/frame-state.js +85 -9
  106. package/dist/tileset-3d/helpers/frame-state.js.map +1 -1
  107. package/dist/tileset-3d/helpers/i3s-lod.d.ts.map +1 -1
  108. package/dist/tileset-3d/helpers/i3s-lod.js +20 -8
  109. package/dist/tileset-3d/helpers/i3s-lod.js.map +1 -1
  110. package/dist/tileset-3d/helpers/tiles-3d-lod.d.ts +61 -9
  111. package/dist/tileset-3d/helpers/tiles-3d-lod.d.ts.map +1 -1
  112. package/dist/tileset-3d/helpers/tiles-3d-lod.js +198 -93
  113. package/dist/tileset-3d/helpers/tiles-3d-lod.js.map +1 -1
  114. package/dist/tileset-3d/helpers/tiles-3d-request-priority.d.ts +134 -0
  115. package/dist/tileset-3d/helpers/tiles-3d-request-priority.d.ts.map +1 -0
  116. package/dist/tileset-3d/helpers/tiles-3d-request-priority.js +179 -0
  117. package/dist/tileset-3d/helpers/tiles-3d-request-priority.js.map +1 -0
  118. package/dist/types.d.ts +5 -0
  119. package/dist/types.d.ts.map +1 -1
  120. package/dist/types.js +3 -0
  121. package/dist/types.js.map +1 -1
  122. package/dist/utils/doubly-linked-list.d.ts.map +1 -1
  123. package/dist/utils/doubly-linked-list.js +1 -0
  124. package/dist/utils/doubly-linked-list.js.map +1 -1
  125. package/dist/utils/managed-array.d.ts.map +1 -1
  126. package/dist/utils/managed-array.js +1 -0
  127. package/dist/utils/managed-array.js.map +1 -1
  128. package/package.json +12 -9
  129. package/src/index.ts +61 -1
  130. package/src/point-cloud/point-cloud-tile.ts +4 -0
  131. package/src/point-cloud/point-cloud-tileset.ts +70 -12
  132. package/src/point-cloud/types.ts +21 -0
  133. package/src/raster-set.ts +118 -45
  134. package/src/spatial/format-spatial-reference.ts +311 -0
  135. package/src/spatial/get-spatial-coordinate-frame.ts +74 -0
  136. package/src/spatial/i3s-elevation.ts +96 -0
  137. package/src/spatial/i3s-spatial-transformer.ts +957 -0
  138. package/src/spatial/spatial-coordinate-transformer.ts +276 -0
  139. package/src/spatial/spatial-resource-registry.ts +79 -0
  140. package/src/spatial/spatial-types.ts +422 -0
  141. package/src/tileset-3d/common/tile-3d.ts +435 -84
  142. package/src/tileset-3d/common/tileset-3d.ts +445 -23
  143. package/src/tileset-3d/common/tileset-cache.ts +15 -7
  144. package/src/tileset-3d/common/tileset-source.ts +36 -0
  145. package/src/tileset-3d/common/tileset-traverser.ts +40 -7
  146. package/src/tileset-3d/format-3d-tiles/implicit-tiling.ts +612 -0
  147. package/src/tileset-3d/format-3d-tiles/tiles-3d-source.ts +254 -21
  148. package/src/tileset-3d/format-3d-tiles/tileset-3d-traverser.ts +77 -0
  149. package/src/tileset-3d/format-i3s/i3s-pending-tiles-register.ts +4 -0
  150. package/src/tileset-3d/format-i3s/i3s-source.ts +119 -18
  151. package/src/tileset-3d/format-i3s/i3s-tile-manager.ts +4 -0
  152. package/src/tileset-3d/format-i3s/i3s-tileset-traverser.ts +4 -0
  153. package/src/tileset-3d/helpers/3d-tiles-options.ts +4 -0
  154. package/src/tileset-3d/helpers/bounding-volume.ts +5 -55
  155. package/src/tileset-3d/helpers/frame-state.ts +142 -11
  156. package/src/tileset-3d/helpers/i3s-lod.ts +22 -8
  157. package/src/tileset-3d/helpers/tiles-3d-lod.ts +291 -112
  158. package/src/tileset-3d/helpers/tiles-3d-request-priority.ts +301 -0
  159. package/src/types.ts +9 -0
  160. package/src/utils/doubly-linked-list.ts +2 -0
  161. package/src/utils/managed-array.ts +2 -0
@@ -10,11 +10,23 @@ import {Stats} from '@probe.gl/stats';
10
10
  import {RequestScheduler, LoaderWithParser, LoaderOptions} from '@loaders.gl/loader-utils';
11
11
  import {TilesetCache} from './tileset-cache';
12
12
  import {calculateTransformProps} from '../helpers/transform-utils';
13
- import {FrameState, getFrameState, limitSelectedTiles} from '../helpers/frame-state';
13
+ import {getFrameState, limitSelectedTiles, updateCameraMotionState} from '../helpers/frame-state';
14
+ import type {CameraMotionState, FrameState} from '../helpers/frame-state';
15
+ import {
16
+ calculateDynamicScreenSpaceErrorDensity,
17
+ updateRootTransformForDynamicScreenSpaceError
18
+ } from '../helpers/tiles-3d-lod';
19
+ import {interpolateLinearly} from '../helpers/tiles-3d-request-priority';
20
+ import type {FoveatedInterpolationCallback} from '../helpers/tiles-3d-request-priority';
14
21
 
15
22
  import type {GeospatialViewport, Viewport} from '../../types';
16
23
  import {Tile3D} from './tile-3d';
17
24
  import {TILESET_TYPE} from '../../constants';
25
+ import type {TilesetSpatialOptions, TilesetSpatialReference} from '../../spatial/spatial-types';
26
+ import {
27
+ applyTilesetSpatialOptions,
28
+ createTilesetSpatialReference
29
+ } from '../../spatial/spatial-types';
18
30
 
19
31
  import {TilesetTraverser} from './tileset-traverser';
20
32
  import {
@@ -34,7 +46,28 @@ export type Tileset3DProps = {
34
46
  loadOptions?: LoaderOptions;
35
47
  loadTiles?: boolean;
36
48
  basePath?: string;
49
+ /**
50
+ * Soft target, in bytes, for cached tile content that is not required by the current frame.
51
+ * The listed default applies to 3D Tiles; I3S retains its existing 32 MiB default.
52
+ * @default 536870912
53
+ */
54
+ cacheBytes?: number;
55
+ /**
56
+ * Additional bytes that the current-frame working set may use before memory-adjusted SSE rises.
57
+ * The listed default applies to 3D Tiles; I3S retains its existing 1 MiB default.
58
+ * @default 536870912
59
+ */
60
+ maximumCacheOverflowBytes?: number;
61
+ /**
62
+ * Soft cache target in mebibytes.
63
+ * @deprecated Use {@link Tileset3DProps.cacheBytes}; when both are supplied, `cacheBytes` wins.
64
+ */
37
65
  maximumMemoryUsage?: number;
66
+ /**
67
+ * Cache overflow headroom in mebibytes.
68
+ * @deprecated Use {@link Tileset3DProps.maximumCacheOverflowBytes}; when both are supplied, the
69
+ * byte-native option wins.
70
+ */
38
71
  memoryCacheOverflow?: number;
39
72
  maximumTilesSelected?: number;
40
73
  debounceTime?: number;
@@ -44,12 +77,44 @@ export type Tileset3DProps = {
44
77
 
45
78
  ellipsoid?: object;
46
79
  modelMatrix?: Matrix4;
80
+ /** Automatic CRS output options and expert source-metadata recovery overrides. */
81
+ spatial?: TilesetSpatialOptions;
47
82
 
48
83
  maximumScreenSpaceError?: number;
84
+ /**
85
+ * Enables replacement traversal that may skip hierarchy levels while retaining ready ancestors
86
+ * as coverage. This trades temporary overdraw for faster refinement on deep trees.
87
+ * @default false
88
+ */
89
+ skipLevelOfDetail?: boolean;
90
+ /** Enables perspective dynamic SSE to reduce distant, horizon-facing refinement. */
91
+ dynamicScreenSpaceError?: boolean;
92
+ /** Base dynamic SSE fog density in inverse meters. */
93
+ dynamicScreenSpaceErrorDensity?: number;
94
+ /** Maximum dynamic SSE reduction in logical/CSS pixels. */
95
+ dynamicScreenSpaceErrorFactor?: number;
96
+ /** Fraction of tileset height at which dynamic SSE begins to fade, clamped to `[0, 1]`. */
97
+ dynamicScreenSpaceErrorHeightFalloff?: number;
98
+ /**
99
+ * Enables adaptive LOD reduction when estimated tile memory exceeds the overflow ceiling.
100
+ * Defaults to `true` for 3D Tiles and retains the existing `false` default for I3S.
101
+ */
49
102
  memoryAdjustedScreenSpaceError?: boolean;
50
103
  viewportTraversersMap?: any;
51
104
  updateTransforms?: boolean;
52
105
  viewDistanceScale?: number;
106
+ /** Reduced viewport-height fraction used to prioritize coarse initial coverage; `0` disables it. */
107
+ progressiveResolutionHeightFraction?: number;
108
+ /** Whether requests nearer the center of a perspective viewport receive higher priority. */
109
+ foveatedScreenSpaceError?: boolean;
110
+ /** Fraction of the perspective field of view that receives no foveated SSE relaxation. */
111
+ foveatedConeSize?: number;
112
+ /** Minimum logical-pixel SSE relaxation immediately outside the foveated cone. */
113
+ foveatedMinimumScreenSpaceErrorRelaxation?: number;
114
+ /** Function that increases SSE relaxation from the foveated cone toward the viewport edge. */
115
+ foveatedInterpolationCallback?: FoveatedInterpolationCallback;
116
+ /** Seconds peripheral requests may wait after the camera moves. */
117
+ foveatedTimeDelay?: number;
53
118
 
54
119
  onTileLoad?: (tile: Tile3D) => any;
55
120
  onTileUnload?: (tile: Tile3D) => any;
@@ -69,7 +134,13 @@ type Props = {
69
134
  modelMatrix: Matrix4;
70
135
  throttleRequests: boolean;
71
136
  maxRequests: number;
137
+ /** Soft target, in bytes, for evictable cached tile content. */
138
+ cacheBytes: number;
139
+ /** Current-frame headroom, in bytes, before memory-adjusted SSE rises. */
140
+ maximumCacheOverflowBytes: number;
141
+ /** @deprecated Byte-native code should use `cacheBytes`. */
72
142
  maximumMemoryUsage: number;
143
+ /** @deprecated Byte-native code should use `maximumCacheOverflowBytes`. */
73
144
  memoryCacheOverflow: number;
74
145
  maximumTilesSelected: number;
75
146
  debounceTime: number;
@@ -81,6 +152,17 @@ type Props = {
81
152
  onTraversalComplete: (selectedTiles: Tile3D[]) => Tile3D[];
82
153
  onUpdate: () => void;
83
154
  maximumScreenSpaceError: number;
155
+ /** Whether replacement traversal may skip levels while retaining ancestor coverage. */
156
+ skipLevelOfDetail: boolean;
157
+ /** Whether perspective dynamic SSE is enabled. */
158
+ dynamicScreenSpaceError: boolean;
159
+ /** Base dynamic SSE fog density in inverse meters. */
160
+ dynamicScreenSpaceErrorDensity: number;
161
+ /** Maximum dynamic SSE reduction in logical/CSS pixels. */
162
+ dynamicScreenSpaceErrorFactor: number;
163
+ /** Fraction of tileset height at which dynamic SSE begins to fade. */
164
+ dynamicScreenSpaceErrorHeightFalloff: number;
165
+ /** Whether cache pressure may adapt the active SSE threshold. */
84
166
  memoryAdjustedScreenSpaceError: boolean;
85
167
  viewportTraversersMap: Record<string, any> | null;
86
168
  attributions: string[];
@@ -88,19 +170,99 @@ type Props = {
88
170
  loadOptions: LoaderOptions;
89
171
  updateTransforms: boolean;
90
172
  viewDistanceScale: number;
173
+ /** Reduced viewport-height fraction used to prioritize coarse initial coverage. */
174
+ progressiveResolutionHeightFraction: number;
175
+ /** Whether perspective requests are prioritized around the viewport center. */
176
+ foveatedScreenSpaceError: boolean;
177
+ /** Fraction of the perspective field of view with no foveated SSE relaxation. */
178
+ foveatedConeSize: number;
179
+ /** Minimum logical-pixel SSE relaxation outside the foveated cone. */
180
+ foveatedMinimumScreenSpaceErrorRelaxation: number;
181
+ /** Function used to interpolate foveated SSE relaxation. */
182
+ foveatedInterpolationCallback: FoveatedInterpolationCallback;
183
+ /** Seconds eligible peripheral requests wait after camera movement. */
184
+ foveatedTimeDelay: number;
91
185
  basePath: string;
92
186
  contentLoader?: (tile: Tile3D) => Promise<void>;
93
187
  i3s: Record<string, any>;
188
+ /** Automatic CRS output options and expert source-metadata recovery overrides. */
189
+ spatial: TilesetSpatialOptions;
94
190
  };
95
191
 
192
+ const BYTES_PER_MEBIBYTE = 1024 * 1024;
193
+ const DEFAULT_CACHE_BYTES = 512 * BYTES_PER_MEBIBYTE;
194
+ const DEFAULT_MAXIMUM_CACHE_OVERFLOW_BYTES = 512 * BYTES_PER_MEBIBYTE;
195
+ const DEFAULT_I3S_CACHE_BYTES = 32 * BYTES_PER_MEBIBYTE;
196
+ const DEFAULT_I3S_MAXIMUM_CACHE_OVERFLOW_BYTES = BYTES_PER_MEBIBYTE;
197
+
198
+ /**
199
+ * Validates a byte-based tile-cache budget.
200
+ *
201
+ * Fractions are accepted because JavaScript memory estimates are numbers, but negative, infinite,
202
+ * and `NaN` values cannot define a stable cache threshold.
203
+ *
204
+ * @param value - Proposed cache budget in bytes.
205
+ * @param optionName - Public option name used in an actionable error message.
206
+ * @returns The validated byte budget.
207
+ */
208
+ function validateCacheByteLength(value: number, optionName: string): number {
209
+ if (!Number.isFinite(value) || value < 0) {
210
+ throw new Error(`${optionName} must be a finite number greater than or equal to 0`);
211
+ }
212
+ return value;
213
+ }
214
+
215
+ /**
216
+ * Resolves the base cache target from byte-native or deprecated mebibyte options.
217
+ *
218
+ * The byte-native option takes precedence so mixed migration configurations are deterministic.
219
+ * The fallback is already expressed in bytes and is used for both construction and runtime updates.
220
+ *
221
+ * @param options - Options that may contain byte-native or deprecated cache values.
222
+ * @param fallbackCacheBytes - Existing or default cache target in bytes.
223
+ * @returns The resolved and validated cache target in bytes.
224
+ */
225
+ function resolveCacheBytes(options: Tileset3DProps, fallbackCacheBytes: number): number {
226
+ const cacheBytes =
227
+ options.cacheBytes ??
228
+ (options.maximumMemoryUsage !== undefined
229
+ ? options.maximumMemoryUsage * BYTES_PER_MEBIBYTE
230
+ : fallbackCacheBytes);
231
+ return validateCacheByteLength(cacheBytes, 'cacheBytes');
232
+ }
233
+
234
+ /**
235
+ * Resolves cache overflow headroom from byte-native or deprecated mebibyte options.
236
+ *
237
+ * The byte-native option takes precedence so callers can migrate the two cache controls
238
+ * independently without unit ambiguity.
239
+ *
240
+ * @param options - Options that may contain byte-native or deprecated overflow values.
241
+ * @param fallbackOverflowBytes - Existing or default overflow headroom in bytes.
242
+ * @returns The resolved and validated overflow headroom in bytes.
243
+ */
244
+ function resolveMaximumCacheOverflowBytes(
245
+ options: Tileset3DProps,
246
+ fallbackOverflowBytes: number
247
+ ): number {
248
+ const maximumCacheOverflowBytes =
249
+ options.maximumCacheOverflowBytes ??
250
+ (options.memoryCacheOverflow !== undefined
251
+ ? options.memoryCacheOverflow * BYTES_PER_MEBIBYTE
252
+ : fallbackOverflowBytes);
253
+ return validateCacheByteLength(maximumCacheOverflowBytes, 'maximumCacheOverflowBytes');
254
+ }
255
+
96
256
  const DEFAULT_PROPS: Props = {
97
257
  description: '',
98
258
  ellipsoid: Ellipsoid.WGS84,
99
259
  modelMatrix: new Matrix4(),
100
260
  throttleRequests: true,
101
261
  maxRequests: 64,
102
- maximumMemoryUsage: 32,
103
- memoryCacheOverflow: 1,
262
+ cacheBytes: DEFAULT_CACHE_BYTES,
263
+ maximumCacheOverflowBytes: DEFAULT_MAXIMUM_CACHE_OVERFLOW_BYTES,
264
+ maximumMemoryUsage: DEFAULT_CACHE_BYTES / BYTES_PER_MEBIBYTE,
265
+ memoryCacheOverflow: DEFAULT_MAXIMUM_CACHE_OVERFLOW_BYTES / BYTES_PER_MEBIBYTE,
104
266
  maximumTilesSelected: 0,
105
267
  debounceTime: 0,
106
268
  onTileLoad: () => {},
@@ -112,15 +274,27 @@ const DEFAULT_PROPS: Props = {
112
274
  onUpdate: () => {},
113
275
  contentLoader: undefined,
114
276
  viewDistanceScale: 1.0,
277
+ progressiveResolutionHeightFraction: 0.3,
278
+ foveatedScreenSpaceError: true,
279
+ foveatedConeSize: 0.1,
280
+ foveatedMinimumScreenSpaceErrorRelaxation: 0,
281
+ foveatedInterpolationCallback: interpolateLinearly,
282
+ foveatedTimeDelay: 0.2,
115
283
  maximumScreenSpaceError: 8,
116
- memoryAdjustedScreenSpaceError: false,
284
+ skipLevelOfDetail: false,
285
+ dynamicScreenSpaceError: true,
286
+ dynamicScreenSpaceErrorDensity: 2.0e-4,
287
+ dynamicScreenSpaceErrorFactor: 24,
288
+ dynamicScreenSpaceErrorHeightFalloff: 0.25,
289
+ memoryAdjustedScreenSpaceError: true,
117
290
  loadTiles: true,
118
291
  updateTransforms: true,
119
292
  viewportTraversersMap: null,
120
293
  loadOptions: {fetch: {}},
121
294
  attributions: [],
122
295
  basePath: '',
123
- i3s: {}
296
+ i3s: {},
297
+ spatial: {}
124
298
  };
125
299
 
126
300
  const TILES_TOTAL = 'Tiles In Tileset(s)';
@@ -159,6 +333,18 @@ export class Tileset3D {
159
333
  root: Tile3D | null = null;
160
334
  roots: Record<string, Tile3D> = {};
161
335
  asset: Record<string, any> = {};
336
+ /** Inline metadata schema declared by the source tileset, when present. */
337
+ schema: Record<string, any> | null = null;
338
+ /** External metadata schema URI declared by the source tileset, when present. */
339
+ schemaUri: string | null = null;
340
+ /** Metadata groups declared by the source tileset, in source order. */
341
+ groups: Array<Record<string, any>> = [];
342
+ /** Tileset-wide metadata entity, preserved without property-table decoding. */
343
+ metadata: Record<string, any> | null = null;
344
+ /** Tileset statistics metadata, preserved for application-level inspection. */
345
+ statistics: unknown = null;
346
+ /** Normalized source and target coordinate reference system metadata. */
347
+ spatialReference: TilesetSpatialReference = createTilesetSpatialReference({});
162
348
 
163
349
  description = '';
164
350
  properties: any;
@@ -174,14 +360,16 @@ export class Tileset3D {
174
360
  zoom = 1;
175
361
  boundingVolume: any = null;
176
362
 
177
- dynamicScreenSpaceErrorComputedDensity = 0.0;
363
+ /** Effective dynamic SSE density calculated for the most recently traversed viewport. */
364
+ dynamicScreenSpaceErrorComputedDensity = 0;
178
365
 
179
- maximumMemoryUsage = 32;
366
+ /** Estimated bytes currently retained by loaded tile content. */
180
367
  gpuMemoryUsageInBytes = 0;
368
+ /** Active SSE threshold after optional cache-pressure adjustment, in logical/CSS pixels. */
181
369
  memoryAdjustedScreenSpaceError = 0.0;
182
370
 
183
- private _cacheBytes = 0;
184
- private _cacheOverflowBytes = 0;
371
+ private _cacheBytes = DEFAULT_CACHE_BYTES;
372
+ private _maximumCacheOverflowBytes = DEFAULT_MAXIMUM_CACHE_OVERFLOW_BYTES;
185
373
 
186
374
  _frameNumber = 0;
187
375
  private _tiles: Record<string, Tile3D> = {};
@@ -195,6 +383,10 @@ export class Tileset3D {
195
383
  private _requestedTiles: Tile3D[] = [];
196
384
  private _emptyTiles: Tile3D[] = [];
197
385
  private frameStateData: any = {};
386
+ /** Last camera pose retained independently for each viewport. */
387
+ private _cameraMotionStates: Record<string, CameraMotionState> = {};
388
+ /** Pending traversal that releases requests after camera motion settles. */
389
+ private _deferredTraversalTimeout: ReturnType<typeof setTimeout> | null = null;
198
390
 
199
391
  _traverser: TilesetTraverser;
200
392
  _cache = new TilesetCache();
@@ -210,12 +402,34 @@ export class Tileset3D {
210
402
  * @param options Traversal and runtime options.
211
403
  */
212
404
  constructor(source: Tileset3DSource, options?: Tileset3DProps) {
213
- this.options = {...DEFAULT_PROPS, ...options};
214
- this.loadOptions = this.options.loadOptions || {};
215
-
216
405
  if (!isTileset3DSource(source)) {
217
406
  throw new Error('Tileset3D requires a Tileset3DSource instance');
218
407
  }
408
+
409
+ const suppliedOptions = options || {};
410
+ const usesTiles3DCacheDefaults = source.type === TILESET_TYPE.TILES3D;
411
+ const defaultCacheBytes = usesTiles3DCacheDefaults
412
+ ? DEFAULT_PROPS.cacheBytes
413
+ : DEFAULT_I3S_CACHE_BYTES;
414
+ const defaultMaximumCacheOverflowBytes = usesTiles3DCacheDefaults
415
+ ? DEFAULT_PROPS.maximumCacheOverflowBytes
416
+ : DEFAULT_I3S_MAXIMUM_CACHE_OVERFLOW_BYTES;
417
+ const cacheBytes = resolveCacheBytes(suppliedOptions, defaultCacheBytes);
418
+ const maximumCacheOverflowBytes = resolveMaximumCacheOverflowBytes(
419
+ suppliedOptions,
420
+ defaultMaximumCacheOverflowBytes
421
+ );
422
+ this.options = {
423
+ ...DEFAULT_PROPS,
424
+ ...suppliedOptions,
425
+ cacheBytes,
426
+ maximumCacheOverflowBytes,
427
+ maximumMemoryUsage: cacheBytes / BYTES_PER_MEBIBYTE,
428
+ memoryCacheOverflow: maximumCacheOverflowBytes / BYTES_PER_MEBIBYTE,
429
+ memoryAdjustedScreenSpaceError:
430
+ suppliedOptions.memoryAdjustedScreenSpaceError ?? usesTiles3DCacheDefaults
431
+ };
432
+ this.loadOptions = this.options.loadOptions || {};
219
433
  this.source = source;
220
434
 
221
435
  this.tileset = null;
@@ -237,8 +451,15 @@ export class Tileset3D {
237
451
  });
238
452
 
239
453
  this.memoryAdjustedScreenSpaceError = this.options.maximumScreenSpaceError;
240
- this._cacheBytes = this.options.maximumMemoryUsage * 1024 * 1024;
241
- this._cacheOverflowBytes = this.options.memoryCacheOverflow * 1024 * 1024;
454
+ this._cacheBytes = cacheBytes;
455
+ this._maximumCacheOverflowBytes = maximumCacheOverflowBytes;
456
+ const metadata = this._getSourceMetadata();
457
+ if (metadata) {
458
+ this.spatialReference = applyTilesetSpatialOptions(
459
+ metadata.spatialReference,
460
+ this.options.spatial
461
+ );
462
+ }
242
463
 
243
464
  this.stats = new Stats({id: this.url});
244
465
  this._initializeStats();
@@ -247,6 +468,8 @@ export class Tileset3D {
247
468
  }
248
469
 
249
470
  destroy(): void {
471
+ this._clearDeferredTraversal();
472
+ this.source.destroy?.();
250
473
  this._destroy();
251
474
  }
252
475
 
@@ -262,14 +485,83 @@ export class Tileset3D {
262
485
  return this._frameNumber;
263
486
  }
264
487
 
488
+ /**
489
+ * Gets or sets the soft target, in bytes, for cached tile content not needed this frame.
490
+ *
491
+ * Current-frame tiles remain protected even when their estimated memory exceeds this target.
492
+ * Reducing the value makes unused least-recently-used tiles eligible for eviction on the next
493
+ * traversal.
494
+ * @default 536870912 for 3D Tiles; 33554432 for I3S
495
+ */
496
+ get cacheBytes(): number {
497
+ return this._cacheBytes;
498
+ }
499
+
500
+ set cacheBytes(value: number) {
501
+ this._cacheBytes = validateCacheByteLength(value, 'cacheBytes');
502
+ this.options.cacheBytes = this._cacheBytes;
503
+ this.options.maximumMemoryUsage = this._cacheBytes / BYTES_PER_MEBIBYTE;
504
+ }
505
+
506
+ /**
507
+ * Gets or sets the additional current-frame headroom, in bytes, before memory-adjusted SSE rises.
508
+ *
509
+ * The pressure ceiling is `cacheBytes + maximumCacheOverflowBytes`. This value does not change
510
+ * the base target used to evict tiles that were not touched in the current frame.
511
+ * @default 536870912 for 3D Tiles; 1048576 for I3S
512
+ */
513
+ get maximumCacheOverflowBytes(): number {
514
+ return this._maximumCacheOverflowBytes;
515
+ }
516
+
517
+ set maximumCacheOverflowBytes(value: number) {
518
+ this._maximumCacheOverflowBytes = validateCacheByteLength(value, 'maximumCacheOverflowBytes');
519
+ this.options.maximumCacheOverflowBytes = this._maximumCacheOverflowBytes;
520
+ this.options.memoryCacheOverflow = this._maximumCacheOverflowBytes / BYTES_PER_MEBIBYTE;
521
+ }
522
+
523
+ /**
524
+ * Gets or sets the soft cache target in mebibytes.
525
+ * @deprecated Use {@link Tileset3D.cacheBytes}. Assignments remain synchronized for compatibility.
526
+ */
527
+ get maximumMemoryUsage(): number {
528
+ return this.cacheBytes / BYTES_PER_MEBIBYTE;
529
+ }
530
+
531
+ set maximumMemoryUsage(value: number) {
532
+ this.cacheBytes = validateCacheByteLength(value * BYTES_PER_MEBIBYTE, 'maximumMemoryUsage');
533
+ }
534
+
265
535
  get queryParams(): string {
266
536
  const rootUrl = this.source.getTileUrl(this.url);
267
537
  const queryIndex = rootUrl.indexOf('?');
268
538
  return queryIndex >= 0 ? rootUrl.slice(queryIndex + 1) : '';
269
539
  }
270
540
 
541
+ /**
542
+ * Updates traversal and runtime options, synchronizing byte-native and deprecated cache values.
543
+ *
544
+ * For each budget, the byte-native option takes precedence when both unit forms are supplied.
545
+ * Unspecified budgets retain their current byte values, avoiding repeated MiB conversion.
546
+ *
547
+ * @param props - Partial runtime options to apply.
548
+ */
271
549
  setProps(props: Tileset3DProps): void {
272
- this.options = {...this.options, ...props};
550
+ const cacheBytes = resolveCacheBytes(props, this.cacheBytes);
551
+ const maximumCacheOverflowBytes = resolveMaximumCacheOverflowBytes(
552
+ props,
553
+ this.maximumCacheOverflowBytes
554
+ );
555
+ this.options = {
556
+ ...this.options,
557
+ ...props,
558
+ cacheBytes,
559
+ maximumCacheOverflowBytes,
560
+ maximumMemoryUsage: cacheBytes / BYTES_PER_MEBIBYTE,
561
+ memoryCacheOverflow: maximumCacheOverflowBytes / BYTES_PER_MEBIBYTE
562
+ };
563
+ this._cacheBytes = cacheBytes;
564
+ this._maximumCacheOverflowBytes = maximumCacheOverflowBytes;
273
565
  }
274
566
 
275
567
  getTileUrl(tilePath: string): string {
@@ -312,13 +604,20 @@ export class Tileset3D {
312
604
  return this.updatePromise;
313
605
  }
314
606
 
607
+ /**
608
+ * Moves the active LOD threshold toward the configured SSE or away from cache pressure.
609
+ *
610
+ * Usage below {@link Tileset3D.cacheBytes} restores quality in two-percent steps. Usage above
611
+ * `cacheBytes + maximumCacheOverflowBytes` reduces refinement in two-percent steps. Values in
612
+ * the overflow window leave the threshold unchanged, preventing rapid load/evict oscillation.
613
+ */
315
614
  adjustScreenSpaceError(): void {
316
- if (this.gpuMemoryUsageInBytes < this._cacheBytes) {
615
+ if (this.gpuMemoryUsageInBytes < this.cacheBytes) {
317
616
  this.memoryAdjustedScreenSpaceError = Math.max(
318
617
  this.memoryAdjustedScreenSpaceError / 1.02,
319
618
  this.options.maximumScreenSpaceError
320
619
  );
321
- } else if (this.gpuMemoryUsageInBytes > this._cacheBytes + this._cacheOverflowBytes) {
620
+ } else if (this.gpuMemoryUsageInBytes > this.cacheBytes + this.maximumCacheOverflowBytes) {
322
621
  this.memoryAdjustedScreenSpaceError *= 1.02;
323
622
  }
324
623
  }
@@ -335,6 +634,8 @@ export class Tileset3D {
335
634
  this._cache.reset();
336
635
  this._frameNumber++;
337
636
  this.traverseCounter = preparedViewports.length;
637
+ const currentTime = Date.now();
638
+ let minimumDeferredTraversalDelay = Number.POSITIVE_INFINITY;
338
639
  const viewportsToTraverse: string[] = [];
339
640
  for (const viewport of preparedViewports) {
340
641
  const id = viewport.id;
@@ -355,7 +656,70 @@ export class Tileset3D {
355
656
  continue;
356
657
  }
357
658
  const frameState = getFrameState(viewport as GeospatialViewport, this._frameNumber);
358
- this._traverser.traverse(this.roots[id], frameState, this.options);
659
+ const root = this.roots[id];
660
+ const cameraMotionUpdate = updateCameraMotionState(
661
+ this._cameraMotionStates[id],
662
+ frameState.camera.position,
663
+ frameState.camera.direction,
664
+ currentTime
665
+ );
666
+ this._cameraMotionStates[id] = cameraMotionUpdate.state;
667
+ frameState.camera.timeSinceMovement = cameraMotionUpdate.timeSinceMovement;
668
+ // Dynamic SSE reads the root's inverse computed transform before traversal updates tile
669
+ // visibility. Refresh it from the current tileset model matrix here so animated local
670
+ // box/sphere tilesets never calculate their height falloff one transform behind the frame.
671
+ updateRootTransformForDynamicScreenSpaceError(root, this.modelMatrix);
672
+ frameState.dynamicScreenSpaceErrorDensity =
673
+ this.type === TILESET_TYPE.TILES3D &&
674
+ this.options.dynamicScreenSpaceError &&
675
+ !frameState.viewport.orthographic
676
+ ? calculateDynamicScreenSpaceErrorDensity(root, frameState, this.options)
677
+ : 0;
678
+ // Keep the legacy diagnostic field, but traversal reads the per-viewport frame value above.
679
+ this.dynamicScreenSpaceErrorComputedDensity = frameState.dynamicScreenSpaceErrorDensity;
680
+ this._traverser.traverse(root, frameState, this.options);
681
+ if (Object.keys(this._traverser.deferredTiles).length > 0) {
682
+ minimumDeferredTraversalDelay = Math.min(
683
+ minimumDeferredTraversalDelay,
684
+ Math.max(this.options.foveatedTimeDelay - cameraMotionUpdate.timeSinceMovement, 0)
685
+ );
686
+ }
687
+ }
688
+
689
+ if (Number.isFinite(minimumDeferredTraversalDelay)) {
690
+ this._scheduleDeferredTraversal(minimumDeferredTraversalDelay);
691
+ } else {
692
+ this._clearDeferredTraversal();
693
+ }
694
+ }
695
+
696
+ /**
697
+ * Schedules one follow-up traversal when the moving-camera deferral window expires.
698
+ *
699
+ * Replacing an existing timer ensures continuing camera movement pushes the retry window forward
700
+ * instead of creating a queue of redundant traversals.
701
+ *
702
+ * @param delayInSeconds - Remaining stationary delay before deferred requests become eligible.
703
+ */
704
+ private _scheduleDeferredTraversal(delayInSeconds: number): void {
705
+ this._clearDeferredTraversal();
706
+ this._deferredTraversalTimeout = setTimeout(
707
+ () => {
708
+ this._deferredTraversalTimeout = null;
709
+ this.selectTiles().catch((error: unknown) => {
710
+ const traversalError = error instanceof Error ? error : new Error(String(error));
711
+ this._onTilesetError(traversalError);
712
+ });
713
+ },
714
+ Math.max(delayInSeconds * 1000, 0)
715
+ );
716
+ }
717
+
718
+ /** Clears a pending moving-camera follow-up traversal. */
719
+ private _clearDeferredTraversal(): void {
720
+ if (this._deferredTraversalTimeout !== null) {
721
+ clearTimeout(this._deferredTraversalTimeout);
722
+ this._deferredTraversalTimeout = null;
359
723
  }
360
724
  }
361
725
 
@@ -475,6 +839,9 @@ export class Tileset3D {
475
839
 
476
840
  _unloadTiles(): void {
477
841
  this._cache.unloadTiles(this, (tileset, tile) => tileset._unloadTile(tile));
842
+ if (this.options.memoryAdjustedScreenSpaceError) {
843
+ this.adjustScreenSpaceError();
844
+ }
478
845
  }
479
846
 
480
847
  _updateStats(): void {
@@ -483,10 +850,13 @@ export class Tileset3D {
483
850
  for (const tile of this.selectedTiles) {
484
851
  if (tile.contentAvailable && tile.content) {
485
852
  tilesRenderable++;
486
- if (tile.content.pointCount) {
487
- pointsRenderable += tile.content.pointCount;
488
- } else {
489
- pointsRenderable += tile.content.vertexCount;
853
+ const contents = tile.contents.length ? tile.contents : [tile.content];
854
+ for (const content of contents) {
855
+ if (content.pointCount) {
856
+ pointsRenderable += content.pointCount;
857
+ } else {
858
+ pointsRenderable += content.vertexCount || 0;
859
+ }
490
860
  }
491
861
  }
492
862
  }
@@ -506,7 +876,8 @@ export class Tileset3D {
506
876
  }
507
877
  if (
508
878
  initialMetadata?.tileset?.root &&
509
- typeof initialMetadata.tileset.root.then !== 'function'
879
+ typeof initialMetadata.tileset.root.then !== 'function' &&
880
+ !this.source.prepareTileset
510
881
  ) {
511
882
  this.root = this._initializeTileHeaders(initialMetadata.tileset, null);
512
883
  this._applyViewState();
@@ -515,6 +886,7 @@ export class Tileset3D {
515
886
  await initializePromise;
516
887
  const metadata = this.source.getMetadata();
517
888
  this._syncSourceState(metadata);
889
+ await this.source.prepareTileset?.(this);
518
890
  if (!this.root) {
519
891
  this.root = this._initializeTileHeaders(metadata.tileset, null);
520
892
  }
@@ -571,6 +943,28 @@ export class Tileset3D {
571
943
  }
572
944
  }
573
945
 
946
+ /**
947
+ * Loads a source-managed lazy child-header group and reports failures through tile callbacks.
948
+ *
949
+ * Subtree metadata shares the normal loading counters but is not reported as render content and
950
+ * is not added to the GPU cache. Traversal is notified separately after the promise settles.
951
+ *
952
+ * @param tile - Runtime tile that owns the lazy subtree reference.
953
+ * @param frameState - View state that made the subtree eligible for refinement.
954
+ */
955
+ async _loadTileChildren(tile: Tile3D, frameState: FrameState): Promise<void> {
956
+ try {
957
+ this._onStartTileLoading();
958
+ await tile.loadChildren(frameState);
959
+ } catch (error: unknown) {
960
+ const tileError = error instanceof Error ? error : new Error('subtree load failed');
961
+ this._onSourceError(tileError, tile);
962
+ this._onTileLoadError(tile, tileError);
963
+ } finally {
964
+ this._onEndTileLoading();
965
+ }
966
+ }
967
+
574
968
  _onTileLoadError(tile: Tile3D, error: Error): void {
575
969
  this.stats.get(TILES_LOAD_FAILED).incrementCount();
576
970
 
@@ -698,6 +1092,20 @@ export class Tileset3D {
698
1092
  this.refine = metadata.refine;
699
1093
  this.contentFormats = this.source.contentFormats;
700
1094
  this.asset = this.source.asset || this.asset;
1095
+ const tileset = metadata.tileset as Record<string, any>;
1096
+ this.schema = tileset.schema || null;
1097
+ this.schemaUri = tileset.schemaUri || null;
1098
+ this.groups = tileset.groups || [];
1099
+ this.metadata = tileset.metadata || null;
1100
+ this.statistics = tileset.statistics ?? null;
1101
+ const spatialReference = applyTilesetSpatialOptions(
1102
+ metadata.spatialReference,
1103
+ this.options.spatial
1104
+ );
1105
+ this.spatialReference = preserveTransformedSpatialReference(
1106
+ this.spatialReference,
1107
+ spatialReference
1108
+ );
701
1109
  this.properties = this.source.properties ?? this.properties;
702
1110
  this.extras = this.source.extras ?? this.extras;
703
1111
  if (this.options.attributions?.length) {
@@ -734,3 +1142,17 @@ export class Tileset3D {
734
1142
  }
735
1143
  }
736
1144
  }
1145
+
1146
+ /** Preserve a completed runtime operation when asynchronous source metadata describes the same request. */
1147
+ function preserveTransformedSpatialReference(
1148
+ current: TilesetSpatialReference,
1149
+ next: TilesetSpatialReference
1150
+ ): TilesetSpatialReference {
1151
+ return current?.status === 'transformed' &&
1152
+ current.sourceCrs === next.sourceCrs &&
1153
+ current.targetCrs === next.targetCrs &&
1154
+ current.targetHeightReference === next.targetHeightReference &&
1155
+ current.outputCoordinates === next.outputCoordinates
1156
+ ? current
1157
+ : next;
1158
+ }