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

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 (68) hide show
  1. package/dist/3d-tiles-converter/3d-tiles-converter.d.ts.map +1 -1
  2. package/dist/3d-tiles-converter/3d-tiles-converter.js +4 -4
  3. package/dist/3d-tiles-converter/3d-tiles-converter.js.map +1 -1
  4. package/dist/3d-tiles-converter/helpers/b3dm-converter.d.ts.map +1 -1
  5. package/dist/3d-tiles-converter/helpers/b3dm-converter.js +8 -0
  6. package/dist/3d-tiles-converter/helpers/b3dm-converter.js.map +1 -1
  7. package/dist/3d-tiles-converter/json-templates/tileset.d.ts.map +1 -1
  8. package/dist/3d-tiles-converter/json-templates/tileset.js +3 -0
  9. package/dist/3d-tiles-converter/json-templates/tileset.js.map +1 -1
  10. package/dist/constants.d.ts +1 -0
  11. package/dist/constants.d.ts.map +1 -1
  12. package/dist/constants.js +1 -0
  13. package/dist/constants.js.map +1 -1
  14. package/dist/converter-cli.js +3 -3
  15. package/dist/converter-cli.js.map +1 -1
  16. package/dist/converter.min.cjs +131 -220
  17. package/dist/deps-installer/deps-installer.js +1 -1
  18. package/dist/i3s-converter/helpers/attribute-metadata-info.d.ts +10 -0
  19. package/dist/i3s-converter/helpers/attribute-metadata-info.d.ts.map +1 -1
  20. package/dist/i3s-converter/helpers/attribute-metadata-info.js +5 -0
  21. package/dist/i3s-converter/helpers/attribute-metadata-info.js.map +1 -1
  22. package/dist/i3s-converter/helpers/node-index-document.d.ts +2 -1
  23. package/dist/i3s-converter/helpers/node-index-document.d.ts.map +1 -1
  24. package/dist/i3s-converter/helpers/node-index-document.js +6 -8
  25. package/dist/i3s-converter/helpers/node-index-document.js.map +1 -1
  26. package/dist/i3s-converter/helpers/progress.js +1 -1
  27. package/dist/i3s-converter/helpers/progress.js.map +1 -1
  28. package/dist/i3s-converter/i3s-converter.d.ts +32 -0
  29. package/dist/i3s-converter/i3s-converter.d.ts.map +1 -1
  30. package/dist/i3s-converter/i3s-converter.js +195 -47
  31. package/dist/i3s-converter/i3s-converter.js.map +1 -1
  32. package/dist/i3s-converter/types.d.ts +7 -0
  33. package/dist/i3s-converter/types.d.ts.map +1 -1
  34. package/dist/i3s-converter/types.js +8 -0
  35. package/dist/i3s-converter/types.js.map +1 -1
  36. package/dist/i3s-server/bin/i3s-server.min.cjs +76 -76
  37. package/dist/index.cjs +638 -139
  38. package/dist/lib/utils/compress-util.d.ts +0 -37
  39. package/dist/lib/utils/compress-util.d.ts.map +1 -1
  40. package/dist/lib/utils/compress-util.js +1 -149
  41. package/dist/lib/utils/compress-util.js.map +1 -1
  42. package/dist/lib/utils/conversion-dump.d.ts +131 -0
  43. package/dist/lib/utils/conversion-dump.d.ts.map +1 -0
  44. package/dist/lib/utils/conversion-dump.js +191 -0
  45. package/dist/lib/utils/conversion-dump.js.map +1 -0
  46. package/dist/lib/utils/statistic-utills.js +1 -1
  47. package/dist/lib/utils/statistic-utills.js.map +1 -1
  48. package/dist/lib/utils/write-queue.d.ts +6 -1
  49. package/dist/lib/utils/write-queue.d.ts.map +1 -1
  50. package/dist/lib/utils/write-queue.js +15 -3
  51. package/dist/lib/utils/write-queue.js.map +1 -1
  52. package/dist/pgm-loader.js +1 -1
  53. package/dist/slpk-extractor.min.cjs +31 -31
  54. package/package.json +16 -16
  55. package/src/3d-tiles-converter/3d-tiles-converter.ts +5 -4
  56. package/src/3d-tiles-converter/helpers/b3dm-converter.ts +19 -0
  57. package/src/3d-tiles-converter/json-templates/tileset.ts +3 -0
  58. package/src/constants.ts +1 -0
  59. package/src/converter-cli.ts +3 -3
  60. package/src/i3s-converter/helpers/attribute-metadata-info.ts +16 -0
  61. package/src/i3s-converter/helpers/node-index-document.ts +18 -8
  62. package/src/i3s-converter/helpers/progress.ts +1 -1
  63. package/src/i3s-converter/i3s-converter.ts +385 -93
  64. package/src/i3s-converter/types.ts +8 -0
  65. package/src/lib/utils/compress-util.ts +1 -264
  66. package/src/lib/utils/conversion-dump.ts +325 -0
  67. package/src/lib/utils/statistic-utills.ts +1 -1
  68. package/src/lib/utils/write-queue.ts +15 -2
@@ -17,7 +17,8 @@ import type {
17
17
  BoundingVolumes,
18
18
  MaxScreenThresholdSQ,
19
19
  NodeInPage,
20
- Attribute
20
+ Attribute,
21
+ Node3DIndexDocument
21
22
  } from '@loaders.gl/i3s';
22
23
  import {load, encode, isBrowser} from '@loaders.gl/core';
23
24
  import {CesiumIonLoader, Tiles3DLoader} from '@loaders.gl/3d-tiles';
@@ -30,12 +31,7 @@ import md5 from 'md5';
30
31
 
