@loaders.gl/tiles 5.0.0-alpha.5 → 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 +5122 -2641
- package/dist/dist.min.js +4 -2
- package/dist/index.cjs +1670 -403
- 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/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.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 +29 -5
- package/dist/tileset-3d/common/tile-3d.d.ts.map +1 -1
- package/dist/tileset-3d/common/tile-3d.js +151 -20
- package/dist/tileset-3d/common/tile-3d.js.map +1 -1
- package/dist/tileset-3d/common/tileset-3d.d.ts +44 -2
- package/dist/tileset-3d/common/tileset-3d.d.ts.map +1 -1
- package/dist/tileset-3d/common/tileset-3d.js +31 -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 +11 -8
- 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 +14 -0
- package/dist/tileset-3d/common/tileset-traverser.d.ts.map +1 -1
- package/dist/tileset-3d/common/tileset-traverser.js +21 -5
- package/dist/tileset-3d/common/tileset-traverser.js.map +1 -1
- package/dist/tileset-3d/format-3d-tiles/implicit-tiling.d.ts +47 -1
- package/dist/tileset-3d/format-3d-tiles/implicit-tiling.d.ts.map +1 -1
- package/dist/tileset-3d/format-3d-tiles/implicit-tiling.js +327 -30
- package/dist/tileset-3d/format-3d-tiles/implicit-tiling.js.map +1 -1
- package/dist/tileset-3d/format-3d-tiles/tiles-3d-source.d.ts +34 -2
- 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 +70 -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 +216 -4
- package/dist/tileset-3d/format-3d-tiles/tileset-3d-traverser.js.map +1 -1
- package/dist/tileset-3d/format-i3s/i3s-source.d.ts +5 -3
- package/dist/tileset-3d/format-i3s/i3s-source.d.ts.map +1 -1
- package/dist/tileset-3d/format-i3s/i3s-source.js +14 -7
- package/dist/tileset-3d/format-i3s/i3s-source.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 +14 -13
- 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/indexed-archive-tileset-source.ts +4 -9
- package/src/tileset-3d/common/tile-3d-contracts.ts +115 -0
- package/src/tileset-3d/common/tile-3d.ts +178 -20
- package/src/tileset-3d/common/tileset-3d.ts +63 -3
- package/src/tileset-3d/common/tileset-observability.ts +100 -0
- package/src/tileset-3d/common/tileset-source.ts +11 -12
- package/src/tileset-3d/common/tileset-traverser.ts +32 -5
- package/src/tileset-3d/format-3d-tiles/implicit-tiling.ts +481 -34
- package/src/tileset-3d/format-3d-tiles/tiles-3d-source.ts +390 -17
- package/src/tileset-3d/format-3d-tiles/tileset-3d-traverser.ts +243 -4
- package/src/tileset-3d/format-i3s/i3s-source.ts +25 -9
- 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
|
@@ -30,6 +30,242 @@ export class Tileset3DTraverser extends TilesetTraverser {
|
|
|
30
30
|
return true;
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
+
/**
|
|
34
|
+
* Runs normal replacement traversal or the dedicated skip-LOD traversal selected by options.
|
|
35
|
+
*
|
|
36
|
+
* @param root - Root of the runtime subtree to traverse.
|
|
37
|
+
* @param frameState - Current culling and LOD state.
|
|
38
|
+
*/
|
|
39
|
+
executeTraversal(root: Tile3D, frameState: FrameState): void {
|
|
40
|
+
if (!this.isSkipLevelOfDetailEnabled()) {
|
|
41
|
+
super.executeTraversal(root, frameState);
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
this.executeSkipTraversal(root, frameState);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Traverses visible replacement branches without requesting skipped intermediate content.
|
|
49
|
+
*
|
|
50
|
+
* Base-traversal tiles establish coarse coverage. Below that base, only final desired tiles and
|
|
51
|
+
* tiles that cross the configured depth/SSE thresholds are requested. Already available
|
|
52
|
+
* ancestors remain selectable while those requests stream in.
|
|
53
|
+
*
|
|
54
|
+
* @param root - Root of the runtime subtree to traverse.
|
|
55
|
+
* @param frameState - Current culling and LOD state.
|
|
56
|
+
*/
|
|
57
|
+
private executeSkipTraversal(root: Tile3D, frameState: FrameState): void {
|
|
58
|
+
const stack: Tile3D[] = [root];
|
|
59
|
+
root._selectionDepth = root.parent ? root.parent._selectionDepth : 1;
|
|
60
|
+
|
|
61
|
+
while (stack.length > 0) {
|
|
62
|
+
const tile = stack.pop() as Tile3D;
|
|
63
|
+
const parentRefines = !tile.parent || tile.parent._shouldRefine;
|
|
64
|
+
const shouldRefine =
|
|
65
|
+
this.canTraverse(tile, frameState) &&
|
|
66
|
+
this.updateAndPushSkipChildren(tile, frameState, stack) &&
|
|
67
|
+
parentRefines;
|
|
68
|
+
const stoppedRefining = !shouldRefine && parentRefines;
|
|
69
|
+
|
|
70
|
+
if (!tile.hasRenderContent) {
|
|
71
|
+
this.emptyTiles[tile.id] = tile;
|
|
72
|
+
this.loadTile(tile, frameState);
|
|
73
|
+
if (stoppedRefining) {
|
|
74
|
+
this.selectDesiredTile(tile, frameState);
|
|
75
|
+
}
|
|
76
|
+
} else if (tile.refine === TILE_REFINEMENT.ADD) {
|
|
77
|
+
this.loadTile(tile, frameState);
|
|
78
|
+
this.selectDesiredTile(tile, frameState);
|
|
79
|
+
} else if (tile.refine === TILE_REFINEMENT.REPLACE) {
|
|
80
|
+
if (this.isInBaseTraversal(tile, frameState)) {
|
|
81
|
+
this.loadTile(tile, frameState);
|
|
82
|
+
if (stoppedRefining) {
|
|
83
|
+
this.selectDesiredTile(tile, frameState);
|
|
84
|
+
}
|
|
85
|
+
} else if (stoppedRefining) {
|
|
86
|
+
this.loadTile(tile, frameState);
|
|
87
|
+
this.selectDesiredTile(tile, frameState);
|
|
88
|
+
} else if (this.hasReachedSkippingThreshold(tile, frameState)) {
|
|
89
|
+
this.loadTile(tile, frameState);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
this.touchTile(tile, frameState);
|
|
94
|
+
tile._shouldRefine = shouldRefine;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
this.completeTraversal(frameState);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Updates and pushes visible children for skip traversal without requiring sibling readiness.
|
|
102
|
+
*
|
|
103
|
+
* @param tile - Parent whose children are considered.
|
|
104
|
+
* @param frameState - Current culling and LOD state.
|
|
105
|
+
* @param stack - Depth-first traversal stack.
|
|
106
|
+
* @returns `true` when at least one visible child continues refinement.
|
|
107
|
+
*/
|
|
108
|
+
private updateAndPushSkipChildren(
|
|
109
|
+
tile: Tile3D,
|
|
110
|
+
frameState: FrameState,
|
|
111
|
+
stack: Tile3D[]
|
|
112
|
+
): boolean {
|
|
113
|
+
this.updateChildTiles(tile, frameState);
|
|
114
|
+
tile.children.sort(this.compareDistanceToCamera.bind(this));
|
|
115
|
+
|
|
116
|
+
let hasVisibleChild = false;
|
|
117
|
+
for (const child of tile.children) {
|
|
118
|
+
child._selectionDepth = tile.hasRenderContent
|
|
119
|
+
? tile._selectionDepth + 1
|
|
120
|
+
: tile._selectionDepth;
|
|
121
|
+
if (child.isVisibleAndInRequestVolume) {
|
|
122
|
+
stack.push(child);
|
|
123
|
+
hasVisibleChild = true;
|
|
124
|
+
} else if (this.options.loadSiblings) {
|
|
125
|
+
this.loadTile(child, frameState);
|
|
126
|
+
this.touchTile(child, frameState);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
return hasVisibleChild;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Determines whether a tile belongs to the coarse non-skipping portion of the traversal.
|
|
134
|
+
*
|
|
135
|
+
* @param tile - Tile considered for loading.
|
|
136
|
+
* @param frameState - Current traversal frame.
|
|
137
|
+
* @returns `true` when the tile should always be requested for fallback coverage.
|
|
138
|
+
*/
|
|
139
|
+
private isInBaseTraversal(tile: Tile3D, frameState: FrameState): boolean {
|
|
140
|
+
if (this.options.immediatelyLoadDesiredLevelOfDetail) {
|
|
141
|
+
return false;
|
|
142
|
+
}
|
|
143
|
+
if (!this.findNearestRequestedOrLoadedAncestor(tile, frameState)) {
|
|
144
|
+
return true;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const configuredBaseScreenSpaceError = Number.isFinite(this.options.baseScreenSpaceError)
|
|
148
|
+
? Math.max(this.options.baseScreenSpaceError, 0)
|
|
149
|
+
: 1024;
|
|
150
|
+
const baseScreenSpaceError = Math.max(
|
|
151
|
+
configuredBaseScreenSpaceError,
|
|
152
|
+
tile.tileset.memoryAdjustedScreenSpaceError
|
|
153
|
+
);
|
|
154
|
+
if (tile._screenSpaceError === 0 && tile.parent) {
|
|
155
|
+
return tile.parent._screenSpaceError > baseScreenSpaceError;
|
|
156
|
+
}
|
|
157
|
+
return tile._screenSpaceError > baseScreenSpaceError;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Determines whether a skipped branch has crossed its configured request threshold.
|
|
162
|
+
*
|
|
163
|
+
* @param tile - Tile considered for loading.
|
|
164
|
+
* @param frameState - Current traversal frame.
|
|
165
|
+
* @returns `true` when the tile must be requested instead of skipped.
|
|
166
|
+
*/
|
|
167
|
+
private hasReachedSkippingThreshold(tile: Tile3D, frameState: FrameState): boolean {
|
|
168
|
+
if (this.options.immediatelyLoadDesiredLevelOfDetail) {
|
|
169
|
+
return false;
|
|
170
|
+
}
|
|
171
|
+
const progressiveResolutionLeaf =
|
|
172
|
+
tile._priorityProgressiveResolution &&
|
|
173
|
+
tile._screenSpaceErrorProgressiveResolution <= tile.tileset.memoryAdjustedScreenSpaceError &&
|
|
174
|
+
Boolean(
|
|
175
|
+
tile.parent &&
|
|
176
|
+
tile.parent._screenSpaceErrorProgressiveResolution >
|
|
177
|
+
tile.tileset.memoryAdjustedScreenSpaceError
|
|
178
|
+
);
|
|
179
|
+
if (progressiveResolutionLeaf) {
|
|
180
|
+
return true;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const ancestor = this.findNearestRequestedOrLoadedAncestor(tile, frameState);
|
|
184
|
+
if (!ancestor) {
|
|
185
|
+
return false;
|
|
186
|
+
}
|
|
187
|
+
const skipScreenSpaceErrorFactor =
|
|
188
|
+
Number.isFinite(this.options.skipScreenSpaceErrorFactor) &&
|
|
189
|
+
this.options.skipScreenSpaceErrorFactor > 0
|
|
190
|
+
? this.options.skipScreenSpaceErrorFactor
|
|
191
|
+
: 16;
|
|
192
|
+
const skipLevels = Number.isFinite(this.options.skipLevels)
|
|
193
|
+
? Math.max(Math.floor(this.options.skipLevels), 0)
|
|
194
|
+
: 1;
|
|
195
|
+
return (
|
|
196
|
+
tile._screenSpaceError < ancestor._screenSpaceError / skipScreenSpaceErrorFactor &&
|
|
197
|
+
tile.depth > ancestor.depth + skipLevels
|
|
198
|
+
);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Finds the nearest ancestor whose render content is loaded, loading, or requested this frame.
|
|
203
|
+
*
|
|
204
|
+
* @param tile - Descendant whose request threshold needs an anchor.
|
|
205
|
+
* @param frameState - Current traversal frame.
|
|
206
|
+
* @returns The nearest request anchor, or `null` when none exists.
|
|
207
|
+
*/
|
|
208
|
+
private findNearestRequestedOrLoadedAncestor(
|
|
209
|
+
tile: Tile3D,
|
|
210
|
+
frameState: FrameState
|
|
211
|
+
): Tile3D | null {
|
|
212
|
+
let ancestor = tile.parent;
|
|
213
|
+
while (ancestor) {
|
|
214
|
+
const contentIsUsableAnchor =
|
|
215
|
+
ancestor.hasRenderContent &&
|
|
216
|
+
!ancestor.contentFailed &&
|
|
217
|
+
(!ancestor.hasUnloadedContent || ancestor._requestedFrame === frameState.frameNumber);
|
|
218
|
+
if (contentIsUsableAnchor) {
|
|
219
|
+
return ancestor;
|
|
220
|
+
}
|
|
221
|
+
ancestor = ancestor.parent;
|
|
222
|
+
}
|
|
223
|
+
return null;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Selects the desired tile, its nearest ready ancestor, or nearby ready descendants.
|
|
228
|
+
*
|
|
229
|
+
* @param tile - Tile that ended the desired refinement branch.
|
|
230
|
+
* @param frameState - Current culling state.
|
|
231
|
+
*/
|
|
232
|
+
private selectDesiredTile(tile: Tile3D, frameState: FrameState): void {
|
|
233
|
+
let fallbackTile: Tile3D | null = tile;
|
|
234
|
+
while (fallbackTile) {
|
|
235
|
+
if (this.shouldSelectTile(fallbackTile, frameState)) {
|
|
236
|
+
this.selectTile(fallbackTile, frameState);
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
fallbackTile = fallbackTile.parent;
|
|
240
|
+
}
|
|
241
|
+
this.selectLoadedDescendants(tile, frameState);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* Selects ready descendants near an unavailable desired tile to reduce temporary empty regions.
|
|
246
|
+
*
|
|
247
|
+
* @param root - Unavailable tile whose descendants are searched.
|
|
248
|
+
* @param frameState - Current culling state.
|
|
249
|
+
*/
|
|
250
|
+
private selectLoadedDescendants(root: Tile3D, frameState: FrameState): void {
|
|
251
|
+
const stack: Array<{tile: Tile3D; depth: number}> = [{tile: root, depth: 0}];
|
|
252
|
+
while (stack.length > 0) {
|
|
253
|
+
const {tile, depth} = stack.pop() as {tile: Tile3D; depth: number};
|
|
254
|
+
for (const child of tile.children) {
|
|
255
|
+
this.updateTile(child, frameState);
|
|
256
|
+
if (!child.isVisibleAndInRequestVolume) {
|
|
257
|
+
continue;
|
|
258
|
+
}
|
|
259
|
+
if (child.contentAvailable) {
|
|
260
|
+
this.selectTile(child, frameState);
|
|
261
|
+
this.touchTile(child, frameState);
|
|
262
|
+
} else if (depth < 1) {
|
|
263
|
+
stack.push({tile: child, depth: depth + 1});
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
33
269
|
compareDistanceToCamera(a, b) {
|
|
34
270
|
// Sort by farthest child first since this is going on a stack
|
|
35
271
|
return b._distanceToCamera === 0 && a._distanceToCamera === 0
|
|
@@ -38,10 +274,12 @@ export class Tileset3DTraverser extends TilesetTraverser {
|
|
|
38
274
|
}
|
|
39
275
|
|
|
40
276
|
/**
|
|
41
|
-
* Starts an implicit subtree request
|
|
277
|
+
* Starts an implicit subtree request after culling and request-volume eligibility.
|
|
42
278
|
*
|
|
43
|
-
* The
|
|
44
|
-
*
|
|
279
|
+
* The first subtree of an implicit hierarchy materializes independently of SSE because its
|
|
280
|
+
* placeholder has no render content. Deeper subtree boundaries remain SSE-gated. The current
|
|
281
|
+
* parent remains the traversal boundary while metadata is loading, which preserves REPLACE
|
|
282
|
+
* coverage and ADD accumulation. Request scheduling reads the tile's existing
|
|
45
283
|
* progressive-resolution and foveated metrics, so subtree metadata follows the same priority
|
|
46
284
|
* policy as render content. A completion notification lets the owning tileset publish the newly
|
|
47
285
|
* materialized traversal result on the next update.
|
|
@@ -50,10 +288,11 @@ export class Tileset3DTraverser extends TilesetTraverser {
|
|
|
50
288
|
* @param frameState - Current culling and LOD state.
|
|
51
289
|
*/
|
|
52
290
|
updateChildTiles(tile: Tile3D, frameState: FrameState): void {
|
|
291
|
+
const isImplicitHierarchyRoot = tile.header.implicitSubtree?.coordinates.level === 0;
|
|
53
292
|
if (
|
|
54
293
|
tile.hasUnloadedChildren &&
|
|
55
294
|
tile.isVisibleAndInRequestVolume &&
|
|
56
|
-
this.shouldRefine(tile, frameState)
|
|
295
|
+
(isImplicitHierarchyRoot || this.shouldRefine(tile, frameState))
|
|
57
296
|
) {
|
|
58
297
|
const requestKey = `${frameState.viewport.id}:${tile.id}`;
|
|
59
298
|
if (!this.pendingImplicitSubtrees.has(requestKey)) {
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
import {path} from '@loaders.gl/loader-utils';
|
|
6
6
|
import {Ellipsoid} from '@math.gl/geospatial';
|
|
7
7
|
import {Vector3} from '@math.gl/core';
|
|
8
|
-
import type {CoreAPI,
|
|
8
|
+
import type {CoreAPI, Loader, LoaderOptions} from '@loaders.gl/loader-utils';
|
|
9
9
|
import type {Tile3D} from '../common/tile-3d';
|
|
10
10
|
import {Tile3D as Tile3DNode} from '../common/tile-3d';
|
|
11
11
|
import {I3STilesetTraverser} from './i3s-tileset-traverser';
|
|
@@ -41,8 +41,10 @@ const EMPTY_CONTENT_FORMATS: TilesetContentFormats = {
|
|
|
41
41
|
export class I3SSource implements Tileset3DSource {
|
|
42
42
|
/** I3S format discriminator. */
|
|
43
43
|
readonly type = TILESET_TYPE.I3S;
|
|
44
|
-
/**
|
|
45
|
-
readonly loader:
|
|
44
|
+
/** Primary I3S loader used for root and child metadata requests. */
|
|
45
|
+
readonly loader: Loader;
|
|
46
|
+
/** Worker-capable loader used for binary tile content when supplied by the format loader. */
|
|
47
|
+
readonly contentLoader: Loader;
|
|
46
48
|
/** Root I3S layer URL. */
|
|
47
49
|
readonly url: string;
|
|
48
50
|
/** Base path used for relative tile resource resolution. */
|
|
@@ -80,6 +82,7 @@ export class I3SSource implements Tileset3DSource {
|
|
|
80
82
|
this.rootTileset = isTilesetRequest(input) ? null : input;
|
|
81
83
|
this.tileset = this.rootTileset;
|
|
82
84
|
this.loader = request.loader;
|
|
85
|
+
this.contentLoader = getI3SContentLoader(request.loader);
|
|
83
86
|
this.url = request.url;
|
|
84
87
|
this.basePath = request.basePath || path.dirname(request.url);
|
|
85
88
|
this.resolver = request.resolver;
|
|
@@ -215,7 +218,7 @@ export class I3SSource implements Tileset3DSource {
|
|
|
215
218
|
}
|
|
216
219
|
};
|
|
217
220
|
|
|
218
|
-
tile.content = await this.loadResourceData(contentUrl, options);
|
|
221
|
+
tile.content = await this.loadResourceData(contentUrl, options, this.contentLoader);
|
|
219
222
|
return {loaded: true};
|
|
220
223
|
}
|
|
221
224
|
|
|
@@ -372,12 +375,16 @@ export class I3SSource implements Tileset3DSource {
|
|
|
372
375
|
/**
|
|
373
376
|
* Loads data through injected core APIs so this module stays independent from `@loaders.gl/core`.
|
|
374
377
|
*/
|
|
375
|
-
private async loadWithCoreApi(
|
|
378
|
+
private async loadWithCoreApi(
|
|
379
|
+
url: string,
|
|
380
|
+
options: LoaderOptions,
|
|
381
|
+
loader: Loader = this.loader
|
|
382
|
+
): Promise<any> {
|
|
376
383
|
if (!this.coreApi) {
|
|
377
384
|
throw new Error('I3SSource requires an injected coreApi to load tileset data');
|
|
378
385
|
}
|
|
379
386
|
|
|
380
|
-
return await this.coreApi.load(url,
|
|
387
|
+
return await this.coreApi.load(url, loader, options);
|
|
381
388
|
}
|
|
382
389
|
|
|
383
390
|
/**
|
|
@@ -408,12 +415,16 @@ export class I3SSource implements Tileset3DSource {
|
|
|
408
415
|
/**
|
|
409
416
|
* Loads tile metadata or content through an injected resolver when present, otherwise through the injected core API.
|
|
410
417
|
*/
|
|
411
|
-
private async loadResourceData(
|
|
418
|
+
private async loadResourceData(
|
|
419
|
+
url: string,
|
|
420
|
+
options: LoaderOptions,
|
|
421
|
+
loader: Loader = this.loader
|
|
422
|
+
): Promise<any> {
|
|
412
423
|
if (this.resolver) {
|
|
413
|
-
return await this.resolver.loadResource(url,
|
|
424
|
+
return await this.resolver.loadResource(url, loader, options);
|
|
414
425
|
}
|
|
415
426
|
|
|
416
|
-
return await this.loadWithCoreApi(url, options);
|
|
427
|
+
return await this.loadWithCoreApi(url, options, loader);
|
|
417
428
|
}
|
|
418
429
|
|
|
419
430
|
/** Transform one I3S header bound after target options and elevation providers are available. */
|
|
@@ -438,6 +449,11 @@ export class I3SSource implements Tileset3DSource {
|
|
|
438
449
|
}
|
|
439
450
|
}
|
|
440
451
|
|
|
452
|
+
/** Returns the format-provided binary content loader, falling back for custom legacy loaders. */
|
|
453
|
+
function getI3SContentLoader(loader: Loader): Loader {
|
|
454
|
+
return (loader as Loader & {contentLoader?: Loader}).contentLoader || loader;
|
|
455
|
+
}
|
|
456
|
+
|
|
441
457
|
function isTilesetRequest(input: TilesetSourceInput): input is TilesetSourceRequest {
|
|
442
458
|
return Boolean(
|
|
443
459
|
input && typeof input === 'object' && 'url' in input && 'loader' in input && !('type' in input)
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
// See LICENSE.md and https://github.com/AnalyticalGraphicsInc/cesium/blob/master/LICENSE.md
|
|
5
5
|
|
|
6
6
|
/* eslint-disable */
|
|
7
|
-
import {Quaternion, Vector3, Matrix3, degrees} from '@math.gl/core';
|
|
7
|
+
import {Quaternion, Vector3, Matrix3, Matrix4, degrees} from '@math.gl/core';
|
|
8
8
|
import {BoundingSphere, OrientedBoundingBox} from '@math.gl/culling';
|
|
9
9
|
import {Ellipsoid, makeOBBFromRegion} from '@math.gl/geospatial';
|
|
10
10
|
import {assert} from '@loaders.gl/loader-utils';
|
|
@@ -18,6 +18,7 @@ function defined(x) {
|
|
|
18
18
|
// const scratchMatrix = new Matrix3();
|
|
19
19
|
const scratchPoint = new Vector3();
|
|
20
20
|
const scratchScale = new Vector3();
|
|
21
|
+
const identityTransform = new Matrix4();
|
|
21
22
|
// const scratchRectangle = new Rectangle();
|
|
22
23
|
// const scratchOrientedBoundingBox = new OrientedBoundingBox();
|
|
23
24
|
// const scratchTransform = new Matrix4();
|
|
@@ -25,20 +26,39 @@ const scratchScale = new Vector3();
|
|
|
25
26
|
/**
|
|
26
27
|
* Create a bounding volume from the tile's bounding volume header.
|
|
27
28
|
* @param {Object} boundingVolumeHeader The tile's bounding volume header.
|
|
28
|
-
* @param {Matrix4} transform The
|
|
29
|
+
* @param {Matrix4} transform The current computed tile transform, including application transforms.
|
|
29
30
|
* @param [result] The object onto which to store the result.
|
|
31
|
+
* @param initialTransform The accumulated JSON tile transform, excluded from region transforms.
|
|
32
|
+
* Defaults to transform so callers without application transforms keep geospatial regions fixed.
|
|
30
33
|
* @returns The modified result parameter or a new TileBoundingVolume instance if none was provided.
|
|
31
34
|
*/
|
|
32
|
-
export function createBoundingVolume(
|
|
35
|
+
export function createBoundingVolume(
|
|
36
|
+
boundingVolumeHeader,
|
|
37
|
+
transform: Matrix4,
|
|
38
|
+
result?,
|
|
39
|
+
initialTransform: Matrix4 = transform
|
|
40
|
+
) {
|
|
33
41
|
assert(boundingVolumeHeader, '3D Tile: boundingVolume must be defined');
|
|
34
42
|
|
|
35
43
|
// boundingVolume schema:
|
|
36
44
|
// https://github.com/AnalyticalGraphicsInc/3d-tiles/blob/master/specification/schema/boundingVolume.schema.json
|
|
37
45
|
if (boundingVolumeHeader.box) {
|
|
38
|
-
|
|
46
|
+
// S2 cells and regions are geospatially anchored. Their conservative ECEF boxes are already
|
|
47
|
+
// world-space volumes and must not receive the tile/node transform a second time.
|
|
48
|
+
const boxTransform = boundingVolumeHeader.s2VolumeInfo ? identityTransform : transform;
|
|
49
|
+
return createBox(boundingVolumeHeader.box, boxTransform, result);
|
|
39
50
|
}
|
|
40
51
|
if (boundingVolumeHeader.region) {
|
|
41
|
-
|
|
52
|
+
const boundingVolume = makeOBBFromRegion(boundingVolumeHeader.region);
|
|
53
|
+
// Regions are already in world coordinates. Apply only the change from the JSON transform.
|
|
54
|
+
const regionTransform = transform.equals(initialTransform)
|
|
55
|
+
? identityTransform
|
|
56
|
+
: transform.clone().multiplyRight(initialTransform.clone().invert());
|
|
57
|
+
return createBox(
|
|
58
|
+
[...boundingVolume.center, ...boundingVolume.halfAxes],
|
|
59
|
+
regionTransform,
|
|
60
|
+
result
|
|
61
|
+
);
|
|
42
62
|
}
|
|
43
63
|
|
|
44
64
|
if (boundingVolumeHeader.sphere) {
|
|
@@ -55,15 +75,17 @@ export type CartographicBounds = [min: number[], max: number[]];
|
|
|
55
75
|
* Calculate the cartographic bounding box the tile's bounding volume.
|
|
56
76
|
* @param {Object} boundingVolumeHeader The tile's bounding volume header.
|
|
57
77
|
* @param {BoundingVolume} boundingVolume The bounding volume.
|
|
78
|
+
* @param isRegionTransformed Whether application transforms moved the region from its JSON bounds.
|
|
58
79
|
* @returns {CartographicBounds}
|
|
59
80
|
*/
|
|
60
81
|
export function getCartographicBounds(
|
|
61
82
|
boundingVolumeHeader,
|
|
62
|
-
boundingVolume: OrientedBoundingBox | BoundingSphere
|
|
83
|
+
boundingVolume: OrientedBoundingBox | BoundingSphere,
|
|
84
|
+
isRegionTransformed = false
|
|
63
85
|
): CartographicBounds {
|
|
64
86
|
// boundingVolume schema:
|
|
65
87
|
// https://github.com/AnalyticalGraphicsInc/3d-tiles/blob/master/specification/schema/boundingVolume.schema.json
|
|
66
|
-
if (boundingVolumeHeader.box) {
|
|
88
|
+
if (boundingVolumeHeader.box || (boundingVolumeHeader.region && isRegionTransformed)) {
|
|
67
89
|
return orientedBoundingBoxToCartographicBounds(boundingVolume as OrientedBoundingBox);
|
|
68
90
|
}
|
|
69
91
|
if (boundingVolumeHeader.region) {
|
|
@@ -142,61 +164,6 @@ function createBox(box, transform, result?) {
|
|
|
142
164
|
return new OrientedBoundingBox(center, halfAxes);
|
|
143
165
|
}
|
|
144
166
|
|
|
145
|
-
/*
|
|
146
|
-
function createBoxFromTransformedRegion(region, transform, initialTransform, result) {
|
|
147
|
-
const rectangle = Rectangle.unpack(region, 0, scratchRectangle);
|
|
148
|
-
const minimumHeight = region[4];
|
|
149
|
-
const maximumHeight = region[5];
|
|
150
|
-
|
|
151
|
-
const orientedBoundingBox = OrientedBoundingBox.fromRectangle(
|
|
152
|
-
rectangle,
|
|
153
|
-
minimumHeight,
|
|
154
|
-
maximumHeight,
|
|
155
|
-
Ellipsoid.WGS84,
|
|
156
|
-
scratchOrientedBoundingBox
|
|
157
|
-
);
|
|
158
|
-
const center = orientedBoundingBox.center;
|
|
159
|
-
const halfAxes = orientedBoundingBox.halfAxes;
|
|
160
|
-
|
|
161
|
-
// A region bounding volume is not transformed by the transform in the tileset JSON,
|
|
162
|
-
// but may be transformed by additional transforms applied in Cesium.
|
|
163
|
-
// This is why the transform is calculated as the difference between the initial transform and the current transform.
|
|
164
|
-
transform = Matrix4.multiplyTransformation(
|
|
165
|
-
transform,
|
|
166
|
-
Matrix4.inverseTransformation(initialTransform, scratchTransform),
|
|
167
|
-
scratchTransform
|
|
168
|
-
);
|
|
169
|
-
center = Matrix4.multiplyByPoint(transform, center, center);
|
|
170
|
-
const rotationScale = Matrix4.getRotation(transform, scratchMatrix);
|
|
171
|
-
halfAxes = Matrix3.multiply(rotationScale, halfAxes, halfAxes);
|
|
172
|
-
|
|
173
|
-
if (defined(result) && result instanceof TileOrientedBoundingBox) {
|
|
174
|
-
result.update(center, halfAxes);
|
|
175
|
-
return result;
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
return new TileOrientedBoundingBox(center, halfAxes);
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
function createRegion(region, transform, initialTransform, result) {
|
|
182
|
-
if (!Matrix4.equalsEpsilon(transform, initialTransform, CesiumMath.EPSILON8)) {
|
|
183
|
-
return createBoxFromTransformedRegion(region, transform, initialTransform, result);
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
if (defined(result)) {
|
|
187
|
-
return result;
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
const rectangleRegion = Rectangle.unpack(region, 0, scratchRectangle);
|
|
191
|
-
|
|
192
|
-
return new TileBoundingRegion({
|
|
193
|
-
rectangle: rectangleRegion,
|
|
194
|
-
minimumHeight: region[4],
|
|
195
|
-
maximumHeight: region[5]
|
|
196
|
-
});
|
|
197
|
-
}
|
|
198
|
-
*/
|
|
199
|
-
|
|
200
167
|
function createSphere(sphere, transform, result?) {
|
|
201
168
|
// Find the transformed center
|
|
202
169
|
const center = new Vector3(sphere[0], sphere[1], sphere[2]);
|
|
@@ -75,6 +75,10 @@ const cullingVolume = new CullingVolume([
|
|
|
75
75
|
/**
|
|
76
76
|
* Extracts a frame state appropriate for tile culling from a structural deck.gl viewport.
|
|
77
77
|
*
|
|
78
|
+
* The viewport must describe the render camera, including its elevation. Geographic heights
|
|
79
|
+
* returned by `unprojectPosition` are used directly; adding a terrain offset only to traversal
|
|
80
|
+
* would make visibility, camera distances, and screen-space error disagree with rendering.
|
|
81
|
+
*
|
|
78
82
|
* `options.timeSinceCameraMovement` can be supplied by a caller that retains camera pose across
|
|
79
83
|
* calls. It defaults to a stationary camera so standalone callers do not defer requests.
|
|
80
84
|
*
|
|
@@ -134,7 +138,7 @@ export function getFrameState(
|
|
|
134
138
|
commonSpacePlanesToWGS84(viewport);
|
|
135
139
|
|
|
136
140
|
const ViewportClass = viewport.constructor;
|
|
137
|
-
const {longitude, latitude, width, bearing, zoom} = viewport;
|
|
141
|
+
const {longitude, latitude, width, bearing, zoom, position} = viewport;
|
|
138
142
|
// @ts-ignore
|
|
139
143
|
const topDownViewport = new ViewportClass({
|
|
140
144
|
longitude,
|
|
@@ -143,6 +147,7 @@ export function getFrameState(
|
|
|
143
147
|
width,
|
|
144
148
|
bearing,
|
|
145
149
|
zoom,
|
|
150
|
+
position,
|
|
146
151
|
pitch: 0
|
|
147
152
|
});
|
|
148
153
|
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
// loaders.gl
|
|
2
|
+
// SPDX-License-Identifier: MIT
|
|
3
|
+
// Copyright vis.gl contributors
|
|
4
|
+
|
|
5
|
+
// TypeScript's legacy `node` resolution does not inspect package `exports`, but this public
|
|
6
|
+
// subpath is resolved by the package at runtime and avoids loading unrelated DGGS decoders.
|
|
7
|
+
// @ts-expect-error Conditional package subpath exports require a modern module resolver.
|
|
8
|
+
import {getS2Bounds} from '@math.gl/dggs/s2';
|
|
9
|
+
import {Ellipsoid, makeOBBFromRegion} from '@math.gl/geospatial';
|
|
10
|
+
|
|
11
|
+
/** Serialized S2 cell metadata used by 3D Tiles bounding-volume extensions. */
|
|
12
|
+
export type S2VolumeInfo = {
|
|
13
|
+
/** S2 key or token. */
|
|
14
|
+
token: string;
|
|
15
|
+
/** Minimum height in meters. */
|
|
16
|
+
minimumHeight: number;
|
|
17
|
+
/** Maximum height in meters. */
|
|
18
|
+
maximumHeight: number;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Converts an S2 cell and its vertical range to the runtime oriented-box representation.
|
|
23
|
+
*
|
|
24
|
+
* @param s2VolumeInfo - S2 cell token and height range.
|
|
25
|
+
* @returns A twelve-component center/half-axis oriented bounding box.
|
|
26
|
+
*/
|
|
27
|
+
export function convertS2BoundingVolumeToOBB(s2VolumeInfo: S2VolumeInfo): number[] {
|
|
28
|
+
const [[west, south], [east, north]] = getS2Bounds(s2VolumeInfo.token);
|
|
29
|
+
const orientedBoundingBox = makeOBBFromRegion(
|
|
30
|
+
[west, south, east, north, s2VolumeInfo.minimumHeight, s2VolumeInfo.maximumHeight],
|
|
31
|
+
Ellipsoid.WGS84,
|
|
32
|
+
{units: 'degrees'}
|
|
33
|
+
);
|
|
34
|
+
return [...orientedBoundingBox.center, ...orientedBoundingBox.halfAxes];
|
|
35
|
+
}
|
package/src/types.ts
CHANGED
|
@@ -46,6 +46,8 @@ export type GeospatialViewport = Viewport & {
|
|
|
46
46
|
cameraDirection: [number, number, number];
|
|
47
47
|
/** @todo This field is not represented in Deck.gl viewports. Can be removed in the next version */
|
|
48
48
|
cameraUp: [number, number, number];
|
|
49
|
+
/** Camera target offset in meters from longitude/latitude, including elevation. */
|
|
50
|
+
position?: number[];
|
|
49
51
|
longitude: number;
|
|
50
52
|
latitude: number;
|
|
51
53
|
bearing: number;
|