@loaders.gl/tile-converter 5.0.0-alpha.10 → 5.0.0-alpha.12
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/README.md +211 -2
- package/bin/converter-v5.js +2 -0
- package/dist/converter.min.cjs +162 -163
- package/dist/i3s-server/bin/i3s-server.min.cjs +73 -73
- package/dist/index.cjs +1933 -2232
- package/dist/index.js +1925 -2235
- package/dist/v5/adapters.cjs +3186 -208
- package/dist/v5/adapters.d.ts +29 -2
- package/dist/v5/adapters.js +3202 -196
- package/dist/v5/archive-source.d.ts +36 -0
- package/dist/v5/browser-tileset-source.d.ts +55 -11
- package/dist/v5/browser.cjs +30 -0
- package/dist/v5/browser.d.ts +26 -2
- package/dist/v5/browser.js +45 -1
- package/dist/v5/conversion-api.d.ts +9 -0
- package/dist/v5/converter-cli.d.ts +1 -0
- package/dist/v5/converter-cli.js +1214 -0
- package/dist/v5/core.cjs +515 -50
- package/dist/v5/core.d.ts +2 -0
- package/dist/v5/core.js +515 -50
- package/dist/v5/i3s-mesh-conversion.d.ts +14 -4
- package/dist/v5/i3s-mesh-sink.d.ts +25 -0
- package/dist/v5/i3s-mesh-source-features.d.ts +13 -0
- package/dist/v5/i3s-mesh-source-material.d.ts +5 -0
- package/dist/v5/i3s-mesh-source.d.ts +30 -0
- package/dist/v5/i3s-selection-source.d.ts +70 -0
- package/dist/v5/index.cjs +20 -0
- package/dist/v5/index.d.ts +18 -2
- package/dist/v5/index.js +31 -1
- package/dist/v5/mesh-conversion.d.ts +29 -2
- package/dist/v5/mesh-features.d.ts +9 -0
- package/dist/v5/mesh-hierarchy.d.ts +47 -0
- package/dist/v5/mesh-source-features.d.ts +2 -1
- package/dist/v5/mesh-source-schema.d.ts +12 -0
- package/dist/v5/mesh-source-texture.d.ts +2 -0
- package/dist/v5/mesh-source.d.ts +13 -3
- package/dist/v5/mesh-tileset-sink.d.ts +5 -0
- package/dist/v5/mesh.d.ts +6 -1
- package/dist/v5/node-cli.d.ts +9 -0
- package/dist/v5/node-conversion.d.ts +32 -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 +900 -0
- package/dist/v5/node.d.ts +6 -0
- package/dist/v5/node.js +877 -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 +4 -7
- package/dist/v5/point-cloud-spatial.d.ts +22 -0
- package/dist/v5/point-cloud-tileset-sink.d.ts +28 -0
- package/dist/v5/point-cloud-tileset.d.ts +44 -0
- package/dist/v5/point-cloud.d.ts +26 -1
- package/dist/v5/resource-fetcher.d.ts +18 -0
- 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 +3 -1
- package/package.json +33 -25
- package/src/index.ts +4 -1
- package/src/v4/3d-tiles-converter/helpers/load-i3s.ts +2 -4
- package/src/v4/converter-cli.ts +8 -2
- package/src/v4/i3s-converter/helpers/load-3d-tiles.ts +3 -51
- package/src/v4/i3s-converter/helpers/node-index-document.ts +5 -2
- package/src/v4/i3s-converter/helpers/preprocess-3d-tiles.ts +1 -1
- package/src/v4/i3s-converter/json-templates/store.ts +2 -1
- package/src/v5/adapters.ts +69 -2
- package/src/v5/archive-source.ts +235 -0
- package/src/v5/browser-tileset-source.ts +418 -48
- package/src/v5/browser.ts +81 -2
- package/src/v5/conversion-api.ts +69 -9
- package/src/v5/converter-cli.ts +28 -0
- package/src/v5/core.ts +6 -0
- package/src/v5/i3s-mesh-conversion.ts +97 -19
- package/src/v5/i3s-mesh-sink.ts +351 -0
- package/src/v5/i3s-mesh-source-features.ts +279 -0
- package/src/v5/i3s-mesh-source-material.ts +157 -0
- package/src/v5/i3s-mesh-source.ts +228 -0
- package/src/v5/i3s-selection-source.ts +298 -0
- package/src/v5/index.ts +60 -2
- package/src/v5/mesh-conversion.ts +61 -11
- package/src/v5/mesh-draco.ts +47 -3
- package/src/v5/mesh-features.ts +319 -0
- package/src/v5/mesh-hierarchy.ts +648 -0
- package/src/v5/mesh-source-features.ts +73 -10
- package/src/v5/mesh-source-schema.ts +134 -0
- package/src/v5/mesh-source-texture.ts +47 -0
- package/src/v5/mesh-source.ts +99 -25
- package/src/v5/mesh-tileset-sink.ts +1 -1
- package/src/v5/mesh.ts +16 -7
- package/src/v5/node-cli.ts +197 -0
- package/src/v5/node-conversion.ts +345 -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-conversion.ts +9 -1
- package/src/v5/point-cloud-file.ts +306 -0
- package/src/v5/point-cloud-potree.ts +130 -0
- package/src/v5/point-cloud-source-encoder.ts +31 -20
- package/src/v5/point-cloud-spatial.ts +280 -0
- package/src/v5/point-cloud-tileset-sink.ts +234 -0
- package/src/v5/point-cloud-tileset.ts +137 -0
- package/src/v5/point-cloud.ts +159 -32
- package/src/v5/resource-fetcher.ts +113 -0
- package/src/v5/resource-manifest.ts +40 -5
- package/src/v5/selected-mesh-source.ts +384 -0
- package/src/v5/spatial-conversion.ts +161 -6
package/dist/v5/core.js
CHANGED
|
@@ -27,6 +27,10 @@ async function convertTileset(options) {
|
|
|
27
27
|
measureOutputBytes,
|
|
28
28
|
maxInputResourceBytes = Number.POSITIVE_INFINITY,
|
|
29
29
|
maxOutputResourceBytes = Number.POSITIVE_INFINITY,
|
|
30
|
+
maxInputBytes = Number.POSITIVE_INFINITY,
|
|
31
|
+
maxOutputBytes = Number.POSITIVE_INFINITY,
|
|
32
|
+
maxInputResources = Number.POSITIVE_INFINITY,
|
|
33
|
+
maxOutputResources = Number.POSITIVE_INFINITY,
|
|
30
34
|
signal,
|
|
31
35
|
onProgress
|
|
32
36
|
} = options;
|
|
@@ -40,16 +44,26 @@ async function convertTileset(options) {
|
|
|
40
44
|
onProgress?.({ phase, inputResources, outputResources, inputBytes, outputBytes });
|
|
41
45
|
};
|
|
42
46
|
try {
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
47
|
+
for (const [name, limit, code] of [
|
|
48
|
+
["maxInputResourceBytes", maxInputResourceBytes, "INVALID_INPUT_RESOURCE_LIMIT"],
|
|
49
|
+
["maxOutputResourceBytes", maxOutputResourceBytes, "INVALID_OUTPUT_RESOURCE_LIMIT"],
|
|
50
|
+
["maxInputBytes", maxInputBytes, "INVALID_INPUT_BYTES_LIMIT"],
|
|
51
|
+
["maxOutputBytes", maxOutputBytes, "INVALID_OUTPUT_BYTES_LIMIT"],
|
|
52
|
+
["maxInputResources", maxInputResources, "INVALID_INPUT_COUNT_LIMIT"],
|
|
53
|
+
["maxOutputResources", maxOutputResources, "INVALID_OUTPUT_COUNT_LIMIT"]
|
|
54
|
+
]) {
|
|
55
|
+
if (limit !== Infinity && (!Number.isSafeInteger(limit) || limit < 0)) {
|
|
56
|
+
throw new TileConversionError(
|
|
57
|
+
code,
|
|
58
|
+
name + " must be a nonnegative safe integer or Infinity"
|
|
59
|
+
);
|
|
60
|
+
}
|
|
48
61
|
}
|
|
49
62
|
reportProgress("inspect");
|
|
50
63
|
const inspection = await inspectTileset(source, signal);
|
|
51
64
|
for await (const inputResource of source.read(inspection, signal)) {
|
|
52
65
|
throwIfAborted(signal);
|
|
66
|
+
checkConversionTotal(inputResources, 1, maxInputResources, "INPUT_COUNT_LIMIT_EXCEEDED");
|
|
53
67
|
const inputResourceBytes = measureInputBytes(inputResource);
|
|
54
68
|
if (!Number.isSafeInteger(inputResourceBytes) || inputResourceBytes < 0) {
|
|
55
69
|
throw new TileConversionError(
|
|
@@ -63,23 +77,41 @@ async function convertTileset(options) {
|
|
|
63
77
|
`Input resource is ${inputResourceBytes} bytes, exceeding the configured limit of ${maxInputResourceBytes} bytes`
|
|
64
78
|
);
|
|
65
79
|
}
|
|
80
|
+
checkConversionTotal(
|
|
81
|
+
inputBytes,
|
|
82
|
+
inputResourceBytes,
|
|
83
|
+
maxInputBytes,
|
|
84
|
+
"INPUT_BYTES_LIMIT_EXCEEDED"
|
|
85
|
+
);
|
|
66
86
|
inputResources++;
|
|
67
87
|
inputBytes += inputResourceBytes;
|
|
68
88
|
reportProgress("convert");
|
|
69
89
|
const convertedResources = await codec.convert(inputResource, inspection, signal);
|
|
70
90
|
for await (const outputResource of convertedResources) {
|
|
71
91
|
throwIfAborted(signal);
|
|
92
|
+
checkConversionTotal(outputResources, 1, maxOutputResources, "OUTPUT_COUNT_LIMIT_EXCEEDED");
|
|
72
93
|
reportProgress("validate");
|
|
73
94
|
const resourceDiagnostics = await codec.validateOutput?.(outputResource, signal) || [];
|
|
74
95
|
diagnostics.push(...resourceDiagnostics);
|
|
75
96
|
const outputResourceBytes = measureOutputBytes(outputResource);
|
|
76
|
-
|
|
97
|
+
if (!Number.isSafeInteger(outputResourceBytes) || outputResourceBytes < 0) {
|
|
98
|
+
throw new TileConversionError(
|
|
99
|
+
"INVALID_OUTPUT_RESOURCE_SIZE",
|
|
100
|
+
"measureOutputBytes must return a nonnegative safe integer"
|
|
101
|
+
);
|
|
102
|
+
}
|
|
77
103
|
if (outputResourceBytes > maxOutputResourceBytes) {
|
|
78
104
|
throw new TileConversionError(
|
|
79
105
|
"OUTPUT_RESOURCE_TOO_LARGE",
|
|
80
106
|
`Output resource is ${outputResourceBytes} bytes, exceeding the configured limit of ${maxOutputResourceBytes} bytes`
|
|
81
107
|
);
|
|
82
108
|
}
|
|
109
|
+
checkConversionTotal(
|
|
110
|
+
outputBytes,
|
|
111
|
+
outputResourceBytes,
|
|
112
|
+
maxOutputBytes,
|
|
113
|
+
"OUTPUT_BYTES_LIMIT_EXCEEDED"
|
|
114
|
+
);
|
|
83
115
|
if (resourceDiagnostics.some((diagnostic) => diagnostic.severity === "error")) {
|
|
84
116
|
throw new TileConversionError(
|
|
85
117
|
"OUTPUT_VALIDATION_FAILED",
|
|
@@ -90,6 +122,7 @@ async function convertTileset(options) {
|
|
|
90
122
|
throwIfAborted(signal);
|
|
91
123
|
await sink.write(outputResource, signal);
|
|
92
124
|
throwIfAborted(signal);
|
|
125
|
+
largestOutputResourceBytes = Math.max(largestOutputResourceBytes, outputResourceBytes);
|
|
93
126
|
outputResources++;
|
|
94
127
|
outputBytes += outputResourceBytes;
|
|
95
128
|
reportProgress("convert");
|
|
@@ -134,6 +167,20 @@ function throwIfAborted(signal) {
|
|
|
134
167
|
throw signal.reason ?? new DOMException("The operation was aborted", "AbortError");
|
|
135
168
|
}
|
|
136
169
|
}
|
|
170
|
+
function checkConversionTotal(current, increment, limit, code) {
|
|
171
|
+
if (increment > limit - current) {
|
|
172
|
+
throw new TileConversionError(
|
|
173
|
+
code,
|
|
174
|
+
`Conversion exceeds cumulative limit ${limit}: ${current} processed, next resource adds ${increment}`
|
|
175
|
+
);
|
|
176
|
+
}
|
|
177
|
+
if (increment > Number.MAX_SAFE_INTEGER - current) {
|
|
178
|
+
throw new TileConversionError(
|
|
179
|
+
"RESOURCE_TOTAL_OVERFLOW",
|
|
180
|
+
"Conversion totals must remain safe integers"
|
|
181
|
+
);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
137
184
|
|
|
138
185
|
// src/v5/browser-source.ts
|
|
139
186
|
function createBrowserTileConversionSource(options) {
|
|
@@ -278,6 +325,12 @@ function createTiles3DConversionSpatialContext(discovered, options = {}) {
|
|
|
278
325
|
if (spatialReference.status === "unresolved") {
|
|
279
326
|
throw createSpatialConversionError(spatialReference);
|
|
280
327
|
}
|
|
328
|
+
if (spatialReference.status !== "native" && spatialReference.coordinateEpoch !== void 0) {
|
|
329
|
+
throw new TileConversionError(
|
|
330
|
+
"SPATIAL_EPOCH_UNSUPPORTED",
|
|
331
|
+
"Epoch-tagged coordinates require an epoch-aware datum operation before conversion"
|
|
332
|
+
);
|
|
333
|
+
}
|
|
281
334
|
const transformer = spatialReference.status === "native" ? void 0 : createSpatialTransformer(
|
|
282
335
|
spatialReference,
|
|
283
336
|
() => new Tiles3DSpatialTransformer(spatialReference, options)
|
|
@@ -285,8 +338,8 @@ function createTiles3DConversionSpatialContext(discovered, options = {}) {
|
|
|
285
338
|
const outputSpatialReference = transformer ? markTilesetSpatialReferenceTransformed(spatialReference) : spatialReference;
|
|
286
339
|
return {
|
|
287
340
|
spatialReference: outputSpatialReference,
|
|
288
|
-
transformPositions: (positions) => transformer ? transformer.transformPositions(positions) :
|
|
289
|
-
transformNormals: (normals, positions) => transformer ? transformer.transformNormals(normals, positions) :
|
|
341
|
+
transformPositions: (positions) => transformer ? transformer.transformPositions(positions) : copyNativePositions(positions),
|
|
342
|
+
transformNormals: (normals, positions) => transformer ? transformer.transformNormals(normals, positions) : copyNativeNormals(normals, positions),
|
|
290
343
|
transformBoundingVolume: (volume, sourceTransform) => transformer ? transformer.transformBoundingVolume(volume, sourceTransform) : cloneBoundingVolume(volume)
|
|
291
344
|
};
|
|
292
345
|
}
|
|
@@ -295,16 +348,57 @@ function createI3SConversionSpatialContext(discovered, options = {}) {
|
|
|
295
348
|
if (spatialReference.status === "unresolved") {
|
|
296
349
|
throw createSpatialConversionError(spatialReference);
|
|
297
350
|
}
|
|
351
|
+
if (spatialReference.status !== "native" && spatialReference.coordinateEpoch !== void 0) {
|
|
352
|
+
throw new TileConversionError(
|
|
353
|
+
"SPATIAL_EPOCH_UNSUPPORTED",
|
|
354
|
+
"Epoch-tagged coordinates require an epoch-aware datum operation before conversion"
|
|
355
|
+
);
|
|
356
|
+
}
|
|
298
357
|
const transformer = spatialReference.status === "native" ? void 0 : createSpatialTransformer(
|
|
299
358
|
spatialReference,
|
|
300
359
|
() => new I3SSpatialTransformer(spatialReference, options)
|
|
301
360
|
);
|
|
302
361
|
const outputSpatialReference = transformer ? transformer.spatialReference : spatialReference;
|
|
362
|
+
let nativeNormalTransformer;
|
|
363
|
+
const transformNativeNormals = (normals, positions, normalReferenceFrame) => {
|
|
364
|
+
validateNormalReferenceFrame(normalReferenceFrame);
|
|
365
|
+
const targetNormals = copyNativeNormals(normals, positions);
|
|
366
|
+
if (!spatialReference.sourceCrs || ["local", "cartesian"].includes(spatialReference.coordinateFrame)) {
|
|
367
|
+
return targetNormals;
|
|
368
|
+
}
|
|
369
|
+
nativeNormalTransformer ||= createSpatialTransformer(
|
|
370
|
+
spatialReference,
|
|
371
|
+
() => new I3SSpatialTransformer(
|
|
372
|
+
{
|
|
373
|
+
...spatialReference,
|
|
374
|
+
status: "transformable",
|
|
375
|
+
targetCrs: spatialReference.sourceCrs
|
|
376
|
+
},
|
|
377
|
+
options
|
|
378
|
+
)
|
|
379
|
+
);
|
|
380
|
+
return nativeNormalTransformer.transformNormals(targetNormals, positions, normalReferenceFrame);
|
|
381
|
+
};
|
|
303
382
|
return {
|
|
304
383
|
spatialReference: outputSpatialReference,
|
|
305
384
|
transformPositions: (positions, sourceOrigin) => transformer ? transformer.transformPositions(positions, sourceOrigin) : createNativeI3SPositions(positions, sourceOrigin, spatialReference),
|
|
385
|
+
transformGeometryAsync: async (positions, normals, normalReferenceFrame) => {
|
|
386
|
+
if (transformer)
|
|
387
|
+
return transformer.transformGeometryAsync(positions, normals, normalReferenceFrame);
|
|
388
|
+
const targetPositions = Float64Array.from(positions);
|
|
389
|
+
if (targetPositions.length % 3 || targetPositions.some((value) => !Number.isFinite(value)) || normals && (normals.length !== positions.length || Array.from(normals).some((value) => !Number.isFinite(value))) || normalReferenceFrame !== void 0 && !["earth-centered", "vertex-reference-frame"].includes(normalReferenceFrame)) {
|
|
390
|
+
throw new TileConversionError(
|
|
391
|
+
"SPATIAL_INPUT_INVALID",
|
|
392
|
+
"Conversion geometry requires finite matching xyz arrays and a supported I3S vector frame"
|
|
393
|
+
);
|
|
394
|
+
}
|
|
395
|
+
return {
|
|
396
|
+
positions: targetPositions,
|
|
397
|
+
normals: normals ? transformNativeNormals(normals, positions, normalReferenceFrame) : void 0
|
|
398
|
+
};
|
|
399
|
+
},
|
|
306
400
|
transformPositionsAsync: (positions, sourceOrigin) => transformer ? transformer.transformPositionsAsync(positions, sourceOrigin) : Promise.resolve(createNativeI3SPositions(positions, sourceOrigin, spatialReference)),
|
|
307
|
-
transformNormals: (normals, positions, normalReferenceFrame) => transformer ? transformer.transformNormals(normals, positions, normalReferenceFrame) :
|
|
401
|
+
transformNormals: (normals, positions, normalReferenceFrame) => transformer ? transformer.transformNormals(normals, positions, normalReferenceFrame) : transformNativeNormals(normals, positions, normalReferenceFrame),
|
|
308
402
|
transformBoundingVolume: (bounds) => transformer ? transformer.transformBoundingVolume(bounds) : cloneI3SBounds(bounds),
|
|
309
403
|
transformBoundingVolumeAsync: (bounds) => transformer ? transformer.transformBoundingVolumeAsync(bounds) : Promise.resolve(cloneI3SBounds(bounds))
|
|
310
404
|
};
|
|
@@ -388,21 +482,30 @@ function cloneI3SBounds(bounds) {
|
|
|
388
482
|
};
|
|
389
483
|
}
|
|
390
484
|
function createNativeI3SPositions(sourcePositions, sourceOrigin, spatialReference) {
|
|
391
|
-
|
|
392
|
-
|
|
485
|
+
const sourcePositions64 = copyNativePositions(sourcePositions);
|
|
486
|
+
if (sourceOrigin.length !== 3 || Array.from(sourceOrigin).some((value) => !Number.isFinite(value))) {
|
|
487
|
+
throw new TileConversionError(
|
|
488
|
+
"SPATIAL_INPUT_INVALID",
|
|
489
|
+
"I3S origins require three finite coordinates"
|
|
490
|
+
);
|
|
393
491
|
}
|
|
394
492
|
const origin = [
|
|
395
493
|
Number(sourceOrigin[0]),
|
|
396
494
|
Number(sourceOrigin[1]),
|
|
397
495
|
Number(sourceOrigin[2])
|
|
398
496
|
];
|
|
399
|
-
const sourcePositions64 = Float64Array.from(sourcePositions);
|
|
400
497
|
const positions = new Float32Array(sourcePositions.length);
|
|
401
498
|
for (let index = 0; index < sourcePositions.length; index += 3) {
|
|
402
499
|
positions[index] = Number(sourcePositions[index]) - origin[0];
|
|
403
500
|
positions[index + 1] = Number(sourcePositions[index + 1]) - origin[1];
|
|
404
501
|
positions[index + 2] = Number(sourcePositions[index + 2]) - origin[2];
|
|
405
502
|
}
|
|
503
|
+
if (positions.some((value) => !Number.isFinite(value))) {
|
|
504
|
+
throw new TileConversionError(
|
|
505
|
+
"SPATIAL_INPUT_INVALID",
|
|
506
|
+
"Native I3S offsets exceed float32 range"
|
|
507
|
+
);
|
|
508
|
+
}
|
|
406
509
|
const isGeographic = spatialReference.coordinateFrame === "geographic";
|
|
407
510
|
const modelMatrix = isGeographic ? new Matrix4() : new Matrix4().translate(origin);
|
|
408
511
|
return {
|
|
@@ -414,6 +517,61 @@ function createNativeI3SPositions(sourcePositions, sourceOrigin, spatialReferenc
|
|
|
414
517
|
modelMatrix
|
|
415
518
|
};
|
|
416
519
|
}
|
|
520
|
+
function copyNativePositions(positions) {
|
|
521
|
+
validateNativePositions(positions);
|
|
522
|
+
return Float64Array.from(positions);
|
|
523
|
+
}
|
|
524
|
+
function validateNativePositions(positions) {
|
|
525
|
+
if (positions.length % 3) {
|
|
526
|
+
throw new TileConversionError(
|
|
527
|
+
"SPATIAL_INPUT_INVALID",
|
|
528
|
+
"Positions require complete finite xyz coordinates"
|
|
529
|
+
);
|
|
530
|
+
}
|
|
531
|
+
for (let index = 0; index < positions.length; index++) {
|
|
532
|
+
if (!Number.isFinite(positions[index])) {
|
|
533
|
+
throw new TileConversionError(
|
|
534
|
+
"SPATIAL_INPUT_INVALID",
|
|
535
|
+
"Positions require complete finite xyz coordinates"
|
|
536
|
+
);
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
function copyNativeNormals(normals, positions) {
|
|
541
|
+
validateNativePositions(positions);
|
|
542
|
+
if (normals.length !== positions.length) {
|
|
543
|
+
throw new TileConversionError(
|
|
544
|
+
"SPATIAL_INPUT_INVALID",
|
|
545
|
+
"Normals require finite xyz values matching positions"
|
|
546
|
+
);
|
|
547
|
+
}
|
|
548
|
+
const output = new Float32Array(normals.length);
|
|
549
|
+
for (let index = 0; index < normals.length; index += 3) {
|
|
550
|
+
const scale = Math.max(
|
|
551
|
+
Math.abs(normals[index]),
|
|
552
|
+
Math.abs(normals[index + 1]),
|
|
553
|
+
Math.abs(normals[index + 2])
|
|
554
|
+
);
|
|
555
|
+
if (!Number.isFinite(scale)) {
|
|
556
|
+
throw new TileConversionError(
|
|
557
|
+
"SPATIAL_INPUT_INVALID",
|
|
558
|
+
"Normals require finite xyz values matching positions"
|
|
559
|
+
);
|
|
560
|
+
}
|
|
561
|
+
const length = scale ? Math.hypot(normals[index] / scale, normals[index + 1] / scale, normals[index + 2] / scale) : 0;
|
|
562
|
+
for (let axis = 0; axis < 3; axis++)
|
|
563
|
+
output[index + axis] = length ? normals[index + axis] / scale / length : 0;
|
|
564
|
+
}
|
|
565
|
+
return output;
|
|
566
|
+
}
|
|
567
|
+
function validateNormalReferenceFrame(frame) {
|
|
568
|
+
if (frame !== void 0 && !["earth-centered", "vertex-reference-frame"].includes(frame)) {
|
|
569
|
+
throw new TileConversionError(
|
|
570
|
+
"SPATIAL_INPUT_INVALID",
|
|
571
|
+
"Unsupported I3S normal reference frame"
|
|
572
|
+
);
|
|
573
|
+
}
|
|
574
|
+
}
|
|
417
575
|
|
|
418
576
|
// src/v5/tileset-source.ts
|
|
419
577
|
import { traverseTilesetContents } from "@loaders.gl/tiles";
|
|
@@ -438,10 +596,17 @@ function createTilesetConversionSource(tileset, options = {}) {
|
|
|
438
596
|
// src/v5/resource-manifest.ts
|
|
439
597
|
var TILE_RESOURCE_MANIFEST_VERSION = 1;
|
|
440
598
|
async function createManifestBackedTileConversionSink(options) {
|
|
599
|
+
const maxResources = options.maxResources ?? Infinity;
|
|
600
|
+
if (maxResources !== Infinity && (!Number.isSafeInteger(maxResources) || maxResources < 0)) {
|
|
601
|
+
throw new TileConversionError(
|
|
602
|
+
"INVALID_MANIFEST_RESOURCE_LIMIT",
|
|
603
|
+
"maxResources must be a nonnegative safe integer or Infinity"
|
|
604
|
+
);
|
|
605
|
+
}
|
|
441
606
|
const priorManifest = await options.manifestStore.load();
|
|
442
607
|
const resources = /* @__PURE__ */ new Map();
|
|
443
608
|
if (priorManifest) {
|
|
444
|
-
validateManifest(priorManifest);
|
|
609
|
+
validateManifest(priorManifest, maxResources);
|
|
445
610
|
for (const entry of priorManifest.resources) {
|
|
446
611
|
resources.set(entry.resourceId, entry);
|
|
447
612
|
}
|
|
@@ -467,6 +632,7 @@ async function createManifestBackedTileConversionSink(options) {
|
|
|
467
632
|
if (writeFailed) {
|
|
468
633
|
throw writeFailure;
|
|
469
634
|
}
|
|
635
|
+
signal?.throwIfAborted();
|
|
470
636
|
const entry = createManifestEntry(options, resource);
|
|
471
637
|
const priorEntry = resources.get(entry.resourceId);
|
|
472
638
|
if (priorEntry) {
|
|
@@ -476,7 +642,14 @@ async function createManifestBackedTileConversionSink(options) {
|
|
|
476
642
|
`Resource "${entry.resourceId}" changed since the saved manifest`
|
|
477
643
|
);
|
|
478
644
|
}
|
|
479
|
-
|
|
645
|
+
const verified = options.verifyResource ? await options.verifyResource({ ...priorEntry }, signal) : true;
|
|
646
|
+
signal?.throwIfAborted();
|
|
647
|
+
if (verified === true) return;
|
|
648
|
+
} else if (resources.size >= maxResources) {
|
|
649
|
+
throw new TileConversionError(
|
|
650
|
+
"MANIFEST_RESOURCE_LIMIT_EXCEEDED",
|
|
651
|
+
"Resource checkpoint exceeds maxResources"
|
|
652
|
+
);
|
|
480
653
|
}
|
|
481
654
|
await options.sink.write(resource, signal);
|
|
482
655
|
resources.set(entry.resourceId, entry);
|
|
@@ -525,11 +698,17 @@ function createManifestEntry(options, resource) {
|
|
|
525
698
|
}
|
|
526
699
|
return { resourceId, byteLength, fingerprint };
|
|
527
700
|
}
|
|
528
|
-
function validateManifest(manifest) {
|
|
701
|
+
function validateManifest(manifest, maxResources) {
|
|
529
702
|
if (typeof manifest !== "object" || manifest === null || manifest.version !== TILE_RESOURCE_MANIFEST_VERSION || typeof manifest.complete !== "boolean" || !Array.isArray(manifest.resources)) {
|
|
530
703
|
throw new TileConversionError(
|
|
531
704
|
"UNSUPPORTED_RESOURCE_MANIFEST",
|
|
532
|
-
`Unsupported or malformed resource manifest version: ${String(manifest
|
|
705
|
+
`Unsupported or malformed resource manifest version: ${String(manifest?.version)}`
|
|
706
|
+
);
|
|
707
|
+
}
|
|
708
|
+
if (manifest.resources.length > maxResources) {
|
|
709
|
+
throw new TileConversionError(
|
|
710
|
+
"MANIFEST_RESOURCE_LIMIT_EXCEEDED",
|
|
711
|
+
"Loaded manifest exceeds maxResources"
|
|
533
712
|
);
|
|
534
713
|
}
|
|
535
714
|
const resourceIds = /* @__PURE__ */ new Set();
|
|
@@ -621,50 +800,67 @@ function measureBlobPart(part) {
|
|
|
621
800
|
|
|
622
801
|
// src/v5/browser-tileset-source.ts
|
|
623
802
|
function createBrowserTilesetConversionSource(options) {
|
|
624
|
-
|
|
803
|
+
const maxTilesetDocuments = options.maxTilesetDocuments ?? 1;
|
|
804
|
+
if (options.inspectNestedTilesets !== void 0 && typeof options.inspectNestedTilesets !== "boolean" || !Number.isSafeInteger(maxTilesetDocuments) || maxTilesetDocuments < 1 || !Number.isSafeInteger(options.maxInputBytes) || options.maxInputBytes < 1 || !Number.isSafeInteger(options.maxInputResources) || options.maxInputResources < 1) {
|
|
625
805
|
throw new TileConversionError(
|
|
626
806
|
"INVALID_BROWSER_SOURCE_OPTIONS",
|
|
627
|
-
"Browser tileset sources require positive byte and
|
|
807
|
+
"Browser tileset sources require positive byte, declaration and document limits"
|
|
628
808
|
);
|
|
629
809
|
}
|
|
630
810
|
const fetcher = options.fetcher ?? fetch;
|
|
631
811
|
return {
|
|
632
812
|
async inspect(signal) {
|
|
633
813
|
throwIfAborted4(signal);
|
|
634
|
-
const
|
|
635
|
-
const response = await fetcher(
|
|
814
|
+
const requestedUrl = new URL(options.input).href;
|
|
815
|
+
const response = await fetcher(requestedUrl, { signal });
|
|
816
|
+
const rootUrl = response.url || requestedUrl;
|
|
636
817
|
if (!response.ok) {
|
|
818
|
+
await response.body?.cancel();
|
|
637
819
|
throw new TileConversionError(
|
|
638
820
|
"BROWSER_SOURCE_FETCH_FAILED",
|
|
639
821
|
`Could not fetch browser tileset (${response.status} ${response.statusText})`
|
|
640
822
|
);
|
|
641
823
|
}
|
|
642
824
|
const data = await readResponseBytes2(response, options.maxInputBytes, signal);
|
|
643
|
-
|
|
644
|
-
try {
|
|
645
|
-
tileset = JSON.parse(new TextDecoder().decode(data));
|
|
646
|
-
} catch {
|
|
647
|
-
throw new TileConversionError("INVALID_BROWSER_TILESET", "Tileset input is not valid JSON");
|
|
648
|
-
}
|
|
649
|
-
if (!isRecord(tileset) || !["1.0", "1.1"].includes(tileset.asset?.version || "") || !isRecord(tileset.root)) {
|
|
650
|
-
throw new TileConversionError(
|
|
651
|
-
"UNSUPPORTED_BROWSER_TILESET",
|
|
652
|
-
"Browser tileset traversal supports explicit 3D Tiles 1.0 and 1.1 documents"
|
|
653
|
-
);
|
|
654
|
-
}
|
|
825
|
+
const tileset = parseTilesetDocument(data);
|
|
655
826
|
const resources = collectResources(tileset.root, options.maxInputResources);
|
|
656
|
-
|
|
827
|
+
const inspection = { rootUrl, tileset, resources, tilesetBytes: data.byteLength };
|
|
828
|
+
return options.inspectNestedTilesets ? expandInspection(inspection, options, maxTilesetDocuments, fetcher, signal) : inspection;
|
|
657
829
|
},
|
|
658
830
|
async *read(inspection, signal) {
|
|
659
831
|
let inputBytes = inspection.tilesetBytes;
|
|
660
|
-
|
|
832
|
+
let declarations = inspection.resources.length;
|
|
833
|
+
let documents = 1;
|
|
834
|
+
const stack = [
|
|
835
|
+
{
|
|
836
|
+
resources: inspection.resources,
|
|
837
|
+
index: 0,
|
|
838
|
+
tileset: inspection.tileset,
|
|
839
|
+
url: inspection.rootUrl,
|
|
840
|
+
ancestors: /* @__PURE__ */ new Set([getDocumentKey(inspection.rootUrl), getDocumentKey(options.input)])
|
|
841
|
+
}
|
|
842
|
+
];
|
|
843
|
+
while (stack.length) {
|
|
661
844
|
throwIfAborted4(signal);
|
|
662
|
-
const
|
|
845
|
+
const frame = stack[stack.length - 1];
|
|
846
|
+
if (frame.index === frame.resources.length) {
|
|
847
|
+
stack.pop();
|
|
848
|
+
continue;
|
|
849
|
+
}
|
|
850
|
+
const descriptor = frame.resources[frame.index++];
|
|
851
|
+
const resourceUrl = resolveResourceUrl(descriptor.uri, descriptor.tilesetUrl ?? frame.url);
|
|
852
|
+
if (frame.ancestors.has(getDocumentKey(resourceUrl))) {
|
|
853
|
+
throw new TileConversionError(
|
|
854
|
+
"BROWSER_TILESET_CYCLE",
|
|
855
|
+
"External tileset references an active ancestor document"
|
|
856
|
+
);
|
|
857
|
+
}
|
|
663
858
|
const response = await fetcher(resourceUrl, { signal });
|
|
664
859
|
if (!response.ok) {
|
|
860
|
+
await response.body?.cancel();
|
|
665
861
|
throw new TileConversionError(
|
|
666
862
|
"BROWSER_SOURCE_FETCH_FAILED",
|
|
667
|
-
|
|
863
|
+
"Could not fetch browser tile content (" + response.status + " " + response.statusText + ")",
|
|
668
864
|
[
|
|
669
865
|
{
|
|
670
866
|
code: "BROWSER_SOURCE_FETCH_FAILED",
|
|
@@ -675,28 +871,280 @@ function createBrowserTilesetConversionSource(options) {
|
|
|
675
871
|
]
|
|
676
872
|
);
|
|
677
873
|
}
|
|
678
|
-
const
|
|
679
|
-
|
|
874
|
+
const documentUrl = response.url || resourceUrl;
|
|
875
|
+
if (frame.ancestors.has(getDocumentKey(documentUrl))) {
|
|
876
|
+
await response.body?.cancel();
|
|
877
|
+
throw new TileConversionError(
|
|
878
|
+
"BROWSER_TILESET_CYCLE",
|
|
879
|
+
"Redirected content references an active ancestor document"
|
|
880
|
+
);
|
|
881
|
+
}
|
|
882
|
+
const data = await readResponseBytes2(response, options.maxInputBytes - inputBytes, signal);
|
|
680
883
|
inputBytes += data.byteLength;
|
|
681
884
|
const contentType = response.headers.get("content-type") ?? void 0;
|
|
682
885
|
if (isJsonContent(data, contentType)) {
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
886
|
+
if (inspection.documents)
|
|
887
|
+
throw new TileConversionError(
|
|
888
|
+
"UNSUPPORTED_BROWSER_TILESET_CONTENT",
|
|
889
|
+
"Expanded inspection selections must be binary content; declare external metadata with a .json URI"
|
|
890
|
+
);
|
|
891
|
+
if (maxTilesetDocuments === 1) {
|
|
892
|
+
throw new TileConversionError(
|
|
893
|
+
"UNSUPPORTED_BROWSER_TILESET_CONTENT",
|
|
894
|
+
"Nested tilesets and JSON tile content require an explicit document budget"
|
|
895
|
+
);
|
|
896
|
+
}
|
|
897
|
+
if (documents >= maxTilesetDocuments) {
|
|
898
|
+
throw new TileConversionError(
|
|
899
|
+
"INPUT_TILESET_COUNT_EXCEEDED",
|
|
900
|
+
"Browser tileset exceeds its document limit"
|
|
901
|
+
);
|
|
902
|
+
}
|
|
903
|
+
let tile = frame.tileset.root;
|
|
904
|
+
for (const childIndex of descriptor.tilePath) tile = getTileChild(tile, childIndex);
|
|
905
|
+
if (tile.children?.length) {
|
|
906
|
+
throw new TileConversionError(
|
|
907
|
+
"INVALID_BROWSER_TILESET",
|
|
908
|
+
"External tileset references cannot also have child tiles"
|
|
909
|
+
);
|
|
910
|
+
}
|
|
911
|
+
const tileset = parseTilesetDocument(data);
|
|
912
|
+
const resources = collectResources(
|
|
913
|
+
tileset.root,
|
|
914
|
+
options.maxInputResources - declarations,
|
|
915
|
+
descriptor.transform
|
|
916
|
+
).map((resource) => ({
|
|
917
|
+
...resource,
|
|
918
|
+
resourceId: descriptor.resourceId + "/" + resource.resourceId,
|
|
919
|
+
tilesetUrl: documentUrl
|
|
920
|
+
}));
|
|
921
|
+
declarations += resources.length;
|
|
922
|
+
documents++;
|
|
923
|
+
stack.push({
|
|
924
|
+
resources,
|
|
925
|
+
index: 0,
|
|
926
|
+
tileset,
|
|
927
|
+
url: documentUrl,
|
|
928
|
+
ancestors: /* @__PURE__ */ new Set([
|
|
929
|
+
...frame.ancestors,
|
|
930
|
+
getDocumentKey(resourceUrl),
|
|
931
|
+
getDocumentKey(documentUrl)
|
|
932
|
+
])
|
|
933
|
+
});
|
|
934
|
+
continue;
|
|
687
935
|
}
|
|
688
|
-
yield { ...descriptor, data, ...contentType ? { contentType } : {} };
|
|
936
|
+
yield { ...descriptor, data, contentUrl: documentUrl, ...contentType ? { contentType } : {} };
|
|
689
937
|
}
|
|
690
938
|
}
|
|
691
939
|
};
|
|
692
940
|
}
|
|
693
|
-
function
|
|
941
|
+
function selectBrowserTilesetContent(inspection, descriptor) {
|
|
942
|
+
const placement = inspection.documents?.[descriptor.documentIndex ?? 0];
|
|
943
|
+
if (inspection.documents && (!Number.isSafeInteger(descriptor.documentIndex) || !placement))
|
|
944
|
+
throw new TileConversionError(
|
|
945
|
+
"INVALID_BROWSER_SELECTION",
|
|
946
|
+
"Content descriptor has no inspected document placement"
|
|
947
|
+
);
|
|
948
|
+
const document = structuredClone(placement?.tileset ?? inspection.tileset);
|
|
949
|
+
const originalRoot = document.root;
|
|
950
|
+
let transform = [...placement?.parentTransform ?? identityMatrix()];
|
|
951
|
+
let maximum = placement?.parentGeometricError ?? 0;
|
|
952
|
+
let tile = originalRoot;
|
|
953
|
+
for (let depth = 0; ; depth++) {
|
|
954
|
+
transform = multiplyMatrices(transform, normalizeMatrix(tile.transform));
|
|
955
|
+
if (typeof tile.geometricError !== "number" || !Number.isFinite(tile.geometricError) || tile.geometricError < 0)
|
|
956
|
+
throw new TileConversionError(
|
|
957
|
+
"MESH_GEOMETRIC_ERROR_INVALID",
|
|
958
|
+
"Every selected ancestor must declare a finite nonnegative geometricError"
|
|
959
|
+
);
|
|
960
|
+
maximum = Math.max(maximum, tile.geometricError * getTransformScale(transform));
|
|
961
|
+
if (depth === descriptor.tilePath.length) break;
|
|
962
|
+
const child = getTileChild(tile, descriptor.tilePath[depth]);
|
|
963
|
+
delete tile.content;
|
|
964
|
+
delete tile.contents;
|
|
965
|
+
tile.children = [child];
|
|
966
|
+
tile = child;
|
|
967
|
+
}
|
|
968
|
+
if (placement && placement.parentGeometricError === void 0 && descriptor.documentIndex !== 0)
|
|
969
|
+
throw new TileConversionError(
|
|
970
|
+
"MESH_GEOMETRIC_ERROR_INVALID",
|
|
971
|
+
"External ancestors must declare geometricError"
|
|
972
|
+
);
|
|
973
|
+
const isLeaf = !tile.children?.length;
|
|
974
|
+
const contentIndex = Number(descriptor.resourceId.match(/content-(\d+)$/)?.[1]);
|
|
975
|
+
const content = tile.contents?.[contentIndex] ?? tile.content;
|
|
976
|
+
if (!content || (content.uri ?? content.url) !== descriptor.uri)
|
|
977
|
+
throw new TileConversionError(
|
|
978
|
+
"INVALID_BROWSER_SELECTION",
|
|
979
|
+
"Content descriptor does not match its inspected document"
|
|
980
|
+
);
|
|
981
|
+
tile.content = content;
|
|
982
|
+
delete tile.contents;
|
|
983
|
+
delete tile.children;
|
|
984
|
+
if (placement)
|
|
985
|
+
originalRoot.transform = multiplyMatrices(
|
|
986
|
+
[...placement.parentTransform],
|
|
987
|
+
normalizeMatrix(originalRoot.transform)
|
|
988
|
+
);
|
|
989
|
+
if (!Number.isFinite(maximum))
|
|
990
|
+
throw new TileConversionError(
|
|
991
|
+
"MESH_GEOMETRIC_ERROR_INVALID",
|
|
992
|
+
"Scaled geometricError must remain finite"
|
|
993
|
+
);
|
|
994
|
+
return {
|
|
995
|
+
tileset: document,
|
|
996
|
+
contentUrl: resolveResourceUrl(descriptor.uri, placement?.url ?? inspection.rootUrl),
|
|
997
|
+
geometricError: maximum,
|
|
998
|
+
isLeaf
|
|
999
|
+
};
|
|
1000
|
+
}
|
|
1001
|
+
function getTileChild(tile, childIndex) {
|
|
1002
|
+
if (typeof childIndex !== "number" || !Number.isSafeInteger(childIndex) || childIndex < 0 || !Array.isArray(tile.children) || childIndex >= tile.children.length)
|
|
1003
|
+
throw new TileConversionError(
|
|
1004
|
+
"INVALID_BROWSER_SELECTION",
|
|
1005
|
+
"Tile paths require in-bounds nonnegative integer child indexes"
|
|
1006
|
+
);
|
|
1007
|
+
const child = tile.children.at(childIndex);
|
|
1008
|
+
if (!isRecord(child))
|
|
1009
|
+
throw new TileConversionError(
|
|
1010
|
+
"INVALID_BROWSER_SELECTION",
|
|
1011
|
+
"Selected child tile must be an object"
|
|
1012
|
+
);
|
|
1013
|
+
return child;
|
|
1014
|
+
}
|
|
1015
|
+
async function expandInspection(inspection, options, maxTilesetDocuments, fetcher, signal) {
|
|
1016
|
+
const documents = [
|
|
1017
|
+
{
|
|
1018
|
+
url: inspection.rootUrl,
|
|
1019
|
+
tileset: inspection.tileset,
|
|
1020
|
+
parentTransform: identityMatrix(),
|
|
1021
|
+
parentGeometricError: 0
|
|
1022
|
+
}
|
|
1023
|
+
];
|
|
694
1024
|
const resources = [];
|
|
1025
|
+
let bytes = inspection.tilesetBytes;
|
|
1026
|
+
let declarations = inspection.resources.length;
|
|
695
1027
|
const stack = [
|
|
696
|
-
{
|
|
1028
|
+
{
|
|
1029
|
+
resources: inspection.resources,
|
|
1030
|
+
index: 0,
|
|
1031
|
+
documentIndex: 0,
|
|
1032
|
+
ancestors: /* @__PURE__ */ new Set([getDocumentKey(inspection.rootUrl), getDocumentKey(options.input)])
|
|
1033
|
+
}
|
|
1034
|
+
];
|
|
1035
|
+
while (stack.length) {
|
|
1036
|
+
throwIfAborted4(signal);
|
|
1037
|
+
const frame = stack[stack.length - 1];
|
|
1038
|
+
if (frame.index === frame.resources.length) {
|
|
1039
|
+
stack.pop();
|
|
1040
|
+
continue;
|
|
1041
|
+
}
|
|
1042
|
+
const descriptor = frame.resources[frame.index++];
|
|
1043
|
+
const placement = documents[frame.documentIndex];
|
|
1044
|
+
const resourceUrl = resolveResourceUrl(descriptor.uri, placement.url);
|
|
1045
|
+
if (!new URL(resourceUrl).pathname.toLowerCase().endsWith(".json")) {
|
|
1046
|
+
resources.push({ ...descriptor, documentIndex: frame.documentIndex });
|
|
1047
|
+
continue;
|
|
1048
|
+
}
|
|
1049
|
+
if (frame.ancestors.has(getDocumentKey(resourceUrl)))
|
|
1050
|
+
throw new TileConversionError(
|
|
1051
|
+
"BROWSER_TILESET_CYCLE",
|
|
1052
|
+
"External tileset references an active ancestor document"
|
|
1053
|
+
);
|
|
1054
|
+
if (documents.length >= maxTilesetDocuments)
|
|
1055
|
+
throw new TileConversionError(
|
|
1056
|
+
"INPUT_TILESET_COUNT_EXCEEDED",
|
|
1057
|
+
"Browser tileset exceeds its document limit"
|
|
1058
|
+
);
|
|
1059
|
+
let tile = placement.tileset.root;
|
|
1060
|
+
for (const childIndex of descriptor.tilePath) tile = tile.children[childIndex];
|
|
1061
|
+
if (tile.children?.length || tile.contents?.length)
|
|
1062
|
+
throw new TileConversionError(
|
|
1063
|
+
"INVALID_BROWSER_TILESET",
|
|
1064
|
+
"Inspected external tilesets require one content and no child tiles"
|
|
1065
|
+
);
|
|
1066
|
+
const response = await fetcher(resourceUrl, { signal });
|
|
1067
|
+
const url = response.url || resourceUrl;
|
|
1068
|
+
if (!response.ok || frame.ancestors.has(getDocumentKey(url))) {
|
|
1069
|
+
await response.body?.cancel();
|
|
1070
|
+
throw new TileConversionError(
|
|
1071
|
+
response.ok ? "BROWSER_TILESET_CYCLE" : "BROWSER_SOURCE_FETCH_FAILED",
|
|
1072
|
+
"Could not inspect external tileset"
|
|
1073
|
+
);
|
|
1074
|
+
}
|
|
1075
|
+
const data = await readResponseBytes2(response, options.maxInputBytes - bytes, signal);
|
|
1076
|
+
bytes += data.byteLength;
|
|
1077
|
+
const tileset = parseTilesetDocument(data);
|
|
1078
|
+
const prefix = descriptor.resourceId + "/";
|
|
1079
|
+
const nestedResources = collectResources(
|
|
1080
|
+
tileset.root,
|
|
1081
|
+
options.maxInputResources - declarations,
|
|
1082
|
+
descriptor.transform
|
|
1083
|
+
).map((resource) => ({ ...resource, resourceId: prefix + resource.resourceId, tilesetUrl: url }));
|
|
1084
|
+
declarations += nestedResources.length;
|
|
1085
|
+
let parentGeometricError;
|
|
1086
|
+
try {
|
|
1087
|
+
parentGeometricError = selectBrowserTilesetContent(
|
|
1088
|
+
{ ...inspection, documents },
|
|
1089
|
+
{ ...descriptor, documentIndex: frame.documentIndex }
|
|
1090
|
+
).geometricError;
|
|
1091
|
+
} catch (error) {
|
|
1092
|
+
if (!(error instanceof TileConversionError) || error.code !== "MESH_GEOMETRIC_ERROR_INVALID")
|
|
1093
|
+
throw error;
|
|
1094
|
+
}
|
|
1095
|
+
const documentIndex = documents.length;
|
|
1096
|
+
documents.push({
|
|
1097
|
+
url,
|
|
1098
|
+
tileset,
|
|
1099
|
+
parentResourceId: descriptor.resourceId,
|
|
1100
|
+
parentTransform: descriptor.transform,
|
|
1101
|
+
parentGeometricError
|
|
1102
|
+
});
|
|
1103
|
+
stack.push({
|
|
1104
|
+
resources: nestedResources,
|
|
1105
|
+
index: 0,
|
|
1106
|
+
documentIndex,
|
|
1107
|
+
ancestors: /* @__PURE__ */ new Set([...frame.ancestors, getDocumentKey(resourceUrl), getDocumentKey(url)])
|
|
1108
|
+
});
|
|
1109
|
+
}
|
|
1110
|
+
return { ...inspection, resources, documents, tilesetBytes: bytes };
|
|
1111
|
+
}
|
|
1112
|
+
function getTransformScale(transform) {
|
|
1113
|
+
const columns = [0, 1, 2].map(
|
|
1114
|
+
(column) => [0, 1, 2].reduce((total, row) => total + Math.abs(transform[column * 4 + row]), 0)
|
|
1115
|
+
);
|
|
1116
|
+
const rows = [0, 1, 2].map(
|
|
1117
|
+
(row) => [0, 1, 2].reduce((total, column) => total + Math.abs(transform[column * 4 + row]), 0)
|
|
1118
|
+
);
|
|
1119
|
+
return Math.sqrt(Math.max(...columns)) * Math.sqrt(Math.max(...rows));
|
|
1120
|
+
}
|
|
1121
|
+
function parseTilesetDocument(data) {
|
|
1122
|
+
let tileset;
|
|
1123
|
+
try {
|
|
1124
|
+
tileset = JSON.parse(new TextDecoder().decode(data));
|
|
1125
|
+
} catch {
|
|
1126
|
+
throw new TileConversionError("INVALID_BROWSER_TILESET", "Tileset input is not valid JSON");
|
|
1127
|
+
}
|
|
1128
|
+
if (!isRecord(tileset) || !["1.0", "1.1"].includes(tileset.asset?.version || "") || !isRecord(tileset.root)) {
|
|
1129
|
+
throw new TileConversionError(
|
|
1130
|
+
"UNSUPPORTED_BROWSER_TILESET",
|
|
1131
|
+
"Browser tileset traversal supports explicit 3D Tiles 1.0 and 1.1 documents"
|
|
1132
|
+
);
|
|
1133
|
+
}
|
|
1134
|
+
return tileset;
|
|
1135
|
+
}
|
|
1136
|
+
function getDocumentKey(url) {
|
|
1137
|
+
const documentUrl = new URL(url);
|
|
1138
|
+
documentUrl.hash = "";
|
|
1139
|
+
return documentUrl.href;
|
|
1140
|
+
}
|
|
1141
|
+
function collectResources(root, maxInputResources, parentTransform = identityMatrix()) {
|
|
1142
|
+
const resources = [];
|
|
1143
|
+
const stack = [
|
|
1144
|
+
{ tile: root, path: [], transform: [...parentTransform] }
|
|
697
1145
|
];
|
|
698
1146
|
while (stack.length > 0) {
|
|
699
|
-
const { tile, path, transform:
|
|
1147
|
+
const { tile, path, transform: parentTransform2 } = stack.pop();
|
|
700
1148
|
if (!isRecord(tile)) {
|
|
701
1149
|
throw new TileConversionError("INVALID_BROWSER_TILESET", "Tile entries must be objects");
|
|
702
1150
|
}
|
|
@@ -712,7 +1160,19 @@ function collectResources(root, maxInputResources) {
|
|
|
712
1160
|
if (tile.children !== void 0 && !Array.isArray(tile.children)) {
|
|
713
1161
|
throw new TileConversionError("INVALID_BROWSER_TILESET", "Tile children must be an array");
|
|
714
1162
|
}
|
|
715
|
-
|
|
1163
|
+
if (tile.content !== void 0 && tile.contents !== void 0) {
|
|
1164
|
+
throw new TileConversionError(
|
|
1165
|
+
"INVALID_BROWSER_TILESET",
|
|
1166
|
+
"Tile content and contents are mutually exclusive"
|
|
1167
|
+
);
|
|
1168
|
+
}
|
|
1169
|
+
const transform = multiplyMatrices(parentTransform2, normalizeMatrix(tile.transform));
|
|
1170
|
+
if (transform.some((value) => !Number.isFinite(value))) {
|
|
1171
|
+
throw new TileConversionError(
|
|
1172
|
+
"INVALID_BROWSER_TILESET",
|
|
1173
|
+
"Composed tile transforms must remain finite"
|
|
1174
|
+
);
|
|
1175
|
+
}
|
|
716
1176
|
const contents = tile.contents ?? (tile.content ? [tile.content] : []);
|
|
717
1177
|
for (let contentIndex = 0; contentIndex < contents.length; contentIndex++) {
|
|
718
1178
|
const content = contents[contentIndex];
|
|
@@ -761,15 +1221,15 @@ async function readResponseBytes2(response, maximumBytes, signal) {
|
|
|
761
1221
|
"Browser inputs must provide a readable response stream"
|
|
762
1222
|
);
|
|
763
1223
|
}
|
|
764
|
-
const data = new Uint8Array(maximumBytes);
|
|
765
1224
|
let byteLength = 0;
|
|
766
1225
|
try {
|
|
1226
|
+
const data = new Uint8Array(maximumBytes);
|
|
767
1227
|
while (true) {
|
|
768
1228
|
throwIfAborted4(signal);
|
|
769
1229
|
const { done, value } = await reader.read();
|
|
1230
|
+
throwIfAborted4(signal);
|
|
770
1231
|
if (done) break;
|
|
771
1232
|
if (value.byteLength > maximumBytes - byteLength) {
|
|
772
|
-
await reader.cancel();
|
|
773
1233
|
throw new TileConversionError(
|
|
774
1234
|
"INPUT_RESOURCE_TOO_LARGE",
|
|
775
1235
|
`Browser tileset input exceeds the configured ${maximumBytes} byte remaining limit`
|
|
@@ -778,10 +1238,14 @@ async function readResponseBytes2(response, maximumBytes, signal) {
|
|
|
778
1238
|
data.set(value, byteLength);
|
|
779
1239
|
byteLength += value.byteLength;
|
|
780
1240
|
}
|
|
1241
|
+
return data.slice(0, byteLength);
|
|
1242
|
+
} catch (error) {
|
|
1243
|
+
await reader.cancel().catch(() => {
|
|
1244
|
+
});
|
|
1245
|
+
throw error;
|
|
781
1246
|
} finally {
|
|
782
1247
|
reader.releaseLock();
|
|
783
1248
|
}
|
|
784
|
-
return data.subarray(0, byteLength);
|
|
785
1249
|
}
|
|
786
1250
|
function isJsonContent(data, contentType) {
|
|
787
1251
|
if (contentType?.includes("json")) return true;
|
|
@@ -840,6 +1304,7 @@ export {
|
|
|
840
1304
|
createTiles3DConversionSpatialContext,
|
|
841
1305
|
createTilesetConversionSource,
|
|
842
1306
|
inspectTileset,
|
|
1307
|
+
selectBrowserTilesetContent,
|
|
843
1308
|
traversePointCloudSource,
|
|
844
1309
|
validateTileset
|
|
845
1310
|
};
|