@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
@@ -3,14 +3,10 @@
3
3
  // Copyright (c) vis.gl contributors
4
4
 
5
5
  import {Matrix4} from '@math.gl/core';
6
- import type {MeshAttribute} from '@loaders.gl/schema';
7
- import type {
8
- GLTFPostprocessed,
9
- GLTFNodePostprocessed,
10
- GLTFMeshPrimitivePostprocessed,
11
- GLTFMaterialPostprocessed
12
- } from '@loaders.gl/gltf';
13
- import type {Tileset3D, TilesetSourceMetadata} from '@loaders.gl/tiles';
6
+ import type {MeshAttribute} from '@loaders.gl/core/schema';
7
+ import type {GLTFWithBuffers, GLTFMeshPrimitive, GLTFMaterial} from '@loaders.gl/gltf';
8
+ import {getGLTFAccessor, getGLTFAccessorValue} from '@loaders.gl/gltf';
9
+ import type {Tileset3D, TilesetSourceMetadata} from '@loaders.gl/tiles-3d';
14
10
  import {TileConversionError} from '@loaders.gl/tile-converter/v5/core';
15
11
  import type {TileConversionSource} from '@loaders.gl/tile-converter/v5/core';
16
12
  import {createTilesetConversionSource} from '@loaders.gl/tile-converter/v5/core';
@@ -22,7 +18,7 @@ import {resolveMeshSourceTexture} from './mesh-source-texture.js';
22
18
  import {extractMeshFeatures} from './mesh-source-features.js';
23
19
  import type {MeshSourceFeatureOptions} from './mesh-source-features.js';
24
20
  import type {MeshSourceSchemaOptions} from './mesh-source-schema.js';
25
- import type {I3SMeshFeatures} from '@loaders.gl/i3s';
21
+ import type {I3SMeshFeatures} from '@loaders.gl/tiles-3d/i3s';
26
22
  export type {MeshSourceFeatureOptions} from './mesh-source-features.js';
27
23
 
28
24
  /** Source mesh with optional explicit-schema feature data and triangle association. */
@@ -50,7 +46,7 @@ export interface MeshTilesetSourceOptions extends TilesetConversionSourceOptions
50
46
  * optional buffer-view or base64 data-URI PNG/JPEG images, TEXCOORD_0, glTF sampling and
51
47
  * KHR_texture_transform controls on TEXCOORD_0. Decode content with
52
48
  * `gltf.excludeExtensions: {KHR_texture_transform: false}` to retain authored UVs/transforms.
53
- * Triangle strips are expanded with alternating winding; the glTF loader already expands fans.
49
+ * Triangle strips are expanded with alternating winding; fans are expanded explicitly at the mesh boundary.
54
50
  * Shared traversal retains placement identity and unloads content according to the supplied policy.
55
51
  * The adapter applies node transforms, glTF up-axis correction, RTC translation, and tile placement
56
52
  * once, retaining Float64 absolute positions and inverse-transpose unit normals. Animation, skins,
@@ -120,7 +116,7 @@ export function createMeshTilesetConversionSource(
120
116
  'Content metadata requires a separate mapping'
121
117
  );
122
118
  const payload = content.payload as {
123
- gltf?: GLTFPostprocessed;
119
+ gltf?: GLTFWithBuffers;
124
120
  gltfUpAxis?: string;
125
121
  rtcCenter?: number[];
126
122
  type?: string;
@@ -137,7 +133,8 @@ export function createMeshTilesetConversionSource(
137
133
  'Mesh extraction requires decoded GLB/glTF or B3DM content'
138
134
  );
139
135
  }
