@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
@@ -1,5 +1,5 @@
1
1
  // src/v5/feature-arrow.ts
2
- import { ArrowTableBuilder } from "@loaders.gl/schema-utils";
2
+ import { ArrowTableBuilder } from "@loaders.gl/core/schema-utils";
3
3
  import { TileConversionError } from "@loaders.gl/tile-converter/v5/core";
4
4
  var DEFAULT_FEATURE_ID_FIELD = "feature_id";
5
5
  var FEATURE_CLASS_METADATA_KEY = "loaders.gl:feature-class";
@@ -321,8 +321,8 @@ function isBinaryType(dataType) {
321
321
  }
322
322
 
323
323
  // src/v5/point-cloud.ts
324
- import { Tile3DWriter, TILE3D_TYPE } from "@loaders.gl/3d-tiles";
325
- import { convertTableToMesh } from "@loaders.gl/schema-utils";
324
+ import { Tile3DWriter, TILE3D_TYPE } from "@loaders.gl/tiles-3d/3d-tiles";
325
+ import { convertTableToMesh } from "@loaders.gl/core/schema-utils";
326
326
  import { TileConversionError as TileConversionError2 } from "@loaders.gl/tile-converter/v5/core";
327
327
  function encodePointCloudTile(pointBatch, options = {}) {
328
328
  return encodePointCloudTileWithMetadata(pointBatch, options).pnts;
@@ -585,8 +585,8 @@ function validateBatchTable(batchTableJson, batchIds) {
585
585
 
586
586
  // src/v5/point-cloud-spatial.ts
587
587
  import { Matrix4 } from "@math.gl/core";
588
- import { getSpatialCoordinateFrame, createTilesetSpatialReference } from "@loaders.gl/tiles";
589
- import { convertTableToMesh as convertTableToMesh2, makeMeshArrowTable } from "@loaders.gl/schema-utils";
588
+ import { getSpatialCoordinateFrame, createTilesetSpatialReference } from "@loaders.gl/tiles-3d";
589
+ import { convertTableToMesh as convertTableToMesh2, makeMeshArrowTable } from "@loaders.gl/core/schema-utils";
590
590
  import { TileConversionError as TileConversionError3 } from "@loaders.gl/tile-converter/v5/core";
591
591
  async function transformPointCloudSourceTile(sourceTile, options) {
592
592
  options.signal?.throwIfAborted();
@@ -844,7 +844,7 @@ import { GLTFScenegraph, GLTFWriter } from "@loaders.gl/gltf";
844
844
  import { getBinaryImageMetadata } from "@loaders.gl/images";
845
845
 
846
846
  // src/v5/mesh-features.ts
847
- import { convertArrowToSchema } from "@loaders.gl/schema-utils";
847
+ import { convertArrowToSchema } from "@loaders.gl/core/schema-utils";
848
848
  import { TileConversionError as TileConversionError5 } from "@loaders.gl/tile-converter/v5/core";
849
849
  function prepareMeshFeatureGeometry(mesh, features) {
850
850
  if (!features) return mesh;
@@ -1398,7 +1398,7 @@ function validateMeshTextureTransform(transform) {
1398
1398
  }
1399
1399
 
1400
1400
  // src/v5/mesh-conversion.ts
1401
- import { getSpatialCoordinateFrame as getSpatialCoordinateFrame2 } from "@loaders.gl/tiles";
1401
+ import { getSpatialCoordinateFrame as getSpatialCoordinateFrame2 } from "@loaders.gl/tiles-3d";
1402
1402
  import { TileConversionError as TileConversionError8 } from "@loaders.gl/tile-converter/v5/core";
1403
1403
 
1404
1404
  // src/v5/mesh-draco.ts
@@ -1630,6 +1630,8 @@ function createMeshConversionCodec(options) {
1630
1630
  }
1631
1631
  yield {
1632
1632
  id: resource.id,
1633
+ contentResourceId: resource.contentResourceId,
1634
+ contentPrimitive: resource.contentPrimitive,
1633
1635
  glb: encoded.glb,
1634
1636
  origin,
1635
1637
  boundingBox: [minimum, maximum],
@@ -1886,8 +1888,8 @@ function createMeshTilesetSink(options) {
1886
1888
  import {
1887
1889
  Tiles3DArchiveWriter,
1888
1890
  encodeTiles3DArchiveInBatches
1889
- } from "@loaders.gl/3d-tiles/3d-tiles-archive-writer";
1890
- import { SLPKWriter, encodeSLPKArchiveInBatches } from "@loaders.gl/i3s/i3s-slpk-writer";
1891
+ } from "@loaders.gl/tiles-3d/3d-tiles/3d-tiles-archive-writer";
1892
+ import { SLPKWriter, encodeSLPKArchiveInBatches } from "@loaders.gl/tiles-3d/i3s/i3s-slpk-writer";
1891
1893
  import { TileConversionError as TileConversionError10 } from "@loaders.gl/tile-converter/v5/core";
1892
1894
  async function createTileConversionArchive(files, options) {
1893
1895
  const { resources, format, maxArchiveBytes, signal } = prepareArchiveInput(files, options);
@@ -1922,7 +1924,7 @@ function prepareArchiveInput(files, options) {
1922
1924
  }
1923
1925
 
1924
1926
  // src/v5/mesh-tileset-archive.ts
1925
- import { Tiles3DArchiveWriter as Tiles3DArchiveWriter2 } from "@loaders.gl/3d-tiles/3d-tiles-archive-writer";
1927
+ import { Tiles3DArchiveWriter as Tiles3DArchiveWriter2 } from "@loaders.gl/tiles-3d/3d-tiles/3d-tiles-archive-writer";
1926
1928
  import { TileConversionError as TileConversionError11 } from "@loaders.gl/tile-converter/v5/core";
1927
1929
  async function createSingleMeshTilesetArchive(files, options) {
1928
1930
  const { maxArchiveBytes, signal } = options;
@@ -1945,16 +1947,18 @@ async function createSingleMeshTilesetArchive(files, options) {
1945
1947
 
1946
1948
  // src/v5/mesh-source.ts
1947
1949
  import { Matrix4 as Matrix42 } from "@math.gl/core";
1948
- import { TileConversionError as TileConversionError14 } from "@loaders.gl/tile-converter/v5/core";
1950
+ import { getGLTFAccessor as getGLTFAccessor2, getGLTFAccessorValue as getGLTFAccessorValue2 } from "@loaders.gl/gltf";
1951
+ import { TileConversionError as TileConversionError15 } from "@loaders.gl/tile-converter/v5/core";
1949
1952
  import { createTilesetConversionSource } from "@loaders.gl/tile-converter/v5/core";
1950
1953
 
1951
1954
  // src/v5/mesh-source-texture.ts
1952
1955
  import { getBinaryImageMetadata as getBinaryImageMetadata2 } from "@loaders.gl/images";
1956
+ import { GLTFScenegraph as GLTFScenegraph2 } from "@loaders.gl/gltf";
1953
1957
  import { TileConversionError as TileConversionError12 } from "@loaders.gl/tile-converter/v5/core";
1954
- function mapMeshSourceTexture(textureInfo) {
1955
- const texture = textureInfo?.texture;
1956
- const image = texture?.source;
1957
- const sampler = texture?.sampler;
1958
+ function mapMeshSourceTexture(gltf, textureInfo) {
1959
+ const texture = textureInfo ? gltf.json.textures?.[textureInfo.index] : void 0;
1960
+ const image = texture?.source === void 0 ? void 0 : gltf.json.images?.[texture.source];
1961
+ const sampler = texture?.sampler === void 0 ? void 0 : gltf.json.samplers?.[texture.sampler];
1958
1962
  const extensions = textureInfo?.extensions || {};
1959
1963
  const hasTransform = Object.hasOwn(extensions, "KHR_texture_transform");
1960
1964
  const transform = extensions.KHR_texture_transform;
@@ -1964,7 +1968,7 @@ function mapMeshSourceTexture(textureInfo) {
1964
1968
  (name) => !["id", "index", "texture", "texCoord", "extensions", "extras"].includes(name)
1965
1969
  ) || !texture || Object.keys(texture).some(
1966
1970
  (name) => !["id", "source", "sampler", "name", "extensions", "extras"].includes(name)
1967
- ) || 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(
1971
+ ) || 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(
1968
1972
  (name) => ![
1969
1973
  "id",
1970
1974
  "parameters",
@@ -1989,7 +1993,7 @@ function mapMeshSourceTexture(textureInfo) {
1989
1993
  minFilter: sampler.minFilter
1990
1994
  } : void 0;
1991
1995
  return {
1992
- ...mapMeshSourceImage(image),
1996
+ ...mapMeshSourceImage(gltf, image),
1993
1997
  ...hasTransform ? {
1994
1998
  transform: {
1995
1999
  offset: transform.offset,
@@ -2000,9 +2004,12 @@ function mapMeshSourceTexture(textureInfo) {
2000
2004
  ...selectedSampler && Object.values(selectedSampler).some((value) => value !== void 0) ? { sampler: selectedSampler } : {}
2001
2005
  };
2002
2006
  }
2003
- function mapMeshSourceImage(image) {
2007
+ function mapMeshSourceImage(gltf, image) {
2004
2008
  if (image.uri === void 0) {
2005
- return { data: image.bufferView.data, mimeType: image.mimeType };
2009
+ return {
2010
+ data: new GLTFScenegraph2(gltf).getTypedArrayForBufferView(image.bufferView),
2011
+ mimeType: image.mimeType
2012
+ };
2006
2013
  }
2007
2014
  const match = typeof image.uri === "string" ? /^data:(image\/(?:png|jpeg));base64,([A-Za-z0-9+/]+={0,2})$/.exec(image.uri) : null;
2008
2015
  if (!match || image.mimeType !== void 0 && image.mimeType !== match[1]) {
@@ -2022,10 +2029,11 @@ function mapMeshSourceImage(image) {
2022
2029
  mimeType: match[1]
2023
2030
  };
2024
2031
  }
2025
- async function resolveMeshSourceTexture(textureInfo, readExternalResource, contentUri, signal) {
2026
- const image = textureInfo?.texture?.source;
2032
+ async function resolveMeshSourceTexture(gltf, textureInfo, readExternalResource, contentUri, signal) {
2033
+ const texture = textureInfo ? gltf.json.textures?.[textureInfo.index] : void 0;
2034
+ const image = texture?.source === void 0 ? void 0 : gltf.json.images?.[texture.source];
2027
2035
  if (!image || image.uri === void 0 || image.uri.startsWith("data:"))
2028
- return mapMeshSourceTexture(textureInfo);
2036
+ return mapMeshSourceTexture(gltf, textureInfo);
2029
2037
  if (!readExternalResource || image.bufferView !== void 0)
2030
2038
  throw new TileConversionError12(
2031
2039
  "MESH_SOURCE_TEXTURE_UNSUPPORTED",
@@ -2044,168 +2052,336 @@ async function resolveMeshSourceTexture(textureInfo, readExternalResource, conte
2044
2052
  "MESH_SOURCE_TEXTURE_UNSUPPORTED",
2045
2053
  "External images require matching PNG/JPEG headers"
2046
2054
  );
2055
+ const resolvedImage = {
2056
+ ...image,
2057
+ uri: void 0,
2058
+ mimeType: metadata.mimeType,
2059
+ bufferView: (gltf.json.bufferViews || []).length
2060
+ };
2047
2061
  const resolved = {
2048
- ...textureInfo,
2049
- texture: {
2050
- ...textureInfo.texture,
2051
- source: { ...image, uri: void 0, mimeType: metadata.mimeType, bufferView: { data } }
2062
+ ...gltf,
2063
+ buffers: [
2064
+ ...gltf.buffers,
2065
+ {
2066
+ arrayBuffer: data.buffer,
2067
+ byteOffset: data.byteOffset,
2068
+ byteLength: data.byteLength
2069
+ }
2070
+ ],
2071
+ json: {
2072
+ ...gltf.json,
2073
+ images: gltf.json.images.map(
2074
+ (entry, index) => index === texture.source ? resolvedImage : entry
2075
+ ),
2076
+ bufferViews: [
2077
+ ...gltf.json.bufferViews || [],
2078
+ { buffer: gltf.buffers.length, byteLength: data.byteLength }
2079
+ ]
2052
2080
  }
2053
2081
  };
2054
- return mapMeshSourceTexture(resolved);
2082
+ return mapMeshSourceTexture(resolved, textureInfo);
2055
2083
  }
2056
2084
 
2057
2085
  // src/v5/mesh-source-features.ts
2086
+ import { getGLTFAccessor, getGLTFAccessorValue } from "@loaders.gl/gltf";
2087
+
2088
+ // src/v5/mesh-source-schema.ts
2058
2089
  import { TileConversionError as TileConversionError13 } from "@loaders.gl/tile-converter/v5/core";
2090
+ var SCALAR_TYPES = {
2091
+ INT8: "int8",
2092
+ UINT8: "uint8",
2093
+ INT16: "int16",
2094
+ UINT16: "uint16",
2095
+ INT32: "int32",
2096
+ UINT32: "uint32",
2097
+ INT64: "int64",
2098
+ UINT64: "uint64",
2099
+ FLOAT32: "float32",
2100
+ FLOAT64: "float64"
2101
+ };
2102
+ function createMeshSourceFeatureOptions(gltf, options) {
2103
+ const metadata = gltf.extensions?.EXT_structural_metadata;
2104
+ const table = metadata?.propertyTables?.[0];
2105
+ const properties = metadata?.schema?.classes?.[options.metadataClass]?.properties;
2106
+ 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) {
2107
+ throw new TileConversionError13(
2108
+ "MESH_FEATURE_SCHEMA_REQUIRED",
2109
+ "Schema mapping requires one inline class, one matching property table and a positive uint32 row count"
2110
+ );
2111
+ }
2112
+ if (Object.keys(table.properties || {}).some((name) => !Object.hasOwn(properties, name))) {
2113
+ throw new TileConversionError13(
2114
+ "MESH_FEATURE_PROPERTY_UNMAPPED",
2115
+ "Property table columns must be declared in the selected class"
2116
+ );
2117
+ }
2118
+ const fields = Object.entries(properties).map(([name, property]) => {
2119
+ const declaration = property;
2120
+ const column = table.properties?.[name];
2121
+ 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)) {
2122
+ throw new TileConversionError13(
2123
+ "MESH_FEATURE_PROPERTY_UNSUPPORTED",
2124
+ `Property ${name} requires an explicit separately qualified mapping`
2125
+ );
2126
+ }
2127
+ const type = declaration.type === "STRING" ? "utf8" : Object.hasOwn(SCALAR_TYPES, declaration.componentType || "") ? SCALAR_TYPES[declaration.componentType] : void 0;
2128
+ if (!type) {
2129
+ throw new TileConversionError13(
2130
+ "MESH_FEATURE_PROPERTY_UNSUPPORTED",
2131
+ `Property ${name} has no supported declared scalar width`
2132
+ );
2133
+ }
2134
+ return {
2135
+ name,
2136
+ type,
2137
+ nullable: declaration.required !== true,
2138
+ metadata: { "loaders.gl:structural-property": JSON.stringify(declaration) }
2139
+ };
2140
+ });
2141
+ const identifierName = options.featureIdField || "feature_id";
2142
+ const sourceIdentifier = options.sourceFeatureIdProperty;
2143
+ const sourceField = sourceIdentifier ? fields.find((field) => field.name === sourceIdentifier) : void 0;
2144
+ if (sourceIdentifier && !sourceField || fields.some((field) => field.name === identifierName && field !== sourceField) || sourceField && !["utf8", "int8", "uint8", "int16", "uint16", "int32", "uint32", "int64", "uint64"].includes(
2145
+ String(sourceField.type)
2146
+ )) {
2147
+ throw new TileConversionError13(
2148
+ "MESH_FEATURE_ID_PROPERTY_REQUIRED",
2149
+ "Select a declared string/integer identifier and a distinct output identifier name"
2150
+ );
2151
+ }
2152
+ return {
2153
+ ...options,
2154
+ schema: {
2155
+ fields: [
2156
+ {
2157
+ ...sourceField,
2158
+ name: identifierName,
2159
+ type: sourceField?.type || "uint32",
2160
+ nullable: false
2161
+ },
2162
+ ...fields.filter((field) => field !== sourceField)
2163
+ ],
2164
+ metadata: { "loaders.gl:source-schema": JSON.stringify(metadata.schema) }
2165
+ }
2166
+ };
2167
+ }
2168
+
2169
+ // src/v5/mesh-source-features.ts
2170
+ import { TileConversionError as TileConversionError14 } from "@loaders.gl/tile-converter/v5/core";
2059
2171
  function extractMeshFeatures(gltf, primitive, payload, mesh, options) {
2060
2172
  const declarations = primitive.extensions?.EXT_mesh_features?.featureIds;
2061
- const metadata = gltf.extensions?.EXT_structural_metadata;
2173
+ const metadata = gltf.json.extensions?.EXT_structural_metadata;
2062
2174
  const legacy = primitive.attributes._BATCHID;
2063
- if (!declarations && !legacy && !metadata && !Object.keys(payload.batchTableJson || {}).length && !payload.header?.batchLength) {
2175
+ if (!declarations && legacy === void 0 && !metadata && !Object.keys(payload.batchTableJson || {}).length && !payload.header?.batchLength) {
2064
2176
  if (Object.keys(primitive.attributes).some((name) => /^_FEATURE_ID_\d+$/.test(name)))
2065
- throw new TileConversionError13(
2177
+ throw new TileConversionError14(
2066
2178
  "MESH_FEATURE_MAPPING_REQUIRED",
2067
2179
  "Feature attributes require an EXT_mesh_features declaration"
2068
2180
  );
2069
2181
  return void 0;
2070
2182
  }
2071
2183
  if (!options)
2072
- throw new TileConversionError13(
2184
+ throw new TileConversionError14(
2073
2185
  "MESH_FEATURE_SCHEMA_REQUIRED",
2074
- "Feature extraction requires an explicit Arrow schema and class mapping"
2186
+ "Feature extraction requires a class selection and a declared Arrow or inline source schema"
2075
2187
  );
2076
2188
  if (!options.metadataClass)
2077
- throw new TileConversionError13(
2189
+ throw new TileConversionError14(
2078
2190
  "MESH_FEATURE_CLASS_REQUIRED",
2079
2191
  "Feature metadata class must be explicit"
2080
2192
  );
2193
+ const mapping = "schema" in options ? options : createMeshSourceFeatureOptions(gltf.json, options);
2194
+ const propertyMappings = /* @__PURE__ */ new Map();
2081
2195
  let count;
2082
- let attribute = legacy;
2196
+ let attributeReference = legacy;
2083
2197
  let properties;
2084
2198
  if (declarations) {
2085
- if (legacy || declarations.length !== 1 || metadata?.propertyTables?.length !== 1 || metadata.propertyTextures?.length || metadata.propertyAttributes?.length)
2086
- throw new TileConversionError13(
2199
+ if (legacy !== void 0 || declarations.length !== 1 || metadata?.propertyTables?.length !== 1 || metadata.propertyTextures?.length || metadata.propertyAttributes?.length)
2200
+ throw new TileConversionError14(
2087
2201
  "MESH_FEATURE_MAPPING_UNSUPPORTED",
2088
2202
  "Only one attribute-backed feature set and one property table are supported"
2089
2203
  );
2090
2204
  const declaration = declarations[0];
2091
- if (!Number.isInteger(declaration.attribute) || declaration.attribute < 0 || declaration.propertyTable !== 0 || declaration.texture || declaration.implicit || declaration.nullFeatureId !== void 0)
2092
- throw new TileConversionError13(
2205
+ if (typeof declaration.attribute !== "number" || !Number.isInteger(declaration.attribute) || declaration.attribute < 0 || declaration.propertyTable !== 0 || declaration.texture || declaration.implicit || declaration.nullFeatureId !== void 0)
2206
+ throw new TileConversionError14(
2093
2207
  "MESH_FEATURE_MAPPING_UNSUPPORTED",
2094
2208
  "Features require an attribute and property table 0 without null/texture/implicit IDs"
2095
2209
  );
2096
- attribute = primitive.attributes[`_FEATURE_ID_${declaration.attribute}`];
2210
+ attributeReference = primitive.attributes[`_FEATURE_ID_${declaration.attribute}`];
2097
2211
  const table = metadata.propertyTables[0];
2098
- if (table.class !== options.metadataClass || table.count !== declaration.featureCount)
2099
- throw new TileConversionError13(
2212
+ if (table.class !== options.metadataClass || !Number.isSafeInteger(declaration.featureCount) || declaration.featureCount <= 0 || declaration.featureCount > table.count)
2213
+ throw new TileConversionError14(
2100
2214
  "MESH_FEATURE_CLASS_MISMATCH",
2101
- "Feature class and declared table counts must agree"
2215
+ "Feature class must agree and declared feature count must fit the property table"
2102
2216
  );
2103
2217
  count = table.count;
2104
- properties = {};
2218
+ if (!Number.isSafeInteger(count) || count <= 0 || count > 4294967295) {
2219
+ throw new TileConversionError14(
2220
+ "MESH_FEATURE_IDS_INVALID",
2221
+ "Feature row count must fit uint32 indices"
2222
+ );
2223
+ }
2224
+ properties = /* @__PURE__ */ Object.create(null);
2105
2225
  const classProperties = metadata.schema?.classes?.[table.class]?.properties;
2106
2226
  if (!classProperties)
2107
- throw new TileConversionError13(
2227
+ throw new TileConversionError14(
2108
2228
  "MESH_FEATURE_SCHEMA_REQUIRED",
2109
2229
  "A decoded inline structural metadata class is required"
2110
2230
  );
2111
2231
  if (Object.keys(table.properties || {}).some((name) => !Object.hasOwn(classProperties, name)))
2112
- throw new TileConversionError13(
2232
+ throw new TileConversionError14(
2113
2233
  "MESH_FEATURE_PROPERTY_UNMAPPED",
2114
2234
  "Property table columns must be declared in the metadata class"
2115
2235
  );
2116
2236
  for (const [name, property] of Object.entries(classProperties)) {
2117
2237
  const column = table.properties?.[name];
2118
2238
  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))
2119
- throw new TileConversionError13(
2239
+ throw new TileConversionError14(
2120
2240
  "MESH_FEATURE_PROPERTY_UNSUPPORTED",
2121
2241
  `Property ${name} requires a separately qualified semantic mapping`
2122
2242
  );
2123
2243
  const data = column?.data;
2124
- if (data === void 0 && property.default === void 0)
2125
- throw new TileConversionError13(
2244
+ if (column !== void 0 && (data === void 0 || data === null) || data === void 0 && property.required === true)
2245
+ throw new TileConversionError14(
2126
2246
  "MESH_FEATURE_PROPERTY_UNAVAILABLE",
2127
2247
  `Property ${name} has not been decoded`
2128
2248
  );
2129
- const decoded = data ?? Array.from({ length: count }, () => property.default);
2249
+ const integer64 = ["INT64", "UINT64"].includes(property.componentType || "");
2250
+ if (integer64 && property.default !== void 0 && (typeof property.default !== "number" || !Number.isSafeInteger(property.default))) {
2251
+ throw new TileConversionError14(
2252
+ "MESH_FEATURE_PROPERTY_UNSUPPORTED",
2253
+ `Property ${name} requires an exact 64-bit default`
2254
+ );
2255
+ }
2256
+ const defaultValue = integer64 && property.default !== void 0 ? BigInt(property.default) : property.default ?? null;
2257
+ const decoded = data;
2130
2258
  if (property.noData === void 0 || data === void 0) {
2131
- properties[name] = decoded;
2259
+ if (decoded !== void 0) properties[name] = decoded;
2260
+ else propertyMappings.set(name, { defaultValue });
2132
2261
  continue;
2133
2262
  }
2134
2263
  const sentinel = property.noData;
2135
- const integer64 = ["INT64", "UINT64"].includes(property.componentType || "");
2136
2264
  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)
2137
- throw new TileConversionError13(
2265
+ throw new TileConversionError14(
2138
2266
  "MESH_FEATURE_PROPERTY_UNSUPPORTED",
2139
2267
  `Property ${name} requires a valid decoded noData mapping`
2140
2268
  );
2141
2269
  const comparable = integer64 ? BigInt(sentinel) : sentinel;
2142
- properties[name] = Array.from(
2143
- decoded,
2144
- (value) => value === comparable ? property.default ?? null : value
2145
- );
2270
+ const values = decoded;
2271
+ properties[name] = values;
2272
+ propertyMappings.set(name, { noData: comparable, defaultValue });
2146
2273
  }
2147
2274
  } else {
2148
2275
  if (metadata)
2149
- throw new TileConversionError13(
2276
+ throw new TileConversionError14(
2150
2277
  "MESH_FEATURE_MAPPING_UNSUPPORTED",
2151
2278
  "Structural metadata requires an attribute-backed feature declaration"
2152
2279
  );
2153
2280
  count = payload.header?.batchLength ?? Number(payload.featureTableJson?.BATCH_LENGTH);
2154
2281
  properties = payload.batchTableJson || {};
2155
2282
  }
2283
+ const accessor = attributeReference === void 0 ? void 0 : getGLTFAccessor(gltf, attributeReference);
2284
+ const attribute = accessor ? {
2285
+ ...accessor,
2286
+ components: accessor.type === "SCALAR" ? 1 : Number(accessor.type.slice(3)),
2287
+ value: getGLTFAccessorValue(gltf, accessor)
2288
+ } : void 0;
2156
2289
  const vertexCount = mesh.attributes.POSITION.value.length / 3;
2157
- if (!Number.isSafeInteger(count) || count <= 0 || !attribute || attribute.components !== 1 || attribute.normalized || attribute.value.length !== vertexCount || Array.from(attribute.value).some(
2158
- (value) => !Number.isSafeInteger(value) || value < 0 || value >= count
2159
- ))
2160
- throw new TileConversionError13(
2290
+ if (!Number.isSafeInteger(count) || count <= 0 || !attribute || attribute.components !== 1 || attribute.normalized || attribute.value.length !== vertexCount)
2291
+ throw new TileConversionError14(
2161
2292
  "MESH_FEATURE_IDS_INVALID",
2162
2293
  "Feature IDs must be one valid property-table row index per vertex"
2163
2294
  );
2295
+ const vertexRows = declarations ? /* @__PURE__ */ new Set() : void 0;
2296
+ for (const value of attribute.value) {
2297
+ if (!Number.isSafeInteger(value) || value < 0 || value >= count)
2298
+ throw new TileConversionError14(
2299
+ "MESH_FEATURE_IDS_INVALID",
2300
+ "Feature IDs must reference valid property-table rows"
2301
+ );
2302
+ vertexRows?.add(Number(value));
2303
+ }
2304
+ if (declarations && vertexRows.size !== declarations[0].featureCount)
2305
+ throw new TileConversionError14(
2306
+ "MESH_FEATURE_IDS_INVALID",
2307
+ "Declared feature count must match distinct vertex feature IDs"
2308
+ );
2164
2309
  const selectedAttribute = declarations ? `_FEATURE_ID_${declarations[0].attribute}` : "_BATCHID";
2165
2310
  if (Object.keys(primitive.attributes).some(
2166
2311
  (name) => (name === "_BATCHID" || /^_FEATURE_ID_\d+$/.test(name)) && name !== selectedAttribute
2167
2312
  ))
2168
- throw new TileConversionError13(
2313
+ throw new TileConversionError14(
2169
2314
  "MESH_FEATURE_MAPPING_UNSUPPORTED",
2170
2315
  "Additional feature ID sets require separate mappings"
2171
2316
  );
2172
2317
  for (const [name, values] of Object.entries(properties)) {
2173
2318
  if (!values || !Array.isArray(values) && !ArrayBuffer.isView(values) || values.length !== count)
2174
- throw new TileConversionError13(
2319
+ throw new TileConversionError14(
2175
2320
  "MESH_FEATURE_PROPERTY_UNAVAILABLE",
2176
- `Property ${name} must be a decoded column matching the table count`
2321
+ `Property ${name} must be a decoded column matching the feature row count`
2177
2322
  );
2178
2323
  }
2179
2324
  const sourceIdentifier = options.sourceFeatureIdProperty;
2180
- if (sourceIdentifier && !properties[sourceIdentifier])
2181
- throw new TileConversionError13(
2325
+ if (sourceIdentifier && !properties[sourceIdentifier] && !propertyMappings.has(sourceIdentifier))
2326
+ throw new TileConversionError14(
2182
2327
  "MESH_FEATURE_ID_PROPERTY_REQUIRED",
2183
2328
  "Stable source identifier property is missing"
2184
2329
  );
2185
- const batches = convertFeatureAttributesToArrowBatches(
2186
- Array.from({ length: count }, (_, index) => ({
2187
- featureId: sourceIdentifier ? properties[sourceIdentifier][index] : index,
2188
- metadataClass: options.metadataClass,
2189
- properties: Object.fromEntries(
2190
- Object.entries(properties).filter(([name]) => name !== sourceIdentifier).map(([name, values]) => [name, values[index]])
2191
- )
2192
- })),
2193
- options
2194
- );
2195
2330
  const indices = mesh.indices?.value;
2196
- const triangleFeatureIndices = new Uint32Array((indices?.length ?? vertexCount) / 3);
2197
- for (let triangle = 0; triangle < triangleFeatureIndices.length; triangle++) {
2198
- const corners = [0, 1, 2].map(
2199
- (corner) => indices?.[triangle * 3 + corner] ?? triangle * 3 + corner
2331
+ const cornerCount = indices?.length ?? vertexCount;
2332
+ if (cornerCount % 3)
2333
+ throw new TileConversionError14(
2334
+ "MESH_FEATURE_IDS_INVALID",
2335
+ "Triangle geometry requires complete triples"
2200
2336
  );
2201
- const rows = corners.map((corner) => attribute.value[corner]);
2202
- if (rows.some((row) => row !== rows[0]) && new Set(corners).size === 3)
2203
- throw new TileConversionError13(
2337
+ const triangleFeatureIndices = new Uint32Array(cornerCount / 3);
2338
+ const referencedRows = /* @__PURE__ */ new Set();
2339
+ const readFeatureRow = (corner) => {
2340
+ if (!Number.isSafeInteger(corner) || corner < 0 || corner >= vertexCount)
2341
+ throw new TileConversionError14(
2342
+ "MESH_FEATURE_IDS_INVALID",
2343
+ "Triangle indices must reference valid mesh vertices"
2344
+ );
2345
+ return Number(attribute.value[corner]);
2346
+ };
2347
+ for (let triangle = 0; triangle < triangleFeatureIndices.length; triangle++) {
2348
+ const firstCorner = indices?.[triangle * 3] ?? triangle * 3;
2349
+ const secondCorner = indices?.[triangle * 3 + 1] ?? triangle * 3 + 1;
2350
+ const thirdCorner = indices?.[triangle * 3 + 2] ?? triangle * 3 + 2;
2351
+ const firstRow = readFeatureRow(firstCorner);
2352
+ const secondRow = readFeatureRow(secondCorner);
2353
+ const thirdRow = readFeatureRow(thirdCorner);
2354
+ if ((firstRow !== secondRow || firstRow !== thirdRow) && firstCorner !== secondCorner && firstCorner !== thirdCorner && secondCorner !== thirdCorner)
2355
+ throw new TileConversionError14(
2204
2356
  "MESH_FEATURE_TRIANGLE_MIXED",
2205
2357
  "All vertices of a nondegenerate triangle must reference the same feature row"
2206
2358
  );
2207
- triangleFeatureIndices[triangle] = Number(rows[0]);
2359
+ triangleFeatureIndices[triangle] = firstRow;
2360
+ referencedRows.add(firstRow);
2208
2361
  }
2362
+ const selectedRows = [...referencedRows].sort((left, right) => left - right);
2363
+ const denseRows = new Map(selectedRows.map((row, index) => [row, index]));
2364
+ for (let triangle = 0; triangle < triangleFeatureIndices.length; triangle++) {
2365
+ triangleFeatureIndices[triangle] = denseRows.get(triangleFeatureIndices[triangle]);
2366
+ }
2367
+ const readProperty = (name, row) => {
2368
+ const values = properties[name];
2369
+ const propertyMapping = propertyMappings.get(name);
2370
+ if (!values) return propertyMapping?.defaultValue;
2371
+ const value = values[row];
2372
+ return propertyMapping && value === propertyMapping.noData ? propertyMapping.defaultValue : value;
2373
+ };
2374
+ const propertyNames = [
2375
+ .../* @__PURE__ */ new Set([...Object.keys(properties), ...propertyMappings.keys()])
2376
+ ].filter((name) => name !== sourceIdentifier);
2377
+ const batches = convertFeatureAttributesToArrowBatches(
2378
+ selectedRows.map((row) => ({
2379
+ featureId: sourceIdentifier ? readProperty(sourceIdentifier, row) : row,
2380
+ metadataClass: options.metadataClass,
2381
+ properties: Object.fromEntries(propertyNames.map((name) => [name, readProperty(name, row)]))
2382
+ })),
2383
+ mapping
2384
+ );
2209
2385
  return {
2210
2386
  batches,
2211
2387
  triangleFeatureIndices,
@@ -2223,13 +2399,13 @@ function createMeshTilesetConversionSource(tileset, options = {}) {
2223
2399
  const metadata = await source.inspect(signal);
2224
2400
  const reference = metadata.spatialReference;
2225
2401
  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")) {
2226
- throw new TileConversionError14(
2402
+ throw new TileConversionError15(
2227
2403
  "MESH_SOURCE_FRAME_UNSUPPORTED",
2228
2404
  "Mesh extraction requires native EPSG:4978 xyz coordinates in meters and ellipsoidal heights"
2229
2405
  );
2230
2406
  }
2231
2407
  if (metadata.tileset.metadata || metadata.tileset.groups?.length) {
2232
- throw new TileConversionError14(
2408
+ throw new TileConversionError15(
2233
2409
  "MESH_SOURCE_METADATA_UNSUPPORTED",
2234
2410
  "Tileset/group metadata requires a separate mapping"
2235
2411
  );
@@ -2241,46 +2417,47 @@ function createMeshTilesetConversionSource(tileset, options = {}) {
2241
2417
  for await (const item of source.read(inspection, signal)) {
2242
2418
  const placementReference = item.tile.header._spatialReference || tileset.spatialReference;
2243
2419
  if (placementReference && (placementReference.status !== "native" || placementReference.sourceCrs !== "EPSG:4978"))
2244
- throw new TileConversionError14(
2420
+ throw new TileConversionError15(
2245
2421
  "MESH_SOURCE_FRAME_UNSUPPORTED",
2246
2422
  "Nested mesh placements require native ECEF coordinates"
2247
2423
  );
2248
2424
  if (item.tile.header.metadata || item.tile.header.implicitMetadata)
2249
- throw new TileConversionError14(
2425
+ throw new TileConversionError15(
2250
2426
  "MESH_SOURCE_METADATA_UNSUPPORTED",
2251
2427
  "Tile metadata requires a separate mapping"
2252
2428
  );
2253
2429
  for (const content of item.contents) {
2254
2430
  signal?.throwIfAborted();
2255
2431
  if (content.metadata || content.group !== void 0)
2256
- throw new TileConversionError14(
2432
+ throw new TileConversionError15(
2257
2433
  "MESH_SOURCE_METADATA_UNSUPPORTED",
2258
2434
  "Content metadata requires a separate mapping"
2259
2435
  );
2260
2436
  const payload = content.payload;
2261
2437
  if (!payload?.gltf || !["glTF", "gltf", "b3dm"].includes(payload.type || content.type || "")) {
2262
- throw new TileConversionError14(
2438
+ throw new TileConversionError15(
2263
2439
  "MESH_SOURCE_CONTENT_UNSUPPORTED",
2264
2440
  "Mesh extraction requires decoded GLB/glTF or B3DM content"
2265
2441
  );
2266
2442
  }
2267
- const gltf = payload.gltf;
2443
+ const gltfWithBuffers = payload.gltf;
2444
+ const gltf = gltfWithBuffers.json;
2268
2445
  if (gltf.animations?.length || gltf.skins?.length || Object.keys(gltf.extensions || {}).some(
2269
2446
  (name) => !["EXT_structural_metadata", "CESIUM_RTC"].includes(name)
2270
2447
  )) {
2271
- throw new TileConversionError14(
2448
+ throw new TileConversionError15(
2272
2449
  "MESH_SOURCE_SCENE_UNSUPPORTED",
2273
2450
  "Animated/skinned scenes and unmapped scene extensions are unsupported"
2274
2451
  );
2275
2452
  }
2276
2453
  const axis = payload.gltfUpAxis || "Y";
2277
2454
  if (!["X", "Y", "Z"].includes(axis))
2278
- throw new TileConversionError14("MESH_SOURCE_AXIS_UNSUPPORTED", "Unknown glTF up axis");
2455
+ throw new TileConversionError15("MESH_SOURCE_AXIS_UNSUPPORTED", "Unknown glTF up axis");
2279
2456
  const placement = new Matrix42(item.tile.computedTransform);
2280
2457
  const rtc = payload.rtcCenter || gltf.extensions?.CESIUM_RTC?.center;
2281
2458
  if (rtc) {
2282
2459
  if (rtc.length !== 3 || rtc.some((value) => !Number.isFinite(value)))
2283
- throw new TileConversionError14(
2460
+ throw new TileConversionError15(
2284
2461
  "MESH_SOURCE_TRANSFORM_INVALID",
2285
2462
  "RTC center must contain finite xyz coordinates"
2286
2463
  );
@@ -2290,15 +2467,20 @@ function createMeshTilesetConversionSource(tileset, options = {}) {
2290
2467
  placement.multiplyRight(new Matrix42([1, 0, 0, 0, 0, 0, 1, 0, 0, -1, 0, 0, 0, 0, 0, 1]));
2291
2468
  if (axis === "X")
2292
2469
  placement.multiplyRight(new Matrix42([0, 0, 1, 0, 0, 1, 0, 0, -1, 0, 0, 0, 0, 0, 0, 1]));
2293
- const roots = gltf.scene?.nodes || (gltf.scenes?.length === 1 ? gltf.scenes[0].nodes : void 0);
2470
+ const roots = (gltf.scene !== void 0 ? gltf.scenes?.[gltf.scene]?.nodes : void 0) || (gltf.scenes?.length === 1 ? gltf.scenes[0].nodes : void 0);
2294
2471
  if (!roots?.length)
2295
- throw new TileConversionError14(
2472
+ throw new TileConversionError15(
2296
2473
  "MESH_SOURCE_SCENE_UNSUPPORTED",
2297
2474
  "A default scene or exactly one scene is required"
2298
2475
  );
2299
2476
  let primitiveIndex = 0;
2300
- for (const selected of traverseMeshNodes(roots, placement)) {
2477
+ const primitives = traverseMeshNodes(gltfWithBuffers, roots, placement);
2478
+ let current = primitives.next();
2479
+ while (!current.done) {
2480
+ const selected = current.value;
2481
+ const next = primitives.next();
2301
2482
  const resource = await extractPrimitive(
2483
+ gltfWithBuffers,
2302
2484
  selected.primitive,
2303
2485
  selected.transform,
2304
2486
  `${item.tile.id}/${content.index}/${primitiveIndex++}`,
@@ -2307,16 +2489,21 @@ function createMeshTilesetConversionSource(tileset, options = {}) {
2307
2489
  signal
2308
2490
  );
2309
2491
  const features = extractMeshFeatures(
2310
- gltf,
2492
+ gltfWithBuffers,
2311
2493
  selected.primitive,
2312
2494
  payload,
2313
2495
  resource.mesh,
2314
2496
  options.features
2315
2497
  );
2316
- yield { ...resource, features };
2498
+ yield {
2499
+ ...resource,
2500
+ features,
2501
+ contentPrimitive: { index: primitiveIndex - 1, last: Boolean(next.done) }
2502
+ };
2503
+ current = next;
2317
2504
  }
2318
2505
  if (!primitiveIndex)
2319
- throw new TileConversionError14(
2506
+ throw new TileConversionError15(
2320
2507
  "MESH_SOURCE_CONTENT_EMPTY",
2321
2508
  "Declared content contains no supported mesh primitive"
2322
2509
  );
@@ -2325,58 +2512,66 @@ function createMeshTilesetConversionSource(tileset, options = {}) {
2325
2512
  }
2326
2513
  };
2327
2514
  }
2328
- function* traverseMeshNodes(nodes, parent, ancestors = /* @__PURE__ */ new Set()) {
2329
- for (const node of nodes) {
2330
- if (ancestors.has(node) || node.skin || node.weights || Object.keys(node.extensions || {}).length)
2331
- throw new TileConversionError14(
2515
+ function* traverseMeshNodes(gltf, nodes, parent, ancestors = /* @__PURE__ */ new Set()) {
2516
+ for (const nodeIndex of nodes) {
2517
+ const node = gltf.json.nodes[nodeIndex];
2518
+ if (!node || ancestors.has(nodeIndex) || node.skin !== void 0 || node.weights || Object.keys(node.extensions || {}).length)
2519
+ throw new TileConversionError15(
2332
2520
  "MESH_SOURCE_NODE_UNSUPPORTED",
2333
2521
  "Cyclic, skinned, morphed, or extended nodes are unsupported"
2334
2522
  );
2335
2523
  const transform = new Matrix42(parent).multiplyRight(
2336
2524
  node.matrix ? new Matrix42(node.matrix) : new Matrix42().translate(node.translation || [0, 0, 0]).multiplyRight(new Matrix42().fromQuaternion(node.rotation || [0, 0, 0, 1])).scale(node.scale || [1, 1, 1])
2337
2525
  );
2338
- if (node.mesh) {
2339
- if (node.mesh.weights || Object.keys(node.mesh.extensions || {}).length)
2340
- throw new TileConversionError14(
2526
+ if (node.mesh !== void 0) {
2527
+ const mesh = gltf.json.meshes[node.mesh];
2528
+ if (mesh.weights || Object.keys(mesh.extensions || {}).length)
2529
+ throw new TileConversionError15(
2341
2530
  "MESH_SOURCE_NODE_UNSUPPORTED",
2342
2531
  "Morph weights and mesh extensions are unsupported"
2343
2532
  );
2344
- for (const primitive of node.mesh.primitives) yield { primitive, transform };
2533
+ for (const primitive of mesh.primitives) yield { primitive, transform };
2345
2534
  }
2346
- yield* traverseMeshNodes(node.children || [], transform, /* @__PURE__ */ new Set([...ancestors, node]));
2535
+ yield* traverseMeshNodes(
2536
+ gltf,
2537
+ node.children || [],
2538
+ transform,
2539
+ /* @__PURE__ */ new Set([...ancestors, nodeIndex])
2540
+ );
2347
2541
  }
2348
2542
  }
2349
- async function extractPrimitive(primitive, transform, id, readExternalResource, contentUri, signal) {
2543
+ async function extractPrimitive(gltf, primitive, transform, id, readExternalResource, contentUri, signal) {
2350
2544
  if (primitive.targets?.length || Object.keys(primitive.extensions || {}).some((name) => name !== "EXT_mesh_features"))
2351
- throw new TileConversionError14(
2545
+ throw new TileConversionError15(
2352
2546
  "MESH_SOURCE_PRIMITIVE_UNSUPPORTED",
2353
2547
  "Morph targets and unmapped primitive extensions are unsupported"
2354
2548
  );
2355
2549
  if (Array.from(transform).some((value) => !Number.isFinite(value)) || transform[3] !== 0 || transform[7] !== 0 || transform[11] !== 0 || transform[15] !== 1 || transform.determinant() <= 0)
2356
- throw new TileConversionError14(
2550
+ throw new TileConversionError15(
2357
2551
  "MESH_SOURCE_TRANSFORM_INVALID",
2358
2552
  "Mesh placement requires a finite invertible affine transform with positive determinant"
2359
2553
  );
2360
2554
  const attributes = {};
2361
- for (const [name, accessor] of Object.entries(primitive.attributes)) {
2555
+ for (const [name, reference] of Object.entries(primitive.attributes)) {
2556
+ const accessor = getGLTFAccessor2(gltf, reference);
2362
2557
  if (name === "_BATCHID" || /^_FEATURE_ID_\d+$/.test(name)) continue;
2363
2558
  if (!["POSITION", "NORMAL", "COLOR_0", "TEXCOORD_0"].includes(name))
2364
- throw new TileConversionError14(
2559
+ throw new TileConversionError15(
2365
2560
  "MESH_SOURCE_ATTRIBUTE_UNSUPPORTED",
2366
2561
  `Unsupported mesh attribute ${name}`
2367
2562
  );
2368
2563
  attributes[name] = {
2369
- value: accessor.value,
2370
- size: accessor.components,
2564
+ value: getGLTFAccessorValue2(gltf, reference),
2565
+ size: accessor.type === "SCALAR" ? 1 : Number(accessor.type.slice(3)),
2371
2566
  ...accessor.normalized ? { normalized: true } : {}
2372
2567
  };
2373
2568
  }
2374
2569
  const mesh = validateMeshGeometry(
2375
2570
  {
2376
2571
  topology: "triangle-list",
2377
- mode: primitive.mode === 5 ? 4 : primitive.mode ?? 4,
2572
+ mode: [5, 6].includes(primitive.mode ?? 4) ? 4 : primitive.mode ?? 4,
2378
2573
  attributes,
2379
- indices: createTriangleIndices(primitive, (attributes.POSITION?.value.length ?? 0) / 3)
2574
+ indices: createTriangleIndices(gltf, primitive, (attributes.POSITION?.value.length ?? 0) / 3)
2380
2575
  },
2381
2576
  true
2382
2577
  );
@@ -2414,14 +2609,15 @@ async function extractPrimitive(primitive, transform, id, readExternalResource,
2414
2609
  mesh,
2415
2610
  origin: minimum.map((value, axis) => value / 2 + maximum[axis] / 2),
2416
2611
  material: await mapMeshSourceMaterial(
2417
- primitive.material,
2612
+ primitive.material === void 0 ? void 0 : gltf.json.materials?.[primitive.material],
2418
2613
  readExternalResource,
2419
2614
  contentUri,
2420
- signal
2615
+ signal,
2616
+ gltf
2421
2617
  )
2422
2618
  };
2423
2619
  }
2424
- async function mapMeshSourceMaterial(material, readExternalResource, contentUri, signal) {
2620
+ async function mapMeshSourceMaterial(material, readExternalResource, contentUri, signal, gltf) {
2425
2621
  if (!material) return void 0;
2426
2622
  const pbr = material.pbrMetallicRoughness;
2427
2623
  if (Object.entries(material).some(
@@ -2438,7 +2634,7 @@ async function mapMeshSourceMaterial(material, readExternalResource, contentUri,
2438
2634
  ) || Object.entries(pbr || {}).some(
2439
2635
  ([name, value]) => value !== void 0 && !["baseColorFactor", "baseColorTexture", "metallicFactor", "roughnessFactor"].includes(name)
2440
2636
  ) || material.emissiveFactor?.some((value) => value !== 0))
2441
- throw new TileConversionError14(
2637
+ throw new TileConversionError15(
2442
2638
  "MESH_SOURCE_MATERIAL_UNSUPPORTED",
2443
2639
  "Only metallic-roughness factors and one embedded base-color image are supported"
2444
2640
  );
@@ -2446,6 +2642,7 @@ async function mapMeshSourceMaterial(material, readExternalResource, contentUri,
2446
2642
  baseColorFactor: pbr?.baseColorFactor,
2447
2643
  ..."baseColorTexture" in (pbr || {}) ? {
2448
2644
  baseColorTexture: await resolveMeshSourceTexture(
2645
+ gltf,
2449
2646
  pbr.baseColorTexture,
2450
2647
  readExternalResource,
2451
2648
  contentUri,
@@ -2459,17 +2656,18 @@ async function mapMeshSourceMaterial(material, readExternalResource, contentUri,
2459
2656
  doubleSided: material.doubleSided
2460
2657
  };
2461
2658
  }
2462
- function createTriangleIndices(primitive, vertexCount) {
2463
- const attribute = primitive.indices ? {
2464
- value: primitive.indices.value,
2465
- size: primitive.indices.components,
2466
- normalized: primitive.indices.normalized
2659
+ function createTriangleIndices(gltf, primitive, vertexCount) {
2660
+ const accessor = primitive.indices === void 0 ? void 0 : getGLTFAccessor2(gltf, primitive.indices);
2661
+ const attribute = accessor ? {
2662
+ value: getGLTFAccessorValue2(gltf, accessor),
2663
+ size: 1,
2664
+ normalized: accessor.normalized
2467
2665
  } : void 0;
2468
- if (primitive.mode !== 5) return attribute;
2666
+ if (primitive.mode !== 5 && primitive.mode !== 6) return attribute;
2469
2667
  const sourceIndices = validateMeshIndices(attribute, vertexCount, 5);
2470
2668
  const indexCount = sourceIndices?.length ?? vertexCount;
2471
2669
  if (!Number.isSafeInteger(indexCount) || indexCount < 3) {
2472
- throw new TileConversionError14(
2670
+ throw new TileConversionError15(
2473
2671
  "MESH_TRIANGLE_COUNT_INVALID",
2474
2672
  "Triangle strips require at least three vertices"
2475
2673
  );
@@ -2477,9 +2675,10 @@ function createTriangleIndices(primitive, vertexCount) {
2477
2675
  const IndexArray = vertexCount < 65536 ? Uint16Array : Uint32Array;
2478
2676
  const indices = new IndexArray((indexCount - 2) * 3);
2479
2677
  for (let triangle = 0; triangle < indexCount - 2; triangle++) {
2480
- const second = triangle + 1 + triangle % 2;
2481
- const third = triangle + 2 - triangle % 2;
2482
- indices[triangle * 3] = sourceIndices?.[triangle] ?? triangle;
2678
+ const second = primitive.mode === 6 ? triangle + 1 : triangle + 1 + triangle % 2;
2679
+ const third = primitive.mode === 6 ? triangle + 2 : triangle + 2 - triangle % 2;
2680
+ const first = primitive.mode === 6 ? 0 : triangle;
2681
+ indices[triangle * 3] = sourceIndices?.[first] ?? first;
2483
2682
  indices[triangle * 3 + 1] = sourceIndices?.[second] ?? second;
2484
2683
  indices[triangle * 3 + 2] = sourceIndices?.[third] ?? third;
2485
2684
  }
@@ -2487,14 +2686,24 @@ function createTriangleIndices(primitive, vertexCount) {
2487
2686
  }
2488
2687
 
2489
2688
  // src/v5/i3s-mesh-conversion.ts
2490
- import { encodeI3SMeshLayer, encodeI3SMeshLayerWithDraco } from "@loaders.gl/i3s";
2491
- import { TileConversionError as TileConversionError15 } from "@loaders.gl/tile-converter/v5/core";
2689
+ import {
2690
+ encodeI3SMeshLayer,
2691
+ encodeI3SMeshLayerWithDraco,
2692
+ preloadI3SMeshCompression
2693
+ } from "@loaders.gl/tiles-3d/i3s";
2694
+ import { TileConversionError as TileConversionError16 } from "@loaders.gl/tile-converter/v5/core";
2492
2695
  import { createBoundedMemoryTileConversionSink as createBoundedMemoryTileConversionSink2 } from "@loaders.gl/tile-converter/v5/core";
2493
2696
  function createI3SMeshConversionCodec(options) {
2697
+ if (options.integratedMeshPolicy !== void 0 && options.integratedMeshPolicy !== "as-3d-object") {
2698
+ throw new TileConversionError16(
2699
+ "I3S_LAYER_TYPE_POLICY_INVALID",
2700
+ "Unknown IntegratedMesh conversion policy"
2701
+ );
2702
+ }
2494
2703
  const reference = options.spatialContext.spatialReference;
2495
2704
  const height = reference.targetHeightReference === "native" ? reference.heightReference : reference.targetHeightReference;
2496
2705
  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")))
2497
- throw new TileConversionError15(
2706
+ throw new TileConversionError16(
2498
2707
  "I3S_MESH_FRAME_UNSUPPORTED",
2499
2708
  "I3S mesh authoring requires resolved ECEF coordinates and ellipsoidal heights"
2500
2709
  );
@@ -2503,6 +2712,12 @@ function createI3SMeshConversionCodec(options) {
2503
2712
  /** Places a source mesh in ECEF and authors one layer without modifying source arrays. */
2504
2713
  async *convert(resource, _inspection, signal) {
2505
2714
  signal?.throwIfAborted();
2715
+ if (resource.sourceLayerType === "IntegratedMesh" && options.integratedMeshPolicy !== "as-3d-object") {
2716
+ throw new TileConversionError16(
2717
+ "I3S_LAYER_TYPE_CHANGE_REQUIRED",
2718
+ "IntegratedMesh to 3DObject conversion requires integratedMeshPolicy=as-3d-object"
2719
+ );
2720
+ }
2506
2721
  const mesh = validateMeshGeometry(resource.mesh, true);
2507
2722
  const positions = mesh.attributes.POSITION.value;
2508
2723
  const context = options.spatialContext;
@@ -2519,6 +2734,7 @@ function createI3SMeshConversionCodec(options) {
2519
2734
  if (mesh.attributes.NORMAL) mesh.attributes.NORMAL = { value: transformed.normals, size: 3 };
2520
2735
  let layer;
2521
2736
  try {
2737
+ await preloadI3SMeshCompression();
2522
2738
  const writerOptions = {
2523
2739
  ...options,
2524
2740
  material: resource.material,
@@ -2527,18 +2743,26 @@ function createI3SMeshConversionCodec(options) {
2527
2743
  };
2528
2744
  layer = options.draco === false ? encodeI3SMeshLayer(mesh, writerOptions) : await encodeI3SMeshLayerWithDraco(mesh, writerOptions, options.dracoLibraryOptions);
2529
2745
  } catch (error) {
2530
- throw new TileConversionError15(
2746
+ throw new TileConversionError16(
2531
2747
  "I3S_MESH_PROFILE_UNSUPPORTED",
2532
2748
  error instanceof Error ? error.message : String(error)
2533
2749
  );
2534
2750
  }
2535
2751
  signal?.throwIfAborted();
2536
2752
  objectIdOffset += resource.features?.batches.reduce((total, batch) => total + batch.data.numRows, 0) ?? 1;
2537
- yield { id: resource.id, ...layer };
2753
+ yield { id: resource.id, ...layer, sourceLayerType: resource.sourceLayerType };
2538
2754
  },
2539
2755
  /** Reports measured precision and explicitly authorized integer representation changes. */
2540
2756
  async validateOutput(resource) {
2541
2757
  return [
2758
+ ...resource.sourceLayerType === "IntegratedMesh" ? [
2759
+ {
2760
+ code: "I3S_LAYER_TYPE_CHANGED",
2761
+ message: "Explicitly authored IntegratedMesh geometry as I3S 1.7 3DObject; integrated surface layer semantics and source LOD are not preserved",
2762
+ severity: "warning",
2763
+ resourceId: resource.id
2764
+ }
2765
+ ] : [],
2542
2766
  ...resource.maximumPositionError > 0 ? [
2543
2767
  {
2544
2768
  code: "I3S_MESH_POSITION_ROUNDING",
@@ -2568,7 +2792,7 @@ function createSingleMeshI3SSink(options) {
2568
2792
  async write(resource, signal) {
2569
2793
  signal?.throwIfAborted();
2570
2794
  if (state !== "empty")
2571
- throw new TileConversionError15(
2795
+ throw new TileConversionError16(
2572
2796
  "SINGLE_I3S_MESH_UNAVAILABLE",
2573
2797
  "The initial I3S sink accepts exactly one mesh layer"
2574
2798
  );
@@ -2587,7 +2811,7 @@ function createSingleMeshI3SSink(options) {
2587
2811
  /** Commits a complete single-layer result. */
2588
2812
  async finalize(report) {
2589
2813
  if (state !== "written")
2590
- throw new TileConversionError15(
2814
+ throw new TileConversionError16(
2591
2815
  "SINGLE_I3S_MESH_INCOMPLETE",
2592
2816
  "Finalization requires exactly one successfully written layer"
2593
2817
  );
@@ -2608,18 +2832,18 @@ function createSingleMeshI3SSink(options) {
2608
2832
  import { Ellipsoid } from "@math.gl/geospatial";
2609
2833
  import { GZipDecompressor, GZipCompressor } from "@loaders.gl/compression";
2610
2834
  import {
2611
- TileConversionError as TileConversionError16,
2835
+ TileConversionError as TileConversionError17,
2612
2836
  createBoundedMemoryTileConversionSink as createBoundedMemoryTileConversionSink3
2613
2837
  } from "@loaders.gl/tile-converter/v5/core";
2614
2838
  function createI3SMeshSink(options) {
2615
2839
  const { maxMeshes, maxResourceBytes } = options;
2616
2840
  if (!Number.isInteger(maxMeshes) || maxMeshes < 1 || maxMeshes > 64 || !Number.isSafeInteger(maxResourceBytes) || maxResourceBytes < 1)
2617
- throw new TileConversionError16(
2841
+ throw new TileConversionError17(
2618
2842
  "I3S_COLLECTION_OPTIONS_INVALID",
2619
2843
  "I3S collections require 1\u201364 meshes and a positive resource byte limit"
2620
2844
  );
2621
2845
  const memory = createBoundedMemoryTileConversionSink3(options);
2622
- const compressor = new GZipCompressor({ useNative: false, gzip: { mtime: 0 } });
2846
+ const compressor = new GZipCompressor({ useNative: false });
2623
2847
  const decompressor = new GZipDecompressor({ useNative: false });
2624
2848
  const leaves = [];
2625
2849
  const identifiers = /* @__PURE__ */ new Set();
@@ -2631,7 +2855,7 @@ function createI3SMeshSink(options) {
2631
2855
  async function readBytes(bytes) {
2632
2856
  const output = await decompressor.decompress(bytes);
2633
2857
  if (output.byteLength > maxResourceBytes)
2634
- throw new TileConversionError16(
2858
+ throw new TileConversionError17(
2635
2859
  "I3S_COLLECTION_RESOURCE_TOO_LARGE",
2636
2860
  "I3S metadata exceeds maxResourceBytes"
2637
2861
  );
@@ -2643,7 +2867,7 @@ function createI3SMeshSink(options) {
2643
2867
  async function writeJson(resourceId, value) {
2644
2868
  const bytes = new TextEncoder().encode(JSON.stringify(value));
2645
2869
  if (bytes.byteLength > maxResourceBytes)
2646
- throw new TileConversionError16(
2870
+ throw new TileConversionError17(
2647
2871
  "I3S_COLLECTION_RESOURCE_TOO_LARGE",
2648
2872
  "I3S index exceeds maxResourceBytes"
2649
2873
  );
@@ -2660,7 +2884,7 @@ function createI3SMeshSink(options) {
2660
2884
  async write(resource, signal) {
2661
2885
  signal?.throwIfAborted();
2662
2886
  if (state !== "open" || identifiers.has(resource.id) || leaves.length >= maxMeshes)
2663
- throw new TileConversionError16(
2887
+ throw new TileConversionError17(
2664
2888
  "I3S_COLLECTION_UNAVAILABLE",
2665
2889
  "I3S collection is busy, closed, full, or repeats a placement"
2666
2890
  );
@@ -2673,7 +2897,7 @@ function createI3SMeshSink(options) {
2673
2897
  authored.store.defaultGeometrySchema
2674
2898
  ]);
2675
2899
  if (schemaKey !== void 0 && key !== schemaKey)
2676
- throw new TileConversionError16(
2900
+ throw new TileConversionError17(
2677
2901
  "I3S_COLLECTION_SCHEMA_MISMATCH",
2678
2902
  "Mesh nodes must share feature fields and the legacy geometry layout"
2679
2903
  );
@@ -2685,7 +2909,7 @@ function createI3SMeshSink(options) {
2685
2909
  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(
2686
2910
  (value, index2) => value !== first + index2
2687
2911
  ))
2688
- throw new TileConversionError16(
2912
+ throw new TileConversionError17(
2689
2913
  "I3S_COLLECTION_IDS_INVALID",
2690
2914
  "Generated object IDs must be contiguous and disjoint across nodes"
2691
2915
  );
@@ -2696,7 +2920,7 @@ function createI3SMeshSink(options) {
2696
2920
  "nodes/1/3dNodeIndexDocument.json.gz"
2697
2921
  );
2698
2922
  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))
2699
- throw new TileConversionError16(
2923
+ throw new TileConversionError17(
2700
2924
  "I3S_COLLECTION_BOUNDS_INVALID",
2701
2925
  "Expected finite axis-aligned bounds from the I3S mesh writer"
2702
2926
  );
@@ -2747,7 +2971,7 @@ function createI3SMeshSink(options) {
2747
2971
  /** Rebuilds both indices and layer bounds only after every leaf has completed. */
2748
2972
  async finalize(report) {
2749
2973
  if (state !== "open" || !layer || !leaves.length)
2750
- throw new TileConversionError16(
2974
+ throw new TileConversionError17(
2751
2975
  "I3S_COLLECTION_INCOMPLETE",
2752
2976
  "Finalization requires successfully written mesh nodes"
2753
2977
  );
@@ -2772,7 +2996,7 @@ function createI3SMeshSink(options) {
2772
2996
  quaternion: [0, 0, 0, 1]
2773
2997
  };
2774
2998
  if ([...obb.center, ...obb.halfSize].some((value) => !Number.isFinite(value)))
2775
- throw new TileConversionError16(
2999
+ throw new TileConversionError17(
2776
3000
  "I3S_COLLECTION_BOUNDS_INVALID",
2777
3001
  "Collection bounds cannot be represented"
2778
3002
  );
@@ -2832,7 +3056,7 @@ function createI3SMeshSink(options) {
2832
3056
  }
2833
3057
 
2834
3058
  // src/v5/conversion-api.ts
2835
- var TileConversionError17 = class extends Error {
3059
+ var TileConversionError18 = class extends Error {
2836
3060
  /** Stable machine-readable failure code. */
2837
3061
  code;
2838
3062
  /** Diagnostics associated with this failure. */
@@ -2850,7 +3074,7 @@ var TileConversionError17 = class extends Error {
2850
3074
  function createTileConversionResourceFetcher(options) {
2851
3075
  let consumed = options.initialInputBytes ?? 0;
2852
3076
  if (!Number.isSafeInteger(options.maxInputBytes) || options.maxInputBytes < 1 || !Number.isSafeInteger(consumed) || consumed < 0 || consumed > options.maxInputBytes)
2853
- throw new TileConversionError17(
3077
+ throw new TileConversionError18(
2854
3078
  "INVALID_INPUT_MEMORY_LIMIT",
2855
3079
  "Resource fetch requires a positive aggregate byte budget and valid initial bytes"
2856
3080
  );
@@ -2859,7 +3083,7 @@ function createTileConversionResourceFetcher(options) {
2859
3083
  options.signal?.throwIfAborted();
2860
3084
  const url = new URL(input instanceof Request ? input.url : String(input));
2861
3085
  if (!["http:", "https:", "blob:", "data:"].includes(url.protocol))
2862
- throw new TileConversionError17(
3086
+ throw new TileConversionError18(
2863
3087
  "EXTERNAL_RESOURCE_UNSUPPORTED",
2864
3088
  "Conversion transport requires HTTP(S), Blob or embedded data resources"
2865
3089
  );
@@ -2868,14 +3092,14 @@ function createTileConversionResourceFetcher(options) {
2868
3092
  const response = await fetcher(input, { ...requestOptions, signal });
2869
3093
  if (!response.ok) {
2870
3094
  await response.body?.cancel();
2871
- throw new TileConversionError17(
3095
+ throw new TileConversionError18(
2872
3096
  "BROWSER_SOURCE_FETCH_FAILED",
2873
3097
  `Could not fetch conversion resource (${response.status})`
2874
3098
  );
2875
3099
  }
2876
3100
  const reader = response.body?.getReader();
2877
3101
  if (!reader)
2878
- throw new TileConversionError17(
3102
+ throw new TileConversionError18(
2879
3103
  "BROWSER_SOURCE_STREAM_UNAVAILABLE",
2880
3104
  "Conversion resources require readable response streams"
2881
3105
  );
@@ -2893,7 +3117,7 @@ function createTileConversionResourceFetcher(options) {
2893
3117
  signal?.throwIfAborted();
2894
3118
  if (done) break;
2895
3119
  if (value.byteLength > options.maxInputBytes - consumed)
2896
- throw new TileConversionError17(
3120
+ throw new TileConversionError18(
2897
3121
  "INPUT_RESOURCE_TOO_LARGE",
2898
3122
  "Selected contents and external dependencies exceed the aggregate input byte budget"
2899
3123
  );
@@ -2914,23 +3138,25 @@ function createTileConversionResourceFetcher(options) {
2914
3138
  bytes.set(part, offset);
2915
3139
  offset += part.byteLength;
2916
3140
  }
2917
- return new Response(bytes, {
3141
+ const boundedResponse = new Response(bytes, {
2918
3142
  status: response.status,
2919
3143
  statusText: response.statusText,
2920
3144
  headers: response.headers
2921
3145
  });
3146
+ Object.defineProperty(boundedResponse, "url", { value: response.url });
3147
+ return boundedResponse;
2922
3148
  };
2923
3149
  }
2924
3150
 
2925
3151
  // src/v5/point-cloud-tileset-sink.ts
2926
3152
  import {
2927
3153
  createBoundedMemoryTileConversionSink as createBoundedMemoryTileConversionSink4,
2928
- TileConversionError as TileConversionError18
3154
+ TileConversionError as TileConversionError19
2929
3155
  } from "@loaders.gl/tile-converter/v5/core";
2930
3156
  function createPointCloudTilesetSink(options) {
2931
3157
  const { maxTiles, geometricError } = options;
2932
3158
  if (!Number.isSafeInteger(maxTiles) || maxTiles < 1 || !Number.isFinite(geometricError) || geometricError < 0) {
2933
- throw new TileConversionError18(
3159
+ throw new TileConversionError19(
2934
3160
  "POINT_CLOUD_TILESET_OPTIONS_INVALID",
2935
3161
  "Positive maxTiles and finite nonnegative geometricError are required"
2936
3162
  );
@@ -2949,12 +3175,12 @@ function createPointCloudTilesetSink(options) {
2949
3175
  async write(resource, signal) {
2950
3176
  signal?.throwIfAborted();
2951
3177
  if (state !== "open")
2952
- throw new TileConversionError18(
3178
+ throw new TileConversionError19(
2953
3179
  "POINT_CLOUD_TILESET_SINK_UNAVAILABLE",
2954
3180
  "Point sink is busy or closed"
2955
3181
  );
2956
3182
  if (!resource.id || identifiers.has(resource.id) || children.length >= maxTiles) {
2957
- throw new TileConversionError18(
3183
+ throw new TileConversionError19(
2958
3184
  "POINT_CLOUD_TILESET_PLACEMENT_INVALID",
2959
3185
  "Placements require unique nonempty identifiers within maxTiles"
2960
3186
  );
@@ -2967,7 +3193,7 @@ function createPointCloudTilesetSink(options) {
2967
3193
  signal
2968
3194
  );
2969
3195
  if (state !== "writing")
2970
- throw new TileConversionError18(
3196
+ throw new TileConversionError19(
2971
3197
  "POINT_CLOUD_TILESET_SINK_UNAVAILABLE",
2972
3198
  "Point sink was aborted during writing"
2973
3199
  );
@@ -2986,7 +3212,7 @@ function createPointCloudTilesetSink(options) {
2986
3212
  /** Serializes the enclosing root after all placements have completed. */
2987
3213
  async finalize(report) {
2988
3214
  if (state !== "open" || !children.length)
2989
- throw new TileConversionError18(
3215
+ throw new TileConversionError19(
2990
3216
  "POINT_CLOUD_TILESET_SINK_INCOMPLETE",
2991
3217
  "Finalization requires successfully written point tiles"
2992
3218
  );
@@ -2996,7 +3222,7 @@ function createPointCloudTilesetSink(options) {
2996
3222
  Math.hypot(...minimum.map((value, axis) => maximum[axis] - value))
2997
3223
  );
2998
3224
  if (!Number.isFinite(rootError))
2999
- throw new TileConversionError18(
3225
+ throw new TileConversionError19(
3000
3226
  "POINT_CLOUD_TILESET_BOUNDS_INVALID",
3001
3227
  "Enclosing bounds must have a finite diagonal"
3002
3228
  );
@@ -3016,13 +3242,13 @@ function createPointCloudTilesetSink(options) {
3016
3242
  contentType: "application/json"
3017
3243
  });
3018
3244
  if (state !== "writing")
3019
- throw new TileConversionError18(
3245
+ throw new TileConversionError19(
3020
3246
  "POINT_CLOUD_TILESET_SINK_UNAVAILABLE",
3021
3247
  "Point sink was aborted during finalization"
3022
3248
  );
3023
3249
  await memory.finalize(report);
3024
3250
  if (state !== "writing")
3025
- throw new TileConversionError18(
3251
+ throw new TileConversionError19(
3026
3252
  "POINT_CLOUD_TILESET_SINK_UNAVAILABLE",
3027
3253
  "Point sink was aborted during finalization"
3028
3254
  );
@@ -3043,13 +3269,13 @@ function getPointTileBounds(resource, geometricError) {
3043
3269
  const reference = resource.spatialReference;
3044
3270
  const identity = [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1];
3045
3271
  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"))) {
3046
- throw new TileConversionError18(
3272
+ throw new TileConversionError19(
3047
3273
  "POINT_CLOUD_TILESET_FRAME_UNSUPPORTED",
3048
3274
  "Point tiles require declared EPSG:4978 xyz meters, ellipsoidal heights and no additional source placement"
3049
3275
  );
3050
3276
  }
3051
3277
  if (!Number.isFinite(resource.maximumPositionError) || resource.maximumPositionError < 0 || resource.maximumPositionError > geometricError) {
3052
- throw new TileConversionError18(
3278
+ throw new TileConversionError19(
3053
3279
  "POINT_CLOUD_GEOMETRIC_ERROR_INVALID",
3054
3280
  "geometricError must include measured position reconstruction error"
3055
3281
  );
@@ -3057,7 +3283,7 @@ function getPointTileBounds(resource, geometricError) {
3057
3283
  const [minimum, maximum] = resource.localBoundingBox;
3058
3284
  const center = resource.rtcCenter;
3059
3285
  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])) {
3060
- throw new TileConversionError18(
3286
+ throw new TileConversionError19(
3061
3287
  "POINT_CLOUD_TILESET_BOUNDS_INVALID",
3062
3288
  "RTC center and encoded xyz bounds must be finite and ordered"
3063
3289
  );
@@ -3065,7 +3291,7 @@ function getPointTileBounds(resource, geometricError) {
3065
3291
  const lower = minimum.map((value, axis) => value + center[axis]);
3066
3292
  const upper = maximum.map((value, axis) => value + center[axis]);
3067
3293
  if ([...lower, ...upper].some((value) => !Number.isFinite(value)))
3068
- throw new TileConversionError18(
3294
+ throw new TileConversionError19(
3069
3295
  "POINT_CLOUD_TILESET_BOUNDS_INVALID",
3070
3296
  "Reconstructed ECEF bounds must be finite"
3071
3297
  );
@@ -3079,14 +3305,15 @@ function createPointBoundsBox(minimum, maximum) {
3079
3305
 
3080
3306
  // src/v5/i3s-mesh-source.ts
3081
3307
  import {
3082
- TileConversionError as TileConversionError21,
3308
+ TileConversionError as TileConversionError22,
3083
3309
  createTilesetConversionSource as createTilesetConversionSource2
3084
3310
  } from "@loaders.gl/tile-converter/v5/core";
3085
3311
 
3086
3312
  // src/v5/i3s-mesh-source-material.ts
3087
3313
  import { getBinaryImageMetadata as getBinaryImageMetadata3 } from "@loaders.gl/images";
3088
- import { TileConversionError as TileConversionError19 } from "@loaders.gl/tile-converter/v5/core";
3314
+ import { TileConversionError as TileConversionError20 } from "@loaders.gl/tile-converter/v5/core";
3089
3315
  async function mapI3SMeshSourceMaterial(content, header, readExternalResource, contentUri, signal) {
3316
+ const gltf = { json: { asset: { version: "2.0" } }, buffers: [] };
3090
3317
  const material = Object.fromEntries(
3091
3318
  Object.entries(content.material || {}).filter(([, value]) => value !== void 0)
3092
3319
  );
@@ -3095,7 +3322,7 @@ async function mapI3SMeshSourceMaterial(content, header, readExternalResource, c
3095
3322
  ) : {};
3096
3323
  material.pbrMetallicRoughness = metallicRoughness;
3097
3324
  if (material.cullFace !== void 0 && material.cullFace !== "back") {
3098
- throw new TileConversionError19(
3325
+ throw new TileConversionError20(
3099
3326
  "I3S_MESH_SOURCE_MATERIAL_UNSUPPORTED",
3100
3327
  "Only back-face culling is supported"
3101
3328
  );
@@ -3114,7 +3341,7 @@ async function mapI3SMeshSourceMaterial(content, header, readExternalResource, c
3114
3341
  }
3115
3342
  const encodedTextures = content.textures || {};
3116
3343
  if (!textureInfo && (resources.length || content.texture || Object.keys(encodedTextures).length)) {
3117
- throw new TileConversionError19(
3344
+ throw new TileConversionError20(
3118
3345
  "I3S_MESH_SOURCE_TEXTURE_UNSUPPORTED",
3119
3346
  "Texture resources require a base-color mapping"
3120
3347
  );
@@ -3127,7 +3354,7 @@ async function mapI3SMeshSourceMaterial(content, header, readExternalResource, c
3127
3354
  ) || ["wrapS", "wrapT"].some(
3128
3355
  (name) => textureInfo[name] !== void 0 && (typeof textureInfo[name] !== "string" || !Object.hasOwn(wrappingModes, textureInfo[name]))
3129
3356
  ) || resources.length > 1 || resources.some((resource) => resource.textureSetDefinitionId !== definitionId) || Object.keys(encodedTextures).some((key) => key !== String(definitionId))) {
3130
- throw new TileConversionError19(
3357
+ throw new TileConversionError20(
3131
3358
  "I3S_MESH_SOURCE_TEXTURE_UNSUPPORTED",
3132
3359
  "Only one base-color texture set on TEXCOORD_0 with qualified wrapping is supported"
3133
3360
  );
@@ -3135,7 +3362,7 @@ async function mapI3SMeshSourceMaterial(content, header, readExternalResource, c
3135
3362
  let encoded = encodedTextures[String(definitionId)] || content.texture || textureInfo.texture?.source?.image;
3136
3363
  if (!encoded) {
3137
3364
  if (!resources.length || !readExternalResource) {
3138
- throw new TileConversionError19(
3365
+ throw new TileConversionError20(
3139
3366
  "I3S_MESH_SOURCE_TEXTURE_UNSUPPORTED",
3140
3367
  "Encoded texture bytes or an explicit bounded resource reader are required"
3141
3368
  );
@@ -3153,8 +3380,8 @@ async function mapI3SMeshSourceMaterial(content, header, readExternalResource, c
3153
3380
  }
3154
3381
  }
3155
3382
  const declaredFormat = resources[0]?.textureFormat;
3156
- if (!image || !["image/png", "image/jpeg"].includes(image.mimeType) || declaredFormat && (declaredFormat === "png" ? image.mimeType !== "image/png" : declaredFormat === "jpg" ? image.mimeType !== "image/jpeg" : true)) {
3157
- throw new TileConversionError19(
3383
+ 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)) {
3384
+ throw new TileConversionError20(
3158
3385
  "I3S_MESH_SOURCE_TEXTURE_UNSUPPORTED",
3159
3386
  "I3S base-color bytes must have matching PNG/JPEG headers"
3160
3387
  );
@@ -3162,22 +3389,27 @@ async function mapI3SMeshSourceMaterial(content, header, readExternalResource, c
3162
3389
  const sampler = Object.fromEntries(
3163
3390
  ["wrapS", "wrapT"].filter((name) => textureInfo[name] !== void 0).map((name) => [name, wrappingModes[textureInfo[name]]])
3164
3391
  );
3165
- metallicRoughness.baseColorTexture = {
3166
- texCoord: 0,
3167
- texture: {
3168
- source: { mimeType: image.mimeType, bufferView: { data } },
3169
- ...Object.keys(sampler).length ? { sampler } : {}
3170
- }
3171
- };
3392
+ gltf.buffers.push({
3393
+ arrayBuffer: data.buffer,
3394
+ byteOffset: data.byteOffset,
3395
+ byteLength: data.byteLength
3396
+ });
3397
+ gltf.json.buffers = [{ byteLength: data.byteLength }];
3398
+ gltf.json.bufferViews = [{ buffer: 0, byteLength: data.byteLength }];
3399
+ gltf.json.images = [{ mimeType: image.mimeType, bufferView: 0 }];
3400
+ gltf.json.samplers = [sampler];
3401
+ gltf.json.textures = [{ source: 0, sampler: 0 }];
3402
+ metallicRoughness.baseColorTexture = { texCoord: 0, index: 0 };
3172
3403
  }
3173
3404
  const mapped = await mapMeshSourceMaterial(
3174
3405
  material,
3175
3406
  void 0,
3176
3407
  contentUri,
3177
- signal
3408
+ signal,
3409
+ gltf
3178
3410
  );
3179
3411
  if (mapped?.baseColorTexture && !content.sourceAttributes?.uv0) {
3180
- throw new TileConversionError19(
3412
+ throw new TileConversionError20(
3181
3413
  "MESH_TEXCOORD_REQUIRED",
3182
3414
  "Base-color textures require TEXCOORD_0"
3183
3415
  );
@@ -3186,8 +3418,8 @@ async function mapI3SMeshSourceMaterial(content, header, readExternalResource, c
3186
3418
  }
3187
3419
 
3188
3420
  // src/v5/i3s-mesh-source-features.ts
3189
- import { I3SAttributeLoader } from "@loaders.gl/i3s";
3190
- import { TileConversionError as TileConversionError20 } from "@loaders.gl/tile-converter/v5/core";
3421
+ import { I3SAttributeLoader } from "@loaders.gl/tiles-3d/i3s";
3422
+ import { TileConversionError as TileConversionError21 } from "@loaders.gl/tile-converter/v5/core";
3191
3423
  async function extractI3SMeshFeatures(content, item, metadata, options, readExternalResource, contentUri, signal) {
3192
3424
  const maximumAttributeBytes = options.maxAttributeBytes;
3193
3425
  const storage = metadata.tileset.attributeStorageInfo;
@@ -3195,7 +3427,7 @@ async function extractI3SMeshFeatures(content, item, metadata, options, readExte
3195
3427
  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(
3196
3428
  (field) => field.domain || field.type === "esriFieldTypeDate" || !storage.some((attribute2) => attribute2.name === field.name)
3197
3429
  )) {
3198
- throw new TileConversionError20(
3430
+ throw new TileConversionError21(
3199
3431
  "I3S_MESH_SOURCE_FEATURE_MAPPING_REQUIRED",
3200
3432
  "I3S attributes require an explicit scalar schema, object ID column, byte budget and resource reader; domains/dates/raw metadata need a custom mapper"
3201
3433
  );
@@ -3218,7 +3450,7 @@ async function extractI3SMeshFeatures(content, item, metadata, options, readExte
3218
3450
  ) || attribute2.ordering && JSON.stringify(
3219
3451
  attribute2.ordering.map((value) => value === "ObjectIds" ? "objectIds" : value)
3220
3452
  ) !== JSON.stringify(expectedOrdering) || type === "String" && (values.encoding !== "UTF-8" || attribute2.attributeByteCounts?.valueType !== "UInt32" || attribute2.attributeByteCounts.valuesPerElement !== 1) || !attributeUrls[index]) {
3221
- throw new TileConversionError20(
3453
+ throw new TileConversionError21(
3222
3454
  "I3S_MESH_SOURCE_ATTRIBUTE_UNSUPPORTED",
3223
3455
  `Attribute ${attribute2.name} requires a standard scalar/string resource layout`
3224
3456
  );
@@ -3227,7 +3459,7 @@ async function extractI3SMeshFeatures(content, item, metadata, options, readExte
3227
3459
  signal?.throwIfAborted();
3228
3460
  inputBytes += bytes instanceof Uint8Array ? bytes.byteLength : Infinity;
3229
3461
  if (!(bytes instanceof Uint8Array) || inputBytes > maximumAttributeBytes) {
3230
- throw new TileConversionError20(
3462
+ throw new TileConversionError21(
3231
3463
  "INPUT_RESOURCE_TOO_LARGE",
3232
3464
  "I3S node attributes exceed maxAttributeBytes"
3233
3465
  );
@@ -3240,14 +3472,14 @@ async function extractI3SMeshFeatures(content, item, metadata, options, readExte
3240
3472
  i3s: { attributeValues: "exact" }
3241
3473
  });
3242
3474
  } catch (error) {
3243
- throw new TileConversionError20(
3475
+ throw new TileConversionError21(
3244
3476
  "I3S_MESH_SOURCE_ATTRIBUTE_INVALID",
3245
3477
  `Invalid attribute ${attribute2.name}: ${error instanceof Error ? error.message : String(error)}`
3246
3478
  );
3247
3479
  }
3248
3480
  const column = parsed[attribute2.name];
3249
3481
  if (!column || count !== void 0 && column.length !== count || !column.length) {
3250
- throw new TileConversionError20(
3482
+ throw new TileConversionError21(
3251
3483
  "I3S_MESH_SOURCE_ATTRIBUTE_COUNT_INVALID",
3252
3484
  "Every attribute must have the same nonzero row count"
3253
3485
  );
@@ -3255,7 +3487,7 @@ async function extractI3SMeshFeatures(content, item, metadata, options, readExte
3255
3487
  if (Array.from(column).some(
3256
3488
  (value) => typeof value === "number" && !Number.isFinite(value)
3257
3489
  )) {
3258
- throw new TileConversionError20(
3490
+ throw new TileConversionError21(
3259
3491
  "I3S_MESH_SOURCE_ATTRIBUTE_UNSUPPORTED",
3260
3492
  "Numeric noData values require an explicit custom mapper"
3261
3493
  );
@@ -3268,7 +3500,7 @@ async function extractI3SMeshFeatures(content, item, metadata, options, readExte
3268
3500
  for (let index = 0; index < count; index++) {
3269
3501
  const identifier = identifiers[index];
3270
3502
  if (!(typeof identifier === "bigint" || typeof identifier === "number" && Number.isSafeInteger(identifier)) || attributeRows.has(BigInt(identifier))) {
3271
- throw new TileConversionError20(
3503
+ throw new TileConversionError21(
3272
3504
  "I3S_MESH_SOURCE_FEATURE_ID_UNSUPPORTED",
3273
3505
  "Object IDs must be unique exact integers"
3274
3506
  );
@@ -3279,7 +3511,7 @@ async function extractI3SMeshFeatures(content, item, metadata, options, readExte
3279
3511
  const geometryIdentifiers = content.featureIds;
3280
3512
  const attribute = content.sourceAttributes?.id;
3281
3513
  if (geometryIdentifiers.length !== vertexCount || attribute?.size !== 1 || attribute.normalized || attribute.value.length !== vertexCount || Array.from(geometryIdentifiers).some((value, index) => value !== attribute.value[index])) {
3282
- throw new TileConversionError20(
3514
+ throw new TileConversionError21(
3283
3515
  "I3S_MESH_SOURCE_FEATURE_ID_UNSUPPORTED",
3284
3516
  "Geometry requires one exact unnormalized object ID per vertex"
3285
3517
  );
@@ -3290,7 +3522,7 @@ async function extractI3SMeshFeatures(content, item, metadata, options, readExte
3290
3522
  const covered = new Uint8Array(triangleFeatureIndices.length);
3291
3523
  for (const range of content.drawRanges) {
3292
3524
  if (!Number.isSafeInteger(range.firstPrimitive) || range.firstPrimitive < 0 || !Number.isSafeInteger(range.primitiveCount) || range.primitiveCount <= 0 || range.firstPrimitive + range.primitiveCount > covered.length) {
3293
- throw new TileConversionError20(
3525
+ throw new TileConversionError21(
3294
3526
  "I3S_MESH_SOURCE_FEATURE_ASSOCIATION_INVALID",
3295
3527
  "Feature draw ranges must cover valid triangles"
3296
3528
  );
@@ -3298,7 +3530,7 @@ async function extractI3SMeshFeatures(content, item, metadata, options, readExte
3298
3530
  for (let triangle = range.firstPrimitive; triangle < range.firstPrimitive + range.primitiveCount; triangle++) {
3299
3531
  const vertex = indices?.[triangle * 3] ?? triangle * 3;
3300
3532
  if (covered[triangle] || geometryIdentifiers[vertex] !== range.featureId) {
3301
- throw new TileConversionError20(
3533
+ throw new TileConversionError21(
3302
3534
  "I3S_MESH_SOURCE_FEATURE_ASSOCIATION_INVALID",
3303
3535
  "Feature draw ranges must agree with decoded ownership without overlapping"
3304
3536
  );
@@ -3307,7 +3539,7 @@ async function extractI3SMeshFeatures(content, item, metadata, options, readExte
3307
3539
  }
3308
3540
  }
3309
3541
  if (covered.some((value) => value === 0)) {
3310
- throw new TileConversionError20(
3542
+ throw new TileConversionError21(
3311
3543
  "I3S_MESH_SOURCE_FEATURE_ASSOCIATION_INVALID",
3312
3544
  "Feature draw ranges must cover every triangle"
3313
3545
  );
@@ -3321,7 +3553,7 @@ async function extractI3SMeshFeatures(content, item, metadata, options, readExte
3321
3553
  const featureIds = corners.map((corner) => geometryIdentifiers[corner]);
3322
3554
  const row = attributeRows.get(BigInt(featureIds[0]));
3323
3555
  if (row === void 0 || featureIds.some((identifier) => identifier !== featureIds[0])) {
3324
- throw new TileConversionError20(
3556
+ throw new TileConversionError21(
3325
3557
  "I3S_MESH_SOURCE_FEATURE_ASSOCIATION_INVALID",
3326
3558
  "Each triangle must reference one declared attribute row"
3327
3559
  );
@@ -3330,14 +3562,14 @@ async function extractI3SMeshFeatures(content, item, metadata, options, readExte
3330
3562
  usedRows.add(row);
3331
3563
  }
3332
3564
  if (usedRows.size !== count) {
3333
- throw new TileConversionError20(
3565
+ throw new TileConversionError21(
3334
3566
  "I3S_MESH_SOURCE_FEATURE_ASSOCIATION_INVALID",
3335
3567
  "Attribute rows without geometry require a separately qualified mapping"
3336
3568
  );
3337
3569
  }
3338
3570
  const stableProperty = options.sourceFeatureIdProperty ?? options.objectIdProperty;
3339
3571
  if (!Object.hasOwn(columns, stableProperty)) {
3340
- throw new TileConversionError20(
3572
+ throw new TileConversionError21(
3341
3573
  "I3S_MESH_SOURCE_FEATURE_MAPPING_REQUIRED",
3342
3574
  "Stable identifier column is missing"
3343
3575
  );
@@ -3358,7 +3590,7 @@ async function extractI3SMeshFeatures(content, item, metadata, options, readExte
3358
3590
  for (let index = 0; index < column.length; index++) {
3359
3591
  const identifier = column.get(index);
3360
3592
  if (stableIds.has(identifier)) {
3361
- throw new TileConversionError20(
3593
+ throw new TileConversionError21(
3362
3594
  "I3S_MESH_SOURCE_FEATURE_ID_UNSUPPORTED",
3363
3595
  "Stable IDs must be unique"
3364
3596
  );
@@ -3381,20 +3613,20 @@ function createI3SMeshTilesetConversionSource(tileset, options = {}) {
3381
3613
  /** Validates the layer profile and explicit source decode policy before reading. */
3382
3614
  async inspect(signal) {
3383
3615
  if (options.features && options.getFeatures) {
3384
- throw new TileConversionError21(
3616
+ throw new TileConversionError22(
3385
3617
  "I3S_MESH_SOURCE_FEATURE_MAPPING_REQUIRED",
3386
3618
  "Choose explicit resource features or a custom getFeatures mapper, not both"
3387
3619
  );
3388
3620
  }
3389
3621
  const metadata = await source.inspect(signal);
3390
3622
  if (metadata.type !== "I3S" || !["3DObject", "IntegratedMesh"].includes(metadata.tileset.layerType) || tileset.options.i3s?.geometryMode !== "source") {
3391
- throw new TileConversionError21(
3623
+ throw new TileConversionError22(
3392
3624
  "I3S_MESH_SOURCE_PROFILE_UNSUPPORTED",
3393
3625
  "I3S mesh extraction requires a 3DObject/IntegratedMesh runtime with geometryMode=source"
3394
3626
  );
3395
3627
  }
3396
3628
  if (!metadata.spatialReference?.sourceCrs) {
3397
- throw new TileConversionError21(
3629
+ throw new TileConversionError22(
3398
3630
  "I3S_MESH_SOURCE_REFERENCE_REQUIRED",
3399
3631
  "I3S conversion requires an explicit source CRS"
3400
3632
  );
@@ -3422,7 +3654,7 @@ function createI3SMeshTilesetConversionSource(tileset, options = {}) {
3422
3654
  ].some(
3423
3655
  (field) => JSON.stringify(content.spatialReference?.[field]) !== JSON.stringify(metadata.spatialReference?.[field])
3424
3656
  )) {
3425
- throw new TileConversionError21(
3657
+ throw new TileConversionError22(
3426
3658
  "I3S_MESH_SOURCE_GEOMETRY_UNSUPPORTED",
3427
3659
  "I3S extraction requires original source-frame Float64 triangle geometry"
3428
3660
  );
@@ -3430,7 +3662,7 @@ function createI3SMeshTilesetConversionSource(tileset, options = {}) {
3430
3662
  if (!["earth-centered", "vertex-reference-frame"].includes(
3431
3663
  content.normalReferenceFrame || ""
3432
3664
  ) || content.normalReferenceFrame !== (metadata.tileset.store?.normalReferenceFrame || "earth-centered")) {
3433
- throw new TileConversionError21(
3665
+ throw new TileConversionError22(
3434
3666
  "I3S_MESH_SOURCE_NORMAL_FRAME_UNSUPPORTED",
3435
3667
  "Unknown I3S vector basis"
3436
3668
  );
@@ -3438,20 +3670,20 @@ function createI3SMeshTilesetConversionSource(tileset, options = {}) {
3438
3670
  if (content.meshSegmentation || Object.entries(attributes).some(
3439
3671
  ([name, value]) => value && !["position", "normal", "uv0", "id", "faceRange"].includes(name)
3440
3672
  )) {
3441
- throw new TileConversionError21(
3673
+ throw new TileConversionError22(
3442
3674
  "I3S_MESH_SOURCE_ATTRIBUTE_UNSUPPORTED",
3443
3675
  "Only positions, normals, TEXCOORD_0 and explicitly mapped feature associations are supported"
3444
3676
  );
3445
3677
  }
3446
3678
  const hasFeatures = content.featureIds.length > 0 || metadata.tileset.fields?.length || metadata.tileset.attributeStorageInfo?.length;
3447
3679
  if (Array.from(content.featureIds).some((value) => !Number.isSafeInteger(value))) {
3448
- throw new TileConversionError21(
3680
+ throw new TileConversionError22(
3449
3681
  "I3S_MESH_SOURCE_FEATURE_ID_UNSUPPORTED",
3450
3682
  "Decoded numeric feature IDs must be exact safe integers"
3451
3683
  );
3452
3684
  }
3453
3685
  if (hasFeatures && !options.getFeatures && !options.features) {
3454
- throw new TileConversionError21(
3686
+ throw new TileConversionError22(
3455
3687
  "I3S_MESH_SOURCE_FEATURE_MAPPING_REQUIRED",
3456
3688
  "I3S feature data requires explicit resource features or a custom Arrow mapper"
3457
3689
  );
@@ -3470,7 +3702,7 @@ function createI3SMeshTilesetConversionSource(tileset, options = {}) {
3470
3702
  true
3471
3703
  );
3472
3704
  if (mesh.attributes.POSITION.value.length / 3 !== content.vertexCount) {
3473
- throw new TileConversionError21(
3705
+ throw new TileConversionError22(
3474
3706
  "I3S_MESH_SOURCE_COUNT_INVALID",
3475
3707
  "I3S vertexCount must match the decoded geometry"
3476
3708
  );
@@ -3486,7 +3718,7 @@ function createI3SMeshTilesetConversionSource(tileset, options = {}) {
3486
3718
  ) : void 0;
3487
3719
  signal?.throwIfAborted();
3488
3720
  if (hasFeatures && !features) {
3489
- throw new TileConversionError21(
3721
+ throw new TileConversionError22(
3490
3722
  "I3S_MESH_SOURCE_FEATURE_MAPPING_REQUIRED",
3491
3723
  "The feature mapper must return all declared feature data"
3492
3724
  );
@@ -3502,6 +3734,7 @@ function createI3SMeshTilesetConversionSource(tileset, options = {}) {
3502
3734
  yield {
3503
3735
  id: `${item.tile.id}/${entry.index}`,
3504
3736
  origin: [0, 0, 0],
3737
+ sourceLayerType: metadata.tileset.layerType,
3505
3738
  mesh,
3506
3739
  normalReferenceFrame: content.normalReferenceFrame === "vertex-reference-frame" ? "vertex-reference-frame" : "earth-centered",
3507
3740
  features,
@@ -3512,26 +3745,1584 @@ function createI3SMeshTilesetConversionSource(tileset, options = {}) {
3512
3745
  }
3513
3746
  };
3514
3747
  }
3748
+
3749
+ // src/v5/point-cloud-attributes.ts
3750
+ import { convertTableToMesh as convertTableToMesh3 } from "@loaders.gl/core/schema-utils";
3751
+ import { TileConversionError as TileConversionError23 } from "@loaders.gl/tile-converter/v5/core";
3752
+ function mapPointCloudAttributes(input, mapping) {
3753
+ const mesh = getPointCloudMesh(input);
3754
+ const attributes = /* @__PURE__ */ Object.create(null);
3755
+ const selected = /* @__PURE__ */ new Set();
3756
+ for (const [target, source] of Object.entries(mapping.attributes)) {
3757
+ if (!target || ["__proto__", "constructor", "prototype"].includes(target) || !Object.hasOwn(mesh.attributes, source))
3758
+ throw new TileConversionError23(
3759
+ "POINT_CLOUD_MAPPING_INVALID",
3760
+ `Invalid point attribute mapping ${target}: ${source}`
3761
+ );
3762
+ if (Object.hasOwn(mapping.drop || {}, source))
3763
+ throw new TileConversionError23(
3764
+ "POINT_CLOUD_MAPPING_INVALID",
3765
+ `Point attribute ${source} is both selected and dropped`
3766
+ );
3767
+ attributes[target] = { ...mesh.attributes[source] };
3768
+ selected.add(source);
3769
+ }
3770
+ const droppedAttributes = /* @__PURE__ */ Object.create(null);
3771
+ for (const [source, reason] of Object.entries(mapping.drop || {}).sort(
3772
+ ([left], [right]) => left < right ? -1 : left > right ? 1 : 0
3773
+ )) {
3774
+ if (!Object.hasOwn(mesh.attributes, source) || typeof reason !== "string" || !reason.trim())
3775
+ throw new TileConversionError23(
3776
+ "POINT_CLOUD_MAPPING_INVALID",
3777
+ `Invalid loss declaration for ${source}`
3778
+ );
3779
+ droppedAttributes[source] = reason;
3780
+ }
3781
+ const unmapped = Object.keys(mesh.attributes).filter(
3782
+ (name) => !selected.has(name) && !Object.hasOwn(droppedAttributes, name)
3783
+ );
3784
+ if (unmapped.length || !attributes.POSITION)
3785
+ throw new TileConversionError23(
3786
+ "POINT_CLOUD_MAPPING_REQUIRED",
3787
+ `Map POSITION and explicitly retain or drop every attribute: ${unmapped.join(", ")}`
3788
+ );
3789
+ return {
3790
+ mesh: { ...mesh, attributes, schema: { fields: [], metadata: { ...mesh.schema?.metadata } } },
3791
+ droppedAttributes
3792
+ };
3793
+ }
3794
+ function getPointCloudMesh(input, maxInputBytes = Infinity) {
3795
+ const buffers = /* @__PURE__ */ new Set();
3796
+ let bytes = 0;
3797
+ const countBuffer = (buffer) => {
3798
+ if (!buffers.has(buffer)) {
3799
+ buffers.add(buffer);
3800
+ bytes += buffer.byteLength;
3801
+ }
3802
+ if (bytes > maxInputBytes)
3803
+ throw new TileConversionError23("POINT_CLOUD_INPUT_BUDGET", "Point input byte budget exceeded");
3804
+ };
3805
+ if ("shape" in input) {
3806
+ const checkColumn = (column) => {
3807
+ if (column.nullCount)
3808
+ throw new TileConversionError23(
3809
+ "POINT_CLOUD_NULL_UNSUPPORTED",
3810
+ "Null point values require an explicit preprocessing step"
3811
+ );
3812
+ for (const buffer of Object.values(column.buffers)) if (buffer) countBuffer(buffer.buffer);
3813
+ for (const view of column.variadicBuffers) countBuffer(view.buffer);
3814
+ for (const child of column.children) checkColumn(child);
3815
+ for (const dictionary of column.dictionary?.data || []) checkColumn(dictionary);
3816
+ };
3817
+ for (const batch of input.data.batches) checkColumn(batch.data);
3818
+ } else
3819
+ for (const attribute of Object.values(input.attributes)) countBuffer(attribute.value.buffer);
3820
+ const mesh = "shape" in input ? convertTableToMesh3(input) : input;
3821
+ if (mesh.topology !== "point-list" || mesh.indices)
3822
+ throw new TileConversionError23(
3823
+ "POINT_CLOUD_INPUT_INVALID",
3824
+ "Decoded unindexed point-list input is required"
3825
+ );
3826
+ return mesh;
3827
+ }
3828
+
3829
+ // src/v5/point-cloud-copc.ts
3830
+ import { COPCWriter } from "@loaders.gl/tiles-3d/copc";
3831
+ import { TileConversionError as TileConversionError24 } from "@loaders.gl/tile-converter/v5/core";
3832
+ var SCALAR_RANGES = {
3833
+ intensity: [0, 65535],
3834
+ classification: [0, 255],
3835
+ nir: [0, 65535],
3836
+ gpsTime: [-Infinity, Infinity],
3837
+ scanAngle: [-32768, 32767],
3838
+ userData: [0, 255],
3839
+ pointSourceId: [0, 65535],
3840
+ returnNumber: [1, 15],
3841
+ numberOfReturns: [1, 15],
3842
+ scannerChannel: [0, 3],
3843
+ scanDirectionFlag: [0, 1],
3844
+ edgeOfFlightLine: [0, 1],
3845
+ synthetic: [0, 1],
3846
+ keyPoint: [0, 1],
3847
+ withheld: [0, 1],
3848
+ overlap: [0, 1]
3849
+ };
3850
+ async function encodePointCloudCOPC(input, options) {
3851
+ const maxInputBytes = options.maxInputBytes ?? 64 * 1024 * 1024;
3852
+ const maxPoints = options.maxPoints ?? 1e6;
3853
+ const maxOutputBytes = options.maxOutputBytes ?? 128 * 1024 * 1024;
3854
+ if ([maxInputBytes, maxPoints, maxOutputBytes].some(
3855
+ (value) => !Number.isSafeInteger(value) || value < 1
3856
+ ) || 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)
3857
+ throw new TileConversionError24(
3858
+ "POINT_CLOUD_COPC_OPTIONS_INVALID",
3859
+ "COPC requires explicit finite scale, offset, CRS, precision and positive budgets"
3860
+ );
3861
+ options.signal?.throwIfAborted();
3862
+ const source = getPointCloudMesh(input, maxInputBytes);
3863
+ const { mesh, droppedAttributes } = mapPointCloudAttributes(source, options.mapping);
3864
+ const position = mesh.attributes.POSITION;
3865
+ const pointCount = mesh.header?.vertexCount ?? position.value.length / 3;
3866
+ if (!Number.isSafeInteger(pointCount) || pointCount < 1 || pointCount > maxPoints)
3867
+ throw new TileConversionError24(
3868
+ "POINT_CLOUD_POINT_BUDGET",
3869
+ "COPC requires a nonempty batch within the point limit"
3870
+ );
3871
+ if (!(position.value instanceof Float32Array || position.value instanceof Float64Array) || position.size !== 3 || position.normalized)
3872
+ throw new TileConversionError24(
3873
+ "POINT_CLOUD_POSITION_INVALID",
3874
+ "COPC requires decoded floating XYZ"
3875
+ );
3876
+ const extraNames = new Set(options.extraBytes?.map((field) => field.attribute) || []);
3877
+ const descriptorNames = new Set(
3878
+ options.extraBytes?.map((field) => field.name || field.attribute) || []
3879
+ );
3880
+ if (extraNames.size !== (options.extraBytes?.length || 0) || descriptorNames.size !== extraNames.size || options.extraBytes?.some((field) => {
3881
+ const name = field.name || field.attribute;
3882
+ const description = field.description || "";
3883
+ return !mesh.attributes[field.attribute] || !field.attribute || !name.length || name.length > 32 || description.length > 32 || /[^\x20-\x7e]/.test(name) || /[^\x20-\x7e]/.test(description);
3884
+ }))
3885
+ throw new TileConversionError24(
3886
+ "POINT_CLOUD_EXTRA_BYTES_INVALID",
3887
+ "Extra Bytes descriptors require unique mapped attributes and names/descriptions within 32 printable ASCII bytes"
3888
+ );
3889
+ for (const name of extraNames)
3890
+ if (name === "POSITION" || name === "COLOR_0" || Object.hasOwn(SCALAR_RANGES, name))
3891
+ throw new TileConversionError24(
3892
+ "POINT_CLOUD_EXTRA_BYTES_INVALID",
3893
+ "Map standard attributes directly instead of duplicating them as Extra Bytes"
3894
+ );
3895
+ for (const [name, attribute] of Object.entries(mesh.attributes)) {
3896
+ const color2 = name === "COLOR_0";
3897
+ const extra = extraNames.has(name);
3898
+ if (name !== "POSITION" && !color2 && !extra && !Object.hasOwn(SCALAR_RANGES, name))
3899
+ throw new TileConversionError24(
3900
+ "POINT_CLOUD_ATTRIBUTE_UNSUPPORTED",
3901
+ `COPC cannot preserve ${name}; supply an explicit supported mapping or drop reason`
3902
+ );
3903
+ 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))
3904
+ throw new TileConversionError24(
3905
+ "POINT_CLOUD_ATTRIBUTE_INVALID",
3906
+ `COPC requires packed decoded attributes: ${name}`
3907
+ );
3908
+ if (color2) {
3909
+ if (attribute.size !== 3 || !(attribute.value instanceof Uint8Array || attribute.value instanceof Uint16Array))
3910
+ throw new TileConversionError24(
3911
+ "POINT_CLOUD_COLOR_INVALID",
3912
+ "COPC requires explicitly mapped uint8/uint16 RGB"
3913
+ );
3914
+ } else if (extra) {
3915
+ for (const value of attribute.value)
3916
+ if (typeof value !== "bigint" && !Number.isFinite(value))
3917
+ throw new TileConversionError24(
3918
+ "POINT_CLOUD_ATTRIBUTE_RANGE",
3919
+ `Nonfinite Extra Bytes value: ${name}`
3920
+ );
3921
+ } else if (name !== "POSITION") {
3922
+ const [minimum2, maximum2] = SCALAR_RANGES[name];
3923
+ for (const value of attribute.value) {
3924
+ if (typeof value !== "number" || !Number.isFinite(value) || value < minimum2 || value > maximum2 || name !== "gpsTime" && !Number.isInteger(value))
3925
+ throw new TileConversionError24(
3926
+ "POINT_CLOUD_ATTRIBUTE_RANGE",
3927
+ `COPC value outside the qualified range: ${name}`
3928
+ );
3929
+ }
3930
+ }
3931
+ }
3932
+ let maximumPositionError = 0;
3933
+ const minimum = [Infinity, Infinity, Infinity];
3934
+ const maximum = [-Infinity, -Infinity, -Infinity];
3935
+ const errors = [0, 0, 0];
3936
+ for (let row = 0; row < pointCount; row++) {
3937
+ for (let axis = 0; axis < 3; axis++) {
3938
+ const value = position.value[row * 3 + axis];
3939
+ const encoded = Math.round((value - options.offset[axis]) / options.scale[axis]);
3940
+ if (!Number.isFinite(value) || !Number.isSafeInteger(encoded) || encoded < -2147483648 || encoded > 2147483647)
3941
+ throw new TileConversionError24(
3942
+ "POINT_CLOUD_POSITION_RANGE",
3943
+ "COPC position exceeds signed int32 quantization"
3944
+ );
3945
+ minimum[axis] = Math.min(minimum[axis], value);
3946
+ maximum[axis] = Math.max(maximum[axis], value);
3947
+ errors[axis] = encoded * options.scale[axis] + options.offset[axis] - value;
3948
+ }
3949
+ maximumPositionError = Math.max(maximumPositionError, Math.hypot(...errors));
3950
+ if (maximumPositionError > options.maxPositionError)
3951
+ throw new TileConversionError24(
3952
+ "POINT_CLOUD_PRECISION_EXCEEDED",
3953
+ "COPC position rounding exceeds the declared error gate"
3954
+ );
3955
+ if (row % 16384 === 0) {
3956
+ await new Promise((resolve) => setTimeout(resolve, 0));
3957
+ options.signal?.throwIfAborted();
3958
+ }
3959
+ }
3960
+ const width = Math.max(...maximum.map((value, axis) => value - minimum[axis]), ...options.scale);
3961
+ const radius = width / 2;
3962
+ if (!Number.isFinite(Math.hypot(radius, radius, radius)) || minimum.some((value, axis) => {
3963
+ const center = value + (maximum[axis] - value) / 2;
3964
+ return !Number.isFinite(value + maximum[axis]) || !Number.isFinite(center - radius) || !Number.isFinite(center + radius);
3965
+ }))
3966
+ throw new TileConversionError24("POINT_CLOUD_BOUNDS_INVALID", "COPC coordinate extent overflow");
3967
+ options.signal?.throwIfAborted();
3968
+ const color = mesh.attributes.COLOR_0;
3969
+ const copc = COPCWriter.encodeSync(mesh, {
3970
+ copc: {
3971
+ ...options.organization,
3972
+ scale: [...options.scale],
3973
+ offset: [...options.offset],
3974
+ wkt: options.wkt,
3975
+ pointDataRecordFormat: mesh.attributes.nir ? 8 : color ? 7 : 6,
3976
+ colorDepth: color?.value instanceof Uint16Array ? 16 : 8,
3977
+ extraBytes: options.extraBytes
3978
+ }
3979
+ });
3980
+ options.signal?.throwIfAborted();
3981
+ if (copc.byteLength > maxOutputBytes)
3982
+ throw new TileConversionError24(
3983
+ "POINT_CLOUD_OUTPUT_BUDGET",
3984
+ "Completed COPC output exceeds the byte budget"
3985
+ );
3986
+ return { copc, pointCount, maximumPositionError, droppedAttributes };
3987
+ }
3988
+ function createCOPCConversionCodec(options) {
3989
+ return {
3990
+ async *convert(input, _inspection, signal) {
3991
+ const combinedSignal = options.signal && signal ? AbortSignal.any([options.signal, signal]) : options.signal || signal;
3992
+ yield await encodePointCloudCOPC(input, { ...options, signal: combinedSignal });
3993
+ }
3994
+ };
3995
+ }
3996
+
3997
+ // src/v5/point-cloud-tileset.ts
3998
+ import { PointCloudTileSource } from "@loaders.gl/tiles-3d/point-cloud-tile-source";
3999
+ import { TileConversionError as TileConversionError25 } from "@loaders.gl/tile-converter/v5/core";
4000
+ async function convertPointCloudToTileset(input, options) {
4001
+ const reference = options.spatialReference;
4002
+ 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")) {
4003
+ throw new TileConversionError25(
4004
+ "POINT_CLOUD_TILESET_FRAME_UNSUPPORTED",
4005
+ "Point authoring requires explicit native EPSG:4978 xyz meters and ellipsoidal heights"
4006
+ );
4007
+ }
4008
+ if (options.archive && (!Number.isSafeInteger(options.archive.maxArchiveBytes) || options.archive.maxArchiveBytes < 0)) {
4009
+ throw new TileConversionError25(
4010
+ "INVALID_ARCHIVE_BYTE_LIMIT",
4011
+ "Archive byte limit must be a non-negative safe integer"
4012
+ );
4013
+ }
4014
+ options.signal?.throwIfAborted();
4015
+ const sink = createPointCloudTilesetSink(options);
4016
+ let source;
4017
+ try {
4018
+ source = new PointCloudTileSource(input, {
4019
+ pointCloudTiler: { ...options.tiling, signal: options.signal, spatialReference: reference }
4020
+ });
4021
+ const { pointCount } = await source.getMetadata();
4022
+ const report = await convertPointCloudSource(source, {
4023
+ sink,
4024
+ signal: options.signal,
4025
+ onProgress: options.onProgress,
4026
+ measureInputBytes: (tile) => {
4027
+ const table = tile.content?.data.data;
4028
+ return table ? table.schema.fields.reduce(
4029
+ (bytes, _field, index) => bytes + (table.getChildAt(index)?.byteLength ?? 0),
4030
+ 0
4031
+ ) : 0;
4032
+ },
4033
+ getTileEncodingOptions: (tile) => ({
4034
+ rtcCenter: [
4035
+ tile.header.boundingVolume.center[0],
4036
+ tile.header.boundingVolume.center[1],
4037
+ tile.header.boundingVolume.center[2]
4038
+ ],
4039
+ maxPositionError: options.geometricError
4040
+ })
4041
+ });
4042
+ options.signal?.throwIfAborted();
4043
+ const files = sink.getFiles();
4044
+ const archive = options.archive ? await createTileConversionArchive(files, {
4045
+ format: "3tz",
4046
+ maxArchiveBytes: options.archive.maxArchiveBytes,
4047
+ signal: options.signal
4048
+ }) : void 0;
4049
+ options.signal?.throwIfAborted();
4050
+ return { pointCount, report, files, archive };
4051
+ } catch (error) {
4052
+ await sink.abort(error);
4053
+ throw error;
4054
+ } finally {
4055
+ source?.close();
4056
+ }
4057
+ }
4058
+
4059
+ // src/v5/point-cloud-file.ts
4060
+ import { COPCSourceLoader } from "@loaders.gl/tiles-3d/copc";
4061
+ import { PotreeSourceLoader, encodePotreeDataset } from "@loaders.gl/tiles-3d/potree";
4062
+ import { deduceMeshSchema } from "@loaders.gl/core/schema-utils";
4063
+ import { encodeZip, openZipArchive } from "@loaders.gl/archives/zip";
4064
+ async function encodePotreeArchive(input, options) {
4065
+ validateLimit(options.maxArchiveBytes);
4066
+ const dataset = await encodePotreeDataset(input, { potree: options.potree });
4067
+ return encodePotreeDatasetArchive(dataset, options.maxArchiveBytes, options.potree?.signal);
4068
+ }
4069
+ async function encodePotreeDatasetArchive(dataset, maxArchiveBytes, signal) {
4070
+ validateLimit(maxArchiveBytes);
4071
+ for (const path of dataset.files.keys()) validatePath(path);
4072
+ signal?.throwIfAborted();
4073
+ const archive = await encodeZip(Object.fromEntries(dataset.files), {
4074
+ compression: "store",
4075
+ zip64: "never",
4076
+ maxEntries: 65535,
4077
+ maxArchiveBytes: BigInt(maxArchiveBytes),
4078
+ signal
4079
+ });
4080
+ signal?.throwIfAborted();
4081
+ return new Blob([archive], { type: "application/zip" });
4082
+ }
4083
+ async function openPointCloudFile(input, options) {
4084
+ validateLimit(options.maxFileBytes);
4085
+ if (input.size > options.maxFileBytes) throw new RangeError("Point file input budget exceeded");
4086
+ options.signal?.throwIfAborted();
4087
+ if (options.format === "copc") {
4088
+ const source2 = COPCSourceLoader.createDataSource(input, options.sourceOptions || {});
4089
+ try {
4090
+ await source2.metadata;
4091
+ options.signal?.throwIfAborted();
4092
+ return { source: source2, close: () => source2.close() };
4093
+ } catch (error) {
4094
+ await source2.close();
4095
+ throw error;
4096
+ }
4097
+ }
4098
+ if (options.format !== "potree-zip") throw new TypeError("Unsupported point file format");
4099
+ const maxEntries = options.maxEntries ?? 1e5;
4100
+ const maxResourceBytes = options.maxResourceBytes ?? 64 * 1024 * 1024;
4101
+ validateLimit(maxEntries);
4102
+ validateLimit(maxResourceBytes);
4103
+ const controller = new AbortController();
4104
+ const signals = [controller.signal, options.signal, options.sourceOptions?.potree?.signal].filter(
4105
+ (value) => Boolean(value)
4106
+ );
4107
+ const signal = AbortSignal.any(signals);
4108
+ let archive;
4109
+ const entries = /* @__PURE__ */ new Map();
4110
+ let source;
4111
+ try {
4112
+ archive = await openZipArchive(input, {
4113
+ maxArchiveBytes: BigInt(options.maxFileBytes),
4114
+ maxEntries,
4115
+ maxReadBytes: Math.max(65557, maxResourceBytes),
4116
+ maxCompressedBytes: BigInt(maxResourceBytes),
4117
+ maxDecodedBytes: BigInt(maxResourceBytes),
4118
+ signal
4119
+ });
4120
+ for await (const entry of archive.entries()) {
4121
+ if (entry.directory) continue;
4122
+ validatePath(entry.path);
4123
+ if (entry.compression !== "store")
4124
+ throw new Error("Potree bundles require unencrypted STORE entries");
4125
+ entries.set(entry.path, entry);
4126
+ }
4127
+ const candidates = ["metadata.json", "cloud.js"].filter((path) => entries.has(path));
4128
+ const metadataPath = options.metadataPath || (candidates.length === 1 ? candidates[0] : "");
4129
+ validatePath(metadataPath);
4130
+ if (!entries.has(metadataPath) || !/(^|\/)(metadata\.json|cloud\.js)$/.test(metadataPath))
4131
+ throw new Error("Potree ZIP requires one metadata entry or an explicit metadataPath");
4132
+ const prefix = "https://point-bundle.loaders.gl.invalid/";
4133
+ const fetchResource = async (resource, request = {}) => {
4134
+ signal.throwIfAborted();
4135
+ request.signal?.throwIfAborted();
4136
+ const url = new URL(resource);
4137
+ if (url.origin !== new URL(prefix).origin || url.search || url.hash)
4138
+ throw new Error("Resource outside the Potree bundle");
4139
+ const path = decodeURIComponent(url.pathname.slice(1));
4140
+ validatePath(path);
4141
+ const entry = entries.get(path);
4142
+ if (!entry) throw new Error(`Missing Potree bundle resource: ${path}`);
4143
+ const range = new Headers(request.headers).get("range");
4144
+ const match = range?.match(/^bytes=(\d+)-(\d+)$/);
4145
+ if (range && !match) throw new RangeError("Unsupported Potree bundle byte range");
4146
+ const begin = match ? Number(match[1]) : 0;
4147
+ const end = match ? Number(match[2]) + 1 : Number(entry.size);
4148
+ if (!Number.isSafeInteger(begin) || !Number.isSafeInteger(end) || begin < 0 || end < begin || BigInt(end) > entry.size || end - begin > maxResourceBytes)
4149
+ throw new RangeError("Potree bundle resource/range budget exceeded");
4150
+ const headers = new Headers({
4151
+ "content-length": String(end - begin),
4152
+ "accept-ranges": "bytes"
4153
+ });
4154
+ if (match) headers.set("content-range", `bytes ${begin}-${end - 1}/${entry.size}`);
4155
+ const bytes = match ? await archive.readRange(entry, {
4156
+ start: BigInt(begin),
4157
+ end: BigInt(end),
4158
+ signal: request.signal ?? void 0
4159
+ }) : await archive.read(entry, { signal: request.signal ?? void 0 });
4160
+ const response = new Response(bytes, {
4161
+ status: match ? 206 : 200,
4162
+ headers
4163
+ });
4164
+ Object.defineProperty(response, "url", { value: url.href });
4165
+ signal.throwIfAborted();
4166
+ return response;
4167
+ };
4168
+ source = PotreeSourceLoader.createDataSource(`${prefix}${metadataPath}`, {
4169
+ ...options.sourceOptions,
4170
+ core: { ...options.sourceOptions?.core, fetch: fetchResource },
4171
+ potree: { ...options.sourceOptions?.potree, signal }
4172
+ });
4173
+ await source.initialize();
4174
+ signal.throwIfAborted();
4175
+ return {
4176
+ source,
4177
+ async close() {
4178
+ controller.abort();
4179
+ source?.close();
4180
+ await archive?.close();
4181
+ }
4182
+ };
4183
+ } catch (error) {
4184
+ controller.abort();
4185
+ source?.close();
4186
+ await archive?.close();
4187
+ throw error;
4188
+ }
4189
+ }
4190
+ function extractMeshVertices(mesh, options) {
4191
+ validateLimit(options.maxPoints);
4192
+ const position = mesh.attributes.POSITION;
4193
+ const pointCount = position?.value.length / 3;
4194
+ 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)))
4195
+ throw new Error(
4196
+ "Mesh vertex harvesting requires packed finite triangle XYZ within the point budget"
4197
+ );
4198
+ const attributes = { POSITION: position };
4199
+ for (const name of options.attributes) {
4200
+ const attribute = mesh.attributes[name];
4201
+ if (!attribute || attribute.transform || attribute.byteStride || attribute.byteOffset || !Number.isSafeInteger(attribute.size) || attribute.size < 1 || attribute.value.length !== pointCount * attribute.size)
4202
+ throw new Error(`Invalid selected mesh vertex attribute: ${name}`);
4203
+ attributes[name] = attribute;
4204
+ }
4205
+ return { topology: "point-list", mode: 0, attributes, schema: deduceMeshSchema(attributes) };
4206
+ }
4207
+ function validatePath(path) {
4208
+ if (!path || !/^[A-Za-z0-9_.\/-]+$/.test(path) || path.split("/").some((part) => !part || part === "." || part === ".."))
4209
+ throw new Error("Potree bundle paths must be canonical relative ASCII paths");
4210
+ }
4211
+ function validateLimit(value) {
4212
+ if (!Number.isSafeInteger(value) || value < 1)
4213
+ throw new RangeError("Point file limits must be positive safe integers");
4214
+ }
4215
+
4216
+ // src/v5/point-cloud-potree.ts
4217
+ import { PointCloudTiler } from "@loaders.gl/core/schema-utils";
4218
+ import { encodePotreeDataset as encodePotreeDataset2 } from "@loaders.gl/tiles-3d/potree";
4219
+ async function convertPointCloudToPotree(input, options) {
4220
+ if (typeof options.projection !== "string" || !options.projection.trim()) {
4221
+ throw new Error("Potree authoring requires an explicit native CRS declaration");
4222
+ }
4223
+ for (const limit of [
4224
+ options.maxGatheredBytes,
4225
+ options.maxOutputBytes,
4226
+ options.archive?.maxArchiveBytes
4227
+ ]) {
4228
+ if (limit !== void 0 && (!Number.isSafeInteger(limit) || limit < 1)) {
4229
+ throw new RangeError("Potree authoring byte limits must be positive safe integers");
4230
+ }
4231
+ }
4232
+ if (options.maxGatheredBytes === void 0 || options.maxOutputBytes === void 0) {
4233
+ throw new RangeError("Potree authoring requires gathered and encoded byte limits");
4234
+ }
4235
+ options.signal?.throwIfAborted();
4236
+ const tiler = new PointCloudTiler(input, {
4237
+ ...options.tiling,
4238
+ maxNodes: Math.min(options.tiling?.maxNodes ?? 1e5, options.potree?.maxNodes ?? 1e5),
4239
+ maxTileBytes: Math.min(
4240
+ options.tiling?.maxTileBytes ?? 64 * 1024 * 1024,
4241
+ options.maxGatheredBytes
4242
+ ),
4243
+ signal: options.signal
4244
+ });
4245
+ const nodes = [];
4246
+ let gatheredBytes = 0;
4247
+ let pointByteLength = 0;
4248
+ let pointCount;
4249
+ try {
4250
+ const pending = [await tiler.getRootNode()];
4251
+ pointCount = tiler.pointCount;
4252
+ while (pending.length) {
4253
+ options.signal?.throwIfAborted();
4254
+ const node = pending.pop();
4255
+ if (node.pointCount * pointByteLength > options.maxGatheredBytes - gatheredBytes) {
4256
+ throw new RangeError("Potree gathered node byte budget exceeded");
4257
+ }
4258
+ const mesh = await tiler.getTileMesh(node.id);
4259
+ const nodeBytes = Array.from(
4260
+ new Set(Object.values(mesh.attributes).map((attribute) => attribute.value.buffer))
4261
+ ).reduce((bytes, buffer) => bytes + buffer.byteLength, 0);
4262
+ if (nodeBytes > options.maxGatheredBytes - gatheredBytes) {
4263
+ throw new RangeError("Potree gathered node byte budget exceeded");
4264
+ }
4265
+ if (node.pointCount) pointByteLength = nodeBytes / node.pointCount;
4266
+ gatheredBytes += nodeBytes;
4267
+ nodes.push({ id: node.id, mesh });
4268
+ const children = await tiler.getChildNodes(node.id);
4269
+ for (let index = children.length - 1; index >= 0; index--) pending.push(children[index]);
4270
+ }
4271
+ } finally {
4272
+ tiler.close();
4273
+ }
4274
+ const dataset = await encodePotreeDataset2(nodes, {
4275
+ potree: {
4276
+ ...options.potree,
4277
+ projection: options.projection,
4278
+ maxOutputBytes: options.maxOutputBytes,
4279
+ signal: options.signal
4280
+ }
4281
+ });
4282
+ const archive = options.archive ? await encodePotreeDatasetArchive(dataset, options.archive.maxArchiveBytes, options.signal) : void 0;
4283
+ options.signal?.throwIfAborted();
4284
+ return { pointCount, nodeCount: nodes.length, dataset, archive };
4285
+ }
4286
+
4287
+ // src/v5/mesh-hierarchy.ts
4288
+ import { Matrix4 as Matrix43 } from "@math.gl/core";
4289
+ import { get3DTilesSpatialReference } from "@loaders.gl/tiles-3d";
4290
+ import {
4291
+ TileConversionError as TileConversionError26,
4292
+ createBoundedMemoryTileConversionSink as createBoundedMemoryTileConversionSink5,
4293
+ createTiles3DConversionSpatialContext
4294
+ } from "@loaders.gl/tile-converter/v5/core";
4295
+ function prepareMeshHierarchy(inspection, resourceIds) {
4296
+ const rejectHierarchy = (message) => {
4297
+ throw new TileConversionError26("MESH_HIERARCHY_UNSUPPORTED", message);
4298
+ };
4299
+ if (!inspection.documents?.length || inspection.documents.length > 32)
4300
+ rejectHierarchy(
4301
+ "Hierarchy preservation requires expanded metadata inspection of at most 32 explicit document placements"
4302
+ );
4303
+ 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)))
4304
+ rejectHierarchy(
4305
+ "Hierarchy preservation requires every content placement, without duplicates (at most 64)"
4306
+ );
4307
+ const entryTransform = new Matrix43();
4308
+ 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]))
4309
+ rejectHierarchy("The entry document must have identity parent placement");
4310
+ const documents = inspection.documents.map((placement) => placement.tileset);
4311
+ const document = documents[0];
4312
+ const externalDocuments = /* @__PURE__ */ new Map();
4313
+ for (let index = 1; index < inspection.documents.length; index++) {
4314
+ const identifier = inspection.documents[index].parentResourceId;
4315
+ if (!identifier || externalDocuments.has(identifier))
4316
+ rejectHierarchy(
4317
+ "Every external document requires a distinct inspected parent content placement"
4318
+ );
4319
+ externalDocuments.set(identifier, index);
4320
+ }
4321
+ for (const document2 of documents) {
4322
+ 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"))
4323
+ rejectHierarchy(
4324
+ "Dataset metadata and unmapped extensions require a separate hierarchy profile"
4325
+ );
4326
+ if (!["1.0", "1.1"].includes(document2.asset?.version ?? ""))
4327
+ rejectHierarchy("Hierarchy preservation requires a 3D Tiles 1.0 or 1.1 document");
4328
+ if (document2.geometricError !== void 0 && (typeof document2.geometricError !== "number" || !Number.isFinite(document2.geometricError) || document2.geometricError < 0))
4329
+ rejectHierarchy("Dataset geometricError must be finite nonnegative meters");
4330
+ }
4331
+ const spatialReferences = documents.map(
4332
+ (document2) => get3DTilesSpatialReference(document2)
4333
+ );
4334
+ const spatialReference = createTiles3DConversionSpatialContext(
4335
+ spatialReferences[0]
4336
+ ).spatialReference;
4337
+ if (spatialReference.status !== "native" || spatialReference.sourceCrs !== "EPSG:4978" || spatialReference.heightReference !== "ellipsoidal" || spatialReference.axisOrder !== "xyz" || spatialReference.verticalUnitScale !== 1 || spatialReference.units?.some((unit) => unit !== "meter"))
4338
+ rejectHierarchy(
4339
+ "Hierarchy preservation requires native EPSG:4978 xyz meters and ellipsoidal heights"
4340
+ );
4341
+ for (const reference of spatialReferences.slice(1)) {
4342
+ const inherited = reference.crs.state === "absent" && reference.coordinateEpoch === void 0 && !reference.verticalCrs && !reference.warnings.length;
4343
+ const context = createTiles3DConversionSpatialContext(inherited ? spatialReference : reference);
4344
+ 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)
4345
+ rejectHierarchy("External document CRS and coordinate epoch must match the entry frame");
4346
+ }
4347
+ const nodes = [];
4348
+ const visitedDocuments = /* @__PURE__ */ new Set([0]);
4349
+ const stack = [
4350
+ {
4351
+ tile: document.root,
4352
+ documentIndex: 0,
4353
+ prefix: "",
4354
+ path: [],
4355
+ parent: -1,
4356
+ transform: new Matrix43(),
4357
+ refine: void 0
4358
+ }
4359
+ ];
4360
+ while (stack.length) {
4361
+ const {
4362
+ tile,
4363
+ path,
4364
+ parent,
4365
+ documentIndex,
4366
+ prefix,
4367
+ transform: parentTransform,
4368
+ refine: inheritedRefine
4369
+ } = stack.pop();
4370
+ const document2 = documents[documentIndex];
4371
+ if (nodes.length >= 256) rejectHierarchy("Hierarchy preservation supports at most 256 tiles");
4372
+ 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))
4373
+ rejectHierarchy(
4374
+ "Hierarchy tiles require explicit children, exclusive content/contents declarations and no unmapped metadata, extensions or request volumes"
4375
+ );
4376
+ const refine = tile.refine ?? inheritedRefine;
4377
+ if (refine !== "ADD" && refine !== "REPLACE")
4378
+ rejectHierarchy("Root refinement must be explicit ADD or REPLACE; children may inherit");
4379
+ const matrix = tile.transform;
4380
+ 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))
4381
+ rejectHierarchy("Hierarchy transforms must be finite affine matrices");
4382
+ const transform = new Matrix43(parentTransform).multiplyRight(matrix ?? new Matrix43());
4383
+ const columns = [0, 1, 2].map(
4384
+ (column) => [0, 1, 2].reduce((sum, row) => sum + Math.abs(transform[column * 4 + row]), 0)
4385
+ );
4386
+ const rows = [0, 1, 2].map(
4387
+ (row) => [0, 1, 2].reduce((sum, column) => sum + Math.abs(transform[column * 4 + row]), 0)
4388
+ );
4389
+ const scale = document2.asset?.version === "1.0" ? 1 : Math.sqrt(Math.max(...columns)) * Math.sqrt(Math.max(...rows));
4390
+ const geometricError2 = (tile.geometricError ?? NaN) * scale;
4391
+ if (typeof tile.geometricError !== "number" || (tile.geometricError ?? -1) < 0 || !Number.isFinite(geometricError2) || Array.from(transform).some((value) => !Number.isFinite(value)))
4392
+ rejectHierarchy("Every hierarchy tile must declare a finite nonnegative metric error");
4393
+ const contents = tile.contents ?? (tile.content ? [tile.content] : []);
4394
+ const identifiers = contents.map(
4395
+ (_, contentIndex) => `${prefix}tile-${path.length ? path.join("-") : "root"}-content-${contentIndex}`
4396
+ );
4397
+ const externalIndexes = identifiers.map((identifier) => externalDocuments.get(identifier));
4398
+ const externalIndex = externalIndexes.find((index2) => index2 !== void 0);
4399
+ for (const [contentIndex, content] of contents.entries()) {
4400
+ if (!content || typeof content !== "object" || Array.isArray(content) || content.metadata !== void 0 || content.group !== void 0 || content.extensions !== void 0)
4401
+ rejectHierarchy("Content metadata, groups and extensions require a separate mapping");
4402
+ if (externalIndexes[contentIndex] === void 0 && !inspection.resources.some(
4403
+ (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])
4404
+ ))
4405
+ rejectHierarchy("Hierarchy content must match the inspected placement");
4406
+ }
4407
+ const resourceId = identifiers[0];
4408
+ if (externalIndex !== void 0) {
4409
+ if (contents.length !== 1 || tile.contents !== void 0 || tile.children?.length || visitedDocuments.has(externalIndex))
4410
+ rejectHierarchy(
4411
+ "External references require a single content, no source children and a unique document placement"
4412
+ );
4413
+ visitedDocuments.add(externalIndex);
4414
+ const placement = inspection.documents[externalIndex];
4415
+ if (!Array.isArray(placement.parentTransform) || placement.parentTransform.length !== 16 || placement.parentTransform.some((value, index2) => value !== transform[index2]))
4416
+ rejectHierarchy("External document placement must match its referencing tile transform");
4417
+ const contentUri = tile.content?.uri ?? tile.content?.url;
4418
+ if (!contentUri || !new URL(contentUri, inspection.documents[documentIndex].url).pathname.toLowerCase().endsWith(".json"))
4419
+ rejectHierarchy("External documents require an inspected .json content reference");
4420
+ }
4421
+ const index = nodes.length;
4422
+ nodes.push({
4423
+ children: [],
4424
+ resourceIds: externalIndex === void 0 ? identifiers : [],
4425
+ geometricError: Math.max(
4426
+ geometricError2,
4427
+ externalIndex === void 0 ? 0 : documents[externalIndex].geometricError ?? 0
4428
+ ),
4429
+ refine
4430
+ });
4431
+ if (parent !== -1) nodes[parent].children.push(index);
4432
+ if (externalIndex !== void 0) {
4433
+ stack.push({
4434
+ tile: documents[externalIndex].root,
4435
+ path: [],
4436
+ parent: index,
4437
+ documentIndex: externalIndex,
4438
+ prefix: resourceId + "/",
4439
+ transform,
4440
+ refine: void 0
4441
+ });
4442
+ }
4443
+ if (nodes.length + stack.length + (tile.children?.length ?? 0) > 256)
4444
+ rejectHierarchy("Hierarchy preservation supports at most 256 tiles");
4445
+ for (let child = (tile.children?.length ?? 0) - 1; child >= 0; child--)
4446
+ stack.push({
4447
+ tile: tile.children[child],
4448
+ documentIndex,
4449
+ prefix,
4450
+ path: [...path, child],
4451
+ parent: index,
4452
+ transform,
4453
+ refine
4454
+ });
4455
+ }
4456
+ 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))
4457
+ rejectHierarchy("Contentless leaves or unmatched content placements cannot be preserved");
4458
+ const geometricError = document.geometricError ?? nodes[0].geometricError;
4459
+ if (typeof geometricError !== "number" || !Number.isFinite(geometricError) || geometricError < 0)
4460
+ rejectHierarchy("Dataset geometricError must be finite nonnegative meters");
4461
+ return {
4462
+ nodes,
4463
+ geometricError: Math.max(geometricError, nodes[0].geometricError),
4464
+ coordinateEpoch: spatialReference.coordinateEpoch
4465
+ };
4466
+ }
4467
+ function createMeshHierarchyTilesetSink(options) {
4468
+ const hierarchy = prepareMeshHierarchy(options.inspection, options.resourceIds);
4469
+ const extensions = structuredClone(
4470
+ options.inspection.tileset.extensions
4471
+ );
4472
+ const extensionsRequired = structuredClone(
4473
+ options.inspection.tileset.extensionsRequired
4474
+ );
4475
+ const { maxPositionError } = options;
4476
+ if (!Number.isFinite(maxPositionError) || maxPositionError < 0 || hierarchy.nodes.some((node) => !Number.isFinite(node.geometricError + maxPositionError)) || !Number.isFinite(hierarchy.geometricError + maxPositionError))
4477
+ throw new TileConversionError26(
4478
+ "MESH_GEOMETRIC_ERROR_INVALID",
4479
+ "Hierarchy position allowance and resulting errors must be finite nonnegative meters"
4480
+ );
4481
+ const memory = createBoundedMemoryTileConversionSink5(options);
4482
+ const placements = /* @__PURE__ */ new Map();
4483
+ const completedContents = /* @__PURE__ */ new Set();
4484
+ const primitiveIdentifiers = /* @__PURE__ */ new Set();
4485
+ let state = "open";
4486
+ let completed = false;
4487
+ return {
4488
+ /** Exposes files only after complete finalization. */
4489
+ getFiles: () => completed ? memory.getFiles() : [],
4490
+ /** Retains ordered codec outputs and requires a terminal primitive for each content. */
4491
+ async write(resource, signal) {
4492
+ signal?.throwIfAborted();
4493
+ const identifier = resource.contentResourceId;
4494
+ const { index: primitiveIndex, last: lastPrimitive } = resource.contentPrimitive ?? {
4495
+ index: 0,
4496
+ last: true
4497
+ };
4498
+ 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)))
4499
+ throw new TileConversionError26(
4500
+ "MESH_HIERARCHY_PLACEMENT_INVALID",
4501
+ "Hierarchy writes require ordered, distinct primitives for declared contents before close (at most 64)"
4502
+ );
4503
+ if (resource.spatialReference.coordinateEpoch !== hierarchy.coordinateEpoch)
4504
+ throw new TileConversionError26(
4505
+ "MESH_SOURCE_EPOCH_MISMATCH",
4506
+ "Hierarchy output coordinate epoch must match its source"
4507
+ );
4508
+ const tile = createMeshTileset(resource, maxPositionError).root;
4509
+ const nodeIndex = hierarchy.nodes.findIndex((node2) => node2.resourceIds.includes(identifier));
4510
+ const node = hierarchy.nodes[nodeIndex];
4511
+ const sibling = node.resourceIds.map((identifier2) => placements.get(identifier2)?.[0]).find(Boolean);
4512
+ if (sibling && sibling.origin.some((value, axis) => value !== resource.origin[axis]))
4513
+ throw new TileConversionError26(
4514
+ "MESH_HIERARCHY_PLACEMENT_INVALID",
4515
+ "Contents on one tile must share an encoded origin"
4516
+ );
4517
+ const suffix = node.resourceIds.length > 1 ? "-" + node.resourceIds.indexOf(identifier) : "";
4518
+ const primitiveSuffix = primitiveIndex > 0 || !lastPrimitive ? "-primitive-" + primitiveIndex : "";
4519
+ const uri = `meshes/${nodeIndex}${suffix}${primitiveSuffix}.glb`;
4520
+ state = "writing";
4521
+ await memory.write(
4522
+ { resourceId: uri, parts: [resource.glb], contentType: "model/gltf-binary" },
4523
+ signal
4524
+ );
4525
+ const contentPlacements = placements.get(identifier) ?? [];
4526
+ contentPlacements.push({
4527
+ transform: [...tile.transform],
4528
+ origin: [...resource.origin],
4529
+ localBoundingBox: resource.localBoundingBox.map((bound) => [...bound]),
4530
+ uri
4531
+ });
4532
+ placements.set(identifier, contentPlacements);
4533
+ primitiveIdentifiers.add(resource.id);
4534
+ if (lastPrimitive) completedContents.add(identifier);
4535
+ state = "open";
4536
+ },
4537
+ /** Publishes original topology only after every source content has completed. */
4538
+ async finalize(report) {
4539
+ if (state !== "open" || completedContents.size !== hierarchy.nodes.reduce((count, node) => count + node.resourceIds.length, 0))
4540
+ throw new TileConversionError26(
4541
+ "MESH_HIERARCHY_INCOMPLETE",
4542
+ "Finalization requires every declared hierarchy content"
4543
+ );
4544
+ state = "writing";
4545
+ const tiles = new Array(hierarchy.nodes.length);
4546
+ const bounds = new Array(hierarchy.nodes.length);
4547
+ const transforms = hierarchy.nodes.map((node) => {
4548
+ const placement = placements.get(node.resourceIds[0])?.[0];
4549
+ return placement ? new Matrix43(placement.transform) : new Matrix43();
4550
+ });
4551
+ for (let index = hierarchy.nodes.length - 1; index >= 0; index--) {
4552
+ const node = hierarchy.nodes[index];
4553
+ const placement = placements.get(node.resourceIds[0])?.[0];
4554
+ const minimum = [Infinity, Infinity, Infinity];
4555
+ const maximum = [-Infinity, -Infinity, -Infinity];
4556
+ const contributors = node.children.map((child) => bounds[child]);
4557
+ const nodePlacements = node.resourceIds.flatMap((identifier) => placements.get(identifier));
4558
+ for (const placement2 of nodePlacements) {
4559
+ contributors.push(
4560
+ placement2.localBoundingBox.map(
4561
+ (bound, boundIndex) => bound.map((value, axis) => {
4562
+ const padding = 8 * Number.EPSILON * Math.max(
4563
+ 1,
4564
+ Math.abs(placement2.origin[axis]),
4565
+ ...placement2.localBoundingBox.map((limit) => Math.abs(limit[axis]))
4566
+ );
4567
+ return value + placement2.origin[axis] + (boundIndex ? padding : -padding);
4568
+ })
4569
+ )
4570
+ );
4571
+ }
4572
+ for (const [lower, upper] of contributors)
4573
+ for (let axis = 0; axis < 3; axis++) {
4574
+ minimum[axis] = Math.min(minimum[axis], lower[axis]);
4575
+ maximum[axis] = Math.max(maximum[axis], upper[axis]);
4576
+ }
4577
+ if ([...minimum, ...maximum].some((value) => !Number.isFinite(value)))
4578
+ throw new TileConversionError26(
4579
+ "MESH_TILESET_BOUNDS_INVALID",
4580
+ "Hierarchy absolute bounds must remain finite"
4581
+ );
4582
+ bounds[index] = [minimum, maximum];
4583
+ const inverse = new Matrix43(transforms[index]).invert();
4584
+ const center = Array.from(
4585
+ inverse.transformAsPoint(minimum.map((value, axis) => value / 2 + maximum[axis] / 2))
4586
+ );
4587
+ const halfSize = minimum.map((value, axis) => maximum[axis] / 2 - value / 2);
4588
+ const halfAxes = [0, 1, 2].flatMap(
4589
+ (axis) => Array.from(
4590
+ inverse.transformAsVector(
4591
+ halfSize.map((value, coordinate) => coordinate === axis ? value : 0)
4592
+ )
4593
+ )
4594
+ );
4595
+ if ([...center, ...halfAxes].some((value) => !Number.isFinite(value)))
4596
+ throw new TileConversionError26(
4597
+ "MESH_TILESET_BOUNDS_INVALID",
4598
+ "Hierarchy local bounds must remain finite"
4599
+ );
4600
+ tiles[index] = {
4601
+ transform: Array.from(transforms[index]),
4602
+ boundingVolume: { box: [...center, ...halfAxes] },
4603
+ geometricError: node.geometricError + maxPositionError,
4604
+ refine: node.refine,
4605
+ ...nodePlacements.length > 1 ? { contents: nodePlacements.map((placement2) => ({ uri: placement2.uri })) } : placement ? { content: { uri: placement.uri } } : {},
4606
+ ...node.children.length ? {
4607
+ children: node.children.map((child) => {
4608
+ tiles[child].transform = Array.from(
4609
+ new Matrix43(inverse).multiplyRight(transforms[child])
4610
+ );
4611
+ if (tiles[child].transform.some((value) => !Number.isFinite(value)))
4612
+ throw new TileConversionError26(
4613
+ "MESH_TILESET_BOUNDS_INVALID",
4614
+ "Relative hierarchy transforms must remain finite"
4615
+ );
4616
+ return tiles[child];
4617
+ })
4618
+ } : {}
4619
+ };
4620
+ }
4621
+ const json = JSON.stringify({
4622
+ asset: { version: "1.1" },
4623
+ ...extensions && Object.keys(extensions).length ? { extensions, extensionsUsed: Object.keys(extensions) } : {},
4624
+ ...extensionsRequired?.length ? { extensionsRequired } : {},
4625
+ geometricError: hierarchy.geometricError + maxPositionError,
4626
+ root: tiles[0]
4627
+ });
4628
+ await memory.write({
4629
+ resourceId: "tileset.json",
4630
+ parts: [json],
4631
+ contentType: "application/json"
4632
+ });
4633
+ await memory.finalize(report);
4634
+ state = "closed";
4635
+ completed = true;
4636
+ },
4637
+ /** Discards retained resources and hides all files after failure. */
4638
+ async abort(error) {
4639
+ state = "closed";
4640
+ completed = false;
4641
+ placements.clear();
4642
+ completedContents.clear();
4643
+ primitiveIdentifiers.clear();
4644
+ await memory.abort(error);
4645
+ }
4646
+ };
4647
+ }
4648
+
4649
+ // src/v5/selected-mesh-source.ts
4650
+ import { coreApi, parse } from "@loaders.gl/core";
4651
+ import { Tiles3DLoader } from "@loaders.gl/tiles-3d/3d-tiles";
4652
+ import { Tiles3DSource, Tileset3D } from "@loaders.gl/tiles-3d";
4653
+ import {
4654
+ createBrowserTilesetConversionSource,
4655
+ selectBrowserTilesetContent,
4656
+ createTiles3DConversionSpatialContext as createTiles3DConversionSpatialContext2,
4657
+ TileConversionError as TileConversionError27
4658
+ } from "@loaders.gl/tile-converter/v5/core";
4659
+ import {
4660
+ createMeshTilesetConversionSource as createMeshTilesetConversionSource2,
4661
+ createTileConversionResourceFetcher as createTileConversionResourceFetcher2
4662
+ } from "@loaders.gl/tile-converter/v5/adapters";
4663
+ var SELECTED_MESH_LIMITS = {
4664
+ /** Root JSON plus selected content transport bytes; also the decoded geometry/image/feature byte gate. */
4665
+ maxInputBytes: 16 * 1024 * 1024,
4666
+ /** Maximum declared content placements inspected. */
4667
+ maxInputResources: 1e3,
4668
+ /** Maximum inspected root/external document placements. */
4669
+ maxTilesetDocuments: 32,
4670
+ /** Maximum explicitly selected content placements in one partial archive. */
4671
+ maxSelectedResources: 64,
4672
+ /** Maximum primitive placements authored across all selected contents. */
4673
+ maxMeshResources: 64,
4674
+ /** Maximum retained file bytes and final archive bytes. */
4675
+ maxOutputBytes: 32 * 1024 * 1024,
4676
+ /** Maximum measured reconstruction error in meters. */
4677
+ maxPositionError: 0.01
4678
+ };
4679
+ async function createSelectedMeshConversionSource(inspection, options) {
4680
+ const {
4681
+ resourceIds,
4682
+ signal,
4683
+ dracoLibraryOptions,
4684
+ features,
4685
+ preserveHierarchy,
4686
+ fetcher = fetch
4687
+ } = options;
4688
+ const maxInputBytes = options.maxInputBytes ?? SELECTED_MESH_LIMITS.maxInputBytes;
4689
+ if (!Number.isSafeInteger(maxInputBytes) || maxInputBytes < 1 || maxInputBytes > SELECTED_MESH_LIMITS.maxInputBytes)
4690
+ throw new TileConversionError27(
4691
+ "INVALID_SELECTED_MESH_OPTIONS",
4692
+ "Selected mesh input budget must be a positive safe integer at most 16 MiB"
4693
+ );
4694
+ if (!resourceIds.length || resourceIds.length > SELECTED_MESH_LIMITS.maxSelectedResources || new Set(resourceIds).size !== resourceIds.length || resourceIds.some(
4695
+ (identifier) => !inspection.resources.some((resource) => resource.resourceId === identifier)
4696
+ ))
4697
+ throw new Error("Select between 1 and 64 distinct content placements.");
4698
+ if (preserveHierarchy !== void 0 && typeof preserveHierarchy !== "boolean")
4699
+ throw new TileConversionError27(
4700
+ "MESH_HIERARCHY_UNSUPPORTED",
4701
+ "preserveHierarchy must be a boolean"
4702
+ );
4703
+ if (preserveHierarchy) prepareMeshHierarchy(inspection, resourceIds);
4704
+ const descriptors = inspection.resources.filter(
4705
+ (resource) => resourceIds.includes(resource.resourceId)
4706
+ );
4707
+ const selections = descriptors.map(
4708
+ (descriptor) => selectBrowserTilesetContent(inspection, descriptor)
4709
+ );
4710
+ const selectedContents = new Map(
4711
+ descriptors.map((descriptor, index) => [descriptor.resourceId, selections[index]])
4712
+ );
4713
+ if (!preserveHierarchy && resourceIds.length > 1 && selections.some((selection) => !selection.isLeaf))
4714
+ throw new Error(
4715
+ "Multi-selection requires leaf contents; source LOD hierarchy is not exported."
4716
+ );
4717
+ signal.throwIfAborted();
4718
+ const boundedFetcher = createTileConversionResourceFetcher2({
4719
+ maxInputBytes,
4720
+ initialInputBytes: inspection.tilesetBytes,
4721
+ fetcher,
4722
+ signal
4723
+ });
4724
+ const qualification = createSelectedRuntime(
4725
+ inspection.tileset,
4726
+ new Uint8Array(),
4727
+ signal,
4728
+ boundedFetcher,
4729
+ inspection.rootUrl,
4730
+ dracoLibraryOptions
4731
+ );
4732
+ let spatialContext;
4733
+ try {
4734
+ await qualification.tilesetInitializationPromise;
4735
+ const metadata = await createMeshTilesetConversionSource2(qualification).inspect(signal);
4736
+ spatialContext = createTiles3DConversionSpatialContext2(metadata.spatialReference);
4737
+ } finally {
4738
+ qualification.destroy();
4739
+ }
4740
+ const rawSource = options.source ?? createBrowserTilesetConversionSource({
4741
+ input: inspection.rootUrl,
4742
+ ...SELECTED_MESH_LIMITS,
4743
+ maxInputBytes,
4744
+ fetcher: boundedFetcher
4745
+ });
4746
+ const selectedInspection = { ...inspection, resources: descriptors };
4747
+ const source = {
4748
+ /** Reuses inspected declarations without fetching the root again. */
4749
+ inspect: async (inspectionSignal) => {
4750
+ signal.throwIfAborted();
4751
+ inspectionSignal?.throwIfAborted();
4752
+ return selectedInspection;
4753
+ },
4754
+ /** Decodes and releases each selected placement in declaration order. */
4755
+ async *read(_inspection, readSignal) {
4756
+ const signal2 = readSignal ? AbortSignal.any([options.signal, readSignal]) : options.signal;
4757
+ signal2.throwIfAborted();
4758
+ let decodedBytes = 0;
4759
+ let primitiveCount = 0;
4760
+ const tileOrigins = /* @__PURE__ */ new Map();
4761
+ for await (const raw of rawSource.read(selectedInspection, signal2)) {
4762
+ const selection = selectedContents.get(raw.resourceId);
4763
+ if (!selection)
4764
+ throw new TileConversionError27(
4765
+ "INVALID_BROWSER_SELECTION",
4766
+ "Source yielded an uninspected content placement"
4767
+ );
4768
+ const contentUrl = raw.contentUrl ?? selection.contentUrl;
4769
+ const runtime = createSelectedRuntime(
4770
+ selection.tileset,
4771
+ raw.data,
4772
+ signal2,
4773
+ boundedFetcher,
4774
+ contentUrl,
4775
+ dracoLibraryOptions,
4776
+ spatialContext.spatialReference
4777
+ );
4778
+ try {
4779
+ await runtime.tilesetInitializationPromise;
4780
+ const meshSource = createMeshTilesetConversionSource2(runtime, {
4781
+ unloadContent: true,
4782
+ features,
4783
+ readExternalResource: async (uri, _contentUri, resourceSignal) => {
4784
+ const response = await boundedFetcher(new URL(uri, contentUrl), {
4785
+ signal: resourceSignal
4786
+ });
4787
+ return new Uint8Array(await response.arrayBuffer());
4788
+ }
4789
+ });
4790
+ const metadata = await meshSource.inspect(signal2);
4791
+ const selectedContext = createTiles3DConversionSpatialContext2(metadata.spatialReference);
4792
+ if (selectedContext.spatialReference.coordinateEpoch !== spatialContext.spatialReference.coordinateEpoch)
4793
+ throw new TileConversionError27(
4794
+ "MESH_SOURCE_EPOCH_MISMATCH",
4795
+ "Selected content coordinate epoch must match the root; epoch transformations are unsupported"
4796
+ );
4797
+ let meshCount = 0;
4798
+ for await (const resource of meshSource.read(metadata, signal2)) {
4799
+ meshCount++;
4800
+ if (++primitiveCount > SELECTED_MESH_LIMITS.maxMeshResources)
4801
+ throw new Error("Selected contents exceed the mesh primitive placement limit.");
4802
+ decodedBytes += measureMeshBytes(resource);
4803
+ if (decodedBytes > maxInputBytes)
4804
+ throw new TileConversionError27(
4805
+ "INPUT_RESOURCE_TOO_LARGE",
4806
+ "Selected decoded geometry and encoded images exceed the aggregate input byte limit."
4807
+ );
4808
+ const tileIdentifier = raw.resourceId.replace(/content-\d+$/, "");
4809
+ if (preserveHierarchy && !tileOrigins.has(tileIdentifier))
4810
+ tileOrigins.set(tileIdentifier, [...resource.origin]);
4811
+ yield {
4812
+ ...resource,
4813
+ origin: preserveHierarchy ? tileOrigins.get(tileIdentifier) : resource.origin,
4814
+ id: `${raw.resourceId}/${resource.id}`,
4815
+ contentResourceId: raw.resourceId
4816
+ };
4817
+ }
4818
+ if (!meshCount) throw new Error("Selected content has no mesh primitive.");
4819
+ } finally {
4820
+ runtime.destroy();
4821
+ }
4822
+ }
4823
+ }
4824
+ };
4825
+ return {
4826
+ source,
4827
+ spatialContext,
4828
+ geometricError: Math.max(...selections.map((selection) => selection.geometricError)) + SELECTED_MESH_LIMITS.maxPositionError
4829
+ };
4830
+ }
4831
+ var SelectedTiles3DSource = class extends Tiles3DSource {
4832
+ /** Parent frame; explicit, unknown or unresolved child declarations are never replaced. */
4833
+ inheritedSpatialReference;
4834
+ /** Creates a selected source with optional external-document frame inheritance. */
4835
+ constructor(input, options, inheritedSpatialReference) {
4836
+ super(input, options);
4837
+ this.inheritedSpatialReference = inheritedSpatialReference;
4838
+ }
4839
+ /** Returns source metadata with the same absent-CRS inheritance used by external tilesets. */
4840
+ getMetadata() {
4841
+ const metadata = super.getMetadata();
4842
+ const reference = metadata.spatialReference;
4843
+ if (this.inheritedSpatialReference && reference?.crs.state === "absent" && reference.coordinateEpoch === void 0 && !reference.verticalCrs && !reference.warnings.length)
4844
+ return { ...metadata, spatialReference: this.inheritedSpatialReference };
4845
+ return metadata;
4846
+ }
4847
+ };
4848
+ function createSelectedRuntime(document, data, signal, boundedFetcher, contentUrl, dracoLibraryOptions, inheritedSpatialReference) {
4849
+ const {
4850
+ modules,
4851
+ decoderProfile,
4852
+ CDN: contentDeliveryNetwork,
4853
+ useLocalLibraries
4854
+ } = dracoLibraryOptions;
4855
+ const loadOptions = {
4856
+ modules,
4857
+ core: {
4858
+ worker: false,
4859
+ fetch: boundedFetcher,
4860
+ CDN: contentDeliveryNetwork,
4861
+ useLocalLibraries
4862
+ },
4863
+ // Reuse the full decoder already bundled for output verification.
4864
+ draco: { decoderProfile },
4865
+ // Preserve authored UVs and the transform for the converter's qualified GLB writer.
4866
+ gltf: {
4867
+ loadImages: false,
4868
+ decompressMeshes: true,
4869
+ excludeExtensions: {
4870
+ KHR_texture_transform: false,
4871
+ // Only Draco input compression is qualified by this conversion profile.
4872
+ EXT_meshopt_compression: false,
4873
+ KHR_meshopt_compression: false
4874
+ }
4875
+ },
4876
+ "3d-tiles": { loadGLTF: true }
4877
+ };
4878
+ return new Tileset3D(
4879
+ new SelectedTiles3DSource(
4880
+ {
4881
+ url: contentUrl,
4882
+ loader: Tiles3DLoader,
4883
+ resolver: {
4884
+ /** Normalizes the selected hierarchy through the format loader, retaining ancestor transforms. */
4885
+ loadRoot: async (_url, loader, options) => parse(new TextEncoder().encode(JSON.stringify(document)), loader, options),
4886
+ /** Decodes only the selected bounded payload; no sibling content can be requested. */
4887
+ loadResource: async (_url, loader, options) => {
4888
+ signal.throwIfAborted();
4889
+ return parse(data, loader, options, {
4890
+ url: contentUrl,
4891
+ baseUrl: contentUrl.startsWith("blob:") ? contentUrl : new URL(".", contentUrl).href,
4892
+ fetch: boundedFetcher,
4893
+ coreApi,
4894
+ _parse: parse
4895
+ });
4896
+ }
4897
+ }
4898
+ },
4899
+ loadOptions,
4900
+ inheritedSpatialReference
4901
+ )
4902
+ );
4903
+ }
4904
+ function measureMeshBytes(resource) {
4905
+ const featureBytes = resource.features ? resource.features.triangleFeatureIndices.byteLength + resource.features.batches.reduce(
4906
+ (bytes, batch) => bytes + batch.data.schema.fields.reduce(
4907
+ (columnBytes, field) => columnBytes + (batch.data.getChild(field.name)?.byteLength ?? 0),
4908
+ 0
4909
+ ),
4910
+ 0
4911
+ ) : 0;
4912
+ return Object.values(resource.mesh.attributes).reduce(
4913
+ (bytes, attribute) => bytes + attribute.value.byteLength,
4914
+ (resource.mesh.indices?.value.byteLength ?? 0) + (resource.material?.baseColorTexture?.data.byteLength ?? 0) + featureBytes
4915
+ );
4916
+ }
4917
+
4918
+ // src/v5/archive-source.ts
4919
+ import { BlobFile, HttpFile } from "@loaders.gl/core/loader-utils";
4920
+ import { parseSLPKArchive } from "@loaders.gl/tiles-3d/i3s";
4921
+ import { parse3DTilesArchive } from "@loaders.gl/tiles-3d/3d-tiles";
4922
+ import { TileConversionError as TileConversionError28 } from "@loaders.gl/tile-converter/v5/core";
4923
+ async function openTileConversionArchive(descriptor, maxInputBytes, signal, fetcher = fetch) {
4924
+ signal.throwIfAborted();
4925
+ if (!["slpk", "3tz"].includes(descriptor.format))
4926
+ throw new TileConversionError28("INVALID_ARCHIVE_SOURCE", "Select SLPK or 3TZ archive input");
4927
+ validateArchiveEntityTag(descriptor.identity?.etag);
4928
+ const transport = createTileConversionResourceFetcher({
4929
+ maxInputBytes,
4930
+ signal,
4931
+ fetcher: async (url, options) => {
4932
+ const response = await fetcher(url, options);
4933
+ if (response.status !== 206) {
4934
+ await response.body?.cancel();
4935
+ throw new Error("Remote conversion archives require HTTP 206 byte-range responses.");
4936
+ }
4937
+ try {
4938
+ validateArchiveResponseValidator(response.headers);
4939
+ } catch (error) {
4940
+ await response.body?.cancel(error);
4941
+ throw error;
4942
+ }
4943
+ return response;
4944
+ }
4945
+ });
4946
+ let file;
4947
+ let closePromise;
4948
+ async function closeFile() {
4949
+ if (file) {
4950
+ closePromise ??= file.close();
4951
+ await closePromise;
4952
+ }
4953
+ }
4954
+ try {
4955
+ if (typeof descriptor.input === "string") {
4956
+ if (!["http:", "https:"].includes(new URL(descriptor.input).protocol))
4957
+ throw new Error("Use an HTTP(S) archive URL.");
4958
+ const remote = new HttpFile(descriptor.input, {
4959
+ fetch: (url, options) => transport(url, options),
4960
+ consistency: "strict",
4961
+ ...descriptor.identity
4962
+ });
4963
+ file = remote;
4964
+ await remote.open(signal);
4965
+ } else file = new BlobFile(descriptor.input);
4966
+ const ownedFile = file;
4967
+ file = void 0;
4968
+ return await createTileConversionArchiveTransport(
4969
+ ownedFile,
4970
+ descriptor.format,
4971
+ maxInputBytes,
4972
+ signal,
4973
+ descriptor
4974
+ );
4975
+ } catch (error) {
4976
+ await closeFile();
4977
+ throw error;
4978
+ }
4979
+ }
4980
+ async function createTileConversionArchiveTransport(file, format, maxInputBytes, signal, descriptor) {
4981
+ const rootUrl = `https://conversion.invalid/input.${format}`;
4982
+ let closed = false;
4983
+ let releasePromise;
4984
+ let closePromise;
4985
+ const closeFile = () => {
4986
+ closed = true;
4987
+ closePromise ??= file.close();
4988
+ return closePromise;
4989
+ };
4990
+ const checkOpen = () => {
4991
+ signal.throwIfAborted();
4992
+ if (closed)
4993
+ throw new TileConversionError28("ARCHIVE_SOURCE_CLOSED", "Conversion archive is closed");
4994
+ };
4995
+ try {
4996
+ checkOpen();
4997
+ if (!["slpk", "3tz"].includes(format) || !Number.isSafeInteger(maxInputBytes) || maxInputBytes < 1)
4998
+ throw new TileConversionError28(
4999
+ "INVALID_ARCHIVE_SOURCE",
5000
+ "Archive source requires a supported format and positive safe byte budget"
5001
+ );
5002
+ if (!Number.isSafeInteger(file.size) || file.size < 0 || file.size > maxInputBytes)
5003
+ throw new Error("Conversion archive input must be at most 16 MiB.");
5004
+ let readBytes = 0;
5005
+ const boundedFile = {
5006
+ handle: file.handle,
5007
+ size: file.size,
5008
+ bigsize: file.bigsize,
5009
+ url: file.url,
5010
+ ...file.stat ? { stat: () => file.stat() } : {},
5011
+ close: closeFile,
5012
+ /** Checks indexed reads before allocation and forwards operation cancellation. */
5013
+ read: async (start, length) => {
5014
+ checkOpen();
5015
+ if (!Number.isSafeInteger(length) || length < 0 || length > maxInputBytes - readBytes)
5016
+ throw new TileConversionError28(
5017
+ "INPUT_RESOURCE_TOO_LARGE",
5018
+ "Archive indexed reads exceed the input budget"
5019
+ );
5020
+ readBytes += length;
5021
+ const bytes = await file.read(start, length, signal);
5022
+ checkOpen();
5023
+ return bytes;
5024
+ }
5025
+ };
5026
+ const archive = format === "slpk" ? await parseSLPKArchive(boundedFile) : await parse3DTilesArchive(boundedFile);
5027
+ const root = new URL(rootUrl);
5028
+ return {
5029
+ rootUrl: format === "3tz" ? `${rootUrl}/tileset.json` : rootUrl,
5030
+ descriptor: descriptor ? {
5031
+ ...descriptor,
5032
+ ...file instanceof HttpFile ? { identity: file.getIdentitySnapshot() } : {}
5033
+ } : void 0,
5034
+ /** Resolves only resources belonging to this archive, without external network fallback. */
5035
+ fetcher: (async (input, options) => {
5036
+ checkOpen();
5037
+ options?.signal?.throwIfAborted();
5038
+ const url = new URL(input instanceof Request ? input.url : String(input));
5039
+ if (url.origin !== root.origin || url.pathname !== root.pathname && !url.pathname.startsWith(`${root.pathname}/`))
5040
+ throw new Error("Conversion dependencies must resolve inside the selected archive.");
5041
+ const path = decodeURIComponent(
5042
+ url.pathname.slice(root.pathname.length).replace(/^\//, "")
5043
+ );
5044
+ if (path.includes("\\") || path.split("/").some((segment) => segment === ".." || segment === "."))
5045
+ throw new Error("Archive dependency path cannot escape its root.");
5046
+ const bytes = format === "slpk" ? await archive.getFile(path, "http") : await archive.getFile(path);
5047
+ checkOpen();
5048
+ options?.signal?.throwIfAborted();
5049
+ return new Response(bytes);
5050
+ }),
5051
+ /** Releases the readable file on success, failure or cancellation. */
5052
+ close: async () => {
5053
+ closed = true;
5054
+ releasePromise ??= archive.close();
5055
+ await releasePromise;
5056
+ }
5057
+ };
5058
+ } catch (error) {
5059
+ await closeFile();
5060
+ throw error;
5061
+ }
5062
+ }
5063
+ function validateArchiveEntityTag(entityTag) {
5064
+ if (entityTag !== void 0 && entityTag !== null && !/^"[\x21\x23-\x7e\x80-\xff]*"$/.test(entityTag))
5065
+ throw new TileConversionError28(
5066
+ "ARCHIVE_VALIDATOR_UNSUPPORTED",
5067
+ "Remote archives require a strong quoted ETag, or Last-Modified without an ETag; weak/malformed ETags are unsupported"
5068
+ );
5069
+ }
5070
+ function validateArchiveResponseValidator(headers) {
5071
+ const entityTag = headers.get("etag");
5072
+ validateArchiveEntityTag(entityTag);
5073
+ if (entityTag !== null) return;
5074
+ const lastModified = headers.get("last-modified");
5075
+ const responseDate = headers.get("date");
5076
+ const modifiedTime = parseArchiveHttpDate(lastModified);
5077
+ const responseTime = parseArchiveHttpDate(responseDate);
5078
+ if (!Number.isFinite(modifiedTime) || !Number.isFinite(responseTime) || responseTime - modifiedTime < 6e4)
5079
+ throw new TileConversionError28(
5080
+ "ARCHIVE_VALIDATOR_UNSUPPORTED",
5081
+ "Last-Modified archive identity requires a valid Date at least 60 seconds later; otherwise supply a strong ETag"
5082
+ );
5083
+ }
5084
+ function parseArchiveHttpDate(value) {
5085
+ const milliseconds = value === null ? NaN : Date.parse(value);
5086
+ return Number.isFinite(milliseconds) && new Date(milliseconds).toUTCString() === value ? milliseconds : NaN;
5087
+ }
5088
+
5089
+ // src/v5/i3s-selection-source.ts
5090
+ import { coreApi as coreApi2, parse as parse2 } from "@loaders.gl/core";
5091
+ import { I3SLoader } from "@loaders.gl/tiles-3d/i3s";
5092
+ import { I3SSource, Tileset3D as Tileset3D2 } from "@loaders.gl/tiles-3d";
5093
+ import {
5094
+ createI3SConversionSpatialContext,
5095
+ TileConversionError as TileConversionError29
5096
+ } from "@loaders.gl/tile-converter/v5/core";
5097
+ function createRuntime(transport, layer, header) {
5098
+ const libraries = transport.dracoLibraryOptions;
5099
+ const sourceUrl = new URL(transport.rootUrl);
5100
+ sourceUrl.search = "";
5101
+ sourceUrl.hash = "";
5102
+ const options = {
5103
+ modules: libraries.modules,
5104
+ core: {
5105
+ worker: false,
5106
+ fetch: transport.fetcher,
5107
+ CDN: libraries.CDN,
5108
+ useLocalLibraries: libraries.useLocalLibraries
5109
+ },
5110
+ draco: { decoderProfile: libraries.decoderProfile },
5111
+ // The injected transport owns credential scope; runtime query parameters apply to every URL.
5112
+ i3s: { geometryMode: "source", decodeTextures: false, useCompressedTextures: false }
5113
+ };
5114
+ const loadResource = async (url, loader, loadOptions) => {
5115
+ const response = await transport.fetcher(url);
5116
+ const contextUrl = new URL(url);
5117
+ contextUrl.search = "";
5118
+ contextUrl.hash = "";
5119
+ return parse2(await response.arrayBuffer(), loader, loadOptions, {
5120
+ url: contextUrl.href,
5121
+ fetch: transport.fetcher,
5122
+ coreApi: coreApi2,
5123
+ _parse: parse2
5124
+ });
5125
+ };
5126
+ const source = new I3SSource(
5127
+ header && layer ? {
5128
+ ...layer,
5129
+ root: { ...header, children: [] },
5130
+ type: "I3S",
5131
+ url: sourceUrl.href,
5132
+ basePath: sourceUrl.href,
5133
+ loader: I3SLoader,
5134
+ coreApi: coreApi2
5135
+ } : {
5136
+ url: sourceUrl.href,
5137
+ loader: I3SLoader,
5138
+ coreApi: coreApi2,
5139
+ resolver: { loadRoot: loadResource, loadResource }
5140
+ },
5141
+ options
5142
+ );
5143
+ source.coreApi = { ...coreApi2, load: loadResource };
5144
+ return new Tileset3D2(source, { i3s: options.i3s });
5145
+ }
5146
+ async function inspectI3SMeshConversionSource(options) {
5147
+ const { signal } = options;
5148
+ validateSelectionOptions(options);
5149
+ signal.throwIfAborted();
5150
+ const transport = options;
5151
+ let runtime;
5152
+ try {
5153
+ const response = await transport.fetcher(transport.rootUrl);
5154
+ const layer = await response.json();
5155
+ if (!["3DObject", "IntegratedMesh"].includes(layer.layerType))
5156
+ throw new Error("Conversion accepts I3S 3DObject or IntegratedMesh layers.");
5157
+ const rootFetcher = transport.fetcher;
5158
+ const cachedFetcher = (url, options2) => {
5159
+ const requestUrl = new URL(url instanceof Request ? url.url : String(url));
5160
+ const root = new URL(transport.rootUrl);
5161
+ return requestUrl.origin === root.origin && requestUrl.pathname === root.pathname ? Promise.resolve(new Response(JSON.stringify(layer))) : rootFetcher(url, options2);
5162
+ };
5163
+ runtime = createRuntime({ ...transport, fetcher: cachedFetcher });
5164
+ await runtime.tilesetInitializationPromise;
5165
+ await createI3SMeshTilesetConversionSource(runtime).inspect(signal);
5166
+ const stack = [runtime.root];
5167
+ const identifiers = /* @__PURE__ */ new Set();
5168
+ const resources = [];
5169
+ while (stack.length) {
5170
+ signal.throwIfAborted();
5171
+ const tile = stack.pop();
5172
+ if (!["string", "number"].includes(typeof tile.header.id) || String(tile.header.id).length === 0)
5173
+ throw new Error("I3S nodes require a declared identity.");
5174
+ const identifier = String(tile.header.id);
5175
+ if (identifiers.has(identifier))
5176
+ throw new Error("Repeated I3S node identity is unsupported.");
5177
+ identifiers.add(identifier);
5178
+ if (identifiers.size + stack.length + (tile.header.children?.length ?? 0) > (options.maxInputResources ?? SELECTED_MESH_LIMITS.maxInputResources))
5179
+ throw new Error("I3S inspection exceeds the 1,000 node limit.");
5180
+ await runtime.source.loadTileChildrenForTraversal(tile, signal);
5181
+ if (!tile.children.length && tile.contentUrl)
5182
+ resources.push({
5183
+ resourceId: identifier,
5184
+ uri: tile.contentUrl,
5185
+ header: structuredClone(tile.header)
5186
+ });
5187
+ for (const child of [...tile.children].reverse()) stack.push(child);
5188
+ }
5189
+ signal.throwIfAborted();
5190
+ if (!resources.length) throw new Error("No supported leaf mesh nodes are declared.");
5191
+ return {
5192
+ kind: "i3s",
5193
+ rootUrl: transport.rootUrl,
5194
+ layer,
5195
+ resources,
5196
+ metadataBytes: 0
5197
+ };
5198
+ } finally {
5199
+ runtime?.destroy();
5200
+ }
5201
+ }
5202
+ async function createSelectedI3SMeshConversionSource(inspection, options) {
5203
+ const { resourceIds, signal, features } = options;
5204
+ validateSelectionOptions(options);
5205
+ signal.throwIfAborted();
5206
+ if (inspection.rootUrl !== options.rootUrl)
5207
+ throw new TileConversionError29(
5208
+ "INVALID_I3S_SELECTION",
5209
+ "Selection transport must match the inspected layer"
5210
+ );
5211
+ if (!resourceIds.length || resourceIds.length > SELECTED_MESH_LIMITS.maxSelectedResources || new Set(resourceIds).size !== resourceIds.length || resourceIds.some(
5212
+ (identifier) => !inspection.resources.some((resource) => resource.resourceId === identifier)
5213
+ ))
5214
+ throw new Error("Select between 1 and 64 distinct leaf mesh nodes.");
5215
+ const selected = inspection.resources.filter(
5216
+ (resource) => resourceIds.includes(resource.resourceId)
5217
+ );
5218
+ if (selected.some(
5219
+ (resource) => resource.header.children?.length || !resource.header.contentUrl || resource.uri !== resource.header.contentUrl
5220
+ ))
5221
+ throw new Error("Only inspected leaf mesh headers can be converted.");
5222
+ const transport = options;
5223
+ let decodedBytes = 0;
5224
+ let meshCount = 0;
5225
+ {
5226
+ const qualification = createRuntime(transport, inspection.layer, selected[0].header);
5227
+ let spatialContext;
5228
+ try {
5229
+ await qualification.tilesetInitializationPromise;
5230
+ const metadata = await createI3SMeshTilesetConversionSource(qualification).inspect(signal);
5231
+ spatialContext = createI3SConversionSpatialContext(metadata.spatialReference, {
5232
+ targetCrs: "EPSG:4978"
5233
+ });
5234
+ } finally {
5235
+ qualification.destroy();
5236
+ }
5237
+ const source = {
5238
+ /** Reuses only immutable inspected declarations. */
5239
+ inspect: async () => inspection,
5240
+ /** Releases each dedicated node runtime before moving on or closing early. */
5241
+ async *read() {
5242
+ for (const descriptor of selected) {
5243
+ const runtime = createRuntime(transport, inspection.layer, descriptor.header);
5244
+ try {
5245
+ await runtime.tilesetInitializationPromise;
5246
+ const meshSource = createI3SMeshTilesetConversionSource(runtime, {
5247
+ unloadContent: true,
5248
+ features,
5249
+ readExternalResource: async (uri) => new Uint8Array(
5250
+ await (await transport.fetcher(runtime.source.getTileUrl(uri))).arrayBuffer()
5251
+ )
5252
+ });
5253
+ const metadata = await meshSource.inspect(signal);
5254
+ for await (const resource of meshSource.read(metadata, signal)) {
5255
+ decodedBytes += measureMeshBytes(resource);
5256
+ if (++meshCount > SELECTED_MESH_LIMITS.maxMeshResources || decodedBytes > (options.maxInputBytes ?? SELECTED_MESH_LIMITS.maxInputBytes))
5257
+ throw new TileConversionError29(
5258
+ "INPUT_RESOURCE_TOO_LARGE",
5259
+ "Selected I3S decoded resources exceed the input/mesh budget"
5260
+ );
5261
+ yield { ...resource, id: descriptor.resourceId };
5262
+ }
5263
+ } finally {
5264
+ runtime.destroy();
5265
+ }
5266
+ }
5267
+ }
5268
+ };
5269
+ return { source, spatialContext };
5270
+ }
5271
+ }
5272
+ function validateSelectionOptions(options) {
5273
+ for (const [value, maximum] of [
5274
+ [
5275
+ options.maxInputBytes ?? SELECTED_MESH_LIMITS.maxInputBytes,
5276
+ SELECTED_MESH_LIMITS.maxInputBytes
5277
+ ],
5278
+ [
5279
+ options.maxInputResources ?? SELECTED_MESH_LIMITS.maxInputResources,
5280
+ SELECTED_MESH_LIMITS.maxInputResources
5281
+ ]
5282
+ ])
5283
+ if (!Number.isSafeInteger(value) || value < 1 || value > maximum)
5284
+ throw new TileConversionError29(
5285
+ "INVALID_I3S_SELECTION_LIMIT",
5286
+ "I3S selection limits must be positive safe integers within the bounded profile"
5287
+ );
5288
+ }
3515
5289
  export {
5290
+ SELECTED_MESH_LIMITS,
3516
5291
  convertFeatureAttributesToArrowBatches,
3517
5292
  convertPointCloudSource,
5293
+ convertPointCloudToPotree,
5294
+ convertPointCloudToTileset,
5295
+ createCOPCConversionCodec,
3518
5296
  createI3SMeshConversionCodec,
3519
5297
  createI3SMeshSink,
3520
5298
  createI3SMeshTilesetConversionSource,
3521
5299
  createMeshConversionCodec,
5300
+ createMeshHierarchyTilesetSink,
5301
+ createMeshSourceFeatureOptions,
3522
5302
  createMeshTilesetConversionSource,
3523
5303
  createMeshTilesetSink,
3524
5304
  createPointCloudTilesetSink,
5305
+ createSelectedI3SMeshConversionSource,
5306
+ createSelectedMeshConversionSource,
3525
5307
  createSingleMeshI3SSink,
3526
5308
  createSingleMeshTilesetArchive,
3527
5309
  createSingleMeshTilesetSink,
3528
5310
  createTileConversionArchive,
5311
+ createTileConversionArchiveTransport,
3529
5312
  createTileConversionResourceFetcher,
3530
5313
  encodeMeshTile,
5314
+ encodePointCloudCOPC,
3531
5315
  encodePointCloudSource,
3532
5316
  encodePointCloudSourceTile,
3533
5317
  encodePointCloudTile,
3534
5318
  encodePointCloudTileWithMetadata,
5319
+ encodePotreeArchive,
3535
5320
  encodeTileConversionArchiveInBatches,
5321
+ extractMeshVertices,
5322
+ inspectI3SMeshConversionSource,
5323
+ mapPointCloudAttributes,
5324
+ measureMeshBytes,
5325
+ openPointCloudFile,
5326
+ openTileConversionArchive,
3536
5327
  transformPointCloudSourceTile
3537
5328
  };