@loaders.gl/tiles 5.0.0-alpha.6 → 5.0.0-alpha.7
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/dist/dist.dev.js +5160 -2896
- package/dist/dist.min.js +4 -2
- package/dist/index.cjs +1509 -454
- package/dist/index.cjs.map +3 -3
- package/dist/index.d.ts +8 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -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 +89 -12
- package/dist/spatial/spatial-coordinate-transformer.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 +29 -5
- package/dist/tileset-3d/common/tile-3d.d.ts.map +1 -1
- package/dist/tileset-3d/common/tile-3d.js +147 -18
- package/dist/tileset-3d/common/tile-3d.js.map +1 -1
- package/dist/tileset-3d/common/tileset-3d.d.ts +14 -0
- package/dist/tileset-3d/common/tileset-3d.d.ts.map +1 -1
- package/dist/tileset-3d/common/tileset-3d.js +27 -1
- package/dist/tileset-3d/common/tileset-3d.js.map +1 -1
- 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 +3 -0
- 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 +32 -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 +265 -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/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 +4 -0
- package/dist/tileset-3d/helpers/frame-state.d.ts.map +1 -1
- package/dist/tileset-3d/helpers/frame-state.js +6 -1
- 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 +13 -12
- package/src/index.ts +13 -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 +106 -15
- 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 +172 -18
- package/src/tileset-3d/common/tileset-3d.ts +29 -1
- package/src/tileset-3d/common/tileset-observability.ts +100 -0
- package/src/tileset-3d/common/tileset-source.ts +3 -0
- 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 +387 -14
- package/src/tileset-3d/format-3d-tiles/tileset-3d-traverser.ts +7 -4
- package/src/tileset-3d/helpers/bounding-volume.ts +29 -62
- package/src/tileset-3d/helpers/frame-state.ts +6 -1
- package/src/tileset-3d/helpers/s2-bounding-volume.ts +35 -0
- package/src/types.ts +2 -0
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
// loaders.gl
|
|
2
|
+
// SPDX-License-Identifier: MIT
|
|
3
|
+
// Copyright (c) vis.gl contributors
|
|
4
|
+
|
|
5
|
+
/** Structural subset implemented by math.gl bounding volumes used by tile renderers. */
|
|
6
|
+
export type Tile3DBoundingVolume = {
|
|
7
|
+
distanceSquaredTo?(point: unknown): number;
|
|
8
|
+
intersectPlane?(plane: unknown): number;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
/** Renderer-neutral description of one ordered tile content entry. */
|
|
12
|
+
export type Tile3DContent = {
|
|
13
|
+
/** Zero-based source order, stable across loading and unloading. */
|
|
14
|
+
index: number;
|
|
15
|
+
/** Resolved content URI when declared by the tileset. */
|
|
16
|
+
uri?: string;
|
|
17
|
+
/** Tileset group index declared by this content entry. */
|
|
18
|
+
group?: number;
|
|
19
|
+
/** Normalized tile content kind, when detected. */
|
|
20
|
+
type?: string;
|
|
21
|
+
/** Decoded payload, or null while unloaded/failed. */
|
|
22
|
+
payload: unknown;
|
|
23
|
+
/** Raw content metadata reference. */
|
|
24
|
+
metadata: Record<string, unknown> | null;
|
|
25
|
+
/** Transformed content volume, with tile-volume fallback. */
|
|
26
|
+
boundingVolume: Tile3DBoundingVolume | null;
|
|
27
|
+
/** Normalized feature-id declarations for this content. */
|
|
28
|
+
featureIds: Tile3DFeatureIdSet[];
|
|
29
|
+
/** Whether the payload can currently be rendered. */
|
|
30
|
+
renderable: boolean;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
/** Renderer-neutral feature-id declaration from a tile content payload. */
|
|
34
|
+
export type Tile3DFeatureIdSet = {
|
|
35
|
+
/** Source of the feature identifiers. */
|
|
36
|
+
source: 'attribute' | 'property-table' | 'constant' | 'texture' | 'implicit';
|
|
37
|
+
/** glTF attribute name for attribute-backed identifiers. */
|
|
38
|
+
attribute?: string;
|
|
39
|
+
/** Structural-metadata property-table index. */
|
|
40
|
+
propertyTable?: number;
|
|
41
|
+
/** Constant feature identifier. */
|
|
42
|
+
constant?: number;
|
|
43
|
+
/** Texture-backed identifier declaration, when present. */
|
|
44
|
+
texture?: Record<string, unknown>;
|
|
45
|
+
/** Implicit identifier declaration, when present. */
|
|
46
|
+
implicit?: Record<string, unknown>;
|
|
47
|
+
/** Decoded identifier values, when supplied by a content loader. */
|
|
48
|
+
values?: Uint32Array | Uint16Array | Uint8Array;
|
|
49
|
+
/** Unrecognized source fields preserved for forward compatibility. */
|
|
50
|
+
details?: Record<string, unknown>;
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
/** Raw metadata references inherited by a tile and its contents. */
|
|
54
|
+
export type Tile3DMetadataContext = {
|
|
55
|
+
/** Tileset-level metadata entity. */
|
|
56
|
+
tileset?: Record<string, unknown> | null;
|
|
57
|
+
/** Group metadata entity selected for the tile's primary content. */
|
|
58
|
+
group?: Record<string, unknown> | null;
|
|
59
|
+
/** All tileset groups, used to resolve a selected content entry's group. */
|
|
60
|
+
groups?: readonly (Record<string, unknown> | null)[];
|
|
61
|
+
/** Tile metadata entity. */
|
|
62
|
+
tile?: Record<string, unknown> | null;
|
|
63
|
+
/** Content metadata entity. */
|
|
64
|
+
content?: Record<string, unknown> | null;
|
|
65
|
+
/** Implicit-subtree metadata entity. */
|
|
66
|
+
subtree?: Record<string, unknown> | null;
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
/** Extracts mesh-feature declarations from a decoded glTF-like payload. */
|
|
70
|
+
export function getTile3DFeatureIdSets(payload: unknown): Tile3DFeatureIdSet[] {
|
|
71
|
+
const featureIdSets: Tile3DFeatureIdSet[] = [];
|
|
72
|
+
const visited = new Set<object>();
|
|
73
|
+
const visit = (value: unknown): void => {
|
|
74
|
+
if (!value || typeof value !== 'object') {
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
if (visited.has(value as object)) {
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
visited.add(value as object);
|
|
81
|
+
const record = value as Record<string, any>;
|
|
82
|
+
const declarations = record.extensions?.EXT_mesh_features?.featureIds;
|
|
83
|
+
if (Array.isArray(declarations)) {
|
|
84
|
+
for (const declaration of declarations) {
|
|
85
|
+
if (!declaration || typeof declaration !== 'object') {
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
const source = declaration.texture
|
|
89
|
+
? 'texture'
|
|
90
|
+
: declaration.implicit
|
|
91
|
+
? 'implicit'
|
|
92
|
+
: declaration.attribute !== undefined
|
|
93
|
+
? 'attribute'
|
|
94
|
+
: declaration.propertyTable !== undefined
|
|
95
|
+
? 'property-table'
|
|
96
|
+
: 'constant';
|
|
97
|
+
featureIdSets.push({
|
|
98
|
+
source,
|
|
99
|
+
attribute: declaration.attribute,
|
|
100
|
+
propertyTable: declaration.propertyTable,
|
|
101
|
+
constant: declaration.constant,
|
|
102
|
+
texture: declaration.texture,
|
|
103
|
+
implicit: declaration.implicit,
|
|
104
|
+
values: declaration.values,
|
|
105
|
+
details: declaration
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
for (const child of Object.values(record)) {
|
|
110
|
+
visit(child);
|
|
111
|
+
}
|
|
112
|
+
};
|
|
113
|
+
visit(payload);
|
|
114
|
+
return featureIdSets;
|
|
115
|
+
}
|
|
@@ -8,6 +8,8 @@
|
|
|
8
8
|
import {Vector3, Matrix4} from '@math.gl/core';
|
|
9
9
|
import {CullingVolume} from '@math.gl/culling';
|
|
10
10
|
import type {CullingResult} from '@math.gl/culling';
|
|
11
|
+
import {getTile3DFeatureIdSets} from './tile-3d-contracts';
|
|
12
|
+
import type {Tile3DContent, Tile3DFeatureIdSet, Tile3DMetadataContext} from './tile-3d-contracts';
|
|
11
13
|
|
|
12
14
|
// Note: circular dependency
|
|
13
15
|
import type {Tileset3D} from './tileset-3d';
|
|
@@ -85,6 +87,14 @@ export function getContentVisibility(
|
|
|
85
87
|
return intersectingVolume ? 'intersecting' : 'inside';
|
|
86
88
|
}
|
|
87
89
|
|
|
90
|
+
function isNestedTilesetPayload(payload: unknown): boolean {
|
|
91
|
+
if (!payload || typeof payload !== 'object') {
|
|
92
|
+
return false;
|
|
93
|
+
}
|
|
94
|
+
const record = payload as Record<string, any>;
|
|
95
|
+
return record.shape === 'tileset3d' || record.type === 'tileset3d' || Boolean(record.tileset);
|
|
96
|
+
}
|
|
97
|
+
|
|
88
98
|
function defined(x) {
|
|
89
99
|
return x !== undefined && x !== null;
|
|
90
100
|
}
|
|
@@ -137,6 +147,9 @@ export class Tile3D {
|
|
|
137
147
|
*/
|
|
138
148
|
private _unscaledLodMetricValue: number = 0;
|
|
139
149
|
|
|
150
|
+
/** Whether the source format defines geometric error in transform-local units. */
|
|
151
|
+
private _scaleGeometricError: boolean = true;
|
|
152
|
+
|
|
140
153
|
/** @todo math.gl is not exporting BoundingVolume base type? */
|
|
141
154
|
boundingVolume: any = null;
|
|
142
155
|
|
|
@@ -147,6 +160,14 @@ export class Tile3D {
|
|
|
147
160
|
content: any = null;
|
|
148
161
|
/** Loaded payloads in the same order as {@link contentUrls}; `content` remains the primary payload. */
|
|
149
162
|
contents: any[] = [];
|
|
163
|
+
/** Ordered renderer-neutral descriptors for each source content entry. */
|
|
164
|
+
contentEntries: Tile3DContent[] = [];
|
|
165
|
+
/** Raw metadata references inherited by this tile and its content. */
|
|
166
|
+
metadataContext: Tile3DMetadataContext = {};
|
|
167
|
+
/** Feature-id declarations normalized from supported content extensions. */
|
|
168
|
+
featureIdSets: Tile3DFeatureIdSet[] = [];
|
|
169
|
+
/** Decoded 3D Tiles vector topology for the primary loaded content, when present. */
|
|
170
|
+
vectorContent: unknown = null;
|
|
150
171
|
/**
|
|
151
172
|
* Metadata entity attached to this tile in the source tileset, if present.
|
|
152
173
|
*
|
|
@@ -228,6 +249,7 @@ export class Tile3D {
|
|
|
228
249
|
return this._viewerRequestVolume;
|
|
229
250
|
}
|
|
230
251
|
|
|
252
|
+
/** Accumulated tile transforms from the source JSON, excluding the tileset model matrix. */
|
|
231
253
|
_initialTransform: Matrix4 = new Matrix4();
|
|
232
254
|
|
|
233
255
|
// Used by traverser, cannot be marked private
|
|
@@ -283,11 +305,7 @@ export class Tile3D {
|
|
|
283
305
|
this.type = header.type;
|
|
284
306
|
this.contentUrl = header.contentUrl;
|
|
285
307
|
this.contentUrls = header.contentUrls || (header.contentUrl ? [header.contentUrl] : []);
|
|
286
|
-
this.
|
|
287
|
-
const contentHeaders = Array.isArray(header.content) ? header.content : [header.content];
|
|
288
|
-
this.contentMetadata = contentHeaders
|
|
289
|
-
.filter(Boolean)
|
|
290
|
-
.map(contentHeader => contentHeader.metadata || null);
|
|
308
|
+
this._initializeMetadata(header);
|
|
291
309
|
this.childrenState = header.implicitSubtree ? 'unloaded' : 'ready';
|
|
292
310
|
|
|
293
311
|
this._initializeLodMetric(header);
|
|
@@ -295,6 +313,7 @@ export class Tile3D {
|
|
|
295
313
|
this._updateLodMetricScale();
|
|
296
314
|
this._initializeBoundingVolumes(header);
|
|
297
315
|
this._initializeContent(header);
|
|
316
|
+
this._initializeContentEntries(header);
|
|
298
317
|
this._initializeRenderingState(header);
|
|
299
318
|
|
|
300
319
|
Object.seal(this);
|
|
@@ -407,12 +426,16 @@ export class Tile3D {
|
|
|
407
426
|
}
|
|
408
427
|
|
|
409
428
|
/**
|
|
410
|
-
* Get bounding
|
|
429
|
+
* Get the current world-space bounding volume in cartographic coordinates.
|
|
411
430
|
* @returns [min, max] each in [longitude, latitude, altitude]
|
|
412
431
|
*/
|
|
413
432
|
get boundingBox(): CartographicBounds {
|
|
414
433
|
if (!this._boundingBox) {
|
|
415
|
-
this._boundingBox = getCartographicBounds(
|
|
434
|
+
this._boundingBox = getCartographicBounds(
|
|
435
|
+
this.header.boundingVolume,
|
|
436
|
+
this.boundingVolume,
|
|
437
|
+
!this.computedTransform.equals(this._initialTransform)
|
|
438
|
+
);
|
|
416
439
|
}
|
|
417
440
|
return this._boundingBox;
|
|
418
441
|
}
|
|
@@ -585,6 +608,8 @@ export class Tile3D {
|
|
|
585
608
|
this.contents = loadResult.contents;
|
|
586
609
|
this.content = this.contents[0] || null;
|
|
587
610
|
}
|
|
611
|
+
this.vectorContent =
|
|
612
|
+
this.contents.find(content => Boolean(content?.vectorContent))?.vectorContent || null;
|
|
588
613
|
|
|
589
614
|
if (this.tileset.options.contentLoader) {
|
|
590
615
|
await this.tileset.options.contentLoader(this);
|
|
@@ -592,6 +617,7 @@ export class Tile3D {
|
|
|
592
617
|
|
|
593
618
|
this.contentState = TILE_CONTENT_STATE.READY;
|
|
594
619
|
this._onContentLoaded();
|
|
620
|
+
this._updateContentEntriesAfterLoad(loadResult);
|
|
595
621
|
return loadResult;
|
|
596
622
|
} catch (error) {
|
|
597
623
|
// Tile is unloaded before the content finishes loading
|
|
@@ -665,34 +691,137 @@ export class Tile3D {
|
|
|
665
691
|
/**
|
|
666
692
|
* Replaces a contentless implicit placeholder with its materialized subtree-root header.
|
|
667
693
|
*
|
|
668
|
-
*
|
|
669
|
-
* transform
|
|
670
|
-
*
|
|
694
|
+
* The placeholder transform is preserved when the subtree omits a root transform. An explicit
|
|
695
|
+
* subtree-root transform replaces it and is recomposed against the runtime parent. Content,
|
|
696
|
+
* metadata, bounds, and raw geometric error are reinitialized from the available header.
|
|
671
697
|
*
|
|
672
698
|
* @param materializedHeader - Available root header produced from one subtree resource.
|
|
673
699
|
*/
|
|
674
700
|
applyImplicitSubtreeHeader(materializedHeader: Record<string, any>): void {
|
|
675
701
|
const existingTransform = this.header.transform;
|
|
676
702
|
const existingTransformMatrix = this.header.transformMatrix;
|
|
703
|
+
const hasMaterializedTransform = materializedHeader.transform !== undefined;
|
|
677
704
|
this.header = {
|
|
678
705
|
...this.header,
|
|
679
706
|
...materializedHeader,
|
|
680
|
-
transform: existingTransform,
|
|
681
|
-
transformMatrix:
|
|
707
|
+
transform: hasMaterializedTransform ? materializedHeader.transform : existingTransform,
|
|
708
|
+
transformMatrix: hasMaterializedTransform
|
|
709
|
+
? materializedHeader.transformMatrix
|
|
710
|
+
: existingTransformMatrix,
|
|
682
711
|
implicitSubtree: undefined
|
|
683
712
|
};
|
|
713
|
+
if (hasMaterializedTransform) {
|
|
714
|
+
this._initializeTransforms(this.header);
|
|
715
|
+
}
|
|
684
716
|
this.refine = this._getRefine(materializedHeader.refine);
|
|
685
717
|
this.type = materializedHeader.type;
|
|
686
718
|
this.contentUrl = materializedHeader.contentUrl;
|
|
687
719
|
this.contentUrls =
|
|
688
720
|
materializedHeader.contentUrls ||
|
|
689
721
|
(materializedHeader.contentUrl ? [materializedHeader.contentUrl] : []);
|
|
722
|
+
this._initializeMetadata(this.header);
|
|
690
723
|
this._initializeLodMetric(materializedHeader);
|
|
691
724
|
this._updateLodMetricScale();
|
|
692
725
|
this._initializeBoundingVolumes(this.header);
|
|
726
|
+
this._boundingBox = undefined;
|
|
693
727
|
// Use the merged header so inherited viewer-request-volume metadata remains available when a
|
|
694
728
|
// materialized implicit tile has no render content of its own.
|
|
695
729
|
this._initializeContent(this.header);
|
|
730
|
+
this._initializeContentEntries(this.header);
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
/** Refreshes tile and content metadata from a source or materialized tile header. */
|
|
734
|
+
private _initializeMetadata(header: Record<string, any>): void {
|
|
735
|
+
this.metadata = header.metadata || null;
|
|
736
|
+
const contentHeaders = Array.isArray(header.content) ? header.content : [header.content];
|
|
737
|
+
this.contentMetadata = contentHeaders
|
|
738
|
+
.filter(Boolean)
|
|
739
|
+
.map(contentHeader => contentHeader.metadata || null);
|
|
740
|
+
const firstContentHeader = contentHeaders.find(Boolean) || {};
|
|
741
|
+
const groupReference = firstContentHeader.group;
|
|
742
|
+
const group =
|
|
743
|
+
typeof groupReference === 'number'
|
|
744
|
+
? this.tileset.groups?.[groupReference] || null
|
|
745
|
+
: groupReference || null;
|
|
746
|
+
const metadataContext: Tile3DMetadataContext = {
|
|
747
|
+
tileset: this.tileset.metadata || null,
|
|
748
|
+
group,
|
|
749
|
+
tile: this.metadata,
|
|
750
|
+
content: this.contentMetadata[0] || null,
|
|
751
|
+
subtree: header.implicitMetadata || null
|
|
752
|
+
};
|
|
753
|
+
if (this.tileset.groups) {
|
|
754
|
+
metadataContext.groups = this.tileset.groups;
|
|
755
|
+
}
|
|
756
|
+
this.metadataContext = metadataContext;
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
/** Updates payload and renderability while retaining immutable metadata descriptors. */
|
|
760
|
+
private _updateContentEntriesAfterLoad(loadResult: TileContentLoadResult): void {
|
|
761
|
+
if (loadResult.contentEntries) {
|
|
762
|
+
this.contentEntries = loadResult.contentEntries.map((entry, index) => ({
|
|
763
|
+
...entry,
|
|
764
|
+
index,
|
|
765
|
+
boundingVolume:
|
|
766
|
+
entry.boundingVolume || this._contentBoundingVolumes[index] || this.boundingVolume,
|
|
767
|
+
payload: entry.payload ?? this.contents[index] ?? null,
|
|
768
|
+
featureIds: entry.featureIds?.length
|
|
769
|
+
? entry.featureIds
|
|
770
|
+
: getTile3DFeatureIdSets(entry.payload ?? this.contents[index]),
|
|
771
|
+
renderable: Boolean(
|
|
772
|
+
entry.renderable !== false &&
|
|
773
|
+
(entry.payload ?? this.contents[index]) &&
|
|
774
|
+
!isNestedTilesetPayload(entry.payload ?? this.contents[index])
|
|
775
|
+
)
|
|
776
|
+
}));
|
|
777
|
+
this.featureIdSets = this.contentEntries.flatMap(entry => entry.featureIds);
|
|
778
|
+
return;
|
|
779
|
+
}
|
|
780
|
+
this.contentEntries = this.contentEntries.map((entry, index) => ({
|
|
781
|
+
...entry,
|
|
782
|
+
payload: this.contents[index] ?? null,
|
|
783
|
+
featureIds: getTile3DFeatureIdSets(this.contents[index]),
|
|
784
|
+
renderable: Boolean(this.contents[index]) && !isNestedTilesetPayload(this.contents[index])
|
|
785
|
+
}));
|
|
786
|
+
this.featureIdSets = this.contentEntries.flatMap(entry => entry.featureIds);
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
/** Returns one ordered content descriptor, or null when the index is out of range. */
|
|
790
|
+
getContentEntry(index: number): Tile3DContent | null {
|
|
791
|
+
return this.contentEntries[index] || null;
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
/** Returns whether an ordered content entry currently has renderable payload. */
|
|
795
|
+
isContentRenderable(index: number): boolean {
|
|
796
|
+
return Boolean(this.contentEntries[index]?.renderable);
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
/** Initializes additive renderer-neutral descriptors from a normalized tile header. */
|
|
800
|
+
private _initializeContentEntries(tileHeader: Record<string, any>): void {
|
|
801
|
+
const headers = Array.isArray(tileHeader.content)
|
|
802
|
+
? tileHeader.content
|
|
803
|
+
: tileHeader.content
|
|
804
|
+
? [tileHeader.content]
|
|
805
|
+
: [];
|
|
806
|
+
this.contentEntries = headers.map((contentHeader, index) => {
|
|
807
|
+
const normalizedContentHeader = contentHeader || {};
|
|
808
|
+
return {
|
|
809
|
+
index,
|
|
810
|
+
uri: this.contentUrls[index] || normalizedContentHeader.uri || normalizedContentHeader.url,
|
|
811
|
+
group: normalizedContentHeader.group,
|
|
812
|
+
type: normalizedContentHeader.type,
|
|
813
|
+
payload: null,
|
|
814
|
+
metadata: normalizedContentHeader.metadata || null,
|
|
815
|
+
boundingVolume: this._contentBoundingVolumes[index] || this.boundingVolume,
|
|
816
|
+
featureIds: this._getFeatureIdSets(normalizedContentHeader),
|
|
817
|
+
renderable: false
|
|
818
|
+
};
|
|
819
|
+
});
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
/** Extracts renderer-neutral feature-id declarations without decoding property values. */
|
|
823
|
+
private _getFeatureIdSets(contentHeader: Record<string, any>): Tile3DFeatureIdSet[] {
|
|
824
|
+
return getTile3DFeatureIdSets(contentHeader);
|
|
696
825
|
}
|
|
697
826
|
|
|
698
827
|
// Unloads the tile's content.
|
|
@@ -704,6 +833,13 @@ export class Tile3D {
|
|
|
704
833
|
}
|
|
705
834
|
this.contents = [];
|
|
706
835
|
this.content = null;
|
|
836
|
+
this.vectorContent = null;
|
|
837
|
+
this.contentEntries = this.contentEntries.map(entry => ({
|
|
838
|
+
...entry,
|
|
839
|
+
payload: null,
|
|
840
|
+
renderable: false
|
|
841
|
+
}));
|
|
842
|
+
this.featureIdSets = this.contentEntries.flatMap(entry => entry.featureIds);
|
|
707
843
|
if (this.header.content && this.header.content.destroy) {
|
|
708
844
|
this.header.content.destroy();
|
|
709
845
|
}
|
|
@@ -848,9 +984,15 @@ export class Tile3D {
|
|
|
848
984
|
* @param frameState Current camera, culling, and optional clipping-plane state.
|
|
849
985
|
* @returns The content visibility classification.
|
|
850
986
|
*/
|
|
851
|
-
contentVisibility(frameState: FrameState): CullingResult {
|
|
987
|
+
contentVisibility(frameState: FrameState, contentIndex?: number): CullingResult {
|
|
988
|
+
const contentVolumes =
|
|
989
|
+
contentIndex === undefined
|
|
990
|
+
? this._contentBoundingVolumes
|
|
991
|
+
: this._contentBoundingVolumes[contentIndex]
|
|
992
|
+
? [this._contentBoundingVolumes[contentIndex]]
|
|
993
|
+
: [];
|
|
852
994
|
return getContentVisibility(
|
|
853
|
-
|
|
995
|
+
contentVolumes,
|
|
854
996
|
this.boundingVolume,
|
|
855
997
|
frameState.cullingVolume,
|
|
856
998
|
this._visibilityPlaneMask,
|
|
@@ -913,6 +1055,10 @@ export class Tile3D {
|
|
|
913
1055
|
|
|
914
1056
|
/** Initializes the tile's LOD metric from its header or its nearest available ancestor. */
|
|
915
1057
|
_initializeLodMetric(header: {[key: string]: any}): void {
|
|
1058
|
+
this._scaleGeometricError =
|
|
1059
|
+
typeof header._scaleGeometricError === 'boolean'
|
|
1060
|
+
? header._scaleGeometricError
|
|
1061
|
+
: (this.parent?._scaleGeometricError ?? true);
|
|
916
1062
|
if ('lodMetricType' in header) {
|
|
917
1063
|
this.lodMetricType = header.lodMetricType;
|
|
918
1064
|
} else {
|
|
@@ -969,7 +1115,7 @@ export class Tile3D {
|
|
|
969
1115
|
* I3S `maxScreenThreshold` is already a screen-space metric and must not be transform-scaled.
|
|
970
1116
|
*/
|
|
971
1117
|
_updateLodMetricScale(): void {
|
|
972
|
-
if (this.lodMetricType !== LOD_METRIC_TYPE.GEOMETRIC_ERROR) {
|
|
1118
|
+
if (this.lodMetricType !== LOD_METRIC_TYPE.GEOMETRIC_ERROR || !this._scaleGeometricError) {
|
|
973
1119
|
this.lodMetricValue = this._unscaledLodMetricValue;
|
|
974
1120
|
return;
|
|
975
1121
|
}
|
|
@@ -1074,11 +1220,13 @@ export class Tile3D {
|
|
|
1074
1220
|
}
|
|
1075
1221
|
|
|
1076
1222
|
_updateBoundingVolume(header) {
|
|
1223
|
+
this._boundingBox = undefined;
|
|
1077
1224
|
// Update the bounding volumes
|
|
1078
1225
|
this.boundingVolume = createBoundingVolume(
|
|
1079
1226
|
header.boundingVolume,
|
|
1080
1227
|
this.computedTransform,
|
|
1081
|
-
this.boundingVolume
|
|
1228
|
+
this.boundingVolume,
|
|
1229
|
+
this._initialTransform
|
|
1082
1230
|
);
|
|
1083
1231
|
|
|
1084
1232
|
// Viewer request volumes constrain traversal, not just render content. A contentless implicit
|
|
@@ -1088,7 +1236,8 @@ export class Tile3D {
|
|
|
1088
1236
|
this._viewerRequestVolume = createBoundingVolume(
|
|
1089
1237
|
header.viewerRequestVolume,
|
|
1090
1238
|
this.computedTransform,
|
|
1091
|
-
this._viewerRequestVolume
|
|
1239
|
+
this._viewerRequestVolume,
|
|
1240
|
+
this._initialTransform
|
|
1092
1241
|
);
|
|
1093
1242
|
}
|
|
1094
1243
|
|
|
@@ -1106,7 +1255,12 @@ export class Tile3D {
|
|
|
1106
1255
|
this._contentBoundingVolumes = contentHeaders.length
|
|
1107
1256
|
? contentHeaders.map(headerEntry =>
|
|
1108
1257
|
headerEntry?.boundingVolume
|
|
1109
|
-
? createBoundingVolume(
|
|
1258
|
+
? createBoundingVolume(
|
|
1259
|
+
headerEntry.boundingVolume,
|
|
1260
|
+
this.computedTransform,
|
|
1261
|
+
undefined,
|
|
1262
|
+
this._initialTransform
|
|
1263
|
+
)
|
|
1110
1264
|
: this.boundingVolume
|
|
1111
1265
|
)
|
|
1112
1266
|
: [this.boundingVolume];
|
|
@@ -517,6 +517,26 @@ export class Tileset3D {
|
|
|
517
517
|
return this._frameNumber;
|
|
518
518
|
}
|
|
519
519
|
|
|
520
|
+
/**
|
|
521
|
+
* Returns the tiles requested during the most recent completed traversal.
|
|
522
|
+
*
|
|
523
|
+
* A defensive copy makes request ordering deterministic for diagnostics and prevents callers
|
|
524
|
+
* from mutating the scheduler's working set.
|
|
525
|
+
*/
|
|
526
|
+
get requestedTiles(): readonly Tile3D[] {
|
|
527
|
+
return this._requestedTiles.slice();
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
/**
|
|
531
|
+
* Returns empty tiles considered during the most recent completed traversal.
|
|
532
|
+
*
|
|
533
|
+
* Empty tiles can still be useful hierarchy placeholders, but their IDs are included in
|
|
534
|
+
* observability snapshots so applications can distinguish traversal work from render content.
|
|
535
|
+
*/
|
|
536
|
+
get emptyTiles(): readonly Tile3D[] {
|
|
537
|
+
return this._emptyTiles.slice();
|
|
538
|
+
}
|
|
539
|
+
|
|
520
540
|
/**
|
|
521
541
|
* Gets or sets the soft target, in bytes, for cached tile content not needed this frame.
|
|
522
542
|
*
|
|
@@ -662,6 +682,14 @@ export class Tileset3D {
|
|
|
662
682
|
return;
|
|
663
683
|
}
|
|
664
684
|
const preparedViewports = viewports instanceof Array ? viewports : [viewports];
|
|
685
|
+
const activeViewportIds = new Set(preparedViewports.map(viewport => viewport.id));
|
|
686
|
+
// A viewport may be removed between frames. Discard its completed state before aggregating
|
|
687
|
+
// requested and selected tiles so observability reflects only the current traversal inputs.
|
|
688
|
+
for (const frameStateId of Object.keys(this.frameStateData)) {
|
|
689
|
+
if (!activeViewportIds.has(frameStateId)) {
|
|
690
|
+
delete this.frameStateData[frameStateId];
|
|
691
|
+
}
|
|
692
|
+
}
|
|
665
693
|
|
|
666
694
|
this._cache.reset();
|
|
667
695
|
this._frameNumber++;
|
|
@@ -909,7 +937,7 @@ export class Tileset3D {
|
|
|
909
937
|
if (
|
|
910
938
|
initialMetadata?.tileset?.root &&
|
|
911
939
|
typeof initialMetadata.tileset.root.then !== 'function' &&
|
|
912
|
-
!this.source.prepareTileset
|
|
940
|
+
(!this.source.prepareTileset || this.spatialReference?.status === 'native')
|
|
913
941
|
) {
|
|
914
942
|
this.root = this._initializeTileHeaders(initialMetadata.tileset, null);
|
|
915
943
|
this._applyViewState();
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
// Copyright (c) vis.gl contributors
|
|
3
|
+
|
|
4
|
+
import type {Tileset3D} from './tileset-3d';
|
|
5
|
+
import type {Tile3D} from './tile-3d';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* A deterministic, renderer-neutral snapshot of one {@link Tileset3D} traversal.
|
|
9
|
+
*
|
|
10
|
+
* Tile IDs are sorted so snapshots can be compared across runs even when child requests complete
|
|
11
|
+
* in a different order. Counts are reported for the completed traversal frame and cache values are
|
|
12
|
+
* expressed in bytes. This is an inspection contract; it does not alter traversal or request policy.
|
|
13
|
+
*/
|
|
14
|
+
export type Tileset3DTraversalSnapshot = {
|
|
15
|
+
/** Monotonically increasing traversal frame number. */
|
|
16
|
+
frameNumber: number;
|
|
17
|
+
/** IDs selected for rendering, sorted for stable comparisons. */
|
|
18
|
+
selectedTileIds: string[];
|
|
19
|
+
/** IDs whose content was requested, sorted for stable comparisons. */
|
|
20
|
+
requestedTileIds: string[];
|
|
21
|
+
/** IDs visited as hierarchy-only or empty tiles, sorted for stable comparisons. */
|
|
22
|
+
emptyTileIds: string[];
|
|
23
|
+
/** Number of selected tiles in the completed frame. */
|
|
24
|
+
visibleTileCount: number;
|
|
25
|
+
/** Number of selected tiles with renderable content. */
|
|
26
|
+
renderableTileCount: number;
|
|
27
|
+
/** Number of tile or subtree loads currently in flight. */
|
|
28
|
+
loadingTileCount: number;
|
|
29
|
+
/** Cumulative number of tiles loaded into the runtime cache. */
|
|
30
|
+
loadedTileCount: number;
|
|
31
|
+
/** Cumulative number of failed tile loads. */
|
|
32
|
+
failedTileCount: number;
|
|
33
|
+
/** Number of tiles currently retained in the runtime cache. */
|
|
34
|
+
cachedTileCount: number;
|
|
35
|
+
/** Estimated cached content bytes. */
|
|
36
|
+
cacheBytes: number;
|
|
37
|
+
/** Active memory-adjusted maximum SSE in logical/CSS pixels. */
|
|
38
|
+
maximumScreenSpaceError: number;
|
|
39
|
+
/** Source-specific implicit subtree counters, when the source exposes them. */
|
|
40
|
+
implicitTiling?: {
|
|
41
|
+
/** Number of subtree resources requested from the source. */
|
|
42
|
+
requestedSubtrees: number;
|
|
43
|
+
/** Number of subtree resources successfully materialized. */
|
|
44
|
+
loadedSubtrees: number;
|
|
45
|
+
/** Number of requests served by the parsed-subtree cache. */
|
|
46
|
+
cacheHits: number;
|
|
47
|
+
/** Number of parsed subtrees currently retained for reuse. */
|
|
48
|
+
cachedSubtrees: number;
|
|
49
|
+
/** Number of subtree requests currently in flight. */
|
|
50
|
+
pendingSubtrees: number;
|
|
51
|
+
/** Number of runtime tile headers created from materialized subtrees. */
|
|
52
|
+
materializedTiles: number;
|
|
53
|
+
};
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Creates a stable observability snapshot from public {@link Tileset3D} state.
|
|
58
|
+
*
|
|
59
|
+
* The helper intentionally reads only public runtime fields and source diagnostics. It is safe to
|
|
60
|
+
* call from an instrumentation loop and does not retain tile or content references.
|
|
61
|
+
*
|
|
62
|
+
* @param tileset - Runtime whose most recent traversal should be inspected.
|
|
63
|
+
* @returns A serializable snapshot suitable for logs, regression fixtures, and benchmark output.
|
|
64
|
+
*/
|
|
65
|
+
export function getTileset3DTraversalSnapshot(tileset: Tileset3D): Tileset3DTraversalSnapshot {
|
|
66
|
+
const selectedTiles = tileset.selectedTiles.slice();
|
|
67
|
+
const requestedTiles = tileset.requestedTiles.slice();
|
|
68
|
+
const emptyTiles = tileset.emptyTiles.slice();
|
|
69
|
+
|
|
70
|
+
const getTileIds = (tiles: readonly Tile3D[]): string[] =>
|
|
71
|
+
tiles.map(tile => String(tile.id)).sort();
|
|
72
|
+
|
|
73
|
+
const getStatCount = (name: string): number => {
|
|
74
|
+
const count = tileset.stats.get(name).count;
|
|
75
|
+
return typeof count === 'number' && Number.isFinite(count) ? count : 0;
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
const implicitTilingSource = tileset.source as Tileset3D['source'] & {
|
|
79
|
+
getImplicitTilingStats?: () => Tileset3DTraversalSnapshot['implicitTiling'];
|
|
80
|
+
};
|
|
81
|
+
const implicitTiling = implicitTilingSource.getImplicitTilingStats?.();
|
|
82
|
+
|
|
83
|
+
return {
|
|
84
|
+
frameNumber: tileset.frameNumber,
|
|
85
|
+
selectedTileIds: getTileIds(selectedTiles),
|
|
86
|
+
requestedTileIds: getTileIds(requestedTiles),
|
|
87
|
+
emptyTileIds: getTileIds(emptyTiles),
|
|
88
|
+
visibleTileCount: selectedTiles.length,
|
|
89
|
+
renderableTileCount: selectedTiles.filter(
|
|
90
|
+
tile => tile.contentAvailable && Boolean(tile.content)
|
|
91
|
+
).length,
|
|
92
|
+
loadingTileCount: getStatCount('Tiles Loading'),
|
|
93
|
+
loadedTileCount: getStatCount('Tiles Loaded'),
|
|
94
|
+
failedTileCount: getStatCount('Failed Tile Loads'),
|
|
95
|
+
cachedTileCount: getStatCount('Tiles In Memory'),
|
|
96
|
+
cacheBytes: tileset.gpuMemoryUsageInBytes,
|
|
97
|
+
maximumScreenSpaceError: tileset.memoryAdjustedScreenSpaceError,
|
|
98
|
+
...(implicitTiling ? {implicitTiling} : {})
|
|
99
|
+
};
|
|
100
|
+
}
|
|
@@ -10,6 +10,7 @@ import type {Tileset3D} from './tileset-3d';
|
|
|
10
10
|
import type {FrameState} from '../helpers/frame-state';
|
|
11
11
|
import type {TILESET_TYPE} from '../../constants';
|
|
12
12
|
import type {TilesetSpatialReference} from '../../spatial/spatial-types';
|
|
13
|
+
import type {Tile3DContent} from './tile-3d-contracts';
|
|
13
14
|
|
|
14
15
|
/**
|
|
15
16
|
* Parsed top-level tileset payload consumed by {@link Tileset3D} or a {@link Tileset3DSource}.
|
|
@@ -68,6 +69,8 @@ export type TileContentLoadResult = {
|
|
|
68
69
|
loaded: boolean;
|
|
69
70
|
/** All payloads loaded for a tile, in source order. */
|
|
70
71
|
contents?: any[];
|
|
72
|
+
/** Optional normalized renderer-neutral descriptors for each content entry. */
|
|
73
|
+
contentEntries?: Tile3DContent[];
|
|
71
74
|
/** Nested tileset metadata to install below the loaded tile, if any. */
|
|
72
75
|
nestedTileset?: TilesetJSON;
|
|
73
76
|
/** Nested tilesets found among multiple content payloads. */
|
|
@@ -219,9 +219,10 @@ export class TilesetTraverser {
|
|
|
219
219
|
}
|
|
220
220
|
stack.push(child);
|
|
221
221
|
hasVisibleChild = true;
|
|
222
|
-
} else if (checkRefines || loadSiblings) {
|
|
223
|
-
// Keep non-visible children loaded
|
|
224
|
-
//
|
|
222
|
+
} else if ((checkRefines || loadSiblings) && child._inRequestVolume) {
|
|
223
|
+
// Keep non-visible children loaded when they are inside their viewer request volume;
|
|
224
|
+
// they may still be needed before a replacement parent can refine. The request-volume
|
|
225
|
+
// check intentionally applies to sibling loading as well, preventing off-volume work.
|
|
225
226
|
this.loadTile(child, frameState);
|
|
226
227
|
this.touchTile(child, frameState);
|
|
227
228
|
}
|