31
32
  import NodePages from './helpers/node-pages';
32
33
  import {writeFile, removeDir, writeFileForSlpk, removeFile} from '../lib/utils/file-utils';
33
- import {
34
- compressFileWithGzip,
35
- compressWithChildProcess
36
- // generateHash128FromZip,
37
- // addFileToZip
38
- } from '../lib/utils/compress-util';
34
+ import {compressFileWithGzip} from '../lib/utils/compress-util';
39
35
  import {calculateFilesSize, timeConverter} from '../lib/utils/statistic-utills';
40
36
  import convertB3dmToI3sGeometry, {getPropertyTable} from './helpers/geometry-converter';
41
37
  import {
@@ -51,7 +47,7 @@ import {GEOMETRY_DEFINITION as geometryDefinitionTemlate} from './json-templates
51
47
  import {SHARED_RESOURCES as sharedResourcesTemplate} from './json-templates/shared-resources';
52
48
  import {validateNodeBoundingVolumes} from './helpers/node-debug';
53
49
  import {KTX2BasisWriterWorker} from '@loaders.gl/textures';
54
- import {FileHandleFile, LoaderWithParser} from '@loaders.gl/loader-utils';
50
+ import {LoaderWithParser} from '@loaders.gl/loader-utils';
55
51
  import {I3SMaterialDefinition, TextureSetDefinitionFormats} from '@loaders.gl/i3s';
56
52
  import {ImageWriter} from '@loaders.gl/images';
57
53
  import {GLTFImagePostprocessed} from '@loaders.gl/gltf';
@@ -59,6 +55,7 @@ import {
59
55
  GLTFPrimitiveModeString,
60
56
  I3SConvertedResources,
61
57
  PreprocessData,
58
+ ResourceType,
62
59
  SharedResourcesArrays
63
60
  } from './types';
64
61
  import {WorkerFarm} from '@loaders.gl/worker-utils';
@@ -81,7 +78,8 @@ import {createBoundingVolume} from '@loaders.gl/tiles';
81
78
  import {TraversalConversionProps, traverseDatasetWith} from './helpers/tileset-traversal';
82
79
  import {analyzeTileContent, mergePreprocessData} from './helpers/preprocess-3d-tiles';
83
80
  import {Progress} from './helpers/progress';
84
- import {addOneFile, composeHashFile} from '@loaders.gl/zip';
81
+ import {composeHashFile, createZip} from '@loaders.gl/zip';
82
+ import {ConversionDump, ConversionDumpOptions, DumpMetadata} from '../lib/utils/conversion-dump';
85
83
 
86
84
  const ION_DEFAULT_TOKEN = process.env?.IonToken;
87
85
  const HARDCODED_NODES_PER_PAGE = 64;
@@ -136,13 +134,14 @@ export default class I3SConverter {
136
134
  generateBoundingVolumes: boolean;
137
135
  layersHasTexture: boolean;
138
136
  workerSource: {[key: string]: string} = {};
139
- writeQueue: WriteQueue<WriteQueueItem> = new WriteQueue();
137
+ writeQueue: WriteQueue<WriteQueueItem> = new WriteQueue(new ConversionDump());
140
138
  compressList: string[] | null = null;
141
139
  preprocessData: PreprocessData = {
142
140
  meshTopologyTypes: new Set(),
143
141
  metadataClasses: new Set()
144
142
  };
145
143
  progresses: Record<string, Progress> = {};
144
+ conversionDump: ConversionDump;
146
145
 
147
146
  constructor() {
148
147
  this.attributeMetadataInfo = new AttributeMetadataInfo();
@@ -165,6 +164,7 @@ export default class I3SConverter {
165
164
  this.generateBoundingVolumes = false;
166
165
  this.layersHasTexture = false;
167
166
  this.compressList = null;
167
+ this.conversionDump = new ConversionDump();
168
168
  }
169
169
 
170
170
  /**
@@ -228,6 +228,8 @@ export default class I3SConverter {
228
228
  analyze = false
229
229
  } = options;
230
230
  this.options = {
231
+ outputPath,
232
+ tilesetName,
231
233
  maxDepth,
232
234
  slpk,
233
235
  sevenZipExe,
@@ -247,7 +249,7 @@ export default class I3SConverter {
247
249
  this.generateTextures = Boolean(generateTextures);
248
250
  this.generateBoundingVolumes = Boolean(generateBoundingVolumes);
249
251
 
250
- this.writeQueue = new WriteQueue();
252
+ this.writeQueue = new WriteQueue(this.conversionDump);
251
253
  this.writeQueue.startListening();
252
254
 
253
255
  console.log('Loading egm file...'); // eslint-disable-line
@@ -411,18 +413,49 @@ export default class I3SConverter {
411
413
  */
412
414
  private async _createAndSaveTileset(outputPath: string, tilesetName: string): Promise<void> {
413
415
  const tilesetPath = join(`${outputPath}`, `${tilesetName}`);
416
+
417
+ await this.conversionDump.createDump(this.options as ConversionDumpOptions);
418
+ if (this.conversionDump.restored && this.options.inquirer) {
419
+ const result = await this.options.inquirer.prompt([
420
+ {
421
+ name: 'resumeConversion',
422
+ type: 'confirm',
423
+ message:
424
+ 'Dump file of the previous conversion exists, do you want to resume that conversion?'
425
+ }
426
+ ]);
427
+ if (!result.resumeConversion) {
428
+ this.conversionDump.reset();
429
+ }
430
+ }
431
+
432
+ this.layers0Path = join(tilesetPath, 'SceneServer', 'layers', '0');
433
+
414
434
  // Removing the tilesetPath needed to exclude erroneous files after conversion
435
+ const removePath = this.conversionDump.restored
436
+ ? join(this.layers0Path, 'nodepages')
437
+ : tilesetPath;
415
438
  try {
416
- await removeDir(tilesetPath);
439
+ await removeDir(removePath);
417
440
  } catch (e) {
418
441
  // do nothing
419
442
  }
420
443
 
421
- this.layers0Path = join(tilesetPath, 'SceneServer', 'layers', '0');
444
+ if (this.conversionDump.restored && this.conversionDump.attributeMetadataInfo) {
445
+ this.attributeMetadataInfo.fromObject(this.conversionDump.attributeMetadataInfo);
446
+ }
422
447
 
423
448
  this.materialDefinitions = [];
424
449
  this.materialMap = new Map();
425
450
 
451
+ if (this.conversionDump.restored && this.conversionDump.materialDefinitions) {
452
+ for (let i = 0; i < this.conversionDump.materialDefinitions.length; i++) {
453
+ const hash = md5(JSON.stringify(this.conversionDump.materialDefinitions[i]));
454
+ this.materialMap.set(hash, i);
455
+ }
456
+ this.materialDefinitions = this.conversionDump.materialDefinitions;
457
+ }
458
+
426
459
  const sourceRootTile: Tiles3DTileJSONPostprocessed = this.sourceTileset!.root!;
427
460
  const sourceBoundingVolume = createBoundingVolume(
428
461
  sourceRootTile.boundingVolume,
@@ -467,6 +500,10 @@ export default class I3SConverter {
467
500
  this.layers0!.layerType = _3D_OBJECT_LAYER_TYPE;
468
501
  }
469
502
 
503
+ if (this.conversionDump.restored && this.conversionDump.textureSetDefinitions) {
504
+ this.layers0!.textureSetDefinitions = this.conversionDump.textureSetDefinitions;
505
+ }
506
+
470
507
  this.layers0!.materialDefinitions = this.materialDefinitions;
471
508
  // @ts-ignore
472
509
  this.layers0.geometryDefinitions = transform(
@@ -554,38 +591,16 @@ export default class I3SConverter {
554
591
  * @param tilesetPath - Path to save file
555
592
  */
556
593
  private async _createSlpk(tilesetPath: string): Promise<void> {
594
+ await this.conversionDump.deleteDumpFile();
557
595
  if (this.options.slpk) {
558
596
  const slpkTilesetPath = join(tilesetPath, 'SceneServer', 'layers', '0');
559
597
  const slpkFileName = `${tilesetPath}.slpk`;
560
- await compressWithChildProcess(
561
- slpkTilesetPath,
562
- slpkFileName,
563
- 0,
564
- '.',
565
- this.options.sevenZipExe
566
- );
567
598
 
568
- const hashTable = await composeHashFile(new FileHandleFile(slpkFileName));
569
- await addOneFile(slpkFileName, hashTable, '@specialIndexFileHASH128@');
570
-
571
- // TODO: `addFileToZip` corrupts archive so it can't be validated with windows i3s_converter.exe
572
- // const fileHash128Path = `${tilesetPath}/@specialIndexFileHASH128@`;
573
- // try {
574
- // await generateHash128FromZip(slpkFileName, fileHash128Path);
575
- // await addFileToZip(
576
- // tilesetPath,
577
- // '@specialIndexFileHASH128@',
578
- // slpkFileName,
579
- // this.options.sevenZipExe
580
- // );
581
- // } catch (error) {
582
- // if (error.code === FS_FILE_TOO_LARGE) {
583
- // console.warn(`${slpkFileName} file is too big to generate a hash`); // eslint-disable-line
584
- // } else {
585
- // console.error(error); // eslint-disable-line
586
- // }
587
- // }
588
- // All converted files are contained in slpk now they can be deleted
599
+ await createZip(slpkTilesetPath, slpkFileName, async (fileList) => ({
600
+ path: '@specialIndexFileHASH128@',
601
+ file: await composeHashFile(fileList)
602
+ }));
603
+
589
604
  try {
590
605
  await removeDir(tilesetPath);
591
606
  } catch (e) {
@@ -618,13 +633,20 @@ export default class I3SConverter {
618
633
  if (sourceTile.id) {
619
634
  console.log(`[convert]: ${sourceTile.id}`); // eslint-disable-line
620
635
  }
636
+
621
637
  const {parentNodes, transform} = traversalProps;
622
638
  let transformationMatrix: Matrix4 = transform.clone();
623
639
  if (sourceTile.transform) {
624
640
  transformationMatrix = transformationMatrix.multiplyRight(sourceTile.transform);
625
641
  }
626
642
  const parentNode = parentNodes[0];
627
- const childNodes = await this._createNode(parentNode, sourceTile, transformationMatrix);
643
+ const restoreResult = await this._restoreNode(parentNode, sourceTile, transformationMatrix);
644
+ let childNodes;
645
+ if (restoreResult === null) {
646
+ childNodes = await this._createNode(parentNode, sourceTile, transformationMatrix);
647
+ } else {
648
+ childNodes = restoreResult;
649
+ }
628
650
  await parentNode.addChildren(childNodes);
629
651
 
630
652
  const newTraversalProps: TraversalConversionProps = {
@@ -642,8 +664,9 @@ export default class I3SConverter {
642
664
  timeRemainingString = `${timeRemainingStringBasedOnCount} left`;
643
665
  }
644
666
 
645
- let percentString = this.progresses[PROGRESS_PHASE1_COUNT].getPercentString();
646
- console.log(`[converted ${percentString}%, ${timeRemainingString}]: ${sourceTile.id}`); // eslint-disable-line
667
+ const percentString = this.progresses[PROGRESS_PHASE1_COUNT].getPercentString();
668
+ const progressString = percentString ? ` ${percentString}%, ${timeRemainingString}` : '';
669
+ console.log(`[converted${progressString}]: ${sourceTile.id}`); // eslint-disable-line
647
670
  }
648
671
  return newTraversalProps;
649
672
  }
@@ -667,6 +690,114 @@ export default class I3SConverter {
667
690
  }
668
691
  }
669
692
 
693
+ /**
694
+ * Generate NodeIndexDocument
695
+ * @param boundingVolumes - Bounding volumes
696
+ * @param resources - converted or dumped node resources data
697
+ * @param parentNode - 3DNodeIndexDocument of parent node
698
+ * @param sourceTile - source 3DTile data
699
+ * @param isDumped - indicator if the node is dumped
700
+ * @return NodeIndexDocument, nodeInPage and node data
701
+ */
702
+ private async _generateNodeIndexDocument(
703
+ boundingVolumes: BoundingVolumes,
704
+ resources: I3SConvertedResources | DumpMetadata,
705
+ parentNode: NodeIndexDocument,
706
+ sourceTile: Tiles3DTileJSONPostprocessed,
707
+ isDumped: boolean
708
+ ): Promise<{node: NodeIndexDocument; nodeInPage: NodeInPage; nodeData: Node3DIndexDocument}> {
709
+ this.layersHasTexture =
710
+ this.layersHasTexture ||
711
+ Boolean(
712
+ ('texture' in resources && resources.texture) ||
713
+ ('texelCountHint' in resources && resources.texelCountHint)
714
+ );
715
+
716
+ if (this.generateBoundingVolumes && resources.boundingVolumes) {
717
+ boundingVolumes = resources.boundingVolumes;
718
+ }
719
+
720
+ const lodSelection = convertGeometricErrorToScreenThreshold(sourceTile, boundingVolumes);
721
+ const maxScreenThresholdSQ = lodSelection.find(
722
+ (val) => val.metricType === 'maxScreenThresholdSQ'
723
+ ) || {maxError: 0};
724
+
725
+ if (isDumped) {
726
+ const draftObb = {
727
+ center: [],
728
+ halfSize: [],
729
+ quaternion: []
730
+ };
731
+ await this.nodePages.push({index: 0, obb: draftObb}, parentNode.inPageId);
732
+ }
733
+
734
+ const nodeInPage = await this._updateNodeInNodePages(
735
+ maxScreenThresholdSQ,
736
+ boundingVolumes,
737
+ sourceTile,
738
+ parentNode.inPageId,
739
+ resources
740
+ );
741
+
742
+ const nodeData = await NodeIndexDocument.createNodeIndexDocument(
743
+ parentNode,
744
+ boundingVolumes,
745
+ lodSelection,
746
+ nodeInPage,
747
+ resources
748
+ );
749
+
750
+ const node = await new NodeIndexDocument(nodeInPage.index, this).addData(nodeData);
751
+ return {node, nodeInPage, nodeData};
752
+ }
753
+
754
+ /**
755
+ * Restore 3DNodeIndexDocument from a comversion dump file
756
+ * @param parentNode - 3DNodeIndexDocument of parent node
757
+ * @param sourceTile - source 3DTile data
758
+ * @param transformationMatrix - transformation matrix of the current tile, calculated recursively multiplying
759
+ * transform of all parent tiles and transform of the current tile
760
+ */
761
+ private async _restoreNode(
762
+ parentNode: NodeIndexDocument,
763
+ sourceTile: Tiles3DTileJSONPostprocessed,
764
+ transformationMatrix: Matrix4
765
+ ): Promise<null | NodeIndexDocument[]> {
766
+ this._checkAddRefinementTypeForTile(sourceTile);
767
+ await this._updateTilesetOptions();
768
+ if (
769
+ this.conversionDump.restored &&
770
+ sourceTile.id &&
771
+ this.conversionDump.isFileConversionComplete(sourceTile.id)
772
+ ) {
773
+ const sourceBoundingVolume = createBoundingVolume(
774
+ sourceTile.boundingVolume,
775
+ transformationMatrix,
776
+ null
777
+ );
778
+ let boundingVolumes = createBoundingVolumes(sourceBoundingVolume, this.geoidHeightModel!);
779
+ const nodes: NodeIndexDocument[] = [];
780
+ for (const convertedNode of this.conversionDump.tilesConverted[sourceTile.id].nodes) {
781
+ const {node} = await this._generateNodeIndexDocument(
782
+ boundingVolumes,
783
+ {
784
+ ...(convertedNode.dumpMetadata as DumpMetadata),
785
+ nodeId: convertedNode.nodeId
786
+ } as I3SConvertedResources | DumpMetadata,
787
+ parentNode,
788
+ sourceTile,
789
+ true
790
+ );
791
+ nodes.push(node);
792
+ }
793
+ return nodes;
794
+ } else if (this.conversionDump.restored && sourceTile.id) {
795
+ //clear existing record in a dump
796
+ this.conversionDump.clearDumpRecord(sourceTile.id);
797
+ }
798
+ return null;
799
+ }
800
+
670
801
  /**
671
802
  * Convert tile to one or more I3S nodes
672
803
  * @param parentNode - 3DNodeIndexDocument of parent node
@@ -700,6 +831,12 @@ export default class I3SConverter {
700
831
  const propertyTable = getPropertyTable(tileContent, this.options.metadataClass);
701
832
  this.createAttributeStorageInfo(tileContent, propertyTable);
702
833
 
834
+ this.conversionDump.attributeMetadataInfo = {
835
+ attributeStorageInfo: this.attributeMetadataInfo.attributeStorageInfo,
836
+ fields: this.attributeMetadataInfo.fields,
837
+ popupInfo: this.attributeMetadataInfo.popupInfo
838
+ };
839
+
703
840
  const resourcesData = await this._convertResources(
704
841
  sourceTile,
705
842
  transformationMatrix,
@@ -726,37 +863,34 @@ export default class I3SConverter {
726
863
  };
727
864
 
728
865
  for (const resources of resourcesData || [emptyResources]) {
729
- this.layersHasTexture = this.layersHasTexture || Boolean(resources.texture);
730
-
731
- if (this.generateBoundingVolumes && resources.boundingVolumes) {
732
- boundingVolumes = resources.boundingVolumes;
733
- }
734
-
735
- const lodSelection = convertGeometricErrorToScreenThreshold(sourceTile, boundingVolumes);
736
- const maxScreenThresholdSQ = lodSelection.find(
737
- (val) => val.metricType === 'maxScreenThresholdSQ'
738
- ) || {maxError: 0};
739
-
740
- const nodeInPage = await this._updateNodeInNodePages(
741
- maxScreenThresholdSQ,
866
+ const {node, nodeInPage, nodeData} = await this._generateNodeIndexDocument(
742
867
  boundingVolumes,
743
- sourceTile,
744
- parentNode.inPageId,
745
- resources
746
- );
747
-
748
- const nodeData = await NodeIndexDocument.createNodeIndexDocument(
868
+ resources,
749
869
  parentNode,
750
- boundingVolumes,
751
- lodSelection,
752
- nodeInPage,
753
- resources
870
+ sourceTile,
871
+ false
754
872
  );
755
- const node = await new NodeIndexDocument(nodeInPage.index, this).addData(nodeData);
756
873
  nodes.push(node);
757
874
 
758
875
  if (nodeInPage.mesh) {
759
- await this._writeResources(resources, node.id);
876
+ //update a record in a dump file
877
+ if (sourceTile.id) {
878
+ const dumpMetadata = {
879
+ boundingVolumes: resources.boundingVolumes,
880
+ attributesCount: resources.attributes?.length,
881
+ featureCount: resources.featureCount,
882
+ geometry: Boolean(resources.geometry),
883
+ hasUvRegions: resources.hasUvRegions,
884
+ materialId: nodeInPage.mesh.material.definition,
885
+ texelCountHint: nodeInPage.mesh.material.texelCountHint,
886
+ vertexCount: resources.vertexCount
887
+ };
888
+ this.conversionDump.setMaterialsDefinitions(this.materialDefinitions);
889
+ await this.conversionDump.addNode(sourceTile.id, nodeInPage.index, dumpMetadata);
890
+ }
891
+
892
+ //write resources
893
+ await this._writeResources(resources, node.id, sourceTile);
760
894
  }
761
895
 
762
896
  if (this.validate) {
@@ -839,9 +973,9 @@ export default class I3SConverter {
839
973
  boundingVolumes: BoundingVolumes,
840
974
  sourceTile: Tiles3DTileJSONPostprocessed,
841
975
  parentId: number,
842
- resources: I3SConvertedResources
976
+ resources: I3SConvertedResources | DumpMetadata
843
977
  ): Promise<NodeInPage> {
844
- const {meshMaterial, texture, vertexCount, featureCount, geometry, hasUvRegions} = resources;
978
+ const {vertexCount, featureCount, geometry, hasUvRegions} = resources;
845
979
  const nodeInPage: NodeInPage = {
846
980
  index: 0,
847
981
  lodThreshold: maxScreenThresholdSQ.maxError,
@@ -851,7 +985,13 @@ export default class I3SConverter {
851
985
  if (geometry && this.isContentSupported(sourceTile)) {
852
986
  nodeInPage.mesh = {
853
987
  geometry: {
854
- definition: this.findOrCreateGeometryDefinition(Boolean(texture), hasUvRegions),
988
+ definition: this.findOrCreateGeometryDefinition(
989
+ Boolean(
990
+ ('texture' in resources && resources.texture) ||
991
+ ('texelCountHint' in resources && resources.texelCountHint)
992
+ ),
993
+ hasUvRegions
994
+ ),
855
995
  resource: 0
856
996
  },
857
997
  attribute: {
@@ -863,7 +1003,7 @@ export default class I3SConverter {
863
1003
  };
864
1004
  }
865
1005
 
866
- let nodeId = resources.nodeId;
1006
+ let nodeId = 'nodeId' in resources ? resources.nodeId : undefined;
867
1007
  let node: NodeInPage;
868
1008
  if (!nodeId) {
869
1009
  node = await this.nodePages.push(nodeInPage, parentId);
@@ -876,12 +1016,16 @@ export default class I3SConverter {
876
1016
  }
877
1017
 
878
1018
  NodePages.updateAll(node, nodeInPage);
879
- if (meshMaterial) {
880
- NodePages.updateMaterialByNodeId(node, this._findOrCreateMaterial(meshMaterial));
1019
+ if ('meshMaterial' in resources && resources.meshMaterial) {
1020
+ NodePages.updateMaterialByNodeId(node, this._findOrCreateMaterial(resources.meshMaterial));
1021
+ } else if ('materialId' in resources && resources.materialId !== null) {
1022
+ NodePages.updateMaterialByNodeId(node, resources.materialId);
881
1023
  }
882
- if (texture) {
883
- const texelCountHint = texture.image.height * texture.image.width;
1024
+ if ('texture' in resources && resources.texture) {
1025
+ const texelCountHint = resources.texture.image.height * resources.texture.image.width;
884
1026
  NodePages.updateTexelCountHintByNodeId(node, texelCountHint);
1027
+ } else if ('texelCountHint' in resources && resources.texelCountHint) {
1028
+ NodePages.updateTexelCountHintByNodeId(node, resources.texelCountHint);
885
1029
  }
886
1030
  if (vertexCount) {
887
1031
  this.vertexCounter += vertexCount;
@@ -905,9 +1049,15 @@ export default class I3SConverter {
905
1049
  * @param resources.texture - texture image
906
1050
  * @param resources.sharedResources - shared resource data object
907
1051
  * @param resources.attributes - feature attributes
1052
+ * @param nodePath - node path
1053
+ * @param sourceTile - source tile (3DTile)
908
1054
  * @return {Promise<void>}
909
1055
  */
910
- private async _writeResources(resources: I3SConvertedResources, nodePath: string): Promise<void> {
1056
+ private async _writeResources(
1057
+ resources: I3SConvertedResources,
1058
+ nodePath: string,
1059
+ sourceTile: Tiles3DTileJSONPostprocessed
1060
+ ): Promise<void> {
911
1061
  const {
912
1062
  geometry: geometryBuffer,
913
1063
  compressedGeometry,
@@ -918,10 +1068,36 @@ export default class I3SConverter {
918
1068
  const childPath = join(this.layers0Path, 'nodes', nodePath);
919
1069
  const slpkChildPath = join('nodes', nodePath);
920
1070
 
921
- await this._writeGeometries(geometryBuffer!, compressedGeometry!, childPath, slpkChildPath);
922
- await this._writeShared(sharedResources, childPath, slpkChildPath, nodePath);
923
- await this._writeTexture(texture, childPath, slpkChildPath);
924
- await this._writeAttributes(attributes, childPath, slpkChildPath);
1071
+ await this._writeGeometries(
1072
+ geometryBuffer!,
1073
+ compressedGeometry!,
1074
+ childPath,
1075
+ slpkChildPath,
1076
+ sourceTile.id || '',
1077
+ parseInt(nodePath)
1078
+ );
1079
+ await this._writeShared(
1080
+ sharedResources,
1081
+ childPath,
1082
+ slpkChildPath,
1083
+ nodePath,
1084
+ sourceTile.id || '',
1085
+ parseInt(nodePath)
1086
+ );
1087
+ await this._writeTexture(
1088
+ texture,
1089
+ childPath,
1090
+ slpkChildPath,
1091
+ sourceTile.id || '',
1092
+ parseInt(nodePath)
1093
+ );
1094
+ await this._writeAttributes(
1095
+ attributes,
1096
+ childPath,
1097
+ slpkChildPath,
1098
+ sourceTile.id || '',
1099
+ parseInt(nodePath)
1100
+ );
925
1101
  }
926
1102
 
927
1103
  /**
@@ -930,37 +1106,57 @@ export default class I3SConverter {
930
1106
  * @param compressedGeometry - Uint8Array with compressed (draco) geometry
931
1107
  * @param childPath - a child path to write resources
932
1108
  * @param slpkChildPath - resource path inside *slpk file
1109
+ * @param sourceId - source filename
1110
+ * @param nodeId - nodeId of a converted node for the writing
933
1111
  */
934
1112
  private async _writeGeometries(
935
1113
  geometryBuffer: ArrayBuffer,
936
1114
  compressedGeometry: Promise<ArrayBuffer>,
937
1115
  childPath: string,
938
- slpkChildPath: string
1116
+ slpkChildPath: string,
1117
+ sourceId: string,
1118
+ nodeId: number
939
1119
  ): Promise<void> {
1120
+ this.conversionDump.updateDoneStatus(sourceId, nodeId, ResourceType.GEOMETRY, false);
1121
+
940
1122
  if (this.options.slpk) {
941
1123
  const slpkGeometryPath = join(childPath, 'geometries');
942
1124
  await this.writeQueue.enqueue({
943
1125
  archiveKey: `${slpkChildPath}/geometries/0.bin.gz`,
1126
+ sourceId,
1127
+ outputId: nodeId,
1128
+ resourceType: ResourceType.GEOMETRY,
944
1129
  writePromise: () => writeFileForSlpk(slpkGeometryPath, geometryBuffer, '0.bin')
945
1130
  });
946
1131
  } else {
947
1132
  const geometryPath = join(childPath, 'geometries/0/');
948
1133
  await this.writeQueue.enqueue({
1134
+ sourceId,
1135
+ outputId: nodeId,
1136
+ resourceType: ResourceType.GEOMETRY,
949
1137
  writePromise: () => writeFile(geometryPath, geometryBuffer, 'index.bin')
950
1138
  });
951
1139
  }
952
1140
 
953
1141
  if (this.options.draco) {
1142
+ this.conversionDump.updateDoneStatus(sourceId, nodeId, ResourceType.DRACO_GEOMETRY, false);
1143
+
954
1144
  if (this.options.slpk) {
955
1145
  const slpkCompressedGeometryPath = join(childPath, 'geometries');
956
1146
  await this.writeQueue.enqueue({
957
1147
  archiveKey: `${slpkChildPath}/geometries/1.bin.gz`,
1148
+ sourceId,
1149
+ outputId: nodeId,
1150
+ resourceType: ResourceType.DRACO_GEOMETRY,
958
1151
  writePromise: () =>
959
1152
  writeFileForSlpk(slpkCompressedGeometryPath, compressedGeometry, '1.bin')
960
1153
  });
961
1154
  } else {
962
1155
  const compressedGeometryPath = join(childPath, 'geometries/1/');
963
1156
  await this.writeQueue.enqueue({
1157
+ sourceId,
1158
+ outputId: nodeId,
1159
+ resourceType: ResourceType.DRACO_GEOMETRY,
964
1160
  writePromise: () => writeFile(compressedGeometryPath, compressedGeometry, 'index.bin')
965
1161
  });
966
1162
  }
@@ -973,12 +1169,16 @@ export default class I3SConverter {
973
1169
  * @param childPath - a child path to write resources
974
1170
  * @param slpkChildPath - resource path inside *slpk file
975
1171
  * @param nodePath - a node path
1172
+ * @param sourceId - source filename
1173
+ * @param nodeId - nodeId of a converted node for the writing
976
1174
  */
977
1175
  private async _writeShared(
978
1176
  sharedResources: SharedResourcesArrays | null,
979
1177
  childPath: string,
980
1178
  slpkChildPath: string,
981
- nodePath: string
1179
+ nodePath: string,
1180
+ sourceId: string,
1181
+ nodeId: number
982
1182
  ): Promise<void> {
983
1183
  if (!sharedResources) {
984
1184
  return;
@@ -986,15 +1186,24 @@ export default class I3SConverter {
986
1186
  sharedResources.nodePath = nodePath;
987
1187
  const sharedData = transform(sharedResources, sharedResourcesTemplate());
988
1188
  const sharedDataStr = JSON.stringify(sharedData);
1189
+ this.conversionDump.updateDoneStatus(sourceId, nodeId, ResourceType.SHARED, false);
989
1190
  if (this.options.slpk) {
990
1191
  const slpkSharedPath = join(childPath, 'shared');
991
1192
  await this.writeQueue.enqueue({
992
1193
  archiveKey: `${slpkChildPath}/shared/sharedResource.json.gz`,
1194
+ sourceId,
1195
+ outputId: nodeId,
1196
+ resourceType: ResourceType.SHARED,
993
1197
  writePromise: () => writeFileForSlpk(slpkSharedPath, sharedDataStr, 'sharedResource.json')
994
1198
  });
995
1199
  } else {
996
1200
  const sharedPath = join(childPath, 'shared/');
997
- await this.writeQueue.enqueue({writePromise: () => writeFile(sharedPath, sharedDataStr)});
1201
+ await this.writeQueue.enqueue({
1202
+ sourceId,
1203
+ outputId: nodeId,
1204
+ resourceType: ResourceType.SHARED,
1205
+ writePromise: () => writeFile(sharedPath, sharedDataStr)
1206
+ });
998
1207
  }
999
1208
  }
1000
1209
 
@@ -1003,11 +1212,15 @@ export default class I3SConverter {
1003
1212
  * @param texture - the texture image
1004
1213
  * @param childPath - a child path to write resources
1005
1214
  * @param slpkChildPath - the resource path inside *slpk file
1215
+ * @param sourceId - source filename
1216
+ * @param nodeId - nodeId of a converted node for the writing
1006
1217
  */
1007
1218
  private async _writeTexture(
1008
1219
  texture: GLTFImagePostprocessed,
1009
1220
  childPath: string,
1010
- slpkChildPath: string
1221
+ slpkChildPath: string,
1222
+ sourceId: string,
1223
+ nodeId: number
1011
1224
  ): Promise<void> {
1012
1225
  if (texture) {
1013
1226
  const format = this._getFormatByMimeType(texture?.mimeType);
@@ -1018,7 +1231,21 @@ export default class I3SConverter {
1018
1231
  case 'jpg':
1019
1232
  case 'png': {
1020
1233
  formats.push({name: '0', format});
1021
- await this.writeTextureFile(textureData, '0', format, childPath, slpkChildPath);
1234
+ this.conversionDump.updateDoneStatus(
1235
+ sourceId,
1236
+ nodeId,
1237
+ `${ResourceType.TEXTURE}/${format}`,
1238
+ false
1239
+ );
1240
+ await this.writeTextureFile(
1241
+ textureData,
1242
+ '0',
1243
+ format,
1244
+ childPath,
1245
+ slpkChildPath,
1246
+ sourceId,
1247
+ nodeId
1248
+ );
1022
1249
 
1023
1250
  if (this.generateTextures) {
1024
1251
  formats.push({name: '1', format: 'ktx2'});
@@ -1041,7 +1268,22 @@ export default class I3SConverter {
1041
1268
  }
1042
1269
  );
1043
1270
 
1044
- await this.writeTextureFile(ktx2TextureData, '1', 'ktx2', childPath, slpkChildPath);
1271
+ this.conversionDump.updateDoneStatus(
1272
+ sourceId,
1273
+ nodeId,
1274
+ `${ResourceType.TEXTURE}/ktx2`,
1275
+ false
1276
+ );
1277
+
1278
+ await this.writeTextureFile(
1279
+ ktx2TextureData,
1280
+ '1',
1281
+ 'ktx2',
1282
+ childPath,
1283
+ slpkChildPath,
1284
+ sourceId,
1285
+ nodeId
1286
+ );
1045
1287
  }
1046
1288
 
1047
1289
  break;
@@ -1049,17 +1291,39 @@ export default class I3SConverter {
1049
1291
 
1050
1292
  case 'ktx2': {
1051
1293
  formats.push({name: '1', format});
1052
- await this.writeTextureFile(textureData, '1', format, childPath, slpkChildPath);
1294
+ this.conversionDump.updateDoneStatus(
1295
+ sourceId,
1296
+ nodeId,
1297
+ `${ResourceType.TEXTURE}/${format}`,
1298
+ false
1299
+ );
1300
+ await this.writeTextureFile(
1301
+ textureData,
1302
+ '1',
1303
+ format,
1304
+ childPath,
1305
+ slpkChildPath,
1306
+ sourceId,
1307
+ nodeId
1308
+ );
1053
1309
 
1054
1310
  if (this.generateTextures) {
1055
1311
  formats.push({name: '0', format: 'jpg'});
1056
1312
  const decodedFromKTX2TextureData = encode(texture.image!.data[0], ImageWriter);
1313
+ this.conversionDump.updateDoneStatus(
1314
+ sourceId,
1315
+ nodeId,
1316
+ `${ResourceType.TEXTURE}/jpg`,
1317
+ false
1318
+ );
1057
1319
  await this.writeTextureFile(
1058
1320
  decodedFromKTX2TextureData,
1059
1321
  '0',
1060
1322
  'jpg',
1061
1323
  childPath,
1062
- slpkChildPath
1324
+ slpkChildPath,
1325
+ sourceId,
1326
+ nodeId
1063
1327
  );
1064
1328
  }
1065
1329
  }
@@ -1068,6 +1332,9 @@ export default class I3SConverter {
1068
1332
  if (!this.layers0!.textureSetDefinitions!.length) {
1069
1333
  this.layers0!.textureSetDefinitions!.push({formats});
1070
1334
  this.layers0!.textureSetDefinitions!.push({formats, atlas: true});
1335
+ if (this.layers0!.textureSetDefinitions) {
1336
+ this.conversionDump.addTexturesDefinitions(this.layers0!.textureSetDefinitions);
1337
+ }
1071
1338
  }
1072
1339
  }
1073
1340
  }
@@ -1079,13 +1346,17 @@ export default class I3SConverter {
1079
1346
  * @param format
1080
1347
  * @param childPath
1081
1348
  * @param slpkChildPath
1349
+ * @param sourceId
1350
+ * @param nodeId
1082
1351
  */
1083
1352
  private async writeTextureFile(
1084
1353
  textureData: Uint8Array | Promise<ArrayBuffer>,
1085
1354
  name: string,
1086
1355
  format: 'jpg' | 'png' | 'ktx2',
1087
1356
  childPath: string,
1088
- slpkChildPath: string
1357
+ slpkChildPath: string,
1358
+ sourceId: string,
1359
+ nodeId: number
1089
1360
  ): Promise<void> {
1090
1361
  if (this.options.slpk) {
1091
1362
  const slpkTexturePath = join(childPath, 'textures');
@@ -1093,12 +1364,18 @@ export default class I3SConverter {
1093
1364
 
1094
1365
  await this.writeQueue.enqueue({
1095
1366
  archiveKey: `${slpkChildPath}/textures/${name}.${format}`,
1367
+ sourceId,
1368
+ outputId: nodeId,
1369
+ resourceType: `${ResourceType.TEXTURE}/${format}`,
1096
1370
  writePromise: () =>
1097
1371
  writeFileForSlpk(slpkTexturePath, textureData, `${name}.${format}`, compress)
1098
1372
  });
1099
1373
  } else {
1100
1374
  const texturePath = join(childPath, `textures/${name}/`);
1101
1375
  await this.writeQueue.enqueue({
1376
+ sourceId,
1377
+ outputId: nodeId,
1378
+ resourceType: `${ResourceType.TEXTURE}/${format}`,
1102
1379
  writePromise: () => writeFile(texturePath, textureData, `index.${format}`)
1103
1380
  });
1104
1381
  }
@@ -1109,11 +1386,15 @@ export default class I3SConverter {
1109
1386
  * @param attributes - feature attributes
1110
1387
  * @param childPath - a child path to write resources
1111
1388
  * @param slpkChildPath - the resource path inside *slpk file
1389
+ * @param sourceId - source filename
1390
+ * @param nodeId - nodeId of a converted node for the writing
1112
1391
  */
1113
1392
  private async _writeAttributes(
1114
1393
  attributes: ArrayBuffer[] | null = [],
1115
1394
  childPath: string,
1116
- slpkChildPath: string
1395
+ slpkChildPath: string,
1396
+ sourceId: string,
1397
+ nodeId: number
1117
1398
  ): Promise<void> {
1118
1399
  if (attributes?.length && this.attributeMetadataInfo.attributeStorageInfo.length) {
1119
1400
  const minimumLength =
@@ -1124,16 +1405,27 @@ export default class I3SConverter {
1124
1405
  for (let index = 0; index < minimumLength; index++) {
1125
1406
  const folderName = this.attributeMetadataInfo.attributeStorageInfo[index].key;
1126
1407
  const fileBuffer = new Uint8Array(attributes[index]);
1127
-
1408
+ this.conversionDump.updateDoneStatus(
1409
+ sourceId,
1410
+ nodeId,
1411
+ `${ResourceType.ATTRIBUTES}/${folderName}`,
1412
+ false
1413
+ );
1128
1414
  if (this.options.slpk) {
1129
1415
  const slpkAttributesPath = join(childPath, 'attributes', folderName);
1130
1416
  await this.writeQueue.enqueue({
1131
1417
  archiveKey: `${slpkChildPath}/attributes/${folderName}.bin.gz`,
1418
+ sourceId,
1419
+ outputId: nodeId,
1420
+ resourceType: `${ResourceType.ATTRIBUTES}/${folderName}`,
1132
1421
  writePromise: () => writeFileForSlpk(slpkAttributesPath, fileBuffer, '0.bin')
1133
1422
  });
1134
1423
  } else {
1135
1424
  const attributesPath = join(childPath, `attributes/${folderName}/0`);
1136
1425
  await this.writeQueue.enqueue({
1426
+ sourceId,
1427
+ outputId: nodeId,
1428
+ resourceType: `${ResourceType.ATTRIBUTES}/${folderName}`,
1137
1429
  writePromise: () => writeFile(attributesPath, fileBuffer, 'index.bin')
1138
1430
  });
1139
1431
  }