@loaders.gl/tile-converter 5.0.0-alpha.10 → 5.0.0-alpha.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (107) hide show
  1. package/README.md +211 -2
  2. package/bin/converter-v5.js +2 -0
  3. package/dist/converter.min.cjs +162 -163
  4. package/dist/i3s-server/bin/i3s-server.min.cjs +73 -73
  5. package/dist/index.cjs +1933 -2232
  6. package/dist/index.js +1925 -2235
  7. package/dist/v5/adapters.cjs +3186 -208
  8. package/dist/v5/adapters.d.ts +29 -2
  9. package/dist/v5/adapters.js +3202 -196
  10. package/dist/v5/archive-source.d.ts +36 -0
  11. package/dist/v5/browser-tileset-source.d.ts +55 -11
  12. package/dist/v5/browser.cjs +30 -0
  13. package/dist/v5/browser.d.ts +26 -2
  14. package/dist/v5/browser.js +45 -1
  15. package/dist/v5/conversion-api.d.ts +9 -0
  16. package/dist/v5/converter-cli.d.ts +1 -0
  17. package/dist/v5/converter-cli.js +1214 -0
  18. package/dist/v5/core.cjs +515 -50
  19. package/dist/v5/core.d.ts +2 -0
  20. package/dist/v5/core.js +515 -50
  21. package/dist/v5/i3s-mesh-conversion.d.ts +14 -4
  22. package/dist/v5/i3s-mesh-sink.d.ts +25 -0
  23. package/dist/v5/i3s-mesh-source-features.d.ts +13 -0
  24. package/dist/v5/i3s-mesh-source-material.d.ts +5 -0
  25. package/dist/v5/i3s-mesh-source.d.ts +30 -0
  26. package/dist/v5/i3s-selection-source.d.ts +70 -0
  27. package/dist/v5/index.cjs +20 -0
  28. package/dist/v5/index.d.ts +18 -2
  29. package/dist/v5/index.js +31 -1
  30. package/dist/v5/mesh-conversion.d.ts +29 -2
  31. package/dist/v5/mesh-features.d.ts +9 -0
  32. package/dist/v5/mesh-hierarchy.d.ts +47 -0
  33. package/dist/v5/mesh-source-features.d.ts +2 -1
  34. package/dist/v5/mesh-source-schema.d.ts +12 -0
  35. package/dist/v5/mesh-source-texture.d.ts +2 -0
  36. package/dist/v5/mesh-source.d.ts +13 -3
  37. package/dist/v5/mesh-tileset-sink.d.ts +5 -0
  38. package/dist/v5/mesh.d.ts +6 -1
  39. package/dist/v5/node-cli.d.ts +9 -0
  40. package/dist/v5/node-conversion.d.ts +32 -0
  41. package/dist/v5/node-feature-schema.d.ts +3 -0
  42. package/dist/v5/node-i3s-source.d.ts +41 -0
  43. package/dist/v5/node-source.d.ts +47 -0
  44. package/dist/v5/node.cjs +900 -0
  45. package/dist/v5/node.d.ts +6 -0
  46. package/dist/v5/node.js +877 -0
  47. package/dist/v5/point-cloud-file.d.ts +83 -0
  48. package/dist/v5/point-cloud-potree.d.ts +47 -0
  49. package/dist/v5/point-cloud-source-encoder.d.ts +4 -7
  50. package/dist/v5/point-cloud-spatial.d.ts +22 -0
  51. package/dist/v5/point-cloud-tileset-sink.d.ts +28 -0
  52. package/dist/v5/point-cloud-tileset.d.ts +44 -0
  53. package/dist/v5/point-cloud.d.ts +26 -1
  54. package/dist/v5/resource-fetcher.d.ts +18 -0
  55. package/dist/v5/resource-manifest.d.ts +10 -1
  56. package/dist/v5/selected-mesh-source.d.ts +53 -0
  57. package/dist/v5/spatial-conversion.d.ts +3 -1
  58. package/package.json +33 -25
  59. package/src/index.ts +4 -1
  60. package/src/v4/3d-tiles-converter/helpers/load-i3s.ts +2 -4
  61. package/src/v4/converter-cli.ts +8 -2
  62. package/src/v4/i3s-converter/helpers/load-3d-tiles.ts +3 -51
  63. package/src/v4/i3s-converter/helpers/node-index-document.ts +5 -2
  64. package/src/v4/i3s-converter/helpers/preprocess-3d-tiles.ts +1 -1
  65. package/src/v4/i3s-converter/json-templates/store.ts +2 -1
  66. package/src/v5/adapters.ts +69 -2
  67. package/src/v5/archive-source.ts +235 -0
  68. package/src/v5/browser-tileset-source.ts +418 -48
  69. package/src/v5/browser.ts +81 -2
  70. package/src/v5/conversion-api.ts +69 -9
  71. package/src/v5/converter-cli.ts +28 -0
  72. package/src/v5/core.ts +6 -0
  73. package/src/v5/i3s-mesh-conversion.ts +97 -19
  74. package/src/v5/i3s-mesh-sink.ts +351 -0
  75. package/src/v5/i3s-mesh-source-features.ts +279 -0
  76. package/src/v5/i3s-mesh-source-material.ts +157 -0
  77. package/src/v5/i3s-mesh-source.ts +228 -0
  78. package/src/v5/i3s-selection-source.ts +298 -0
  79. package/src/v5/index.ts +60 -2
  80. package/src/v5/mesh-conversion.ts +61 -11
  81. package/src/v5/mesh-draco.ts +47 -3
  82. package/src/v5/mesh-features.ts +319 -0
  83. package/src/v5/mesh-hierarchy.ts +648 -0
  84. package/src/v5/mesh-source-features.ts +73 -10
  85. package/src/v5/mesh-source-schema.ts +134 -0
  86. package/src/v5/mesh-source-texture.ts +47 -0
  87. package/src/v5/mesh-source.ts +99 -25
  88. package/src/v5/mesh-tileset-sink.ts +1 -1
  89. package/src/v5/mesh.ts +16 -7
  90. package/src/v5/node-cli.ts +197 -0
  91. package/src/v5/node-conversion.ts +345 -0
  92. package/src/v5/node-feature-schema.ts +155 -0
  93. package/src/v5/node-i3s-source.ts +181 -0
  94. package/src/v5/node-source.ts +328 -0
  95. package/src/v5/node.ts +16 -0
  96. package/src/v5/point-cloud-conversion.ts +9 -1
  97. package/src/v5/point-cloud-file.ts +306 -0
  98. package/src/v5/point-cloud-potree.ts +130 -0
  99. package/src/v5/point-cloud-source-encoder.ts +31 -20
  100. package/src/v5/point-cloud-spatial.ts +280 -0
  101. package/src/v5/point-cloud-tileset-sink.ts +234 -0
  102. package/src/v5/point-cloud-tileset.ts +137 -0
  103. package/src/v5/point-cloud.ts +159 -32
  104. package/src/v5/resource-fetcher.ts +113 -0
  105. package/src/v5/resource-manifest.ts +40 -5
  106. package/src/v5/selected-mesh-source.ts +384 -0
  107. package/src/v5/spatial-conversion.ts +161 -6