140
- const gltf = payload.gltf;
136
+ const gltfWithBuffers = payload.gltf;
137
+ const gltf = gltfWithBuffers.json;
141
138
  if (
142
139
  gltf.animations?.length ||
143
140
  gltf.skins?.length ||
@@ -154,7 +151,9 @@ export function createMeshTilesetConversionSource(
154
151
  if (!['X', 'Y', 'Z'].includes(axis))
155
152
  throw new TileConversionError('MESH_SOURCE_AXIS_UNSUPPORTED', 'Unknown glTF up axis');
156
153
  const placement = new Matrix4(item.tile.computedTransform);
157
- const rtc = payload.rtcCenter || gltf.extensions?.CESIUM_RTC?.center;
154
+ const rtc =
155
+ payload.rtcCenter ||
156
+ (gltf.extensions?.CESIUM_RTC as {center?: number[]} | undefined)?.center;
158
157
  if (rtc) {
159
158
  if (rtc.length !== 3 || rtc.some((value: number) => !Number.isFinite(value)))
160
159
  throw new TileConversionError(
@@ -168,19 +167,21 @@ export function createMeshTilesetConversionSource(
168
167
  if (axis === 'X')
169
168
  placement.multiplyRight(new Matrix4([0, 0, 1, 0, 0, 1, 0, 0, -1, 0, 0, 0, 0, 0, 0, 1]));
170
169
  const roots =
171
- gltf.scene?.nodes || (gltf.scenes?.length === 1 ? gltf.scenes[0].nodes : undefined);
170
+ (gltf.scene !== undefined ? gltf.scenes?.[gltf.scene]?.nodes : undefined) ||
171
+ (gltf.scenes?.length === 1 ? gltf.scenes[0].nodes : undefined);
172
172
  if (!roots?.length)
173
173
  throw new TileConversionError(
174
174
  'MESH_SOURCE_SCENE_UNSUPPORTED',
175
175
  'A default scene or exactly one scene is required'
176
176
  );
177
177
  let primitiveIndex = 0;
178
- const primitives = traverseMeshNodes(roots, placement);
178
+ const primitives = traverseMeshNodes(gltfWithBuffers, roots, placement);
179
179
  let current = primitives.next();
180
180
  while (!current.done) {
181
181
  const selected = current.value;
182
182
  const next = primitives.next();
183
183
  const resource = await extractPrimitive(
184
+ gltfWithBuffers,
184
185
  selected.primitive,
185
186
  selected.transform,
186
187
  `${item.tile.id}/${content.index}/${primitiveIndex++}`,
@@ -189,7 +190,7 @@ export function createMeshTilesetConversionSource(
189
190
  signal
190
191
  );
191
192
  const features = extractMeshFeatures(
192
- gltf,
193
+ gltfWithBuffers,
193
194
  selected.primitive,
194
195
  payload,
195
196
  resource.mesh,
@@ -213,16 +214,19 @@ export function createMeshTilesetConversionSource(
213
214
  };
214
215
  }
215
216
 
216
- /** Walks scene placements without deduplicating a shared mesh or mutating postprocessed nodes. */
217
+ /** Walks scene placements without deduplicating a shared mesh or mutating source nodes. */
217
218
  function* traverseMeshNodes(
218
- nodes: readonly GLTFNodePostprocessed[],
219
+ gltf: GLTFWithBuffers,
220
+ nodes: readonly number[],
219
221
  parent: Matrix4,
220
- ancestors = new Set<GLTFNodePostprocessed>()
221
- ): Generator<{primitive: GLTFMeshPrimitivePostprocessed; transform: Matrix4}> {
222
- for (const node of nodes) {
222
+ ancestors = new Set<number>()
223
+ ): Generator<{primitive: GLTFMeshPrimitive; transform: Matrix4}> {
224
+ for (const nodeIndex of nodes) {
225
+ const node = gltf.json.nodes![nodeIndex];
223
226
  if (
224
- ancestors.has(node) ||
225
- node.skin ||
227
+ !node ||
228
+ ancestors.has(nodeIndex) ||
229
+ node.skin !== undefined ||
226
230
  node.weights ||
227
231
  Object.keys(node.extensions || {}).length
228
232
  )
@@ -238,21 +242,28 @@ function* traverseMeshNodes(
238
242
  .multiplyRight(new Matrix4().fromQuaternion(node.rotation || [0, 0, 0, 1]))
239
243
  .scale(node.scale || [1, 1, 1])
240
244
  );
241
- if (node.mesh) {
242
- if (node.mesh.weights || Object.keys(node.mesh.extensions || {}).length)
245
+ if (node.mesh !== undefined) {
246
+ const mesh = gltf.json.meshes![node.mesh];
247
+ if (mesh.weights || Object.keys(mesh.extensions || {}).length)
243
248
  throw new TileConversionError(
244
249
  'MESH_SOURCE_NODE_UNSUPPORTED',
245
250
  'Morph weights and mesh extensions are unsupported'
246
251
  );
247
- for (const primitive of node.mesh.primitives) yield {primitive, transform};
252
+ for (const primitive of mesh.primitives) yield {primitive, transform};
248
253
  }
249
- yield* traverseMeshNodes(node.children || [], transform, new Set([...ancestors, node]));
254
+ yield* traverseMeshNodes(
255
+ gltf,
256
+ node.children || [],
257
+ transform,
258
+ new Set([...ancestors, nodeIndex])
259
+ );
250
260
  }
251
261
  }
252
262
 
253
263
  /** Copies one packed primitive, places it once, and derives a useful ECEF local origin. */
254
264
  async function extractPrimitive(
255
- primitive: GLTFMeshPrimitivePostprocessed,
265
+ gltf: GLTFWithBuffers,
266
+ primitive: GLTFMeshPrimitive,
256
267
  transform: Matrix4,
257
268
  id: string,
258
269
  readExternalResource: MeshTilesetSourceOptions['readExternalResource'],
@@ -280,7 +291,8 @@ async function extractPrimitive(
280
291
  'Mesh placement requires a finite invertible affine transform with positive determinant'
281
292
  );
282
293
  const attributes: Record<string, MeshAttribute> = {};
283
- for (const [name, accessor] of Object.entries(primitive.attributes)) {
294
+ for (const [name, reference] of Object.entries(primitive.attributes)) {
295
+ const accessor = getGLTFAccessor(gltf, reference);
284
296
  if (name === '_BATCHID' || /^_FEATURE_ID_\d+$/.test(name)) continue;
285
297
  if (!['POSITION', 'NORMAL', 'COLOR_0', 'TEXCOORD_0'].includes(name))
286
298
  throw new TileConversionError(
@@ -288,17 +300,17 @@ async function extractPrimitive(
288
300
  `Unsupported mesh attribute ${name}`
289
301
  );
290
302
  attributes[name] = {
291
- value: accessor.value as MeshAttribute['value'],
292
- size: accessor.components,
303
+ value: getGLTFAccessorValue(gltf, reference) as MeshAttribute['value'],
304
+ size: accessor.type === 'SCALAR' ? 1 : Number(accessor.type.slice(3)),
293
305
  ...(accessor.normalized ? {normalized: true} : {})
294
306
  };
295
307
  }
296
308
  const mesh = validateMeshGeometry(
297
309
  {
298
310
  topology: 'triangle-list',
299
- mode: primitive.mode === 5 ? 4 : (primitive.mode ?? 4),
311
+ mode: [5, 6].includes(primitive.mode ?? 4) ? 4 : (primitive.mode ?? 4),
300
312
  attributes,
301
- indices: createTriangleIndices(primitive, (attributes.POSITION?.value.length ?? 0) / 3)
313
+ indices: createTriangleIndices(gltf, primitive, (attributes.POSITION?.value.length ?? 0) / 3)
302
314
  },
303
315
  true
304
316
  );
@@ -336,20 +348,22 @@ async function extractPrimitive(
336
348
  mesh,
337
349
  origin: minimum.map((value, axis) => value / 2 + maximum[axis] / 2) as [number, number, number],
338
350
  material: await mapMeshSourceMaterial(
339
- primitive.material,
351
+ primitive.material === undefined ? undefined : gltf.json.materials?.[primitive.material],
340
352
  readExternalResource,
341
353
  contentUri,
342
- signal
354
+ signal,
355
+ gltf
343
356
  )
344
357
  };
345
358
  }
346
359
 
347
360
  /** Maps supported PBR controls and an embedded base-color image, rejecting unmapped semantics. */
348
361
  export async function mapMeshSourceMaterial(
349
- material: GLTFMaterialPostprocessed | undefined,
362
+ material: GLTFMaterial | undefined,
350
363
  readExternalResource: MeshTilesetSourceOptions['readExternalResource'],
351
364
  contentUri: string | undefined,
352
- signal?: AbortSignal
365
+ signal: AbortSignal | undefined,
366
+ gltf: GLTFWithBuffers
353
367
  ): Promise<MeshTileMaterial | undefined> {
354
368
  if (!material) return undefined;
355
369
  const pbr = material.pbrMetallicRoughness;
@@ -384,6 +398,7 @@ export async function mapMeshSourceMaterial(
384
398
  ...('baseColorTexture' in (pbr || {})
385
399
  ? {
386
400
  baseColorTexture: await resolveMeshSourceTexture(
401
+ gltf,
387
402
  pbr!.baseColorTexture,
388
403
  readExternalResource,
389
404
  contentUri,
@@ -401,17 +416,20 @@ export async function mapMeshSourceMaterial(
401
416
 
402
417
  /** Expands a strip without changing vertex storage, feature IDs, source indices, or parity. */
403
418
  function createTriangleIndices(
404
- primitive: GLTFMeshPrimitivePostprocessed,
419
+ gltf: GLTFWithBuffers,
420
+ primitive: GLTFMeshPrimitive,
405
421
  vertexCount: number
406
422
  ): MeshAttribute | undefined {
407
- const attribute = primitive.indices
423
+ const accessor =
424
+ primitive.indices === undefined ? undefined : getGLTFAccessor(gltf, primitive.indices);
425
+ const attribute = accessor
408
426
  ? {
409
- value: primitive.indices.value as MeshAttribute['value'],
410
- size: primitive.indices.components,
411
- normalized: primitive.indices.normalized
427
+ value: getGLTFAccessorValue(gltf, accessor) as MeshAttribute['value'],
428
+ size: 1,
429
+ normalized: accessor.normalized
412
430
  }
413
431
  : undefined;
414
- if (primitive.mode !== 5) return attribute;
432
+ if (primitive.mode !== 5 && primitive.mode !== 6) return attribute;
415
433
  const sourceIndices = validateMeshIndices(attribute, vertexCount, 5);
416
434
  const indexCount = sourceIndices?.length ?? vertexCount;
417
435
  if (!Number.isSafeInteger(indexCount) || indexCount < 3) {
@@ -424,9 +442,10 @@ function createTriangleIndices(
424
442
  const indices = new IndexArray((indexCount - 2) * 3);
425
443
  for (let triangle = 0; triangle < indexCount - 2; triangle++) {
426
444
  // Match the glTF strip equation, including degenerate connectors; they advance parity too.
427
- const second = triangle + 1 + (triangle % 2);
428
- const third = triangle + 2 - (triangle % 2);
429
- indices[triangle * 3] = sourceIndices?.[triangle] ?? triangle;
445
+ const second = primitive.mode === 6 ? triangle + 1 : triangle + 1 + (triangle % 2);
446
+ const third = primitive.mode === 6 ? triangle + 2 : triangle + 2 - (triangle % 2);
447
+ const first = primitive.mode === 6 ? 0 : triangle;
448
+ indices[triangle * 3] = sourceIndices?.[first] ?? first;
430
449
  indices[triangle * 3 + 1] = sourceIndices?.[second] ?? second;
431
450
  indices[triangle * 3 + 2] = sourceIndices?.[third] ?? third;
432
451
  }
@@ -2,7 +2,7 @@
2
2
  // SPDX-License-Identifier: MIT
3
3
  // Copyright (c) vis.gl contributors
4
4
 
5
- import {Tiles3DArchiveWriter} from '@loaders.gl/3d-tiles/3d-tiles-archive-writer';
5
+ import {Tiles3DArchiveWriter} from '@loaders.gl/tiles-3d/3d-tiles/3d-tiles-archive-writer';
6
6
  import {TileConversionError} from '@loaders.gl/tile-converter/v5/core';
7
7
  import type {BrowserTileConversionFile} from '@loaders.gl/tile-converter/v5/core';
8
8
 
@@ -2,7 +2,7 @@
2
2
  // SPDX-License-Identifier: MIT
3
3
  // Copyright (c) vis.gl contributors
4
4
 
5
- import type {Tiles3DTilesetJSON, Tiles3DTileJSON} from '@loaders.gl/3d-tiles';
5
+ import type {Tiles3DTilesetJSON, Tiles3DTileJSON} from '@loaders.gl/tiles-3d/3d-tiles';
6
6
  import {TileConversionError} from '@loaders.gl/tile-converter/v5/core';
7
7
  import type {TileConversionSink} from '@loaders.gl/tile-converter/v5/core';
8
8
  import {createBoundedMemoryTileConversionSink} from '@loaders.gl/tile-converter/v5/core';
package/src/v5/mesh.ts CHANGED
@@ -4,7 +4,7 @@
4
4
 
5
5
  import {GLTFScenegraph, GLTFWriter} from '@loaders.gl/gltf';
6
6
  import {getBinaryImageMetadata} from '@loaders.gl/images';
7
- import type {MeshAttribute, MeshGeometry} from '@loaders.gl/schema';
7
+ import type {MeshAttribute, MeshGeometry} from '@loaders.gl/core/schema';
8
8
  import {
9
9
  prepareMeshFeatureGeometry,
10
10
  encodeMeshFeatures,
@@ -31,6 +31,7 @@ const HELP = `Usage: tile-converter-v5 inspect|convert|validate <input path or U
31
31
  --max-resources N Lower the 1,000 content-declaration limit
32
32
  --max-documents N Lower the 32 metadata-document limit
33
33
  --max-output-bytes N Lower the 32 MiB output/archive budget
34
+ --profile Include elapsed conversion stage timings in JSON
34
35
  --help Show this help
35
36
 
36
37
  Inspect reads metadata only, following declared .json external tilesets.
@@ -54,6 +55,7 @@ export async function runTileConverterCLI(
54
55
  allowPositionals: true,
55
56
  options: {
56
57
  help: {type: 'boolean'},
58
+ profile: {type: 'boolean'},
57
59
  'preserve-hierarchy': {type: 'boolean'},
58
60
  'input-format': {type: 'string', default: '3d-tiles'},
59
61
  'max-input-bytes': {type: 'string', default: String(NODE_TILESET_LIMITS.maxInputBytes)},
@@ -123,6 +125,7 @@ export async function runTileConverterCLI(
123
125
  ...sourceOptions,
124
126
  ...features,
125
127
  resourceIds: values.resource,
128
+ profile: values.profile,
126
129
  preserveHierarchy: values['preserve-hierarchy'],
127
130
  outputFormat: values['output-format'] as NodeTileConversionOutputFormat,
128
131
  output: values.output,
@@ -139,6 +142,7 @@ export async function runTileConverterCLI(
139
142
  return 0;
140
143
  }
141
144
  if (
145
+ values.profile !== undefined ||
142
146
  values['feature-schema'] !== undefined ||
143
147
  values['preserve-hierarchy'] !== undefined ||
144
148
  values['geometric-error'] !== undefined ||
@@ -35,6 +35,8 @@ export type NodeTileConversionOutputFormat = '3d-tiles' | '3tz' | 'slpk';
35
35
  /** Selected static mesh conversion with application-owned Node output. */
36
36
  export interface NodeTileConversionOptions
37
37
  extends Omit<NodeTilesetConversionSourceOptions, 'format'> {
38
+ /** Include portable stages and Node setup/output elapsed milliseconds in the report. */
39
+ readonly profile?: boolean;
38
40
  /** Explicit 3D Tiles, I3S layer or indexed archive source. */
39
41
  readonly format?: '3d-tiles' | '3tz' | 'i3s' | 'slpk';
40
42
  /** Explicit I3S attribute mapping, also supplied by the CLI feature-schema file. */
@@ -61,6 +63,8 @@ export interface NodeTileConversionOptions
61
63
  export async function convertNodeTileset(
62
64
  options: NodeTileConversionOptions
63
65
  ): Promise<TileConversionReport> {
66
+ const started = options.profile ? performance.now() : 0;
67
+ let setupMilliseconds = 0;
64
68
  const signal = options.signal ?? new AbortController().signal;
65
69
  const maxOutputBytes = options.maxOutputBytes ?? SELECTED_MESH_LIMITS.maxOutputBytes;
66
70
  if (
@@ -124,7 +128,7 @@ export async function convertNodeTileset(
124
128
  let report: TileConversionReport;
125
129
  try {
126
130
  const inspection = await rawSource.inspect(signal);
127
- await import('@loaders.gl/polyfills');
131
+ await import('@loaders.gl/core/polyfills');
128
132
  const {getDracoLibraryOptions} = await import('@loaders.gl/draco/bundled');
129
133
  const dracoLibraryOptions = getDracoLibraryOptions({
130
134
  decoderProfile: 'full',
@@ -155,7 +159,9 @@ export async function convertNodeTileset(
155
159
  }
156
160
  );
157
161
  const {source, spatialContext, geometricError} = selection;
162
+ setupMilliseconds = options.profile ? performance.now() - started : 0;
158
163
  const common = {
164
+ profile: options.profile,
159
165
  source,
160
166
  signal,
161
167
  measureInputBytes: measureMeshBytes,
@@ -209,6 +215,7 @@ export async function convertNodeTileset(
209
215
  } finally {
210
216
  await rawSource.close();
211
217
  }
218
+ const outputStarted = options.profile ? performance.now() : 0;
212
219
  signal.throwIfAborted();
213
220
  // Validate packaging as well as content, including on the write-free validation path.
214
221
  if (options.outputFormat !== '3d-tiles' && !options.output) {
@@ -227,7 +234,17 @@ export async function convertNodeTileset(
227
234
  maxOutputBytes,
228
235
  signal
229
236
  );
230
- return report;
237
+ return report.timings
238
+ ? {
239
+ ...report,
240
+ timings: {
241
+ ...report.timings,
242
+ setupMilliseconds,
243
+ outputMilliseconds: performance.now() - outputStarted,
244
+ totalMilliseconds: performance.now() - started
245
+ }
246
+ }
247
+ : report;
231
248
  }
232
249
 
233
250
  /** Saves fixed relative resource names or an archive; existing destinations are never overwritten. */
@@ -5,8 +5,8 @@
5
5
  import {open, realpath} from 'node:fs/promises';
6
6
  import {basename, dirname, isAbsolute, relative, resolve, sep} from 'node:path';
7
7
  import {fileURLToPath} from 'node:url';
8
- import type {ReadableFile} from '@loaders.gl/loader-utils';
9
- import {parse3DTilesArchive} from '@loaders.gl/3d-tiles';
8
+ import type {ReadableFile} from '@loaders.gl/core/loader-utils';
9
+ import {parse3DTilesArchive} from '@loaders.gl/tiles-3d/3d-tiles';
10
10
  import {
11
11
  createBrowserTilesetConversionSource,
12
12
  TileConversionError
@@ -0,0 +1,113 @@
1
+ // loaders.gl
2
+ // SPDX-License-Identifier: MIT
3
+ // Copyright (c) vis.gl contributors
4
+
5
+ import type {Mesh, MeshArrowTable} from '@loaders.gl/core/schema';
6
+ import {convertTableToMesh} from '@loaders.gl/core/schema-utils';
7
+ import {TileConversionError} from '@loaders.gl/tile-converter/v5/core';
8
+
9
+ /** Explicit source-name mapping and authorized semantic losses. */
10
+ export interface PointCloudAttributeMapping {
11
+ /** Destination attribute name to source attribute name. Values and normalization remain unchanged. */
12
+ readonly attributes: Readonly<Record<string, string>>;
13
+ /** Source attributes intentionally omitted, each with a nonempty reason. */
14
+ readonly drop?: Readonly<Record<string, string>>;
15
+ }
16
+
17
+ /** Decoded mapped rows and a record of every authorized omission. */
18
+ export interface MappedPointCloud {
19
+ /** Mesh with renamed attributes sharing immutable source values. */
20
+ readonly mesh: Mesh;
21
+ /** Explicit source-name/reason pairs, sorted deterministically. */
22
+ readonly droppedAttributes: Readonly<Record<string, string>>;
23
+ }
24
+
25
+ /** Maps decoded point attributes without implicit aliases, casts, projection or silent omissions. */
26
+ export function mapPointCloudAttributes(
27
+ input: Mesh | MeshArrowTable,
28
+ mapping: PointCloudAttributeMapping
29
+ ): MappedPointCloud {
30
+ const mesh = getPointCloudMesh(input);
31
+ const attributes: Mesh['attributes'] = Object.create(null);
32
+ const selected = new Set<string>();
33
+ for (const [target, source] of Object.entries(mapping.attributes)) {
34
+ if (
35
+ !target ||
36
+ ['__proto__', 'constructor', 'prototype'].includes(target) ||
37
+ !Object.hasOwn(mesh.attributes, source)
38
+ )
39
+ throw new TileConversionError(
40
+ 'POINT_CLOUD_MAPPING_INVALID',
41
+ `Invalid point attribute mapping ${target}: ${source}`
42
+ );
43
+ if (Object.hasOwn(mapping.drop || {}, source))
44
+ throw new TileConversionError(
45
+ 'POINT_CLOUD_MAPPING_INVALID',
46
+ `Point attribute ${source} is both selected and dropped`
47
+ );
48
+ attributes[target] = {...mesh.attributes[source]};
49
+ selected.add(source);
50
+ }
51
+ const droppedAttributes: Record<string, string> = Object.create(null);
52
+ for (const [source, reason] of Object.entries(mapping.drop || {}).sort(([left], [right]) =>
53
+ left < right ? -1 : left > right ? 1 : 0
54
+ )) {
55
+ if (!Object.hasOwn(mesh.attributes, source) || typeof reason !== 'string' || !reason.trim())
56
+ throw new TileConversionError(
57
+ 'POINT_CLOUD_MAPPING_INVALID',
58
+ `Invalid loss declaration for ${source}`
59
+ );
60
+ droppedAttributes[source] = reason;
61
+ }
62
+ const unmapped = Object.keys(mesh.attributes).filter(
63
+ name => !selected.has(name) && !Object.hasOwn(droppedAttributes, name)
64
+ );
65
+ if (unmapped.length || !attributes.POSITION)
66
+ throw new TileConversionError(
67
+ 'POINT_CLOUD_MAPPING_REQUIRED',
68
+ `Map POSITION and explicitly retain or drop every attribute: ${unmapped.join(', ')}`
69
+ );
70
+ return {
71
+ mesh: {...mesh, attributes, schema: {fields: [], metadata: {...mesh.schema?.metadata}}},
72
+ droppedAttributes
73
+ };
74
+ }
75
+
76
+ /** Converts decoded point input after optional retained-buffer accounting and null validation. */
77
+ export function getPointCloudMesh(input: Mesh | MeshArrowTable, maxInputBytes = Infinity): Mesh {
78
+ const buffers = new Set<ArrayBufferLike>();
79
+ let bytes = 0;
80
+ /** Counts distinct retained buffers rather than narrow views. */
81
+ const countBuffer = (buffer: ArrayBufferLike): void => {
82
+ if (!buffers.has(buffer)) {
83
+ buffers.add(buffer);
84
+ bytes += buffer.byteLength;
85
+ }
86
+ if (bytes > maxInputBytes)
87
+ throw new TileConversionError('POINT_CLOUD_INPUT_BUDGET', 'Point input byte budget exceeded');
88
+ };
89
+ if ('shape' in input) {
90
+ /** Validates nullable nested Arrow columns before conversion. */
91
+ type ArrowColumn = MeshArrowTable['data']['batches'][number]['data']['children'][number];
92
+ const checkColumn = (column: ArrowColumn): void => {
93
+ if (column.nullCount)
94
+ throw new TileConversionError(
95
+ 'POINT_CLOUD_NULL_UNSUPPORTED',
96
+ 'Null point values require an explicit preprocessing step'
97
+ );
98
+ for (const buffer of Object.values(column.buffers)) if (buffer) countBuffer(buffer.buffer);
99
+ for (const view of column.variadicBuffers) countBuffer(view.buffer);
100
+ for (const child of column.children) checkColumn(child);
101
+ for (const dictionary of column.dictionary?.data || []) checkColumn(dictionary);
102
+ };
103
+ for (const batch of input.data.batches) checkColumn(batch.data);
104
+ } else
105
+ for (const attribute of Object.values(input.attributes)) countBuffer(attribute.value.buffer);
106
+ const mesh = 'shape' in input ? convertTableToMesh(input) : input;
107
+ if (mesh.topology !== 'point-list' || mesh.indices)
108
+ throw new TileConversionError(
109
+ 'POINT_CLOUD_INPUT_INVALID',
110
+ 'Decoded unindexed point-list input is required'
111
+ );
112
+ return mesh;
113
+ }
@@ -2,7 +2,7 @@
2
2
  // SPDX-License-Identifier: MIT
3
3
  // Copyright (c) vis.gl contributors
4
4
 
5
- import type {PointCloudTilesetSource} from '@loaders.gl/tiles';
5
+ import type {PointCloudTilesetSource} from '@loaders.gl/tiles-3d';
6
6
  import {convertTileset} from '@loaders.gl/tile-converter/v5/core';
7
7
  import type {
8
8
  TileConversionProgress,