@loaders.gl/tile-converter 4.1.1 → 4.2.0-alpha.2

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 (55) hide show
  1. package/dist/3d-tiles-converter/3d-tiles-converter.d.ts +11 -0
  2. package/dist/3d-tiles-converter/3d-tiles-converter.d.ts.map +1 -1
  3. package/dist/3d-tiles-converter/3d-tiles-converter.js +77 -27
  4. package/dist/3d-tiles-converter/3d-tiles-converter.js.map +1 -1
  5. package/dist/3d-tiles-converter/helpers/load-i3s.d.ts +22 -1
  6. package/dist/3d-tiles-converter/helpers/load-i3s.d.ts.map +1 -1
  7. package/dist/3d-tiles-converter/helpers/load-i3s.js +49 -4
  8. package/dist/3d-tiles-converter/helpers/load-i3s.js.map +1 -1
  9. package/dist/converter-cli.js +2 -1
  10. package/dist/converter-cli.js.map +1 -1
  11. package/dist/converter.min.cjs +137 -130
  12. package/dist/deps-installer/deps-installer.js +1 -1
  13. package/dist/deps-installer/deps-installer.js.map +1 -1
  14. package/dist/i3s-converter/helpers/attribute-metadata-info.d.ts +10 -0
  15. package/dist/i3s-converter/helpers/attribute-metadata-info.d.ts.map +1 -1
  16. package/dist/i3s-converter/helpers/attribute-metadata-info.js +5 -0
  17. package/dist/i3s-converter/helpers/attribute-metadata-info.js.map +1 -1
  18. package/dist/i3s-converter/helpers/load-3d-tiles.d.ts.map +1 -1
  19. package/dist/i3s-converter/helpers/load-3d-tiles.js +22 -2
  20. package/dist/i3s-converter/helpers/load-3d-tiles.js.map +1 -1
  21. package/dist/i3s-converter/helpers/node-index-document.d.ts +2 -1
  22. package/dist/i3s-converter/helpers/node-index-document.d.ts.map +1 -1
  23. package/dist/i3s-converter/helpers/node-index-document.js +6 -8
  24. package/dist/i3s-converter/helpers/node-index-document.js.map +1 -1
  25. package/dist/i3s-converter/i3s-converter.d.ts +18 -0
  26. package/dist/i3s-converter/i3s-converter.d.ts.map +1 -1
  27. package/dist/i3s-converter/i3s-converter.js +121 -24
  28. package/dist/i3s-converter/i3s-converter.js.map +1 -1
  29. package/dist/i3s-server/bin/i3s-server.min.cjs +86 -86
  30. package/dist/index.cjs +792 -101
  31. package/dist/lib/json-schemas/conversion-dump-json-schema.d.ts +463 -0
  32. package/dist/lib/json-schemas/conversion-dump-json-schema.d.ts.map +1 -0
  33. package/dist/lib/json-schemas/conversion-dump-json-schema.js +463 -0
  34. package/dist/lib/json-schemas/conversion-dump-json-schema.js.map +1 -0
  35. package/dist/lib/utils/conversion-dump.d.ts +65 -8
  36. package/dist/lib/utils/conversion-dump.d.ts.map +1 -1
  37. package/dist/lib/utils/conversion-dump.js +98 -18
  38. package/dist/lib/utils/conversion-dump.js.map +1 -1
  39. package/dist/lib/utils/file-utils.d.ts +6 -0
  40. package/dist/lib/utils/file-utils.d.ts.map +1 -1
  41. package/dist/lib/utils/file-utils.js +7 -0
  42. package/dist/lib/utils/file-utils.js.map +1 -1
  43. package/dist/pgm-loader.js +1 -1
  44. package/dist/pgm-loader.js.map +1 -1
  45. package/package.json +15 -14
  46. package/src/3d-tiles-converter/3d-tiles-converter.ts +104 -31
  47. package/src/3d-tiles-converter/helpers/load-i3s.ts +86 -7
  48. package/src/converter-cli.ts +2 -1
  49. package/src/i3s-converter/helpers/attribute-metadata-info.ts +16 -0
  50. package/src/i3s-converter/helpers/load-3d-tiles.ts +52 -2
  51. package/src/i3s-converter/helpers/node-index-document.ts +18 -8
  52. package/src/i3s-converter/i3s-converter.ts +198 -41
  53. package/src/lib/json-schemas/conversion-dump-json-schema.ts +285 -0
  54. package/src/lib/utils/conversion-dump.ts +200 -26
  55. package/src/lib/utils/file-utils.ts +13 -0
