@loaders.gl/tile-converter 5.0.0-alpha.12 → 5.0.0-alpha.14

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 (132) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +4 -4
  3. package/dist/converter.min.cjs +149 -158
  4. package/dist/i3s-server/bin/i3s-server.min.cjs +113 -158
  5. package/dist/index.cjs +17486 -17479
  6. package/dist/index.js +25572 -25560
  7. package/dist/v5/adapters.cjs +540 -190
  8. package/dist/v5/adapters.d.ts +4 -0
  9. package/dist/v5/adapters.js +501 -151
  10. package/dist/v5/archive-source.d.ts +1 -1
  11. package/dist/v5/browser.d.ts +1 -1
  12. package/dist/v5/conversion-api.d.ts +25 -0
  13. package/dist/v5/converter-cli.js +23 -7
  14. package/dist/v5/core.cjs +76 -17
  15. package/dist/v5/core.d.ts +1 -1
  16. package/dist/v5/core.js +69 -10
  17. package/dist/v5/feature-arrow.d.ts +1 -1
  18. package/dist/v5/i3s-mesh-conversion.d.ts +1 -1
  19. package/dist/v5/i3s-mesh-source-features.d.ts +2 -2
  20. package/dist/v5/i3s-mesh-source-material.d.ts +1 -1
  21. package/dist/v5/i3s-mesh-source.d.ts +2 -2
  22. package/dist/v5/i3s-selection-source.d.ts +3 -3
  23. package/dist/v5/index.cjs +11 -6
  24. package/dist/v5/index.d.ts +5 -1
  25. package/dist/v5/index.js +8 -0
  26. package/dist/v5/mesh-conversion.d.ts +3 -3
  27. package/dist/v5/mesh-draco.d.ts +2 -2
  28. package/dist/v5/mesh-features.d.ts +2 -2
  29. package/dist/v5/mesh-source-features.d.ts +4 -4
  30. package/dist/v5/mesh-source-schema.d.ts +2 -2
  31. package/dist/v5/mesh-source-texture.d.ts +3 -3
  32. package/dist/v5/mesh-source.d.ts +5 -5
  33. package/dist/v5/mesh-tileset-sink.d.ts +1 -1
  34. package/dist/v5/mesh.d.ts +1 -1
  35. package/dist/v5/node-conversion.d.ts +2 -0
  36. package/dist/v5/node-i3s-source.d.ts +3 -3
  37. package/dist/v5/node-source.d.ts +1 -1
  38. package/dist/v5/node.cjs +19 -6
  39. package/dist/v5/node.js +19 -6
  40. package/dist/v5/point-cloud-attributes.d.ts +19 -0
  41. package/dist/v5/point-cloud-conversion.d.ts +1 -1
  42. package/dist/v5/point-cloud-copc.d.ts +44 -0
  43. package/dist/v5/point-cloud-file.d.ts +4 -4
  44. package/dist/v5/point-cloud-potree.d.ts +3 -3
  45. package/dist/v5/point-cloud-source-encoder.d.ts +1 -1
  46. package/dist/v5/point-cloud-source.d.ts +1 -1
  47. package/dist/v5/point-cloud-tileset.d.ts +3 -3
  48. package/dist/v5/point-cloud.d.ts +1 -1
  49. package/dist/v5/spatial-conversion.d.ts +1 -1
  50. package/dist/v5/tileset-source.d.ts +1 -1
  51. package/package.json +17 -32
  52. package/src/v4/3d-tiles-converter/3d-tiles-converter.ts +11 -12
  53. package/src/v4/3d-tiles-converter/helpers/3d-tiles-content-converter.ts +7 -3
  54. package/src/v4/3d-tiles-converter/helpers/i3s-obb-to-3d-tiles-obb.ts +2 -2
  55. package/src/v4/3d-tiles-converter/helpers/load-i3s.ts +3 -3
  56. package/src/v4/3d-tiles-converter/helpers/texture-atlas.ts +1 -1
  57. package/src/v4/3d-tiles-converter/helpers/validate-i3s-spatial-reference.ts +2 -2
  58. package/src/v4/3d-tiles-converter/json-templates/tileset.ts +58 -39
  59. package/src/v4/converter-cli.ts +3 -3
  60. package/src/v4/deps-installer/deps-installer.ts +3 -3
  61. package/src/v4/i3s-converter/helpers/attribute-metadata-info.ts +1 -1
  62. package/src/v4/i3s-converter/helpers/batch-ids-extensions.ts +9 -10
  63. package/src/v4/i3s-converter/helpers/conversion-gltf.ts +108 -0
  64. package/src/v4/i3s-converter/helpers/coordinate-converter.ts +6 -5
  65. package/src/v4/i3s-converter/helpers/create-scene-server-path.ts +5 -6
  66. package/src/v4/i3s-converter/helpers/feature-attributes.ts +4 -4
  67. package/src/v4/i3s-converter/helpers/geometry-attributes.ts +1 -1
  68. package/src/v4/i3s-converter/helpers/geometry-converter.ts +69 -77
  69. package/src/v4/i3s-converter/helpers/gltf-attributes.ts +58 -74
  70. package/src/v4/i3s-converter/helpers/load-3d-tiles.ts +5 -5
  71. package/src/v4/i3s-converter/helpers/node-debug.ts +7 -3
  72. package/src/v4/i3s-converter/helpers/node-index-document.ts +6 -7
  73. package/src/v4/i3s-converter/helpers/node-pages.ts +3 -4
  74. package/src/v4/i3s-converter/helpers/preprocess-3d-tiles.ts +1 -1
  75. package/src/v4/i3s-converter/helpers/tileset-traversal.ts +1 -1
  76. package/src/v4/i3s-converter/i3s-converter.ts +29 -31
  77. package/src/v4/i3s-converter/json-templates/geometry-definitions.ts +71 -66
  78. package/src/v4/i3s-converter/json-templates/layers.ts +97 -132
  79. package/src/v4/i3s-converter/json-templates/metadata.ts +31 -21
  80. package/src/v4/i3s-converter/json-templates/node.ts +60 -74
  81. package/src/v4/i3s-converter/json-templates/scene-server.ts +36 -27
  82. package/src/v4/i3s-converter/json-templates/shared-resources.ts +62 -123
  83. package/src/v4/i3s-converter/json-templates/store.ts +39 -58
  84. package/src/v4/i3s-converter/types.ts +4 -4
  85. package/src/v4/i3s-server/controllers/slpk-controller.ts +3 -3
  86. package/src/v4/i3s-server/utils/create-scene-server.ts +3 -3
  87. package/src/v4/lib/utils/builder-input.ts +8 -0
  88. package/src/v4/lib/utils/conversion-dump.ts +5 -1
  89. package/src/v4/lib/utils/file-utils.ts +1 -1
  90. package/src/v4/lib/utils/hash-string.ts +21 -0
  91. package/src/v4/lib/utils/lod-conversion-utils.ts +3 -3
  92. package/src/v4/pgm-loader.ts +2 -2
  93. package/src/v5/adapters.ts +5 -0
  94. package/src/v5/archive-source.ts +4 -4
  95. package/src/v5/browser.ts +1 -0
  96. package/src/v5/conversion-api.ts +115 -8
  97. package/src/v5/conversion-archive.ts +2 -2
  98. package/src/v5/core.ts +1 -0
  99. package/src/v5/feature-arrow.ts +2 -2
  100. package/src/v5/i3s-mesh-conversion.ts +2 -2
  101. package/src/v5/i3s-mesh-source-features.ts +3 -3
  102. package/src/v5/i3s-mesh-source-material.ts +18 -11
  103. package/src/v5/i3s-mesh-source.ts +2 -2
  104. package/src/v5/i3s-selection-source.ts +4 -4
  105. package/src/v5/index.ts +15 -0
  106. package/src/v5/mesh-conversion.ts +4 -4
  107. package/src/v5/mesh-draco.ts +2 -2
  108. package/src/v5/mesh-features.ts +3 -3
  109. package/src/v5/mesh-hierarchy.ts +1 -1
  110. package/src/v5/mesh-source-features.ts +137 -55
  111. package/src/v5/mesh-source-schema.ts +10 -4
  112. package/src/v5/mesh-source-texture.ts +49 -19
  113. package/src/v5/mesh-source.ts +65 -46
  114. package/src/v5/mesh-tileset-archive.ts +1 -1
  115. package/src/v5/mesh-tileset-sink.ts +1 -1
  116. package/src/v5/mesh.ts +1 -1
  117. package/src/v5/node-cli.ts +4 -0
  118. package/src/v5/node-conversion.ts +19 -2
  119. package/src/v5/node-source.ts +2 -2
  120. package/src/v5/point-cloud-attributes.ts +113 -0
  121. package/src/v5/point-cloud-conversion.ts +1 -1
  122. package/src/v5/point-cloud-copc.ts +289 -0
  123. package/src/v5/point-cloud-file.ts +8 -8
  124. package/src/v5/point-cloud-potree.ts +8 -4
  125. package/src/v5/point-cloud-source-encoder.ts +1 -1
  126. package/src/v5/point-cloud-source.ts +1 -1
  127. package/src/v5/point-cloud-spatial.ts +2 -2
  128. package/src/v5/point-cloud-tileset.ts +4 -4
  129. package/src/v5/point-cloud.ts +3 -3
  130. package/src/v5/selected-mesh-source.ts +4 -4
  131. package/src/v5/spatial-conversion.ts +2 -2
  132. package/src/v5/tileset-source.ts +2 -2
