@loaders.gl/3d-tiles 4.3.0-alpha.7 → 4.3.0-beta.1

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.
@@ -79,47 +79,52 @@ function getChildS2VolumeBox(
79
79
  * Recursively parse implicit tiles tree
80
80
  * Spec - https://github.com/CesiumGS/3d-tiles/tree/main/extensions/3DTILES_implicit_tiling
81
81
  * TODO Check out do we able to use Tile3D class as return type here.
82
- * @param subtree
83
- * @param lodMetricValue
84
- * @param options
85
- * @param parentData
86
- * @param childIndex
87
- * @param level
88
- * @param globalData
82
+ *
83
+ * @param subtree - the current subtree. Subtrees contain availability data for <implicitOptions.subtreeLevels>.
84
+ * Once we go deeper than that many levels, we will need load a child subtree to get further availability data.
85
+ * @param subtreeData - the coordinates of the current subtree, relative to the root of this implicit tiles tree.
86
+ * @param parentData - the coordinates of the parent tile, relative to the current subtree.
87
+ * The overall coordinates of the current tile can be found by combining the coordinates of the current subtree, the parent tile,
88
+ * and tje single-bit coordinates that can be calculated from the childIndex.
89
+ * @param childIndex - which child the current tile is of its parent. In the range 0-7 for OCTREE, 0-3 for QUADTREE.
90
+ * @param implicitOptions - options specified at the root of this implicit tile tree - numbers of levels, URL templates.
91
+ * @param loaderOptions - see Tiles3DLoaderOptions.
89
92
  */
90
- // eslint-disable-next-line max-statements
93
+ // eslint-disable-next-line max-statements, complexity
91
94
  export async function parseImplicitTiles(params: {
92
95
  subtree: Subtree;
93
- implicitOptions: ImplicitOptions;
94
- parentData?: {mortonIndex: number; x: number; y: number; z: number};
96
+ subtreeData?: {level: number; x: number; y: number; z: number};
97
+ parentData?: {
98
+ mortonIndex: number;
99
+ localLevel: number;
100
+ localX: number;
101
+ localY: number;
102
+ localZ: number;
103
+ };
95
104
  childIndex?: number;
96
- level?: number;
97
- globalData?: {level: number; mortonIndex: number; x: number; y: number; z: number};
98
- s2VolumeBox?: S2VolumeBox;
105
+ implicitOptions: ImplicitOptions;
99
106
  loaderOptions: Tiles3DLoaderOptions;
107
+ s2VolumeBox?: S2VolumeBox;
100
108
  }) {
101
109
  const {
102
- implicitOptions,
103
- parentData = {
104
- mortonIndex: 0,
105
- x: 0,
106
- y: 0,
107
- z: 0
108
- },
109
- childIndex = 0,
110
- s2VolumeBox,
111
- loaderOptions
112
- } = params;
113
- let {
114
110
  subtree,
115
- level = 0,
116
- globalData = {
111
+ subtreeData = {
117
112
  level: 0,
118
- mortonIndex: 0,
119
113
  x: 0,
120
114
  y: 0,
121
115
  z: 0
122
- }
116
+ },
117
+ parentData = {
118
+ mortonIndex: 0,
119
+ localLevel: -1,
120
+ localX: 0,
121
+ localY: 0,
122
+ localZ: 0
123
+ },
124
+ childIndex = 0,
125
+ implicitOptions,
126
+ loaderOptions,
127
+ s2VolumeBox
123
128
  } = params;
124
129
  const {
125
130
  subdivisionScheme,
@@ -138,82 +143,88 @@ export async function parseImplicitTiles(params: {
138
143
  return tile;
139
144
  }
140
145
 
141
- const lev = level + globalData.level;
142
- if (lev > maximumLevel) {
146
+ // Local tile level - relative to the current subtree.
147
+ const localLevel = parentData.localLevel + 1;
148
+ // Global tile level - relative to the root tile of this implicit subdivision scheme.
149
+ const level = subtreeData.level + localLevel;
150
+
151
+ if (level > maximumLevel) {
143
152
  return tile;
144
153
  }
145
154
 
146
155
  const childrenPerTile = SUBDIVISION_COUNT_MAP[subdivisionScheme];
147
156
  const bitsPerTile = Math.log2(childrenPerTile);
148
157
 
149
- // childIndex is in range [0,4] for quadtrees and [0, 7] for octrees
150
- const childX = childIndex & 0b01; // Get first bit for X
151
- const childY = (childIndex >> 1) & 0b01; // Get second bit for Y
152
- const childZ = (childIndex >> 2) & 0b01; // Get third bit for Z
158
+ // childIndex is in range 0...3 for quadtrees and 0...7 for octrees
159
+ const lastBitX = childIndex & 0b01; // Get first bit for X
160
+ const lastBitY = (childIndex >> 1) & 0b01; // Get second bit for Y
161
+ const lastBitZ = (childIndex >> 2) & 0b01; // Get third bit for Z
153
162
 
154
- const levelOffset = (childrenPerTile ** level - 1) / (childrenPerTile - 1);
155
- let childTileMortonIndex = concatBits(parentData.mortonIndex, childIndex, bitsPerTile);
156
- let tileAvailabilityIndex = levelOffset + childTileMortonIndex;
163
+ // Local tile coordinates - relative to the current subtree root.
164
+ const localX = concatBits(parentData.localX, lastBitX, 1);
165
+ const localY = concatBits(parentData.localY, lastBitY, 1);
166
+ const localZ = concatBits(parentData.localZ, lastBitZ, 1);
157
167
 
158
- // Local tile coordinates
159
- let childTileX = concatBits(parentData.x, childX, 1);
160
- let childTileY = concatBits(parentData.y, childY, 1);
161
- let childTileZ = concatBits(parentData.z, childZ, 1);
168
+ // Global tile coordinates - relative to the implicit-tile-tree root.
169
+ // Found by combining the local coordinates which are relative to the current subtree, with the subtree coordinates.
170
+ const x = concatBits(subtreeData.x, localX, localLevel);
171
+ const y = concatBits(subtreeData.y, localY, localLevel);
172
+ const z = concatBits(subtreeData.z, localZ, localLevel);
162
173
 
163
- let isChildSubtreeAvailable = false;
174
+ const mortonIndex = concatBits(parentData.mortonIndex, childIndex, bitsPerTile);
164
175
 
165
- if (level >= subtreeLevels) {
166
- isChildSubtreeAvailable = getAvailabilityResult(
167
- subtree.childSubtreeAvailability,
168
- childTileMortonIndex
169
- );
170
- }
176
+ const isChildSubtreeAvailable =
177
+ localLevel === subtreeLevels &&
178
+ getAvailabilityResult(subtree.childSubtreeAvailability, mortonIndex);
171
179
 
172
- const x = concatBits(globalData.x, childTileX, level);
173
- const y = concatBits(globalData.y, childTileY, level);
174
- const z = concatBits(globalData.z, childTileZ, level);
180
+ // Context to provide the next recursive call.
181
+ // This context is set up differently depending on whether its time to start a new subtree or not.
182
+ let nextSubtree;
183
+ let nextSubtreeData;
184
+ let nextParentData;
185
+ let tileAvailabilityIndex;
175
186
 
176
187
  if (isChildSubtreeAvailable) {
177
188
  const subtreePath = `${basePath}/${subtreesUriTemplate}`;
178
- const childSubtreeUrl = replaceContentUrlTemplate(subtreePath, lev, x, y, z);
189
+ const childSubtreeUrl = replaceContentUrlTemplate(subtreePath, level, x, y, z);
179
190
  const childSubtree = await load(childSubtreeUrl, Tile3DSubtreeLoader, loaderOptions);
180
191
 
181
- subtree = childSubtree;
182
-
183
- globalData = {
184
- mortonIndex: childTileMortonIndex,
185
- x: childTileX,
186
- y: childTileY,
187
- z: childTileZ,
188
- level
189
- };
190
-
191
- childTileMortonIndex = 0;
192
+ // The next subtree is the newly-loaded child subtree.
193
+ nextSubtree = childSubtree;
194
+ // The current tile is actually the root tile in the next subtree, so it has a tileAvailabilityIndex of 0.
192
195
  tileAvailabilityIndex = 0;
193
- childTileX = 0;
194
- childTileY = 0;
195
- childTileZ = 0;
196
- level = 0;
196
+ // The next subtree starts HERE - at the current tile.
197
+ nextSubtreeData = {level, x, y, z};
198
+ // The next parent is also the current tile - so it has local coordinates of 0 relative to the next subtree.
199
+ nextParentData = {mortonIndex: 0, localLevel: 0, localX: 0, localY: 0, localZ: 0};
200
+ } else {
201
+ // Continue on with the same subtree as we're using currently.
202
+ nextSubtree = subtree;
203
+ // Calculate a tileAvailabilityIndex for the current tile within the current subtree.
204
+ const levelOffset = (childrenPerTile ** localLevel - 1) / (childrenPerTile - 1);
205
+ tileAvailabilityIndex = levelOffset + mortonIndex;
206
+ // The next subtree is the same as the current subtree.
207
+ nextSubtreeData = subtreeData;
208
+ // The next parent is the current tile: it has the local coordinates we already calculated.
209
+ nextParentData = {mortonIndex, localLevel, localX, localY, localZ};
197
210
  }
198
211
 
199
- const isTileAvailable = getAvailabilityResult(subtree.tileAvailability, tileAvailabilityIndex);
200
-
212
+ const isTileAvailable = getAvailabilityResult(
213
+ nextSubtree.tileAvailability,
214
+ tileAvailabilityIndex
215
+ );
201
216
  if (!isTileAvailable) {
202
217
  return tile;
203
218
  }
204
219
 
205
220
  const isContentAvailable = getAvailabilityResult(
206
- subtree.contentAvailability,
221
+ nextSubtree.contentAvailability,
207
222
  tileAvailabilityIndex
208
223
  );
209
-
210
224
  if (isContentAvailable) {
211
- tile.contentUrl = replaceContentUrlTemplate(contentUrlTemplate, lev, x, y, z);
225
+ tile.contentUrl = replaceContentUrlTemplate(contentUrlTemplate, level, x, y, z);
212
226
  }
213
227
 
214
- const childTileLevel = level + 1;
215
- const pData = {mortonIndex: childTileMortonIndex, x: childTileX, y: childTileY, z: childTileZ};
216
-
217
228
  for (let index = 0; index < childrenPerTile; index++) {
218
229
  const childS2VolumeBox: S2VolumeBox | undefined = getChildS2VolumeBox(
219
230
  s2VolumeBox,
@@ -222,32 +233,28 @@ export async function parseImplicitTiles(params: {
222
233
  );
223
234
 
224
235
  // Recursive calling...
225
- const childTileParsed = await parseImplicitTiles({
226
- subtree,
236
+ const childTile = await parseImplicitTiles({
237
+ subtree: nextSubtree,
238
+ subtreeData: nextSubtreeData,
239
+ parentData: nextParentData,
240
+ childIndex: index,
227
241
  implicitOptions,
228
242
  loaderOptions,
229
- parentData: pData,
230
- childIndex: index,
231
- level: childTileLevel,
232
- globalData: {...globalData},
233
243
  s2VolumeBox: childS2VolumeBox
234
244
  });
235
245
 
236
- if (childTileParsed.contentUrl || childTileParsed.children.length) {
237
- const globalLevel = lev + 1;
238
- const childCoordinates = {childTileX, childTileY, childTileZ};
239
- const formattedTile = formatTileData(
240
- childTileParsed,
241
- globalLevel,
242
- childCoordinates,
243
- implicitOptions,
244
- s2VolumeBox
245
- );
246
+ if (childTile.contentUrl || childTile.children.length) {
246
247
  // @ts-ignore
247
- tile.children.push(formattedTile);
248
+ tile.children.push(childTile);
248
249
  }
249
250
  }
250
251
 
252
+ if (tile.contentUrl || tile.children.length) {
253
+ const coordinates = {level, x, y, z};
254
+ const formattedTile = formatTileData(tile, coordinates, implicitOptions, s2VolumeBox);
255
+ return formattedTile;
256
+ }
257
+
251
258
  return tile;
252
259
  }
253
260
 
@@ -290,15 +297,16 @@ function getAvailabilityResult(
290
297
  /**
291
298
  * Do formatting of implicit tile data.
292
299
  * TODO Check out do we able to use Tile3D class as type here.
293
- * @param tile
294
- * @param lodMetricValue
295
- * @param options
300
+ *
301
+ * @param tile - tile data to format.
302
+ * @param coordinates - global tile coordinates (relative to the root of the implicit tile tree).
303
+ * @param options - options specified at the root of this implicit tile tree - numbers of levels, URL templates.
304
+ * @param s2VolumeBox - the S2VolumeBox for this particular child, if available.
296
305
  * @returns
297
306
  */
298
307
  function formatTileData(
299
308
  tile,
300
- level: number,
301
- childCoordinates: {childTileX: number; childTileY: number; childTileZ: number},
309
+ coordinates: {level: number; x: number; y: number; z: number},
302
310
  options: ImplicitOptions,
303
311
  s2VolumeBox?: S2VolumeBox
304
312
  ) {
@@ -312,16 +320,16 @@ function formatTileData(
312
320
  rootBoundingVolume
313
321
  } = options;
314
322
  const uri = tile.contentUrl && tile.contentUrl.replace(`${basePath}/`, '');
315
- const lodMetricValue = rootLodMetricValue / 2 ** level;
323
+ const lodMetricValue = rootLodMetricValue / 2 ** coordinates.level;
316
324
 
317
325
  const boundingVolume: Tile3DBoundingVolume = s2VolumeBox?.box
318
326
  ? {box: s2VolumeBox.box}
319
327
  : rootBoundingVolume;
320
328
 
321
329
  const boundingVolumeForChildTile = calculateBoundingVolumeForChildTile(
322
- level,
323
330
  boundingVolume,
324
- childCoordinates
331
+ coordinates,
332
+ options.subdivisionScheme
325
333
  );
326
334
 
327
335
  return {
@@ -342,37 +350,43 @@ function formatTileData(
342
350
  /**
343
351
  * Calculate child bounding volume.
344
352
  * Spec - https://github.com/CesiumGS/3d-tiles/tree/main/extensions/3DTILES_implicit_tiling#subdivision-rules
345
- * @param level
346
353
  * @param rootBoundingVolume
347
- * @param childCoordinates
354
+ * @param coordinates
355
+ * @param subdivisionScheme
348
356
  */
349
357
  function calculateBoundingVolumeForChildTile(
350
- level: number,
351
358
  rootBoundingVolume: Tile3DBoundingVolume,
352
- childCoordinates: {childTileX: number; childTileY: number; childTileZ: number}
359
+ coordinates: {level: number; x: number; y: number; z: number},
360
+ subdivisionScheme: string
353
361
  ): Tile3DBoundingVolume {
354
362
  if (rootBoundingVolume.region) {
355
- const {childTileX, childTileY, childTileZ} = childCoordinates;
363
+ const {level, x, y, z} = coordinates;
356
364
  const [west, south, east, north, minimumHeight, maximumHeight] = rootBoundingVolume.region;
357
365
  const boundingVolumesCount = 2 ** level;
358
366
 
359
367
  const sizeX = (east - west) / boundingVolumesCount;
360
- const sizeY = (north - south) / boundingVolumesCount;
368
+ const [childWest, childEast] = [west + sizeX * x, west + sizeX * (x + 1)];
361
369
 
362
- // TODO : Why is the subdivisionScheme not being checked here?
370
+ const sizeY = (north - south) / boundingVolumesCount;
371
+ const [childSouth, childNorth] = [south + sizeY * y, south + sizeY * (y + 1)];
363
372
 
364
373
  // In case of QUADTREE the sizeZ should NOT be changed!
365
374
  // https://portal.ogc.org/files/102132
366
- // A quadtree divides space only on the x and y dimensions. It divides each tile into 4 smaller tiles where the x and y dimensions are halved. The quadtree z minimum and maximum remain unchanged.
367
-
368
- const sizeZ = (maximumHeight - minimumHeight) / boundingVolumesCount;
375
+ // A quadtree divides space only on the x and y dimensions.
376
+ // It divides each tile into 4 smaller tiles where the x and y dimensions are halved.
377
+ // The quadtree z minimum and maximum remain unchanged.
369
378
 
370
- const [childWest, childEast] = [west + sizeX * childTileX, west + sizeX * (childTileX + 1)];
371
- const [childSouth, childNorth] = [south + sizeY * childTileY, south + sizeY * (childTileY + 1)];
372
- const [childMinimumHeight, childMaximumHeight] = [
373
- minimumHeight + sizeZ * childTileZ,
374
- minimumHeight + sizeZ * (childTileZ + 1)
375
- ];
379
+ let childMinimumHeight: number;
380
+ let childMaximumHeight: number;
381
+ if (subdivisionScheme === 'OCTREE') {
382
+ const sizeZ = (maximumHeight - minimumHeight) / boundingVolumesCount;
383
+ [childMinimumHeight, childMaximumHeight] = [
384
+ minimumHeight + sizeZ * z,
385
+ minimumHeight + sizeZ * (z + 1)
386
+ ];
387
+ } else {
388
+ [childMinimumHeight, childMaximumHeight] = [minimumHeight, maximumHeight];
389
+ }
376
390
 
377
391
  return {
378
392
  region: [childWest, childSouth, childEast, childNorth, childMinimumHeight, childMaximumHeight]
@@ -6,9 +6,8 @@ import {Vector3} from '@math.gl/core';
6
6
  import {OrientedBoundingBox, makeOrientedBoundingBoxFromPoints} from '@math.gl/culling';
7
7
 
8
8
  import type {S2HeightInfo} from '../../utils/s2/index';
9
- import {getS2OrientedBoundingBoxCornerPoints} from '../../utils/s2/index';
9
+ import {getS2OrientedBoundingBoxCornerPoints, getS2LngLat} from '../../utils/s2/index';
10
10
 
11
- import {getS2LngLat} from '../../utils/s2/index';
12
11
  import {Ellipsoid} from '@math.gl/geospatial';
13
12
 
14
13
  export type S2VolumeInfo = {