@loaders.gl/tile-converter 4.2.0-alpha.4 → 4.2.0-alpha.6

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