@loaders.gl/tile-converter 5.0.0-alpha.11 → 5.0.0-alpha.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (152) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +180 -4
  3. package/bin/converter-v5.js +2 -0
  4. package/dist/converter.min.cjs +172 -182
  5. package/dist/i3s-server/bin/i3s-server.min.cjs +113 -158
  6. package/dist/index.cjs +18832 -19228
  7. package/dist/index.js +19566 -19968
  8. package/dist/v5/adapters.cjs +2002 -230
  9. package/dist/v5/adapters.d.ts +20 -0
  10. package/dist/v5/adapters.js +2005 -214
  11. package/dist/v5/archive-source.d.ts +36 -0
  12. package/dist/v5/browser-tileset-source.d.ts +55 -11
  13. package/dist/v5/browser.cjs +18 -0
  14. package/dist/v5/browser.d.ts +15 -1
  15. package/dist/v5/browser.js +26 -0
  16. package/dist/v5/conversion-api.d.ts +34 -0
  17. package/dist/v5/converter-cli.d.ts +1 -0
  18. package/dist/v5/converter-cli.js +1230 -0
  19. package/dist/v5/core.cjs +547 -77
  20. package/dist/v5/core.d.ts +3 -1
  21. package/dist/v5/core.js +540 -70
  22. package/dist/v5/feature-arrow.d.ts +1 -1
  23. package/dist/v5/i3s-mesh-conversion.d.ts +5 -1
  24. package/dist/v5/i3s-mesh-source-features.d.ts +2 -2
  25. package/dist/v5/i3s-mesh-source-material.d.ts +1 -1
  26. package/dist/v5/i3s-mesh-source.d.ts +2 -2
  27. package/dist/v5/i3s-selection-source.d.ts +70 -0
  28. package/dist/v5/index.cjs +15 -0
  29. package/dist/v5/index.d.ts +13 -1
  30. package/dist/v5/index.js +22 -0
  31. package/dist/v5/mesh-conversion.d.ts +21 -3
  32. package/dist/v5/mesh-draco.d.ts +2 -2
  33. package/dist/v5/mesh-features.d.ts +2 -2
  34. package/dist/v5/mesh-hierarchy.d.ts +47 -0
  35. package/dist/v5/mesh-source-features.d.ts +6 -5
  36. package/dist/v5/mesh-source-schema.d.ts +12 -0
  37. package/dist/v5/mesh-source-texture.d.ts +3 -3
  38. package/dist/v5/mesh-source.d.ts +10 -7
  39. package/dist/v5/mesh-tileset-sink.d.ts +5 -0
  40. package/dist/v5/mesh.d.ts +1 -1
  41. package/dist/v5/node-cli.d.ts +9 -0
  42. package/dist/v5/node-conversion.d.ts +34 -0
  43. package/dist/v5/node-feature-schema.d.ts +3 -0
  44. package/dist/v5/node-i3s-source.d.ts +41 -0
  45. package/dist/v5/node-source.d.ts +47 -0
  46. package/dist/v5/node.cjs +913 -0
  47. package/dist/v5/node.d.ts +6 -0
  48. package/dist/v5/node.js +890 -0
  49. package/dist/v5/point-cloud-attributes.d.ts +19 -0
  50. package/dist/v5/point-cloud-conversion.d.ts +1 -1
  51. package/dist/v5/point-cloud-copc.d.ts +44 -0
  52. package/dist/v5/point-cloud-file.d.ts +83 -0
  53. package/dist/v5/point-cloud-potree.d.ts +47 -0
  54. package/dist/v5/point-cloud-source-encoder.d.ts +1 -1
  55. package/dist/v5/point-cloud-source.d.ts +1 -1
  56. package/dist/v5/point-cloud-tileset.d.ts +44 -0
  57. package/dist/v5/point-cloud.d.ts +1 -1
  58. package/dist/v5/resource-manifest.d.ts +10 -1
  59. package/dist/v5/selected-mesh-source.d.ts +53 -0
  60. package/dist/v5/spatial-conversion.d.ts +1 -1
  61. package/dist/v5/tileset-source.d.ts +1 -1
  62. package/package.json +25 -32
  63. package/src/index.ts +4 -1
  64. package/src/v4/3d-tiles-converter/3d-tiles-converter.ts +11 -12
  65. package/src/v4/3d-tiles-converter/helpers/3d-tiles-content-converter.ts +7 -3
  66. package/src/v4/3d-tiles-converter/helpers/i3s-obb-to-3d-tiles-obb.ts +2 -2
  67. package/src/v4/3d-tiles-converter/helpers/load-i3s.ts +4 -6
  68. package/src/v4/3d-tiles-converter/helpers/texture-atlas.ts +1 -1
  69. package/src/v4/3d-tiles-converter/helpers/validate-i3s-spatial-reference.ts +2 -2
  70. package/src/v4/3d-tiles-converter/json-templates/tileset.ts +58 -39
  71. package/src/v4/converter-cli.ts +10 -4
  72. package/src/v4/deps-installer/deps-installer.ts +3 -3
  73. package/src/v4/i3s-converter/helpers/attribute-metadata-info.ts +1 -1
  74. package/src/v4/i3s-converter/helpers/batch-ids-extensions.ts +9 -10
  75. package/src/v4/i3s-converter/helpers/conversion-gltf.ts +108 -0
  76. package/src/v4/i3s-converter/helpers/coordinate-converter.ts +6 -5
  77. package/src/v4/i3s-converter/helpers/create-scene-server-path.ts +5 -6
  78. package/src/v4/i3s-converter/helpers/feature-attributes.ts +4 -4
  79. package/src/v4/i3s-converter/helpers/geometry-attributes.ts +1 -1
  80. package/src/v4/i3s-converter/helpers/geometry-converter.ts +69 -77
  81. package/src/v4/i3s-converter/helpers/gltf-attributes.ts +58 -74
  82. package/src/v4/i3s-converter/helpers/load-3d-tiles.ts +6 -54
  83. package/src/v4/i3s-converter/helpers/node-debug.ts +7 -3
  84. package/src/v4/i3s-converter/helpers/node-index-document.ts +11 -9
  85. package/src/v4/i3s-converter/helpers/node-pages.ts +3 -4
  86. package/src/v4/i3s-converter/helpers/preprocess-3d-tiles.ts +2 -2
  87. package/src/v4/i3s-converter/helpers/tileset-traversal.ts +1 -1
  88. package/src/v4/i3s-converter/i3s-converter.ts +29 -31
  89. package/src/v4/i3s-converter/json-templates/geometry-definitions.ts +71 -66
  90. package/src/v4/i3s-converter/json-templates/layers.ts +97 -132
  91. package/src/v4/i3s-converter/json-templates/metadata.ts +31 -21
  92. package/src/v4/i3s-converter/json-templates/node.ts +60 -74
  93. package/src/v4/i3s-converter/json-templates/scene-server.ts +36 -27
  94. package/src/v4/i3s-converter/json-templates/shared-resources.ts +62 -123
  95. package/src/v4/i3s-converter/json-templates/store.ts +39 -57
  96. package/src/v4/i3s-converter/types.ts +4 -4
  97. package/src/v4/i3s-server/controllers/slpk-controller.ts +3 -3
  98. package/src/v4/i3s-server/utils/create-scene-server.ts +3 -3
  99. package/src/v4/lib/utils/builder-input.ts +8 -0
  100. package/src/v4/lib/utils/conversion-dump.ts +5 -1
  101. package/src/v4/lib/utils/file-utils.ts +1 -1
  102. package/src/v4/lib/utils/hash-string.ts +21 -0
  103. package/src/v4/lib/utils/lod-conversion-utils.ts +3 -3
  104. package/src/v4/pgm-loader.ts +2 -2
  105. package/src/v5/adapters.ts +53 -0
  106. package/src/v5/archive-source.ts +235 -0
  107. package/src/v5/browser-tileset-source.ts +418 -48
  108. package/src/v5/browser.ts +46 -0
  109. package/src/v5/conversion-api.ts +184 -17
  110. package/src/v5/conversion-archive.ts +2 -2
  111. package/src/v5/converter-cli.ts +28 -0
  112. package/src/v5/core.ts +7 -0
  113. package/src/v5/feature-arrow.ts +2 -2
  114. package/src/v5/i3s-mesh-conversion.ts +41 -3
  115. package/src/v5/i3s-mesh-sink.ts +1 -1
  116. package/src/v5/i3s-mesh-source-features.ts +3 -3
  117. package/src/v5/i3s-mesh-source-material.ts +18 -11
  118. package/src/v5/i3s-mesh-source.ts +3 -2
  119. package/src/v5/i3s-selection-source.ts +298 -0
  120. package/src/v5/index.ts +41 -0
  121. package/src/v5/mesh-conversion.ts +24 -4
  122. package/src/v5/mesh-draco.ts +2 -2
  123. package/src/v5/mesh-features.ts +3 -3
  124. package/src/v5/mesh-hierarchy.ts +648 -0
  125. package/src/v5/mesh-source-features.ts +165 -51
  126. package/src/v5/mesh-source-schema.ts +140 -0
  127. package/src/v5/mesh-source-texture.ts +49 -19
  128. package/src/v5/mesh-source.ts +80 -49
  129. package/src/v5/mesh-tileset-archive.ts +1 -1
  130. package/src/v5/mesh-tileset-sink.ts +2 -2
  131. package/src/v5/mesh.ts +1 -1
  132. package/src/v5/node-cli.ts +201 -0
  133. package/src/v5/node-conversion.ts +362 -0
  134. package/src/v5/node-feature-schema.ts +155 -0
  135. package/src/v5/node-i3s-source.ts +181 -0
  136. package/src/v5/node-source.ts +328 -0
  137. package/src/v5/node.ts +16 -0
  138. package/src/v5/point-cloud-attributes.ts +113 -0
  139. package/src/v5/point-cloud-conversion.ts +1 -1
  140. package/src/v5/point-cloud-copc.ts +289 -0
  141. package/src/v5/point-cloud-file.ts +306 -0
  142. package/src/v5/point-cloud-potree.ts +134 -0
  143. package/src/v5/point-cloud-source-encoder.ts +1 -1
  144. package/src/v5/point-cloud-source.ts +1 -1
  145. package/src/v5/point-cloud-spatial.ts +2 -2
  146. package/src/v5/point-cloud-tileset.ts +137 -0
  147. package/src/v5/point-cloud.ts +3 -3
  148. package/src/v5/resource-fetcher.ts +4 -1
  149. package/src/v5/resource-manifest.ts +40 -5
  150. package/src/v5/selected-mesh-source.ts +384 -0
  151. package/src/v5/spatial-conversion.ts +90 -19
  152. package/src/v5/tileset-source.ts +2 -2
@@ -20,32 +20,49 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
20
20
  // src/v5/adapters.ts
21
21
  var adapters_exports = {};
22
22
  __export(adapters_exports, {
23
+ SELECTED_MESH_LIMITS: () => SELECTED_MESH_LIMITS,
23
24
  convertFeatureAttributesToArrowBatches: () => convertFeatureAttributesToArrowBatches,
24
25
  convertPointCloudSource: () => convertPointCloudSource,
26
+ convertPointCloudToPotree: () => convertPointCloudToPotree,
27
+ convertPointCloudToTileset: () => convertPointCloudToTileset,
28
+ createCOPCConversionCodec: () => createCOPCConversionCodec,
25
29
  createI3SMeshConversionCodec: () => createI3SMeshConversionCodec,
26
30
  createI3SMeshSink: () => createI3SMeshSink,
27
31
  createI3SMeshTilesetConversionSource: () => createI3SMeshTilesetConversionSource,
28
32
  createMeshConversionCodec: () => createMeshConversionCodec,
33
+ createMeshHierarchyTilesetSink: () => createMeshHierarchyTilesetSink,
34
+ createMeshSourceFeatureOptions: () => createMeshSourceFeatureOptions,
29
35
  createMeshTilesetConversionSource: () => createMeshTilesetConversionSource,
30
36
  createMeshTilesetSink: () => createMeshTilesetSink,
31
37
  createPointCloudTilesetSink: () => createPointCloudTilesetSink,
38
+ createSelectedI3SMeshConversionSource: () => createSelectedI3SMeshConversionSource,
39
+ createSelectedMeshConversionSource: () => createSelectedMeshConversionSource,
32
40
  createSingleMeshI3SSink: () => createSingleMeshI3SSink,
33
41
  createSingleMeshTilesetArchive: () => createSingleMeshTilesetArchive,
34
42
  createSingleMeshTilesetSink: () => createSingleMeshTilesetSink,
35
43
  createTileConversionArchive: () => createTileConversionArchive,
44
+ createTileConversionArchiveTransport: () => createTileConversionArchiveTransport,
36
45
  createTileConversionResourceFetcher: () => createTileConversionResourceFetcher,
37
46
  encodeMeshTile: () => encodeMeshTile,
47
+ encodePointCloudCOPC: () => encodePointCloudCOPC,
38
48
  encodePointCloudSource: () => encodePointCloudSource,
39
49
  encodePointCloudSourceTile: () => encodePointCloudSourceTile,
40
50
  encodePointCloudTile: () => encodePointCloudTile,
41
51
  encodePointCloudTileWithMetadata: () => encodePointCloudTileWithMetadata,
52
+ encodePotreeArchive: () => encodePotreeArchive,
42
53
  encodeTileConversionArchiveInBatches: () => encodeTileConversionArchiveInBatches,
54
+ extractMeshVertices: () => extractMeshVertices,
55
+ inspectI3SMeshConversionSource: () => inspectI3SMeshConversionSource,
56
+ mapPointCloudAttributes: () => mapPointCloudAttributes,
57
+ measureMeshBytes: () => measureMeshBytes,
58
+ openPointCloudFile: () => openPointCloudFile,
59
+ openTileConversionArchive: () => openTileConversionArchive,
43
60
  transformPointCloudSourceTile: () => transformPointCloudSourceTile
44
61
  });
45
62
  module.exports = __toCommonJS(adapters_exports);
46
63
 
47
64
  // src/v5/feature-arrow.ts
48
- var import_schema_utils = require("@loaders.gl/schema-utils");
65
+ var import_schema_utils = require("@loaders.gl/core/schema-utils");
49
66
  var import_core = require("@loaders.gl/tile-converter/v5/core");
50
67
  var DEFAULT_FEATURE_ID_FIELD = "feature_id";
51
68
  var FEATURE_CLASS_METADATA_KEY = "loaders.gl:feature-class";
@@ -367,8 +384,8 @@ function isBinaryType(dataType) {
367
384
  }
368
385
 
369
386
  // src/v5/point-cloud.ts
370
- var import_d_tiles = require("@loaders.gl/3d-tiles");
371
- var import_schema_utils2 = require("@loaders.gl/schema-utils");
387
+ var import_d_tiles = require("@loaders.gl/tiles-3d/3d-tiles");
388
+ var import_schema_utils2 = require("@loaders.gl/core/schema-utils");
372
389
  var import_core2 = require("@loaders.gl/tile-converter/v5/core");
