@loaders.gl/tiles 5.0.0-alpha.6 → 5.0.0-alpha.8
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.
- package/README.md +26 -0
- package/dist/dist.dev.js +10015 -8172
- package/dist/dist.min.js +3 -4
- package/dist/index.cjs +1676 -477
- package/dist/index.cjs.map +3 -3
- package/dist/index.d.ts +10 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -1
- package/dist/spatial/format-spatial-reference.d.ts +1 -0
- package/dist/spatial/format-spatial-reference.d.ts.map +1 -1
- package/dist/spatial/format-spatial-reference.js +104 -46
- package/dist/spatial/format-spatial-reference.js.map +1 -1
- package/dist/spatial/get-spatial-coordinate-frame.d.ts +12 -1
- package/dist/spatial/get-spatial-coordinate-frame.d.ts.map +1 -1
- package/dist/spatial/get-spatial-coordinate-frame.js +105 -24
- package/dist/spatial/get-spatial-coordinate-frame.js.map +1 -1
- package/dist/spatial/normalize-crs-identifier.d.ts +15 -0
- package/dist/spatial/normalize-crs-identifier.d.ts.map +1 -0
- package/dist/spatial/normalize-crs-identifier.js +22 -0
- package/dist/spatial/normalize-crs-identifier.js.map +1 -0
- package/dist/spatial/spatial-coordinate-transformer.d.ts +4 -0
- package/dist/spatial/spatial-coordinate-transformer.d.ts.map +1 -1
- package/dist/spatial/spatial-coordinate-transformer.js +102 -18
- package/dist/spatial/spatial-coordinate-transformer.js.map +1 -1
- package/dist/spatial/spatial-resource-registry.js +3 -3
- package/dist/spatial/spatial-resource-registry.js.map +1 -1
- package/dist/spatial/tiles-3d-spatial-transformer.d.ts +32 -0
- package/dist/spatial/tiles-3d-spatial-transformer.d.ts.map +1 -0
- package/dist/spatial/tiles-3d-spatial-transformer.js +175 -0
- package/dist/spatial/tiles-3d-spatial-transformer.js.map +1 -0
- package/dist/tileset-3d/common/tile-3d-contracts.d.ts +63 -0
- package/dist/tileset-3d/common/tile-3d-contracts.d.ts.map +1 -0
- package/dist/tileset-3d/common/tile-3d-contracts.js +51 -0
- package/dist/tileset-3d/common/tile-3d-contracts.js.map +1 -0
- package/dist/tileset-3d/common/tile-3d.d.ts +33 -5
- package/dist/tileset-3d/common/tile-3d.d.ts.map +1 -1
- package/dist/tileset-3d/common/tile-3d.js +179 -29
- package/dist/tileset-3d/common/tile-3d.js.map +1 -1
- package/dist/tileset-3d/common/tileset-3d.d.ts +15 -25
- package/dist/tileset-3d/common/tileset-3d.d.ts.map +1 -1
- package/dist/tileset-3d/common/tileset-3d.js +35 -42
- package/dist/tileset-3d/common/tileset-3d.js.map +1 -1
- package/dist/tileset-3d/common/tileset-content-traversal.d.ts +37 -0
- package/dist/tileset-3d/common/tileset-content-traversal.d.ts.map +1 -0
- package/dist/tileset-3d/common/tileset-content-traversal.js +80 -0
- package/dist/tileset-3d/common/tileset-content-traversal.js.map +1 -0
- package/dist/tileset-3d/common/tileset-observability.d.ts +60 -0
- package/dist/tileset-3d/common/tileset-observability.d.ts.map +1 -0
- package/dist/tileset-3d/common/tileset-observability.js +39 -0
- package/dist/tileset-3d/common/tileset-observability.js.map +1 -0
- package/dist/tileset-3d/common/tileset-source.d.ts +11 -1
- package/dist/tileset-3d/common/tileset-source.d.ts.map +1 -1
- package/dist/tileset-3d/common/tileset-source.js.map +1 -1
- package/dist/tileset-3d/common/tileset-traverser.d.ts.map +1 -1
- package/dist/tileset-3d/common/tileset-traverser.js +4 -3
- package/dist/tileset-3d/common/tileset-traverser.js.map +1 -1
- package/dist/tileset-3d/format-3d-tiles/implicit-tiling.d.ts +47 -1
- package/dist/tileset-3d/format-3d-tiles/implicit-tiling.d.ts.map +1 -1
- package/dist/tileset-3d/format-3d-tiles/implicit-tiling.js +327 -30
- package/dist/tileset-3d/format-3d-tiles/implicit-tiling.js.map +1 -1
- package/dist/tileset-3d/format-3d-tiles/tiles-3d-source.d.ts +34 -0
- package/dist/tileset-3d/format-3d-tiles/tiles-3d-source.d.ts.map +1 -1
- package/dist/tileset-3d/format-3d-tiles/tiles-3d-source.js +278 -14
- package/dist/tileset-3d/format-3d-tiles/tiles-3d-source.js.map +1 -1
- package/dist/tileset-3d/format-3d-tiles/tileset-3d-traverser.d.ts +5 -3
- package/dist/tileset-3d/format-3d-tiles/tileset-3d-traverser.d.ts.map +1 -1
- package/dist/tileset-3d/format-3d-tiles/tileset-3d-traverser.js +7 -4
- package/dist/tileset-3d/format-3d-tiles/tileset-3d-traverser.js.map +1 -1
- package/dist/tileset-3d/format-i3s/i3s-source.d.ts +4 -2
- package/dist/tileset-3d/format-i3s/i3s-source.d.ts.map +1 -1
- package/dist/tileset-3d/format-i3s/i3s-source.js +60 -9
- package/dist/tileset-3d/format-i3s/i3s-source.js.map +1 -1
- package/dist/tileset-3d/format-i3s/i3s-tileset-traverser.d.ts.map +1 -1
- package/dist/tileset-3d/format-i3s/i3s-tileset-traverser.js +1 -1
- package/dist/tileset-3d/format-i3s/i3s-tileset-traverser.js.map +1 -1
- package/dist/tileset-3d/helpers/bounding-volume.d.ts +7 -3
- package/dist/tileset-3d/helpers/bounding-volume.d.ts.map +1 -1
- package/dist/tileset-3d/helpers/bounding-volume.js +19 -61
- package/dist/tileset-3d/helpers/bounding-volume.js.map +1 -1
- package/dist/tileset-3d/helpers/frame-state.d.ts +5 -0
- package/dist/tileset-3d/helpers/frame-state.d.ts.map +1 -1
- package/dist/tileset-3d/helpers/frame-state.js +11 -5
- package/dist/tileset-3d/helpers/frame-state.js.map +1 -1
- package/dist/tileset-3d/helpers/s2-bounding-volume.d.ts +17 -0
- package/dist/tileset-3d/helpers/s2-bounding-volume.d.ts.map +1 -0
- package/dist/tileset-3d/helpers/s2-bounding-volume.js +20 -0
- package/dist/tileset-3d/helpers/s2-bounding-volume.js.map +1 -0
- package/dist/types.d.ts +2 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +11 -12
- package/src/index.ts +18 -1
- package/src/spatial/format-spatial-reference.ts +127 -53
- package/src/spatial/get-spatial-coordinate-frame.ts +126 -31
- package/src/spatial/normalize-crs-identifier.ts +26 -0
- package/src/spatial/spatial-coordinate-transformer.ts +124 -24
- package/src/spatial/spatial-resource-registry.ts +3 -3
- package/src/spatial/tiles-3d-spatial-transformer.ts +206 -0
- package/src/tileset-3d/common/tile-3d-contracts.ts +115 -0
- package/src/tileset-3d/common/tile-3d.ts +208 -32
- package/src/tileset-3d/common/tileset-3d.ts +37 -61
- package/src/tileset-3d/common/tileset-content-traversal.ts +114 -0
- package/src/tileset-3d/common/tileset-observability.ts +100 -0
- package/src/tileset-3d/common/tileset-source.ts +19 -1
- package/src/tileset-3d/common/tileset-traverser.ts +4 -3
- package/src/tileset-3d/format-3d-tiles/implicit-tiling.ts +481 -34
- package/src/tileset-3d/format-3d-tiles/tiles-3d-source.ts +405 -14
- package/src/tileset-3d/format-3d-tiles/tileset-3d-traverser.ts +7 -4
- package/src/tileset-3d/format-i3s/i3s-source.ts +84 -13
- package/src/tileset-3d/format-i3s/i3s-tileset-traverser.ts +2 -1
- package/src/tileset-3d/helpers/bounding-volume.ts +29 -62
- package/src/tileset-3d/helpers/frame-state.ts +15 -7
- package/src/tileset-3d/helpers/s2-bounding-volume.ts +35 -0
- package/src/types.ts +2 -0
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
// SPDX-License-Identifier: MIT
|
|
3
3
|
// Copyright (c) vis.gl contributors
|
|
4
4
|
|
|
5
|
-
import {path, RequestCache} from '@loaders.gl/loader-utils';
|
|
5
|
+
import {path, RequestCache, sliceArrayBuffer} from '@loaders.gl/loader-utils';
|
|
6
6
|
import {Ellipsoid} from '@math.gl/geospatial';
|
|
7
|
-
import {Vector3} from '@math.gl/core';
|
|
8
|
-
import type {CoreAPI, Loader, LoaderOptions} from '@loaders.gl/loader-utils';
|
|
7
|
+
import {Matrix4, Vector3} from '@math.gl/core';
|
|
8
|
+
import type {CoreAPI, Loader, LoaderContext, LoaderOptions} from '@loaders.gl/loader-utils';
|
|
9
9
|
import type {Tile3D} from '../common/tile-3d';
|
|
10
10
|
import {Tileset3DTraverser} from './tileset-3d-traverser';
|
|
11
11
|
import type {Tileset3D} from '../common/tileset-3d';
|
|
@@ -27,6 +27,12 @@ import {TILESET_TYPE} from '../../constants';
|
|
|
27
27
|
import type {TilesetTraverser, TilesetTraverserProps} from '../common/tileset-traverser';
|
|
28
28
|
import type {FrameState} from '../helpers/frame-state';
|
|
29
29
|
import {get3DTilesSpatialReference} from '../../spatial/format-spatial-reference';
|
|
30
|
+
import {Tiles3DSpatialTransformer} from '../../spatial/tiles-3d-spatial-transformer';
|
|
31
|
+
import {
|
|
32
|
+
applyTilesetSpatialOptions,
|
|
33
|
+
markTilesetSpatialReferenceTransformed
|
|
34
|
+
} from '../../spatial/spatial-types';
|
|
35
|
+
import type {TilesetSpatialReference} from '../../spatial/spatial-types';
|
|
30
36
|
import {
|
|
31
37
|
materializeImplicitSubtree,
|
|
32
38
|
type ImplicitSubtreeReference,
|
|
@@ -42,6 +48,24 @@ const EMPTY_CONTENT_FORMATS: TilesetContentFormats = {
|
|
|
42
48
|
|
|
43
49
|
const DEFAULT_MAXIMUM_CACHED_SUBTREES = 32;
|
|
44
50
|
|
|
51
|
+
type PackageFile = {
|
|
52
|
+
name?: string;
|
|
53
|
+
mimeType: string;
|
|
54
|
+
uri?: string;
|
|
55
|
+
originalUri?: string;
|
|
56
|
+
data?: ArrayBuffer;
|
|
57
|
+
byteOffset: number;
|
|
58
|
+
byteLength: number;
|
|
59
|
+
bufferUri?: string;
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
type PackageResource = {fileIndex: number; files: PackageFile[]};
|
|
63
|
+
|
|
64
|
+
type Tiles3DPackageLoaderContext = LoaderContext & {
|
|
65
|
+
/** Parent package records available while parsing an embedded tileset or subtree. */
|
|
66
|
+
_tiles3dPackageFiles: PackageFile[];
|
|
67
|
+
};
|
|
68
|
+
|
|
45
69
|
/** Diagnostics for source-managed implicit subtree loading. */
|
|
46
70
|
export type ImplicitTilingStats = {
|
|
47
71
|
/** Subtree resources requested from the resolver or core API. */
|
|
@@ -93,6 +117,12 @@ export class Tiles3DSource implements Tileset3DSource {
|
|
|
93
117
|
private readonly queryParams: Record<string, string> = {};
|
|
94
118
|
/** Final request URLs cached by unmodified tile content URL. */
|
|
95
119
|
private readonly tileUrlCache: Map<string, string> = new Map();
|
|
120
|
+
/** Original content descriptors retained across {@link Tile3D.unloadContent} calls. */
|
|
121
|
+
private readonly tileContentHeaders = new WeakMap<Tile3D, Record<string, any>[]>();
|
|
122
|
+
/** Source-local namespaces assigned to distinct embedded glTF package file collections. */
|
|
123
|
+
private readonly packageNamespaces = new WeakMap<PackageFile[], number>();
|
|
124
|
+
/** Next source-local package namespace. */
|
|
125
|
+
private nextPackageNamespace = 0;
|
|
96
126
|
/** Parsed subtree requests keyed by final source URL for deduplication and LRU reuse. */
|
|
97
127
|
private readonly implicitSubtreeCache: RequestCache<ParsedImplicitSubtree>;
|
|
98
128
|
/** Mutable counters exposed as a defensive snapshot through {@link getImplicitTilingStats}. */
|
|
@@ -110,6 +140,10 @@ export class Tiles3DSource implements Tileset3DSource {
|
|
|
110
140
|
private readonly extensionsUsed: string[] = [];
|
|
111
141
|
private readonly resolver?: TilesetSourceResolver;
|
|
112
142
|
private rootTileset: TilesetJSON;
|
|
143
|
+
/** CRS transformer shared by this source's headers and decoded glTF content. */
|
|
144
|
+
private spatialTransformer?: Tiles3DSpatialTransformer;
|
|
145
|
+
/** Root header after composing source-frame transforms and rebuilding target bounds. */
|
|
146
|
+
private preparedRootHeader?: Record<string, any>;
|
|
113
147
|
|
|
114
148
|
/**
|
|
115
149
|
* Creates a 3D Tiles source.
|
|
@@ -175,11 +209,12 @@ export class Tiles3DSource implements Tileset3DSource {
|
|
|
175
209
|
throw new Error('Tileset must have an asset property.');
|
|
176
210
|
}
|
|
177
211
|
if (
|
|
212
|
+
this.rootTileset.formatVersion !== '2.0-draft' &&
|
|
178
213
|
this.asset.version !== '0.0' &&
|
|
179
214
|
this.asset.version !== '1.0' &&
|
|
180
215
|
this.asset.version !== '1.1'
|
|
181
216
|
) {
|
|
182
|
-
throw new Error('The tileset must be 3D Tiles version
|
|
217
|
+
throw new Error('The tileset must be 3D Tiles version 0.0, 1.0, 1.1, or 2.0-draft.');
|
|
183
218
|
}
|
|
184
219
|
|
|
185
220
|
if ('tilesetVersion' in this.asset) {
|
|
@@ -224,6 +259,29 @@ export class Tiles3DSource implements Tileset3DSource {
|
|
|
224
259
|
return this.getMetadata().tileset;
|
|
225
260
|
}
|
|
226
261
|
|
|
262
|
+
/** Prepare nonlinear CRS state before runtime headers are materialized. */
|
|
263
|
+
async prepareTileset(tileset: Tileset3D): Promise<void> {
|
|
264
|
+
const spatialReference = tileset.spatialReference;
|
|
265
|
+
if (spatialReference.status === 'unresolved') {
|
|
266
|
+
throw new Error(
|
|
267
|
+
spatialReference.warnings[0] ||
|
|
268
|
+
'3D Tiles spatial operations cannot be resolved from the supplied metadata and options'
|
|
269
|
+
);
|
|
270
|
+
}
|
|
271
|
+
if (spatialReference.status !== 'transformable' && spatialReference.status !== 'transformed') {
|
|
272
|
+
return;
|
|
273
|
+
}
|
|
274
|
+
this.spatialTransformer ||= new Tiles3DSpatialTransformer(
|
|
275
|
+
spatialReference,
|
|
276
|
+
tileset.options.spatial
|
|
277
|
+
);
|
|
278
|
+
tileset.spatialReference = markTilesetSpatialReferenceTransformed(spatialReference);
|
|
279
|
+
this.preparedRootHeader = this.transformTileHeader(
|
|
280
|
+
this.getMetadata().tileset.root,
|
|
281
|
+
new Matrix4()
|
|
282
|
+
);
|
|
283
|
+
}
|
|
284
|
+
|
|
227
285
|
/**
|
|
228
286
|
* Builds explicit runtime headers while leaving implicit subtree references lazy.
|
|
229
287
|
*/
|
|
@@ -232,7 +290,8 @@ export class Tiles3DSource implements Tileset3DSource {
|
|
|
232
290
|
tilesetJson: TilesetJSON,
|
|
233
291
|
parentTile?: Tile3D | null
|
|
234
292
|
): Tile3D {
|
|
235
|
-
const
|
|
293
|
+
const rootHeader = parentTile ? tilesetJson.root : this.preparedRootHeader || tilesetJson.root;
|
|
294
|
+
const rootTile = new Tile3DNode(tileset, rootHeader, parentTile || undefined);
|
|
236
295
|
|
|
237
296
|
if (parentTile) {
|
|
238
297
|
parentTile.children.push(rootTile);
|
|
@@ -274,6 +333,17 @@ export class Tiles3DSource implements Tileset3DSource {
|
|
|
274
333
|
*/
|
|
275
334
|
async loadTileContent(tile: Tile3D): Promise<TileContentLoadResult> {
|
|
276
335
|
const contentUrls = (tile.contentUrls || [tile.contentUrl]).filter(Boolean);
|
|
336
|
+
const cachedContentHeaders = this.tileContentHeaders.get(tile);
|
|
337
|
+
const contentHeaders: Record<string, any>[] =
|
|
338
|
+
cachedContentHeaders ||
|
|
339
|
+
(Array.isArray(tile.header?.content)
|
|
340
|
+
? tile.header.content
|
|
341
|
+
: tile.header?.content
|
|
342
|
+
? [tile.header.content]
|
|
343
|
+
: []);
|
|
344
|
+
if (!cachedContentHeaders) {
|
|
345
|
+
this.tileContentHeaders.set(tile, contentHeaders);
|
|
346
|
+
}
|
|
277
347
|
const tilesetLoaderOptions =
|
|
278
348
|
(this.loadOptions[this.loader.id] as Record<string, unknown>) || {};
|
|
279
349
|
const options = {
|
|
@@ -283,12 +353,37 @@ export class Tiles3DSource implements Tileset3DSource {
|
|
|
283
353
|
// Content bytes, rather than URL suffixes, distinguish external tilesets from renderable
|
|
284
354
|
// payloads. This is required for signed and extensionless resources.
|
|
285
355
|
isTileset: 'auto',
|
|
286
|
-
assetGltfUpAxis: (this.asset && this.asset.gltfUpAxis) || 'Y'
|
|
356
|
+
assetGltfUpAxis: (this.asset && this.asset.gltfUpAxis) || 'Y',
|
|
357
|
+
_tilesetOptions: {
|
|
358
|
+
spatialReference: tile.header?._spatialReference || tile.tileset?.spatialReference,
|
|
359
|
+
spatialOptions: tile.tileset?.options.spatial,
|
|
360
|
+
spatialTransform: tile.header?._spatialTransform
|
|
361
|
+
}
|
|
287
362
|
}
|
|
288
363
|
};
|
|
289
364
|
|
|
290
365
|
const contents = await Promise.all(
|
|
291
|
-
contentUrls.map(
|
|
366
|
+
contentUrls.map((contentUrl, contentIndex) => {
|
|
367
|
+
const contentHeader = contentHeaders[contentIndex] || {};
|
|
368
|
+
const vectorExtension = contentHeader.extensions?.['3DTILES_content_gltf_vector'];
|
|
369
|
+
const vectorContent =
|
|
370
|
+
contentHeader._vectorContent ||
|
|
371
|
+
(vectorExtension?.vector === true ? {clip: vectorExtension.clip === true} : undefined);
|
|
372
|
+
const contentOptions = vectorContent
|
|
373
|
+
? {
|
|
374
|
+
...options,
|
|
375
|
+
[this.loader.id]: {
|
|
376
|
+
...(options[this.loader.id] as Record<string, unknown>),
|
|
377
|
+
vectorContent
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
: options;
|
|
381
|
+
return this.loadContentResource(
|
|
382
|
+
this.getTileUrl(contentUrl),
|
|
383
|
+
contentHeader._resource,
|
|
384
|
+
contentOptions
|
|
385
|
+
);
|
|
386
|
+
})
|
|
292
387
|
);
|
|
293
388
|
tile.contents = contents;
|
|
294
389
|
tile.content = contents[0] || null;
|
|
@@ -302,6 +397,72 @@ export class Tiles3DSource implements Tileset3DSource {
|
|
|
302
397
|
};
|
|
303
398
|
}
|
|
304
399
|
|
|
400
|
+
/** Transform a tile header and its eagerly declared descendants. */
|
|
401
|
+
private transformTileHeader(
|
|
402
|
+
header: Record<string, any>,
|
|
403
|
+
parentTransform: Matrix4 = new Matrix4()
|
|
404
|
+
): Record<string, any> {
|
|
405
|
+
if (!this.spatialTransformer) {
|
|
406
|
+
return header;
|
|
407
|
+
}
|
|
408
|
+
return this.transformTileHeaderWithTransformer(
|
|
409
|
+
header,
|
|
410
|
+
this.spatialTransformer,
|
|
411
|
+
parentTransform
|
|
412
|
+
);
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
/** Rebuilds one header subtree using an explicitly selected CRS transformer. */
|
|
416
|
+
private transformTileHeaderWithTransformer(
|
|
417
|
+
header: Record<string, any>,
|
|
418
|
+
transformer: Tiles3DSpatialTransformer,
|
|
419
|
+
parentTransform: Matrix4,
|
|
420
|
+
spatialReference?: TilesetSpatialReference
|
|
421
|
+
): Record<string, any> {
|
|
422
|
+
const localTransform = header.transform ? new Matrix4(header.transform) : new Matrix4();
|
|
423
|
+
const composedTransform = new Matrix4(parentTransform).multiplyRight(localTransform);
|
|
424
|
+
const content = Array.isArray(header.content)
|
|
425
|
+
? header.content.map((entry: Record<string, any>) =>
|
|
426
|
+
this.transformContentHeader(entry, composedTransform, transformer)
|
|
427
|
+
)
|
|
428
|
+
: header.content
|
|
429
|
+
? this.transformContentHeader(header.content, composedTransform, transformer)
|
|
430
|
+
: header.content;
|
|
431
|
+
return {
|
|
432
|
+
...header,
|
|
433
|
+
transform: undefined,
|
|
434
|
+
boundingVolume: header.boundingVolume
|
|
435
|
+
? transformer.transformBoundingVolume(header.boundingVolume, composedTransform)
|
|
436
|
+
: header.boundingVolume,
|
|
437
|
+
_spatialReference: spatialReference,
|
|
438
|
+
_spatialTransform: composedTransform.toArray(),
|
|
439
|
+
content,
|
|
440
|
+
children: Array.isArray(header.children)
|
|
441
|
+
? header.children.map((child: Record<string, any>) =>
|
|
442
|
+
this.transformTileHeaderWithTransformer(
|
|
443
|
+
child,
|
|
444
|
+
transformer,
|
|
445
|
+
composedTransform,
|
|
446
|
+
spatialReference
|
|
447
|
+
)
|
|
448
|
+
)
|
|
449
|
+
: header.children
|
|
450
|
+
};
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
private transformContentHeader(
|
|
454
|
+
content: Record<string, any>,
|
|
455
|
+
composedTransform: Matrix4,
|
|
456
|
+
transformer: Tiles3DSpatialTransformer
|
|
457
|
+
): Record<string, any> {
|
|
458
|
+
return {
|
|
459
|
+
...content,
|
|
460
|
+
boundingVolume: content.boundingVolume
|
|
461
|
+
? transformer.transformBoundingVolume(content.boundingVolume, composedTransform)
|
|
462
|
+
: content.boundingVolume
|
|
463
|
+
};
|
|
464
|
+
}
|
|
465
|
+
|
|
305
466
|
/**
|
|
306
467
|
* Loads, materializes, and installs exactly one implicit subtree.
|
|
307
468
|
*
|
|
@@ -321,7 +482,7 @@ export class Tiles3DSource implements Tileset3DSource {
|
|
|
321
482
|
}
|
|
322
483
|
|
|
323
484
|
const subtreeUrl = this.getTileUrl(reference.subtreeUrl);
|
|
324
|
-
const subtree = await this.loadImplicitSubtreeResource(subtreeUrl);
|
|
485
|
+
const subtree = await this.loadImplicitSubtreeResource(subtreeUrl, reference.resource);
|
|
325
486
|
if (this.destroyed || tile.isDestroyed()) {
|
|
326
487
|
return {loaded: false, tileCount: 0, childSubtreeCount: 0};
|
|
327
488
|
}
|
|
@@ -330,11 +491,25 @@ export class Tiles3DSource implements Tileset3DSource {
|
|
|
330
491
|
subtreeUrl
|
|
331
492
|
});
|
|
332
493
|
|
|
333
|
-
tile.
|
|
494
|
+
const nestedSpatialReference = tile.header._spatialReference as
|
|
495
|
+
| TilesetSpatialReference
|
|
496
|
+
| undefined;
|
|
497
|
+
const subtreeTransformer = nestedSpatialReference
|
|
498
|
+
? new Tiles3DSpatialTransformer(nestedSpatialReference, tile.tileset.options.spatial)
|
|
499
|
+
: this.spatialTransformer;
|
|
500
|
+
const materializedRoot = subtreeTransformer
|
|
501
|
+
? this.transformTileHeaderWithTransformer(
|
|
502
|
+
materializedSubtree.root,
|
|
503
|
+
subtreeTransformer,
|
|
504
|
+
new Matrix4(),
|
|
505
|
+
nestedSpatialReference
|
|
506
|
+
)
|
|
507
|
+
: materializedSubtree.root;
|
|
508
|
+
tile.applyImplicitSubtreeHeader(materializedRoot);
|
|
334
509
|
const materializedTileCount = this.initializeMaterializedChildren(
|
|
335
510
|
tile.tileset,
|
|
336
511
|
tile,
|
|
337
|
-
|
|
512
|
+
materializedRoot.children
|
|
338
513
|
);
|
|
339
514
|
this.implicitTilingStats.loadedSubtrees++;
|
|
340
515
|
this.implicitTilingStats.materializedTiles += materializedTileCount;
|
|
@@ -346,6 +521,17 @@ export class Tiles3DSource implements Tileset3DSource {
|
|
|
346
521
|
};
|
|
347
522
|
}
|
|
348
523
|
|
|
524
|
+
/** Loads an implicit subtree for dataset traversal without camera state. */
|
|
525
|
+
async loadTileChildrenForTraversal(
|
|
526
|
+
tile: Tile3D,
|
|
527
|
+
signal?: AbortSignal
|
|
528
|
+
): Promise<TileChildrenLoadResult> {
|
|
529
|
+
throwIfTraversalAborted(signal);
|
|
530
|
+
const result = await this.loadTileChildren(tile, {} as FrameState);
|
|
531
|
+
throwIfTraversalAborted(signal);
|
|
532
|
+
return result;
|
|
533
|
+
}
|
|
534
|
+
|
|
349
535
|
/**
|
|
350
536
|
* Returns a snapshot of implicit-subtree request, cache, and materialization counters.
|
|
351
537
|
*
|
|
@@ -477,8 +663,97 @@ export class Tiles3DSource implements Tileset3DSource {
|
|
|
477
663
|
const nestedTilesets =
|
|
478
664
|
loadResult.nestedTilesets || (loadResult.nestedTileset ? [loadResult.nestedTileset] : []);
|
|
479
665
|
for (const nestedTileset of nestedTilesets) {
|
|
480
|
-
tileset._initializeTileHeaders(nestedTileset, tile);
|
|
666
|
+
tileset._initializeTileHeaders(this.prepareNestedTileset(tileset, nestedTileset), tile);
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
/**
|
|
671
|
+
* Resolves a nested tileset's CRS independently and adapts it to the owning tileset output frame.
|
|
672
|
+
*
|
|
673
|
+
* Nested resources may use a different source CRS from their parent. Their headers and glTF
|
|
674
|
+
* payloads must therefore use a transformer built from the nested descriptor, while the parent
|
|
675
|
+
* runtime continues to expose one requested output frame to traversal and rendering.
|
|
676
|
+
*/
|
|
677
|
+
private prepareNestedTileset(tileset: Tileset3D, nestedTileset: TilesetJSON): TilesetJSON {
|
|
678
|
+
const nestedSpatialReference = nestedTileset.spatialMetadata;
|
|
679
|
+
const parentSpatialReference = tileset.spatialReference;
|
|
680
|
+
|
|
681
|
+
if (
|
|
682
|
+
nestedSpatialReference?.status === 'unresolved' ||
|
|
683
|
+
nestedSpatialReference?.crs.state === 'unknown'
|
|
684
|
+
) {
|
|
685
|
+
throw new Error(
|
|
686
|
+
nestedSpatialReference.warnings[0] ||
|
|
687
|
+
'Nested 3D Tiles CRS is explicitly unresolved and cannot be placed in the parent frame'
|
|
688
|
+
);
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
if (!nestedSpatialReference || !nestedSpatialReference.sourceCrs) {
|
|
692
|
+
if (
|
|
693
|
+
this.spatialTransformer &&
|
|
694
|
+
(parentSpatialReference.status === 'transformable' ||
|
|
695
|
+
parentSpatialReference.status === 'transformed')
|
|
696
|
+
) {
|
|
697
|
+
return {
|
|
698
|
+
...nestedTileset,
|
|
699
|
+
spatialMetadata: parentSpatialReference,
|
|
700
|
+
root: this.transformTileHeaderWithTransformer(
|
|
701
|
+
nestedTileset.root,
|
|
702
|
+
this.spatialTransformer,
|
|
703
|
+
new Matrix4(),
|
|
704
|
+
parentSpatialReference
|
|
705
|
+
)
|
|
706
|
+
};
|
|
707
|
+
}
|
|
708
|
+
return addNestedSpatialReference(nestedTileset, parentSpatialReference);
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
const parentCoordinateEpoch = parentSpatialReference.coordinateEpoch;
|
|
712
|
+
const nestedCoordinateEpoch = nestedSpatialReference.coordinateEpoch;
|
|
713
|
+
if (
|
|
714
|
+
parentCoordinateEpoch !== undefined &&
|
|
715
|
+
nestedCoordinateEpoch !== undefined &&
|
|
716
|
+
parentCoordinateEpoch !== nestedCoordinateEpoch
|
|
717
|
+
) {
|
|
718
|
+
throw new Error(
|
|
719
|
+
`Nested 3D Tiles coordinate epoch ${nestedCoordinateEpoch} differs from parent epoch ${parentCoordinateEpoch}; epoch-aware transformation is unavailable`
|
|
720
|
+
);
|
|
481
721
|
}
|
|
722
|
+
|
|
723
|
+
const outputCrs = parentSpatialReference.targetCrs || parentSpatialReference.sourceCrs;
|
|
724
|
+
if (!outputCrs) {
|
|
725
|
+
return addNestedSpatialReference(nestedTileset, nestedSpatialReference);
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
const nestedOutputReference = applyTilesetSpatialOptions(nestedSpatialReference, {
|
|
729
|
+
...tileset.options.spatial,
|
|
730
|
+
outputCoordinates: 'target-crs',
|
|
731
|
+
targetCrs: outputCrs
|
|
732
|
+
});
|
|
733
|
+
if (nestedOutputReference.status === 'unresolved') {
|
|
734
|
+
throw new Error(
|
|
735
|
+
nestedOutputReference.warnings[0] ||
|
|
736
|
+
'Nested 3D Tiles CRS cannot be resolved in the parent output frame'
|
|
737
|
+
);
|
|
738
|
+
}
|
|
739
|
+
if (nestedOutputReference.status === 'native') {
|
|
740
|
+
return addNestedSpatialReference(nestedTileset, nestedOutputReference);
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
const nestedTransformer = new Tiles3DSpatialTransformer(
|
|
744
|
+
nestedOutputReference,
|
|
745
|
+
tileset.options.spatial
|
|
746
|
+
);
|
|
747
|
+
return {
|
|
748
|
+
...nestedTileset,
|
|
749
|
+
spatialMetadata: nestedOutputReference,
|
|
750
|
+
root: this.transformTileHeaderWithTransformer(
|
|
751
|
+
nestedTileset.root,
|
|
752
|
+
nestedTransformer,
|
|
753
|
+
new Matrix4(),
|
|
754
|
+
nestedOutputReference
|
|
755
|
+
)
|
|
756
|
+
};
|
|
482
757
|
}
|
|
483
758
|
|
|
484
759
|
/**
|
|
@@ -522,13 +797,102 @@ export class Tiles3DSource implements Tileset3DSource {
|
|
|
522
797
|
return await this.loadWithCoreApi(url, options, loader);
|
|
523
798
|
}
|
|
524
799
|
|
|
800
|
+
/** Loads a URL content reference or parses a buffer-view file retained from a glTF package. */
|
|
801
|
+
private async loadContentResource(
|
|
802
|
+
url: string,
|
|
803
|
+
packageResource: PackageResource | undefined,
|
|
804
|
+
options: LoaderOptions
|
|
805
|
+
): Promise<any> {
|
|
806
|
+
if (!packageResource) {
|
|
807
|
+
return await this.loadResourceData(url, options);
|
|
808
|
+
}
|
|
809
|
+
const file = packageResource.files[packageResource.fileIndex];
|
|
810
|
+
if (!file) {
|
|
811
|
+
throw new Error(`3D Tiles package references missing file ${packageResource.fileIndex}`);
|
|
812
|
+
}
|
|
813
|
+
if (file.uri) {
|
|
814
|
+
return await this.loadResourceData(this.getTileUrl(file.uri), options);
|
|
815
|
+
}
|
|
816
|
+
if (!this.coreApi) {
|
|
817
|
+
throw new Error('Tiles3DSource requires an injected coreApi to parse embedded package data');
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
const data = await this.getPackageFileData(file);
|
|
821
|
+
const packageNamespace = this.getPackageNamespace(packageResource.files);
|
|
822
|
+
const packageRootUrl = `gltf-package://${packageNamespace}/${packageResource.fileIndex}`;
|
|
823
|
+
const fileName = file.name || 'content';
|
|
824
|
+
const directorySeparatorIndex = fileName.lastIndexOf('/');
|
|
825
|
+
const packageBaseUrl =
|
|
826
|
+
directorySeparatorIndex >= 0
|
|
827
|
+
? `${packageRootUrl}/${fileName.slice(0, directorySeparatorIndex)}`
|
|
828
|
+
: packageRootUrl;
|
|
829
|
+
return await this.coreApi.parse(data, this.loader, options, {
|
|
830
|
+
url: `${packageRootUrl}/${fileName}`,
|
|
831
|
+
filename: fileName,
|
|
832
|
+
baseUrl: packageBaseUrl,
|
|
833
|
+
loaders: [this.loader],
|
|
834
|
+
coreApi: this.coreApi,
|
|
835
|
+
_parse: this.coreApi.parse,
|
|
836
|
+
_tiles3dPackageFiles: packageResource.files,
|
|
837
|
+
fetch: async (resource: string, init?: RequestInit) => {
|
|
838
|
+
const reference = resource.startsWith(`${packageRootUrl}/`)
|
|
839
|
+
? resource.slice(packageRootUrl.length + 1)
|
|
840
|
+
: resource;
|
|
841
|
+
const referencedFile = packageResource.files.find(
|
|
842
|
+
packageFile => packageFile.name === reference || packageFile.originalUri === reference
|
|
843
|
+
);
|
|
844
|
+
if (!referencedFile) {
|
|
845
|
+
throw new Error(`3D Tiles glTF package does not contain file ${reference}`);
|
|
846
|
+
}
|
|
847
|
+
if (referencedFile.uri) {
|
|
848
|
+
return await this.coreApi!.fetchFile(this.getTileUrl(referencedFile.uri), init);
|
|
849
|
+
}
|
|
850
|
+
const referencedData = await this.getPackageFileData(referencedFile);
|
|
851
|
+
return new Response(referencedData, {
|
|
852
|
+
headers: {'content-type': referencedFile.mimeType}
|
|
853
|
+
});
|
|
854
|
+
}
|
|
855
|
+
} as Tiles3DPackageLoaderContext);
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
/** Returns a stable source-local namespace for one embedded glTF package file collection. */
|
|
859
|
+
private getPackageNamespace(files: PackageFile[]): number {
|
|
860
|
+
const existingNamespace = this.packageNamespaces.get(files);
|
|
861
|
+
if (existingNamespace !== undefined) {
|
|
862
|
+
return existingNamespace;
|
|
863
|
+
}
|
|
864
|
+
const packageNamespace = this.nextPackageNamespace++;
|
|
865
|
+
this.packageNamespaces.set(files, packageNamespace);
|
|
866
|
+
return packageNamespace;
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
/** Resolves a retained package-file byte range without eagerly copying other package files. */
|
|
870
|
+
private async getPackageFileData(file: PackageFile): Promise<ArrayBuffer> {
|
|
871
|
+
if (file.data) {
|
|
872
|
+
return sliceArrayBuffer(file.data, file.byteOffset, file.byteLength);
|
|
873
|
+
}
|
|
874
|
+
if (file.bufferUri && this.coreApi) {
|
|
875
|
+
const response = await this.coreApi.fetchFile(this.getTileUrl(file.bufferUri));
|
|
876
|
+
if (!response.ok) {
|
|
877
|
+
throw new Error(`Failed to fetch 3D Tiles package buffer: HTTP ${response.status}`);
|
|
878
|
+
}
|
|
879
|
+
const buffer = await response.arrayBuffer();
|
|
880
|
+
return sliceArrayBuffer(buffer, file.byteOffset, file.byteLength);
|
|
881
|
+
}
|
|
882
|
+
throw new Error('3D Tiles package file has no available byte source');
|
|
883
|
+
}
|
|
884
|
+
|
|
525
885
|
/**
|
|
526
886
|
* Returns a parsed subtree from the LRU cache or starts one source-managed request.
|
|
527
887
|
*
|
|
528
888
|
* @param subtreeUrl - Final subtree URL after query inheritance.
|
|
889
|
+
* @param packageResource - Optional glTF package file satisfying the subtree reference.
|
|
529
890
|
* @returns Parsed subtree availability data.
|
|
530
891
|
*/
|
|
531
|
-
private async loadImplicitSubtreeResource(
|
|
892
|
+
private async loadImplicitSubtreeResource(
|
|
893
|
+
subtreeUrl: string,
|
|
894
|
+
packageResource?: PackageResource
|
|
895
|
+
): Promise<ParsedImplicitSubtree> {
|
|
532
896
|
const cachedSubtree = this.implicitSubtreeCache.get(subtreeUrl);
|
|
533
897
|
if (cachedSubtree) {
|
|
534
898
|
this.implicitTilingStats.cacheHits++;
|
|
@@ -538,14 +902,17 @@ export class Tiles3DSource implements Tileset3DSource {
|
|
|
538
902
|
this.implicitTilingStats.requestedSubtrees++;
|
|
539
903
|
const loaderOptions = (this.loadOptions[this.loader.id] as Record<string, unknown>) || {};
|
|
540
904
|
return await this.implicitSubtreeCache.getOrLoad(subtreeUrl, async () => {
|
|
541
|
-
|
|
905
|
+
const options = {
|
|
542
906
|
...this.loadOptions,
|
|
543
907
|
[this.loader.id]: {
|
|
544
908
|
...loaderOptions,
|
|
545
909
|
isTileset: false,
|
|
546
910
|
isSubtree: true
|
|
547
911
|
}
|
|
548
|
-
}
|
|
912
|
+
};
|
|
913
|
+
return (await (packageResource
|
|
914
|
+
? this.loadContentResource(subtreeUrl, packageResource, options)
|
|
915
|
+
: this.loadResourceData(subtreeUrl, options))) as ParsedImplicitSubtree;
|
|
549
916
|
});
|
|
550
917
|
}
|
|
551
918
|
|
|
@@ -583,6 +950,13 @@ export class Tiles3DSource implements Tileset3DSource {
|
|
|
583
950
|
}
|
|
584
951
|
}
|
|
585
952
|
|
|
953
|
+
/** Throws a cancellation reason before or after source-managed traversal work. */
|
|
954
|
+
function throwIfTraversalAborted(signal?: AbortSignal): void {
|
|
955
|
+
if (signal?.aborted) {
|
|
956
|
+
throw signal.reason ?? new DOMException('The operation was aborted', 'AbortError');
|
|
957
|
+
}
|
|
958
|
+
}
|
|
959
|
+
|
|
586
960
|
function isTilesetRequest(input: TilesetSourceInput): input is TilesetSourceRequest {
|
|
587
961
|
return Boolean(
|
|
588
962
|
input && typeof input === 'object' && 'url' in input && 'loader' in input && !('type' in input)
|
|
@@ -610,3 +984,20 @@ function normalizeTiles3DRequest(input: TilesetSourceInput): TilesetSourceReques
|
|
|
610
984
|
coreApi: (input as TilesetSourceRequest).coreApi
|
|
611
985
|
};
|
|
612
986
|
}
|
|
987
|
+
|
|
988
|
+
/** Adds a nested CRS descriptor to each header without changing its native placement. */
|
|
989
|
+
function addNestedSpatialReference(
|
|
990
|
+
nestedTileset: TilesetJSON,
|
|
991
|
+
spatialReference: TilesetSpatialReference
|
|
992
|
+
): TilesetJSON {
|
|
993
|
+
const addToHeader = (header: Record<string, any>): Record<string, any> => ({
|
|
994
|
+
...header,
|
|
995
|
+
_spatialReference: spatialReference,
|
|
996
|
+
children: Array.isArray(header.children) ? header.children.map(addToHeader) : header.children
|
|
997
|
+
});
|
|
998
|
+
return {
|
|
999
|
+
...nestedTileset,
|
|
1000
|
+
spatialMetadata: spatialReference,
|
|
1001
|
+
root: addToHeader(nestedTileset.root)
|
|
1002
|
+
};
|
|
1003
|
+
}
|
|
@@ -274,10 +274,12 @@ export class Tileset3DTraverser extends TilesetTraverser {
|
|
|
274
274
|
}
|
|
275
275
|
|
|
276
276
|
/**
|
|
277
|
-
* Starts an implicit subtree request
|
|
277
|
+
* Starts an implicit subtree request after culling and request-volume eligibility.
|
|
278
278
|
*
|
|
279
|
-
* The
|
|
280
|
-
*
|
|
279
|
+
* The first subtree of an implicit hierarchy materializes independently of SSE because its
|
|
280
|
+
* placeholder has no render content. Deeper subtree boundaries remain SSE-gated. The current
|
|
281
|
+
* parent remains the traversal boundary while metadata is loading, which preserves REPLACE
|
|
282
|
+
* coverage and ADD accumulation. Request scheduling reads the tile's existing
|
|
281
283
|
* progressive-resolution and foveated metrics, so subtree metadata follows the same priority
|
|
282
284
|
* policy as render content. A completion notification lets the owning tileset publish the newly
|
|
283
285
|
* materialized traversal result on the next update.
|
|
@@ -286,10 +288,11 @@ export class Tileset3DTraverser extends TilesetTraverser {
|
|
|
286
288
|
* @param frameState - Current culling and LOD state.
|
|
287
289
|
*/
|
|
288
290
|
updateChildTiles(tile: Tile3D, frameState: FrameState): void {
|
|
291
|
+
const isImplicitHierarchyRoot = tile.header.implicitSubtree?.coordinates.level === 0;
|
|
289
292
|
if (
|
|
290
293
|
tile.hasUnloadedChildren &&
|
|
291
294
|
tile.isVisibleAndInRequestVolume &&
|
|
292
|
-
this.shouldRefine(tile, frameState)
|
|
295
|
+
(isImplicitHierarchyRoot || this.shouldRefine(tile, frameState))
|
|
293
296
|
) {
|
|
294
297
|
const requestKey = `${frameState.viewport.id}:${tile.id}`;
|
|
295
298
|
if (!this.pendingImplicitSubtrees.has(requestKey)) {
|