@loaders.gl/tile-converter 4.2.0-alpha.3 → 4.2.0-alpha.5

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 (144) hide show
  1. package/dist/3d-tiles-converter/3d-tiles-converter.d.ts +3 -3
  2. package/dist/3d-tiles-converter/3d-tiles-converter.d.ts.map +1 -1
  3. package/dist/3d-tiles-converter/3d-tiles-converter.js +329 -293
  4. package/dist/3d-tiles-converter/helpers/b3dm-converter.js +257 -200
  5. package/dist/3d-tiles-converter/helpers/i3s-obb-to-3d-tiles-obb.js +14 -5
  6. package/dist/3d-tiles-converter/helpers/load-i3s.js +106 -75
  7. package/dist/3d-tiles-converter/helpers/texture-atlas.js +44 -21
  8. package/dist/3d-tiles-converter/json-templates/tileset.js +32 -33
  9. package/dist/constants.js +0 -1
  10. package/dist/converter-cli.js +255 -234
  11. package/dist/converter.min.cjs +80 -90
  12. package/dist/deps-installer/deps-installer.js +73 -59
  13. package/dist/i3s-converter/helpers/attribute-metadata-info.js +207 -153
  14. package/dist/i3s-converter/helpers/batch-ids-extensions.d.ts +1 -1
  15. package/dist/i3s-converter/helpers/batch-ids-extensions.d.ts.map +1 -1
  16. package/dist/i3s-converter/helpers/batch-ids-extensions.js +145 -103
  17. package/dist/i3s-converter/helpers/coordinate-converter.js +100 -65
  18. package/dist/i3s-converter/helpers/create-scene-server-path.js +14 -9
  19. package/dist/i3s-converter/helpers/feature-attributes.js +168 -105
  20. package/dist/i3s-converter/helpers/geometry-attributes.d.ts +1 -1
  21. package/dist/i3s-converter/helpers/geometry-attributes.d.ts.map +1 -1
  22. package/dist/i3s-converter/helpers/geometry-attributes.js +204 -212
  23. package/dist/i3s-converter/helpers/geometry-converter.d.ts +2 -2
  24. package/dist/i3s-converter/helpers/geometry-converter.d.ts.map +1 -1
  25. package/dist/i3s-converter/helpers/geometry-converter.js +1148 -829
  26. package/dist/i3s-converter/helpers/gltf-attributes.d.ts +1 -1
  27. package/dist/i3s-converter/helpers/gltf-attributes.d.ts.map +1 -1
  28. package/dist/i3s-converter/helpers/gltf-attributes.js +111 -97
  29. package/dist/i3s-converter/helpers/load-3d-tiles.js +103 -66
  30. package/dist/i3s-converter/helpers/node-debug.js +98 -54
  31. package/dist/i3s-converter/helpers/node-index-document.d.ts +3 -3
  32. package/dist/i3s-converter/helpers/node-index-document.d.ts.map +1 -1
  33. package/dist/i3s-converter/helpers/node-index-document.js +248 -177
  34. package/dist/i3s-converter/helpers/node-pages.d.ts +1 -1
  35. package/dist/i3s-converter/helpers/node-pages.d.ts.map +1 -1
  36. package/dist/i3s-converter/helpers/node-pages.js +299 -194
  37. package/dist/i3s-converter/helpers/preprocess-3d-tiles.d.ts +1 -1
  38. package/dist/i3s-converter/helpers/preprocess-3d-tiles.d.ts.map +1 -1
  39. package/dist/i3s-converter/helpers/preprocess-3d-tiles.js +92 -60
  40. package/dist/i3s-converter/helpers/progress.js +134 -83
  41. package/dist/i3s-converter/helpers/tileset-traversal.d.ts +1 -1
  42. package/dist/i3s-converter/helpers/tileset-traversal.d.ts.map +1 -1
  43. package/dist/i3s-converter/helpers/tileset-traversal.js +24 -13
  44. package/dist/i3s-converter/i3s-converter.d.ts +7 -7
  45. package/dist/i3s-converter/i3s-converter.d.ts.map +1 -1
  46. package/dist/i3s-converter/i3s-converter.js +1044 -898
  47. package/dist/i3s-converter/json-templates/geometry-definitions.js +70 -79
  48. package/dist/i3s-converter/json-templates/layers.js +120 -121
  49. package/dist/i3s-converter/json-templates/metadata.js +19 -20
  50. package/dist/i3s-converter/json-templates/node.js +73 -71
  51. package/dist/i3s-converter/json-templates/scene-server.js +25 -26
  52. package/dist/i3s-converter/json-templates/shared-resources.js +107 -108
  53. package/dist/i3s-converter/json-templates/store.js +96 -94
  54. package/dist/i3s-converter/types.js +35 -23
  55. package/dist/i3s-server/app.js +15 -12
  56. package/dist/i3s-server/bin/www.js +14 -7
  57. package/dist/i3s-server/controllers/index-controller.js +18 -15
  58. package/dist/i3s-server/controllers/slpk-controller.js +22 -11
  59. package/dist/i3s-server/routes/index.js +9 -8
  60. package/dist/i3s-server/routes/slpk-router.js +18 -17
  61. package/dist/i3s-server/utils/create-scene-server.js +15 -10
  62. package/dist/i3s-server/utils/server-utils.js +49 -32
  63. package/dist/index.cjs +292 -851
  64. package/dist/index.cjs.map +7 -0
  65. package/dist/index.d.ts +2 -2
  66. package/dist/index.d.ts.map +1 -1
  67. package/dist/index.js +0 -1
  68. package/dist/lib/json-schemas/conversion-dump-json-schema.js +243 -421
  69. package/dist/lib/utils/cli-utils.js +63 -36
  70. package/dist/lib/utils/compress-util.js +20 -15
  71. package/dist/lib/utils/conversion-dump.d.ts +1 -1
  72. package/dist/lib/utils/conversion-dump.d.ts.map +1 -1
  73. package/dist/lib/utils/conversion-dump.js +209 -187
  74. package/dist/lib/utils/file-utils.js +122 -74
  75. package/dist/lib/utils/geometry-utils.js +13 -7
  76. package/dist/lib/utils/lod-conversion-utils.js +65 -33
  77. package/dist/lib/utils/queue.js +12 -13
  78. package/dist/lib/utils/statistic-utills.d.ts +6 -23
  79. package/dist/lib/utils/statistic-utills.js +64 -59
  80. package/dist/lib/utils/write-queue.d.ts +2 -2
  81. package/dist/lib/utils/write-queue.d.ts.map +1 -1
  82. package/dist/lib/utils/write-queue.js +68 -86
  83. package/dist/pgm-loader.js +17 -13
  84. package/dist/slpk-extractor/slpk-extractor.js +59 -50
  85. package/dist/slpk-extractor-cli.js +82 -59
  86. package/package.json +18 -18
  87. package/dist/3d-tiles-converter/3d-tiles-converter.js.map +0 -1
  88. package/dist/3d-tiles-converter/helpers/b3dm-converter.js.map +0 -1
  89. package/dist/3d-tiles-converter/helpers/i3s-obb-to-3d-tiles-obb.js.map +0 -1
  90. package/dist/3d-tiles-converter/helpers/load-i3s.js.map +0 -1
  91. package/dist/3d-tiles-converter/helpers/texture-atlas.js.map +0 -1
  92. package/dist/3d-tiles-converter/json-templates/tileset.js.map +0 -1
  93. package/dist/constants.js.map +0 -1
  94. package/dist/converter-cli.js.map +0 -1
  95. package/dist/deps-installer/deps-installer.js.map +0 -1
  96. package/dist/i3s-converter/helpers/attribute-metadata-info.js.map +0 -1
  97. package/dist/i3s-converter/helpers/batch-ids-extensions.js.map +0 -1
  98. package/dist/i3s-converter/helpers/coordinate-converter.js.map +0 -1
  99. package/dist/i3s-converter/helpers/create-scene-server-path.js.map +0 -1
  100. package/dist/i3s-converter/helpers/feature-attributes.js.map +0 -1
  101. package/dist/i3s-converter/helpers/geometry-attributes.js.map +0 -1
  102. package/dist/i3s-converter/helpers/geometry-converter.js.map +0 -1
  103. package/dist/i3s-converter/helpers/gltf-attributes.js.map +0 -1
  104. package/dist/i3s-converter/helpers/load-3d-tiles.js.map +0 -1
  105. package/dist/i3s-converter/helpers/node-debug.js.map +0 -1
  106. package/dist/i3s-converter/helpers/node-index-document.js.map +0 -1
  107. package/dist/i3s-converter/helpers/node-pages.js.map +0 -1
  108. package/dist/i3s-converter/helpers/preprocess-3d-tiles.js.map +0 -1
  109. package/dist/i3s-converter/helpers/progress.js.map +0 -1
  110. package/dist/i3s-converter/helpers/tileset-traversal.js.map +0 -1
  111. package/dist/i3s-converter/i3s-converter.js.map +0 -1
  112. package/dist/i3s-converter/json-templates/geometry-definitions.js.map +0 -1
  113. package/dist/i3s-converter/json-templates/layers.js.map +0 -1
  114. package/dist/i3s-converter/json-templates/metadata.js.map +0 -1
  115. package/dist/i3s-converter/json-templates/node.js.map +0 -1
  116. package/dist/i3s-converter/json-templates/scene-server.js.map +0 -1
  117. package/dist/i3s-converter/json-templates/shared-resources.js.map +0 -1
  118. package/dist/i3s-converter/json-templates/store.js.map +0 -1
  119. package/dist/i3s-converter/types.js.map +0 -1
  120. package/dist/i3s-server/README.md +0 -63
  121. package/dist/i3s-server/app.js.map +0 -1
  122. package/dist/i3s-server/bin/www.js.map +0 -1
  123. package/dist/i3s-server/certs/cert.pem +0 -19
  124. package/dist/i3s-server/certs/key.pem +0 -27
  125. package/dist/i3s-server/controllers/index-controller.js.map +0 -1
  126. package/dist/i3s-server/controllers/slpk-controller.js.map +0 -1
  127. package/dist/i3s-server/routes/index.js.map +0 -1
  128. package/dist/i3s-server/routes/slpk-router.js.map +0 -1
  129. package/dist/i3s-server/utils/create-scene-server.js.map +0 -1
  130. package/dist/i3s-server/utils/server-utils.js.map +0 -1
  131. package/dist/index.js.map +0 -1
  132. package/dist/lib/json-schemas/conversion-dump-json-schema.js.map +0 -1
  133. package/dist/lib/utils/cli-utils.js.map +0 -1
  134. package/dist/lib/utils/compress-util.js.map +0 -1
  135. package/dist/lib/utils/conversion-dump.js.map +0 -1
  136. package/dist/lib/utils/file-utils.js.map +0 -1
  137. package/dist/lib/utils/geometry-utils.js.map +0 -1
  138. package/dist/lib/utils/lod-conversion-utils.js.map +0 -1
  139. package/dist/lib/utils/queue.js.map +0 -1
  140. package/dist/lib/utils/statistic-utills.js.map +0 -1
  141. package/dist/lib/utils/write-queue.js.map +0 -1
  142. package/dist/pgm-loader.js.map +0 -1
  143. package/dist/slpk-extractor/slpk-extractor.js.map +0 -1
  144. package/dist/slpk-extractor-cli.js.map +0 -1
@@ -1,4 +1,6 @@
1
- var _process$env;
1
+ // loaders.gl
2
+ // SPDX-License-Identifier: MIT
3
+ // Copyright (c) vis.gl contributors
2
4
  import { AttributeMetadataInfo } from "./helpers/attribute-metadata-info.js";
3
5
  import { load, encode, isBrowser } from '@loaders.gl/core';
4
6
  import { CesiumIonLoader, Tiles3DLoader } from '@loaders.gl/3d-tiles';
@@ -36,946 +38,1090 @@ import { analyzeTileContent, mergePreprocessData } from "./helpers/preprocess-3d
36
38
  import { Progress } from "./helpers/progress.js";
37
39
  import { composeHashFile, createZip } from '@loaders.gl/zip';
38
40
  import { ConversionDump } from "../lib/utils/conversion-dump.js";
39
- const ION_DEFAULT_TOKEN = (_process$env = process.env) === null || _process$env === void 0 ? void 0 : _process$env.IonToken;
41
+ const ION_DEFAULT_TOKEN = process.env?.IonToken;
40
42
  const HARDCODED_NODES_PER_PAGE = 64;
41
43
  const _3D_TILES = '3DTILES';
42
44
  const _3D_OBJECT_LAYER_TYPE = '3DObject';
43
- const REFRESH_TOKEN_TIMEOUT = 1800;
45
+ const REFRESH_TOKEN_TIMEOUT = 1800; // 30 minutes in seconds
44
46
  const CESIUM_DATASET_PREFIX = 'https://';
47
+ // const FS_FILE_TOO_LARGE = 'ERR_FS_FILE_TOO_LARGE';
45
48
  const PROGRESS_PHASE1_COUNT = 'phase1-count';