373
390
  function encodePointCloudTile(pointBatch, options = {}) {
374
391
  return encodePointCloudTileWithMetadata(pointBatch, options).pnts;
@@ -631,8 +648,8 @@ function validateBatchTable(batchTableJson, batchIds) {
631
648
 
632
649
  // src/v5/point-cloud-spatial.ts
633
650
  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");
651
+ var import_tiles_3d = require("@loaders.gl/tiles-3d");
652
+ var import_schema_utils3 = require("@loaders.gl/core/schema-utils");
636
653
  var import_core4 = require("@loaders.gl/tile-converter/v5/core");
637
654
  async function transformPointCloudSourceTile(sourceTile, options) {
638
655
  options.signal?.throwIfAborted();
@@ -641,7 +658,7 @@ async function transformPointCloudSourceTile(sourceTile, options) {
641
658
  const reference = options.spatialContext.spatialReference;
642
659
  const contentReference = content.spatialReference;
643
660
  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)({
661
+ const sourceReference = retainTransformedEcef ? (0, import_tiles_3d.createTilesetSpatialReference)({
645
662
  sourceCrs: "EPSG:4978",
646
663
  coordinateFrame: "geocentric",
647
664
  axisOrder: "xyz",
@@ -655,7 +672,7 @@ async function transformPointCloudSourceTile(sourceTile, options) {
655
672
  "Point content and context must agree on the input CRS, units, height reference and epoch"
656
673
  );
657
674
  }
658
- const sourceFrame = reference.coordinateFrame === "unknown" && reference.sourceCrs ? (0, import_tiles.getSpatialCoordinateFrame)(reference.sourceCrs) : reference.coordinateFrame;
675
+ const sourceFrame = reference.coordinateFrame === "unknown" && reference.sourceCrs ? (0, import_tiles_3d.getSpatialCoordinateFrame)(reference.sourceCrs) : reference.coordinateFrame;
659
676
  const geographic = sourceFrame === "geographic";
660
677
  if (!["xy", "xyz", "unknown"].includes(reference.axisOrder) || (geographic ? !["lnglat", "lnglat-offsets"].includes(content.coordinateSystem) : content.coordinateSystem !== "cartesian" || !["projected", "geocentric", "cartesian", "local"].includes(sourceFrame))) {
661
678
  throw new import_core4.TileConversionError(
@@ -766,7 +783,7 @@ async function transformPointCloudSourceTile(sourceTile, options) {
766
783
  maximum[axis] = Math.max(maximum[axis], value);
767
784
  }
768
785
  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);
786
+ const targetFrame = reference.status === "native" ? sourceFrame : (0, import_tiles_3d.getSpatialCoordinateFrame)(reference.targetCrs || reference.sourceCrs);
770
787
  const targetIsGeographic = targetFrame === "geographic";
771
788
  const attributes = {
772
789
  ...mesh.attributes,
@@ -890,7 +907,7 @@ var import_gltf = require("@loaders.gl/gltf");
890
907
  var import_images = require("@loaders.gl/images");
891
908
 
892
909
  // src/v5/mesh-features.ts
893
- var import_schema_utils4 = require("@loaders.gl/schema-utils");
910
+ var import_schema_utils4 = require("@loaders.gl/core/schema-utils");
894
911
  var import_core8 = require("@loaders.gl/tile-converter/v5/core");
895
912
  function prepareMeshFeatureGeometry(mesh, features) {
896
913
  if (!features) return mesh;
@@ -1444,7 +1461,7 @@ function validateMeshTextureTransform(transform) {
1444
1461
  }
1445
1462
 
1446
1463
  // src/v5/mesh-conversion.ts
1447
- var import_tiles2 = require("@loaders.gl/tiles");
1464
+ var import_tiles_3d2 = require("@loaders.gl/tiles-3d");
1448
1465
  var import_core11 = require("@loaders.gl/tile-converter/v5/core");
1449
1466
 
1450
1467
  // src/v5/mesh-draco.ts
@@ -1587,7 +1604,7 @@ function createMeshConversionCodec(options) {
1587
1604
  }
1588
1605
  const spatialReference = spatialContext.spatialReference;
1589
1606
  const outputCrs = spatialReference.targetCrs || spatialReference.sourceCrs;
1590
- const outputFrame = spatialReference.status === "native" ? spatialReference.coordinateFrame : outputCrs ? (0, import_tiles2.getSpatialCoordinateFrame)(outputCrs) : "unknown";
1607
+ const outputFrame = spatialReference.status === "native" ? spatialReference.coordinateFrame : outputCrs ? (0, import_tiles_3d2.getSpatialCoordinateFrame)(outputCrs) : "unknown";
1591
1608
  if (!["native", "transformed"].includes(spatialReference.status) || !["projected", "geocentric", "local", "cartesian"].includes(outputFrame)) {
1592
1609
  throw new import_core11.TileConversionError(
1593
1610
  "MESH_OUTPUT_FRAME_UNSUPPORTED",
@@ -1676,6 +1693,8 @@ function createMeshConversionCodec(options) {
1676
1693
  }
1677
1694
  yield {
1678
1695
  id: resource.id,
1696
+ contentResourceId: resource.contentResourceId,
1697
+ contentPrimitive: resource.contentPrimitive,
1679
1698
  glb: encoded.glb,
1680
1699
  origin,
1681
1700
  boundingBox: [minimum, maximum],
@@ -1929,8 +1948,8 @@ function createMeshTilesetSink(options) {
1929
1948
  }
1930
1949
 
1931
1950
  // src/v5/conversion-archive.ts
1932
- var import_d_tiles_archive_writer = require("@loaders.gl/3d-tiles/3d-tiles-archive-writer");
1933
- var import_i3s_slpk_writer = require("@loaders.gl/i3s/i3s-slpk-writer");
1951
+ var import_d_tiles_archive_writer = require("@loaders.gl/tiles-3d/3d-tiles/3d-tiles-archive-writer");
1952
+ var import_i3s_slpk_writer = require("@loaders.gl/tiles-3d/i3s/i3s-slpk-writer");
1934
1953
  var import_core14 = require("@loaders.gl/tile-converter/v5/core");
1935
1954
  async function createTileConversionArchive(files, options) {
1936
1955
  const { resources, format, maxArchiveBytes, signal } = prepareArchiveInput(files, options);
@@ -1965,7 +1984,7 @@ function prepareArchiveInput(files, options) {
1965
1984
  }
1966
1985
 
1967
1986
  // src/v5/mesh-tileset-archive.ts
1968
- var import_d_tiles_archive_writer2 = require("@loaders.gl/3d-tiles/3d-tiles-archive-writer");
1987
+ var import_d_tiles_archive_writer2 = require("@loaders.gl/tiles-3d/3d-tiles/3d-tiles-archive-writer");
1969
1988
  var import_core15 = require("@loaders.gl/tile-converter/v5/core");
1970
1989
  async function createSingleMeshTilesetArchive(files, options) {
1971
1990
  const { maxArchiveBytes, signal } = options;
@@ -1987,17 +2006,19 @@ async function createSingleMeshTilesetArchive(files, options) {
1987
2006
  }
1988
2007
 
1989
2008
  // src/v5/mesh-source.ts
1990
- var import_core18 = require("@math.gl/core");
1991
- var import_core19 = require("@loaders.gl/tile-converter/v5/core");
2009
+ var import_core19 = require("@math.gl/core");
2010
+ var import_gltf4 = require("@loaders.gl/gltf");
1992
2011
  var import_core20 = require("@loaders.gl/tile-converter/v5/core");
2012
+ var import_core21 = require("@loaders.gl/tile-converter/v5/core");
1993
2013
 
1994
2014
  // src/v5/mesh-source-texture.ts
1995
2015
  var import_images2 = require("@loaders.gl/images");
2016
+ var import_gltf2 = require("@loaders.gl/gltf");
1996
2017
  var import_core16 = require("@loaders.gl/tile-converter/v5/core");
1997
- function mapMeshSourceTexture(textureInfo) {
1998
- const texture = textureInfo?.texture;
1999
- const image = texture?.source;
2000
- const sampler = texture?.sampler;
2018
+ function mapMeshSourceTexture(gltf, textureInfo) {
2019
+ const texture = textureInfo ? gltf.json.textures?.[textureInfo.index] : void 0;
2020
+ const image = texture?.source === void 0 ? void 0 : gltf.json.images?.[texture.source];
2021
+ const sampler = texture?.sampler === void 0 ? void 0 : gltf.json.samplers?.[texture.sampler];
2001
2022
  const extensions = textureInfo?.extensions || {};
2002
2023
  const hasTransform = Object.hasOwn(extensions, "KHR_texture_transform");
2003
2024
  const transform = extensions.KHR_texture_transform;
@@ -2007,7 +2028,7 @@ function mapMeshSourceTexture(textureInfo) {
2007
2028
  (name) => !["id", "index", "texture", "texCoord", "extensions", "extras"].includes(name)
2008
2029
  ) || !texture || Object.keys(texture).some(
2009
2030
  (name) => !["id", "source", "sampler", "name", "extensions", "extras"].includes(name)
2010
- ) || Object.keys(texture.extensions || {}).length || !image || image.uri !== void 0 && image.bufferView !== void 0 || Object.keys(image.extensions || {}).length || image.uri === void 0 && (!(image.bufferView?.data instanceof Uint8Array) || Object.keys(image.bufferView.extensions || {}).length || !["image/png", "image/jpeg"].includes(image.mimeType || "")) || sampler && (Object.keys(sampler.extensions || {}).length || Object.keys(sampler).some(
2031
+ ) || Object.keys(texture.extensions || {}).length || !image || image.uri !== void 0 && image.bufferView !== void 0 || Object.keys(image.extensions || {}).length || image.uri === void 0 && (image.bufferView === void 0 || !gltf.json.bufferViews?.[image.bufferView] || Object.keys(gltf.json.bufferViews?.[image.bufferView]?.extensions || {}).length || !["image/png", "image/jpeg"].includes(image.mimeType || "")) || sampler && (Object.keys(sampler.extensions || {}).length || Object.keys(sampler).some(
2011
2032
  (name) => ![
2012
2033
  "id",
2013
2034
  "parameters",
@@ -2032,7 +2053,7 @@ function mapMeshSourceTexture(textureInfo) {
2032
2053
  minFilter: sampler.minFilter
2033
2054
  } : void 0;
2034
2055
  return {
2035
- ...mapMeshSourceImage(image),
2056
+ ...mapMeshSourceImage(gltf, image),
2036
2057
  ...hasTransform ? {
2037
2058
  transform: {
2038
2059
  offset: transform.offset,
@@ -2043,9 +2064,12 @@ function mapMeshSourceTexture(textureInfo) {
2043
2064
  ...selectedSampler && Object.values(selectedSampler).some((value) => value !== void 0) ? { sampler: selectedSampler } : {}
2044
2065
  };
2045
2066
  }
2046
- function mapMeshSourceImage(image) {
2067
+ function mapMeshSourceImage(gltf, image) {
2047
2068
  if (image.uri === void 0) {
2048
- return { data: image.bufferView.data, mimeType: image.mimeType };
2069
+ return {
2070
+ data: new import_gltf2.GLTFScenegraph(gltf).getTypedArrayForBufferView(image.bufferView),
2071
+ mimeType: image.mimeType
2072
+ };
2049
2073
  }
2050
2074
  const match = typeof image.uri === "string" ? /^data:(image\/(?:png|jpeg));base64,([A-Za-z0-9+/]+={0,2})$/.exec(image.uri) : null;
2051
2075
  if (!match || image.mimeType !== void 0 && image.mimeType !== match[1]) {
@@ -2065,10 +2089,11 @@ function mapMeshSourceImage(image) {
2065
2089
  mimeType: match[1]
2066
2090
  };
2067
2091
  }
2068
- async function resolveMeshSourceTexture(textureInfo, readExternalResource, contentUri, signal) {
2069
- const image = textureInfo?.texture?.source;
2092
+ async function resolveMeshSourceTexture(gltf, textureInfo, readExternalResource, contentUri, signal) {
2093
+ const texture = textureInfo ? gltf.json.textures?.[textureInfo.index] : void 0;
2094
+ const image = texture?.source === void 0 ? void 0 : gltf.json.images?.[texture.source];
2070
2095
  if (!image || image.uri === void 0 || image.uri.startsWith("data:"))
2071
- return mapMeshSourceTexture(textureInfo);
2096
+ return mapMeshSourceTexture(gltf, textureInfo);
2072
2097
  if (!readExternalResource || image.bufferView !== void 0)
2073
2098
  throw new import_core16.TileConversionError(
2074
2099
  "MESH_SOURCE_TEXTURE_UNSUPPORTED",
@@ -2087,168 +2112,336 @@ async function resolveMeshSourceTexture(textureInfo, readExternalResource, conte
2087
2112
  "MESH_SOURCE_TEXTURE_UNSUPPORTED",
2088
2113
  "External images require matching PNG/JPEG headers"
2089
2114
  );
2115
+ const resolvedImage = {
2116
+ ...image,
2117
+ uri: void 0,
2118
+ mimeType: metadata.mimeType,
2119
+ bufferView: (gltf.json.bufferViews || []).length
2120
+ };
2090
2121
  const resolved = {
2091
- ...textureInfo,
2092
- texture: {
2093
- ...textureInfo.texture,
2094
- source: { ...image, uri: void 0, mimeType: metadata.mimeType, bufferView: { data } }
2122
+ ...gltf,
2123
+ buffers: [
2124
+ ...gltf.buffers,
2125
+ {
2126
+ arrayBuffer: data.buffer,
2127
+ byteOffset: data.byteOffset,
2128
+ byteLength: data.byteLength
2129
+ }
2130
+ ],
2131
+ json: {
2132
+ ...gltf.json,
2133
+ images: gltf.json.images.map(
2134
+ (entry, index) => index === texture.source ? resolvedImage : entry
2135
+ ),
2136
+ bufferViews: [
2137
+ ...gltf.json.bufferViews || [],
2138
+ { buffer: gltf.buffers.length, byteLength: data.byteLength }
2139
+ ]
2095
2140
  }
2096
2141
  };
2097
- return mapMeshSourceTexture(resolved);
2142
+ return mapMeshSourceTexture(resolved, textureInfo);
2098
2143
  }
2099
2144
 
2100
2145
  // src/v5/mesh-source-features.ts
2146
+ var import_gltf3 = require("@loaders.gl/gltf");
2147
+
2148
+ // src/v5/mesh-source-schema.ts
2101
2149
  var import_core17 = require("@loaders.gl/tile-converter/v5/core");
2150
+ var SCALAR_TYPES = {
2151
+ INT8: "int8",
2152
+ UINT8: "uint8",
2153
+ INT16: "int16",
2154
+ UINT16: "uint16",
2155
+ INT32: "int32",
2156
+ UINT32: "uint32",
2157
+ INT64: "int64",
2158
+ UINT64: "uint64",
2159
+ FLOAT32: "float32",
2160
+ FLOAT64: "float64"
2161
+ };
2162
+ function createMeshSourceFeatureOptions(gltf, options) {
2163
+ const metadata = gltf.extensions?.EXT_structural_metadata;
2164
+ const table = metadata?.propertyTables?.[0];
2165
+ const properties = metadata?.schema?.classes?.[options.metadataClass]?.properties;
2166
+ if (!options.metadataClass || metadata?.propertyTables?.length !== 1 || table?.class !== options.metadataClass || !properties || !Number.isSafeInteger(table.count) || table.count < 1 || table.count > 4294967295 || metadata.propertyTextures?.length || metadata.propertyAttributes?.length) {
2167
+ throw new import_core17.TileConversionError(
2168
+ "MESH_FEATURE_SCHEMA_REQUIRED",
2169
+ "Schema mapping requires one inline class, one matching property table and a positive uint32 row count"
2170
+ );
2171
+ }
2172
+ if (Object.keys(table.properties || {}).some((name) => !Object.hasOwn(properties, name))) {
2173
+ throw new import_core17.TileConversionError(
2174
+ "MESH_FEATURE_PROPERTY_UNMAPPED",
2175
+ "Property table columns must be declared in the selected class"
2176
+ );
2177
+ }
2178
+ const fields = Object.entries(properties).map(([name, property]) => {
2179
+ const declaration = property;
2180
+ const column = table.properties?.[name];
2181
+ if (declaration.array || !["SCALAR", "STRING"].includes(declaration.type) || declaration.normalized || declaration.offset !== void 0 || declaration.scale !== void 0 || column?.offset !== void 0 || column?.scale !== void 0 || declaration.required && (declaration.noData !== void 0 || declaration.default !== void 0)) {
2182
+ throw new import_core17.TileConversionError(
2183
+ "MESH_FEATURE_PROPERTY_UNSUPPORTED",
2184
+ `Property ${name} requires an explicit separately qualified mapping`
2185
+ );
2186
+ }
2187
+ const type = declaration.type === "STRING" ? "utf8" : Object.hasOwn(SCALAR_TYPES, declaration.componentType || "") ? SCALAR_TYPES[declaration.componentType] : void 0;
2188
+ if (!type) {
2189
+ throw new import_core17.TileConversionError(
2190
+ "MESH_FEATURE_PROPERTY_UNSUPPORTED",
2191
+ `Property ${name} has no supported declared scalar width`
2192
+ );
2193
+ }
2194
+ return {
2195
+ name,
2196
+ type,
2197
+ nullable: declaration.required !== true,
2198
+ metadata: { "loaders.gl:structural-property": JSON.stringify(declaration) }
2199
+ };
2200
+ });
2201
+ const identifierName = options.featureIdField || "feature_id";
2202
+ const sourceIdentifier = options.sourceFeatureIdProperty;
2203
+ const sourceField = sourceIdentifier ? fields.find((field) => field.name === sourceIdentifier) : void 0;
2204
+ if (sourceIdentifier && !sourceField || fields.some((field) => field.name === identifierName && field !== sourceField) || sourceField && !["utf8", "int8", "uint8", "int16", "uint16", "int32", "uint32", "int64", "uint64"].includes(
2205
+ String(sourceField.type)
2206
+ )) {
2207
+ throw new import_core17.TileConversionError(
2208
+ "MESH_FEATURE_ID_PROPERTY_REQUIRED",
2209
+ "Select a declared string/integer identifier and a distinct output identifier name"
2210
+ );
2211
+ }
2212
+ return {
2213
+ ...options,
2214
+ schema: {
2215
+ fields: [
2216
+ {
2217
+ ...sourceField,
2218
+ name: identifierName,
2219
+ type: sourceField?.type || "uint32",
2220
+ nullable: false
2221
+ },
2222
+ ...fields.filter((field) => field !== sourceField)
2223
+ ],
2224
+ metadata: { "loaders.gl:source-schema": JSON.stringify(metadata.schema) }
2225
+ }
2226
+ };
2227
+ }
2228
+
2229
+ // src/v5/mesh-source-features.ts
2230
+ var import_core18 = require("@loaders.gl/tile-converter/v5/core");
2102
2231
  function extractMeshFeatures(gltf, primitive, payload, mesh, options) {
2103
2232
  const declarations = primitive.extensions?.EXT_mesh_features?.featureIds;
2104
- const metadata = gltf.extensions?.EXT_structural_metadata;
2233
+ const metadata = gltf.json.extensions?.EXT_structural_metadata;
2105
2234
  const legacy = primitive.attributes._BATCHID;
2106
- if (!declarations && !legacy && !metadata && !Object.keys(payload.batchTableJson || {}).length && !payload.header?.batchLength) {
2235
+ if (!declarations && legacy === void 0 && !metadata && !Object.keys(payload.batchTableJson || {}).length && !payload.header?.batchLength) {
2107
2236
  if (Object.keys(primitive.attributes).some((name) => /^_FEATURE_ID_\d+$/.test(name)))
2108
- throw new import_core17.TileConversionError(
2237
+ throw new import_core18.TileConversionError(
2109
2238
  "MESH_FEATURE_MAPPING_REQUIRED",
2110
2239
  "Feature attributes require an EXT_mesh_features declaration"
2111
2240
  );
2112
2241
  return void 0;
2113
2242
  }
2114
2243
  if (!options)
2115
- throw new import_core17.TileConversionError(
2244
+ throw new import_core18.TileConversionError(
2116
2245
  "MESH_FEATURE_SCHEMA_REQUIRED",
2117
- "Feature extraction requires an explicit Arrow schema and class mapping"
2246
+ "Feature extraction requires a class selection and a declared Arrow or inline source schema"
2118
2247
  );
2119
2248
  if (!options.metadataClass)
2120
- throw new import_core17.TileConversionError(
2249
+ throw new import_core18.TileConversionError(
2121
2250
  "MESH_FEATURE_CLASS_REQUIRED",
2122
2251
  "Feature metadata class must be explicit"
2123
2252
  );
2253
+ const mapping = "schema" in options ? options : createMeshSourceFeatureOptions(gltf.json, options);
2254
+ const propertyMappings = /* @__PURE__ */ new Map();
2124
2255
  let count;
2125
- let attribute = legacy;
2256
+ let attributeReference = legacy;
2126
2257
  let properties;
2127
2258
  if (declarations) {
2128
- if (legacy || declarations.length !== 1 || metadata?.propertyTables?.length !== 1 || metadata.propertyTextures?.length || metadata.propertyAttributes?.length)
2129
- throw new import_core17.TileConversionError(
2259
+ if (legacy !== void 0 || declarations.length !== 1 || metadata?.propertyTables?.length !== 1 || metadata.propertyTextures?.length || metadata.propertyAttributes?.length)
2260
+ throw new import_core18.TileConversionError(
2130
2261
  "MESH_FEATURE_MAPPING_UNSUPPORTED",
2131
2262
  "Only one attribute-backed feature set and one property table are supported"
2132
2263
  );
2133
2264
  const declaration = declarations[0];
2134
- if (!Number.isInteger(declaration.attribute) || declaration.attribute < 0 || declaration.propertyTable !== 0 || declaration.texture || declaration.implicit || declaration.nullFeatureId !== void 0)
2135
- throw new import_core17.TileConversionError(
2265
+ if (typeof declaration.attribute !== "number" || !Number.isInteger(declaration.attribute) || declaration.attribute < 0 || declaration.propertyTable !== 0 || declaration.texture || declaration.implicit || declaration.nullFeatureId !== void 0)
2266
+ throw new import_core18.TileConversionError(
2136
2267
  "MESH_FEATURE_MAPPING_UNSUPPORTED",
2137
2268
  "Features require an attribute and property table 0 without null/texture/implicit IDs"
2138
2269
  );
2139
- attribute = primitive.attributes[`_FEATURE_ID_${declaration.attribute}`];
2270
+ attributeReference = primitive.attributes[`_FEATURE_ID_${declaration.attribute}`];
2140
2271
  const table = metadata.propertyTables[0];
2141
- if (table.class !== options.metadataClass || table.count !== declaration.featureCount)
2142
- throw new import_core17.TileConversionError(
2272
+ if (table.class !== options.metadataClass || !Number.isSafeInteger(declaration.featureCount) || declaration.featureCount <= 0 || declaration.featureCount > table.count)
2273
+ throw new import_core18.TileConversionError(
2143
2274
  "MESH_FEATURE_CLASS_MISMATCH",
2144
- "Feature class and declared table counts must agree"
2275
+ "Feature class must agree and declared feature count must fit the property table"
2145
2276
  );
2146
2277
  count = table.count;
2147
- properties = {};
2278
+ if (!Number.isSafeInteger(count) || count <= 0 || count > 4294967295) {
2279
+ throw new import_core18.TileConversionError(
2280
+ "MESH_FEATURE_IDS_INVALID",
2281
+ "Feature row count must fit uint32 indices"
2282
+ );
2283
+ }
2284
+ properties = /* @__PURE__ */ Object.create(null);
2148
2285
  const classProperties = metadata.schema?.classes?.[table.class]?.properties;
2149
2286
  if (!classProperties)
2150
- throw new import_core17.TileConversionError(
2287
+ throw new import_core18.TileConversionError(
2151
2288
  "MESH_FEATURE_SCHEMA_REQUIRED",
2152
2289
  "A decoded inline structural metadata class is required"
2153
2290
  );
2154
2291
  if (Object.keys(table.properties || {}).some((name) => !Object.hasOwn(classProperties, name)))
2155
- throw new import_core17.TileConversionError(
2292
+ throw new import_core18.TileConversionError(
2156
2293
  "MESH_FEATURE_PROPERTY_UNMAPPED",
2157
2294
  "Property table columns must be declared in the metadata class"
2158
2295
  );
2159
2296
  for (const [name, property] of Object.entries(classProperties)) {
2160
2297
  const column = table.properties?.[name];
2161
2298
  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(
2299
+ throw new import_core18.TileConversionError(
2163
2300
  "MESH_FEATURE_PROPERTY_UNSUPPORTED",
2164
2301
  `Property ${name} requires a separately qualified semantic mapping`
2165
2302
  );
2166
2303
  const data = column?.data;
2167
- if (data === void 0 && property.default === void 0)
2168
- throw new import_core17.TileConversionError(
2304
+ if (column !== void 0 && (data === void 0 || data === null) || data === void 0 && property.required === true)
2305
+ throw new import_core18.TileConversionError(
2169
2306
  "MESH_FEATURE_PROPERTY_UNAVAILABLE",
2170
2307
  `Property ${name} has not been decoded`
2171
2308
  );
2172
- const decoded = data ?? Array.from({ length: count }, () => property.default);
2309
+ const integer64 = ["INT64", "UINT64"].includes(property.componentType || "");
2310
+ if (integer64 && property.default !== void 0 && (typeof property.default !== "number" || !Number.isSafeInteger(property.default))) {
2311
+ throw new import_core18.TileConversionError(
2312
+ "MESH_FEATURE_PROPERTY_UNSUPPORTED",
2313
+ `Property ${name} requires an exact 64-bit default`
2314
+ );
2315
+ }
2316
+ const defaultValue = integer64 && property.default !== void 0 ? BigInt(property.default) : property.default ?? null;
2317
+ const decoded = data;
2173
2318
  if (property.noData === void 0 || data === void 0) {
2174
- properties[name] = decoded;
2319
+ if (decoded !== void 0) properties[name] = decoded;
2320
+ else propertyMappings.set(name, { defaultValue });
2175
2321
  continue;
2176
2322
  }
2177
2323
  const sentinel = property.noData;
2178
- const integer64 = ["INT64", "UINT64"].includes(property.componentType || "");
2179
2324
  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(
2325
+ throw new import_core18.TileConversionError(
2181
2326
  "MESH_FEATURE_PROPERTY_UNSUPPORTED",
2182
2327
  `Property ${name} requires a valid decoded noData mapping`
2183
2328
  );
2184
2329
  const comparable = integer64 ? BigInt(sentinel) : sentinel;
2185
- properties[name] = Array.from(
2186
- decoded,
2187
- (value) => value === comparable ? property.default ?? null : value
2188
- );
2330
+ const values = decoded;
2331
+ properties[name] = values;
2332
+ propertyMappings.set(name, { noData: comparable, defaultValue });
2189
2333
  }
2190
2334
  } else {
2191
2335
  if (metadata)
2192
- throw new import_core17.TileConversionError(
2336
+ throw new import_core18.TileConversionError(
2193
2337
  "MESH_FEATURE_MAPPING_UNSUPPORTED",
2194
2338
  "Structural metadata requires an attribute-backed feature declaration"
2195
2339
  );
2196
2340
  count = payload.header?.batchLength ?? Number(payload.featureTableJson?.BATCH_LENGTH);
2197
2341
  properties = payload.batchTableJson || {};
2198
2342
  }
2343
+ const accessor = attributeReference === void 0 ? void 0 : (0, import_gltf3.getGLTFAccessor)(gltf, attributeReference);
2344
+ const attribute = accessor ? {
2345
+ ...accessor,
2346
+ components: accessor.type === "SCALAR" ? 1 : Number(accessor.type.slice(3)),
2347
+ value: (0, import_gltf3.getGLTFAccessorValue)(gltf, accessor)
2348
+ } : void 0;
2199
2349
  const vertexCount = mesh.attributes.POSITION.value.length / 3;
2200
- if (!Number.isSafeInteger(count) || count <= 0 || !attribute || attribute.components !== 1 || attribute.normalized || attribute.value.length !== vertexCount || Array.from(attribute.value).some(
2201
- (value) => !Number.isSafeInteger(value) || value < 0 || value >= count
2202
- ))
2203
- throw new import_core17.TileConversionError(
2350
+ if (!Number.isSafeInteger(count) || count <= 0 || !attribute || attribute.components !== 1 || attribute.normalized || attribute.value.length !== vertexCount)
2351
+ throw new import_core18.TileConversionError(
2204
2352
  "MESH_FEATURE_IDS_INVALID",
2205
2353
  "Feature IDs must be one valid property-table row index per vertex"
2206
2354
  );
2355
+ const vertexRows = declarations ? /* @__PURE__ */ new Set() : void 0;
2356
+ for (const value of attribute.value) {
2357
+ if (!Number.isSafeInteger(value) || value < 0 || value >= count)
2358
+ throw new import_core18.TileConversionError(
2359
+ "MESH_FEATURE_IDS_INVALID",
2360
+ "Feature IDs must reference valid property-table rows"
2361
+ );
2362
+ vertexRows?.add(Number(value));
2363
+ }
2364
+ if (declarations && vertexRows.size !== declarations[0].featureCount)
2365
+ throw new import_core18.TileConversionError(
2366
+ "MESH_FEATURE_IDS_INVALID",
2367
+ "Declared feature count must match distinct vertex feature IDs"
2368
+ );
2207
2369
  const selectedAttribute = declarations ? `_FEATURE_ID_${declarations[0].attribute}` : "_BATCHID";
2208
2370
  if (Object.keys(primitive.attributes).some(
2209
2371
  (name) => (name === "_BATCHID" || /^_FEATURE_ID_\d+$/.test(name)) && name !== selectedAttribute
2210
2372
  ))
2211
- throw new import_core17.TileConversionError(
2373
+ throw new import_core18.TileConversionError(
2212
2374
  "MESH_FEATURE_MAPPING_UNSUPPORTED",
2213
2375
  "Additional feature ID sets require separate mappings"
2214
2376
  );
2215
2377
  for (const [name, values] of Object.entries(properties)) {
2216
2378
  if (!values || !Array.isArray(values) && !ArrayBuffer.isView(values) || values.length !== count)
2217
- throw new import_core17.TileConversionError(
2379
+ throw new import_core18.TileConversionError(
2218
2380
  "MESH_FEATURE_PROPERTY_UNAVAILABLE",
2219
- `Property ${name} must be a decoded column matching the table count`
2381
+ `Property ${name} must be a decoded column matching the feature row count`
2220
2382
  );
2221
2383
  }
2222
2384
  const sourceIdentifier = options.sourceFeatureIdProperty;
2223
- if (sourceIdentifier && !properties[sourceIdentifier])
2224
- throw new import_core17.TileConversionError(
2385
+ if (sourceIdentifier && !properties[sourceIdentifier] && !propertyMappings.has(sourceIdentifier))
2386
+ throw new import_core18.TileConversionError(
2225
2387
  "MESH_FEATURE_ID_PROPERTY_REQUIRED",
2226
2388
  "Stable source identifier property is missing"
2227
2389
  );
2228
- const batches = convertFeatureAttributesToArrowBatches(
2229
- Array.from({ length: count }, (_, index) => ({
2230
- featureId: sourceIdentifier ? properties[sourceIdentifier][index] : index,
2231
- metadataClass: options.metadataClass,
2232
- properties: Object.fromEntries(
2233
- Object.entries(properties).filter(([name]) => name !== sourceIdentifier).map(([name, values]) => [name, values[index]])
2234
- )
2235
- })),
2236
- options
2237
- );
2238
2390
  const indices = mesh.indices?.value;
2239
- const triangleFeatureIndices = new Uint32Array((indices?.length ?? vertexCount) / 3);
2240
- for (let triangle = 0; triangle < triangleFeatureIndices.length; triangle++) {
2241
- const corners = [0, 1, 2].map(
2242
- (corner) => indices?.[triangle * 3 + corner] ?? triangle * 3 + corner
2391
+ const cornerCount = indices?.length ?? vertexCount;
2392
+ if (cornerCount % 3)
2393
+ throw new import_core18.TileConversionError(
2394
+ "MESH_FEATURE_IDS_INVALID",
2395
+ "Triangle geometry requires complete triples"
2243
2396
  );
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(
2397
+ const triangleFeatureIndices = new Uint32Array(cornerCount / 3);
2398
+ const referencedRows = /* @__PURE__ */ new Set();
2399
+ const readFeatureRow = (corner) => {
2400
+ if (!Number.isSafeInteger(corner) || corner < 0 || corner >= vertexCount)
2401
+ throw new import_core18.TileConversionError(
2402
+ "MESH_FEATURE_IDS_INVALID",
2403
+ "Triangle indices must reference valid mesh vertices"
2404
+ );
2405
+ return Number(attribute.value[corner]);
2406
+ };
2407
+ for (let triangle = 0; triangle < triangleFeatureIndices.length; triangle++) {
2408
+ const firstCorner = indices?.[triangle * 3] ?? triangle * 3;
2409
+ const secondCorner = indices?.[triangle * 3 + 1] ?? triangle * 3 + 1;
2410
+ const thirdCorner = indices?.[triangle * 3 + 2] ?? triangle * 3 + 2;
2411
+ const firstRow = readFeatureRow(firstCorner);
2412
+ const secondRow = readFeatureRow(secondCorner);
2413
+ const thirdRow = readFeatureRow(thirdCorner);
2414
+ if ((firstRow !== secondRow || firstRow !== thirdRow) && firstCorner !== secondCorner && firstCorner !== thirdCorner && secondCorner !== thirdCorner)
2415
+ throw new import_core18.TileConversionError(
2247
2416
  "MESH_FEATURE_TRIANGLE_MIXED",
2248
2417
  "All vertices of a nondegenerate triangle must reference the same feature row"
2249
2418
  );
2250
- triangleFeatureIndices[triangle] = Number(rows[0]);
2419
+ triangleFeatureIndices[triangle] = firstRow;
2420
+ referencedRows.add(firstRow);
2251
2421
  }
2422
+ const selectedRows = [...referencedRows].sort((left, right) => left - right);
2423
+ const denseRows = new Map(selectedRows.map((row, index) => [row, index]));
2424
+ for (let triangle = 0; triangle < triangleFeatureIndices.length; triangle++) {
2425
+ triangleFeatureIndices[triangle] = denseRows.get(triangleFeatureIndices[triangle]);
2426
+ }
2427
+ const readProperty = (name, row) => {
2428
+ const values = properties[name];
2429
+ const propertyMapping = propertyMappings.get(name);
2430
+ if (!values) return propertyMapping?.defaultValue;
2431
+ const value = values[row];
2432
+ return propertyMapping && value === propertyMapping.noData ? propertyMapping.defaultValue : value;
2433
+ };
2434
+ const propertyNames = [
2435
+ .../* @__PURE__ */ new Set([...Object.keys(properties), ...propertyMappings.keys()])
2436
+ ].filter((name) => name !== sourceIdentifier);
2437
+ const batches = convertFeatureAttributesToArrowBatches(
2438
+ selectedRows.map((row) => ({
2439
+ featureId: sourceIdentifier ? readProperty(sourceIdentifier, row) : row,
2440
+ metadataClass: options.metadataClass,
2441
+ properties: Object.fromEntries(propertyNames.map((name) => [name, readProperty(name, row)]))
2442
+ })),
2443
+ mapping
2444
+ );
2252
2445
  return {
2253
2446
  batches,
2254
2447
  triangleFeatureIndices,
@@ -2259,20 +2452,20 @@ function extractMeshFeatures(gltf, primitive, payload, mesh, options) {
2259
2452
 
2260
2453
  // src/v5/mesh-source.ts
2261
2454
  function createMeshTilesetConversionSource(tileset, options = {}) {
2262
- const source = (0, import_core20.createTilesetConversionSource)(tileset, options);
2455
+ const source = (0, import_core21.createTilesetConversionSource)(tileset, options);
2263
2456
  return {
2264
2457
  /** Validates the source frame and supported dataset metadata before traversal. */
2265
2458
  async inspect(signal) {
2266
2459
  const metadata = await source.inspect(signal);
2267
2460
  const reference = metadata.spatialReference;
2268
2461
  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")) {
2269
- throw new import_core19.TileConversionError(
2462
+ throw new import_core20.TileConversionError(
2270
2463
  "MESH_SOURCE_FRAME_UNSUPPORTED",
2271
2464
  "Mesh extraction requires native EPSG:4978 xyz coordinates in meters and ellipsoidal heights"
2272
2465
  );
2273
2466
  }
2274
2467
  if (metadata.tileset.metadata || metadata.tileset.groups?.length) {
2275
- throw new import_core19.TileConversionError(
2468
+ throw new import_core20.TileConversionError(
2276
2469
  "MESH_SOURCE_METADATA_UNSUPPORTED",
2277
2470
  "Tileset/group metadata requires a separate mapping"
2278
2471
  );
@@ -2284,64 +2477,70 @@ function createMeshTilesetConversionSource(tileset, options = {}) {
2284
2477
  for await (const item of source.read(inspection, signal)) {
2285
2478
  const placementReference = item.tile.header._spatialReference || tileset.spatialReference;
2286
2479
  if (placementReference && (placementReference.status !== "native" || placementReference.sourceCrs !== "EPSG:4978"))
2287
- throw new import_core19.TileConversionError(
2480
+ throw new import_core20.TileConversionError(
2288
2481
  "MESH_SOURCE_FRAME_UNSUPPORTED",
2289
2482
  "Nested mesh placements require native ECEF coordinates"
2290
2483
  );
2291
2484
  if (item.tile.header.metadata || item.tile.header.implicitMetadata)
2292
- throw new import_core19.TileConversionError(
2485
+ throw new import_core20.TileConversionError(
2293
2486
  "MESH_SOURCE_METADATA_UNSUPPORTED",
2294
2487
  "Tile metadata requires a separate mapping"
2295
2488
  );
2296
2489
  for (const content of item.contents) {
2297
2490
  signal?.throwIfAborted();
2298
2491
  if (content.metadata || content.group !== void 0)
2299
- throw new import_core19.TileConversionError(
2492
+ throw new import_core20.TileConversionError(
2300
2493
  "MESH_SOURCE_METADATA_UNSUPPORTED",
2301
2494
  "Content metadata requires a separate mapping"
2302
2495
  );
2303
2496
  const payload = content.payload;
2304
2497
  if (!payload?.gltf || !["glTF", "gltf", "b3dm"].includes(payload.type || content.type || "")) {
2305
- throw new import_core19.TileConversionError(
2498
+ throw new import_core20.TileConversionError(
2306
2499
  "MESH_SOURCE_CONTENT_UNSUPPORTED",
2307
2500
  "Mesh extraction requires decoded GLB/glTF or B3DM content"
2308
2501
  );
2309
2502
  }
2310
- const gltf = payload.gltf;
2503
+ const gltfWithBuffers = payload.gltf;
2504
+ const gltf = gltfWithBuffers.json;
2311
2505
  if (gltf.animations?.length || gltf.skins?.length || Object.keys(gltf.extensions || {}).some(
2312
2506
  (name) => !["EXT_structural_metadata", "CESIUM_RTC"].includes(name)
2313
2507
  )) {
2314
- throw new import_core19.TileConversionError(
2508
+ throw new import_core20.TileConversionError(
2315
2509
  "MESH_SOURCE_SCENE_UNSUPPORTED",
2316
2510
  "Animated/skinned scenes and unmapped scene extensions are unsupported"
2317
2511
  );
2318
2512
  }
2319
2513
  const axis = payload.gltfUpAxis || "Y";
2320
2514
  if (!["X", "Y", "Z"].includes(axis))
2321
- throw new import_core19.TileConversionError("MESH_SOURCE_AXIS_UNSUPPORTED", "Unknown glTF up axis");
2322
- const placement = new import_core18.Matrix4(item.tile.computedTransform);
2515
+ throw new import_core20.TileConversionError("MESH_SOURCE_AXIS_UNSUPPORTED", "Unknown glTF up axis");
2516
+ const placement = new import_core19.Matrix4(item.tile.computedTransform);
2323
2517
  const rtc = payload.rtcCenter || gltf.extensions?.CESIUM_RTC?.center;
2324
2518
  if (rtc) {
2325
2519
  if (rtc.length !== 3 || rtc.some((value) => !Number.isFinite(value)))
2326
- throw new import_core19.TileConversionError(
2520
+ throw new import_core20.TileConversionError(
2327
2521
  "MESH_SOURCE_TRANSFORM_INVALID",
2328
2522
  "RTC center must contain finite xyz coordinates"
2329
2523
  );
2330
2524
  placement.translate(rtc);
2331
2525
  }
2332
2526
  if (axis === "Y")
2333
- placement.multiplyRight(new import_core18.Matrix4([1, 0, 0, 0, 0, 0, 1, 0, 0, -1, 0, 0, 0, 0, 0, 1]));
2527
+ placement.multiplyRight(new import_core19.Matrix4([1, 0, 0, 0, 0, 0, 1, 0, 0, -1, 0, 0, 0, 0, 0, 1]));
2334
2528
  if (axis === "X")
2335
- placement.multiplyRight(new import_core18.Matrix4([0, 0, 1, 0, 0, 1, 0, 0, -1, 0, 0, 0, 0, 0, 0, 1]));
2336
- const roots = gltf.scene?.nodes || (gltf.scenes?.length === 1 ? gltf.scenes[0].nodes : void 0);
2529
+ placement.multiplyRight(new import_core19.Matrix4([0, 0, 1, 0, 0, 1, 0, 0, -1, 0, 0, 0, 0, 0, 0, 1]));
2530
+ const roots = (gltf.scene !== void 0 ? gltf.scenes?.[gltf.scene]?.nodes : void 0) || (gltf.scenes?.length === 1 ? gltf.scenes[0].nodes : void 0);
2337
2531
  if (!roots?.length)
2338
- throw new import_core19.TileConversionError(
2532
+ throw new import_core20.TileConversionError(
2339
2533
  "MESH_SOURCE_SCENE_UNSUPPORTED",
2340
2534
  "A default scene or exactly one scene is required"
2341
2535
  );
2342
2536
  let primitiveIndex = 0;
2343
- for (const selected of traverseMeshNodes(roots, placement)) {
2537
+ const primitives = traverseMeshNodes(gltfWithBuffers, roots, placement);
2538
+ let current = primitives.next();
2539
+ while (!current.done) {
2540
+ const selected = current.value;
2541
+ const next = primitives.next();
2344
2542
  const resource = await extractPrimitive(
2543
+ gltfWithBuffers,
2345
2544
  selected.primitive,
2346
2545
  selected.transform,
2347
2546
  `${item.tile.id}/${content.index}/${primitiveIndex++}`,
@@ -2350,16 +2549,21 @@ function createMeshTilesetConversionSource(tileset, options = {}) {
2350
2549
  signal
2351
2550
  );
2352
2551
  const features = extractMeshFeatures(
2353
- gltf,
2552
+ gltfWithBuffers,
2354
2553
  selected.primitive,
2355
2554
  payload,
2356
2555
  resource.mesh,
2357
2556
  options.features
2358
2557
  );
2359
- yield { ...resource, features };
2558
+ yield {
2559
+ ...resource,
2560
+ features,
2561
+ contentPrimitive: { index: primitiveIndex - 1, last: Boolean(next.done) }
2562
+ };
2563
+ current = next;
2360
2564
  }
2361
2565
  if (!primitiveIndex)
2362
- throw new import_core19.TileConversionError(
2566
+ throw new import_core20.TileConversionError(
2363
2567
  "MESH_SOURCE_CONTENT_EMPTY",
2364
2568
  "Declared content contains no supported mesh primitive"
2365
2569
  );
@@ -2368,58 +2572,66 @@ function createMeshTilesetConversionSource(tileset, options = {}) {
2368
2572
  }
2369
2573
  };
2370
2574
  }
2371
- function* traverseMeshNodes(nodes, parent, ancestors = /* @__PURE__ */ new Set()) {
2372
- for (const node of nodes) {
2373
- if (ancestors.has(node) || node.skin || node.weights || Object.keys(node.extensions || {}).length)
2374
- throw new import_core19.TileConversionError(
2575
+ function* traverseMeshNodes(gltf, nodes, parent, ancestors = /* @__PURE__ */ new Set()) {
2576
+ for (const nodeIndex of nodes) {
2577
+ const node = gltf.json.nodes[nodeIndex];
2578
+ if (!node || ancestors.has(nodeIndex) || node.skin !== void 0 || node.weights || Object.keys(node.extensions || {}).length)
2579
+ throw new import_core20.TileConversionError(
2375
2580
  "MESH_SOURCE_NODE_UNSUPPORTED",
2376
2581
  "Cyclic, skinned, morphed, or extended nodes are unsupported"
2377
2582
  );
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])
2583
+ const transform = new import_core19.Matrix4(parent).multiplyRight(
2584
+ node.matrix ? new import_core19.Matrix4(node.matrix) : new import_core19.Matrix4().translate(node.translation || [0, 0, 0]).multiplyRight(new import_core19.Matrix4().fromQuaternion(node.rotation || [0, 0, 0, 1])).scale(node.scale || [1, 1, 1])
2380
2585
  );
2381
- if (node.mesh) {
2382
- if (node.mesh.weights || Object.keys(node.mesh.extensions || {}).length)
2383
- throw new import_core19.TileConversionError(
2586
+ if (node.mesh !== void 0) {
2587
+ const mesh = gltf.json.meshes[node.mesh];
2588
+ if (mesh.weights || Object.keys(mesh.extensions || {}).length)
2589
+ throw new import_core20.TileConversionError(
2384
2590
  "MESH_SOURCE_NODE_UNSUPPORTED",
2385
2591
  "Morph weights and mesh extensions are unsupported"
2386
2592
  );
2387
- for (const primitive of node.mesh.primitives) yield { primitive, transform };
2593
+ for (const primitive of mesh.primitives) yield { primitive, transform };
2388
2594
  }
2389
- yield* traverseMeshNodes(node.children || [], transform, /* @__PURE__ */ new Set([...ancestors, node]));
2595
+ yield* traverseMeshNodes(
2596
+ gltf,
2597
+ node.children || [],
2598
+ transform,
2599
+ /* @__PURE__ */ new Set([...ancestors, nodeIndex])
2600
+ );
2390
2601
  }
2391
2602
  }
2392
- async function extractPrimitive(primitive, transform, id, readExternalResource, contentUri, signal) {
2603
+ async function extractPrimitive(gltf, primitive, transform, id, readExternalResource, contentUri, signal) {
2393
2604
  if (primitive.targets?.length || Object.keys(primitive.extensions || {}).some((name) => name !== "EXT_mesh_features"))
2394
- throw new import_core19.TileConversionError(
2605
+ throw new import_core20.TileConversionError(
2395
2606
  "MESH_SOURCE_PRIMITIVE_UNSUPPORTED",
2396
2607
  "Morph targets and unmapped primitive extensions are unsupported"
2397
2608
  );
2398
2609
  if (Array.from(transform).some((value) => !Number.isFinite(value)) || transform[3] !== 0 || transform[7] !== 0 || transform[11] !== 0 || transform[15] !== 1 || transform.determinant() <= 0)
2399
- throw new import_core19.TileConversionError(
2610
+ throw new import_core20.TileConversionError(
2400
2611
  "MESH_SOURCE_TRANSFORM_INVALID",
2401
2612
  "Mesh placement requires a finite invertible affine transform with positive determinant"
2402
2613
  );
2403
2614
  const attributes = {};
2404
- for (const [name, accessor] of Object.entries(primitive.attributes)) {
2615
+ for (const [name, reference] of Object.entries(primitive.attributes)) {
2616
+ const accessor = (0, import_gltf4.getGLTFAccessor)(gltf, reference);
2405
2617
  if (name === "_BATCHID" || /^_FEATURE_ID_\d+$/.test(name)) continue;
2406
2618
  if (!["POSITION", "NORMAL", "COLOR_0", "TEXCOORD_0"].includes(name))
2407
- throw new import_core19.TileConversionError(
2619
+ throw new import_core20.TileConversionError(
2408
2620
  "MESH_SOURCE_ATTRIBUTE_UNSUPPORTED",
2409
2621
  `Unsupported mesh attribute ${name}`
2410
2622
  );
2411
2623
  attributes[name] = {
2412
- value: accessor.value,
2413
- size: accessor.components,
2624
+ value: (0, import_gltf4.getGLTFAccessorValue)(gltf, reference),
2625
+ size: accessor.type === "SCALAR" ? 1 : Number(accessor.type.slice(3)),
2414
2626
  ...accessor.normalized ? { normalized: true } : {}
2415
2627
  };
2416
2628
  }
2417
2629
  const mesh = validateMeshGeometry(
2418
2630
  {
2419
2631
  topology: "triangle-list",
2420
- mode: primitive.mode === 5 ? 4 : primitive.mode ?? 4,
2632
+ mode: [5, 6].includes(primitive.mode ?? 4) ? 4 : primitive.mode ?? 4,
2421
2633
  attributes,
2422
- indices: createTriangleIndices(primitive, (attributes.POSITION?.value.length ?? 0) / 3)
2634
+ indices: createTriangleIndices(gltf, primitive, (attributes.POSITION?.value.length ?? 0) / 3)
2423
2635
  },
2424
2636
  true
2425
2637
  );
@@ -2440,7 +2652,7 @@ async function extractPrimitive(primitive, transform, id, readExternalResource,
2440
2652
  mesh.attributes.POSITION = { value: positions, size: 3 };
2441
2653
  if (mesh.attributes.NORMAL) {
2442
2654
  const values = mesh.attributes.NORMAL.value;
2443
- const normalMatrix = new import_core18.Matrix4(transform).invert().transpose();
2655
+ const normalMatrix = new import_core19.Matrix4(transform).invert().transpose();
2444
2656
  const normals = new Float32Array(values.length);
2445
2657
  for (let index = 0; index < values.length; index += 3) {
2446
2658
  const normal = normalMatrix.transformAsVector(Array.from(values.subarray(index, index + 3)));
@@ -2457,14 +2669,15 @@ async function extractPrimitive(primitive, transform, id, readExternalResource,
2457
2669
  mesh,
2458
2670
  origin: minimum.map((value, axis) => value / 2 + maximum[axis] / 2),
2459
2671
  material: await mapMeshSourceMaterial(
2460
- primitive.material,
2672
+ primitive.material === void 0 ? void 0 : gltf.json.materials?.[primitive.material],
2461
2673
  readExternalResource,
2462
2674
  contentUri,
2463
- signal
2675
+ signal,
2676
+ gltf
2464
2677
  )
2465
2678
  };
2466
2679
  }
2467
- async function mapMeshSourceMaterial(material, readExternalResource, contentUri, signal) {
2680
+ async function mapMeshSourceMaterial(material, readExternalResource, contentUri, signal, gltf) {
2468
2681
  if (!material) return void 0;
2469
2682
  const pbr = material.pbrMetallicRoughness;
2470
2683
  if (Object.entries(material).some(
@@ -2481,7 +2694,7 @@ async function mapMeshSourceMaterial(material, readExternalResource, contentUri,
2481
2694
  ) || Object.entries(pbr || {}).some(
2482
2695
  ([name, value]) => value !== void 0 && !["baseColorFactor", "baseColorTexture", "metallicFactor", "roughnessFactor"].includes(name)
2483
2696
  ) || material.emissiveFactor?.some((value) => value !== 0))
2484
- throw new import_core19.TileConversionError(
2697
+ throw new import_core20.TileConversionError(
2485
2698
  "MESH_SOURCE_MATERIAL_UNSUPPORTED",
2486
2699
  "Only metallic-roughness factors and one embedded base-color image are supported"
2487
2700
  );
@@ -2489,6 +2702,7 @@ async function mapMeshSourceMaterial(material, readExternalResource, contentUri,
2489
2702
  baseColorFactor: pbr?.baseColorFactor,
2490
2703
  ..."baseColorTexture" in (pbr || {}) ? {
2491
2704
  baseColorTexture: await resolveMeshSourceTexture(
2705
+ gltf,
2492
2706
  pbr.baseColorTexture,
2493
2707
  readExternalResource,
2494
2708
  contentUri,
@@ -2502,17 +2716,18 @@ async function mapMeshSourceMaterial(material, readExternalResource, contentUri,
2502
2716
  doubleSided: material.doubleSided
2503
2717
  };
2504
2718
  }
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
2719
+ function createTriangleIndices(gltf, primitive, vertexCount) {
2720
+ const accessor = primitive.indices === void 0 ? void 0 : (0, import_gltf4.getGLTFAccessor)(gltf, primitive.indices);
2721
+ const attribute = accessor ? {
2722
+ value: (0, import_gltf4.getGLTFAccessorValue)(gltf, accessor),
2723
+ size: 1,
2724
+ normalized: accessor.normalized
2510
2725
  } : void 0;
2511
- if (primitive.mode !== 5) return attribute;
2726
+ if (primitive.mode !== 5 && primitive.mode !== 6) return attribute;
2512
2727
  const sourceIndices = validateMeshIndices(attribute, vertexCount, 5);
2513
2728
  const indexCount = sourceIndices?.length ?? vertexCount;
2514
2729
  if (!Number.isSafeInteger(indexCount) || indexCount < 3) {
2515
- throw new import_core19.TileConversionError(
2730
+ throw new import_core20.TileConversionError(
2516
2731
  "MESH_TRIANGLE_COUNT_INVALID",
2517
2732
  "Triangle strips require at least three vertices"
2518
2733
  );
@@ -2520,9 +2735,10 @@ function createTriangleIndices(primitive, vertexCount) {
2520
2735
  const IndexArray = vertexCount < 65536 ? Uint16Array : Uint32Array;
2521
2736
  const indices = new IndexArray((indexCount - 2) * 3);
2522
2737
  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;
2738
+ const second = primitive.mode === 6 ? triangle + 1 : triangle + 1 + triangle % 2;
2739
+ const third = primitive.mode === 6 ? triangle + 2 : triangle + 2 - triangle % 2;
2740
+ const first = primitive.mode === 6 ? 0 : triangle;
2741
+ indices[triangle * 3] = sourceIndices?.[first] ?? first;
2526
2742
  indices[triangle * 3 + 1] = sourceIndices?.[second] ?? second;
2527
2743
  indices[triangle * 3 + 2] = sourceIndices?.[third] ?? third;
2528
2744
  }
@@ -2530,14 +2746,20 @@ function createTriangleIndices(primitive, vertexCount) {
2530
2746
  }
2531
2747
 
2532
2748
  // src/v5/i3s-mesh-conversion.ts
2533
- var import_i3s = require("@loaders.gl/i3s");
2534
- var import_core21 = require("@loaders.gl/tile-converter/v5/core");
2749
+ var import_i3s = require("@loaders.gl/tiles-3d/i3s");
2535
2750
  var import_core22 = require("@loaders.gl/tile-converter/v5/core");
2751
+ var import_core23 = require("@loaders.gl/tile-converter/v5/core");
2536
2752
  function createI3SMeshConversionCodec(options) {
2753
+ if (options.integratedMeshPolicy !== void 0 && options.integratedMeshPolicy !== "as-3d-object") {
2754
+ throw new import_core22.TileConversionError(
2755
+ "I3S_LAYER_TYPE_POLICY_INVALID",
2756
+ "Unknown IntegratedMesh conversion policy"
2757
+ );
2758
+ }
2537
2759
  const reference = options.spatialContext.spatialReference;
2538
2760
  const height = reference.targetHeightReference === "native" ? reference.heightReference : reference.targetHeightReference;
2539
2761
  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")))
2540
- throw new import_core21.TileConversionError(
2762
+ throw new import_core22.TileConversionError(
2541
2763
  "I3S_MESH_FRAME_UNSUPPORTED",
2542
2764
  "I3S mesh authoring requires resolved ECEF coordinates and ellipsoidal heights"
2543
2765
  );
@@ -2546,6 +2768,12 @@ function createI3SMeshConversionCodec(options) {
2546
2768
  /** Places a source mesh in ECEF and authors one layer without modifying source arrays. */
2547
2769
  async *convert(resource, _inspection, signal) {
2548
2770
  signal?.throwIfAborted();
2771
+ if (resource.sourceLayerType === "IntegratedMesh" && options.integratedMeshPolicy !== "as-3d-object") {
2772
+ throw new import_core22.TileConversionError(
2773
+ "I3S_LAYER_TYPE_CHANGE_REQUIRED",
2774
+ "IntegratedMesh to 3DObject conversion requires integratedMeshPolicy=as-3d-object"
2775
+ );
2776
+ }
2549
2777
  const mesh = validateMeshGeometry(resource.mesh, true);
2550
2778
  const positions = mesh.attributes.POSITION.value;
2551
2779
  const context = options.spatialContext;
@@ -2562,6 +2790,7 @@ function createI3SMeshConversionCodec(options) {
2562
2790
  if (mesh.attributes.NORMAL) mesh.attributes.NORMAL = { value: transformed.normals, size: 3 };
2563
2791
  let layer;
2564
2792
  try {
2793
+ await (0, import_i3s.preloadI3SMeshCompression)();
2565
2794
  const writerOptions = {
2566
2795
  ...options,
2567
2796
  material: resource.material,
@@ -2570,18 +2799,26 @@ function createI3SMeshConversionCodec(options) {
2570
2799
  };
2571
2800
  layer = options.draco === false ? (0, import_i3s.encodeI3SMeshLayer)(mesh, writerOptions) : await (0, import_i3s.encodeI3SMeshLayerWithDraco)(mesh, writerOptions, options.dracoLibraryOptions);
2572
2801
  } catch (error) {
2573
- throw new import_core21.TileConversionError(
2802
+ throw new import_core22.TileConversionError(
2574
2803
  "I3S_MESH_PROFILE_UNSUPPORTED",
2575
2804
  error instanceof Error ? error.message : String(error)
2576
2805
  );
2577
2806
  }
2578
2807
  signal?.throwIfAborted();
2579
2808
  objectIdOffset += resource.features?.batches.reduce((total, batch) => total + batch.data.numRows, 0) ?? 1;
2580
- yield { id: resource.id, ...layer };
2809
+ yield { id: resource.id, ...layer, sourceLayerType: resource.sourceLayerType };
2581
2810
  },
2582
2811
  /** Reports measured precision and explicitly authorized integer representation changes. */
2583
2812
  async validateOutput(resource) {
2584
2813
  return [
2814
+ ...resource.sourceLayerType === "IntegratedMesh" ? [
2815
+ {
2816
+ code: "I3S_LAYER_TYPE_CHANGED",
2817
+ message: "Explicitly authored IntegratedMesh geometry as I3S 1.7 3DObject; integrated surface layer semantics and source LOD are not preserved",
2818
+ severity: "warning",
2819
+ resourceId: resource.id
2820
+ }
2821
+ ] : [],
2585
2822
  ...resource.maximumPositionError > 0 ? [
2586
2823
  {
2587
2824
  code: "I3S_MESH_POSITION_ROUNDING",
@@ -2601,7 +2838,7 @@ function createI3SMeshConversionCodec(options) {
2601
2838
  };
2602
2839
  }
2603
2840
  function createSingleMeshI3SSink(options) {
2604
- const memory = (0, import_core22.createBoundedMemoryTileConversionSink)(options);
2841
+ const memory = (0, import_core23.createBoundedMemoryTileConversionSink)(options);
2605
2842
  let state = "empty";
2606
2843
  let completed = false;
2607
2844
  return {
@@ -2611,7 +2848,7 @@ function createSingleMeshI3SSink(options) {
2611
2848
  async write(resource, signal) {
2612
2849
  signal?.throwIfAborted();
2613
2850
  if (state !== "empty")
2614
- throw new import_core21.TileConversionError(
2851
+ throw new import_core22.TileConversionError(
2615
2852
  "SINGLE_I3S_MESH_UNAVAILABLE",
2616
2853
  "The initial I3S sink accepts exactly one mesh layer"
2617
2854
  );
@@ -2630,7 +2867,7 @@ function createSingleMeshI3SSink(options) {
2630
2867
  /** Commits a complete single-layer result. */
2631
2868
  async finalize(report) {
2632
2869
  if (state !== "written")
2633
- throw new import_core21.TileConversionError(
2870
+ throw new import_core22.TileConversionError(
2634
2871
  "SINGLE_I3S_MESH_INCOMPLETE",
2635
2872
  "Finalization requires exactly one successfully written layer"
2636
2873
  );
@@ -2650,16 +2887,16 @@ function createSingleMeshI3SSink(options) {
2650
2887
  // src/v5/i3s-mesh-sink.ts
2651
2888
  var import_geospatial = require("@math.gl/geospatial");
2652
2889
  var import_compression = require("@loaders.gl/compression");
2653
- var import_core23 = require("@loaders.gl/tile-converter/v5/core");
2890
+ var import_core24 = require("@loaders.gl/tile-converter/v5/core");
2654
2891
  function createI3SMeshSink(options) {
2655
2892
  const { maxMeshes, maxResourceBytes } = options;
2656
2893
  if (!Number.isInteger(maxMeshes) || maxMeshes < 1 || maxMeshes > 64 || !Number.isSafeInteger(maxResourceBytes) || maxResourceBytes < 1)
2657
- throw new import_core23.TileConversionError(
2894
+ throw new import_core24.TileConversionError(
2658
2895
  "I3S_COLLECTION_OPTIONS_INVALID",
2659
2896
  "I3S collections require 1\u201364 meshes and a positive resource byte limit"
2660
2897
  );
2661
- const memory = (0, import_core23.createBoundedMemoryTileConversionSink)(options);
2662
- const compressor = new import_compression.GZipCompressor({ useNative: false, gzip: { mtime: 0 } });
2898
+ const memory = (0, import_core24.createBoundedMemoryTileConversionSink)(options);
2899
+ const compressor = new import_compression.GZipCompressor({ useNative: false });
2663
2900
  const decompressor = new import_compression.GZipDecompressor({ useNative: false });
2664
2901
  const leaves = [];
2665
2902
  const identifiers = /* @__PURE__ */ new Set();
@@ -2671,7 +2908,7 @@ function createI3SMeshSink(options) {
2671
2908
  async function readBytes(bytes) {
2672
2909
  const output = await decompressor.decompress(bytes);
2673
2910
  if (output.byteLength > maxResourceBytes)
2674
- throw new import_core23.TileConversionError(
2911
+ throw new import_core24.TileConversionError(
2675
2912
  "I3S_COLLECTION_RESOURCE_TOO_LARGE",
2676
2913
  "I3S metadata exceeds maxResourceBytes"
2677
2914
  );
@@ -2683,7 +2920,7 @@ function createI3SMeshSink(options) {
2683
2920
  async function writeJson(resourceId, value) {
2684
2921
  const bytes = new TextEncoder().encode(JSON.stringify(value));
2685
2922
  if (bytes.byteLength > maxResourceBytes)
2686
- throw new import_core23.TileConversionError(
2923
+ throw new import_core24.TileConversionError(
2687
2924
  "I3S_COLLECTION_RESOURCE_TOO_LARGE",
2688
2925
  "I3S index exceeds maxResourceBytes"
2689
2926
  );
@@ -2700,7 +2937,7 @@ function createI3SMeshSink(options) {
2700
2937
  async write(resource, signal) {
2701
2938
  signal?.throwIfAborted();
2702
2939
  if (state !== "open" || identifiers.has(resource.id) || leaves.length >= maxMeshes)
2703
- throw new import_core23.TileConversionError(
2940
+ throw new import_core24.TileConversionError(
2704
2941
  "I3S_COLLECTION_UNAVAILABLE",
2705
2942
  "I3S collection is busy, closed, full, or repeats a placement"
2706
2943
  );
@@ -2713,7 +2950,7 @@ function createI3SMeshSink(options) {
2713
2950
  authored.store.defaultGeometrySchema
2714
2951
  ]);
2715
2952
  if (schemaKey !== void 0 && key !== schemaKey)
2716
- throw new import_core23.TileConversionError(
2953
+ throw new import_core24.TileConversionError(
2717
2954
  "I3S_COLLECTION_SCHEMA_MISMATCH",
2718
2955
  "Mesh nodes must share feature fields and the legacy geometry layout"
2719
2956
  );
@@ -2725,7 +2962,7 @@ function createI3SMeshSink(options) {
2725
2962
  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
2963
  (value, index2) => value !== first + index2
2727
2964
  ))
2728
- throw new import_core23.TileConversionError(
2965
+ throw new import_core24.TileConversionError(
2729
2966
  "I3S_COLLECTION_IDS_INVALID",
2730
2967
  "Generated object IDs must be contiguous and disjoint across nodes"
2731
2968
  );
@@ -2736,7 +2973,7 @@ function createI3SMeshSink(options) {
2736
2973
  "nodes/1/3dNodeIndexDocument.json.gz"
2737
2974
  );
2738
2975
  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(
2976
+ throw new import_core24.TileConversionError(
2740
2977
  "I3S_COLLECTION_BOUNDS_INVALID",
2741
2978
  "Expected finite axis-aligned bounds from the I3S mesh writer"
2742
2979
  );
@@ -2787,7 +3024,7 @@ function createI3SMeshSink(options) {
2787
3024
  /** Rebuilds both indices and layer bounds only after every leaf has completed. */
2788
3025
  async finalize(report) {
2789
3026
  if (state !== "open" || !layer || !leaves.length)
2790
- throw new import_core23.TileConversionError(
3027
+ throw new import_core24.TileConversionError(
2791
3028
  "I3S_COLLECTION_INCOMPLETE",
2792
3029
  "Finalization requires successfully written mesh nodes"
2793
3030
  );
@@ -2812,7 +3049,7 @@ function createI3SMeshSink(options) {
2812
3049
  quaternion: [0, 0, 0, 1]
2813
3050
  };
2814
3051
  if ([...obb.center, ...obb.halfSize].some((value) => !Number.isFinite(value)))
2815
- throw new import_core23.TileConversionError(
3052
+ throw new import_core24.TileConversionError(
2816
3053
  "I3S_COLLECTION_BOUNDS_INVALID",
2817
3054
  "Collection bounds cannot be represented"
2818
3055
  );
@@ -2872,7 +3109,7 @@ function createI3SMeshSink(options) {
2872
3109
  }
2873
3110
 
2874
3111
  // src/v5/conversion-api.ts
2875
- var TileConversionError17 = class extends Error {
3112
+ var TileConversionError18 = class extends Error {
2876
3113
  /** Stable machine-readable failure code. */
2877
3114
  code;
2878
3115
  /** Diagnostics associated with this failure. */
@@ -2890,7 +3127,7 @@ var TileConversionError17 = class extends Error {
2890
3127
  function createTileConversionResourceFetcher(options) {
2891
3128
  let consumed = options.initialInputBytes ?? 0;
2892
3129
  if (!Number.isSafeInteger(options.maxInputBytes) || options.maxInputBytes < 1 || !Number.isSafeInteger(consumed) || consumed < 0 || consumed > options.maxInputBytes)
2893
- throw new TileConversionError17(
3130
+ throw new TileConversionError18(
2894
3131
  "INVALID_INPUT_MEMORY_LIMIT",
2895
3132
  "Resource fetch requires a positive aggregate byte budget and valid initial bytes"
2896
3133
  );
@@ -2899,7 +3136,7 @@ function createTileConversionResourceFetcher(options) {
2899
3136
  options.signal?.throwIfAborted();
2900
3137
  const url = new URL(input instanceof Request ? input.url : String(input));
2901
3138
  if (!["http:", "https:", "blob:", "data:"].includes(url.protocol))
2902
- throw new TileConversionError17(
3139
+ throw new TileConversionError18(
2903
3140
  "EXTERNAL_RESOURCE_UNSUPPORTED",
2904
3141
  "Conversion transport requires HTTP(S), Blob or embedded data resources"
2905
3142
  );
@@ -2908,14 +3145,14 @@ function createTileConversionResourceFetcher(options) {
2908
3145
  const response = await fetcher(input, { ...requestOptions, signal });
2909
3146
  if (!response.ok) {
2910
3147
  await response.body?.cancel();
2911
- throw new TileConversionError17(
3148
+ throw new TileConversionError18(
2912
3149
  "BROWSER_SOURCE_FETCH_FAILED",
2913
3150
  `Could not fetch conversion resource (${response.status})`
2914
3151
  );
2915
3152
  }
2916
3153
  const reader = response.body?.getReader();
2917
3154
  if (!reader)
2918
- throw new TileConversionError17(
3155
+ throw new TileConversionError18(
2919
3156
  "BROWSER_SOURCE_STREAM_UNAVAILABLE",
2920
3157
  "Conversion resources require readable response streams"
2921
3158
  );
@@ -2933,7 +3170,7 @@ function createTileConversionResourceFetcher(options) {
2933
3170
  signal?.throwIfAborted();
2934
3171
  if (done) break;
2935
3172
  if (value.byteLength > options.maxInputBytes - consumed)
2936
- throw new TileConversionError17(
3173
+ throw new TileConversionError18(
2937
3174
  "INPUT_RESOURCE_TOO_LARGE",
2938
3175
  "Selected contents and external dependencies exceed the aggregate input byte budget"
2939
3176
  );
@@ -2954,25 +3191,27 @@ function createTileConversionResourceFetcher(options) {
2954
3191
  bytes.set(part, offset);
2955
3192
  offset += part.byteLength;
2956
3193
  }
2957
- return new Response(bytes, {
3194
+ const boundedResponse = new Response(bytes, {
2958
3195
  status: response.status,
2959
3196
  statusText: response.statusText,
2960
3197
  headers: response.headers
2961
3198
  });
3199
+ Object.defineProperty(boundedResponse, "url", { value: response.url });
3200
+ return boundedResponse;
2962
3201
  };
2963
3202
  }
2964
3203
 
2965
3204
  // src/v5/point-cloud-tileset-sink.ts
2966
- var import_core24 = require("@loaders.gl/tile-converter/v5/core");
3205
+ var import_core25 = require("@loaders.gl/tile-converter/v5/core");
2967
3206
  function createPointCloudTilesetSink(options) {
2968
3207
  const { maxTiles, geometricError } = options;
2969
3208
  if (!Number.isSafeInteger(maxTiles) || maxTiles < 1 || !Number.isFinite(geometricError) || geometricError < 0) {
2970
- throw new import_core24.TileConversionError(
3209
+ throw new import_core25.TileConversionError(
2971
3210
  "POINT_CLOUD_TILESET_OPTIONS_INVALID",
2972
3211
  "Positive maxTiles and finite nonnegative geometricError are required"
2973
3212
  );
2974
3213
  }
2975
- const memory = (0, import_core24.createBoundedMemoryTileConversionSink)(options);
3214
+ const memory = (0, import_core25.createBoundedMemoryTileConversionSink)(options);
2976
3215
  const children = [];
2977
3216
  const identifiers = /* @__PURE__ */ new Set();
2978
3217
  const minimum = [Infinity, Infinity, Infinity];
@@ -2986,12 +3225,12 @@ function createPointCloudTilesetSink(options) {
2986
3225
  async write(resource, signal) {
2987
3226
  signal?.throwIfAborted();
2988
3227
  if (state !== "open")
2989
- throw new import_core24.TileConversionError(
3228
+ throw new import_core25.TileConversionError(
2990
3229
  "POINT_CLOUD_TILESET_SINK_UNAVAILABLE",
2991
3230
  "Point sink is busy or closed"
2992
3231
  );
2993
3232
  if (!resource.id || identifiers.has(resource.id) || children.length >= maxTiles) {
2994
- throw new import_core24.TileConversionError(
3233
+ throw new import_core25.TileConversionError(
2995
3234
  "POINT_CLOUD_TILESET_PLACEMENT_INVALID",
2996
3235
  "Placements require unique nonempty identifiers within maxTiles"
2997
3236
  );
@@ -3004,7 +3243,7 @@ function createPointCloudTilesetSink(options) {
3004
3243
  signal
3005
3244
  );
3006
3245
  if (state !== "writing")
3007
- throw new import_core24.TileConversionError(
3246
+ throw new import_core25.TileConversionError(
3008
3247
  "POINT_CLOUD_TILESET_SINK_UNAVAILABLE",
3009
3248
  "Point sink was aborted during writing"
3010
3249
  );
@@ -3023,7 +3262,7 @@ function createPointCloudTilesetSink(options) {
3023
3262
  /** Serializes the enclosing root after all placements have completed. */
3024
3263
  async finalize(report) {
3025
3264
  if (state !== "open" || !children.length)
3026
- throw new import_core24.TileConversionError(
3265
+ throw new import_core25.TileConversionError(
3027
3266
  "POINT_CLOUD_TILESET_SINK_INCOMPLETE",
3028
3267
  "Finalization requires successfully written point tiles"
3029
3268
  );
@@ -3033,7 +3272,7 @@ function createPointCloudTilesetSink(options) {
3033
3272
  Math.hypot(...minimum.map((value, axis) => maximum[axis] - value))
3034
3273
  );
3035
3274
  if (!Number.isFinite(rootError))
3036
- throw new import_core24.TileConversionError(
3275
+ throw new import_core25.TileConversionError(
3037
3276
  "POINT_CLOUD_TILESET_BOUNDS_INVALID",
3038
3277
  "Enclosing bounds must have a finite diagonal"
3039
3278
  );
@@ -3053,13 +3292,13 @@ function createPointCloudTilesetSink(options) {
3053
3292
  contentType: "application/json"
3054
3293
  });
3055
3294
  if (state !== "writing")
3056
- throw new import_core24.TileConversionError(
3295
+ throw new import_core25.TileConversionError(
3057
3296
  "POINT_CLOUD_TILESET_SINK_UNAVAILABLE",
3058
3297
  "Point sink was aborted during finalization"
3059
3298
  );
3060
3299
  await memory.finalize(report);
3061
3300
  if (state !== "writing")
3062
- throw new import_core24.TileConversionError(
3301
+ throw new import_core25.TileConversionError(
3063
3302
  "POINT_CLOUD_TILESET_SINK_UNAVAILABLE",
3064
3303
  "Point sink was aborted during finalization"
3065
3304
  );
@@ -3080,13 +3319,13 @@ function getPointTileBounds(resource, geometricError) {
3080
3319
  const reference = resource.spatialReference;
3081
3320
  const identity = [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1];
3082
3321
  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(
3322
+ throw new import_core25.TileConversionError(
3084
3323
  "POINT_CLOUD_TILESET_FRAME_UNSUPPORTED",
3085
3324
  "Point tiles require declared EPSG:4978 xyz meters, ellipsoidal heights and no additional source placement"
3086
3325
  );
3087
3326
  }
3088
3327
  if (!Number.isFinite(resource.maximumPositionError) || resource.maximumPositionError < 0 || resource.maximumPositionError > geometricError) {
3089
- throw new import_core24.TileConversionError(
3328
+ throw new import_core25.TileConversionError(
3090
3329
  "POINT_CLOUD_GEOMETRIC_ERROR_INVALID",
3091
3330
  "geometricError must include measured position reconstruction error"
3092
3331
  );
@@ -3094,7 +3333,7 @@ function getPointTileBounds(resource, geometricError) {
3094
3333
  const [minimum, maximum] = resource.localBoundingBox;
3095
3334
  const center = resource.rtcCenter;
3096
3335
  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(
3336
+ throw new import_core25.TileConversionError(
3098
3337
  "POINT_CLOUD_TILESET_BOUNDS_INVALID",
3099
3338
  "RTC center and encoded xyz bounds must be finite and ordered"
3100
3339
  );
@@ -3102,7 +3341,7 @@ function getPointTileBounds(resource, geometricError) {
3102
3341
  const lower = minimum.map((value, axis) => value + center[axis]);
3103
3342
  const upper = maximum.map((value, axis) => value + center[axis]);
3104
3343
  if ([...lower, ...upper].some((value) => !Number.isFinite(value)))
3105
- throw new import_core24.TileConversionError(
3344
+ throw new import_core25.TileConversionError(
3106
3345
  "POINT_CLOUD_TILESET_BOUNDS_INVALID",
3107
3346
  "Reconstructed ECEF bounds must be finite"
3108
3347
  );
@@ -3115,12 +3354,13 @@ function createPointBoundsBox(minimum, maximum) {
3115
3354
  }
3116
3355
 
3117
3356
  // src/v5/i3s-mesh-source.ts
3118
- var import_core27 = require("@loaders.gl/tile-converter/v5/core");
3357
+ var import_core28 = require("@loaders.gl/tile-converter/v5/core");
3119
3358
 
3120
3359
  // src/v5/i3s-mesh-source-material.ts
3121
3360
  var import_images3 = require("@loaders.gl/images");
3122
- var import_core25 = require("@loaders.gl/tile-converter/v5/core");
3361
+ var import_core26 = require("@loaders.gl/tile-converter/v5/core");
3123
3362
  async function mapI3SMeshSourceMaterial(content, header, readExternalResource, contentUri, signal) {
3363
+ const gltf = { json: { asset: { version: "2.0" } }, buffers: [] };
3124
3364
  const material = Object.fromEntries(
3125
3365
  Object.entries(content.material || {}).filter(([, value]) => value !== void 0)
3126
3366
  );
@@ -3129,7 +3369,7 @@ async function mapI3SMeshSourceMaterial(content, header, readExternalResource, c
3129
3369
  ) : {};
3130
3370
  material.pbrMetallicRoughness = metallicRoughness;
3131
3371
  if (material.cullFace !== void 0 && material.cullFace !== "back") {
3132
- throw new import_core25.TileConversionError(
3372
+ throw new import_core26.TileConversionError(
3133
3373
  "I3S_MESH_SOURCE_MATERIAL_UNSUPPORTED",
3134
3374
  "Only back-face culling is supported"
3135
3375
  );
@@ -3148,7 +3388,7 @@ async function mapI3SMeshSourceMaterial(content, header, readExternalResource, c
3148
3388
  }
3149
3389
  const encodedTextures = content.textures || {};
3150
3390
  if (!textureInfo && (resources.length || content.texture || Object.keys(encodedTextures).length)) {
3151
- throw new import_core25.TileConversionError(
3391
+ throw new import_core26.TileConversionError(
3152
3392
  "I3S_MESH_SOURCE_TEXTURE_UNSUPPORTED",
3153
3393
  "Texture resources require a base-color mapping"
3154
3394
  );
@@ -3161,7 +3401,7 @@ async function mapI3SMeshSourceMaterial(content, header, readExternalResource, c
3161
3401
  ) || ["wrapS", "wrapT"].some(
3162
3402
  (name) => textureInfo[name] !== void 0 && (typeof textureInfo[name] !== "string" || !Object.hasOwn(wrappingModes, textureInfo[name]))
3163
3403
  ) || resources.length > 1 || resources.some((resource) => resource.textureSetDefinitionId !== definitionId) || Object.keys(encodedTextures).some((key) => key !== String(definitionId))) {
3164
- throw new import_core25.TileConversionError(
3404
+ throw new import_core26.TileConversionError(
3165
3405
  "I3S_MESH_SOURCE_TEXTURE_UNSUPPORTED",
3166
3406
  "Only one base-color texture set on TEXCOORD_0 with qualified wrapping is supported"
3167
3407
  );
@@ -3169,7 +3409,7 @@ async function mapI3SMeshSourceMaterial(content, header, readExternalResource, c
3169
3409
  let encoded = encodedTextures[String(definitionId)] || content.texture || textureInfo.texture?.source?.image;
3170
3410
  if (!encoded) {
3171
3411
  if (!resources.length || !readExternalResource) {
3172
- throw new import_core25.TileConversionError(
3412
+ throw new import_core26.TileConversionError(
3173
3413
  "I3S_MESH_SOURCE_TEXTURE_UNSUPPORTED",
3174
3414
  "Encoded texture bytes or an explicit bounded resource reader are required"
3175
3415
  );
@@ -3187,8 +3427,8 @@ async function mapI3SMeshSourceMaterial(content, header, readExternalResource, c
3187
3427
  }
3188
3428
  }
3189
3429
  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(
3430
+ if (!(data instanceof Uint8Array) || !image || !["image/png", "image/jpeg"].includes(image.mimeType) || declaredFormat && (declaredFormat === "png" ? image.mimeType !== "image/png" : declaredFormat === "jpg" ? image.mimeType !== "image/jpeg" : true)) {
3431
+ throw new import_core26.TileConversionError(
3192
3432
  "I3S_MESH_SOURCE_TEXTURE_UNSUPPORTED",
3193
3433
  "I3S base-color bytes must have matching PNG/JPEG headers"
3194
3434
  );
@@ -3196,22 +3436,27 @@ async function mapI3SMeshSourceMaterial(content, header, readExternalResource, c
3196
3436
  const sampler = Object.fromEntries(
3197
3437
  ["wrapS", "wrapT"].filter((name) => textureInfo[name] !== void 0).map((name) => [name, wrappingModes[textureInfo[name]]])
3198
3438
  );
3199
- metallicRoughness.baseColorTexture = {
3200
- texCoord: 0,
3201
- texture: {
3202
- source: { mimeType: image.mimeType, bufferView: { data } },
3203
- ...Object.keys(sampler).length ? { sampler } : {}
3204
- }
3205
- };
3439
+ gltf.buffers.push({
3440
+ arrayBuffer: data.buffer,
3441
+ byteOffset: data.byteOffset,
3442
+ byteLength: data.byteLength
3443
+ });
3444
+ gltf.json.buffers = [{ byteLength: data.byteLength }];
3445
+ gltf.json.bufferViews = [{ buffer: 0, byteLength: data.byteLength }];
3446
+ gltf.json.images = [{ mimeType: image.mimeType, bufferView: 0 }];
3447
+ gltf.json.samplers = [sampler];
3448
+ gltf.json.textures = [{ source: 0, sampler: 0 }];
3449
+ metallicRoughness.baseColorTexture = { texCoord: 0, index: 0 };
3206
3450
  }
3207
3451
  const mapped = await mapMeshSourceMaterial(
3208
3452
  material,
3209
3453
  void 0,
3210
3454
  contentUri,
3211
- signal
3455
+ signal,
3456
+ gltf
3212
3457
  );
3213
3458
  if (mapped?.baseColorTexture && !content.sourceAttributes?.uv0) {
3214
- throw new import_core25.TileConversionError(
3459
+ throw new import_core26.TileConversionError(
3215
3460
  "MESH_TEXCOORD_REQUIRED",
3216
3461
  "Base-color textures require TEXCOORD_0"
3217
3462
  );
@@ -3220,8 +3465,8 @@ async function mapI3SMeshSourceMaterial(content, header, readExternalResource, c
3220
3465
  }
3221
3466
 
3222
3467
  // 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");
3468
+ var import_i3s2 = require("@loaders.gl/tiles-3d/i3s");
3469
+ var import_core27 = require("@loaders.gl/tile-converter/v5/core");
3225
3470
  async function extractI3SMeshFeatures(content, item, metadata, options, readExternalResource, contentUri, signal) {
3226
3471
  const maximumAttributeBytes = options.maxAttributeBytes;
3227
3472
  const storage = metadata.tileset.attributeStorageInfo;
@@ -3229,7 +3474,7 @@ async function extractI3SMeshFeatures(content, item, metadata, options, readExte
3229
3474
  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
3475
  (field) => field.domain || field.type === "esriFieldTypeDate" || !storage.some((attribute2) => attribute2.name === field.name)
3231
3476
  )) {
3232
- throw new import_core26.TileConversionError(
3477
+ throw new import_core27.TileConversionError(
3233
3478
  "I3S_MESH_SOURCE_FEATURE_MAPPING_REQUIRED",
3234
3479
  "I3S attributes require an explicit scalar schema, object ID column, byte budget and resource reader; domains/dates/raw metadata need a custom mapper"
3235
3480
  );
@@ -3252,7 +3497,7 @@ async function extractI3SMeshFeatures(content, item, metadata, options, readExte
3252
3497
  ) || attribute2.ordering && JSON.stringify(
3253
3498
  attribute2.ordering.map((value) => value === "ObjectIds" ? "objectIds" : value)
3254
3499
  ) !== 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(
3500
+ throw new import_core27.TileConversionError(
3256
3501
  "I3S_MESH_SOURCE_ATTRIBUTE_UNSUPPORTED",
3257
3502
  `Attribute ${attribute2.name} requires a standard scalar/string resource layout`
3258
3503
  );
@@ -3261,7 +3506,7 @@ async function extractI3SMeshFeatures(content, item, metadata, options, readExte
3261
3506
  signal?.throwIfAborted();
3262
3507
  inputBytes += bytes instanceof Uint8Array ? bytes.byteLength : Infinity;
3263
3508
  if (!(bytes instanceof Uint8Array) || inputBytes > maximumAttributeBytes) {
3264
- throw new import_core26.TileConversionError(
3509
+ throw new import_core27.TileConversionError(
3265
3510
  "INPUT_RESOURCE_TOO_LARGE",
3266
3511
  "I3S node attributes exceed maxAttributeBytes"
3267
3512
  );
@@ -3274,14 +3519,14 @@ async function extractI3SMeshFeatures(content, item, metadata, options, readExte
3274
3519
  i3s: { attributeValues: "exact" }
3275
3520
  });
3276
3521
  } catch (error) {
3277
- throw new import_core26.TileConversionError(
3522
+ throw new import_core27.TileConversionError(
3278
3523
  "I3S_MESH_SOURCE_ATTRIBUTE_INVALID",
3279
3524
  `Invalid attribute ${attribute2.name}: ${error instanceof Error ? error.message : String(error)}`
3280
3525
  );
3281
3526
  }
3282
3527
  const column = parsed[attribute2.name];
3283
3528
  if (!column || count !== void 0 && column.length !== count || !column.length) {
3284
- throw new import_core26.TileConversionError(
3529
+ throw new import_core27.TileConversionError(
3285
3530
  "I3S_MESH_SOURCE_ATTRIBUTE_COUNT_INVALID",
3286
3531
  "Every attribute must have the same nonzero row count"
3287
3532
  );
@@ -3289,7 +3534,7 @@ async function extractI3SMeshFeatures(content, item, metadata, options, readExte
3289
3534
  if (Array.from(column).some(
3290
3535
  (value) => typeof value === "number" && !Number.isFinite(value)
3291
3536
  )) {
3292
- throw new import_core26.TileConversionError(
3537
+ throw new import_core27.TileConversionError(
3293
3538
  "I3S_MESH_SOURCE_ATTRIBUTE_UNSUPPORTED",
3294
3539
  "Numeric noData values require an explicit custom mapper"
3295
3540
  );
@@ -3302,7 +3547,7 @@ async function extractI3SMeshFeatures(content, item, metadata, options, readExte
3302
3547
  for (let index = 0; index < count; index++) {
3303
3548
  const identifier = identifiers[index];
3304
3549
  if (!(typeof identifier === "bigint" || typeof identifier === "number" && Number.isSafeInteger(identifier)) || attributeRows.has(BigInt(identifier))) {
3305
- throw new import_core26.TileConversionError(
3550
+ throw new import_core27.TileConversionError(
3306
3551
  "I3S_MESH_SOURCE_FEATURE_ID_UNSUPPORTED",
3307
3552
  "Object IDs must be unique exact integers"
3308
3553
  );
@@ -3313,7 +3558,7 @@ async function extractI3SMeshFeatures(content, item, metadata, options, readExte
3313
3558
  const geometryIdentifiers = content.featureIds;
3314
3559
  const attribute = content.sourceAttributes?.id;
3315
3560
  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(
3561
+ throw new import_core27.TileConversionError(
3317
3562
  "I3S_MESH_SOURCE_FEATURE_ID_UNSUPPORTED",
3318
3563
  "Geometry requires one exact unnormalized object ID per vertex"
3319
3564
  );
@@ -3324,7 +3569,7 @@ async function extractI3SMeshFeatures(content, item, metadata, options, readExte
3324
3569
  const covered = new Uint8Array(triangleFeatureIndices.length);
3325
3570
  for (const range of content.drawRanges) {
3326
3571
  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(
3572
+ throw new import_core27.TileConversionError(
3328
3573
  "I3S_MESH_SOURCE_FEATURE_ASSOCIATION_INVALID",
3329
3574
  "Feature draw ranges must cover valid triangles"
3330
3575
  );
@@ -3332,7 +3577,7 @@ async function extractI3SMeshFeatures(content, item, metadata, options, readExte
3332
3577
  for (let triangle = range.firstPrimitive; triangle < range.firstPrimitive + range.primitiveCount; triangle++) {
3333
3578
  const vertex = indices?.[triangle * 3] ?? triangle * 3;
3334
3579
  if (covered[triangle] || geometryIdentifiers[vertex] !== range.featureId) {
3335
- throw new import_core26.TileConversionError(
3580
+ throw new import_core27.TileConversionError(
3336
3581
  "I3S_MESH_SOURCE_FEATURE_ASSOCIATION_INVALID",
3337
3582
  "Feature draw ranges must agree with decoded ownership without overlapping"
3338
3583
  );
@@ -3341,7 +3586,7 @@ async function extractI3SMeshFeatures(content, item, metadata, options, readExte
3341
3586
  }
3342
3587
  }
3343
3588
  if (covered.some((value) => value === 0)) {
3344
- throw new import_core26.TileConversionError(
3589
+ throw new import_core27.TileConversionError(
3345
3590
  "I3S_MESH_SOURCE_FEATURE_ASSOCIATION_INVALID",
3346
3591
  "Feature draw ranges must cover every triangle"
3347
3592
  );
@@ -3355,7 +3600,7 @@ async function extractI3SMeshFeatures(content, item, metadata, options, readExte
3355
3600
  const featureIds = corners.map((corner) => geometryIdentifiers[corner]);
3356
3601
  const row = attributeRows.get(BigInt(featureIds[0]));
3357
3602
  if (row === void 0 || featureIds.some((identifier) => identifier !== featureIds[0])) {
3358
- throw new import_core26.TileConversionError(
3603
+ throw new import_core27.TileConversionError(
3359
3604
  "I3S_MESH_SOURCE_FEATURE_ASSOCIATION_INVALID",
3360
3605
  "Each triangle must reference one declared attribute row"
3361
3606
  );
@@ -3364,14 +3609,14 @@ async function extractI3SMeshFeatures(content, item, metadata, options, readExte
3364
3609
  usedRows.add(row);
3365
3610
  }
3366
3611
  if (usedRows.size !== count) {
3367
- throw new import_core26.TileConversionError(
3612
+ throw new import_core27.TileConversionError(
3368
3613
  "I3S_MESH_SOURCE_FEATURE_ASSOCIATION_INVALID",
3369
3614
  "Attribute rows without geometry require a separately qualified mapping"
3370
3615
  );
3371
3616
  }
3372
3617
  const stableProperty = options.sourceFeatureIdProperty ?? options.objectIdProperty;
3373
3618
  if (!Object.hasOwn(columns, stableProperty)) {
3374
- throw new import_core26.TileConversionError(
3619
+ throw new import_core27.TileConversionError(
3375
3620
  "I3S_MESH_SOURCE_FEATURE_MAPPING_REQUIRED",
3376
3621
  "Stable identifier column is missing"
3377
3622
  );
@@ -3392,7 +3637,7 @@ async function extractI3SMeshFeatures(content, item, metadata, options, readExte
3392
3637
  for (let index = 0; index < column.length; index++) {
3393
3638
  const identifier = column.get(index);
3394
3639
  if (stableIds.has(identifier)) {
3395
- throw new import_core26.TileConversionError(
3640
+ throw new import_core27.TileConversionError(
3396
3641
  "I3S_MESH_SOURCE_FEATURE_ID_UNSUPPORTED",
3397
3642
  "Stable IDs must be unique"
3398
3643
  );
@@ -3410,25 +3655,25 @@ async function extractI3SMeshFeatures(content, item, metadata, options, readExte
3410
3655
 
3411
3656
  // src/v5/i3s-mesh-source.ts
3412
3657
  function createI3SMeshTilesetConversionSource(tileset, options = {}) {
3413
- const source = (0, import_core27.createTilesetConversionSource)(tileset, options);
3658
+ const source = (0, import_core28.createTilesetConversionSource)(tileset, options);
3414
3659
  return {
3415
3660
  /** Validates the layer profile and explicit source decode policy before reading. */
3416
3661
  async inspect(signal) {
3417
3662
  if (options.features && options.getFeatures) {
3418
- throw new import_core27.TileConversionError(
3663
+ throw new import_core28.TileConversionError(
3419
3664
  "I3S_MESH_SOURCE_FEATURE_MAPPING_REQUIRED",
3420
3665
  "Choose explicit resource features or a custom getFeatures mapper, not both"
3421
3666
  );
3422
3667
  }
3423
3668
  const metadata = await source.inspect(signal);
3424
3669
  if (metadata.type !== "I3S" || !["3DObject", "IntegratedMesh"].includes(metadata.tileset.layerType) || tileset.options.i3s?.geometryMode !== "source") {
3425
- throw new import_core27.TileConversionError(
3670
+ throw new import_core28.TileConversionError(
3426
3671
  "I3S_MESH_SOURCE_PROFILE_UNSUPPORTED",
3427
3672
  "I3S mesh extraction requires a 3DObject/IntegratedMesh runtime with geometryMode=source"
3428
3673
  );
3429
3674
  }
3430
3675
  if (!metadata.spatialReference?.sourceCrs) {
3431
- throw new import_core27.TileConversionError(
3676
+ throw new import_core28.TileConversionError(
3432
3677
  "I3S_MESH_SOURCE_REFERENCE_REQUIRED",
3433
3678
  "I3S conversion requires an explicit source CRS"
3434
3679
  );
@@ -3456,7 +3701,7 @@ function createI3SMeshTilesetConversionSource(tileset, options = {}) {
3456
3701
  ].some(
3457
3702
  (field) => JSON.stringify(content.spatialReference?.[field]) !== JSON.stringify(metadata.spatialReference?.[field])
3458
3703
  )) {
3459
- throw new import_core27.TileConversionError(
3704
+ throw new import_core28.TileConversionError(
3460
3705
  "I3S_MESH_SOURCE_GEOMETRY_UNSUPPORTED",
3461
3706
  "I3S extraction requires original source-frame Float64 triangle geometry"
3462
3707
  );
@@ -3464,7 +3709,7 @@ function createI3SMeshTilesetConversionSource(tileset, options = {}) {
3464
3709
  if (!["earth-centered", "vertex-reference-frame"].includes(
3465
3710
  content.normalReferenceFrame || ""
3466
3711
  ) || content.normalReferenceFrame !== (metadata.tileset.store?.normalReferenceFrame || "earth-centered")) {
3467
- throw new import_core27.TileConversionError(
3712
+ throw new import_core28.TileConversionError(
3468
3713
  "I3S_MESH_SOURCE_NORMAL_FRAME_UNSUPPORTED",
3469
3714
  "Unknown I3S vector basis"
3470
3715
  );
@@ -3472,20 +3717,20 @@ function createI3SMeshTilesetConversionSource(tileset, options = {}) {
3472
3717
  if (content.meshSegmentation || Object.entries(attributes).some(
3473
3718
  ([name, value]) => value && !["position", "normal", "uv0", "id", "faceRange"].includes(name)
3474
3719
  )) {
3475
- throw new import_core27.TileConversionError(
3720
+ throw new import_core28.TileConversionError(
3476
3721
  "I3S_MESH_SOURCE_ATTRIBUTE_UNSUPPORTED",
3477
3722
  "Only positions, normals, TEXCOORD_0 and explicitly mapped feature associations are supported"
3478
3723
  );
3479
3724
  }
3480
3725
  const hasFeatures = content.featureIds.length > 0 || metadata.tileset.fields?.length || metadata.tileset.attributeStorageInfo?.length;
3481
3726
  if (Array.from(content.featureIds).some((value) => !Number.isSafeInteger(value))) {
3482
- throw new import_core27.TileConversionError(
3727
+ throw new import_core28.TileConversionError(
3483
3728
  "I3S_MESH_SOURCE_FEATURE_ID_UNSUPPORTED",
3484
3729
  "Decoded numeric feature IDs must be exact safe integers"
3485
3730
  );
3486
3731
  }
3487
3732
  if (hasFeatures && !options.getFeatures && !options.features) {
3488
- throw new import_core27.TileConversionError(
3733
+ throw new import_core28.TileConversionError(
3489
3734
  "I3S_MESH_SOURCE_FEATURE_MAPPING_REQUIRED",
3490
3735
  "I3S feature data requires explicit resource features or a custom Arrow mapper"
3491
3736
  );
@@ -3504,7 +3749,7 @@ function createI3SMeshTilesetConversionSource(tileset, options = {}) {
3504
3749
  true
3505
3750
  );
3506
3751
  if (mesh.attributes.POSITION.value.length / 3 !== content.vertexCount) {
3507
- throw new import_core27.TileConversionError(
3752
+ throw new import_core28.TileConversionError(
3508
3753
  "I3S_MESH_SOURCE_COUNT_INVALID",
3509
3754
  "I3S vertexCount must match the decoded geometry"
3510
3755
  );
@@ -3520,7 +3765,7 @@ function createI3SMeshTilesetConversionSource(tileset, options = {}) {
3520
3765
  ) : void 0;
3521
3766
  signal?.throwIfAborted();
3522
3767
  if (hasFeatures && !features) {
3523
- throw new import_core27.TileConversionError(
3768
+ throw new import_core28.TileConversionError(
3524
3769
  "I3S_MESH_SOURCE_FEATURE_MAPPING_REQUIRED",
3525
3770
  "The feature mapper must return all declared feature data"
3526
3771
  );
@@ -3536,6 +3781,7 @@ function createI3SMeshTilesetConversionSource(tileset, options = {}) {
3536
3781
  yield {
3537
3782
  id: `${item.tile.id}/${entry.index}`,
3538
3783
  origin: [0, 0, 0],
3784
+ sourceLayerType: metadata.tileset.layerType,
3539
3785
  mesh,
3540
3786
  normalReferenceFrame: content.normalReferenceFrame === "vertex-reference-frame" ? "vertex-reference-frame" : "earth-centered",
3541
3787
  features,
@@ -3546,3 +3792,1529 @@ function createI3SMeshTilesetConversionSource(tileset, options = {}) {
3546
3792
  }
3547
3793
  };
3548
3794
  }
3795
+
3796
+ // src/v5/point-cloud-attributes.ts
3797
+ var import_schema_utils5 = require("@loaders.gl/core/schema-utils");
3798
+ var import_core29 = require("@loaders.gl/tile-converter/v5/core");
3799
+ function mapPointCloudAttributes(input, mapping) {
3800
+ const mesh = getPointCloudMesh(input);
3801
+ const attributes = /* @__PURE__ */ Object.create(null);
3802
+ const selected = /* @__PURE__ */ new Set();
3803
+ for (const [target, source] of Object.entries(mapping.attributes)) {
3804
+ if (!target || ["__proto__", "constructor", "prototype"].includes(target) || !Object.hasOwn(mesh.attributes, source))
3805
+ throw new import_core29.TileConversionError(
3806
+ "POINT_CLOUD_MAPPING_INVALID",
3807
+ `Invalid point attribute mapping ${target}: ${source}`
3808
+ );
3809
+ if (Object.hasOwn(mapping.drop || {}, source))
3810
+ throw new import_core29.TileConversionError(
3811
+ "POINT_CLOUD_MAPPING_INVALID",
3812
+ `Point attribute ${source} is both selected and dropped`
3813
+ );
3814
+ attributes[target] = { ...mesh.attributes[source] };
3815
+ selected.add(source);
3816
+ }
3817
+ const droppedAttributes = /* @__PURE__ */ Object.create(null);
3818
+ for (const [source, reason] of Object.entries(mapping.drop || {}).sort(
3819
+ ([left], [right]) => left < right ? -1 : left > right ? 1 : 0
3820
+ )) {
3821
+ if (!Object.hasOwn(mesh.attributes, source) || typeof reason !== "string" || !reason.trim())
3822
+ throw new import_core29.TileConversionError(
3823
+ "POINT_CLOUD_MAPPING_INVALID",
3824
+ `Invalid loss declaration for ${source}`
3825
+ );
3826
+ droppedAttributes[source] = reason;
3827
+ }
3828
+ const unmapped = Object.keys(mesh.attributes).filter(
3829
+ (name) => !selected.has(name) && !Object.hasOwn(droppedAttributes, name)
3830
+ );
3831
+ if (unmapped.length || !attributes.POSITION)
3832
+ throw new import_core29.TileConversionError(
3833
+ "POINT_CLOUD_MAPPING_REQUIRED",
3834
+ `Map POSITION and explicitly retain or drop every attribute: ${unmapped.join(", ")}`
3835
+ );
3836
+ return {
3837
+ mesh: { ...mesh, attributes, schema: { fields: [], metadata: { ...mesh.schema?.metadata } } },
3838
+ droppedAttributes
3839
+ };
3840
+ }
3841
+ function getPointCloudMesh(input, maxInputBytes = Infinity) {
3842
+ const buffers = /* @__PURE__ */ new Set();
3843
+ let bytes = 0;
3844
+ const countBuffer = (buffer) => {
3845
+ if (!buffers.has(buffer)) {
3846
+ buffers.add(buffer);
3847
+ bytes += buffer.byteLength;
3848
+ }
3849
+ if (bytes > maxInputBytes)
3850
+ throw new import_core29.TileConversionError("POINT_CLOUD_INPUT_BUDGET", "Point input byte budget exceeded");
3851
+ };
3852
+ if ("shape" in input) {
3853
+ const checkColumn = (column) => {
3854
+ if (column.nullCount)
3855
+ throw new import_core29.TileConversionError(
3856
+ "POINT_CLOUD_NULL_UNSUPPORTED",
3857
+ "Null point values require an explicit preprocessing step"
3858
+ );
3859
+ for (const buffer of Object.values(column.buffers)) if (buffer) countBuffer(buffer.buffer);
3860
+ for (const view of column.variadicBuffers) countBuffer(view.buffer);
3861
+ for (const child of column.children) checkColumn(child);
3862
+ for (const dictionary of column.dictionary?.data || []) checkColumn(dictionary);
3863
+ };
3864
+ for (const batch of input.data.batches) checkColumn(batch.data);
3865
+ } else
3866
+ for (const attribute of Object.values(input.attributes)) countBuffer(attribute.value.buffer);
3867
+ const mesh = "shape" in input ? (0, import_schema_utils5.convertTableToMesh)(input) : input;
3868
+ if (mesh.topology !== "point-list" || mesh.indices)
3869
+ throw new import_core29.TileConversionError(
3870
+ "POINT_CLOUD_INPUT_INVALID",
3871
+ "Decoded unindexed point-list input is required"
3872
+ );
3873
+ return mesh;
3874
+ }
3875
+
3876
+ // src/v5/point-cloud-copc.ts
3877
+ var import_copc = require("@loaders.gl/tiles-3d/copc");
3878
+ var import_core30 = require("@loaders.gl/tile-converter/v5/core");
3879
+ var SCALAR_RANGES = {
3880
+ intensity: [0, 65535],
3881
+ classification: [0, 255],
3882
+ nir: [0, 65535],
3883
+ gpsTime: [-Infinity, Infinity],
3884
+ scanAngle: [-32768, 32767],
3885
+ userData: [0, 255],
3886
+ pointSourceId: [0, 65535],
3887
+ returnNumber: [1, 15],
3888
+ numberOfReturns: [1, 15],
3889
+ scannerChannel: [0, 3],
3890
+ scanDirectionFlag: [0, 1],
3891
+ edgeOfFlightLine: [0, 1],
3892
+ synthetic: [0, 1],
3893
+ keyPoint: [0, 1],
3894
+ withheld: [0, 1],
3895
+ overlap: [0, 1]
3896
+ };
3897
+ async function encodePointCloudCOPC(input, options) {
3898
+ const maxInputBytes = options.maxInputBytes ?? 64 * 1024 * 1024;
3899
+ const maxPoints = options.maxPoints ?? 1e6;
3900
+ const maxOutputBytes = options.maxOutputBytes ?? 128 * 1024 * 1024;
3901
+ if ([maxInputBytes, maxPoints, maxOutputBytes].some(
3902
+ (value) => !Number.isSafeInteger(value) || value < 1
3903
+ ) || options.scale.length !== 3 || options.scale.some((value) => !Number.isFinite(value) || value <= 0) || options.offset.length !== 3 || options.offset.some((value) => !Number.isFinite(value)) || typeof options.wkt !== "string" || !options.wkt.trim() || !Number.isFinite(options.maxPositionError) || options.maxPositionError < 0)
3904
+ throw new import_core30.TileConversionError(
3905
+ "POINT_CLOUD_COPC_OPTIONS_INVALID",
3906
+ "COPC requires explicit finite scale, offset, CRS, precision and positive budgets"
3907
+ );
3908
+ options.signal?.throwIfAborted();
3909
+ const source = getPointCloudMesh(input, maxInputBytes);
3910
+ const { mesh, droppedAttributes } = mapPointCloudAttributes(source, options.mapping);
3911
+ const position = mesh.attributes.POSITION;
3912
+ const pointCount = mesh.header?.vertexCount ?? position.value.length / 3;
3913
+ if (!Number.isSafeInteger(pointCount) || pointCount < 1 || pointCount > maxPoints)
3914
+ throw new import_core30.TileConversionError(
3915
+ "POINT_CLOUD_POINT_BUDGET",
3916
+ "COPC requires a nonempty batch within the point limit"
3917
+ );
3918
+ if (!(position.value instanceof Float32Array || position.value instanceof Float64Array) || position.size !== 3 || position.normalized)
3919
+ throw new import_core30.TileConversionError(
3920
+ "POINT_CLOUD_POSITION_INVALID",
3921
+ "COPC requires decoded floating XYZ"
3922
+ );
3923
+ const extraNames = new Set(options.extraBytes?.map((field) => field.attribute) || []);
3924
+ const descriptorNames = new Set(
3925
+ options.extraBytes?.map((field) => field.name || field.attribute) || []
3926
+ );
3927
+ if (extraNames.size !== (options.extraBytes?.length || 0) || descriptorNames.size !== extraNames.size || options.extraBytes?.some((field) => {
3928
+ const name = field.name || field.attribute;
3929
+ const description = field.description || "";
3930
+ return !mesh.attributes[field.attribute] || !field.attribute || !name.length || name.length > 32 || description.length > 32 || /[^\x20-\x7e]/.test(name) || /[^\x20-\x7e]/.test(description);
3931
+ }))
3932
+ throw new import_core30.TileConversionError(
3933
+ "POINT_CLOUD_EXTRA_BYTES_INVALID",
3934
+ "Extra Bytes descriptors require unique mapped attributes and names/descriptions within 32 printable ASCII bytes"
3935
+ );
3936
+ for (const name of extraNames)
3937
+ if (name === "POSITION" || name === "COLOR_0" || Object.hasOwn(SCALAR_RANGES, name))
3938
+ throw new import_core30.TileConversionError(
3939
+ "POINT_CLOUD_EXTRA_BYTES_INVALID",
3940
+ "Map standard attributes directly instead of duplicating them as Extra Bytes"
3941
+ );
3942
+ for (const [name, attribute] of Object.entries(mesh.attributes)) {
3943
+ const color2 = name === "COLOR_0";
3944
+ const extra = extraNames.has(name);
3945
+ if (name !== "POSITION" && !color2 && !extra && !Object.hasOwn(SCALAR_RANGES, name))
3946
+ throw new import_core30.TileConversionError(
3947
+ "POINT_CLOUD_ATTRIBUTE_UNSUPPORTED",
3948
+ `COPC cannot preserve ${name}; supply an explicit supported mapping or drop reason`
3949
+ );
3950
+ if (attribute.transform || attribute.byteOffset || attribute.byteStride || attribute.componentType === "float16" || attribute.value.length !== pointCount * attribute.size || !color2 && attribute.normalized || name !== "POSITION" && !color2 && (extra ? ![1, 3].includes(attribute.size) : attribute.size !== 1))
3951
+ throw new import_core30.TileConversionError(
3952
+ "POINT_CLOUD_ATTRIBUTE_INVALID",
3953
+ `COPC requires packed decoded attributes: ${name}`
3954
+ );
3955
+ if (color2) {
3956
+ if (attribute.size !== 3 || !(attribute.value instanceof Uint8Array || attribute.value instanceof Uint16Array))
3957
+ throw new import_core30.TileConversionError(
3958
+ "POINT_CLOUD_COLOR_INVALID",
3959
+ "COPC requires explicitly mapped uint8/uint16 RGB"
3960
+ );
3961
+ } else if (extra) {
3962
+ for (const value of attribute.value)
3963
+ if (typeof value !== "bigint" && !Number.isFinite(value))
3964
+ throw new import_core30.TileConversionError(
3965
+ "POINT_CLOUD_ATTRIBUTE_RANGE",
3966
+ `Nonfinite Extra Bytes value: ${name}`
3967
+ );
3968
+ } else if (name !== "POSITION") {
3969
+ const [minimum2, maximum2] = SCALAR_RANGES[name];
3970
+ for (const value of attribute.value) {
3971
+ if (typeof value !== "number" || !Number.isFinite(value) || value < minimum2 || value > maximum2 || name !== "gpsTime" && !Number.isInteger(value))
3972
+ throw new import_core30.TileConversionError(
3973
+ "POINT_CLOUD_ATTRIBUTE_RANGE",
3974
+ `COPC value outside the qualified range: ${name}`
3975
+ );
3976
+ }
3977
+ }
3978
+ }
3979
+ let maximumPositionError = 0;
3980
+ const minimum = [Infinity, Infinity, Infinity];
3981
+ const maximum = [-Infinity, -Infinity, -Infinity];
3982
+ const errors = [0, 0, 0];
3983
+ for (let row = 0; row < pointCount; row++) {
3984
+ for (let axis = 0; axis < 3; axis++) {
3985
+ const value = position.value[row * 3 + axis];
3986
+ const encoded = Math.round((value - options.offset[axis]) / options.scale[axis]);
3987
+ if (!Number.isFinite(value) || !Number.isSafeInteger(encoded) || encoded < -2147483648 || encoded > 2147483647)
3988
+ throw new import_core30.TileConversionError(
3989
+ "POINT_CLOUD_POSITION_RANGE",
3990
+ "COPC position exceeds signed int32 quantization"
3991
+ );
3992
+ minimum[axis] = Math.min(minimum[axis], value);
3993
+ maximum[axis] = Math.max(maximum[axis], value);
3994
+ errors[axis] = encoded * options.scale[axis] + options.offset[axis] - value;
3995
+ }
3996
+ maximumPositionError = Math.max(maximumPositionError, Math.hypot(...errors));
3997
+ if (maximumPositionError > options.maxPositionError)
3998
+ throw new import_core30.TileConversionError(
3999
+ "POINT_CLOUD_PRECISION_EXCEEDED",
4000
+ "COPC position rounding exceeds the declared error gate"
4001
+ );
4002
+ if (row % 16384 === 0) {
4003
+ await new Promise((resolve) => setTimeout(resolve, 0));
4004
+ options.signal?.throwIfAborted();
4005
+ }
4006
+ }
4007
+ const width = Math.max(...maximum.map((value, axis) => value - minimum[axis]), ...options.scale);
4008
+ const radius = width / 2;
4009
+ if (!Number.isFinite(Math.hypot(radius, radius, radius)) || minimum.some((value, axis) => {
4010
+ const center = value + (maximum[axis] - value) / 2;
4011
+ return !Number.isFinite(value + maximum[axis]) || !Number.isFinite(center - radius) || !Number.isFinite(center + radius);
4012
+ }))
4013
+ throw new import_core30.TileConversionError("POINT_CLOUD_BOUNDS_INVALID", "COPC coordinate extent overflow");
4014
+ options.signal?.throwIfAborted();
4015
+ const color = mesh.attributes.COLOR_0;
4016
+ const copc = import_copc.COPCWriter.encodeSync(mesh, {
4017
+ copc: {
4018
+ ...options.organization,
4019
+ scale: [...options.scale],
4020
+ offset: [...options.offset],
4021
+ wkt: options.wkt,
4022
+ pointDataRecordFormat: mesh.attributes.nir ? 8 : color ? 7 : 6,
4023
+ colorDepth: color?.value instanceof Uint16Array ? 16 : 8,
4024
+ extraBytes: options.extraBytes
4025
+ }
4026
+ });
4027
+ options.signal?.throwIfAborted();
4028
+ if (copc.byteLength > maxOutputBytes)
4029
+ throw new import_core30.TileConversionError(
4030
+ "POINT_CLOUD_OUTPUT_BUDGET",
4031
+ "Completed COPC output exceeds the byte budget"
4032
+ );
4033
+ return { copc, pointCount, maximumPositionError, droppedAttributes };
4034
+ }
4035
+ function createCOPCConversionCodec(options) {
4036
+ return {
4037
+ async *convert(input, _inspection, signal) {
4038
+ const combinedSignal = options.signal && signal ? AbortSignal.any([options.signal, signal]) : options.signal || signal;
4039
+ yield await encodePointCloudCOPC(input, { ...options, signal: combinedSignal });
4040
+ }
4041
+ };
4042
+ }
4043
+
4044
+ // src/v5/point-cloud-tileset.ts
4045
+ var import_point_cloud_tile_source = require("@loaders.gl/tiles-3d/point-cloud-tile-source");
4046
+ var import_core31 = require("@loaders.gl/tile-converter/v5/core");
4047
+ async function convertPointCloudToTileset(input, options) {
4048
+ const reference = options.spatialReference;
4049
+ if (!reference || reference.status !== "native" || reference.sourceCrs !== "EPSG:4978" || reference.targetCrs && reference.targetCrs !== "EPSG:4978" || reference.coordinateFrame !== "geocentric" || reference.axisOrder !== "xyz" || reference.heightReference !== "ellipsoidal" || reference.verticalUnitScale !== 1 || reference.units?.some((unit) => unit !== "meter")) {
4050
+ throw new import_core31.TileConversionError(
4051
+ "POINT_CLOUD_TILESET_FRAME_UNSUPPORTED",
4052
+ "Point authoring requires explicit native EPSG:4978 xyz meters and ellipsoidal heights"
4053
+ );
4054
+ }
4055
+ if (options.archive && (!Number.isSafeInteger(options.archive.maxArchiveBytes) || options.archive.maxArchiveBytes < 0)) {
4056
+ throw new import_core31.TileConversionError(
4057
+ "INVALID_ARCHIVE_BYTE_LIMIT",
4058
+ "Archive byte limit must be a non-negative safe integer"
4059
+ );
4060
+ }
4061
+ options.signal?.throwIfAborted();
4062
+ const sink = createPointCloudTilesetSink(options);
4063
+ let source;
4064
+ try {
4065
+ source = new import_point_cloud_tile_source.PointCloudTileSource(input, {
4066
+ pointCloudTiler: { ...options.tiling, signal: options.signal, spatialReference: reference }
4067
+ });
4068
+ const { pointCount } = await source.getMetadata();
4069
+ const report = await convertPointCloudSource(source, {
4070
+ sink,
4071
+ signal: options.signal,
4072
+ onProgress: options.onProgress,
4073
+ measureInputBytes: (tile) => {
4074
+ const table = tile.content?.data.data;
4075
+ return table ? table.schema.fields.reduce(
4076
+ (bytes, _field, index) => bytes + (table.getChildAt(index)?.byteLength ?? 0),
4077
+ 0
4078
+ ) : 0;
4079
+ },
4080
+ getTileEncodingOptions: (tile) => ({
4081
+ rtcCenter: [
4082
+ tile.header.boundingVolume.center[0],
4083
+ tile.header.boundingVolume.center[1],
4084
+ tile.header.boundingVolume.center[2]
4085
+ ],
4086
+ maxPositionError: options.geometricError
4087
+ })
4088
+ });
4089
+ options.signal?.throwIfAborted();
4090
+ const files = sink.getFiles();
4091
+ const archive = options.archive ? await createTileConversionArchive(files, {
4092
+ format: "3tz",
4093
+ maxArchiveBytes: options.archive.maxArchiveBytes,
4094
+ signal: options.signal
4095
+ }) : void 0;
4096
+ options.signal?.throwIfAborted();
4097
+ return { pointCount, report, files, archive };
4098
+ } catch (error) {
4099
+ await sink.abort(error);
4100
+ throw error;
4101
+ } finally {
4102
+ source?.close();
4103
+ }
4104
+ }
4105
+
4106
+ // src/v5/point-cloud-file.ts
4107
+ var import_copc2 = require("@loaders.gl/tiles-3d/copc");
4108
+ var import_potree = require("@loaders.gl/tiles-3d/potree");
4109
+ var import_schema_utils6 = require("@loaders.gl/core/schema-utils");
4110
+ var import_zip = require("@loaders.gl/archives/zip");
4111
+ async function encodePotreeArchive(input, options) {
4112
+ validateLimit(options.maxArchiveBytes);
4113
+ const dataset = await (0, import_potree.encodePotreeDataset)(input, { potree: options.potree });
4114
+ return encodePotreeDatasetArchive(dataset, options.maxArchiveBytes, options.potree?.signal);
4115
+ }
4116
+ async function encodePotreeDatasetArchive(dataset, maxArchiveBytes, signal) {
4117
+ validateLimit(maxArchiveBytes);
4118
+ for (const path of dataset.files.keys()) validatePath(path);
4119
+ signal?.throwIfAborted();
4120
+ const archive = await (0, import_zip.encodeZip)(Object.fromEntries(dataset.files), {
4121
+ compression: "store",
4122
+ zip64: "never",
4123
+ maxEntries: 65535,
4124
+ maxArchiveBytes: BigInt(maxArchiveBytes),
4125
+ signal
4126
+ });
4127
+ signal?.throwIfAborted();
4128
+ return new Blob([archive], { type: "application/zip" });
4129
+ }
4130
+ async function openPointCloudFile(input, options) {
4131
+ validateLimit(options.maxFileBytes);
4132
+ if (input.size > options.maxFileBytes) throw new RangeError("Point file input budget exceeded");
4133
+ options.signal?.throwIfAborted();
4134
+ if (options.format === "copc") {
4135
+ const source2 = import_copc2.COPCSourceLoader.createDataSource(input, options.sourceOptions || {});
4136
+ try {
4137
+ await source2.metadata;
4138
+ options.signal?.throwIfAborted();
4139
+ return { source: source2, close: () => source2.close() };
4140
+ } catch (error) {
4141
+ await source2.close();
4142
+ throw error;
4143
+ }
4144
+ }
4145
+ if (options.format !== "potree-zip") throw new TypeError("Unsupported point file format");
4146
+ const maxEntries = options.maxEntries ?? 1e5;
4147
+ const maxResourceBytes = options.maxResourceBytes ?? 64 * 1024 * 1024;
4148
+ validateLimit(maxEntries);
4149
+ validateLimit(maxResourceBytes);
4150
+ const controller = new AbortController();
4151
+ const signals = [controller.signal, options.signal, options.sourceOptions?.potree?.signal].filter(
4152
+ (value) => Boolean(value)
4153
+ );
4154
+ const signal = AbortSignal.any(signals);
4155
+ let archive;
4156
+ const entries = /* @__PURE__ */ new Map();
4157
+ let source;
4158
+ try {
4159
+ archive = await (0, import_zip.openZipArchive)(input, {
4160
+ maxArchiveBytes: BigInt(options.maxFileBytes),
4161
+ maxEntries,
4162
+ maxReadBytes: Math.max(65557, maxResourceBytes),
4163
+ maxCompressedBytes: BigInt(maxResourceBytes),
4164
+ maxDecodedBytes: BigInt(maxResourceBytes),
4165
+ signal
4166
+ });
4167
+ for await (const entry of archive.entries()) {
4168
+ if (entry.directory) continue;
4169
+ validatePath(entry.path);
4170
+ if (entry.compression !== "store")
4171
+ throw new Error("Potree bundles require unencrypted STORE entries");
4172
+ entries.set(entry.path, entry);
4173
+ }
4174
+ const candidates = ["metadata.json", "cloud.js"].filter((path) => entries.has(path));
4175
+ const metadataPath = options.metadataPath || (candidates.length === 1 ? candidates[0] : "");
4176
+ validatePath(metadataPath);
4177
+ if (!entries.has(metadataPath) || !/(^|\/)(metadata\.json|cloud\.js)$/.test(metadataPath))
4178
+ throw new Error("Potree ZIP requires one metadata entry or an explicit metadataPath");
4179
+ const prefix = "https://point-bundle.loaders.gl.invalid/";
4180
+ const fetchResource = async (resource, request = {}) => {
4181
+ signal.throwIfAborted();
4182
+ request.signal?.throwIfAborted();
4183
+ const url = new URL(resource);
4184
+ if (url.origin !== new URL(prefix).origin || url.search || url.hash)
4185
+ throw new Error("Resource outside the Potree bundle");
4186
+ const path = decodeURIComponent(url.pathname.slice(1));
4187
+ validatePath(path);
4188
+ const entry = entries.get(path);
4189
+ if (!entry) throw new Error(`Missing Potree bundle resource: ${path}`);
4190
+ const range = new Headers(request.headers).get("range");
4191
+ const match = range?.match(/^bytes=(\d+)-(\d+)$/);
4192
+ if (range && !match) throw new RangeError("Unsupported Potree bundle byte range");
4193
+ const begin = match ? Number(match[1]) : 0;
4194
+ const end = match ? Number(match[2]) + 1 : Number(entry.size);
4195
+ if (!Number.isSafeInteger(begin) || !Number.isSafeInteger(end) || begin < 0 || end < begin || BigInt(end) > entry.size || end - begin > maxResourceBytes)
4196
+ throw new RangeError("Potree bundle resource/range budget exceeded");
4197
+ const headers = new Headers({
4198
+ "content-length": String(end - begin),
4199
+ "accept-ranges": "bytes"
4200
+ });
4201
+ if (match) headers.set("content-range", `bytes ${begin}-${end - 1}/${entry.size}`);
4202
+ const bytes = match ? await archive.readRange(entry, {
4203
+ start: BigInt(begin),
4204
+ end: BigInt(end),
4205
+ signal: request.signal ?? void 0
4206
+ }) : await archive.read(entry, { signal: request.signal ?? void 0 });
4207
+ const response = new Response(bytes, {
4208
+ status: match ? 206 : 200,
4209
+ headers
4210
+ });
4211
+ Object.defineProperty(response, "url", { value: url.href });
4212
+ signal.throwIfAborted();
4213
+ return response;
4214
+ };
4215
+ source = import_potree.PotreeSourceLoader.createDataSource(`${prefix}${metadataPath}`, {
4216
+ ...options.sourceOptions,
4217
+ core: { ...options.sourceOptions?.core, fetch: fetchResource },
4218
+ potree: { ...options.sourceOptions?.potree, signal }
4219
+ });
4220
+ await source.initialize();
4221
+ signal.throwIfAborted();
4222
+ return {
4223
+ source,
4224
+ async close() {
4225
+ controller.abort();
4226
+ source?.close();
4227
+ await archive?.close();
4228
+ }
4229
+ };
4230
+ } catch (error) {
4231
+ controller.abort();
4232
+ source?.close();
4233
+ await archive?.close();
4234
+ throw error;
4235
+ }
4236
+ }
4237
+ function extractMeshVertices(mesh, options) {
4238
+ validateLimit(options.maxPoints);
4239
+ const position = mesh.attributes.POSITION;
4240
+ const pointCount = position?.value.length / 3;
4241
+ if (mesh.topology !== "triangle-list" || mesh.mode !== 4 || !position || position.size !== 3 || position.normalized || position.transform || position.byteStride || position.byteOffset || !(position.value instanceof Float32Array || position.value instanceof Float64Array) || !Number.isSafeInteger(pointCount) || pointCount > options.maxPoints || position.value.some((value) => !Number.isFinite(value)))
4242
+ throw new Error(
4243
+ "Mesh vertex harvesting requires packed finite triangle XYZ within the point budget"
4244
+ );
4245
+ const attributes = { POSITION: position };
4246
+ for (const name of options.attributes) {
4247
+ const attribute = mesh.attributes[name];
4248
+ if (!attribute || attribute.transform || attribute.byteStride || attribute.byteOffset || !Number.isSafeInteger(attribute.size) || attribute.size < 1 || attribute.value.length !== pointCount * attribute.size)
4249
+ throw new Error(`Invalid selected mesh vertex attribute: ${name}`);
4250
+ attributes[name] = attribute;
4251
+ }
4252
+ return { topology: "point-list", mode: 0, attributes, schema: (0, import_schema_utils6.deduceMeshSchema)(attributes) };
4253
+ }
4254
+ function validatePath(path) {
4255
+ if (!path || !/^[A-Za-z0-9_.\/-]+$/.test(path) || path.split("/").some((part) => !part || part === "." || part === ".."))
4256
+ throw new Error("Potree bundle paths must be canonical relative ASCII paths");
4257
+ }
4258
+ function validateLimit(value) {
4259
+ if (!Number.isSafeInteger(value) || value < 1)
4260
+ throw new RangeError("Point file limits must be positive safe integers");
4261
+ }
4262
+
4263
+ // src/v5/point-cloud-potree.ts
4264
+ var import_schema_utils7 = require("@loaders.gl/core/schema-utils");
4265
+ var import_potree2 = require("@loaders.gl/tiles-3d/potree");
4266
+ async function convertPointCloudToPotree(input, options) {
4267
+ if (typeof options.projection !== "string" || !options.projection.trim()) {
4268
+ throw new Error("Potree authoring requires an explicit native CRS declaration");
4269
+ }
4270
+ for (const limit of [
4271
+ options.maxGatheredBytes,
4272
+ options.maxOutputBytes,
4273
+ options.archive?.maxArchiveBytes
4274
+ ]) {
4275
+ if (limit !== void 0 && (!Number.isSafeInteger(limit) || limit < 1)) {
4276
+ throw new RangeError("Potree authoring byte limits must be positive safe integers");
4277
+ }
4278
+ }
4279
+ if (options.maxGatheredBytes === void 0 || options.maxOutputBytes === void 0) {
4280
+ throw new RangeError("Potree authoring requires gathered and encoded byte limits");
4281
+ }
4282
+ options.signal?.throwIfAborted();
4283
+ const tiler = new import_schema_utils7.PointCloudTiler(input, {
4284
+ ...options.tiling,
4285
+ maxNodes: Math.min(options.tiling?.maxNodes ?? 1e5, options.potree?.maxNodes ?? 1e5),
4286
+ maxTileBytes: Math.min(
4287
+ options.tiling?.maxTileBytes ?? 64 * 1024 * 1024,
4288
+ options.maxGatheredBytes
4289
+ ),
4290
+ signal: options.signal
4291
+ });
4292
+ const nodes = [];
4293
+ let gatheredBytes = 0;
4294
+ let pointByteLength = 0;
4295
+ let pointCount;
4296
+ try {
4297
+ const pending = [await tiler.getRootNode()];
4298
+ pointCount = tiler.pointCount;
4299
+ while (pending.length) {
4300
+ options.signal?.throwIfAborted();
4301
+ const node = pending.pop();
4302
+ if (node.pointCount * pointByteLength > options.maxGatheredBytes - gatheredBytes) {
4303
+ throw new RangeError("Potree gathered node byte budget exceeded");
4304
+ }
4305
+ const mesh = await tiler.getTileMesh(node.id);
4306
+ const nodeBytes = Array.from(
4307
+ new Set(Object.values(mesh.attributes).map((attribute) => attribute.value.buffer))
4308
+ ).reduce((bytes, buffer) => bytes + buffer.byteLength, 0);
4309
+ if (nodeBytes > options.maxGatheredBytes - gatheredBytes) {
4310
+ throw new RangeError("Potree gathered node byte budget exceeded");
4311
+ }
4312
+ if (node.pointCount) pointByteLength = nodeBytes / node.pointCount;
4313
+ gatheredBytes += nodeBytes;
4314
+ nodes.push({ id: node.id, mesh });
4315
+ const children = await tiler.getChildNodes(node.id);
4316
+ for (let index = children.length - 1; index >= 0; index--) pending.push(children[index]);
4317
+ }
4318
+ } finally {
4319
+ tiler.close();
4320
+ }
4321
+ const dataset = await (0, import_potree2.encodePotreeDataset)(nodes, {
4322
+ potree: {
4323
+ ...options.potree,
4324
+ projection: options.projection,
4325
+ maxOutputBytes: options.maxOutputBytes,
4326
+ signal: options.signal
4327
+ }
4328
+ });
4329
+ const archive = options.archive ? await encodePotreeDatasetArchive(dataset, options.archive.maxArchiveBytes, options.signal) : void 0;
4330
+ options.signal?.throwIfAborted();
4331
+ return { pointCount, nodeCount: nodes.length, dataset, archive };
4332
+ }
4333
+
4334
+ // src/v5/mesh-hierarchy.ts
4335
+ var import_core32 = require("@math.gl/core");
4336
+ var import_tiles_3d3 = require("@loaders.gl/tiles-3d");
4337
+ var import_core33 = require("@loaders.gl/tile-converter/v5/core");
4338
+ function prepareMeshHierarchy(inspection, resourceIds) {
4339
+ const rejectHierarchy = (message) => {
4340
+ throw new import_core33.TileConversionError("MESH_HIERARCHY_UNSUPPORTED", message);
4341
+ };
4342
+ if (!inspection.documents?.length || inspection.documents.length > 32)
4343
+ rejectHierarchy(
4344
+ "Hierarchy preservation requires expanded metadata inspection of at most 32 explicit document placements"
4345
+ );
4346
+ if (!resourceIds.length || resourceIds.length > 64 || new Set(resourceIds).size !== resourceIds.length || resourceIds.length !== inspection.resources.length || inspection.resources.some((resource) => !resourceIds.includes(resource.resourceId)))
4347
+ rejectHierarchy(
4348
+ "Hierarchy preservation requires every content placement, without duplicates (at most 64)"
4349
+ );
4350
+ const entryTransform = new import_core32.Matrix4();
4351
+ if (inspection.documents[0].parentResourceId !== void 0 || !Array.isArray(inspection.documents[0].parentTransform) || inspection.documents[0].parentTransform.length !== 16 || inspection.documents[0].parentTransform.some((value, index) => value !== entryTransform[index]))
4352
+ rejectHierarchy("The entry document must have identity parent placement");
4353
+ const documents = inspection.documents.map((placement) => placement.tileset);
4354
+ const document = documents[0];
4355
+ const externalDocuments = /* @__PURE__ */ new Map();
4356
+ for (let index = 1; index < inspection.documents.length; index++) {
4357
+ const identifier = inspection.documents[index].parentResourceId;
4358
+ if (!identifier || externalDocuments.has(identifier))
4359
+ rejectHierarchy(
4360
+ "Every external document requires a distinct inspected parent content placement"
4361
+ );
4362
+ externalDocuments.set(identifier, index);
4363
+ }
4364
+ for (const document2 of documents) {
4365
+ if (document2.metadata || document2.groups || document2.schema || document2.schemaUri || Object.keys(document2.extensions ?? {}).some((name) => name !== "EXT_geospatial_crs") || (document2.extensionsRequired ?? []).some((name) => name !== "EXT_geospatial_crs"))
4366
+ rejectHierarchy(
4367
+ "Dataset metadata and unmapped extensions require a separate hierarchy profile"
4368
+ );
4369
+ if (!["1.0", "1.1"].includes(document2.asset?.version ?? ""))
4370
+ rejectHierarchy("Hierarchy preservation requires a 3D Tiles 1.0 or 1.1 document");
4371
+ if (document2.geometricError !== void 0 && (typeof document2.geometricError !== "number" || !Number.isFinite(document2.geometricError) || document2.geometricError < 0))
4372
+ rejectHierarchy("Dataset geometricError must be finite nonnegative meters");
4373
+ }
4374
+ const spatialReferences = documents.map(
4375
+ (document2) => (0, import_tiles_3d3.get3DTilesSpatialReference)(document2)
4376
+ );
4377
+ const spatialReference = (0, import_core33.createTiles3DConversionSpatialContext)(
4378
+ spatialReferences[0]
4379
+ ).spatialReference;
4380
+ if (spatialReference.status !== "native" || spatialReference.sourceCrs !== "EPSG:4978" || spatialReference.heightReference !== "ellipsoidal" || spatialReference.axisOrder !== "xyz" || spatialReference.verticalUnitScale !== 1 || spatialReference.units?.some((unit) => unit !== "meter"))
4381
+ rejectHierarchy(
4382
+ "Hierarchy preservation requires native EPSG:4978 xyz meters and ellipsoidal heights"
4383
+ );
4384
+ for (const reference of spatialReferences.slice(1)) {
4385
+ const inherited = reference.crs.state === "absent" && reference.coordinateEpoch === void 0 && !reference.verticalCrs && !reference.warnings.length;
4386
+ const context = (0, import_core33.createTiles3DConversionSpatialContext)(inherited ? spatialReference : reference);
4387
+ if (context.spatialReference.status !== "native" || context.spatialReference.sourceCrs !== spatialReference.sourceCrs || context.spatialReference.heightReference !== spatialReference.heightReference || context.spatialReference.axisOrder !== spatialReference.axisOrder || context.spatialReference.verticalUnitScale !== 1 || context.spatialReference.units?.some((unit) => unit !== "meter") || context.spatialReference.coordinateEpoch !== spatialReference.coordinateEpoch)
4388
+ rejectHierarchy("External document CRS and coordinate epoch must match the entry frame");
4389
+ }
4390
+ const nodes = [];
4391
+ const visitedDocuments = /* @__PURE__ */ new Set([0]);
4392
+ const stack = [
4393
+ {
4394
+ tile: document.root,
4395
+ documentIndex: 0,
4396
+ prefix: "",
4397
+ path: [],
4398
+ parent: -1,
4399
+ transform: new import_core32.Matrix4(),
4400
+ refine: void 0
4401
+ }
4402
+ ];
4403
+ while (stack.length) {
4404
+ const {
4405
+ tile,
4406
+ path,
4407
+ parent,
4408
+ documentIndex,
4409
+ prefix,
4410
+ transform: parentTransform,
4411
+ refine: inheritedRefine
4412
+ } = stack.pop();
4413
+ const document2 = documents[documentIndex];
4414
+ if (nodes.length >= 256) rejectHierarchy("Hierarchy preservation supports at most 256 tiles");
4415
+ if (!tile || typeof tile !== "object" || Array.isArray(tile) || tile.implicitTiling !== void 0 || tile.extensions !== void 0 || tile.metadata !== void 0 || tile.viewerRequestVolume !== void 0 || tile.content !== void 0 && tile.contents !== void 0 || tile.contents !== void 0 && (!Array.isArray(tile.contents) || !tile.contents.length || document2.asset?.version !== "1.1") || tile.children !== void 0 && !Array.isArray(tile.children))
4416
+ rejectHierarchy(
4417
+ "Hierarchy tiles require explicit children, exclusive content/contents declarations and no unmapped metadata, extensions or request volumes"
4418
+ );
4419
+ const refine = tile.refine ?? inheritedRefine;
4420
+ if (refine !== "ADD" && refine !== "REPLACE")
4421
+ rejectHierarchy("Root refinement must be explicit ADD or REPLACE; children may inherit");
4422
+ const matrix = tile.transform;
4423
+ if (matrix !== void 0 && (!Array.isArray(matrix) || matrix.length !== 16 || matrix.some((value) => typeof value !== "number" || !Number.isFinite(value)) || matrix[3] !== 0 || matrix[7] !== 0 || matrix[11] !== 0 || matrix[15] !== 1))
4424
+ rejectHierarchy("Hierarchy transforms must be finite affine matrices");
4425
+ const transform = new import_core32.Matrix4(parentTransform).multiplyRight(matrix ?? new import_core32.Matrix4());
4426
+ const columns = [0, 1, 2].map(
4427
+ (column) => [0, 1, 2].reduce((sum, row) => sum + Math.abs(transform[column * 4 + row]), 0)
4428
+ );
4429
+ const rows = [0, 1, 2].map(
4430
+ (row) => [0, 1, 2].reduce((sum, column) => sum + Math.abs(transform[column * 4 + row]), 0)
4431
+ );
4432
+ const scale = document2.asset?.version === "1.0" ? 1 : Math.sqrt(Math.max(...columns)) * Math.sqrt(Math.max(...rows));
4433
+ const geometricError2 = (tile.geometricError ?? NaN) * scale;
4434
+ if (typeof tile.geometricError !== "number" || (tile.geometricError ?? -1) < 0 || !Number.isFinite(geometricError2) || Array.from(transform).some((value) => !Number.isFinite(value)))
4435
+ rejectHierarchy("Every hierarchy tile must declare a finite nonnegative metric error");
4436
+ const contents = tile.contents ?? (tile.content ? [tile.content] : []);
4437
+ const identifiers = contents.map(
4438
+ (_, contentIndex) => `${prefix}tile-${path.length ? path.join("-") : "root"}-content-${contentIndex}`
4439
+ );
4440
+ const externalIndexes = identifiers.map((identifier) => externalDocuments.get(identifier));
4441
+ const externalIndex = externalIndexes.find((index2) => index2 !== void 0);
4442
+ for (const [contentIndex, content] of contents.entries()) {
4443
+ if (!content || typeof content !== "object" || Array.isArray(content) || content.metadata !== void 0 || content.group !== void 0 || content.extensions !== void 0)
4444
+ rejectHierarchy("Content metadata, groups and extensions require a separate mapping");
4445
+ if (externalIndexes[contentIndex] === void 0 && !inspection.resources.some(
4446
+ (resource) => resource.resourceId === identifiers[contentIndex] && resource.documentIndex === documentIndex && resource.uri === (content.uri ?? content.url) && resource.tilePath.length === path.length && resource.tilePath.every((value, index2) => value === path[index2])
4447
+ ))
4448
+ rejectHierarchy("Hierarchy content must match the inspected placement");
4449
+ }
4450
+ const resourceId = identifiers[0];
4451
+ if (externalIndex !== void 0) {
4452
+ if (contents.length !== 1 || tile.contents !== void 0 || tile.children?.length || visitedDocuments.has(externalIndex))
4453
+ rejectHierarchy(
4454
+ "External references require a single content, no source children and a unique document placement"
4455
+ );
4456
+ visitedDocuments.add(externalIndex);
4457
+ const placement = inspection.documents[externalIndex];
4458
+ if (!Array.isArray(placement.parentTransform) || placement.parentTransform.length !== 16 || placement.parentTransform.some((value, index2) => value !== transform[index2]))
4459
+ rejectHierarchy("External document placement must match its referencing tile transform");
4460
+ const contentUri = tile.content?.uri ?? tile.content?.url;
4461
+ if (!contentUri || !new URL(contentUri, inspection.documents[documentIndex].url).pathname.toLowerCase().endsWith(".json"))
4462
+ rejectHierarchy("External documents require an inspected .json content reference");
4463
+ }
4464
+ const index = nodes.length;
4465
+ nodes.push({
4466
+ children: [],
4467
+ resourceIds: externalIndex === void 0 ? identifiers : [],
4468
+ geometricError: Math.max(
4469
+ geometricError2,
4470
+ externalIndex === void 0 ? 0 : documents[externalIndex].geometricError ?? 0
4471
+ ),
4472
+ refine
4473
+ });
4474
+ if (parent !== -1) nodes[parent].children.push(index);
4475
+ if (externalIndex !== void 0) {
4476
+ stack.push({
4477
+ tile: documents[externalIndex].root,
4478
+ path: [],
4479
+ parent: index,
4480
+ documentIndex: externalIndex,
4481
+ prefix: resourceId + "/",
4482
+ transform,
4483
+ refine: void 0
4484
+ });
4485
+ }
4486
+ if (nodes.length + stack.length + (tile.children?.length ?? 0) > 256)
4487
+ rejectHierarchy("Hierarchy preservation supports at most 256 tiles");
4488
+ for (let child = (tile.children?.length ?? 0) - 1; child >= 0; child--)
4489
+ stack.push({
4490
+ tile: tile.children[child],
4491
+ documentIndex,
4492
+ prefix,
4493
+ path: [...path, child],
4494
+ parent: index,
4495
+ transform,
4496
+ refine
4497
+ });
4498
+ }
4499
+ if (visitedDocuments.size !== documents.length || nodes.reduce((count, node) => count + node.resourceIds.length, 0) !== resourceIds.length || nodes.some((node) => !node.resourceIds.length && !node.children.length))
4500
+ rejectHierarchy("Contentless leaves or unmatched content placements cannot be preserved");
4501
+ const geometricError = document.geometricError ?? nodes[0].geometricError;
4502
+ if (typeof geometricError !== "number" || !Number.isFinite(geometricError) || geometricError < 0)
4503
+ rejectHierarchy("Dataset geometricError must be finite nonnegative meters");
4504
+ return {
4505
+ nodes,
4506
+ geometricError: Math.max(geometricError, nodes[0].geometricError),
4507
+ coordinateEpoch: spatialReference.coordinateEpoch
4508
+ };
4509
+ }
4510
+ function createMeshHierarchyTilesetSink(options) {
4511
+ const hierarchy = prepareMeshHierarchy(options.inspection, options.resourceIds);
4512
+ const extensions = structuredClone(
4513
+ options.inspection.tileset.extensions
4514
+ );
4515
+ const extensionsRequired = structuredClone(
4516
+ options.inspection.tileset.extensionsRequired
4517
+ );
4518
+ const { maxPositionError } = options;
4519
+ if (!Number.isFinite(maxPositionError) || maxPositionError < 0 || hierarchy.nodes.some((node) => !Number.isFinite(node.geometricError + maxPositionError)) || !Number.isFinite(hierarchy.geometricError + maxPositionError))
4520
+ throw new import_core33.TileConversionError(
4521
+ "MESH_GEOMETRIC_ERROR_INVALID",
4522
+ "Hierarchy position allowance and resulting errors must be finite nonnegative meters"
4523
+ );
4524
+ const memory = (0, import_core33.createBoundedMemoryTileConversionSink)(options);
4525
+ const placements = /* @__PURE__ */ new Map();
4526
+ const completedContents = /* @__PURE__ */ new Set();
4527
+ const primitiveIdentifiers = /* @__PURE__ */ new Set();
4528
+ let state = "open";
4529
+ let completed = false;
4530
+ return {
4531
+ /** Exposes files only after complete finalization. */
4532
+ getFiles: () => completed ? memory.getFiles() : [],
4533
+ /** Retains ordered codec outputs and requires a terminal primitive for each content. */
4534
+ async write(resource, signal) {
4535
+ signal?.throwIfAborted();
4536
+ const identifier = resource.contentResourceId;
4537
+ const { index: primitiveIndex, last: lastPrimitive } = resource.contentPrimitive ?? {
4538
+ index: 0,
4539
+ last: true
4540
+ };
4541
+ if (state !== "open" || !identifier || completedContents.has(identifier) || primitiveIdentifiers.has(resource.id) || primitiveIdentifiers.size >= 64 || !Number.isSafeInteger(primitiveIndex) || primitiveIndex !== (placements.get(identifier)?.length ?? 0) || typeof lastPrimitive !== "boolean" || !hierarchy.nodes.some((node2) => node2.resourceIds.includes(identifier)))
4542
+ throw new import_core33.TileConversionError(
4543
+ "MESH_HIERARCHY_PLACEMENT_INVALID",
4544
+ "Hierarchy writes require ordered, distinct primitives for declared contents before close (at most 64)"
4545
+ );
4546
+ if (resource.spatialReference.coordinateEpoch !== hierarchy.coordinateEpoch)
4547
+ throw new import_core33.TileConversionError(
4548
+ "MESH_SOURCE_EPOCH_MISMATCH",
4549
+ "Hierarchy output coordinate epoch must match its source"
4550
+ );
4551
+ const tile = createMeshTileset(resource, maxPositionError).root;
4552
+ const nodeIndex = hierarchy.nodes.findIndex((node2) => node2.resourceIds.includes(identifier));
4553
+ const node = hierarchy.nodes[nodeIndex];
4554
+ const sibling = node.resourceIds.map((identifier2) => placements.get(identifier2)?.[0]).find(Boolean);
4555
+ if (sibling && sibling.origin.some((value, axis) => value !== resource.origin[axis]))
4556
+ throw new import_core33.TileConversionError(
4557
+ "MESH_HIERARCHY_PLACEMENT_INVALID",
4558
+ "Contents on one tile must share an encoded origin"
4559
+ );
4560
+ const suffix = node.resourceIds.length > 1 ? "-" + node.resourceIds.indexOf(identifier) : "";
4561
+ const primitiveSuffix = primitiveIndex > 0 || !lastPrimitive ? "-primitive-" + primitiveIndex : "";
4562
+ const uri = `meshes/${nodeIndex}${suffix}${primitiveSuffix}.glb`;
4563
+ state = "writing";
4564
+ await memory.write(
4565
+ { resourceId: uri, parts: [resource.glb], contentType: "model/gltf-binary" },
4566
+ signal
4567
+ );
4568
+ const contentPlacements = placements.get(identifier) ?? [];
4569
+ contentPlacements.push({
4570
+ transform: [...tile.transform],
4571
+ origin: [...resource.origin],
4572
+ localBoundingBox: resource.localBoundingBox.map((bound) => [...bound]),
4573
+ uri
4574
+ });
4575
+ placements.set(identifier, contentPlacements);
4576
+ primitiveIdentifiers.add(resource.id);
4577
+ if (lastPrimitive) completedContents.add(identifier);
4578
+ state = "open";
4579
+ },
4580
+ /** Publishes original topology only after every source content has completed. */
4581
+ async finalize(report) {
4582
+ if (state !== "open" || completedContents.size !== hierarchy.nodes.reduce((count, node) => count + node.resourceIds.length, 0))
4583
+ throw new import_core33.TileConversionError(
4584
+ "MESH_HIERARCHY_INCOMPLETE",
4585
+ "Finalization requires every declared hierarchy content"
4586
+ );
4587
+ state = "writing";
4588
+ const tiles = new Array(hierarchy.nodes.length);
4589
+ const bounds = new Array(hierarchy.nodes.length);
4590
+ const transforms = hierarchy.nodes.map((node) => {
4591
+ const placement = placements.get(node.resourceIds[0])?.[0];
4592
+ return placement ? new import_core32.Matrix4(placement.transform) : new import_core32.Matrix4();
4593
+ });
4594
+ for (let index = hierarchy.nodes.length - 1; index >= 0; index--) {
4595
+ const node = hierarchy.nodes[index];
4596
+ const placement = placements.get(node.resourceIds[0])?.[0];
4597
+ const minimum = [Infinity, Infinity, Infinity];
4598
+ const maximum = [-Infinity, -Infinity, -Infinity];
4599
+ const contributors = node.children.map((child) => bounds[child]);
4600
+ const nodePlacements = node.resourceIds.flatMap((identifier) => placements.get(identifier));
4601
+ for (const placement2 of nodePlacements) {
4602
+ contributors.push(
4603
+ placement2.localBoundingBox.map(
4604
+ (bound, boundIndex) => bound.map((value, axis) => {
4605
+ const padding = 8 * Number.EPSILON * Math.max(
4606
+ 1,
4607
+ Math.abs(placement2.origin[axis]),
4608
+ ...placement2.localBoundingBox.map((limit) => Math.abs(limit[axis]))
4609
+ );
4610
+ return value + placement2.origin[axis] + (boundIndex ? padding : -padding);
4611
+ })
4612
+ )
4613
+ );
4614
+ }
4615
+ for (const [lower, upper] of contributors)
4616
+ for (let axis = 0; axis < 3; axis++) {
4617
+ minimum[axis] = Math.min(minimum[axis], lower[axis]);
4618
+ maximum[axis] = Math.max(maximum[axis], upper[axis]);
4619
+ }
4620
+ if ([...minimum, ...maximum].some((value) => !Number.isFinite(value)))
4621
+ throw new import_core33.TileConversionError(
4622
+ "MESH_TILESET_BOUNDS_INVALID",
4623
+ "Hierarchy absolute bounds must remain finite"
4624
+ );
4625
+ bounds[index] = [minimum, maximum];
4626
+ const inverse = new import_core32.Matrix4(transforms[index]).invert();
4627
+ const center = Array.from(
4628
+ inverse.transformAsPoint(minimum.map((value, axis) => value / 2 + maximum[axis] / 2))
4629
+ );
4630
+ const halfSize = minimum.map((value, axis) => maximum[axis] / 2 - value / 2);
4631
+ const halfAxes = [0, 1, 2].flatMap(
4632
+ (axis) => Array.from(
4633
+ inverse.transformAsVector(
4634
+ halfSize.map((value, coordinate) => coordinate === axis ? value : 0)
4635
+ )
4636
+ )
4637
+ );
4638
+ if ([...center, ...halfAxes].some((value) => !Number.isFinite(value)))
4639
+ throw new import_core33.TileConversionError(
4640
+ "MESH_TILESET_BOUNDS_INVALID",
4641
+ "Hierarchy local bounds must remain finite"
4642
+ );
4643
+ tiles[index] = {
4644
+ transform: Array.from(transforms[index]),
4645
+ boundingVolume: { box: [...center, ...halfAxes] },
4646
+ geometricError: node.geometricError + maxPositionError,
4647
+ refine: node.refine,
4648
+ ...nodePlacements.length > 1 ? { contents: nodePlacements.map((placement2) => ({ uri: placement2.uri })) } : placement ? { content: { uri: placement.uri } } : {},
4649
+ ...node.children.length ? {
4650
+ children: node.children.map((child) => {
4651
+ tiles[child].transform = Array.from(
4652
+ new import_core32.Matrix4(inverse).multiplyRight(transforms[child])
4653
+ );
4654
+ if (tiles[child].transform.some((value) => !Number.isFinite(value)))
4655
+ throw new import_core33.TileConversionError(
4656
+ "MESH_TILESET_BOUNDS_INVALID",
4657
+ "Relative hierarchy transforms must remain finite"
4658
+ );
4659
+ return tiles[child];
4660
+ })
4661
+ } : {}
4662
+ };
4663
+ }
4664
+ const json = JSON.stringify({
4665
+ asset: { version: "1.1" },
4666
+ ...extensions && Object.keys(extensions).length ? { extensions, extensionsUsed: Object.keys(extensions) } : {},
4667
+ ...extensionsRequired?.length ? { extensionsRequired } : {},
4668
+ geometricError: hierarchy.geometricError + maxPositionError,
4669
+ root: tiles[0]
4670
+ });
4671
+ await memory.write({
4672
+ resourceId: "tileset.json",
4673
+ parts: [json],
4674
+ contentType: "application/json"
4675
+ });
4676
+ await memory.finalize(report);
4677
+ state = "closed";
4678
+ completed = true;
4679
+ },
4680
+ /** Discards retained resources and hides all files after failure. */
4681
+ async abort(error) {
4682
+ state = "closed";
4683
+ completed = false;
4684
+ placements.clear();
4685
+ completedContents.clear();
4686
+ primitiveIdentifiers.clear();
4687
+ await memory.abort(error);
4688
+ }
4689
+ };
4690
+ }
4691
+
4692
+ // src/v5/selected-mesh-source.ts
4693
+ var import_core34 = require("@loaders.gl/core");
4694
+ var import_d_tiles2 = require("@loaders.gl/tiles-3d/3d-tiles");
4695
+ var import_tiles_3d4 = require("@loaders.gl/tiles-3d");
4696
+ var import_core35 = require("@loaders.gl/tile-converter/v5/core");
4697
+ var import_adapters = require("@loaders.gl/tile-converter/v5/adapters");
4698
+ var SELECTED_MESH_LIMITS = {
4699
+ /** Root JSON plus selected content transport bytes; also the decoded geometry/image/feature byte gate. */
4700
+ maxInputBytes: 16 * 1024 * 1024,
4701
+ /** Maximum declared content placements inspected. */
4702
+ maxInputResources: 1e3,
4703
+ /** Maximum inspected root/external document placements. */
4704
+ maxTilesetDocuments: 32,
4705
+ /** Maximum explicitly selected content placements in one partial archive. */
4706
+ maxSelectedResources: 64,
4707
+ /** Maximum primitive placements authored across all selected contents. */
4708
+ maxMeshResources: 64,
4709
+ /** Maximum retained file bytes and final archive bytes. */
4710
+ maxOutputBytes: 32 * 1024 * 1024,
4711
+ /** Maximum measured reconstruction error in meters. */
4712
+ maxPositionError: 0.01
4713
+ };
4714
+ async function createSelectedMeshConversionSource(inspection, options) {
4715
+ const {
4716
+ resourceIds,
4717
+ signal,
4718
+ dracoLibraryOptions,
4719
+ features,
4720
+ preserveHierarchy,
4721
+ fetcher = fetch
4722
+ } = options;
4723
+ const maxInputBytes = options.maxInputBytes ?? SELECTED_MESH_LIMITS.maxInputBytes;
4724
+ if (!Number.isSafeInteger(maxInputBytes) || maxInputBytes < 1 || maxInputBytes > SELECTED_MESH_LIMITS.maxInputBytes)
4725
+ throw new import_core35.TileConversionError(
4726
+ "INVALID_SELECTED_MESH_OPTIONS",
4727
+ "Selected mesh input budget must be a positive safe integer at most 16 MiB"
4728
+ );
4729
+ if (!resourceIds.length || resourceIds.length > SELECTED_MESH_LIMITS.maxSelectedResources || new Set(resourceIds).size !== resourceIds.length || resourceIds.some(
4730
+ (identifier) => !inspection.resources.some((resource) => resource.resourceId === identifier)
4731
+ ))
4732
+ throw new Error("Select between 1 and 64 distinct content placements.");
4733
+ if (preserveHierarchy !== void 0 && typeof preserveHierarchy !== "boolean")
4734
+ throw new import_core35.TileConversionError(
4735
+ "MESH_HIERARCHY_UNSUPPORTED",
4736
+ "preserveHierarchy must be a boolean"
4737
+ );
4738
+ if (preserveHierarchy) prepareMeshHierarchy(inspection, resourceIds);
4739
+ const descriptors = inspection.resources.filter(
4740
+ (resource) => resourceIds.includes(resource.resourceId)
4741
+ );
4742
+ const selections = descriptors.map(
4743
+ (descriptor) => (0, import_core35.selectBrowserTilesetContent)(inspection, descriptor)
4744
+ );
4745
+ const selectedContents = new Map(
4746
+ descriptors.map((descriptor, index) => [descriptor.resourceId, selections[index]])
4747
+ );
4748
+ if (!preserveHierarchy && resourceIds.length > 1 && selections.some((selection) => !selection.isLeaf))
4749
+ throw new Error(
4750
+ "Multi-selection requires leaf contents; source LOD hierarchy is not exported."
4751
+ );
4752
+ signal.throwIfAborted();
4753
+ const boundedFetcher = (0, import_adapters.createTileConversionResourceFetcher)({
4754
+ maxInputBytes,
4755
+ initialInputBytes: inspection.tilesetBytes,
4756
+ fetcher,
4757
+ signal
4758
+ });
4759
+ const qualification = createSelectedRuntime(
4760
+ inspection.tileset,
4761
+ new Uint8Array(),
4762
+ signal,
4763
+ boundedFetcher,
4764
+ inspection.rootUrl,
4765
+ dracoLibraryOptions
4766
+ );
4767
+ let spatialContext;
4768
+ try {
4769
+ await qualification.tilesetInitializationPromise;
4770
+ const metadata = await (0, import_adapters.createMeshTilesetConversionSource)(qualification).inspect(signal);
4771
+ spatialContext = (0, import_core35.createTiles3DConversionSpatialContext)(metadata.spatialReference);
4772
+ } finally {
4773
+ qualification.destroy();
4774
+ }
4775
+ const rawSource = options.source ?? (0, import_core35.createBrowserTilesetConversionSource)({
4776
+ input: inspection.rootUrl,
4777
+ ...SELECTED_MESH_LIMITS,
4778
+ maxInputBytes,
4779
+ fetcher: boundedFetcher
4780
+ });
4781
+ const selectedInspection = { ...inspection, resources: descriptors };
4782
+ const source = {
4783
+ /** Reuses inspected declarations without fetching the root again. */
4784
+ inspect: async (inspectionSignal) => {
4785
+ signal.throwIfAborted();
4786
+ inspectionSignal?.throwIfAborted();
4787
+ return selectedInspection;
4788
+ },
4789
+ /** Decodes and releases each selected placement in declaration order. */
4790
+ async *read(_inspection, readSignal) {
4791
+ const signal2 = readSignal ? AbortSignal.any([options.signal, readSignal]) : options.signal;
4792
+ signal2.throwIfAborted();
4793
+ let decodedBytes = 0;
4794
+ let primitiveCount = 0;
4795
+ const tileOrigins = /* @__PURE__ */ new Map();
4796
+ for await (const raw of rawSource.read(selectedInspection, signal2)) {
4797
+ const selection = selectedContents.get(raw.resourceId);
4798
+ if (!selection)
4799
+ throw new import_core35.TileConversionError(
4800
+ "INVALID_BROWSER_SELECTION",
4801
+ "Source yielded an uninspected content placement"
4802
+ );
4803
+ const contentUrl = raw.contentUrl ?? selection.contentUrl;
4804
+ const runtime = createSelectedRuntime(
4805
+ selection.tileset,
4806
+ raw.data,
4807
+ signal2,
4808
+ boundedFetcher,
4809
+ contentUrl,
4810
+ dracoLibraryOptions,
4811
+ spatialContext.spatialReference
4812
+ );
4813
+ try {
4814
+ await runtime.tilesetInitializationPromise;
4815
+ const meshSource = (0, import_adapters.createMeshTilesetConversionSource)(runtime, {
4816
+ unloadContent: true,
4817
+ features,
4818
+ readExternalResource: async (uri, _contentUri, resourceSignal) => {
4819
+ const response = await boundedFetcher(new URL(uri, contentUrl), {
4820
+ signal: resourceSignal
4821
+ });
4822
+ return new Uint8Array(await response.arrayBuffer());
4823
+ }
4824
+ });
4825
+ const metadata = await meshSource.inspect(signal2);
4826
+ const selectedContext = (0, import_core35.createTiles3DConversionSpatialContext)(metadata.spatialReference);
4827
+ if (selectedContext.spatialReference.coordinateEpoch !== spatialContext.spatialReference.coordinateEpoch)
4828
+ throw new import_core35.TileConversionError(
4829
+ "MESH_SOURCE_EPOCH_MISMATCH",
4830
+ "Selected content coordinate epoch must match the root; epoch transformations are unsupported"
4831
+ );
4832
+ let meshCount = 0;
4833
+ for await (const resource of meshSource.read(metadata, signal2)) {
4834
+ meshCount++;
4835
+ if (++primitiveCount > SELECTED_MESH_LIMITS.maxMeshResources)
4836
+ throw new Error("Selected contents exceed the mesh primitive placement limit.");
4837
+ decodedBytes += measureMeshBytes(resource);
4838
+ if (decodedBytes > maxInputBytes)
4839
+ throw new import_core35.TileConversionError(
4840
+ "INPUT_RESOURCE_TOO_LARGE",
4841
+ "Selected decoded geometry and encoded images exceed the aggregate input byte limit."
4842
+ );
4843
+ const tileIdentifier = raw.resourceId.replace(/content-\d+$/, "");
4844
+ if (preserveHierarchy && !tileOrigins.has(tileIdentifier))
4845
+ tileOrigins.set(tileIdentifier, [...resource.origin]);
4846
+ yield {
4847
+ ...resource,
4848
+ origin: preserveHierarchy ? tileOrigins.get(tileIdentifier) : resource.origin,
4849
+ id: `${raw.resourceId}/${resource.id}`,
4850
+ contentResourceId: raw.resourceId
4851
+ };
4852
+ }
4853
+ if (!meshCount) throw new Error("Selected content has no mesh primitive.");
4854
+ } finally {
4855
+ runtime.destroy();
4856
+ }
4857
+ }
4858
+ }
4859
+ };
4860
+ return {
4861
+ source,
4862
+ spatialContext,
4863
+ geometricError: Math.max(...selections.map((selection) => selection.geometricError)) + SELECTED_MESH_LIMITS.maxPositionError
4864
+ };
4865
+ }
4866
+ var SelectedTiles3DSource = class extends import_tiles_3d4.Tiles3DSource {
4867
+ /** Parent frame; explicit, unknown or unresolved child declarations are never replaced. */
4868
+ inheritedSpatialReference;
4869
+ /** Creates a selected source with optional external-document frame inheritance. */
4870
+ constructor(input, options, inheritedSpatialReference) {
4871
+ super(input, options);
4872
+ this.inheritedSpatialReference = inheritedSpatialReference;
4873
+ }
4874
+ /** Returns source metadata with the same absent-CRS inheritance used by external tilesets. */
4875
+ getMetadata() {
4876
+ const metadata = super.getMetadata();
4877
+ const reference = metadata.spatialReference;
4878
+ if (this.inheritedSpatialReference && reference?.crs.state === "absent" && reference.coordinateEpoch === void 0 && !reference.verticalCrs && !reference.warnings.length)
4879
+ return { ...metadata, spatialReference: this.inheritedSpatialReference };
4880
+ return metadata;
4881
+ }
4882
+ };
4883
+ function createSelectedRuntime(document, data, signal, boundedFetcher, contentUrl, dracoLibraryOptions, inheritedSpatialReference) {
4884
+ const {
4885
+ modules,
4886
+ decoderProfile,
4887
+ CDN: contentDeliveryNetwork,
4888
+ useLocalLibraries
4889
+ } = dracoLibraryOptions;
4890
+ const loadOptions = {
4891
+ modules,
4892
+ core: {
4893
+ worker: false,
4894
+ fetch: boundedFetcher,
4895
+ CDN: contentDeliveryNetwork,
4896
+ useLocalLibraries
4897
+ },
4898
+ // Reuse the full decoder already bundled for output verification.
4899
+ draco: { decoderProfile },
4900
+ // Preserve authored UVs and the transform for the converter's qualified GLB writer.
4901
+ gltf: {
4902
+ loadImages: false,
4903
+ decompressMeshes: true,
4904
+ excludeExtensions: {
4905
+ KHR_texture_transform: false,
4906
+ // Only Draco input compression is qualified by this conversion profile.
4907
+ EXT_meshopt_compression: false,
4908
+ KHR_meshopt_compression: false
4909
+ }
4910
+ },
4911
+ "3d-tiles": { loadGLTF: true }
4912
+ };
4913
+ return new import_tiles_3d4.Tileset3D(
4914
+ new SelectedTiles3DSource(
4915
+ {
4916
+ url: contentUrl,
4917
+ loader: import_d_tiles2.Tiles3DLoader,
4918
+ resolver: {
4919
+ /** Normalizes the selected hierarchy through the format loader, retaining ancestor transforms. */
4920
+ loadRoot: async (_url, loader, options) => (0, import_core34.parse)(new TextEncoder().encode(JSON.stringify(document)), loader, options),
4921
+ /** Decodes only the selected bounded payload; no sibling content can be requested. */
4922
+ loadResource: async (_url, loader, options) => {
4923
+ signal.throwIfAborted();
4924
+ return (0, import_core34.parse)(data, loader, options, {
4925
+ url: contentUrl,
4926
+ baseUrl: contentUrl.startsWith("blob:") ? contentUrl : new URL(".", contentUrl).href,
4927
+ fetch: boundedFetcher,
4928
+ coreApi: import_core34.coreApi,
4929
+ _parse: import_core34.parse
4930
+ });
4931
+ }
4932
+ }
4933
+ },
4934
+ loadOptions,
4935
+ inheritedSpatialReference
4936
+ )
4937
+ );
4938
+ }
4939
+ function measureMeshBytes(resource) {
4940
+ const featureBytes = resource.features ? resource.features.triangleFeatureIndices.byteLength + resource.features.batches.reduce(
4941
+ (bytes, batch) => bytes + batch.data.schema.fields.reduce(
4942
+ (columnBytes, field) => columnBytes + (batch.data.getChild(field.name)?.byteLength ?? 0),
4943
+ 0
4944
+ ),
4945
+ 0
4946
+ ) : 0;
4947
+ return Object.values(resource.mesh.attributes).reduce(
4948
+ (bytes, attribute) => bytes + attribute.value.byteLength,
4949
+ (resource.mesh.indices?.value.byteLength ?? 0) + (resource.material?.baseColorTexture?.data.byteLength ?? 0) + featureBytes
4950
+ );
4951
+ }
4952
+
4953
+ // src/v5/archive-source.ts
4954
+ var import_loader_utils = require("@loaders.gl/core/loader-utils");
4955
+ var import_i3s3 = require("@loaders.gl/tiles-3d/i3s");
4956
+ var import_d_tiles3 = require("@loaders.gl/tiles-3d/3d-tiles");
4957
+ var import_core36 = require("@loaders.gl/tile-converter/v5/core");
4958
+ async function openTileConversionArchive(descriptor, maxInputBytes, signal, fetcher = fetch) {
4959
+ signal.throwIfAborted();
4960
+ if (!["slpk", "3tz"].includes(descriptor.format))
4961
+ throw new import_core36.TileConversionError("INVALID_ARCHIVE_SOURCE", "Select SLPK or 3TZ archive input");
4962
+ validateArchiveEntityTag(descriptor.identity?.etag);
4963
+ const transport = createTileConversionResourceFetcher({
4964
+ maxInputBytes,
4965
+ signal,
4966
+ fetcher: async (url, options) => {
4967
+ const response = await fetcher(url, options);
4968
+ if (response.status !== 206) {
4969
+ await response.body?.cancel();
4970
+ throw new Error("Remote conversion archives require HTTP 206 byte-range responses.");
4971
+ }
4972
+ try {
4973
+ validateArchiveResponseValidator(response.headers);
4974
+ } catch (error) {
4975
+ await response.body?.cancel(error);
4976
+ throw error;
4977
+ }
4978
+ return response;
4979
+ }
4980
+ });
4981
+ let file;
4982
+ let closePromise;
4983
+ async function closeFile() {
4984
+ if (file) {
4985
+ closePromise ??= file.close();
4986
+ await closePromise;
4987
+ }
4988
+ }
4989
+ try {
4990
+ if (typeof descriptor.input === "string") {
4991
+ if (!["http:", "https:"].includes(new URL(descriptor.input).protocol))
4992
+ throw new Error("Use an HTTP(S) archive URL.");
4993
+ const remote = new import_loader_utils.HttpFile(descriptor.input, {
4994
+ fetch: (url, options) => transport(url, options),
4995
+ consistency: "strict",
4996
+ ...descriptor.identity
4997
+ });
4998
+ file = remote;
4999
+ await remote.open(signal);
5000
+ } else file = new import_loader_utils.BlobFile(descriptor.input);
5001
+ const ownedFile = file;
5002
+ file = void 0;
5003
+ return await createTileConversionArchiveTransport(
5004
+ ownedFile,
5005
+ descriptor.format,
5006
+ maxInputBytes,
5007
+ signal,
5008
+ descriptor
5009
+ );
5010
+ } catch (error) {
5011
+ await closeFile();
5012
+ throw error;
5013
+ }
5014
+ }
5015
+ async function createTileConversionArchiveTransport(file, format, maxInputBytes, signal, descriptor) {
5016
+ const rootUrl = `https://conversion.invalid/input.${format}`;
5017
+ let closed = false;
5018
+ let releasePromise;
5019
+ let closePromise;
5020
+ const closeFile = () => {
5021
+ closed = true;
5022
+ closePromise ??= file.close();
5023
+ return closePromise;
5024
+ };
5025
+ const checkOpen = () => {
5026
+ signal.throwIfAborted();
5027
+ if (closed)
5028
+ throw new import_core36.TileConversionError("ARCHIVE_SOURCE_CLOSED", "Conversion archive is closed");
5029
+ };
5030
+ try {
5031
+ checkOpen();
5032
+ if (!["slpk", "3tz"].includes(format) || !Number.isSafeInteger(maxInputBytes) || maxInputBytes < 1)
5033
+ throw new import_core36.TileConversionError(
5034
+ "INVALID_ARCHIVE_SOURCE",
5035
+ "Archive source requires a supported format and positive safe byte budget"
5036
+ );
5037
+ if (!Number.isSafeInteger(file.size) || file.size < 0 || file.size > maxInputBytes)
5038
+ throw new Error("Conversion archive input must be at most 16 MiB.");
5039
+ let readBytes = 0;
5040
+ const boundedFile = {
5041
+ handle: file.handle,
5042
+ size: file.size,
5043
+ bigsize: file.bigsize,
5044
+ url: file.url,
5045
+ ...file.stat ? { stat: () => file.stat() } : {},
5046
+ close: closeFile,
5047
+ /** Checks indexed reads before allocation and forwards operation cancellation. */
5048
+ read: async (start, length) => {
5049
+ checkOpen();
5050
+ if (!Number.isSafeInteger(length) || length < 0 || length > maxInputBytes - readBytes)
5051
+ throw new import_core36.TileConversionError(
5052
+ "INPUT_RESOURCE_TOO_LARGE",
5053
+ "Archive indexed reads exceed the input budget"
5054
+ );
5055
+ readBytes += length;
5056
+ const bytes = await file.read(start, length, signal);
5057
+ checkOpen();
5058
+ return bytes;
5059
+ }
5060
+ };
5061
+ const archive = format === "slpk" ? await (0, import_i3s3.parseSLPKArchive)(boundedFile) : await (0, import_d_tiles3.parse3DTilesArchive)(boundedFile);
5062
+ const root = new URL(rootUrl);
5063
+ return {
5064
+ rootUrl: format === "3tz" ? `${rootUrl}/tileset.json` : rootUrl,
5065
+ descriptor: descriptor ? {
5066
+ ...descriptor,
5067
+ ...file instanceof import_loader_utils.HttpFile ? { identity: file.getIdentitySnapshot() } : {}
5068
+ } : void 0,
5069
+ /** Resolves only resources belonging to this archive, without external network fallback. */
5070
+ fetcher: (async (input, options) => {
5071
+ checkOpen();
5072
+ options?.signal?.throwIfAborted();
5073
+ const url = new URL(input instanceof Request ? input.url : String(input));
5074
+ if (url.origin !== root.origin || url.pathname !== root.pathname && !url.pathname.startsWith(`${root.pathname}/`))
5075
+ throw new Error("Conversion dependencies must resolve inside the selected archive.");
5076
+ const path = decodeURIComponent(
5077
+ url.pathname.slice(root.pathname.length).replace(/^\//, "")
5078
+ );
5079
+ if (path.includes("\\") || path.split("/").some((segment) => segment === ".." || segment === "."))
5080
+ throw new Error("Archive dependency path cannot escape its root.");
5081
+ const bytes = format === "slpk" ? await archive.getFile(path, "http") : await archive.getFile(path);
5082
+ checkOpen();
5083
+ options?.signal?.throwIfAborted();
5084
+ return new Response(bytes);
5085
+ }),
5086
+ /** Releases the readable file on success, failure or cancellation. */
5087
+ close: async () => {
5088
+ closed = true;
5089
+ releasePromise ??= archive.close();
5090
+ await releasePromise;
5091
+ }
5092
+ };
5093
+ } catch (error) {
5094
+ await closeFile();
5095
+ throw error;
5096
+ }
5097
+ }
5098
+ function validateArchiveEntityTag(entityTag) {
5099
+ if (entityTag !== void 0 && entityTag !== null && !/^"[\x21\x23-\x7e\x80-\xff]*"$/.test(entityTag))
5100
+ throw new import_core36.TileConversionError(
5101
+ "ARCHIVE_VALIDATOR_UNSUPPORTED",
5102
+ "Remote archives require a strong quoted ETag, or Last-Modified without an ETag; weak/malformed ETags are unsupported"
5103
+ );
5104
+ }
5105
+ function validateArchiveResponseValidator(headers) {
5106
+ const entityTag = headers.get("etag");
5107
+ validateArchiveEntityTag(entityTag);
5108
+ if (entityTag !== null) return;
5109
+ const lastModified = headers.get("last-modified");
5110
+ const responseDate = headers.get("date");
5111
+ const modifiedTime = parseArchiveHttpDate(lastModified);
5112
+ const responseTime = parseArchiveHttpDate(responseDate);
5113
+ if (!Number.isFinite(modifiedTime) || !Number.isFinite(responseTime) || responseTime - modifiedTime < 6e4)
5114
+ throw new import_core36.TileConversionError(
5115
+ "ARCHIVE_VALIDATOR_UNSUPPORTED",
5116
+ "Last-Modified archive identity requires a valid Date at least 60 seconds later; otherwise supply a strong ETag"
5117
+ );
5118
+ }
5119
+ function parseArchiveHttpDate(value) {
5120
+ const milliseconds = value === null ? NaN : Date.parse(value);
5121
+ return Number.isFinite(milliseconds) && new Date(milliseconds).toUTCString() === value ? milliseconds : NaN;
5122
+ }
5123
+
5124
+ // src/v5/i3s-selection-source.ts
5125
+ var import_core37 = require("@loaders.gl/core");
5126
+ var import_i3s4 = require("@loaders.gl/tiles-3d/i3s");
5127
+ var import_tiles_3d5 = require("@loaders.gl/tiles-3d");
5128
+ var import_core38 = require("@loaders.gl/tile-converter/v5/core");
5129
+ function createRuntime(transport, layer, header) {
5130
+ const libraries = transport.dracoLibraryOptions;
5131
+ const sourceUrl = new URL(transport.rootUrl);
5132
+ sourceUrl.search = "";
5133
+ sourceUrl.hash = "";
5134
+ const options = {
5135
+ modules: libraries.modules,
5136
+ core: {
5137
+ worker: false,
5138
+ fetch: transport.fetcher,
5139
+ CDN: libraries.CDN,
5140
+ useLocalLibraries: libraries.useLocalLibraries
5141
+ },
5142
+ draco: { decoderProfile: libraries.decoderProfile },
5143
+ // The injected transport owns credential scope; runtime query parameters apply to every URL.
5144
+ i3s: { geometryMode: "source", decodeTextures: false, useCompressedTextures: false }
5145
+ };
5146
+ const loadResource = async (url, loader, loadOptions) => {
5147
+ const response = await transport.fetcher(url);
5148
+ const contextUrl = new URL(url);
5149
+ contextUrl.search = "";
5150
+ contextUrl.hash = "";
5151
+ return (0, import_core37.parse)(await response.arrayBuffer(), loader, loadOptions, {
5152
+ url: contextUrl.href,
5153
+ fetch: transport.fetcher,
5154
+ coreApi: import_core37.coreApi,
5155
+ _parse: import_core37.parse
5156
+ });
5157
+ };
5158
+ const source = new import_tiles_3d5.I3SSource(
5159
+ header && layer ? {
5160
+ ...layer,
5161
+ root: { ...header, children: [] },
5162
+ type: "I3S",
5163
+ url: sourceUrl.href,
5164
+ basePath: sourceUrl.href,
5165
+ loader: import_i3s4.I3SLoader,
5166
+ coreApi: import_core37.coreApi
5167
+ } : {
5168
+ url: sourceUrl.href,
5169
+ loader: import_i3s4.I3SLoader,
5170
+ coreApi: import_core37.coreApi,
5171
+ resolver: { loadRoot: loadResource, loadResource }
5172
+ },
5173
+ options
5174
+ );
5175
+ source.coreApi = { ...import_core37.coreApi, load: loadResource };
5176
+ return new import_tiles_3d5.Tileset3D(source, { i3s: options.i3s });
5177
+ }
5178
+ async function inspectI3SMeshConversionSource(options) {
5179
+ const { signal } = options;
5180
+ validateSelectionOptions(options);
5181
+ signal.throwIfAborted();
5182
+ const transport = options;
5183
+ let runtime;
5184
+ try {
5185
+ const response = await transport.fetcher(transport.rootUrl);
5186
+ const layer = await response.json();
5187
+ if (!["3DObject", "IntegratedMesh"].includes(layer.layerType))
5188
+ throw new Error("Conversion accepts I3S 3DObject or IntegratedMesh layers.");
5189
+ const rootFetcher = transport.fetcher;
5190
+ const cachedFetcher = (url, options2) => {
5191
+ const requestUrl = new URL(url instanceof Request ? url.url : String(url));
5192
+ const root = new URL(transport.rootUrl);
5193
+ return requestUrl.origin === root.origin && requestUrl.pathname === root.pathname ? Promise.resolve(new Response(JSON.stringify(layer))) : rootFetcher(url, options2);
5194
+ };
5195
+ runtime = createRuntime({ ...transport, fetcher: cachedFetcher });
5196
+ await runtime.tilesetInitializationPromise;
5197
+ await createI3SMeshTilesetConversionSource(runtime).inspect(signal);
5198
+ const stack = [runtime.root];
5199
+ const identifiers = /* @__PURE__ */ new Set();
5200
+ const resources = [];
5201
+ while (stack.length) {
5202
+ signal.throwIfAborted();
5203
+ const tile = stack.pop();
5204
+ if (!["string", "number"].includes(typeof tile.header.id) || String(tile.header.id).length === 0)
5205
+ throw new Error("I3S nodes require a declared identity.");
5206
+ const identifier = String(tile.header.id);
5207
+ if (identifiers.has(identifier))
5208
+ throw new Error("Repeated I3S node identity is unsupported.");
5209
+ identifiers.add(identifier);
5210
+ if (identifiers.size + stack.length + (tile.header.children?.length ?? 0) > (options.maxInputResources ?? SELECTED_MESH_LIMITS.maxInputResources))
5211
+ throw new Error("I3S inspection exceeds the 1,000 node limit.");
5212
+ await runtime.source.loadTileChildrenForTraversal(tile, signal);
5213
+ if (!tile.children.length && tile.contentUrl)
5214
+ resources.push({
5215
+ resourceId: identifier,
5216
+ uri: tile.contentUrl,
5217
+ header: structuredClone(tile.header)
5218
+ });
5219
+ for (const child of [...tile.children].reverse()) stack.push(child);
5220
+ }
5221
+ signal.throwIfAborted();
5222
+ if (!resources.length) throw new Error("No supported leaf mesh nodes are declared.");
5223
+ return {
5224
+ kind: "i3s",
5225
+ rootUrl: transport.rootUrl,
5226
+ layer,
5227
+ resources,
5228
+ metadataBytes: 0
5229
+ };
5230
+ } finally {
5231
+ runtime?.destroy();
5232
+ }
5233
+ }
5234
+ async function createSelectedI3SMeshConversionSource(inspection, options) {
5235
+ const { resourceIds, signal, features } = options;
5236
+ validateSelectionOptions(options);
5237
+ signal.throwIfAborted();
5238
+ if (inspection.rootUrl !== options.rootUrl)
5239
+ throw new import_core38.TileConversionError(
5240
+ "INVALID_I3S_SELECTION",
5241
+ "Selection transport must match the inspected layer"
5242
+ );
5243
+ if (!resourceIds.length || resourceIds.length > SELECTED_MESH_LIMITS.maxSelectedResources || new Set(resourceIds).size !== resourceIds.length || resourceIds.some(
5244
+ (identifier) => !inspection.resources.some((resource) => resource.resourceId === identifier)
5245
+ ))
5246
+ throw new Error("Select between 1 and 64 distinct leaf mesh nodes.");
5247
+ const selected = inspection.resources.filter(
5248
+ (resource) => resourceIds.includes(resource.resourceId)
5249
+ );
5250
+ if (selected.some(
5251
+ (resource) => resource.header.children?.length || !resource.header.contentUrl || resource.uri !== resource.header.contentUrl
5252
+ ))
5253
+ throw new Error("Only inspected leaf mesh headers can be converted.");
5254
+ const transport = options;
5255
+ let decodedBytes = 0;
5256
+ let meshCount = 0;
5257
+ {
5258
+ const qualification = createRuntime(transport, inspection.layer, selected[0].header);
5259
+ let spatialContext;
5260
+ try {
5261
+ await qualification.tilesetInitializationPromise;
5262
+ const metadata = await createI3SMeshTilesetConversionSource(qualification).inspect(signal);
5263
+ spatialContext = (0, import_core38.createI3SConversionSpatialContext)(metadata.spatialReference, {
5264
+ targetCrs: "EPSG:4978"
5265
+ });
5266
+ } finally {
5267
+ qualification.destroy();
5268
+ }
5269
+ const source = {
5270
+ /** Reuses only immutable inspected declarations. */
5271
+ inspect: async () => inspection,
5272
+ /** Releases each dedicated node runtime before moving on or closing early. */
5273
+ async *read() {
5274
+ for (const descriptor of selected) {
5275
+ const runtime = createRuntime(transport, inspection.layer, descriptor.header);
5276
+ try {
5277
+ await runtime.tilesetInitializationPromise;
5278
+ const meshSource = createI3SMeshTilesetConversionSource(runtime, {
5279
+ unloadContent: true,
5280
+ features,
5281
+ readExternalResource: async (uri) => new Uint8Array(
5282
+ await (await transport.fetcher(runtime.source.getTileUrl(uri))).arrayBuffer()
5283
+ )
5284
+ });
5285
+ const metadata = await meshSource.inspect(signal);
5286
+ for await (const resource of meshSource.read(metadata, signal)) {
5287
+ decodedBytes += measureMeshBytes(resource);
5288
+ if (++meshCount > SELECTED_MESH_LIMITS.maxMeshResources || decodedBytes > (options.maxInputBytes ?? SELECTED_MESH_LIMITS.maxInputBytes))
5289
+ throw new import_core38.TileConversionError(
5290
+ "INPUT_RESOURCE_TOO_LARGE",
5291
+ "Selected I3S decoded resources exceed the input/mesh budget"
5292
+ );
5293
+ yield { ...resource, id: descriptor.resourceId };
5294
+ }
5295
+ } finally {
5296
+ runtime.destroy();
5297
+ }
5298
+ }
5299
+ }
5300
+ };
5301
+ return { source, spatialContext };
5302
+ }
5303
+ }
5304
+ function validateSelectionOptions(options) {
5305
+ for (const [value, maximum] of [
5306
+ [
5307
+ options.maxInputBytes ?? SELECTED_MESH_LIMITS.maxInputBytes,
5308
+ SELECTED_MESH_LIMITS.maxInputBytes
5309
+ ],
5310
+ [
5311
+ options.maxInputResources ?? SELECTED_MESH_LIMITS.maxInputResources,
5312
+ SELECTED_MESH_LIMITS.maxInputResources
5313
+ ]
5314
+ ])
5315
+ if (!Number.isSafeInteger(value) || value < 1 || value > maximum)
5316
+ throw new import_core38.TileConversionError(
5317
+ "INVALID_I3S_SELECTION_LIMIT",
5318
+ "I3S selection limits must be positive safe integers within the bounded profile"
5319
+ );
5320
+ }