@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.
- package/LICENSE +1 -1
- package/README.md +4 -4
- package/dist/converter.min.cjs +149 -158
- package/dist/i3s-server/bin/i3s-server.min.cjs +113 -158
- package/dist/index.cjs +17486 -17479
- package/dist/index.js +25572 -25560
- package/dist/v5/adapters.cjs +540 -190
- package/dist/v5/adapters.d.ts +4 -0
- package/dist/v5/adapters.js +501 -151
- package/dist/v5/archive-source.d.ts +1 -1
- package/dist/v5/browser.d.ts +1 -1
- package/dist/v5/conversion-api.d.ts +25 -0
- package/dist/v5/converter-cli.js +23 -7
- package/dist/v5/core.cjs +76 -17
- package/dist/v5/core.d.ts +1 -1
- package/dist/v5/core.js +69 -10
- package/dist/v5/feature-arrow.d.ts +1 -1
- package/dist/v5/i3s-mesh-conversion.d.ts +1 -1
- package/dist/v5/i3s-mesh-source-features.d.ts +2 -2
- package/dist/v5/i3s-mesh-source-material.d.ts +1 -1
- package/dist/v5/i3s-mesh-source.d.ts +2 -2
- package/dist/v5/i3s-selection-source.d.ts +3 -3
- package/dist/v5/index.cjs +11 -6
- package/dist/v5/index.d.ts +5 -1
- package/dist/v5/index.js +8 -0
- package/dist/v5/mesh-conversion.d.ts +3 -3
- package/dist/v5/mesh-draco.d.ts +2 -2
- package/dist/v5/mesh-features.d.ts +2 -2
- package/dist/v5/mesh-source-features.d.ts +4 -4
- package/dist/v5/mesh-source-schema.d.ts +2 -2
- package/dist/v5/mesh-source-texture.d.ts +3 -3
- package/dist/v5/mesh-source.d.ts +5 -5
- package/dist/v5/mesh-tileset-sink.d.ts +1 -1
- package/dist/v5/mesh.d.ts +1 -1
- package/dist/v5/node-conversion.d.ts +2 -0
- package/dist/v5/node-i3s-source.d.ts +3 -3
- package/dist/v5/node-source.d.ts +1 -1
- package/dist/v5/node.cjs +19 -6
- package/dist/v5/node.js +19 -6
- package/dist/v5/point-cloud-attributes.d.ts +19 -0
- package/dist/v5/point-cloud-conversion.d.ts +1 -1
- package/dist/v5/point-cloud-copc.d.ts +44 -0
- package/dist/v5/point-cloud-file.d.ts +4 -4
- package/dist/v5/point-cloud-potree.d.ts +3 -3
- package/dist/v5/point-cloud-source-encoder.d.ts +1 -1
- package/dist/v5/point-cloud-source.d.ts +1 -1
- package/dist/v5/point-cloud-tileset.d.ts +3 -3
- package/dist/v5/point-cloud.d.ts +1 -1
- package/dist/v5/spatial-conversion.d.ts +1 -1
- package/dist/v5/tileset-source.d.ts +1 -1
- package/package.json +17 -32
- package/src/v4/3d-tiles-converter/3d-tiles-converter.ts +11 -12
- package/src/v4/3d-tiles-converter/helpers/3d-tiles-content-converter.ts +7 -3
- package/src/v4/3d-tiles-converter/helpers/i3s-obb-to-3d-tiles-obb.ts +2 -2
- package/src/v4/3d-tiles-converter/helpers/load-i3s.ts +3 -3
- package/src/v4/3d-tiles-converter/helpers/texture-atlas.ts +1 -1
- package/src/v4/3d-tiles-converter/helpers/validate-i3s-spatial-reference.ts +2 -2
- package/src/v4/3d-tiles-converter/json-templates/tileset.ts +58 -39
- package/src/v4/converter-cli.ts +3 -3
- package/src/v4/deps-installer/deps-installer.ts +3 -3
- package/src/v4/i3s-converter/helpers/attribute-metadata-info.ts +1 -1
- package/src/v4/i3s-converter/helpers/batch-ids-extensions.ts +9 -10
- package/src/v4/i3s-converter/helpers/conversion-gltf.ts +108 -0
- package/src/v4/i3s-converter/helpers/coordinate-converter.ts +6 -5
- package/src/v4/i3s-converter/helpers/create-scene-server-path.ts +5 -6
- package/src/v4/i3s-converter/helpers/feature-attributes.ts +4 -4
- package/src/v4/i3s-converter/helpers/geometry-attributes.ts +1 -1
- package/src/v4/i3s-converter/helpers/geometry-converter.ts +69 -77
- package/src/v4/i3s-converter/helpers/gltf-attributes.ts +58 -74
- package/src/v4/i3s-converter/helpers/load-3d-tiles.ts +5 -5
- package/src/v4/i3s-converter/helpers/node-debug.ts +7 -3
- package/src/v4/i3s-converter/helpers/node-index-document.ts +6 -7
- package/src/v4/i3s-converter/helpers/node-pages.ts +3 -4
- package/src/v4/i3s-converter/helpers/preprocess-3d-tiles.ts +1 -1
- package/src/v4/i3s-converter/helpers/tileset-traversal.ts +1 -1
- package/src/v4/i3s-converter/i3s-converter.ts +29 -31
- package/src/v4/i3s-converter/json-templates/geometry-definitions.ts +71 -66
- package/src/v4/i3s-converter/json-templates/layers.ts +97 -132
- package/src/v4/i3s-converter/json-templates/metadata.ts +31 -21
- package/src/v4/i3s-converter/json-templates/node.ts +60 -74
- package/src/v4/i3s-converter/json-templates/scene-server.ts +36 -27
- package/src/v4/i3s-converter/json-templates/shared-resources.ts +62 -123
- package/src/v4/i3s-converter/json-templates/store.ts +39 -58
- package/src/v4/i3s-converter/types.ts +4 -4
- package/src/v4/i3s-server/controllers/slpk-controller.ts +3 -3
- package/src/v4/i3s-server/utils/create-scene-server.ts +3 -3
- package/src/v4/lib/utils/builder-input.ts +8 -0
- package/src/v4/lib/utils/conversion-dump.ts +5 -1
- package/src/v4/lib/utils/file-utils.ts +1 -1
- package/src/v4/lib/utils/hash-string.ts +21 -0
- package/src/v4/lib/utils/lod-conversion-utils.ts +3 -3
- package/src/v4/pgm-loader.ts +2 -2
- package/src/v5/adapters.ts +5 -0
- package/src/v5/archive-source.ts +4 -4
- package/src/v5/browser.ts +1 -0
- package/src/v5/conversion-api.ts +115 -8
- package/src/v5/conversion-archive.ts +2 -2
- package/src/v5/core.ts +1 -0
- package/src/v5/feature-arrow.ts +2 -2
- package/src/v5/i3s-mesh-conversion.ts +2 -2
- package/src/v5/i3s-mesh-source-features.ts +3 -3
- package/src/v5/i3s-mesh-source-material.ts +18 -11
- package/src/v5/i3s-mesh-source.ts +2 -2
- package/src/v5/i3s-selection-source.ts +4 -4
- package/src/v5/index.ts +15 -0
- package/src/v5/mesh-conversion.ts +4 -4
- package/src/v5/mesh-draco.ts +2 -2
- package/src/v5/mesh-features.ts +3 -3
- package/src/v5/mesh-hierarchy.ts +1 -1
- package/src/v5/mesh-source-features.ts +137 -55
- package/src/v5/mesh-source-schema.ts +10 -4
- package/src/v5/mesh-source-texture.ts +49 -19
- package/src/v5/mesh-source.ts +65 -46
- package/src/v5/mesh-tileset-archive.ts +1 -1
- package/src/v5/mesh-tileset-sink.ts +1 -1
- package/src/v5/mesh.ts +1 -1
- package/src/v5/node-cli.ts +4 -0
- package/src/v5/node-conversion.ts +19 -2
- package/src/v5/node-source.ts +2 -2
- package/src/v5/point-cloud-attributes.ts +113 -0
- package/src/v5/point-cloud-conversion.ts +1 -1
- package/src/v5/point-cloud-copc.ts +289 -0
- package/src/v5/point-cloud-file.ts +8 -8
- package/src/v5/point-cloud-potree.ts +8 -4
- package/src/v5/point-cloud-source-encoder.ts +1 -1
- package/src/v5/point-cloud-source.ts +1 -1
- package/src/v5/point-cloud-spatial.ts +2 -2
- package/src/v5/point-cloud-tileset.ts +4 -4
- package/src/v5/point-cloud.ts +3 -3
- package/src/v5/selected-mesh-source.ts +4 -4
- package/src/v5/spatial-conversion.ts +2 -2
- package/src/v5/tileset-source.ts +2 -2
package/src/v5/mesh-source.ts
CHANGED
|
@@ -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
|
-
|
|
9
|
-
|
|
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;
|
|
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?:
|
|
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
|
|
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 =
|
|
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
|
|
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
|
-
|
|
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
|
|
217
|
+
/** Walks scene placements without deduplicating a shared mesh or mutating source nodes. */
|
|
217
218
|
function* traverseMeshNodes(
|
|
218
|
-
|
|
219
|
+
gltf: GLTFWithBuffers,
|
|
220
|
+
nodes: readonly number[],
|
|
219
221
|
parent: Matrix4,
|
|
220
|
-
ancestors = new Set<
|
|
221
|
-
): Generator<{primitive:
|
|
222
|
-
for (const
|
|
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
|
-
|
|
225
|
-
|
|
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
|
-
|
|
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
|
|
252
|
+
for (const primitive of mesh.primitives) yield {primitive, transform};
|
|
248
253
|
}
|
|
249
|
-
yield* traverseMeshNodes(
|
|
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
|
-
|
|
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,
|
|
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:
|
|
292
|
-
size: accessor.
|
|
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
|
|
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:
|
|
362
|
+
material: GLTFMaterial | undefined,
|
|
350
363
|
readExternalResource: MeshTilesetSourceOptions['readExternalResource'],
|
|
351
364
|
contentUri: string | undefined,
|
|
352
|
-
signal
|
|
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
|
-
|
|
419
|
+
gltf: GLTFWithBuffers,
|
|
420
|
+
primitive: GLTFMeshPrimitive,
|
|
405
421
|
vertexCount: number
|
|
406
422
|
): MeshAttribute | undefined {
|
|
407
|
-
const
|
|
423
|
+
const accessor =
|
|
424
|
+
primitive.indices === undefined ? undefined : getGLTFAccessor(gltf, primitive.indices);
|
|
425
|
+
const attribute = accessor
|
|
408
426
|
? {
|
|
409
|
-
value:
|
|
410
|
-
size:
|
|
411
|
-
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
|
-
|
|
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,
|
package/src/v5/node-cli.ts
CHANGED
|
@@ -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. */
|
package/src/v5/node-source.ts
CHANGED
|
@@ -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,
|