@loaders.gl/tile-converter 4.0.0-alpha.7 → 4.0.0-alpha.9

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.
Files changed (149) hide show
  1. package/dist/3d-tiles-attributes-worker.js +2 -2
  2. package/dist/3d-tiles-attributes-worker.js.map +2 -2
  3. package/dist/converter.min.js +67 -67
  4. package/dist/deps-installer/deps-installer.d.ts.map +1 -1
  5. package/dist/deps-installer/deps-installer.js +3 -2
  6. package/dist/dist.min.js +698 -340
  7. package/dist/es5/3d-tiles-attributes-worker.js +1 -1
  8. package/dist/es5/3d-tiles-converter/3d-tiles-converter.js.map +1 -1
  9. package/dist/es5/deps-installer/deps-installer.js +4 -3
  10. package/dist/es5/deps-installer/deps-installer.js.map +1 -1
  11. package/dist/es5/i3s-attributes-worker.js +1 -1
  12. package/dist/es5/i3s-converter/helpers/batch-ids-extensions.js +2 -2
  13. package/dist/es5/i3s-converter/helpers/batch-ids-extensions.js.map +1 -1
  14. package/dist/es5/i3s-converter/helpers/coordinate-converter.js +6 -7
  15. package/dist/es5/i3s-converter/helpers/coordinate-converter.js.map +1 -1
  16. package/dist/es5/i3s-converter/helpers/geometry-converter.js +25 -14
  17. package/dist/es5/i3s-converter/helpers/geometry-converter.js.map +1 -1
  18. package/dist/es5/i3s-converter/helpers/gltf-attributes.js +45 -12
  19. package/dist/es5/i3s-converter/helpers/gltf-attributes.js.map +1 -1
  20. package/dist/es5/i3s-converter/helpers/load-3d-tiles.js +82 -0
  21. package/dist/es5/i3s-converter/helpers/load-3d-tiles.js.map +1 -0
  22. package/dist/es5/i3s-converter/helpers/node-index-document.js +74 -45
  23. package/dist/es5/i3s-converter/helpers/node-index-document.js.map +1 -1
  24. package/dist/es5/i3s-converter/helpers/preprocess-3d-tiles.js +111 -0
  25. package/dist/es5/i3s-converter/helpers/preprocess-3d-tiles.js.map +1 -0
  26. package/dist/es5/i3s-converter/helpers/tileset-traversal.js +82 -0
  27. package/dist/es5/i3s-converter/helpers/tileset-traversal.js.map +1 -0
  28. package/dist/es5/i3s-converter/i3s-converter.js +545 -516
  29. package/dist/es5/i3s-converter/i3s-converter.js.map +1 -1
  30. package/dist/es5/i3s-converter/types.js +16 -0
  31. package/dist/es5/i3s-converter/types.js.map +1 -1
  32. package/dist/es5/i3s-server/README.md +19 -0
  33. package/dist/es5/i3s-server/app.js +10 -1
  34. package/dist/es5/i3s-server/app.js.map +1 -1
  35. package/dist/es5/i3s-server/controllers/slpk-controller.js +84 -0
  36. package/dist/es5/i3s-server/controllers/slpk-controller.js.map +1 -0
  37. package/dist/es5/i3s-server/routes/slpk-router.js +71 -0
  38. package/dist/es5/i3s-server/routes/slpk-router.js.map +1 -0
  39. package/dist/es5/i3s-server/utils/create-scene-server.js +17 -0
  40. package/dist/es5/i3s-server/utils/create-scene-server.js.map +1 -0
  41. package/dist/es5/lib/utils/file-utils.js +1 -1
  42. package/dist/es5/lib/utils/file-utils.js.map +1 -1
  43. package/dist/es5/lib/utils/lod-conversion-utils.js.map +1 -1
  44. package/dist/es5/pgm-loader.js +1 -1
  45. package/dist/esm/3d-tiles-attributes-worker.js +1 -1
  46. package/dist/esm/3d-tiles-converter/3d-tiles-converter.js.map +1 -1
  47. package/dist/esm/deps-installer/deps-installer.js +4 -3
  48. package/dist/esm/deps-installer/deps-installer.js.map +1 -1
  49. package/dist/esm/i3s-attributes-worker.js +1 -1
  50. package/dist/esm/i3s-converter/helpers/batch-ids-extensions.js +2 -2
  51. package/dist/esm/i3s-converter/helpers/batch-ids-extensions.js.map +1 -1
  52. package/dist/esm/i3s-converter/helpers/coordinate-converter.js +6 -7
  53. package/dist/esm/i3s-converter/helpers/coordinate-converter.js.map +1 -1
  54. package/dist/esm/i3s-converter/helpers/geometry-converter.js +19 -8
  55. package/dist/esm/i3s-converter/helpers/geometry-converter.js.map +1 -1
  56. package/dist/esm/i3s-converter/helpers/gltf-attributes.js +49 -12
  57. package/dist/esm/i3s-converter/helpers/gltf-attributes.js.map +1 -1
  58. package/dist/esm/i3s-converter/helpers/load-3d-tiles.js +35 -0
  59. package/dist/esm/i3s-converter/helpers/load-3d-tiles.js.map +1 -0
  60. package/dist/esm/i3s-converter/helpers/node-index-document.js +14 -1
  61. package/dist/esm/i3s-converter/helpers/node-index-document.js.map +1 -1
  62. package/dist/esm/i3s-converter/helpers/preprocess-3d-tiles.js +48 -0
  63. package/dist/esm/i3s-converter/helpers/preprocess-3d-tiles.js.map +1 -0
  64. package/dist/esm/i3s-converter/helpers/tileset-traversal.js +14 -0
  65. package/dist/esm/i3s-converter/helpers/tileset-traversal.js.map +1 -0
  66. package/dist/esm/i3s-converter/i3s-converter.js +134 -120
  67. package/dist/esm/i3s-converter/i3s-converter.js.map +1 -1
  68. package/dist/esm/i3s-converter/types.js +10 -1
  69. package/dist/esm/i3s-converter/types.js.map +1 -1
  70. package/dist/esm/i3s-server/README.md +19 -0
  71. package/dist/esm/i3s-server/app.js +11 -1
  72. package/dist/esm/i3s-server/app.js.map +1 -1
  73. package/dist/esm/i3s-server/controllers/slpk-controller.js +36 -0
  74. package/dist/esm/i3s-server/controllers/slpk-controller.js.map +1 -0
  75. package/dist/esm/i3s-server/routes/slpk-router.js +33 -0
  76. package/dist/esm/i3s-server/routes/slpk-router.js.map +1 -0
  77. package/dist/esm/i3s-server/utils/create-scene-server.js +16 -0
  78. package/dist/esm/i3s-server/utils/create-scene-server.js.map +1 -0
  79. package/dist/esm/lib/utils/file-utils.js +1 -1
  80. package/dist/esm/lib/utils/file-utils.js.map +1 -1
  81. package/dist/esm/lib/utils/lod-conversion-utils.js.map +1 -1
  82. package/dist/esm/pgm-loader.js +1 -1
  83. package/dist/i3s-attributes-worker.js +2 -2
  84. package/dist/i3s-attributes-worker.js.map +2 -2
  85. package/dist/i3s-converter/helpers/batch-ids-extensions.js +2 -5
  86. package/dist/i3s-converter/helpers/coordinate-converter.d.ts +3 -4
  87. package/dist/i3s-converter/helpers/coordinate-converter.d.ts.map +1 -1
  88. package/dist/i3s-converter/helpers/coordinate-converter.js +8 -9
  89. package/dist/i3s-converter/helpers/geometry-converter.d.ts +9 -4
  90. package/dist/i3s-converter/helpers/geometry-converter.d.ts.map +1 -1
  91. package/dist/i3s-converter/helpers/geometry-converter.js +34 -12
  92. package/dist/i3s-converter/helpers/gltf-attributes.d.ts +22 -3
  93. package/dist/i3s-converter/helpers/gltf-attributes.d.ts.map +1 -1
  94. package/dist/i3s-converter/helpers/gltf-attributes.js +61 -18
  95. package/dist/i3s-converter/helpers/load-3d-tiles.d.ts +18 -0
  96. package/dist/i3s-converter/helpers/load-3d-tiles.d.ts.map +1 -0
  97. package/dist/i3s-converter/helpers/load-3d-tiles.js +53 -0
  98. package/dist/i3s-converter/helpers/node-index-document.d.ts +8 -0
  99. package/dist/i3s-converter/helpers/node-index-document.d.ts.map +1 -1
  100. package/dist/i3s-converter/helpers/node-index-document.js +20 -2
  101. package/dist/i3s-converter/helpers/node-pages.js +1 -1
  102. package/dist/i3s-converter/helpers/preprocess-3d-tiles.d.ts +23 -0
  103. package/dist/i3s-converter/helpers/preprocess-3d-tiles.d.ts.map +1 -0
  104. package/dist/i3s-converter/helpers/preprocess-3d-tiles.js +76 -0
  105. package/dist/i3s-converter/helpers/tileset-traversal.d.ts +25 -0
  106. package/dist/i3s-converter/helpers/tileset-traversal.d.ts.map +1 -0
  107. package/dist/i3s-converter/helpers/tileset-traversal.js +29 -0
  108. package/dist/i3s-converter/i3s-converter.d.ts +40 -40
  109. package/dist/i3s-converter/i3s-converter.d.ts.map +1 -1
  110. package/dist/i3s-converter/i3s-converter.js +150 -126
  111. package/dist/i3s-converter/types.d.ts +18 -0
  112. package/dist/i3s-converter/types.d.ts.map +1 -1
  113. package/dist/i3s-converter/types.js +15 -0
  114. package/dist/i3s-server/app.d.ts.map +1 -1
  115. package/dist/i3s-server/app.js +9 -1
  116. package/dist/i3s-server/controllers/slpk-controller.d.ts +3 -0
  117. package/dist/i3s-server/controllers/slpk-controller.d.ts.map +1 -0
  118. package/dist/i3s-server/controllers/slpk-controller.js +32 -0
  119. package/dist/i3s-server/routes/slpk-router.d.ts +3 -0
  120. package/dist/i3s-server/routes/slpk-router.d.ts.map +1 -0
  121. package/dist/i3s-server/routes/slpk-router.js +33 -0
  122. package/dist/i3s-server/utils/create-scene-server.d.ts +11 -0
  123. package/dist/i3s-server/utils/create-scene-server.d.ts.map +1 -0
  124. package/dist/i3s-server/utils/create-scene-server.js +14 -0
  125. package/dist/lib/utils/file-utils.d.ts.map +1 -1
  126. package/dist/lib/utils/file-utils.js +2 -1
  127. package/dist/lib/utils/lod-conversion-utils.d.ts +3 -2
  128. package/dist/lib/utils/lod-conversion-utils.d.ts.map +1 -1
  129. package/dist/lib/utils/lod-conversion-utils.js +1 -1
  130. package/package.json +15 -15
  131. package/src/3d-tiles-converter/3d-tiles-converter.ts +5 -5
  132. package/src/deps-installer/deps-installer.ts +3 -2
  133. package/src/i3s-converter/helpers/batch-ids-extensions.ts +2 -5
  134. package/src/i3s-converter/helpers/coordinate-converter.ts +11 -10
  135. package/src/i3s-converter/helpers/geometry-converter.ts +51 -19
  136. package/src/i3s-converter/helpers/gltf-attributes.ts +84 -21
  137. package/src/i3s-converter/helpers/load-3d-tiles.ts +68 -0
  138. package/src/i3s-converter/helpers/node-index-document.ts +22 -2
  139. package/src/i3s-converter/helpers/preprocess-3d-tiles.ts +81 -0
  140. package/src/i3s-converter/helpers/tileset-traversal.ts +51 -0
  141. package/src/i3s-converter/i3s-converter.ts +228 -178
  142. package/src/i3s-converter/types.ts +20 -0
  143. package/src/i3s-server/README.md +19 -0
  144. package/src/i3s-server/app.js +8 -1
  145. package/src/i3s-server/controllers/slpk-controller.js +38 -0
  146. package/src/i3s-server/routes/slpk-router.js +33 -0
  147. package/src/i3s-server/utils/create-scene-server.js +15 -0
  148. package/src/lib/utils/file-utils.ts +2 -1
  149. package/src/lib/utils/lod-conversion-utils.ts +6 -2
