@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.
- package/LICENSE +1 -1
- package/README.md +180 -4
- package/bin/converter-v5.js +2 -0
- package/dist/converter.min.cjs +172 -182
- package/dist/i3s-server/bin/i3s-server.min.cjs +113 -158
- package/dist/index.cjs +18832 -19228
- package/dist/index.js +19566 -19968
- package/dist/v5/adapters.cjs +2002 -230
- package/dist/v5/adapters.d.ts +20 -0
- package/dist/v5/adapters.js +2005 -214
- package/dist/v5/archive-source.d.ts +36 -0
- package/dist/v5/browser-tileset-source.d.ts +55 -11
- package/dist/v5/browser.cjs +18 -0
- package/dist/v5/browser.d.ts +15 -1
- package/dist/v5/browser.js +26 -0
- package/dist/v5/conversion-api.d.ts +34 -0
- package/dist/v5/converter-cli.d.ts +1 -0
- package/dist/v5/converter-cli.js +1230 -0
- package/dist/v5/core.cjs +547 -77
- package/dist/v5/core.d.ts +3 -1
- package/dist/v5/core.js +540 -70
- package/dist/v5/feature-arrow.d.ts +1 -1
- package/dist/v5/i3s-mesh-conversion.d.ts +5 -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 +70 -0
- package/dist/v5/index.cjs +15 -0
- package/dist/v5/index.d.ts +13 -1
- package/dist/v5/index.js +22 -0
- package/dist/v5/mesh-conversion.d.ts +21 -3
- package/dist/v5/mesh-draco.d.ts +2 -2
- package/dist/v5/mesh-features.d.ts +2 -2
- package/dist/v5/mesh-hierarchy.d.ts +47 -0
- package/dist/v5/mesh-source-features.d.ts +6 -5
- package/dist/v5/mesh-source-schema.d.ts +12 -0
- package/dist/v5/mesh-source-texture.d.ts +3 -3
- package/dist/v5/mesh-source.d.ts +10 -7
- package/dist/v5/mesh-tileset-sink.d.ts +5 -0
- package/dist/v5/mesh.d.ts +1 -1
- package/dist/v5/node-cli.d.ts +9 -0
- package/dist/v5/node-conversion.d.ts +34 -0
- package/dist/v5/node-feature-schema.d.ts +3 -0
- package/dist/v5/node-i3s-source.d.ts +41 -0
- package/dist/v5/node-source.d.ts +47 -0
- package/dist/v5/node.cjs +913 -0
- package/dist/v5/node.d.ts +6 -0
- package/dist/v5/node.js +890 -0
- 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 +83 -0
- package/dist/v5/point-cloud-potree.d.ts +47 -0
- 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 +44 -0
- package/dist/v5/point-cloud.d.ts +1 -1
- package/dist/v5/resource-manifest.d.ts +10 -1
- package/dist/v5/selected-mesh-source.d.ts +53 -0
- package/dist/v5/spatial-conversion.d.ts +1 -1
- package/dist/v5/tileset-source.d.ts +1 -1
- package/package.json +25 -32
- package/src/index.ts +4 -1
- 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 +4 -6
- 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 +10 -4
- 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 +6 -54
- package/src/v4/i3s-converter/helpers/node-debug.ts +7 -3
- package/src/v4/i3s-converter/helpers/node-index-document.ts +11 -9
- package/src/v4/i3s-converter/helpers/node-pages.ts +3 -4
- package/src/v4/i3s-converter/helpers/preprocess-3d-tiles.ts +2 -2
- 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 -57
- 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 +53 -0
- package/src/v5/archive-source.ts +235 -0
- package/src/v5/browser-tileset-source.ts +418 -48
- package/src/v5/browser.ts +46 -0
- package/src/v5/conversion-api.ts +184 -17
- package/src/v5/conversion-archive.ts +2 -2
- package/src/v5/converter-cli.ts +28 -0
- package/src/v5/core.ts +7 -0
- package/src/v5/feature-arrow.ts +2 -2
- package/src/v5/i3s-mesh-conversion.ts +41 -3
- package/src/v5/i3s-mesh-sink.ts +1 -1
- 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 +3 -2
- package/src/v5/i3s-selection-source.ts +298 -0
- package/src/v5/index.ts +41 -0
- package/src/v5/mesh-conversion.ts +24 -4
- package/src/v5/mesh-draco.ts +2 -2
- package/src/v5/mesh-features.ts +3 -3
- package/src/v5/mesh-hierarchy.ts +648 -0
- package/src/v5/mesh-source-features.ts +165 -51
- package/src/v5/mesh-source-schema.ts +140 -0
- package/src/v5/mesh-source-texture.ts +49 -19
- package/src/v5/mesh-source.ts +80 -49
- package/src/v5/mesh-tileset-archive.ts +1 -1
- package/src/v5/mesh-tileset-sink.ts +2 -2
- package/src/v5/mesh.ts +1 -1
- package/src/v5/node-cli.ts +201 -0
- package/src/v5/node-conversion.ts +362 -0
- package/src/v5/node-feature-schema.ts +155 -0
- package/src/v5/node-i3s-source.ts +181 -0
- package/src/v5/node-source.ts +328 -0
- package/src/v5/node.ts +16 -0
- 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 +306 -0
- package/src/v5/point-cloud-potree.ts +134 -0
- 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 +137 -0
- package/src/v5/point-cloud.ts +3 -3
- package/src/v5/resource-fetcher.ts +4 -1
- package/src/v5/resource-manifest.ts +40 -5
- package/src/v5/selected-mesh-source.ts +384 -0
- package/src/v5/spatial-conversion.ts +90 -19
- 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';
|
|
@@ -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 {
|
|
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
|
|
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;
|
|
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?:
|
|
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
|
|
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 =
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
193
|
+
gltfWithBuffers,
|
|
186
194
|
selected.primitive,
|
|
187
195
|
payload,
|
|
188
196
|
resource.mesh,
|
|
189
197
|
options.features
|
|
190
198
|
);
|
|
191
|
-
yield {
|
|
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
|
|
217
|
+
/** Walks scene placements without deduplicating a shared mesh or mutating source nodes. */
|
|
205
218
|
function* traverseMeshNodes(
|
|
206
|
-
|
|
219
|
+
gltf: GLTFWithBuffers,
|
|
220
|
+
nodes: readonly number[],
|
|
207
221
|
parent: Matrix4,
|
|
208
|
-
ancestors = new Set<
|
|
209
|
-
): Generator<{primitive:
|
|
210
|
-
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];
|
|
211
226
|
if (
|
|
212
|
-
|
|
213
|
-
|
|
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
|
-
|
|
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
|
|
252
|
+
for (const primitive of mesh.primitives) yield {primitive, transform};
|
|
236
253
|
}
|
|
237
|
-
yield* traverseMeshNodes(
|
|
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
|
-
|
|
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,
|
|
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:
|
|
280
|
-
size: accessor.
|
|
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
|
|
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:
|
|
362
|
+
material: GLTFMaterial | undefined,
|
|
338
363
|
readExternalResource: MeshTilesetSourceOptions['readExternalResource'],
|
|
339
364
|
contentUri: string | undefined,
|
|
340
|
-
signal
|
|
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
|
-
|
|
419
|
+
gltf: GLTFWithBuffers,
|
|
420
|
+
primitive: GLTFMeshPrimitive,
|
|
393
421
|
vertexCount: number
|
|
394
422
|
): MeshAttribute | undefined {
|
|
395
|
-
const
|
|
423
|
+
const accessor =
|
|
424
|
+
primitive.indices === undefined ? undefined : getGLTFAccessor(gltf, primitive.indices);
|
|
425
|
+
const attribute = accessor
|
|
396
426
|
? {
|
|
397
|
-
value:
|
|
398
|
-
size:
|
|
399
|
-
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
|
-
|
|
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
|
+
}
|