@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.
- package/LICENSE +1 -1
- package/README.md +180 -4
- package/bin/converter-v5.js +2 -0
- package/dist/converter.min.cjs +172 -182
- package/dist/i3s-server/bin/i3s-server.min.cjs +113 -158
- package/dist/index.cjs +18832 -19228
- package/dist/index.js +19566 -19968
- package/dist/v5/adapters.cjs +2002 -230
- package/dist/v5/adapters.d.ts +20 -0
- package/dist/v5/adapters.js +2005 -214
- package/dist/v5/archive-source.d.ts +36 -0
- package/dist/v5/browser-tileset-source.d.ts +55 -11
- package/dist/v5/browser.cjs +18 -0
- package/dist/v5/browser.d.ts +15 -1
- package/dist/v5/browser.js +26 -0
- package/dist/v5/conversion-api.d.ts +34 -0
- package/dist/v5/converter-cli.d.ts +1 -0
- package/dist/v5/converter-cli.js +1230 -0
- package/dist/v5/core.cjs +547 -77
- package/dist/v5/core.d.ts +3 -1
- package/dist/v5/core.js +540 -70
- package/dist/v5/feature-arrow.d.ts +1 -1
- package/dist/v5/i3s-mesh-conversion.d.ts +5 -1
- package/dist/v5/i3s-mesh-source-features.d.ts +2 -2
- package/dist/v5/i3s-mesh-source-material.d.ts +1 -1
- package/dist/v5/i3s-mesh-source.d.ts +2 -2
- package/dist/v5/i3s-selection-source.d.ts +70 -0
- package/dist/v5/index.cjs +15 -0
- package/dist/v5/index.d.ts +13 -1
- package/dist/v5/index.js +22 -0
- package/dist/v5/mesh-conversion.d.ts +21 -3
- package/dist/v5/mesh-draco.d.ts +2 -2
- package/dist/v5/mesh-features.d.ts +2 -2
- package/dist/v5/mesh-hierarchy.d.ts +47 -0
- package/dist/v5/mesh-source-features.d.ts +6 -5
- package/dist/v5/mesh-source-schema.d.ts +12 -0
- package/dist/v5/mesh-source-texture.d.ts +3 -3
- package/dist/v5/mesh-source.d.ts +10 -7
- package/dist/v5/mesh-tileset-sink.d.ts +5 -0
- package/dist/v5/mesh.d.ts +1 -1
- package/dist/v5/node-cli.d.ts +9 -0
- package/dist/v5/node-conversion.d.ts +34 -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 +913 -0
- package/dist/v5/node.d.ts +6 -0
- package/dist/v5/node.js +890 -0
- package/dist/v5/point-cloud-attributes.d.ts +19 -0
- package/dist/v5/point-cloud-conversion.d.ts +1 -1
- package/dist/v5/point-cloud-copc.d.ts +44 -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 +1 -1
- package/dist/v5/point-cloud-source.d.ts +1 -1
- package/dist/v5/point-cloud-tileset.d.ts +44 -0
- package/dist/v5/point-cloud.d.ts +1 -1
- 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 +1 -1
- package/dist/v5/tileset-source.d.ts +1 -1
- package/package.json +25 -32
- package/src/index.ts +4 -1
- package/src/v4/3d-tiles-converter/3d-tiles-converter.ts +11 -12
- package/src/v4/3d-tiles-converter/helpers/3d-tiles-content-converter.ts +7 -3
- package/src/v4/3d-tiles-converter/helpers/i3s-obb-to-3d-tiles-obb.ts +2 -2
- package/src/v4/3d-tiles-converter/helpers/load-i3s.ts +4 -6
- package/src/v4/3d-tiles-converter/helpers/texture-atlas.ts +1 -1
- package/src/v4/3d-tiles-converter/helpers/validate-i3s-spatial-reference.ts +2 -2
- package/src/v4/3d-tiles-converter/json-templates/tileset.ts +58 -39
- package/src/v4/converter-cli.ts +10 -4
- package/src/v4/deps-installer/deps-installer.ts +3 -3
- package/src/v4/i3s-converter/helpers/attribute-metadata-info.ts +1 -1
- package/src/v4/i3s-converter/helpers/batch-ids-extensions.ts +9 -10
- package/src/v4/i3s-converter/helpers/conversion-gltf.ts +108 -0
- package/src/v4/i3s-converter/helpers/coordinate-converter.ts +6 -5
- package/src/v4/i3s-converter/helpers/create-scene-server-path.ts +5 -6
- package/src/v4/i3s-converter/helpers/feature-attributes.ts +4 -4
- package/src/v4/i3s-converter/helpers/geometry-attributes.ts +1 -1
- package/src/v4/i3s-converter/helpers/geometry-converter.ts +69 -77
- package/src/v4/i3s-converter/helpers/gltf-attributes.ts +58 -74
- package/src/v4/i3s-converter/helpers/load-3d-tiles.ts +6 -54
- package/src/v4/i3s-converter/helpers/node-debug.ts +7 -3
- package/src/v4/i3s-converter/helpers/node-index-document.ts +11 -9
- package/src/v4/i3s-converter/helpers/node-pages.ts +3 -4
- package/src/v4/i3s-converter/helpers/preprocess-3d-tiles.ts +2 -2
- package/src/v4/i3s-converter/helpers/tileset-traversal.ts +1 -1
- package/src/v4/i3s-converter/i3s-converter.ts +29 -31
- package/src/v4/i3s-converter/json-templates/geometry-definitions.ts +71 -66
- package/src/v4/i3s-converter/json-templates/layers.ts +97 -132
- package/src/v4/i3s-converter/json-templates/metadata.ts +31 -21
- package/src/v4/i3s-converter/json-templates/node.ts +60 -74
- package/src/v4/i3s-converter/json-templates/scene-server.ts +36 -27
- package/src/v4/i3s-converter/json-templates/shared-resources.ts +62 -123
- package/src/v4/i3s-converter/json-templates/store.ts +39 -57
- package/src/v4/i3s-converter/types.ts +4 -4
- package/src/v4/i3s-server/controllers/slpk-controller.ts +3 -3
- package/src/v4/i3s-server/utils/create-scene-server.ts +3 -3
- package/src/v4/lib/utils/builder-input.ts +8 -0
- package/src/v4/lib/utils/conversion-dump.ts +5 -1
- package/src/v4/lib/utils/file-utils.ts +1 -1
- package/src/v4/lib/utils/hash-string.ts +21 -0
- package/src/v4/lib/utils/lod-conversion-utils.ts +3 -3
- package/src/v4/pgm-loader.ts +2 -2
- package/src/v5/adapters.ts +53 -0
- package/src/v5/archive-source.ts +235 -0
- package/src/v5/browser-tileset-source.ts +418 -48
- package/src/v5/browser.ts +46 -0
- package/src/v5/conversion-api.ts +184 -17
- package/src/v5/conversion-archive.ts +2 -2
- package/src/v5/converter-cli.ts +28 -0
- package/src/v5/core.ts +7 -0
- package/src/v5/feature-arrow.ts +2 -2
- package/src/v5/i3s-mesh-conversion.ts +41 -3
- package/src/v5/i3s-mesh-sink.ts +1 -1
- package/src/v5/i3s-mesh-source-features.ts +3 -3
- package/src/v5/i3s-mesh-source-material.ts +18 -11
- package/src/v5/i3s-mesh-source.ts +3 -2
- package/src/v5/i3s-selection-source.ts +298 -0
- package/src/v5/index.ts +41 -0
- package/src/v5/mesh-conversion.ts +24 -4
- package/src/v5/mesh-draco.ts +2 -2
- package/src/v5/mesh-features.ts +3 -3
- package/src/v5/mesh-hierarchy.ts +648 -0
- package/src/v5/mesh-source-features.ts +165 -51
- package/src/v5/mesh-source-schema.ts +140 -0
- package/src/v5/mesh-source-texture.ts +49 -19
- package/src/v5/mesh-source.ts +80 -49
- package/src/v5/mesh-tileset-archive.ts +1 -1
- package/src/v5/mesh-tileset-sink.ts +2 -2
- package/src/v5/mesh.ts +1 -1
- package/src/v5/node-cli.ts +201 -0
- package/src/v5/node-conversion.ts +362 -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-attributes.ts +113 -0
- package/src/v5/point-cloud-conversion.ts +1 -1
- package/src/v5/point-cloud-copc.ts +289 -0
- package/src/v5/point-cloud-file.ts +306 -0
- package/src/v5/point-cloud-potree.ts +134 -0
- package/src/v5/point-cloud-source-encoder.ts +1 -1
- package/src/v5/point-cloud-source.ts +1 -1
- package/src/v5/point-cloud-spatial.ts +2 -2
- package/src/v5/point-cloud-tileset.ts +137 -0
- package/src/v5/point-cloud.ts +3 -3
- package/src/v5/resource-fetcher.ts +4 -1
- package/src/v5/resource-manifest.ts +40 -5
- package/src/v5/selected-mesh-source.ts +384 -0
- package/src/v5/spatial-conversion.ts +90 -19
- package/src/v5/tileset-source.ts +2 -2
|
@@ -0,0 +1,384 @@
|
|
|
1
|
+
// loaders.gl
|
|
2
|
+
// SPDX-License-Identifier: MIT
|
|
3
|
+
// Copyright (c) vis.gl contributors
|
|
4
|
+
|
|
5
|
+
import {prepareMeshHierarchy} from './mesh-hierarchy.js';
|
|
6
|
+
import {coreApi, parse} from '@loaders.gl/core';
|
|
7
|
+
import type {Loader, LoaderOptions} from '@loaders.gl/core/loader-utils';
|
|
8
|
+
import {Tiles3DLoader} from '@loaders.gl/tiles-3d/3d-tiles';
|
|
9
|
+
import {Tiles3DSource, Tileset3D} from '@loaders.gl/tiles-3d';
|
|
10
|
+
import type {
|
|
11
|
+
TilesetSourceInput,
|
|
12
|
+
TilesetSourceMetadata,
|
|
13
|
+
TilesetSpatialReference
|
|
14
|
+
} from '@loaders.gl/tiles-3d';
|
|
15
|
+
import type {DracoLibraryOptions} from '@loaders.gl/draco/bundled';
|
|
16
|
+
import {
|
|
17
|
+
createBrowserTilesetConversionSource,
|
|
18
|
+
selectBrowserTilesetContent,
|
|
19
|
+
createTiles3DConversionSpatialContext,
|
|
20
|
+
TileConversionError
|
|
21
|
+
} from '@loaders.gl/tile-converter/v5/core';
|
|
22
|
+
import type {
|
|
23
|
+
BrowserTilesetConversionInspection,
|
|
24
|
+
BrowserTilesetConversionInputResource,
|
|
25
|
+
TileConversionSource
|
|
26
|
+
} from '@loaders.gl/tile-converter/v5/core';
|
|
27
|
+
import {
|
|
28
|
+
createMeshTilesetConversionSource,
|
|
29
|
+
createTileConversionResourceFetcher
|
|
30
|
+
} from '@loaders.gl/tile-converter/v5/adapters';
|
|
31
|
+
import type {
|
|
32
|
+
MeshSourceResource,
|
|
33
|
+
MeshSourceFeatureOptions
|
|
34
|
+
} from '@loaders.gl/tile-converter/v5/adapters';
|
|
35
|
+
|
|
36
|
+
/** Required transport, decoded geometry, retained output, and archive budgets for this demo. */
|
|
37
|
+
export const SELECTED_MESH_LIMITS = {
|
|
38
|
+
/** Root JSON plus selected content transport bytes; also the decoded geometry/image/feature byte gate. */
|
|
39
|
+
maxInputBytes: 16 * 1024 * 1024,
|
|
40
|
+
/** Maximum declared content placements inspected. */
|
|
41
|
+
maxInputResources: 1000,
|
|
42
|
+
/** Maximum inspected root/external document placements. */
|
|
43
|
+
maxTilesetDocuments: 32,
|
|
44
|
+
/** Maximum explicitly selected content placements in one partial archive. */
|
|
45
|
+
maxSelectedResources: 64,
|
|
46
|
+
/** Maximum primitive placements authored across all selected contents. */
|
|
47
|
+
maxMeshResources: 64,
|
|
48
|
+
/** Maximum retained file bytes and final archive bytes. */
|
|
49
|
+
maxOutputBytes: 32 * 1024 * 1024,
|
|
50
|
+
/** Maximum measured reconstruction error in meters. */
|
|
51
|
+
maxPositionError: 0.01
|
|
52
|
+
} as const;
|
|
53
|
+
|
|
54
|
+
/** Injected transport and decoder assets for the bounded selected mesh profile. */
|
|
55
|
+
export interface SelectedMeshSourceOptions {
|
|
56
|
+
/** Require a complete qualified explicit tree; pair with createMeshHierarchyTilesetSink. */
|
|
57
|
+
readonly preserveHierarchy?: boolean;
|
|
58
|
+
/** Inspected content placement identifiers; one to 64 distinct selections. */
|
|
59
|
+
readonly resourceIds: readonly string[];
|
|
60
|
+
/** Cancellation across qualification, raw reads and decoding. */
|
|
61
|
+
readonly signal: AbortSignal;
|
|
62
|
+
/** Application-owned decoder asset locations or modules. */
|
|
63
|
+
readonly dracoLibraryOptions: DracoLibraryOptions;
|
|
64
|
+
/** Transport for decoded buffers/images, confined by a platform adapter when local. */
|
|
65
|
+
readonly fetcher?: typeof fetch;
|
|
66
|
+
/** Optional source owning the raw input lifetime, such as a Node file/archive adapter. */
|
|
67
|
+
readonly source?: TileConversionSource<
|
|
68
|
+
BrowserTilesetConversionInspection,
|
|
69
|
+
BrowserTilesetConversionInputResource
|
|
70
|
+
>;
|
|
71
|
+
/** Explicit feature mapping, or the qualified inline structural metadata profile. */
|
|
72
|
+
readonly features?: MeshSourceFeatureOptions;
|
|
73
|
+
/** Lower the shared 16 MiB transport and decoded mesh limit. */
|
|
74
|
+
readonly maxInputBytes?: number;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** Qualified selected-mesh decoding and source error policy. */
|
|
78
|
+
export interface SelectedMeshConversionSource {
|
|
79
|
+
/** Lazy source that releases each dedicated mesh runtime after use. */
|
|
80
|
+
readonly source: TileConversionSource<BrowserTilesetConversionInspection, MeshSourceResource>;
|
|
81
|
+
/** Native ECEF coordinate context, qualified before reading mesh content. */
|
|
82
|
+
readonly spatialContext: ReturnType<typeof createTiles3DConversionSpatialContext>;
|
|
83
|
+
/** Conservative metric source error plus the position reconstruction allowance. */
|
|
84
|
+
readonly geometricError: number;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/** Qualifies and constructs a lazy selected mesh source without platform I/O or output ownership. */
|
|
88
|
+
export async function createSelectedMeshConversionSource(
|
|
89
|
+
inspection: BrowserTilesetConversionInspection,
|
|
90
|
+
options: SelectedMeshSourceOptions
|
|
91
|
+
): Promise<SelectedMeshConversionSource> {
|
|
92
|
+
const {
|
|
93
|
+
resourceIds,
|
|
94
|
+
signal,
|
|
95
|
+
dracoLibraryOptions,
|
|
96
|
+
features,
|
|
97
|
+
preserveHierarchy,
|
|
98
|
+
fetcher = fetch
|
|
99
|
+
} = options;
|
|
100
|
+
const maxInputBytes = options.maxInputBytes ?? SELECTED_MESH_LIMITS.maxInputBytes;
|
|
101
|
+
if (
|
|
102
|
+
!Number.isSafeInteger(maxInputBytes) ||
|
|
103
|
+
maxInputBytes < 1 ||
|
|
104
|
+
maxInputBytes > SELECTED_MESH_LIMITS.maxInputBytes
|
|
105
|
+
)
|
|
106
|
+
throw new TileConversionError(
|
|
107
|
+
'INVALID_SELECTED_MESH_OPTIONS',
|
|
108
|
+
'Selected mesh input budget must be a positive safe integer at most 16 MiB'
|
|
109
|
+
);
|
|
110
|
+
if (
|
|
111
|
+
!resourceIds.length ||
|
|
112
|
+
resourceIds.length > SELECTED_MESH_LIMITS.maxSelectedResources ||
|
|
113
|
+
new Set(resourceIds).size !== resourceIds.length ||
|
|
114
|
+
resourceIds.some(
|
|
115
|
+
identifier => !inspection.resources.some(resource => resource.resourceId === identifier)
|
|
116
|
+
)
|
|
117
|
+
)
|
|
118
|
+
throw new Error('Select between 1 and 64 distinct content placements.');
|
|
119
|
+
if (preserveHierarchy !== undefined && typeof preserveHierarchy !== 'boolean')
|
|
120
|
+
throw new TileConversionError(
|
|
121
|
+
'MESH_HIERARCHY_UNSUPPORTED',
|
|
122
|
+
'preserveHierarchy must be a boolean'
|
|
123
|
+
);
|
|
124
|
+
if (preserveHierarchy) prepareMeshHierarchy(inspection, resourceIds);
|
|
125
|
+
const descriptors = inspection.resources.filter(resource =>
|
|
126
|
+
resourceIds.includes(resource.resourceId)
|
|
127
|
+
);
|
|
128
|
+
const selections = descriptors.map(descriptor =>
|
|
129
|
+
selectBrowserTilesetContent(inspection, descriptor)
|
|
130
|
+
);
|
|
131
|
+
const selectedContents = new Map(
|
|
132
|
+
descriptors.map((descriptor, index) => [descriptor.resourceId, selections[index]])
|
|
133
|
+
);
|
|
134
|
+
if (
|
|
135
|
+
!preserveHierarchy &&
|
|
136
|
+
resourceIds.length > 1 &&
|
|
137
|
+
selections.some(selection => !selection.isLeaf)
|
|
138
|
+
)
|
|
139
|
+
throw new Error(
|
|
140
|
+
'Multi-selection requires leaf contents; source LOD hierarchy is not exported.'
|
|
141
|
+
);
|
|
142
|
+
signal.throwIfAborted();
|
|
143
|
+
const boundedFetcher = createTileConversionResourceFetcher({
|
|
144
|
+
maxInputBytes,
|
|
145
|
+
initialInputBytes: inspection.tilesetBytes,
|
|
146
|
+
fetcher,
|
|
147
|
+
signal
|
|
148
|
+
});
|
|
149
|
+
const qualification = createSelectedRuntime(
|
|
150
|
+
inspection.tileset,
|
|
151
|
+
new Uint8Array(),
|
|
152
|
+
signal,
|
|
153
|
+
boundedFetcher,
|
|
154
|
+
inspection.rootUrl,
|
|
155
|
+
dracoLibraryOptions
|
|
156
|
+
);
|
|
157
|
+
let spatialContext: ReturnType<typeof createTiles3DConversionSpatialContext>;
|
|
158
|
+
try {
|
|
159
|
+
await qualification.tilesetInitializationPromise;
|
|
160
|
+
const metadata = await createMeshTilesetConversionSource(qualification).inspect(signal);
|
|
161
|
+
spatialContext = createTiles3DConversionSpatialContext(metadata.spatialReference!);
|
|
162
|
+
} finally {
|
|
163
|
+
qualification.destroy();
|
|
164
|
+
}
|
|
165
|
+
const rawSource =
|
|
166
|
+
options.source ??
|
|
167
|
+
createBrowserTilesetConversionSource({
|
|
168
|
+
input: inspection.rootUrl,
|
|
169
|
+
...SELECTED_MESH_LIMITS,
|
|
170
|
+
maxInputBytes,
|
|
171
|
+
fetcher: boundedFetcher
|
|
172
|
+
});
|
|
173
|
+
const selectedInspection = {...inspection, resources: descriptors};
|
|
174
|
+
const source = {
|
|
175
|
+
/** Reuses inspected declarations without fetching the root again. */
|
|
176
|
+
inspect: async (inspectionSignal?: AbortSignal) => {
|
|
177
|
+
signal.throwIfAborted();
|
|
178
|
+
inspectionSignal?.throwIfAborted();
|
|
179
|
+
return selectedInspection;
|
|
180
|
+
},
|
|
181
|
+
/** Decodes and releases each selected placement in declaration order. */
|
|
182
|
+
async *read(_inspection: BrowserTilesetConversionInspection, readSignal?: AbortSignal) {
|
|
183
|
+
const signal = readSignal ? AbortSignal.any([options.signal, readSignal]) : options.signal;
|
|
184
|
+
signal.throwIfAborted();
|
|
185
|
+
let decodedBytes = 0;
|
|
186
|
+
let primitiveCount = 0;
|
|
187
|
+
// A 3D Tiles contents array has one tile transform, so its GLBs need a shared origin.
|
|
188
|
+
const tileOrigins = new Map<string, readonly [number, number, number]>();
|
|
189
|
+
for await (const raw of rawSource.read(selectedInspection, signal)) {
|
|
190
|
+
const selection = selectedContents.get(raw.resourceId);
|
|
191
|
+
if (!selection)
|
|
192
|
+
throw new TileConversionError(
|
|
193
|
+
'INVALID_BROWSER_SELECTION',
|
|
194
|
+
'Source yielded an uninspected content placement'
|
|
195
|
+
);
|
|
196
|
+
const contentUrl = raw.contentUrl ?? selection.contentUrl;
|
|
197
|
+
const runtime = createSelectedRuntime(
|
|
198
|
+
selection.tileset,
|
|
199
|
+
raw.data,
|
|
200
|
+
signal,
|
|
201
|
+
boundedFetcher,
|
|
202
|
+
contentUrl,
|
|
203
|
+
dracoLibraryOptions,
|
|
204
|
+
spatialContext.spatialReference
|
|
205
|
+
);
|
|
206
|
+
try {
|
|
207
|
+
await runtime.tilesetInitializationPromise;
|
|
208
|
+
const meshSource = createMeshTilesetConversionSource(runtime, {
|
|
209
|
+
unloadContent: true,
|
|
210
|
+
features,
|
|
211
|
+
readExternalResource: async (uri, _contentUri, resourceSignal) => {
|
|
212
|
+
const response = await boundedFetcher(new URL(uri, contentUrl), {
|
|
213
|
+
signal: resourceSignal
|
|
214
|
+
});
|
|
215
|
+
return new Uint8Array(await response.arrayBuffer());
|
|
216
|
+
}
|
|
217
|
+
});
|
|
218
|
+
const metadata = await meshSource.inspect(signal);
|
|
219
|
+
const selectedContext = createTiles3DConversionSpatialContext(metadata.spatialReference!);
|
|
220
|
+
if (
|
|
221
|
+
selectedContext.spatialReference.coordinateEpoch !==
|
|
222
|
+
spatialContext.spatialReference.coordinateEpoch
|
|
223
|
+
)
|
|
224
|
+
throw new TileConversionError(
|
|
225
|
+
'MESH_SOURCE_EPOCH_MISMATCH',
|
|
226
|
+
'Selected content coordinate epoch must match the root; epoch transformations are unsupported'
|
|
227
|
+
);
|
|
228
|
+
let meshCount = 0;
|
|
229
|
+
for await (const resource of meshSource.read(metadata, signal)) {
|
|
230
|
+
meshCount++;
|
|
231
|
+
if (++primitiveCount > SELECTED_MESH_LIMITS.maxMeshResources)
|
|
232
|
+
throw new Error('Selected contents exceed the mesh primitive placement limit.');
|
|
233
|
+
decodedBytes += measureMeshBytes(resource);
|
|
234
|
+
if (decodedBytes > maxInputBytes)
|
|
235
|
+
throw new TileConversionError(
|
|
236
|
+
'INPUT_RESOURCE_TOO_LARGE',
|
|
237
|
+
'Selected decoded geometry and encoded images exceed the aggregate input byte limit.'
|
|
238
|
+
);
|
|
239
|
+
const tileIdentifier = raw.resourceId.replace(/content-\d+$/, '');
|
|
240
|
+
if (preserveHierarchy && !tileOrigins.has(tileIdentifier))
|
|
241
|
+
tileOrigins.set(tileIdentifier, [...resource.origin]);
|
|
242
|
+
yield {
|
|
243
|
+
...resource,
|
|
244
|
+
origin: preserveHierarchy ? tileOrigins.get(tileIdentifier)! : resource.origin,
|
|
245
|
+
id: `${raw.resourceId}/${resource.id}`,
|
|
246
|
+
contentResourceId: raw.resourceId
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
if (!meshCount) throw new Error('Selected content has no mesh primitive.');
|
|
250
|
+
} finally {
|
|
251
|
+
runtime.destroy();
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
};
|
|
256
|
+
return {
|
|
257
|
+
source,
|
|
258
|
+
spatialContext,
|
|
259
|
+
geometricError:
|
|
260
|
+
Math.max(...selections.map(selection => selection.geometricError)) +
|
|
261
|
+
SELECTED_MESH_LIMITS.maxPositionError
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/** Dedicated runtime source that inherits an absent external-document CRS from the qualified root. */
|
|
266
|
+
class SelectedTiles3DSource extends Tiles3DSource {
|
|
267
|
+
/** Parent frame; explicit, unknown or unresolved child declarations are never replaced. */
|
|
268
|
+
private readonly inheritedSpatialReference?: TilesetSpatialReference;
|
|
269
|
+
|
|
270
|
+
/** Creates a selected source with optional external-document frame inheritance. */
|
|
271
|
+
constructor(
|
|
272
|
+
input: TilesetSourceInput,
|
|
273
|
+
options: LoaderOptions,
|
|
274
|
+
inheritedSpatialReference?: TilesetSpatialReference
|
|
275
|
+
) {
|
|
276
|
+
super(input, options);
|
|
277
|
+
this.inheritedSpatialReference = inheritedSpatialReference;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/** Returns source metadata with the same absent-CRS inheritance used by external tilesets. */
|
|
281
|
+
override getMetadata(): TilesetSourceMetadata {
|
|
282
|
+
const metadata = super.getMetadata();
|
|
283
|
+
const reference = metadata.spatialReference;
|
|
284
|
+
if (
|
|
285
|
+
this.inheritedSpatialReference &&
|
|
286
|
+
reference?.crs.state === 'absent' &&
|
|
287
|
+
reference.coordinateEpoch === undefined &&
|
|
288
|
+
!reference.verticalCrs &&
|
|
289
|
+
!reference.warnings.length
|
|
290
|
+
)
|
|
291
|
+
return {...metadata, spatialReference: this.inheritedSpatialReference};
|
|
292
|
+
return metadata;
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/** Creates a dedicated runtime whose only content response is the bounded selected payload. */
|
|
297
|
+
function createSelectedRuntime(
|
|
298
|
+
document: BrowserTilesetConversionInspection['tileset'],
|
|
299
|
+
data: Uint8Array,
|
|
300
|
+
signal: AbortSignal,
|
|
301
|
+
boundedFetcher: typeof fetch,
|
|
302
|
+
contentUrl: string,
|
|
303
|
+
dracoLibraryOptions: DracoLibraryOptions,
|
|
304
|
+
inheritedSpatialReference?: TilesetSpatialReference
|
|
305
|
+
): Tileset3D {
|
|
306
|
+
const {
|
|
307
|
+
modules,
|
|
308
|
+
decoderProfile,
|
|
309
|
+
CDN: contentDeliveryNetwork,
|
|
310
|
+
useLocalLibraries
|
|
311
|
+
} = dracoLibraryOptions;
|
|
312
|
+
const loadOptions = {
|
|
313
|
+
modules,
|
|
314
|
+
core: {
|
|
315
|
+
worker: false,
|
|
316
|
+
fetch: boundedFetcher,
|
|
317
|
+
CDN: contentDeliveryNetwork,
|
|
318
|
+
useLocalLibraries
|
|
319
|
+
},
|
|
320
|
+
// Reuse the full decoder already bundled for output verification.
|
|
321
|
+
draco: {decoderProfile},
|
|
322
|
+
// Preserve authored UVs and the transform for the converter's qualified GLB writer.
|
|
323
|
+
gltf: {
|
|
324
|
+
loadImages: false,
|
|
325
|
+
decompressMeshes: true,
|
|
326
|
+
excludeExtensions: {
|
|
327
|
+
KHR_texture_transform: false,
|
|
328
|
+
// Only Draco input compression is qualified by this conversion profile.
|
|
329
|
+
EXT_meshopt_compression: false,
|
|
330
|
+
KHR_meshopt_compression: false
|
|
331
|
+
}
|
|
332
|
+
},
|
|
333
|
+
'3d-tiles': {loadGLTF: true}
|
|
334
|
+
};
|
|
335
|
+
return new Tileset3D(
|
|
336
|
+
new SelectedTiles3DSource(
|
|
337
|
+
{
|
|
338
|
+
url: contentUrl,
|
|
339
|
+
loader: Tiles3DLoader,
|
|
340
|
+
resolver: {
|
|
341
|
+
/** Normalizes the selected hierarchy through the format loader, retaining ancestor transforms. */
|
|
342
|
+
loadRoot: async (_url: string, loader: Loader, options: LoaderOptions) =>
|
|
343
|
+
parse(new TextEncoder().encode(JSON.stringify(document)), loader, options),
|
|
344
|
+
/** Decodes only the selected bounded payload; no sibling content can be requested. */
|
|
345
|
+
loadResource: async (_url: string, loader: Loader, options: LoaderOptions) => {
|
|
346
|
+
signal.throwIfAborted();
|
|
347
|
+
return parse(data, loader, options, {
|
|
348
|
+
url: contentUrl,
|
|
349
|
+
baseUrl: contentUrl.startsWith('blob:') ? contentUrl : new URL('.', contentUrl).href,
|
|
350
|
+
fetch: boundedFetcher,
|
|
351
|
+
coreApi,
|
|
352
|
+
_parse: parse
|
|
353
|
+
});
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
},
|
|
357
|
+
loadOptions,
|
|
358
|
+
inheritedSpatialReference
|
|
359
|
+
)
|
|
360
|
+
);
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
/** Charges geometry, encoded image bytes, triangle associations and Arrow columns after extraction. */
|
|
364
|
+
export function measureMeshBytes(resource: MeshSourceResource): number {
|
|
365
|
+
const featureBytes = resource.features
|
|
366
|
+
? resource.features.triangleFeatureIndices.byteLength +
|
|
367
|
+
resource.features.batches.reduce(
|
|
368
|
+
(bytes, batch) =>
|
|
369
|
+
bytes +
|
|
370
|
+
batch.data.schema.fields.reduce(
|
|
371
|
+
(columnBytes, field) =>
|
|
372
|
+
columnBytes + (batch.data.getChild(field.name)?.byteLength ?? 0),
|
|
373
|
+
0
|
|
374
|
+
),
|
|
375
|
+
0
|
|
376
|
+
)
|
|
377
|
+
: 0;
|
|
378
|
+
return Object.values(resource.mesh.attributes).reduce(
|
|
379
|
+
(bytes, attribute) => bytes + attribute.value.byteLength,
|
|
380
|
+
(resource.mesh.indices?.value.byteLength ?? 0) +
|
|
381
|
+
(resource.material?.baseColorTexture?.data.byteLength ?? 0) +
|
|
382
|
+
featureBytes
|
|
383
|
+
);
|
|
384
|
+
}
|
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
I3SSpatialTransformer,
|
|
8
8
|
markTilesetSpatialReferenceTransformed,
|
|
9
9
|
Tiles3DSpatialTransformer
|
|
10
|
-
} from '@loaders.gl/tiles';
|
|
10
|
+
} from '@loaders.gl/tiles-3d';
|
|
11
11
|
import {Matrix4} from '@math.gl/core';
|
|
12
12
|
import type {
|
|
13
13
|
CreateTilesetSpatialReferenceOptions,
|
|
@@ -17,7 +17,7 @@ import type {
|
|
|
17
17
|
Tiles3DSpatialBoundingVolume,
|
|
18
18
|
TilesetSpatialOptions,
|
|
19
19
|
TilesetSpatialReference
|
|
20
|
-
} from '@loaders.gl/tiles';
|
|
20
|
+
} from '@loaders.gl/tiles-3d';
|
|
21
21
|
import {TileConversionError} from './conversion-api.js';
|
|
22
22
|
import type {TileConversionDiagnostic} from './conversion-api.js';
|
|
23
23
|
|
|
@@ -115,9 +115,11 @@ export function createTiles3DConversionSpatialContext(
|
|
|
115
115
|
return {
|
|
116
116
|
spatialReference: outputSpatialReference,
|
|
117
117
|
transformPositions: positions =>
|
|
118
|
-
transformer ? transformer.transformPositions(positions) :
|
|
118
|
+
transformer ? transformer.transformPositions(positions) : copyNativePositions(positions),
|
|
119
119
|
transformNormals: (normals, positions) =>
|
|
120
|
-
transformer
|
|
120
|
+
transformer
|
|
121
|
+
? transformer.transformNormals(normals, positions)
|
|
122
|
+
: copyNativeNormals(normals, positions),
|
|
121
123
|
transformBoundingVolume: (volume, sourceTransform) =>
|
|
122
124
|
transformer
|
|
123
125
|
? transformer.transformBoundingVolume(volume, sourceTransform)
|
|
@@ -168,20 +170,12 @@ export function createI3SConversionSpatialContext(
|
|
|
168
170
|
positions: ArrayLike<number>,
|
|
169
171
|
normalReferenceFrame?: string
|
|
170
172
|
): Float32Array => {
|
|
173
|
+
validateNormalReferenceFrame(normalReferenceFrame);
|
|
174
|
+
const targetNormals = copyNativeNormals(normals, positions);
|
|
171
175
|
if (
|
|
172
176
|
!spatialReference.sourceCrs ||
|
|
173
177
|
['local', 'cartesian'].includes(spatialReference.coordinateFrame)
|
|
174
178
|
) {
|
|
175
|
-
// Unreferenced/native local vectors already use the retained position basis.
|
|
176
|
-
const targetNormals = Float32Array.from(normals);
|
|
177
|
-
for (let index = 0; index < normals.length; index += 3) {
|
|
178
|
-
const length = Math.hypot(normals[index], normals[index + 1], normals[index + 2]);
|
|
179
|
-
if (length) {
|
|
180
|
-
for (let axis = 0; axis < 3; axis++) {
|
|
181
|
-
targetNormals[index + axis] = normals[index + axis] / length;
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
179
|
return targetNormals;
|
|
186
180
|
}
|
|
187
181
|
nativeNormalTransformer ||= createSpatialTransformer(
|
|
@@ -196,7 +190,7 @@ export function createI3SConversionSpatialContext(
|
|
|
196
190
|
options
|
|
197
191
|
)
|
|
198
192
|
);
|
|
199
|
-
return nativeNormalTransformer.transformNormals(
|
|
193
|
+
return nativeNormalTransformer.transformNormals(targetNormals, positions, normalReferenceFrame);
|
|
200
194
|
};
|
|
201
195
|
|
|
202
196
|
return {
|
|
@@ -237,7 +231,7 @@ export function createI3SConversionSpatialContext(
|
|
|
237
231
|
transformNormals: (normals, positions, normalReferenceFrame) =>
|
|
238
232
|
transformer
|
|
239
233
|
? transformer.transformNormals(normals, positions, normalReferenceFrame)
|
|
240
|
-
:
|
|
234
|
+
: transformNativeNormals(normals, positions, normalReferenceFrame),
|
|
241
235
|
transformBoundingVolume: bounds =>
|
|
242
236
|
transformer ? transformer.transformBoundingVolume(bounds) : cloneI3SBounds(bounds),
|
|
243
237
|
transformBoundingVolumeAsync: bounds =>
|
|
@@ -361,21 +355,33 @@ function createNativeI3SPositions(
|
|
|
361
355
|
sourceOrigin: ArrayLike<number>,
|
|
362
356
|
spatialReference: TilesetSpatialReference
|
|
363
357
|
): I3STransformedPositions {
|
|
364
|
-
|
|
365
|
-
|
|
358
|
+
const sourcePositions64 = copyNativePositions(sourcePositions);
|
|
359
|
+
if (
|
|
360
|
+
sourceOrigin.length !== 3 ||
|
|
361
|
+
Array.from(sourceOrigin).some(value => !Number.isFinite(value))
|
|
362
|
+
) {
|
|
363
|
+
throw new TileConversionError(
|
|
364
|
+
'SPATIAL_INPUT_INVALID',
|
|
365
|
+
'I3S origins require three finite coordinates'
|
|
366
|
+
);
|
|
366
367
|
}
|
|
367
368
|
const origin: [number, number, number] = [
|
|
368
369
|
Number(sourceOrigin[0]),
|
|
369
370
|
Number(sourceOrigin[1]),
|
|
370
371
|
Number(sourceOrigin[2])
|
|
371
372
|
];
|
|
372
|
-
const sourcePositions64 = Float64Array.from(sourcePositions);
|
|
373
373
|
const positions = new Float32Array(sourcePositions.length);
|
|
374
374
|
for (let index = 0; index < sourcePositions.length; index += 3) {
|
|
375
375
|
positions[index] = Number(sourcePositions[index]) - origin[0];
|
|
376
376
|
positions[index + 1] = Number(sourcePositions[index + 1]) - origin[1];
|
|
377
377
|
positions[index + 2] = Number(sourcePositions[index + 2]) - origin[2];
|
|
378
378
|
}
|
|
379
|
+
if (positions.some(value => !Number.isFinite(value))) {
|
|
380
|
+
throw new TileConversionError(
|
|
381
|
+
'SPATIAL_INPUT_INVALID',
|
|
382
|
+
'Native I3S offsets exceed float32 range'
|
|
383
|
+
);
|
|
384
|
+
}
|
|
379
385
|
const isGeographic = spatialReference.coordinateFrame === 'geographic';
|
|
380
386
|
const modelMatrix = isGeographic ? new Matrix4() : new Matrix4().translate(origin);
|
|
381
387
|
return {
|
|
@@ -387,3 +393,68 @@ function createNativeI3SPositions(
|
|
|
387
393
|
modelMatrix
|
|
388
394
|
};
|
|
389
395
|
}
|
|
396
|
+
|
|
397
|
+
/** Validates and copies native xyz coordinates without invoking a CRS transformer. */
|
|
398
|
+
function copyNativePositions(positions: ArrayLike<number>): Float64Array {
|
|
399
|
+
validateNativePositions(positions);
|
|
400
|
+
return Float64Array.from(positions);
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
/** Checks native coordinates without allocating an additional position buffer. */
|
|
404
|
+
function validateNativePositions(positions: ArrayLike<number>): void {
|
|
405
|
+
if (positions.length % 3) {
|
|
406
|
+
throw new TileConversionError(
|
|
407
|
+
'SPATIAL_INPUT_INVALID',
|
|
408
|
+
'Positions require complete finite xyz coordinates'
|
|
409
|
+
);
|
|
410
|
+
}
|
|
411
|
+
for (let index = 0; index < positions.length; index++) {
|
|
412
|
+
if (!Number.isFinite(positions[index])) {
|
|
413
|
+
throw new TileConversionError(
|
|
414
|
+
'SPATIAL_INPUT_INVALID',
|
|
415
|
+
'Positions require complete finite xyz coordinates'
|
|
416
|
+
);
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
/** Validates matching native normals and normalizes them without changing their basis. */
|
|
422
|
+
function copyNativeNormals(normals: ArrayLike<number>, positions: ArrayLike<number>): Float32Array {
|
|
423
|
+
validateNativePositions(positions);
|
|
424
|
+
if (normals.length !== positions.length) {
|
|
425
|
+
throw new TileConversionError(
|
|
426
|
+
'SPATIAL_INPUT_INVALID',
|
|
427
|
+
'Normals require finite xyz values matching positions'
|
|
428
|
+
);
|
|
429
|
+
}
|
|
430
|
+
const output = new Float32Array(normals.length);
|
|
431
|
+
for (let index = 0; index < normals.length; index += 3) {
|
|
432
|
+
const scale = Math.max(
|
|
433
|
+
Math.abs(normals[index]),
|
|
434
|
+
Math.abs(normals[index + 1]),
|
|
435
|
+
Math.abs(normals[index + 2])
|
|
436
|
+
);
|
|
437
|
+
if (!Number.isFinite(scale)) {
|
|
438
|
+
throw new TileConversionError(
|
|
439
|
+
'SPATIAL_INPUT_INVALID',
|
|
440
|
+
'Normals require finite xyz values matching positions'
|
|
441
|
+
);
|
|
442
|
+
}
|
|
443
|
+
const length = scale
|
|
444
|
+
? Math.hypot(normals[index] / scale, normals[index + 1] / scale, normals[index + 2] / scale)
|
|
445
|
+
: 0;
|
|
446
|
+
for (let axis = 0; axis < 3; axis++)
|
|
447
|
+
output[index + axis] = length ? normals[index + axis] / scale / length : 0;
|
|
448
|
+
}
|
|
449
|
+
return output;
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
/** Rejects unknown I3S vector frames even when no position reprojection is requested. */
|
|
453
|
+
function validateNormalReferenceFrame(frame?: string): void {
|
|
454
|
+
if (frame !== undefined && !['earth-centered', 'vertex-reference-frame'].includes(frame)) {
|
|
455
|
+
throw new TileConversionError(
|
|
456
|
+
'SPATIAL_INPUT_INVALID',
|
|
457
|
+
'Unsupported I3S normal reference frame'
|
|
458
|
+
);
|
|
459
|
+
}
|
|
460
|
+
}
|
package/src/v5/tileset-source.ts
CHANGED
|
@@ -2,13 +2,13 @@
|
|
|
2
2
|
// SPDX-License-Identifier: MIT
|
|
3
3
|
// Copyright (c) vis.gl contributors
|
|
4
4
|
|
|
5
|
-
import {traverseTilesetContents} from '@loaders.gl/tiles';
|
|
5
|
+
import {traverseTilesetContents} from '@loaders.gl/tiles-3d';
|
|
6
6
|
import type {
|
|
7
7
|
Tileset3D,
|
|
8
8
|
TilesetContentTraversalItem,
|
|
9
9
|
TilesetSourceMetadata,
|
|
10
10
|
TraverseTilesetContentsOptions
|
|
11
|
-
} from '@loaders.gl/tiles';
|
|
11
|
+
} from '@loaders.gl/tiles-3d';
|
|
12
12
|
import {TileConversionError} from './conversion-api.js';
|
|
13
13
|
import type {TileConversionSource} from './conversion-api.js';
|
|
14
14
|
|