@loaders.gl/tiles 5.0.0-alpha.1 → 5.0.0-alpha.11
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 +14 -3
- package/README.md +26 -0
- package/dist/dist.dev.js +20341 -5081
- package/dist/dist.min.js +3 -1
- package/dist/index.cjs +7904 -2071
- package/dist/index.cjs.map +3 -4
- package/dist/index.d.ts +25 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +11 -0
- package/dist/index.js.map +1 -1
- package/dist/point-cloud/point-cloud-tile.d.ts.map +1 -1
- package/dist/point-cloud/point-cloud-tile.js +3 -0
- package/dist/point-cloud/point-cloud-tile.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 +54 -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/point-cloud/types.js +3 -0
- package/dist/point-cloud/types.js.map +1 -1
- package/dist/raster-set.d.ts +48 -31
- package/dist/raster-set.d.ts.map +1 -1
- package/dist/raster-set.js +104 -24
- package/dist/raster-set.js.map +1 -1
- package/dist/spatial/format-spatial-reference.d.ts +68 -0
- package/dist/spatial/format-spatial-reference.d.ts.map +1 -0
- package/dist/spatial/format-spatial-reference.js +291 -0
- package/dist/spatial/format-spatial-reference.js.map +1 -0
- package/dist/spatial/get-spatial-coordinate-frame.d.ts +20 -0
- package/dist/spatial/get-spatial-coordinate-frame.d.ts.map +1 -0
- package/dist/spatial/get-spatial-coordinate-frame.js +139 -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 +205 -0
- package/dist/spatial/i3s-spatial-transformer.d.ts.map +1 -0
- package/dist/spatial/i3s-spatial-transformer.js +731 -0
- package/dist/spatial/i3s-spatial-transformer.js.map +1 -0
- 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 +52 -0
- package/dist/spatial/spatial-coordinate-transformer.d.ts.map +1 -0
- package/dist/spatial/spatial-coordinate-transformer.js +295 -0
- package/dist/spatial/spatial-coordinate-transformer.js.map +1 -0
- package/dist/spatial/spatial-resource-registry.d.ts +47 -0
- package/dist/spatial/spatial-resource-registry.d.ts.map +1 -0
- package/dist/spatial/spatial-resource-registry.js +76 -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/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 +163 -0
- package/dist/spatial/tiles-3d-spatial-transformer.js.map +1 -0
- package/dist/tileset-2d/adapter.d.ts +2 -3
- package/dist/tileset-2d/adapter.d.ts.map +1 -1
- package/dist/tileset-2d/tileset-2d.d.ts +2 -3
- package/dist/tileset-2d/tileset-2d.d.ts.map +1 -1
- package/dist/tileset-2d/tileset-2d.js.map +1 -1
- package/dist/tileset-3d/common/indexed-archive-tileset-source.d.ts +3 -3
- package/dist/tileset-3d/common/indexed-archive-tileset-source.d.ts.map +1 -1
- package/dist/tileset-3d/common/indexed-archive-tileset-source.js +9 -4
- package/dist/tileset-3d/common/indexed-archive-tileset-source.js.map +1 -1
- 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 +176 -5
- package/dist/tileset-3d/common/tile-3d.d.ts.map +1 -1
- package/dist/tileset-3d/common/tile-3d.js +523 -89
- package/dist/tileset-3d/common/tile-3d.js.map +1 -1
- package/dist/tileset-3d/common/tileset-3d.d.ts +199 -9
- package/dist/tileset-3d/common/tileset-3d.d.ts.map +1 -1
- package/dist/tileset-3d/common/tileset-3d.js +345 -32
- package/dist/tileset-3d/common/tileset-3d.js.map +1 -1
- package/dist/tileset-3d/common/tileset-cache.d.ts +11 -1
- package/dist/tileset-3d/common/tileset-cache.d.ts.map +1 -1
- package/dist/tileset-3d/common/tileset-cache.js +11 -3
- package/dist/tileset-3d/common/tileset-cache.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 +51 -9
- 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 +33 -1
- package/dist/tileset-3d/common/tileset-traverser.d.ts.map +1 -1
- package/dist/tileset-3d/common/tileset-traverser.js +61 -14
- package/dist/tileset-3d/common/tileset-traverser.js.map +1 -1
- package/dist/tileset-3d/format-3d-tiles/implicit-tiling.d.ts +182 -0
- package/dist/tileset-3d/format-3d-tiles/implicit-tiling.d.ts.map +1 -0
- package/dist/tileset-3d/format-3d-tiles/implicit-tiling.js +681 -0
- package/dist/tileset-3d/format-3d-tiles/implicit-tiling.js.map +1 -0
- package/dist/tileset-3d/format-3d-tiles/tiles-3d-source.d.ts +127 -6
- 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 +485 -27
- 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 +104 -0
- 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 +286 -0
- package/dist/tileset-3d/format-3d-tiles/tileset-3d-traverser.js.map +1 -1
- package/dist/tileset-3d/format-i3s/i3s-pending-tiles-register.d.ts.map +1 -1
- package/dist/tileset-3d/format-i3s/i3s-pending-tiles-register.js +3 -0
- package/dist/tileset-3d/format-i3s/i3s-pending-tiles-register.js.map +1 -1
- package/dist/tileset-3d/format-i3s/i3s-source.d.ts +23 -6
- package/dist/tileset-3d/format-i3s/i3s-source.d.ts.map +1 -1
- package/dist/tileset-3d/format-i3s/i3s-source.js +176 -25
- package/dist/tileset-3d/format-i3s/i3s-source.js.map +1 -1
- package/dist/tileset-3d/format-i3s/i3s-tile-manager.d.ts.map +1 -1
- package/dist/tileset-3d/format-i3s/i3s-tile-manager.js +3 -0
- package/dist/tileset-3d/format-i3s/i3s-tile-manager.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 +4 -1
- package/dist/tileset-3d/format-i3s/i3s-tileset-traverser.js.map +1 -1
- package/dist/tileset-3d/helpers/3d-tiles-options.d.ts.map +1 -1
- package/dist/tileset-3d/helpers/3d-tiles-options.js +3 -0
- package/dist/tileset-3d/helpers/3d-tiles-options.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 +20 -91
- package/dist/tileset-3d/helpers/bounding-volume.js.map +1 -1
- package/dist/tileset-3d/helpers/frame-state.d.ts +67 -2
- package/dist/tileset-3d/helpers/frame-state.d.ts.map +1 -1
- package/dist/tileset-3d/helpers/frame-state.js +92 -10
- 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/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/tileset-3d/helpers/tiles-3d-lod.d.ts +61 -9
- package/dist/tileset-3d/helpers/tiles-3d-lod.d.ts.map +1 -1
- package/dist/tileset-3d/helpers/tiles-3d-lod.js +198 -93
- package/dist/tileset-3d/helpers/tiles-3d-lod.js.map +1 -1
- package/dist/tileset-3d/helpers/tiles-3d-request-priority.d.ts +134 -0
- package/dist/tileset-3d/helpers/tiles-3d-request-priority.d.ts.map +1 -0
- package/dist/tileset-3d/helpers/tiles-3d-request-priority.js +179 -0
- package/dist/tileset-3d/helpers/tiles-3d-request-priority.js.map +1 -0
- package/dist/types.d.ts +7 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +3 -0
- package/dist/types.js.map +1 -1
- package/dist/utils/doubly-linked-list.d.ts.map +1 -1
- package/dist/utils/doubly-linked-list.js +1 -0
- package/dist/utils/doubly-linked-list.js.map +1 -1
- package/dist/utils/managed-array.d.ts.map +1 -1
- package/dist/utils/managed-array.js +1 -0
- package/dist/utils/managed-array.js.map +1 -1
- package/package.json +12 -10
- package/src/index.ts +79 -1
- package/src/point-cloud/point-cloud-tile.ts +4 -0
- package/src/point-cloud/point-cloud-tileset.ts +70 -12
- package/src/point-cloud/types.ts +21 -0
- package/src/raster-set.ts +197 -67
- package/src/spatial/format-spatial-reference.ts +385 -0
- package/src/spatial/get-spatial-coordinate-frame.ts +169 -0
- package/src/spatial/i3s-elevation.ts +96 -0
- package/src/spatial/i3s-spatial-transformer.ts +1021 -0
- package/src/spatial/normalize-crs-identifier.ts +26 -0
- package/src/spatial/spatial-coordinate-transformer.ts +377 -0
- package/src/spatial/spatial-resource-registry.ts +90 -0
- package/src/spatial/spatial-types.ts +422 -0
- package/src/spatial/tiles-3d-spatial-transformer.ts +194 -0
- package/src/tileset-2d/adapter.ts +2 -3
- package/src/tileset-2d/tileset-2d.ts +2 -3
- package/src/tileset-3d/common/indexed-archive-tileset-source.ts +13 -13
- package/src/tileset-3d/common/tile-3d-contracts.ts +115 -0
- package/src/tileset-3d/common/tile-3d.ts +629 -98
- package/src/tileset-3d/common/tileset-3d.ts +512 -40
- package/src/tileset-3d/common/tileset-cache.ts +15 -7
- 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 +63 -13
- package/src/tileset-3d/common/tileset-traverser.ts +73 -15
- package/src/tileset-3d/format-3d-tiles/implicit-tiling.ts +1059 -0
- package/src/tileset-3d/format-3d-tiles/tiles-3d-source.ts +674 -28
- package/src/tileset-3d/format-3d-tiles/tileset-3d-traverser.ts +328 -0
- package/src/tileset-3d/format-i3s/i3s-pending-tiles-register.ts +4 -0
- package/src/tileset-3d/format-i3s/i3s-source.ts +220 -29
- package/src/tileset-3d/format-i3s/i3s-tile-manager.ts +4 -0
- package/src/tileset-3d/format-i3s/i3s-tileset-traverser.ts +6 -1
- package/src/tileset-3d/helpers/3d-tiles-options.ts +4 -0
- package/src/tileset-3d/helpers/bounding-volume.ts +31 -114
- package/src/tileset-3d/helpers/frame-state.ts +152 -13
- package/src/tileset-3d/helpers/i3s-lod.ts +22 -8
- package/src/tileset-3d/helpers/s2-bounding-volume.ts +35 -0
- package/src/tileset-3d/helpers/tiles-3d-lod.ts +291 -112
- package/src/tileset-3d/helpers/tiles-3d-request-priority.ts +301 -0
- package/src/types.ts +11 -0
- package/src/utils/doubly-linked-list.ts +2 -0
- package/src/utils/managed-array.ts +2 -0
|
@@ -7,12 +7,15 @@
|
|
|
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';
|
|
11
|
+
import {getTile3DFeatureIdSets} from './tile-3d-contracts';
|
|
12
|
+
import type {Tile3DContent, Tile3DFeatureIdSet, Tile3DMetadataContext} from './tile-3d-contracts';
|
|
10
13
|
|
|
11
14
|
// Note: circular dependency
|
|
12
15
|
import type {Tileset3D} from './tileset-3d';
|
|
13
16
|
import type {DoublyLinkedListNode} from '../../utils/doubly-linked-list-node';
|
|
14
17
|
import {LOD_METRIC_TYPE, TILE_REFINEMENT, TILE_CONTENT_STATE, TILESET_TYPE} from '../../constants';
|
|
15
|
-
import type {TileContentLoadResult} from './tileset-source';
|
|
18
|
+
import type {TileChildrenLoadResult, TileContentLoadResult} from './tileset-source';
|
|
16
19
|
|
|
17
20
|
import {FrameState} from '../helpers/frame-state';
|
|
18
21
|
import {
|
|
@@ -23,9 +26,75 @@ import {
|
|
|
23
26
|
import {getTiles3DScreenSpaceError} from '../helpers/tiles-3d-lod';
|
|
24
27
|
import {getProjectedRadius} from '../helpers/i3s-lod';
|
|
25
28
|
import {TilesetTraverser} from './tileset-traverser';
|
|
29
|
+
import {
|
|
30
|
+
calculateFoveatedFactor,
|
|
31
|
+
calculateTileRequestPriority,
|
|
32
|
+
isFoveatedRequestDelayActive,
|
|
33
|
+
isFoveatedRequestDeferred,
|
|
34
|
+
isProgressiveResolutionPriority
|
|
35
|
+
} from '../helpers/tiles-3d-request-priority';
|
|
26
36
|
|
|
27
37
|
const scratchVector = new Vector3();
|
|
28
38
|
|
|
39
|
+
/**
|
|
40
|
+
* Classifies render-content visibility across all content volumes owned by a tile.
|
|
41
|
+
*
|
|
42
|
+
* Content volumes form a union: one visible content entry keeps the tile renderable even when
|
|
43
|
+
* another entry is outside the frustum or clipping planes. The traversal volume is intentionally
|
|
44
|
+
* not used here except as a fallback when a tile has no content-specific volume. This separation
|
|
45
|
+
* keeps clipping render-only and prevents a tight content volume from pruning descendants.
|
|
46
|
+
*
|
|
47
|
+
* @param contentVolumes - Transformed content volumes in source order.
|
|
48
|
+
* @param fallbackVolume - Tile volume used when content does not declare its own volume.
|
|
49
|
+
* @param cullingVolume - Current view frustum.
|
|
50
|
+
* @param visibilityPlaneMask - Parent visibility mask optimization.
|
|
51
|
+
* @param clippingPlanes - Optional world-space planes applied to render content only.
|
|
52
|
+
* @returns Union visibility classification for the renderable content.
|
|
53
|
+
*/
|
|
54
|
+
export function getContentVisibility(
|
|
55
|
+
contentVolumes: any[],
|
|
56
|
+
fallbackVolume: any,
|
|
57
|
+
cullingVolume: CullingVolume,
|
|
58
|
+
visibilityPlaneMask: number | undefined,
|
|
59
|
+
clippingPlanes: any[] = []
|
|
60
|
+
): CullingResult {
|
|
61
|
+
const volumes = contentVolumes.length ? contentVolumes : [fallbackVolume];
|
|
62
|
+
let visibleVolume = false;
|
|
63
|
+
let intersectingVolume = false;
|
|
64
|
+
for (const contentVolume of volumes) {
|
|
65
|
+
let visibility: CullingResult = 'inside';
|
|
66
|
+
if (visibilityPlaneMask !== CullingVolume.MASK_INSIDE) {
|
|
67
|
+
visibility = cullingVolume.computeVisibility(contentVolume);
|
|
68
|
+
if (visibility === 'outside') {
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
let clipped = false;
|
|
73
|
+
for (const clippingPlane of clippingPlanes) {
|
|
74
|
+
if (contentVolume.intersectPlane(clippingPlane) === 'outside') {
|
|
75
|
+
clipped = true;
|
|
76
|
+
break;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
if (!clipped) {
|
|
80
|
+
visibleVolume = true;
|
|
81
|
+
intersectingVolume = intersectingVolume || visibility === 'intersecting';
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
if (!visibleVolume) {
|
|
85
|
+
return 'outside';
|
|
86
|
+
}
|
|
87
|
+
return intersectingVolume ? 'intersecting' : 'inside';
|
|
88
|
+
}
|
|
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
|
+
|
|
29
98
|
function defined(x) {
|
|
30
99
|
return x !== undefined && x !== null;
|
|
31
100
|
}
|
|
@@ -44,6 +113,9 @@ export type Tile3DProps = {
|
|
|
44
113
|
extendedId: string;
|
|
45
114
|
};
|
|
46
115
|
|
|
116
|
+
/** Lifecycle of a source-managed lazy child-header group. */
|
|
117
|
+
export type TileChildrenState = 'unloaded' | 'loading' | 'ready' | 'failed';
|
|
118
|
+
|
|
47
119
|
/**
|
|
48
120
|
* A Tile3DHeader represents a tile as Tileset3D. When a tile is first created, its content is not loaded;
|
|
49
121
|
* the content is loaded on-demand when needed based on the view.
|
|
@@ -59,6 +131,8 @@ export class Tile3D {
|
|
|
59
131
|
refine: TILE_REFINEMENT;
|
|
60
132
|
type: string;
|
|
61
133
|
contentUrl: string;
|
|
134
|
+
/** Resolved render-content URLs in source order; empty for contentless tiles. */
|
|
135
|
+
contentUrls: string[] = [];
|
|
62
136
|
/** Different refinement algorithms used by I3S and 3D tiles */
|
|
63
137
|
lodMetricType = 'geometricError';
|
|
64
138
|
/**
|
|
@@ -73,6 +147,9 @@ export class Tile3D {
|
|
|
73
147
|
*/
|
|
74
148
|
private _unscaledLodMetricValue: number = 0;
|
|
75
149
|
|
|
150
|
+
/** Whether the source format defines geometric error in transform-local units. */
|
|
151
|
+
private _scaleGeometricError: boolean = true;
|
|
152
|
+
|
|
76
153
|
/** @todo math.gl is not exporting BoundingVolume base type? */
|
|
77
154
|
boundingVolume: any = null;
|
|
78
155
|
|
|
@@ -81,11 +158,32 @@ export class Tile3D {
|
|
|
81
158
|
* not the content's metadata in the tileset JSON file.
|
|
82
159
|
*/
|
|
83
160
|
content: any = null;
|
|
161
|
+
/** Loaded payloads in the same order as {@link contentUrls}; `content` remains the primary payload. */
|
|
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;
|
|
171
|
+
/**
|
|
172
|
+
* Metadata entity attached to this tile in the source tileset, if present.
|
|
173
|
+
*
|
|
174
|
+
* This is the raw 3D Tiles metadata reference. The generic tiles runtime preserves it without
|
|
175
|
+
* decoding schema classes or binary property tables.
|
|
176
|
+
*/
|
|
177
|
+
metadata: Record<string, any> | null = null;
|
|
178
|
+
/** Raw metadata entities attached to tile contents, in content source order. */
|
|
179
|
+
contentMetadata: Array<Record<string, any> | null> = [];
|
|
84
180
|
contentState: number = TILE_CONTENT_STATE.UNLOADED;
|
|
85
181
|
gpuMemoryUsageInBytes: number = 0;
|
|
86
182
|
|
|
87
183
|
/** The tile's children - an array of Tile3D objects. */
|
|
88
184
|
children: Tile3D[] = [];
|
|
185
|
+
/** Current lifecycle state for a lazy implicit subtree rooted at this tile. */
|
|
186
|
+
childrenState: TileChildrenState = 'ready';
|
|
89
187
|
depth: number = 0;
|
|
90
188
|
viewportIds: any[] = [];
|
|
91
189
|
transform = new Matrix4();
|
|
@@ -111,17 +209,47 @@ export class Tile3D {
|
|
|
111
209
|
private _expireDate: any = null;
|
|
112
210
|
private _expiredContent: any = null;
|
|
113
211
|
|
|
212
|
+
/** In-flight source request shared by repeated traversal frames. */
|
|
213
|
+
private _childrenPromise: Promise<TileChildrenLoadResult> | null = null;
|
|
214
|
+
|
|
114
215
|
private _boundingBox?: CartographicBounds = undefined;
|
|
115
216
|
|
|
116
217
|
/** updated every frame for tree traversal and rendering optimizations: */
|
|
117
218
|
public _distanceToCamera: number = 0;
|
|
118
219
|
_screenSpaceError: number = 0;
|
|
220
|
+
/** SSE calculated at the reduced viewport height used to prioritize coarse coverage. */
|
|
221
|
+
_screenSpaceErrorProgressiveResolution: number = 0;
|
|
222
|
+
/** Whether this tile should be promoted into the progressive-resolution request pass. */
|
|
223
|
+
_priorityProgressiveResolution: boolean = false;
|
|
224
|
+
/** Angular distance from the camera view axis used to prioritize center tiles. */
|
|
225
|
+
_foveatedFactor: number = 0;
|
|
226
|
+
/** Whether this peripheral request is currently inside the camera-motion deferral window. */
|
|
227
|
+
priorityDeferred: boolean = false;
|
|
119
228
|
private _visibilityPlaneMask: any;
|
|
120
229
|
private _visible: boolean | undefined = undefined;
|
|
121
230
|
|
|
122
231
|
private _contentBoundingVolume: any;
|
|
232
|
+
private _contentBoundingVolumes: any[] = [];
|
|
233
|
+
/** Source content-volume headers retained after render content is unloaded. */
|
|
234
|
+
private _contentBoundingVolumeHeaders: any[] = [];
|
|
123
235
|
private _viewerRequestVolume: any;
|
|
124
236
|
|
|
237
|
+
/** Bounding volume used to cull the tile's first renderable content entry. */
|
|
238
|
+
get contentBoundingVolume(): any {
|
|
239
|
+
return this._contentBoundingVolume;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/** All transformed render-content bounding volumes, including tile-volume fallbacks. */
|
|
243
|
+
get contentBoundingVolumes(): any[] {
|
|
244
|
+
return this._contentBoundingVolumes;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/** Bounding volume that limits when this tile may be requested, when declared. */
|
|
248
|
+
get viewerRequestVolume(): any {
|
|
249
|
+
return this._viewerRequestVolume;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/** Accumulated tile transforms from the source JSON, excluding the tileset model matrix. */
|
|
125
253
|
_initialTransform: Matrix4 = new Matrix4();
|
|
126
254
|
|
|
127
255
|
// Used by traverser, cannot be marked private
|
|
@@ -176,12 +304,16 @@ export class Tile3D {
|
|
|
176
304
|
this.refine = this._getRefine(header.refine);
|
|
177
305
|
this.type = header.type;
|
|
178
306
|
this.contentUrl = header.contentUrl;
|
|
307
|
+
this.contentUrls = header.contentUrls || (header.contentUrl ? [header.contentUrl] : []);
|
|
308
|
+
this._initializeMetadata(header);
|
|
309
|
+
this.childrenState = header.implicitSubtree ? 'unloaded' : 'ready';
|
|
179
310
|
|
|
180
311
|
this._initializeLodMetric(header);
|
|
181
312
|
this._initializeTransforms(header);
|
|
182
313
|
this._updateLodMetricScale();
|
|
183
314
|
this._initializeBoundingVolumes(header);
|
|
184
315
|
this._initializeContent(header);
|
|
316
|
+
this._initializeContentEntries(header);
|
|
185
317
|
this._initializeRenderingState(header);
|
|
186
318
|
|
|
187
319
|
Object.seal(this);
|
|
@@ -214,7 +346,24 @@ export class Tile3D {
|
|
|
214
346
|
|
|
215
347
|
/** Returns true if tile has children */
|
|
216
348
|
get hasChildren() {
|
|
217
|
-
return
|
|
349
|
+
return (
|
|
350
|
+
this.children.length > 0 ||
|
|
351
|
+
(this.header.children && this.header.children.length > 0) ||
|
|
352
|
+
Boolean(this.header.implicitSubtree && this.childrenState !== 'ready')
|
|
353
|
+
);
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
/** Returns whether traversal may request an unresolved lazy child-header group. */
|
|
357
|
+
get hasUnloadedChildren(): boolean {
|
|
358
|
+
return Boolean(
|
|
359
|
+
this.header.implicitSubtree &&
|
|
360
|
+
(this.childrenState === 'unloaded' || this.childrenState === 'failed')
|
|
361
|
+
);
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
/** Returns whether a lazy child-header request is currently in flight. */
|
|
365
|
+
get childrenLoading(): boolean {
|
|
366
|
+
return this.childrenState === 'loading';
|
|
218
367
|
}
|
|
219
368
|
|
|
220
369
|
/**
|
|
@@ -277,23 +426,42 @@ export class Tile3D {
|
|
|
277
426
|
}
|
|
278
427
|
|
|
279
428
|
/**
|
|
280
|
-
* Get bounding
|
|
429
|
+
* Get the current world-space bounding volume in cartographic coordinates.
|
|
281
430
|
* @returns [min, max] each in [longitude, latitude, altitude]
|
|
282
431
|
*/
|
|
283
432
|
get boundingBox(): CartographicBounds {
|
|
284
433
|
if (!this._boundingBox) {
|
|
285
|
-
this._boundingBox = getCartographicBounds(
|
|
434
|
+
this._boundingBox = getCartographicBounds(
|
|
435
|
+
this.header.boundingVolume,
|
|
436
|
+
this.boundingVolume,
|
|
437
|
+
!this.computedTransform.equals(this._initialTransform)
|
|
438
|
+
);
|
|
286
439
|
}
|
|
287
440
|
return this._boundingBox;
|
|
288
441
|
}
|
|
289
442
|
|
|
290
|
-
/**
|
|
291
|
-
|
|
443
|
+
/**
|
|
444
|
+
* Gets the tile's screen-space error for normal refinement or a reduced-height priority pass.
|
|
445
|
+
*
|
|
446
|
+
* I3S already supplies a screen-oriented LOD metric and intentionally ignores the optional 3D
|
|
447
|
+
* Tiles progressive-resolution height fraction.
|
|
448
|
+
*
|
|
449
|
+
* @param frameState - Current camera and viewport measurements.
|
|
450
|
+
* @param useParentLodMetric - Whether to substitute the parent LOD metric.
|
|
451
|
+
* @param viewportHeightFraction - Fraction of viewport height represented by the priority pass.
|
|
452
|
+
* @returns Screen-space LOD error for the current tileset format.
|
|
453
|
+
*/
|
|
454
|
+
getScreenSpaceError(frameState, useParentLodMetric, viewportHeightFraction = 1) {
|
|
292
455
|
switch (this.tileset.type) {
|
|
293
456
|
case TILESET_TYPE.I3S:
|
|
294
457
|
return getProjectedRadius(this, frameState);
|
|
295
458
|
case TILESET_TYPE.TILES3D:
|
|
296
|
-
return getTiles3DScreenSpaceError(
|
|
459
|
+
return getTiles3DScreenSpaceError(
|
|
460
|
+
this,
|
|
461
|
+
frameState,
|
|
462
|
+
useParentLodMetric,
|
|
463
|
+
viewportHeightFraction
|
|
464
|
+
);
|
|
297
465
|
default:
|
|
298
466
|
// eslint-disable-next-line
|
|
299
467
|
throw new Error('Unsupported tileset type');
|
|
@@ -312,17 +480,46 @@ export class Tile3D {
|
|
|
312
480
|
* Memory usage of tile on GPU
|
|
313
481
|
*/
|
|
314
482
|
_getGpuMemoryUsageInBytes(): number {
|
|
315
|
-
|
|
483
|
+
const contents = this.contents.length ? this.contents : [this.content];
|
|
484
|
+
return contents.reduce(
|
|
485
|
+
(total, content) => total + (content?.gpuMemoryUsageInBytes || content?.byteLength || 0),
|
|
486
|
+
0
|
|
487
|
+
);
|
|
316
488
|
}
|
|
317
489
|
|
|
318
|
-
|
|
319
|
-
*
|
|
320
|
-
*
|
|
490
|
+
/**
|
|
491
|
+
* Returns scheduler priority for render-content requests.
|
|
492
|
+
*
|
|
493
|
+
* @returns Numeric priority, or a negative value when the queued request should be cancelled.
|
|
494
|
+
*/
|
|
495
|
+
_getPriority(): number {
|
|
496
|
+
return this._calculateRequestPriority(false);
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
/**
|
|
500
|
+
* Returns scheduler priority for lazy child-header requests.
|
|
501
|
+
*
|
|
502
|
+
* Contentless implicit placeholders intentionally retain `UNLOADED` content state, so metadata
|
|
503
|
+
* scheduling skips only that render-content cancellation check and preserves every visibility,
|
|
504
|
+
* recency, progressive, foveated, and motion-deferral rule.
|
|
505
|
+
*
|
|
506
|
+
* @returns Numeric priority, or a negative value when the queued request should be cancelled.
|
|
507
|
+
*/
|
|
508
|
+
private _getChildrenPriority(): number {
|
|
509
|
+
return this._calculateRequestPriority(true);
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
/**
|
|
513
|
+
* Calculates the common scheduler priority for content or hierarchy metadata.
|
|
514
|
+
*
|
|
515
|
+
* @param allowUnloadedContent - Whether an empty placeholder may request child metadata.
|
|
516
|
+
* @returns Numeric priority, or a negative value when the queued request should be cancelled.
|
|
321
517
|
*/
|
|
322
518
|
// eslint-disable-next-line complexity
|
|
323
|
-
|
|
519
|
+
private _calculateRequestPriority(allowUnloadedContent: boolean): number {
|
|
324
520
|
const traverser = this.tileset._traverser;
|
|
325
|
-
const
|
|
521
|
+
const skipLevelOfDetail =
|
|
522
|
+
traverser.options.skipLevelOfDetail && !traverser.disableSkipLevelOfDetail;
|
|
326
523
|
|
|
327
524
|
/*
|
|
328
525
|
* Tiles that are outside of the camera's frustum could be skipped if we are in 'ADD' mode
|
|
@@ -340,7 +537,13 @@ export class Tile3D {
|
|
|
340
537
|
if (this.tileset._frameNumber - this._touchedFrame >= 1) {
|
|
341
538
|
return -1;
|
|
342
539
|
}
|
|
343
|
-
|
|
540
|
+
// RequestScheduler re-evaluates this callback before a queued request receives a slot. A
|
|
541
|
+
// negative priority cancels work that became deferred after it was queued; the scheduled
|
|
542
|
+
// follow-up traversal submits it again after the motion delay expires.
|
|
543
|
+
if (this.priorityDeferred) {
|
|
544
|
+
return -1;
|
|
545
|
+
}
|
|
546
|
+
if (!allowUnloadedContent && this.contentState === TILE_CONTENT_STATE.UNLOADED) {
|
|
344
547
|
return -1;
|
|
345
548
|
}
|
|
346
549
|
|
|
@@ -354,8 +557,15 @@ export class Tile3D {
|
|
|
354
557
|
|
|
355
558
|
const rootScreenSpaceError = traverser.root ? traverser.root._screenSpaceError : 0.0;
|
|
356
559
|
|
|
357
|
-
// Map higher SSE to lower values (e.g. root tile is highest priority)
|
|
358
|
-
|
|
560
|
+
// Map higher SSE to lower values (e.g. root tile is highest priority), then combine the
|
|
561
|
+
// existing order with independent progressive and foveated priority bands.
|
|
562
|
+
const reverseScreenSpaceError = Math.max(rootScreenSpaceError - screenSpaceError, 0);
|
|
563
|
+
return calculateTileRequestPriority({
|
|
564
|
+
priorityProgressiveResolution: this._priorityProgressiveResolution,
|
|
565
|
+
foveatedFactor: this._foveatedFactor,
|
|
566
|
+
reverseScreenSpaceError,
|
|
567
|
+
rootScreenSpaceError
|
|
568
|
+
});
|
|
359
569
|
}
|
|
360
570
|
|
|
361
571
|
/**
|
|
@@ -392,30 +602,266 @@ export class Tile3D {
|
|
|
392
602
|
}
|
|
393
603
|
|
|
394
604
|
try {
|
|
395
|
-
|
|
605
|
+
return await this._loadContentFromSource();
|
|
606
|
+
} catch (error) {
|
|
607
|
+
// Tile is unloaded before the content finishes loading
|
|
608
|
+
this.contentState = TILE_CONTENT_STATE.FAILED;
|
|
609
|
+
throw error;
|
|
610
|
+
} finally {
|
|
611
|
+
requestToken.done();
|
|
612
|
+
}
|
|
613
|
+
}
|
|
396
614
|
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
615
|
+
/** Loads content for complete dataset operations without viewport-based request priority. */
|
|
616
|
+
async loadContentForTraversal(): Promise<TileContentLoadResult> {
|
|
617
|
+
if (this.hasEmptyContent) {
|
|
618
|
+
return {loaded: false};
|
|
619
|
+
}
|
|
620
|
+
if (this.content) {
|
|
621
|
+
return {loaded: true, contents: this.contents};
|
|
622
|
+
}
|
|
400
623
|
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
return
|
|
624
|
+
this.contentState = TILE_CONTENT_STATE.LOADING;
|
|
625
|
+
try {
|
|
626
|
+
return await this._loadContentFromSource();
|
|
404
627
|
} catch (error) {
|
|
405
|
-
// Tile is unloaded before the content finishes loading
|
|
406
628
|
this.contentState = TILE_CONTENT_STATE.FAILED;
|
|
407
629
|
throw error;
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
/** Loads source content and updates the tile's renderer-neutral payload state. */
|
|
634
|
+
private async _loadContentFromSource(): Promise<TileContentLoadResult> {
|
|
635
|
+
const loadResult = await this.tileset.source.loadTileContent(this);
|
|
636
|
+
if (loadResult.contents) {
|
|
637
|
+
this.contents = loadResult.contents;
|
|
638
|
+
this.content = this.contents[0] || null;
|
|
639
|
+
}
|
|
640
|
+
this.vectorContent =
|
|
641
|
+
this.contents.find(content => Boolean(content?.vectorContent))?.vectorContent || null;
|
|
642
|
+
|
|
643
|
+
if (this.tileset.options.contentLoader) {
|
|
644
|
+
await this.tileset.options.contentLoader(this);
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
this.contentState = TILE_CONTENT_STATE.READY;
|
|
648
|
+
this._onContentLoaded();
|
|
649
|
+
this._updateContentEntriesAfterLoad(loadResult);
|
|
650
|
+
return loadResult;
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
/**
|
|
654
|
+
* Requests and installs this tile's lazy child-header group through its source.
|
|
655
|
+
*
|
|
656
|
+
* The same request scheduler and SSE/foveated priority value used for render content are reused
|
|
657
|
+
* for subtree metadata. Repeated traversal frames share one promise, and scheduler cancellation
|
|
658
|
+
* restores the unloaded state so a later frame can retry.
|
|
659
|
+
*
|
|
660
|
+
* @param frameState - View state that made the subtree eligible for refinement.
|
|
661
|
+
* @returns Source load result, or an unloaded result if scheduling was cancelled.
|
|
662
|
+
*/
|
|
663
|
+
async loadChildren(frameState: FrameState): Promise<TileChildrenLoadResult> {
|
|
664
|
+
if (!this.header.implicitSubtree || this.childrenState === 'ready') {
|
|
665
|
+
return {loaded: false, tileCount: 0, childSubtreeCount: 0};
|
|
666
|
+
}
|
|
667
|
+
if (this._childrenPromise) {
|
|
668
|
+
return await this._childrenPromise;
|
|
669
|
+
}
|
|
670
|
+
if (!this.tileset.source.loadTileChildren) {
|
|
671
|
+
throw new Error('Tileset source does not support lazy tile children');
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
this.childrenState = 'loading';
|
|
675
|
+
this._childrenPromise = this._loadChildrenWithScheduler(frameState);
|
|
676
|
+
try {
|
|
677
|
+
return await this._childrenPromise;
|
|
678
|
+
} finally {
|
|
679
|
+
this._childrenPromise = null;
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
/**
|
|
684
|
+
* Runs one lazy child-header load after obtaining a scheduler token.
|
|
685
|
+
*
|
|
686
|
+
* @param frameState - View state forwarded to the source implementation.
|
|
687
|
+
* @returns Source load result.
|
|
688
|
+
*/
|
|
689
|
+
private async _loadChildrenWithScheduler(
|
|
690
|
+
frameState: FrameState
|
|
691
|
+
): Promise<TileChildrenLoadResult> {
|
|
692
|
+
const requestToken = await this.tileset._requestScheduler.scheduleRequest(
|
|
693
|
+
`${this.id}:implicit-subtree`,
|
|
694
|
+
this._getChildrenPriority.bind(this)
|
|
695
|
+
);
|
|
696
|
+
if (!requestToken) {
|
|
697
|
+
this.childrenState = 'unloaded';
|
|
698
|
+
return {loaded: false, tileCount: 0, childSubtreeCount: 0};
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
try {
|
|
702
|
+
const loadResult = await this.tileset.source.loadTileChildren!(this, frameState);
|
|
703
|
+
this.childrenState = loadResult.loaded ? 'ready' : 'unloaded';
|
|
704
|
+
return loadResult;
|
|
705
|
+
} catch (error) {
|
|
706
|
+
this.childrenState = 'failed';
|
|
707
|
+
throw error;
|
|
408
708
|
} finally {
|
|
409
709
|
requestToken.done();
|
|
410
710
|
}
|
|
411
711
|
}
|
|
412
712
|
|
|
713
|
+
/**
|
|
714
|
+
* Replaces a contentless implicit placeholder with its materialized subtree-root header.
|
|
715
|
+
*
|
|
716
|
+
* The placeholder transform is preserved when the subtree omits a root transform. An explicit
|
|
717
|
+
* subtree-root transform replaces it and is recomposed against the runtime parent. Content,
|
|
718
|
+
* metadata, bounds, and raw geometric error are reinitialized from the available header.
|
|
719
|
+
*
|
|
720
|
+
* @param materializedHeader - Available root header produced from one subtree resource.
|
|
721
|
+
*/
|
|
722
|
+
applyImplicitSubtreeHeader(materializedHeader: Record<string, any>): void {
|
|
723
|
+
const existingTransform = this.header.transform;
|
|
724
|
+
const existingTransformMatrix = this.header.transformMatrix;
|
|
725
|
+
const hasMaterializedTransform = materializedHeader.transform !== undefined;
|
|
726
|
+
this.header = {
|
|
727
|
+
...this.header,
|
|
728
|
+
...materializedHeader,
|
|
729
|
+
transform: hasMaterializedTransform ? materializedHeader.transform : existingTransform,
|
|
730
|
+
transformMatrix: hasMaterializedTransform
|
|
731
|
+
? materializedHeader.transformMatrix
|
|
732
|
+
: existingTransformMatrix,
|
|
733
|
+
implicitSubtree: undefined
|
|
734
|
+
};
|
|
735
|
+
if (hasMaterializedTransform) {
|
|
736
|
+
this._initializeTransforms(this.header);
|
|
737
|
+
}
|
|
738
|
+
this.refine = this._getRefine(materializedHeader.refine);
|
|
739
|
+
this.type = materializedHeader.type;
|
|
740
|
+
this.contentUrl = materializedHeader.contentUrl;
|
|
741
|
+
this.contentUrls =
|
|
742
|
+
materializedHeader.contentUrls ||
|
|
743
|
+
(materializedHeader.contentUrl ? [materializedHeader.contentUrl] : []);
|
|
744
|
+
this._initializeMetadata(this.header);
|
|
745
|
+
this._initializeLodMetric(materializedHeader);
|
|
746
|
+
this._updateLodMetricScale();
|
|
747
|
+
this._initializeBoundingVolumes(this.header);
|
|
748
|
+
this._boundingBox = undefined;
|
|
749
|
+
// Use the merged header so inherited viewer-request-volume metadata remains available when a
|
|
750
|
+
// materialized implicit tile has no render content of its own.
|
|
751
|
+
this._initializeContent(this.header);
|
|
752
|
+
this._initializeContentEntries(this.header);
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
/** Refreshes tile and content metadata from a source or materialized tile header. */
|
|
756
|
+
private _initializeMetadata(header: Record<string, any>): void {
|
|
757
|
+
this.metadata = header.metadata || null;
|
|
758
|
+
const contentHeaders = Array.isArray(header.content) ? header.content : [header.content];
|
|
759
|
+
this.contentMetadata = contentHeaders
|
|
760
|
+
.filter(Boolean)
|
|
761
|
+
.map(contentHeader => contentHeader.metadata || null);
|
|
762
|
+
const firstContentHeader = contentHeaders.find(Boolean) || {};
|
|
763
|
+
const groupReference = firstContentHeader.group;
|
|
764
|
+
const group =
|
|
765
|
+
typeof groupReference === 'number'
|
|
766
|
+
? this.tileset.groups?.[groupReference] || null
|
|
767
|
+
: groupReference || null;
|
|
768
|
+
const metadataContext: Tile3DMetadataContext = {
|
|
769
|
+
tileset: this.tileset.metadata || null,
|
|
770
|
+
group,
|
|
771
|
+
tile: this.metadata,
|
|
772
|
+
content: this.contentMetadata[0] || null,
|
|
773
|
+
subtree: header.implicitMetadata || null
|
|
774
|
+
};
|
|
775
|
+
if (this.tileset.groups) {
|
|
776
|
+
metadataContext.groups = this.tileset.groups;
|
|
777
|
+
}
|
|
778
|
+
this.metadataContext = metadataContext;
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
/** Updates payload and renderability while retaining immutable metadata descriptors. */
|
|
782
|
+
private _updateContentEntriesAfterLoad(loadResult: TileContentLoadResult): void {
|
|
783
|
+
if (loadResult.contentEntries) {
|
|
784
|
+
this.contentEntries = loadResult.contentEntries.map((entry, index) => ({
|
|
785
|
+
...entry,
|
|
786
|
+
index,
|
|
787
|
+
boundingVolume:
|
|
788
|
+
entry.boundingVolume || this._contentBoundingVolumes[index] || this.boundingVolume,
|
|
789
|
+
payload: entry.payload ?? this.contents[index] ?? null,
|
|
790
|
+
featureIds: entry.featureIds?.length
|
|
791
|
+
? entry.featureIds
|
|
792
|
+
: getTile3DFeatureIdSets(entry.payload ?? this.contents[index]),
|
|
793
|
+
renderable: Boolean(
|
|
794
|
+
entry.renderable !== false &&
|
|
795
|
+
(entry.payload ?? this.contents[index]) &&
|
|
796
|
+
!isNestedTilesetPayload(entry.payload ?? this.contents[index])
|
|
797
|
+
)
|
|
798
|
+
}));
|
|
799
|
+
this.featureIdSets = this.contentEntries.flatMap(entry => entry.featureIds);
|
|
800
|
+
return;
|
|
801
|
+
}
|
|
802
|
+
this.contentEntries = this.contentEntries.map((entry, index) => ({
|
|
803
|
+
...entry,
|
|
804
|
+
payload: this.contents[index] ?? null,
|
|
805
|
+
featureIds: getTile3DFeatureIdSets(this.contents[index]),
|
|
806
|
+
renderable: Boolean(this.contents[index]) && !isNestedTilesetPayload(this.contents[index])
|
|
807
|
+
}));
|
|
808
|
+
this.featureIdSets = this.contentEntries.flatMap(entry => entry.featureIds);
|
|
809
|
+
}
|
|
810
|
+
|
|
811
|
+
/** Returns one ordered content descriptor, or null when the index is out of range. */
|
|
812
|
+
getContentEntry(index: number): Tile3DContent | null {
|
|
813
|
+
return this.contentEntries[index] || null;
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
/** Returns whether an ordered content entry currently has renderable payload. */
|
|
817
|
+
isContentRenderable(index: number): boolean {
|
|
818
|
+
return Boolean(this.contentEntries[index]?.renderable);
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
/** Initializes additive renderer-neutral descriptors from a normalized tile header. */
|
|
822
|
+
private _initializeContentEntries(tileHeader: Record<string, any>): void {
|
|
823
|
+
const headers = Array.isArray(tileHeader.content)
|
|
824
|
+
? tileHeader.content
|
|
825
|
+
: tileHeader.content
|
|
826
|
+
? [tileHeader.content]
|
|
827
|
+
: [];
|
|
828
|
+
this.contentEntries = headers.map((contentHeader, index) => {
|
|
829
|
+
const normalizedContentHeader = contentHeader || {};
|
|
830
|
+
return {
|
|
831
|
+
index,
|
|
832
|
+
uri: this.contentUrls[index] || normalizedContentHeader.uri || normalizedContentHeader.url,
|
|
833
|
+
group: normalizedContentHeader.group,
|
|
834
|
+
type: normalizedContentHeader.type,
|
|
835
|
+
payload: null,
|
|
836
|
+
metadata: normalizedContentHeader.metadata || null,
|
|
837
|
+
boundingVolume: this._contentBoundingVolumes[index] || this.boundingVolume,
|
|
838
|
+
featureIds: this._getFeatureIdSets(normalizedContentHeader),
|
|
839
|
+
renderable: false
|
|
840
|
+
};
|
|
841
|
+
});
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
/** Extracts renderer-neutral feature-id declarations without decoding property values. */
|
|
845
|
+
private _getFeatureIdSets(contentHeader: Record<string, any>): Tile3DFeatureIdSet[] {
|
|
846
|
+
return getTile3DFeatureIdSets(contentHeader);
|
|
847
|
+
}
|
|
848
|
+
|
|
413
849
|
// Unloads the tile's content.
|
|
414
850
|
unloadContent() {
|
|
415
|
-
|
|
416
|
-
|
|
851
|
+
for (const content of this.contents.length ? this.contents : [this.content]) {
|
|
852
|
+
if (content && content.destroy) {
|
|
853
|
+
content.destroy();
|
|
854
|
+
}
|
|
417
855
|
}
|
|
856
|
+
this.contents = [];
|
|
418
857
|
this.content = null;
|
|
858
|
+
this.vectorContent = null;
|
|
859
|
+
this.contentEntries = this.contentEntries.map(entry => ({
|
|
860
|
+
...entry,
|
|
861
|
+
payload: null,
|
|
862
|
+
renderable: false
|
|
863
|
+
}));
|
|
864
|
+
this.featureIdSets = this.contentEntries.flatMap(entry => entry.featureIds);
|
|
419
865
|
if (this.header.content && this.header.content.destroy) {
|
|
420
866
|
this.header.content.destroy();
|
|
421
867
|
}
|
|
@@ -450,6 +896,7 @@ export class Tile3D {
|
|
|
450
896
|
|
|
451
897
|
this._distanceToCamera = this.distanceToTile(frameState);
|
|
452
898
|
this._screenSpaceError = this.getScreenSpaceError(frameState, false);
|
|
899
|
+
this._updateRequestPriorityMetrics(frameState);
|
|
453
900
|
this._visibilityPlaneMask = this.visibility(frameState, parentVisibilityPlaneMask); // Use parent's plane mask to speed up visibility test
|
|
454
901
|
this._visible = this._visibilityPlaneMask !== CullingVolume.MASK_OUTSIDE;
|
|
455
902
|
this._inRequestVolume = this.insideViewerRequestVolume(frameState);
|
|
@@ -458,6 +905,74 @@ export class Tile3D {
|
|
|
458
905
|
this.viewportIds = viewportIds;
|
|
459
906
|
}
|
|
460
907
|
|
|
908
|
+
/**
|
|
909
|
+
* Updates progressive-resolution and foveated request metrics for a 3D Tiles traversal frame.
|
|
910
|
+
*
|
|
911
|
+
* I3S retains its format-specific screen-threshold ordering and is explicitly isolated from
|
|
912
|
+
* geometric-error scheduling. Orthographic viewports still receive progressive coverage, but
|
|
913
|
+
* angular foveation is disabled because an orthographic frustum has no perspective field of view.
|
|
914
|
+
*
|
|
915
|
+
* @param frameState - Current camera and viewport measurements.
|
|
916
|
+
*/
|
|
917
|
+
_updateRequestPriorityMetrics(frameState: FrameState): void {
|
|
918
|
+
if (this.tileset.type !== TILESET_TYPE.TILES3D) {
|
|
919
|
+
this._screenSpaceErrorProgressiveResolution = this._screenSpaceError;
|
|
920
|
+
this._priorityProgressiveResolution = false;
|
|
921
|
+
this._foveatedFactor = 0;
|
|
922
|
+
this.priorityDeferred = false;
|
|
923
|
+
return;
|
|
924
|
+
}
|
|
925
|
+
|
|
926
|
+
const options = this.tileset.options;
|
|
927
|
+
const progressiveResolutionHeightFraction = options.progressiveResolutionHeightFraction;
|
|
928
|
+
this._screenSpaceErrorProgressiveResolution = this.getScreenSpaceError(
|
|
929
|
+
frameState,
|
|
930
|
+
false,
|
|
931
|
+
progressiveResolutionHeightFraction
|
|
932
|
+
);
|
|
933
|
+
this._priorityProgressiveResolution = isProgressiveResolutionPriority(
|
|
934
|
+
this._screenSpaceErrorProgressiveResolution,
|
|
935
|
+
this.parent?._screenSpaceErrorProgressiveResolution,
|
|
936
|
+
this.tileset.memoryAdjustedScreenSpaceError,
|
|
937
|
+
progressiveResolutionHeightFraction
|
|
938
|
+
);
|
|
939
|
+
|
|
940
|
+
if (
|
|
941
|
+
!options.foveatedScreenSpaceError ||
|
|
942
|
+
frameState.viewport?.orthographic ||
|
|
943
|
+
!frameState.camera?.position ||
|
|
944
|
+
!frameState.camera?.direction
|
|
945
|
+
) {
|
|
946
|
+
this._foveatedFactor = 0;
|
|
947
|
+
this.priorityDeferred = false;
|
|
948
|
+
return;
|
|
949
|
+
}
|
|
950
|
+
|
|
951
|
+
this._foveatedFactor = calculateFoveatedFactor(this.boundingVolume, frameState.camera);
|
|
952
|
+
const traverser = this.tileset._traverser;
|
|
953
|
+
const skipLevelOfDetail =
|
|
954
|
+
traverser.options.skipLevelOfDetail && !traverser.disableSkipLevelOfDetail;
|
|
955
|
+
const deferralEligible = isFoveatedRequestDeferred({
|
|
956
|
+
refinement: this.refine,
|
|
957
|
+
skipLevelOfDetail,
|
|
958
|
+
foveatedScreenSpaceError: options.foveatedScreenSpaceError,
|
|
959
|
+
foveatedConeSize: options.foveatedConeSize,
|
|
960
|
+
minimumScreenSpaceErrorRelaxation: options.foveatedMinimumScreenSpaceErrorRelaxation,
|
|
961
|
+
interpolationCallback: options.foveatedInterpolationCallback,
|
|
962
|
+
foveatedFactor: this._foveatedFactor,
|
|
963
|
+
verticalFieldOfView: frameState.camera.verticalFieldOfView,
|
|
964
|
+
screenSpaceError: this._screenSpaceError,
|
|
965
|
+
parentScreenSpaceError: this.parent?._screenSpaceError,
|
|
966
|
+
maximumScreenSpaceError: this.tileset.memoryAdjustedScreenSpaceError,
|
|
967
|
+
priorityProgressiveResolution: this._priorityProgressiveResolution
|
|
968
|
+
});
|
|
969
|
+
this.priorityDeferred = isFoveatedRequestDelayActive(
|
|
970
|
+
deferralEligible,
|
|
971
|
+
frameState.camera.timeSinceMovement,
|
|
972
|
+
options.foveatedTimeDelay
|
|
973
|
+
);
|
|
974
|
+
}
|
|
975
|
+
|
|
461
976
|
// Determines whether the tile's bounding volume intersects the culling volume.
|
|
462
977
|
// @param {FrameState} frameState The frame state.
|
|
463
978
|
// @param {Number} parentVisibilityPlaneMask The parent's plane mask to speed up the visibility check.
|
|
@@ -483,47 +998,28 @@ export class Tile3D {
|
|
|
483
998
|
return cullingVolume.computeVisibilityWithPlaneMask(boundingVolume, parentVisibilityPlaneMask);
|
|
484
999
|
}
|
|
485
1000
|
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
// tile's (not the content's) bounding volume intersects the culling volume?
|
|
509
|
-
const cullingVolume = frameState.cullingVolume;
|
|
510
|
-
const boundingVolume = tile.contentBoundingVolume;
|
|
511
|
-
|
|
512
|
-
const tileset = this.tileset;
|
|
513
|
-
const clippingPlanes = tileset.clippingPlanes;
|
|
514
|
-
if (defined(clippingPlanes) && clippingPlanes.enabled) {
|
|
515
|
-
const intersection = clippingPlanes.computeIntersectionWithBoundingVolume(
|
|
516
|
-
boundingVolume,
|
|
517
|
-
tileset.clippingPlanesOriginMatrix
|
|
518
|
-
);
|
|
519
|
-
this._isClipped = intersection !== Intersect.INSIDE;
|
|
520
|
-
if (intersection === Intersect.OUTSIDE) {
|
|
521
|
-
return Intersect.OUTSIDE;
|
|
522
|
-
}
|
|
523
|
-
}
|
|
524
|
-
|
|
525
|
-
return cullingVolume.computeVisibility(boundingVolume);
|
|
526
|
-
*/
|
|
1001
|
+
/**
|
|
1002
|
+
* Determines whether renderable content intersects the view and configured clipping planes.
|
|
1003
|
+
*
|
|
1004
|
+
* Clipping planes are render-only constraints; traversal continues to use the tile volume.
|
|
1005
|
+
*
|
|
1006
|
+
* @param frameState Current camera, culling, and optional clipping-plane state.
|
|
1007
|
+
* @returns The content visibility classification.
|
|
1008
|
+
*/
|
|
1009
|
+
contentVisibility(frameState: FrameState, contentIndex?: number): CullingResult {
|
|
1010
|
+
const contentVolumes =
|
|
1011
|
+
contentIndex === undefined
|
|
1012
|
+
? this._contentBoundingVolumes
|
|
1013
|
+
: this._contentBoundingVolumes[contentIndex]
|
|
1014
|
+
? [this._contentBoundingVolumes[contentIndex]]
|
|
1015
|
+
: [];
|
|
1016
|
+
return getContentVisibility(
|
|
1017
|
+
contentVolumes,
|
|
1018
|
+
this.boundingVolume,
|
|
1019
|
+
frameState.cullingVolume,
|
|
1020
|
+
this._visibilityPlaneMask,
|
|
1021
|
+
frameState.clippingPlanes
|
|
1022
|
+
);
|
|
527
1023
|
}
|
|
528
1024
|
|
|
529
1025
|
/**
|
|
@@ -581,6 +1077,10 @@ export class Tile3D {
|
|
|
581
1077
|
|
|
582
1078
|
/** Initializes the tile's LOD metric from its header or its nearest available ancestor. */
|
|
583
1079
|
_initializeLodMetric(header: {[key: string]: any}): void {
|
|
1080
|
+
this._scaleGeometricError =
|
|
1081
|
+
typeof header._scaleGeometricError === 'boolean'
|
|
1082
|
+
? header._scaleGeometricError
|
|
1083
|
+
: (this.parent?._scaleGeometricError ?? true);
|
|
584
1084
|
if ('lodMetricType' in header) {
|
|
585
1085
|
this.lodMetricType = header.lodMetricType;
|
|
586
1086
|
} else {
|
|
@@ -637,7 +1137,7 @@ export class Tile3D {
|
|
|
637
1137
|
* I3S `maxScreenThreshold` is already a screen-space metric and must not be transform-scaled.
|
|
638
1138
|
*/
|
|
639
1139
|
_updateLodMetricScale(): void {
|
|
640
|
-
if (this.lodMetricType !== LOD_METRIC_TYPE.GEOMETRIC_ERROR) {
|
|
1140
|
+
if (this.lodMetricType !== LOD_METRIC_TYPE.GEOMETRIC_ERROR || !this._scaleGeometricError) {
|
|
641
1141
|
this.lodMetricValue = this._unscaledLodMetricValue;
|
|
642
1142
|
return;
|
|
643
1143
|
}
|
|
@@ -649,6 +1149,7 @@ export class Tile3D {
|
|
|
649
1149
|
|
|
650
1150
|
_initializeBoundingVolumes(tileHeader) {
|
|
651
1151
|
this._contentBoundingVolume = null;
|
|
1152
|
+
this._contentBoundingVolumeHeaders = [];
|
|
652
1153
|
this._viewerRequestVolume = null;
|
|
653
1154
|
|
|
654
1155
|
this._updateBoundingVolume(tileHeader);
|
|
@@ -657,6 +1158,7 @@ export class Tile3D {
|
|
|
657
1158
|
_initializeContent(tileHeader) {
|
|
658
1159
|
// Empty tile by default
|
|
659
1160
|
this.content = {_tileset: this.tileset, _tile: this};
|
|
1161
|
+
this.contents = [];
|
|
660
1162
|
this.hasEmptyContent = true;
|
|
661
1163
|
this.contentState = TILE_CONTENT_STATE.UNLOADED;
|
|
662
1164
|
|
|
@@ -664,7 +1166,7 @@ export class Tile3D {
|
|
|
664
1166
|
// This is `false` until the tile's content is loaded.
|
|
665
1167
|
this.hasTilesetContent = false;
|
|
666
1168
|
|
|
667
|
-
if (tileHeader.contentUrl) {
|
|
1169
|
+
if (tileHeader.contentUrl || tileHeader.contentUrls?.length) {
|
|
668
1170
|
this.content = null;
|
|
669
1171
|
this.hasEmptyContent = false;
|
|
670
1172
|
}
|
|
@@ -679,6 +1181,10 @@ export class Tile3D {
|
|
|
679
1181
|
this._distanceToCamera = 0;
|
|
680
1182
|
this._centerZDepth = 0;
|
|
681
1183
|
this._screenSpaceError = 0;
|
|
1184
|
+
this._screenSpaceErrorProgressiveResolution = 0;
|
|
1185
|
+
this._priorityProgressiveResolution = false;
|
|
1186
|
+
this._foveatedFactor = 0;
|
|
1187
|
+
this.priorityDeferred = false;
|
|
682
1188
|
this._visibilityPlaneMask = CullingVolume.MASK_INDETERMINATE;
|
|
683
1189
|
this._visible = undefined;
|
|
684
1190
|
this._inRequestVolume = false;
|
|
@@ -700,19 +1206,31 @@ export class Tile3D {
|
|
|
700
1206
|
return refine || (this.parent && this.parent.refine) || TILE_REFINEMENT.REPLACE;
|
|
701
1207
|
}
|
|
702
1208
|
|
|
703
|
-
|
|
704
|
-
|
|
1209
|
+
/**
|
|
1210
|
+
* Returns whether loaded content is an external tileset.
|
|
1211
|
+
*
|
|
1212
|
+
* Parsed shape is authoritative because valid external resources may be extensionless, signed,
|
|
1213
|
+
* or use a misleading filename. This method is called only after source loading has populated
|
|
1214
|
+
* `content`.
|
|
1215
|
+
*/
|
|
1216
|
+
_isTileset(): boolean {
|
|
1217
|
+
return (this.contents.length ? this.contents : [this.content]).some(
|
|
1218
|
+
content => content?.shape === 'tileset3d'
|
|
1219
|
+
);
|
|
705
1220
|
}
|
|
706
1221
|
|
|
707
1222
|
_onContentLoaded() {
|
|
708
1223
|
// Vector and Geometry tile rendering do not support the skip LOD optimization.
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
1224
|
+
const contents = this.contents.length ? this.contents : [this.content];
|
|
1225
|
+
for (const content of contents) {
|
|
1226
|
+
switch (content && content.type) {
|
|
1227
|
+
case 'vctr':
|
|
1228
|
+
case 'geom':
|
|
1229
|
+
// @ts-ignore
|
|
1230
|
+
this.tileset._traverser.disableSkipLevelOfDetail = true;
|
|
1231
|
+
break;
|
|
1232
|
+
default:
|
|
1233
|
+
}
|
|
716
1234
|
}
|
|
717
1235
|
|
|
718
1236
|
// The content may be tileset json
|
|
@@ -724,38 +1242,51 @@ export class Tile3D {
|
|
|
724
1242
|
}
|
|
725
1243
|
|
|
726
1244
|
_updateBoundingVolume(header) {
|
|
1245
|
+
this._boundingBox = undefined;
|
|
727
1246
|
// Update the bounding volumes
|
|
728
1247
|
this.boundingVolume = createBoundingVolume(
|
|
729
1248
|
header.boundingVolume,
|
|
730
1249
|
this.computedTransform,
|
|
731
|
-
this.boundingVolume
|
|
1250
|
+
this.boundingVolume,
|
|
1251
|
+
this._initialTransform
|
|
732
1252
|
);
|
|
733
1253
|
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
}
|
|
738
|
-
|
|
739
|
-
// TODO Cesium specific
|
|
740
|
-
// Non-leaf tiles may have a content bounding-volume, which is a tight-fit bounding volume
|
|
741
|
-
// around only the features in the tile. This box is useful for culling for rendering,
|
|
742
|
-
// but not for culling for traversing the tree since it does not guarantee spatial coherence, i.e.,
|
|
743
|
-
// since it only bounds features in the tile, not the entire tile, children may be
|
|
744
|
-
// outside of this box.
|
|
745
|
-
if (content.boundingVolume) {
|
|
746
|
-
this._contentBoundingVolume = createBoundingVolume(
|
|
747
|
-
content.boundingVolume,
|
|
748
|
-
this.computedTransform,
|
|
749
|
-
this._contentBoundingVolume
|
|
750
|
-
);
|
|
751
|
-
}
|
|
1254
|
+
// Viewer request volumes constrain traversal, not just render content. A contentless implicit
|
|
1255
|
+
// connector can still own an inherited request volume, so initialize it before the
|
|
1256
|
+
// content-specific early return below.
|
|
752
1257
|
if (header.viewerRequestVolume) {
|
|
753
1258
|
this._viewerRequestVolume = createBoundingVolume(
|
|
754
1259
|
header.viewerRequestVolume,
|
|
755
1260
|
this.computedTransform,
|
|
756
|
-
this._viewerRequestVolume
|
|
1261
|
+
this._viewerRequestVolume,
|
|
1262
|
+
this._initialTransform
|
|
757
1263
|
);
|
|
758
1264
|
}
|
|
1265
|
+
|
|
1266
|
+
const content = header.content;
|
|
1267
|
+
if (content) {
|
|
1268
|
+
this._contentBoundingVolumeHeaders = Array.isArray(content) ? content : [content];
|
|
1269
|
+
}
|
|
1270
|
+
const contentHeaders = content
|
|
1271
|
+
? Array.isArray(content)
|
|
1272
|
+
? content
|
|
1273
|
+
: [content]
|
|
1274
|
+
: this._contentBoundingVolumeHeaders;
|
|
1275
|
+
// Preserve source order: an unbounded content entry uses the tile volume at its
|
|
1276
|
+
// corresponding index rather than being appended after bounded sibling entries.
|
|
1277
|
+
this._contentBoundingVolumes = contentHeaders.length
|
|
1278
|
+
? contentHeaders.map(headerEntry =>
|
|
1279
|
+
headerEntry?.boundingVolume
|
|
1280
|
+
? createBoundingVolume(
|
|
1281
|
+
headerEntry.boundingVolume,
|
|
1282
|
+
this.computedTransform,
|
|
1283
|
+
undefined,
|
|
1284
|
+
this._initialTransform
|
|
1285
|
+
)
|
|
1286
|
+
: this.boundingVolume
|
|
1287
|
+
)
|
|
1288
|
+
: [this.boundingVolume];
|
|
1289
|
+
this._contentBoundingVolume = this._contentBoundingVolumes[0] || null;
|
|
759
1290
|
}
|
|
760
1291
|
|
|
761
1292
|
/**
|