@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
@@ -13,6 +13,7 @@ import { checkPropertiesLength, flattenPropertyTableByFeatureIds } from "./featu
13
13
  import { GL } from '@loaders.gl/math';
14
14
  import { generateSyntheticIndices } from "../../lib/utils/geometry-utils.js";
15
15
  import { EXT_FEATURE_METADATA, EXT_STRUCTURAL_METADATA } from '@loaders.gl/gltf';
16
+ // Spec - https://github.com/Esri/i3s-spec/blob/master/docs/1.7/pbrMetallicRoughness.cmn.md
16
17
  const DEFAULT_ROUGHNESS_FACTOR = 1;
17
18
  const DEFAULT_METALLIC_FACTOR = 1;
18
19
  const VALUES_PER_VERTEX = 3;
@@ -22,927 +23,1285 @@ const STRING_TYPE = 'string';
22
23
  const SHORT_INT_TYPE = 'Int32';
23
24
  const DOUBLE_TYPE = 'Float64';
24
25
  const OBJECT_ID_TYPE = 'Oid32';
26
+ /*
27
+ * 'CUSTOM_ATTRIBUTE_2' - Attribute name which includes batch info and used by New York map.
28
+ * _BATCHID - Default attribute name which includes batch info.
29
+ * BATCHID - Legacy attribute name which includes batch info.
30
+ */
25
31
  const BATCHED_ID_POSSIBLE_ATTRIBUTE_NAMES = ['CUSTOM_ATTRIBUTE_2', '_BATCHID', 'BATCHID'];
26
32
  let scratchVector = new Vector3();