@@ -2,15 +2,29 @@
2
2
  // SPDX-License-Identifier: MIT
3
3
  // Copyright (c) vis.gl contributors
4
4
 
5
- import type {MeshGeometry} from '@loaders.gl/schema';
6
- import type {GLTFPostprocessed, GLTFMeshPrimitivePostprocessed} from '@loaders.gl/gltf';
7
- import type {I3SMeshFeatures} from '@loaders.gl/i3s';
5
+ import type {MeshGeometry} from '@loaders.gl/core/schema';
6
+ import type {
7
+ GLTFWithBuffers,
8
+ GLTFMeshPrimitive,
9
+ GLTF_EXT_mesh_features,
10
+ GLTF_EXT_structural_metadata_GLTF
11
+ } from '@loaders.gl/gltf';
12
+ import {getGLTFAccessor, getGLTFAccessorValue} from '@loaders.gl/gltf';
13
+ import type {I3SMeshFeatures} from '@loaders.gl/tiles-3d/i3s';
8
14
  import {convertFeatureAttributesToArrowBatches} from './feature-arrow.js';
9
15
  import type {FeatureArrowBatchOptions} from './feature-arrow.js';
10
16
  import {createMeshSourceFeatureOptions} from './mesh-source-schema.js';
11
17
  import type {MeshSourceSchemaOptions} from './mesh-source-schema.js';
