@loaders.gl/tile-converter 5.0.0-alpha.11 → 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 (152) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +180 -4
  3. package/bin/converter-v5.js +2 -0
  4. package/dist/converter.min.cjs +172 -182
  5. package/dist/i3s-server/bin/i3s-server.min.cjs +113 -158
  6. package/dist/index.cjs +18832 -19228
  7. package/dist/index.js +19566 -19968
  8. package/dist/v5/adapters.cjs +2002 -230
  9. package/dist/v5/adapters.d.ts +20 -0
  10. package/dist/v5/adapters.js +2005 -214
  11. package/dist/v5/archive-source.d.ts +36 -0
  12. package/dist/v5/browser-tileset-source.d.ts +55 -11
  13. package/dist/v5/browser.cjs +18 -0
  14. package/dist/v5/browser.d.ts +15 -1
  15. package/dist/v5/browser.js +26 -0
  16. package/dist/v5/conversion-api.d.ts +34 -0
  17. package/dist/v5/converter-cli.d.ts +1 -0
  18. package/dist/v5/converter-cli.js +1230 -0
  19. package/dist/v5/core.cjs +547 -77
  20. package/dist/v5/core.d.ts +3 -1
  21. package/dist/v5/core.js +540 -70
  22. package/dist/v5/feature-arrow.d.ts +1 -1
  23. package/dist/v5/i3s-mesh-conversion.d.ts +5 -1
  24. package/dist/v5/i3s-mesh-source-features.d.ts +2 -2
  25. package/dist/v5/i3s-mesh-source-material.d.ts +1 -1
  26. package/dist/v5/i3s-mesh-source.d.ts +2 -2
  27. package/dist/v5/i3s-selection-source.d.ts +70 -0
  28. package/dist/v5/index.cjs +15 -0
  29. package/dist/v5/index.d.ts +13 -1
  30. package/dist/v5/index.js +22 -0
  31. package/dist/v5/mesh-conversion.d.ts +21 -3
  32. package/dist/v5/mesh-draco.d.ts +2 -2
  33. package/dist/v5/mesh-features.d.ts +2 -2
  34. package/dist/v5/mesh-hierarchy.d.ts +47 -0
  35. package/dist/v5/mesh-source-features.d.ts +6 -5
  36. package/dist/v5/mesh-source-schema.d.ts +12 -0
  37. package/dist/v5/mesh-source-texture.d.ts +3 -3
  38. package/dist/v5/mesh-source.d.ts +10 -7
  39. package/dist/v5/mesh-tileset-sink.d.ts +5 -0
  40. package/dist/v5/mesh.d.ts +1 -1
  41. package/dist/v5/node-cli.d.ts +9 -0
  42. package/dist/v5/node-conversion.d.ts +34 -0
  43. package/dist/v5/node-feature-schema.d.ts +3 -0
  44. package/dist/v5/node-i3s-source.d.ts +41 -0
  45. package/dist/v5/node-source.d.ts +47 -0
  46. package/dist/v5/node.cjs +913 -0
  47. package/dist/v5/node.d.ts +6 -0
  48. package/dist/v5/node.js +890 -0
  49. package/dist/v5/point-cloud-attributes.d.ts +19 -0
  50. package/dist/v5/point-cloud-conversion.d.ts +1 -1
  51. package/dist/v5/point-cloud-copc.d.ts +44 -0
  52. package/dist/v5/point-cloud-file.d.ts +83 -0
  53. package/dist/v5/point-cloud-potree.d.ts +47 -0
  54. package/dist/v5/point-cloud-source-encoder.d.ts +1 -1
  55. package/dist/v5/point-cloud-source.d.ts +1 -1
  56. package/dist/v5/point-cloud-tileset.d.ts +44 -0
  57. package/dist/v5/point-cloud.d.ts +1 -1
  58. package/dist/v5/resource-manifest.d.ts +10 -1
  59. package/dist/v5/selected-mesh-source.d.ts +53 -0
  60. package/dist/v5/spatial-conversion.d.ts +1 -1
  61. package/dist/v5/tileset-source.d.ts +1 -1
  62. package/package.json +25 -32
  63. package/src/index.ts +4 -1
  64. package/src/v4/3d-tiles-converter/3d-tiles-converter.ts +11 -12
  65. package/src/v4/3d-tiles-converter/helpers/3d-tiles-content-converter.ts +7 -3
  66. package/src/v4/3d-tiles-converter/helpers/i3s-obb-to-3d-tiles-obb.ts +2 -2
  67. package/src/v4/3d-tiles-converter/helpers/load-i3s.ts +4 -6
  68. package/src/v4/3d-tiles-converter/helpers/texture-atlas.ts +1 -1
  69. package/src/v4/3d-tiles-converter/helpers/validate-i3s-spatial-reference.ts +2 -2
  70. package/src/v4/3d-tiles-converter/json-templates/tileset.ts +58 -39
  71. package/src/v4/converter-cli.ts +10 -4
  72. package/src/v4/deps-installer/deps-installer.ts +3 -3
  73. package/src/v4/i3s-converter/helpers/attribute-metadata-info.ts +1 -1
  74. package/src/v4/i3s-converter/helpers/batch-ids-extensions.ts +9 -10
  75. package/src/v4/i3s-converter/helpers/conversion-gltf.ts +108 -0
  76. package/src/v4/i3s-converter/helpers/coordinate-converter.ts +6 -5
  77. package/src/v4/i3s-converter/helpers/create-scene-server-path.ts +5 -6
  78. package/src/v4/i3s-converter/helpers/feature-attributes.ts +4 -4
  79. package/src/v4/i3s-converter/helpers/geometry-attributes.ts +1 -1
  80. package/src/v4/i3s-converter/helpers/geometry-converter.ts +69 -77
  81. package/src/v4/i3s-converter/helpers/gltf-attributes.ts +58 -74
  82. package/src/v4/i3s-converter/helpers/load-3d-tiles.ts +6 -54
  83. package/src/v4/i3s-converter/helpers/node-debug.ts +7 -3
  84. package/src/v4/i3s-converter/helpers/node-index-document.ts +11 -9
  85. package/src/v4/i3s-converter/helpers/node-pages.ts +3 -4
  86. package/src/v4/i3s-converter/helpers/preprocess-3d-tiles.ts +2 -2
  87. package/src/v4/i3s-converter/helpers/tileset-traversal.ts +1 -1
  88. package/src/v4/i3s-converter/i3s-converter.ts +29 -31
  89. package/src/v4/i3s-converter/json-templates/geometry-definitions.ts +71 -66
  90. package/src/v4/i3s-converter/json-templates/layers.ts +97 -132
  91. package/src/v4/i3s-converter/json-templates/metadata.ts +31 -21
  92. package/src/v4/i3s-converter/json-templates/node.ts +60 -74
  93. package/src/v4/i3s-converter/json-templates/scene-server.ts +36 -27
  94. package/src/v4/i3s-converter/json-templates/shared-resources.ts +62 -123
  95. package/src/v4/i3s-converter/json-templates/store.ts +39 -57
  96. package/src/v4/i3s-converter/types.ts +4 -4
  97. package/src/v4/i3s-server/controllers/slpk-controller.ts +3 -3
  98. package/src/v4/i3s-server/utils/create-scene-server.ts +3 -3
  99. package/src/v4/lib/utils/builder-input.ts +8 -0
  100. package/src/v4/lib/utils/conversion-dump.ts +5 -1
  101. package/src/v4/lib/utils/file-utils.ts +1 -1
  102. package/src/v4/lib/utils/hash-string.ts +21 -0
  103. package/src/v4/lib/utils/lod-conversion-utils.ts +3 -3
  104. package/src/v4/pgm-loader.ts +2 -2
  105. package/src/v5/adapters.ts +53 -0
  106. package/src/v5/archive-source.ts +235 -0
  107. package/src/v5/browser-tileset-source.ts +418 -48
  108. package/src/v5/browser.ts +46 -0
  109. package/src/v5/conversion-api.ts +184 -17
  110. package/src/v5/conversion-archive.ts +2 -2
  111. package/src/v5/converter-cli.ts +28 -0
  112. package/src/v5/core.ts +7 -0
  113. package/src/v5/feature-arrow.ts +2 -2
  114. package/src/v5/i3s-mesh-conversion.ts +41 -3
  115. package/src/v5/i3s-mesh-sink.ts +1 -1
  116. package/src/v5/i3s-mesh-source-features.ts +3 -3
  117. package/src/v5/i3s-mesh-source-material.ts +18 -11
  118. package/src/v5/i3s-mesh-source.ts +3 -2
  119. package/src/v5/i3s-selection-source.ts +298 -0
  120. package/src/v5/index.ts +41 -0
  121. package/src/v5/mesh-conversion.ts +24 -4
  122. package/src/v5/mesh-draco.ts +2 -2
  123. package/src/v5/mesh-features.ts +3 -3
  124. package/src/v5/mesh-hierarchy.ts +648 -0
  125. package/src/v5/mesh-source-features.ts +165 -51
  126. package/src/v5/mesh-source-schema.ts +140 -0
  127. package/src/v5/mesh-source-texture.ts +49 -19
  128. package/src/v5/mesh-source.ts +80 -49
  129. package/src/v5/mesh-tileset-archive.ts +1 -1
  130. package/src/v5/mesh-tileset-sink.ts +2 -2
  131. package/src/v5/mesh.ts +1 -1
  132. package/src/v5/node-cli.ts +201 -0
  133. package/src/v5/node-conversion.ts +362 -0
  134. package/src/v5/node-feature-schema.ts +155 -0
  135. package/src/v5/node-i3s-source.ts +181 -0
  136. package/src/v5/node-source.ts +328 -0
  137. package/src/v5/node.ts +16 -0
  138. package/src/v5/point-cloud-attributes.ts +113 -0
  139. package/src/v5/point-cloud-conversion.ts +1 -1
  140. package/src/v5/point-cloud-copc.ts +289 -0
  141. package/src/v5/point-cloud-file.ts +306 -0
  142. package/src/v5/point-cloud-potree.ts +134 -0
  143. package/src/v5/point-cloud-source-encoder.ts +1 -1
  144. package/src/v5/point-cloud-source.ts +1 -1
  145. package/src/v5/point-cloud-spatial.ts +2 -2
  146. package/src/v5/point-cloud-tileset.ts +137 -0
  147. package/src/v5/point-cloud.ts +3 -3
  148. package/src/v5/resource-fetcher.ts +4 -1
  149. package/src/v5/resource-manifest.ts +40 -5
  150. package/src/v5/selected-mesh-source.ts +384 -0
  151. package/src/v5/spatial-conversion.ts +90 -19
  152. 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';
