@loaders.gl/tile-converter 3.3.0-alpha.7 → 3.3.0

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 (136) hide show
  1. package/dist/3d-tiles-attributes-worker.d.ts +2 -2
  2. package/dist/3d-tiles-attributes-worker.d.ts.map +1 -1
  3. package/dist/3d-tiles-attributes-worker.js +2 -2
  4. package/dist/3d-tiles-attributes-worker.js.map +3 -3
  5. package/dist/converter-cli.js +14 -2
  6. package/dist/converter.min.js +22 -22
  7. package/dist/deps-installer/deps-installer.d.ts.map +1 -1
  8. package/dist/deps-installer/deps-installer.js +8 -0
  9. package/dist/dist.min.js +1407 -1242
  10. package/dist/es5/3d-tiles-attributes-worker.js +1 -1
  11. package/dist/es5/3d-tiles-attributes-worker.js.map +1 -1
  12. package/dist/es5/converter-cli.js +14 -2
  13. package/dist/es5/converter-cli.js.map +1 -1
  14. package/dist/es5/deps-installer/deps-installer.js +13 -2
  15. package/dist/es5/deps-installer/deps-installer.js.map +1 -1
  16. package/dist/es5/i3s-attributes-worker.js +1 -1
  17. package/dist/es5/i3s-attributes-worker.js.map +1 -1
  18. package/dist/es5/i3s-converter/helpers/batch-ids-extensions.js.map +1 -1
  19. package/dist/es5/i3s-converter/helpers/geometry-attributes.js +16 -7
  20. package/dist/es5/i3s-converter/helpers/geometry-attributes.js.map +1 -1
  21. package/dist/es5/i3s-converter/helpers/geometry-converter.js +363 -113
  22. package/dist/es5/i3s-converter/helpers/geometry-converter.js.map +1 -1
  23. package/dist/es5/i3s-converter/helpers/gltf-attributes.js +6 -11
  24. package/dist/es5/i3s-converter/helpers/gltf-attributes.js.map +1 -1
  25. package/dist/es5/i3s-converter/helpers/node-index-document.js +517 -0
  26. package/dist/es5/i3s-converter/helpers/node-index-document.js.map +1 -0
  27. package/dist/es5/i3s-converter/helpers/node-pages.js +455 -173
  28. package/dist/es5/i3s-converter/helpers/node-pages.js.map +1 -1
  29. package/dist/es5/i3s-converter/i3s-converter.js +549 -618
  30. package/dist/es5/i3s-converter/i3s-converter.js.map +1 -1
  31. package/dist/es5/i3s-converter/json-templates/geometry-definitions.js +107 -0
  32. package/dist/es5/i3s-converter/json-templates/geometry-definitions.js.map +1 -0
  33. package/dist/es5/i3s-converter/json-templates/layers.js +2 -93
  34. package/dist/es5/i3s-converter/json-templates/layers.js.map +1 -1
  35. package/dist/es5/i3s-converter/json-templates/shared-resources.js +3 -3
  36. package/dist/es5/i3s-converter/json-templates/shared-resources.js.map +1 -1
  37. package/dist/es5/i3s-converter/types.js.map +1 -1
  38. package/dist/es5/lib/utils/file-utils.js +93 -9
  39. package/dist/es5/lib/utils/file-utils.js.map +1 -1
  40. package/dist/es5/lib/utils/write-queue.js +38 -25
  41. package/dist/es5/lib/utils/write-queue.js.map +1 -1
  42. package/dist/es5/pgm-loader.js +1 -1
  43. package/dist/es5/pgm-loader.js.map +1 -1
  44. package/dist/es5/workers/i3s-attributes-worker.js +1 -1
  45. package/dist/es5/workers/i3s-attributes-worker.js.map +1 -1
  46. package/dist/esm/3d-tiles-attributes-worker.js +1 -1
  47. package/dist/esm/3d-tiles-attributes-worker.js.map +1 -1
  48. package/dist/esm/converter-cli.js +14 -2
  49. package/dist/esm/converter-cli.js.map +1 -1
  50. package/dist/esm/deps-installer/deps-installer.js +9 -1
  51. package/dist/esm/deps-installer/deps-installer.js.map +1 -1
  52. package/dist/esm/i3s-attributes-worker.js +1 -1
  53. package/dist/esm/i3s-attributes-worker.js.map +1 -1
  54. package/dist/esm/i3s-converter/helpers/batch-ids-extensions.js.map +1 -1
  55. package/dist/esm/i3s-converter/helpers/geometry-attributes.js +16 -7
  56. package/dist/esm/i3s-converter/helpers/geometry-attributes.js.map +1 -1
  57. package/dist/esm/i3s-converter/helpers/geometry-converter.js +150 -40
  58. package/dist/esm/i3s-converter/helpers/geometry-converter.js.map +1 -1
  59. package/dist/esm/i3s-converter/helpers/gltf-attributes.js +6 -9
  60. package/dist/esm/i3s-converter/helpers/gltf-attributes.js.map +1 -1
  61. package/dist/esm/i3s-converter/helpers/node-index-document.js +202 -0
  62. package/dist/esm/i3s-converter/helpers/node-index-document.js.map +1 -0
  63. package/dist/esm/i3s-converter/helpers/node-pages.js +162 -76
  64. package/dist/esm/i3s-converter/helpers/node-pages.js.map +1 -1
  65. package/dist/esm/i3s-converter/i3s-converter.js +115 -220
  66. package/dist/esm/i3s-converter/i3s-converter.js.map +1 -1
  67. package/dist/esm/i3s-converter/json-templates/geometry-definitions.js +89 -0
  68. package/dist/esm/i3s-converter/json-templates/geometry-definitions.js.map +1 -0
  69. package/dist/esm/i3s-converter/json-templates/layers.js +2 -85
  70. package/dist/esm/i3s-converter/json-templates/layers.js.map +1 -1
  71. package/dist/esm/i3s-converter/json-templates/shared-resources.js +3 -3
  72. package/dist/esm/i3s-converter/json-templates/shared-resources.js.map +1 -1
  73. package/dist/esm/i3s-converter/types.js.map +1 -1
  74. package/dist/esm/lib/utils/file-utils.js +44 -3
  75. package/dist/esm/lib/utils/file-utils.js.map +1 -1
  76. package/dist/esm/lib/utils/write-queue.js +19 -10
  77. package/dist/esm/lib/utils/write-queue.js.map +1 -1
  78. package/dist/esm/pgm-loader.js +1 -1
  79. package/dist/esm/pgm-loader.js.map +1 -1
  80. package/dist/esm/workers/i3s-attributes-worker.js +1 -1
  81. package/dist/esm/workers/i3s-attributes-worker.js.map +1 -1
  82. package/dist/i3s-attributes-worker.d.ts +2 -2
  83. package/dist/i3s-attributes-worker.d.ts.map +1 -1
  84. package/dist/i3s-attributes-worker.js +2 -2
  85. package/dist/i3s-attributes-worker.js.map +2 -2
  86. package/dist/i3s-converter/helpers/batch-ids-extensions.d.ts +3 -3
  87. package/dist/i3s-converter/helpers/batch-ids-extensions.js +3 -3
  88. package/dist/i3s-converter/helpers/geometry-attributes.d.ts.map +1 -1
  89. package/dist/i3s-converter/helpers/geometry-attributes.js +16 -10
  90. package/dist/i3s-converter/helpers/geometry-converter.d.ts +8 -4
  91. package/dist/i3s-converter/helpers/geometry-converter.d.ts.map +1 -1
  92. package/dist/i3s-converter/helpers/geometry-converter.js +200 -44
  93. package/dist/i3s-converter/helpers/gltf-attributes.d.ts.map +1 -1
  94. package/dist/i3s-converter/helpers/gltf-attributes.js +2 -3
  95. package/dist/i3s-converter/helpers/node-index-document.d.ts +95 -0
  96. package/dist/i3s-converter/helpers/node-index-document.d.ts.map +1 -0
  97. package/dist/i3s-converter/helpers/node-index-document.js +250 -0
  98. package/dist/i3s-converter/helpers/node-pages.d.ts +78 -43
  99. package/dist/i3s-converter/helpers/node-pages.d.ts.map +1 -1
  100. package/dist/i3s-converter/helpers/node-pages.js +195 -94
  101. package/dist/i3s-converter/i3s-converter.d.ts +33 -58
  102. package/dist/i3s-converter/i3s-converter.d.ts.map +1 -1
  103. package/dist/i3s-converter/i3s-converter.js +122 -233
  104. package/dist/i3s-converter/json-templates/geometry-definitions.d.ts +7 -0
  105. package/dist/i3s-converter/json-templates/geometry-definitions.d.ts.map +1 -0
  106. package/dist/i3s-converter/json-templates/geometry-definitions.js +87 -0
  107. package/dist/i3s-converter/json-templates/layers.d.ts +1 -30
  108. package/dist/i3s-converter/json-templates/layers.d.ts.map +1 -1
  109. package/dist/i3s-converter/json-templates/layers.js +2 -86
  110. package/dist/i3s-converter/json-templates/shared-resources.js +3 -3
  111. package/dist/i3s-converter/types.d.ts +34 -8
  112. package/dist/i3s-converter/types.d.ts.map +1 -1
  113. package/dist/lib/utils/file-utils.d.ts +17 -1
  114. package/dist/lib/utils/file-utils.d.ts.map +1 -1
  115. package/dist/lib/utils/file-utils.js +64 -7
  116. package/dist/lib/utils/write-queue.d.ts +19 -3
  117. package/dist/lib/utils/write-queue.d.ts.map +1 -1
  118. package/dist/lib/utils/write-queue.js +18 -12
  119. package/dist/workers/i3s-attributes-worker.js +1 -1
  120. package/package.json +25 -20
  121. package/src/converter-cli.ts +22 -2
  122. package/src/deps-installer/deps-installer.ts +9 -0
  123. package/src/i3s-converter/helpers/batch-ids-extensions.ts +3 -3
  124. package/src/i3s-converter/helpers/geometry-attributes.ts +16 -11
  125. package/src/i3s-converter/helpers/geometry-converter.ts +217 -48
  126. package/src/i3s-converter/helpers/gltf-attributes.ts +2 -3
  127. package/src/i3s-converter/helpers/node-index-document.ts +315 -0
  128. package/src/i3s-converter/helpers/node-pages.ts +215 -110
  129. package/src/i3s-converter/i3s-converter.ts +170 -312
  130. package/src/i3s-converter/json-templates/geometry-definitions.ts +83 -0
  131. package/src/i3s-converter/json-templates/layers.ts +2 -91
  132. package/src/i3s-converter/json-templates/shared-resources.ts +3 -3
  133. package/src/i3s-converter/types.ts +29 -2
  134. package/src/lib/utils/file-utils.ts +62 -7
  135. package/src/lib/utils/write-queue.ts +36 -15
  136. package/src/workers/i3s-attributes-worker.ts +2 -1