49
+ /**
50
+ * Converter from 3d-tiles tileset to i3s layer
51
+ */
46
52
  export default class I3SConverter {
47
- constructor() {
48
- this.attributeMetadataInfo = void 0;
49
- this.nodePages = void 0;
50
- this.options = void 0;
51
- this.layers0Path = void 0;
52
- this.materialMap = void 0;
53
- this.materialDefinitions = void 0;
54
- this.geometryMap = void 0;
55
- this.geometryConfigs = void 0;
56
- this.vertexCounter = void 0;
57
- this.layers0 = void 0;
58
- this.featuresHashArray = void 0;
59
- this.refinementCounter = void 0;
60
- this.validate = void 0;
61
- this.boundingVolumeWarnings = [];
62
- this.conversionStartTime = [0, 0];
63
- this.refreshTokenTime = [0, 0];
64
- this.sourceTileset = null;
65
- this.loadOptions = {
66
- _nodeWorkers: true,
67
- reuseWorkers: true,
68
- useLocalLibraries: true,
69
- basis: {
70
- format: 'rgba32',
71
- workerUrl: './modules/textures/dist/basis-worker-node.js'
72
- },
73
- draco: {
74
- workerUrl: './modules/draco/dist/draco-worker-node.js'
75
- },
76
- fetch: {},
77
- modules: {}
78
- };
79
- this.geoidHeightModel = null;
80
- this.Loader = Tiles3DLoader;
81
- this.generateTextures = void 0;
82
- this.generateBoundingVolumes = void 0;
83
- this.layersHasTexture = void 0;
84
- this.workerSource = {};
85
- this.writeQueue = new WriteQueue(new ConversionDump());
86
- this.compressList = null;
87
- this.preprocessData = {
88
- meshTopologyTypes: new Set(),
89
- metadataClasses: new Set()
90
- };
91
- this.progresses = {};
92
- this.conversionDump = void 0;
93
- this.attributeMetadataInfo = new AttributeMetadataInfo();
94
- this.nodePages = new NodePages(writeFile, HARDCODED_NODES_PER_PAGE, this);
95
- this.options = {};
96
- this.layers0Path = '';
97
- this.materialMap = new Map();
98
- this.materialDefinitions = [];
99
- this.geometryMap = new Map();
100
- this.geometryConfigs = [];
101
- this.vertexCounter = 0;
102
- this.layers0 = null;
103
- this.featuresHashArray = [];
104
- this.refinementCounter = {
105
- tilesCount: 0,
106
- tilesWithAddRefineCount: 0
107
- };
108
- this.validate = false;
109
- this.generateTextures = false;
110
- this.generateBoundingVolumes = false;
111
- this.layersHasTexture = false;
112
- this.compressList = null;
113
- this.conversionDump = new ConversionDump();
114
- }
115
- async convert(options) {
116
- if (isBrowser) {
117
- console.log(BROWSER_ERROR_MESSAGE);
118
- return BROWSER_ERROR_MESSAGE;
119
- }
120
- this.conversionStartTime = process.hrtime();
121
- const {
122
- tilesetName,
123
- slpk,
124
- egmFilePath,
125
- inputUrl,
126
- validate,
127
- outputPath,
128
- draco = true,
129
- sevenZipExe,
130
- maxDepth,
131
- token,
132
- generateTextures,
133
- generateBoundingVolumes,
134
- instantNodeWriting = false,
135
- mergeMaterials = true,
136
- inquirer,
137
- metadataClass,
138
- analyze = false
139
- } = options;
140
- this.options = {
141
- outputPath,
142
- tilesetName,
143
- maxDepth,
144
- slpk,
145
- sevenZipExe,
146
- egmFilePath,
147
- draco,
148
- token,
149
- inputUrl,
150
- instantNodeWriting,
151
- mergeMaterials,
152
- inquirer,
153
- metadataClass
154
- };
155
- this.progresses[PROGRESS_PHASE1_COUNT] = new Progress();
156
- this.compressList = this.options.instantNodeWriting && [] || null;
157
- this.validate = Boolean(validate);
158
- this.Loader = inputUrl.indexOf(CESIUM_DATASET_PREFIX) !== -1 ? CesiumIonLoader : Tiles3DLoader;
159
- this.generateTextures = Boolean(generateTextures);
160
- this.generateBoundingVolumes = Boolean(generateBoundingVolumes);
161
- this.writeQueue = new WriteQueue(this.conversionDump);
162
- this.writeQueue.startListening();
163
- console.log('Loading egm file...');
164
- this.geoidHeightModel = await load(egmFilePath, PGMLoader);
165
- console.log('Loading egm file completed!');
166
- if (slpk) {
167
- this.nodePages.useWriteFunction(writeFileForSlpk);
168
- }
169
- try {
170
- const preloadOptions = await this._fetchPreloadOptions();
171
- let tilesetUrl = inputUrl;
172
- if (preloadOptions.url) {
173
- tilesetUrl = preloadOptions.url;
174
- }
175
- if (preloadOptions.headers) {
176
- this.loadOptions.fetch = {
177
- headers: preloadOptions.headers
53
+ constructor() {
54
+ this.boundingVolumeWarnings = [];
55
+ this.conversionStartTime = [0, 0];
56
+ this.refreshTokenTime = [0, 0];
57
+ this.sourceTileset = null;
58
+ this.loadOptions = {
59
+ _nodeWorkers: true,
60
+ reuseWorkers: true,
61
+ useLocalLibraries: true,
62
+ basis: {
63
+ format: 'rgba32',
64
+ // We need to load local fs workers because nodejs can't load workers from the Internet
65
+ workerUrl: './modules/textures/dist/basis-worker-node.js'
66
+ },
67
+ // We need to load local fs workers because nodejs can't load workers from the Internet
68
+ draco: { workerUrl: './modules/draco/dist/draco-worker-node.js' },
69
+ fetch: {},
70
+ modules: {}
178
71
  };
179
- }
180
- this.sourceTileset = await loadFromArchive(tilesetUrl, this.Loader, this.loadOptions);
181
- const preprocessResult = this.Loader === Tiles3DLoader || analyze ? await this.preprocessConversion() : true;
182
- if (preprocessResult && !analyze) {
183
- const selectMetadataClassResult = await this.selectMetadataClass();
184
- if (selectMetadataClassResult) {
185
- await this._createAndSaveTileset(outputPath, tilesetName);
186
- await this._finishConversion({
187
- slpk: Boolean(slpk),
188
- outputPath,
189
- tilesetName
190
- });
191
- }
192
- }
193
- } catch (error) {
194
- throw error;
195
- } finally {
196
- await this.writeQueue.finalize();
197
- const workerFarm = WorkerFarm.getWorkerFarm({});
198
- workerFarm.destroy();
199
- }
200
- return 'success';
201
- }
202
- async preprocessConversion() {
203
- console.log(`Analyze source tileset`);
204
- const sourceRootTile = this.sourceTileset.root;
205
- await traverseDatasetWith(sourceRootTile, null, this.analyzeTile.bind(this), undefined, this.options.maxDepth);
206
- const {
207
- meshTopologyTypes,
208
- metadataClasses
209
- } = this.preprocessData;
210
- console.log(`------------------------------------------------`);
211
- console.log(`Preprocess results:`);
212
- console.log(`Tile count: ${this.progresses[PROGRESS_PHASE1_COUNT].stepsTotal}`);
213
- console.log(`glTF mesh topology types: ${Array.from(meshTopologyTypes).join(', ')}`);
214
- if (metadataClasses.size) {
215
- console.log(`Feature metadata classes have been found: ${Array.from(metadataClasses).join(', ')}`);
216
- } else {
217
- console.log('Feature metadata classes have not been found');
218
- }
219
- if (!meshTopologyTypes.has(GLTFPrimitiveModeString.TRIANGLES) && !meshTopologyTypes.has(GLTFPrimitiveModeString.TRIANGLE_STRIP)) {
220
- console.log('The tileset is of unsupported mesh topology types. The conversion will be interrupted.');
221
- console.log(`------------------------------------------------`);
222
- return false;
223
- }
224
- console.log(`------------------------------------------------`);
225
- return true;
226
- }
227
- async analyzeTile(sourceTile, traversalProps) {
228
- const isTileset = isNestedTileset(sourceTile);
229
- if (isTileset) {
230
- await loadNestedTileset(this.sourceTileset, sourceTile, this.loadOptions);
231
- return null;
232
- }
233
- if (sourceTile.id) {
234
- this.progresses[PROGRESS_PHASE1_COUNT].stepsTotal += 1;
235
- console.log(`[analyze]: ${sourceTile.id}`);
72
+ this.geoidHeightModel = null;
73
+ this.Loader = Tiles3DLoader;
74
+ this.workerSource = {};
75
+ this.writeQueue = new WriteQueue(new ConversionDump());
76
+ this.compressList = null;
77
+ this.preprocessData = {
78
+ meshTopologyTypes: new Set(),
79
+ metadataClasses: new Set()
80
+ };
81
+ this.progresses = {};
82
+ this.attributeMetadataInfo = new AttributeMetadataInfo();
83
+ this.nodePages = new NodePages(writeFile, HARDCODED_NODES_PER_PAGE, this);
84
+ this.options = {};
85
+ this.layers0Path = '';
86
+ this.materialMap = new Map();
87
+ this.materialDefinitions = [];
88
+ this.geometryMap = new Map();
89
+ this.geometryConfigs = [];
90
+ this.vertexCounter = 0;
91
+ this.layers0 = null;
92
+ this.featuresHashArray = [];
93
+ this.refinementCounter = {
94
+ tilesCount: 0,
95
+ tilesWithAddRefineCount: 0
96
+ };
97
+ this.validate = false;
98
+ this.generateTextures = false;
99
+ this.generateBoundingVolumes = false;
100
+ this.layersHasTexture = false;
101
+ this.compressList = null;
102
+ this.conversionDump = new ConversionDump();
236
103
  }
237
- let tileContent = null;
238
- try {
239
- tileContent = await loadTile3DContent(this.sourceTileset, sourceTile, {
240
- ...this.loadOptions,
241
- '3d-tiles': {
242
- ...this.loadOptions['3d-tiles'],
243
- loadGLTF: false
244
- }
245
- });
246
- } catch (error) {
247
- console.log(`[warning]: Failed to load ${sourceTile.contentUrl}. An I3S tile with empty content will be added to the output tileset`);
104
+ /**
105
+ * Convert a 3d tileset
106
+ * @param options
107
+ * @param options.inputUrl the url to read the tileset from
108
+ * @param options.outputPath the output filename
109
+ * @param options.tilesetName the output name of the tileset
110
+ * @param options.maxDepth The max tree depth of conversion
111
+ * @param options.slpk Generate slpk (Scene Layer Packages) output file
112
+ * @param options.sevenZipExe Location of 7z.exe archiver to create slpk on Windows
113
+ * @param options.egmFilePath location of *.pgm file to convert heights from ellipsoidal to gravity-related format
114
+ * @param options.token Token for Cesium ION tilesets authentication
115
+ * @param options.draco Generate I3S 1.7 draco compressed geometries
116
+ * @param options.validate -enable validation
117
+ * @param options.generateTextures - generate alternative type of textures (to have non-compressed jpeg/png and compressed ktx2)
118
+ * @param options.generateBoundingVolumes - generate bounding volumes from vertices coordinates instead of source tiles bounding volumes
119
+ * @param options.instantNodeWriting - Keep created 3DNodeIndexDocument files on disk instead of memory. This option reduce memory usage but decelerates conversion speed
120
+ */
121
+ async convert(options) {
122
+ if (isBrowser) {
123
+ console.log(BROWSER_ERROR_MESSAGE);
124
+ return BROWSER_ERROR_MESSAGE;
125
+ }
126
+ this.conversionStartTime = process.hrtime();
127
+ const { tilesetName, slpk, egmFilePath, inputUrl, validate, outputPath, draco = true, sevenZipExe, maxDepth, token, generateTextures, generateBoundingVolumes, instantNodeWriting = false, mergeMaterials = true, inquirer, metadataClass, analyze = false } = options;
128
+ this.options = {
129
+ outputPath,
130
+ tilesetName,
131
+ maxDepth,
132
+ slpk,
133
+ sevenZipExe,
134
+ egmFilePath,
135
+ draco,
136
+ token,
137
+ inputUrl,
138
+ instantNodeWriting,
139
+ mergeMaterials,
140
+ inquirer,
141
+ metadataClass
142
+ };
143
+ this.progresses[PROGRESS_PHASE1_COUNT] = new Progress();
144
+ this.compressList = (this.options.instantNodeWriting && []) || null;
145
+ this.validate = Boolean(validate);
146
+ this.Loader = inputUrl.indexOf(CESIUM_DATASET_PREFIX) !== -1 ? CesiumIonLoader : Tiles3DLoader;
147
+ this.generateTextures = Boolean(generateTextures);
148
+ this.generateBoundingVolumes = Boolean(generateBoundingVolumes);
149
+ this.writeQueue = new WriteQueue(this.conversionDump);
150
+ this.writeQueue.startListening();
151
+ console.log('Loading egm file...'); // eslint-disable-line
152
+ this.geoidHeightModel = await load(egmFilePath, PGMLoader);
153
+ console.log('Loading egm file completed!'); // eslint-disable-line
154
+ if (slpk) {
155
+ this.nodePages.useWriteFunction(writeFileForSlpk);
156
+ }
157
+ try {
158
+ const preloadOptions = await this._fetchPreloadOptions();
159
+ let tilesetUrl = inputUrl;
160
+ if (preloadOptions.url) {
161
+ tilesetUrl = preloadOptions.url;
162
+ }
163
+ if (preloadOptions.headers) {
164
+ this.loadOptions.fetch = { headers: preloadOptions.headers };
165
+ }
166
+ this.sourceTileset = await loadFromArchive(tilesetUrl, this.Loader, this.loadOptions);
167
+ const preprocessResult = this.Loader === Tiles3DLoader || analyze ? await this.preprocessConversion() : true;
168
+ if (preprocessResult && !analyze) {
169
+ const selectMetadataClassResult = await this.selectMetadataClass();
170
+ if (selectMetadataClassResult) {
171
+ await this._createAndSaveTileset(outputPath, tilesetName);
172
+ await this._finishConversion({ slpk: Boolean(slpk), outputPath, tilesetName });
173
+ }
174
+ }
175
+ }
176
+ catch (error) {
177
+ throw error;
178
+ }
179
+ finally {
180
+ await this.writeQueue.finalize();
181
+ // Clean up worker pools
182
+ const workerFarm = WorkerFarm.getWorkerFarm({});
183
+ workerFarm.destroy();
184
+ }
185
+ return 'success';
248
186
  }
249
- const tilePreprocessData = await analyzeTileContent(tileContent);
250
- mergePreprocessData(this.preprocessData, tilePreprocessData);
251
- return null;
252
- }
253
- async selectMetadataClass() {
254
- const {
255
- metadataClasses
256
- } = this.preprocessData;
257
- if (metadataClasses.size > 1) {
258
- var _this$options$metadat;
259
- if ((_this$options$metadat = this.options.metadataClass) !== null && _this$options$metadat !== void 0 && _this$options$metadat.length) {
260
- console.log(`${this.options.metadataClass} has been selected`);
261
- } else if (this.options.inquirer) {
262
- const result = await this.options.inquirer.prompt([{
263
- name: 'metadataClass',
264
- type: 'list',
265
- message: 'Select feature metadata data class to convert...',
266
- choices: Array.from(metadataClasses)
267
- }]);
268
- this.options.metadataClass = result.metadataClass;
269
- console.log(`${result.metadataClass} has been selected`);
270
- } else {
271
- console.log(`A feature metadata class has not been selected. Start the converter with option "--metadata-class". For example, "npx tile-converter ... --metadata-class ${Array.from(metadataClasses)[0]}"`);
187
+ /**
188
+ * Preprocess stage of the tile converter. Traverse all the tiles tree and
189
+ * check a tile content to be sure that the data is supported
190
+ * @returns true - the conversion is possible, false - the tileset's content is not supported
191
+ */
192
+ async preprocessConversion() {
193
+ console.log(`Analyze source tileset`);
194
+ const sourceRootTile = this.sourceTileset.root;
195
+ await traverseDatasetWith(sourceRootTile, null, this.analyzeTile.bind(this), undefined, this.options.maxDepth);
196
+ const { meshTopologyTypes, metadataClasses } = this.preprocessData;
272
197
  console.log(`------------------------------------------------`);
273
- return false;
274
- }
275
- }
276
- return true;
277
- }
278
- async _createAndSaveTileset(outputPath, tilesetName) {
279
- var _this$sourceTileset, _this$sourceTileset$r, _this$sourceTileset$r2;
280
- const tilesetPath = join(`${outputPath}`, `${tilesetName}`);
281
- await this.conversionDump.createDump(this.options);
282
- if (this.conversionDump.restored && this.options.inquirer) {
283
- const result = await this.options.inquirer.prompt([{
284
- name: 'resumeConversion',
285
- type: 'confirm',
286
- message: 'Dump file of the previous conversion exists, do you want to resume that conversion?'
287
- }]);
288
- if (!result.resumeConversion) {
289
- this.conversionDump.reset();
290
- }
291
- }
292
- this.layers0Path = join(tilesetPath, 'SceneServer', 'layers', '0');
293
- const removePath = this.conversionDump.restored ? join(this.layers0Path, 'nodepages') : tilesetPath;
294
- try {
295
- await removeDir(removePath);
296
- } catch (e) {}
297
- if (this.conversionDump.restored && this.conversionDump.attributeMetadataInfo) {
298
- this.attributeMetadataInfo.fromObject(this.conversionDump.attributeMetadataInfo);
299
- }
300
- this.materialDefinitions = [];
301
- this.materialMap = new Map();
302
- if (this.conversionDump.restored && this.conversionDump.materialDefinitions) {
303
- for (let i = 0; i < this.conversionDump.materialDefinitions.length; i++) {
304
- const hash = md5(JSON.stringify(this.conversionDump.materialDefinitions[i]));
305
- this.materialMap.set(hash, i);
306
- }
307
- this.materialDefinitions = this.conversionDump.materialDefinitions;
308
- }
309
- const sourceRootTile = this.sourceTileset.root;
310
- const sourceBoundingVolume = createBoundingVolume(sourceRootTile.boundingVolume, new Matrix4(sourceRootTile.transform), null);
311
- this._formLayers0(tilesetName, sourceBoundingVolume, (_this$sourceTileset = this.sourceTileset) === null || _this$sourceTileset === void 0 ? void 0 : (_this$sourceTileset$r = _this$sourceTileset.root) === null || _this$sourceTileset$r === void 0 ? void 0 : (_this$sourceTileset$r2 = _this$sourceTileset$r.boundingVolume) === null || _this$sourceTileset$r2 === void 0 ? void 0 : _this$sourceTileset$r2.region);
312
- const boundingVolumes = createBoundingVolumes(sourceBoundingVolume, this.geoidHeightModel);
313
- await this.nodePages.push({
314
- index: 0,
315
- lodThreshold: 0,
316
- obb: boundingVolumes.obb,
317
- children: []
318
- });
319
- this.progresses[PROGRESS_PHASE1_COUNT].startMonitoring();
320
- const rootNode = await NodeIndexDocument.createRootNode(boundingVolumes, this);
321
- await traverseDatasetWith(sourceRootTile, {
322
- transform: new Matrix4(sourceRootTile.transform),
323
- parentNodes: [rootNode]
324
- }, this.convertTile.bind(this), this.finalizeTile.bind(this), this.options.maxDepth);
325
- this.progresses[PROGRESS_PHASE1_COUNT].stopMonitoring();
326
- console.log(`[finalizing conversion]`);
327
- this.layers0.attributeStorageInfo = this.attributeMetadataInfo.attributeStorageInfo;
328
- this.layers0.fields = this.attributeMetadataInfo.fields;
329
- this.layers0.popupInfo = this.attributeMetadataInfo.popupInfo;
330
- if (this.attributeMetadataInfo.attributeStorageInfo.length) {
331
- this.layers0.layerType = _3D_OBJECT_LAYER_TYPE;
332
- }
333
- if (this.conversionDump.restored && this.conversionDump.textureSetDefinitions) {
334
- this.layers0.textureSetDefinitions = this.conversionDump.textureSetDefinitions;
335
- }
336
- this.layers0.materialDefinitions = this.materialDefinitions;
337
- this.layers0.geometryDefinitions = transform(this.geometryConfigs.map(config => ({
338
- geometryConfig: {
339
- ...config,
340
- draco: this.options.draco
341
- }
342
- })), geometryDefinitionTemlate());
343
- if (this.layersHasTexture === false) {
344
- this.layers0.store.defaultGeometrySchema.ordering = this.layers0.store.defaultGeometrySchema.ordering.filter(attribute => attribute !== 'uv0');
345
- }
346
- await this._writeLayers0();
347
- createSceneServerPath(tilesetName, this.layers0, tilesetPath);
348
- for (const filePath of this.compressList || []) {
349
- await compressFileWithGzip(filePath);
350
- await removeFile(filePath);
351
- }
352
- await this.nodePages.save();
353
- await this.writeQueue.finalize();
354
- await this._createSlpk(tilesetPath);
355
- }
356
- _formLayers0(tilesetName, sourceBoundingVolume, boundingVolumeRegion) {
357
- var _this$sourceTileset2;
358
- if (!((_this$sourceTileset2 = this.sourceTileset) !== null && _this$sourceTileset2 !== void 0 && _this$sourceTileset2.root)) {
359
- return;
360
- }
361
- const fullExtent = convertBoundingVolumeToI3SFullExtent(sourceBoundingVolume);
362
- if (boundingVolumeRegion) {
363
- fullExtent.zmin = boundingVolumeRegion[4];
364
- fullExtent.zmax = boundingVolumeRegion[5];
365
- }
366
- const extent = [fullExtent.xmin, fullExtent.ymin, fullExtent.xmax, fullExtent.ymax];
367
- const layers0data = {
368
- version: `{${uuidv4().toUpperCase()}}`,
369
- id: 0,
370
- name: tilesetName,
371
- href: './layers/0',
372
- store: {
373
- id: `{${uuidv4().toUpperCase()}}`,
374
- extent
375
- },
376
- nodePages: {
377
- nodesPerPage: HARDCODED_NODES_PER_PAGE
378
- },
379
- compressGeometry: this.options.draco,
380
- fullExtent
381
- };
382
- this.layers0 = transform(layers0data, layersTemplate());
383
- }
384
- async _writeLayers0() {
385
- if (this.options.slpk) {
386
- await this.writeQueue.enqueue({
387
- archiveKey: '3dSceneLayer.json.gz',
388
- writePromise: () => writeFileForSlpk(this.layers0Path, JSON.stringify(this.layers0), '3dSceneLayer.json')
389
- });
390
- } else {
391
- await this.writeQueue.enqueue({
392
- writePromise: () => writeFile(this.layers0Path, JSON.stringify(this.layers0))
393
- });
394
- }
395
- }
396
- async _createSlpk(tilesetPath) {
397
- await this.conversionDump.deleteDumpFile();
398
- if (this.options.slpk) {
399
- const slpkTilesetPath = join(tilesetPath, 'SceneServer', 'layers', '0');
400
- const slpkFileName = `${tilesetPath}.slpk`;
401
- await createZip(slpkTilesetPath, slpkFileName, async fileList => ({
402
- path: '@specialIndexFileHASH128@',
403
- file: await composeHashFile(fileList)
404
- }));
405
- try {
406
- await removeDir(tilesetPath);
407
- } catch (e) {}
198
+ console.log(`Preprocess results:`);
199
+ console.log(`Tile count: ${this.progresses[PROGRESS_PHASE1_COUNT].stepsTotal}`);
200
+ console.log(`glTF mesh topology types: ${Array.from(meshTopologyTypes).join(', ')}`);
201
+ if (metadataClasses.size) {
202
+ console.log(`Feature metadata classes have been found: ${Array.from(metadataClasses).join(', ')}`);
203
+ }
204
+ else {
205
+ console.log('Feature metadata classes have not been found');
206
+ }
207
+ if (!meshTopologyTypes.has(GLTFPrimitiveModeString.TRIANGLES) &&
208
+ !meshTopologyTypes.has(GLTFPrimitiveModeString.TRIANGLE_STRIP)) {
209
+ console.log('The tileset is of unsupported mesh topology types. The conversion will be interrupted.');
210
+ console.log(`------------------------------------------------`);
211
+ return false;
212
+ }
213
+ console.log(`------------------------------------------------`);
214
+ return true;
408
215
  }
409
- }
410
- async convertTile(sourceTile, traversalProps) {
411
- const isTileset = isNestedTileset(sourceTile);
412
- if (isTileset || sourceTile.type === 'empty') {
413
- if (isTileset) {
216
+ /**
217
+ * Analyze a tile content. The callback for preprocess stage.
218
+ * @param sourceTile - 3DTiles tile JSON metadata
219
+ * @param traversalProps - mandatory argument but it is not used for the preprocess stage
220
+ * @returns - nothing
221
+ */
222
+ async analyzeTile(sourceTile, traversalProps) {
223
+ const isTileset = isNestedTileset(sourceTile);
224
+ if (isTileset) {
225
+ await loadNestedTileset(this.sourceTileset, sourceTile, this.loadOptions);
226
+ return null;
227
+ }
414
228
  if (sourceTile.id) {
415
- console.log(`[load]: ${sourceTile.id}`);
229
+ this.progresses[PROGRESS_PHASE1_COUNT].stepsTotal += 1;
230
+ console.log(`[analyze]: ${sourceTile.id}`); // eslint-disable-line
416
231
  }
417
- await loadNestedTileset(this.sourceTileset, sourceTile, this.loadOptions);
418
- }
419
- return traversalProps;
420
- }
421
- if (sourceTile.id) {
422
- console.log(`[convert]: ${sourceTile.id}`);
423
- }
424
- const {
425
- parentNodes,
426
- transform
427
- } = traversalProps;
428
- let transformationMatrix = transform.clone();
429
- if (sourceTile.transform) {
430
- transformationMatrix = transformationMatrix.multiplyRight(sourceTile.transform);
431
- }
432
- const parentNode = parentNodes[0];
433
- const restoreResult = await this._restoreNode(parentNode, sourceTile, transformationMatrix);
434
- let childNodes;
435
- if (restoreResult === null) {
436
- childNodes = await this._createNode(parentNode, sourceTile, transformationMatrix);
437
- } else {
438
- childNodes = restoreResult;
439
- }
440
- await parentNode.addChildren(childNodes);
441
- const newTraversalProps = {
442
- transform: transformationMatrix,
443
- parentNodes: childNodes
444
- };
445
- if (sourceTile.id) {
446
- this.progresses[PROGRESS_PHASE1_COUNT].stepsDone += 1;
447
- let timeRemainingString = 'Calculating time left...';
448
- const timeRemainingStringBasedOnCount = this.progresses[PROGRESS_PHASE1_COUNT].getTimeRemainingString();
449
- if (timeRemainingStringBasedOnCount) {
450
- timeRemainingString = `${timeRemainingStringBasedOnCount} left`;
451
- }
452
- const percentString = this.progresses[PROGRESS_PHASE1_COUNT].getPercentString();
453
- const progressString = percentString ? ` ${percentString}%, ${timeRemainingString}` : '';
454
- console.log(`[converted${progressString}]: ${sourceTile.id}`);
455
- }
456
- return newTraversalProps;
457
- }
458
- async finalizeTile(conversionResults, currentTraversalProps) {
459
- for (const result of conversionResults) {
460
- for (const node of result.parentNodes) {
461
- await node.addNeighbors();
462
- }
232
+ let tileContent = null;
233
+ try {
234
+ tileContent = await loadTile3DContent(this.sourceTileset, sourceTile, {
235
+ ...this.loadOptions,
236
+ '3d-tiles': { ...this.loadOptions['3d-tiles'], loadGLTF: false }
237
+ });
238
+ }
239
+ catch (error) {
240
+ console.log(`[warning]: Failed to load ${sourceTile.contentUrl}. An I3S tile with empty content will be added to the output tileset`);
241
+ }
242
+ const tilePreprocessData = await analyzeTileContent(tileContent);
243
+ mergePreprocessData(this.preprocessData, tilePreprocessData);
244
+ return null;
463
245
  }
464
- for (const node of currentTraversalProps.parentNodes) {
465
- await node.save();
246
+ /**
247
+ * Select metadata class associated with the set of feature attributes
248
+ * @returns true if the metadata class has been successfully selected
249
+ */
250
+ async selectMetadataClass() {
251
+ const { metadataClasses } = this.preprocessData;
252
+ if (metadataClasses.size > 1) {
253
+ if (this.options.metadataClass?.length) {
254
+ console.log(`${this.options.metadataClass} has been selected`);
255
+ }
256
+ else if (this.options.inquirer) {
257
+ const result = await this.options.inquirer.prompt([
258
+ {
259
+ name: 'metadataClass',
260
+ type: 'list',
261
+ message: 'Select feature metadata data class to convert...',
262
+ choices: Array.from(metadataClasses)
263
+ }
264
+ ]);
265
+ this.options.metadataClass = result.metadataClass;
266
+ console.log(`${result.metadataClass} has been selected`);
267
+ }
268
+ else {
269
+ console.log(`A feature metadata class has not been selected. Start the converter with option "--metadata-class". For example, "npx tile-converter ... --metadata-class ${Array.from(metadataClasses)[0]}"`);
270
+ console.log(`------------------------------------------------`);
271
+ return false;
272
+ }
273
+ }
274
+ return true;
466
275
  }
467
- }
468
- async _generateNodeIndexDocument(boundingVolumes, resources, parentNode, sourceTile, isDumped) {
469
- this.layersHasTexture = this.layersHasTexture || Boolean('texture' in resources && resources.texture || 'texelCountHint' in resources && resources.texelCountHint);
470
- if (this.generateBoundingVolumes && resources.boundingVolumes) {
471
- boundingVolumes = resources.boundingVolumes;
276
+ /**
277
+ * Convert and save the layer and embedded tiles
278
+ * @param outputPath - path to save output data
279
+ * @param tilesetName - new tileset path
280
+ */
281
+ async _createAndSaveTileset(outputPath, tilesetName) {
282
+ const tilesetPath = join(`${outputPath}`, `${tilesetName}`);
283
+ await this.conversionDump.createDump(this.options);
284
+ if (this.conversionDump.restored && this.options.inquirer) {
285
+ const result = await this.options.inquirer.prompt([
286
+ {
287
+ name: 'resumeConversion',
288
+ type: 'confirm',
289
+ message: 'Dump file of the previous conversion exists, do you want to resume that conversion?'
290
+ }
291
+ ]);
292
+ if (!result.resumeConversion) {
293
+ this.conversionDump.reset();
294
+ }
295
+ }
296
+ this.layers0Path = join(tilesetPath, 'SceneServer', 'layers', '0');
297
+ // Removing the tilesetPath needed to exclude erroneous files after conversion
298
+ const removePath = this.conversionDump.restored
299
+ ? join(this.layers0Path, 'nodepages')
300
+ : tilesetPath;
301
+ try {
302
+ await removeDir(removePath);
303
+ }
304
+ catch (e) {
305
+ // do nothing
306
+ }
307
+ if (this.conversionDump.restored && this.conversionDump.attributeMetadataInfo) {
308
+ this.attributeMetadataInfo.fromObject(this.conversionDump.attributeMetadataInfo);
309
+ }
310
+ this.materialDefinitions = [];
311
+ this.materialMap = new Map();
312
+ if (this.conversionDump.restored && this.conversionDump.materialDefinitions) {
313
+ for (let i = 0; i < this.conversionDump.materialDefinitions.length; i++) {
314
+ const hash = md5(JSON.stringify(this.conversionDump.materialDefinitions[i]));
315
+ this.materialMap.set(hash, i);
316
+ }
317
+ this.materialDefinitions = this.conversionDump.materialDefinitions;
318
+ }
319
+ const sourceRootTile = this.sourceTileset.root;
320
+ const sourceBoundingVolume = createBoundingVolume(sourceRootTile.boundingVolume, new Matrix4(sourceRootTile.transform), null);
321
+ this._formLayers0(tilesetName, sourceBoundingVolume, this.sourceTileset?.root?.boundingVolume?.region);
322
+ const boundingVolumes = createBoundingVolumes(sourceBoundingVolume, this.geoidHeightModel);
323
+ await this.nodePages.push({
324
+ index: 0,
325
+ lodThreshold: 0,
326
+ obb: boundingVolumes.obb,
327
+ children: []
328
+ });
329
+ this.progresses[PROGRESS_PHASE1_COUNT].startMonitoring();
330
+ const rootNode = await NodeIndexDocument.createRootNode(boundingVolumes, this);
331
+ await traverseDatasetWith(sourceRootTile, {
332
+ transform: new Matrix4(sourceRootTile.transform),
333
+ parentNodes: [rootNode]
334
+ }, this.convertTile.bind(this), this.finalizeTile.bind(this), this.options.maxDepth);
335
+ this.progresses[PROGRESS_PHASE1_COUNT].stopMonitoring();
336
+ console.log(`[finalizing conversion]`); // eslint-disable-line
337
+ this.layers0.attributeStorageInfo = this.attributeMetadataInfo.attributeStorageInfo;
338
+ this.layers0.fields = this.attributeMetadataInfo.fields;
339
+ this.layers0.popupInfo = this.attributeMetadataInfo.popupInfo;
340
+ if (this.attributeMetadataInfo.attributeStorageInfo.length) {
341
+ this.layers0.layerType = _3D_OBJECT_LAYER_TYPE;
342
+ }
343
+ if (this.conversionDump.restored && this.conversionDump.textureSetDefinitions) {
344
+ this.layers0.textureSetDefinitions = this.conversionDump.textureSetDefinitions;
345
+ }
346
+ this.layers0.materialDefinitions = this.materialDefinitions;
347
+ // @ts-ignore
348
+ this.layers0.geometryDefinitions = transform(this.geometryConfigs.map((config) => ({
349
+ geometryConfig: { ...config, draco: this.options.draco }
350
+ })), geometryDefinitionTemlate());
351
+ if (this.layersHasTexture === false) {
352
+ this.layers0.store.defaultGeometrySchema.ordering =
353
+ this.layers0.store.defaultGeometrySchema.ordering.filter((attribute) => attribute !== 'uv0');
354
+ }
355
+ await this._writeLayers0();
356
+ createSceneServerPath(tilesetName, this.layers0, tilesetPath);
357
+ for (const filePath of this.compressList || []) {
358
+ await compressFileWithGzip(filePath);
359
+ await removeFile(filePath);
360
+ }
361
+ await this.nodePages.save();
362
+ await this.writeQueue.finalize();
363
+ await this._createSlpk(tilesetPath);
472
364
  }
473
- const lodSelection = convertGeometricErrorToScreenThreshold(sourceTile, boundingVolumes);
474
- const maxScreenThresholdSQ = lodSelection.find(val => val.metricType === 'maxScreenThresholdSQ') || {
475
- maxError: 0
476
- };
477
- if (isDumped) {
478
- const draftObb = {
479
- center: [],
480
- halfSize: [],
481
- quaternion: []
482
- };
483
- await this.nodePages.push({
484
- index: 0,
485
- obb: draftObb
486
- }, parentNode.inPageId);
365
+ /**
366
+ * Form object of 3DSceneLayer https://github.com/Esri/i3s-spec/blob/master/docs/1.7/3DSceneLayer.cmn.md
367
+ * @param tilesetName - Name of layer
368
+ * @param sourceBoundingVolume - initialized bounding volume of the source root tile
369
+ * @param boundingVolumeRegion - region bounding volume of the source root tile
370
+ */
371
+ _formLayers0(tilesetName, sourceBoundingVolume, boundingVolumeRegion) {
372
+ if (!this.sourceTileset?.root) {
373
+ return;
374
+ }
375
+ const fullExtent = convertBoundingVolumeToI3SFullExtent(sourceBoundingVolume);
376
+ if (boundingVolumeRegion) {
377
+ fullExtent.zmin = boundingVolumeRegion[4];
378
+ fullExtent.zmax = boundingVolumeRegion[5];
379
+ }
380
+ const extent = [fullExtent.xmin, fullExtent.ymin, fullExtent.xmax, fullExtent.ymax];
381
+ const layers0data = {
382
+ version: `{${uuidv4().toUpperCase()}}`,
383
+ id: 0,
384
+ name: tilesetName,
385
+ href: './layers/0',
386
+ store: {
387
+ id: `{${uuidv4().toUpperCase()}}`,
388
+ extent
389
+ },
390
+ nodePages: {
391
+ nodesPerPage: HARDCODED_NODES_PER_PAGE
392
+ },
393
+ compressGeometry: this.options.draco,
394
+ fullExtent
395
+ };
396
+ this.layers0 = transform(layers0data, layersTemplate());
487
397
  }
488
- const nodeInPage = await this._updateNodeInNodePages(maxScreenThresholdSQ, boundingVolumes, sourceTile, parentNode.inPageId, resources);
489
- const nodeData = await NodeIndexDocument.createNodeIndexDocument(parentNode, boundingVolumes, lodSelection, nodeInPage, resources);
490
- const node = await new NodeIndexDocument(nodeInPage.index, this).addData(nodeData);
491
- return {
492
- node,
493
- nodeInPage,
494
- nodeData
495
- };
496
- }
497
- async _restoreNode(parentNode, sourceTile, transformationMatrix) {
498
- this._checkAddRefinementTypeForTile(sourceTile);
499
- await this._updateTilesetOptions();
500
- if (this.conversionDump.restored && sourceTile.id && this.conversionDump.isFileConversionComplete(sourceTile.id)) {
501
- const sourceBoundingVolume = createBoundingVolume(sourceTile.boundingVolume, transformationMatrix, null);
502
- let boundingVolumes = createBoundingVolumes(sourceBoundingVolume, this.geoidHeightModel);
503
- const nodes = [];
504
- for (const convertedNode of this.conversionDump.tilesConverted[sourceTile.id].nodes) {
505
- const {
506
- node
507
- } = await this._generateNodeIndexDocument(boundingVolumes, {
508
- ...convertedNode.dumpMetadata,
509
- nodeId: convertedNode.nodeId
510
- }, parentNode, sourceTile, true);
511
- nodes.push(node);
512
- }
513
- return nodes;
514
- } else if (this.conversionDump.restored && sourceTile.id) {
515
- this.conversionDump.clearDumpRecord(sourceTile.id);
398
+ /**
399
+ * Write 3DSceneLayer https://github.com/Esri/i3s-spec/blob/master/docs/1.7/3DSceneLayer.cmn.md in file
400
+ */
401
+ async _writeLayers0() {
402
+ if (this.options.slpk) {
403
+ await this.writeQueue.enqueue({
404
+ archiveKey: '3dSceneLayer.json.gz',
405
+ writePromise: () => writeFileForSlpk(this.layers0Path, JSON.stringify(this.layers0), '3dSceneLayer.json')
406
+ });
407
+ }
408
+ else {
409
+ await this.writeQueue.enqueue({
410
+ writePromise: () => writeFile(this.layers0Path, JSON.stringify(this.layers0))
411
+ });
412
+ }
516
413
  }
517
- return null;
518
- }
519
- async _createNode(parentNode, sourceTile, transformationMatrix) {
520
- this._checkAddRefinementTypeForTile(sourceTile);
521
- await this._updateTilesetOptions();
522
- let tileContent = null;
523
- try {
524
- tileContent = await loadTile3DContent(this.sourceTileset, sourceTile, this.loadOptions);
525
- } catch (error) {
526
- console.log(`[warning]: Failed to load ${sourceTile.contentUrl}`);
414
+ /**
415
+ * Pack files into *.slpk archive
416
+ * @param tilesetPath - Path to save file
417
+ */
418
+ async _createSlpk(tilesetPath) {
419
+ await this.conversionDump.deleteDumpFile();
420
+ if (this.options.slpk) {
421
+ const slpkTilesetPath = join(tilesetPath, 'SceneServer', 'layers', '0');
422
+ const slpkFileName = `${tilesetPath}.slpk`;
423
+ await createZip(slpkTilesetPath, slpkFileName, async (fileList) => ({
424
+ path: '@specialIndexFileHASH128@',
425
+ file: await composeHashFile(fileList)
426
+ }));
427
+ try {
428
+ await removeDir(tilesetPath);
429
+ }
430
+ catch (e) {
431
+ // do nothing
432
+ }
433
+ }
527
434
  }
528
- const sourceBoundingVolume = createBoundingVolume(sourceTile.boundingVolume, transformationMatrix, null);
529
- let boundingVolumes = createBoundingVolumes(sourceBoundingVolume, this.geoidHeightModel);
530
- const propertyTable = getPropertyTable(tileContent, this.options.metadataClass);
531
- this.createAttributeStorageInfo(tileContent, propertyTable);
532
- this.conversionDump.attributeMetadataInfo = {
533
- attributeStorageInfo: this.attributeMetadataInfo.attributeStorageInfo,
534
- fields: this.attributeMetadataInfo.fields,
535
- popupInfo: this.attributeMetadataInfo.popupInfo
536
- };
537
- const resourcesData = await this._convertResources(sourceTile, transformationMatrix, sourceBoundingVolume, tileContent, parentNode.inPageId, propertyTable);
538
- const nodes = [];
539
- const nodeIds = [];
540
- const nodesInPage = [];
541
- const emptyResources = {
542
- geometry: null,
543
- compressedGeometry: null,
544
- texture: null,
545
- hasUvRegions: false,
546
- sharedResources: null,
547
- meshMaterial: null,
548
- vertexCount: null,
549
- attributes: null,
550
- featureCount: null,
551
- boundingVolumes: null
552
- };
553
- for (const resources of resourcesData || [emptyResources]) {
554
- const {
555
- node,
556
- nodeInPage,
557
- nodeData
558
- } = await this._generateNodeIndexDocument(boundingVolumes, resources, parentNode, sourceTile, false);
559
- nodes.push(node);
560
- if (nodeInPage.mesh) {
435
+ /**
436
+ * Convert the specific 3DTiles tile to I3S nodes.
437
+ * This is callback function for the traversal generic function
438
+ * @param sourceTile - current 3DTiles tile JSON metadata
439
+ * @param traversalProps - traversal properties calculated recursively
440
+ * @returns - traversal properties for the child tiles
441
+ */
442
+ async convertTile(sourceTile, traversalProps) {
443
+ const isTileset = isNestedTileset(sourceTile);
444
+ if (isTileset || sourceTile.type === 'empty') {
445
+ if (isTileset) {
446
+ if (sourceTile.id) {
447
+ console.log(`[load]: ${sourceTile.id}`); // eslint-disable-line
448
+ }
449
+ await loadNestedTileset(this.sourceTileset, sourceTile, this.loadOptions);
450
+ }
451
+ return traversalProps;
452
+ }
561
453
  if (sourceTile.id) {
562
- var _resources$attributes;
563
- const dumpMetadata = {
564
- boundingVolumes: resources.boundingVolumes,
565
- attributesCount: (_resources$attributes = resources.attributes) === null || _resources$attributes === void 0 ? void 0 : _resources$attributes.length,
566
- featureCount: resources.featureCount,
567
- geometry: Boolean(resources.geometry),
568
- hasUvRegions: resources.hasUvRegions,
569
- materialId: nodeInPage.mesh.material.definition,
570
- texelCountHint: nodeInPage.mesh.material.texelCountHint,
571
- vertexCount: resources.vertexCount
572
- };
573
- this.conversionDump.setMaterialsDefinitions(this.materialDefinitions);
574
- await this.conversionDump.addNode(sourceTile.id, nodeInPage.index, dumpMetadata);
575
- }
576
- await this._writeResources(resources, node.id, sourceTile);
577
- }
578
- if (this.validate) {
579
- this.boundingVolumeWarnings = validateNodeBoundingVolumes(nodeData);
580
- if (this.boundingVolumeWarnings && this.boundingVolumeWarnings.length) {
581
- console.warn('Bounding Volume Warnings: ', ...this.boundingVolumeWarnings);
582
- }
583
- }
584
- nodeIds.push(nodeInPage.index);
585
- nodesInPage.push(nodeInPage);
586
- }
587
- return nodes;
588
- }
589
- async _convertResources(sourceTile, transformationMatrix, boundingVolume, tileContent, parentId, propertyTable) {
590
- if (!this.isContentSupported(sourceTile) || !tileContent) {
591
- return null;
592
- }
593
- const draftObb = {
594
- center: [],
595
- halfSize: [],
596
- quaternion: []
597
- };
598
- const resourcesData = await convertB3dmToI3sGeometry(tileContent, transformationMatrix, boundingVolume, async () => (await this.nodePages.push({
599
- index: 0,
600
- obb: draftObb
601
- }, parentId)).index, propertyTable, this.featuresHashArray, this.attributeMetadataInfo.attributeStorageInfo, this.options.draco, this.generateBoundingVolumes, this.options.mergeMaterials, this.geoidHeightModel, this.loadOptions.modules, this.options.metadataClass);
602
- return resourcesData;
603
- }
604
- async _updateNodeInNodePages(maxScreenThresholdSQ, boundingVolumes, sourceTile, parentId, resources) {
605
- const {
606
- vertexCount,
607
- featureCount,
608
- geometry,
609
- hasUvRegions
610
- } = resources;
611
- const nodeInPage = {
612
- index: 0,
613
- lodThreshold: maxScreenThresholdSQ.maxError,
614
- obb: boundingVolumes.obb,
615
- children: []
616
- };
617
- if (geometry && this.isContentSupported(sourceTile)) {
618
- nodeInPage.mesh = {
619
- geometry: {
620
- definition: this.findOrCreateGeometryDefinition(Boolean('texture' in resources && resources.texture || 'texelCountHint' in resources && resources.texelCountHint), hasUvRegions),
621
- resource: 0
622
- },
623
- attribute: {
624
- resource: 0
625
- },
626
- material: {
627
- definition: 0
628
- }
629
- };
630
- }
631
- let nodeId = 'nodeId' in resources ? resources.nodeId : undefined;
632
- let node;
633
- if (!nodeId) {
634
- node = await this.nodePages.push(nodeInPage, parentId);
635
- } else {
636
- node = await this.nodePages.getNodeById(nodeId);
637
- }
638
- if (!nodeInPage.mesh) {
639
- console.log(`[warning]: node ${node.index} is created with empty content`);
640
- }
641
- NodePages.updateAll(node, nodeInPage);
642
- if ('meshMaterial' in resources && resources.meshMaterial) {
643
- NodePages.updateMaterialByNodeId(node, this._findOrCreateMaterial(resources.meshMaterial));
644
- } else if ('materialId' in resources && resources.materialId !== null) {
645
- NodePages.updateMaterialByNodeId(node, resources.materialId);
454
+ console.log(`[convert]: ${sourceTile.id}`); // eslint-disable-line
455
+ }
456
+ const { parentNodes, transform } = traversalProps;
457
+ let transformationMatrix = transform.clone();
458
+ if (sourceTile.transform) {
459
+ transformationMatrix = transformationMatrix.multiplyRight(sourceTile.transform);
460
+ }
461
+ const parentNode = parentNodes[0];
462
+ const restoreResult = await this._restoreNode(parentNode, sourceTile, transformationMatrix);
463
+ let childNodes;
464
+ if (restoreResult === null) {
465
+ childNodes = await this._createNode(parentNode, sourceTile, transformationMatrix);
466
+ }
467
+ else {
468
+ childNodes = restoreResult;
469
+ }
470
+ await parentNode.addChildren(childNodes);
471
+ const newTraversalProps = {
472
+ transform: transformationMatrix,
473
+ parentNodes: childNodes
474
+ };
475
+ if (sourceTile.id) {
476
+ this.progresses[PROGRESS_PHASE1_COUNT].stepsDone += 1;
477
+ let timeRemainingString = 'Calculating time left...';
478
+ const timeRemainingStringBasedOnCount = this.progresses[PROGRESS_PHASE1_COUNT].getTimeRemainingString();
479
+ if (timeRemainingStringBasedOnCount) {
480
+ timeRemainingString = `${timeRemainingStringBasedOnCount} left`;
481
+ }
482
+ const percentString = this.progresses[PROGRESS_PHASE1_COUNT].getPercentString();
483
+ const progressString = percentString ? ` ${percentString}%, ${timeRemainingString}` : '';
484
+ console.log(`[converted${progressString}]: ${sourceTile.id}`); // eslint-disable-line
485
+ }
486
+ return newTraversalProps;
646
487
  }
647
- if ('texture' in resources && resources.texture) {
648
- const texelCountHint = resources.texture.image.height * resources.texture.image.width;
649
- NodePages.updateTexelCountHintByNodeId(node, texelCountHint);
650
- } else if ('texelCountHint' in resources && resources.texelCountHint) {
651
- NodePages.updateTexelCountHintByNodeId(node, resources.texelCountHint);
488
+ /**
489
+ * Do final action with nodes after the current node and all child nodes been converted.
490
+ * @param conversionResults - array of conversion results of the current node
491
+ * @param currentTraversalProps - traversal properties of the current node
492
+ */
493
+ async finalizeTile(conversionResults, currentTraversalProps) {
494
+ for (const result of conversionResults) {
495
+ for (const node of result.parentNodes) {
496
+ await node.addNeighbors();
497
+ }
498
+ }
499
+ for (const node of currentTraversalProps.parentNodes) {
500
+ await node.save();
501
+ }
652
502
  }
653
- if (vertexCount) {
654
- this.vertexCounter += vertexCount;
655
- NodePages.updateVertexCountByNodeId(node, vertexCount);
503
+ /**
504
+ * Generate NodeIndexDocument
505
+ * @param boundingVolumes - Bounding volumes
506
+ * @param resources - converted or dumped node resources data
507
+ * @param parentNode - 3DNodeIndexDocument of parent node
508
+ * @param sourceTile - source 3DTile data
509
+ * @param isDumped - indicator if the node is dumped
510
+ * @return NodeIndexDocument, nodeInPage and node data
511
+ */
512
+ async _generateNodeIndexDocument(boundingVolumes, resources, parentNode, sourceTile, isDumped) {
513
+ this.layersHasTexture =
514
+ this.layersHasTexture ||
515
+ Boolean(('texture' in resources && resources.texture) ||
516
+ ('texelCountHint' in resources && resources.texelCountHint));
517
+ if (this.generateBoundingVolumes && resources.boundingVolumes) {
518
+ boundingVolumes = resources.boundingVolumes;
519
+ }
520
+ const lodSelection = convertGeometricErrorToScreenThreshold(sourceTile, boundingVolumes);
521
+ const maxScreenThresholdSQ = lodSelection.find((val) => val.metricType === 'maxScreenThresholdSQ') || { maxError: 0 };
522
+ if (isDumped) {
523
+ const draftObb = {
524
+ center: [],
525
+ halfSize: [],
526
+ quaternion: []
527
+ };
528
+ await this.nodePages.push({ index: 0, obb: draftObb }, parentNode.inPageId);
529
+ }
530
+ const nodeInPage = await this._updateNodeInNodePages(maxScreenThresholdSQ, boundingVolumes, sourceTile, parentNode.inPageId, resources);
531
+ const nodeData = await NodeIndexDocument.createNodeIndexDocument(parentNode, boundingVolumes, lodSelection, nodeInPage, resources);
532
+ const node = await new NodeIndexDocument(nodeInPage.index, this).addData(nodeData);
533
+ return { node, nodeInPage, nodeData };
656
534
  }
657
- NodePages.updateNodeAttributeByNodeId(node);
658
- if (featureCount) {
659
- NodePages.updateFeatureCountByNodeId(node, featureCount);
535
+ /**
536
+ * Restore 3DNodeIndexDocument from a comversion dump file
537
+ * @param parentNode - 3DNodeIndexDocument of parent node
538
+ * @param sourceTile - source 3DTile data
539
+ * @param transformationMatrix - transformation matrix of the current tile, calculated recursively multiplying
540
+ * transform of all parent tiles and transform of the current tile
541
+ */
542
+ async _restoreNode(parentNode, sourceTile, transformationMatrix) {
543
+ this._checkAddRefinementTypeForTile(sourceTile);
544
+ await this._updateTilesetOptions();
545
+ if (this.conversionDump.restored &&
546
+ sourceTile.id &&
547
+ this.conversionDump.isFileConversionComplete(sourceTile.id)) {
548
+ const sourceBoundingVolume = createBoundingVolume(sourceTile.boundingVolume, transformationMatrix, null);
549
+ let boundingVolumes = createBoundingVolumes(sourceBoundingVolume, this.geoidHeightModel);
550
+ const nodes = [];
551
+ for (const convertedNode of this.conversionDump.tilesConverted[sourceTile.id].nodes) {
552
+ const { node } = await this._generateNodeIndexDocument(boundingVolumes, {
553
+ ...convertedNode.dumpMetadata,
554
+ nodeId: convertedNode.nodeId
555
+ }, parentNode, sourceTile, true);
556
+ nodes.push(node);
557
+ }
558
+ return nodes;
559
+ }
560
+ else if (this.conversionDump.restored && sourceTile.id) {
561
+ //clear existing record in a dump
562
+ this.conversionDump.clearDumpRecord(sourceTile.id);
563
+ }
564
+ return null;
660
565
  }
661
- this.nodePages.saveNode(node);
662
- return node;
663
- }
664
- async _writeResources(resources, nodePath, sourceTile) {
665
- const {
666
- geometry: geometryBuffer,
667
- compressedGeometry,
668
- texture,
669
- sharedResources,
670
- attributes
671
- } = resources;
672
- const childPath = join(this.layers0Path, 'nodes', nodePath);
673
- const slpkChildPath = join('nodes', nodePath);
674
- await this._writeGeometries(geometryBuffer, compressedGeometry, childPath, slpkChildPath, sourceTile.id || '', parseInt(nodePath));
675
- await this._writeShared(sharedResources, childPath, slpkChildPath, nodePath, sourceTile.id || '', parseInt(nodePath));
676
- await this._writeTexture(texture, childPath, slpkChildPath, sourceTile.id || '', parseInt(nodePath));
677
- await this._writeAttributes(attributes, childPath, slpkChildPath, sourceTile.id || '', parseInt(nodePath));
678
- }
679
- async _writeGeometries(geometryBuffer, compressedGeometry, childPath, slpkChildPath, sourceId, nodeId) {
680
- this.conversionDump.updateDoneStatus(sourceId, nodeId, ResourceType.GEOMETRY, false);
681
- if (this.options.slpk) {
682
- const slpkGeometryPath = join(childPath, 'geometries');
683
- await this.writeQueue.enqueue({
684
- archiveKey: `${slpkChildPath}/geometries/0.bin.gz`,
685
- sourceId,
686
- outputId: nodeId,
687
- resourceType: ResourceType.GEOMETRY,
688
- writePromise: () => writeFileForSlpk(slpkGeometryPath, geometryBuffer, '0.bin')
689
- });
690
- } else {
691
- const geometryPath = join(childPath, 'geometries/0/');
692
- await this.writeQueue.enqueue({
693
- sourceId,
694
- outputId: nodeId,
695
- resourceType: ResourceType.GEOMETRY,
696
- writePromise: () => writeFile(geometryPath, geometryBuffer, 'index.bin')
697
- });
566
+ /**
567
+ * Convert tile to one or more I3S nodes
568
+ * @param parentNode - 3DNodeIndexDocument of parent node
569
+ * @param sourceTile - source 3DTile data
570
+ * @param transformationMatrix - transformation matrix of the current tile, calculated recursively multiplying
571
+ * transform of all parent tiles and transform of the current tile
572
+ * @param level - tree level
573
+ */
574
+ async _createNode(parentNode, sourceTile, transformationMatrix) {
575
+ this._checkAddRefinementTypeForTile(sourceTile);
576
+ await this._updateTilesetOptions();
577
+ let tileContent = null;
578
+ try {
579
+ tileContent = await loadTile3DContent(this.sourceTileset, sourceTile, this.loadOptions);
580
+ }
581
+ catch (error) {
582
+ console.log(`[warning]: Failed to load ${sourceTile.contentUrl}`);
583
+ }
584
+ const sourceBoundingVolume = createBoundingVolume(sourceTile.boundingVolume, transformationMatrix, null);
585
+ let boundingVolumes = createBoundingVolumes(sourceBoundingVolume, this.geoidHeightModel);
586
+ const propertyTable = getPropertyTable(tileContent, this.options.metadataClass);
587
+ this.createAttributeStorageInfo(tileContent, propertyTable);
588
+ this.conversionDump.attributeMetadataInfo = {
589
+ attributeStorageInfo: this.attributeMetadataInfo.attributeStorageInfo,
590
+ fields: this.attributeMetadataInfo.fields,
591
+ popupInfo: this.attributeMetadataInfo.popupInfo
592
+ };
593
+ const resourcesData = await this._convertResources(sourceTile, transformationMatrix, sourceBoundingVolume, tileContent, parentNode.inPageId, propertyTable);
594
+ const nodes = [];
595
+ const nodeIds = [];
596
+ const nodesInPage = [];
597
+ const emptyResources = {
598
+ geometry: null,
599
+ compressedGeometry: null,
600
+ texture: null,
601
+ hasUvRegions: false,
602
+ sharedResources: null,
603
+ meshMaterial: null,
604
+ vertexCount: null,
605
+ attributes: null,
606
+ featureCount: null,
607
+ boundingVolumes: null
608
+ };
609
+ for (const resources of resourcesData || [emptyResources]) {
610
+ const { node, nodeInPage, nodeData } = await this._generateNodeIndexDocument(boundingVolumes, resources, parentNode, sourceTile, false);
611
+ nodes.push(node);
612
+ if (nodeInPage.mesh) {
613
+ //update a record in a dump file
614
+ if (sourceTile.id) {
615
+ const dumpMetadata = {
616
+ boundingVolumes: resources.boundingVolumes,
617
+ attributesCount: resources.attributes?.length,
618
+ featureCount: resources.featureCount,
619
+ geometry: Boolean(resources.geometry),
620
+ hasUvRegions: resources.hasUvRegions,
621
+ materialId: nodeInPage.mesh.material.definition,
622
+ texelCountHint: nodeInPage.mesh.material.texelCountHint,
623
+ vertexCount: resources.vertexCount
624
+ };
625
+ this.conversionDump.setMaterialsDefinitions(this.materialDefinitions);
626
+ await this.conversionDump.addNode(sourceTile.id, nodeInPage.index, dumpMetadata);
627
+ }
628
+ //write resources
629
+ await this._writeResources(resources, node.id, sourceTile);
630
+ }
631
+ if (this.validate) {
632
+ this.boundingVolumeWarnings = validateNodeBoundingVolumes(nodeData);
633
+ if (this.boundingVolumeWarnings && this.boundingVolumeWarnings.length) {
634
+ console.warn('Bounding Volume Warnings: ', ...this.boundingVolumeWarnings); //eslint-disable-line
635
+ }
636
+ }
637
+ nodeIds.push(nodeInPage.index);
638
+ nodesInPage.push(nodeInPage);
639
+ }
640
+ return nodes;
698
641
  }
699
- if (this.options.draco) {
700
- this.conversionDump.updateDoneStatus(sourceId, nodeId, ResourceType.DRACO_GEOMETRY, false);
701
- if (this.options.slpk) {
702
- const slpkCompressedGeometryPath = join(childPath, 'geometries');
703
- await this.writeQueue.enqueue({
704
- archiveKey: `${slpkChildPath}/geometries/1.bin.gz`,
705
- sourceId,
706
- outputId: nodeId,
707
- resourceType: ResourceType.DRACO_GEOMETRY,
708
- writePromise: () => writeFileForSlpk(slpkCompressedGeometryPath, compressedGeometry, '1.bin')
709
- });
710
- } else {
711
- const compressedGeometryPath = join(childPath, 'geometries/1/');
712
- await this.writeQueue.enqueue({
713
- sourceId,
714
- outputId: nodeId,
715
- resourceType: ResourceType.DRACO_GEOMETRY,
716
- writePromise: () => writeFile(compressedGeometryPath, compressedGeometry, 'index.bin')
717
- });
718
- }
642
+ /**
643
+ * Convert tile to one or more I3S nodes
644
+ * @param sourceTile - source tile (3DTile)
645
+ * @param transformationMatrix - transformation matrix of the current tile, calculated recursively multiplying
646
+ * transform of all parent tiles and transform of the current tile
647
+ * @param boundingVolume - initialized bounding volume of the source tile
648
+ * @param tileContent - content of the source tile
649
+ * @param parentId - id of parent node in node pages
650
+ * @param propertyTable - batch table from b3dm / feature properties from EXT_FEATURE_METADATA, EXT_MESH_FEATURES or EXT_STRUCTURAL_METADATA
651
+ * @returns - converted node resources
652
+ */
653
+ async _convertResources(sourceTile, transformationMatrix, boundingVolume, tileContent, parentId, propertyTable) {
654
+ if (!this.isContentSupported(sourceTile) || !tileContent) {
655
+ return null;
656
+ }
657
+ const draftObb = {
658
+ center: [],
659
+ halfSize: [],
660
+ quaternion: []
661
+ };
662
+ const resourcesData = await convertB3dmToI3sGeometry(tileContent, transformationMatrix, boundingVolume, async () => (await this.nodePages.push({ index: 0, obb: draftObb }, parentId)).index, propertyTable, this.featuresHashArray, this.attributeMetadataInfo.attributeStorageInfo, this.options.draco, this.generateBoundingVolumes, this.options.mergeMaterials, this.geoidHeightModel, this.loadOptions.modules, this.options.metadataClass);
663
+ return resourcesData;
719
664
  }
720
- }
721
- async _writeShared(sharedResources, childPath, slpkChildPath, nodePath, sourceId, nodeId) {
722
- if (!sharedResources) {
723
- return;
665
+ /**
666
+ * Update node object (https://github.com/Esri/i3s-spec/blob/master/docs/1.7/node.cmn.md)
667
+ * in node pages (https://github.com/Esri/i3s-spec/blob/master/docs/1.7/nodePage.cmn.md)
668
+ * @param maxScreenThresholdSQ - Level of Details (LOD) metric
669
+ * @param boundingVolumes - Bounding volumes
670
+ * @param sourceTile - source tile (3DTile)
671
+ * @param parentId - id of parent node in node pages
672
+ * @param resources - the node resources data
673
+ * @param resources.meshMaterial - PBR-like material object
674
+ * @param resources.texture - texture image
675
+ * @param resources.vertexCount - number of vertices in geometry
676
+ * @param resources.featureCount - number of features
677
+ * @param resources.geometry - Uint8Array with geometry attributes
678
+ * @return the node object in node pages
679
+ */
680
+ async _updateNodeInNodePages(maxScreenThresholdSQ, boundingVolumes, sourceTile, parentId, resources) {
681
+ const { vertexCount, featureCount, geometry, hasUvRegions } = resources;
682
+ const nodeInPage = {
683
+ index: 0,
684
+ lodThreshold: maxScreenThresholdSQ.maxError,
685
+ obb: boundingVolumes.obb,
686
+ children: []
687
+ };
688
+ if (geometry && this.isContentSupported(sourceTile)) {
689
+ nodeInPage.mesh = {
690
+ geometry: {
691
+ definition: this.findOrCreateGeometryDefinition(Boolean(('texture' in resources && resources.texture) ||
692
+ ('texelCountHint' in resources && resources.texelCountHint)), hasUvRegions),
693
+ resource: 0
694
+ },
695
+ attribute: {
696
+ resource: 0
697
+ },
698
+ material: {
699
+ definition: 0
700
+ }
701
+ };
702
+ }
703
+ let nodeId = 'nodeId' in resources ? resources.nodeId : undefined;
704
+ let node;
705
+ if (!nodeId) {
706
+ node = await this.nodePages.push(nodeInPage, parentId);
707
+ }
708
+ else {
709
+ node = await this.nodePages.getNodeById(nodeId);
710
+ }
711
+ if (!nodeInPage.mesh) {
712
+ console.log(`[warning]: node ${node.index} is created with empty content`);
713
+ }
714
+ NodePages.updateAll(node, nodeInPage);
715
+ if ('meshMaterial' in resources && resources.meshMaterial) {
716
+ NodePages.updateMaterialByNodeId(node, this._findOrCreateMaterial(resources.meshMaterial));
717
+ }
718
+ else if ('materialId' in resources && resources.materialId !== null) {
719
+ NodePages.updateMaterialByNodeId(node, resources.materialId);
720
+ }
721
+ if ('texture' in resources && resources.texture) {
722
+ const texelCountHint = resources.texture.image.height * resources.texture.image.width;
723
+ NodePages.updateTexelCountHintByNodeId(node, texelCountHint);
724
+ }
725
+ else if ('texelCountHint' in resources && resources.texelCountHint) {
726
+ NodePages.updateTexelCountHintByNodeId(node, resources.texelCountHint);
727
+ }
728
+ if (vertexCount) {
729
+ this.vertexCounter += vertexCount;
730
+ NodePages.updateVertexCountByNodeId(node, vertexCount);
731
+ }
732
+ NodePages.updateNodeAttributeByNodeId(node);
733
+ if (featureCount) {
734
+ NodePages.updateFeatureCountByNodeId(node, featureCount);
735
+ }
736
+ this.nodePages.saveNode(node);
737
+ return node;
724
738
  }
725
- sharedResources.nodePath = nodePath;
726
- const sharedData = transform(sharedResources, sharedResourcesTemplate());
727
- const sharedDataStr = JSON.stringify(sharedData);
728
- this.conversionDump.updateDoneStatus(sourceId, nodeId, ResourceType.SHARED, false);
729
- if (this.options.slpk) {
730
- const slpkSharedPath = join(childPath, 'shared');
731
- await this.writeQueue.enqueue({
732
- archiveKey: `${slpkChildPath}/shared/sharedResource.json.gz`,
733
- sourceId,
734
- outputId: nodeId,
735
- resourceType: ResourceType.SHARED,
736
- writePromise: () => writeFileForSlpk(slpkSharedPath, sharedDataStr, 'sharedResource.json')
737
- });
738
- } else {
739
- const sharedPath = join(childPath, 'shared/');
740
- await this.writeQueue.enqueue({
741
- sourceId,
742
- outputId: nodeId,
743
- resourceType: ResourceType.SHARED,
744
- writePromise: () => writeFile(sharedPath, sharedDataStr)
745
- });
739
+ /**
740
+ * Write node resources in files
741
+ * @param resources - source tile (3DTile)
742
+ * @param resources.geometry - Uint8Array with geometry attributes
743
+ * @param resources.compressedGeometry - Uint8Array with compressed (draco) geometry
744
+ * @param resources.texture - texture image
745
+ * @param resources.sharedResources - shared resource data object
746
+ * @param resources.attributes - feature attributes
747
+ * @param nodePath - node path
748
+ * @param sourceTile - source tile (3DTile)
749
+ * @return {Promise<void>}
750
+ */
751
+ async _writeResources(resources, nodePath, sourceTile) {
752
+ const { geometry: geometryBuffer, compressedGeometry, texture, sharedResources, attributes } = resources;
753
+ const childPath = join(this.layers0Path, 'nodes', nodePath);
754
+ const slpkChildPath = join('nodes', nodePath);
755
+ await this._writeGeometries(geometryBuffer, compressedGeometry, childPath, slpkChildPath, sourceTile.id || '', parseInt(nodePath));
756
+ await this._writeShared(sharedResources, childPath, slpkChildPath, nodePath, sourceTile.id || '', parseInt(nodePath));
757
+ await this._writeTexture(texture, childPath, slpkChildPath, sourceTile.id || '', parseInt(nodePath));
758
+ await this._writeAttributes(attributes, childPath, slpkChildPath, sourceTile.id || '', parseInt(nodePath));
746
759
  }
747
- }
748
- async _writeTexture(texture, childPath, slpkChildPath, sourceId, nodeId) {
749
- if (texture) {
750
- const format = this._getFormatByMimeType(texture === null || texture === void 0 ? void 0 : texture.mimeType);
751
- const formats = [];
752
- const textureData = texture.bufferView.data;
753
- switch (format) {
754
- case 'jpg':
755
- case 'png':
756
- {
757
- formats.push({
758
- name: '0',
759
- format
760
+ /**
761
+ * Write non-compressed and compressed geometries in files
762
+ * @param geometryBuffer - Uint8Array with geometry attributes
763
+ * @param compressedGeometry - Uint8Array with compressed (draco) geometry
764
+ * @param childPath - a child path to write resources
765
+ * @param slpkChildPath - resource path inside *slpk file
766
+ * @param sourceId - source filename
767
+ * @param nodeId - nodeId of a converted node for the writing
768
+ */
769
+ async _writeGeometries(geometryBuffer, compressedGeometry, childPath, slpkChildPath, sourceId, nodeId) {
770
+ this.conversionDump.updateDoneStatus(sourceId, nodeId, ResourceType.GEOMETRY, false);
771
+ if (this.options.slpk) {
772
+ const slpkGeometryPath = join(childPath, 'geometries');
773
+ await this.writeQueue.enqueue({
774
+ archiveKey: `${slpkChildPath}/geometries/0.bin.gz`,
775
+ sourceId,
776
+ outputId: nodeId,
777
+ resourceType: ResourceType.GEOMETRY,
778
+ writePromise: () => writeFileForSlpk(slpkGeometryPath, geometryBuffer, '0.bin')
760
779
  });
761
- this.conversionDump.updateDoneStatus(sourceId, nodeId, `${ResourceType.TEXTURE}/${format}`, false);
762
- await this.writeTextureFile(textureData, '0', format, childPath, slpkChildPath, sourceId, nodeId);
763
- if (this.generateTextures) {
764
- formats.push({
765
- name: '1',
766
- format: 'ktx2'
767
- });
768
- const copyArrayBuffer = texture.image.data.subarray();
769
- const arrayToEncode = new Uint8Array(copyArrayBuffer);
770
- const ktx2TextureData = encode({
771
- ...texture.image,
772
- data: arrayToEncode
773
- }, KTX2BasisWriterWorker, {
774
- ...KTX2BasisWriterWorker.options,
775
- ['ktx2-basis-writer']: {
776
- workerUrl: './modules/textures/dist/ktx2-basis-writer-worker-node.js'
777
- },
778
- reuseWorkers: true,
779
- _nodeWorkers: true,
780
- useLocalLibraries: true
781
- });
782
- this.conversionDump.updateDoneStatus(sourceId, nodeId, `${ResourceType.TEXTURE}/ktx2`, false);
783
- await this.writeTextureFile(ktx2TextureData, '1', 'ktx2', childPath, slpkChildPath, sourceId, nodeId);
784
- }
785
- break;
786
- }
787
- case 'ktx2':
788
- {
789
- formats.push({
790
- name: '1',
791
- format
780
+ }
781
+ else {
782
+ const geometryPath = join(childPath, 'geometries/0/');
783
+ await this.writeQueue.enqueue({
784
+ sourceId,
785
+ outputId: nodeId,
786
+ resourceType: ResourceType.GEOMETRY,
787
+ writePromise: () => writeFile(geometryPath, geometryBuffer, 'index.bin')
792
788
  });
793
- this.conversionDump.updateDoneStatus(sourceId, nodeId, `${ResourceType.TEXTURE}/${format}`, false);
794
- await this.writeTextureFile(textureData, '1', format, childPath, slpkChildPath, sourceId, nodeId);
795
- if (this.generateTextures) {
796
- formats.push({
797
- name: '0',
798
- format: 'jpg'
799
- });
800
- const decodedFromKTX2TextureData = encode(texture.image.data[0], ImageWriter);
801
- this.conversionDump.updateDoneStatus(sourceId, nodeId, `${ResourceType.TEXTURE}/jpg`, false);
802
- await this.writeTextureFile(decodedFromKTX2TextureData, '0', 'jpg', childPath, slpkChildPath, sourceId, nodeId);
789
+ }
790
+ if (this.options.draco) {
791
+ this.conversionDump.updateDoneStatus(sourceId, nodeId, ResourceType.DRACO_GEOMETRY, false);
792
+ if (this.options.slpk) {
793
+ const slpkCompressedGeometryPath = join(childPath, 'geometries');
794
+ await this.writeQueue.enqueue({
795
+ archiveKey: `${slpkChildPath}/geometries/1.bin.gz`,
796
+ sourceId,
797
+ outputId: nodeId,
798
+ resourceType: ResourceType.DRACO_GEOMETRY,
799
+ writePromise: () => writeFileForSlpk(slpkCompressedGeometryPath, compressedGeometry, '1.bin')
800
+ });
801
+ }
802
+ else {
803
+ const compressedGeometryPath = join(childPath, 'geometries/1/');
804
+ await this.writeQueue.enqueue({
805
+ sourceId,
806
+ outputId: nodeId,
807
+ resourceType: ResourceType.DRACO_GEOMETRY,
808
+ writePromise: () => writeFile(compressedGeometryPath, compressedGeometry, 'index.bin')
809
+ });
803
810
  }
804
- }
805
- }
806
- if (!this.layers0.textureSetDefinitions.length) {
807
- this.layers0.textureSetDefinitions.push({
808
- formats
809
- });
810
- this.layers0.textureSetDefinitions.push({
811
- formats,
812
- atlas: true
813
- });
814
- if (this.layers0.textureSetDefinitions) {
815
- this.conversionDump.addTexturesDefinitions(this.layers0.textureSetDefinitions);
816
811
  }
817
- }
818
812
  }
819
- }
820
- async writeTextureFile(textureData, name, format, childPath, slpkChildPath, sourceId, nodeId) {
821
- if (this.options.slpk) {
822
- const slpkTexturePath = join(childPath, 'textures');
823
- const compress = false;
824
- await this.writeQueue.enqueue({
825
- archiveKey: `${slpkChildPath}/textures/${name}.${format}`,
826
- sourceId,
827
- outputId: nodeId,
828
- resourceType: `${ResourceType.TEXTURE}/${format}`,
829
- writePromise: () => writeFileForSlpk(slpkTexturePath, textureData, `${name}.${format}`, compress)
830
- });
831
- } else {
832
- const texturePath = join(childPath, `textures/${name}/`);
833
- await this.writeQueue.enqueue({
834
- sourceId,
835
- outputId: nodeId,
836
- resourceType: `${ResourceType.TEXTURE}/${format}`,
837
- writePromise: () => writeFile(texturePath, textureData, `index.${format}`)
838
- });
813
+ /**
814
+ * Write shared resources in a file
815
+ * @param sharedResources - shared resource data object
816
+ * @param childPath - a child path to write resources
817
+ * @param slpkChildPath - resource path inside *slpk file
818
+ * @param nodePath - a node path
819
+ * @param sourceId - source filename
820
+ * @param nodeId - nodeId of a converted node for the writing
821
+ */
822
+ async _writeShared(sharedResources, childPath, slpkChildPath, nodePath, sourceId, nodeId) {
823
+ if (!sharedResources) {
824
+ return;
825
+ }
826
+ sharedResources.nodePath = nodePath;
827
+ const sharedData = transform(sharedResources, sharedResourcesTemplate());
828
+ const sharedDataStr = JSON.stringify(sharedData);
829
+ this.conversionDump.updateDoneStatus(sourceId, nodeId, ResourceType.SHARED, false);
830
+ if (this.options.slpk) {
831
+ const slpkSharedPath = join(childPath, 'shared');
832
+ await this.writeQueue.enqueue({
833
+ archiveKey: `${slpkChildPath}/shared/sharedResource.json.gz`,
834
+ sourceId,
835
+ outputId: nodeId,
836
+ resourceType: ResourceType.SHARED,
837
+ writePromise: () => writeFileForSlpk(slpkSharedPath, sharedDataStr, 'sharedResource.json')
838
+ });
839
+ }
840
+ else {
841
+ const sharedPath = join(childPath, 'shared/');
842
+ await this.writeQueue.enqueue({
843
+ sourceId,
844
+ outputId: nodeId,
845
+ resourceType: ResourceType.SHARED,
846
+ writePromise: () => writeFile(sharedPath, sharedDataStr)
847
+ });
848
+ }
849
+ }
850
+ /**
851
+ * Generates textures based on texture mime type and fill in textureSetDefinitions data.
852
+ * @param texture - the texture image
853
+ * @param childPath - a child path to write resources
854
+ * @param slpkChildPath - the resource path inside *slpk file
855
+ * @param sourceId - source filename
856
+ * @param nodeId - nodeId of a converted node for the writing
857
+ */
858
+ async _writeTexture(texture, childPath, slpkChildPath, sourceId, nodeId) {
859
+ if (texture) {
860
+ const format = this._getFormatByMimeType(texture?.mimeType);
861
+ const formats = [];
862
+ const textureData = texture.bufferView.data;
863
+ switch (format) {
864
+ case 'jpg':
865
+ case 'png': {
866
+ formats.push({ name: '0', format });
867
+ this.conversionDump.updateDoneStatus(sourceId, nodeId, `${ResourceType.TEXTURE}/${format}`, false);
868
+ await this.writeTextureFile(textureData, '0', format, childPath, slpkChildPath, sourceId, nodeId);
869
+ if (this.generateTextures) {
870
+ formats.push({ name: '1', format: 'ktx2' });
871
+ // For Node.js texture.image.data is type of Buffer
872
+ const copyArrayBuffer = texture.image.data.subarray();
873
+ const arrayToEncode = new Uint8Array(copyArrayBuffer);
874
+ const ktx2TextureData = encode({ ...texture.image, data: arrayToEncode },
875
+ // @ts-expect-error - Worker encoder typing is still WIP
876
+ KTX2BasisWriterWorker, {
877
+ ...KTX2BasisWriterWorker.options,
878
+ ['ktx2-basis-writer']: {
879
+ // We need to load local fs workers because nodejs can't load workers from the Internet
880
+ workerUrl: './modules/textures/dist/ktx2-basis-writer-worker-node.js'
881
+ },
882
+ reuseWorkers: true,
883
+ _nodeWorkers: true,
884
+ useLocalLibraries: true
885
+ });
886
+ this.conversionDump.updateDoneStatus(sourceId, nodeId, `${ResourceType.TEXTURE}/ktx2`, false);
887
+ await this.writeTextureFile(ktx2TextureData, '1', 'ktx2', childPath, slpkChildPath, sourceId, nodeId);
888
+ }
889
+ break;
890
+ }
891
+ case 'ktx2': {
892
+ formats.push({ name: '1', format });
893
+ this.conversionDump.updateDoneStatus(sourceId, nodeId, `${ResourceType.TEXTURE}/${format}`, false);
894
+ await this.writeTextureFile(textureData, '1', format, childPath, slpkChildPath, sourceId, nodeId);
895
+ if (this.generateTextures) {
896
+ formats.push({ name: '0', format: 'jpg' });
897
+ const decodedFromKTX2TextureData = encode(texture.image.data[0], ImageWriter);
898
+ this.conversionDump.updateDoneStatus(sourceId, nodeId, `${ResourceType.TEXTURE}/jpg`, false);
899
+ await this.writeTextureFile(decodedFromKTX2TextureData, '0', 'jpg', childPath, slpkChildPath, sourceId, nodeId);
900
+ }
901
+ }
902
+ }
903
+ if (!this.layers0.textureSetDefinitions.length) {
904
+ this.layers0.textureSetDefinitions.push({ formats });
905
+ this.layers0.textureSetDefinitions.push({ formats, atlas: true });
906
+ if (this.layers0.textureSetDefinitions) {
907
+ this.conversionDump.addTexturesDefinitions(this.layers0.textureSetDefinitions);
908
+ }
909
+ }
910
+ }
839
911
  }
840
- }
841
- async _writeAttributes() {
842
- let attributes = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : [];
843
- let childPath = arguments.length > 1 ? arguments[1] : undefined;
844
- let slpkChildPath = arguments.length > 2 ? arguments[2] : undefined;
845
- let sourceId = arguments.length > 3 ? arguments[3] : undefined;
846
- let nodeId = arguments.length > 4 ? arguments[4] : undefined;
847
- if (attributes !== null && attributes !== void 0 && attributes.length && this.attributeMetadataInfo.attributeStorageInfo.length) {
848
- const minimumLength = attributes.length < this.attributeMetadataInfo.attributeStorageInfo.length ? attributes.length : this.attributeMetadataInfo.attributeStorageInfo.length;
849
- for (let index = 0; index < minimumLength; index++) {
850
- const folderName = this.attributeMetadataInfo.attributeStorageInfo[index].key;
851
- const fileBuffer = new Uint8Array(attributes[index]);
852
- this.conversionDump.updateDoneStatus(sourceId, nodeId, `${ResourceType.ATTRIBUTES}/${folderName}`, false);
912
+ /**
913
+ * Write the texture image in a file
914
+ * @param textureData
915
+ * @param name
916
+ * @param format
917
+ * @param childPath
918
+ * @param slpkChildPath
919
+ * @param sourceId
920
+ * @param nodeId
921
+ */
922
+ async writeTextureFile(textureData, name, format, childPath, slpkChildPath, sourceId, nodeId) {
853
923
  if (this.options.slpk) {
854
- const slpkAttributesPath = join(childPath, 'attributes', folderName);
855
- await this.writeQueue.enqueue({
856
- archiveKey: `${slpkChildPath}/attributes/${folderName}.bin.gz`,
857
- sourceId,
858
- outputId: nodeId,
859
- resourceType: `${ResourceType.ATTRIBUTES}/${folderName}`,
860
- writePromise: () => writeFileForSlpk(slpkAttributesPath, fileBuffer, '0.bin')
861
- });
862
- } else {
863
- const attributesPath = join(childPath, `attributes/${folderName}/0`);
864
- await this.writeQueue.enqueue({
865
- sourceId,
866
- outputId: nodeId,
867
- resourceType: `${ResourceType.ATTRIBUTES}/${folderName}`,
868
- writePromise: () => writeFile(attributesPath, fileBuffer, 'index.bin')
869
- });
870
- }
871
- }
924
+ const slpkTexturePath = join(childPath, 'textures');
925
+ const compress = false;
926
+ await this.writeQueue.enqueue({
927
+ archiveKey: `${slpkChildPath}/textures/${name}.${format}`,
928
+ sourceId,
929
+ outputId: nodeId,
930
+ resourceType: `${ResourceType.TEXTURE}/${format}`,
931
+ writePromise: () => writeFileForSlpk(slpkTexturePath, textureData, `${name}.${format}`, compress)
932
+ });
933
+ }
934
+ else {
935
+ const texturePath = join(childPath, `textures/${name}/`);
936
+ await this.writeQueue.enqueue({
937
+ sourceId,
938
+ outputId: nodeId,
939
+ resourceType: `${ResourceType.TEXTURE}/${format}`,
940
+ writePromise: () => writeFile(texturePath, textureData, `index.${format}`)
941
+ });
942
+ }
872
943
  }
873
- }
874
- _getFormatByMimeType(mimeType) {
875
- switch (mimeType) {
876
- case 'image/jpeg':
877
- return 'jpg';
878
- case 'image/png':
879
- return 'png';
880
- case 'image/ktx2':
881
- return 'ktx2';
882
- default:
883
- return 'jpg';
944
+ /**
945
+ * Write feature attributes in files
946
+ * @param attributes - feature attributes
947
+ * @param childPath - a child path to write resources
948
+ * @param slpkChildPath - the resource path inside *slpk file
949
+ * @param sourceId - source filename
950
+ * @param nodeId - nodeId of a converted node for the writing
951
+ */
952
+ async _writeAttributes(attributes = [], childPath, slpkChildPath, sourceId, nodeId) {
953
+ if (attributes?.length && this.attributeMetadataInfo.attributeStorageInfo.length) {
954
+ const minimumLength = attributes.length < this.attributeMetadataInfo.attributeStorageInfo.length
955
+ ? attributes.length
956
+ : this.attributeMetadataInfo.attributeStorageInfo.length;
957
+ for (let index = 0; index < minimumLength; index++) {
958
+ const folderName = this.attributeMetadataInfo.attributeStorageInfo[index].key;
959
+ const fileBuffer = new Uint8Array(attributes[index]);
960
+ this.conversionDump.updateDoneStatus(sourceId, nodeId, `${ResourceType.ATTRIBUTES}/${folderName}`, false);
961
+ if (this.options.slpk) {
962
+ const slpkAttributesPath = join(childPath, 'attributes', folderName);
963
+ await this.writeQueue.enqueue({
964
+ archiveKey: `${slpkChildPath}/attributes/${folderName}.bin.gz`,
965
+ sourceId,
966
+ outputId: nodeId,
967
+ resourceType: `${ResourceType.ATTRIBUTES}/${folderName}`,
968
+ writePromise: () => writeFileForSlpk(slpkAttributesPath, fileBuffer, '0.bin')
969
+ });
970
+ }
971
+ else {
972
+ const attributesPath = join(childPath, `attributes/${folderName}/0`);
973
+ await this.writeQueue.enqueue({
974
+ sourceId,
975
+ outputId: nodeId,
976
+ resourceType: `${ResourceType.ATTRIBUTES}/${folderName}`,
977
+ writePromise: () => writeFile(attributesPath, fileBuffer, 'index.bin')
978
+ });
979
+ }
980
+ }
981
+ }
884
982
  }
885
- }
886
- _findOrCreateMaterial(material) {
887
- const hash = md5(JSON.stringify(material));
888
- if (this.materialMap.has(hash)) {
889
- return this.materialMap.get(hash) || 0;
983
+ /**
984
+ * Return file format by its MIME type
985
+ * @param mimeType - feature attributes
986
+ */
987
+ _getFormatByMimeType(mimeType) {
988
+ switch (mimeType) {
989
+ case 'image/jpeg':
990
+ return 'jpg';
991
+ case 'image/png':
992
+ return 'png';
993
+ case 'image/ktx2':
994
+ return 'ktx2';
995
+ default:
996
+ return 'jpg';
997
+ }
890
998
  }
891
- const newMaterialId = this.materialDefinitions.push(material) - 1;
892
- this.materialMap.set(hash, newMaterialId);
893
- return newMaterialId;
894
- }
895
- findOrCreateGeometryDefinition(hasTexture, hasUvRegions) {
896
- const geometryConfig = {
897
- hasTexture,
898
- hasUvRegions
899
- };
900
- const hash = md5(JSON.stringify(geometryConfig));
901
- if (this.geometryMap.has(hash)) {
902
- return this.geometryMap.get(hash) || 0;
999
+ /**
1000
+ * Find or create material in materialDefinitions array
1001
+ * @param material - end-to-end index of the node
1002
+ * @return material id
1003
+ */
1004
+ _findOrCreateMaterial(material) {
1005
+ const hash = md5(JSON.stringify(material));
1006
+ if (this.materialMap.has(hash)) {
1007
+ return this.materialMap.get(hash) || 0;
1008
+ }
1009
+ const newMaterialId = this.materialDefinitions.push(material) - 1;
1010
+ this.materialMap.set(hash, newMaterialId);
1011
+ return newMaterialId;
903
1012
  }
904
- const newGeometryId = this.geometryConfigs.push(geometryConfig) - 1;
905
- this.geometryMap.set(hash, newGeometryId);
906
- return newGeometryId;
907
- }
908
- createAttributeStorageInfo(tileContent, propertyTable) {
909
- let attributeTypesMap = null;
910
- if (this.options.metadataClass) {
911
- if (!this.attributeMetadataInfo.attributeStorageInfo.length && tileContent !== null && tileContent !== void 0 && tileContent.gltf) {
912
- attributeTypesMap = getAttributeTypesMapFromSchema(tileContent.gltf, this.options.metadataClass);
913
- }
914
- } else if (propertyTable) {
915
- attributeTypesMap = getAttributeTypesMapFromPropertyTable(propertyTable);
1013
+ /**
1014
+ * Get unique geometry configuration index
1015
+ * In the end of conversion configurations will be transformed to geometryDefinitions array
1016
+ * @param hasTexture
1017
+ * @param hasUvRegions
1018
+ * @returns
1019
+ */
1020
+ findOrCreateGeometryDefinition(hasTexture, hasUvRegions) {
1021
+ const geometryConfig = { hasTexture, hasUvRegions };
1022
+ const hash = md5(JSON.stringify(geometryConfig));
1023
+ if (this.geometryMap.has(hash)) {
1024
+ return this.geometryMap.get(hash) || 0;
1025
+ }
1026
+ const newGeometryId = this.geometryConfigs.push(geometryConfig) - 1;
1027
+ this.geometryMap.set(hash, newGeometryId);
1028
+ return newGeometryId;
916
1029
  }
917
- if (attributeTypesMap) {
918
- this.attributeMetadataInfo.addMetadataInfo(attributeTypesMap);
1030
+ /**
1031
+ * Creates attribute storage info based on either extension schema or property table.
1032
+ * @param tileContent - content of the source tile
1033
+ * @param propertyTable - feature properties from EXT_FEATURE_METADATA, EXT_STRUCTURAL_METADATA
1034
+ */
1035
+ createAttributeStorageInfo(tileContent, propertyTable) {
1036
+ /*
1037
+ In case the tileset doesn't have either EXT_structural_metadata or EXT_feature_metadata
1038
+ that can be a source of attribute information so metadataClass is not specified
1039
+ we will collect attribute information for node attributes from the property table
1040
+ taken from each tile.
1041
+ */
1042
+ let attributeTypesMap = null;
1043
+ if (this.options.metadataClass) {
1044
+ if (!this.attributeMetadataInfo.attributeStorageInfo.length && tileContent?.gltf) {
1045
+ attributeTypesMap = getAttributeTypesMapFromSchema(tileContent.gltf, this.options.metadataClass);
1046
+ }
1047
+ }
1048
+ else if (propertyTable) {
1049
+ attributeTypesMap = getAttributeTypesMapFromPropertyTable(propertyTable);
1050
+ }
1051
+ if (attributeTypesMap) {
1052
+ // Add new storage attributes, fields and create popupInfo
1053
+ this.attributeMetadataInfo.addMetadataInfo(attributeTypesMap);
1054
+ }
919
1055
  }
920
- }
921
- async _finishConversion(params) {
922
- const {
923
- tilesCount,
924
- tilesWithAddRefineCount
925
- } = this.refinementCounter;
926
- const addRefinementPercentage = tilesWithAddRefineCount ? tilesWithAddRefineCount / tilesCount * 100 : 0;
927
- const filesSize = await calculateFilesSize(params);
928
- const diff = process.hrtime(this.conversionStartTime);
929
- const conversionTime = timeConverter(diff);
930
- console.log(`------------------------------------------------`);
931
- console.log(`Finishing conversion of ${_3D_TILES}`);
932
- console.log(`Total conversion time: ${conversionTime}`);
933
- console.log(`Vertex count: `, this.vertexCounter);
934
- console.log(`File(s) size: `, filesSize, ' bytes');
935
- console.log(`Percentage of tiles with "ADD" refinement type:`, addRefinementPercentage, '%');
936
- console.log(`------------------------------------------------`);
937
- }
938
- async _fetchPreloadOptions() {
939
- if (!this.Loader.preload) {
940
- return {};
1056
+ /**
1057
+ * Print statistics in the end of conversion
1058
+ * @param params - output files data
1059
+ */
1060
+ async _finishConversion(params) {
1061
+ const { tilesCount, tilesWithAddRefineCount } = this.refinementCounter;
1062
+ const addRefinementPercentage = tilesWithAddRefineCount
1063
+ ? (tilesWithAddRefineCount / tilesCount) * 100
1064
+ : 0;
1065
+ const filesSize = await calculateFilesSize(params);
1066
+ const diff = process.hrtime(this.conversionStartTime);
1067
+ const conversionTime = timeConverter(diff);
1068
+ console.log(`------------------------------------------------`); // eslint-disable-line no-undef, no-console
1069
+ console.log(`Finishing conversion of ${_3D_TILES}`); // eslint-disable-line no-undef, no-console
1070
+ console.log(`Total conversion time: ${conversionTime}`); // eslint-disable-line no-undef, no-console
1071
+ console.log(`Vertex count: `, this.vertexCounter); // eslint-disable-line no-undef, no-console
1072
+ console.log(`File(s) size: `, filesSize, ' bytes'); // eslint-disable-line no-undef, no-console
1073
+ console.log(`Percentage of tiles with "ADD" refinement type:`, addRefinementPercentage, '%'); // eslint-disable-line no-undef, no-console
1074
+ console.log(`------------------------------------------------`); // eslint-disable-line no-undef, no-console
941
1075
  }
942
- const options = {
943
- 'cesium-ion': {
944
- accessToken: this.options.token || ION_DEFAULT_TOKEN
945
- }
946
- };
947
- const preloadOptions = await this.Loader.preload(this.options.inputUrl, options);
948
- this.refreshTokenTime = process.hrtime();
949
- return {
950
- ...options,
951
- ...preloadOptions
952
- };
953
- }
954
- async _updateTilesetOptions() {
955
- const diff = process.hrtime(this.refreshTokenTime);
956
- if (diff[0] < REFRESH_TOKEN_TIMEOUT) {
957
- return;
1076
+ /**
1077
+ * Fetch preload options for ION tileset
1078
+ */
1079
+ async _fetchPreloadOptions() {
1080
+ if (!this.Loader.preload) {
1081
+ return {};
1082
+ }
1083
+ const options = {
1084
+ 'cesium-ion': { accessToken: this.options.token || ION_DEFAULT_TOKEN }
1085
+ };
1086
+ const preloadOptions = await this.Loader.preload(this.options.inputUrl, options);
1087
+ this.refreshTokenTime = process.hrtime();
1088
+ return { ...options, ...preloadOptions };
958
1089
  }
959
- this.refreshTokenTime = process.hrtime();
960
- const preloadOptions = await this._fetchPreloadOptions();
961
- if (preloadOptions.headers) {
962
- this.loadOptions.fetch = {
963
- ...this.loadOptions.fetch,
964
- headers: preloadOptions.headers
965
- };
966
- console.log('Authorization Bearer token has been updated');
1090
+ /**
1091
+ * Update options of source tileset
1092
+ */
1093
+ async _updateTilesetOptions() {
1094
+ const diff = process.hrtime(this.refreshTokenTime);
1095
+ if (diff[0] < REFRESH_TOKEN_TIMEOUT) {
1096
+ return;
1097
+ }
1098
+ this.refreshTokenTime = process.hrtime();
1099
+ const preloadOptions = await this._fetchPreloadOptions();
1100
+ if (preloadOptions.headers) {
1101
+ this.loadOptions.fetch = {
1102
+ ...this.loadOptions.fetch,
1103
+ headers: preloadOptions.headers
1104
+ };
1105
+ console.log('Authorization Bearer token has been updated'); // eslint-disable-line no-undef, no-console
1106
+ }
1107
+ }
1108
+ /** Do calculations of all tiles and tiles with "ADD" type of refinement.
1109
+ * @param tile
1110
+ */
1111
+ _checkAddRefinementTypeForTile(tile) {
1112
+ const ADD_TILE_REFINEMENT = 1;
1113
+ if (tile.refine === ADD_TILE_REFINEMENT) {
1114
+ this.refinementCounter.tilesWithAddRefineCount += 1;
1115
+ console.warn('This tile uses "ADD" type of refinement'); // eslint-disable-line
1116
+ }
1117
+ this.refinementCounter.tilesCount += 1;
967
1118
  }
968
- }
969
- _checkAddRefinementTypeForTile(tile) {
970
- const ADD_TILE_REFINEMENT = 1;
971
- if (tile.refine === ADD_TILE_REFINEMENT) {
972
- this.refinementCounter.tilesWithAddRefineCount += 1;
973
- console.warn('This tile uses "ADD" type of refinement');
1119
+ /**
1120
+ * Check if the tile's content format is supported by the converter
1121
+ * @param sourceTile
1122
+ * @returns
1123
+ */
1124
+ isContentSupported(sourceTile) {
1125
+ return ['b3dm', 'glTF', 'scenegraph'].includes(sourceTile.type || '');
974
1126
  }
975
- this.refinementCounter.tilesCount += 1;
976
- }
977
- isContentSupported(sourceTile) {
978
- return ['b3dm', 'glTF', 'scenegraph'].includes(sourceTile.type || '');
979
- }
980
1127
  }
981
- //# sourceMappingURL=i3s-converter.js.map