27
- export default async function convertB3dmToI3sGeometry(tileContent, tileTransform, tileBoundingVolume, addNodeToNodePage, propertyTable, featuresHashArray, attributeStorageInfo, draco, generateBoundingVolumes, shouldMergeMaterials, geoidHeightModel, libraries, metadataClass) {
28
- var _tileContent$gltf;
29
- const useCartesianPositions = generateBoundingVolumes;
30
- const materialAndTextureList = await convertMaterials((_tileContent$gltf = tileContent.gltf) === null || _tileContent$gltf === void 0 ? void 0 : _tileContent$gltf.materials, shouldMergeMaterials);
31
- const dataForAttributesConversion = prepareDataForAttributesConversion(tileContent, tileTransform, tileBoundingVolume);
32
- const featureTexture = getTextureByMetadataClass(tileContent, metadataClass);
33
- const convertedAttributesMap = await convertAttributes(dataForAttributesConversion, materialAndTextureList, useCartesianPositions, featureTexture);
34
- if (generateBoundingVolumes) {
35
- _generateBoundingVolumesFromGeometry(convertedAttributesMap, geoidHeightModel);
36
- }
37
- const result = [];
38
- for (const materialAndTexture of materialAndTextureList) {
39
- const originarMaterialId = materialAndTexture.mergedMaterials[0].originalMaterialId;
40
- if (!convertedAttributesMap.has(originarMaterialId)) {
41
- continue;
42
- }
43
- const convertedAttributes = convertedAttributesMap.get(originarMaterialId);
44
- if (!convertedAttributes) {
45
- continue;
46
- }
47
- const {
48
- material,
49
- texture
50
- } = materialAndTexture;
51
- const nodeId = await addNodeToNodePage();
52
- result.push(await _makeNodeResources({
53
- convertedAttributes,
54
- material,
55
- texture,
56
- tileContent,
57
- nodeId,
58
- featuresHashArray,
59
- propertyTable,
60
- attributeStorageInfo,
61
- draco,
62
- libraries
63
- }));
64
- }
65
- if (!result.length) {
66
- return null;
67
- }
68
- return result;
33
+ /**
34
+ * Convert binary data from b3dm file to i3s resources
35
+ *
36
+ * @param tileContent - 3d tile content
37
+ * @param tileTransform - transformation matrix of the tile, calculated recursively multiplying
38
+ * transform of all parent tiles and transform of the current tile
39
+ * @param tileBoundingVolume - initialized bounding volume of the source tile
40
+ * @param addNodeToNodePage - function to add new node to node pages
41
+ * @param propertyTable - batch table (corresponding to feature attributes data)
42
+ * @param featuresHashArray - hash array of features that is needed to not to mix up same features in parent and child nodes
43
+ * @param attributeStorageInfo - attributes metadata from 3DSceneLayer json
44
+ * @param draco - is converter should create draco compressed geometry
45
+ * @param generateBoundingVolumes - is converter should create accurate bounding voulmes from geometry attributes
46
+ * @param shouldMergeMaterials - Try to merge similar materials to be able to merge meshes into one node
47
+ * @param geoidHeightModel - model to convert elevation from elipsoidal to geoid
48
+ * @param libraries - dynamicaly loaded 3rd-party libraries
49
+ * @param metadataClass `- user selected feature metadata class name`
50
+ * @returns Array of node resources to create one or more i3s nodes
51
+ */
52
+ export default async function convertB3dmToI3sGeometry({ tileContent, tileTransform, tileBoundingVolume, addNodeToNodePage, propertyTable, featuresHashArray, attributeStorageInfo, draco, generateBoundingVolumes, shouldMergeMaterials, geoidHeightModel, libraries, metadataClass }) {
53
+ const useCartesianPositions = generateBoundingVolumes;
54
+ const materialAndTextureList = await convertMaterials(tileContent.gltf?.materials, shouldMergeMaterials);
55
+ const dataForAttributesConversion = prepareDataForAttributesConversion(tileContent, tileTransform, tileBoundingVolume);
56
+ const featureTexture = getTextureByMetadataClass(tileContent, metadataClass);
57
+ const convertedAttributesMap = await convertAttributes(dataForAttributesConversion, materialAndTextureList, useCartesianPositions, featureTexture);
58
+ /** Usage of worker here brings more overhead than advantage */
59
+ // const convertedAttributesMap: Map<string, ConvertedAttributes> =
60
+ // await transformI3SAttributesOnWorker(dataForAttributesConversion, {
61
+ // reuseWorkers: true,
62
+ // _nodeWorkers: true,
63
+ // useCartesianPositions,
64
+ // source: workerSource.I3SAttributes
65
+ // });
66
+ if (generateBoundingVolumes) {
67
+ _generateBoundingVolumesFromGeometry(convertedAttributesMap, geoidHeightModel);
68
+ }
69
+ const result = [];
70
+ for (const materialAndTexture of materialAndTextureList) {
71
+ const originarMaterialId = materialAndTexture.mergedMaterials[0].originalMaterialId;
72
+ if (!convertedAttributesMap.has(originarMaterialId)) {
73
+ continue; // eslint-disable-line no-continue
74
+ }
75
+ const convertedAttributes = convertedAttributesMap.get(originarMaterialId);
76
+ if (!convertedAttributes) {
77
+ continue; // eslint-disable-line no-continue
78
+ }
79
+ const { material, texture } = materialAndTexture;
80
+ const nodeId = await addNodeToNodePage();
81
+ result.push(await _makeNodeResources({
82
+ convertedAttributes,
83
+ material,
84
+ texture,
85
+ tileContent,
86
+ nodeId,
87
+ featuresHashArray,
88
+ propertyTable,
89
+ attributeStorageInfo,
90
+ draco,
91
+ libraries
92
+ }));
93
+ }
94
+ if (!result.length) {
95
+ return null;
96
+ }
97
+ return result;
69
98
  }
99
+ /**
100
+ * Create bounding volumes based on positions
101
+ * @param convertedAttributesMap - geometry attributes map
102
+ * @param geoidHeightModel - geoid height model to convert elevation from elipsoidal to geoid
103
+ */
70
104
  function _generateBoundingVolumesFromGeometry(convertedAttributesMap, geoidHeightModel) {
71
- for (const attributes of convertedAttributesMap.values()) {
72
- const boundingVolumes = createBoundingVolumesFromGeometry(attributes.positions, geoidHeightModel);
73
- attributes.boundingVolumes = boundingVolumes;
74
- const cartographicOrigin = boundingVolumes.obb.center;
75
- for (let index = 0; index < attributes.positions.length; index += VALUES_PER_VERTEX) {
76
- const vertex = attributes.positions.subarray(index, index + VALUES_PER_VERTEX);
77
- Ellipsoid.WGS84.cartesianToCartographic(Array.from(vertex), scratchVector);
78
- scratchVector[2] = scratchVector[2] - geoidHeightModel.getHeight(scratchVector[1], scratchVector[0]);
79
- scratchVector = scratchVector.subtract(cartographicOrigin);
80
- attributes.positions.set(scratchVector, index);
81
- }
82
- }
83
- }
84
- async function _makeNodeResources(_ref) {
85
- var _tileContent$gltf2;
86
- let {
87
- convertedAttributes,
88
- material,
89
- texture,
90
- tileContent,
91
- nodeId,
92
- featuresHashArray,
93
- propertyTable,
94
- attributeStorageInfo,
95
- draco,
96
- libraries
97
- } = _ref;
98
- const boundingVolumes = convertedAttributes.boundingVolumes;
99
- const vertexCount = convertedAttributes.positions.length / VALUES_PER_VERTEX;
100
- const {
101
- faceRange,
102
- featureIds,
103
- positions,
104
- normals,
105
- colors,
106
- uvRegions,
107
- texCoords,
108
- featureCount
109
- } = generateAttributes(convertedAttributes);
110
- let featureIdsMap = {};
111
- if (propertyTable) {
112
- featureIdsMap = makeFeatureIdsUnique(featureIds, convertedAttributes.featureIndices, featuresHashArray, propertyTable);
113
- }
114
- const header = new Uint32Array(2);
115
- const typedFeatureIds = generateBigUint64Array(featureIds);
116
- header.set([vertexCount, featureCount], 0);
117
- const fileBuffer = new Uint8Array(concatenateArrayBuffers(header.buffer, positions.buffer, normals.buffer, texture ? texCoords.buffer : new ArrayBuffer(0), colors.buffer, uvRegions, typedFeatureIds.buffer, faceRange.buffer));
118
- const compressedGeometry = draco ? generateCompressedGeometry(vertexCount, convertedAttributes, {
119
- positions,
120
- normals,
121
- texCoords: texture ? texCoords : new Float32Array(0),
122
- colors,
123
- uvRegions,
124
- featureIds,
125
- faceRange
126
- }, libraries) : null;
127
- let attributes = [];
128
- if (attributeStorageInfo && propertyTable) {
129
- attributes = convertPropertyTableToAttributeBuffers(featureIds, featureIdsMap, propertyTable, attributeStorageInfo);
130
- }
131
- return {
132
- nodeId,
133
- geometry: fileBuffer,
134
- compressedGeometry,
135
- texture,
136
- hasUvRegions: Boolean(uvRegions.length),
137
- sharedResources: getSharedResources(((_tileContent$gltf2 = tileContent.gltf) === null || _tileContent$gltf2 === void 0 ? void 0 : _tileContent$gltf2.materials) || [], nodeId),
138
- meshMaterial: material,
139
- vertexCount,
140
- attributes,
141
- featureCount,
142
- boundingVolumes
143
- };
105
+ for (const attributes of convertedAttributesMap.values()) {
106
+ const boundingVolumes = createBoundingVolumesFromGeometry(attributes.positions, geoidHeightModel);
107
+ attributes.boundingVolumes = boundingVolumes;
108
+ const cartographicOrigin = boundingVolumes.obb.center;
109
+ for (let index = 0; index < attributes.positions.length; index += VALUES_PER_VERTEX) {
110
+ const vertex = attributes.positions.subarray(index, index + VALUES_PER_VERTEX);
111
+ Ellipsoid.WGS84.cartesianToCartographic(Array.from(vertex), scratchVector);
112
+ scratchVector[2] =
113
+ scratchVector[2] - geoidHeightModel.getHeight(scratchVector[1], scratchVector[0]);
114
+ scratchVector = scratchVector.subtract(cartographicOrigin);
115
+ attributes.positions.set(scratchVector, index);
116
+ }
117
+ }
144
118
  }
145
- export async function convertAttributes(attributesData, materialAndTextureList, useCartesianPositions, featureTexture) {
146
- const {
147
- nodes,
148
- images,
149
- cartographicOrigin,
150
- cartesianModelMatrix
151
- } = attributesData;
152
- const attributesMap = new Map();
153
- for (const materialAndTexture of materialAndTextureList) {
154
- const attributes = {
155
- positions: new Float32Array(0),
156
- normals: new Float32Array(0),
157
- texCoords: new Float32Array(0),
158
- colors: new Uint8Array(0),
159
- uvRegions: new Uint16Array(0),
160
- featureIndicesGroups: [],
161
- featureIndices: [],
162
- boundingVolumes: null,
163
- mergedMaterials: materialAndTexture.mergedMaterials
164
- };
165
- for (const mergedMaterial of materialAndTexture.mergedMaterials) {
166
- attributesMap.set(mergedMaterial.originalMaterialId, attributes);
119
+ /**
120
+ *
121
+ * @param params
122
+ * @param params.convertedAttributes - Converted geometry attributes
123
+ * @param params.material - I3S PBR-like material definition
124
+ * @param params.texture - texture content
125
+ * @param params.tileContent - 3DTiles decoded content
126
+ * @param params.nodeId - new node ID
127
+ * @param params.featuresHashArray - hash array of features that is needed to not to mix up same features in parent and child nodes
128
+ * @param params.propertyTable - batch table (corresponding to feature attributes data)
129
+ * @param params.attributeStorageInfo - attributes metadata from 3DSceneLayer json
130
+ * @param params.draco - is converter should create draco compressed geometry
131
+ * @param libraries - dynamicaly loaded 3rd-party libraries
132
+ * @returns Array of I3S node resources
133
+ */
134
+ async function _makeNodeResources({ convertedAttributes, material, texture, tileContent, nodeId, featuresHashArray, propertyTable, attributeStorageInfo, draco, libraries }) {
135
+ const boundingVolumes = convertedAttributes.boundingVolumes;
136
+ const vertexCount = convertedAttributes.positions.length / VALUES_PER_VERTEX;
137
+ const { faceRange, featureIds, positions, normals, colors, uvRegions, texCoords, featureCount } = generateAttributes(convertedAttributes);
138
+ let featureIdsMap = {};
139
+ if (propertyTable) {
140
+ /**
141
+ * 3DTiles has featureIndices unique only for one tile.
142
+ * In I3S featureIds are unique layer-wide. We create featureIds from all feature properties.
143
+ * If 3DTiles features has equal set of properties they are considered as same feature in I3S.
144
+ */
145
+ featureIdsMap = makeFeatureIdsUnique(featureIds, convertedAttributes.featureIndices, featuresHashArray, propertyTable);
167
146
  }
168
- }
169
- convertNodes(nodes, images, cartographicOrigin, cartesianModelMatrix, attributesMap, useCartesianPositions, undefined, featureTexture);
170
- for (const attrKey of attributesMap.keys()) {
171
- const attributes = attributesMap.get(attrKey);
172
- if (!attributes) {
173
- continue;
147
+ const header = new Uint32Array(2);
148
+ const typedFeatureIds = generateBigUint64Array(featureIds);
149
+ header.set([vertexCount, featureCount], 0);
150
+ const fileBuffer = new Uint8Array(concatenateArrayBuffers(header.buffer, positions.buffer, normals.buffer, texture ? texCoords.buffer : new ArrayBuffer(0), colors.buffer, uvRegions, typedFeatureIds.buffer, faceRange.buffer));
151
+ // prettier-ignore
152
+ const compressedGeometry = draco
153
+ ? generateCompressedGeometry(vertexCount, convertedAttributes, {
154
+ positions,
155
+ normals,
156
+ texCoords: texture ? texCoords : new Float32Array(0),
157
+ colors,
158
+ uvRegions,
159
+ featureIds,
160
+ faceRange
161
+ }, libraries)
162
+ : null;
163
+ let attributes = [];
164
+ if (attributeStorageInfo && propertyTable) {
165
+ attributes = convertPropertyTableToAttributeBuffers(featureIds, featureIdsMap, propertyTable, attributeStorageInfo);
174
166
  }
175
- if (attributes.positions.length === 0) {
176
- attributesMap.delete(attrKey);
177
- continue;
167
+ return {
168
+ nodeId,
169
+ geometry: fileBuffer,
170
+ compressedGeometry,
171
+ texture,
172
+ hasUvRegions: Boolean(uvRegions.length),
173
+ sharedResources: getSharedResources(tileContent.gltf?.materials || [], nodeId),
174
+ meshMaterial: material,
175
+ vertexCount,
176
+ attributes,
177
+ featureCount,
178
+ boundingVolumes
179
+ };
180
+ }
181
+ /**
182
+ * Convert attributes from the gltf nodes tree to i3s plain geometry
183
+ * @param attributesData - geometry attributes from gltf
184
+ * @param materialAndTextureList - array of data about materials and textures of the content
185
+ * @param useCartesianPositions - convert positions to absolute cartesian coordinates instead of cartographic offsets.
186
+ * Cartesian coordinates will be required for creating bounding voulmest from geometry positions
187
+ * @param featureTexture - feature texture key
188
+ * @returns map of converted geometry attributes
189
+ */
190
+ export async function convertAttributes(attributesData, materialAndTextureList, useCartesianPositions, featureTexture) {
191
+ const { nodes, images, cartographicOrigin, cartesianModelMatrix } = attributesData;
192
+ const attributesMap = new Map();
193
+ for (const materialAndTexture of materialAndTextureList) {
194
+ const attributes = {
195
+ positions: new Float32Array(0),
196
+ normals: new Float32Array(0),
197
+ texCoords: new Float32Array(0),
198
+ colors: new Uint8Array(0),
199
+ uvRegions: new Uint16Array(0),
200
+ featureIndicesGroups: [],
201
+ featureIndices: [],
202
+ boundingVolumes: null,
203
+ mergedMaterials: materialAndTexture.mergedMaterials
204
+ };
205
+ for (const mergedMaterial of materialAndTexture.mergedMaterials) {
206
+ attributesMap.set(mergedMaterial.originalMaterialId, attributes);
207
+ }
178
208
  }
179
- if (attributes.featureIndicesGroups) {
180
- attributes.featureIndices = attributes.featureIndicesGroups.reduce((acc, value) => acc.concat(value));
181
- delete attributes.featureIndicesGroups;
209
+ convertNodes({
210
+ nodes,
211
+ images,
212
+ cartographicOrigin,
213
+ cartesianModelMatrix,
214
+ attributesMap,
215
+ useCartesianPositions,
216
+ featureTexture
217
+ });
218
+ for (const attrKey of attributesMap.keys()) {
219
+ const attributes = attributesMap.get(attrKey);
220
+ if (!attributes) {
221
+ continue; // eslint-disable-line no-continue
222
+ }
223
+ if (attributes.positions.length === 0) {
224
+ attributesMap.delete(attrKey);
225
+ continue; // eslint-disable-line no-continue
226
+ }
227
+ if (attributes.featureIndicesGroups) {
228
+ attributes.featureIndices = attributes.featureIndicesGroups.reduce((acc, value) => acc.concat(value));
229
+ delete attributes.featureIndicesGroups;
230
+ }
182
231
  }
183
- }
184
- return attributesMap;
232
+ return attributesMap;
185
233
  }
186
- function convertNodes(nodes, images, cartographicOrigin, cartesianModelMatrix, attributesMap, useCartesianPositions) {
187
- let matrix = arguments.length > 6 && arguments[6] !== undefined ? arguments[6] : new Matrix4([1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]);
188
- let featureTexture = arguments.length > 7 ? arguments[7] : undefined;
189
- if (nodes) {
190
- for (const node of nodes) {
191
- convertNode(node, images, cartographicOrigin, cartesianModelMatrix, attributesMap, useCartesianPositions, matrix, featureTexture);
234
+ /**
235
+ * glTF has hierarchical structure of nodes. This function converts nodes starting from those which are in gltf scene object.
236
+ * The goal is applying tranformation matrix for all children. Functions "convertNodes" and "convertNode" work together recursively.
237
+ * @param nodes - gltf nodes array
238
+ * @param images - gltf images array
239
+ * @param cartographicOrigin - cartographic origin of bounding volume
240
+ * @param cartesianModelMatrix - cartesian model matrix to convert coordinates to cartographic
241
+ * @param attributesMap - for recursive concatenation of attributes
242
+ * @param useCartesianPositions - convert positions to absolute cartesian coordinates instead of cartographic offsets.
243
+ * Cartesian coordinates will be required for creating bounding voulmest from geometry positions
244
+ * @param matrix - transformation matrix - cumulative transformation matrix formed from all parent node matrices
245
+ * @param featureTexture - feature texture key
246
+ * @returns {void}
247
+ */
248
+ function convertNodes({ nodes, images, cartographicOrigin, cartesianModelMatrix, attributesMap, useCartesianPositions, matrix = new Matrix4([1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]), featureTexture }) {
249
+ if (nodes) {
250
+ for (const node of nodes) {
251
+ convertNode({
252
+ node,
253
+ images,
254
+ cartographicOrigin,
255
+ cartesianModelMatrix,
256
+ attributesMap,
257
+ useCartesianPositions,
258
+ matrix,
259
+ featureTexture
260
+ });
261
+ }
192
262
  }
193
- }
194
263
  }
264
+ /**
265
+ * Generate transformation matrix for node
266
+ * Aapply all gltf transformations to initial transformation matrix.
267
+ * @param node
268
+ * @param matrix
269
+ */
195
270
  function getCompositeTransformationMatrix(node, matrix) {
196
- let transformationMatrix = matrix;
197
- const {
198
- matrix: nodeMatrix,
199
- rotation,
200
- scale,
201
- translation
202
- } = node;
203
- if (nodeMatrix) {
204
- transformationMatrix = matrix.multiplyRight(nodeMatrix);
205
- }
206
- if (translation) {
207
- transformationMatrix = transformationMatrix.translate(translation);
208
- }
209
- if (rotation) {
210
- transformationMatrix = transformationMatrix.rotateXYZ(rotation);
211
- }
212
- if (scale) {
213
- transformationMatrix = transformationMatrix.scale(scale);
214
- }
215
- return transformationMatrix;
216
- }
217
- function convertNode(node, images, cartographicOrigin, cartesianModelMatrix, attributesMap, useCartesianPositions) {
218
- let matrix = arguments.length > 6 && arguments[6] !== undefined ? arguments[6] : new Matrix4([1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]);
219
- let featureTexture = arguments.length > 7 ? arguments[7] : undefined;
220
- const transformationMatrix = getCompositeTransformationMatrix(node, matrix);
221
- const mesh = node.mesh;
222
- if (mesh) {
223
- convertMesh(mesh, images, cartographicOrigin, cartesianModelMatrix, attributesMap, useCartesianPositions, transformationMatrix, featureTexture);
224
- }
225
- convertNodes(node.children || [], images, cartographicOrigin, cartesianModelMatrix, attributesMap, useCartesianPositions, transformationMatrix, featureTexture);
226
- }
227
- function convertMesh(mesh, images, cartographicOrigin, cartesianModelMatrix, attributesMap) {
228
- let useCartesianPositions = arguments.length > 5 && arguments[5] !== undefined ? arguments[5] : false;
229
- let matrix = arguments.length > 6 && arguments[6] !== undefined ? arguments[6] : new Matrix4([1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]);
230
- let featureTexture = arguments.length > 7 ? arguments[7] : undefined;
231
- for (const primitive of mesh.primitives) {
232
- let outputAttributes = null;
233
- let materialUvRegion;
234
- if (primitive.material) {
235
- var _outputAttributes, _outputAttributes$mer;
236
- outputAttributes = attributesMap.get(primitive.material.id);
237
- materialUvRegion = (_outputAttributes = outputAttributes) === null || _outputAttributes === void 0 ? void 0 : (_outputAttributes$mer = _outputAttributes.mergedMaterials.find(_ref2 => {
238
- var _primitive$material;
239
- let {
240
- originalMaterialId
241
- } = _ref2;
242
- return originalMaterialId === ((_primitive$material = primitive.material) === null || _primitive$material === void 0 ? void 0 : _primitive$material.id);
243
- })) === null || _outputAttributes$mer === void 0 ? void 0 : _outputAttributes$mer.uvRegion;
244
- } else if (attributesMap.has('default')) {
245
- outputAttributes = attributesMap.get('default');
246
- }
247
- assert(outputAttributes !== null, 'Primitive - material mapping failed');
248
- assert(primitive.mode === undefined || primitive.mode === GL.TRIANGLES || primitive.mode === GL.TRIANGLE_STRIP, `Primitive - unsupported mode ${primitive.mode}`);
249
- const attributes = primitive.attributes;
250
- if (!outputAttributes) {
251
- continue;
252
- }
253
- const indices = normalizeIndices(primitive);
254
- outputAttributes.positions = concatenateTypedArrays(outputAttributes.positions, transformVertexArray({
255
- vertices: attributes.POSITION.value,
256
- cartographicOrigin,
257
- cartesianModelMatrix,
258
- nodeMatrix: matrix,
259
- indices,
260
- attributeSpecificTransformation: transformVertexPositions,
261
- useCartesianPositions
262
- }));
263
- outputAttributes.normals = concatenateTypedArrays(outputAttributes.normals, transformVertexArray({
264
- vertices: attributes.NORMAL && attributes.NORMAL.value,
265
- cartographicOrigin,
266
- cartesianModelMatrix,
267
- nodeMatrix: matrix,
268
- indices,
269
- attributeSpecificTransformation: transformVertexNormals,
270
- useCartesianPositions: false
271
- }));
272
- outputAttributes.texCoords = concatenateTypedArrays(outputAttributes.texCoords, flattenTexCoords(attributes.TEXCOORD_0 && attributes.TEXCOORD_0.value, indices));
273
- outputAttributes.colors = concatenateTypedArrays(outputAttributes.colors, flattenColors(attributes.COLOR_0, indices));
274
- if (materialUvRegion) {
275
- outputAttributes.uvRegions = concatenateTypedArrays(outputAttributes.uvRegions, createUvRegion(materialUvRegion, indices));
276
- }
277
- outputAttributes.featureIndicesGroups = outputAttributes.featureIndicesGroups || [];
278
- outputAttributes.featureIndicesGroups.push(flattenBatchIds(getBatchIds(attributes, primitive, images, featureTexture), indices));
279
- }
271
+ let transformationMatrix = matrix;
272
+ const { matrix: nodeMatrix, rotation, scale, translation } = node;
273
+ if (nodeMatrix) {
274
+ transformationMatrix = matrix.multiplyRight(nodeMatrix);
275
+ }
276
+ if (translation) {
277
+ transformationMatrix = transformationMatrix.translate(translation);
278
+ }
279
+ if (rotation) {
280
+ transformationMatrix = transformationMatrix.rotateXYZ(rotation);
281
+ }
282
+ if (scale) {
283
+ transformationMatrix = transformationMatrix.scale(scale);
284
+ }
285
+ return transformationMatrix;
286
+ }
287
+ /**
288
+ * Convert all primitives of node and all children nodes
289
+ * @param node - gltf node
290
+ * @param images - gltf images array
291
+ * @param cartographicOrigin - cartographic origin of bounding volume
292
+ * @param cartesianModelMatrix - cartesian model matrix to convert coordinates to cartographic
293
+ * @param attributesMap Map<{positions: Float32Array, normals: Float32Array, texCoords: Float32Array, colors: Uint8Array, featureIndices: Array}> - for recursive concatenation of
294
+ * attributes
295
+ * @param useCartesianPositions - convert positions to absolute cartesian coordinates instead of cartographic offsets.
296
+ * Cartesian coordinates will be required for creating bounding voulmest from geometry positions
297
+ * @param matrix - transformation matrix - cumulative transformation matrix formed from all parent node matrices
298
+ * @param featureTexture - feature texture key
299
+ */
300
+ function convertNode({ node, images, cartographicOrigin, cartesianModelMatrix, attributesMap, useCartesianPositions, matrix = new Matrix4([1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]), featureTexture }) {
301
+ const transformationMatrix = getCompositeTransformationMatrix(node, matrix);
302
+ const mesh = node.mesh;
303
+ if (mesh) {
304
+ convertMesh({
305
+ mesh,
306
+ images,
307
+ cartographicOrigin,
308
+ cartesianModelMatrix,
309
+ attributesMap,
310
+ useCartesianPositions,
311
+ matrix: transformationMatrix,
312
+ featureTexture
313
+ });
314
+ }
315
+ convertNodes({
316
+ nodes: node.children || [],
317
+ images,
318
+ cartographicOrigin,
319
+ cartesianModelMatrix,
320
+ attributesMap,
321
+ useCartesianPositions,
322
+ matrix: transformationMatrix,
323
+ featureTexture
324
+ });
280
325
  }
326
+ /**
327
+ * Convert all primitives of the mesh
328
+ * @param mesh - gltf mesh data
329
+ * @param images - gltf images array
330
+ * @param cartographicOrigin - cartographic origin of bounding volume
331
+ * @param cartesianModelMatrix - cartesian model matrix to convert coordinates to cartographic
332
+ * @param attributesMap Map<{positions: Float32Array, normals: Float32Array, texCoords: Float32Array, colors: Uint8Array, featureIndices: Array}> - for recursive concatenation of
333
+ * attributes
334
+ * @param useCartesianPositions - convert positions to absolute cartesian coordinates instead of cartographic offsets.
335
+ * Cartesian coordinates will be required for creating bounding voulmest from geometry positions
336
+ * @param attributesMap Map<{positions: Float32Array, normals: Float32Array, texCoords: Float32Array, colors: Uint8Array, featureIndices: Array}> - for recursive concatenation of
337
+ * attributes
338
+ * @param matrix - transformation matrix - cumulative transformation matrix formed from all parent node matrices
339
+ * @param featureTexture - feature texture key
340
+ */
341
+ function convertMesh({ mesh, images, cartographicOrigin, cartesianModelMatrix, attributesMap, useCartesianPositions = false, matrix, featureTexture }) {
342
+ for (const primitive of mesh.primitives) {
343
+ let outputAttributes = null;
344
+ let materialUvRegion;
345
+ if (primitive.material) {
346
+ outputAttributes = attributesMap.get(primitive.material.id);
347
+ materialUvRegion = outputAttributes?.mergedMaterials.find(({ originalMaterialId }) => originalMaterialId === primitive.material?.id)?.uvRegion;
348
+ }
349
+ else if (attributesMap.has('default')) {
350
+ outputAttributes = attributesMap.get('default');
351
+ }
352
+ assert(outputAttributes !== null, 'Primitive - material mapping failed');
353
+ // Per the spec https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html#_mesh_primitive_mode
354
+ // GL.TRIANGLES is default. So in case `mode` is `undefined`, it is 'TRIANGLES'
355
+ assert(primitive.mode === undefined ||
356
+ primitive.mode === GL.TRIANGLES ||
357
+ primitive.mode === GL.TRIANGLE_STRIP, `Primitive - unsupported mode ${primitive.mode}`);
358
+ const attributes = primitive.attributes;
359
+ if (!outputAttributes) {
360
+ continue; // eslint-disable-line no-continue
361
+ }
362
+ const indices = normalizeIndices(primitive);
363
+ outputAttributes.positions = concatenateTypedArrays(outputAttributes.positions, transformVertexArray({
364
+ vertices: attributes.POSITION.value,
365
+ cartographicOrigin,
366
+ cartesianModelMatrix,
367
+ nodeMatrix: matrix,
368
+ indices,
369
+ attributeSpecificTransformation: transformVertexPositions,
370
+ useCartesianPositions
371
+ }));
372
+ outputAttributes.normals = concatenateTypedArrays(outputAttributes.normals, transformVertexArray({
373
+ vertices: attributes.NORMAL && attributes.NORMAL.value,
374
+ cartographicOrigin,
375
+ cartesianModelMatrix,
376
+ nodeMatrix: matrix,
377
+ indices,
378
+ attributeSpecificTransformation: transformVertexNormals,
379
+ useCartesianPositions: false
380
+ }));
381
+ outputAttributes.texCoords = concatenateTypedArrays(outputAttributes.texCoords, flattenTexCoords(attributes.TEXCOORD_0 && attributes.TEXCOORD_0.value, indices));
382
+ outputAttributes.colors = concatenateTypedArrays(outputAttributes.colors, flattenColors(attributes.COLOR_0, indices));
383
+ if (materialUvRegion) {
384
+ outputAttributes.uvRegions = concatenateTypedArrays(outputAttributes.uvRegions, createUvRegion(materialUvRegion, indices));
385
+ }
386
+ outputAttributes.featureIndicesGroups = outputAttributes.featureIndicesGroups || [];
387
+ outputAttributes.featureIndicesGroups.push(flattenBatchIds(getBatchIds(attributes, primitive, images, featureTexture), indices));
388
+ }
389
+ }
390
+ /**
391
+ * Converts TRIANGLE-STRIPS to independent TRIANGLES
392
+ * @param primitive - the primitive to get the indices from
393
+ * @returns indices of vertices of the independent triangles
394
+ */
281
395
  function normalizeIndices(primitive) {
282
- var _primitive$indices;
283
- let indices = (_primitive$indices = primitive.indices) === null || _primitive$indices === void 0 ? void 0 : _primitive$indices.value;
284
- if (!indices) {
285
- const positions = primitive.attributes.POSITION.value;
286
- return generateSyntheticIndices(positions.length / VALUES_PER_VERTEX);
287
- }
288
- if (indices && primitive.mode === GL.TRIANGLE_STRIP) {
289
- const TypedArrayConstructor = indices.constructor;
290
- const newIndices = new TypedArrayConstructor((indices.length - 2) * 3);
291
- let triangleIndex = 0;
292
- let currentTriangle = indices.slice(0, 3);
293
- newIndices.set(currentTriangle, 0);
294
- for (let i = 1; i + 2 < indices.length; i++) {
295
- triangleIndex += 3;
296
- currentTriangle = indices.slice(i, i + 3);
297
- if (i % 2 === 0) {
298
- newIndices.set(currentTriangle, triangleIndex);
299
- } else {
300
- newIndices.set(currentTriangle.reverse(), triangleIndex);
301
- }
302
- }
303
- indices = newIndices;
304
- }
305
- return indices;
396
+ let indices = primitive.indices?.value;
397
+ if (!indices) {
398
+ const positions = primitive.attributes.POSITION.value;
399
+ return generateSyntheticIndices(positions.length / VALUES_PER_VERTEX);
400
+ }
401
+ if (indices && primitive.mode === GL.TRIANGLE_STRIP) {
402
+ /*
403
+ TRIANGLE_STRIP geometry contains n+2 vertices for n triangles;
404
+ TRIANGLE geometry contains n*3 vertices for n triangles.
405
+ The conversion from TRIANGLE_STRIP to TRIANGLE implies duplicating adjacent vertices.
406
+ */
407
+ const TypedArrayConstructor = indices.constructor;
408
+ const newIndices = new TypedArrayConstructor((indices.length - 2) * 3);
409
+ // Copy the first triangle indices with no modification like [i0, i1, i2, ...] -> [i0, i1, i2, ...]
410
+ let triangleIndex = 0;
411
+ let currentTriangle = indices.slice(0, 3);
412
+ newIndices.set(currentTriangle, 0);
413
+ // The rest triangle indices are being taken from strips using the following logic:
414
+ // [i1, i2, i3, i4, i5, i6, ...] -> [i3, i2, i1, i2, i3, i4, i5, i4, i3, i4, i5, i6, ...]
415
+ for (let i = 1; i + 2 < indices.length; i++) {
416
+ triangleIndex += 3;
417
+ currentTriangle = indices.slice(i, i + 3);
418
+ if (i % 2 === 0) {
419
+ newIndices.set(currentTriangle, triangleIndex);
420
+ }
421
+ else {
422
+ // The following "reverce" is necessary to calculate normals correctly
423
+ newIndices.set(currentTriangle.reverse(), triangleIndex);
424
+ }
425
+ }
426
+ indices = newIndices;
427
+ }
428
+ return indices;
306
429
  }
430
+ /**
431
+ * Convert vertices attributes (POSITIONS or NORMALS) to i3s compatible format
432
+ * @param args
433
+ * @param args.vertices - gltf primitive POSITION or NORMAL attribute
434
+ * @param args.cartographicOrigin - cartographic origin coordinates
435
+ * @param args.cartesianModelMatrix - a cartesian model matrix to transform coordnates from cartesian to cartographic format
436
+ * @param args.nodeMatrix - a gltf node transformation matrix - cumulative transformation matrix formed from all parent node matrices
437
+ * @param args.indices - gltf primitive indices
438
+ * @param args.attributeSpecificTransformation - function to do attribute - specific transformations
439
+ * @param args.useCartesianPositions - use coordinates as it is.
440
+ * @returns
441
+ */
307
442
  function transformVertexArray(args) {
308
- const {
309
- vertices,
310
- indices,
311
- attributeSpecificTransformation
312
- } = args;
313
- const newVertices = new Float32Array(indices.length * VALUES_PER_VERTEX);
314
- if (!vertices) {
443
+ const { vertices, indices, attributeSpecificTransformation } = args;
444
+ const newVertices = new Float32Array(indices.length * VALUES_PER_VERTEX);
445
+ if (!vertices) {
446
+ return newVertices;
447
+ }
448
+ for (let i = 0; i < indices.length; i++) {
449
+ const coordIndex = indices[i] * VALUES_PER_VERTEX;
450
+ const vertex = vertices.subarray(coordIndex, coordIndex + VALUES_PER_VERTEX);
451
+ let vertexVector = new Vector3(Array.from(vertex));
452
+ vertexVector = attributeSpecificTransformation(vertexVector, args);
453
+ newVertices[i * VALUES_PER_VERTEX] = vertexVector.x;
454
+ newVertices[i * VALUES_PER_VERTEX + 1] = vertexVector.y;
455
+ newVertices[i * VALUES_PER_VERTEX + 2] = vertexVector.z;
456
+ }
315
457
  return newVertices;
316
- }
317
- for (let i = 0; i < indices.length; i++) {
318
- const coordIndex = indices[i] * VALUES_PER_VERTEX;
319
- const vertex = vertices.subarray(coordIndex, coordIndex + VALUES_PER_VERTEX);
320
- let vertexVector = new Vector3(Array.from(vertex));
321
- vertexVector = attributeSpecificTransformation(vertexVector, args);
322
- newVertices[i * VALUES_PER_VERTEX] = vertexVector.x;
323
- newVertices[i * VALUES_PER_VERTEX + 1] = vertexVector.y;
324
- newVertices[i * VALUES_PER_VERTEX + 2] = vertexVector.z;
325
- }
326
- return newVertices;
327
458
  }
459
+ /**
460
+ * Trasform positions vector with the attribute specific transformations
461
+ * @param vertexVector - source positions vector to transform
462
+ * @param calleeArgs
463
+ * @param calleeArgs.cartesianModelMatrix - a cartesian model matrix to transform coordnates from cartesian to cartographic format
464
+ * @param calleeArgs.cartographicOrigin - cartographic origin coordinates
465
+ * @param calleeArgs.nodeMatrix - a gltf node transformation matrix - cumulative transformation matrix formed from all parent node matrices
466
+ * @param calleeArgs.useCartesianPositions - use coordinates as it is.
467
+ * @returns transformed positions vector
468
+ */
328
469
  function transformVertexPositions(vertexVector, calleeArgs) {
329
- const {
330
- cartesianModelMatrix,
331
- cartographicOrigin,
332
- nodeMatrix,
333
- useCartesianPositions
334
- } = calleeArgs;
335
- if (nodeMatrix) {
336
- vertexVector = vertexVector.transform(nodeMatrix);
337
- }
338
- vertexVector = vertexVector.transform(cartesianModelMatrix);
339
- if (useCartesianPositions) {
470
+ const { cartesianModelMatrix, cartographicOrigin, nodeMatrix, useCartesianPositions } = calleeArgs;
471
+ if (nodeMatrix) {
472
+ vertexVector = vertexVector.transform(nodeMatrix);
473
+ }
474
+ vertexVector = vertexVector.transform(cartesianModelMatrix);
475
+ if (useCartesianPositions) {
476
+ return vertexVector;
477
+ }
478
+ Ellipsoid.WGS84.cartesianToCartographic([vertexVector[0], vertexVector[1], vertexVector[2]], vertexVector);
479
+ vertexVector = vertexVector.subtract(cartographicOrigin);
340
480
  return vertexVector;
341
- }
342
- Ellipsoid.WGS84.cartesianToCartographic([vertexVector[0], vertexVector[1], vertexVector[2]], vertexVector);
343
- vertexVector = vertexVector.subtract(cartographicOrigin);
344
- return vertexVector;
345
481
  }
482
+ /**
483
+ * Trasform normals vector with the attribute specific transformations
484
+ * @param vertexVector - source normals vector to transform
485
+ * @param calleeArgs
486
+ * @param calleeArgs.cartesianModelMatrix - a cartesian model matrix to transform coordnates from cartesian to cartographic format
487
+ * @param calleeArgs.nodeMatrix - a gltf node transformation matrix - cumulative transformation matrix formed from all parent node matrices
488
+ * @returns transformed normals vector
489
+ */
346
490
  function transformVertexNormals(vertexVector, calleeArgs) {
347
- const {
348
- cartesianModelMatrix,
349
- nodeMatrix
350
- } = calleeArgs;
351
- if (nodeMatrix) {
352
- vertexVector = vertexVector.transformAsVector(nodeMatrix);
353
- }
354
- vertexVector = vertexVector.transformAsVector(cartesianModelMatrix);
355
- return vertexVector;
491
+ const { cartesianModelMatrix, nodeMatrix } = calleeArgs;
492
+ if (nodeMatrix) {
493
+ vertexVector = vertexVector.transformAsVector(nodeMatrix);
494
+ }
495
+ vertexVector = vertexVector.transformAsVector(cartesianModelMatrix);
496
+ return vertexVector;
356
497
  }
498
+ /**
499
+ * Convert uv0 (texture coordinates) from coords based on indices to plain arrays, compatible with i3s
500
+ * @param texCoords - gltf primitive TEXCOORD_0 attribute
501
+ * @param indices - gltf primitive indices
502
+ * @returns flattened texture coordinates
503
+ */
357
504
  function flattenTexCoords(texCoords, indices) {
358
- const newTexCoords = new Float32Array(indices.length * VALUES_PER_TEX_COORD);
359
- if (!texCoords) {
360
- newTexCoords.fill(1);
505
+ const newTexCoords = new Float32Array(indices.length * VALUES_PER_TEX_COORD);
506
+ if (!texCoords) {
507
+ // We need dummy UV0s because it is required in 1.6
508
+ // https://github.com/Esri/i3s-spec/blob/master/docs/1.6/vertexAttribute.cmn.md
509
+ newTexCoords.fill(1);
510
+ return newTexCoords;
511
+ }
512
+ for (let i = 0; i < indices.length; i++) {
513
+ const coordIndex = indices[i] * VALUES_PER_TEX_COORD;
514
+ const texCoord = texCoords.subarray(coordIndex, coordIndex + VALUES_PER_TEX_COORD);
515
+ newTexCoords[i * VALUES_PER_TEX_COORD] = texCoord[0];
516
+ newTexCoords[i * VALUES_PER_TEX_COORD + 1] = texCoord[1];
517
+ }
361
518
  return newTexCoords;
362
- }
363
- for (let i = 0; i < indices.length; i++) {
364
- const coordIndex = indices[i] * VALUES_PER_TEX_COORD;
365
- const texCoord = texCoords.subarray(coordIndex, coordIndex + VALUES_PER_TEX_COORD);
366
- newTexCoords[i * VALUES_PER_TEX_COORD] = texCoord[0];
367
- newTexCoords[i * VALUES_PER_TEX_COORD + 1] = texCoord[1];
368
- }
369
- return newTexCoords;
370
519
  }
520
+ /**
521
+ * Convert color from COLOR_0 based on indices to plain arrays, compatible with i3s
522
+ * @param colorsAttribute - gltf primitive COLOR_0 attribute
523
+ * @param indices - gltf primitive indices
524
+ * @returns flattened colors attribute
525
+ */
371
526
  function flattenColors(colorsAttribute, indices) {
372
- const components = (colorsAttribute === null || colorsAttribute === void 0 ? void 0 : colorsAttribute.components) || VALUES_PER_COLOR_ELEMENT;
373
- const newColors = new Uint8Array(indices.length * components);
374
- if (!colorsAttribute) {
375
- newColors.fill(255);
527
+ const components = colorsAttribute?.components || VALUES_PER_COLOR_ELEMENT;
528
+ const newColors = new Uint8Array(indices.length * components);
529
+ if (!colorsAttribute) {
530
+ // Vertex color multiplies by material color so it must be normalized 1 by default
531
+ newColors.fill(255);
532
+ return newColors;
533
+ }
534
+ const colors = colorsAttribute.value;
535
+ for (let i = 0; i < indices.length; i++) {
536
+ const colorIndex = indices[i] * components;
537
+ const color = colors.subarray(colorIndex, colorIndex + components);
538
+ const colorUint8 = new Uint8Array(components);
539
+ for (let j = 0; j < color.length; j++) {
540
+ colorUint8[j] = color[j] * 255;
541
+ }
542
+ newColors.set(colorUint8, i * components);
543
+ }
376
544
  return newColors;
377
- }
378
- const colors = colorsAttribute.value;
379
- for (let i = 0; i < indices.length; i++) {
380
- const colorIndex = indices[i] * components;
381
- const color = colors.subarray(colorIndex, colorIndex + components);
382
- const colorUint8 = new Uint8Array(components);
383
- for (let j = 0; j < color.length; j++) {
384
- colorUint8[j] = color[j] * 255;
385
- }
386
- newColors.set(colorUint8, i * components);
387
- }
388
- return newColors;
389
545
  }
546
+ /**
547
+ * Create per-vertex uv-region array
548
+ * @param materialUvRegion - uv-region fragment for a single vertex
549
+ * @param indices - geometry indices data
550
+ * @returns - uv-region array
551
+ */
390
552
  function createUvRegion(materialUvRegion, indices) {
391
- const result = new Uint16Array(indices.length * 4);
392
- for (let i = 0; i < result.length; i += 4) {
393
- result.set(materialUvRegion, i);
394
- }
395
- return result;
553
+ const result = new Uint16Array(indices.length * 4);
554
+ for (let i = 0; i < result.length; i += 4) {
555
+ result.set(materialUvRegion, i);
556
+ }
557
+ return result;
396
558
  }
559
+ /**
560
+ * Flatten batchedIds list based on indices to right ordered array, compatible with i3s
561
+ * @param batchedIds - gltf primitive
562
+ * @param indices - gltf primitive indices
563
+ * @returns flattened batch ids
564
+ */
397
565
  function flattenBatchIds(batchedIds, indices) {
398
- if (!batchedIds.length || !indices.length) {
399
- return [];
400
- }
401
- const newBatchIds = [];
402
- for (let i = 0; i < indices.length; i++) {
403
- const coordIndex = indices[i];
404
- newBatchIds.push(batchedIds[coordIndex]);
405
- }
406
- return newBatchIds;
566
+ if (!batchedIds.length || !indices.length) {
567
+ return [];
568
+ }
569
+ const newBatchIds = [];
570
+ for (let i = 0; i < indices.length; i++) {
571
+ const coordIndex = indices[i];
572
+ newBatchIds.push(batchedIds[coordIndex]);
573
+ }
574
+ return newBatchIds;
407
575
  }
576
+ /**
577
+ * Get batchIds for featureIds creation
578
+ * @param attributes - gltf accessors
579
+ * @param primitive - gltf primitive data
580
+ * @param images - gltf texture images
581
+ * @param featureTexture - feature texture key
582
+ * @return batch IDs
583
+ */
408
584
  function getBatchIds(attributes, primitive, images, featureTexture) {
409
- const batchIds = handleBatchIdsExtensions(attributes, primitive, images, featureTexture);
410
- if (batchIds.length) {
411
- return batchIds;
412
- }
413
- for (let index = 0; index < BATCHED_ID_POSSIBLE_ATTRIBUTE_NAMES.length; index++) {
414
- const possibleBatchIdAttributeName = BATCHED_ID_POSSIBLE_ATTRIBUTE_NAMES[index];
415
- if (attributes[possibleBatchIdAttributeName] && attributes[possibleBatchIdAttributeName].value) {
416
- return attributes[possibleBatchIdAttributeName].value;
417
- }
418
- }
419
- return [];
420
- }
421
- async function convertMaterials() {
422
- let sourceMaterials = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : [];
423
- let shouldMergeMaterials = arguments.length > 1 ? arguments[1] : undefined;
424
- let materials = [];
425
- for (const sourceMaterial of sourceMaterials) {
426
- materials.push(convertMaterial(sourceMaterial));
427
- }
428
- if (shouldMergeMaterials) {
429
- materials = await mergeAllMaterials(materials);
430
- }
431
- return materials;
585
+ const batchIds = handleBatchIdsExtensions(attributes, primitive, images, featureTexture);
586
+ if (batchIds.length) {
587
+ return batchIds;
588
+ }
589
+ for (let index = 0; index < BATCHED_ID_POSSIBLE_ATTRIBUTE_NAMES.length; index++) {
590
+ const possibleBatchIdAttributeName = BATCHED_ID_POSSIBLE_ATTRIBUTE_NAMES[index];
591
+ if (attributes[possibleBatchIdAttributeName] &&
592
+ attributes[possibleBatchIdAttributeName].value) {
593
+ return attributes[possibleBatchIdAttributeName].value;
594
+ }
595
+ }
596
+ return [];
597
+ }
598
+ /**
599
+ * Convert GLTF material to I3S material definitions and textures
600
+ * @param sourceMaterials Source GLTF materials
601
+ * @param shouldMergeMaterials - if true - the converter will try to merge similar materials
602
+ * to be able to merge primitives having those materials
603
+ * @returns Array of Couples I3SMaterialDefinition + texture content
604
+ */
605
+ async function convertMaterials(sourceMaterials = [], shouldMergeMaterials) {
606
+ let materials = [];
607
+ for (const sourceMaterial of sourceMaterials) {
608
+ materials.push(convertMaterial(sourceMaterial));
609
+ }
610
+ if (shouldMergeMaterials) {
611
+ materials = await mergeAllMaterials(materials);
612
+ }
613
+ return materials;
432
614
  }
615
+ /**
616
+ * Merge materials when possible
617
+ * @param materials materials array
618
+ * @returns merged materials array
619
+ */
620
+ // eslint-disable-next-line max-statements, complexity
433
621
  async function mergeAllMaterials(materials) {
434
- const result = [];
435
- while (materials.length > 0) {
436
- let newMaterial = materials.splice(0, 1)[0];
437
- const mergedIndices = [];
438
- for (let i = 0; i < materials.length; i++) {
439
- const material = materials[i];
440
- if (newMaterial.texture && material.texture || !newMaterial.texture && !material.texture) {
441
- newMaterial = await mergeMaterials(newMaterial, material);
442
- mergedIndices.push(i);
443
- }
444
- }
445
- if (newMaterial.texture && mergedIndices.length) {
446
- var _newMaterial$mergedMa, _newMaterial$mergedMa2;
447
- const newWidth = (_newMaterial$mergedMa = newMaterial.mergedMaterials) === null || _newMaterial$mergedMa === void 0 ? void 0 : _newMaterial$mergedMa.reduce((accum, _ref3) => {
448
- let {
449
- textureSize
450
- } = _ref3;
451
- return accum + ((textureSize === null || textureSize === void 0 ? void 0 : textureSize.width) || 0);
452
- }, 0);
453
- const newHeight = (_newMaterial$mergedMa2 = newMaterial.mergedMaterials) === null || _newMaterial$mergedMa2 === void 0 ? void 0 : _newMaterial$mergedMa2.reduce((accum, _ref4) => {
454
- let {
455
- textureSize
456
- } = _ref4;
457
- return Math.max(accum, (textureSize === null || textureSize === void 0 ? void 0 : textureSize.height) || 0);
458
- }, 0);
459
- let currentX = -1;
460
- for (const aTextureMetadata of newMaterial.mergedMaterials) {
461
- if (aTextureMetadata.textureSize) {
462
- const newX = currentX + 1 + aTextureMetadata.textureSize.width / newWidth * 2 ** (Uint16Array.BYTES_PER_ELEMENT * 8) - 1;
463
- aTextureMetadata.uvRegion = new Uint16Array([currentX + 1, 0, newX, aTextureMetadata.textureSize.height / newHeight * 2 ** (Uint16Array.BYTES_PER_ELEMENT * 8) - 1]);
464
- currentX = newX;
465
- }
466
- }
467
- newMaterial.texture.image.width = newWidth;
468
- newMaterial.texture.image.height = newHeight;
469
- }
470
- for (const index of mergedIndices.reverse()) {
471
- materials.splice(index, 1);
472
- }
473
- result.push(newMaterial);
474
- }
475
- if (!result.length) {
476
- result.push({
477
- material: getDefaultMaterial(),
478
- mergedMaterials: [{
479
- originalMaterialId: 'default'
480
- }]
481
- });
482
- }
483
- return result;
622
+ const result = [];
623
+ while (materials.length > 0) {
624
+ let newMaterial = materials.splice(0, 1)[0];
625
+ const mergedIndices = [];
626
+ for (let i = 0; i < materials.length; i++) {
627
+ const material = materials[i];
628
+ if ((newMaterial.texture && material.texture) ||
629
+ (!newMaterial.texture && !material.texture)) {
630
+ newMaterial = await mergeMaterials(newMaterial, material);
631
+ mergedIndices.push(i);
632
+ }
633
+ }
634
+ if (newMaterial.texture && mergedIndices.length) {
635
+ const newWidth = newMaterial.mergedMaterials?.reduce((accum, { textureSize }) => accum + (textureSize?.width || 0), 0);
636
+ const newHeight = newMaterial.mergedMaterials?.reduce((accum, { textureSize }) => Math.max(accum, textureSize?.height || 0), 0);
637
+ let currentX = -1;
638
+ for (const aTextureMetadata of newMaterial.mergedMaterials) {
639
+ if (aTextureMetadata.textureSize) {
640
+ const newX = currentX +
641
+ 1 +
642
+ (aTextureMetadata.textureSize.width / newWidth) *
643
+ 2 ** (Uint16Array.BYTES_PER_ELEMENT * 8) -
644
+ 1;
645
+ aTextureMetadata.uvRegion = new Uint16Array([
646
+ currentX + 1,
647
+ 0,
648
+ newX,
649
+ (aTextureMetadata.textureSize.height / newHeight) *
650
+ 2 ** (Uint16Array.BYTES_PER_ELEMENT * 8) -
651
+ 1
652
+ ]);
653
+ currentX = newX;
654
+ }
655
+ }
656
+ newMaterial.texture.image.width = newWidth;
657
+ newMaterial.texture.image.height = newHeight;
658
+ }
659
+ for (const index of mergedIndices.reverse()) {
660
+ materials.splice(index, 1);
661
+ }
662
+ result.push(newMaterial);
663
+ }
664
+ if (!result.length) {
665
+ result.push({
666
+ material: getDefaultMaterial(),
667
+ mergedMaterials: [{ originalMaterialId: 'default' }]
668
+ });
669
+ }
670
+ return result;
484
671
  }
672
+ /**
673
+ * Merge 2 materials including texture
674
+ * @param material1
675
+ * @param material2
676
+ * @returns
677
+ */
485
678
  async function mergeMaterials(material1, material2) {
486
- var _material1$texture, _material2$texture;
487
- if ((_material1$texture = material1.texture) !== null && _material1$texture !== void 0 && _material1$texture.bufferView && (_material2$texture = material2.texture) !== null && _material2$texture !== void 0 && _material2$texture.bufferView && material1.mergedMaterials && material2.mergedMaterials) {
488
- const buffer1 = Buffer.from(material1.texture.bufferView.data);
489
- const buffer2 = Buffer.from(material2.texture.bufferView.data);
490
- try {
491
- const {
492
- joinImages
493
- } = await import('join-images');
494
- const sharpData = await joinImages([buffer1, buffer2], {
495
- direction: 'horizontal'
496
- });
497
- material1.texture.bufferView.data = await sharpData.toFormat(material1.texture.mimeType === 'image/png' ? 'png' : 'jpeg').toBuffer();
498
- } catch (error) {
499
- console.log('Join images into a texture atlas has failed. Consider usage `--split-nodes` option. (See documentation https://loaders.gl/modules/tile-converter/docs/cli-reference/tile-converter)');
500
- throw error;
501
- }
502
- material1.material.pbrMetallicRoughness.baseColorTexture.textureSetDefinitionId = 1;
503
- }
504
- material1.mergedMaterials = material1.mergedMaterials.concat(material2.mergedMaterials);
505
- return material1;
679
+ if (material1.texture?.bufferView &&
680
+ material2.texture?.bufferView &&
681
+ material1.mergedMaterials &&
682
+ material2.mergedMaterials) {
683
+ const buffer1 = Buffer.from(material1.texture.bufferView.data);
684
+ const buffer2 = Buffer.from(material2.texture.bufferView.data);
685
+ try {
686
+ // @ts-ignore
687
+ const { joinImages } = await import('join-images');
688
+ const sharpData = await joinImages([buffer1, buffer2], { direction: 'horizontal' });
689
+ material1.texture.bufferView.data = await sharpData
690
+ .toFormat(material1.texture.mimeType === 'image/png' ? 'png' : 'jpeg')
691
+ .toBuffer();
692
+ }
693
+ catch (error) {
694
+ // eslint-disable-next-line no-console
695
+ console.log('Join images into a texture atlas has failed. Consider usage `--split-nodes` option. (See documentation https://loaders.gl/modules/tile-converter/docs/cli-reference/tile-converter)');
696
+ throw error;
697
+ }
698
+ // @ts-ignore
699
+ material1.material.pbrMetallicRoughness.baseColorTexture.textureSetDefinitionId = 1;
700
+ }
701
+ material1.mergedMaterials = material1.mergedMaterials.concat(material2.mergedMaterials);
702
+ return material1;
506
703
  }
704
+ /**
705
+ * Convert texture and material from gltf 2.0 material object
706
+ * @param sourceMaterial - material object
707
+ * @returns I3S material definition and texture
708
+ */
507
709
  function convertMaterial(sourceMaterial) {
508
- var _sourceMaterial$emiss, _sourceMaterial$pbrMe, _sourceMaterial$pbrMe2, _sourceMaterial$pbrMe3;
509
- const material = {
510
- doubleSided: sourceMaterial.doubleSided,
511
- emissiveFactor: (_sourceMaterial$emiss = sourceMaterial.emissiveFactor) === null || _sourceMaterial$emiss === void 0 ? void 0 : _sourceMaterial$emiss.map(c => Math.round(c * 255)),
512
- alphaMode: convertAlphaMode(sourceMaterial.alphaMode),
513
- pbrMetallicRoughness: {
514
- roughnessFactor: (sourceMaterial === null || sourceMaterial === void 0 ? void 0 : (_sourceMaterial$pbrMe = sourceMaterial.pbrMetallicRoughness) === null || _sourceMaterial$pbrMe === void 0 ? void 0 : _sourceMaterial$pbrMe.roughnessFactor) || DEFAULT_ROUGHNESS_FACTOR,
515
- metallicFactor: (sourceMaterial === null || sourceMaterial === void 0 ? void 0 : (_sourceMaterial$pbrMe2 = sourceMaterial.pbrMetallicRoughness) === null || _sourceMaterial$pbrMe2 === void 0 ? void 0 : _sourceMaterial$pbrMe2.metallicFactor) || DEFAULT_METALLIC_FACTOR
516
- }
517
- };
518
- let texture;
519
- if (sourceMaterial !== null && sourceMaterial !== void 0 && (_sourceMaterial$pbrMe3 = sourceMaterial.pbrMetallicRoughness) !== null && _sourceMaterial$pbrMe3 !== void 0 && _sourceMaterial$pbrMe3.baseColorTexture) {
520
- texture = sourceMaterial.pbrMetallicRoughness.baseColorTexture.texture.source;
521
- material.pbrMetallicRoughness.baseColorTexture = {
522
- textureSetDefinitionId: 0
523
- };
524
- } else if (sourceMaterial.emissiveTexture) {
525
- texture = sourceMaterial.emissiveTexture.texture.source;
526
- material.pbrMetallicRoughness.baseColorTexture = {
527
- textureSetDefinitionId: 0
528
- };
529
- }
530
- sourceMaterial.id = Number.isFinite(sourceMaterial.id) ? sourceMaterial.id : uuidv4();
531
- let mergedMaterials = [{
532
- originalMaterialId: sourceMaterial.id
533
- }];
534
- if (!texture) {
535
- var _sourceMaterial$pbrMe4;
536
- const baseColorFactor = sourceMaterial === null || sourceMaterial === void 0 ? void 0 : (_sourceMaterial$pbrMe4 = sourceMaterial.pbrMetallicRoughness) === null || _sourceMaterial$pbrMe4 === void 0 ? void 0 : _sourceMaterial$pbrMe4.baseColorFactor;
537
- material.pbrMetallicRoughness.baseColorFactor = baseColorFactor && baseColorFactor.map(c => Math.round(c * 255)) || undefined;
538
- } else {
539
- mergedMaterials[0].textureSize = {
540
- width: texture.image.width,
541
- height: texture.image.height
710
+ const material = {
711
+ doubleSided: sourceMaterial.doubleSided,
712
+ emissiveFactor: sourceMaterial.emissiveFactor?.map((c) => Math.round(c * 255)),
713
+ // It is in upper case in GLTF: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#alpha-coverage
714
+ // But it is in lower case in I3S: https://github.com/Esri/i3s-spec/blob/master/docs/1.7/materialDefinitions.cmn.md
715
+ alphaMode: convertAlphaMode(sourceMaterial.alphaMode),
716
+ pbrMetallicRoughness: {
717
+ roughnessFactor: sourceMaterial?.pbrMetallicRoughness?.roughnessFactor || DEFAULT_ROUGHNESS_FACTOR,
718
+ metallicFactor: sourceMaterial?.pbrMetallicRoughness?.metallicFactor || DEFAULT_METALLIC_FACTOR
719
+ }
542
720
  };
543
- }
544
- return {
545
- material,
546
- texture,
547
- mergedMaterials
548
- };
721
+ let texture;
722
+ if (sourceMaterial?.pbrMetallicRoughness?.baseColorTexture) {
723
+ texture = sourceMaterial.pbrMetallicRoughness.baseColorTexture.texture.source;
724
+ material.pbrMetallicRoughness.baseColorTexture = {
725
+ textureSetDefinitionId: 0
726
+ };
727
+ }
728
+ else if (sourceMaterial.emissiveTexture) {
729
+ texture = sourceMaterial.emissiveTexture.texture.source;
730
+ // ArcGIS webscene doesn't show emissiveTexture but shows baseColorTexture
731
+ material.pbrMetallicRoughness.baseColorTexture = {
732
+ textureSetDefinitionId: 0
733
+ };
734
+ }
735
+ sourceMaterial.id = Number.isFinite(sourceMaterial.id) ? sourceMaterial.id : uuidv4();
736
+ const mergedMaterials = [{ originalMaterialId: sourceMaterial.id }];
737
+ if (!texture) {
738
+ // Should use default baseColorFactor if it is not present in source material
739
+ // https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#reference-pbrmetallicroughness
740
+ const baseColorFactor = sourceMaterial?.pbrMetallicRoughness?.baseColorFactor;
741
+ material.pbrMetallicRoughness.baseColorFactor =
742
+ (baseColorFactor && baseColorFactor.map((c) => Math.round(c * 255))) || undefined;
743
+ }
744
+ else {
745
+ mergedMaterials[0].textureSize = { width: texture.image.width, height: texture.image.height };
746
+ }
747
+ return { material, texture, mergedMaterials };
549
748
  }
749
+ /**
750
+ * Converts from `alphaMode` material property from GLTF to I3S format
751
+ * @param gltfAlphaMode glTF material `alphaMode` property
752
+ * @returns I3SMaterialDefinition.alphaMode property
753
+ */
550
754
  function convertAlphaMode(gltfAlphaMode) {
551
- switch (gltfAlphaMode) {
552
- case 'OPAQUE':
553
- return 'opaque';
554
- case 'MASK':
555
- return 'mask';
556
- case 'BLEND':
557
- return 'blend';
558
- default:
559
- return 'opaque';
560
- }
755
+ switch (gltfAlphaMode) {
756
+ case 'OPAQUE':
757
+ return 'opaque';
758
+ case 'MASK':
759
+ return 'mask';
760
+ case 'BLEND':
761
+ return 'blend';
762
+ default:
763
+ return 'opaque';
764
+ }
561
765
  }
766
+ /**
767
+ * Form default I3SMaterialDefinition
768
+ * @returns I3S material definition
769
+ */
562
770
  function getDefaultMaterial() {
563
- return {
564
- alphaMode: 'opaque',
565
- pbrMetallicRoughness: {
566
- metallicFactor: 1,
567
- roughnessFactor: 1
568
- }
569
- };
771
+ return {
772
+ alphaMode: 'opaque',
773
+ pbrMetallicRoughness: {
774
+ metallicFactor: 1,
775
+ roughnessFactor: 1
776
+ }
777
+ };
570
778
  }
779
+ /**
780
+ * Form "sharedResources" from gltf materials array
781
+ * @param gltfMaterials - GLTF materials array
782
+ * @param nodeId - I3S node ID
783
+ * @returns {materialDefinitionInfos: Object[], textureDefinitionInfos: Object[]} -
784
+ * 2 arrays in format of i3s sharedResources data https://github.com/Esri/i3s-spec/blob/master/docs/1.7/sharedResource.cmn.md
785
+ */
571
786
  function getSharedResources(gltfMaterials, nodeId) {
572
- const i3sResources = {};
573
- if (!gltfMaterials || !gltfMaterials.length) {
787
+ const i3sResources = {};
788
+ if (!gltfMaterials || !gltfMaterials.length) {
789
+ return i3sResources;
790
+ }
791
+ i3sResources.materialDefinitionInfos = [];
792
+ for (const gltfMaterial of gltfMaterials) {
793
+ const { materialDefinitionInfo, textureDefinitionInfo } = convertGLTFMaterialToI3sSharedResources(gltfMaterial, nodeId);
794
+ i3sResources.materialDefinitionInfos.push(materialDefinitionInfo);
795
+ if (textureDefinitionInfo) {
796
+ i3sResources.textureDefinitionInfos = i3sResources.textureDefinitionInfos || [];
797
+ i3sResources.textureDefinitionInfos.push(textureDefinitionInfo);
798
+ }
799
+ }
574
800
  return i3sResources;
575
- }
576
- i3sResources.materialDefinitionInfos = [];
577
- for (const gltfMaterial of gltfMaterials) {
578
- const {
579
- materialDefinitionInfo,
580
- textureDefinitionInfo
581
- } = convertGLTFMaterialToI3sSharedResources(gltfMaterial, nodeId);
582
- i3sResources.materialDefinitionInfos.push(materialDefinitionInfo);
583
- if (textureDefinitionInfo) {
584
- i3sResources.textureDefinitionInfos = i3sResources.textureDefinitionInfos || [];
585
- i3sResources.textureDefinitionInfos.push(textureDefinitionInfo);
586
- }
587
- }
588
- return i3sResources;
589
801
  }
802
+ /**
803
+ * Convert gltf material into I3S sharedResources data
804
+ * @param gltfMaterial - gltf material data
805
+ * @param nodeId - I3S node ID
806
+ * @returns - Couple {materialDefinitionInfo, textureDefinitionInfo} extracted from gltf material data
807
+ */
590
808
  function convertGLTFMaterialToI3sSharedResources(gltfMaterial, nodeId) {
591
- var _gltfMaterial$pbrMeta;
592
- const texture = (gltfMaterial === null || gltfMaterial === void 0 ? void 0 : (_gltfMaterial$pbrMeta = gltfMaterial.pbrMetallicRoughness) === null || _gltfMaterial$pbrMeta === void 0 ? void 0 : _gltfMaterial$pbrMeta.baseColorTexture) || gltfMaterial.emissiveTexture;
593
- let textureDefinitionInfo = null;
594
- if (texture) {
595
- textureDefinitionInfo = extractSharedResourcesTextureInfo(texture.texture, nodeId);
596
- }
597
- const {
598
- baseColorFactor,
599
- metallicFactor
600
- } = (gltfMaterial === null || gltfMaterial === void 0 ? void 0 : gltfMaterial.pbrMetallicRoughness) || {};
601
- let colorFactor = baseColorFactor;
602
- if ((!baseColorFactor || baseColorFactor[3] === 0) && gltfMaterial.emissiveFactor) {
603
- colorFactor = gltfMaterial.emissiveFactor;
604
- colorFactor[3] = colorFactor[3] || 1;
605
- }
606
- return {
607
- materialDefinitionInfo: extractSharedResourcesMaterialInfo(colorFactor || [1, 1, 1, 1], metallicFactor),
608
- textureDefinitionInfo
609
- };
610
- }
611
- function extractSharedResourcesMaterialInfo(baseColorFactor) {
612
- let metallicFactor = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 1;
613
- const matDielectricColorComponent = 0.04 / 255;
614
- const black = new Vector4(0, 0, 0, 1);
615
- const unitVector = new Vector4(1, 1, 1, 1);
616
- const dielectricSpecular = new Vector4(matDielectricColorComponent, matDielectricColorComponent, matDielectricColorComponent, 0);
617
- const baseColorVector = new Vector4(baseColorFactor);
618
- const firstOperand = unitVector.subtract(dielectricSpecular).multiply(baseColorVector);
619
- const diffuse = firstOperand.lerp(firstOperand, black, metallicFactor);
620
- dielectricSpecular[3] = 1;
621
- const specular = dielectricSpecular.lerp(dielectricSpecular, baseColorVector, metallicFactor);
622
- return {
623
- params: {
624
- diffuse: diffuse.toArray(),
625
- specular: specular.toArray(),
626
- renderMode: 'solid'
627
- }
628
- };
809
+ const texture = gltfMaterial?.pbrMetallicRoughness?.baseColorTexture || gltfMaterial.emissiveTexture;
810
+ let textureDefinitionInfo = null;
811
+ if (texture) {
812
+ textureDefinitionInfo = extractSharedResourcesTextureInfo(texture.texture, nodeId);
813
+ }
814
+ const { baseColorFactor, metallicFactor } = gltfMaterial?.pbrMetallicRoughness || {};
815
+ let colorFactor = baseColorFactor;
816
+ // If alpha channel is 0 try to get emissive factor from gltf material.
817
+ if ((!baseColorFactor || baseColorFactor[3] === 0) && gltfMaterial.emissiveFactor) {
818
+ colorFactor = gltfMaterial.emissiveFactor;
819
+ colorFactor[3] = colorFactor[3] || 1;
820
+ }
821
+ return {
822
+ materialDefinitionInfo: extractSharedResourcesMaterialInfo(colorFactor || [1, 1, 1, 1], metallicFactor),
823
+ textureDefinitionInfo
824
+ };
629
825
  }
826
+ /**
827
+ * Form "materialDefinition" which is part of "sharedResouces"
828
+ * https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#materials
829
+ * See formulas in appendix "Appendix B: BRDF Implementation":
830
+ * const dielectricSpecular = rgb(0.04, 0.04, 0.04)
831
+ * const black = rgb(0, 0, 0)
832
+ * cdiff = lerp(baseColor.rgb * (1 - dielectricSpecular.r), black, metallic)
833
+ * F0 = lerp(dieletricSpecular, baseColor.rgb, metallic)
834
+ *
835
+ * Assumption: F0 - specular in i3s ("specular reflection" <-> "reflectance value at normal incidence")
836
+ * cdiff - diffuse in i3s ("Diffuse color" <-> "'c' diffuse" (c means color?))
837
+ * @param baseColorFactor - RGBA color in 0..1 format
838
+ * @param metallicFactor - "metallicFactor" attribute of gltf material object
839
+ * @returns material definition info for I3S shared resource
840
+ */
841
+ function extractSharedResourcesMaterialInfo(baseColorFactor, metallicFactor = 1) {
842
+ const matDielectricColorComponent = 0.04 / 255; // Color from rgb (255) to 0..1 resolution
843
+ // All color resolutions are 0..1
844
+ const black = new Vector4(0, 0, 0, 1);
845
+ const unitVector = new Vector4(1, 1, 1, 1);
846
+ const dielectricSpecular = new Vector4(matDielectricColorComponent, matDielectricColorComponent, matDielectricColorComponent, 0);
847
+ const baseColorVector = new Vector4(baseColorFactor);
848
+ // https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#metallic-roughness-material
849
+ // Formulas for Cdiff & F0
850
+ const firstOperand = unitVector.subtract(dielectricSpecular).multiply(baseColorVector);
851
+ const diffuse = firstOperand.lerp(firstOperand, black, metallicFactor);
852
+ dielectricSpecular[3] = 1;
853
+ const specular = dielectricSpecular.lerp(dielectricSpecular, baseColorVector, metallicFactor);
854
+ return {
855
+ params: {
856
+ // @ts-expect-error NumericArray
857
+ diffuse: diffuse.toArray(),
858
+ // @ts-expect-error NumericArray
859
+ specular: specular.toArray(),
860
+ renderMode: 'solid'
861
+ }
862
+ };
863
+ }
864
+ /**
865
+ * Form "textureDefinition" which is part of "sharedResouces"
866
+ * @param texture - texture image info
867
+ * @param nodeId - I3S node ID
868
+ * @returns texture definition infor for shared resource
869
+ */
630
870
  function extractSharedResourcesTextureInfo(texture, nodeId) {
631
- var _texture$source, _texture$source2, _texture$source3, _texture$source4;
632
- return {
633
- encoding: texture !== null && texture !== void 0 && (_texture$source = texture.source) !== null && _texture$source !== void 0 && _texture$source.mimeType ? [texture.source.mimeType] : undefined,
634
- images: [{
635
- id: generateImageId(texture, nodeId),
636
- size: (_texture$source2 = texture.source) === null || _texture$source2 === void 0 ? void 0 : _texture$source2.image.width,
637
- length: (_texture$source3 = texture.source) !== null && _texture$source3 !== void 0 && _texture$source3.image.data.length ? [(_texture$source4 = texture.source) === null || _texture$source4 === void 0 ? void 0 : _texture$source4.image.data.length] : undefined
638
- }]
639
- };
871
+ return {
872
+ encoding: texture?.source?.mimeType ? [texture.source.mimeType] : undefined,
873
+ images: [
874
+ {
875
+ // 'i3s' has just size which is width of the image. Images are supposed to be square.
876
+ // https://github.com/Esri/i3s-spec/blob/master/docs/1.7/image.cmn.md
877
+ id: generateImageId(texture, nodeId),
878
+ size: texture.source?.image.width,
879
+ length: texture.source?.image.data.length ? [texture.source?.image.data.length] : undefined
880
+ }
881
+ ]
882
+ };
640
883
  }
884
+ /**
885
+ * Formula for calculating imageId:
886
+ * https://github.com/Esri/i3s-spec/blob/0a6366a9249b831db8436c322f8d27521e86cf07/format/Indexed%203d%20Scene%20Layer%20Format%20Specification.md#generating-image-ids
887
+ * @param texture - texture image info
888
+ * @param nodeId - I3S node ID
889
+ * @returns calculate image ID according to the spec
890
+ */
641
891
  function generateImageId(texture, nodeId) {
642
- var _texture$source5;
643
- const {
644
- width,
645
- height
646
- } = ((_texture$source5 = texture.source) === null || _texture$source5 === void 0 ? void 0 : _texture$source5.image) || {};
647
- if (!width || !height) {
648
- return '';
649
- }
650
- const levelCountOfTexture = 1;
651
- const indexOfLevel = 0;
652
- const indexOfTextureInStore = nodeId + 1;
653
- const zerosCount = 32 - indexOfTextureInStore.toString(2).length;
654
- const rightHalf = '0'.repeat(zerosCount).concat(indexOfTextureInStore.toString(2));
655
- const shiftedLevelCountOfTexture = levelCountOfTexture << 28;
656
- const shiftedIndexOfLevel = indexOfLevel << 24;
657
- const shiftedWidth = width - 1 << 12;
658
- const shiftedHeight = height - 1 << 0;
659
- const leftHalf = shiftedLevelCountOfTexture + shiftedIndexOfLevel + shiftedWidth + shiftedHeight;
660
- const imageId = BigInt(`0b${leftHalf.toString(2)}${rightHalf}`);
661
- return imageId.toString();
892
+ const { width, height } = texture.source?.image || {};
893
+ if (!width || !height) {
894
+ return '';
895
+ }
896
+ const levelCountOfTexture = 1;
897
+ const indexOfLevel = 0;
898
+ const indexOfTextureInStore = nodeId + 1;
899
+ const zerosCount = 32 - indexOfTextureInStore.toString(2).length;
900
+ const rightHalf = '0'.repeat(zerosCount).concat(indexOfTextureInStore.toString(2));
901
+ const shiftedLevelCountOfTexture = levelCountOfTexture << 28;
902
+ const shiftedIndexOfLevel = indexOfLevel << 24;
903
+ const shiftedWidth = (width - 1) << 12;
904
+ const shiftedHeight = (height - 1) << 0;
905
+ const leftHalf = shiftedLevelCountOfTexture + shiftedIndexOfLevel + shiftedWidth + shiftedHeight;
906
+ const imageId = BigInt(`0b${leftHalf.toString(2)}${rightHalf}`);
907
+ return imageId.toString();
662
908
  }
909
+ /**
910
+ * Make all feature ids unique through all nodes in layout.
911
+ * @param featureIds
912
+ * @param featureIndices
913
+ * @param featuresHashArray
914
+ * @param batchTable
915
+ * @returns propertyTable indices to map featureIds
916
+ */
663
917
  function makeFeatureIdsUnique(featureIds, featureIndices, featuresHashArray, batchTable) {
664
- const replaceMap = getFeaturesReplaceMap(featureIds, batchTable, featuresHashArray);
665
- replaceIndicesByUnique(featureIndices, replaceMap);
666
- replaceIndicesByUnique(featureIds, replaceMap);
667
- return replaceMap;
918
+ const replaceMap = getFeaturesReplaceMap(featureIds, batchTable, featuresHashArray);
919
+ replaceIndicesByUnique(featureIndices, replaceMap);
920
+ replaceIndicesByUnique(featureIds, replaceMap);
921
+ return replaceMap;
668
922
  }
923
+ /**
924
+ * Generate replace map to make featureIds unique.
925
+ * @param featureIds
926
+ * @param batchTable
927
+ * @param featuresHashArray
928
+ * @returns
929
+ */
669
930
  function getFeaturesReplaceMap(featureIds, batchTable, featuresHashArray) {
670
- const featureMap = {};
671
- for (let index = 0; index < featureIds.length; index++) {
672
- const oldFeatureId = featureIds[index];
673
- const uniqueFeatureId = getOrCreateUniqueFeatureId(index, batchTable, featuresHashArray);
674
- featureMap[oldFeatureId.toString()] = uniqueFeatureId;
675
- }
676
- return featureMap;
931
+ const featureMap = {};
932
+ for (let index = 0; index < featureIds.length; index++) {
933
+ const oldFeatureId = featureIds[index];
934
+ const uniqueFeatureId = getOrCreateUniqueFeatureId(index, batchTable, featuresHashArray);
935
+ featureMap[oldFeatureId.toString()] = uniqueFeatureId;
936
+ }
937
+ return featureMap;
677
938
  }
939
+ /**
940
+ * Generates string for unique batch id creation.
941
+ * @param batchTable
942
+ * @param index
943
+ * @returns
944
+ */
678
945
  function generateStringFromBatchTableByIndex(batchTable, index) {
679
- let str = '';
680
- for (const key in batchTable) {
681
- str += batchTable[key][index];
682
- }
683
- return str;
946
+ let str = '';
947
+ for (const key in batchTable) {
948
+ str += batchTable[key][index];
949
+ }
950
+ return str;
684
951
  }
952
+ /**
953
+ * Return already exited featureId or creates and returns new to support unique feature ids throw nodes.
954
+ * @param index
955
+ * @param batchTable
956
+ * @param featuresHashArray
957
+ * @returns
958
+ */
685
959
  function getOrCreateUniqueFeatureId(index, batchTable, featuresHashArray) {
686
- const batchTableStr = generateStringFromBatchTableByIndex(batchTable, index);
687
- const hash = md5(batchTableStr);
688
- if (featuresHashArray.includes(hash)) {
689
- return featuresHashArray.indexOf(hash);
690
- }
691
- return featuresHashArray.push(hash) - 1;
960
+ const batchTableStr = generateStringFromBatchTableByIndex(batchTable, index);
961
+ const hash = md5(batchTableStr);
962
+ if (featuresHashArray.includes(hash)) {
963
+ return featuresHashArray.indexOf(hash);
964
+ }
965
+ return featuresHashArray.push(hash) - 1;
692
966
  }
967
+ /**
968
+ * Do replacement of indices for making them unique through all nodes.
969
+ * @param indicesArray
970
+ * @param featureMap
971
+ * @returns
972
+ */
693
973
  function replaceIndicesByUnique(indicesArray, featureMap) {
694
- for (let index = 0; index < indicesArray.length; index++) {
695
- indicesArray[index] = featureMap[indicesArray[index]];
696
- }
974
+ for (let index = 0; index < indicesArray.length; index++) {
975
+ indicesArray[index] = featureMap[indicesArray[index]];
976
+ }
697
977
  }
978
+ /**
979
+ * Convert property table data to attribute buffers.
980
+ * @param featureIds
981
+ * @param propertyTable - table with metadata for particular feature.
982
+ * @param attributeStorageInfo
983
+ * @returns - Array of file buffers.
984
+ */
698
985
  function convertPropertyTableToAttributeBuffers(featureIds, featureIdsMap, propertyTable, attributeStorageInfo) {
699
- const attributeBuffers = [];
700
- const needFlattenPropertyTable = checkPropertiesLength(featureIds, propertyTable);
701
- const properties = needFlattenPropertyTable ? flattenPropertyTableByFeatureIds(featureIdsMap, propertyTable) : propertyTable;
702
- const propertyTableWithObjectIds = {
703
- OBJECTID: featureIds,
704
- ...properties
705
- };
706
- for (const propertyName in propertyTableWithObjectIds) {
707
- const type = getAttributeType(propertyName, attributeStorageInfo);
708
- if (type) {
709
- const value = propertyTableWithObjectIds[propertyName];
710
- const attributeBuffer = generateAttributeBuffer(type, value);
711
- attributeBuffers.push(attributeBuffer);
712
- }
713
- }
714
- return attributeBuffers;
986
+ const attributeBuffers = [];
987
+ const needFlattenPropertyTable = checkPropertiesLength(featureIds, propertyTable);
988
+ const properties = needFlattenPropertyTable
989
+ ? flattenPropertyTableByFeatureIds(featureIdsMap, propertyTable)
990
+ : propertyTable;
991
+ const propertyTableWithObjectIds = {
992
+ OBJECTID: featureIds,
993
+ ...properties
994
+ };
995
+ for (const propertyName in propertyTableWithObjectIds) {
996
+ const type = getAttributeType(propertyName, attributeStorageInfo);
997
+ if (type) {
998
+ const value = propertyTableWithObjectIds[propertyName];
999
+ const attributeBuffer = generateAttributeBuffer(type, value);
1000
+ attributeBuffers.push(attributeBuffer);
1001
+ }
1002
+ }
1003
+ return attributeBuffers;
715
1004
  }
1005
+ /**
1006
+ * Generates attribute buffer based on attribute type
1007
+ * @param type
1008
+ * @param value
1009
+ */
716
1010
  function generateAttributeBuffer(type, value) {
717
- let attributeBuffer;
718
- switch (type) {
719
- case OBJECT_ID_TYPE:
720
- case SHORT_INT_TYPE:
721
- attributeBuffer = generateShortIntegerAttributeBuffer(value);
722
- break;
723
- case DOUBLE_TYPE:
724
- attributeBuffer = generateDoubleAttributeBuffer(value);
725
- break;
726
- case STRING_TYPE:
727
- attributeBuffer = generateStringAttributeBuffer(value);
728
- break;
729
- default:
730
- attributeBuffer = generateStringAttributeBuffer(value);
731
- }
732
- return attributeBuffer;
1011
+ let attributeBuffer;
1012
+ switch (type) {
1013
+ case OBJECT_ID_TYPE:
1014
+ case SHORT_INT_TYPE:
1015
+ attributeBuffer = generateShortIntegerAttributeBuffer(value);
1016
+ break;
1017
+ case DOUBLE_TYPE:
1018
+ attributeBuffer = generateDoubleAttributeBuffer(value);
1019
+ break;
1020
+ case STRING_TYPE:
1021
+ attributeBuffer = generateStringAttributeBuffer(value);
1022
+ break;
1023
+ default:
1024
+ attributeBuffer = generateStringAttributeBuffer(value);
1025
+ }
1026
+ return attributeBuffer;
733
1027
  }
1028
+ /**
1029
+ * Return attribute type.
1030
+ * @param key
1031
+ * @param attributeStorageInfo
1032
+ * @returns attribute type.
1033
+ */
734
1034
  function getAttributeType(key, attributeStorageInfo) {
735
- const attribute = attributeStorageInfo.find(attr => attr.name === key);
736
- if (!attribute) {
737
- console.error(`attribute is null, key=${key}, attributeStorageInfo=${JSON.stringify(attributeStorageInfo, null, 2)}`);
738
- return '';
739
- }
740
- if (!attribute.attributeValues) {
741
- console.error(`attributeValues is null, attribute=${attribute}`);
742
- return '';
743
- }
744
- return attribute.attributeValues.valueType;
1035
+ const attribute = attributeStorageInfo.find((attr) => attr.name === key);
1036
+ if (!attribute) {
1037
+ // eslint-disable-next-line no-console
1038
+ console.error(`attribute is null, key=${key}, attributeStorageInfo=${JSON.stringify(attributeStorageInfo, null, 2)}`);
1039
+ return '';
1040
+ }
1041
+ if (!attribute.attributeValues) {
1042
+ // eslint-disable-next-line no-console
1043
+ console.error(`attributeValues is null, attribute=${attribute}`);
1044
+ return '';
1045
+ }
1046
+ return attribute.attributeValues.valueType;
745
1047
  }
1048
+ /**
1049
+ * Convert short integer to attribute arrayBuffer.
1050
+ * @param featureIds
1051
+ * @returns - Buffer with objectId data.
1052
+ */
746
1053
  function generateShortIntegerAttributeBuffer(featureIds) {
747
- const count = new Uint32Array([featureIds.length]);
748
- const valuesArray = new Uint32Array(featureIds);
749
- return concatenateArrayBuffers(count.buffer, valuesArray.buffer);
1054
+ const count = new Uint32Array([featureIds.length]);
1055
+ const valuesArray = new Uint32Array(featureIds);
1056
+ return concatenateArrayBuffers(count.buffer, valuesArray.buffer);
750
1057
  }
1058
+ /**
1059
+ * Convert double to attribute arrayBuffer.
1060
+ * @param featureIds
1061
+ * @returns - Buffer with objectId data.
1062
+ */
751
1063
  function generateDoubleAttributeBuffer(featureIds) {
752
- const count = new Uint32Array([featureIds.length]);
753
- const padding = new Uint8Array(4);
754
- const valuesArray = new Float64Array(featureIds);
755
- return concatenateArrayBuffers(count.buffer, padding.buffer, valuesArray.buffer);
1064
+ const count = new Uint32Array([featureIds.length]);
1065
+ const padding = new Uint8Array(4);
1066
+ const valuesArray = new Float64Array(featureIds);
1067
+ return concatenateArrayBuffers(count.buffer, padding.buffer, valuesArray.buffer);
756
1068
  }
1069
+ /**
1070
+ * Convert batch table attributes to array buffer with batch table data.
1071
+ * @param batchAttributes
1072
+ * @returns - Buffer with batch table data.
1073
+ */
757
1074
  function generateStringAttributeBuffer(batchAttributes) {
758
- const stringCountArray = new Uint32Array([batchAttributes.length]);
759
- let totalNumberOfBytes = 0;
760
- const stringSizesArray = new Uint32Array(batchAttributes.length);
761
- const stringBufferArray = [];
762
- for (let index = 0; index < batchAttributes.length; index++) {
763
- const currentString = `${String(batchAttributes[index])}\0`;
764
- const currentStringBuffer = Buffer.from(currentString);
765
- const currentStringSize = currentStringBuffer.length;
766
- totalNumberOfBytes += currentStringSize;
767
- stringSizesArray[index] = currentStringSize;
768
- stringBufferArray.push(currentStringBuffer);
769
- }
770
- const totalBytes = new Uint32Array([totalNumberOfBytes]);
771
- return concatenateArrayBuffers(stringCountArray.buffer, totalBytes.buffer, stringSizesArray.buffer, ...stringBufferArray);
1075
+ const stringCountArray = new Uint32Array([batchAttributes.length]);
1076
+ let totalNumberOfBytes = 0;
1077
+ const stringSizesArray = new Uint32Array(batchAttributes.length);
1078
+ const stringBufferArray = [];
1079
+ for (let index = 0; index < batchAttributes.length; index++) {
1080
+ const currentString = `${String(batchAttributes[index])}\0`;
1081
+ const currentStringBuffer = Buffer.from(currentString);
1082
+ const currentStringSize = currentStringBuffer.length;
1083
+ totalNumberOfBytes += currentStringSize;
1084
+ stringSizesArray[index] = currentStringSize;
1085
+ stringBufferArray.push(currentStringBuffer);
1086
+ }
1087
+ const totalBytes = new Uint32Array([totalNumberOfBytes]);
1088
+ return concatenateArrayBuffers(stringCountArray.buffer, totalBytes.buffer, stringSizesArray.buffer, ...stringBufferArray);
772
1089
  }
1090
+ /**
1091
+ * Convert featureIds to BigUint64Array.
1092
+ * @param featureIds
1093
+ * @returns - Array of feature ids in BigUint64 format.
1094
+ */
773
1095
  function generateBigUint64Array(featureIds) {
774
- const typedFeatureIds = new BigUint64Array(featureIds.length);
775
- for (let index = 0; index < featureIds.length; index++) {
776
- typedFeatureIds[index] = BigInt(featureIds[index]);
777
- }
778
- return typedFeatureIds;
1096
+ const typedFeatureIds = new BigUint64Array(featureIds.length);
1097
+ for (let index = 0; index < featureIds.length; index++) {
1098
+ typedFeatureIds[index] = BigInt(featureIds[index]);
1099
+ }
1100
+ return typedFeatureIds;
779
1101
  }
1102
+ /**
1103
+ * Generates draco compressed geometry
1104
+ * @param vertexCount
1105
+ * @param convertedAttributes - get rid of this argument here
1106
+ * @param attributes - geometry attributes to compress
1107
+ * @param libraries - dynamicaly loaded 3rd-party libraries
1108
+ * @returns - Compressed geometry.
1109
+ */
780
1110
  async function generateCompressedGeometry(vertexCount, convertedAttributes, attributes, libraries) {
781
- const {
782
- positions,
783
- normals,
784
- texCoords,
785
- colors,
786
- uvRegions,
787
- featureIds,
788
- faceRange
789
- } = attributes;
790
- const indices = new Uint32Array(vertexCount);
791
- for (let index = 0; index < indices.length; index++) {
792
- indices.set([index], index);
793
- }
794
- const featureIndices = new Uint32Array(convertedAttributes.featureIndices.length ? convertedAttributes.featureIndices : vertexCount);
795
- const featureIndex = generateFeatureIndexAttribute(featureIndices, faceRange);
796
- const compressedAttributes = {
797
- positions,
798
- normals,
799
- colors,
800
- 'feature-index': featureIndex
801
- };
802
- if (texCoords.length) {
803
- compressedAttributes.texCoords = texCoords;
804
- }
805
- const attributesMetadata = {
806
- 'feature-index': {
807
- 'i3s-attribute-type': 'feature-index',
808
- 'i3s-feature-ids': new Int32Array(featureIds)
809
- }
810
- };
811
- if (uvRegions.length) {
812
- compressedAttributes['uv-region'] = uvRegions;
813
- attributesMetadata['uv-region'] = {
814
- 'i3s-attribute-type': 'uv-region'
1111
+ const { positions, normals, texCoords, colors, uvRegions, featureIds, faceRange } = attributes;
1112
+ const indices = new Uint32Array(vertexCount);
1113
+ for (let index = 0; index < indices.length; index++) {
1114
+ indices.set([index], index);
1115
+ }
1116
+ const featureIndices = new Uint32Array(convertedAttributes.featureIndices.length ? convertedAttributes.featureIndices : vertexCount);
1117
+ const featureIndex = generateFeatureIndexAttribute(featureIndices, faceRange);
1118
+ const compressedAttributes = {
1119
+ positions,
1120
+ normals,
1121
+ colors,
1122
+ 'feature-index': featureIndex
815
1123
  };
816
- }
817
- return encode({
818
- attributes: compressedAttributes,
819
- indices
820
- }, DracoWriterWorker, {
821
- ...DracoWriterWorker.options,
822
- reuseWorkers: true,
823
- _nodeWorkers: true,
824
- modules: libraries,
825
- useLocalLibraries: true,
826
- draco: {
827
- method: 'MESH_SEQUENTIAL_ENCODING',
828
- attributesMetadata
829
- },
830
- ['draco-writer']: {
831
- workerUrl: './modules/draco/dist/draco-writer-worker-node.js'
832
- }
833
- });
1124
+ if (texCoords.length) {
1125
+ compressedAttributes.texCoords = texCoords;
1126
+ }
1127
+ const attributesMetadata = {
1128
+ 'feature-index': {
1129
+ 'i3s-attribute-type': 'feature-index',
1130
+ 'i3s-feature-ids': new Int32Array(featureIds)
1131
+ }
1132
+ };
1133
+ if (uvRegions.length) {
1134
+ compressedAttributes['uv-region'] = uvRegions;
1135
+ attributesMetadata['uv-region'] = {
1136
+ 'i3s-attribute-type': 'uv-region'
1137
+ };
1138
+ }
1139
+ return encode({ attributes: compressedAttributes, indices },
1140
+ // @ts-expect-error if encoded supports worker writer, we should update its type signature
1141
+ DracoWriterWorker, {
1142
+ ...DracoWriterWorker.options,
1143
+ reuseWorkers: true,
1144
+ _nodeWorkers: true,
1145
+ modules: libraries,
1146
+ useLocalLibraries: true,
1147
+ draco: {
1148
+ method: 'MESH_SEQUENTIAL_ENCODING',
1149
+ attributesMetadata
1150
+ },
1151
+ ['draco-writer']: {
1152
+ // We need to load local fs workers because nodejs can't load workers from the Internet
1153
+ workerUrl: './modules/draco/dist/draco-writer-worker-node.js'
1154
+ }
1155
+ });
834
1156
  }
1157
+ /**
1158
+ * Generates ordered feature indices based on face range
1159
+ * @param featureIndex
1160
+ * @param faceRange
1161
+ * @returns
1162
+ */
835
1163
  function generateFeatureIndexAttribute(featureIndex, faceRange) {
836
- const orderedFeatureIndices = new Uint32Array(featureIndex.length);
837
- let fillIndex = 0;
838
- let startIndex = 0;
839
- for (let index = 1; index < faceRange.length; index += 2) {
840
- const endIndex = (faceRange[index] + 1) * VALUES_PER_VERTEX;
841
- orderedFeatureIndices.fill(fillIndex, startIndex, endIndex);
842
- fillIndex++;
843
- startIndex = endIndex + 1;
844
- }
845
- return orderedFeatureIndices;
1164
+ const orderedFeatureIndices = new Uint32Array(featureIndex.length);
1165
+ let fillIndex = 0;
1166
+ let startIndex = 0;
1167
+ for (let index = 1; index < faceRange.length; index += 2) {
1168
+ const endIndex = (faceRange[index] + 1) * VALUES_PER_VERTEX;
1169
+ orderedFeatureIndices.fill(fillIndex, startIndex, endIndex);
1170
+ fillIndex++;
1171
+ startIndex = endIndex + 1;
1172
+ }
1173
+ return orderedFeatureIndices;
846
1174
  }
1175
+ /**
1176
+ * Find property table in tile
1177
+ * For example it can be batchTable for b3dm files or property table in gLTF extension.
1178
+ * @param tileContent - 3DTiles tile content
1179
+ * @param metadataClass - user selected feature metadata class name
1180
+ * @return batch table from b3dm / feature properties from EXT_FEATURE_METADATA or EXT_STRUCTURAL_METADATA.
1181
+ */
847
1182
  export function getPropertyTable(tileContent, metadataClass) {
848
- if (!tileContent) {
849
- return null;
850
- }
851
- let propertyTable;
852
- const batchTableJson = tileContent.batchTableJson;
853
- if (batchTableJson) {
854
- return batchTableJson;
855
- }
856
- const {
857
- extensionName,
858
- extension
859
- } = getPropertyTableExtension(tileContent);
860
- switch (extensionName) {
861
- case EXT_STRUCTURAL_METADATA:
862
- {
863
- propertyTable = getPropertyTableFromExtStructuralMetadata(extension, metadataClass);
864
- return propertyTable;
865
- }
866
- case EXT_FEATURE_METADATA:
867
- {
868
- propertyTable = getPropertyTableFromExtFeatureMetadata(extension, metadataClass);
869
- return propertyTable;
870
- }
871
- default:
872
- return null;
873
- }
1183
+ if (!tileContent) {
1184
+ return null;
1185
+ }
1186
+ let propertyTable;
1187
+ const batchTableJson = tileContent.batchTableJson;
1188
+ if (batchTableJson) {
1189
+ return batchTableJson;
1190
+ }
1191
+ const { extensionName, extension } = getPropertyTableExtension(tileContent);
1192
+ switch (extensionName) {
1193
+ case EXT_STRUCTURAL_METADATA: {
1194
+ propertyTable = getPropertyTableFromExtStructuralMetadata(extension, metadataClass);
1195
+ return propertyTable;
1196
+ }
1197
+ case EXT_FEATURE_METADATA: {
1198
+ propertyTable = getPropertyTableFromExtFeatureMetadata(extension, metadataClass);
1199
+ return propertyTable;
1200
+ }
1201
+ default:
1202
+ return null;
1203
+ }
874
1204
  }
1205
+ /**
1206
+ * Handles EXT_structural_metadata to get property table.
1207
+ * @param extension - Global level of EXT_STRUCTURAL_METADATA extension.
1208
+ * @param metadataClass - User selected feature metadata class name.
1209
+ * @returns {FeatureTableJson | null} Property table or null if the extension can't be handled properly.
1210
+ */
875
1211
  function getPropertyTableFromExtStructuralMetadata(extension, metadataClass) {
876
- if (extension.propertyTables) {
877
- for (const propertyTable of extension.propertyTables) {
878
- if (propertyTable.class === metadataClass || !metadataClass) {
879
- return getPropertyData(propertyTable);
880
- }
881
- }
882
- }
883
- if (extension.propertyTextures) {
884
- for (const propertyTexture of extension.propertyTextures) {
885
- if (propertyTexture.class === metadataClass || !metadataClass) {
886
- return getPropertyData(propertyTexture);
887
- }
888
- }
889
- }
890
- return null;
1212
+ /**
1213
+ * Note, 3dTiles is able to have multiple featureId attributes and multiple feature tables.
1214
+ * In I3S we should decide which featureIds attribute will be passed to geometry data.
1215
+ * So, we take only the feature table / feature texture to generate attributes storage info object.
1216
+ * If the user has selected the metadataClass, the table with the corresponding class will be used,
1217
+ * or just the first one otherwise.
1218
+ */
1219
+ if (extension.propertyTables) {
1220
+ for (const propertyTable of extension.propertyTables) {
1221
+ if (propertyTable.class === metadataClass || !metadataClass) {
1222
+ return getPropertyData(propertyTable);
1223
+ }
1224
+ }
1225
+ }
1226
+ if (extension.propertyTextures) {
1227
+ for (const propertyTexture of extension.propertyTextures) {
1228
+ if (propertyTexture.class === metadataClass || !metadataClass) {
1229
+ return getPropertyData(propertyTexture);
1230
+ }
1231
+ }
1232
+ }
1233
+ return null;
891
1234
  }
1235
+ /**
1236
+ * Handles EXT_feature_metadata to get property table.
1237
+ * @param extension - Global level of EXT_FEATURE_METADATA extension.
1238
+ * @param metadataClass - User selected feature metadata class name.
1239
+ * @returns {FeatureTableJson | null} Property table or null if the extension can't be handled properly.
1240
+ */
892
1241
  function getPropertyTableFromExtFeatureMetadata(extension, metadataClass) {
893
- if (extension.featureTables) {
894
- for (const featureTableName in extension.featureTables) {
895
- const featureTable = extension.featureTables[featureTableName];
896
- if (featureTable.class === metadataClass || !metadataClass) {
897
- return getPropertyData(featureTable);
898
- }
899
- }
900
- }
901
- if (extension.featureTextures) {
902
- for (const featureTextureName in extension.featureTextures) {
903
- const featureTexture = extension.featureTextures[featureTextureName];
904
- if (featureTexture.class === metadataClass || !metadataClass) {
905
- return getPropertyData(featureTexture);
906
- }
907
- }
908
- }
909
- return null;
1242
+ /**
1243
+ * Note, 3dTiles is able to have multiple featureId attributes and multiple feature tables.
1244
+ * In I3S we should decide which featureIds attribute will be passed to geometry data.
1245
+ * So, we take only the feature table / feature texture to generate attributes storage info object.
1246
+ * If the user has selected the metadataClass, the table with the corresponding class will be used,
1247
+ * or just the first one otherwise.
1248
+ */
1249
+ if (extension.featureTables) {
1250
+ for (const featureTableName in extension.featureTables) {
1251
+ const featureTable = extension.featureTables[featureTableName];
1252
+ if (featureTable.class === metadataClass || !metadataClass) {
1253
+ return getPropertyData(featureTable);
1254
+ }
1255
+ }
1256
+ }
1257
+ if (extension.featureTextures) {
1258
+ for (const featureTextureName in extension.featureTextures) {
1259
+ const featureTexture = extension.featureTextures[featureTextureName];
1260
+ if (featureTexture.class === metadataClass || !metadataClass) {
1261
+ return getPropertyData(featureTexture);
1262
+ }
1263
+ }
1264
+ }
1265
+ return null;
910
1266
  }
1267
+ /**
1268
+ * Gets data from Property Table or Property Texture
1269
+ * @param featureObject - property table or texture from the extension
1270
+ * @returns Table containing property data
1271
+ */
911
1272
  function getPropertyData(featureObject) {
912
- const propertyTableWithData = {};
913
- for (const propertyName in featureObject.properties) {
914
- propertyTableWithData[propertyName] = featureObject.properties[propertyName].data;
915
- }
916
- return propertyTableWithData;
1273
+ const propertyTableWithData = {};
1274
+ for (const propertyName in featureObject.properties) {
1275
+ propertyTableWithData[propertyName] = featureObject.properties[propertyName].data;
1276
+ }
1277
+ return propertyTableWithData;
917
1278
  }
1279
+ /**
1280
+ * Check extensions which can be with property table inside.
1281
+ * @param tileContent - 3DTiles tile content
1282
+ */
918
1283
  function getPropertyTableExtension(tileContent) {
919
- var _tileContent$gltf3, _tileContent$gltf5, _tileContent$gltf5$ex;
920
- const extensionsWithPropertyTables = [EXT_FEATURE_METADATA, EXT_STRUCTURAL_METADATA];
921
- const extensionsUsed = tileContent === null || tileContent === void 0 ? void 0 : (_tileContent$gltf3 = tileContent.gltf) === null || _tileContent$gltf3 === void 0 ? void 0 : _tileContent$gltf3.extensionsUsed;
922
- if (!extensionsUsed) {
923
- return {
924
- extensionName: null,
925
- extension: null
926
- };
927
- }
928
- let extensionName = '';
929
- for (const extensionItem of (tileContent === null || tileContent === void 0 ? void 0 : (_tileContent$gltf4 = tileContent.gltf) === null || _tileContent$gltf4 === void 0 ? void 0 : _tileContent$gltf4.extensionsUsed) || []) {
930
- var _tileContent$gltf4;
931
- if (extensionsWithPropertyTables.includes(extensionItem)) {
932
- extensionName = extensionItem;
933
- break;
934
- }
935
- }
936
- if (!extensionName) {
937
- return {
938
- extensionName: null,
939
- extension: null
940
- };
941
- }
942
- const extension = tileContent === null || tileContent === void 0 ? void 0 : (_tileContent$gltf5 = tileContent.gltf) === null || _tileContent$gltf5 === void 0 ? void 0 : (_tileContent$gltf5$ex = _tileContent$gltf5.extensions) === null || _tileContent$gltf5$ex === void 0 ? void 0 : _tileContent$gltf5$ex[extensionName];
943
- return {
944
- extensionName,
945
- extension
946
- };
947
- }
948
- //# sourceMappingURL=geometry-converter.js.map
1284
+ const extensionsWithPropertyTables = [EXT_FEATURE_METADATA, EXT_STRUCTURAL_METADATA];
1285
+ const extensionsUsed = tileContent?.gltf?.extensionsUsed;
1286
+ if (!extensionsUsed) {
1287
+ return { extensionName: null, extension: null };
1288
+ }
1289
+ let extensionName = '';
1290
+ for (const extensionItem of tileContent?.gltf?.extensionsUsed || []) {
1291
+ if (extensionsWithPropertyTables.includes(extensionItem)) {
1292
+ extensionName = extensionItem;
1293
+ /*
1294
+ It returns the first extension containing the property table.
1295
+ We assume that there can be only one extension containing the property table:
1296
+ either EXT_FEATURE_METADATA, which is a depricated extension,
1297
+ or EXT_STRUCTURAL_METADATA.
1298
+ */
1299
+ break;
1300
+ }
1301
+ }
1302
+ if (!extensionName) {
1303
+ return { extensionName: null, extension: null };
1304
+ }
1305
+ const extension = tileContent?.gltf?.extensions?.[extensionName];
1306
+ return { extensionName, extension };
1307
+ }