@loaders.gl/tile-converter 5.0.0-alpha.11 → 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 +178 -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 +1749 -2152
- package/dist/index.js +1748 -2162
- package/dist/v5/adapters.cjs +1550 -128
- package/dist/v5/adapters.d.ts +16 -0
- package/dist/v5/adapters.js +1558 -117
- 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 +14 -0
- package/dist/v5/browser.js +26 -0
- 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 +471 -60
- package/dist/v5/core.d.ts +2 -0
- package/dist/v5/core.js +471 -60
- package/dist/v5/i3s-mesh-conversion.d.ts +4 -0
- package/dist/v5/i3s-selection-source.d.ts +70 -0
- package/dist/v5/index.cjs +10 -0
- package/dist/v5/index.d.ts +8 -0
- package/dist/v5/index.js +14 -0
- package/dist/v5/mesh-conversion.d.ts +18 -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.d.ts +5 -2
- package/dist/v5/mesh-tileset-sink.d.ts +5 -0
- 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-tileset.d.ts +44 -0
- package/dist/v5/resource-manifest.d.ts +10 -1
- package/dist/v5/selected-mesh-source.d.ts +53 -0
- 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 +48 -0
- package/src/v5/archive-source.ts +235 -0
- package/src/v5/browser-tileset-source.ts +418 -48
- package/src/v5/browser.ts +45 -0
- 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 +40 -2
- package/src/v5/i3s-mesh-sink.ts +1 -1
- package/src/v5/i3s-mesh-source.ts +1 -0
- package/src/v5/i3s-selection-source.ts +298 -0
- package/src/v5/index.ts +26 -0
- package/src/v5/mesh-conversion.ts +20 -0
- package/src/v5/mesh-hierarchy.ts +648 -0
- package/src/v5/mesh-source-features.ts +39 -7
- package/src/v5/mesh-source-schema.ts +134 -0
- package/src/v5/mesh-source.ts +16 -4
- package/src/v5/mesh-tileset-sink.ts +1 -1
- 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-file.ts +306 -0
- package/src/v5/point-cloud-potree.ts +130 -0
- package/src/v5/point-cloud-tileset.ts +137 -0
- 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 +88 -17
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) {
|
|
@@ -291,8 +338,8 @@ function createTiles3DConversionSpatialContext(discovered, options = {}) {
|
|
|
291
338
|
const outputSpatialReference = transformer ? markTilesetSpatialReferenceTransformed(spatialReference) : spatialReference;
|
|
292
339
|
return {
|
|
293
340
|
spatialReference: outputSpatialReference,
|
|
294
|
-
transformPositions: (positions) => transformer ? transformer.transformPositions(positions) :
|
|
295
|
-
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),
|
|
296
343
|
transformBoundingVolume: (volume, sourceTransform) => transformer ? transformer.transformBoundingVolume(volume, sourceTransform) : cloneBoundingVolume(volume)
|
|
297
344
|
};
|
|
298
345
|
}
|
|
@@ -314,16 +361,9 @@ function createI3SConversionSpatialContext(discovered, options = {}) {
|
|
|
314
361
|
const outputSpatialReference = transformer ? transformer.spatialReference : spatialReference;
|
|
315
362
|
let nativeNormalTransformer;
|
|
316
363
|
const transformNativeNormals = (normals, positions, normalReferenceFrame) => {
|
|
364
|
+
validateNormalReferenceFrame(normalReferenceFrame);
|
|
365
|
+
const targetNormals = copyNativeNormals(normals, positions);
|
|
317
366
|
if (!spatialReference.sourceCrs || ["local", "cartesian"].includes(spatialReference.coordinateFrame)) {
|
|
318
|
-
const targetNormals = Float32Array.from(normals);
|
|
319
|
-
for (let index = 0; index < normals.length; index += 3) {
|
|
320
|
-
const length = Math.hypot(normals[index], normals[index + 1], normals[index + 2]);
|
|
321
|
-
if (length) {
|
|
322
|
-
for (let axis = 0; axis < 3; axis++) {
|
|
323
|
-
targetNormals[index + axis] = normals[index + axis] / length;
|
|
324
|
-
}
|
|
325
|
-
}
|
|
326
|
-
}
|
|
327
367
|
return targetNormals;
|
|
328
368
|
}
|
|
329
369
|
nativeNormalTransformer ||= createSpatialTransformer(
|
|
@@ -337,7 +377,7 @@ function createI3SConversionSpatialContext(discovered, options = {}) {
|
|
|
337
377
|
options
|
|
338
378
|
)
|
|
339
379
|
);
|
|
340
|
-
return nativeNormalTransformer.transformNormals(
|
|
380
|
+
return nativeNormalTransformer.transformNormals(targetNormals, positions, normalReferenceFrame);
|
|
341
381
|
};
|
|
342
382
|
return {
|
|
343
383
|
spatialReference: outputSpatialReference,
|
|
@@ -358,7 +398,7 @@ function createI3SConversionSpatialContext(discovered, options = {}) {
|
|
|
358
398
|
};
|
|
359
399
|
},
|
|
360
400
|
transformPositionsAsync: (positions, sourceOrigin) => transformer ? transformer.transformPositionsAsync(positions, sourceOrigin) : Promise.resolve(createNativeI3SPositions(positions, sourceOrigin, spatialReference)),
|
|
361
|
-
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),
|
|
362
402
|
transformBoundingVolume: (bounds) => transformer ? transformer.transformBoundingVolume(bounds) : cloneI3SBounds(bounds),
|
|
363
403
|
transformBoundingVolumeAsync: (bounds) => transformer ? transformer.transformBoundingVolumeAsync(bounds) : Promise.resolve(cloneI3SBounds(bounds))
|
|
364
404
|
};
|
|
@@ -442,21 +482,30 @@ function cloneI3SBounds(bounds) {
|
|
|
442
482
|
};
|
|
443
483
|
}
|
|
444
484
|
function createNativeI3SPositions(sourcePositions, sourceOrigin, spatialReference) {
|
|
445
|
-
|
|
446
|
-
|
|
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
|
+
);
|
|
447
491
|
}
|
|
448
492
|
const origin = [
|
|
449
493
|
Number(sourceOrigin[0]),
|
|
450
494
|
Number(sourceOrigin[1]),
|
|
451
495
|
Number(sourceOrigin[2])
|
|
452
496
|
];
|
|
453
|
-
const sourcePositions64 = Float64Array.from(sourcePositions);
|
|
454
497
|
const positions = new Float32Array(sourcePositions.length);
|
|
455
498
|
for (let index = 0; index < sourcePositions.length; index += 3) {
|
|
456
499
|
positions[index] = Number(sourcePositions[index]) - origin[0];
|
|
457
500
|
positions[index + 1] = Number(sourcePositions[index + 1]) - origin[1];
|
|
458
501
|
positions[index + 2] = Number(sourcePositions[index + 2]) - origin[2];
|
|
459
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
|
+
}
|
|
460
509
|
const isGeographic = spatialReference.coordinateFrame === "geographic";
|
|
461
510
|
const modelMatrix = isGeographic ? new Matrix4() : new Matrix4().translate(origin);
|
|
462
511
|
return {
|
|
@@ -468,6 +517,61 @@ function createNativeI3SPositions(sourcePositions, sourceOrigin, spatialReferenc
|
|
|
468
517
|
modelMatrix
|
|
469
518
|
};
|
|
470
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
|
+
}
|
|
471
575
|
|
|
472
576
|
// src/v5/tileset-source.ts
|
|
473
577
|
import { traverseTilesetContents } from "@loaders.gl/tiles";
|
|
@@ -492,10 +596,17 @@ function createTilesetConversionSource(tileset, options = {}) {
|
|
|
492
596
|
// src/v5/resource-manifest.ts
|
|
493
597
|
var TILE_RESOURCE_MANIFEST_VERSION = 1;
|
|
494
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
|
+
}
|
|
495
606
|
const priorManifest = await options.manifestStore.load();
|
|
496
607
|
const resources = /* @__PURE__ */ new Map();
|
|
497
608
|
if (priorManifest) {
|
|
498
|
-
validateManifest(priorManifest);
|
|
609
|
+
validateManifest(priorManifest, maxResources);
|
|
499
610
|
for (const entry of priorManifest.resources) {
|
|
500
611
|
resources.set(entry.resourceId, entry);
|
|
501
612
|
}
|
|
@@ -521,6 +632,7 @@ async function createManifestBackedTileConversionSink(options) {
|
|
|
521
632
|
if (writeFailed) {
|
|
522
633
|
throw writeFailure;
|
|
523
634
|
}
|
|
635
|
+
signal?.throwIfAborted();
|
|
524
636
|
const entry = createManifestEntry(options, resource);
|
|
525
637
|
const priorEntry = resources.get(entry.resourceId);
|
|
526
638
|
if (priorEntry) {
|
|
@@ -530,7 +642,14 @@ async function createManifestBackedTileConversionSink(options) {
|
|
|
530
642
|
`Resource "${entry.resourceId}" changed since the saved manifest`
|
|
531
643
|
);
|
|
532
644
|
}
|
|
533
|
-
|
|
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
|
+
);
|
|
534
653
|
}
|
|
535
654
|
await options.sink.write(resource, signal);
|
|
536
655
|
resources.set(entry.resourceId, entry);
|
|
@@ -579,11 +698,17 @@ function createManifestEntry(options, resource) {
|
|
|
579
698
|
}
|
|
580
699
|
return { resourceId, byteLength, fingerprint };
|
|
581
700
|
}
|
|
582
|
-
function validateManifest(manifest) {
|
|
701
|
+
function validateManifest(manifest, maxResources) {
|
|
583
702
|
if (typeof manifest !== "object" || manifest === null || manifest.version !== TILE_RESOURCE_MANIFEST_VERSION || typeof manifest.complete !== "boolean" || !Array.isArray(manifest.resources)) {
|
|
584
703
|
throw new TileConversionError(
|
|
585
704
|
"UNSUPPORTED_RESOURCE_MANIFEST",
|
|
586
|
-
`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"
|
|
587
712
|
);
|
|
588
713
|
}
|
|
589
714
|
const resourceIds = /* @__PURE__ */ new Set();
|
|
@@ -675,50 +800,67 @@ function measureBlobPart(part) {
|
|
|
675
800
|
|
|
676
801
|
// src/v5/browser-tileset-source.ts
|
|
677
802
|
function createBrowserTilesetConversionSource(options) {
|
|
678
|
-
|
|
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) {
|
|
679
805
|
throw new TileConversionError(
|
|
680
806
|
"INVALID_BROWSER_SOURCE_OPTIONS",
|
|
681
|
-
"Browser tileset sources require positive byte and
|
|
807
|
+
"Browser tileset sources require positive byte, declaration and document limits"
|
|
682
808
|
);
|
|
683
809
|
}
|
|
684
810
|
const fetcher = options.fetcher ?? fetch;
|
|
685
811
|
return {
|
|
686
812
|
async inspect(signal) {
|
|
687
813
|
throwIfAborted4(signal);
|
|
688
|
-
const
|
|
689
|
-
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;
|
|
690
817
|
if (!response.ok) {
|
|
818
|
+
await response.body?.cancel();
|
|
691
819
|
throw new TileConversionError(
|
|
692
820
|
"BROWSER_SOURCE_FETCH_FAILED",
|
|
693
821
|
`Could not fetch browser tileset (${response.status} ${response.statusText})`
|
|
694
822
|
);
|
|
695
823
|
}
|
|
696
824
|
const data = await readResponseBytes2(response, options.maxInputBytes, signal);
|
|
697
|
-
|
|
698
|
-
try {
|
|
699
|
-
tileset = JSON.parse(new TextDecoder().decode(data));
|
|
700
|
-
} catch {
|
|
701
|
-
throw new TileConversionError("INVALID_BROWSER_TILESET", "Tileset input is not valid JSON");
|
|
702
|
-
}
|
|
703
|
-
if (!isRecord(tileset) || !["1.0", "1.1"].includes(tileset.asset?.version || "") || !isRecord(tileset.root)) {
|
|
704
|
-
throw new TileConversionError(
|
|
705
|
-
"UNSUPPORTED_BROWSER_TILESET",
|
|
706
|
-
"Browser tileset traversal supports explicit 3D Tiles 1.0 and 1.1 documents"
|
|
707
|
-
);
|
|
708
|
-
}
|
|
825
|
+
const tileset = parseTilesetDocument(data);
|
|
709
826
|
const resources = collectResources(tileset.root, options.maxInputResources);
|
|
710
|
-
|
|
827
|
+
const inspection = { rootUrl, tileset, resources, tilesetBytes: data.byteLength };
|
|
828
|
+
return options.inspectNestedTilesets ? expandInspection(inspection, options, maxTilesetDocuments, fetcher, signal) : inspection;
|
|
711
829
|
},
|
|
712
830
|
async *read(inspection, signal) {
|
|
713
831
|
let inputBytes = inspection.tilesetBytes;
|
|
714
|
-
|
|
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) {
|
|
715
844
|
throwIfAborted4(signal);
|
|
716
|
-
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
|
+
}
|
|
717
858
|
const response = await fetcher(resourceUrl, { signal });
|
|
718
859
|
if (!response.ok) {
|
|
860
|
+
await response.body?.cancel();
|
|
719
861
|
throw new TileConversionError(
|
|
720
862
|
"BROWSER_SOURCE_FETCH_FAILED",
|
|
721
|
-
|
|
863
|
+
"Could not fetch browser tile content (" + response.status + " " + response.statusText + ")",
|
|
722
864
|
[
|
|
723
865
|
{
|
|
724
866
|
code: "BROWSER_SOURCE_FETCH_FAILED",
|
|
@@ -729,28 +871,280 @@ function createBrowserTilesetConversionSource(options) {
|
|
|
729
871
|
]
|
|
730
872
|
);
|
|
731
873
|
}
|
|
732
|
-
const
|
|
733
|
-
|
|
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);
|
|
734
883
|
inputBytes += data.byteLength;
|
|
735
884
|
const contentType = response.headers.get("content-type") ?? void 0;
|
|
736
885
|
if (isJsonContent(data, contentType)) {
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
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;
|
|
741
935
|
}
|
|
742
|
-
yield { ...descriptor, data, ...contentType ? { contentType } : {} };
|
|
936
|
+
yield { ...descriptor, data, contentUrl: documentUrl, ...contentType ? { contentType } : {} };
|
|
743
937
|
}
|
|
744
938
|
}
|
|
745
939
|
};
|
|
746
940
|
}
|
|
747
|
-
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
|
+
];
|
|
748
1024
|
const resources = [];
|
|
1025
|
+
let bytes = inspection.tilesetBytes;
|
|
1026
|
+
let declarations = inspection.resources.length;
|
|
749
1027
|
const stack = [
|
|
750
|
-
{
|
|
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] }
|
|
751
1145
|
];
|
|
752
1146
|
while (stack.length > 0) {
|
|
753
|
-
const { tile, path, transform:
|
|
1147
|
+
const { tile, path, transform: parentTransform2 } = stack.pop();
|
|
754
1148
|
if (!isRecord(tile)) {
|
|
755
1149
|
throw new TileConversionError("INVALID_BROWSER_TILESET", "Tile entries must be objects");
|
|
756
1150
|
}
|
|
@@ -766,7 +1160,19 @@ function collectResources(root, maxInputResources) {
|
|
|
766
1160
|
if (tile.children !== void 0 && !Array.isArray(tile.children)) {
|
|
767
1161
|
throw new TileConversionError("INVALID_BROWSER_TILESET", "Tile children must be an array");
|
|
768
1162
|
}
|
|
769
|
-
|
|
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
|
+
}
|
|
770
1176
|
const contents = tile.contents ?? (tile.content ? [tile.content] : []);
|
|
771
1177
|
for (let contentIndex = 0; contentIndex < contents.length; contentIndex++) {
|
|
772
1178
|
const content = contents[contentIndex];
|
|
@@ -815,15 +1221,15 @@ async function readResponseBytes2(response, maximumBytes, signal) {
|
|
|
815
1221
|
"Browser inputs must provide a readable response stream"
|
|
816
1222
|
);
|
|
817
1223
|
}
|
|
818
|
-
const data = new Uint8Array(maximumBytes);
|
|
819
1224
|
let byteLength = 0;
|
|
820
1225
|
try {
|
|
1226
|
+
const data = new Uint8Array(maximumBytes);
|
|
821
1227
|
while (true) {
|
|
822
1228
|
throwIfAborted4(signal);
|
|
823
1229
|
const { done, value } = await reader.read();
|
|
1230
|
+
throwIfAborted4(signal);
|
|
824
1231
|
if (done) break;
|
|
825
1232
|
if (value.byteLength > maximumBytes - byteLength) {
|
|
826
|
-
await reader.cancel();
|
|
827
1233
|
throw new TileConversionError(
|
|
828
1234
|
"INPUT_RESOURCE_TOO_LARGE",
|
|
829
1235
|
`Browser tileset input exceeds the configured ${maximumBytes} byte remaining limit`
|
|
@@ -832,10 +1238,14 @@ async function readResponseBytes2(response, maximumBytes, signal) {
|
|
|
832
1238
|
data.set(value, byteLength);
|
|
833
1239
|
byteLength += value.byteLength;
|
|
834
1240
|
}
|
|
1241
|
+
return data.slice(0, byteLength);
|
|
1242
|
+
} catch (error) {
|
|
1243
|
+
await reader.cancel().catch(() => {
|
|
1244
|
+
});
|
|
1245
|
+
throw error;
|
|
835
1246
|
} finally {
|
|
836
1247
|
reader.releaseLock();
|
|
837
1248
|
}
|
|
838
|
-
return data.subarray(0, byteLength);
|
|
839
1249
|
}
|
|
840
1250
|
function isJsonContent(data, contentType) {
|
|
841
1251
|
if (contentType?.includes("json")) return true;
|
|
@@ -894,6 +1304,7 @@ export {
|
|
|
894
1304
|
createTiles3DConversionSpatialContext,
|
|
895
1305
|
createTilesetConversionSource,
|
|
896
1306
|
inspectTileset,
|
|
1307
|
+
selectBrowserTilesetContent,
|
|
897
1308
|
traversePointCloudSource,
|
|
898
1309
|
validateTileset
|
|
899
1310
|
};
|