@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.
- package/README.md +211 -2
- package/bin/converter-v5.js +2 -0
- package/dist/converter.min.cjs +162 -163
- package/dist/i3s-server/bin/i3s-server.min.cjs +73 -73
- package/dist/index.cjs +1933 -2232
- package/dist/index.js +1925 -2235
- package/dist/v5/adapters.cjs +3186 -208
- package/dist/v5/adapters.d.ts +29 -2
- package/dist/v5/adapters.js +3202 -196
- package/dist/v5/archive-source.d.ts +36 -0
- package/dist/v5/browser-tileset-source.d.ts +55 -11
- package/dist/v5/browser.cjs +30 -0
- package/dist/v5/browser.d.ts +26 -2
- package/dist/v5/browser.js +45 -1
- package/dist/v5/conversion-api.d.ts +9 -0
- package/dist/v5/converter-cli.d.ts +1 -0
- package/dist/v5/converter-cli.js +1214 -0
- package/dist/v5/core.cjs +515 -50
- package/dist/v5/core.d.ts +2 -0
- package/dist/v5/core.js +515 -50
- package/dist/v5/i3s-mesh-conversion.d.ts +14 -4
- package/dist/v5/i3s-mesh-sink.d.ts +25 -0
- package/dist/v5/i3s-mesh-source-features.d.ts +13 -0
- package/dist/v5/i3s-mesh-source-material.d.ts +5 -0
- package/dist/v5/i3s-mesh-source.d.ts +30 -0
- package/dist/v5/i3s-selection-source.d.ts +70 -0
- package/dist/v5/index.cjs +20 -0
- package/dist/v5/index.d.ts +18 -2
- package/dist/v5/index.js +31 -1
- package/dist/v5/mesh-conversion.d.ts +29 -2
- package/dist/v5/mesh-features.d.ts +9 -0
- package/dist/v5/mesh-hierarchy.d.ts +47 -0
- package/dist/v5/mesh-source-features.d.ts +2 -1
- package/dist/v5/mesh-source-schema.d.ts +12 -0
- package/dist/v5/mesh-source-texture.d.ts +2 -0
- package/dist/v5/mesh-source.d.ts +13 -3
- package/dist/v5/mesh-tileset-sink.d.ts +5 -0
- package/dist/v5/mesh.d.ts +6 -1
- package/dist/v5/node-cli.d.ts +9 -0
- package/dist/v5/node-conversion.d.ts +32 -0
- package/dist/v5/node-feature-schema.d.ts +3 -0
- package/dist/v5/node-i3s-source.d.ts +41 -0
- package/dist/v5/node-source.d.ts +47 -0
- package/dist/v5/node.cjs +900 -0
- package/dist/v5/node.d.ts +6 -0
- package/dist/v5/node.js +877 -0
- package/dist/v5/point-cloud-file.d.ts +83 -0
- package/dist/v5/point-cloud-potree.d.ts +47 -0
- package/dist/v5/point-cloud-source-encoder.d.ts +4 -7
- package/dist/v5/point-cloud-spatial.d.ts +22 -0
- package/dist/v5/point-cloud-tileset-sink.d.ts +28 -0
- package/dist/v5/point-cloud-tileset.d.ts +44 -0
- package/dist/v5/point-cloud.d.ts +26 -1
- package/dist/v5/resource-fetcher.d.ts +18 -0
- package/dist/v5/resource-manifest.d.ts +10 -1
- package/dist/v5/selected-mesh-source.d.ts +53 -0
- package/dist/v5/spatial-conversion.d.ts +3 -1
- package/package.json +33 -25
- package/src/index.ts +4 -1
- package/src/v4/3d-tiles-converter/helpers/load-i3s.ts +2 -4
- package/src/v4/converter-cli.ts +8 -2
- package/src/v4/i3s-converter/helpers/load-3d-tiles.ts +3 -51
- package/src/v4/i3s-converter/helpers/node-index-document.ts +5 -2
- package/src/v4/i3s-converter/helpers/preprocess-3d-tiles.ts +1 -1
- package/src/v4/i3s-converter/json-templates/store.ts +2 -1
- package/src/v5/adapters.ts +69 -2
- package/src/v5/archive-source.ts +235 -0
- package/src/v5/browser-tileset-source.ts +418 -48
- package/src/v5/browser.ts +81 -2
- package/src/v5/conversion-api.ts +69 -9
- package/src/v5/converter-cli.ts +28 -0
- package/src/v5/core.ts +6 -0
- package/src/v5/i3s-mesh-conversion.ts +97 -19
- package/src/v5/i3s-mesh-sink.ts +351 -0
- package/src/v5/i3s-mesh-source-features.ts +279 -0
- package/src/v5/i3s-mesh-source-material.ts +157 -0
- package/src/v5/i3s-mesh-source.ts +228 -0
- package/src/v5/i3s-selection-source.ts +298 -0
- package/src/v5/index.ts +60 -2
- package/src/v5/mesh-conversion.ts +61 -11
- package/src/v5/mesh-draco.ts +47 -3
- package/src/v5/mesh-features.ts +319 -0
- package/src/v5/mesh-hierarchy.ts +648 -0
- package/src/v5/mesh-source-features.ts +73 -10
- package/src/v5/mesh-source-schema.ts +134 -0
- package/src/v5/mesh-source-texture.ts +47 -0
- package/src/v5/mesh-source.ts +99 -25
- package/src/v5/mesh-tileset-sink.ts +1 -1
- package/src/v5/mesh.ts +16 -7
- package/src/v5/node-cli.ts +197 -0
- package/src/v5/node-conversion.ts +345 -0
- package/src/v5/node-feature-schema.ts +155 -0
- package/src/v5/node-i3s-source.ts +181 -0
- package/src/v5/node-source.ts +328 -0
- package/src/v5/node.ts +16 -0
- package/src/v5/point-cloud-conversion.ts +9 -1
- package/src/v5/point-cloud-file.ts +306 -0
- package/src/v5/point-cloud-potree.ts +130 -0
- package/src/v5/point-cloud-source-encoder.ts +31 -20
- package/src/v5/point-cloud-spatial.ts +280 -0
- package/src/v5/point-cloud-tileset-sink.ts +234 -0
- package/src/v5/point-cloud-tileset.ts +137 -0
- package/src/v5/point-cloud.ts +159 -32
- package/src/v5/resource-fetcher.ts +113 -0
- package/src/v5/resource-manifest.ts +40 -5
- package/src/v5/selected-mesh-source.ts +384 -0
- 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 {
|
|
39
|
-
|
|
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';
|
package/src/v5/conversion-api.ts
CHANGED
|
@@ -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
|
-
|
|
185
|
-
maxInputResourceBytes
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
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
|
-
|
|
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 {
|
|
6
|
-
|
|
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 {
|
|
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
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
/**
|
|
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(
|
|
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. */
|