@loaders.gl/tiles 5.0.0-alpha.2 → 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.
- package/LICENSE +1 -1
- package/dist/dist.dev.js +17325 -5650
- package/dist/dist.min.js +4 -1
- package/dist/index.cjs +1924 -178
- package/dist/index.cjs.map +3 -3
- package/dist/index.d.ts +9 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -1
- package/dist/point-cloud/point-cloud-tileset.d.ts +4 -0
- package/dist/point-cloud/point-cloud-tileset.d.ts.map +1 -1
- package/dist/point-cloud/point-cloud-tileset.js +51 -12
- package/dist/point-cloud/point-cloud-tileset.js.map +1 -1
- package/dist/point-cloud/types.d.ts +17 -0
- package/dist/point-cloud/types.d.ts.map +1 -1
- package/dist/raster-set.d.ts +10 -8
- package/dist/raster-set.d.ts.map +1 -1
- package/dist/raster-set.js +18 -1
- package/dist/raster-set.js.map +1 -1
- package/dist/spatial/format-spatial-reference.d.ts +67 -0
- package/dist/spatial/format-spatial-reference.d.ts.map +1 -0
- package/dist/spatial/format-spatial-reference.js +233 -0
- package/dist/spatial/format-spatial-reference.js.map +1 -0
- package/dist/spatial/get-spatial-coordinate-frame.d.ts +9 -0
- package/dist/spatial/get-spatial-coordinate-frame.d.ts.map +1 -0
- package/dist/spatial/get-spatial-coordinate-frame.js +58 -0
- package/dist/spatial/get-spatial-coordinate-frame.js.map +1 -0
- package/dist/spatial/i3s-elevation.d.ts +20 -0
- package/dist/spatial/i3s-elevation.d.ts.map +1 -0
- package/dist/spatial/i3s-elevation.js +90 -0
- package/dist/spatial/i3s-elevation.js.map +1 -0
- package/dist/spatial/i3s-spatial-transformer.d.ts +189 -0
- package/dist/spatial/i3s-spatial-transformer.d.ts.map +1 -0
- package/dist/spatial/i3s-spatial-transformer.js +686 -0
- package/dist/spatial/i3s-spatial-transformer.js.map +1 -0
- package/dist/spatial/spatial-coordinate-transformer.d.ts +48 -0
- package/dist/spatial/spatial-coordinate-transformer.d.ts.map +1 -0
- package/dist/spatial/spatial-coordinate-transformer.js +211 -0
- package/dist/spatial/spatial-coordinate-transformer.js.map +1 -0
- package/dist/spatial/spatial-resource-registry.d.ts +44 -0
- package/dist/spatial/spatial-resource-registry.d.ts.map +1 -0
- package/dist/spatial/spatial-resource-registry.js +67 -0
- package/dist/spatial/spatial-resource-registry.js.map +1 -0
- package/dist/spatial/spatial-types.d.ts +190 -0
- package/dist/spatial/spatial-types.d.ts.map +1 -0
- package/dist/spatial/spatial-types.js +192 -0
- package/dist/spatial/spatial-types.js.map +1 -0
- package/dist/tileset-3d/common/tile-3d.d.ts +49 -1
- package/dist/tileset-3d/common/tile-3d.d.ts.map +1 -1
- package/dist/tileset-3d/common/tile-3d.js +132 -64
- package/dist/tileset-3d/common/tile-3d.js.map +1 -1
- package/dist/tileset-3d/common/tileset-3d.d.ts +25 -0
- package/dist/tileset-3d/common/tileset-3d.d.ts.map +1 -1
- package/dist/tileset-3d/common/tileset-3d.js +49 -7
- package/dist/tileset-3d/common/tileset-3d.js.map +1 -1
- package/dist/tileset-3d/common/tileset-source.d.ts +14 -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 +6 -1
- package/dist/tileset-3d/common/tileset-traverser.d.ts.map +1 -1
- package/dist/tileset-3d/common/tileset-traverser.js +11 -6
- package/dist/tileset-3d/common/tileset-traverser.js.map +1 -1
- package/dist/tileset-3d/format-3d-tiles/implicit-tiling.d.ts +26 -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 +62 -18
- 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 +1 -7
- 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 +32 -55
- package/dist/tileset-3d/format-3d-tiles/tiles-3d-source.js.map +1 -1
- package/dist/tileset-3d/format-i3s/i3s-source.d.ts +14 -1
- package/dist/tileset-3d/format-i3s/i3s-source.d.ts.map +1 -1
- package/dist/tileset-3d/format-i3s/i3s-source.js +107 -17
- package/dist/tileset-3d/format-i3s/i3s-source.js.map +1 -1
- package/dist/tileset-3d/helpers/bounding-volume.d.ts.map +1 -1
- package/dist/tileset-3d/helpers/bounding-volume.js +3 -33
- package/dist/tileset-3d/helpers/bounding-volume.js.map +1 -1
- package/dist/tileset-3d/helpers/frame-state.d.ts +3 -1
- package/dist/tileset-3d/helpers/frame-state.d.ts.map +1 -1
- package/dist/tileset-3d/helpers/frame-state.js +1 -0
- package/dist/tileset-3d/helpers/frame-state.js.map +1 -1
- package/dist/tileset-3d/helpers/i3s-lod.d.ts.map +1 -1
- package/dist/tileset-3d/helpers/i3s-lod.js +20 -8
- package/dist/tileset-3d/helpers/i3s-lod.js.map +1 -1
- package/dist/types.d.ts +3 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +12 -9
- package/src/index.ts +42 -1
- package/src/point-cloud/point-cloud-tileset.ts +66 -12
- package/src/point-cloud/types.ts +17 -0
- package/src/raster-set.ts +46 -11
- package/src/spatial/format-spatial-reference.ts +311 -0
- package/src/spatial/get-spatial-coordinate-frame.ts +74 -0
- package/src/spatial/i3s-elevation.ts +96 -0
- package/src/spatial/i3s-spatial-transformer.ts +957 -0
- package/src/spatial/spatial-coordinate-transformer.ts +276 -0
- package/src/spatial/spatial-resource-registry.ts +79 -0
- package/src/spatial/spatial-types.ts +422 -0
- package/src/tileset-3d/common/tile-3d.ts +156 -69
- package/src/tileset-3d/common/tileset-3d.ts +77 -6
- package/src/tileset-3d/common/tileset-source.ts +15 -0
- package/src/tileset-3d/common/tileset-traverser.ts +11 -6
- package/src/tileset-3d/format-3d-tiles/implicit-tiling.ts +100 -22
- package/src/tileset-3d/format-3d-tiles/tiles-3d-source.ts +36 -56
- package/src/tileset-3d/format-i3s/i3s-source.ts +119 -18
- package/src/tileset-3d/helpers/bounding-volume.ts +3 -55
- package/src/tileset-3d/helpers/frame-state.ts +3 -0
- package/src/tileset-3d/helpers/i3s-lod.ts +22 -8
- package/src/types.ts +3 -0
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
import {Vector3, Matrix4} from '@math.gl/core';
|
|
9
9
|
import {CullingVolume} from '@math.gl/culling';
|
|
10
|
+
import type {CullingResult} from '@math.gl/culling';
|
|
10
11
|
|
|
11
12
|
// Note: circular dependency
|
|
12
13
|
import type {Tileset3D} from './tileset-3d';
|
|
@@ -33,6 +34,57 @@ import {
|
|
|
33
34
|
|
|
34
35
|
const scratchVector = new Vector3();
|
|
35
36
|
|
|
37
|
+
/**
|
|
38
|
+
* Classifies render-content visibility across all content volumes owned by a tile.
|
|
39
|
+
*
|
|
40
|
+
* Content volumes form a union: one visible content entry keeps the tile renderable even when
|
|
41
|
+
* another entry is outside the frustum or clipping planes. The traversal volume is intentionally
|
|
42
|
+
* not used here except as a fallback when a tile has no content-specific volume. This separation
|
|
43
|
+
* keeps clipping render-only and prevents a tight content volume from pruning descendants.
|
|
44
|
+
*
|
|
45
|
+
* @param contentVolumes - Transformed content volumes in source order.
|
|
46
|
+
* @param fallbackVolume - Tile volume used when content does not declare its own volume.
|
|
47
|
+
* @param cullingVolume - Current view frustum.
|
|
48
|
+
* @param visibilityPlaneMask - Parent visibility mask optimization.
|
|
49
|
+
* @param clippingPlanes - Optional world-space planes applied to render content only.
|
|
50
|
+
* @returns Union visibility classification for the renderable content.
|
|
51
|
+
*/
|
|
52
|
+
export function getContentVisibility(
|
|
53
|
+
contentVolumes: any[],
|
|
54
|
+
fallbackVolume: any,
|
|
55
|
+
cullingVolume: CullingVolume,
|
|
56
|
+
visibilityPlaneMask: number | undefined,
|
|
57
|
+
clippingPlanes: any[] = []
|
|
58
|
+
): CullingResult {
|
|
59
|
+
const volumes = contentVolumes.length ? contentVolumes : [fallbackVolume];
|
|
60
|
+
let visibleVolume = false;
|
|
61
|
+
let intersectingVolume = false;
|
|
62
|
+
for (const contentVolume of volumes) {
|
|
63
|
+
let visibility: CullingResult = 'inside';
|
|
64
|
+
if (visibilityPlaneMask !== CullingVolume.MASK_INSIDE) {
|
|
65
|
+
visibility = cullingVolume.computeVisibility(contentVolume);
|
|
66
|
+
if (visibility === 'outside') {
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
let clipped = false;
|
|
71
|
+
for (const clippingPlane of clippingPlanes) {
|
|
72
|
+
if (contentVolume.intersectPlane(clippingPlane) === 'outside') {
|
|
73
|
+
clipped = true;
|
|
74
|
+
break;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
if (!clipped) {
|
|
78
|
+
visibleVolume = true;
|
|
79
|
+
intersectingVolume = intersectingVolume || visibility === 'intersecting';
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
if (!visibleVolume) {
|
|
83
|
+
return 'outside';
|
|
84
|
+
}
|
|
85
|
+
return intersectingVolume ? 'intersecting' : 'inside';
|
|
86
|
+
}
|
|
87
|
+
|
|
36
88
|
function defined(x) {
|
|
37
89
|
return x !== undefined && x !== null;
|
|
38
90
|
}
|
|
@@ -69,6 +121,8 @@ export class Tile3D {
|
|
|
69
121
|
refine: TILE_REFINEMENT;
|
|
70
122
|
type: string;
|
|
71
123
|
contentUrl: string;
|
|
124
|
+
/** Resolved render-content URLs in source order; empty for contentless tiles. */
|
|
125
|
+
contentUrls: string[] = [];
|
|
72
126
|
/** Different refinement algorithms used by I3S and 3D tiles */
|
|
73
127
|
lodMetricType = 'geometricError';
|
|
74
128
|
/**
|
|
@@ -91,6 +145,17 @@ export class Tile3D {
|
|
|
91
145
|
* not the content's metadata in the tileset JSON file.
|
|
92
146
|
*/
|
|
93
147
|
content: any = null;
|
|
148
|
+
/** Loaded payloads in the same order as {@link contentUrls}; `content` remains the primary payload. */
|
|
149
|
+
contents: any[] = [];
|
|
150
|
+
/**
|
|
151
|
+
* Metadata entity attached to this tile in the source tileset, if present.
|
|
152
|
+
*
|
|
153
|
+
* This is the raw 3D Tiles metadata reference. The generic tiles runtime preserves it without
|
|
154
|
+
* decoding schema classes or binary property tables.
|
|
155
|
+
*/
|
|
156
|
+
metadata: Record<string, any> | null = null;
|
|
157
|
+
/** Raw metadata entities attached to tile contents, in content source order. */
|
|
158
|
+
contentMetadata: Array<Record<string, any> | null> = [];
|
|
94
159
|
contentState: number = TILE_CONTENT_STATE.UNLOADED;
|
|
95
160
|
gpuMemoryUsageInBytes: number = 0;
|
|
96
161
|
|
|
@@ -143,8 +208,26 @@ export class Tile3D {
|
|
|
143
208
|
private _visible: boolean | undefined = undefined;
|
|
144
209
|
|
|
145
210
|
private _contentBoundingVolume: any;
|
|
211
|
+
private _contentBoundingVolumes: any[] = [];
|
|
212
|
+
/** Source content-volume headers retained after render content is unloaded. */
|
|
213
|
+
private _contentBoundingVolumeHeaders: any[] = [];
|
|
146
214
|
private _viewerRequestVolume: any;
|
|
147
215
|
|
|
216
|
+
/** Bounding volume used to cull the tile's first renderable content entry. */
|
|
217
|
+
get contentBoundingVolume(): any {
|
|
218
|
+
return this._contentBoundingVolume;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/** All transformed render-content bounding volumes, including tile-volume fallbacks. */
|
|
222
|
+
get contentBoundingVolumes(): any[] {
|
|
223
|
+
return this._contentBoundingVolumes;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/** Bounding volume that limits when this tile may be requested, when declared. */
|
|
227
|
+
get viewerRequestVolume(): any {
|
|
228
|
+
return this._viewerRequestVolume;
|
|
229
|
+
}
|
|
230
|
+
|
|
148
231
|
_initialTransform: Matrix4 = new Matrix4();
|
|
149
232
|
|
|
150
233
|
// Used by traverser, cannot be marked private
|
|
@@ -199,6 +282,12 @@ export class Tile3D {
|
|
|
199
282
|
this.refine = this._getRefine(header.refine);
|
|
200
283
|
this.type = header.type;
|
|
201
284
|
this.contentUrl = header.contentUrl;
|
|
285
|
+
this.contentUrls = header.contentUrls || (header.contentUrl ? [header.contentUrl] : []);
|
|
286
|
+
this.metadata = header.metadata || null;
|
|
287
|
+
const contentHeaders = Array.isArray(header.content) ? header.content : [header.content];
|
|
288
|
+
this.contentMetadata = contentHeaders
|
|
289
|
+
.filter(Boolean)
|
|
290
|
+
.map(contentHeader => contentHeader.metadata || null);
|
|
202
291
|
this.childrenState = header.implicitSubtree ? 'unloaded' : 'ready';
|
|
203
292
|
|
|
204
293
|
this._initializeLodMetric(header);
|
|
@@ -368,7 +457,11 @@ export class Tile3D {
|
|
|
368
457
|
* Memory usage of tile on GPU
|
|
369
458
|
*/
|
|
370
459
|
_getGpuMemoryUsageInBytes(): number {
|
|
371
|
-
|
|
460
|
+
const contents = this.contents.length ? this.contents : [this.content];
|
|
461
|
+
return contents.reduce(
|
|
462
|
+
(total, content) => total + (content?.gpuMemoryUsageInBytes || content?.byteLength || 0),
|
|
463
|
+
0
|
|
464
|
+
);
|
|
372
465
|
}
|
|
373
466
|
|
|
374
467
|
/**
|
|
@@ -487,6 +580,11 @@ export class Tile3D {
|
|
|
487
580
|
try {
|
|
488
581
|
const loadResult = await this.tileset.source.loadTileContent(this);
|
|
489
582
|
|
|
583
|
+
if (loadResult.contents) {
|
|
584
|
+
this.contents = loadResult.contents;
|
|
585
|
+
this.content = this.contents[0] || null;
|
|
586
|
+
}
|
|
587
|
+
|
|
490
588
|
if (this.tileset.options.contentLoader) {
|
|
491
589
|
await this.tileset.options.contentLoader(this);
|
|
492
590
|
}
|
|
@@ -585,6 +683,9 @@ export class Tile3D {
|
|
|
585
683
|
this.refine = this._getRefine(materializedHeader.refine);
|
|
586
684
|
this.type = materializedHeader.type;
|
|
587
685
|
this.contentUrl = materializedHeader.contentUrl;
|
|
686
|
+
this.contentUrls =
|
|
687
|
+
materializedHeader.contentUrls ||
|
|
688
|
+
(materializedHeader.contentUrl ? [materializedHeader.contentUrl] : []);
|
|
588
689
|
this._initializeLodMetric(materializedHeader);
|
|
589
690
|
this._updateLodMetricScale();
|
|
590
691
|
this._initializeBoundingVolumes(this.header);
|
|
@@ -595,9 +696,12 @@ export class Tile3D {
|
|
|
595
696
|
|
|
596
697
|
// Unloads the tile's content.
|
|
597
698
|
unloadContent() {
|
|
598
|
-
|
|
599
|
-
|
|
699
|
+
for (const content of this.contents.length ? this.contents : [this.content]) {
|
|
700
|
+
if (content && content.destroy) {
|
|
701
|
+
content.destroy();
|
|
702
|
+
}
|
|
600
703
|
}
|
|
704
|
+
this.contents = [];
|
|
601
705
|
this.content = null;
|
|
602
706
|
if (this.header.content && this.header.content.destroy) {
|
|
603
707
|
this.header.content.destroy();
|
|
@@ -732,47 +836,22 @@ export class Tile3D {
|
|
|
732
836
|
return cullingVolume.computeVisibilityWithPlaneMask(boundingVolume, parentVisibilityPlaneMask);
|
|
733
837
|
}
|
|
734
838
|
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
// The tile's bounding volume is completely inside the culling volume so
|
|
752
|
-
// the content bounding volume must also be inside.
|
|
753
|
-
return Intersect.INSIDE;
|
|
754
|
-
}
|
|
755
|
-
|
|
756
|
-
// PERFORMANCE_IDEA: is it possible to burn less CPU on this test since we know the
|
|
757
|
-
// tile's (not the content's) bounding volume intersects the culling volume?
|
|
758
|
-
const cullingVolume = frameState.cullingVolume;
|
|
759
|
-
const boundingVolume = tile.contentBoundingVolume;
|
|
760
|
-
|
|
761
|
-
const tileset = this.tileset;
|
|
762
|
-
const clippingPlanes = tileset.clippingPlanes;
|
|
763
|
-
if (defined(clippingPlanes) && clippingPlanes.enabled) {
|
|
764
|
-
const intersection = clippingPlanes.computeIntersectionWithBoundingVolume(
|
|
765
|
-
boundingVolume,
|
|
766
|
-
tileset.clippingPlanesOriginMatrix
|
|
767
|
-
);
|
|
768
|
-
this._isClipped = intersection !== Intersect.INSIDE;
|
|
769
|
-
if (intersection === Intersect.OUTSIDE) {
|
|
770
|
-
return Intersect.OUTSIDE;
|
|
771
|
-
}
|
|
772
|
-
}
|
|
773
|
-
|
|
774
|
-
return cullingVolume.computeVisibility(boundingVolume);
|
|
775
|
-
*/
|
|
839
|
+
/**
|
|
840
|
+
* Determines whether renderable content intersects the view and configured clipping planes.
|
|
841
|
+
*
|
|
842
|
+
* Clipping planes are render-only constraints; traversal continues to use the tile volume.
|
|
843
|
+
*
|
|
844
|
+
* @param frameState Current camera, culling, and optional clipping-plane state.
|
|
845
|
+
* @returns The content visibility classification.
|
|
846
|
+
*/
|
|
847
|
+
contentVisibility(frameState: FrameState): CullingResult {
|
|
848
|
+
return getContentVisibility(
|
|
849
|
+
this._contentBoundingVolumes,
|
|
850
|
+
this.boundingVolume,
|
|
851
|
+
frameState.cullingVolume,
|
|
852
|
+
this._visibilityPlaneMask,
|
|
853
|
+
frameState.clippingPlanes
|
|
854
|
+
);
|
|
776
855
|
}
|
|
777
856
|
|
|
778
857
|
/**
|
|
@@ -898,6 +977,7 @@ export class Tile3D {
|
|
|
898
977
|
|
|
899
978
|
_initializeBoundingVolumes(tileHeader) {
|
|
900
979
|
this._contentBoundingVolume = null;
|
|
980
|
+
this._contentBoundingVolumeHeaders = [];
|
|
901
981
|
this._viewerRequestVolume = null;
|
|
902
982
|
|
|
903
983
|
this._updateBoundingVolume(tileHeader);
|
|
@@ -906,6 +986,7 @@ export class Tile3D {
|
|
|
906
986
|
_initializeContent(tileHeader) {
|
|
907
987
|
// Empty tile by default
|
|
908
988
|
this.content = {_tileset: this.tileset, _tile: this};
|
|
989
|
+
this.contents = [];
|
|
909
990
|
this.hasEmptyContent = true;
|
|
910
991
|
this.contentState = TILE_CONTENT_STATE.UNLOADED;
|
|
911
992
|
|
|
@@ -913,7 +994,7 @@ export class Tile3D {
|
|
|
913
994
|
// This is `false` until the tile's content is loaded.
|
|
914
995
|
this.hasTilesetContent = false;
|
|
915
996
|
|
|
916
|
-
if (tileHeader.contentUrl) {
|
|
997
|
+
if (tileHeader.contentUrl || tileHeader.contentUrls?.length) {
|
|
917
998
|
this.content = null;
|
|
918
999
|
this.hasEmptyContent = false;
|
|
919
1000
|
}
|
|
@@ -961,18 +1042,23 @@ export class Tile3D {
|
|
|
961
1042
|
* `content`.
|
|
962
1043
|
*/
|
|
963
1044
|
_isTileset(): boolean {
|
|
964
|
-
return this.
|
|
1045
|
+
return (this.contents.length ? this.contents : [this.content]).some(
|
|
1046
|
+
content => content?.shape === 'tileset3d'
|
|
1047
|
+
);
|
|
965
1048
|
}
|
|
966
1049
|
|
|
967
1050
|
_onContentLoaded() {
|
|
968
1051
|
// Vector and Geometry tile rendering do not support the skip LOD optimization.
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
1052
|
+
const contents = this.contents.length ? this.contents : [this.content];
|
|
1053
|
+
for (const content of contents) {
|
|
1054
|
+
switch (content && content.type) {
|
|
1055
|
+
case 'vctr':
|
|
1056
|
+
case 'geom':
|
|
1057
|
+
// @ts-ignore
|
|
1058
|
+
this.tileset._traverser.disableSkipLevelOfDetail = true;
|
|
1059
|
+
break;
|
|
1060
|
+
default:
|
|
1061
|
+
}
|
|
976
1062
|
}
|
|
977
1063
|
|
|
978
1064
|
// The content may be tileset json
|
|
@@ -1003,23 +1089,24 @@ export class Tile3D {
|
|
|
1003
1089
|
}
|
|
1004
1090
|
|
|
1005
1091
|
const content = header.content;
|
|
1006
|
-
if (
|
|
1007
|
-
|
|
1008
|
-
}
|
|
1009
|
-
|
|
1010
|
-
// TODO Cesium specific
|
|
1011
|
-
// Non-leaf tiles may have a content bounding-volume, which is a tight-fit bounding volume
|
|
1012
|
-
// around only the features in the tile. This box is useful for culling for rendering,
|
|
1013
|
-
// but not for culling for traversing the tree since it does not guarantee spatial coherence, i.e.,
|
|
1014
|
-
// since it only bounds features in the tile, not the entire tile, children may be
|
|
1015
|
-
// outside of this box.
|
|
1016
|
-
if (content.boundingVolume) {
|
|
1017
|
-
this._contentBoundingVolume = createBoundingVolume(
|
|
1018
|
-
content.boundingVolume,
|
|
1019
|
-
this.computedTransform,
|
|
1020
|
-
this._contentBoundingVolume
|
|
1021
|
-
);
|
|
1092
|
+
if (content) {
|
|
1093
|
+
this._contentBoundingVolumeHeaders = Array.isArray(content) ? content : [content];
|
|
1022
1094
|
}
|
|
1095
|
+
const contentHeaders = content
|
|
1096
|
+
? Array.isArray(content)
|
|
1097
|
+
? content
|
|
1098
|
+
: [content]
|
|
1099
|
+
: this._contentBoundingVolumeHeaders;
|
|
1100
|
+
// Preserve source order: an unbounded content entry uses the tile volume at its
|
|
1101
|
+
// corresponding index rather than being appended after bounded sibling entries.
|
|
1102
|
+
this._contentBoundingVolumes = contentHeaders.length
|
|
1103
|
+
? contentHeaders.map(headerEntry =>
|
|
1104
|
+
headerEntry?.boundingVolume
|
|
1105
|
+
? createBoundingVolume(headerEntry.boundingVolume, this.computedTransform)
|
|
1106
|
+
: this.boundingVolume
|
|
1107
|
+
)
|
|
1108
|
+
: [this.boundingVolume];
|
|
1109
|
+
this._contentBoundingVolume = this._contentBoundingVolumes[0] || null;
|
|
1023
1110
|
}
|
|
1024
1111
|
|
|
1025
1112
|
/**
|
|
@@ -22,6 +22,11 @@ import type {FoveatedInterpolationCallback} from '../helpers/tiles-3d-request-pr
|
|
|
22
22
|
import type {GeospatialViewport, Viewport} from '../../types';
|
|
23
23
|
import {Tile3D} from './tile-3d';
|
|
24
24
|
import {TILESET_TYPE} from '../../constants';
|
|
25
|
+
import type {TilesetSpatialOptions, TilesetSpatialReference} from '../../spatial/spatial-types';
|
|
26
|
+
import {
|
|
27
|
+
applyTilesetSpatialOptions,
|
|
28
|
+
createTilesetSpatialReference
|
|
29
|
+
} from '../../spatial/spatial-types';
|
|
25
30
|
|
|
26
31
|
import {TilesetTraverser} from './tileset-traverser';
|
|
27
32
|
import {
|
|
@@ -72,8 +77,16 @@ export type Tileset3DProps = {
|
|
|
72
77
|
|
|
73
78
|
ellipsoid?: object;
|
|
74
79
|
modelMatrix?: Matrix4;
|
|
80
|
+
/** Automatic CRS output options and expert source-metadata recovery overrides. */
|
|
81
|
+
spatial?: TilesetSpatialOptions;
|
|
75
82
|
|
|
76
83
|
maximumScreenSpaceError?: number;
|
|
84
|
+
/**
|
|
85
|
+
* Enables replacement traversal that may skip hierarchy levels while retaining ready ancestors
|
|
86
|
+
* as coverage. This trades temporary overdraw for faster refinement on deep trees.
|
|
87
|
+
* @default false
|
|
88
|
+
*/
|
|
89
|
+
skipLevelOfDetail?: boolean;
|
|
77
90
|
/** Enables perspective dynamic SSE to reduce distant, horizon-facing refinement. */
|
|
78
91
|
dynamicScreenSpaceError?: boolean;
|
|
79
92
|
/** Base dynamic SSE fog density in inverse meters. */
|
|
@@ -139,6 +152,8 @@ type Props = {
|
|
|
139
152
|
onTraversalComplete: (selectedTiles: Tile3D[]) => Tile3D[];
|
|
140
153
|
onUpdate: () => void;
|
|
141
154
|
maximumScreenSpaceError: number;
|
|
155
|
+
/** Whether replacement traversal may skip levels while retaining ancestor coverage. */
|
|
156
|
+
skipLevelOfDetail: boolean;
|
|
142
157
|
/** Whether perspective dynamic SSE is enabled. */
|
|
143
158
|
dynamicScreenSpaceError: boolean;
|
|
144
159
|
/** Base dynamic SSE fog density in inverse meters. */
|
|
@@ -170,6 +185,8 @@ type Props = {
|
|
|
170
185
|
basePath: string;
|
|
171
186
|
contentLoader?: (tile: Tile3D) => Promise<void>;
|
|
172
187
|
i3s: Record<string, any>;
|
|
188
|
+
/** Automatic CRS output options and expert source-metadata recovery overrides. */
|
|
189
|
+
spatial: TilesetSpatialOptions;
|
|
173
190
|
};
|
|
174
191
|
|
|
175
192
|
const BYTES_PER_MEBIBYTE = 1024 * 1024;
|
|
@@ -264,6 +281,7 @@ const DEFAULT_PROPS: Props = {
|
|
|
264
281
|
foveatedInterpolationCallback: interpolateLinearly,
|
|
265
282
|
foveatedTimeDelay: 0.2,
|
|
266
283
|
maximumScreenSpaceError: 8,
|
|
284
|
+
skipLevelOfDetail: false,
|
|
267
285
|
dynamicScreenSpaceError: true,
|
|
268
286
|
dynamicScreenSpaceErrorDensity: 2.0e-4,
|
|
269
287
|
dynamicScreenSpaceErrorFactor: 24,
|
|
@@ -275,7 +293,8 @@ const DEFAULT_PROPS: Props = {
|
|
|
275
293
|
loadOptions: {fetch: {}},
|
|
276
294
|
attributions: [],
|
|
277
295
|
basePath: '',
|
|
278
|
-
i3s: {}
|
|
296
|
+
i3s: {},
|
|
297
|
+
spatial: {}
|
|
279
298
|
};
|
|
280
299
|
|
|
281
300
|
const TILES_TOTAL = 'Tiles In Tileset(s)';
|
|
@@ -314,6 +333,18 @@ export class Tileset3D {
|
|
|
314
333
|
root: Tile3D | null = null;
|
|
315
334
|
roots: Record<string, Tile3D> = {};
|
|
316
335
|
asset: Record<string, any> = {};
|
|
336
|
+
/** Inline metadata schema declared by the source tileset, when present. */
|
|
337
|
+
schema: Record<string, any> | null = null;
|
|
338
|
+
/** External metadata schema URI declared by the source tileset, when present. */
|
|
339
|
+
schemaUri: string | null = null;
|
|
340
|
+
/** Metadata groups declared by the source tileset, in source order. */
|
|
341
|
+
groups: Array<Record<string, any>> = [];
|
|
342
|
+
/** Tileset-wide metadata entity, preserved without property-table decoding. */
|
|
343
|
+
metadata: Record<string, any> | null = null;
|
|
344
|
+
/** Tileset statistics metadata, preserved for application-level inspection. */
|
|
345
|
+
statistics: unknown = null;
|
|
346
|
+
/** Normalized source and target coordinate reference system metadata. */
|
|
347
|
+
spatialReference: TilesetSpatialReference = createTilesetSpatialReference({});
|
|
317
348
|
|
|
318
349
|
description = '';
|
|
319
350
|
properties: any;
|
|
@@ -422,6 +453,13 @@ export class Tileset3D {
|
|
|
422
453
|
this.memoryAdjustedScreenSpaceError = this.options.maximumScreenSpaceError;
|
|
423
454
|
this._cacheBytes = cacheBytes;
|
|
424
455
|
this._maximumCacheOverflowBytes = maximumCacheOverflowBytes;
|
|
456
|
+
const metadata = this._getSourceMetadata();
|
|
457
|
+
if (metadata) {
|
|
458
|
+
this.spatialReference = applyTilesetSpatialOptions(
|
|
459
|
+
metadata.spatialReference,
|
|
460
|
+
this.options.spatial
|
|
461
|
+
);
|
|
462
|
+
}
|
|
425
463
|
|
|
426
464
|
this.stats = new Stats({id: this.url});
|
|
427
465
|
this._initializeStats();
|
|
@@ -812,10 +850,13 @@ export class Tileset3D {
|
|
|
812
850
|
for (const tile of this.selectedTiles) {
|
|
813
851
|
if (tile.contentAvailable && tile.content) {
|
|
814
852
|
tilesRenderable++;
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
853
|
+
const contents = tile.contents.length ? tile.contents : [tile.content];
|
|
854
|
+
for (const content of contents) {
|
|
855
|
+
if (content.pointCount) {
|
|
856
|
+
pointsRenderable += content.pointCount;
|
|
857
|
+
} else {
|
|
858
|
+
pointsRenderable += content.vertexCount || 0;
|
|
859
|
+
}
|
|
819
860
|
}
|
|
820
861
|
}
|
|
821
862
|
}
|
|
@@ -835,7 +876,8 @@ export class Tileset3D {
|
|
|
835
876
|
}
|
|
836
877
|
if (
|
|
837
878
|
initialMetadata?.tileset?.root &&
|
|
838
|
-
typeof initialMetadata.tileset.root.then !== 'function'
|
|
879
|
+
typeof initialMetadata.tileset.root.then !== 'function' &&
|
|
880
|
+
!this.source.prepareTileset
|
|
839
881
|
) {
|
|
840
882
|
this.root = this._initializeTileHeaders(initialMetadata.tileset, null);
|
|
841
883
|
this._applyViewState();
|
|
@@ -844,6 +886,7 @@ export class Tileset3D {
|
|
|
844
886
|
await initializePromise;
|
|
845
887
|
const metadata = this.source.getMetadata();
|
|
846
888
|
this._syncSourceState(metadata);
|
|
889
|
+
await this.source.prepareTileset?.(this);
|
|
847
890
|
if (!this.root) {
|
|
848
891
|
this.root = this._initializeTileHeaders(metadata.tileset, null);
|
|
849
892
|
}
|
|
@@ -1049,6 +1092,20 @@ export class Tileset3D {
|
|
|
1049
1092
|
this.refine = metadata.refine;
|
|
1050
1093
|
this.contentFormats = this.source.contentFormats;
|
|
1051
1094
|
this.asset = this.source.asset || this.asset;
|
|
1095
|
+
const tileset = metadata.tileset as Record<string, any>;
|
|
1096
|
+
this.schema = tileset.schema || null;
|
|
1097
|
+
this.schemaUri = tileset.schemaUri || null;
|
|
1098
|
+
this.groups = tileset.groups || [];
|
|
1099
|
+
this.metadata = tileset.metadata || null;
|
|
1100
|
+
this.statistics = tileset.statistics ?? null;
|
|
1101
|
+
const spatialReference = applyTilesetSpatialOptions(
|
|
1102
|
+
metadata.spatialReference,
|
|
1103
|
+
this.options.spatial
|
|
1104
|
+
);
|
|
1105
|
+
this.spatialReference = preserveTransformedSpatialReference(
|
|
1106
|
+
this.spatialReference,
|
|
1107
|
+
spatialReference
|
|
1108
|
+
);
|
|
1052
1109
|
this.properties = this.source.properties ?? this.properties;
|
|
1053
1110
|
this.extras = this.source.extras ?? this.extras;
|
|
1054
1111
|
if (this.options.attributions?.length) {
|
|
@@ -1085,3 +1142,17 @@ export class Tileset3D {
|
|
|
1085
1142
|
}
|
|
1086
1143
|
}
|
|
1087
1144
|
}
|
|
1145
|
+
|
|
1146
|
+
/** Preserve a completed runtime operation when asynchronous source metadata describes the same request. */
|
|
1147
|
+
function preserveTransformedSpatialReference(
|
|
1148
|
+
current: TilesetSpatialReference,
|
|
1149
|
+
next: TilesetSpatialReference
|
|
1150
|
+
): TilesetSpatialReference {
|
|
1151
|
+
return current?.status === 'transformed' &&
|
|
1152
|
+
current.sourceCrs === next.sourceCrs &&
|
|
1153
|
+
current.targetCrs === next.targetCrs &&
|
|
1154
|
+
current.targetHeightReference === next.targetHeightReference &&
|
|
1155
|
+
current.outputCoordinates === next.outputCoordinates
|
|
1156
|
+
? current
|
|
1157
|
+
: next;
|
|
1158
|
+
}
|
|
@@ -9,6 +9,7 @@ import type {Tile3D} from './tile-3d';
|
|
|
9
9
|
import type {Tileset3D} from './tileset-3d';
|
|
10
10
|
import type {FrameState} from '../helpers/frame-state';
|
|
11
11
|
import type {TILESET_TYPE} from '../../constants';
|
|
12
|
+
import type {TilesetSpatialReference} from '../../spatial/spatial-types';
|
|
12
13
|
|
|
13
14
|
/**
|
|
14
15
|
* Parsed top-level tileset payload consumed by {@link Tileset3D} or a {@link Tileset3DSource}.
|
|
@@ -69,8 +70,12 @@ export type TilesetSourceInput = TilesetJSON | TilesetSourceRequest;
|
|
|
69
70
|
export type TileContentLoadResult = {
|
|
70
71
|
/** Whether the tile content request produced usable content. */
|
|
71
72
|
loaded: boolean;
|
|
73
|
+
/** All payloads loaded for a tile, in source order. */
|
|
74
|
+
contents?: any[];
|
|
72
75
|
/** Nested tileset metadata to install below the loaded tile, if any. */
|
|
73
76
|
nestedTileset?: TilesetJSON;
|
|
77
|
+
/** Nested tilesets found among multiple content payloads. */
|
|
78
|
+
nestedTilesets?: TilesetJSON[];
|
|
74
79
|
};
|
|
75
80
|
|
|
76
81
|
/** Result of a source-managed lazy child-header request. */
|
|
@@ -128,6 +133,8 @@ export type TilesetSourceMetadata = {
|
|
|
128
133
|
lodMetricValue: number;
|
|
129
134
|
/** Root refinement mode used as fallback by runtime tiles. */
|
|
130
135
|
refine: string;
|
|
136
|
+
/** Format-discovered coordinate reference system metadata. */
|
|
137
|
+
spatialReference?: TilesetSpatialReference;
|
|
131
138
|
};
|
|
132
139
|
|
|
133
140
|
/**
|
|
@@ -167,6 +174,14 @@ export interface Tileset3DSource {
|
|
|
167
174
|
*/
|
|
168
175
|
initialize(): Promise<void>;
|
|
169
176
|
|
|
177
|
+
/**
|
|
178
|
+
* Prepares option-dependent asynchronous source state before runtime headers are constructed.
|
|
179
|
+
*
|
|
180
|
+
* I3S uses this hook to sample elevation surfaces for its root bound after `Tileset3D` has
|
|
181
|
+
* applied spatial options. Sources without asynchronous header preparation omit the hook.
|
|
182
|
+
*/
|
|
183
|
+
prepareTileset?(tileset: Tileset3D): Promise<void>;
|
|
184
|
+
|
|
170
185
|
/** Releases source-local caches and prevents late asynchronous work from mutating runtime tiles. */
|
|
171
186
|
destroy?(): void;
|
|
172
187
|
|
|
@@ -140,7 +140,9 @@ export class TilesetTraverser {
|
|
|
140
140
|
// Always load tiles in the base traversal
|
|
141
141
|
// Select tiles that can't refine further
|
|
142
142
|
this.loadTile(tile, frameState);
|
|
143
|
-
|
|
143
|
+
// Skip-LOD keeps a ready ancestor selected while descendants stream in. This prevents
|
|
144
|
+
// replacement holes when traversal jumps over one or more hierarchy levels.
|
|
145
|
+
if (stoppedRefining || this.options.skipLevelOfDetail) {
|
|
144
146
|
this.selectTile(tile, frameState);
|
|
145
147
|
}
|
|
146
148
|
}
|
|
@@ -228,7 +230,7 @@ export class TilesetTraverser {
|
|
|
228
230
|
|
|
229
231
|
// tile to render in the browser
|
|
230
232
|
selectTile(tile: Tile3D, frameState: FrameState): void {
|
|
231
|
-
if (this.shouldSelectTile(tile)) {
|
|
233
|
+
if (this.shouldSelectTile(tile, frameState)) {
|
|
232
234
|
// The tile can be selected right away and does not require traverseAndSelect
|
|
233
235
|
tile._selectedFrame = frameState.frameNumber;
|
|
234
236
|
this.selectedTiles[tile.id] = tile;
|
|
@@ -292,10 +294,13 @@ export class TilesetTraverser {
|
|
|
292
294
|
return tile.hasUnloadedContent || tile.contentExpired;
|
|
293
295
|
}
|
|
294
296
|
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
297
|
+
/**
|
|
298
|
+
* Returns whether a tile's content is available and visible for rendering.
|
|
299
|
+
* @param tile Tile considered for rendering.
|
|
300
|
+
* @param frameState Current culling state.
|
|
301
|
+
*/
|
|
302
|
+
shouldSelectTile(tile: Tile3D, frameState: FrameState): boolean {
|
|
303
|
+
return tile.contentAvailable && tile.contentVisibility(frameState) !== 'outside';
|
|
299
304
|
}
|
|
300
305
|
|
|
301
306
|
/** Decide if tile LoD (level of detail) is not sufficient under current viewport */
|