@@ -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';
@@ -78,7 +79,7 @@ import {TraversalConversionProps, traverseDatasetWith} from './helpers/tileset-t
78
79
  import {analyzeTileContent, mergePreprocessData} from './helpers/preprocess-3d-tiles';
79
80
  import {Progress} from './helpers/progress';
80
81
  import {composeHashFile, createZip} from '@loaders.gl/zip';
81
- import {ConversionDump, ConversionDumpOptions} from '../lib/utils/conversion-dump';
82
+ import {ConversionDump, ConversionDumpOptions, DumpMetadata} from '../lib/utils/conversion-dump';
82
83
 
83
84
  const ION_DEFAULT_TOKEN = process.env?.IonToken;
84
85
  const HARDCODED_NODES_PER_PAGE = 64;
@@ -259,9 +260,6 @@ export default class I3SConverter {
259
260
  this.nodePages.useWriteFunction(writeFileForSlpk);
260
261
  }
261
262
 
262
- //create a dump file with convertion options
263
- await this.conversionDump.createDumpFile(options as ConversionDumpOptions);
264
-
265
263
  try {
266
264
  const preloadOptions = await this._fetchPreloadOptions();
267
265
  let tilesetUrl = inputUrl;
@@ -415,18 +413,49 @@ export default class I3SConverter {
415
413
  */
416
414
  private async _createAndSaveTileset(outputPath: string, tilesetName: string): Promise<void> {
417
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
+
418
434
  // Removing the tilesetPath needed to exclude erroneous files after conversion
435
+ const removePath = this.conversionDump.restored
436
+ ? join(this.layers0Path, 'nodepages')
437
+ : tilesetPath;
419
438
  try {
420
- await removeDir(tilesetPath);
439
+ await removeDir(removePath);
421
440
  } catch (e) {
422
441
  // do nothing
423
442
  }
424
443
 
425
- this.layers0Path = join(tilesetPath, 'SceneServer', 'layers', '0');
444
+ if (this.conversionDump.restored && this.conversionDump.attributeMetadataInfo) {
445
+ this.attributeMetadataInfo.fromObject(this.conversionDump.attributeMetadataInfo);
446
+ }
426
447
 
427
448
  this.materialDefinitions = [];
428
449
  this.materialMap = new Map();
429
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
+
430
459
  const sourceRootTile: Tiles3DTileJSONPostprocessed = this.sourceTileset!.root!;
431
460
  const sourceBoundingVolume = createBoundingVolume(
432
461
  sourceRootTile.boundingVolume,
@@ -471,6 +500,10 @@ export default class I3SConverter {
471
500
  this.layers0!.layerType = _3D_OBJECT_LAYER_TYPE;
472
501
  }
473
502
 
503
+ if (this.conversionDump.restored && this.conversionDump.textureSetDefinitions) {
504
+ this.layers0!.textureSetDefinitions = this.conversionDump.textureSetDefinitions;
505
+ }
506
+
474
507
  this.layers0!.materialDefinitions = this.materialDefinitions;
475
508
  // @ts-ignore
476
509
  this.layers0.geometryDefinitions = transform(
@@ -607,7 +640,13 @@ export default class I3SConverter {
607
640
  transformationMatrix = transformationMatrix.multiplyRight(sourceTile.transform);
608
641
  }
609
642
  const parentNode = parentNodes[0];
610
- 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
+ }
611
650
  await parentNode.addChildren(childNodes);
612
651
 
613
652
  const newTraversalProps: TraversalConversionProps = {
@@ -651,6 +690,114 @@ export default class I3SConverter {
651
690
  }
652
691
  }
653
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
+
654
801
  /**
655
802
  * Convert tile to one or more I3S nodes
656
803
  * @param parentNode - 3DNodeIndexDocument of parent node
@@ -684,6 +831,12 @@ export default class I3SConverter {
684
831
  const propertyTable = getPropertyTable(tileContent, this.options.metadataClass);
685
832
  this.createAttributeStorageInfo(tileContent, propertyTable);
686
833
 
834
+ this.conversionDump.attributeMetadataInfo = {
835
+ attributeStorageInfo: this.attributeMetadataInfo.attributeStorageInfo,
836
+ fields: this.attributeMetadataInfo.fields,
837
+ popupInfo: this.attributeMetadataInfo.popupInfo
838
+ };
839
+
687
840
  const resourcesData = await this._convertResources(
688
841
  sourceTile,
689
842
  transformationMatrix,
@@ -710,39 +863,30 @@ export default class I3SConverter {
710
863
  };
711
864
 
712
865
  for (const resources of resourcesData || [emptyResources]) {
713
- this.layersHasTexture = this.layersHasTexture || Boolean(resources.texture);
714
-
715
- if (this.generateBoundingVolumes && resources.boundingVolumes) {
716
- boundingVolumes = resources.boundingVolumes;
717
- }
718
-
719
- const lodSelection = convertGeometricErrorToScreenThreshold(sourceTile, boundingVolumes);
720
- const maxScreenThresholdSQ = lodSelection.find(
721
- (val) => val.metricType === 'maxScreenThresholdSQ'
722
- ) || {maxError: 0};
723
-
724
- const nodeInPage = await this._updateNodeInNodePages(
725
- maxScreenThresholdSQ,
866
+ const {node, nodeInPage, nodeData} = await this._generateNodeIndexDocument(
726
867
  boundingVolumes,
727
- sourceTile,
728
- parentNode.inPageId,
729
- resources
730
- );
731
-
732
- const nodeData = await NodeIndexDocument.createNodeIndexDocument(
868
+ resources,
733
869
  parentNode,
734
- boundingVolumes,
735
- lodSelection,
736
- nodeInPage,
737
- resources
870
+ sourceTile,
871
+ false
738
872
  );
739
- const node = await new NodeIndexDocument(nodeInPage.index, this).addData(nodeData);
740
873
  nodes.push(node);
741
874
 
742
875
  if (nodeInPage.mesh) {
743
876
  //update a record in a dump file
744
877
  if (sourceTile.id) {
745
- await this.conversionDump.addNode(sourceTile.id, nodeInPage.index);
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);
746
890
  }
747
891
 
748
892
  //write resources
@@ -829,9 +973,9 @@ export default class I3SConverter {
829
973
  boundingVolumes: BoundingVolumes,
830
974
  sourceTile: Tiles3DTileJSONPostprocessed,
831
975
  parentId: number,
832
- resources: I3SConvertedResources
976
+ resources: I3SConvertedResources | DumpMetadata
833
977
  ): Promise<NodeInPage> {
834
- const {meshMaterial, texture, vertexCount, featureCount, geometry, hasUvRegions} = resources;
978
+ const {vertexCount, featureCount, geometry, hasUvRegions} = resources;
835
979
  const nodeInPage: NodeInPage = {
836
980
  index: 0,
837
981
  lodThreshold: maxScreenThresholdSQ.maxError,
@@ -841,7 +985,13 @@ export default class I3SConverter {
841
985
  if (geometry && this.isContentSupported(sourceTile)) {
842
986
  nodeInPage.mesh = {
843
987
  geometry: {
844
- 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
+ ),
845
995
  resource: 0
846
996
  },
847
997
  attribute: {
@@ -853,7 +1003,7 @@ export default class I3SConverter {
853
1003
  };
854
1004
  }
855
1005
 
856
- let nodeId = resources.nodeId;
1006
+ let nodeId = 'nodeId' in resources ? resources.nodeId : undefined;
857
1007
  let node: NodeInPage;
858
1008
  if (!nodeId) {
859
1009
  node = await this.nodePages.push(nodeInPage, parentId);
@@ -866,12 +1016,16 @@ export default class I3SConverter {
866
1016
  }
867
1017
 
868
1018
  NodePages.updateAll(node, nodeInPage);
869
- if (meshMaterial) {
870
- 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);
871
1023
  }
872
- if (texture) {
873
- 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;
874
1026
  NodePages.updateTexelCountHintByNodeId(node, texelCountHint);
1027
+ } else if ('texelCountHint' in resources && resources.texelCountHint) {
1028
+ NodePages.updateTexelCountHintByNodeId(node, resources.texelCountHint);
875
1029
  }
876
1030
  if (vertexCount) {
877
1031
  this.vertexCounter += vertexCount;
@@ -1178,6 +1332,9 @@ export default class I3SConverter {
1178
1332
  if (!this.layers0!.textureSetDefinitions!.length) {
1179
1333
  this.layers0!.textureSetDefinitions!.push({formats});
1180
1334
  this.layers0!.textureSetDefinitions!.push({formats, atlas: true});
1335
+ if (this.layers0!.textureSetDefinitions) {
1336
+ this.conversionDump.addTexturesDefinitions(this.layers0!.textureSetDefinitions);
1337
+ }
1181
1338
  }
1182
1339
  }
1183
1340
  }
@@ -0,0 +1,285 @@
1
+ export const dumpJsonSchema = {
2
+ type: 'object',
3
+ properties: {
4
+ options: {
5
+ type: 'object',
6
+ properties: {
7
+ inputUrl: {type: 'string'},
8
+ outputPath: {type: 'string'},
9
+ tilesetName: {type: 'string'},
10
+ maxDepth: {type: 'number'},
11
+ slpk: {type: 'boolean'},
12
+ egmFilePath: {type: 'string'},
13
+ token: {type: 'string'},
14
+ draco: {type: 'boolean'},
15
+ mergeMaterials: {type: 'boolean'},
16
+ generateTextures: {type: 'boolean'},
17
+ generateBoundingVolumes: {type: 'boolean'},
18
+ metadataClass: {type: 'string'},
19
+ analyze: {type: 'boolean'}
20
+ },
21
+ required: ['inputUrl', 'outputPath', 'tilesetName']
22
+ },
23
+ tilesConverted: {
24
+ type: 'object',
25
+ patternProperties: {
26
+ '.*': {
27
+ type: 'object',
28
+ properties: {
29
+ nodes: {
30
+ type: 'array',
31
+ items: {
32
+ type: 'object',
33
+ properties: {
34
+ nodeId: {type: ['number', 'string']},
35
+ done: {type: 'boolean'},
36
+ progress: {type: 'object', patternProperties: {'.*': {type: 'boolean'}}},
37
+ dumpMetadata: {
38
+ type: 'object',
39
+ properties: {
40
+ boundingVolumes: {
41
+ type: ['object', 'null'],
42
+ properties: {
43
+ mbs: {
44
+ type: 'array',
45
+ minItems: 4,
46
+ maxItems: 4,
47
+ items: {type: 'number'}
48
+ },
49
+ obb: {
50
+ type: 'object',
51
+ properties: {
52
+ center: {
53
+ type: 'array',
54
+ minItems: 3,
55
+ maxItems: 3,
56
+ items: {type: 'number'}
57
+ },
58
+ halfSize: {
59
+ type: 'array',
60
+ minItems: 3,
61
+ maxItems: 3,
62
+ items: {type: 'number'}
63
+ },
64
+ quaternion: {
65
+ type: 'array',
66
+ minItems: 4,
67
+ maxItems: 4,
68
+ items: {type: 'number'}
69
+ }
70
+ },
71
+ required: ['center', 'halfSize', 'quaternion']
72
+ }
73
+ },
74
+ required: ['mbs', 'obb']
75
+ },
76
+ attributesCount: {type: 'number'},
77
+ featureCount: {type: 'number'},
78
+ geometry: {type: 'boolean'},
79
+ hasUvRegions: {type: 'boolean'},
80
+ materialId: {type: 'number'},
81
+ texelCountHint: {type: 'number'},
82
+ vertexCount: {type: 'number'}
83
+ },
84
+ required: [
85
+ 'boundingVolumes',
86
+ 'featureCount',
87
+ 'geometry',
88
+ 'hasUvRegions',
89
+ 'materialId',
90
+ 'vertexCount'
91
+ ]
92
+ }
93
+ },
94
+ required: ['nodeId', 'done']
95
+ }
96
+ }
97
+ },
98
+ required: ['nodes']
99
+ }
100
+ }
101
+ },
102
+ textureSetDefinitions: {
103
+ type: 'array',
104
+ items: {
105
+ type: 'object',
106
+ properties: {
107
+ formats: {
108
+ type: 'array',
109
+ items: {
110
+ type: 'object',
111
+ properties: {
112
+ name: {type: 'string'},
113
+ format: {enum: ['jpg', 'png', 'ktx-etc2', 'dds', 'ktx2']}
114
+ },
115
+ required: ['name', 'format']
116
+ }
117
+ },
118
+ atlas: {type: 'boolean'}
119
+ },
120
+ required: ['formats']
121
+ }
122
+ },
123
+ attributeMetadataInfo: {
124
+ type: 'object',
125
+ properties: {
126
+ attributeStorageInfo: {
127
+ type: 'array',
128
+ items: {
129
+ type: 'object',
130
+ properties: {
131
+ key: {type: 'string'},
132
+ name: {type: 'string'},
133
+ header: {
134
+ type: 'array',
135
+ items: {
136
+ type: 'object',
137
+ properties: {property: {type: 'string'}, valueType: {type: 'string'}},
138
+ required: ['property', 'valueType']
139
+ }
140
+ },
141
+ ordering: {type: 'array', items: {type: 'string'}},
142
+ attributeValues: {$ref: '#/$defs/AttributeValue'},
143
+ attributeByteCounts: {$ref: '#/$defs/AttributeValue'},
144
+ objectIds: {$ref: '#/$defs/AttributeValue'}
145
+ },
146
+ required: ['key', 'name', 'header']
147
+ }
148
+ },
149
+ fields: {
150
+ type: 'array',
151
+ items: {
152
+ type: 'object',
153
+ properties: {
154
+ name: {type: 'string'},
155
+ type: {$ref: '#/$defs/ESRIField'},
156
+ alias: {type: 'string'},
157
+ domain: {$ref: '#/$defs/Domain'}
158
+ },
159
+ required: ['name', 'type']
160
+ }
161
+ },
162
+ popupInfo: {
163
+ type: 'object',
164
+ properties: {
165
+ title: {type: 'string'},
166
+ description: {type: 'string'},
167
+ expressionInfos: {type: 'array', items: {}},
168
+ fieldInfos: {type: 'array', items: {$ref: '#/$defs/FieldInfo'}},
169
+ mediaInfos: {type: 'array', items: {}},
170
+ popupElements: {
171
+ type: 'array',
172
+ items: {
173
+ type: 'object',
174
+ properties: {
175
+ text: {type: 'string'},
176
+ type: {type: 'string'},
177
+ fieldInfos: {type: 'array', items: {$ref: '#/$defs/FieldInfo'}}
178
+ }
179
+ }
180
+ }
181
+ }
182
+ }
183
+ },
184
+ required: ['attributeStorageInfo', 'fields']
185
+ },
186
+ materialDefinitions: {
187
+ type: 'array',
188
+ items: {
189
+ type: 'object',
190
+ properties: {
191
+ pbrMetallicRoughness: {
192
+ type: 'object',
193
+ properties: {
194
+ baseColorFactor: {
195
+ type: 'array',
196
+ minItems: 4,
197
+ maxItems: 4,
198
+ items: {type: 'number'}
199
+ },
200
+ baseColorTexture: {$ref: '#/$defs/I3SMaterialTexture'},
201
+ metallicFactor: {type: 'number'},
202
+ roughnessFactor: {type: 'number'},
203
+ metallicRoughnessTexture: {$ref: '#/$defs/I3SMaterialTexture'}
204
+ },
205
+ required: ['metallicFactor', 'roughnessFactor']
206
+ },
207
+ normalTexture: {$ref: '#/$defs/I3SMaterialTexture'},
208
+ occlusionTexture: {$ref: '#/$defs/I3SMaterialTexture'},
209
+ emissiveTexture: {$ref: '#/$defs/I3SMaterialTexture'},
210
+ emissiveFactor: {type: 'array', minItems: 3, maxItems: 3, items: {type: 'number'}},
211
+ alphaMode: {enum: ['opaque', 'mask', 'blend']},
212
+ alphaCutoff: {type: 'number'},
213
+ doubleSided: {type: 'boolean'},
214
+ cullFace: {enum: ['none', 'front', 'back']}
215
+ },
216
+ required: ['pbrMetallicRoughness', 'alphaMode']
217
+ }
218
+ }
219
+ },
220
+ required: ['options', 'tilesConverted'],
221
+ $defs: {
222
+ AttributeValue: {
223
+ type: 'object',
224
+ properties: {
225
+ valueType: {type: 'string'},
226
+ encoding: {type: 'string'},
227
+ valuesPerElement: {type: 'number'}
228
+ },
229
+ required: ['valueType']
230
+ },
231
+ ESRIField: {
232
+ enum: [
233
+ 'esriFieldTypeDate',
234
+ 'esriFieldTypeSingle',
235
+ 'esriFieldTypeDouble',
236
+ 'esriFieldTypeGUID',
237
+ 'esriFieldTypeGlobalID',
238
+ 'esriFieldTypeInteger',
239
+ 'esriFieldTypeOID',
240
+ 'esriFieldTypeSmallInteger',
241
+ 'esriFieldTypeString'
242
+ ]
243
+ },
244
+ Domain: {
245
+ type: 'object',
246
+ properties: {
247
+ type: {type: 'string'},
248
+ name: {type: 'string'},
249
+ description: {type: 'string'},
250
+ fieldType: {type: 'string'},
251
+ range: {type: 'array', items: {type: 'number'}},
252
+ codedValues: {
253
+ type: 'array',
254
+ items: {
255
+ type: 'object',
256
+ properties: {name: {type: 'string'}, code: {type: ['string', 'number']}},
257
+ required: ['name', 'code']
258
+ }
259
+ },
260
+ mergePolicy: {type: 'string'},
261
+ splitPolicy: {type: 'string'}
262
+ },
263
+ required: ['type', 'name']
264
+ },
265
+ FieldInfo: {
266
+ type: 'object',
267
+ properties: {
268
+ fieldName: {type: 'string'},
269
+ visible: {type: 'boolean'},
270
+ isEditable: {type: 'boolean'},
271
+ label: {type: 'string'}
272
+ },
273
+ required: ['fieldName', 'visible', 'isEditable', 'label']
274
+ },
275
+ I3SMaterialTexture: {
276
+ type: 'object',
277
+ properties: {
278
+ textureSetDefinitionId: {type: 'number'},
279
+ texCoord: {type: 'number'},
280
+ factor: {type: 'number'}
281
+ },
282
+ required: ['textureSetDefinitionId']
283
+ }
284
+ }
285
+ };