@loaders.gl/tile-converter 4.0.0-alpha.23 → 4.0.0-alpha.24

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