@@ -21,19 +17,22 @@ import {validateMeshGeometry, validateMeshIndices} from './mesh.js';
21
17
  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
- import type {I3SMeshFeatures} from '@loaders.gl/i3s';
20
+ import type {MeshSourceSchemaOptions} from './mesh-source-schema.js';
21
+ import type {I3SMeshFeatures} from '@loaders.gl/tiles-3d/i3s';
25
22
  export type {MeshSourceFeatureOptions} from './mesh-source-features.js';
26
23
 
27
24
  /** Source mesh with optional explicit-schema feature data and triangle association. */
28
25
  export interface MeshSourceResource extends MeshConversionInput {
29
26
  /** Arrow features associated with the selected primitive; IDs are distinct from table row indices. */
30
27
  readonly features?: I3SMeshFeatures;
28
+ /** Original I3S layer semantics, retained for destination policy and diagnostics. */
29
+ readonly sourceLayerType?: '3DObject' | 'IntegratedMesh';
31
30
  }
32
31
 
33
32
  /** Narrow source profile and content lifetime controls. */
34
33
  export interface MeshTilesetSourceOptions extends TilesetConversionSourceOptions {
35
- /** Required when the source has batch/structural metadata; no schema is inferred. */
36
- readonly features?: MeshSourceFeatureOptions;
34
+ /** Required for feature-bearing content; omit schema only to map an inline structural metadata class. */
35
+ readonly features?: MeshSourceFeatureOptions | MeshSourceSchemaOptions;
37
36
  /** Explicit bounded reader for external encoded images, relative to the content URI. */
38
37
  readonly readExternalResource?: (
39
38
  uri: string,
@@ -47,7 +46,7 @@ export interface MeshTilesetSourceOptions extends TilesetConversionSourceOptions
47
46
  * optional buffer-view or base64 data-URI PNG/JPEG images, TEXCOORD_0, glTF sampling and
48
47
  * KHR_texture_transform controls on TEXCOORD_0. Decode content with
49
48
  * `gltf.excludeExtensions: {KHR_texture_transform: false}` to retain authored UVs/transforms.
50
- * 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.
51
50
  * Shared traversal retains placement identity and unloads content according to the supplied policy.
52
51
  * The adapter applies node transforms, glTF up-axis correction, RTC translation, and tile placement
53
52
  * once, retaining Float64 absolute positions and inverse-transpose unit normals. Animation, skins,
@@ -117,7 +116,7 @@ export function createMeshTilesetConversionSource(
117
116
  'Content metadata requires a separate mapping'
118
117
  );
119
118
  const payload = content.payload as {
120
- gltf?: GLTFPostprocessed;
119
+ gltf?: GLTFWithBuffers;
121
120
  gltfUpAxis?: string;
122
121
  rtcCenter?: number[];
123
122
  type?: string;
@@ -134,7 +133,8 @@ export function createMeshTilesetConversionSource(
134
133
  'Mesh extraction requires decoded GLB/glTF or B3DM content'
135
134
  );
136
135
  }
137
- const gltf = payload.gltf;
136
+ const gltfWithBuffers = payload.gltf;
137
+ const gltf = gltfWithBuffers.json;
138
138
  if (
139
139
  gltf.animations?.length ||
140
140
  gltf.skins?.length ||
@@ -151,7 +151,9 @@ export function createMeshTilesetConversionSource(
151
151
  if (!['X', 'Y', 'Z'].includes(axis))
152
152
  throw new TileConversionError('MESH_SOURCE_AXIS_UNSUPPORTED', 'Unknown glTF up axis');
153
153
  const placement = new Matrix4(item.tile.computedTransform);
154
- 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;
155
157
  if (rtc) {
156
158
  if (rtc.length !== 3 || rtc.some((value: number) => !Number.isFinite(value)))
157
159
  throw new TileConversionError(
@@ -165,15 +167,21 @@ export function createMeshTilesetConversionSource(
165
167
  if (axis === 'X')
166
168
  placement.multiplyRight(new Matrix4([0, 0, 1, 0, 0, 1, 0, 0, -1, 0, 0, 0, 0, 0, 0, 1]));
167
169
  const roots =
168
- 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);
169
172
  if (!roots?.length)
170
173
  throw new TileConversionError(
171
174
  'MESH_SOURCE_SCENE_UNSUPPORTED',
172
175
  'A default scene or exactly one scene is required'
173
176
  );
174
177
  let primitiveIndex = 0;
175
- for (const selected of traverseMeshNodes(roots, placement)) {
178
+ const primitives = traverseMeshNodes(gltfWithBuffers, roots, placement);
179
+ let current = primitives.next();
180
+ while (!current.done) {
181
+ const selected = current.value;
182
+ const next = primitives.next();
176
183
  const resource = await extractPrimitive(
184
+ gltfWithBuffers,
177
185
  selected.primitive,
178
186
  selected.transform,
179
187
  `${item.tile.id}/${content.index}/${primitiveIndex++}`,
@@ -182,13 +190,18 @@ export function createMeshTilesetConversionSource(
182
190
  signal
183
191
  );
184
192
  const features = extractMeshFeatures(
185
- gltf,
193
+ gltfWithBuffers,
186
194
  selected.primitive,
187
195
  payload,
188
196
  resource.mesh,
189
197
  options.features
190
198
  );
191
- yield {...resource, features};
199
+ yield {
200
+ ...resource,
201
+ features,
202
+ contentPrimitive: {index: primitiveIndex - 1, last: Boolean(next.done)}
203
+ };
204
+ current = next;
192
205
  }
193
206
  if (!primitiveIndex)
194
207
  throw new TileConversionError(
@@ -201,16 +214,19 @@ export function createMeshTilesetConversionSource(
201
214
  };
202
215
  }
203
216
 
204
- /** 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. */
205
218
  function* traverseMeshNodes(
206
- nodes: readonly GLTFNodePostprocessed[],
219
+ gltf: GLTFWithBuffers,
220
+ nodes: readonly number[],
207
221
  parent: Matrix4,
208
- ancestors = new Set<GLTFNodePostprocessed>()
209
- ): Generator<{primitive: GLTFMeshPrimitivePostprocessed; transform: Matrix4}> {
210
- 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];
211
226
  if (
212
- ancestors.has(node) ||
213
- node.skin ||
227
+ !node ||
228
+ ancestors.has(nodeIndex) ||
229
+ node.skin !== undefined ||
214
230
  node.weights ||
215
231
  Object.keys(node.extensions || {}).length
216
232
  )
@@ -226,21 +242,28 @@ function* traverseMeshNodes(
226
242
  .multiplyRight(new Matrix4().fromQuaternion(node.rotation || [0, 0, 0, 1]))
227
243
  .scale(node.scale || [1, 1, 1])
228
244
  );
229
- if (node.mesh) {
230
- 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)
231
248
  throw new TileConversionError(
232
249
  'MESH_SOURCE_NODE_UNSUPPORTED',
233
250
  'Morph weights and mesh extensions are unsupported'
234
251
  );
235
- for (const primitive of node.mesh.primitives) yield {primitive, transform};
252
+ for (const primitive of mesh.primitives) yield {primitive, transform};
236
253
  }
237
- 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
+ );
238
260
  }
239
261
  }
240
262
 
241
263
  /** Copies one packed primitive, places it once, and derives a useful ECEF local origin. */
242
264
  async function extractPrimitive(
243
- primitive: GLTFMeshPrimitivePostprocessed,
265
+ gltf: GLTFWithBuffers,
266
+ primitive: GLTFMeshPrimitive,
244
267
  transform: Matrix4,
245
268
  id: string,
246
269
  readExternalResource: MeshTilesetSourceOptions['readExternalResource'],
@@ -268,7 +291,8 @@ async function extractPrimitive(
268
291
  'Mesh placement requires a finite invertible affine transform with positive determinant'
269
292
  );
270
293
  const attributes: Record<string, MeshAttribute> = {};
271
- 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);
272
296
  if (name === '_BATCHID' || /^_FEATURE_ID_\d+$/.test(name)) continue;
273
297
  if (!['POSITION', 'NORMAL', 'COLOR_0', 'TEXCOORD_0'].includes(name))
274
298
  throw new TileConversionError(
@@ -276,17 +300,17 @@ async function extractPrimitive(
276
300
  `Unsupported mesh attribute ${name}`
277
301
  );
278
302
  attributes[name] = {
279
- value: accessor.value as MeshAttribute['value'],
280
- size: accessor.components,
303
+ value: getGLTFAccessorValue(gltf, reference) as MeshAttribute['value'],
304
+ size: accessor.type === 'SCALAR' ? 1 : Number(accessor.type.slice(3)),
281
305
  ...(accessor.normalized ? {normalized: true} : {})
282
306
  };
283
307
  }
284
308
  const mesh = validateMeshGeometry(
285
309
  {
286
310
  topology: 'triangle-list',
287
- mode: primitive.mode === 5 ? 4 : (primitive.mode ?? 4),
311
+ mode: [5, 6].includes(primitive.mode ?? 4) ? 4 : (primitive.mode ?? 4),
288
312
  attributes,
289
- indices: createTriangleIndices(primitive, (attributes.POSITION?.value.length ?? 0) / 3)
313
+ indices: createTriangleIndices(gltf, primitive, (attributes.POSITION?.value.length ?? 0) / 3)
290
314
  },
291
315
  true
292
316
  );
@@ -324,20 +348,22 @@ async function extractPrimitive(
324
348
  mesh,
325
349
  origin: minimum.map((value, axis) => value / 2 + maximum[axis] / 2) as [number, number, number],
326
350
  material: await mapMeshSourceMaterial(
327
- primitive.material,
351
+ primitive.material === undefined ? undefined : gltf.json.materials?.[primitive.material],
328
352
  readExternalResource,
329
353
  contentUri,
330
- signal
354
+ signal,
355
+ gltf
331
356
  )
332
357
  };
333
358
  }
334
359
 
335
360
  /** Maps supported PBR controls and an embedded base-color image, rejecting unmapped semantics. */
336
361
  export async function mapMeshSourceMaterial(
337
- material: GLTFMaterialPostprocessed | undefined,
362
+ material: GLTFMaterial | undefined,
338
363
  readExternalResource: MeshTilesetSourceOptions['readExternalResource'],
339
364
  contentUri: string | undefined,
340
- signal?: AbortSignal
365
+ signal: AbortSignal | undefined,
366
+ gltf: GLTFWithBuffers
341
367
  ): Promise<MeshTileMaterial | undefined> {
342
368
  if (!material) return undefined;
343
369
  const pbr = material.pbrMetallicRoughness;
@@ -372,6 +398,7 @@ export async function mapMeshSourceMaterial(
372
398
  ...('baseColorTexture' in (pbr || {})
373
399
  ? {
374
400
  baseColorTexture: await resolveMeshSourceTexture(
401
+ gltf,
375
402
  pbr!.baseColorTexture,
376
403
  readExternalResource,
377
404
  contentUri,
@@ -389,17 +416,20 @@ export async function mapMeshSourceMaterial(
389
416
 
390
417
  /** Expands a strip without changing vertex storage, feature IDs, source indices, or parity. */
391
418
  function createTriangleIndices(
392
- primitive: GLTFMeshPrimitivePostprocessed,
419
+ gltf: GLTFWithBuffers,
420
+ primitive: GLTFMeshPrimitive,
393
421
  vertexCount: number
394
422
  ): MeshAttribute | undefined {
395
- const attribute = primitive.indices
423
+ const accessor =
424
+ primitive.indices === undefined ? undefined : getGLTFAccessor(gltf, primitive.indices);
425
+ const attribute = accessor
396
426
  ? {
397
- value: primitive.indices.value as MeshAttribute['value'],
398
- size: primitive.indices.components,
399
- normalized: primitive.indices.normalized
427
+ value: getGLTFAccessorValue(gltf, accessor) as MeshAttribute['value'],
428
+ size: 1,
429
+ normalized: accessor.normalized
400
430
  }
401
431
  : undefined;
402
- if (primitive.mode !== 5) return attribute;
432
+ if (primitive.mode !== 5 && primitive.mode !== 6) return attribute;
403
433
  const sourceIndices = validateMeshIndices(attribute, vertexCount, 5);
404
434
  const indexCount = sourceIndices?.length ?? vertexCount;
405
435
  if (!Number.isSafeInteger(indexCount) || indexCount < 3) {
@@ -412,9 +442,10 @@ function createTriangleIndices(
412
442
  const indices = new IndexArray((indexCount - 2) * 3);
413
443
  for (let triangle = 0; triangle < indexCount - 2; triangle++) {
414
444
  // Match the glTF strip equation, including degenerate connectors; they advance parity too.
415
- const second = triangle + 1 + (triangle % 2);
416
- const third = triangle + 2 - (triangle % 2);
417
- 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;
418
449
  indices[triangle * 3 + 1] = sourceIndices?.[second] ?? second;
419
450
  indices[triangle * 3 + 2] = sourceIndices?.[third] ?? third;
420
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';
@@ -96,7 +96,7 @@ export function createSingleMeshTilesetSink(
96
96
  }
97
97
 
98
98
  /** Builds placement and local bounds for a trusted single-mesh codec output. */
99
- function createMeshTileset(
99
+ export function createMeshTileset(
100
100
  resource: EncodedMeshConversionResource,
101
101
  geometricError: number
102
102
  ): Omit<Tiles3DTilesetJSON, 'root'> & {root: Omit<Tiles3DTileJSON, 'children'>} {
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,
@@ -0,0 +1,201 @@
1
+ // loaders.gl
2
+ // SPDX-License-Identifier: MIT
3
+ // Copyright (c) vis.gl contributors
4
+
5
+ import {parseArgs} from 'node:util';
6
+ import {readFeatureSchema} from './node-feature-schema.js';
7
+ import {TileConversionError} from '@loaders.gl/tile-converter/v5/core';
8
+ import {createNodeTilesetConversionSource, NODE_TILESET_LIMITS} from './node-source.js';
9
+ import type {NodeTileConversionOutputFormat} from './node-conversion.js';
10
+
11
+ /** Text destinations supplied by the process wrapper or a hermetic caller. */
12
+ export interface TileConverterCLIOutput {
13
+ /** Writes a complete JSON result or help text. */
14
+ readonly stdout: (text: string) => void;
15
+ /** Writes a JSON error without contaminating stdout. */
16
+ readonly stderr: (text: string) => void;
17
+ }
18
+
19
+ /** Usage for bounded metadata inspection and selected static mesh conversion. */
20
+ const HELP = `Usage: tile-converter-v5 inspect|convert|validate <input path or URL> [options]
21
+
22
+ --input-format 3d-tiles|3tz|i3s|slpk Explicit input format (default: 3d-tiles)
23
+ --geometric-error N Required source error in meters for I3S to 3D Tiles
24
+ --integrated-mesh-policy as-3d-object Authorize I3S layer-type change
25
+ --preserve-hierarchy Retain a complete explicit 3D Tiles tree (3D Tiles/3TZ only)
26
+ --feature-schema PATH Local JSON feature class/schema mapping (at most 64 KiB)
27
+ --resource ID Selected content placement; repeat for multiple selections
28
+ --output-format 3d-tiles|3tz|slpk Target profile (default: 3tz)
29
+ --output PATH New destination (required for convert, forbidden for validate)
30
+ --max-input-bytes N Lower the 16 MiB input/read budget
31
+ --max-resources N Lower the 1,000 content-declaration limit
32
+ --max-documents N Lower the 32 metadata-document limit
33
+ --max-output-bytes N Lower the 32 MiB output/archive budget
34
+ --profile Include elapsed conversion stage timings in JSON
35
+ --help Show this help
36
+
37
+ Inspect reads metadata only, following declared .json external tilesets.
38
+ Convert/validate require explicit selections; I3S uses declared CRS and shared spatial preparation.
39
+ Validate decodes, converts and packages the selected target profile without writing files.
40
+ Output defaults to a partial flat mesh collection. --preserve-hierarchy requires every declared content.
41
+ Remote archives require HTTP 206 ranges and a stable ETag or Last-Modified validator.
42
+ I3S inspect lists leaf nodes; feature-bearing conversion requires --feature-schema.
43
+ Hierarchy mode supports at most 32 document placements, 256 tiles and 64 total mesh primitive placements, including ordered 1.1 contents arrays.
44
+ `;
45
+
46
+ /** Runs a bounded v5 operation, returning an exit code after source/output cleanup. */
47
+ export async function runTileConverterCLI(
48
+ argumentsList: string[],
49
+ output: TileConverterCLIOutput,
50
+ signal?: AbortSignal
51
+ ): Promise<number> {
52
+ try {
53
+ const {values, positionals} = parseArgs({
54
+ args: argumentsList,
55
+ allowPositionals: true,
56
+ options: {
57
+ help: {type: 'boolean'},
58
+ profile: {type: 'boolean'},
59
+ 'preserve-hierarchy': {type: 'boolean'},
60
+ 'input-format': {type: 'string', default: '3d-tiles'},
61
+ 'max-input-bytes': {type: 'string', default: String(NODE_TILESET_LIMITS.maxInputBytes)},
62
+ 'max-resources': {type: 'string', default: String(NODE_TILESET_LIMITS.maxInputResources)},
63
+ 'max-documents': {type: 'string', default: String(NODE_TILESET_LIMITS.maxTilesetDocuments)},
64
+ 'max-output-bytes': {type: 'string', default: String(32 * 1024 * 1024)},
65
+ 'output-format': {type: 'string', default: '3tz'},
66
+ output: {type: 'string'},
67
+ resource: {type: 'string', multiple: true},
68
+ 'feature-schema': {type: 'string'},
69
+ 'geometric-error': {type: 'string'},
70
+ 'integrated-mesh-policy': {type: 'string'}
71
+ }
72
+ });
73
+ if (values.help || !argumentsList.length) {
74
+ output.stdout(HELP);
75
+ return 0;
76
+ }
77
+ if (
78
+ positionals.length !== 2 ||
79
+ !['inspect', 'convert', 'validate'].includes(positionals[0]) ||
80
+ !['3d-tiles', '3tz', 'i3s', 'slpk'].includes(values['input-format'])
81
+ )
82
+ throw new TileConversionError(
83
+ 'CLI_USAGE_ERROR',
84
+ 'Use inspect, convert or validate with one input and --input-format 3d-tiles, 3tz, i3s or slpk'
85
+ );
86
+ const format = values['input-format'] as '3d-tiles' | '3tz' | 'i3s' | 'slpk';
87
+ const operation = positionals[0];
88
+ const sourceOptions = {
89
+ input: positionals[1],
90
+ format,
91
+ signal,
92
+ maxInputBytes: parseLimit(values['max-input-bytes']),
93
+ maxInputResources: parseLimit(values['max-resources']),
94
+ maxTilesetDocuments: parseLimit(values['max-documents']),
95
+ inspectNestedTilesets: true
96
+ };
97
+ if (operation !== 'inspect') {
98
+ if (
99
+ !values.resource?.length ||
100
+ !['3d-tiles', '3tz', 'slpk'].includes(values['output-format']) ||
101
+ (operation === 'convert' ? !values.output : values.output !== undefined)
102
+ )
103
+ throw new TileConversionError(
104
+ 'CLI_USAGE_ERROR',
105
+ 'Convert/validate require --resource selections and a supported --output-format; only convert requires --output'
106
+ );
107
+ if (
108
+ values['integrated-mesh-policy'] !== undefined &&
109
+ values['integrated-mesh-policy'] !== 'as-3d-object'
110
+ )
111
+ throw new TileConversionError(
112
+ 'CLI_USAGE_ERROR',
113
+ 'Use --integrated-mesh-policy as-3d-object'
114
+ );
115
+ const features =
116
+ values['feature-schema'] === undefined
117
+ ? undefined
118
+ : await readFeatureSchema(
119
+ values['feature-schema'],
120
+ format === 'i3s' || format === 'slpk',
121
+ signal
122
+ );
123
+ const {convertNodeTileset} = await import('./node-conversion.js');
124
+ const report = await convertNodeTileset({
125
+ ...sourceOptions,
126
+ ...features,
127
+ resourceIds: values.resource,
128
+ profile: values.profile,
129
+ preserveHierarchy: values['preserve-hierarchy'],
130
+ outputFormat: values['output-format'] as NodeTileConversionOutputFormat,
131
+ output: values.output,
132
+ maxOutputBytes: parseLimit(values['max-output-bytes']),
133
+ geometricError:
134
+ values['geometric-error'] === undefined
135
+ ? undefined
136
+ : parseGeometricError(values['geometric-error']),
137
+ integratedMeshPolicy: values['integrated-mesh-policy'] as 'as-3d-object' | undefined
138
+ });
139
+ output.stdout(
140
+ `${JSON.stringify({operation, input: positionals[1], inputFormat: format, outputFormat: values['output-format'], ...(values.output ? {output: values.output} : {}), report}, null, 2)}\n`
141
+ );
142
+ return 0;
143
+ }
144
+ if (
145
+ values.profile !== undefined ||
146
+ values['feature-schema'] !== undefined ||
147
+ values['preserve-hierarchy'] !== undefined ||
148
+ values['geometric-error'] !== undefined ||
149
+ values['integrated-mesh-policy'] !== undefined ||
150
+ values.resource ||
151
+ values.output ||
152
+ values['output-format'] !== '3tz' ||
153
+ values['max-output-bytes'] !== String(32 * 1024 * 1024)
154
+ )
155
+ throw new TileConversionError(
156
+ 'CLI_USAGE_ERROR',
157
+ 'Inspect does not accept conversion/output options'
158
+ );
159
+ const source =
160
+ format === 'i3s' || format === 'slpk'
161
+ ? await (await import('./node-i3s-source.js')).createNodeI3SConversionSource({
162
+ ...sourceOptions,
163
+ format
164
+ })
165
+ : await createNodeTilesetConversionSource({...sourceOptions, format});
166
+ const inspection = await source.inspect(signal).finally(() => source.close());
167
+ output.stdout(
168
+ `${JSON.stringify({operation: 'inspect', input: positionals[1], inputFormat: format, ...inspection}, null, 2)}\n`
169
+ );
170
+ return 0;
171
+ } catch (error) {
172
+ output.stderr(
173
+ `${JSON.stringify({
174
+ code: error instanceof Error && 'code' in error ? error.code : 'CONVERSION_FAILED',
175
+ message: error instanceof Error ? error.message : String(error),
176
+ diagnostics: error instanceof TileConversionError ? error.diagnostics : []
177
+ })}\n`
178
+ );
179
+ return 1;
180
+ }
181
+ }
182
+
183
+ /** Parses a positive decimal byte/count limit without accepting fractional or infinite values. */
184
+ function parseLimit(value: string): number {
185
+ if (!/^[1-9]\d*$/.test(value) || !Number.isSafeInteger(Number(value)))
186
+ throw new TileConversionError(
187
+ 'CLI_USAGE_ERROR',
188
+ 'Limits must be positive safe decimal integers'
189
+ );
190
+ return Number(value);
191
+ }
192
+
193
+ /** Parses an explicit metric source error without inferring I3S screen-size semantics. */
194
+ function parseGeometricError(value: string): number {
195
+ if (!/^(?:\d+(?:\.\d+)?|\.\d+)(?:[eE][+-]?\d+)?$/.test(value) || !Number.isFinite(Number(value)))
196
+ throw new TileConversionError(
197
+ 'CLI_USAGE_ERROR',
198
+ 'Geometric error must be a finite nonnegative number in meters'
199
+ );
200
+ return Number(value);
201
+ }