@loaders.gl/tile-converter 3.4.11 → 3.4.13

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