@loaders.gl/tile-converter 5.0.0-alpha.10 → 5.0.0-alpha.11
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 +33 -0
- package/dist/converter.min.cjs +103 -103
- package/dist/i3s-server/bin/i3s-server.min.cjs +1 -1
- package/dist/index.cjs +651 -547
- package/dist/index.js +641 -537
- package/dist/v5/adapters.cjs +1756 -200
- package/dist/v5/adapters.d.ts +13 -2
- package/dist/v5/adapters.js +1755 -190
- package/dist/v5/browser.cjs +12 -0
- package/dist/v5/browser.d.ts +12 -2
- package/dist/v5/browser.js +19 -1
- package/dist/v5/core.cjs +54 -0
- package/dist/v5/core.js +54 -0
- package/dist/v5/i3s-mesh-conversion.d.ts +10 -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/index.cjs +10 -0
- package/dist/v5/index.d.ts +10 -2
- package/dist/v5/index.js +17 -1
- package/dist/v5/mesh-conversion.d.ts +11 -2
- package/dist/v5/mesh-features.d.ts +9 -0
- package/dist/v5/mesh-source-texture.d.ts +2 -0
- package/dist/v5/mesh-source.d.ts +8 -1
- package/dist/v5/mesh.d.ts +6 -1
- 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.d.ts +26 -1
- package/dist/v5/resource-fetcher.d.ts +18 -0
- package/dist/v5/spatial-conversion.d.ts +3 -1
- package/package.json +23 -23
- package/src/v5/adapters.ts +21 -2
- package/src/v5/browser.ts +36 -2
- package/src/v5/i3s-mesh-conversion.ts +58 -18
- 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 +227 -0
- package/src/v5/index.ts +34 -2
- package/src/v5/mesh-conversion.ts +41 -11
- package/src/v5/mesh-draco.ts +47 -3
- package/src/v5/mesh-features.ts +319 -0
- package/src/v5/mesh-source-features.ts +37 -6
- package/src/v5/mesh-source-texture.ts +47 -0
- package/src/v5/mesh-source.ts +83 -21
- package/src/v5/mesh.ts +16 -7
- package/src/v5/point-cloud-conversion.ts +9 -1
- 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.ts +159 -32
- package/src/v5/resource-fetcher.ts +110 -0
- package/src/v5/spatial-conversion.ts +84 -0
package/dist/v5/adapters.cjs
CHANGED
|
@@ -23,18 +23,24 @@ __export(adapters_exports, {
|
|
|
23
23
|
convertFeatureAttributesToArrowBatches: () => convertFeatureAttributesToArrowBatches,
|
|
24
24
|
convertPointCloudSource: () => convertPointCloudSource,
|
|
25
25
|
createI3SMeshConversionCodec: () => createI3SMeshConversionCodec,
|
|
26
|
+
createI3SMeshSink: () => createI3SMeshSink,
|
|
27
|
+
createI3SMeshTilesetConversionSource: () => createI3SMeshTilesetConversionSource,
|
|
26
28
|
createMeshConversionCodec: () => createMeshConversionCodec,
|
|
27
29
|
createMeshTilesetConversionSource: () => createMeshTilesetConversionSource,
|
|
28
30
|
createMeshTilesetSink: () => createMeshTilesetSink,
|
|
31
|
+
createPointCloudTilesetSink: () => createPointCloudTilesetSink,
|
|
29
32
|
createSingleMeshI3SSink: () => createSingleMeshI3SSink,
|
|
30
33
|
createSingleMeshTilesetArchive: () => createSingleMeshTilesetArchive,
|
|
31
34
|
createSingleMeshTilesetSink: () => createSingleMeshTilesetSink,
|
|
32
35
|
createTileConversionArchive: () => createTileConversionArchive,
|
|
36
|
+
createTileConversionResourceFetcher: () => createTileConversionResourceFetcher,
|
|
33
37
|
encodeMeshTile: () => encodeMeshTile,
|
|
34
38
|
encodePointCloudSource: () => encodePointCloudSource,
|
|
35
39
|
encodePointCloudSourceTile: () => encodePointCloudSourceTile,
|
|
36
40
|
encodePointCloudTile: () => encodePointCloudTile,
|
|
37
|
-
|
|
41
|
+
encodePointCloudTileWithMetadata: () => encodePointCloudTileWithMetadata,
|
|
42
|
+
encodeTileConversionArchiveInBatches: () => encodeTileConversionArchiveInBatches,
|
|
43
|
+
transformPointCloudSourceTile: () => transformPointCloudSourceTile
|
|
38
44
|
});
|
|
39
45
|
module.exports = __toCommonJS(adapters_exports);
|
|
40
46
|
|
|
@@ -365,29 +371,22 @@ var import_d_tiles = require("@loaders.gl/3d-tiles");
|
|
|
365
371
|
var import_schema_utils2 = require("@loaders.gl/schema-utils");
|
|
366
372
|
var import_core2 = require("@loaders.gl/tile-converter/v5/core");
|
|
367
373
|
function encodePointCloudTile(pointBatch, options = {}) {
|
|
374
|
+
return encodePointCloudTileWithMetadata(pointBatch, options).pnts;
|
|
375
|
+
}
|
|
376
|
+
function encodePointCloudTileWithMetadata(pointBatch, options = {}) {
|
|
368
377
|
const mesh = "attributes" in pointBatch ? pointBatch : (0, import_schema_utils2.convertTableToMesh)(pointBatch);
|
|
369
378
|
validatePointAttributes(mesh.attributes);
|
|
370
379
|
const positionAttribute = mesh.attributes.POSITION;
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
"Point cloud batches must contain a three-component POSITION attribute"
|
|
375
|
-
);
|
|
376
|
-
}
|
|
377
|
-
const positions = getPositionValues(positionAttribute, options.rtcCenter);
|
|
380
|
+
validatePointCloudPosition(positionAttribute);
|
|
381
|
+
const placement = encodePointPositions(positionAttribute, options);
|
|
382
|
+
const { positions } = placement;
|
|
378
383
|
const pointCount = positions.length / 3;
|
|
379
|
-
if (pointCount === 0 || !Number.isInteger(pointCount)) {
|
|
380
|
-
throw new import_core2.TileConversionError(
|
|
381
|
-
"POINT_CLOUD_POSITION_COUNT_INVALID",
|
|
382
|
-
"Point cloud POSITION must contain one or more xyz triples"
|
|
383
|
-
);
|
|
384
|
-
}
|
|
385
384
|
const colors = getColorAttribute(mesh.attributes.COLOR_0 || mesh.attributes.COLOR, pointCount);
|
|
386
385
|
const constantRGBA = colors ? null : getConstantRgba(options.constantRGBA);
|
|
387
386
|
const normals = getNormalAttribute(mesh.attributes.NORMAL, pointCount);
|
|
388
387
|
const batchIds = getBatchIds(mesh.attributes.BATCH_ID, pointCount);
|
|
389
388
|
const batchTableJson = validateBatchTable(options.batchTableJson, batchIds);
|
|
390
|
-
|
|
389
|
+
const pnts = import_d_tiles.Tile3DWriter.encodeSync(
|
|
391
390
|
{
|
|
392
391
|
type: import_d_tiles.TILE3D_TYPE.POINT_CLOUD,
|
|
393
392
|
featureTableJson: options.rtcCenter || constantRGBA ? {
|
|
@@ -399,6 +398,13 @@ function encodePointCloudTile(pointBatch, options = {}) {
|
|
|
399
398
|
},
|
|
400
399
|
{}
|
|
401
400
|
);
|
|
401
|
+
return {
|
|
402
|
+
pnts,
|
|
403
|
+
pointCount,
|
|
404
|
+
rtcCenter: placement.rtcCenter,
|
|
405
|
+
localBoundingBox: placement.localBoundingBox,
|
|
406
|
+
maximumPositionError: placement.maximumPositionError
|
|
407
|
+
};
|
|
402
408
|
}
|
|
403
409
|
function validatePointAttributes(attributes) {
|
|
404
410
|
const supportedAttributes = /* @__PURE__ */ new Set([
|
|
@@ -429,23 +435,88 @@ function getConstantRgba(constantRGBA) {
|
|
|
429
435
|
}
|
|
430
436
|
return [constantRGBA[0], constantRGBA[1], constantRGBA[2], constantRGBA[3]];
|
|
431
437
|
}
|
|
432
|
-
function
|
|
433
|
-
if (
|
|
438
|
+
function validatePointCloudPosition(attribute) {
|
|
439
|
+
if (!attribute || attribute.size !== 3 || attribute.byteOffset || attribute.byteStride || attribute.normalized || attribute.componentType) {
|
|
440
|
+
throw new import_core2.TileConversionError(
|
|
441
|
+
"POINT_CLOUD_POSITION_REQUIRED",
|
|
442
|
+
"Point cloud batches must contain packed, non-normalized three-component POSITION values"
|
|
443
|
+
);
|
|
444
|
+
}
|
|
445
|
+
if (!attribute.value.length || attribute.value.length % 3) {
|
|
446
|
+
throw new import_core2.TileConversionError(
|
|
447
|
+
"POINT_CLOUD_POSITION_COUNT_INVALID",
|
|
448
|
+
"Point cloud POSITION must contain one or more xyz triples"
|
|
449
|
+
);
|
|
450
|
+
}
|
|
451
|
+
const transform = attribute.transform;
|
|
452
|
+
if (transform && (transform.type !== "quantization" || !Number.isInteger(transform.bits) || transform.bits < 1 || transform.bits > 32 || transform.origin.length !== 3 || transform.origin.some((value) => !Number.isFinite(value)) || !Number.isFinite(transform.range) || transform.range < 0)) {
|
|
453
|
+
throw new import_core2.TileConversionError(
|
|
454
|
+
"POINT_CLOUD_POSITION_TRANSFORM_INVALID",
|
|
455
|
+
"Position quantization requires finite xyz origin, nonnegative range and 1\u201332 bits"
|
|
456
|
+
);
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
function decodePointCloudPositions(attribute) {
|
|
460
|
+
validatePointCloudPosition(attribute);
|
|
461
|
+
const transform = attribute.transform;
|
|
462
|
+
return Float64Array.from(
|
|
463
|
+
attribute.value,
|
|
464
|
+
(value, index) => transform ? transform.origin[index % 3] + value / (2 ** transform.bits - 1) * transform.range : value
|
|
465
|
+
);
|
|
466
|
+
}
|
|
467
|
+
function encodePointPositions(attribute, options) {
|
|
468
|
+
const { rtcCenter = [0, 0, 0], maxPositionError = Infinity } = options;
|
|
469
|
+
if (rtcCenter.length !== 3 || rtcCenter.some((value) => !Number.isFinite(value))) {
|
|
434
470
|
throw new import_core2.TileConversionError(
|
|
435
471
|
"POINT_CLOUD_RTC_CENTER_INVALID",
|
|
436
472
|
"Point cloud RTC_CENTER must contain three finite values"
|
|
437
473
|
);
|
|
438
474
|
}
|
|
475
|
+
if (options.maxPositionError !== void 0 && (!Number.isFinite(maxPositionError) || maxPositionError < 0)) {
|
|
476
|
+
throw new import_core2.TileConversionError(
|
|
477
|
+
"POINT_CLOUD_PRECISION_INVALID",
|
|
478
|
+
"maxPositionError must be finite and nonnegative"
|
|
479
|
+
);
|
|
480
|
+
}
|
|
439
481
|
const values = attribute.value;
|
|
440
|
-
const
|
|
482
|
+
const positions = new Float32Array(values.length);
|
|
483
|
+
const minimum = [Infinity, Infinity, Infinity];
|
|
484
|
+
const maximum = [-Infinity, -Infinity, -Infinity];
|
|
485
|
+
let maximumPositionError = 0;
|
|
441
486
|
const transform = attribute.transform;
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
487
|
+
const errors = [0, 0, 0];
|
|
488
|
+
for (let point = 0; point < values.length; point += 3) {
|
|
489
|
+
for (let axis = 0; axis < 3; axis++) {
|
|
490
|
+
const value = values[point + axis];
|
|
491
|
+
const absoluteValue = transform ? transform.origin[axis] + value / (2 ** transform.bits - 1) * transform.range : value;
|
|
492
|
+
const relativeValue = absoluteValue - rtcCenter[axis];
|
|
493
|
+
positions[point + axis] = relativeValue;
|
|
494
|
+
const encodedValue = positions[point + axis];
|
|
495
|
+
const reconstructed = encodedValue + rtcCenter[axis];
|
|
496
|
+
if (!Number.isFinite(absoluteValue) || !Number.isFinite(relativeValue) || !Number.isFinite(encodedValue) || !Number.isFinite(reconstructed)) {
|
|
497
|
+
throw new import_core2.TileConversionError(
|
|
498
|
+
"POINT_CLOUD_POSITION_INVALID",
|
|
499
|
+
"Point positions and their float32 reconstruction must be finite"
|
|
500
|
+
);
|
|
501
|
+
}
|
|
502
|
+
errors[axis] = reconstructed - absoluteValue;
|
|
503
|
+
minimum[axis] = Math.min(minimum[axis], encodedValue);
|
|
504
|
+
maximum[axis] = Math.max(maximum[axis], encodedValue);
|
|
505
|
+
}
|
|
506
|
+
maximumPositionError = Math.max(maximumPositionError, Math.hypot(...errors));
|
|
507
|
+
}
|
|
508
|
+
if (!Number.isFinite(maximumPositionError) || maximumPositionError > maxPositionError) {
|
|
509
|
+
throw new import_core2.TileConversionError(
|
|
510
|
+
"POINT_CLOUD_PRECISION_EXCEEDED",
|
|
511
|
+
"Float32 position reconstruction exceeds maxPositionError; select a closer RTC center"
|
|
512
|
+
);
|
|
447
513
|
}
|
|
448
|
-
return
|
|
514
|
+
return {
|
|
515
|
+
positions,
|
|
516
|
+
rtcCenter: [...rtcCenter],
|
|
517
|
+
localBoundingBox: [minimum, maximum],
|
|
518
|
+
maximumPositionError
|
|
519
|
+
};
|
|
449
520
|
}
|
|
450
521
|
function getColorAttribute(attribute, pointCount) {
|
|
451
522
|
if (!attribute) {
|
|
@@ -558,10 +629,191 @@ function validateBatchTable(batchTableJson, batchIds) {
|
|
|
558
629
|
return normalizedBatchTable;
|
|
559
630
|
}
|
|
560
631
|
|
|
632
|
+
// src/v5/point-cloud-spatial.ts
|
|
633
|
+
var import_core3 = require("@math.gl/core");
|
|
634
|
+
var import_tiles = require("@loaders.gl/tiles");
|
|
635
|
+
var import_schema_utils3 = require("@loaders.gl/schema-utils");
|
|
636
|
+
var import_core4 = require("@loaders.gl/tile-converter/v5/core");
|
|
637
|
+
async function transformPointCloudSourceTile(sourceTile, options) {
|
|
638
|
+
options.signal?.throwIfAborted();
|
|
639
|
+
const { content } = sourceTile;
|
|
640
|
+
if (!content) return sourceTile;
|
|
641
|
+
const reference = options.spatialContext.spatialReference;
|
|
642
|
+
const contentReference = content.spatialReference;
|
|
643
|
+
const retainTransformedEcef = contentReference?.status === "transformed" && (contentReference.targetCrs || contentReference.sourceCrs) === "EPSG:4978" && reference.status === "native" && reference.sourceCrs === "EPSG:4978" && reference.verticalUnitScale === 1 && (reference.units?.every((unit) => unit === "meter") ?? true);
|
|
644
|
+
const sourceReference = retainTransformedEcef ? (0, import_tiles.createTilesetSpatialReference)({
|
|
645
|
+
sourceCrs: "EPSG:4978",
|
|
646
|
+
coordinateFrame: "geocentric",
|
|
647
|
+
axisOrder: "xyz",
|
|
648
|
+
units: reference.units,
|
|
649
|
+
coordinateEpoch: contentReference.coordinateEpoch,
|
|
650
|
+
heightReference: contentReference.targetHeightReference === "native" ? contentReference.heightReference : contentReference.targetHeightReference
|
|
651
|
+
}) : contentReference;
|
|
652
|
+
if (sourceReference && (sourceReference.status === "transformed" || sourceReference.status === "unresolved" || JSON.stringify(sourceReference.sourceCrs) !== JSON.stringify(reference.sourceCrs) || sourceReference.heightReference !== reference.heightReference || sourceReference.verticalUnitScale !== reference.verticalUnitScale || sourceReference.coordinateEpoch !== reference.coordinateEpoch || JSON.stringify(sourceReference.units) !== JSON.stringify(reference.units) || sourceReference.elevationMode !== reference.elevationMode || sourceReference.elevationOffset !== reference.elevationOffset || sourceReference.elevationUnitScale !== reference.elevationUnitScale)) {
|
|
653
|
+
throw new import_core4.TileConversionError(
|
|
654
|
+
"POINT_CLOUD_SPATIAL_REFERENCE_MISMATCH",
|
|
655
|
+
"Point content and context must agree on the input CRS, units, height reference and epoch"
|
|
656
|
+
);
|
|
657
|
+
}
|
|
658
|
+
const sourceFrame = reference.coordinateFrame === "unknown" && reference.sourceCrs ? (0, import_tiles.getSpatialCoordinateFrame)(reference.sourceCrs) : reference.coordinateFrame;
|
|
659
|
+
const geographic = sourceFrame === "geographic";
|
|
660
|
+
if (!["xy", "xyz", "unknown"].includes(reference.axisOrder) || (geographic ? !["lnglat", "lnglat-offsets"].includes(content.coordinateSystem) : content.coordinateSystem !== "cartesian" || !["projected", "geocentric", "cartesian", "local"].includes(sourceFrame))) {
|
|
661
|
+
throw new import_core4.TileConversionError(
|
|
662
|
+
"POINT_CLOUD_SOURCE_FRAME_UNSUPPORTED",
|
|
663
|
+
"Spatial preparation requires explicit Cartesian or longitude/latitude positions in source xyz order"
|
|
664
|
+
);
|
|
665
|
+
}
|
|
666
|
+
if (content.cartographicOrigin.length !== 3 || content.cartographicOrigin.some((value) => !Number.isFinite(value)) || content.coordinateSystem !== "lnglat-offsets" && content.cartographicOrigin.some((value) => value !== 0)) {
|
|
667
|
+
throw new import_core4.TileConversionError(
|
|
668
|
+
"POINT_CLOUD_SOURCE_ORIGIN_INVALID",
|
|
669
|
+
"Only longitude/latitude offsets use a nonzero cartographic origin"
|
|
670
|
+
);
|
|
671
|
+
}
|
|
672
|
+
if (content.modelMatrix && (content.modelMatrix.length !== 16 || Array.from(content.modelMatrix).some((value) => !Number.isFinite(value)))) {
|
|
673
|
+
throw new import_core4.TileConversionError(
|
|
674
|
+
"POINT_CLOUD_SOURCE_PLACEMENT_UNSUPPORTED",
|
|
675
|
+
"Point placement must contain sixteen finite matrix values"
|
|
676
|
+
);
|
|
677
|
+
}
|
|
678
|
+
const matrix = new import_core3.Matrix4(content.modelMatrix ? Array.from(content.modelMatrix) : void 0);
|
|
679
|
+
const determinant = matrix.determinant();
|
|
680
|
+
if (matrix[3] !== 0 || matrix[7] !== 0 || matrix[11] !== 0 || matrix[15] !== 1 || !Number.isFinite(determinant) || determinant === 0 || geographic && !matrix.equals(new import_core3.Matrix4())) {
|
|
681
|
+
throw new import_core4.TileConversionError(
|
|
682
|
+
"POINT_CLOUD_SOURCE_PLACEMENT_UNSUPPORTED",
|
|
683
|
+
"Cartesian placement must be finite, affine and invertible; geographic placement must be identity"
|
|
684
|
+
);
|
|
685
|
+
}
|
|
686
|
+
const mesh = (0, import_schema_utils3.convertTableToMesh)(content.data);
|
|
687
|
+
const positionAttribute = mesh.attributes.POSITION;
|
|
688
|
+
const declaredTransform = mesh.schema?.fields.find((field) => field.name === "POSITION")?.metadata?.["loaders.gl.transform"];
|
|
689
|
+
if (declaredTransform && !positionAttribute?.transform) {
|
|
690
|
+
throw new import_core4.TileConversionError(
|
|
691
|
+
"POINT_CLOUD_POSITION_TRANSFORM_INVALID",
|
|
692
|
+
"Arrow position transform metadata is unsupported or invalid"
|
|
693
|
+
);
|
|
694
|
+
}
|
|
695
|
+
const sourcePositions = decodePointCloudPositions(positionAttribute);
|
|
696
|
+
if (sourcePositions.some((value) => !Number.isFinite(value))) {
|
|
697
|
+
throw new import_core4.TileConversionError(
|
|
698
|
+
"POINT_CLOUD_POSITION_INVALID",
|
|
699
|
+
"Decoded point positions must be finite"
|
|
700
|
+
);
|
|
701
|
+
}
|
|
702
|
+
const translationOnly = Array.from(matrix).every(
|
|
703
|
+
(value, index) => index >= 12 && index <= 14 || value === (index % 5 === 0 ? 1 : 0)
|
|
704
|
+
);
|
|
705
|
+
for (let index = 0; index < sourcePositions.length; index += 3) {
|
|
706
|
+
if (geographic || translationOnly) {
|
|
707
|
+
for (let axis = 0; axis < 3; axis++) {
|
|
708
|
+
sourcePositions[index + axis] += geographic ? content.cartographicOrigin[axis] : matrix[12 + axis];
|
|
709
|
+
}
|
|
710
|
+
} else {
|
|
711
|
+
sourcePositions.set(
|
|
712
|
+
matrix.transformAsPoint(Array.from(sourcePositions.subarray(index, index + 3))),
|
|
713
|
+
index
|
|
714
|
+
);
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
const normalAttribute = mesh.attributes.NORMAL;
|
|
718
|
+
let normals;
|
|
719
|
+
if (normalAttribute) {
|
|
720
|
+
if (!options.normalReferenceFrame || normalAttribute.size !== 3 || normalAttribute.value.length !== sourcePositions.length || normalAttribute.transform || normalAttribute.normalized || normalAttribute.byteOffset || normalAttribute.byteStride || normalAttribute.componentType || !translationOnly) {
|
|
721
|
+
throw new import_core4.TileConversionError(
|
|
722
|
+
"POINT_CLOUD_NORMAL_FRAME_REQUIRED",
|
|
723
|
+
"Spatial point normals require a declared vector frame and packed xyz values"
|
|
724
|
+
);
|
|
725
|
+
}
|
|
726
|
+
normals = new Float32Array(normalAttribute.value.length);
|
|
727
|
+
for (let index = 0; index < normals.length; index += 3) {
|
|
728
|
+
const normal = Array.from(normalAttribute.value.slice(index, index + 3));
|
|
729
|
+
const length = Math.hypot(...normal);
|
|
730
|
+
if (!Number.isFinite(length) || length === 0) {
|
|
731
|
+
throw new import_core4.TileConversionError(
|
|
732
|
+
"POINT_CLOUD_NORMAL_INVALID",
|
|
733
|
+
"Spatial point normals must be finite and nonzero"
|
|
734
|
+
);
|
|
735
|
+
}
|
|
736
|
+
normals.set(
|
|
737
|
+
normal.map((value) => value / length),
|
|
738
|
+
index
|
|
739
|
+
);
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
const transformed = await options.spatialContext.transformGeometryAsync(
|
|
743
|
+
sourcePositions,
|
|
744
|
+
normals,
|
|
745
|
+
options.normalReferenceFrame
|
|
746
|
+
);
|
|
747
|
+
options.signal?.throwIfAborted();
|
|
748
|
+
if (transformed.positions.length !== sourcePositions.length) {
|
|
749
|
+
throw new import_core4.TileConversionError(
|
|
750
|
+
"POINT_CLOUD_POSITION_INVALID",
|
|
751
|
+
"Spatial conversion must return one xyz triple per point"
|
|
752
|
+
);
|
|
753
|
+
}
|
|
754
|
+
const minimum = [Infinity, Infinity, Infinity];
|
|
755
|
+
const maximum = [-Infinity, -Infinity, -Infinity];
|
|
756
|
+
for (let index = 0; index < transformed.positions.length; index++) {
|
|
757
|
+
const value = transformed.positions[index];
|
|
758
|
+
if (!Number.isFinite(value)) {
|
|
759
|
+
throw new import_core4.TileConversionError(
|
|
760
|
+
"POINT_CLOUD_POSITION_INVALID",
|
|
761
|
+
"Transformed point positions must be finite"
|
|
762
|
+
);
|
|
763
|
+
}
|
|
764
|
+
const axis = index % 3;
|
|
765
|
+
minimum[axis] = Math.min(minimum[axis], value);
|
|
766
|
+
maximum[axis] = Math.max(maximum[axis], value);
|
|
767
|
+
}
|
|
768
|
+
const center = minimum.map((value, axis) => value / 2 + maximum[axis] / 2);
|
|
769
|
+
const targetFrame = reference.status === "native" ? sourceFrame : (0, import_tiles.getSpatialCoordinateFrame)(reference.targetCrs || reference.sourceCrs);
|
|
770
|
+
const targetIsGeographic = targetFrame === "geographic";
|
|
771
|
+
const attributes = {
|
|
772
|
+
...mesh.attributes,
|
|
773
|
+
POSITION: { value: transformed.positions, size: 3 },
|
|
774
|
+
...transformed.normals ? { NORMAL: { value: transformed.normals, size: 3 } } : {}
|
|
775
|
+
};
|
|
776
|
+
return {
|
|
777
|
+
header: sourceTile.header,
|
|
778
|
+
content: {
|
|
779
|
+
...content,
|
|
780
|
+
data: (0, import_schema_utils3.makeMeshArrowTable)(attributes, {
|
|
781
|
+
topology: "point-list",
|
|
782
|
+
boundingBox: [minimum, maximum],
|
|
783
|
+
schema: mesh.schema ? {
|
|
784
|
+
...mesh.schema,
|
|
785
|
+
metadata: {
|
|
786
|
+
...mesh.schema.metadata,
|
|
787
|
+
topology: "point-list",
|
|
788
|
+
boundingBox: JSON.stringify([minimum, maximum])
|
|
789
|
+
},
|
|
790
|
+
fields: mesh.schema.fields.map(
|
|
791
|
+
(field) => field.name === "POSITION" || transformed.normals && field.name === "NORMAL" ? { ...field, metadata: {} } : field
|
|
792
|
+
)
|
|
793
|
+
} : void 0
|
|
794
|
+
}),
|
|
795
|
+
coordinateSystem: targetIsGeographic ? "lnglat" : "cartesian",
|
|
796
|
+
cartographicOrigin: [0, 0, 0],
|
|
797
|
+
modelMatrix: void 0,
|
|
798
|
+
spatialReference: reference,
|
|
799
|
+
spatialBoundingVolume: {
|
|
800
|
+
cartographicBounds: [minimum, maximum],
|
|
801
|
+
coordinateFrame: targetIsGeographic ? "geographic" : "cartesian",
|
|
802
|
+
center,
|
|
803
|
+
radius: Math.hypot(...maximum.map((value, axis) => value - center[axis]))
|
|
804
|
+
}
|
|
805
|
+
}
|
|
806
|
+
};
|
|
807
|
+
}
|
|
808
|
+
|
|
561
809
|
// src/v5/point-cloud-source-encoder.ts
|
|
562
|
-
var
|
|
810
|
+
var import_core5 = require("@loaders.gl/tile-converter/v5/core");
|
|
563
811
|
async function* encodePointCloudSource(source, options = {}) {
|
|
564
|
-
for await (const
|
|
812
|
+
for await (const inputTile of (0, import_core5.traversePointCloudSource)(source, options)) {
|
|
813
|
+
const sourceTile = options.spatialContext ? await transformPointCloudSourceTile(inputTile, {
|
|
814
|
+
...options,
|
|
815
|
+
spatialContext: options.spatialContext
|
|
816
|
+
}) : inputTile;
|
|
565
817
|
const encodingOptions = sourceTile.content ? options.getTileEncodingOptions?.(sourceTile) : void 0;
|
|
566
818
|
const encodedTile = encodePointCloudSourceTile(sourceTile, encodingOptions);
|
|
567
819
|
if (encodedTile) {
|
|
@@ -574,14 +826,20 @@ function encodePointCloudSourceTile(sourceTile, options = {}) {
|
|
|
574
826
|
if (!content) {
|
|
575
827
|
return null;
|
|
576
828
|
}
|
|
829
|
+
const encoded = encodePointCloudTileWithMetadata(content.data, {
|
|
830
|
+
...options,
|
|
831
|
+
constantRGBA: options.constantRGBA ?? content.constantRGBA
|
|
832
|
+
});
|
|
833
|
+
if (content.pointCount !== encoded.pointCount) {
|
|
834
|
+
throw new import_core5.TileConversionError(
|
|
835
|
+
"POINT_CLOUD_SOURCE_COUNT_INVALID",
|
|
836
|
+
"Source pointCount must match the encoded Arrow point rows"
|
|
837
|
+
);
|
|
838
|
+
}
|
|
577
839
|
return {
|
|
578
840
|
id: header.id,
|
|
579
841
|
header,
|
|
580
|
-
|
|
581
|
-
...options,
|
|
582
|
-
constantRGBA: options.constantRGBA ?? content.constantRGBA
|
|
583
|
-
}),
|
|
584
|
-
pointCount: content.pointCount,
|
|
842
|
+
...encoded,
|
|
585
843
|
coordinateSystem: content.coordinateSystem,
|
|
586
844
|
cartographicOrigin: [...content.cartographicOrigin],
|
|
587
845
|
modelMatrix: content.modelMatrix ? Array.from(content.modelMatrix) : void 0,
|
|
@@ -591,20 +849,25 @@ function encodePointCloudSourceTile(sourceTile, options = {}) {
|
|
|
591
849
|
}
|
|
592
850
|
|
|
593
851
|
// src/v5/point-cloud-conversion.ts
|
|
594
|
-
var
|
|
595
|
-
var
|
|
852
|
+
var import_core6 = require("@loaders.gl/tile-converter/v5/core");
|
|
853
|
+
var import_core7 = require("@loaders.gl/tile-converter/v5/core");
|
|
596
854
|
function convertPointCloudSource(source, options) {
|
|
597
|
-
return (0,
|
|
855
|
+
return (0, import_core6.convertTileset)({
|
|
598
856
|
source: {
|
|
599
857
|
async inspect() {
|
|
600
858
|
if (!source.isReady) {
|
|
601
859
|
await source.initialize();
|
|
602
860
|
}
|
|
603
861
|
},
|
|
604
|
-
read: (_inspection, signal) => (0,
|
|
862
|
+
read: (_inspection, signal) => (0, import_core7.traversePointCloudSource)(source, { maxDepth: options.maxDepth, signal })
|
|
605
863
|
},
|
|
606
864
|
codec: {
|
|
607
|
-
async *convert(
|
|
865
|
+
async *convert(inputTile, _inspection, signal) {
|
|
866
|
+
const sourceTile = options.spatialContext ? await transformPointCloudSourceTile(inputTile, {
|
|
867
|
+
...options,
|
|
868
|
+
spatialContext: options.spatialContext,
|
|
869
|
+
signal
|
|
870
|
+
}) : inputTile;
|
|
608
871
|
const encodingOptions = sourceTile.content ? options.getTileEncodingOptions?.(sourceTile) : void 0;
|
|
609
872
|
const encodedTile = encodePointCloudSourceTile(sourceTile, encodingOptions);
|
|
610
873
|
if (encodedTile) {
|
|
@@ -625,9 +888,257 @@ function convertPointCloudSource(source, options) {
|
|
|
625
888
|
// src/v5/mesh.ts
|
|
626
889
|
var import_gltf = require("@loaders.gl/gltf");
|
|
627
890
|
var import_images = require("@loaders.gl/images");
|
|
628
|
-
|
|
891
|
+
|
|
892
|
+
// src/v5/mesh-features.ts
|
|
893
|
+
var import_schema_utils4 = require("@loaders.gl/schema-utils");
|
|
894
|
+
var import_core8 = require("@loaders.gl/tile-converter/v5/core");
|
|
895
|
+
function prepareMeshFeatureGeometry(mesh, features) {
|
|
896
|
+
if (!features) return mesh;
|
|
897
|
+
const count = features.batches.reduce((total, batch) => total + batch.data.numRows, 0);
|
|
898
|
+
const indices = mesh.indices?.value;
|
|
899
|
+
const vertexCount = indices?.length ?? mesh.attributes.POSITION.value.length / 3;
|
|
900
|
+
const associations = features.triangleFeatureIndices;
|
|
901
|
+
if (!(associations instanceof Uint32Array) || associations.length !== vertexCount / 3 || associations.some((row) => row >= count) || new Set(associations).size !== count) {
|
|
902
|
+
throw new import_core8.TileConversionError(
|
|
903
|
+
"MESH_FEATURE_ASSOCIATION_INVALID",
|
|
904
|
+
"Every feature row must own a triangle and every triangle must reference a valid row"
|
|
905
|
+
);
|
|
906
|
+
}
|
|
907
|
+
if (count > 2 ** 24)
|
|
908
|
+
throw new import_core8.TileConversionError(
|
|
909
|
+
"MESH_FEATURE_COUNT_UNSUPPORTED",
|
|
910
|
+
"Feature row indices must be exactly representable in a glTF vertex attribute"
|
|
911
|
+
);
|
|
912
|
+
const attributes = Object.fromEntries(
|
|
913
|
+
Object.entries(mesh.attributes).map(([name, attribute]) => {
|
|
914
|
+
const values = attribute.value;
|
|
915
|
+
const Constructor = values.constructor;
|
|
916
|
+
const output = new Constructor(vertexCount * attribute.size);
|
|
917
|
+
for (let vertex = 0; vertex < vertexCount; vertex++) {
|
|
918
|
+
const sourceVertex = indices?.[vertex] ?? vertex;
|
|
919
|
+
for (let component = 0; component < attribute.size; component++)
|
|
920
|
+
output[vertex * attribute.size + component] = Number(
|
|
921
|
+
values[sourceVertex * attribute.size + component]
|
|
922
|
+
);
|
|
923
|
+
}
|
|
924
|
+
return [name, { ...attribute, value: output }];
|
|
925
|
+
})
|
|
926
|
+
);
|
|
927
|
+
const featureIndices = count <= 65536 ? new Uint16Array(vertexCount) : new Float32Array(vertexCount);
|
|
928
|
+
for (let vertex = 0; vertex < vertexCount; vertex++)
|
|
929
|
+
featureIndices[vertex] = associations[Math.floor(vertex / 3)];
|
|
930
|
+
attributes._FEATURE_ID_0 = { value: featureIndices, size: 1 };
|
|
931
|
+
return { ...mesh, indices: void 0, attributes };
|
|
932
|
+
}
|
|
933
|
+
function encodeMeshFeatures(scenegraph, meshIndex, features) {
|
|
934
|
+
if (!features) return;
|
|
935
|
+
const { batches, featureIdField } = features;
|
|
936
|
+
const schema = batches[0]?.schema;
|
|
937
|
+
const count = batches.reduce((total, batch) => total + batch.data.numRows, 0);
|
|
938
|
+
const metadataClass = schema?.metadata?.["loaders.gl:feature-class"] || "features";
|
|
939
|
+
if (!schema || !count || !/^[A-Za-z_][A-Za-z0-9_]*$/.test(metadataClass) || !schema.fields.some((field) => field.name === featureIdField && field.nullable !== true) || new Set(schema.fields.map((field) => field.name)).size !== schema.fields.length) {
|
|
940
|
+
throw new import_core8.TileConversionError(
|
|
941
|
+
"MESH_FEATURE_SCHEMA_INVALID",
|
|
942
|
+
"Features require a single named class, unique fields, and a non-null stable identifier"
|
|
943
|
+
);
|
|
944
|
+
}
|
|
945
|
+
for (const batch of batches) {
|
|
946
|
+
const actual = (0, import_schema_utils4.convertArrowToSchema)(batch.data.schema).fields;
|
|
947
|
+
if (JSON.stringify(batch.schema) !== JSON.stringify(schema) || batch.length !== batch.data.numRows || JSON.stringify(actual.map((field) => [field.name, field.type])) !== JSON.stringify(schema.fields.map((field) => [field.name, field.type]))) {
|
|
948
|
+
throw new import_core8.TileConversionError(
|
|
949
|
+
"MESH_FEATURE_SCHEMA_INVALID",
|
|
950
|
+
"Declared feature schemas and Arrow column types must agree across batches"
|
|
951
|
+
);
|
|
952
|
+
}
|
|
953
|
+
}
|
|
954
|
+
const properties = /* @__PURE__ */ Object.create(null);
|
|
955
|
+
const columns = /* @__PURE__ */ Object.create(null);
|
|
956
|
+
for (const field of schema.fields) {
|
|
957
|
+
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(field.name))
|
|
958
|
+
throw new import_core8.TileConversionError(
|
|
959
|
+
"MESH_FEATURE_SCHEMA_INVALID",
|
|
960
|
+
"Property names must be metadata identifiers"
|
|
961
|
+
);
|
|
962
|
+
const values = batches.flatMap((batch) => {
|
|
963
|
+
const column = batch.data.getChild(field.name);
|
|
964
|
+
if (!column)
|
|
965
|
+
throw new import_core8.TileConversionError(
|
|
966
|
+
"MESH_FEATURE_SCHEMA_INVALID",
|
|
967
|
+
`Missing Arrow column ${field.name}`
|
|
968
|
+
);
|
|
969
|
+
return Array.from({ length: column.length }, (_, index) => column.get(index));
|
|
970
|
+
});
|
|
971
|
+
if (field.name === featureIdField && (values.some(
|
|
972
|
+
(value) => !(typeof value === "string" || typeof value === "bigint" || Number.isSafeInteger(value))
|
|
973
|
+
) || new Set(values).size !== count))
|
|
974
|
+
throw new import_core8.TileConversionError(
|
|
975
|
+
"MESH_FEATURE_IDS_INVALID",
|
|
976
|
+
"Stable identifiers must be unique strings or exact integers"
|
|
977
|
+
);
|
|
978
|
+
const encoded = encodeFeatureColumn(field, values);
|
|
979
|
+
properties[field.name] = encoded.property;
|
|
980
|
+
columns[field.name] = {
|
|
981
|
+
values: addMetadataBuffer(scenegraph, encoded.data),
|
|
982
|
+
...encoded.offsets ? {
|
|
983
|
+
stringOffsets: addMetadataBuffer(scenegraph, encoded.offsets),
|
|
984
|
+
stringOffsetType: "UINT32"
|
|
985
|
+
} : {}
|
|
986
|
+
};
|
|
987
|
+
}
|
|
988
|
+
scenegraph.json.extensions ||= {};
|
|
989
|
+
scenegraph.json.extensions.EXT_structural_metadata = {
|
|
990
|
+
schema: { id: "loaders_gl_features", classes: { [metadataClass]: { properties } } },
|
|
991
|
+
propertyTables: [{ class: metadataClass, count, properties: columns }]
|
|
992
|
+
};
|
|
993
|
+
const primitive = scenegraph.json.meshes[meshIndex].primitives[0];
|
|
994
|
+
primitive.extensions ||= {};
|
|
995
|
+
primitive.extensions.EXT_mesh_features = {
|
|
996
|
+
featureIds: [{ featureCount: count, attribute: 0, propertyTable: 0 }]
|
|
997
|
+
};
|
|
998
|
+
scenegraph.registerUsedExtension("EXT_structural_metadata");
|
|
999
|
+
scenegraph.registerUsedExtension("EXT_mesh_features");
|
|
1000
|
+
}
|
|
1001
|
+
function addMetadataBuffer(scenegraph, data) {
|
|
1002
|
+
if (scenegraph.byteLength % 8) scenegraph.addBufferView(new Uint8Array(4));
|
|
1003
|
+
return scenegraph.addBufferView(data);
|
|
1004
|
+
}
|
|
1005
|
+
function encodeFeatureColumn(field, values) {
|
|
1006
|
+
const type = field.type;
|
|
1007
|
+
const hasNull = values.includes(null);
|
|
1008
|
+
if (hasNull && field.nullable !== true)
|
|
1009
|
+
throw new import_core8.TileConversionError(
|
|
1010
|
+
"MESH_FEATURE_VALUE_INVALID",
|
|
1011
|
+
`Required field ${field.name} contains null`
|
|
1012
|
+
);
|
|
1013
|
+
const present = values.filter((value) => value !== null);
|
|
1014
|
+
let noData;
|
|
1015
|
+
if (type === "utf8") {
|
|
1016
|
+
if (present.some(
|
|
1017
|
+
(value) => typeof value !== "string" || new TextDecoder().decode(new TextEncoder().encode(value)) !== value
|
|
1018
|
+
))
|
|
1019
|
+
throw new import_core8.TileConversionError("MESH_FEATURE_VALUE_INVALID", "Strings require valid Unicode");
|
|
1020
|
+
if (hasNull) {
|
|
1021
|
+
noData = "";
|
|
1022
|
+
const strings = new Set(present);
|
|
1023
|
+
while (strings.has(noData)) noData += "_";
|
|
1024
|
+
}
|
|
1025
|
+
const encoded = values.map(
|
|
1026
|
+
(value) => new TextEncoder().encode(value === null ? noData : value)
|
|
1027
|
+
);
|
|
1028
|
+
const offsets = new Uint32Array(values.length + 1);
|
|
1029
|
+
for (let index = 0; index < values.length; index++)
|
|
1030
|
+
offsets[index + 1] = offsets[index] + encoded[index].length;
|
|
1031
|
+
const data2 = new Uint8Array(offsets[values.length]);
|
|
1032
|
+
encoded.forEach((bytes, index) => data2.set(bytes, offsets[index]));
|
|
1033
|
+
return {
|
|
1034
|
+
property: { type: "STRING", required: field.nullable !== true, ...hasNull ? { noData } : {} },
|
|
1035
|
+
data: data2,
|
|
1036
|
+
offsets
|
|
1037
|
+
};
|
|
1038
|
+
}
|
|
1039
|
+
const widths = {
|
|
1040
|
+
int8: 1,
|
|
1041
|
+
uint8: 1,
|
|
1042
|
+
int16: 2,
|
|
1043
|
+
uint16: 2,
|
|
1044
|
+
int32: 4,
|
|
1045
|
+
uint32: 4,
|
|
1046
|
+
int64: 8,
|
|
1047
|
+
uint64: 8,
|
|
1048
|
+
float32: 4,
|
|
1049
|
+
float64: 8
|
|
1050
|
+
};
|
|
1051
|
+
const width = widths[String(type)];
|
|
1052
|
+
if (!width)
|
|
1053
|
+
throw new import_core8.TileConversionError(
|
|
1054
|
+
"MESH_FEATURE_TYPE_UNSUPPORTED",
|
|
1055
|
+
`No qualified GLB property mapping for ${String(type)}`
|
|
1056
|
+
);
|
|
1057
|
+
const isInteger64 = type === "int64" || type === "uint64";
|
|
1058
|
+
const isFloat = type === "float32" || type === "float64";
|
|
1059
|
+
const minimum = isFloat ? -Infinity : String(type).startsWith("uint") ? 0n : -(1n << BigInt(width * 8 - 1));
|
|
1060
|
+
const maximum = isFloat ? Infinity : (1n << BigInt(width * 8 - (String(type).startsWith("uint") ? 0 : 1))) - 1n;
|
|
1061
|
+
if (present.some(
|
|
1062
|
+
(value) => isInteger64 ? typeof value !== "bigint" || value < minimum || value > maximum : typeof value !== "number" || !Number.isFinite(value) || !isFloat && (!Number.isSafeInteger(value) || BigInt(value) < minimum || BigInt(value) > maximum)
|
|
1063
|
+
))
|
|
1064
|
+
throw new import_core8.TileConversionError(
|
|
1065
|
+
"MESH_FEATURE_VALUE_INVALID",
|
|
1066
|
+
`Values do not fit ${String(type)}`
|
|
1067
|
+
);
|
|
1068
|
+
if (hasNull) {
|
|
1069
|
+
const used = new Set(present.map(String));
|
|
1070
|
+
noData = 0;
|
|
1071
|
+
while (used.has(String(noData))) noData++;
|
|
1072
|
+
if (noData > maximum && minimum < 0) {
|
|
1073
|
+
noData = -1;
|
|
1074
|
+
while (used.has(String(noData))) noData--;
|
|
1075
|
+
}
|
|
1076
|
+
if (noData > maximum || noData < minimum)
|
|
1077
|
+
throw new import_core8.TileConversionError(
|
|
1078
|
+
"MESH_FEATURE_NULL_UNREPRESENTABLE",
|
|
1079
|
+
"No unused value remains for null without losing a real value"
|
|
1080
|
+
);
|
|
1081
|
+
}
|
|
1082
|
+
const data = new Uint8Array(values.length * width);
|
|
1083
|
+
const view = new DataView(data.buffer);
|
|
1084
|
+
values.forEach((value, index) => {
|
|
1085
|
+
const selected = value === null ? isInteger64 ? BigInt(noData) : noData : value;
|
|
1086
|
+
const offset = index * width;
|
|
1087
|
+
switch (type) {
|
|
1088
|
+
case "int8":
|
|
1089
|
+
view.setInt8(offset, selected);
|
|
1090
|
+
break;
|
|
1091
|
+
case "uint8":
|
|
1092
|
+
view.setUint8(offset, selected);
|
|
1093
|
+
break;
|
|
1094
|
+
case "int16":
|
|
1095
|
+
view.setInt16(offset, selected, true);
|
|
1096
|
+
break;
|
|
1097
|
+
case "uint16":
|
|
1098
|
+
view.setUint16(offset, selected, true);
|
|
1099
|
+
break;
|
|
1100
|
+
case "int32":
|
|
1101
|
+
view.setInt32(offset, selected, true);
|
|
1102
|
+
break;
|
|
1103
|
+
case "uint32":
|
|
1104
|
+
view.setUint32(offset, selected, true);
|
|
1105
|
+
break;
|
|
1106
|
+
case "int64":
|
|
1107
|
+
view.setBigInt64(offset, selected, true);
|
|
1108
|
+
break;
|
|
1109
|
+
case "uint64":
|
|
1110
|
+
view.setBigUint64(offset, selected, true);
|
|
1111
|
+
break;
|
|
1112
|
+
case "float32":
|
|
1113
|
+
view.setFloat32(offset, selected, true);
|
|
1114
|
+
break;
|
|
1115
|
+
case "float64":
|
|
1116
|
+
view.setFloat64(offset, selected, true);
|
|
1117
|
+
break;
|
|
1118
|
+
}
|
|
1119
|
+
if (isFloat && !Number.isFinite(
|
|
1120
|
+
type === "float32" ? view.getFloat32(offset, true) : view.getFloat64(offset, true)
|
|
1121
|
+
))
|
|
1122
|
+
throw new import_core8.TileConversionError(
|
|
1123
|
+
"MESH_FEATURE_VALUE_INVALID",
|
|
1124
|
+
"Floating point property overflow"
|
|
1125
|
+
);
|
|
1126
|
+
});
|
|
1127
|
+
return {
|
|
1128
|
+
property: {
|
|
1129
|
+
type: "SCALAR",
|
|
1130
|
+
componentType: String(type).toUpperCase(),
|
|
1131
|
+
required: field.nullable !== true,
|
|
1132
|
+
...hasNull ? { noData } : {}
|
|
1133
|
+
},
|
|
1134
|
+
data
|
|
1135
|
+
};
|
|
1136
|
+
}
|
|
1137
|
+
|
|
1138
|
+
// src/v5/mesh.ts
|
|
1139
|
+
var import_core9 = require("@loaders.gl/tile-converter/v5/core");
|
|
629
1140
|
function encodeMeshTile(mesh, options = {}) {
|
|
630
|
-
const geometry = validateMeshGeometry(mesh);
|
|
1141
|
+
const geometry = prepareMeshFeatureGeometry(validateMeshGeometry(mesh), options.features);
|
|
631
1142
|
const { scenegraph, materialIndex } = createMeshTileScenegraph(options, geometry);
|
|
632
1143
|
const meshIndex = scenegraph.addMesh({
|
|
633
1144
|
attributes: geometry.attributes,
|
|
@@ -635,6 +1146,7 @@ function encodeMeshTile(mesh, options = {}) {
|
|
|
635
1146
|
material: materialIndex,
|
|
636
1147
|
mode: 4
|
|
637
1148
|
});
|
|
1149
|
+
encodeMeshFeatures(scenegraph, meshIndex, options.features);
|
|
638
1150
|
return finalizeMeshTileScenegraph(scenegraph, meshIndex);
|
|
639
1151
|
}
|
|
640
1152
|
function createMeshTileScenegraph(options, geometry) {
|
|
@@ -651,14 +1163,14 @@ function finalizeMeshTileScenegraph(scenegraph, meshIndex) {
|
|
|
651
1163
|
}
|
|
652
1164
|
function validateMeshGeometry(mesh, allowFloat64Positions = false) {
|
|
653
1165
|
if (mesh.topology !== "triangle-list" || mesh.mode !== 4) {
|
|
654
|
-
throw new
|
|
1166
|
+
throw new import_core9.TileConversionError(
|
|
655
1167
|
"MESH_TOPOLOGY_UNSUPPORTED",
|
|
656
1168
|
"Only triangle-list mode 4 is supported"
|
|
657
1169
|
);
|
|
658
1170
|
}
|
|
659
1171
|
for (const name of Object.keys(mesh.attributes)) {
|
|
660
1172
|
if (name !== "POSITION" && name !== "NORMAL" && name !== "COLOR_0" && name !== "TEXCOORD_0") {
|
|
661
|
-
throw new
|
|
1173
|
+
throw new import_core9.TileConversionError(
|
|
662
1174
|
"MESH_ATTRIBUTE_UNSUPPORTED",
|
|
663
1175
|
`Mesh attribute ${name} is unsupported`
|
|
664
1176
|
);
|
|
@@ -671,14 +1183,14 @@ function validateMeshGeometry(mesh, allowFloat64Positions = false) {
|
|
|
671
1183
|
if (mesh.attributes.NORMAL) {
|
|
672
1184
|
const normals = getFloatAttribute(mesh.attributes.NORMAL, "NORMAL");
|
|
673
1185
|
if (normals.length !== positions.length) {
|
|
674
|
-
throw new
|
|
1186
|
+
throw new import_core9.TileConversionError(
|
|
675
1187
|
"MESH_NORMAL_COUNT_MISMATCH",
|
|
676
1188
|
"NORMAL count must match POSITION count"
|
|
677
1189
|
);
|
|
678
1190
|
}
|
|
679
1191
|
for (let index = 0; index < normals.length; index += 3) {
|
|
680
1192
|
if (Math.abs(Math.hypot(normals[index], normals[index + 1], normals[index + 2]) - 1) > 1e-4) {
|
|
681
|
-
throw new
|
|
1193
|
+
throw new import_core9.TileConversionError(
|
|
682
1194
|
"MESH_NORMAL_INVALID",
|
|
683
1195
|
"NORMAL vectors must have unit length"
|
|
684
1196
|
);
|
|
@@ -690,7 +1202,7 @@ function validateMeshGeometry(mesh, allowFloat64Positions = false) {
|
|
|
690
1202
|
const colors = mesh.attributes.COLOR_0;
|
|
691
1203
|
const normalized = isNormalizedUnsignedAttribute(colors);
|
|
692
1204
|
if (!colors || !(colors.value instanceof Float32Array || normalized) || colors.size !== 3 && colors.size !== 4 || colors.value.length / colors.size !== positions.length / 3 || colors.value instanceof Float32Array && colors.value.some((value) => !Number.isFinite(value) || value < 0 || value > 1)) {
|
|
693
|
-
throw new
|
|
1205
|
+
throw new import_core9.TileConversionError(
|
|
694
1206
|
"MESH_COLOR_INVALID",
|
|
695
1207
|
"COLOR_0 requires packed linear RGB/RGBA: Float32 in [0, 1] or normalized Uint8/Uint16, one per vertex"
|
|
696
1208
|
);
|
|
@@ -706,7 +1218,7 @@ function validateMeshGeometry(mesh, allowFloat64Positions = false) {
|
|
|
706
1218
|
const textureCoordinates = mesh.attributes.TEXCOORD_0;
|
|
707
1219
|
const normalized = isNormalizedUnsignedAttribute(textureCoordinates);
|
|
708
1220
|
if (!textureCoordinates || !(textureCoordinates.value instanceof Float32Array || normalized) || textureCoordinates.size !== 2 || textureCoordinates.value.length / 2 !== positions.length / 3 || textureCoordinates.value.some((value) => !Number.isFinite(value))) {
|
|
709
|
-
throw new
|
|
1221
|
+
throw new import_core9.TileConversionError(
|
|
710
1222
|
"MESH_TEXCOORD_INVALID",
|
|
711
1223
|
"TEXCOORD_0 requires packed finite Float32 or normalized Uint8/Uint16 UV pairs, one per vertex"
|
|
712
1224
|
);
|
|
@@ -718,9 +1230,9 @@ function validateMeshGeometry(mesh, allowFloat64Positions = false) {
|
|
|
718
1230
|
...normalized ? { normalized: true } : {}
|
|
719
1231
|
};
|
|
720
1232
|
}
|
|
721
|
-
const indices =
|
|
1233
|
+
const indices = validateMeshIndices(mesh.indices, positions.length / 3);
|
|
722
1234
|
if (!indices && positions.length % 9 !== 0) {
|
|
723
|
-
throw new
|
|
1235
|
+
throw new import_core9.TileConversionError(
|
|
724
1236
|
"MESH_TRIANGLE_COUNT_INVALID",
|
|
725
1237
|
"Nonindexed meshes require complete triangles"
|
|
726
1238
|
);
|
|
@@ -734,36 +1246,36 @@ function validateMeshGeometry(mesh, allowFloat64Positions = false) {
|
|
|
734
1246
|
}
|
|
735
1247
|
function getFloatAttribute(attribute, name, allowFloat64 = false) {
|
|
736
1248
|
if (!attribute || !(attribute.value instanceof Float32Array || allowFloat64 && attribute.value instanceof Float64Array) || attribute.size !== 3 || attribute.value.length === 0 || attribute.value.length % 3 !== 0) {
|
|
737
|
-
throw new
|
|
1249
|
+
throw new import_core9.TileConversionError(
|
|
738
1250
|
"MESH_ATTRIBUTE_INVALID",
|
|
739
1251
|
`${name} must contain packed ${allowFloat64 ? "Float32 or Float64" : "Float32"} xyz triples`
|
|
740
1252
|
);
|
|
741
1253
|
}
|
|
742
1254
|
validateAttributeLayout(attribute, name);
|
|
743
1255
|
if (attribute.value.some((value) => !Number.isFinite(value))) {
|
|
744
|
-
throw new
|
|
1256
|
+
throw new import_core9.TileConversionError("MESH_ATTRIBUTE_NONFINITE", `${name} must contain finite values`);
|
|
745
1257
|
}
|
|
746
1258
|
return attribute.value;
|
|
747
1259
|
}
|
|
748
|
-
function
|
|
1260
|
+
function validateMeshIndices(attribute, vertexCount, primitiveMode = 4) {
|
|
749
1261
|
if (!attribute) return void 0;
|
|
750
1262
|
validateAttributeLayout(attribute, "indices");
|
|
751
1263
|
const values = attribute.value;
|
|
752
1264
|
if (attribute.size !== 1 || !(values instanceof Uint8Array || values instanceof Uint16Array || values instanceof Uint32Array)) {
|
|
753
|
-
throw new
|
|
1265
|
+
throw new import_core9.TileConversionError(
|
|
754
1266
|
"MESH_INDICES_INVALID",
|
|
755
1267
|
"Indices must contain packed unsigned integer scalars"
|
|
756
1268
|
);
|
|
757
1269
|
}
|
|
758
|
-
if (values.length === 0 || values.length % 3 !== 0) {
|
|
759
|
-
throw new
|
|
1270
|
+
if (primitiveMode === 5 ? values.length < 3 : values.length === 0 || values.length % 3 !== 0) {
|
|
1271
|
+
throw new import_core9.TileConversionError(
|
|
760
1272
|
"MESH_TRIANGLE_COUNT_INVALID",
|
|
761
|
-
"Indices must contain complete triangles"
|
|
1273
|
+
"Indices must contain complete triangles or at least three strip vertices"
|
|
762
1274
|
);
|
|
763
1275
|
}
|
|
764
1276
|
const restartIndex = 2 ** (values.BYTES_PER_ELEMENT * 8) - 1;
|
|
765
1277
|
if (values.some((value) => value >= vertexCount || value === restartIndex)) {
|
|
766
|
-
throw new
|
|
1278
|
+
throw new import_core9.TileConversionError(
|
|
767
1279
|
"MESH_INDEX_OUT_OF_RANGE",
|
|
768
1280
|
"Indices must reference existing vertices and exclude primitive-restart values"
|
|
769
1281
|
);
|
|
@@ -775,7 +1287,7 @@ function isNormalizedUnsignedAttribute(attribute) {
|
|
|
775
1287
|
}
|
|
776
1288
|
function validateAttributeLayout(attribute, name, allowNormalized = false) {
|
|
777
1289
|
if ((attribute.byteOffset ?? 0) !== 0 || (attribute.byteStride ?? 0) !== 0 || attribute.normalized && !allowNormalized || attribute.transform || attribute.componentType) {
|
|
778
|
-
throw new
|
|
1290
|
+
throw new import_core9.TileConversionError(
|
|
779
1291
|
"MESH_ATTRIBUTE_LAYOUT_UNSUPPORTED",
|
|
780
1292
|
`${name} must use packed contiguous storage with supported normalization`
|
|
781
1293
|
);
|
|
@@ -783,7 +1295,7 @@ function validateAttributeLayout(attribute, name, allowNormalized = false) {
|
|
|
783
1295
|
}
|
|
784
1296
|
function validateMeshMaterial(material, scenegraph, hasTextureCoordinates) {
|
|
785
1297
|
if (!material || typeof material !== "object" || Array.isArray(material)) {
|
|
786
|
-
throw new
|
|
1298
|
+
throw new import_core9.TileConversionError("MESH_MATERIAL_INVALID", "Material must be an object");
|
|
787
1299
|
}
|
|
788
1300
|
const allowedProperties = [
|
|
789
1301
|
"baseColorFactor",
|
|
@@ -796,7 +1308,7 @@ function validateMeshMaterial(material, scenegraph, hasTextureCoordinates) {
|
|
|
796
1308
|
];
|
|
797
1309
|
for (const name of Object.keys(material)) {
|
|
798
1310
|
if (!allowedProperties.includes(name)) {
|
|
799
|
-
throw new
|
|
1311
|
+
throw new import_core9.TileConversionError(
|
|
800
1312
|
"MESH_MATERIAL_UNSUPPORTED",
|
|
801
1313
|
`Material property ${name} is unsupported`
|
|
802
1314
|
);
|
|
@@ -808,7 +1320,7 @@ function validateMeshMaterial(material, scenegraph, hasTextureCoordinates) {
|
|
|
808
1320
|
)) || [metallicFactor, roughnessFactor].some(
|
|
809
1321
|
(value) => value !== void 0 && (!Number.isFinite(value) || value < 0 || value > 1)
|
|
810
1322
|
) || alphaMode !== void 0 && !["OPAQUE", "MASK", "BLEND"].includes(alphaMode) || alphaCutoff !== void 0 && (alphaMode !== "MASK" || !Number.isFinite(alphaCutoff) || alphaCutoff < 0) || doubleSided !== void 0 && typeof doubleSided !== "boolean") {
|
|
811
|
-
throw new
|
|
1323
|
+
throw new import_core9.TileConversionError(
|
|
812
1324
|
"MESH_MATERIAL_INVALID",
|
|
813
1325
|
"Material values must satisfy the selected glTF profile"
|
|
814
1326
|
);
|
|
@@ -829,13 +1341,13 @@ function encodeBaseColorTexture(texture, scenegraph, hasTextureCoordinates) {
|
|
|
829
1341
|
if (!texture || Object.keys(texture).some(
|
|
830
1342
|
(name) => name !== "data" && name !== "mimeType" && name !== "sampler" && name !== "transform"
|
|
831
1343
|
) || !(texture.data instanceof Uint8Array) || !["image/png", "image/jpeg"].includes(texture.mimeType)) {
|
|
832
|
-
throw new
|
|
1344
|
+
throw new import_core9.TileConversionError(
|
|
833
1345
|
"MESH_TEXTURE_INVALID",
|
|
834
1346
|
"Base-color texture must supply Uint8Array data, image/png or image/jpeg mimeType, and optional sampling/transform controls"
|
|
835
1347
|
);
|
|
836
1348
|
}
|
|
837
1349
|
if (!hasTextureCoordinates) {
|
|
838
|
-
throw new
|
|
1350
|
+
throw new import_core9.TileConversionError(
|
|
839
1351
|
"MESH_TEXCOORD_REQUIRED",
|
|
840
1352
|
"Base-color texture requires TEXCOORD_0"
|
|
841
1353
|
);
|
|
@@ -847,7 +1359,7 @@ function encodeBaseColorTexture(texture, scenegraph, hasTextureCoordinates) {
|
|
|
847
1359
|
} catch {
|
|
848
1360
|
}
|
|
849
1361
|
if (!metadata || metadata.mimeType !== texture.mimeType || metadata.width <= 0 || metadata.height <= 0) {
|
|
850
|
-
throw new
|
|
1362
|
+
throw new import_core9.TileConversionError(
|
|
851
1363
|
"MESH_TEXTURE_INVALID",
|
|
852
1364
|
"Image header must match mimeType and declare positive dimensions"
|
|
853
1365
|
);
|
|
@@ -865,7 +1377,7 @@ function encodeBaseColorTexture(texture, scenegraph, hasTextureCoordinates) {
|
|
|
865
1377
|
}
|
|
866
1378
|
function validateMeshSampler(sampler) {
|
|
867
1379
|
if (!sampler || typeof sampler !== "object" || Array.isArray(sampler)) {
|
|
868
|
-
throw new
|
|
1380
|
+
throw new import_core9.TileConversionError("MESH_SAMPLER_INVALID", "Sampler must be an object");
|
|
869
1381
|
}
|
|
870
1382
|
const allowedValues = {
|
|
871
1383
|
wrapS: [33071, 33648, 10497],
|
|
@@ -875,7 +1387,7 @@ function validateMeshSampler(sampler) {
|
|
|
875
1387
|
};
|
|
876
1388
|
for (const name in sampler) {
|
|
877
1389
|
if (!Object.hasOwn(allowedValues, name)) {
|
|
878
|
-
throw new
|
|
1390
|
+
throw new import_core9.TileConversionError(
|
|
879
1391
|
"MESH_SAMPLER_UNSUPPORTED",
|
|
880
1392
|
`Sampler property ${name} is unsupported`
|
|
881
1393
|
);
|
|
@@ -890,7 +1402,7 @@ function validateMeshSampler(sampler) {
|
|
|
890
1402
|
for (const name of Object.keys(allowedValues)) {
|
|
891
1403
|
const property = name;
|
|
892
1404
|
if (selectedSampler[property] !== void 0 && !allowedValues[property].includes(selectedSampler[property])) {
|
|
893
|
-
throw new
|
|
1405
|
+
throw new import_core9.TileConversionError(
|
|
894
1406
|
"MESH_SAMPLER_INVALID",
|
|
895
1407
|
`Sampler ${name} must use a glTF wrapping/filtering value`
|
|
896
1408
|
);
|
|
@@ -900,14 +1412,14 @@ function validateMeshSampler(sampler) {
|
|
|
900
1412
|
}
|
|
901
1413
|
function validateMeshTextureTransform(transform) {
|
|
902
1414
|
if (!transform || typeof transform !== "object" || Array.isArray(transform)) {
|
|
903
|
-
throw new
|
|
1415
|
+
throw new import_core9.TileConversionError(
|
|
904
1416
|
"MESH_TEXTURE_TRANSFORM_INVALID",
|
|
905
1417
|
"Texture transform must be an object"
|
|
906
1418
|
);
|
|
907
1419
|
}
|
|
908
1420
|
for (const name in transform) {
|
|
909
1421
|
if (name !== "offset" && name !== "rotation" && name !== "scale") {
|
|
910
|
-
throw new
|
|
1422
|
+
throw new import_core9.TileConversionError(
|
|
911
1423
|
"MESH_TEXTURE_TRANSFORM_UNSUPPORTED",
|
|
912
1424
|
`Texture transform property ${name} is unsupported`
|
|
913
1425
|
);
|
|
@@ -919,7 +1431,7 @@ function validateMeshTextureTransform(transform) {
|
|
|
919
1431
|
if ([offsetValues, scaleValues].some(
|
|
920
1432
|
(value) => value !== void 0 && (!Array.isArray(value) || value.length !== 2 || value.some((component) => !Number.isFinite(component)))
|
|
921
1433
|
) || rotation !== void 0 && !Number.isFinite(rotation)) {
|
|
922
|
-
throw new
|
|
1434
|
+
throw new import_core9.TileConversionError(
|
|
923
1435
|
"MESH_TEXTURE_TRANSFORM_INVALID",
|
|
924
1436
|
"Texture transform requires finite UV pairs and a finite rotation"
|
|
925
1437
|
);
|
|
@@ -932,15 +1444,15 @@ function validateMeshTextureTransform(transform) {
|
|
|
932
1444
|
}
|
|
933
1445
|
|
|
934
1446
|
// src/v5/mesh-conversion.ts
|
|
935
|
-
var
|
|
936
|
-
var
|
|
1447
|
+
var import_tiles2 = require("@loaders.gl/tiles");
|
|
1448
|
+
var import_core11 = require("@loaders.gl/tile-converter/v5/core");
|
|
937
1449
|
|
|
938
1450
|
// src/v5/mesh-draco.ts
|
|
939
1451
|
var import_draco = require("@loaders.gl/draco");
|
|
940
|
-
var
|
|
1452
|
+
var import_core10 = require("@loaders.gl/tile-converter/v5/core");
|
|
941
1453
|
var DRACO_EXTENSION = "KHR_draco_mesh_compression";
|
|
942
1454
|
async function encodeDracoMeshTile(geometry, options, libraryOptions = {}) {
|
|
943
|
-
geometry = validateMeshGeometry(geometry);
|
|
1455
|
+
geometry = prepareMeshFeatureGeometry(validateMeshGeometry(geometry), options.features);
|
|
944
1456
|
if (!geometry.indices) {
|
|
945
1457
|
geometry.indices = {
|
|
946
1458
|
size: 1,
|
|
@@ -962,7 +1474,7 @@ async function encodeDracoMeshTile(geometry, options, libraryOptions = {}) {
|
|
|
962
1474
|
const decoded = await loader.parse(encoded.data, loaderOptions);
|
|
963
1475
|
const positions = decoded.attributes.POSITION.value;
|
|
964
1476
|
if (!(positions instanceof Float32Array) || positions.length !== encoded.report.pointCount * 3 || encoded.report.pointCount === 0 || !decoded.indices || decoded.indices.value.length !== encoded.report.faceCount * 3 || encoded.report.faceCount * 3 !== (geometry.indices?.value.length ?? geometry.attributes.POSITION.value.length / 3)) {
|
|
965
|
-
throw new
|
|
1477
|
+
throw new import_core10.TileConversionError(
|
|
966
1478
|
"MESH_DRACO_ENCODING_INVALID",
|
|
967
1479
|
"Draco must preserve the triangle count and emit finite positions"
|
|
968
1480
|
);
|
|
@@ -974,12 +1486,18 @@ async function encodeDracoMeshTile(geometry, options, libraryOptions = {}) {
|
|
|
974
1486
|
}
|
|
975
1487
|
for (let index = 0; index < positions.length; index += 3) {
|
|
976
1488
|
if (!positionKeys.has(getPositionKey(positions, index))) {
|
|
977
|
-
throw new
|
|
1489
|
+
throw new import_core10.TileConversionError(
|
|
978
1490
|
"MESH_DRACO_POSITION_CHANGED",
|
|
979
1491
|
"Lossless Draco must preserve the encoded Float32 positions"
|
|
980
1492
|
);
|
|
981
1493
|
}
|
|
982
1494
|
}
|
|
1495
|
+
if (JSON.stringify(getMeshTriangles(geometry)) !== JSON.stringify(getMeshTriangles(decoded))) {
|
|
1496
|
+
throw new import_core10.TileConversionError(
|
|
1497
|
+
"MESH_DRACO_ATTRIBUTE_CHANGED",
|
|
1498
|
+
"Lossless Draco must preserve oriented triangles and every attribute, including feature ownership"
|
|
1499
|
+
);
|
|
1500
|
+
}
|
|
983
1501
|
const bufferView = scenegraph.addBufferView(encoded.data);
|
|
984
1502
|
const attributes = {};
|
|
985
1503
|
const attributeIdentifiers = {};
|
|
@@ -987,12 +1505,12 @@ async function encodeDracoMeshTile(geometry, options, libraryOptions = {}) {
|
|
|
987
1505
|
for (const [name, attribute] of Object.entries(geometry.attributes)) {
|
|
988
1506
|
const decodedAttribute = decoded.attributes[name];
|
|
989
1507
|
if (!decodedAttribute || decodedAttribute.value.length !== encoded.report.pointCount * attribute.size) {
|
|
990
|
-
throw new
|
|
1508
|
+
throw new import_core10.TileConversionError("MESH_DRACO_ENCODING_INVALID", `Draco must preserve ${name}`);
|
|
991
1509
|
}
|
|
992
1510
|
const accessor = {
|
|
993
|
-
componentType: attribute.value instanceof Uint8Array ? 5121 : attribute.value instanceof Uint16Array ? 5123 : 5126,
|
|
1511
|
+
componentType: attribute.value instanceof Uint8Array ? 5121 : attribute.value instanceof Uint16Array ? 5123 : attribute.value instanceof Uint32Array ? 5125 : 5126,
|
|
994
1512
|
count: encoded.report.pointCount,
|
|
995
|
-
type: attribute.size === 2 ? "VEC2" : attribute.size === 3 ? "VEC3" : "VEC4",
|
|
1513
|
+
type: attribute.size === 1 ? "SCALAR" : attribute.size === 2 ? "VEC2" : attribute.size === 3 ? "VEC3" : "VEC4",
|
|
996
1514
|
...attribute.normalized ? { normalized: true } : {}
|
|
997
1515
|
};
|
|
998
1516
|
if (name === "POSITION") {
|
|
@@ -1027,30 +1545,51 @@ async function encodeDracoMeshTile(geometry, options, libraryOptions = {}) {
|
|
|
1027
1545
|
]
|
|
1028
1546
|
}
|
|
1029
1547
|
];
|
|
1548
|
+
encodeMeshFeatures(scenegraph, 0, options.features);
|
|
1030
1549
|
scenegraph.registerRequiredExtension(DRACO_EXTENSION);
|
|
1031
1550
|
return { glb: finalizeMeshTileScenegraph(scenegraph, 0), positions };
|
|
1032
1551
|
}
|
|
1033
1552
|
function getPositionKey(positions, index) {
|
|
1034
1553
|
return `${positions[index]},${positions[index + 1]},${positions[index + 2]}`;
|
|
1035
1554
|
}
|
|
1555
|
+
function getMeshTriangles(mesh) {
|
|
1556
|
+
const names = Object.keys(mesh.attributes).sort();
|
|
1557
|
+
const indices = mesh.indices.value;
|
|
1558
|
+
const triangles = [];
|
|
1559
|
+
for (let index = 0; index < indices.length; index += 3) {
|
|
1560
|
+
const vertices = [0, 1, 2].map(
|
|
1561
|
+
(corner) => names.map((name) => {
|
|
1562
|
+
const attribute = mesh.attributes[name];
|
|
1563
|
+
return Array.from(
|
|
1564
|
+
{ length: attribute.size },
|
|
1565
|
+
(_, component) => attribute.value[indices[index + corner] * attribute.size + component]
|
|
1566
|
+
).join(",");
|
|
1567
|
+
}).join("|")
|
|
1568
|
+
);
|
|
1569
|
+
triangles.push(
|
|
1570
|
+
[0, 1, 2].map((corner) => [...vertices.slice(corner), ...vertices.slice(0, corner)].join(";")).sort()[0]
|
|
1571
|
+
);
|
|
1572
|
+
}
|
|
1573
|
+
return triangles.sort();
|
|
1574
|
+
}
|
|
1036
1575
|
|
|
1037
1576
|
// src/v5/mesh-conversion.ts
|
|
1038
1577
|
function createMeshConversionCodec(options) {
|
|
1039
1578
|
const { spatialContext, maxPositionError, draco = true, dracoLibraryOptions } = options;
|
|
1040
1579
|
if (typeof draco !== "boolean") {
|
|
1041
|
-
throw new
|
|
1580
|
+
throw new import_core11.TileConversionError("MESH_DRACO_OPTIONS_INVALID", "draco must be a boolean");
|
|
1042
1581
|
}
|
|
1043
1582
|
if (!Number.isFinite(maxPositionError) || maxPositionError < 0) {
|
|
1044
|
-
throw new
|
|
1583
|
+
throw new import_core11.TileConversionError(
|
|
1045
1584
|
"MESH_PRECISION_LIMIT_INVALID",
|
|
1046
1585
|
"maxPositionError must be finite and nonnegative"
|
|
1047
1586
|
);
|
|
1048
1587
|
}
|
|
1049
1588
|
const spatialReference = spatialContext.spatialReference;
|
|
1050
1589
|
const outputCrs = spatialReference.targetCrs || spatialReference.sourceCrs;
|
|
1051
|
-
const outputFrame = spatialReference.status === "native" ? spatialReference.coordinateFrame : outputCrs ? (0,
|
|
1590
|
+
const outputFrame = spatialReference.status === "native" ? spatialReference.coordinateFrame : outputCrs ? (0, import_tiles2.getSpatialCoordinateFrame)(outputCrs) : "unknown";
|
|
1052
1591
|
if (!["native", "transformed"].includes(spatialReference.status) || !["projected", "geocentric", "local", "cartesian"].includes(outputFrame)) {
|
|
1053
|
-
throw new
|
|
1592
|
+
throw new import_core11.TileConversionError(
|
|
1054
1593
|
"MESH_OUTPUT_FRAME_UNSUPPORTED",
|
|
1055
1594
|
"Mesh output requires a resolved Cartesian coordinate frame"
|
|
1056
1595
|
);
|
|
@@ -1059,28 +1598,41 @@ function createMeshConversionCodec(options) {
|
|
|
1059
1598
|
/** Prepares and encodes one geometry while honoring cancellation. */
|
|
1060
1599
|
async *convert(resource, _inspection, signal) {
|
|
1061
1600
|
signal?.throwIfAborted();
|
|
1062
|
-
if ("features" in resource && resource.features) {
|
|
1063
|
-
throw new import_core8.TileConversionError(
|
|
1064
|
-
"MESH_FEATURE_OUTPUT_UNSUPPORTED",
|
|
1065
|
-
"The GLB codec requires an explicit feature metadata writer; use the I3S codec for supported feature-bearing meshes"
|
|
1066
|
-
);
|
|
1067
|
-
}
|
|
1068
1601
|
const geometry = validateMeshGeometry(resource.mesh, true);
|
|
1069
1602
|
const sourcePositions = geometry.attributes.POSITION.value;
|
|
1070
1603
|
const origin = [...resource.origin];
|
|
1071
1604
|
if (origin.length !== 3 || origin.some((value) => !Number.isFinite(value))) {
|
|
1072
|
-
throw new
|
|
1605
|
+
throw new import_core11.TileConversionError(
|
|
1073
1606
|
"MESH_ORIGIN_INVALID",
|
|
1074
1607
|
"Target origin must contain three finite coordinates"
|
|
1075
1608
|
);
|
|
1076
1609
|
}
|
|
1077
|
-
const
|
|
1610
|
+
const transformed = "transformGeometryAsync" in spatialContext ? await spatialContext.transformGeometryAsync(
|
|
1611
|
+
sourcePositions,
|
|
1612
|
+
geometry.attributes.NORMAL?.value,
|
|
1613
|
+
resource.normalReferenceFrame ?? options.normalReferenceFrame
|
|
1614
|
+
) : {
|
|
1615
|
+
positions: spatialContext.transformPositions(sourcePositions),
|
|
1616
|
+
normals: geometry.attributes.NORMAL ? spatialContext.transformNormals(geometry.attributes.NORMAL.value, sourcePositions) : void 0
|
|
1617
|
+
};
|
|
1618
|
+
signal?.throwIfAborted();
|
|
1619
|
+
const targetPositions = transformed.positions;
|
|
1078
1620
|
if (targetPositions.length !== sourcePositions.length || targetPositions.some((value) => !Number.isFinite(value))) {
|
|
1079
|
-
throw new
|
|
1621
|
+
throw new import_core11.TileConversionError(
|
|
1080
1622
|
"MESH_TRANSFORM_INVALID",
|
|
1081
1623
|
"Spatial transformation must return one finite xyz position per vertex"
|
|
1082
1624
|
);
|
|
1083
1625
|
}
|
|
1626
|
+
if (options.autoOrigin) {
|
|
1627
|
+
const minimum2 = [Infinity, Infinity, Infinity];
|
|
1628
|
+
const maximum2 = [-Infinity, -Infinity, -Infinity];
|
|
1629
|
+
for (let index = 0; index < targetPositions.length; index++) {
|
|
1630
|
+
const axis = index % 3;
|
|
1631
|
+
minimum2[axis] = Math.min(minimum2[axis], targetPositions[index]);
|
|
1632
|
+
maximum2[axis] = Math.max(maximum2[axis], targetPositions[index]);
|
|
1633
|
+
}
|
|
1634
|
+
for (let axis = 0; axis < 3; axis++) origin[axis] = minimum2[axis] / 2 + maximum2[axis] / 2;
|
|
1635
|
+
}
|
|
1084
1636
|
const localPositions = new Float32Array(targetPositions.length);
|
|
1085
1637
|
const minimum = [Infinity, Infinity, Infinity];
|
|
1086
1638
|
const maximum = [-Infinity, -Infinity, -Infinity];
|
|
@@ -1097,7 +1649,7 @@ function createMeshConversionCodec(options) {
|
|
|
1097
1649
|
localPositions[index + 2] + origin[2] - targetPositions[index + 2]
|
|
1098
1650
|
);
|
|
1099
1651
|
if (!Number.isFinite(positionError) || positionError > maxPositionError) {
|
|
1100
|
-
throw new
|
|
1652
|
+
throw new import_core11.TileConversionError(
|
|
1101
1653
|
"MESH_POSITION_PRECISION_EXCEEDED",
|
|
1102
1654
|
"Float32 mesh positions exceed the configured reconstruction error budget"
|
|
1103
1655
|
);
|
|
@@ -1107,11 +1659,11 @@ function createMeshConversionCodec(options) {
|
|
|
1107
1659
|
geometry.attributes.POSITION = { value: localPositions, size: 3 };
|
|
1108
1660
|
if (geometry.attributes.NORMAL) {
|
|
1109
1661
|
geometry.attributes.NORMAL = {
|
|
1110
|
-
value:
|
|
1662
|
+
value: transformed.normals,
|
|
1111
1663
|
size: 3
|
|
1112
1664
|
};
|
|
1113
1665
|
}
|
|
1114
|
-
const appearance = { material: resource.material };
|
|
1666
|
+
const appearance = { material: resource.material, features: resource.features };
|
|
1115
1667
|
const encoded = draco ? await encodeDracoMeshTile(geometry, appearance, dracoLibraryOptions) : { glb: encodeMeshTile(geometry, appearance), positions: localPositions };
|
|
1116
1668
|
signal?.throwIfAborted();
|
|
1117
1669
|
for (let index = 0; index < encoded.positions.length; index++) {
|
|
@@ -1147,17 +1699,17 @@ function createMeshConversionCodec(options) {
|
|
|
1147
1699
|
}
|
|
1148
1700
|
|
|
1149
1701
|
// src/v5/mesh-tileset-sink.ts
|
|
1150
|
-
var
|
|
1151
|
-
var
|
|
1702
|
+
var import_core12 = require("@loaders.gl/tile-converter/v5/core");
|
|
1703
|
+
var import_core13 = require("@loaders.gl/tile-converter/v5/core");
|
|
1152
1704
|
function createSingleMeshTilesetSink(options) {
|
|
1153
1705
|
const { geometricError } = options;
|
|
1154
1706
|
if (!Number.isFinite(geometricError) || geometricError < 0) {
|
|
1155
|
-
throw new
|
|
1707
|
+
throw new import_core12.TileConversionError(
|
|
1156
1708
|
"MESH_GEOMETRIC_ERROR_INVALID",
|
|
1157
1709
|
"geometricError must be finite and nonnegative, in meters"
|
|
1158
1710
|
);
|
|
1159
1711
|
}
|
|
1160
|
-
const memorySink = (0,
|
|
1712
|
+
const memorySink = (0, import_core13.createBoundedMemoryTileConversionSink)(options);
|
|
1161
1713
|
let state = "empty";
|
|
1162
1714
|
let completed = false;
|
|
1163
1715
|
return {
|
|
@@ -1167,7 +1719,7 @@ function createSingleMeshTilesetSink(options) {
|
|
|
1167
1719
|
async write(resource, signal) {
|
|
1168
1720
|
signal?.throwIfAborted();
|
|
1169
1721
|
if (state !== "empty") {
|
|
1170
|
-
throw new
|
|
1722
|
+
throw new import_core12.TileConversionError(
|
|
1171
1723
|
"SINGLE_MESH_SINK_UNAVAILABLE",
|
|
1172
1724
|
"The sink accepts exactly one mesh before finalization or abort"
|
|
1173
1725
|
);
|
|
@@ -1186,7 +1738,7 @@ function createSingleMeshTilesetSink(options) {
|
|
|
1186
1738
|
},
|
|
1187
1739
|
async finalize(report) {
|
|
1188
1740
|
if (state !== "written") {
|
|
1189
|
-
throw new
|
|
1741
|
+
throw new import_core12.TileConversionError(
|
|
1190
1742
|
"SINGLE_MESH_SINK_INCOMPLETE",
|
|
1191
1743
|
"Finalization requires exactly one successfully written mesh"
|
|
1192
1744
|
);
|
|
@@ -1207,13 +1759,13 @@ function createMeshTileset(resource, geometricError) {
|
|
|
1207
1759
|
const outputCrs = reference.targetCrs || reference.sourceCrs;
|
|
1208
1760
|
const heightReference = reference.targetHeightReference === "native" ? reference.heightReference : reference.targetHeightReference;
|
|
1209
1761
|
if (!["native", "transformed"].includes(reference.status) || outputCrs !== "EPSG:4978" || heightReference !== "ellipsoidal" || reference.status === "native" && (reference.coordinateFrame !== "geocentric" || reference.axisOrder !== "xyz" || reference.verticalUnitScale !== 1 || reference.units?.some((unit) => unit !== "meter"))) {
|
|
1210
|
-
throw new
|
|
1762
|
+
throw new import_core12.TileConversionError(
|
|
1211
1763
|
"MESH_TILESET_FRAME_UNSUPPORTED",
|
|
1212
1764
|
"Single-mesh tilesets require resolved EPSG:4978 xyz coordinates in meters with ellipsoidal heights"
|
|
1213
1765
|
);
|
|
1214
1766
|
}
|
|
1215
1767
|
if (!Number.isFinite(resource.maximumPositionError) || resource.maximumPositionError < 0 || resource.maximumPositionError > geometricError) {
|
|
1216
|
-
throw new
|
|
1768
|
+
throw new import_core12.TileConversionError(
|
|
1217
1769
|
"MESH_GEOMETRIC_ERROR_INVALID",
|
|
1218
1770
|
"geometricError must include the measured position reconstruction error"
|
|
1219
1771
|
);
|
|
@@ -1221,7 +1773,7 @@ function createMeshTileset(resource, geometricError) {
|
|
|
1221
1773
|
const origin = [...resource.origin];
|
|
1222
1774
|
const [minimum, maximum] = resource.localBoundingBox.map((bound) => [...bound]);
|
|
1223
1775
|
if (origin.length !== 3 || minimum.length !== 3 || maximum.length !== 3 || [...origin, ...minimum, ...maximum].some((value) => !Number.isFinite(value)) || minimum.some((value, axis) => value > maximum[axis])) {
|
|
1224
|
-
throw new
|
|
1776
|
+
throw new import_core12.TileConversionError(
|
|
1225
1777
|
"MESH_TILESET_BOUNDS_INVALID",
|
|
1226
1778
|
"Origin and local bounds must be finite xyz coordinates with ordered bounds"
|
|
1227
1779
|
);
|
|
@@ -1260,12 +1812,12 @@ function createMeshTileset(resource, geometricError) {
|
|
|
1260
1812
|
function createMeshTilesetSink(options) {
|
|
1261
1813
|
const { geometricError, maxMeshes } = options;
|
|
1262
1814
|
if (!Number.isFinite(geometricError) || geometricError < 0 || !Number.isSafeInteger(maxMeshes) || maxMeshes < 1) {
|
|
1263
|
-
throw new
|
|
1815
|
+
throw new import_core12.TileConversionError(
|
|
1264
1816
|
"MESH_TILESET_OPTIONS_INVALID",
|
|
1265
1817
|
"A leaf collection requires finite nonnegative geometricError and positive maxMeshes"
|
|
1266
1818
|
);
|
|
1267
1819
|
}
|
|
1268
|
-
const memory = (0,
|
|
1820
|
+
const memory = (0, import_core13.createBoundedMemoryTileConversionSink)(options);
|
|
1269
1821
|
const children = [];
|
|
1270
1822
|
const identifiers = /* @__PURE__ */ new Set();
|
|
1271
1823
|
const minimum = [Infinity, Infinity, Infinity];
|
|
@@ -1279,12 +1831,12 @@ function createMeshTilesetSink(options) {
|
|
|
1279
1831
|
async write(resource, signal) {
|
|
1280
1832
|
signal?.throwIfAborted();
|
|
1281
1833
|
if (state !== "open")
|
|
1282
|
-
throw new
|
|
1834
|
+
throw new import_core12.TileConversionError(
|
|
1283
1835
|
"MESH_TILESET_SINK_UNAVAILABLE",
|
|
1284
1836
|
"The mesh sink is busy or closed"
|
|
1285
1837
|
);
|
|
1286
1838
|
if (children.length >= maxMeshes || identifiers.has(resource.id))
|
|
1287
|
-
throw new
|
|
1839
|
+
throw new import_core12.TileConversionError(
|
|
1288
1840
|
"MESH_TILESET_PLACEMENT_INVALID",
|
|
1289
1841
|
"Mesh count exceeds maxMeshes or a placement identifier is repeated"
|
|
1290
1842
|
);
|
|
@@ -1296,7 +1848,7 @@ function createMeshTilesetSink(options) {
|
|
|
1296
1848
|
(value, axis) => value + resource.origin[axis]
|
|
1297
1849
|
);
|
|
1298
1850
|
if ([...lower, ...upper].some((value) => !Number.isFinite(value)))
|
|
1299
|
-
throw new
|
|
1851
|
+
throw new import_core12.TileConversionError(
|
|
1300
1852
|
"MESH_TILESET_BOUNDS_INVALID",
|
|
1301
1853
|
"Absolute bounds must be finite"
|
|
1302
1854
|
);
|
|
@@ -1318,7 +1870,7 @@ function createMeshTilesetSink(options) {
|
|
|
1318
1870
|
/** Writes the enclosing root only when every leaf has completed. */
|
|
1319
1871
|
async finalize(report) {
|
|
1320
1872
|
if (state !== "open" || !children.length)
|
|
1321
|
-
throw new
|
|
1873
|
+
throw new import_core12.TileConversionError(
|
|
1322
1874
|
"MESH_TILESET_SINK_INCOMPLETE",
|
|
1323
1875
|
"Finalization requires written mesh leaves"
|
|
1324
1876
|
);
|
|
@@ -1327,7 +1879,7 @@ function createMeshTilesetSink(options) {
|
|
|
1327
1879
|
const halfSize = minimum.map((value, axis) => maximum[axis] / 2 - value / 2);
|
|
1328
1880
|
const rootGeometricError = Math.max(geometricError, 2 * Math.hypot(...halfSize));
|
|
1329
1881
|
if (!Number.isFinite(rootGeometricError))
|
|
1330
|
-
throw new
|
|
1882
|
+
throw new import_core12.TileConversionError(
|
|
1331
1883
|
"MESH_TILESET_BOUNDS_INVALID",
|
|
1332
1884
|
"Root bounds must have a finite diagonal"
|
|
1333
1885
|
);
|
|
@@ -1379,7 +1931,7 @@ function createMeshTilesetSink(options) {
|
|
|
1379
1931
|
// src/v5/conversion-archive.ts
|
|
1380
1932
|
var import_d_tiles_archive_writer = require("@loaders.gl/3d-tiles/3d-tiles-archive-writer");
|
|
1381
1933
|
var import_i3s_slpk_writer = require("@loaders.gl/i3s/i3s-slpk-writer");
|
|
1382
|
-
var
|
|
1934
|
+
var import_core14 = require("@loaders.gl/tile-converter/v5/core");
|
|
1383
1935
|
async function createTileConversionArchive(files, options) {
|
|
1384
1936
|
const { resources, format, maxArchiveBytes, signal } = prepareArchiveInput(files, options);
|
|
1385
1937
|
const writer = format === "3tz" ? import_d_tiles_archive_writer.Tiles3DArchiveWriter : import_i3s_slpk_writer.SLPKWriter;
|
|
@@ -1394,17 +1946,17 @@ async function* encodeTileConversionArchiveInBatches(files, options) {
|
|
|
1394
1946
|
function prepareArchiveInput(files, options) {
|
|
1395
1947
|
const { format, maxArchiveBytes, signal } = options;
|
|
1396
1948
|
if (!Number.isSafeInteger(maxArchiveBytes) || maxArchiveBytes < 0) {
|
|
1397
|
-
throw new
|
|
1949
|
+
throw new import_core14.TileConversionError(
|
|
1398
1950
|
"INVALID_ARCHIVE_BYTE_LIMIT",
|
|
1399
1951
|
"Archive byte limit must be a non-negative safe integer"
|
|
1400
1952
|
);
|
|
1401
1953
|
}
|
|
1402
1954
|
if (format !== "3tz" && format !== "slpk") {
|
|
1403
|
-
throw new
|
|
1955
|
+
throw new import_core14.TileConversionError("INVALID_ARCHIVE_FORMAT", "Expected 3tz or slpk archive format");
|
|
1404
1956
|
}
|
|
1405
1957
|
const resources = Object.fromEntries(files.map((file) => [file.resourceId, file.blob]));
|
|
1406
1958
|
if (Object.keys(resources).length !== files.length) {
|
|
1407
|
-
throw new
|
|
1959
|
+
throw new import_core14.TileConversionError(
|
|
1408
1960
|
"DUPLICATE_ARCHIVE_RESOURCE",
|
|
1409
1961
|
"Archive resource identifiers must be unique"
|
|
1410
1962
|
);
|
|
@@ -1414,17 +1966,17 @@ function prepareArchiveInput(files, options) {
|
|
|
1414
1966
|
|
|
1415
1967
|
// src/v5/mesh-tileset-archive.ts
|
|
1416
1968
|
var import_d_tiles_archive_writer2 = require("@loaders.gl/3d-tiles/3d-tiles-archive-writer");
|
|
1417
|
-
var
|
|
1969
|
+
var import_core15 = require("@loaders.gl/tile-converter/v5/core");
|
|
1418
1970
|
async function createSingleMeshTilesetArchive(files, options) {
|
|
1419
1971
|
const { maxArchiveBytes, signal } = options;
|
|
1420
1972
|
if (!Number.isSafeInteger(maxArchiveBytes) || maxArchiveBytes < 0) {
|
|
1421
|
-
throw new
|
|
1973
|
+
throw new import_core15.TileConversionError(
|
|
1422
1974
|
"INVALID_ARCHIVE_BYTE_LIMIT",
|
|
1423
1975
|
"Archive byte limit must be a non-negative safe integer"
|
|
1424
1976
|
);
|
|
1425
1977
|
}
|
|
1426
1978
|
if (files.length !== 2 || !files.some((file) => file.resourceId === "mesh.glb") || !files.some((file) => file.resourceId === "tileset.json")) {
|
|
1427
|
-
throw new
|
|
1979
|
+
throw new import_core15.TileConversionError(
|
|
1428
1980
|
"INVALID_SINGLE_MESH_ARCHIVE",
|
|
1429
1981
|
"Expected mesh.glb and tileset.json from a finalized single-mesh sink"
|
|
1430
1982
|
);
|
|
@@ -1435,12 +1987,13 @@ async function createSingleMeshTilesetArchive(files, options) {
|
|
|
1435
1987
|
}
|
|
1436
1988
|
|
|
1437
1989
|
// src/v5/mesh-source.ts
|
|
1438
|
-
var
|
|
1439
|
-
var
|
|
1440
|
-
var
|
|
1990
|
+
var import_core18 = require("@math.gl/core");
|
|
1991
|
+
var import_core19 = require("@loaders.gl/tile-converter/v5/core");
|
|
1992
|
+
var import_core20 = require("@loaders.gl/tile-converter/v5/core");
|
|
1441
1993
|
|
|
1442
1994
|
// src/v5/mesh-source-texture.ts
|
|
1443
|
-
var
|
|
1995
|
+
var import_images2 = require("@loaders.gl/images");
|
|
1996
|
+
var import_core16 = require("@loaders.gl/tile-converter/v5/core");
|
|
1444
1997
|
function mapMeshSourceTexture(textureInfo) {
|
|
1445
1998
|
const texture = textureInfo?.texture;
|
|
1446
1999
|
const image = texture?.source;
|
|
@@ -1467,7 +2020,7 @@ function mapMeshSourceTexture(textureInfo) {
|
|
|
1467
2020
|
"extras"
|
|
1468
2021
|
].includes(name)
|
|
1469
2022
|
))) {
|
|
1470
|
-
throw new
|
|
2023
|
+
throw new import_core16.TileConversionError(
|
|
1471
2024
|
"MESH_SOURCE_TEXTURE_UNSUPPORTED",
|
|
1472
2025
|
"Base-color textures require a PNG/JPEG bufferView or base64 data URI, TEXCOORD_0, glTF sampling and an optional KHR_texture_transform on TEXCOORD_0"
|
|
1473
2026
|
);
|
|
@@ -1496,7 +2049,7 @@ function mapMeshSourceImage(image) {
|
|
|
1496
2049
|
}
|
|
1497
2050
|
const match = typeof image.uri === "string" ? /^data:(image\/(?:png|jpeg));base64,([A-Za-z0-9+/]+={0,2})$/.exec(image.uri) : null;
|
|
1498
2051
|
if (!match || image.mimeType !== void 0 && image.mimeType !== match[1]) {
|
|
1499
|
-
throw new
|
|
2052
|
+
throw new import_core16.TileConversionError(
|
|
1500
2053
|
"MESH_SOURCE_TEXTURE_UNSUPPORTED",
|
|
1501
2054
|
"Inline images require a PNG/JPEG base64 data URI and matching optional mimeType"
|
|
1502
2055
|
);
|
|
@@ -1505,35 +2058,66 @@ function mapMeshSourceImage(image) {
|
|
|
1505
2058
|
try {
|
|
1506
2059
|
encodedBytes = atob(match[2]);
|
|
1507
2060
|
} catch {
|
|
1508
|
-
throw new
|
|
2061
|
+
throw new import_core16.TileConversionError("MESH_SOURCE_TEXTURE_UNSUPPORTED", "Invalid inline image base64");
|
|
1509
2062
|
}
|
|
1510
2063
|
return {
|
|
1511
2064
|
data: Uint8Array.from(encodedBytes, (character) => character.charCodeAt(0)),
|
|
1512
2065
|
mimeType: match[1]
|
|
1513
2066
|
};
|
|
1514
2067
|
}
|
|
2068
|
+
async function resolveMeshSourceTexture(textureInfo, readExternalResource, contentUri, signal) {
|
|
2069
|
+
const image = textureInfo?.texture?.source;
|
|
2070
|
+
if (!image || image.uri === void 0 || image.uri.startsWith("data:"))
|
|
2071
|
+
return mapMeshSourceTexture(textureInfo);
|
|
2072
|
+
if (!readExternalResource || image.bufferView !== void 0)
|
|
2073
|
+
throw new import_core16.TileConversionError(
|
|
2074
|
+
"MESH_SOURCE_TEXTURE_UNSUPPORTED",
|
|
2075
|
+
"External images require an explicit bounded resource reader"
|
|
2076
|
+
);
|
|
2077
|
+
signal?.throwIfAborted();
|
|
2078
|
+
const data = await readExternalResource(image.uri, contentUri, signal);
|
|
2079
|
+
signal?.throwIfAborted();
|
|
2080
|
+
let metadata;
|
|
2081
|
+
try {
|
|
2082
|
+
metadata = (0, import_images2.getBinaryImageMetadata)(new DataView(data.buffer, data.byteOffset, data.byteLength));
|
|
2083
|
+
} catch {
|
|
2084
|
+
}
|
|
2085
|
+
if (!metadata || !["image/png", "image/jpeg"].includes(metadata.mimeType) || image.mimeType !== void 0 && image.mimeType !== metadata.mimeType)
|
|
2086
|
+
throw new import_core16.TileConversionError(
|
|
2087
|
+
"MESH_SOURCE_TEXTURE_UNSUPPORTED",
|
|
2088
|
+
"External images require matching PNG/JPEG headers"
|
|
2089
|
+
);
|
|
2090
|
+
const resolved = {
|
|
2091
|
+
...textureInfo,
|
|
2092
|
+
texture: {
|
|
2093
|
+
...textureInfo.texture,
|
|
2094
|
+
source: { ...image, uri: void 0, mimeType: metadata.mimeType, bufferView: { data } }
|
|
2095
|
+
}
|
|
2096
|
+
};
|
|
2097
|
+
return mapMeshSourceTexture(resolved);
|
|
2098
|
+
}
|
|
1515
2099
|
|
|
1516
2100
|
// src/v5/mesh-source-features.ts
|
|
1517
|
-
var
|
|
2101
|
+
var import_core17 = require("@loaders.gl/tile-converter/v5/core");
|
|
1518
2102
|
function extractMeshFeatures(gltf, primitive, payload, mesh, options) {
|
|
1519
2103
|
const declarations = primitive.extensions?.EXT_mesh_features?.featureIds;
|
|
1520
2104
|
const metadata = gltf.extensions?.EXT_structural_metadata;
|
|
1521
2105
|
const legacy = primitive.attributes._BATCHID;
|
|
1522
2106
|
if (!declarations && !legacy && !metadata && !Object.keys(payload.batchTableJson || {}).length && !payload.header?.batchLength) {
|
|
1523
2107
|
if (Object.keys(primitive.attributes).some((name) => /^_FEATURE_ID_\d+$/.test(name)))
|
|
1524
|
-
throw new
|
|
2108
|
+
throw new import_core17.TileConversionError(
|
|
1525
2109
|
"MESH_FEATURE_MAPPING_REQUIRED",
|
|
1526
2110
|
"Feature attributes require an EXT_mesh_features declaration"
|
|
1527
2111
|
);
|
|
1528
2112
|
return void 0;
|
|
1529
2113
|
}
|
|
1530
2114
|
if (!options)
|
|
1531
|
-
throw new
|
|
2115
|
+
throw new import_core17.TileConversionError(
|
|
1532
2116
|
"MESH_FEATURE_SCHEMA_REQUIRED",
|
|
1533
2117
|
"Feature extraction requires an explicit Arrow schema and class mapping"
|
|
1534
2118
|
);
|
|
1535
2119
|
if (!options.metadataClass)
|
|
1536
|
-
throw new
|
|
2120
|
+
throw new import_core17.TileConversionError(
|
|
1537
2121
|
"MESH_FEATURE_CLASS_REQUIRED",
|
|
1538
2122
|
"Feature metadata class must be explicit"
|
|
1539
2123
|
);
|
|
@@ -1542,20 +2126,20 @@ function extractMeshFeatures(gltf, primitive, payload, mesh, options) {
|
|
|
1542
2126
|
let properties;
|
|
1543
2127
|
if (declarations) {
|
|
1544
2128
|
if (legacy || declarations.length !== 1 || metadata?.propertyTables?.length !== 1 || metadata.propertyTextures?.length || metadata.propertyAttributes?.length)
|
|
1545
|
-
throw new
|
|
2129
|
+
throw new import_core17.TileConversionError(
|
|
1546
2130
|
"MESH_FEATURE_MAPPING_UNSUPPORTED",
|
|
1547
2131
|
"Only one attribute-backed feature set and one property table are supported"
|
|
1548
2132
|
);
|
|
1549
2133
|
const declaration = declarations[0];
|
|
1550
2134
|
if (!Number.isInteger(declaration.attribute) || declaration.attribute < 0 || declaration.propertyTable !== 0 || declaration.texture || declaration.implicit || declaration.nullFeatureId !== void 0)
|
|
1551
|
-
throw new
|
|
2135
|
+
throw new import_core17.TileConversionError(
|
|
1552
2136
|
"MESH_FEATURE_MAPPING_UNSUPPORTED",
|
|
1553
2137
|
"Features require an attribute and property table 0 without null/texture/implicit IDs"
|
|
1554
2138
|
);
|
|
1555
2139
|
attribute = primitive.attributes[`_FEATURE_ID_${declaration.attribute}`];
|
|
1556
2140
|
const table = metadata.propertyTables[0];
|
|
1557
2141
|
if (table.class !== options.metadataClass || table.count !== declaration.featureCount)
|
|
1558
|
-
throw new
|
|
2142
|
+
throw new import_core17.TileConversionError(
|
|
1559
2143
|
"MESH_FEATURE_CLASS_MISMATCH",
|
|
1560
2144
|
"Feature class and declared table counts must agree"
|
|
1561
2145
|
);
|
|
@@ -1563,33 +2147,49 @@ function extractMeshFeatures(gltf, primitive, payload, mesh, options) {
|
|
|
1563
2147
|
properties = {};
|
|
1564
2148
|
const classProperties = metadata.schema?.classes?.[table.class]?.properties;
|
|
1565
2149
|
if (!classProperties)
|
|
1566
|
-
throw new
|
|
2150
|
+
throw new import_core17.TileConversionError(
|
|
1567
2151
|
"MESH_FEATURE_SCHEMA_REQUIRED",
|
|
1568
2152
|
"A decoded inline structural metadata class is required"
|
|
1569
2153
|
);
|
|
1570
2154
|
if (Object.keys(table.properties || {}).some((name) => !Object.hasOwn(classProperties, name)))
|
|
1571
|
-
throw new
|
|
2155
|
+
throw new import_core17.TileConversionError(
|
|
1572
2156
|
"MESH_FEATURE_PROPERTY_UNMAPPED",
|
|
1573
2157
|
"Property table columns must be declared in the metadata class"
|
|
1574
2158
|
);
|
|
1575
2159
|
for (const [name, property] of Object.entries(classProperties)) {
|
|
1576
2160
|
const column = table.properties?.[name];
|
|
1577
|
-
if (!["SCALAR", "STRING"].includes(property.type) || property.array || property.noData !== void 0 || ["INT64", "UINT64"].includes(property.componentType || "") && (property.normalized || property.offset !== void 0 || property.scale !== void 0 || column?.offset !== void 0 || column?.scale !== void 0))
|
|
1578
|
-
throw new
|
|
2161
|
+
if (!["SCALAR", "STRING"].includes(property.type) || property.array || property.noData !== void 0 && (property.normalized || property.offset !== void 0 || property.scale !== void 0 || column?.offset !== void 0 || column?.scale !== void 0) || ["INT64", "UINT64"].includes(property.componentType || "") && (property.normalized || property.offset !== void 0 || property.scale !== void 0 || column?.offset !== void 0 || column?.scale !== void 0))
|
|
2162
|
+
throw new import_core17.TileConversionError(
|
|
1579
2163
|
"MESH_FEATURE_PROPERTY_UNSUPPORTED",
|
|
1580
2164
|
`Property ${name} requires a separately qualified semantic mapping`
|
|
1581
2165
|
);
|
|
1582
2166
|
const data = column?.data;
|
|
1583
2167
|
if (data === void 0 && property.default === void 0)
|
|
1584
|
-
throw new
|
|
2168
|
+
throw new import_core17.TileConversionError(
|
|
1585
2169
|
"MESH_FEATURE_PROPERTY_UNAVAILABLE",
|
|
1586
2170
|
`Property ${name} has not been decoded`
|
|
1587
2171
|
);
|
|
1588
|
-
|
|
2172
|
+
const decoded = data ?? Array.from({ length: count }, () => property.default);
|
|
2173
|
+
if (property.noData === void 0 || data === void 0) {
|
|
2174
|
+
properties[name] = decoded;
|
|
2175
|
+
continue;
|
|
2176
|
+
}
|
|
2177
|
+
const sentinel = property.noData;
|
|
2178
|
+
const integer64 = ["INT64", "UINT64"].includes(property.componentType || "");
|
|
2179
|
+
if ((property.type === "STRING" ? typeof sentinel !== "string" : typeof sentinel !== "number" || !Number.isFinite(sentinel) || integer64 && !Number.isSafeInteger(sentinel)) || !decoded || !Array.isArray(decoded) && !ArrayBuffer.isView(decoded) || decoded.length !== count)
|
|
2180
|
+
throw new import_core17.TileConversionError(
|
|
2181
|
+
"MESH_FEATURE_PROPERTY_UNSUPPORTED",
|
|
2182
|
+
`Property ${name} requires a valid decoded noData mapping`
|
|
2183
|
+
);
|
|
2184
|
+
const comparable = integer64 ? BigInt(sentinel) : sentinel;
|
|
2185
|
+
properties[name] = Array.from(
|
|
2186
|
+
decoded,
|
|
2187
|
+
(value) => value === comparable ? property.default ?? null : value
|
|
2188
|
+
);
|
|
1589
2189
|
}
|
|
1590
2190
|
} else {
|
|
1591
2191
|
if (metadata)
|
|
1592
|
-
throw new
|
|
2192
|
+
throw new import_core17.TileConversionError(
|
|
1593
2193
|
"MESH_FEATURE_MAPPING_UNSUPPORTED",
|
|
1594
2194
|
"Structural metadata requires an attribute-backed feature declaration"
|
|
1595
2195
|
);
|
|
@@ -1600,7 +2200,7 @@ function extractMeshFeatures(gltf, primitive, payload, mesh, options) {
|
|
|
1600
2200
|
if (!Number.isSafeInteger(count) || count <= 0 || !attribute || attribute.components !== 1 || attribute.normalized || attribute.value.length !== vertexCount || Array.from(attribute.value).some(
|
|
1601
2201
|
(value) => !Number.isSafeInteger(value) || value < 0 || value >= count
|
|
1602
2202
|
))
|
|
1603
|
-
throw new
|
|
2203
|
+
throw new import_core17.TileConversionError(
|
|
1604
2204
|
"MESH_FEATURE_IDS_INVALID",
|
|
1605
2205
|
"Feature IDs must be one valid property-table row index per vertex"
|
|
1606
2206
|
);
|
|
@@ -1608,20 +2208,20 @@ function extractMeshFeatures(gltf, primitive, payload, mesh, options) {
|
|
|
1608
2208
|
if (Object.keys(primitive.attributes).some(
|
|
1609
2209
|
(name) => (name === "_BATCHID" || /^_FEATURE_ID_\d+$/.test(name)) && name !== selectedAttribute
|
|
1610
2210
|
))
|
|
1611
|
-
throw new
|
|
2211
|
+
throw new import_core17.TileConversionError(
|
|
1612
2212
|
"MESH_FEATURE_MAPPING_UNSUPPORTED",
|
|
1613
2213
|
"Additional feature ID sets require separate mappings"
|
|
1614
2214
|
);
|
|
1615
2215
|
for (const [name, values] of Object.entries(properties)) {
|
|
1616
2216
|
if (!values || !Array.isArray(values) && !ArrayBuffer.isView(values) || values.length !== count)
|
|
1617
|
-
throw new
|
|
2217
|
+
throw new import_core17.TileConversionError(
|
|
1618
2218
|
"MESH_FEATURE_PROPERTY_UNAVAILABLE",
|
|
1619
2219
|
`Property ${name} must be a decoded column matching the table count`
|
|
1620
2220
|
);
|
|
1621
2221
|
}
|
|
1622
2222
|
const sourceIdentifier = options.sourceFeatureIdProperty;
|
|
1623
2223
|
if (sourceIdentifier && !properties[sourceIdentifier])
|
|
1624
|
-
throw new
|
|
2224
|
+
throw new import_core17.TileConversionError(
|
|
1625
2225
|
"MESH_FEATURE_ID_PROPERTY_REQUIRED",
|
|
1626
2226
|
"Stable source identifier property is missing"
|
|
1627
2227
|
);
|
|
@@ -1638,13 +2238,14 @@ function extractMeshFeatures(gltf, primitive, payload, mesh, options) {
|
|
|
1638
2238
|
const indices = mesh.indices?.value;
|
|
1639
2239
|
const triangleFeatureIndices = new Uint32Array((indices?.length ?? vertexCount) / 3);
|
|
1640
2240
|
for (let triangle = 0; triangle < triangleFeatureIndices.length; triangle++) {
|
|
1641
|
-
const
|
|
1642
|
-
(corner) =>
|
|
2241
|
+
const corners = [0, 1, 2].map(
|
|
2242
|
+
(corner) => indices?.[triangle * 3 + corner] ?? triangle * 3 + corner
|
|
1643
2243
|
);
|
|
1644
|
-
|
|
1645
|
-
|
|
2244
|
+
const rows = corners.map((corner) => attribute.value[corner]);
|
|
2245
|
+
if (rows.some((row) => row !== rows[0]) && new Set(corners).size === 3)
|
|
2246
|
+
throw new import_core17.TileConversionError(
|
|
1646
2247
|
"MESH_FEATURE_TRIANGLE_MIXED",
|
|
1647
|
-
"All vertices of a triangle must reference the same feature row"
|
|
2248
|
+
"All vertices of a nondegenerate triangle must reference the same feature row"
|
|
1648
2249
|
);
|
|
1649
2250
|
triangleFeatureIndices[triangle] = Number(rows[0]);
|
|
1650
2251
|
}
|
|
@@ -1658,20 +2259,20 @@ function extractMeshFeatures(gltf, primitive, payload, mesh, options) {
|
|
|
1658
2259
|
|
|
1659
2260
|
// src/v5/mesh-source.ts
|
|
1660
2261
|
function createMeshTilesetConversionSource(tileset, options = {}) {
|
|
1661
|
-
const source = (0,
|
|
2262
|
+
const source = (0, import_core20.createTilesetConversionSource)(tileset, options);
|
|
1662
2263
|
return {
|
|
1663
2264
|
/** Validates the source frame and supported dataset metadata before traversal. */
|
|
1664
2265
|
async inspect(signal) {
|
|
1665
2266
|
const metadata = await source.inspect(signal);
|
|
1666
2267
|
const reference = metadata.spatialReference;
|
|
1667
2268
|
if (metadata.type !== "TILES3D" || !reference || reference.status !== "native" || reference.sourceCrs !== "EPSG:4978" || reference.heightReference !== "ellipsoidal" || reference.axisOrder !== "xyz" || reference.verticalUnitScale !== 1 || reference.units?.some((unit) => unit !== "meter")) {
|
|
1668
|
-
throw new
|
|
2269
|
+
throw new import_core19.TileConversionError(
|
|
1669
2270
|
"MESH_SOURCE_FRAME_UNSUPPORTED",
|
|
1670
2271
|
"Mesh extraction requires native EPSG:4978 xyz coordinates in meters and ellipsoidal heights"
|
|
1671
2272
|
);
|
|
1672
2273
|
}
|
|
1673
2274
|
if (metadata.tileset.metadata || metadata.tileset.groups?.length) {
|
|
1674
|
-
throw new
|
|
2275
|
+
throw new import_core19.TileConversionError(
|
|
1675
2276
|
"MESH_SOURCE_METADATA_UNSUPPORTED",
|
|
1676
2277
|
"Tileset/group metadata requires a separate mapping"
|
|
1677
2278
|
);
|
|
@@ -1683,25 +2284,25 @@ function createMeshTilesetConversionSource(tileset, options = {}) {
|
|
|
1683
2284
|
for await (const item of source.read(inspection, signal)) {
|
|
1684
2285
|
const placementReference = item.tile.header._spatialReference || tileset.spatialReference;
|
|
1685
2286
|
if (placementReference && (placementReference.status !== "native" || placementReference.sourceCrs !== "EPSG:4978"))
|
|
1686
|
-
throw new
|
|
2287
|
+
throw new import_core19.TileConversionError(
|
|
1687
2288
|
"MESH_SOURCE_FRAME_UNSUPPORTED",
|
|
1688
2289
|
"Nested mesh placements require native ECEF coordinates"
|
|
1689
2290
|
);
|
|
1690
2291
|
if (item.tile.header.metadata || item.tile.header.implicitMetadata)
|
|
1691
|
-
throw new
|
|
2292
|
+
throw new import_core19.TileConversionError(
|
|
1692
2293
|
"MESH_SOURCE_METADATA_UNSUPPORTED",
|
|
1693
2294
|
"Tile metadata requires a separate mapping"
|
|
1694
2295
|
);
|
|
1695
2296
|
for (const content of item.contents) {
|
|
1696
2297
|
signal?.throwIfAborted();
|
|
1697
2298
|
if (content.metadata || content.group !== void 0)
|
|
1698
|
-
throw new
|
|
2299
|
+
throw new import_core19.TileConversionError(
|
|
1699
2300
|
"MESH_SOURCE_METADATA_UNSUPPORTED",
|
|
1700
2301
|
"Content metadata requires a separate mapping"
|
|
1701
2302
|
);
|
|
1702
2303
|
const payload = content.payload;
|
|
1703
2304
|
if (!payload?.gltf || !["glTF", "gltf", "b3dm"].includes(payload.type || content.type || "")) {
|
|
1704
|
-
throw new
|
|
2305
|
+
throw new import_core19.TileConversionError(
|
|
1705
2306
|
"MESH_SOURCE_CONTENT_UNSUPPORTED",
|
|
1706
2307
|
"Mesh extraction requires decoded GLB/glTF or B3DM content"
|
|
1707
2308
|
);
|
|
@@ -1710,40 +2311,43 @@ function createMeshTilesetConversionSource(tileset, options = {}) {
|
|
|
1710
2311
|
if (gltf.animations?.length || gltf.skins?.length || Object.keys(gltf.extensions || {}).some(
|
|
1711
2312
|
(name) => !["EXT_structural_metadata", "CESIUM_RTC"].includes(name)
|
|
1712
2313
|
)) {
|
|
1713
|
-
throw new
|
|
2314
|
+
throw new import_core19.TileConversionError(
|
|
1714
2315
|
"MESH_SOURCE_SCENE_UNSUPPORTED",
|
|
1715
2316
|
"Animated/skinned scenes and unmapped scene extensions are unsupported"
|
|
1716
2317
|
);
|
|
1717
2318
|
}
|
|
1718
2319
|
const axis = payload.gltfUpAxis || "Y";
|
|
1719
2320
|
if (!["X", "Y", "Z"].includes(axis))
|
|
1720
|
-
throw new
|
|
1721
|
-
const placement = new
|
|
2321
|
+
throw new import_core19.TileConversionError("MESH_SOURCE_AXIS_UNSUPPORTED", "Unknown glTF up axis");
|
|
2322
|
+
const placement = new import_core18.Matrix4(item.tile.computedTransform);
|
|
1722
2323
|
const rtc = payload.rtcCenter || gltf.extensions?.CESIUM_RTC?.center;
|
|
1723
2324
|
if (rtc) {
|
|
1724
2325
|
if (rtc.length !== 3 || rtc.some((value) => !Number.isFinite(value)))
|
|
1725
|
-
throw new
|
|
2326
|
+
throw new import_core19.TileConversionError(
|
|
1726
2327
|
"MESH_SOURCE_TRANSFORM_INVALID",
|
|
1727
2328
|
"RTC center must contain finite xyz coordinates"
|
|
1728
2329
|
);
|
|
1729
2330
|
placement.translate(rtc);
|
|
1730
2331
|
}
|
|
1731
2332
|
if (axis === "Y")
|
|
1732
|
-
placement.multiplyRight(new
|
|
2333
|
+
placement.multiplyRight(new import_core18.Matrix4([1, 0, 0, 0, 0, 0, 1, 0, 0, -1, 0, 0, 0, 0, 0, 1]));
|
|
1733
2334
|
if (axis === "X")
|
|
1734
|
-
placement.multiplyRight(new
|
|
2335
|
+
placement.multiplyRight(new import_core18.Matrix4([0, 0, 1, 0, 0, 1, 0, 0, -1, 0, 0, 0, 0, 0, 0, 1]));
|
|
1735
2336
|
const roots = gltf.scene?.nodes || (gltf.scenes?.length === 1 ? gltf.scenes[0].nodes : void 0);
|
|
1736
2337
|
if (!roots?.length)
|
|
1737
|
-
throw new
|
|
2338
|
+
throw new import_core19.TileConversionError(
|
|
1738
2339
|
"MESH_SOURCE_SCENE_UNSUPPORTED",
|
|
1739
2340
|
"A default scene or exactly one scene is required"
|
|
1740
2341
|
);
|
|
1741
2342
|
let primitiveIndex = 0;
|
|
1742
2343
|
for (const selected of traverseMeshNodes(roots, placement)) {
|
|
1743
|
-
const resource = extractPrimitive(
|
|
2344
|
+
const resource = await extractPrimitive(
|
|
1744
2345
|
selected.primitive,
|
|
1745
2346
|
selected.transform,
|
|
1746
|
-
`${item.tile.id}/${content.index}/${primitiveIndex++}
|
|
2347
|
+
`${item.tile.id}/${content.index}/${primitiveIndex++}`,
|
|
2348
|
+
options.readExternalResource,
|
|
2349
|
+
content.uri,
|
|
2350
|
+
signal
|
|
1747
2351
|
);
|
|
1748
2352
|
const features = extractMeshFeatures(
|
|
1749
2353
|
gltf,
|
|
@@ -1755,7 +2359,7 @@ function createMeshTilesetConversionSource(tileset, options = {}) {
|
|
|
1755
2359
|
yield { ...resource, features };
|
|
1756
2360
|
}
|
|
1757
2361
|
if (!primitiveIndex)
|
|
1758
|
-
throw new
|
|
2362
|
+
throw new import_core19.TileConversionError(
|
|
1759
2363
|
"MESH_SOURCE_CONTENT_EMPTY",
|
|
1760
2364
|
"Declared content contains no supported mesh primitive"
|
|
1761
2365
|
);
|
|
@@ -1767,16 +2371,16 @@ function createMeshTilesetConversionSource(tileset, options = {}) {
|
|
|
1767
2371
|
function* traverseMeshNodes(nodes, parent, ancestors = /* @__PURE__ */ new Set()) {
|
|
1768
2372
|
for (const node of nodes) {
|
|
1769
2373
|
if (ancestors.has(node) || node.skin || node.weights || Object.keys(node.extensions || {}).length)
|
|
1770
|
-
throw new
|
|
2374
|
+
throw new import_core19.TileConversionError(
|
|
1771
2375
|
"MESH_SOURCE_NODE_UNSUPPORTED",
|
|
1772
2376
|
"Cyclic, skinned, morphed, or extended nodes are unsupported"
|
|
1773
2377
|
);
|
|
1774
|
-
const transform = new
|
|
1775
|
-
node.matrix ? new
|
|
2378
|
+
const transform = new import_core18.Matrix4(parent).multiplyRight(
|
|
2379
|
+
node.matrix ? new import_core18.Matrix4(node.matrix) : new import_core18.Matrix4().translate(node.translation || [0, 0, 0]).multiplyRight(new import_core18.Matrix4().fromQuaternion(node.rotation || [0, 0, 0, 1])).scale(node.scale || [1, 1, 1])
|
|
1776
2380
|
);
|
|
1777
2381
|
if (node.mesh) {
|
|
1778
2382
|
if (node.mesh.weights || Object.keys(node.mesh.extensions || {}).length)
|
|
1779
|
-
throw new
|
|
2383
|
+
throw new import_core19.TileConversionError(
|
|
1780
2384
|
"MESH_SOURCE_NODE_UNSUPPORTED",
|
|
1781
2385
|
"Morph weights and mesh extensions are unsupported"
|
|
1782
2386
|
);
|
|
@@ -1785,14 +2389,14 @@ function* traverseMeshNodes(nodes, parent, ancestors = /* @__PURE__ */ new Set()
|
|
|
1785
2389
|
yield* traverseMeshNodes(node.children || [], transform, /* @__PURE__ */ new Set([...ancestors, node]));
|
|
1786
2390
|
}
|
|
1787
2391
|
}
|
|
1788
|
-
function extractPrimitive(primitive, transform, id) {
|
|
2392
|
+
async function extractPrimitive(primitive, transform, id, readExternalResource, contentUri, signal) {
|
|
1789
2393
|
if (primitive.targets?.length || Object.keys(primitive.extensions || {}).some((name) => name !== "EXT_mesh_features"))
|
|
1790
|
-
throw new
|
|
2394
|
+
throw new import_core19.TileConversionError(
|
|
1791
2395
|
"MESH_SOURCE_PRIMITIVE_UNSUPPORTED",
|
|
1792
2396
|
"Morph targets and unmapped primitive extensions are unsupported"
|
|
1793
2397
|
);
|
|
1794
2398
|
if (Array.from(transform).some((value) => !Number.isFinite(value)) || transform[3] !== 0 || transform[7] !== 0 || transform[11] !== 0 || transform[15] !== 1 || transform.determinant() <= 0)
|
|
1795
|
-
throw new
|
|
2399
|
+
throw new import_core19.TileConversionError(
|
|
1796
2400
|
"MESH_SOURCE_TRANSFORM_INVALID",
|
|
1797
2401
|
"Mesh placement requires a finite invertible affine transform with positive determinant"
|
|
1798
2402
|
);
|
|
@@ -1800,7 +2404,7 @@ function extractPrimitive(primitive, transform, id) {
|
|
|
1800
2404
|
for (const [name, accessor] of Object.entries(primitive.attributes)) {
|
|
1801
2405
|
if (name === "_BATCHID" || /^_FEATURE_ID_\d+$/.test(name)) continue;
|
|
1802
2406
|
if (!["POSITION", "NORMAL", "COLOR_0", "TEXCOORD_0"].includes(name))
|
|
1803
|
-
throw new
|
|
2407
|
+
throw new import_core19.TileConversionError(
|
|
1804
2408
|
"MESH_SOURCE_ATTRIBUTE_UNSUPPORTED",
|
|
1805
2409
|
`Unsupported mesh attribute ${name}`
|
|
1806
2410
|
);
|
|
@@ -1813,9 +2417,9 @@ function extractPrimitive(primitive, transform, id) {
|
|
|
1813
2417
|
const mesh = validateMeshGeometry(
|
|
1814
2418
|
{
|
|
1815
2419
|
topology: "triangle-list",
|
|
1816
|
-
mode: primitive.mode ?? 4,
|
|
2420
|
+
mode: primitive.mode === 5 ? 4 : primitive.mode ?? 4,
|
|
1817
2421
|
attributes,
|
|
1818
|
-
indices: primitive.
|
|
2422
|
+
indices: createTriangleIndices(primitive, (attributes.POSITION?.value.length ?? 0) / 3)
|
|
1819
2423
|
},
|
|
1820
2424
|
true
|
|
1821
2425
|
);
|
|
@@ -1836,7 +2440,7 @@ function extractPrimitive(primitive, transform, id) {
|
|
|
1836
2440
|
mesh.attributes.POSITION = { value: positions, size: 3 };
|
|
1837
2441
|
if (mesh.attributes.NORMAL) {
|
|
1838
2442
|
const values = mesh.attributes.NORMAL.value;
|
|
1839
|
-
const normalMatrix = new
|
|
2443
|
+
const normalMatrix = new import_core18.Matrix4(transform).invert().transpose();
|
|
1840
2444
|
const normals = new Float32Array(values.length);
|
|
1841
2445
|
for (let index = 0; index < values.length; index += 3) {
|
|
1842
2446
|
const normal = normalMatrix.transformAsVector(Array.from(values.subarray(index, index + 3)));
|
|
@@ -1852,14 +2456,19 @@ function extractPrimitive(primitive, transform, id) {
|
|
|
1852
2456
|
id,
|
|
1853
2457
|
mesh,
|
|
1854
2458
|
origin: minimum.map((value, axis) => value / 2 + maximum[axis] / 2),
|
|
1855
|
-
material:
|
|
2459
|
+
material: await mapMeshSourceMaterial(
|
|
2460
|
+
primitive.material,
|
|
2461
|
+
readExternalResource,
|
|
2462
|
+
contentUri,
|
|
2463
|
+
signal
|
|
2464
|
+
)
|
|
1856
2465
|
};
|
|
1857
2466
|
}
|
|
1858
|
-
function
|
|
2467
|
+
async function mapMeshSourceMaterial(material, readExternalResource, contentUri, signal) {
|
|
1859
2468
|
if (!material) return void 0;
|
|
1860
2469
|
const pbr = material.pbrMetallicRoughness;
|
|
1861
|
-
if (Object.
|
|
1862
|
-
(name) => ![
|
|
2470
|
+
if (Object.entries(material).some(
|
|
2471
|
+
([name, value]) => value !== void 0 && ![
|
|
1863
2472
|
"id",
|
|
1864
2473
|
"name",
|
|
1865
2474
|
"pbrMetallicRoughness",
|
|
@@ -1869,16 +2478,23 @@ function mapMaterial(material) {
|
|
|
1869
2478
|
"emissiveFactor",
|
|
1870
2479
|
"extras"
|
|
1871
2480
|
].includes(name)
|
|
1872
|
-
) || Object.
|
|
1873
|
-
(name) => !["baseColorFactor", "baseColorTexture", "metallicFactor", "roughnessFactor"].includes(name)
|
|
2481
|
+
) || Object.entries(pbr || {}).some(
|
|
2482
|
+
([name, value]) => value !== void 0 && !["baseColorFactor", "baseColorTexture", "metallicFactor", "roughnessFactor"].includes(name)
|
|
1874
2483
|
) || material.emissiveFactor?.some((value) => value !== 0))
|
|
1875
|
-
throw new
|
|
2484
|
+
throw new import_core19.TileConversionError(
|
|
1876
2485
|
"MESH_SOURCE_MATERIAL_UNSUPPORTED",
|
|
1877
2486
|
"Only metallic-roughness factors and one embedded base-color image are supported"
|
|
1878
2487
|
);
|
|
1879
2488
|
return {
|
|
1880
2489
|
baseColorFactor: pbr?.baseColorFactor,
|
|
1881
|
-
..."baseColorTexture" in (pbr || {}) ? {
|
|
2490
|
+
..."baseColorTexture" in (pbr || {}) ? {
|
|
2491
|
+
baseColorTexture: await resolveMeshSourceTexture(
|
|
2492
|
+
pbr.baseColorTexture,
|
|
2493
|
+
readExternalResource,
|
|
2494
|
+
contentUri,
|
|
2495
|
+
signal
|
|
2496
|
+
)
|
|
2497
|
+
} : {},
|
|
1882
2498
|
metallicFactor: pbr?.metallicFactor,
|
|
1883
2499
|
roughnessFactor: pbr?.roughnessFactor,
|
|
1884
2500
|
alphaMode: material.alphaMode,
|
|
@@ -1886,48 +2502,81 @@ function mapMaterial(material) {
|
|
|
1886
2502
|
doubleSided: material.doubleSided
|
|
1887
2503
|
};
|
|
1888
2504
|
}
|
|
2505
|
+
function createTriangleIndices(primitive, vertexCount) {
|
|
2506
|
+
const attribute = primitive.indices ? {
|
|
2507
|
+
value: primitive.indices.value,
|
|
2508
|
+
size: primitive.indices.components,
|
|
2509
|
+
normalized: primitive.indices.normalized
|
|
2510
|
+
} : void 0;
|
|
2511
|
+
if (primitive.mode !== 5) return attribute;
|
|
2512
|
+
const sourceIndices = validateMeshIndices(attribute, vertexCount, 5);
|
|
2513
|
+
const indexCount = sourceIndices?.length ?? vertexCount;
|
|
2514
|
+
if (!Number.isSafeInteger(indexCount) || indexCount < 3) {
|
|
2515
|
+
throw new import_core19.TileConversionError(
|
|
2516
|
+
"MESH_TRIANGLE_COUNT_INVALID",
|
|
2517
|
+
"Triangle strips require at least three vertices"
|
|
2518
|
+
);
|
|
2519
|
+
}
|
|
2520
|
+
const IndexArray = vertexCount < 65536 ? Uint16Array : Uint32Array;
|
|
2521
|
+
const indices = new IndexArray((indexCount - 2) * 3);
|
|
2522
|
+
for (let triangle = 0; triangle < indexCount - 2; triangle++) {
|
|
2523
|
+
const second = triangle + 1 + triangle % 2;
|
|
2524
|
+
const third = triangle + 2 - triangle % 2;
|
|
2525
|
+
indices[triangle * 3] = sourceIndices?.[triangle] ?? triangle;
|
|
2526
|
+
indices[triangle * 3 + 1] = sourceIndices?.[second] ?? second;
|
|
2527
|
+
indices[triangle * 3 + 2] = sourceIndices?.[third] ?? third;
|
|
2528
|
+
}
|
|
2529
|
+
return { value: indices, size: 1 };
|
|
2530
|
+
}
|
|
1889
2531
|
|
|
1890
2532
|
// src/v5/i3s-mesh-conversion.ts
|
|
1891
2533
|
var import_i3s = require("@loaders.gl/i3s");
|
|
1892
|
-
var
|
|
1893
|
-
var
|
|
2534
|
+
var import_core21 = require("@loaders.gl/tile-converter/v5/core");
|
|
2535
|
+
var import_core22 = require("@loaders.gl/tile-converter/v5/core");
|
|
1894
2536
|
function createI3SMeshConversionCodec(options) {
|
|
1895
2537
|
const reference = options.spatialContext.spatialReference;
|
|
1896
2538
|
const height = reference.targetHeightReference === "native" ? reference.heightReference : reference.targetHeightReference;
|
|
1897
2539
|
if (!["native", "transformed"].includes(reference.status) || (reference.targetCrs || reference.sourceCrs) !== "EPSG:4978" || height !== "ellipsoidal" || reference.status === "native" && (!["xyz", "unknown"].includes(reference.axisOrder) || reference.verticalUnitScale !== 1 || reference.units?.some((unit) => unit !== "meter")))
|
|
1898
|
-
throw new
|
|
2540
|
+
throw new import_core21.TileConversionError(
|
|
1899
2541
|
"I3S_MESH_FRAME_UNSUPPORTED",
|
|
1900
2542
|
"I3S mesh authoring requires resolved ECEF coordinates and ellipsoidal heights"
|
|
1901
2543
|
);
|
|
2544
|
+
let objectIdOffset = 0;
|
|
1902
2545
|
return {
|
|
1903
2546
|
/** Places a source mesh in ECEF and authors one layer without modifying source arrays. */
|
|
1904
2547
|
async *convert(resource, _inspection, signal) {
|
|
1905
2548
|
signal?.throwIfAborted();
|
|
1906
2549
|
const mesh = validateMeshGeometry(resource.mesh, true);
|
|
1907
2550
|
const positions = mesh.attributes.POSITION.value;
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
2551
|
+
const context = options.spatialContext;
|
|
2552
|
+
const transformed = "transformGeometryAsync" in context ? await context.transformGeometryAsync(
|
|
2553
|
+
positions,
|
|
2554
|
+
mesh.attributes.NORMAL?.value,
|
|
2555
|
+
resource.normalReferenceFrame ?? options.normalReferenceFrame
|
|
2556
|
+
) : {
|
|
2557
|
+
positions: context.transformPositions(positions),
|
|
2558
|
+
normals: mesh.attributes.NORMAL ? context.transformNormals(mesh.attributes.NORMAL.value, positions) : void 0
|
|
1911
2559
|
};
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
size: 3
|
|
1916
|
-
};
|
|
2560
|
+
signal?.throwIfAborted();
|
|
2561
|
+
mesh.attributes.POSITION = { value: transformed.positions, size: 3 };
|
|
2562
|
+
if (mesh.attributes.NORMAL) mesh.attributes.NORMAL = { value: transformed.normals, size: 3 };
|
|
1917
2563
|
let layer;
|
|
1918
2564
|
try {
|
|
1919
|
-
|
|
2565
|
+
const writerOptions = {
|
|
1920
2566
|
...options,
|
|
1921
2567
|
material: resource.material,
|
|
1922
|
-
features: resource.features
|
|
1923
|
-
|
|
2568
|
+
features: resource.features,
|
|
2569
|
+
objectIdOffset
|
|
2570
|
+
};
|
|
2571
|
+
layer = options.draco === false ? (0, import_i3s.encodeI3SMeshLayer)(mesh, writerOptions) : await (0, import_i3s.encodeI3SMeshLayerWithDraco)(mesh, writerOptions, options.dracoLibraryOptions);
|
|
1924
2572
|
} catch (error) {
|
|
1925
|
-
throw new
|
|
2573
|
+
throw new import_core21.TileConversionError(
|
|
1926
2574
|
"I3S_MESH_PROFILE_UNSUPPORTED",
|
|
1927
2575
|
error instanceof Error ? error.message : String(error)
|
|
1928
2576
|
);
|
|
1929
2577
|
}
|
|
1930
2578
|
signal?.throwIfAborted();
|
|
2579
|
+
objectIdOffset += resource.features?.batches.reduce((total, batch) => total + batch.data.numRows, 0) ?? 1;
|
|
1931
2580
|
yield { id: resource.id, ...layer };
|
|
1932
2581
|
},
|
|
1933
2582
|
/** Reports measured precision and explicitly authorized integer representation changes. */
|
|
@@ -1952,7 +2601,7 @@ function createI3SMeshConversionCodec(options) {
|
|
|
1952
2601
|
};
|
|
1953
2602
|
}
|
|
1954
2603
|
function createSingleMeshI3SSink(options) {
|
|
1955
|
-
const memory = (0,
|
|
2604
|
+
const memory = (0, import_core22.createBoundedMemoryTileConversionSink)(options);
|
|
1956
2605
|
let state = "empty";
|
|
1957
2606
|
let completed = false;
|
|
1958
2607
|
return {
|
|
@@ -1962,19 +2611,26 @@ function createSingleMeshI3SSink(options) {
|
|
|
1962
2611
|
async write(resource, signal) {
|
|
1963
2612
|
signal?.throwIfAborted();
|
|
1964
2613
|
if (state !== "empty")
|
|
1965
|
-
throw new
|
|
2614
|
+
throw new import_core21.TileConversionError(
|
|
1966
2615
|
"SINGLE_I3S_MESH_UNAVAILABLE",
|
|
1967
2616
|
"The initial I3S sink accepts exactly one mesh layer"
|
|
1968
2617
|
);
|
|
1969
2618
|
state = "writing";
|
|
1970
2619
|
for (const [resourceId, bytes] of Object.entries(resource.files))
|
|
1971
|
-
await memory.write(
|
|
2620
|
+
await memory.write(
|
|
2621
|
+
{
|
|
2622
|
+
resourceId,
|
|
2623
|
+
parts: [bytes],
|
|
2624
|
+
contentType: resourceId.endsWith(".png") ? "image/png" : resourceId.endsWith(".jpg") ? "image/jpeg" : "application/gzip"
|
|
2625
|
+
},
|
|
2626
|
+
signal
|
|
2627
|
+
);
|
|
1972
2628
|
state = "written";
|
|
1973
2629
|
},
|
|
1974
2630
|
/** Commits a complete single-layer result. */
|
|
1975
2631
|
async finalize(report) {
|
|
1976
2632
|
if (state !== "written")
|
|
1977
|
-
throw new
|
|
2633
|
+
throw new import_core21.TileConversionError(
|
|
1978
2634
|
"SINGLE_I3S_MESH_INCOMPLETE",
|
|
1979
2635
|
"Finalization requires exactly one successfully written layer"
|
|
1980
2636
|
);
|
|
@@ -1990,3 +2646,903 @@ function createSingleMeshI3SSink(options) {
|
|
|
1990
2646
|
}
|
|
1991
2647
|
};
|
|
1992
2648
|
}
|
|
2649
|
+
|
|
2650
|
+
// src/v5/i3s-mesh-sink.ts
|
|
2651
|
+
var import_geospatial = require("@math.gl/geospatial");
|
|
2652
|
+
var import_compression = require("@loaders.gl/compression");
|
|
2653
|
+
var import_core23 = require("@loaders.gl/tile-converter/v5/core");
|
|
2654
|
+
function createI3SMeshSink(options) {
|
|
2655
|
+
const { maxMeshes, maxResourceBytes } = options;
|
|
2656
|
+
if (!Number.isInteger(maxMeshes) || maxMeshes < 1 || maxMeshes > 64 || !Number.isSafeInteger(maxResourceBytes) || maxResourceBytes < 1)
|
|
2657
|
+
throw new import_core23.TileConversionError(
|
|
2658
|
+
"I3S_COLLECTION_OPTIONS_INVALID",
|
|
2659
|
+
"I3S collections require 1\u201364 meshes and a positive resource byte limit"
|
|
2660
|
+
);
|
|
2661
|
+
const memory = (0, import_core23.createBoundedMemoryTileConversionSink)(options);
|
|
2662
|
+
const compressor = new import_compression.GZipCompressor({ useNative: false, gzip: { mtime: 0 } });
|
|
2663
|
+
const decompressor = new import_compression.GZipDecompressor({ useNative: false });
|
|
2664
|
+
const leaves = [];
|
|
2665
|
+
const identifiers = /* @__PURE__ */ new Set();
|
|
2666
|
+
const objectIdRanges = [];
|
|
2667
|
+
let layer;
|
|
2668
|
+
let schemaKey;
|
|
2669
|
+
let state = "open";
|
|
2670
|
+
let completed = false;
|
|
2671
|
+
async function readBytes(bytes) {
|
|
2672
|
+
const output = await decompressor.decompress(bytes);
|
|
2673
|
+
if (output.byteLength > maxResourceBytes)
|
|
2674
|
+
throw new import_core23.TileConversionError(
|
|
2675
|
+
"I3S_COLLECTION_RESOURCE_TOO_LARGE",
|
|
2676
|
+
"I3S metadata exceeds maxResourceBytes"
|
|
2677
|
+
);
|
|
2678
|
+
return output;
|
|
2679
|
+
}
|
|
2680
|
+
async function readJson(resource, name) {
|
|
2681
|
+
return JSON.parse(new TextDecoder().decode(await readBytes(resource.files[name])));
|
|
2682
|
+
}
|
|
2683
|
+
async function writeJson(resourceId, value) {
|
|
2684
|
+
const bytes = new TextEncoder().encode(JSON.stringify(value));
|
|
2685
|
+
if (bytes.byteLength > maxResourceBytes)
|
|
2686
|
+
throw new import_core23.TileConversionError(
|
|
2687
|
+
"I3S_COLLECTION_RESOURCE_TOO_LARGE",
|
|
2688
|
+
"I3S index exceeds maxResourceBytes"
|
|
2689
|
+
);
|
|
2690
|
+
await memory.write({
|
|
2691
|
+
resourceId,
|
|
2692
|
+
parts: [await compressor.compress(bytes.buffer)],
|
|
2693
|
+
contentType: "application/gzip"
|
|
2694
|
+
});
|
|
2695
|
+
}
|
|
2696
|
+
return {
|
|
2697
|
+
/** Exposes the whole collection atomically. */
|
|
2698
|
+
getFiles: () => completed ? memory.getFiles() : [],
|
|
2699
|
+
/** Retains one leaf's binary resources and validates its shared schema and IDs. */
|
|
2700
|
+
async write(resource, signal) {
|
|
2701
|
+
signal?.throwIfAborted();
|
|
2702
|
+
if (state !== "open" || identifiers.has(resource.id) || leaves.length >= maxMeshes)
|
|
2703
|
+
throw new import_core23.TileConversionError(
|
|
2704
|
+
"I3S_COLLECTION_UNAVAILABLE",
|
|
2705
|
+
"I3S collection is busy, closed, full, or repeats a placement"
|
|
2706
|
+
);
|
|
2707
|
+
state = "writing";
|
|
2708
|
+
const authored = await readJson(resource, "3dSceneLayer.json.gz");
|
|
2709
|
+
const key = JSON.stringify([
|
|
2710
|
+
authored.fields,
|
|
2711
|
+
authored.attributeStorageInfo,
|
|
2712
|
+
authored.description,
|
|
2713
|
+
authored.store.defaultGeometrySchema
|
|
2714
|
+
]);
|
|
2715
|
+
if (schemaKey !== void 0 && key !== schemaKey)
|
|
2716
|
+
throw new import_core23.TileConversionError(
|
|
2717
|
+
"I3S_COLLECTION_SCHEMA_MISMATCH",
|
|
2718
|
+
"Mesh nodes must share feature fields and the legacy geometry layout"
|
|
2719
|
+
);
|
|
2720
|
+
const objectIds = new DataView(
|
|
2721
|
+
await readBytes(resource.files["nodes/1/attributes/f_0/0.bin.gz"])
|
|
2722
|
+
);
|
|
2723
|
+
const count = objectIds.getUint32(0, true);
|
|
2724
|
+
const first = objectIds.getUint32(4, true);
|
|
2725
|
+
if (objectIds.byteLength !== 4 + count * 4 || !count || objectIdRanges.some(([start, end]) => first < end && first + count > start) || Array.from({ length: count }, (_, index2) => objectIds.getUint32(4 + index2 * 4, true)).some(
|
|
2726
|
+
(value, index2) => value !== first + index2
|
|
2727
|
+
))
|
|
2728
|
+
throw new import_core23.TileConversionError(
|
|
2729
|
+
"I3S_COLLECTION_IDS_INVALID",
|
|
2730
|
+
"Generated object IDs must be contiguous and disjoint across nodes"
|
|
2731
|
+
);
|
|
2732
|
+
const index = leaves.length + 1;
|
|
2733
|
+
const node = (await readJson(resource, "nodepages/0.json.gz")).nodes[1];
|
|
2734
|
+
const legacy = await readJson(
|
|
2735
|
+
resource,
|
|
2736
|
+
"nodes/1/3dNodeIndexDocument.json.gz"
|
|
2737
|
+
);
|
|
2738
|
+
if (node.obb.quaternion.join(",") !== "0,0,0,1" || [...node.obb.center, ...node.obb.halfSize].some((value) => !Number.isFinite(value)) || node.obb.halfSize.some((value) => value < 0))
|
|
2739
|
+
throw new import_core23.TileConversionError(
|
|
2740
|
+
"I3S_COLLECTION_BOUNDS_INVALID",
|
|
2741
|
+
"Expected finite axis-aligned bounds from the I3S mesh writer"
|
|
2742
|
+
);
|
|
2743
|
+
if (!layer) {
|
|
2744
|
+
layer = structuredClone(authored);
|
|
2745
|
+
layer.materialDefinitions = [];
|
|
2746
|
+
layer.geometryDefinitions = [];
|
|
2747
|
+
layer.textureSetDefinitions = [];
|
|
2748
|
+
schemaKey = key;
|
|
2749
|
+
}
|
|
2750
|
+
const material = structuredClone(authored.materialDefinitions[0]);
|
|
2751
|
+
const texture = material.pbrMetallicRoughness?.baseColorTexture;
|
|
2752
|
+
if (texture) {
|
|
2753
|
+
texture.textureSetDefinitionId = layer.textureSetDefinitions.length;
|
|
2754
|
+
layer.textureSetDefinitions.push(authored.textureSetDefinitions[0]);
|
|
2755
|
+
node.mesh.material.resource = index;
|
|
2756
|
+
}
|
|
2757
|
+
node.index = index;
|
|
2758
|
+
node.parentIndex = 0;
|
|
2759
|
+
node.mesh.material.definition = layer.materialDefinitions.length;
|
|
2760
|
+
node.mesh.geometry.definition = layer.geometryDefinitions.length;
|
|
2761
|
+
node.mesh.geometry.resource = index;
|
|
2762
|
+
node.mesh.attribute.resource = index;
|
|
2763
|
+
layer.materialDefinitions.push(material);
|
|
2764
|
+
layer.geometryDefinitions.push(authored.geometryDefinitions[0]);
|
|
2765
|
+
layer.store.resourcePattern = [
|
|
2766
|
+
.../* @__PURE__ */ new Set([...layer.store.resourcePattern, ...authored.store.resourcePattern])
|
|
2767
|
+
];
|
|
2768
|
+
layer.store.extent = layer.store.extent.map(
|
|
2769
|
+
(value, axis) => axis < 2 ? Math.min(value, authored.store.extent[axis]) : Math.max(value, authored.store.extent[axis])
|
|
2770
|
+
);
|
|
2771
|
+
for (const [name, bytes] of Object.entries(resource.files)) {
|
|
2772
|
+
if (name.startsWith("nodes/1/") && !name.endsWith("3dNodeIndexDocument.json.gz"))
|
|
2773
|
+
await memory.write(
|
|
2774
|
+
{
|
|
2775
|
+
resourceId: name.replace("nodes/1/", `nodes/${index}/`),
|
|
2776
|
+
parts: [bytes],
|
|
2777
|
+
contentType: name.endsWith(".gz") ? "application/gzip" : name.endsWith(".png") ? "image/png" : "image/jpeg"
|
|
2778
|
+
},
|
|
2779
|
+
signal
|
|
2780
|
+
);
|
|
2781
|
+
}
|
|
2782
|
+
leaves.push({ node, legacy: { ...legacy, id: String(index) } });
|
|
2783
|
+
identifiers.add(resource.id);
|
|
2784
|
+
objectIdRanges.push([first, first + count]);
|
|
2785
|
+
state = "open";
|
|
2786
|
+
},
|
|
2787
|
+
/** Rebuilds both indices and layer bounds only after every leaf has completed. */
|
|
2788
|
+
async finalize(report) {
|
|
2789
|
+
if (state !== "open" || !layer || !leaves.length)
|
|
2790
|
+
throw new import_core23.TileConversionError(
|
|
2791
|
+
"I3S_COLLECTION_INCOMPLETE",
|
|
2792
|
+
"Finalization requires successfully written mesh nodes"
|
|
2793
|
+
);
|
|
2794
|
+
state = "writing";
|
|
2795
|
+
const minimum = [Infinity, Infinity, Infinity];
|
|
2796
|
+
const maximum = [-Infinity, -Infinity, -Infinity];
|
|
2797
|
+
for (const { node } of leaves) {
|
|
2798
|
+
const center2 = import_geospatial.Ellipsoid.WGS84.cartographicToCartesian(node.obb.center);
|
|
2799
|
+
for (let axis = 0; axis < 3; axis++) {
|
|
2800
|
+
minimum[axis] = Math.min(minimum[axis], center2[axis] - node.obb.halfSize[axis]);
|
|
2801
|
+
maximum[axis] = Math.max(maximum[axis], center2[axis] + node.obb.halfSize[axis]);
|
|
2802
|
+
}
|
|
2803
|
+
}
|
|
2804
|
+
const center = minimum.map((value, axis) => value / 2 + maximum[axis] / 2);
|
|
2805
|
+
const geographicCenter = Array.from(import_geospatial.Ellipsoid.WGS84.cartesianToCartographic(center));
|
|
2806
|
+
const reconstructedCenter = import_geospatial.Ellipsoid.WGS84.cartographicToCartesian(geographicCenter);
|
|
2807
|
+
const obb = {
|
|
2808
|
+
center: geographicCenter,
|
|
2809
|
+
halfSize: minimum.map(
|
|
2810
|
+
(value, axis) => maximum[axis] / 2 - value / 2 + Math.abs(center[axis] - reconstructedCenter[axis]) + 1e-7
|
|
2811
|
+
),
|
|
2812
|
+
quaternion: [0, 0, 0, 1]
|
|
2813
|
+
};
|
|
2814
|
+
if ([...obb.center, ...obb.halfSize].some((value) => !Number.isFinite(value)))
|
|
2815
|
+
throw new import_core23.TileConversionError(
|
|
2816
|
+
"I3S_COLLECTION_BOUNDS_INVALID",
|
|
2817
|
+
"Collection bounds cannot be represented"
|
|
2818
|
+
);
|
|
2819
|
+
const mbs = [...obb.center, Math.hypot(...obb.halfSize)];
|
|
2820
|
+
const children = leaves.map(({ node }) => ({
|
|
2821
|
+
id: String(node.index),
|
|
2822
|
+
href: `../${node.index}`,
|
|
2823
|
+
obb: node.obb,
|
|
2824
|
+
mbs: [...node.obb.center, Math.hypot(...node.obb.halfSize)]
|
|
2825
|
+
}));
|
|
2826
|
+
await writeJson("nodes/root/3dNodeIndexDocument.json.gz", {
|
|
2827
|
+
id: "root",
|
|
2828
|
+
level: 0,
|
|
2829
|
+
version: "1",
|
|
2830
|
+
obb,
|
|
2831
|
+
mbs,
|
|
2832
|
+
lodSelection: [{ metricType: "maxScreenThresholdSQ", maxError: 0 }],
|
|
2833
|
+
children
|
|
2834
|
+
});
|
|
2835
|
+
for (const { node, legacy } of leaves)
|
|
2836
|
+
await writeJson(`nodes/${node.index}/3dNodeIndexDocument.json.gz`, {
|
|
2837
|
+
...legacy,
|
|
2838
|
+
parentNode: { id: "root", href: "../root", obb, mbs }
|
|
2839
|
+
});
|
|
2840
|
+
const nodes = [
|
|
2841
|
+
{ index: 0, obb, lodThreshold: 0, children: leaves.map(({ node }) => node.index) },
|
|
2842
|
+
...leaves.map(({ node }) => node)
|
|
2843
|
+
];
|
|
2844
|
+
for (let page = 0; page * 64 < nodes.length; page++)
|
|
2845
|
+
await writeJson(`nodepages/${page}.json.gz`, {
|
|
2846
|
+
nodes: nodes.slice(page * 64, (page + 1) * 64)
|
|
2847
|
+
});
|
|
2848
|
+
if (!layer.textureSetDefinitions?.length) delete layer.textureSetDefinitions;
|
|
2849
|
+
await writeJson("3dSceneLayer.json.gz", layer);
|
|
2850
|
+
await writeJson("metadata.json.gz", {
|
|
2851
|
+
folderPattern: "BASIC",
|
|
2852
|
+
archiveCompressionType: "STORE",
|
|
2853
|
+
resourceCompressionType: "GZIP",
|
|
2854
|
+
I3SVersion: "1.7",
|
|
2855
|
+
nodeCount: nodes.length
|
|
2856
|
+
});
|
|
2857
|
+
await memory.finalize(report);
|
|
2858
|
+
state = "closed";
|
|
2859
|
+
completed = true;
|
|
2860
|
+
},
|
|
2861
|
+
/** Discards binary resources and index metadata after any failure. */
|
|
2862
|
+
async abort(error) {
|
|
2863
|
+
completed = false;
|
|
2864
|
+
state = "closed";
|
|
2865
|
+
leaves.length = 0;
|
|
2866
|
+
identifiers.clear();
|
|
2867
|
+
objectIdRanges.length = 0;
|
|
2868
|
+
layer = void 0;
|
|
2869
|
+
await memory.abort(error);
|
|
2870
|
+
}
|
|
2871
|
+
};
|
|
2872
|
+
}
|
|
2873
|
+
|
|
2874
|
+
// src/v5/conversion-api.ts
|
|
2875
|
+
var TileConversionError17 = class extends Error {
|
|
2876
|
+
/** Stable machine-readable failure code. */
|
|
2877
|
+
code;
|
|
2878
|
+
/** Diagnostics associated with this failure. */
|
|
2879
|
+
diagnostics;
|
|
2880
|
+
/** Creates a typed conversion failure. */
|
|
2881
|
+
constructor(code, message, diagnostics = []) {
|
|
2882
|
+
super(message);
|
|
2883
|
+
this.name = "TileConversionError";
|
|
2884
|
+
this.code = code;
|
|
2885
|
+
this.diagnostics = diagnostics;
|
|
2886
|
+
}
|
|
2887
|
+
};
|
|
2888
|
+
|
|
2889
|
+
// src/v5/resource-fetcher.ts
|
|
2890
|
+
function createTileConversionResourceFetcher(options) {
|
|
2891
|
+
let consumed = options.initialInputBytes ?? 0;
|
|
2892
|
+
if (!Number.isSafeInteger(options.maxInputBytes) || options.maxInputBytes < 1 || !Number.isSafeInteger(consumed) || consumed < 0 || consumed > options.maxInputBytes)
|
|
2893
|
+
throw new TileConversionError17(
|
|
2894
|
+
"INVALID_INPUT_MEMORY_LIMIT",
|
|
2895
|
+
"Resource fetch requires a positive aggregate byte budget and valid initial bytes"
|
|
2896
|
+
);
|
|
2897
|
+
const fetcher = options.fetcher ?? fetch;
|
|
2898
|
+
return async (input, requestOptions) => {
|
|
2899
|
+
options.signal?.throwIfAborted();
|
|
2900
|
+
const url = new URL(input instanceof Request ? input.url : String(input));
|
|
2901
|
+
if (!["http:", "https:", "blob:", "data:"].includes(url.protocol))
|
|
2902
|
+
throw new TileConversionError17(
|
|
2903
|
+
"EXTERNAL_RESOURCE_UNSUPPORTED",
|
|
2904
|
+
"Conversion transport requires HTTP(S), Blob or embedded data resources"
|
|
2905
|
+
);
|
|
2906
|
+
const requestSignal = requestOptions?.signal ?? (input instanceof Request ? input.signal : void 0);
|
|
2907
|
+
const signal = options.signal && requestSignal ? AbortSignal.any([options.signal, requestSignal]) : options.signal ?? requestSignal;
|
|
2908
|
+
const response = await fetcher(input, { ...requestOptions, signal });
|
|
2909
|
+
if (!response.ok) {
|
|
2910
|
+
await response.body?.cancel();
|
|
2911
|
+
throw new TileConversionError17(
|
|
2912
|
+
"BROWSER_SOURCE_FETCH_FAILED",
|
|
2913
|
+
`Could not fetch conversion resource (${response.status})`
|
|
2914
|
+
);
|
|
2915
|
+
}
|
|
2916
|
+
const reader = response.body?.getReader();
|
|
2917
|
+
if (!reader)
|
|
2918
|
+
throw new TileConversionError17(
|
|
2919
|
+
"BROWSER_SOURCE_STREAM_UNAVAILABLE",
|
|
2920
|
+
"Conversion resources require readable response streams"
|
|
2921
|
+
);
|
|
2922
|
+
const cancelRead = () => {
|
|
2923
|
+
void reader.cancel(signal?.reason).catch(() => {
|
|
2924
|
+
});
|
|
2925
|
+
};
|
|
2926
|
+
signal?.addEventListener("abort", cancelRead, { once: true });
|
|
2927
|
+
const parts = [];
|
|
2928
|
+
let byteLength = 0;
|
|
2929
|
+
try {
|
|
2930
|
+
while (true) {
|
|
2931
|
+
signal?.throwIfAborted();
|
|
2932
|
+
const { done, value } = await reader.read();
|
|
2933
|
+
signal?.throwIfAborted();
|
|
2934
|
+
if (done) break;
|
|
2935
|
+
if (value.byteLength > options.maxInputBytes - consumed)
|
|
2936
|
+
throw new TileConversionError17(
|
|
2937
|
+
"INPUT_RESOURCE_TOO_LARGE",
|
|
2938
|
+
"Selected contents and external dependencies exceed the aggregate input byte budget"
|
|
2939
|
+
);
|
|
2940
|
+
consumed += value.byteLength;
|
|
2941
|
+
byteLength += value.byteLength;
|
|
2942
|
+
parts.push(value);
|
|
2943
|
+
}
|
|
2944
|
+
} catch (error) {
|
|
2945
|
+
await reader.cancel(error);
|
|
2946
|
+
throw error;
|
|
2947
|
+
} finally {
|
|
2948
|
+
signal?.removeEventListener("abort", cancelRead);
|
|
2949
|
+
reader.releaseLock();
|
|
2950
|
+
}
|
|
2951
|
+
const bytes = new Uint8Array(byteLength);
|
|
2952
|
+
let offset = 0;
|
|
2953
|
+
for (const part of parts) {
|
|
2954
|
+
bytes.set(part, offset);
|
|
2955
|
+
offset += part.byteLength;
|
|
2956
|
+
}
|
|
2957
|
+
return new Response(bytes, {
|
|
2958
|
+
status: response.status,
|
|
2959
|
+
statusText: response.statusText,
|
|
2960
|
+
headers: response.headers
|
|
2961
|
+
});
|
|
2962
|
+
};
|
|
2963
|
+
}
|
|
2964
|
+
|
|
2965
|
+
// src/v5/point-cloud-tileset-sink.ts
|
|
2966
|
+
var import_core24 = require("@loaders.gl/tile-converter/v5/core");
|
|
2967
|
+
function createPointCloudTilesetSink(options) {
|
|
2968
|
+
const { maxTiles, geometricError } = options;
|
|
2969
|
+
if (!Number.isSafeInteger(maxTiles) || maxTiles < 1 || !Number.isFinite(geometricError) || geometricError < 0) {
|
|
2970
|
+
throw new import_core24.TileConversionError(
|
|
2971
|
+
"POINT_CLOUD_TILESET_OPTIONS_INVALID",
|
|
2972
|
+
"Positive maxTiles and finite nonnegative geometricError are required"
|
|
2973
|
+
);
|
|
2974
|
+
}
|
|
2975
|
+
const memory = (0, import_core24.createBoundedMemoryTileConversionSink)(options);
|
|
2976
|
+
const children = [];
|
|
2977
|
+
const identifiers = /* @__PURE__ */ new Set();
|
|
2978
|
+
const minimum = [Infinity, Infinity, Infinity];
|
|
2979
|
+
const maximum = [-Infinity, -Infinity, -Infinity];
|
|
2980
|
+
let state = "open";
|
|
2981
|
+
let completed = false;
|
|
2982
|
+
return {
|
|
2983
|
+
/** Exposes finalized files without exposing a partial package. */
|
|
2984
|
+
getFiles: () => completed ? memory.getFiles() : [],
|
|
2985
|
+
/** Captures one validated placement and encoded PNTS resource. */
|
|
2986
|
+
async write(resource, signal) {
|
|
2987
|
+
signal?.throwIfAborted();
|
|
2988
|
+
if (state !== "open")
|
|
2989
|
+
throw new import_core24.TileConversionError(
|
|
2990
|
+
"POINT_CLOUD_TILESET_SINK_UNAVAILABLE",
|
|
2991
|
+
"Point sink is busy or closed"
|
|
2992
|
+
);
|
|
2993
|
+
if (!resource.id || identifiers.has(resource.id) || children.length >= maxTiles) {
|
|
2994
|
+
throw new import_core24.TileConversionError(
|
|
2995
|
+
"POINT_CLOUD_TILESET_PLACEMENT_INVALID",
|
|
2996
|
+
"Placements require unique nonempty identifiers within maxTiles"
|
|
2997
|
+
);
|
|
2998
|
+
}
|
|
2999
|
+
const [lower, upper] = getPointTileBounds(resource, geometricError);
|
|
3000
|
+
const resourceId = `points/${children.length}.pnts`;
|
|
3001
|
+
state = "writing";
|
|
3002
|
+
await memory.write(
|
|
3003
|
+
{ resourceId, parts: [resource.pnts], contentType: "application/octet-stream" },
|
|
3004
|
+
signal
|
|
3005
|
+
);
|
|
3006
|
+
if (state !== "writing")
|
|
3007
|
+
throw new import_core24.TileConversionError(
|
|
3008
|
+
"POINT_CLOUD_TILESET_SINK_UNAVAILABLE",
|
|
3009
|
+
"Point sink was aborted during writing"
|
|
3010
|
+
);
|
|
3011
|
+
children.push({
|
|
3012
|
+
boundingVolume: { box: createPointBoundsBox(lower, upper) },
|
|
3013
|
+
geometricError,
|
|
3014
|
+
content: { uri: resourceId }
|
|
3015
|
+
});
|
|
3016
|
+
identifiers.add(resource.id);
|
|
3017
|
+
for (let axis = 0; axis < 3; axis++) {
|
|
3018
|
+
minimum[axis] = Math.min(minimum[axis], lower[axis]);
|
|
3019
|
+
maximum[axis] = Math.max(maximum[axis], upper[axis]);
|
|
3020
|
+
}
|
|
3021
|
+
state = "open";
|
|
3022
|
+
},
|
|
3023
|
+
/** Serializes the enclosing root after all placements have completed. */
|
|
3024
|
+
async finalize(report) {
|
|
3025
|
+
if (state !== "open" || !children.length)
|
|
3026
|
+
throw new import_core24.TileConversionError(
|
|
3027
|
+
"POINT_CLOUD_TILESET_SINK_INCOMPLETE",
|
|
3028
|
+
"Finalization requires successfully written point tiles"
|
|
3029
|
+
);
|
|
3030
|
+
state = "writing";
|
|
3031
|
+
const rootError = Math.max(
|
|
3032
|
+
geometricError,
|
|
3033
|
+
Math.hypot(...minimum.map((value, axis) => maximum[axis] - value))
|
|
3034
|
+
);
|
|
3035
|
+
if (!Number.isFinite(rootError))
|
|
3036
|
+
throw new import_core24.TileConversionError(
|
|
3037
|
+
"POINT_CLOUD_TILESET_BOUNDS_INVALID",
|
|
3038
|
+
"Enclosing bounds must have a finite diagonal"
|
|
3039
|
+
);
|
|
3040
|
+
const json = JSON.stringify({
|
|
3041
|
+
asset: { version: "1.0" },
|
|
3042
|
+
geometricError: rootError,
|
|
3043
|
+
root: {
|
|
3044
|
+
boundingVolume: { box: createPointBoundsBox(minimum, maximum) },
|
|
3045
|
+
geometricError: rootError,
|
|
3046
|
+
refine: "ADD",
|
|
3047
|
+
children
|
|
3048
|
+
}
|
|
3049
|
+
});
|
|
3050
|
+
await memory.write({
|
|
3051
|
+
resourceId: "tileset.json",
|
|
3052
|
+
parts: [json],
|
|
3053
|
+
contentType: "application/json"
|
|
3054
|
+
});
|
|
3055
|
+
if (state !== "writing")
|
|
3056
|
+
throw new import_core24.TileConversionError(
|
|
3057
|
+
"POINT_CLOUD_TILESET_SINK_UNAVAILABLE",
|
|
3058
|
+
"Point sink was aborted during finalization"
|
|
3059
|
+
);
|
|
3060
|
+
await memory.finalize(report);
|
|
3061
|
+
if (state !== "writing")
|
|
3062
|
+
throw new import_core24.TileConversionError(
|
|
3063
|
+
"POINT_CLOUD_TILESET_SINK_UNAVAILABLE",
|
|
3064
|
+
"Point sink was aborted during finalization"
|
|
3065
|
+
);
|
|
3066
|
+
state = "closed";
|
|
3067
|
+
completed = true;
|
|
3068
|
+
},
|
|
3069
|
+
/** Clears resource and hierarchy storage on failure or cancellation. */
|
|
3070
|
+
async abort(error) {
|
|
3071
|
+
state = "closed";
|
|
3072
|
+
completed = false;
|
|
3073
|
+
children.length = 0;
|
|
3074
|
+
identifiers.clear();
|
|
3075
|
+
await memory.abort(error);
|
|
3076
|
+
}
|
|
3077
|
+
};
|
|
3078
|
+
}
|
|
3079
|
+
function getPointTileBounds(resource, geometricError) {
|
|
3080
|
+
const reference = resource.spatialReference;
|
|
3081
|
+
const identity = [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1];
|
|
3082
|
+
if (!reference || !["native", "transformed"].includes(reference.status) || (reference.targetCrs || reference.sourceCrs) !== "EPSG:4978" || (reference.targetHeightReference === "native" ? reference.heightReference : reference.targetHeightReference) !== "ellipsoidal" || resource.coordinateSystem !== "cartesian" || resource.cartographicOrigin.length !== 3 || resource.cartographicOrigin.some((value) => value !== 0) || resource.modelMatrix && (resource.modelMatrix.length !== 16 || resource.modelMatrix.some((value, index) => value !== identity[index])) || reference.status === "native" && (reference.coordinateFrame !== "geocentric" || reference.axisOrder !== "xyz" || reference.verticalUnitScale !== 1 || reference.units?.some((unit) => unit !== "meter"))) {
|
|
3083
|
+
throw new import_core24.TileConversionError(
|
|
3084
|
+
"POINT_CLOUD_TILESET_FRAME_UNSUPPORTED",
|
|
3085
|
+
"Point tiles require declared EPSG:4978 xyz meters, ellipsoidal heights and no additional source placement"
|
|
3086
|
+
);
|
|
3087
|
+
}
|
|
3088
|
+
if (!Number.isFinite(resource.maximumPositionError) || resource.maximumPositionError < 0 || resource.maximumPositionError > geometricError) {
|
|
3089
|
+
throw new import_core24.TileConversionError(
|
|
3090
|
+
"POINT_CLOUD_GEOMETRIC_ERROR_INVALID",
|
|
3091
|
+
"geometricError must include measured position reconstruction error"
|
|
3092
|
+
);
|
|
3093
|
+
}
|
|
3094
|
+
const [minimum, maximum] = resource.localBoundingBox;
|
|
3095
|
+
const center = resource.rtcCenter;
|
|
3096
|
+
if (center.length !== 3 || minimum.length !== 3 || maximum.length !== 3 || [...center, ...minimum, ...maximum].some((value) => !Number.isFinite(value)) || minimum.some((value, axis) => value > maximum[axis])) {
|
|
3097
|
+
throw new import_core24.TileConversionError(
|
|
3098
|
+
"POINT_CLOUD_TILESET_BOUNDS_INVALID",
|
|
3099
|
+
"RTC center and encoded xyz bounds must be finite and ordered"
|
|
3100
|
+
);
|
|
3101
|
+
}
|
|
3102
|
+
const lower = minimum.map((value, axis) => value + center[axis]);
|
|
3103
|
+
const upper = maximum.map((value, axis) => value + center[axis]);
|
|
3104
|
+
if ([...lower, ...upper].some((value) => !Number.isFinite(value)))
|
|
3105
|
+
throw new import_core24.TileConversionError(
|
|
3106
|
+
"POINT_CLOUD_TILESET_BOUNDS_INVALID",
|
|
3107
|
+
"Reconstructed ECEF bounds must be finite"
|
|
3108
|
+
);
|
|
3109
|
+
return [lower, upper];
|
|
3110
|
+
}
|
|
3111
|
+
function createPointBoundsBox(minimum, maximum) {
|
|
3112
|
+
const center = minimum.map((value, axis) => value / 2 + maximum[axis] / 2);
|
|
3113
|
+
const halfSize = minimum.map((value, axis) => maximum[axis] / 2 - value / 2);
|
|
3114
|
+
return [center[0], center[1], center[2], halfSize[0], 0, 0, 0, halfSize[1], 0, 0, 0, halfSize[2]];
|
|
3115
|
+
}
|
|
3116
|
+
|
|
3117
|
+
// src/v5/i3s-mesh-source.ts
|
|
3118
|
+
var import_core27 = require("@loaders.gl/tile-converter/v5/core");
|
|
3119
|
+
|
|
3120
|
+
// src/v5/i3s-mesh-source-material.ts
|
|
3121
|
+
var import_images3 = require("@loaders.gl/images");
|
|
3122
|
+
var import_core25 = require("@loaders.gl/tile-converter/v5/core");
|
|
3123
|
+
async function mapI3SMeshSourceMaterial(content, header, readExternalResource, contentUri, signal) {
|
|
3124
|
+
const material = Object.fromEntries(
|
|
3125
|
+
Object.entries(content.material || {}).filter(([, value]) => value !== void 0)
|
|
3126
|
+
);
|
|
3127
|
+
const metallicRoughness = material.pbrMetallicRoughness ? Object.fromEntries(
|
|
3128
|
+
Object.entries(material.pbrMetallicRoughness).filter(([, value]) => value !== void 0)
|
|
3129
|
+
) : {};
|
|
3130
|
+
material.pbrMetallicRoughness = metallicRoughness;
|
|
3131
|
+
if (material.cullFace !== void 0 && material.cullFace !== "back") {
|
|
3132
|
+
throw new import_core25.TileConversionError(
|
|
3133
|
+
"I3S_MESH_SOURCE_MATERIAL_UNSUPPORTED",
|
|
3134
|
+
"Only back-face culling is supported"
|
|
3135
|
+
);
|
|
3136
|
+
}
|
|
3137
|
+
delete material.cullFace;
|
|
3138
|
+
let textureInfo = metallicRoughness.baseColorTexture ? { ...metallicRoughness.baseColorTexture } : void 0;
|
|
3139
|
+
const resources = header.textureUrls?.length ? header.textureUrls : header.textureUrl ? [
|
|
3140
|
+
{
|
|
3141
|
+
textureSetDefinitionId: textureInfo?.textureSetDefinitionId ?? 0,
|
|
3142
|
+
textureUrl: header.textureUrl,
|
|
3143
|
+
textureFormat: header.textureFormat || "jpg"
|
|
3144
|
+
}
|
|
3145
|
+
] : [];
|
|
3146
|
+
if (!textureInfo && !header.materialDefinition && resources.length === 1) {
|
|
3147
|
+
textureInfo = { textureSetDefinitionId: resources[0].textureSetDefinitionId };
|
|
3148
|
+
}
|
|
3149
|
+
const encodedTextures = content.textures || {};
|
|
3150
|
+
if (!textureInfo && (resources.length || content.texture || Object.keys(encodedTextures).length)) {
|
|
3151
|
+
throw new import_core25.TileConversionError(
|
|
3152
|
+
"I3S_MESH_SOURCE_TEXTURE_UNSUPPORTED",
|
|
3153
|
+
"Texture resources require a base-color mapping"
|
|
3154
|
+
);
|
|
3155
|
+
}
|
|
3156
|
+
if (textureInfo) {
|
|
3157
|
+
const definitionId = textureInfo.textureSetDefinitionId;
|
|
3158
|
+
const wrappingModes = { none: 33071, repeat: 10497, mirror: 33648 };
|
|
3159
|
+
if (typeof definitionId !== "number" || !Number.isSafeInteger(definitionId) || definitionId < 0 || textureInfo.texCoord !== void 0 && textureInfo.texCoord !== 0 || Object.keys(textureInfo).some(
|
|
3160
|
+
(name) => !["textureSetDefinitionId", "texCoord", "wrapS", "wrapT", "texture"].includes(name)
|
|
3161
|
+
) || ["wrapS", "wrapT"].some(
|
|
3162
|
+
(name) => textureInfo[name] !== void 0 && (typeof textureInfo[name] !== "string" || !Object.hasOwn(wrappingModes, textureInfo[name]))
|
|
3163
|
+
) || resources.length > 1 || resources.some((resource) => resource.textureSetDefinitionId !== definitionId) || Object.keys(encodedTextures).some((key) => key !== String(definitionId))) {
|
|
3164
|
+
throw new import_core25.TileConversionError(
|
|
3165
|
+
"I3S_MESH_SOURCE_TEXTURE_UNSUPPORTED",
|
|
3166
|
+
"Only one base-color texture set on TEXCOORD_0 with qualified wrapping is supported"
|
|
3167
|
+
);
|
|
3168
|
+
}
|
|
3169
|
+
let encoded = encodedTextures[String(definitionId)] || content.texture || textureInfo.texture?.source?.image;
|
|
3170
|
+
if (!encoded) {
|
|
3171
|
+
if (!resources.length || !readExternalResource) {
|
|
3172
|
+
throw new import_core25.TileConversionError(
|
|
3173
|
+
"I3S_MESH_SOURCE_TEXTURE_UNSUPPORTED",
|
|
3174
|
+
"Encoded texture bytes or an explicit bounded resource reader are required"
|
|
3175
|
+
);
|
|
3176
|
+
}
|
|
3177
|
+
signal?.throwIfAborted();
|
|
3178
|
+
encoded = await readExternalResource(resources[0].textureUrl, contentUri, signal);
|
|
3179
|
+
signal?.throwIfAborted();
|
|
3180
|
+
}
|
|
3181
|
+
const data = encoded instanceof ArrayBuffer ? new Uint8Array(encoded) : encoded;
|
|
3182
|
+
let image;
|
|
3183
|
+
if (data instanceof Uint8Array) {
|
|
3184
|
+
try {
|
|
3185
|
+
image = (0, import_images3.getBinaryImageMetadata)(new DataView(data.buffer, data.byteOffset, data.byteLength));
|
|
3186
|
+
} catch {
|
|
3187
|
+
}
|
|
3188
|
+
}
|
|
3189
|
+
const declaredFormat = resources[0]?.textureFormat;
|
|
3190
|
+
if (!image || !["image/png", "image/jpeg"].includes(image.mimeType) || declaredFormat && (declaredFormat === "png" ? image.mimeType !== "image/png" : declaredFormat === "jpg" ? image.mimeType !== "image/jpeg" : true)) {
|
|
3191
|
+
throw new import_core25.TileConversionError(
|
|
3192
|
+
"I3S_MESH_SOURCE_TEXTURE_UNSUPPORTED",
|
|
3193
|
+
"I3S base-color bytes must have matching PNG/JPEG headers"
|
|
3194
|
+
);
|
|
3195
|
+
}
|
|
3196
|
+
const sampler = Object.fromEntries(
|
|
3197
|
+
["wrapS", "wrapT"].filter((name) => textureInfo[name] !== void 0).map((name) => [name, wrappingModes[textureInfo[name]]])
|
|
3198
|
+
);
|
|
3199
|
+
metallicRoughness.baseColorTexture = {
|
|
3200
|
+
texCoord: 0,
|
|
3201
|
+
texture: {
|
|
3202
|
+
source: { mimeType: image.mimeType, bufferView: { data } },
|
|
3203
|
+
...Object.keys(sampler).length ? { sampler } : {}
|
|
3204
|
+
}
|
|
3205
|
+
};
|
|
3206
|
+
}
|
|
3207
|
+
const mapped = await mapMeshSourceMaterial(
|
|
3208
|
+
material,
|
|
3209
|
+
void 0,
|
|
3210
|
+
contentUri,
|
|
3211
|
+
signal
|
|
3212
|
+
);
|
|
3213
|
+
if (mapped?.baseColorTexture && !content.sourceAttributes?.uv0) {
|
|
3214
|
+
throw new import_core25.TileConversionError(
|
|
3215
|
+
"MESH_TEXCOORD_REQUIRED",
|
|
3216
|
+
"Base-color textures require TEXCOORD_0"
|
|
3217
|
+
);
|
|
3218
|
+
}
|
|
3219
|
+
return mapped;
|
|
3220
|
+
}
|
|
3221
|
+
|
|
3222
|
+
// src/v5/i3s-mesh-source-features.ts
|
|
3223
|
+
var import_i3s2 = require("@loaders.gl/i3s");
|
|
3224
|
+
var import_core26 = require("@loaders.gl/tile-converter/v5/core");
|
|
3225
|
+
async function extractI3SMeshFeatures(content, item, metadata, options, readExternalResource, contentUri, signal) {
|
|
3226
|
+
const maximumAttributeBytes = options.maxAttributeBytes;
|
|
3227
|
+
const storage = metadata.tileset.attributeStorageInfo;
|
|
3228
|
+
const attributeUrls = item.tile.header.attributeUrls;
|
|
3229
|
+
if (!options.metadataClass || !options.objectIdProperty || options.rawMetadataField || !Number.isSafeInteger(maximumAttributeBytes) || maximumAttributeBytes <= 0 || !storage?.length || attributeUrls?.length !== storage.length || !readExternalResource || new Set(storage.map((attribute2) => attribute2.name)).size !== storage.length || new Set(storage.map((attribute2) => attribute2.key)).size !== storage.length || !storage.some((attribute2) => attribute2.name === options.objectIdProperty) || (metadata.tileset.fields || []).some(
|
|
3230
|
+
(field) => field.domain || field.type === "esriFieldTypeDate" || !storage.some((attribute2) => attribute2.name === field.name)
|
|
3231
|
+
)) {
|
|
3232
|
+
throw new import_core26.TileConversionError(
|
|
3233
|
+
"I3S_MESH_SOURCE_FEATURE_MAPPING_REQUIRED",
|
|
3234
|
+
"I3S attributes require an explicit scalar schema, object ID column, byte budget and resource reader; domains/dates/raw metadata need a custom mapper"
|
|
3235
|
+
);
|
|
3236
|
+
}
|
|
3237
|
+
const attributeLoader = await import_i3s2.I3SAttributeLoader.preload();
|
|
3238
|
+
const columns = /* @__PURE__ */ Object.create(null);
|
|
3239
|
+
let inputBytes = 0;
|
|
3240
|
+
let count;
|
|
3241
|
+
for (const [index, attribute2] of storage.entries()) {
|
|
3242
|
+
signal?.throwIfAborted();
|
|
3243
|
+
const values = attribute2.objectIds ?? attribute2.attributeValues;
|
|
3244
|
+
const type = values?.valueType;
|
|
3245
|
+
const expectedOrdering = attribute2.objectIds ? ["objectIds"] : type === "String" ? ["attributeByteCounts", "attributeValues"] : ["attributeValues"];
|
|
3246
|
+
const expectedHeader = type === "String" ? [
|
|
3247
|
+
{ property: "count", valueType: "UInt32" },
|
|
3248
|
+
{ property: "attributeValuesByteCount", valueType: "UInt32" }
|
|
3249
|
+
] : [{ property: "count", valueType: "UInt32" }];
|
|
3250
|
+
if (!type || values?.valuesPerElement !== 1 || attribute2.objectIds && attribute2.attributeValues || attribute2.header?.length !== expectedHeader.length || expectedHeader.some(
|
|
3251
|
+
(value, headerIndex) => attribute2.header[headerIndex]?.property !== value.property || attribute2.header[headerIndex]?.valueType !== value.valueType
|
|
3252
|
+
) || attribute2.ordering && JSON.stringify(
|
|
3253
|
+
attribute2.ordering.map((value) => value === "ObjectIds" ? "objectIds" : value)
|
|
3254
|
+
) !== JSON.stringify(expectedOrdering) || type === "String" && (values.encoding !== "UTF-8" || attribute2.attributeByteCounts?.valueType !== "UInt32" || attribute2.attributeByteCounts.valuesPerElement !== 1) || !attributeUrls[index]) {
|
|
3255
|
+
throw new import_core26.TileConversionError(
|
|
3256
|
+
"I3S_MESH_SOURCE_ATTRIBUTE_UNSUPPORTED",
|
|
3257
|
+
`Attribute ${attribute2.name} requires a standard scalar/string resource layout`
|
|
3258
|
+
);
|
|
3259
|
+
}
|
|
3260
|
+
const bytes = await readExternalResource(attributeUrls[index], contentUri, signal);
|
|
3261
|
+
signal?.throwIfAborted();
|
|
3262
|
+
inputBytes += bytes instanceof Uint8Array ? bytes.byteLength : Infinity;
|
|
3263
|
+
if (!(bytes instanceof Uint8Array) || inputBytes > maximumAttributeBytes) {
|
|
3264
|
+
throw new import_core26.TileConversionError(
|
|
3265
|
+
"INPUT_RESOURCE_TOO_LARGE",
|
|
3266
|
+
"I3S node attributes exceed maxAttributeBytes"
|
|
3267
|
+
);
|
|
3268
|
+
}
|
|
3269
|
+
let parsed;
|
|
3270
|
+
try {
|
|
3271
|
+
parsed = await attributeLoader.parse(Uint8Array.from(bytes).buffer, {
|
|
3272
|
+
attributeName: attribute2.name,
|
|
3273
|
+
attributeType: type,
|
|
3274
|
+
i3s: { attributeValues: "exact" }
|
|
3275
|
+
});
|
|
3276
|
+
} catch (error) {
|
|
3277
|
+
throw new import_core26.TileConversionError(
|
|
3278
|
+
"I3S_MESH_SOURCE_ATTRIBUTE_INVALID",
|
|
3279
|
+
`Invalid attribute ${attribute2.name}: ${error instanceof Error ? error.message : String(error)}`
|
|
3280
|
+
);
|
|
3281
|
+
}
|
|
3282
|
+
const column = parsed[attribute2.name];
|
|
3283
|
+
if (!column || count !== void 0 && column.length !== count || !column.length) {
|
|
3284
|
+
throw new import_core26.TileConversionError(
|
|
3285
|
+
"I3S_MESH_SOURCE_ATTRIBUTE_COUNT_INVALID",
|
|
3286
|
+
"Every attribute must have the same nonzero row count"
|
|
3287
|
+
);
|
|
3288
|
+
}
|
|
3289
|
+
if (Array.from(column).some(
|
|
3290
|
+
(value) => typeof value === "number" && !Number.isFinite(value)
|
|
3291
|
+
)) {
|
|
3292
|
+
throw new import_core26.TileConversionError(
|
|
3293
|
+
"I3S_MESH_SOURCE_ATTRIBUTE_UNSUPPORTED",
|
|
3294
|
+
"Numeric noData values require an explicit custom mapper"
|
|
3295
|
+
);
|
|
3296
|
+
}
|
|
3297
|
+
count = column.length;
|
|
3298
|
+
columns[attribute2.name] = column;
|
|
3299
|
+
}
|
|
3300
|
+
const identifiers = columns[options.objectIdProperty];
|
|
3301
|
+
const attributeRows = /* @__PURE__ */ new Map();
|
|
3302
|
+
for (let index = 0; index < count; index++) {
|
|
3303
|
+
const identifier = identifiers[index];
|
|
3304
|
+
if (!(typeof identifier === "bigint" || typeof identifier === "number" && Number.isSafeInteger(identifier)) || attributeRows.has(BigInt(identifier))) {
|
|
3305
|
+
throw new import_core26.TileConversionError(
|
|
3306
|
+
"I3S_MESH_SOURCE_FEATURE_ID_UNSUPPORTED",
|
|
3307
|
+
"Object IDs must be unique exact integers"
|
|
3308
|
+
);
|
|
3309
|
+
}
|
|
3310
|
+
attributeRows.set(BigInt(identifier), index);
|
|
3311
|
+
}
|
|
3312
|
+
const vertexCount = content.vertexCount;
|
|
3313
|
+
const geometryIdentifiers = content.featureIds;
|
|
3314
|
+
const attribute = content.sourceAttributes?.id;
|
|
3315
|
+
if (geometryIdentifiers.length !== vertexCount || attribute?.size !== 1 || attribute.normalized || attribute.value.length !== vertexCount || Array.from(geometryIdentifiers).some((value, index) => value !== attribute.value[index])) {
|
|
3316
|
+
throw new import_core26.TileConversionError(
|
|
3317
|
+
"I3S_MESH_SOURCE_FEATURE_ID_UNSUPPORTED",
|
|
3318
|
+
"Geometry requires one exact unnormalized object ID per vertex"
|
|
3319
|
+
);
|
|
3320
|
+
}
|
|
3321
|
+
const indices = content.indices;
|
|
3322
|
+
const triangleFeatureIndices = new Uint32Array((indices?.length ?? vertexCount) / 3);
|
|
3323
|
+
if (content.drawRanges?.length) {
|
|
3324
|
+
const covered = new Uint8Array(triangleFeatureIndices.length);
|
|
3325
|
+
for (const range of content.drawRanges) {
|
|
3326
|
+
if (!Number.isSafeInteger(range.firstPrimitive) || range.firstPrimitive < 0 || !Number.isSafeInteger(range.primitiveCount) || range.primitiveCount <= 0 || range.firstPrimitive + range.primitiveCount > covered.length) {
|
|
3327
|
+
throw new import_core26.TileConversionError(
|
|
3328
|
+
"I3S_MESH_SOURCE_FEATURE_ASSOCIATION_INVALID",
|
|
3329
|
+
"Feature draw ranges must cover valid triangles"
|
|
3330
|
+
);
|
|
3331
|
+
}
|
|
3332
|
+
for (let triangle = range.firstPrimitive; triangle < range.firstPrimitive + range.primitiveCount; triangle++) {
|
|
3333
|
+
const vertex = indices?.[triangle * 3] ?? triangle * 3;
|
|
3334
|
+
if (covered[triangle] || geometryIdentifiers[vertex] !== range.featureId) {
|
|
3335
|
+
throw new import_core26.TileConversionError(
|
|
3336
|
+
"I3S_MESH_SOURCE_FEATURE_ASSOCIATION_INVALID",
|
|
3337
|
+
"Feature draw ranges must agree with decoded ownership without overlapping"
|
|
3338
|
+
);
|
|
3339
|
+
}
|
|
3340
|
+
covered[triangle] = 1;
|
|
3341
|
+
}
|
|
3342
|
+
}
|
|
3343
|
+
if (covered.some((value) => value === 0)) {
|
|
3344
|
+
throw new import_core26.TileConversionError(
|
|
3345
|
+
"I3S_MESH_SOURCE_FEATURE_ASSOCIATION_INVALID",
|
|
3346
|
+
"Feature draw ranges must cover every triangle"
|
|
3347
|
+
);
|
|
3348
|
+
}
|
|
3349
|
+
}
|
|
3350
|
+
const usedRows = /* @__PURE__ */ new Set();
|
|
3351
|
+
for (let triangle = 0; triangle < triangleFeatureIndices.length; triangle++) {
|
|
3352
|
+
const corners = [0, 1, 2].map(
|
|
3353
|
+
(corner) => indices?.[triangle * 3 + corner] ?? triangle * 3 + corner
|
|
3354
|
+
);
|
|
3355
|
+
const featureIds = corners.map((corner) => geometryIdentifiers[corner]);
|
|
3356
|
+
const row = attributeRows.get(BigInt(featureIds[0]));
|
|
3357
|
+
if (row === void 0 || featureIds.some((identifier) => identifier !== featureIds[0])) {
|
|
3358
|
+
throw new import_core26.TileConversionError(
|
|
3359
|
+
"I3S_MESH_SOURCE_FEATURE_ASSOCIATION_INVALID",
|
|
3360
|
+
"Each triangle must reference one declared attribute row"
|
|
3361
|
+
);
|
|
3362
|
+
}
|
|
3363
|
+
triangleFeatureIndices[triangle] = row;
|
|
3364
|
+
usedRows.add(row);
|
|
3365
|
+
}
|
|
3366
|
+
if (usedRows.size !== count) {
|
|
3367
|
+
throw new import_core26.TileConversionError(
|
|
3368
|
+
"I3S_MESH_SOURCE_FEATURE_ASSOCIATION_INVALID",
|
|
3369
|
+
"Attribute rows without geometry require a separately qualified mapping"
|
|
3370
|
+
);
|
|
3371
|
+
}
|
|
3372
|
+
const stableProperty = options.sourceFeatureIdProperty ?? options.objectIdProperty;
|
|
3373
|
+
if (!Object.hasOwn(columns, stableProperty)) {
|
|
3374
|
+
throw new import_core26.TileConversionError(
|
|
3375
|
+
"I3S_MESH_SOURCE_FEATURE_MAPPING_REQUIRED",
|
|
3376
|
+
"Stable identifier column is missing"
|
|
3377
|
+
);
|
|
3378
|
+
}
|
|
3379
|
+
const batches = convertFeatureAttributesToArrowBatches(
|
|
3380
|
+
Array.from({ length: count }, (_, index) => ({
|
|
3381
|
+
featureId: columns[stableProperty][index],
|
|
3382
|
+
metadataClass: options.metadataClass,
|
|
3383
|
+
properties: Object.fromEntries(
|
|
3384
|
+
Object.entries(columns).filter(([name]) => name !== options.objectIdProperty && name !== stableProperty).map(([name, values]) => [name, values[index]])
|
|
3385
|
+
)
|
|
3386
|
+
})),
|
|
3387
|
+
options
|
|
3388
|
+
);
|
|
3389
|
+
const stableIds = /* @__PURE__ */ new Set();
|
|
3390
|
+
for (const batch of batches) {
|
|
3391
|
+
const column = batch.data.getChild(options.featureIdField || "feature_id");
|
|
3392
|
+
for (let index = 0; index < column.length; index++) {
|
|
3393
|
+
const identifier = column.get(index);
|
|
3394
|
+
if (stableIds.has(identifier)) {
|
|
3395
|
+
throw new import_core26.TileConversionError(
|
|
3396
|
+
"I3S_MESH_SOURCE_FEATURE_ID_UNSUPPORTED",
|
|
3397
|
+
"Stable IDs must be unique"
|
|
3398
|
+
);
|
|
3399
|
+
}
|
|
3400
|
+
stableIds.add(identifier);
|
|
3401
|
+
}
|
|
3402
|
+
}
|
|
3403
|
+
return {
|
|
3404
|
+
batches,
|
|
3405
|
+
triangleFeatureIndices,
|
|
3406
|
+
featureIdField: options.featureIdField || "feature_id",
|
|
3407
|
+
integer64Encoding: options.integer64Encoding
|
|
3408
|
+
};
|
|
3409
|
+
}
|
|
3410
|
+
|
|
3411
|
+
// src/v5/i3s-mesh-source.ts
|
|
3412
|
+
function createI3SMeshTilesetConversionSource(tileset, options = {}) {
|
|
3413
|
+
const source = (0, import_core27.createTilesetConversionSource)(tileset, options);
|
|
3414
|
+
return {
|
|
3415
|
+
/** Validates the layer profile and explicit source decode policy before reading. */
|
|
3416
|
+
async inspect(signal) {
|
|
3417
|
+
if (options.features && options.getFeatures) {
|
|
3418
|
+
throw new import_core27.TileConversionError(
|
|
3419
|
+
"I3S_MESH_SOURCE_FEATURE_MAPPING_REQUIRED",
|
|
3420
|
+
"Choose explicit resource features or a custom getFeatures mapper, not both"
|
|
3421
|
+
);
|
|
3422
|
+
}
|
|
3423
|
+
const metadata = await source.inspect(signal);
|
|
3424
|
+
if (metadata.type !== "I3S" || !["3DObject", "IntegratedMesh"].includes(metadata.tileset.layerType) || tileset.options.i3s?.geometryMode !== "source") {
|
|
3425
|
+
throw new import_core27.TileConversionError(
|
|
3426
|
+
"I3S_MESH_SOURCE_PROFILE_UNSUPPORTED",
|
|
3427
|
+
"I3S mesh extraction requires a 3DObject/IntegratedMesh runtime with geometryMode=source"
|
|
3428
|
+
);
|
|
3429
|
+
}
|
|
3430
|
+
if (!metadata.spatialReference?.sourceCrs) {
|
|
3431
|
+
throw new import_core27.TileConversionError(
|
|
3432
|
+
"I3S_MESH_SOURCE_REFERENCE_REQUIRED",
|
|
3433
|
+
"I3S conversion requires an explicit source CRS"
|
|
3434
|
+
);
|
|
3435
|
+
}
|
|
3436
|
+
return metadata;
|
|
3437
|
+
},
|
|
3438
|
+
/** Converts each supported content entry before shared traversal releases its buffers. */
|
|
3439
|
+
async *read(metadata, signal) {
|
|
3440
|
+
for await (const item of source.read(metadata, signal)) {
|
|
3441
|
+
for (const entry of item.contents) {
|
|
3442
|
+
signal?.throwIfAborted();
|
|
3443
|
+
const content = entry.payload;
|
|
3444
|
+
const attributes = content?.sourceAttributes;
|
|
3445
|
+
if (content?.geometryMode !== "source" || content.topology !== "triangle-list" || !attributes || !(attributes.position?.value instanceof Float64Array) || content.spatialReference?.status !== "native" || [
|
|
3446
|
+
"sourceCrs",
|
|
3447
|
+
"verticalCrs",
|
|
3448
|
+
"axisOrder",
|
|
3449
|
+
"units",
|
|
3450
|
+
"verticalUnitScale",
|
|
3451
|
+
"heightReference",
|
|
3452
|
+
"elevationMode",
|
|
3453
|
+
"elevationOffset",
|
|
3454
|
+
"elevationUnitScale",
|
|
3455
|
+
"coordinateEpoch"
|
|
3456
|
+
].some(
|
|
3457
|
+
(field) => JSON.stringify(content.spatialReference?.[field]) !== JSON.stringify(metadata.spatialReference?.[field])
|
|
3458
|
+
)) {
|
|
3459
|
+
throw new import_core27.TileConversionError(
|
|
3460
|
+
"I3S_MESH_SOURCE_GEOMETRY_UNSUPPORTED",
|
|
3461
|
+
"I3S extraction requires original source-frame Float64 triangle geometry"
|
|
3462
|
+
);
|
|
3463
|
+
}
|
|
3464
|
+
if (!["earth-centered", "vertex-reference-frame"].includes(
|
|
3465
|
+
content.normalReferenceFrame || ""
|
|
3466
|
+
) || content.normalReferenceFrame !== (metadata.tileset.store?.normalReferenceFrame || "earth-centered")) {
|
|
3467
|
+
throw new import_core27.TileConversionError(
|
|
3468
|
+
"I3S_MESH_SOURCE_NORMAL_FRAME_UNSUPPORTED",
|
|
3469
|
+
"Unknown I3S vector basis"
|
|
3470
|
+
);
|
|
3471
|
+
}
|
|
3472
|
+
if (content.meshSegmentation || Object.entries(attributes).some(
|
|
3473
|
+
([name, value]) => value && !["position", "normal", "uv0", "id", "faceRange"].includes(name)
|
|
3474
|
+
)) {
|
|
3475
|
+
throw new import_core27.TileConversionError(
|
|
3476
|
+
"I3S_MESH_SOURCE_ATTRIBUTE_UNSUPPORTED",
|
|
3477
|
+
"Only positions, normals, TEXCOORD_0 and explicitly mapped feature associations are supported"
|
|
3478
|
+
);
|
|
3479
|
+
}
|
|
3480
|
+
const hasFeatures = content.featureIds.length > 0 || metadata.tileset.fields?.length || metadata.tileset.attributeStorageInfo?.length;
|
|
3481
|
+
if (Array.from(content.featureIds).some((value) => !Number.isSafeInteger(value))) {
|
|
3482
|
+
throw new import_core27.TileConversionError(
|
|
3483
|
+
"I3S_MESH_SOURCE_FEATURE_ID_UNSUPPORTED",
|
|
3484
|
+
"Decoded numeric feature IDs must be exact safe integers"
|
|
3485
|
+
);
|
|
3486
|
+
}
|
|
3487
|
+
if (hasFeatures && !options.getFeatures && !options.features) {
|
|
3488
|
+
throw new import_core27.TileConversionError(
|
|
3489
|
+
"I3S_MESH_SOURCE_FEATURE_MAPPING_REQUIRED",
|
|
3490
|
+
"I3S feature data requires explicit resource features or a custom Arrow mapper"
|
|
3491
|
+
);
|
|
3492
|
+
}
|
|
3493
|
+
const mesh = validateMeshGeometry(
|
|
3494
|
+
{
|
|
3495
|
+
mode: 4,
|
|
3496
|
+
topology: "triangle-list",
|
|
3497
|
+
attributes: {
|
|
3498
|
+
POSITION: attributes.position,
|
|
3499
|
+
...attributes.normal ? { NORMAL: attributes.normal } : {},
|
|
3500
|
+
...attributes.uv0 ? { TEXCOORD_0: attributes.uv0 } : {}
|
|
3501
|
+
},
|
|
3502
|
+
...content.indices ? { indices: { value: content.indices, size: 1 } } : {}
|
|
3503
|
+
},
|
|
3504
|
+
true
|
|
3505
|
+
);
|
|
3506
|
+
if (mesh.attributes.POSITION.value.length / 3 !== content.vertexCount) {
|
|
3507
|
+
throw new import_core27.TileConversionError(
|
|
3508
|
+
"I3S_MESH_SOURCE_COUNT_INVALID",
|
|
3509
|
+
"I3S vertexCount must match the decoded geometry"
|
|
3510
|
+
);
|
|
3511
|
+
}
|
|
3512
|
+
const features = options.getFeatures ? await options.getFeatures(content, item, metadata, signal) : options.features ? await extractI3SMeshFeatures(
|
|
3513
|
+
content,
|
|
3514
|
+
item,
|
|
3515
|
+
metadata,
|
|
3516
|
+
options.features,
|
|
3517
|
+
options.readExternalResource,
|
|
3518
|
+
entry.uri,
|
|
3519
|
+
signal
|
|
3520
|
+
) : void 0;
|
|
3521
|
+
signal?.throwIfAborted();
|
|
3522
|
+
if (hasFeatures && !features) {
|
|
3523
|
+
throw new import_core27.TileConversionError(
|
|
3524
|
+
"I3S_MESH_SOURCE_FEATURE_MAPPING_REQUIRED",
|
|
3525
|
+
"The feature mapper must return all declared feature data"
|
|
3526
|
+
);
|
|
3527
|
+
}
|
|
3528
|
+
const mappedMaterial = await mapI3SMeshSourceMaterial(
|
|
3529
|
+
content,
|
|
3530
|
+
item.tile.header,
|
|
3531
|
+
options.readExternalResource,
|
|
3532
|
+
entry.uri,
|
|
3533
|
+
signal
|
|
3534
|
+
);
|
|
3535
|
+
signal?.throwIfAborted();
|
|
3536
|
+
yield {
|
|
3537
|
+
id: `${item.tile.id}/${entry.index}`,
|
|
3538
|
+
origin: [0, 0, 0],
|
|
3539
|
+
mesh,
|
|
3540
|
+
normalReferenceFrame: content.normalReferenceFrame === "vertex-reference-frame" ? "vertex-reference-frame" : "earth-centered",
|
|
3541
|
+
features,
|
|
3542
|
+
material: mappedMaterial
|
|
3543
|
+
};
|
|
3544
|
+
}
|
|
3545
|
+
}
|
|
3546
|
+
}
|
|
3547
|
+
};
|
|
3548
|
+
}
|