12
18
  import {TileConversionError} from '@loaders.gl/tile-converter/v5/core';
13
19
 
20
+ /** Data-only rule for absent columns and decoded noData values. */
21
+ interface MeshSourcePropertyMapping {
22
+ /** Value used for an absent column or a decoded noData cell. */
23
+ readonly defaultValue: unknown;
24
+ /** Decoded sentinel; omitted for absent-column defaults. */
25
+ readonly noData?: unknown;
26
+ }
27
+
14
28
  /** Explicit source feature mapping; target type changes must be authorized separately. */
15
29
  export interface MeshSourceFeatureOptions extends FeatureArrowBatchOptions {
16
30
  /** Batch-table class label, or the exact structural-metadata class to select. */
@@ -23,8 +37,8 @@ export interface MeshSourceFeatureOptions extends FeatureArrowBatchOptions {
23
37
 
24
38
  /** Extracts legacy batch tables or one decoded modern property table and validates geometry ownership. */
25
39
  export function extractMeshFeatures(
26
- gltf: GLTFPostprocessed,
27
- primitive: GLTFMeshPrimitivePostprocessed,
40
+ gltf: GLTFWithBuffers,
41
+ primitive: GLTFMeshPrimitive,
28
42
  payload: {
29
43
  batchTableJson?: Record<string, unknown>;
30
44
  header?: {batchLength?: number};
@@ -33,12 +47,16 @@ export function extractMeshFeatures(
33
47
  mesh: MeshGeometry,
34
48
  options?: MeshSourceFeatureOptions | MeshSourceSchemaOptions
35
49
  ): I3SMeshFeatures | undefined {
36
- const declarations = primitive.extensions?.EXT_mesh_features?.featureIds;
37
- const metadata = gltf.extensions?.EXT_structural_metadata;
50
+ const declarations = (
51
+ primitive.extensions?.EXT_mesh_features as GLTF_EXT_mesh_features | undefined
52
+ )?.featureIds;
53
+ const metadata = gltf.json.extensions?.EXT_structural_metadata as
54
+ | GLTF_EXT_structural_metadata_GLTF
55
+ | undefined;
38
56
  const legacy = primitive.attributes._BATCHID;
39
57
  if (
40
58
  !declarations &&
41
- !legacy &&
59
+ legacy === undefined &&
42
60
  !metadata &&
43
61
  !Object.keys(payload.batchTableJson || {}).length &&
44
62
  !payload.header?.batchLength
@@ -60,13 +78,15 @@ export function extractMeshFeatures(
60
78
  'MESH_FEATURE_CLASS_REQUIRED',
61
79
  'Feature metadata class must be explicit'
62
80
  );
63
- const mapping = 'schema' in options ? options : createMeshSourceFeatureOptions(gltf, options);
81
+ const mapping =
82
+ 'schema' in options ? options : createMeshSourceFeatureOptions(gltf.json, options);
83
+ const propertyMappings = new Map<string, MeshSourcePropertyMapping>();
64
84
  let count: number;
65
- let attribute = legacy;
85
+ let attributeReference = legacy;
66
86
  let properties: Record<string, ArrayLike<unknown>>;
67
87
  if (declarations) {
68
88
  if (
69
- legacy ||
89
+ legacy !== undefined ||
70
90
  declarations.length !== 1 ||
71
91
  metadata?.propertyTables?.length !== 1 ||
72
92
  metadata.propertyTextures?.length ||
@@ -78,6 +98,7 @@ export function extractMeshFeatures(
78
98
  );
79
99
  const declaration = declarations[0];
80
100
  if (
101
+ typeof declaration.attribute !== 'number' ||
81
102
  !Number.isInteger(declaration.attribute) ||
82
103
  declaration.attribute < 0 ||
83
104
  declaration.propertyTable !== 0 ||
@@ -89,26 +110,27 @@ export function extractMeshFeatures(
89
110
  'MESH_FEATURE_MAPPING_UNSUPPORTED',
90
111
  'Features require an attribute and property table 0 without null/texture/implicit IDs'
91
112
  );
92
- attribute = primitive.attributes[`_FEATURE_ID_${declaration.attribute}`];
113
+ attributeReference = primitive.attributes[`_FEATURE_ID_${declaration.attribute}`];
93
114
  const table = metadata.propertyTables[0];
94
- if (table.class !== options.metadataClass || table.count !== declaration.featureCount)
115
+ if (
116
+ table.class !== options.metadataClass ||
117
+ !Number.isSafeInteger(declaration.featureCount) ||
118
+ declaration.featureCount <= 0 ||
119
+ declaration.featureCount > table.count
120
+ )
95
121
  throw new TileConversionError(
96
122
  'MESH_FEATURE_CLASS_MISMATCH',
97
- 'Feature class and declared table counts must agree'
123
+ 'Feature class must agree and declared feature count must fit the property table'
98
124
  );
99
125
  count = table.count;
100
- // Qualified mesh writers require every feature row to own geometry. Check before defaults allocate.
101
- if (
102
- !Number.isSafeInteger(count) ||
103
- count <= 0 ||
104
- count > mesh.attributes.POSITION.value.length / 3
105
- ) {
126
+ // Source tables may be shared by primitives; dense output indices are uint32.
127
+ if (!Number.isSafeInteger(count) || count <= 0 || count > 0xffffffff) {
106
128
  throw new TileConversionError(
107
129
  'MESH_FEATURE_IDS_INVALID',
108
- 'Feature row count exceeds the available mesh vertices'
130
+ 'Feature row count must fit uint32 indices'
109
131
  );
110
132
  }
111
- properties = {};
133
+ properties = Object.create(null);
112
134
  const classProperties = metadata.schema?.classes?.[table.class]?.properties;
113
135
  if (!classProperties)
114
136
  throw new TileConversionError(
@@ -179,9 +201,10 @@ export function extractMeshFeatures(
179
201
  integer64 && property.default !== undefined
180
202
  ? BigInt(property.default as number)
181
203
  : (property.default ?? null);
182
- const decoded = data ?? Array.from({length: count}, () => defaultValue);
204
+ const decoded = data;
183
205
  if (property.noData === undefined || data === undefined) {
184
- properties[name] = decoded;
206
+ if (decoded !== undefined) properties[name] = decoded as ArrayLike<unknown>;
207
+ else propertyMappings.set(name, {defaultValue});
185
208
  continue;
186
209
  }
187
210
  const sentinel = property.noData;
@@ -200,9 +223,9 @@ export function extractMeshFeatures(
200
223
  `Property ${name} requires a valid decoded noData mapping`
201
224
  );
202
225
  const comparable = integer64 ? BigInt(sentinel as number) : sentinel;
203
- properties[name] = Array.from(decoded as ArrayLike<unknown>, value =>
204
- value === comparable ? defaultValue : value
205
- );
226
+ const values = decoded as ArrayLike<unknown>;
227
+ properties[name] = values;
228
+ propertyMappings.set(name, {noData: comparable, defaultValue});
206
229
  }
207
230
  } else {
208
231
  if (metadata)
@@ -213,6 +236,15 @@ export function extractMeshFeatures(
213
236
  count = payload.header?.batchLength ?? Number(payload.featureTableJson?.BATCH_LENGTH);
214
237
  properties = (payload.batchTableJson as Record<string, ArrayLike<unknown>>) || {};
215
238
  }
239
+ const accessor =
240
+ attributeReference === undefined ? undefined : getGLTFAccessor(gltf, attributeReference);
241
+ const attribute = accessor
242
+ ? {
243
+ ...accessor,
244
+ components: accessor.type === 'SCALAR' ? 1 : Number(accessor.type.slice(3)),
245
+ value: getGLTFAccessorValue(gltf, accessor)
246
+ }
247
+ : undefined;
216
248
  const vertexCount = mesh.attributes.POSITION.value.length / 3;
217
249
  if (
218
250
  !Number.isSafeInteger(count) ||
@@ -220,15 +252,26 @@ export function extractMeshFeatures(
220
252
  !attribute ||
221
253
  attribute.components !== 1 ||
222
254
  attribute.normalized ||
223
- attribute.value.length !== vertexCount ||
224
- Array.from(attribute.value as ArrayLike<number>).some(
225
- value => !Number.isSafeInteger(value) || value < 0 || value >= count
226
- )
255
+ attribute.value.length !== vertexCount
227
256
  )
228
257
  throw new TileConversionError(
229
258
  'MESH_FEATURE_IDS_INVALID',
230
259
  'Feature IDs must be one valid property-table row index per vertex'
231
260
  );
261
+ const vertexRows = declarations ? new Set<number>() : undefined;
262
+ for (const value of attribute.value) {
263
+ if (!Number.isSafeInteger(value) || value < 0 || value >= count)
264
+ throw new TileConversionError(
265
+ 'MESH_FEATURE_IDS_INVALID',
266
+ 'Feature IDs must reference valid property-table rows'
267
+ );
268
+ vertexRows?.add(Number(value));
269
+ }
270
+ if (declarations && vertexRows!.size !== declarations[0].featureCount)
271
+ throw new TileConversionError(
272
+ 'MESH_FEATURE_IDS_INVALID',
273
+ 'Declared feature count must match distinct vertex feature IDs'
274
+ );
232
275
  const selectedAttribute = declarations ? `_FEATURE_ID_${declarations[0].attribute}` : '_BATCHID';
233
276
  if (
234
277
  Object.keys(primitive.attributes).some(
@@ -247,45 +290,84 @@ export function extractMeshFeatures(
247
290
  )
248
291
  throw new TileConversionError(
249
292
  'MESH_FEATURE_PROPERTY_UNAVAILABLE',
250
- `Property ${name} must be a decoded column matching the table count`
293
+ `Property ${name} must be a decoded column matching the feature row count`
251
294
  );
252
295
  }
253
296
  const sourceIdentifier = options.sourceFeatureIdProperty;
254
- if (sourceIdentifier && !properties[sourceIdentifier])
297
+ if (sourceIdentifier && !properties[sourceIdentifier] && !propertyMappings.has(sourceIdentifier))
255
298
  throw new TileConversionError(
256
299
  'MESH_FEATURE_ID_PROPERTY_REQUIRED',
257
300
  'Stable source identifier property is missing'
258
301
  );
259
- const batches = convertFeatureAttributesToArrowBatches(
260
- Array.from({length: count}, (_, index) => ({
261
- featureId: (sourceIdentifier ? properties[sourceIdentifier][index] : index) as
262
- | string
263
- | number
264
- | bigint,
265
- metadataClass: options.metadataClass,
266
- properties: Object.fromEntries(
267
- Object.entries(properties)
268
- .filter(([name]) => name !== sourceIdentifier)
269
- .map(([name, values]) => [name, values[index]])
270
- )
271
- })),
272
- mapping
273
- );
274
302
  const indices = mesh.indices?.value;
275
- const triangleFeatureIndices = new Uint32Array((indices?.length ?? vertexCount) / 3);
276
- for (let triangle = 0; triangle < triangleFeatureIndices.length; triangle++) {
277
- const corners = [0, 1, 2].map(
278
- corner => indices?.[triangle * 3 + corner] ?? triangle * 3 + corner
303
+ const cornerCount = indices?.length ?? vertexCount;
304
+ if (cornerCount % 3)
305
+ throw new TileConversionError(
306
+ 'MESH_FEATURE_IDS_INVALID',
307
+ 'Triangle geometry requires complete triples'
279
308
  );
280
- const rows = corners.map(corner => attribute.value[corner]);
309
+ const triangleFeatureIndices = new Uint32Array(cornerCount / 3);
310
+ const referencedRows = new Set<number>();
311
+ /** Reads a validated triangle corner without allocating per-triangle arrays. */
312
+ const readFeatureRow = (corner: number): number => {
313
+ if (!Number.isSafeInteger(corner) || corner < 0 || corner >= vertexCount)
314
+ throw new TileConversionError(
315
+ 'MESH_FEATURE_IDS_INVALID',
316
+ 'Triangle indices must reference valid mesh vertices'
317
+ );
318
+ return Number(attribute.value[corner]);
319
+ };
320
+ for (let triangle = 0; triangle < triangleFeatureIndices.length; triangle++) {
321
+ const firstCorner = indices?.[triangle * 3] ?? triangle * 3;
322
+ const secondCorner = indices?.[triangle * 3 + 1] ?? triangle * 3 + 1;
323
+ const thirdCorner = indices?.[triangle * 3 + 2] ?? triangle * 3 + 2;
324
+ const firstRow = readFeatureRow(firstCorner);
325
+ const secondRow = readFeatureRow(secondCorner);
326
+ const thirdRow = readFeatureRow(thirdCorner);
281
327
  // Repeated-index strip connectors have no surface; retain the first corner's feature row.
282
- if (rows.some(row => row !== rows[0]) && new Set(corners).size === 3)
328
+ if (
329
+ (firstRow !== secondRow || firstRow !== thirdRow) &&
330
+ firstCorner !== secondCorner &&
331
+ firstCorner !== thirdCorner &&
332
+ secondCorner !== thirdCorner
333
+ )
283
334
  throw new TileConversionError(
284
335
  'MESH_FEATURE_TRIANGLE_MIXED',
285
336
  'All vertices of a nondegenerate triangle must reference the same feature row'
286
337
  );
287
- triangleFeatureIndices[triangle] = Number(rows[0]);
338
+ triangleFeatureIndices[triangle] = firstRow;
339
+ referencedRows.add(firstRow);
288
340
  }
341
+ // Sort once to retain source table order, then materialize only geometry-owned rows.
342
+ const selectedRows = [...referencedRows].sort((left, right) => left - right);
343
+ const denseRows = new Map(selectedRows.map((row, index) => [row, index]));
344
+ for (let triangle = 0; triangle < triangleFeatureIndices.length; triangle++) {
345
+ triangleFeatureIndices[triangle] = denseRows.get(triangleFeatureIndices[triangle])!;
346
+ }
347
+ /** Reads one referenced source value without expanding defaults or noData columns. */
348
+ const readProperty = (name: string, row: number): unknown => {
349
+ const values = properties[name];
350
+ const propertyMapping = propertyMappings.get(name);
351
+ if (!values) return propertyMapping?.defaultValue;
352
+ const value = values[row];
353
+ return propertyMapping && value === propertyMapping.noData
354
+ ? propertyMapping.defaultValue
355
+ : value;
356
+ };
357
+ const propertyNames = [
358
+ ...new Set([...Object.keys(properties), ...propertyMappings.keys()])
359
+ ].filter(name => name !== sourceIdentifier);
360
+ const batches = convertFeatureAttributesToArrowBatches(
361
+ selectedRows.map(row => ({
362
+ featureId: (sourceIdentifier ? readProperty(sourceIdentifier, row) : row) as
363
+ | string
364
+ | number
365
+ | bigint,
366
+ metadataClass: options.metadataClass,
367
+ properties: Object.fromEntries(propertyNames.map(name => [name, readProperty(name, row)]))
368
+ })),
369
+ mapping
370
+ );
289
371
  return {
290
372
  batches,
291
373
  triangleFeatureIndices,
@@ -2,8 +2,12 @@
2
2
  // SPDX-License-Identifier: MIT
3
3
  // Copyright (c) vis.gl contributors
4
4
 
5
- import type {GLTFPostprocessed, GLTF_EXT_structural_metadata_ClassProperty} from '@loaders.gl/gltf';
6
- import type {DataType, Field} from '@loaders.gl/schema';
5
+ import type {
6
+ GLTF,
7
+ GLTF_EXT_structural_metadata_ClassProperty,
8
+ GLTF_EXT_structural_metadata_GLTF
9
+ } from '@loaders.gl/gltf';
10
+ import type {DataType, Field} from '@loaders.gl/core/schema';
7
11
  import {TileConversionError} from '@loaders.gl/tile-converter/v5/core';
8
12
  import type {MeshSourceFeatureOptions} from './mesh-source-features.js';
9
13
 
@@ -32,10 +36,12 @@ const SCALAR_TYPES: Readonly<Record<string, DataType>> = {
32
36
  * This does not authorize destination type changes or loss of source class semantics.
33
37
  */
34
38
  export function createMeshSourceFeatureOptions(
35
- gltf: GLTFPostprocessed,
39
+ gltf: GLTF,
36
40
  options: MeshSourceSchemaOptions
37
41
  ): MeshSourceFeatureOptions {
38
- const metadata = gltf.extensions?.EXT_structural_metadata;
42
+ const metadata = gltf.extensions?.EXT_structural_metadata as
43
+ | GLTF_EXT_structural_metadata_GLTF
44
+ | undefined;
39
45
  const table = metadata?.propertyTables?.[0];
40
46
  const properties = metadata?.schema?.classes?.[options.metadataClass]?.properties;
41
47
  if (
@@ -3,17 +3,20 @@
3
3
  // Copyright (c) vis.gl contributors
4
4
 
5
5
  import {getBinaryImageMetadata} from '@loaders.gl/images';
6
- import type {GLTFMaterialPostprocessed, GLTFImagePostprocessed} from '@loaders.gl/gltf';
6
+ import type {GLTFMaterial, GLTFImage, GLTFWithBuffers} from '@loaders.gl/gltf';
7
+ import {GLTFScenegraph} from '@loaders.gl/gltf';
7
8
  import {TileConversionError} from '@loaders.gl/tile-converter/v5/core';
8
9
  import type {MeshTileSampler, MeshTileTexture, MeshTileTextureTransform} from './mesh.js';
9
10
 
10
11
  /** Maps a buffer-view or base64 PNG/JPEG image, sampling and UV transform without decoding pixels. */
11
12
  export function mapMeshSourceTexture(
12
- textureInfo: NonNullable<GLTFMaterialPostprocessed['pbrMetallicRoughness']>['baseColorTexture']
13
+ gltf: GLTFWithBuffers,
14
+ textureInfo: NonNullable<GLTFMaterial['pbrMetallicRoughness']>['baseColorTexture']
13
15
  ): MeshTileTexture {
14
- const texture = textureInfo?.texture;
15
- const image = texture?.source;
16
- const sampler = texture?.sampler;
16
+ const texture = textureInfo ? gltf.json.textures?.[textureInfo.index] : undefined;
17
+ const image = texture?.source === undefined ? undefined : gltf.json.images?.[texture.source];
18
+ const sampler =
19
+ texture?.sampler === undefined ? undefined : gltf.json.samplers?.[texture.sampler];
17
20
  const extensions = textureInfo?.extensions || {};
18
21
  const hasTransform = Object.hasOwn(extensions, 'KHR_texture_transform');
19
22
  const transform = extensions.KHR_texture_transform;
@@ -43,8 +46,9 @@ export function mapMeshSourceTexture(
43
46
  (image.uri !== undefined && image.bufferView !== undefined) ||
44
47
  Object.keys(image.extensions || {}).length ||
45
48
  (image.uri === undefined &&
46
- (!(image.bufferView?.data instanceof Uint8Array) ||
47
- Object.keys(image.bufferView.extensions || {}).length ||
49
+ (image.bufferView === undefined ||
50
+ !gltf.json.bufferViews?.[image.bufferView] ||
51
+ Object.keys(gltf.json.bufferViews?.[image.bufferView!]?.extensions || {}).length ||
48
52
  !['image/png', 'image/jpeg'].includes(image.mimeType || ''))) ||
49
53
  (sampler &&
50
54
  (Object.keys(sampler.extensions || {}).length ||
@@ -77,7 +81,7 @@ export function mapMeshSourceTexture(
77
81
  } as MeshTileSampler)
78
82
  : undefined;
79
83
  return {
80
- ...mapMeshSourceImage(image),
84
+ ...mapMeshSourceImage(gltf, image),
81
85
  ...(hasTransform
82
86
  ? {
83
87
  transform: {
@@ -95,10 +99,14 @@ export function mapMeshSourceTexture(
95
99
 
96
100
  /** Reads self-contained encoded bytes without fetch, pixel decoding, or Node APIs. */
97
101
  function mapMeshSourceImage(
98
- image: GLTFImagePostprocessed
102
+ gltf: GLTFWithBuffers,
103
+ image: GLTFImage
99
104
  ): Pick<MeshTileTexture, 'data' | 'mimeType'> {
100
105
  if (image.uri === undefined) {
101
- return {data: image.bufferView!.data, mimeType: image.mimeType as MeshTileTexture['mimeType']};
106
+ return {
107
+ data: new GLTFScenegraph(gltf).getTypedArrayForBufferView(image.bufferView!),
108
+ mimeType: image.mimeType as MeshTileTexture['mimeType']
109
+ };
102
110
  }
103
111
  const match =
104
112
  typeof image.uri === 'string'
@@ -124,16 +132,18 @@ function mapMeshSourceImage(
124
132
 
125
133
  /** Resolves one external encoded image through an explicitly supplied application reader. */
126
134
  export async function resolveMeshSourceTexture(
127
- textureInfo: NonNullable<GLTFMaterialPostprocessed['pbrMetallicRoughness']>['baseColorTexture'],
135
+ gltf: GLTFWithBuffers,
136
+ textureInfo: NonNullable<GLTFMaterial['pbrMetallicRoughness']>['baseColorTexture'],
128
137
  readExternalResource:
129
138
  | ((uri: string, contentUri: string | undefined, signal?: AbortSignal) => Promise<Uint8Array>)
130
139
  | undefined,
131
140
  contentUri: string | undefined,
132
141
  signal?: AbortSignal
133
142
  ): Promise<MeshTileTexture> {
134
- const image = textureInfo?.texture?.source;
143
+ const texture = textureInfo ? gltf.json.textures?.[textureInfo.index] : undefined;
144
+ const image = texture?.source === undefined ? undefined : gltf.json.images?.[texture.source];
135
145
  if (!image || image.uri === undefined || image.uri.startsWith('data:'))
136
- return mapMeshSourceTexture(textureInfo);
146
+ return mapMeshSourceTexture(gltf, textureInfo);
137
147
  if (!readExternalResource || image.bufferView !== undefined)
138
148
  throw new TileConversionError(
139
149
  'MESH_SOURCE_TEXTURE_UNSUPPORTED',
@@ -158,12 +168,32 @@ export async function resolveMeshSourceTexture(
158
168
  'External images require matching PNG/JPEG headers'
159
169
  );
160
170
  // Select encoded bytes without decoding pixels or modifying the caller-owned glTF image.
161
- const resolved = {
162
- ...textureInfo,
163
- texture: {
164
- ...textureInfo!.texture,
165
- source: {...image, uri: undefined, mimeType: metadata.mimeType, bufferView: {data}}
171
+ const resolvedImage = {
172
+ ...image,
173
+ uri: undefined,
174
+ mimeType: metadata.mimeType,
175
+ bufferView: (gltf.json.bufferViews || []).length
176
+ };
177
+ const resolved: GLTFWithBuffers = {
178
+ ...gltf,
179
+ buffers: [
180
+ ...gltf.buffers,
181
+ {
182
+ arrayBuffer: data.buffer as ArrayBuffer,
183
+ byteOffset: data.byteOffset,
184
+ byteLength: data.byteLength
185
+ }
186
+ ],
187
+ json: {
188
+ ...gltf.json,
189
+ images: gltf.json.images!.map((entry, index) =>
190
+ index === texture!.source ? resolvedImage : entry
191
+ ),
192
+ bufferViews: [
193
+ ...(gltf.json.bufferViews || []),
194
+ {buffer: gltf.buffers.length, byteLength: data.byteLength}
195
+ ]
166
196
  }
167
197
  };
168
- return mapMeshSourceTexture(resolved as typeof textureInfo);
198
+ return mapMeshSourceTexture(resolved, textureInfo);
169
199
  }