package/src/v5/browser.ts CHANGED
@@ -35,8 +35,14 @@ export type {
35
35
  BrowserTileConversionSourceOptions
36
36
  } from '@loaders.gl/tile-converter/v5/core';
37
37
  export {createBrowserTilesetConversionSource} from '@loaders.gl/tile-converter/v5/core';
38
- export {encodePointCloudTile} from '@loaders.gl/tile-converter/v5/adapters';
39
- export type {EncodePointCloudTileOptions} from '@loaders.gl/tile-converter/v5/adapters';
38
+ export {
39
+ encodePointCloudTile,
40
+ encodePointCloudTileWithMetadata
41
+ } from '@loaders.gl/tile-converter/v5/adapters';
42
+ export type {
43
+ EncodePointCloudTileOptions,
44
+ EncodedPointCloudTile
45
+ } from '@loaders.gl/tile-converter/v5/adapters';
40
46
  export {traversePointCloudSource} from '@loaders.gl/tile-converter/v5/core';
41
47
  export type {
42
48
  PointCloudSourceTile,
@@ -103,3 +109,76 @@ export type {
103
109
  I3SMeshConversionCodecOptions,
104
110
  SingleMeshI3SSink
105
111
  } from '@loaders.gl/tile-converter/v5/adapters';
112
+
113
+ export {
114
+ createI3SMeshSink,
115
+ createTileConversionResourceFetcher
116
+ } from '@loaders.gl/tile-converter/v5/adapters';
117
+ export type {
118
+ I3SMeshSink,
119
+ I3SMeshSinkOptions,
120
+ TileConversionResourceFetcherOptions,
121
+ MeshTileFeatures
122
+ } from '@loaders.gl/tile-converter/v5/adapters';
123
+
124
+ export {createPointCloudTilesetSink} from '@loaders.gl/tile-converter/v5/adapters';
125
+ export type {
126
+ PointCloudTilesetSink,
127
+ PointCloudTilesetSinkOptions
128
+ } from '@loaders.gl/tile-converter/v5/adapters';
129
+
130
+ export {transformPointCloudSourceTile} from '@loaders.gl/tile-converter/v5/adapters';
131
+ export type {PointCloudSpatialOptions} from '@loaders.gl/tile-converter/v5/adapters';
132
+ export {createI3SConversionSpatialContext} from '@loaders.gl/tile-converter/v5/core';
133
+ export type {I3SConversionSpatialContext} from '@loaders.gl/tile-converter/v5/core';
134
+
135
+ export {createI3SMeshTilesetConversionSource} from '@loaders.gl/tile-converter/v5/adapters';
136
+ export type {
137
+ I3SMeshTilesetSourceOptions,
138
+ I3SMeshSourceFeatureOptions
139
+ } from '@loaders.gl/tile-converter/v5/adapters';
140
+
141
+ export {convertPointCloudToTileset} from '@loaders.gl/tile-converter/v5/adapters';
142
+ export type {
143
+ ConvertPointCloudToTilesetOptions,
144
+ ConvertedPointCloudTileset
145
+ } from '@loaders.gl/tile-converter/v5/adapters';
146
+
147
+ export {
148
+ encodePotreeArchive,
149
+ openPointCloudFile,
150
+ extractMeshVertices
151
+ } from '@loaders.gl/tile-converter/v5/adapters';
152
+ export type {
153
+ EncodePotreeArchiveOptions,
154
+ OpenPointCloudFileOptions,
155
+ OpenedPointCloudFile,
156
+ ExtractMeshVerticesOptions
157
+ } from '@loaders.gl/tile-converter/v5/adapters';
158
+
159
+ export {createMeshSourceFeatureOptions} from '@loaders.gl/tile-converter/v5/adapters';
160
+ export type {MeshSourceSchemaOptions} from '@loaders.gl/tile-converter/v5/adapters';
161
+ export {convertPointCloudToPotree} from '@loaders.gl/tile-converter/v5/adapters';
162
+ export type {
163
+ ConvertPointCloudToPotreeOptions,
164
+ ConvertedPointCloudPotree
165
+ } from '@loaders.gl/tile-converter/v5/adapters';
166
+
167
+ export {selectBrowserTilesetContent} from '@loaders.gl/tile-converter/v5/core';
168
+ export type {
169
+ BrowserTilesetDocumentPlacement,
170
+ BrowserTilesetContentSelection
171
+ } from '@loaders.gl/tile-converter/v5/core';
172
+
173
+ export {
174
+ createSelectedMeshConversionSource,
175
+ measureMeshBytes,
176
+ SELECTED_MESH_LIMITS
177
+ } from '@loaders.gl/tile-converter/v5/adapters';
178
+ export type {
179
+ SelectedMeshSourceOptions,
180
+ SelectedMeshConversionSource
181
+ } from '@loaders.gl/tile-converter/v5/adapters';
182
+
183
+ export {createMeshHierarchyTilesetSink} from '@loaders.gl/tile-converter/v5/adapters';
184
+ export type {MeshHierarchyTilesetSinkOptions} from '@loaders.gl/tile-converter/v5/adapters';
@@ -111,6 +111,14 @@ export interface ConvertTilesetOptions<TInspection, TInput, TOutput> {
111
111
  readonly maxInputResourceBytes?: number;
112
112
  /** Reject any output resource larger than this configured byte limit. */
113
113
  readonly maxOutputResourceBytes?: number;
114
+ /** Maximum measured input bytes across this run; does not bound source allocations. */
115
+ readonly maxInputBytes?: number;
116
+ /** Maximum measured output bytes across this run, including checkpointed resources. */
117
+ readonly maxOutputBytes?: number;
118
+ /** Maximum input resources consumed by this run, including zero-byte resources. */
119
+ readonly maxInputResources?: number;
120
+ /** Maximum outputs processed by this run, including zero-byte and checkpointed resources. */
121
+ readonly maxOutputResources?: number;
114
122
  /** Cancel inspection, conversion, validation, or writes. */
115
123
  readonly signal?: AbortSignal;
116
124
  /** Receive progress updates after each resource transition. */
@@ -155,6 +163,7 @@ export async function inspectTileset<TInspection, TInput>(
155
163
  * This does not limit source decoding allocations or content retained by a source.
156
164
  * Writes are awaited one resource at a time, keeping queued output bytes bounded by the
157
165
  * largest emitted resource and the configured `maxOutputResourceBytes` limit.
166
+ * Cumulative byte/count limits bound measured work in this run, not total allocation memory.
158
167
  */
159
168
  export async function convertTileset<TInspection, TInput, TOutput>(
160
169
  options: ConvertTilesetOptions<TInspection, TInput, TOutput>
@@ -167,6 +176,10 @@ export async function convertTileset<TInspection, TInput, TOutput>(
167
176
  measureOutputBytes,
168
177
  maxInputResourceBytes = Number.POSITIVE_INFINITY,
169
178
  maxOutputResourceBytes = Number.POSITIVE_INFINITY,
179
+ maxInputBytes = Number.POSITIVE_INFINITY,
180
+ maxOutputBytes = Number.POSITIVE_INFINITY,
181
+ maxInputResources = Number.POSITIVE_INFINITY,
182
+ maxOutputResources = Number.POSITIVE_INFINITY,
170
183
  signal,
171
184
  onProgress
172
185
  } = options;
@@ -181,19 +194,26 @@ export async function convertTileset<TInspection, TInput, TOutput>(
181
194
  };
182
195
 
183
196
  try {
184
- if (
185
- maxInputResourceBytes !== Number.POSITIVE_INFINITY &&
186
- (!Number.isSafeInteger(maxInputResourceBytes) || maxInputResourceBytes < 0)
187
- ) {
188
- throw new TileConversionError(
189
- 'INVALID_INPUT_RESOURCE_LIMIT',
190
- 'maxInputResourceBytes must be a nonnegative safe integer or Infinity'
191
- );
197
+ for (const [name, limit, code] of [
198
+ ['maxInputResourceBytes', maxInputResourceBytes, 'INVALID_INPUT_RESOURCE_LIMIT'],
199
+ ['maxOutputResourceBytes', maxOutputResourceBytes, 'INVALID_OUTPUT_RESOURCE_LIMIT'],
200
+ ['maxInputBytes', maxInputBytes, 'INVALID_INPUT_BYTES_LIMIT'],
201
+ ['maxOutputBytes', maxOutputBytes, 'INVALID_OUTPUT_BYTES_LIMIT'],
202
+ ['maxInputResources', maxInputResources, 'INVALID_INPUT_COUNT_LIMIT'],
203
+ ['maxOutputResources', maxOutputResources, 'INVALID_OUTPUT_COUNT_LIMIT']
204
+ ] as const) {
205
+ if (limit !== Infinity && (!Number.isSafeInteger(limit) || limit < 0)) {
206
+ throw new TileConversionError(
207
+ code,
208
+ name + ' must be a nonnegative safe integer or Infinity'
209
+ );
210
+ }
192
211
  }
193
212
  reportProgress('inspect');
194
213
  const inspection = await inspectTileset(source, signal);
195
214
  for await (const inputResource of source.read(inspection, signal)) {
196
215
  throwIfAborted(signal);
216
+ checkConversionTotal(inputResources, 1, maxInputResources, 'INPUT_COUNT_LIMIT_EXCEEDED');
197
217
  const inputResourceBytes = measureInputBytes(inputResource);
198
218
  if (!Number.isSafeInteger(inputResourceBytes) || inputResourceBytes < 0) {
199
219
  throw new TileConversionError(
@@ -207,23 +227,41 @@ export async function convertTileset<TInspection, TInput, TOutput>(
207
227
  `Input resource is ${inputResourceBytes} bytes, exceeding the configured limit of ${maxInputResourceBytes} bytes`
208
228
  );
209
229
  }
230
+ checkConversionTotal(
231
+ inputBytes,
232
+ inputResourceBytes,
233
+ maxInputBytes,
234
+ 'INPUT_BYTES_LIMIT_EXCEEDED'
235
+ );
210
236
  inputResources++;
211
237
  inputBytes += inputResourceBytes;
212
238
  reportProgress('convert');
213
239
  const convertedResources = await codec.convert(inputResource, inspection, signal);
214
240
  for await (const outputResource of convertedResources) {
215
241
  throwIfAborted(signal);
242
+ checkConversionTotal(outputResources, 1, maxOutputResources, 'OUTPUT_COUNT_LIMIT_EXCEEDED');
216
243
  reportProgress('validate');
217
244
  const resourceDiagnostics = (await codec.validateOutput?.(outputResource, signal)) || [];
218
245
  diagnostics.push(...resourceDiagnostics);
219
246
  const outputResourceBytes = measureOutputBytes(outputResource);
220
- largestOutputResourceBytes = Math.max(largestOutputResourceBytes, outputResourceBytes);
247
+ if (!Number.isSafeInteger(outputResourceBytes) || outputResourceBytes < 0) {
248
+ throw new TileConversionError(
249
+ 'INVALID_OUTPUT_RESOURCE_SIZE',
250
+ 'measureOutputBytes must return a nonnegative safe integer'
251
+ );
252
+ }
221
253
  if (outputResourceBytes > maxOutputResourceBytes) {
222
254
  throw new TileConversionError(
223
255
  'OUTPUT_RESOURCE_TOO_LARGE',
224
256
  `Output resource is ${outputResourceBytes} bytes, exceeding the configured limit of ${maxOutputResourceBytes} bytes`
225
257
  );
226
258
  }
259
+ checkConversionTotal(
260
+ outputBytes,
261
+ outputResourceBytes,
262
+ maxOutputBytes,
263
+ 'OUTPUT_BYTES_LIMIT_EXCEEDED'
264
+ );
227
265
  if (resourceDiagnostics.some(diagnostic => diagnostic.severity === 'error')) {
228
266
  throw new TileConversionError(
229
267
  'OUTPUT_VALIDATION_FAILED',
@@ -234,6 +272,7 @@ export async function convertTileset<TInspection, TInput, TOutput>(
234
272
  throwIfAborted(signal);
235
273
  await sink.write(outputResource, signal);
236
274
  throwIfAborted(signal);
275
+ largestOutputResourceBytes = Math.max(largestOutputResourceBytes, outputResourceBytes);
237
276
  outputResources++;
238
277
  outputBytes += outputResourceBytes;
239
278
  reportProgress('convert');
@@ -285,3 +324,24 @@ function throwIfAborted(signal?: AbortSignal): void {
285
324
  throw signal.reason ?? new DOMException('The operation was aborted', 'AbortError');
286
325
  }
287
326
  }
327
+
328
+ /** Checks a cumulative budget without overflowing safe integer accounting. */
329
+ function checkConversionTotal(
330
+ current: number,
331
+ increment: number,
332
+ limit: number,
333
+ code: string
334
+ ): void {
335
+ if (increment > limit - current) {
336
+ throw new TileConversionError(
337
+ code,
338
+ `Conversion exceeds cumulative limit ${limit}: ${current} processed, next resource adds ${increment}`
339
+ );
340
+ }
341
+ if (increment > Number.MAX_SAFE_INTEGER - current) {
342
+ throw new TileConversionError(
343
+ 'RESOURCE_TOTAL_OVERFLOW',
344
+ 'Conversion totals must remain safe integers'
345
+ );
346
+ }
347
+ }
@@ -0,0 +1,28 @@
1
+ // loaders.gl
2
+ // SPDX-License-Identifier: MIT
3
+ // Copyright (c) vis.gl contributors
4
+
5
+ import {runTileConverterCLI} from './node-cli.js';
6
+
7
+ /** Runs the Node process adapter and maps SIGINT into cooperative I/O cancellation. */
8
+ async function main(): Promise<void> {
9
+ const controller = new AbortController();
10
+ /** Cancels the active operation; promise cleanup closes source and output handles. */
11
+ const cancelOperation = (): void =>
12
+ controller.abort(new Error('Tile conversion operation interrupted'));
13
+ process.on('SIGINT', cancelOperation);
14
+ try {
15
+ process.exitCode = await runTileConverterCLI(
16
+ process.argv.slice(2),
17
+ {
18
+ stdout: text => process.stdout.write(text),
19
+ stderr: text => process.stderr.write(text)
20
+ },
21
+ controller.signal
22
+ );
23
+ } finally {
24
+ process.removeListener('SIGINT', cancelOperation);
25
+ }
26
+ }
27
+
28
+ void main();
package/src/v5/core.ts CHANGED
@@ -59,3 +59,9 @@ export type {
59
59
  BrowserTilesetResourceDescriptor,
60
60
  BrowserTilesetConversionInputResource
61
61
  } from './browser-tileset-source.js';
62
+
63
+ export {selectBrowserTilesetContent} from './browser-tileset-source.js';
64
+ export type {
65
+ BrowserTilesetDocumentPlacement,
66
+ BrowserTilesetContentSelection
67
+ } from './browser-tileset-source.js';
@@ -2,11 +2,22 @@
2
2
  // SPDX-License-Identifier: MIT
3
3
  // Copyright (c) vis.gl contributors
4
4
 
5
- import {encodeI3SMeshLayer} from '@loaders.gl/i3s';
6
- import type {EncodedI3SMeshLayer, I3SMeshWriterOptions} from '@loaders.gl/i3s';
5
+ import {
6
+ encodeI3SMeshLayer,
7
+ encodeI3SMeshLayerWithDraco,
8
+ preloadI3SMeshCompression
9
+ } from '@loaders.gl/i3s';
10
+ import type {
11
+ EncodedI3SMeshLayer,
12
+ I3SMeshWriterOptions,
13
+ I3SDracoLibraryOptions
14
+ } from '@loaders.gl/i3s';
7
15
  import {TileConversionError} from '@loaders.gl/tile-converter/v5/core';
8
16
  import type {TileConversionCodec, TileConversionSink} from '@loaders.gl/tile-converter/v5/core';
9
- import type {Tiles3DConversionSpatialContext} from '@loaders.gl/tile-converter/v5/core';
17
+ import type {
18
+ Tiles3DConversionSpatialContext,
19
+ I3SConversionSpatialContext
20
+ } from '@loaders.gl/tile-converter/v5/core';
10
21
  import type {MeshSourceResource} from './mesh-source.js';
11
22
  import {validateMeshGeometry} from './mesh.js';
12
23
  import {createBoundedMemoryTileConversionSink} from '@loaders.gl/tile-converter/v5/core';
@@ -16,19 +27,38 @@ import type {BrowserTileConversionFile} from '@loaders.gl/tile-converter/v5/core
16
27
  export interface I3SMeshConversionResource extends EncodedI3SMeshLayer {
17
28
  /** Source placement identifier used for diagnostics. */
18
29
  readonly id: string;
30
+ /** Original I3S layer type when authoring from an I3S source. */
31
+ readonly sourceLayerType?: '3DObject' | 'IntegratedMesh';
19
32
  }
20
33
 
21
34
  /** Spatial preparation and required authoring limits for the I3S codec. */
22
35
  export interface I3SMeshConversionCodecOptions
23
36
  extends Pick<I3SMeshWriterOptions, 'name' | 'maxPositionError' | 'maxResourceBytes'> {
24
37
  /** Shared source-to-ECEF context, applied exactly once before I3S geographic encoding. */
25
- readonly spatialContext: Tiles3DConversionSpatialContext;
38
+ readonly spatialContext: Tiles3DConversionSpatialContext | I3SConversionSpatialContext;
39
+ /** I3S vector frame when using an I3S context; defaults to earth-centered. */
40
+ readonly normalReferenceFrame?: 'earth-centered' | 'vertex-reference-frame';
41
+ /** Lossless Edge Breaker geometry by default; false retains raw I3S geometry. */
42
+ readonly draco?: boolean;
43
+ /** Explicitly authorize IntegratedMesh input to become 3DObject output; omission rejects it. */
44
+ readonly integratedMeshPolicy?: 'as-3d-object';
45
+ /** Full Draco encoder/decoder runtime URLs or injected modules. */
46
+ readonly dracoLibraryOptions?: I3SDracoLibraryOptions;
26
47
  }
27
48
 
28
49
  /** Creates a portable source-mesh to I3S layer codec with explicit precision and feature diagnostics. */
29
50
  export function createI3SMeshConversionCodec<TInspection = unknown>(
30
51
  options: I3SMeshConversionCodecOptions
31
52
  ): TileConversionCodec<TInspection, MeshSourceResource, I3SMeshConversionResource> {
53
+ if (
54
+ options.integratedMeshPolicy !== undefined &&
55
+ options.integratedMeshPolicy !== 'as-3d-object'
56
+ ) {
57
+ throw new TileConversionError(
58
+ 'I3S_LAYER_TYPE_POLICY_INVALID',
59
+ 'Unknown IntegratedMesh conversion policy'
60
+ );
61
+ }
32
62
  const reference = options.spatialContext.spatialReference;
33
63
  const height =
34
64
  reference.targetHeightReference === 'native'
@@ -47,28 +77,52 @@ export function createI3SMeshConversionCodec<TInspection = unknown>(
47
77
  'I3S_MESH_FRAME_UNSUPPORTED',
48
78
  'I3S mesh authoring requires resolved ECEF coordinates and ellipsoidal heights'
49
79
  );
80
+ let objectIdOffset = 0;
50
81
  return {
51
82
  /** Places a source mesh in ECEF and authors one layer without modifying source arrays. */
52
83
  async *convert(resource, _inspection, signal) {
53
84
  signal?.throwIfAborted();
85
+ if (
86
+ resource.sourceLayerType === 'IntegratedMesh' &&
87
+ options.integratedMeshPolicy !== 'as-3d-object'
88
+ ) {
89
+ throw new TileConversionError(
90
+ 'I3S_LAYER_TYPE_CHANGE_REQUIRED',
91
+ 'IntegratedMesh to 3DObject conversion requires integratedMeshPolicy=as-3d-object'
92
+ );
93
+ }
54
94
  const mesh = validateMeshGeometry(resource.mesh, true);
55
95
  const positions = mesh.attributes.POSITION.value;
56
- mesh.attributes.POSITION = {
57
- value: options.spatialContext.transformPositions(positions),
58
- size: 3
59
- };
60
- if (mesh.attributes.NORMAL)
61
- mesh.attributes.NORMAL = {
62
- value: options.spatialContext.transformNormals(mesh.attributes.NORMAL.value, positions),
63
- size: 3
64
- };
96
+ const context = options.spatialContext;
97
+ const transformed =
98
+ 'transformGeometryAsync' in context
99
+ ? await context.transformGeometryAsync(
100
+ positions,
101
+ mesh.attributes.NORMAL?.value,
102
+ resource.normalReferenceFrame ?? options.normalReferenceFrame
103
+ )
104
+ : {
105
+ positions: context.transformPositions(positions),
106
+ normals: mesh.attributes.NORMAL
107
+ ? context.transformNormals(mesh.attributes.NORMAL.value, positions)
108
+ : undefined
109
+ };
110
+ signal?.throwIfAborted();
111
+ mesh.attributes.POSITION = {value: transformed.positions, size: 3};
112
+ if (mesh.attributes.NORMAL) mesh.attributes.NORMAL = {value: transformed.normals!, size: 3};
65
113
  let layer: EncodedI3SMeshLayer;
66
114
  try {
67
- layer = encodeI3SMeshLayer(mesh, {
115
+ await preloadI3SMeshCompression();
116
+ const writerOptions = {
68
117
  ...options,
69
118
  material: resource.material,
70
- features: resource.features
71
- });
119
+ features: resource.features,
120
+ objectIdOffset
121
+ };
122
+ layer =
123
+ options.draco === false
124
+ ? encodeI3SMeshLayer(mesh, writerOptions)
125
+ : await encodeI3SMeshLayerWithDraco(mesh, writerOptions, options.dracoLibraryOptions);
72
126
  } catch (error) {
73
127
  throw new TileConversionError(
74
128
  'I3S_MESH_PROFILE_UNSUPPORTED',
@@ -76,11 +130,24 @@ export function createI3SMeshConversionCodec<TInspection = unknown>(
76
130
  );
77
131
  }
78
132
  signal?.throwIfAborted();
79
- yield {id: resource.id, ...layer};
133
+ objectIdOffset +=
134
+ resource.features?.batches.reduce((total, batch) => total + batch.data.numRows, 0) ?? 1;
135
+ yield {id: resource.id, ...layer, sourceLayerType: resource.sourceLayerType};
80
136
  },
81
137
  /** Reports measured precision and explicitly authorized integer representation changes. */
82
138
  async validateOutput(resource) {
83
139
  return [
140
+ ...(resource.sourceLayerType === 'IntegratedMesh'
141
+ ? [
142
+ {
143
+ code: 'I3S_LAYER_TYPE_CHANGED',
144
+ message:
145
+ 'Explicitly authored IntegratedMesh geometry as I3S 1.7 3DObject; integrated surface layer semantics and source LOD are not preserved',
146
+ severity: 'warning' as const,
147
+ resourceId: resource.id
148
+ }
149
+ ]
150
+ : []),
84
151
  ...(resource.maximumPositionError > 0
85
152
  ? [
86
153
  {
@@ -104,7 +171,7 @@ export function createI3SMeshConversionCodec<TInspection = unknown>(
104
171
 
105
172
  /** Atomic bounded output for one authored I3S layer. */
106
173
  export interface SingleMeshI3SSink extends TileConversionSink<I3SMeshConversionResource> {
107
- /** Compressed archive-relative files visible only after successful finalization. */
174
+ /** Archive-relative geometry, metadata and images visible only after successful finalization. */
108
175
  getFiles(): readonly BrowserTileConversionFile[];
109
176
  }
110
177
 
@@ -128,7 +195,18 @@ export function createSingleMeshI3SSink(options: {
128
195
  );
129
196
  state = 'writing';
130
197
  for (const [resourceId, bytes] of Object.entries(resource.files))
131
- await memory.write({resourceId, parts: [bytes], contentType: 'application/gzip'}, signal);
198
+ await memory.write(
199
+ {
200
+ resourceId,
201
+ parts: [bytes],
202
+ contentType: resourceId.endsWith('.png')
203
+ ? 'image/png'
204
+ : resourceId.endsWith('.jpg')
205
+ ? 'image/jpeg'
206
+ : 'application/gzip'
207
+ },
208
+ signal
209
+ );
132
210
  state = 'written';
133
211
  },
134
212
  /** Commits a complete single-layer result. */