@@ -1,7 +1,12 @@
1
1
  // loaders.gl, MIT license
2
2
 
3
- import type {Tileset3DProps} from '@loaders.gl/tiles';
4
- import type {FeatureTableJson} from '@loaders.gl/3d-tiles';
3
+ import type {
4
+ FeatureTableJson,
5
+ Tiles3DLoaderOptions,
6
+ Tiles3DTileContent,
7
+ Tiles3DTileJSONPostprocessed,
8
+ Tiles3DTilesetJSONPostprocessed
9
+ } from '@loaders.gl/3d-tiles';
5
10
  import type {WriteQueueItem} from '../lib/utils/write-queue';
6
11
  import type {
7
12
  SceneLayer3D,
@@ -10,7 +15,6 @@ import type {
10
15
  NodeInPage
11
16
  } from '@loaders.gl/i3s';
12
17
  import {load, encode, fetchFile, getLoaderOptions, isBrowser} from '@loaders.gl/core';
13
- import {Tileset3D} from '@loaders.gl/tiles';
14
18
  import {CesiumIonLoader, Tiles3DLoader} from '@loaders.gl/3d-tiles';
15
19
  import {Geoid} from '@math.gl/geoid';
16
20
  import {join} from 'path';
@@ -41,15 +45,17 @@ import {LAYERS as layersTemplate} from './json-templates/layers';
41
45
  import {GEOMETRY_DEFINITION as geometryDefinitionTemlate} from './json-templates/geometry-definitions';
42
46
  import {SHARED_RESOURCES as sharedResourcesTemplate} from './json-templates/shared-resources';
43
47
  import {validateNodeBoundingVolumes} from './helpers/node-debug';
44
- // loaders.gl, MIT license
45
-
46
- import {Tile3D} from '@loaders.gl/tiles';
47
48
  import {KTX2BasisWriterWorker} from '@loaders.gl/textures';
48
49
  import {LoaderWithParser} from '@loaders.gl/loader-utils';
49
- import {I3SMaterialDefinition, TextureSetDefinitionFormats} from '@loaders.gl/i3s/src/types';
50
+ import {I3SMaterialDefinition, TextureSetDefinitionFormats} from '@loaders.gl/i3s';
50
51
  import {ImageWriter} from '@loaders.gl/images';
51
52
  import {GLTFImagePostprocessed} from '@loaders.gl/gltf';
52
- import {I3SConvertedResources, SharedResourcesArrays} from './types';
53
+ import {
54
+ GltfPrimitiveModeString,
55
+ I3SConvertedResources,
56
+ PreprocessData,
57
+ SharedResourcesArrays
58
+ } from './types';
53
59
  import {getWorkerURL, WorkerFarm} from '@loaders.gl/worker-utils';
54
60
  import {DracoWriterWorker} from '@loaders.gl/draco';
55
61
  import WriteQueue from '../lib/utils/write-queue';
@@ -63,6 +69,12 @@ import {
63
69
  getFieldAttributeType
64
70
  } from './helpers/feature-attributes';
65
71
  import {NodeIndexDocument} from './helpers/node-index-document';
72
+ import {loadNestedTileset, loadTile3DContent} from './helpers/load-3d-tiles';
73
+ import {Matrix4} from '@math.gl/core';
74
+ import {BoundingSphere, OrientedBoundingBox} from '@math.gl/culling';
75
+ import {createBoundingVolume} from '@loaders.gl/tiles';
76
+ import {TraversalConversionProps, traverseDatasetWith} from './helpers/tileset-traversal';
77
+ import {analyzeTileContent, mergePreprocessData} from './helpers/preprocess-3d-tiles';
66
78
 
67
79
  const ION_DEFAULT_TOKEN =
68
80
  process.env?.IonToken || // eslint-disable-line
@@ -96,7 +108,19 @@ export default class I3SConverter {
96
108
  boundingVolumeWarnings?: string[] = [];
97
109
  conversionStartTime: [number, number] = [0, 0];
98
110
  refreshTokenTime: [number, number] = [0, 0];
99
- sourceTileset: Tileset3D | null = null;
111
+ sourceTileset: Tiles3DTilesetJSONPostprocessed | null = null;
112
+ loadOptions: Tiles3DLoaderOptions = {
113
+ _nodeWorkers: true,
114
+ reuseWorkers: true,
115
+ basis: {
116
+ format: 'rgba32',
117
+ // We need to load local fs workers because nodejs can't load workers from the Internet
118
+ workerUrl: './modules/textures/dist/basis-worker-node.js'
119
+ },
120
+ // We need to load local fs workers because nodejs can't load workers from the Internet
121
+ draco: {workerUrl: './modules/draco/dist/draco-worker-node.js'},
122
+ fetch: {}
123
+ };
100
124
  geoidHeightModel: Geoid | null = null;
101
125
  Loader: LoaderWithParser = Tiles3DLoader;
102
126
  generateTextures: boolean;
@@ -105,6 +129,9 @@ export default class I3SConverter {
105
129
  workerSource: {[key: string]: string} = {};
106
130
  writeQueue: WriteQueue<WriteQueueItem> = new WriteQueue();
107
131
  compressList: string[] | null = null;
132
+ preprocessData: PreprocessData = {
133
+ meshTopologyTypes: new Set()
134
+ };
108
135
 
109
136
  constructor() {
110
137
  this.nodePages = new NodePages(writeFile, HARDCODED_NODES_PER_PAGE, this);
@@ -160,7 +187,7 @@ export default class I3SConverter {
160
187
  generateTextures?: boolean;
161
188
  generateBoundingVolumes?: boolean;
162
189
  instantNodeWriting?: boolean;
163
- }): Promise<any> {
190
+ }): Promise<string> {
164
191
  if (isBrowser) {
165
192
  console.log(BROWSER_ERROR_MESSAGE);
166
193
  return BROWSER_ERROR_MESSAGE;
@@ -214,41 +241,86 @@ export default class I3SConverter {
214
241
 
215
242
  try {
216
243
  const preloadOptions = await this._fetchPreloadOptions();
217
- const tilesetOptions: Tileset3DProps = {
218
- loadOptions: {
219
- _nodeWorkers: true,
220
- reuseWorkers: true,
221
- basis: {format: 'rgba32'}
222
- // TODO - should no longer be needed with new Node workers
223
- // 'basis-nodejs': {
224
- // format: 'rgba32',
225
- // workerUrl: './modules/textures/dist/basis-worker-node.js'
226
- // },
227
- // 'draco-nodejs': {workerUrl: './modules/draco/dist/draco-worker-node.js'}
228
- }
229
- };
230
244
  if (preloadOptions.headers) {
231
- tilesetOptions.loadOptions!.fetch = {headers: preloadOptions.headers};
245
+ this.loadOptions.fetch = {headers: preloadOptions.headers};
246
+ }
247
+ this.sourceTileset = await load(inputUrl, this.Loader, this.loadOptions);
248
+
249
+ const preprocessResult = await this.preprocessConversion();
250
+
251
+ if (preprocessResult) {
252
+ await this._createAndSaveTileset(outputPath, tilesetName);
253
+ await this._finishConversion({slpk: Boolean(slpk), outputPath, tilesetName});
232
254
  }
233
- Object.assign(tilesetOptions, preloadOptions);
234
- const sourceTilesetJson = await load(inputUrl, this.Loader, tilesetOptions.loadOptions);
235
- // console.log(tilesetJson); // eslint-disable-line
236
- this.sourceTileset = new Tileset3D(sourceTilesetJson, tilesetOptions);
237
-
238
- await this._createAndSaveTileset(
239
- outputPath,
240
- tilesetName,
241
- sourceTilesetJson?.root?.boundingVolume?.region
242
- );
243
- await this._finishConversion({slpk: Boolean(slpk), outputPath, tilesetName});
244
- return sourceTilesetJson;
245
255
  } catch (error) {
246
256
  throw error;
247
257
  } finally {
258
+ await this.writeQueue.finalize();
248
259
  // Clean up worker pools
249
260
  const workerFarm = WorkerFarm.getWorkerFarm({});
250
261
  workerFarm.destroy();
251
262
  }
263
+ return 'success';
264
+ }
265
+
266
+ /**
267
+ * Preprocess stage of the tile converter. Traverse all the tiles tree and
268
+ * check a tile content to be sure that the data is supported
269
+ * @returns true - the conversion is possible, false - the tileset's content is not supported
270
+ */
271
+ private async preprocessConversion(): Promise<boolean> {
272
+ console.log(`Analyze source tileset`);
273
+ const sourceRootTile: Tiles3DTileJSONPostprocessed = this.sourceTileset!.root!;
274
+ await traverseDatasetWith<null>(
275
+ sourceRootTile,
276
+ null,
277
+ this.analyzeTile.bind(this),
278
+ undefined,
279
+ this.options.maxDepth
280
+ );
281
+ const {meshTopologyTypes} = this.preprocessData;
282
+ console.log(`------------------------------------------------`);
283
+ console.log(`Preprocess results:`);
284
+ console.log(`glTF mesh topology types: ${Array.from(meshTopologyTypes).join(', ')}`);
285
+ console.log(`------------------------------------------------`);
286
+ if (
287
+ !meshTopologyTypes.has(GltfPrimitiveModeString.TRIANGLES) &&
288
+ !meshTopologyTypes.has(GltfPrimitiveModeString.TRIANGLE_STRIP)
289
+ ) {
290
+ console.log(
291
+ 'The tileset is of unsupported mesh topology types. The conversion will be interrupted.'
292
+ );
293
+ console.log(`------------------------------------------------`);
294
+ return false;
295
+ }
296
+ return true;
297
+ }
298
+
299
+ /**
300
+ * Analyze a tile content. The callback for preprocess stage.
301
+ * @param sourceTile - 3DTiles tile JSON metadata
302
+ * @param traversalProps - mandatory argument but it is not used for the preprocess stage
303
+ * @returns - nothing
304
+ */
305
+ private async analyzeTile(
306
+ sourceTile: Tiles3DTileJSONPostprocessed,
307
+ traversalProps: null
308
+ ): Promise<null> {
309
+ if (sourceTile.type === 'json') {
310
+ await loadNestedTileset(this.sourceTileset, sourceTile, this.loadOptions);
311
+ return null;
312
+ }
313
+ if (sourceTile.id) {
314
+ console.log(`[analyze]: ${sourceTile.id}`); // eslint-disable-line
315
+ }
316
+ const tileContent = await loadTile3DContent(this.sourceTileset, sourceTile, {
317
+ ...this.loadOptions,
318
+ '3d-tiles': {...this.loadOptions['3d-tiles'], loadGLTF: false}
319
+ });
320
+ const tilePreprocessData = await analyzeTileContent(sourceTile, tileContent);
321
+ mergePreprocessData(this.preprocessData, tilePreprocessData);
322
+
323
+ return null;
252
324
  }
253
325
 
254
326
  /**
@@ -256,11 +328,7 @@ export default class I3SConverter {
256
328
  * @param outputPath - path to save output data
257
329
  * @param tilesetName - new tileset path
258
330
  */
259
- private async _createAndSaveTileset(
260
- outputPath: string,
261
- tilesetName: string,
262
- boundingVolumeRegion?: number[]
263
- ): Promise<void> {
331
+ private async _createAndSaveTileset(outputPath: string, tilesetName: string): Promise<void> {
264
332
  const tilesetPath = join(`${outputPath}`, `${tilesetName}`);
265
333
  // Removing the tilesetPath needed to exclude erroneous files after conversion
266
334
  try {
@@ -271,13 +339,24 @@ export default class I3SConverter {
271
339
 
272
340
  this.layers0Path = join(tilesetPath, 'SceneServer', 'layers', '0');
273
341
 
274
- this._formLayers0(tilesetName, boundingVolumeRegion);
275
-
276
342
  this.materialDefinitions = [];
277
343
  this.materialMap = new Map();
278
344
 
279
- const sourceRootTile: Tile3D = this.sourceTileset!.root!;
280
- const boundingVolumes = createBoundingVolumes(sourceRootTile, this.geoidHeightModel!);
345
+ const sourceRootTile: Tiles3DTileJSONPostprocessed = this.sourceTileset!.root!;
346
+ const sourceBoundingVolume = createBoundingVolume(
347
+ sourceRootTile.boundingVolume,
348
+ new Matrix4(sourceRootTile.transform),
349
+ null
350
+ );
351
+
352
+ this._formLayers0(
353
+ tilesetName,
354
+ sourceBoundingVolume,
355
+ this.sourceTileset?.root?.boundingVolume?.region
356
+ );
357
+
358
+ const boundingVolumes = createBoundingVolumes(sourceBoundingVolume, this.geoidHeightModel!);
359
+
281
360
  await this.nodePages.push({
282
361
  index: 0,
283
362
  lodThreshold: 0,
@@ -286,7 +365,16 @@ export default class I3SConverter {
286
365
  });
287
366
 
288
367
  const rootNode = await NodeIndexDocument.createRootNode(boundingVolumes, this);
289
- await this._convertNodesTree(rootNode, sourceRootTile);
368
+ await traverseDatasetWith<TraversalConversionProps>(
369
+ sourceRootTile,
370
+ {
371
+ transform: new Matrix4(sourceRootTile.transform),
372
+ parentNodes: [rootNode]
373
+ },
374
+ this.convertTile.bind(this),
375
+ this.finalizeTile.bind(this),
376
+ this.options.maxDepth
377
+ );
290
378
 
291
379
  this.layers0!.materialDefinitions = this.materialDefinitions;
292
380
  // @ts-ignore
@@ -317,12 +405,19 @@ export default class I3SConverter {
317
405
 
318
406
  /**
319
407
  * Form object of 3DSceneLayer https://github.com/Esri/i3s-spec/blob/master/docs/1.7/3DSceneLayer.cmn.md
320
- * @param tilesetName - Name of layer
408
+ * @param tilesetName - Name of layer
409
+ * @param sourceBoundingVolume - initialized bounding volume of the source root tile
410
+ * @param boundingVolumeRegion - region bounding volume of the source root tile
321
411
  */
322
- private _formLayers0(tilesetName: string, boundingVolumeRegion?: number[]): void {
323
- const fullExtent = convertBoundingVolumeToI3SFullExtent(
324
- this.sourceTileset?.boundingVolume || this.sourceTileset?.root?.boundingVolume
325
- );
412
+ private _formLayers0(
413
+ tilesetName: string,
414
+ sourceBoundingVolume: OrientedBoundingBox | BoundingSphere,
415
+ boundingVolumeRegion?: number[]
416
+ ): void {
417
+ if (!this.sourceTileset?.root) {
418
+ return;
419
+ }
420
+ const fullExtent = convertBoundingVolumeToI3SFullExtent(sourceBoundingVolume);
326
421
  if (boundingVolumeRegion) {
327
422
  fullExtent.zmin = boundingVolumeRegion[4];
328
423
  fullExtent.zmax = boundingVolumeRegion[5];
@@ -346,33 +441,6 @@ export default class I3SConverter {
346
441
  this.layers0 = transform(layers0data, layersTemplate());
347
442
  }
348
443
 
349
- /**
350
- * Form object of 3DSceneLayer https://github.com/Esri/i3s-spec/blob/master/docs/1.7/3DSceneLayer.cmn.md
351
- * @param rootNode - 3DNodeIndexDocument of root node https://github.com/Esri/i3s-spec/blob/master/docs/1.7/3DNodeIndexDocument.cmn.md
352
- * @param sourceRootTile - Source (3DTile) tile data
353
- */
354
- private async _convertNodesTree(
355
- rootNode: NodeIndexDocument,
356
- sourceRootTile: Tile3D
357
- ): Promise<void> {
358
- await this.sourceTileset!._loadTile(sourceRootTile);
359
- if (this.isContentSupported(sourceRootTile)) {
360
- const childNodes = await this._createNode(rootNode, sourceRootTile, 0);
361
- for (const childNode of childNodes) {
362
- await childNode.save();
363
- }
364
- await rootNode.addChildren(childNodes);
365
- } else {
366
- await this._addChildrenWithNeighborsAndWriteFile({
367
- parentNode: rootNode,
368
- sourceTiles: sourceRootTile.children,
369
- level: 1
370
- });
371
- }
372
- await sourceRootTile.unloadContent();
373
- await rootNode.save();
374
- }
375
-
376
444
  /**
377
445
  * Write 3DSceneLayer https://github.com/Esri/i3s-spec/blob/master/docs/1.7/3DSceneLayer.cmn.md in file
378
446
  */
@@ -433,113 +501,90 @@ export default class I3SConverter {
433
501
  }
434
502
 
435
503
  /**
436
- * Add child nodes recursively and write them to files
437
- * @param data - arguments
438
- * @param data.parentNode - 3DNodeIndexDocument of parent node
439
- * @param data.sourceTiles - array of source child nodes
440
- * @param data.level - level of node (distanse to root node in the tree)
441
- */
442
- private async _addChildrenWithNeighborsAndWriteFile(data: {
443
- parentNode: NodeIndexDocument;
444
- sourceTiles: Tile3D[];
445
- level: number;
446
- }): Promise<void> {
447
- await this._addChildren(data);
448
- await data.parentNode.addNeighbors();
449
- }
450
-
451
- /**
452
- * Convert nested subtree of 3DTiles dataset
453
- * @param param0
454
- * @param data.parentNode - 3DNodeIndexDocument of parent node
455
- * @param param0.sourceTile - source 3DTile data
456
- * @param param0.level - tree level
504
+ * Convert the specific 3DTiles tile to I3S nodes.
505
+ * This is callback function for the traversal generic function
506
+ * @param sourceTile - current 3DTiles tile JSON metadata
507
+ * @param traversalProps - traversal properties calculated recursively
508
+ * @returns - traversal properties for the child tiles
457
509
  */
458
- private async convertNestedTileset({
459
- parentNode,
460
- sourceTile,
461
- level
462
- }: {
463
- parentNode: NodeIndexDocument;
464
- sourceTile: Tile3D;
465
- level: number;
466
- }) {
467
- await this.sourceTileset!._loadTile(sourceTile);
468
- await this._addChildren({
469
- parentNode,
470
- sourceTiles: sourceTile.children,
471
- level: level + 1
472
- });
473
- await sourceTile.unloadContent();
474
- }
510
+ private async convertTile(
511
+ sourceTile: Tiles3DTileJSONPostprocessed,
512
+ traversalProps: TraversalConversionProps
513
+ ): Promise<TraversalConversionProps> {
514
+ if (sourceTile.type === 'json' || sourceTile.type === 'empty') {
515
+ if (sourceTile.type === 'json') {
516
+ if (sourceTile.id) {
517
+ console.log(`[load]: ${sourceTile.id}`); // eslint-disable-line
518
+ }
519
+ await loadNestedTileset(this.sourceTileset, sourceTile, this.loadOptions);
520
+ }
521
+ return traversalProps;
522
+ }
523
+ if (sourceTile.id) {
524
+ console.log(`[convert]: ${sourceTile.id}`); // eslint-disable-line
525
+ }
475
526
 
476
- /**
477
- * Convert 3DTiles tile to I3S node
478
- * @param param0
479
- * @param param0.parentNode - 3DNodeIndexDocument of parent node
480
- * @param param0.sourceTile - source 3DTile data
481
- * @param param0.level - tree level
482
- */
483
- private async convertNode({
484
- parentNode,
485
- sourceTile,
486
- level
487
- }: {
488
- parentNode: NodeIndexDocument;
489
- sourceTile: Tile3D;
490
- level: number;
491
- }) {
492
- const childNodes = await this._createNode(parentNode, sourceTile, level);
527
+ const {parentNodes, transform} = traversalProps;
528
+ let transformationMatrix: Matrix4 = transform.clone();
529
+ if (sourceTile.transform) {
530
+ transformationMatrix = transformationMatrix.multiplyRight(sourceTile.transform);
531
+ }
532
+ const parentNode = parentNodes[0];
533
+ const childNodes = await this._createNode(parentNode, sourceTile, transformationMatrix);
493
534
  await parentNode.addChildren(childNodes);
535
+
536
+ const newTraversalProps: TraversalConversionProps = {
537
+ transform: transformationMatrix,
538
+ parentNodes: childNodes
539
+ };
540
+ return newTraversalProps;
494
541
  }
495
542
 
496
543
  /**
497
- * Add child nodes recursively and write them to files
498
- * @param param0 - arguments
499
- * @param param0.parentNode - 3DNodeIndexDocument of parent node
500
- * @param param0.sourceTile - source 3DTile data
501
- * @param param0.level - tree level
544
+ * Do final action with nodes after the current node and all child nodes been converted.
545
+ * @param conversionResults - array of conversion results of the current node
546
+ * @param currentTraversalProps - traversal properties of the current node
502
547
  */
503
- private async _addChildren(data: {
504
- parentNode: NodeIndexDocument;
505
- sourceTiles: Tile3D[];
506
- level: number;
507
- }): Promise<void> {
508
- const {sourceTiles, parentNode, level} = data;
509
- if (this.options.maxDepth && level > this.options.maxDepth) {
510
- return;
511
- }
512
- for (const sourceTile of sourceTiles) {
513
- if (sourceTile.type === 'json') {
514
- await this.convertNestedTileset({parentNode, sourceTile, level});
515
- } else {
516
- await this.convertNode({parentNode, sourceTile, level});
517
- }
518
- if (sourceTile.id) {
519
- console.log(sourceTile.id); // eslint-disable-line
548
+ private async finalizeTile(
549
+ conversionResults: TraversalConversionProps[],
550
+ currentTraversalProps: TraversalConversionProps
551
+ ): Promise<void> {
552
+ for (const result of conversionResults) {
553
+ for (const node of result.parentNodes) {
554
+ await node.addNeighbors();
520
555
  }
521
556
  }
557
+ for (const node of currentTraversalProps.parentNodes) {
558
+ await node.save();
559
+ }
522
560
  }
523
561
 
524
562
  /**
525
563
  * Convert tile to one or more I3S nodes
526
564
  * @param parentNode - 3DNodeIndexDocument of parent node
527
565
  * @param sourceTile - source 3DTile data
566
+ * @param transformationMatrix - transformation matrix of the current tile, calculated recursively multiplying
567
+ * transform of all parent tiles and transform of the current tile
528
568
  * @param level - tree level
529
569
  */
530
570
  private async _createNode(
531
571
  parentNode: NodeIndexDocument,
532
- sourceTile: Tile3D,
533
- level: number
572
+ sourceTile: Tiles3DTileJSONPostprocessed,
573
+ transformationMatrix: Matrix4
534
574
  ): Promise<NodeIndexDocument[]> {
535
575
  this._checkAddRefinementTypeForTile(sourceTile);
536
576
 
537
577
  await this._updateTilesetOptions();
538
- await this.sourceTileset!._loadTile(sourceTile);
539
578
 
540
- let boundingVolumes = createBoundingVolumes(sourceTile, this.geoidHeightModel!);
579
+ const tileContent = await loadTile3DContent(this.sourceTileset, sourceTile, this.loadOptions);
580
+ const sourceBoundingVolume = createBoundingVolume(
581
+ sourceTile.boundingVolume,
582
+ transformationMatrix,
583
+ null
584
+ );
585
+ let boundingVolumes = createBoundingVolumes(sourceBoundingVolume, this.geoidHeightModel!);
541
586
 
542
- const propertyTable = getPropertyTable(sourceTile.content);
587
+ const propertyTable = getPropertyTable(tileContent);
543
588
 
544
589
  if (propertyTable && !this.layers0?.attributeStorageInfo?.length) {
545
590
  this._convertPropertyTableToNodeAttributes(propertyTable);
@@ -547,6 +592,9 @@ export default class I3SConverter {
547
592
 
548
593
  const resourcesData = await this._convertResources(
549
594
  sourceTile,
595
+ transformationMatrix,
596
+ sourceBoundingVolume,
597
+ tileContent,
550
598
  parentNode.inPageId,
551
599
  propertyTable
552
600
  );
@@ -613,29 +661,29 @@ export default class I3SConverter {
613
661
  nodesInPage.push(nodeInPage);
614
662
  }
615
663
 
616
- sourceTile.unloadContent();
617
-
618
- await this._addChildrenWithNeighborsAndWriteFile({
619
- parentNode: nodes[0],
620
- sourceTiles: sourceTile.children,
621
- level: level + 1
622
- });
623
664
  return nodes;
624
665
  }
625
666
 
626
667
  /**
627
668
  * Convert tile to one or more I3S nodes
628
669
  * @param sourceTile - source tile (3DTile)
670
+ * @param transformationMatrix - transformation matrix of the current tile, calculated recursively multiplying
671
+ * transform of all parent tiles and transform of the current tile
672
+ * @param boundingVolume - initialized bounding volume of the source tile
673
+ * @param tileContent - content of the source tile
629
674
  * @param parentId - id of parent node in node pages
630
675
  * @param propertyTable - batch table from b3dm / feature properties from EXT_FEATURE_METADATA
631
676
  * @returns - converted node resources
632
677
  */
633
678
  private async _convertResources(
634
- sourceTile: Tile3D,
679
+ sourceTile: Tiles3DTileJSONPostprocessed,
680
+ transformationMatrix: Matrix4,
681
+ boundingVolume: OrientedBoundingBox | BoundingSphere,
682
+ tileContent: Tiles3DTileContent | null,
635
683
  parentId: number,
636
684
  propertyTable: FeatureTableJson | null
637
685
  ): Promise<I3SConvertedResources[] | null> {
638
- if (!this.isContentSupported(sourceTile)) {
686
+ if (!this.isContentSupported(sourceTile) || !tileContent) {
639
687
  return null;
640
688
  }
641
689
  const draftObb = {
@@ -644,7 +692,9 @@ export default class I3SConverter {
644
692
  quaternion: []
645
693
  };
646
694
  const resourcesData = await convertB3dmToI3sGeometry(
647
- sourceTile.content,
695
+ tileContent,
696
+ transformationMatrix,
697
+ boundingVolume,
648
698
  async () => (await this.nodePages.push({index: 0, obb: draftObb}, parentId)).index,
649
699
  propertyTable,
650
700
  this.featuresHashArray,
@@ -676,7 +726,7 @@ export default class I3SConverter {
676
726
  private async _updateNodeInNodePages(
677
727
  maxScreenThresholdSQ: MaxScreenThresholdSQ,
678
728
  boundingVolumes: BoundingVolumes,
679
- sourceTile: Tile3D,
729
+ sourceTile: Tiles3DTileJSONPostprocessed,
680
730
  parentId: number,
681
731
  resources: I3SConvertedResources
682
732
  ): Promise<NodeInPage> {
@@ -1096,10 +1146,9 @@ export default class I3SConverter {
1096
1146
  this.refreshTokenTime = process.hrtime();
1097
1147
 
1098
1148
  const preloadOptions = await this._fetchPreloadOptions();
1099
- this.sourceTileset!.options = {...this.sourceTileset!.options, ...preloadOptions};
1100
1149
  if (preloadOptions.headers) {
1101
- this.sourceTileset!.loadOptions.fetch = {
1102
- ...this.sourceTileset!.loadOptions.fetch,
1150
+ this.loadOptions.fetch = {
1151
+ ...this.loadOptions.fetch,
1103
1152
  headers: preloadOptions.headers
1104
1153
  };
1105
1154
  console.log('Authorization Bearer token has been updated'); // eslint-disable-line no-undef, no-console
@@ -1109,7 +1158,7 @@ export default class I3SConverter {
1109
1158
  /** Do calculations of all tiles and tiles with "ADD" type of refinement.
1110
1159
  * @param tile
1111
1160
  */
1112
- private _checkAddRefinementTypeForTile(tile: Tile3D): void {
1161
+ private _checkAddRefinementTypeForTile(tile: Tiles3DTileJSONPostprocessed): void {
1113
1162
  const ADD_TILE_REFINEMENT = 1;
1114
1163
 
1115
1164
  if (tile.refine === ADD_TILE_REFINEMENT) {
@@ -1119,13 +1168,14 @@ export default class I3SConverter {
1119
1168
 
1120
1169
  this.refinementCounter.tilesCount += 1;
1121
1170
  }
1171
+
1122
1172
  /**
1123
1173
  * Check if the tile's content format is supported by the converter
1124
- * @param sourceRootTile
1174
+ * @param sourceTile
1125
1175
  * @returns
1126
1176
  */
1127
- private isContentSupported(sourceRootTile: Tile3D): boolean {
1128
- return ['b3dm', 'glTF'].includes(sourceRootTile?.content?.type);
1177
+ private isContentSupported(sourceTile: Tiles3DTileJSONPostprocessed): boolean {
1178
+ return ['b3dm', 'glTF', 'scenegraph'].includes(sourceTile.type || '');
1129
1179
  }
1130
1180
 
1131
1181
  private async loadWorkers(): Promise<void> {
@@ -163,3 +163,23 @@ export type TypedArrayConstructor =
163
163
  | Uint32ArrayConstructor
164
164
  | Float32ArrayConstructor
165
165
  | Float64ArrayConstructor;
166
+
167
+ /**
168
+ * glTF primitive modes (mesh topology types)
169
+ * @see https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html#_mesh_primitive_mode
170
+ */
171
+ export enum GltfPrimitiveModeString {
172
+ POINTS = 'POINTS',
173
+ LINES = 'LINES',
174
+ LINE_LOOP = 'LINE_LOOP',
175
+ LINE_STRIP = 'LINE_STRIP',
176
+ TRIANGLES = 'TRIANGLES',
177
+ TRIANGLE_STRIP = 'TRIANGLE_STRIP',
178
+ TRIANGLE_FAN = 'TRIANGLE_FAN'
179
+ }
180
+
181
+ /** Preprocessed data gathered from child tiles binary content */
182
+ export type PreprocessData = {
183
+ /** Mesh topology types used in gltf primitives of the tileset */
184
+ meshTopologyTypes: Set<GltfPrimitiveModeString>;
185
+ };
@@ -0,0 +1,19 @@
1
+ # I3S server
2
+
3
+ The http server run on NodeJS ExpressJS framework.
4
+ The server provides I3S Rest endpoints per specification https://github.com/Esri/i3s-spec/blob/master/docs/1.7/3Dobject_ReadMe.md#http-api-overview-17
5
+
6
+ ## Usage
7
+
8
+ ### Serve 3DTiles to I3S converted dataset
9
+
10
+ - Convert data set from 3DTiles to I3S without `--slpk` option
11
+ - Serve output folder `I3sLayerPath="./data/BatchedTextured" DEBUG=i3s-server:* npx i3s-server`
12
+
13
+ ### Serve SLPK
14
+
15
+ - Serve slpk file `I3sLayerPath="../datasets/Rancho_Mesh_mesh_v17_1.slpk" DEBUG=i3s-server:* npx i3s-server`
16
+
17
+ ## ENV variables
18
+
19
+ - `I3sLayerPath` - path to converted data or SLPK file.
@@ -4,7 +4,9 @@ const logger = require('morgan');
4
4
  const cors = require('cors');
5
5
 
6
6
  const indexRouter = require('./routes/index');
7
+ const {sceneServerRouter, router} = require('./routes/slpk-router');
7
8
 
9
+ const I3S_LAYER_PATH = process.env.I3sLayerPath || ''; // eslint-disable-line no-process-env, no-undef
8
10
  const app = express();
9
11
 
10
12
  app.use(logger('dev'));
@@ -13,6 +15,11 @@ app.use(express.urlencoded({extended: false}));
13
15
  app.use(express.static(path.join(__dirname, 'public')));
14
16
  app.use(cors());
15
17
 
16
- app.use('/', indexRouter);
18
+ if (/\.slpk$/.test(I3S_LAYER_PATH)) {
19
+ app.use('/SceneServer/layers/0', router);
20
+ app.use('/SceneServer', sceneServerRouter);
21
+ } else {
22
+ app.use('/', indexRouter);
23
+ }
17
24
 
18
25
  module.exports = app;