@loaders.gl/tile-converter 5.0.0-alpha.8 → 5.0.0-alpha.9

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 CHANGED
@@ -41,6 +41,9 @@ finish. Cleanup also runs when iteration closes on failure or cancellation, incl
41
41
  the input-size gate. Consume payloads before advancing; codecs and sinks must not retain them after
42
42
  their work completes. Preloaded payloads are retained. Omit the option to keep all loaded content
43
43
  attached, as before. Applications still own tileset destruction and source/archive lifetimes.
44
+ With `Tiles3DSource`, repeated reads reuse installed nested roots and descendants while reloading
45
+ their payloads. Separate content slots and parent placements retain their own nested roots even
46
+ when they reference the same resource URL.
44
47
 
45
48
  This adapter does not bound aggregate input memory, apply CRS transforms, or supply a mesh codec
46
49
  or output packaging. Inspection observes
@@ -55,6 +58,237 @@ inputs throw `INPUT_RESOURCE_TOO_LARGE`, close source iteration, and abort the d
55
58
  This check runs after the source read, so decoding allocations and retained source content still
56
59
  require source-level controls. `convertPointCloudSource` accepts the same option for decoded tiles.
57
60
 
61
+ ## V5 single mesh encoding
62
+
63
+ `encodeMeshTile(mesh, {material})` is available from both `/v5` and `/v5/browser`. It encodes one
64
+ triangle-list `MeshGeometry` (`mode: 4`) into a self-contained glTF 2.0 GLB.
65
+
66
+ ```ts
67
+ import {encodeMeshTile} from '@loaders.gl/tile-converter/v5/browser';
68
+
69
+ const glb = encodeMeshTile({
70
+ topology: 'triangle-list',
71
+ mode: 4,
72
+ attributes: {
73
+ POSITION: {value: new Float32Array([0, 0, 0, 1, 0, 0, 0, 1, 0]), size: 3}
74
+ }
75
+ });
76
+ ```
77
+
78
+ The initial profile accepts finite packed Float32 `POSITION` and optional `NORMAL` xyz
79
+ triples. Normals must have unit length within 0.0001 and match the vertex count. Optional
80
+ indices are packed Uint8, Uint16, or Uint32 scalars forming complete triangles, referencing
81
+ existing vertices and excluding each type's maximum primitive-restart value. Typed array
82
+ subviews are supported; descriptor offsets/strides and encoded transforms
83
+ are rejected. Normalization is supported only for the selected integer colors and UVs below. Other attributes fail with typed diagnostics instead of disappearing.
84
+
85
+ The encoder preserves coordinates and does not mutate input arrays. Callers select a local
86
+ coordinate frame and own CRS conversion, double-precision origins, placement, source
87
+ material mapping, feature mappings, and tileset packaging. This geometry helper does not extract an entire
88
+ source scene or preserve its metadata and appearance automatically.
89
+
90
+ ### Selected appearance
91
+
92
+ Optional `COLOR_0` contains packed Float32 linear RGB (`size: 3`) or RGBA (`size: 4`)
93
+ values in [0, 1], or packed `Uint8Array`/`Uint16Array` values with `normalized: true`, one per
94
+ position. Integer colors represent linear values divided by 255 or 65535, including alpha;
95
+ no sRGB conversion is performed. Component types, normalization, and subview values are preserved.
96
+ Other color sets remain unsupported.
97
+
98
+ One optional `MeshTileMaterial` applies to every triangle:
99
+
100
+ ```ts
101
+ const glb = encodeMeshTile(mesh, {
102
+ material: {
103
+ baseColorFactor: [0.8, 0.5, 0.2, 0.75],
104
+ metallicFactor: 0,
105
+ roughnessFactor: 1,
106
+ alphaMode: 'BLEND',
107
+ doubleSided: true
108
+ }
109
+ });
110
+ ```
111
+
112
+ Base color is a linear RGBA multiplier in [0, 1], multiplied by `COLOR_0` when present.
113
+ Metallic/roughness factors are in [0, 1]. Alpha mode is `OPAQUE`, `MASK`, or `BLEND`;
114
+ finite nonnegative `alphaCutoff` is allowed only with `MASK`. Omitted properties retain
115
+ [glTF defaults](https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html#materials):
116
+ white base color, metallic/roughness 1, opaque alpha, mask cutoff 0.5, and single-sided rendering.
117
+ No material is added unless explicitly supplied. Unsupported material properties and extensions
118
+ fail with `MESH_MATERIAL_UNSUPPORTED`; invalid values fail with
119
+ `MESH_MATERIAL_INVALID`. Invalid color values/counts/types fail with `MESH_COLOR_INVALID`.
120
+ Inputs are not mutated. Applications explicitly map source colors and materials into this
121
+ subset; other texture maps, multiple materials, and
122
+ feature mappings remain open work.
123
+
124
+ Optional `TEXCOORD_0` contains packed finite Float32 or explicitly normalized Uint8/Uint16
125
+ UV pairs (`size: 2`), one per position. Float32 values outside [0, 1] are allowed for repeating
126
+ textures; integer values represent [0, 1] after division by 255 or 65535. Integer storage requires
127
+ `normalized: true`; signed, Uint32, and normalized floating-point appearance attributes are rejected.
128
+ The glTF writer pads vertex elements to four-byte alignment without changing their values.
129
+ Texture transforms apply to the normalized UV values. A selected `baseColorTexture` requires
130
+ these UVs and embeds an already encoded PNG or JPEG:
131
+
132
+ ```ts
133
+ const glb = encodeMeshTile(meshWithUvs, {
134
+ material: {
135
+ baseColorTexture: {
136
+ data: encodedImageBytes,
137
+ mimeType: 'image/png',
138
+ sampler: {wrapS: 33071, wrapT: 33071, minFilter: 9729, magFilter: 9729},
139
+ transform: {offset: [0, 1], scale: [1, -1]}
140
+ },
141
+ metallicFactor: 0,
142
+ roughnessFactor: 1
143
+ }
144
+ });
145
+ ```
146
+
147
+ `MeshTileTexture.data` is a `Uint8Array`; subviews preserve only their selected bytes. The
148
+ encoder checks header MIME type and positive dimensions, then copies the encoded bytes
149
+ without decoding, re-encoding, flipping UVs, or changing the selected sampling controls. Applications
150
+ supply valid encoded image payloads and map source sampling conventions explicitly. Base-color
151
+ images use glTF's sRGB interpretation and multiply the linear material factor and vertex colors.
152
+ Optional `MeshTileSampler` preserves glTF `wrapS`/`wrapT` (33071 clamp-to-edge,
153
+ 33648 mirrored-repeat, 10497 repeat), `magFilter` (9728 nearest, 9729 linear), and
154
+ `minFilter` (9728/9729 or mipmap filters 9984–9987). Omitted wrapping uses repeat;
155
+ omitted filters are chosen by the renderer. An omitted sampler emits no sampler resource;
156
+ an explicitly selected `{}` emits an empty sampler using the same defaults. Invalid values
157
+ or malformed selected samplers fail with `MESH_SAMPLER_INVALID`; unsupported sampler
158
+ properties fail with `MESH_SAMPLER_UNSUPPORTED`. Inputs are not mutated. Mipmap filter
159
+ selection does not generate mip levels; source adapters own sampling and image conventions.
160
+ Optional `MeshTileTextureTransform` preserves finite `offset`/`scale` UV pairs and a finite
161
+ counterclockwise `rotation` in radians through
162
+ [KHR_texture_transform](https://github.com/KhronosGroup/glTF/blob/main/extensions/2.0/Khronos/KHR_texture_transform/README.md).
163
+ The reader applies scale, then rotation about the UV origin, then offset. Omitted controls use
164
+ [0, 0], 0, and [1, 1]; zero/negative scales are supported. An omitted transform adds no extension;
165
+ a selected `{}` adds the default transform. The extension is declared required because no baked
166
+ fallback UV set is generated. UV/image bytes are unchanged and inputs are not mutated.
167
+ Malformed/nonfinite controls fail with `MESH_TEXTURE_TRANSFORM_INVALID`; unsupported properties
168
+ (including a `texCoord` override) fail with `MESH_TEXTURE_TRANSFORM_UNSUPPORTED`.
169
+ Source transform extraction/mapping, external image URLs, other UV sets, and other texture
170
+ extensions remain unsupported.
171
+ Invalid selected UVs fail with `MESH_TEXCOORD_INVALID`, missing UVs with
172
+ `MESH_TEXCOORD_REQUIRED`, and invalid/unsupported image descriptors or headers with
173
+ `MESH_TEXTURE_INVALID`. The spatial mesh codec forwards the same selected UVs, images, sampling, and transforms.
174
+
175
+ ## V5 spatial mesh codec
176
+
177
+ `createMeshConversionCodec` connects the mesh encoder to `convertTileset`. Supply
178
+ selected triangle geometries with **absolute source-frame positions**, matching normals, a
179
+ resource ID, an explicit target-frame origin, and an optional selected `material`. Positions
180
+ may be packed Float32 or Float64; the remaining geometry restrictions match `encodeMeshTile`.
181
+ The input byte estimator must include selected image bytes as well as geometry buffers.
182
+
183
+ ```ts
184
+ import {
185
+ convertTileset,
186
+ createMeshConversionCodec,
187
+ createTiles3DConversionSpatialContext
188
+ } from '@loaders.gl/tile-converter/v5/browser';
189
+
190
+ const spatialContext = createTiles3DConversionSpatialContext(sourceSpatialReference, {
191
+ targetCrs: 'EPSG:3857'
192
+ });
193
+ const report = await convertTileset({
194
+ source: selectedMeshSource, // Yields {id, mesh, origin, material?}; origin is in the target frame.
195
+ codec: createMeshConversionCodec({spatialContext, maxPositionError: 0.001}),
196
+ sink: meshSink,
197
+ measureInputBytes: measureDecodedMeshBytes,
198
+ measureOutputBytes: resource => resource.glb.byteLength,
199
+ maxInputResourceBytes: 16 * 1024 * 1024,
200
+ maxOutputResourceBytes: 16 * 1024 * 1024,
201
+ signal: abortController.signal
202
+ });
203
+ ```
204
+
205
+ The codec applies the shared position/normal operations once, subtracts the selected origin
206
+ in double precision, then encodes local Float32 positions. `maxPositionError` is required,
207
+ finite and nonnegative: it limits Euclidean reconstruction error per vertex in **target
208
+ coordinate units** (0.001 meters for EPSG:3857). Equality is allowed; zero requires exact
209
+ reconstruction. Geographic/unknown output frames are rejected. Native coordinates require
210
+ an explicitly declared Cartesian frame in the discovered spatial metadata.
211
+
212
+ Each output contains `{id, glb, origin, boundingBox, localBoundingBox, spatialReference, maximumPositionError}`.
213
+ `localBoundingBox` bounds the actual encoded Float32 positions before adding the origin; use
214
+ it for packaging to avoid precision loss from subtracting large absolute coordinates.
215
+ Accepted rounding is recorded as an informational `MESH_POSITION_ROUNDING` diagnostic in
216
+ the conversion report, with its resource ID and measured error. Bounds describe the
217
+ reconstructed encoded vertices in the target frame, including float32 rounding. Preserve
218
+ this metadata when writing resources: the GLB itself contains local coordinates and does
219
+ not embed its absolute origin or CRS. Source arrays are not mutated. The conversion core
220
+ awaits writes, checks byte limits, reports progress, finalizes on success, and aborts on
221
+ read/codec/write/cancellation failure.
222
+
223
+ Applications still select source geometry, apply source placement and corresponding normal
224
+ transforms before conversion, reconcile format axis conventions, and package hierarchy,
225
+ refinement and geometric error. This increment does not extract complete scenes, convert
226
+ I3S elevation placement, map full source materials/features, or emit a complete tileset.
227
+
228
+ ## V5 single-mesh browser package
229
+
230
+ `createSingleMeshTilesetSink` connects the spatial mesh codec to a bounded **3D Tiles 1.1**
231
+ output package. It accepts exactly one unmodified codec output, with resolved **EPSG:4978**
232
+ ECEF xyz coordinates in meters and ellipsoidal heights. The CRS must use the compact string
233
+ `'EPSG:4978'`; other output frames and identifiers are rejected in this initial profile.
234
+
235
+ ```ts
236
+ import {createSingleMeshTilesetSink} from '@loaders.gl/tile-converter/v5/browser';
237
+
238
+ const meshSink = createSingleMeshTilesetSink({
239
+ maxTotalBytes: 16 * 1024 * 1024,
240
+ geometricError: 0.001 // Meters; include source geometric error and position rounding.
241
+ });
242
+ // Pass meshSink to convertTileset with a mesh codec targeting EPSG:4978.
243
+ // After conversion completes, save both files together in the same directory:
244
+ const files = meshSink.getFiles(); // [{resourceId: 'mesh.glb', blob}, {resourceId: 'tileset.json', blob}]
245
+ ```
246
+
247
+ The package uses fixed relative names, a single root tile with `REPLACE` refinement, and
248
+ explicit geometric error. The tile transform cancels the standard glTF y-up to z-up rotation
249
+ and adds the ECEF origin; its box encloses encoded local positions in the corresponding tile
250
+ frame. The supplied `geometricError` must be finite, nonnegative, and at least the codec's
251
+ measured reconstruction error. It is the application's responsibility to include source
252
+ LOD or simplification error; the sink does not estimate it.
253
+
254
+ `maxTotalBytes` is captured when the sink is created and includes the GLB and UTF-8 JSON Blob sizes. This bounds **retained output**,
255
+ not peak conversion or serialization memory. Conversion report counts/byte totals describe
256
+ codec resources (one GLB), while this sink retains two files. Files are exposed only after
257
+ successful finalization; failed writes, cancellation, zero/multiple meshes, or conversion
258
+ failure abort and clear the package through the core lifecycle. The sink trusts codec GLB
259
+ and local-bound metadata; applications must not alter them before writing. Download UI,
260
+ workers, multi-tile hierarchy, and source-scene extraction remain separate work.
261
+
262
+ ### Download a 3TZ archive
263
+
264
+ `createSingleMeshTilesetArchive` packages the finalized sink's two files into a **3TZ archive**
265
+ with a final `@3dtilesIndex1@` path-hash index. The format writer lives in `@loaders.gl/3d-tiles`,
266
+ uses typed arrays and native Blobs, and needs no filesystem or Node.js Buffer.
267
+
268
+ ```ts
269
+ import {createSingleMeshTilesetArchive} from '@loaders.gl/tile-converter/v5/browser';
270
+
271
+ const archive = await createSingleMeshTilesetArchive(meshSink.getFiles(), {
272
+ maxArchiveBytes: 16 * 1024 * 1024
273
+ });
274
+ const downloadUrl = URL.createObjectURL(archive);
275
+ // Attach downloadUrl to an application-owned link with download="tileset.3tz".
276
+ // When the link is no longer needed:
277
+ URL.revokeObjectURL(downloadUrl);
278
+ ```
279
+
280
+ The required budget includes all ZIP headers and the index (368 bytes of overhead for this
281
+ fixed two-file profile) and is checked **before either Blob is read**. STORE compression,
282
+ fixed timestamps, stable file order, and relative paths produce deterministic bytes. The
283
+ returned Blob uses `application/vnd.maxar.archive.3tz+zip`; save it with `.3tz`. Inputs must be
284
+ unmodified output from a successfully finalized sink. The budget covers archive size, not
285
+ peak memory: the input Blobs and transient ZIP buffers remain additional allocations.
286
+ Packaging is asynchronous but does not support mid-encoding cancellation or streaming.
287
+
288
+ The legacy v4 I3S converter already writes **SLPK** archives. Portable v5 SLPK output requires
289
+ an I3S scene-layer writer and its node/resource layout; a 3D Tiles package cannot be saved as
290
+ SLPK merely by changing its extension.
291
+
58
292
  ## V5 spatial conversion
59
293
 
60
294
  The `@loaders.gl/tile-converter/v5` entrypoint can reuse the CRS and elevation operations from