@loaders.gl/tile-converter 4.4.0-alpha.1 → 4.4.0-alpha.9

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