@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
@@ -25,6 +25,8 @@ import type {
25
25
  import {getZoomFromExtent, getZoomFromFullExtent} from '../helpers/zoom';
26
26
  import {TILESET_TYPE} from '../../constants';
27
27
  import type {TilesetTraverser, TilesetTraverserProps} from '../common/tileset-traverser';
28
+ import {getI3SSpatialReference} from '../../spatial/format-spatial-reference';
29
+ import {I3SSpatialTransformer} from '../../spatial/i3s-spatial-transformer';
28
30
 
29
31
  const EMPTY_CONTENT_FORMATS: TilesetContentFormats = {
30
32
  draco: false,
@@ -63,6 +65,10 @@ export class I3SSource implements Tileset3DSource {
63
65
  private readonly queryParams: Record<string, string> = {};
64
66
  private readonly resolver?: TilesetSourceResolver;
65
67
  private rootTileset: TilesetJSON;
68
+ /** Spatial adapter created lazily after Tileset3D applies target options. */
69
+ private spatialTransformer?: I3SSpatialTransformer;
70
+ /** Root header prepared after asynchronous elevation sampling. */
71
+ private preparedRootHeader?: any;
66
72
 
67
73
  /**
68
74
  * Creates an I3S source.
@@ -79,6 +85,7 @@ export class I3SSource implements Tileset3DSource {
79
85
  this.resolver = request.resolver;
80
86
  this.coreApi = request.coreApi;
81
87
  this.loadOptions = loadOptions;
88
+ this.initializeQueryParams(loadOptions);
82
89
  }
83
90
 
84
91
  /**
@@ -86,7 +93,7 @@ export class I3SSource implements Tileset3DSource {
86
93
  */
87
94
  async initialize(): Promise<void> {
88
95
  if (!this.rootTileset) {
89
- this.rootTileset = await this.loadRootData(this.url, this.loadOptions);
96
+ this.rootTileset = await this.loadRootData(this.getTileUrl(this.url), this.loadOptions);
90
97
  }
91
98
  this.tileset = this.rootTileset;
92
99
 
@@ -94,14 +101,6 @@ export class I3SSource implements Tileset3DSource {
94
101
  this.rootTileset.root = await this.rootTileset.root;
95
102
  }
96
103
 
97
- const i3sOptions = this.loadOptions.i3s;
98
- if (i3sOptions && typeof i3sOptions === 'object' && 'token' in i3sOptions) {
99
- const token = (i3sOptions as Record<string, unknown>).token;
100
- if (typeof token === 'string') {
101
- this.queryParams.token = token;
102
- }
103
- }
104
-
105
104
  this.metadata = {
106
105
  type: this.type,
107
106
  loader: this.loader,
@@ -110,7 +109,8 @@ export class I3SSource implements Tileset3DSource {
110
109
  tileset: this.rootTileset,
111
110
  lodMetricType: this.rootTileset.lodMetricType,
112
111
  lodMetricValue: this.rootTileset.lodMetricValue,
113
- refine: this.rootTileset.root?.refine
112
+ refine: this.rootTileset.root?.refine,
113
+ spatialReference: getI3SSpatialReference(this.rootTileset)
114
114
  };
115
115
  }
116
116
 
@@ -131,6 +131,26 @@ export class I3SSource implements Tileset3DSource {
131
131
  return this.getMetadata().tileset;
132
132
  }
133
133
 
134
+ /** Prepare option-dependent spatial state and the asynchronously placed root bound. */
135
+ async prepareTileset(tileset: Tileset3D): Promise<void> {
136
+ const spatialReference = tileset.spatialReference;
137
+ if (spatialReference.status === 'unresolved') {
138
+ throw new Error(
139
+ spatialReference.warnings[0] ||
140
+ 'I3S spatial operations cannot be resolved from the supplied metadata and options'
141
+ );
142
+ }
143
+ if (spatialReference.status !== 'transformable' && spatialReference.status !== 'transformed') {
144
+ return;
145
+ }
146
+ this.spatialTransformer ||= new I3SSpatialTransformer(
147
+ spatialReference,
148
+ tileset.options.spatial
149
+ );
150
+ tileset.spatialReference = this.spatialTransformer.spatialReference;
151
+ this.preparedRootHeader = await this.transformTileHeader(this.getMetadata().tileset.root);
152
+ }
153
+
134
154
  /**
135
155
  * Creates the runtime root tile for an I3S subtree.
136
156
  */
@@ -139,7 +159,11 @@ export class I3SSource implements Tileset3DSource {
139
159
  tilesetJson: TilesetJSON,
140
160
  parentTile?: Tile3D | null
141
161
  ): Tile3D {
142
- const rootTile = new Tile3DNode(tileset, tilesetJson.root, parentTile || undefined);
162
+ const rootHeader = parentTile ? tilesetJson.root : this.preparedRootHeader || tilesetJson.root;
163
+ if (this.spatialTransformer && !this.preparedRootHeader) {
164
+ throw new Error('I3S spatial root header was not prepared before tile initialization');
165
+ }
166
+ const rootTile = new Tile3DNode(tileset, rootHeader, parentTile || undefined);
143
167
  if (parentTile) {
144
168
  parentTile.children.push(rootTile);
145
169
  rootTile.depth = parentTile.depth + 1;
@@ -170,16 +194,22 @@ export class I3SSource implements Tileset3DSource {
170
194
  _tileOptions: {
171
195
  attributeUrls: tile.header.attributeUrls,
172
196
  textureUrl: tile.header.textureUrl,
197
+ textureUrls: tile.header.textureUrls,
173
198
  textureFormat: tile.header.textureFormat,
174
199
  textureLoaderOptions: tile.header.textureLoaderOptions,
175
200
  materialDefinition: tile.header.materialDefinition,
201
+ layerType: tile.header.layerType,
202
+ pointRenderer: tile.header.pointRenderer,
203
+ pointSymbol: tile.header.pointSymbol,
176
204
  isDracoGeometry: tile.header.isDracoGeometry,
177
205
  mbs: tile.header.mbs
178
206
  },
179
207
  _tilesetOptions: {
180
208
  store: metadata.tileset.store,
181
209
  attributeStorageInfo: metadata.tileset.attributeStorageInfo,
182
- fields: metadata.tileset.fields
210
+ fields: metadata.tileset.fields,
211
+ spatialReference: tile.tileset.spatialReference,
212
+ spatialOptions: tile.tileset.options.spatial
183
213
  },
184
214
  isTileHeader: false
185
215
  }
@@ -198,8 +228,9 @@ export class I3SSource implements Tileset3DSource {
198
228
  _frameState: FrameState
199
229
  ): Promise<any> {
200
230
  const metadata = this.getMetadata();
201
- if (metadata.tileset.nodePages) {
202
- return await metadata.tileset.nodePagesTile.formTileFromNodePages(childId);
231
+ if (metadata.tileset.nodePages || metadata.tileset.pointNodePages) {
232
+ const header = await metadata.tileset.nodePagesTile.formTileFromNodePages(childId);
233
+ return await this.transformTileHeader(header);
203
234
  }
204
235
 
205
236
  const nodeUrl = this.getTileUrl(`${this.url}/nodes/${childId}`);
@@ -215,7 +246,8 @@ export class I3SSource implements Tileset3DSource {
215
246
  }
216
247
  };
217
248
 
218
- return await this.loadResourceData(nodeUrl, options);
249
+ const header = await this.loadResourceData(nodeUrl, options);
250
+ return await this.transformTileHeader(header);
219
251
  }
220
252
 
221
253
  /**
@@ -230,14 +262,48 @@ export class I3SSource implements Tileset3DSource {
230
262
  return tilePath;
231
263
  }
232
264
 
233
- const queryParams = new URLSearchParams(this.queryParams).toString();
234
- return `${tilePath}${tilePath.includes('?') ? '&' : '?'}${queryParams}`;
265
+ const queryDelimiterIndex = tilePath.indexOf('?');
266
+ if (queryDelimiterIndex === -1) {
267
+ return `${tilePath}?${new URLSearchParams(this.queryParams).toString()}`;
268
+ }
269
+
270
+ const existingQuery = tilePath.slice(queryDelimiterIndex + 1);
271
+ const existingQueryKeys = new Set(
272
+ existingQuery.split('&').map(parameter => parameter.split('=', 1)[0])
273
+ );
274
+ const queryParams = new URLSearchParams();
275
+ for (const [key, value] of Object.entries(this.queryParams)) {
276
+ if (!existingQueryKeys.has(key)) {
277
+ queryParams.set(key, value);
278
+ }
279
+ }
280
+
281
+ const queryString = queryParams.toString();
282
+ return queryString ? `${tilePath}${existingQuery ? '&' : ''}${queryString}` : tilePath;
235
283
  }
236
284
 
237
285
  /**
238
286
  * Derives the default view state from full extent or store extent metadata.
239
287
  */
240
- getViewState(_rootTile: Tile3D | null): TilesetSourceViewState {
288
+ getViewState(rootTile: Tile3D | null): TilesetSourceViewState {
289
+ if (this.spatialTransformer && rootTile) {
290
+ const center = new Vector3(rootTile.boundingVolume.center);
291
+ const cartographicCenter = new Vector3(rootTile.header.i3sLodMbs.slice(0, 3));
292
+ if (this.spatialTransformer.targetCoordinateFrame === 'geographic') {
293
+ return {
294
+ boundingVolume: rootTile.boundingVolume,
295
+ cartographicCenter,
296
+ cartesianCenter: center,
297
+ zoom: 1
298
+ };
299
+ }
300
+ return {
301
+ boundingVolume: rootTile.boundingVolume,
302
+ cartographicCenter,
303
+ cartesianCenter: center,
304
+ zoom: 1
305
+ };
306
+ }
241
307
  const metadata = this.getMetadata();
242
308
  const fullExtent = metadata.tileset.fullExtent;
243
309
  if (fullExtent) {
@@ -325,6 +391,20 @@ export class I3SSource implements Tileset3DSource {
325
391
  return await this.loadWithCoreApi(url, options);
326
392
  }
327
393
 
394
+ /**
395
+ * Initializes query parameters before any URL-backed resource is requested.
396
+ * @param loadOptions Loader options that may contain an ArcGIS token.
397
+ */
398
+ private initializeQueryParams(loadOptions: LoaderOptions): void {
399
+ const i3sOptions = loadOptions.i3s;
400
+ if (i3sOptions && typeof i3sOptions === 'object' && 'token' in i3sOptions) {
401
+ const token = (i3sOptions as Record<string, unknown>).token;
402
+ if (typeof token === 'string') {
403
+ this.queryParams.token = token;
404
+ }
405
+ }
406
+ }
407
+
328
408
  /**
329
409
  * Loads tile metadata or content through an injected resolver when present, otherwise through the injected core API.
330
410
  */
@@ -335,6 +415,27 @@ export class I3SSource implements Tileset3DSource {
335
415
 
336
416
  return await this.loadWithCoreApi(url, options);
337
417
  }
418
+
419
+ /** Transform one I3S header bound after target options and elevation providers are available. */
420
+ private async transformTileHeader(header: any): Promise<any> {
421
+ if (!this.spatialTransformer) {
422
+ return header;
423
+ }
424
+ const sourceBounds = {
425
+ mbs: header.mbs,
426
+ obb: header.obb,
427
+ normalReferenceFrame: this.getMetadata().tileset.store?.normalReferenceFrame
428
+ };
429
+ const transformedBounds = await this.spatialTransformer.transformBoundsAsync(sourceBounds);
430
+ return {
431
+ ...header,
432
+ // Tileset3D traversal and culling operate in WGS84 ECEF independently of content output.
433
+ boundingVolume: transformedBounds.boundingVolume,
434
+ // Preserve the renderer/output-frame bound for applications and content coordination.
435
+ spatialBoundingVolume: transformedBounds.spatialBoundingVolume,
436
+ i3sLodMbs: transformedBounds.i3sLodMbs
437
+ };
438
+ }
338
439
  }
339
440
 
340
441
  function isTilesetRequest(input: TilesetSourceInput): input is TilesetSourceRequest {
@@ -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,10 +1,12 @@
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
 
4
6
  /* eslint-disable */
5
- import {Quaternion, Vector3, Matrix3, Matrix4, degrees} from '@math.gl/core';
7
+ import {Quaternion, Vector3, Matrix3, degrees} from '@math.gl/core';
6
8
  import {BoundingSphere, OrientedBoundingBox} from '@math.gl/culling';
7
- import {Ellipsoid} from '@math.gl/geospatial';
9
+ import {Ellipsoid, makeOBBFromRegion} from '@math.gl/geospatial';
8
10
  import {assert} from '@loaders.gl/loader-utils';
9
11
 
10
12
  // const scratchProjectedBoundingSphere = new BoundingSphere();
@@ -16,12 +18,6 @@ function defined(x) {
16
18
  // const scratchMatrix = new Matrix3();
17
19
  const scratchPoint = new Vector3();
18
20
  const scratchScale = new Vector3();
19
- const scratchNorthWest = new Vector3();
20
- const scratchSouthEast = new Vector3();
21
- const scratchCenter = new Vector3();
22
- const scratchXAxis = new Vector3();
23
- const scratchYAxis = new Vector3();
24
- const scratchZAxis = new Vector3();
25
21
  // const scratchRectangle = new Rectangle();
26
22
  // const scratchOrientedBoundingBox = new OrientedBoundingBox();
27
23
  // const scratchTransform = new Matrix4();
@@ -42,7 +38,7 @@ export function createBoundingVolume(boundingVolumeHeader, transform, result?) {
42
38
  return createBox(boundingVolumeHeader.box, transform, result);
43
39
  }
44
40
  if (boundingVolumeHeader.region) {
45
- return createObbFromRegion(boundingVolumeHeader.region);
41
+ return makeOBBFromRegion(boundingVolumeHeader.region);
46
42
  }
47
43
 
48
44
  if (boundingVolumeHeader.sphere) {
@@ -219,52 +215,6 @@ function createSphere(sphere, transform, result?) {
219
215
  return new BoundingSphere(center, radius);
220
216
  }
221
217
 
222
- /**
223
- * Create OrientedBoundingBox instance from region 3D tiles bounding volume
224
- * @param region - region 3D tiles bounding volume
225
- * @returns OrientedBoundingBox instance
226
- */
227
- function createObbFromRegion(region: number[]): OrientedBoundingBox {
228
- // [west, south, east, north, minimum height, maximum height]
229
- // Latitudes and longitudes are in the WGS 84 datum as defined in EPSG 4979 and are in radians.
230
- // Heights are in meters above (or below) the WGS 84 ellipsoid.
231
- const [west, south, east, north, minHeight, maxHeight] = region;
232
-
233
- const northWest = Ellipsoid.WGS84.cartographicToCartesian(
234
- [degrees(west), degrees(north), minHeight],
235
- scratchNorthWest
236
- );
237
- const southEast = Ellipsoid.WGS84.cartographicToCartesian(
238
- [degrees(east), degrees(south), maxHeight],
239
- scratchSouthEast
240
- );
241
- const centerInCartesian = new Vector3().addVectors(northWest, southEast).multiplyByScalar(0.5);
242
- Ellipsoid.WGS84.cartesianToCartographic(centerInCartesian, scratchCenter);
243
-
244
- Ellipsoid.WGS84.cartographicToCartesian(
245
- [degrees(east), scratchCenter[1], scratchCenter[2]],
246
- scratchXAxis
247
- );
248
- Ellipsoid.WGS84.cartographicToCartesian(
249
- [scratchCenter[0], degrees(north), scratchCenter[2]],
250
- scratchYAxis
251
- );
252
- Ellipsoid.WGS84.cartographicToCartesian(
253
- [scratchCenter[0], scratchCenter[1], maxHeight],
254
- scratchZAxis
255
- );
256
-
257
- return createBox(
258
- [
259
- ...centerInCartesian,
260
- ...scratchXAxis.subtract(centerInCartesian),
261
- ...scratchYAxis.subtract(centerInCartesian),
262
- ...scratchZAxis.subtract(centerInCartesian)
263
- ],
264
- new Matrix4()
265
- );
266
- }
267
-
268
218
  /**
269
219
  * Convert a bounding volume defined by OrientedBoundingBox to cartographic bounds
270
220
  * @returns {CartographicBounds}
@@ -1,3 +1,7 @@
1
+ // loaders.gl
2
+ // SPDX-License-Identifier: MIT
3
+ // Copyright (c) vis.gl contributors
4
+
1
5
  import type {Tile3D} from '../common/tile-3d';
2
6
  import {Vector3} from '@math.gl/core';
3
7
  import {CullingVolume, Plane} from '@math.gl/culling';
@@ -6,20 +10,59 @@ import {GeospatialViewport, Viewport} from '../../types';
6
10
 
7
11
  export type FrameState = {
8
12
  camera: {
13
+ /** Camera position in WGS84 Cartesian meters. */
9
14
  position: number[];
15
+ /** Normalized camera direction in WGS84 Cartesian coordinates. */
10
16
  direction: number[];
17
+ /** Normalized camera up direction in WGS84 Cartesian coordinates. */
11
18
  up: number[];
19
+ /** Camera position as `[longitude, latitude, height]`, with height in meters. */
20
+ cartographicPosition: [number, number, number];
21
+ /** Perspective vertical field of view in radians. */
22
+ verticalFieldOfView: number;
23
+ /** Elapsed seconds since camera position or direction last changed. */
24
+ timeSinceMovement: number;
12
25
  };
13
26
  viewport: GeospatialViewport;
14
27
  topDownViewport: GeospatialViewport; // Use it to calculate projected radius for a tile
15
28
  height: number;
16
29
  cullingVolume: CullingVolume;
30
+ /** Optional world-space clipping planes applied to render-content culling. */
31
+ clippingPlanes?: Plane[];
17
32
  frameNumber: number; // TODO: This can be the same between updates, what number is unique for between updates?
18
33
  sseDenominator: number; // Assumes fovy = 60 degrees
34
+ /** View-dependent density used by perspective dynamic screen-space error for this traversal. */
35
+ dynamicScreenSpaceErrorDensity: number;
36
+ };
37
+
38
+ /** Optional camera and projection measurements supplied when constructing a frame state. */
39
+ export type GetFrameStateOptions = {
40
+ /** Elapsed seconds since camera position or direction last changed. */
41
+ timeSinceCameraMovement?: number;
42
+ };
43
+
44
+ /** Camera pose and timestamp retained between traversal frames. */
45
+ export type CameraMotionState = {
46
+ /** Last observed Earth-centered, Earth-fixed camera position. */
47
+ position: number[];
48
+ /** Last observed normalized Earth-centered, Earth-fixed camera direction. */
49
+ direction: number[];
50
+ /** Timestamp in milliseconds of the last observed pose change. */
51
+ lastMovementTime: number;
52
+ };
53
+
54
+ /** Updated camera state and elapsed stationary time for a traversal frame. */
55
+ export type CameraMotionUpdate = {
56
+ /** Camera pose to retain for the next traversal. */
57
+ state: CameraMotionState;
58
+ /** Elapsed seconds since the camera pose last changed. */
59
+ timeSinceMovement: number;
19
60
  };
20
61
 
21
62
  const scratchVector = new Vector3();
22
63
  const scratchPosition = new Vector3();
64
+ const CAMERA_POSITION_EPSILON = 1e-5;
65
+ const CAMERA_DIRECTION_EPSILON = 1e-7;
23
66
  const cullingVolume = new CullingVolume([
24
67
  new Plane(),
25
68
  new Plane(),
@@ -29,12 +72,26 @@ const cullingVolume = new CullingVolume([
29
72
  new Plane()
30
73
  ]);
31
74
 
32
- // Extracts a frame state appropriate for tile culling from a deck.gl viewport
33
- // TODO - this could likely be generalized and merged back into deck.gl for other culling scenarios
34
- export function getFrameState(viewport: GeospatialViewport, frameNumber: number): FrameState {
75
+ /**
76
+ * Extracts a frame state appropriate for tile culling from a structural deck.gl viewport.
77
+ *
78
+ * `options.timeSinceCameraMovement` can be supplied by a caller that retains camera pose across
79
+ * calls. It defaults to a stationary camera so standalone callers do not defer requests.
80
+ *
81
+ * @param viewport - Geospatial viewport supplying camera and projection measurements.
82
+ * @param frameNumber - Current tileset traversal frame number.
83
+ * @param options - Additional camera and projection measurements for traversal.
84
+ * @returns Frame state used for visibility, SSE, and request-priority calculations.
85
+ */
86
+ export function getFrameState(
87
+ viewport: GeospatialViewport,
88
+ frameNumber: number,
89
+ options: GetFrameStateOptions = {}
90
+ ): FrameState {
35
91
  // Traverse and and request. Update _selectedTiles so that we know what to render.
36
92
  // Traverse and and request. Update _selectedTiles so that we know what to render.
37
- const {cameraDirection, cameraUp, height} = viewport;
93
+ const {cameraUp, height} = viewport;
94
+ const cameraDirection = viewport.cameraDirection as number[] | undefined;
38
95
  const {metersPerUnit} = viewport.distanceScales;
39
96
 
40
97
  // TODO - Ellipsoid.eastNorthUpToFixedFrame() breaks on raw array, create a Vector.
@@ -49,10 +106,26 @@ export function getFrameState(viewport: GeospatialViewport, frameNumber: number)
49
106
  );
50
107
 
51
108
  // These should still be normalized as the transform has scale 1 (goes from meters to meters)
52
- const cameraDirectionCartesian = new Vector3(
53
- // @ts-ignore
54
- enuToFixedTransform.transformAsVector(new Vector3(cameraDirection).scale(metersPerUnit))
55
- ).normalize();
109
+ let cameraDirectionCartesian: Vector3;
110
+ if (cameraDirection) {
111
+ cameraDirectionCartesian = new Vector3(
112
+ // @ts-ignore
113
+ enuToFixedTransform.transformAsVector(new Vector3(cameraDirection).scale(metersPerUnit))
114
+ ).normalize();
115
+ } else {
116
+ // Concrete deck.gl viewports do not expose the legacy structural cameraDirection field.
117
+ // The near-plane center is on the view axis, so its world-space direction is equivalent and
118
+ // works for perspective foveation without coupling loaders.gl to deck.gl internals.
119
+ // getFrustumPlanes is required by traversal but omitted from the minimal structural Viewport
120
+ // type for compatibility with existing application-side declarations.
121
+ // @ts-expect-error Runtime geospatial viewports provide getFrustumPlanes.
122
+ const nearPlane = viewport.getFrustumPlanes().near;
123
+ const nearCenterCommon = closestPointOnPlane(nearPlane, viewport.cameraPosition);
124
+ const nearCenterCartesian = worldToCartesian(viewport, nearCenterCommon);
125
+ cameraDirectionCartesian = new Vector3(nearCenterCartesian)
126
+ .subtract(cameraPositionCartesian)
127
+ .normalize();
128
+ }
56
129
  const cameraUpCartesian = new Vector3(
57
130
  // @ts-ignore
58
131
  enuToFixedTransform.transformAsVector(new Vector3(cameraUp).scale(metersPerUnit))
@@ -78,14 +151,72 @@ export function getFrameState(viewport: GeospatialViewport, frameNumber: number)
78
151
  camera: {
79
152
  position: cameraPositionCartesian,
80
153
  direction: cameraDirectionCartesian,
81
- up: cameraUpCartesian
154
+ up: cameraUpCartesian,
155
+ cartographicPosition: cameraPositionCartographic,
156
+ verticalFieldOfView:
157
+ Number.isFinite(viewport.fovy) && Number(viewport.fovy) > 0
158
+ ? (Number(viewport.fovy) * Math.PI) / 180
159
+ : Math.PI / 3,
160
+ timeSinceMovement: options.timeSinceCameraMovement ?? Number.POSITIVE_INFINITY
82
161
  },
83
162
  viewport,
84
163
  topDownViewport,
85
164
  height,
86
165
  cullingVolume,
166
+ clippingPlanes: viewport.clippingPlanes,
87
167
  frameNumber, // TODO: This can be the same between updates, what number is unique for between updates?
88
- sseDenominator: 1.15 // Assumes fovy = 60 degrees
168
+ sseDenominator: 1.15, // Assumes fovy = 60 degrees
169
+ // Tileset3D fills this immediately before traversal because it depends on the root volume.
170
+ dynamicScreenSpaceErrorDensity: 0
171
+ };
172
+ }
173
+
174
+ /**
175
+ * Updates retained camera motion state and calculates how long the current pose has been stable.
176
+ *
177
+ * The first observation is treated as stationary so initial tileset loading is never delayed.
178
+ * Position and direction are both compared because orbiting in place changes request relevance
179
+ * without necessarily changing the viewport's camera position.
180
+ *
181
+ * @param previousState - Pose retained from the preceding traversal for this viewport.
182
+ * @param position - Current Earth-centered, Earth-fixed camera position.
183
+ * @param direction - Current normalized Earth-centered, Earth-fixed camera direction.
184
+ * @param currentTime - Current monotonic-enough wall-clock time in milliseconds.
185
+ * @returns Updated pose and elapsed stationary time.
186
+ */
187
+ export function updateCameraMotionState(
188
+ previousState: CameraMotionState | undefined,
189
+ position: number[] | Vector3,
190
+ direction: number[] | Vector3,
191
+ currentTime: number
192
+ ): CameraMotionUpdate {
193
+ const positionArray = Array.from(position);
194
+ const directionArray = Array.from(direction);
195
+ const positionChanged = previousState
196
+ ? positionArray.length !== previousState.position.length ||
197
+ positionArray.some(
198
+ (value, index) => Math.abs(value - previousState.position[index]) > CAMERA_POSITION_EPSILON
199
+ )
200
+ : false;
201
+ const directionChanged = previousState
202
+ ? directionArray.length !== previousState.direction.length ||
203
+ directionArray.some(
204
+ (value, index) =>
205
+ Math.abs(value - previousState.direction[index]) > CAMERA_DIRECTION_EPSILON
206
+ )
207
+ : false;
208
+ const lastMovementTime =
209
+ positionChanged || directionChanged
210
+ ? currentTime
211
+ : (previousState?.lastMovementTime ?? Number.NEGATIVE_INFINITY);
212
+
213
+ return {
214
+ state: {
215
+ position: positionArray,
216
+ direction: directionArray,
217
+ lastMovementTime
218
+ },
219
+ timeSinceMovement: Math.max((currentTime - lastMovementTime) / 1000, 0)
89
220
  };
90
221
  }
91
222
 
@@ -162,7 +293,7 @@ function commonSpacePlanesToWGS84(viewport) {
162
293
 
163
294
  function closestPointOnPlane(
164
295
  plane: {distance: number; normal: Vector3},
165
- refPoint: [number, number, number] | Vector3,
296
+ refPoint: number[] | Vector3,
166
297
  out: Vector3 = new Vector3()
167
298
  ): Vector3 {
168
299
  const distanceToRef = plane.normal.dot(refPoint);
@@ -32,12 +32,25 @@ export function getLodStatus(tile: Tile3D, frameState: FrameState): 'DIG' | 'OUT
32
32
  if (screenSize < 2) {
33
33
  return 'OUT';
34
34
  }
35
- if (!tile.header.children || screenSize <= tile.lodMetricValue) {
35
+ if (!tile.header.children) {
36
36
  return 'DRAW';
37
- } else if (tile.header.children) {
38
- return 'DIG';
39
37
  }
40
- return 'OUT';
38
+
39
+ switch (tile.lodMetricType) {
40
+ case 'screenSpaceRelative': {
41
+ const viewportHeight = frameState.topDownViewport.height || 1;
42
+ return screenSize / viewportHeight <= tile.lodMetricValue ? 'DRAW' : 'DIG';
43
+ }
44
+ case 'distanceRangeFromDefaultCamera':
45
+ return tile.distanceToTile(frameState) >= tile.lodMetricValue ? 'DRAW' : 'DIG';
46
+ case 'maxScreenThresholdSQ': {
47
+ const diameterThreshold = Math.sqrt(tile.lodMetricValue / (Math.PI * 0.25));
48
+ return screenSize <= diameterThreshold ? 'DRAW' : 'DIG';
49
+ }
50
+ case 'maxScreenThreshold':
51
+ default:
52
+ return screenSize <= tile.lodMetricValue ? 'DRAW' : 'DIG';
53
+ }
41
54
  }
42
55
 
43
56
  /**
@@ -49,10 +62,11 @@ export function getLodStatus(tile: Tile3D, frameState: FrameState): 'DIG' | 'OUT
49
62
  // eslint-disable-next-line max-statements
50
63
  export function getProjectedRadius(tile: Tile3D, frameState: FrameState): number {
51
64
  const {topDownViewport: viewport} = frameState;
52
- const mbsLat = tile.header.mbs[1];
53
- const mbsLon = tile.header.mbs[0];
54
- const mbsZ = tile.header.mbs[2];
55
- const mbsR = tile.header.mbs[3];
65
+ const mbs = tile.header.i3sLodMbs || tile.header.mbs;
66
+ const mbsLat = mbs[1];
67
+ const mbsLon = mbs[0];
68
+ const mbsZ = mbs[2];
69
+ const mbsR = mbs[3];
56
70
  const mbsCenterCartesian = [...tile.boundingVolume.center];
57
71
  const cameraPositionCartographic = viewport.unprojectPosition(viewport.cameraPosition);
58
72
  Ellipsoid.WGS84.cartographicToCartesian(cameraPositionCartographic, cameraPositionCartesian);