@@ -17,12 +17,14 @@ import {DracoWriterWorker} from '@loaders.gl/draco';
17
17
  import {assert, encode} from '@loaders.gl/core';
18
18
  import {concatenateArrayBuffers, concatenateTypedArrays} from '@loaders.gl/loader-utils';
19
19
  import md5 from 'md5';
20
+ import {v4 as uuidv4} from 'uuid';
20
21
  import {generateAttributes} from './geometry-attributes';
21
22
  import {createBoundingVolumesFromGeometry} from './coordinate-converter';
22
23
  import {
23
24
  ConvertedAttributes,
24
25
  I3SConvertedResources,
25
26
  I3SMaterialWithTexture,
27
+ MergedMaterial,
26
28
  SharedResourcesArrays
27
29
  } from '../types';
28
30
  import {
@@ -68,33 +70,38 @@ let scratchVector = new Vector3();
68
70
  *
69
71
  * @param tileContent - 3d tile content
70
72
  * @param addNodeToNodePage - function to add new node to node pages
73
+ * @param propertyTable - batch table (corresponding to feature attributes data)
71
74
  * @param featuresHashArray - hash array of features that is needed to not to mix up same features in parent and child nodes
72
75
  * @param attributeStorageInfo - attributes metadata from 3DSceneLayer json
73
76
  * @param draco - is converter should create draco compressed geometry
74
77
  * @param generateBoundingVolumes - is converter should create accurate bounding voulmes from geometry attributes
78
+ * @param shouldMergeMaterials - Try to merge similar materials to be able to merge meshes into one node
75
79
  * @param geoidHeightModel - model to convert elevation from elipsoidal to geoid
80
+ * @param workerSource - source code of used workers
76
81
  * @returns Array of node resources to create one or more i3s nodes
77
82
  */
78
83
  export default async function convertB3dmToI3sGeometry(
79
84
  tileContent: B3DMContent,
80
- addNodeToNodePage: () => number,
85
+ addNodeToNodePage: () => Promise<number>,
81
86
  propertyTable: FeatureTableJson | null,
82
87
  featuresHashArray: string[],
83
88
  attributeStorageInfo: AttributeStorageInfo[] | undefined,
84
89
  draco: boolean,
85
90
  generateBoundingVolumes: boolean,
91
+ shouldMergeMaterials: boolean,
86
92
  geoidHeightModel: Geoid,
87
93
  workerSource: {[key: string]: string}
88
94
  ): Promise<I3SConvertedResources[] | null> {
89
95
  const useCartesianPositions = generateBoundingVolumes;
90
- const materialAndTextureList: I3SMaterialWithTexture[] = convertMaterials(
91
- tileContent.gltf?.materials
96
+ const materialAndTextureList: I3SMaterialWithTexture[] = await convertMaterials(
97
+ tileContent.gltf?.materials,
98
+ shouldMergeMaterials
92
99
  );
93
100
 
94
101
  const dataForAttributesConversion = prepareDataForAttributesConversion(tileContent);
95
-
96
102
  const convertedAttributesMap: Map<string, ConvertedAttributes> = await convertAttributes(
97
103
  dataForAttributesConversion,
104
+ materialAndTextureList,
98
105
  useCartesianPositions
99
106
  );
100
107
  /** Usage of worker here brings more overhead than advantage */
@@ -110,28 +117,18 @@ export default async function convertB3dmToI3sGeometry(
110
117
  _generateBoundingVolumesFromGeometry(convertedAttributesMap, geoidHeightModel);
111
118
  }
112
119
 
113
- if (convertedAttributesMap.has('default')) {
114
- materialAndTextureList.push({
115
- material: getDefaultMaterial()
116
- });
117
- }
118
-
119
120
  const result: I3SConvertedResources[] = [];
120
- let {materials = []} = tileContent.gltf || {materials: []};
121
- if (!materials?.length) {
122
- materials.push({id: 'default'});
123
- }
124
- for (let i = 0; i < materials.length; i++) {
125
- const sourceMaterial = materials[i];
126
- if (!convertedAttributesMap.has(sourceMaterial.id)) {
121
+ for (const materialAndTexture of materialAndTextureList) {
122
+ const originarMaterialId = materialAndTexture.mergedMaterials[0].originalMaterialId;
123
+ if (!convertedAttributesMap.has(originarMaterialId)) {
127
124
  continue; // eslint-disable-line no-continue
128
125
  }
129
- const convertedAttributes = convertedAttributesMap.get(sourceMaterial.id);
126
+ const convertedAttributes = convertedAttributesMap.get(originarMaterialId);
130
127
  if (!convertedAttributes) {
131
128
  continue;
132
129
  }
133
- const {material, texture} = materialAndTextureList[i];
134
- const nodeId = addNodeToNodePage();
130
+ const {material, texture} = materialAndTexture;
131
+ const nodeId = await addNodeToNodePage();
135
132
  result.push(
136
133
  await _makeNodeResources({
137
134
  convertedAttributes,
@@ -192,8 +189,10 @@ function _generateBoundingVolumesFromGeometry(
192
189
  * @param params.tileContent - B3DM decoded content
193
190
  * @param params.nodeId - new node ID
194
191
  * @param params.featuresHashArray - hash array of features that is needed to not to mix up same features in parent and child nodes
195
- * @param params.attributesStorageInfo - attributes metadata from 3DSceneLayer json
192
+ * @param params.propertyTable - batch table (corresponding to feature attributes data)
193
+ * @param params.attributeStorageInfo - attributes metadata from 3DSceneLayer json
196
194
  * @param params.draco - is converter should create draco compressed geometry
195
+ * @param params.workerSource - source code of used workers
197
196
  * @returns Array of I3S node resources
198
197
  */
199
198
  async function _makeNodeResources({
@@ -221,7 +220,7 @@ async function _makeNodeResources({
221
220
  }): Promise<I3SConvertedResources> {
222
221
  const boundingVolumes = convertedAttributes.boundingVolumes;
223
222
  const vertexCount = convertedAttributes.positions.length / VALUES_PER_VERTEX;
224
- const {faceRange, featureIds, positions, normals, colors, texCoords, featureCount} =
223
+ const {faceRange, featureIds, positions, normals, colors, uvRegions, texCoords, featureCount} =
225
224
  generateAttributes(convertedAttributes);
226
225
 
227
226
  if (tileContent.batchTableJson) {
@@ -244,6 +243,7 @@ async function _makeNodeResources({
244
243
  normals.buffer,
245
244
  texture ? texCoords.buffer : new ArrayBuffer(0),
246
245
  colors.buffer,
246
+ uvRegions,
247
247
  typedFeatureIds.buffer,
248
248
  faceRange.buffer
249
249
  )
@@ -257,6 +257,7 @@ async function _makeNodeResources({
257
257
  normals,
258
258
  texCoords: texture ? texCoords : new Float32Array(0),
259
259
  colors,
260
+ uvRegions,
260
261
  featureIds,
261
262
  faceRange
262
263
  },
@@ -279,6 +280,7 @@ async function _makeNodeResources({
279
280
  geometry: fileBuffer,
280
281
  compressedGeometry,
281
282
  texture,
283
+ hasUvRegions: Boolean(uvRegions.length),
282
284
  sharedResources: getSharedResources(tileContent.gltf?.materials || [], nodeId),
283
285
  meshMaterial: material,
284
286
  vertexCount,
@@ -290,28 +292,35 @@ async function _makeNodeResources({
290
292
 
291
293
  /**
292
294
  * Convert attributes from the gltf nodes tree to i3s plain geometry
293
- * @param tileContent - 3d tile content
295
+ * @param attributesData - geometry attributes from gltf
296
+ * @param materialAndTextureList - array of data about materials and textures of the content
294
297
  * @param useCartesianPositions - convert positions to absolute cartesian coordinates instead of cartographic offsets.
295
298
  * Cartesian coordinates will be required for creating bounding voulmest from geometry positions
296
299
  * @returns map of converted geometry attributes
297
300
  */
298
301
  export async function convertAttributes(
299
302
  attributesData: B3DMAttributesData,
303
+ materialAndTextureList: I3SMaterialWithTexture[],
300
304
  useCartesianPositions: boolean
301
305
  ): Promise<Map<string, ConvertedAttributes>> {
302
- const {gltfMaterials, nodes, images, cartographicOrigin, cartesianModelMatrix} = attributesData;
306
+ const {nodes, images, cartographicOrigin, cartesianModelMatrix} = attributesData;
303
307
  const attributesMap = new Map<string, ConvertedAttributes>();
304
308
 
305
- for (const material of gltfMaterials || [{id: 'default'}]) {
306
- attributesMap.set(material.id, {
309
+ for (const materialAndTexture of materialAndTextureList) {
310
+ const attributes = {
307
311
  positions: new Float32Array(0),
308
312
  normals: new Float32Array(0),
309
313
  texCoords: new Float32Array(0),
310
314
  colors: new Uint8Array(0),
315
+ uvRegions: new Uint16Array(0),
311
316
  featureIndicesGroups: [],
312
317
  featureIndices: [],
313
- boundingVolumes: null
314
- });
318
+ boundingVolumes: null,
319
+ mergedMaterials: materialAndTexture.mergedMaterials
320
+ };
321
+ for (const mergedMaterial of materialAndTexture.mergedMaterials) {
322
+ attributesMap.set(mergedMaterial.originalMaterialId, attributes);
323
+ }
315
324
  }
316
325
 
317
326
  convertNodes(
@@ -348,7 +357,8 @@ export async function convertAttributes(
348
357
  * The goal is applying tranformation matrix for all children. Functions "convertNodes" and "convertNode" work together recursively.
349
358
  * @param nodes - gltf nodes array
350
359
  * @param images - gltf images array
351
- * @param tileContent - 3d tile content
360
+ * @param cartographicOrigin - cartographic origin of bounding volume
361
+ * @param cartesianModelMatrix - cartesian model matrix to convert coordinates to cartographic
352
362
  * @param attributesMap - for recursive concatenation of attributes
353
363
  * @param useCartesianPositions - convert positions to absolute cartesian coordinates instead of cartographic offsets.
354
364
  * Cartesian coordinates will be required for creating bounding voulmest from geometry positions
@@ -385,7 +395,7 @@ function convertNodes(
385
395
  * @param node
386
396
  * @param matrix
387
397
  */
388
- function getCompositeTransformationMatrix(node, matrix) {
398
+ function getCompositeTransformationMatrix(node: GLTFNodePostprocessed, matrix: Matrix4) {
389
399
  let transformationMatrix = matrix;
390
400
 
391
401
  const {matrix: nodeMatrix, rotation, scale, translation} = node;
@@ -413,7 +423,8 @@ function getCompositeTransformationMatrix(node, matrix) {
413
423
  * Convert all primitives of node and all children nodes
414
424
  * @param node - gltf node
415
425
  * @param images - gltf images array
416
- * @param {Object} tileContent - 3d tile content
426
+ * @param cartographicOrigin - cartographic origin of bounding volume
427
+ * @param cartesianModelMatrix - cartesian model matrix to convert coordinates to cartographic
417
428
  * @param {Map} attributesMap Map<{positions: Float32Array, normals: Float32Array, texCoords: Float32Array, colors: Uint8Array, featureIndices: Array}> - for recursive concatenation of
418
429
  * attributes
419
430
  * @param useCartesianPositions - convert positions to absolute cartesian coordinates instead of cartographic offsets.
@@ -457,9 +468,13 @@ function convertNode(
457
468
  }
458
469
 
459
470
  /**
460
- * Convert all primitives of node and all children nodes
461
- * @param mesh - gltf node
462
- * @param content - 3d tile content
471
+ * Convert all primitives of the mesh
472
+ * @param mesh - gltf mesh data
473
+ * @param images - gltf images array
474
+ * @param cartographicOrigin - cartographic origin of bounding volume
475
+ * @param cartesianModelMatrix - cartesian model matrix to convert coordinates to cartographic
476
+ * @param attributesMap Map<{positions: Float32Array, normals: Float32Array, texCoords: Float32Array, colors: Uint8Array, featureIndices: Array}> - for recursive concatenation of
477
+ * attributes
463
478
  * @param useCartesianPositions - convert positions to absolute cartesian coordinates instead of cartographic offsets.
464
479
  * Cartesian coordinates will be required for creating bounding voulmest from geometry positions
465
480
  * @param attributesMap Map<{positions: Float32Array, normals: Float32Array, texCoords: Float32Array, colors: Uint8Array, featureIndices: Array}> - for recursive concatenation of
@@ -478,8 +493,12 @@ function convertMesh(
478
493
  ) {
479
494
  for (const primitive of mesh.primitives) {
480
495
  let outputAttributes: ConvertedAttributes | null | undefined = null;
496
+ let materialUvRegion: Uint16Array | undefined;
481
497
  if (primitive.material) {
482
- outputAttributes = attributesMap.get(primitive.material.id);
498
+ outputAttributes = attributesMap.get(primitive.material.uniqueId);
499
+ materialUvRegion = outputAttributes?.mergedMaterials.find(
500
+ ({originalMaterialId}) => originalMaterialId === primitive.material?.uniqueId
501
+ )?.uvRegion;
483
502
  } else if (attributesMap.has('default')) {
484
503
  outputAttributes = attributesMap.get('default');
485
504
  }
@@ -525,6 +544,13 @@ function convertMesh(
525
544
  flattenColors(attributes.COLOR_0, primitive.indices?.value)
526
545
  );
527
546
 
547
+ if (materialUvRegion) {
548
+ outputAttributes.uvRegions = concatenateTypedArrays(
549
+ outputAttributes.uvRegions,
550
+ createUvRegion(materialUvRegion, primitive.indices?.value)
551
+ );
552
+ }
553
+
528
554
  outputAttributes.featureIndicesGroups = outputAttributes.featureIndicesGroups || [];
529
555
  outputAttributes.featureIndicesGroups.push(
530
556
  flattenBatchIds(getBatchIds(attributes, primitive, images), primitive.indices?.value)
@@ -675,6 +701,20 @@ function flattenColors(
675
701
  return newColors;
676
702
  }
677
703
 
704
+ /**
705
+ * Create per-vertex uv-region array
706
+ * @param materialUvRegion - uv-region fragment for a single vertex
707
+ * @param indices - geometry indices data
708
+ * @returns - uv-region array
709
+ */
710
+ function createUvRegion(materialUvRegion: Uint16Array, indices: Uint8Array): Uint16Array {
711
+ const result = new Uint16Array(indices.length * 4);
712
+ for (let i = 0; i < result.length; i += 4) {
713
+ result.set(materialUvRegion, i);
714
+ }
715
+ return result;
716
+ }
717
+
678
718
  /**
679
719
  * Flatten batchedIds list based on indices to right ordered array, compatible with i3s
680
720
  * @param batchedIds - gltf primitive
@@ -695,9 +735,9 @@ function flattenBatchIds(batchedIds: number[], indices: Uint8Array): number[] {
695
735
 
696
736
  /**
697
737
  * Get batchIds for featureIds creation
698
- * @param attributes
699
- * @param primitive
700
- * @param textures
738
+ * @param attributes - gltf accessors
739
+ * @param primitive - gltf primitive data
740
+ * @param images - gltf texture images
701
741
  */
702
742
  function getBatchIds(
703
743
  attributes: {
@@ -728,18 +768,134 @@ function getBatchIds(
728
768
  /**
729
769
  * Convert GLTF material to I3S material definitions and textures
730
770
  * @param sourceMaterials Source GLTF materials
771
+ * @param shouldMergeMaterials - if true - the converter will try to merge similar materials
772
+ * to be able to merge primitives having those materials
731
773
  * @returns Array of Couples I3SMaterialDefinition + texture content
732
774
  */
733
- function convertMaterials(
734
- sourceMaterials: GLTFMaterialPostprocessed[] = []
735
- ): I3SMaterialWithTexture[] {
736
- const result: I3SMaterialWithTexture[] = [];
775
+ async function convertMaterials(
776
+ sourceMaterials: GLTFMaterialPostprocessed[] = [],
777
+ shouldMergeMaterials: boolean
778
+ ): Promise<I3SMaterialWithTexture[]> {
779
+ let materials: I3SMaterialWithTexture[] = [];
737
780
  for (const sourceMaterial of sourceMaterials) {
738
- result.push(convertMaterial(sourceMaterial));
781
+ materials.push(convertMaterial(sourceMaterial));
782
+ }
783
+
784
+ if (shouldMergeMaterials) {
785
+ materials = await mergeAllMaterials(materials);
786
+ }
787
+
788
+ return materials;
789
+ }
790
+
791
+ /**
792
+ * Merge materials when possible
793
+ * @param materials materials array
794
+ * @returns merged materials array
795
+ */
796
+ async function mergeAllMaterials(
797
+ materials: I3SMaterialWithTexture[]
798
+ ): Promise<I3SMaterialWithTexture[]> {
799
+ const result: I3SMaterialWithTexture[] = [];
800
+ while (materials.length > 0) {
801
+ let newMaterial = materials.splice(0, 1)[0];
802
+ const mergedIndices: number[] = [];
803
+ for (let i = 0; i < materials.length; i++) {
804
+ const material = materials[i];
805
+ if (
806
+ (newMaterial.texture && material.texture) ||
807
+ (!newMaterial.texture && !material.texture)
808
+ ) {
809
+ newMaterial = await mergeMaterials(newMaterial, material);
810
+ mergedIndices.push(i);
811
+ }
812
+ }
813
+ if (newMaterial.texture && mergedIndices.length) {
814
+ const newWidth = newMaterial.mergedMaterials?.reduce(
815
+ (accum, {textureSize}) => accum + (textureSize?.width || 0),
816
+ 0
817
+ );
818
+ const newHeight = newMaterial.mergedMaterials?.reduce(
819
+ (accum, {textureSize}) => Math.max(accum, textureSize?.height || 0),
820
+ 0
821
+ );
822
+ let currentX = -1;
823
+ for (const aTextureMetadata of newMaterial.mergedMaterials) {
824
+ if (aTextureMetadata.textureSize) {
825
+ const newX =
826
+ currentX +
827
+ 1 +
828
+ (aTextureMetadata.textureSize.width / newWidth) *
829
+ 2 ** (Uint16Array.BYTES_PER_ELEMENT * 8) -
830
+ 1;
831
+ aTextureMetadata.uvRegion = new Uint16Array([
832
+ currentX + 1,
833
+ 0,
834
+ newX,
835
+ (aTextureMetadata.textureSize.height / newHeight) *
836
+ 2 ** (Uint16Array.BYTES_PER_ELEMENT * 8) -
837
+ 1
838
+ ]);
839
+ currentX = newX;
840
+ }
841
+ }
842
+
843
+ newMaterial.texture.image.width = newWidth;
844
+ newMaterial.texture.image.height = newHeight;
845
+ }
846
+ for (const index of mergedIndices.reverse()) {
847
+ materials.splice(index, 1);
848
+ }
849
+ result.push(newMaterial);
850
+ }
851
+
852
+ if (!result.length) {
853
+ result.push({
854
+ material: getDefaultMaterial(),
855
+ mergedMaterials: [{originalMaterialId: 'default'}]
856
+ });
739
857
  }
740
858
  return result;
741
859
  }
742
860
 
861
+ /**
862
+ * Merge 2 materials including texture
863
+ * @param material1
864
+ * @param material2
865
+ * @returns
866
+ */
867
+ async function mergeMaterials(
868
+ material1: I3SMaterialWithTexture,
869
+ material2: I3SMaterialWithTexture
870
+ ): Promise<I3SMaterialWithTexture> {
871
+ if (
872
+ material1.texture?.bufferView &&
873
+ material2.texture?.bufferView &&
874
+ material1.mergedMaterials &&
875
+ material2.mergedMaterials
876
+ ) {
877
+ const buffer1 = Buffer.from(material1.texture.bufferView.data);
878
+ const buffer2 = Buffer.from(material2.texture.bufferView.data);
879
+ try {
880
+ // @ts-ignore
881
+ const {joinImages} = await import('join-images');
882
+ const sharpData = await joinImages([buffer1, buffer2], {direction: 'horizontal'});
883
+ material1.texture.bufferView.data = await sharpData
884
+ .toFormat(material1.texture.mimeType === 'image/png' ? 'png' : 'jpeg')
885
+ .toBuffer();
886
+ } catch (error) {
887
+ console.log(
888
+ '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)'
889
+ );
890
+ throw error;
891
+ }
892
+ // @ts-ignore
893
+ material1.material.pbrMetallicRoughness.baseColorTexture.textureSetDefinitionId = 1;
894
+ }
895
+ material1.mergedMaterials = material1.mergedMaterials.concat(material2.mergedMaterials);
896
+ return material1;
897
+ }
898
+
743
899
  /**
744
900
  * Convert texture and material from gltf 2.0 material object
745
901
  * @param sourceMaterial - material object
@@ -778,6 +934,9 @@ function convertMaterial(sourceMaterial: GLTFMaterialPostprocessed): I3SMaterial
778
934
  };
779
935
  }
780
936
 
937
+ const uniqueId = uuidv4();
938
+ sourceMaterial.uniqueId = uniqueId;
939
+ let mergedMaterials: MergedMaterial[] = [{originalMaterialId: uniqueId}];
781
940
  if (!texture) {
782
941
  // Should use default baseColorFactor if it is not present in source material
783
942
  // https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#reference-pbrmetallicroughness
@@ -789,9 +948,11 @@ function convertMaterial(sourceMaterial: GLTFMaterialPostprocessed): I3SMaterial
789
948
  number,
790
949
  number
791
950
  ]) || undefined;
951
+ } else {
952
+ mergedMaterials[0].textureSize = {width: texture.image.width, height: texture.image.height};
792
953
  }
793
954
 
794
- return {material, texture};
955
+ return {material, texture, mergedMaterials};
795
956
  }
796
957
 
797
958
  /**
@@ -968,13 +1129,13 @@ function extractSharedResourcesTextureInfo(
968
1129
  }
969
1130
 
970
1131
  /**
971
- * Formula for counting imageId:
1132
+ * Formula for calculating imageId:
972
1133
  * https://github.com/Esri/i3s-spec/blob/0a6366a9249b831db8436c322f8d27521e86cf07/format/Indexed%203d%20Scene%20Layer%20Format%20Specification.md#generating-image-ids
973
1134
  * @param texture - texture image info
974
1135
  * @param nodeId - I3S node ID
975
1136
  * @returns calculate image ID according to the spec
976
1137
  */
977
- function generateImageId(texture: GLTFTexturePostprocessed, nodeId) {
1138
+ function generateImageId(texture: GLTFTexturePostprocessed, nodeId: number) {
978
1139
  const {width, height} = texture.source?.image;
979
1140
  const levelCountOfTexture = 1;
980
1141
  const indexOfLevel = 0;
@@ -1076,8 +1237,8 @@ function replaceIndicesByUnique(indicesArray, featureMap) {
1076
1237
 
1077
1238
  /**
1078
1239
  * Convert property table data to attribute buffers.
1079
- * @param {Object} propertyTable - table with metadata for particular feature.
1080
1240
  * @param {Array} featureIds
1241
+ * @param {Object} propertyTable - table with metadata for particular feature.
1081
1242
  * @param {Array} attributeStorageInfo
1082
1243
  * @returns {Array} - Array of file buffers.
1083
1244
  */
@@ -1227,7 +1388,7 @@ async function generateCompressedGeometry(
1227
1388
  attributes,
1228
1389
  dracoWorkerSoure
1229
1390
  ) {
1230
- const {positions, normals, texCoords, colors, featureIds, faceRange} = attributes;
1391
+ const {positions, normals, texCoords, colors, uvRegions, featureIds, faceRange} = attributes;
1231
1392
  const indices = new Uint32Array(vertexCount);
1232
1393
 
1233
1394
  for (let index = 0; index < indices.length; index++) {
@@ -1246,6 +1407,7 @@ async function generateCompressedGeometry(
1246
1407
  colors: TypedArray;
1247
1408
  'feature-index': TypedArray;
1248
1409
  texCoords?: TypedArray;
1410
+ 'uv-region'?: TypedArray;
1249
1411
  } = {
1250
1412
  positions,
1251
1413
  normals,
@@ -1264,6 +1426,13 @@ async function generateCompressedGeometry(
1264
1426
  }
1265
1427
  };
1266
1428
 
1429
+ if (uvRegions.length) {
1430
+ compressedAttributes['uv-region'] = uvRegions;
1431
+ attributesMetadata['uv-region'] = {
1432
+ 'i3s-attribute-type': 'uv-region'
1433
+ };
1434
+ }
1435
+
1267
1436
  return encode({attributes: compressedAttributes, indices}, DracoWriterWorker, {
1268
1437
  ...DracoWriterWorker.options,
1269
1438
  source: dracoWorkerSoure,
@@ -28,7 +28,6 @@ function getB3DMAttributesWithoutBufferView(attributes: AttributesObject): Attri
28
28
  * @returns
29
29
  */
30
30
  export function prepareDataForAttributesConversion(tileContent: B3DMContent): B3DMAttributesData {
31
- const gltfMaterials = tileContent.gltf?.materials?.map((material) => ({id: material.id}));
32
31
  let nodes =
33
32
  tileContent.gltf?.scene?.nodes ||
34
33
  tileContent.gltf?.scenes?.[0]?.nodes ||
@@ -64,7 +63,6 @@ export function prepareDataForAttributesConversion(tileContent: B3DMContent): B3
64
63
  const cartesianModelMatrix = tileContent.cartesianModelMatrix;
65
64
 
66
65
  return {
67
- gltfMaterials,
68
66
  nodes,
69
67
  images,
70
68
  cartographicOrigin,
@@ -90,7 +88,8 @@ function prepareNodes(nodes: GLTFNodePostprocessed[]): void {
90
88
  indices: {value: primitive?.indices?.value},
91
89
  attributes: getB3DMAttributesWithoutBufferView(primitive.attributes),
92
90
  material: {
93
- id: primitive?.material?.id
91
+ id: primitive?.material?.id,
92
+ uniqueId: primitive?.material?.uniqueId
94
93
  }
95
94
  }))
96
95
  }