@openusd-wasm/three-loader 0.0.1

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.
@@ -0,0 +1,2148 @@
1
+ import { createPxr } from "@openusd-wasm/pxr";
2
+ import * as THREE from "three";
3
+ import { FileLoader, Loader } from "three";
4
+ //#region src/asset-resolver.ts
5
+ const DEFAULT_ASSET_SEARCH_EXTENSIONS = [
6
+ ".usd",
7
+ ".usda",
8
+ ".usdc",
9
+ ".usdz",
10
+ ".png",
11
+ ".jpg",
12
+ ".jpeg",
13
+ ".webp",
14
+ ".ktx2",
15
+ ".exr",
16
+ ".hdr"
17
+ ];
18
+ const DEFAULT_ASSET_SEARCH_ROOTS = [
19
+ "resource",
20
+ "resources",
21
+ "asset",
22
+ "assets",
23
+ "img",
24
+ "image",
25
+ "images",
26
+ "texture",
27
+ "textures",
28
+ "material",
29
+ "materials"
30
+ ];
31
+ const DEFAULT_MAX_ASSET_REFERENCES = 80;
32
+ const DEFAULT_MAX_ASSET_REFERENCE_DEPTH = 4;
33
+ function toFetchableUrl(value) {
34
+ try {
35
+ const url = new URL(value, globalThis.location?.href);
36
+ return url.protocol === "http:" || url.protocol === "https:" || url.protocol === "file:" ? url.href : null;
37
+ } catch {
38
+ return null;
39
+ }
40
+ }
41
+ function normalizeFsRelativePath(path) {
42
+ const parts = [];
43
+ for (const part of path.replaceAll("\\", "/").split("/")) {
44
+ if (!part || part === ".") continue;
45
+ if (part === "..") {
46
+ parts.pop();
47
+ continue;
48
+ }
49
+ parts.push(part);
50
+ }
51
+ return parts.join("/");
52
+ }
53
+ function dirname(path) {
54
+ const normalized = normalizeFsRelativePath(path);
55
+ const index = normalized.lastIndexOf("/");
56
+ return index === -1 ? "" : normalized.slice(0, index);
57
+ }
58
+ function joinRelativePath(base, path) {
59
+ return normalizeFsRelativePath(base ? `${base}/${path}` : path);
60
+ }
61
+ function extensionOf(path) {
62
+ const match = /\.([A-Za-z0-9]+)$/.exec(path.split(/[?#]/)[0] ?? path);
63
+ return match ? `.${match[1]?.toLowerCase()}` : "";
64
+ }
65
+ function contentTypeForPath(path) {
66
+ switch (extensionOf(path)) {
67
+ case ".png": return "image/png";
68
+ case ".jpg":
69
+ case ".jpeg": return "image/jpeg";
70
+ case ".webp": return "image/webp";
71
+ default: return "application/octet-stream";
72
+ }
73
+ }
74
+ function baseName(path) {
75
+ return normalizeFsRelativePath(path).split("/").pop() ?? "";
76
+ }
77
+ function fileNameForSource$1(sourcePath) {
78
+ const name = (sourcePath.split(/[?#]/)[0] ?? sourcePath).split("/").filter(Boolean).pop();
79
+ return name && /\.[a-z0-9]+$/i.test(name) ? name : "scene.usda";
80
+ }
81
+ function toArrayBuffer(data) {
82
+ const buffer = new ArrayBuffer(data.byteLength);
83
+ new Uint8Array(buffer).set(data);
84
+ return buffer;
85
+ }
86
+ function trimAssetReference(value) {
87
+ let ref = value.trim();
88
+ if (!ref || ref.length > 220) return null;
89
+ ref = ref.replace(/^[@<"']+/, "").replace(/[@>"')\]},;]+$/g, "");
90
+ if (!ref || ref.startsWith("#") || ref.startsWith("$")) return null;
91
+ if (/^[A-Za-z]:[\\/]/.test(ref)) return null;
92
+ if (/^[A-Za-z][A-Za-z0-9+.-]*:/.test(ref)) return null;
93
+ if (ref.startsWith("/")) return null;
94
+ if (ref.includes("\0") || /\s/.test(ref)) return null;
95
+ if (!/^[A-Za-z0-9._~!$&'()+,;=@/-]+$/.test(ref)) return null;
96
+ return ref;
97
+ }
98
+ function isUsdCrate(data) {
99
+ return data[0] === 80 && data[1] === 88 && data[2] === 82 && data[3] === 45 && data[4] === 85 && data[5] === 83 && data[6] === 68 && data[7] === 67;
100
+ }
101
+ function startsWithAssetSearchRoot(value) {
102
+ return /^(resource|resources|asset|assets|img|image|images|texture|textures|material|materials)(\/|$)/i.test(value);
103
+ }
104
+ function startsWithAssetSearchRootPath(value) {
105
+ const normalized = normalizeFsRelativePath(value);
106
+ return normalized.includes("/") && startsWithAssetSearchRoot(normalized);
107
+ }
108
+ function looksLikeExtensionlessAssetPath(value) {
109
+ const parts = normalizeFsRelativePath(value).split("/");
110
+ if (parts.length < 2 || parts.length > 3) return false;
111
+ if (parts.some((part) => part.length < 2 || part.length > 80)) return false;
112
+ if (parts.some((part) => part.startsWith("."))) return false;
113
+ if (parts.some((part) => !/^[A-Za-z0-9._-]+$/.test(part))) return false;
114
+ if (!/_/.test(value) || !/\d/.test(value)) return false;
115
+ return true;
116
+ }
117
+ function looksLikeKnownExtensionlessAsset(value) {
118
+ const normalized = normalizeFsRelativePath(value);
119
+ if (!normalized.includes("/")) return false;
120
+ return [
121
+ "material",
122
+ "model",
123
+ "scene"
124
+ ].includes(baseName(normalized).toLowerCase());
125
+ }
126
+ function looksLikeAssetReference(value, searchExtensions, includeExtensionlessAssetPaths, wholeString) {
127
+ const extension = extensionOf(value);
128
+ if (extension) {
129
+ if (!searchExtensions.includes(extension)) return false;
130
+ const name = baseName(value);
131
+ if (name === extension) return false;
132
+ if (wholeString && !value.startsWith("./") && !value.startsWith("../") && !value.includes("/")) return name.length >= 8;
133
+ return true;
134
+ }
135
+ if (value.startsWith("./") || value.startsWith("../")) {
136
+ const relative = normalizeFsRelativePath(value);
137
+ if (startsWithAssetSearchRootPath(relative)) return looksLikeExtensionlessAssetPath(relative) || looksLikeKnownExtensionlessAsset(relative);
138
+ return looksLikeExtensionlessAssetPath(relative);
139
+ }
140
+ if (startsWithAssetSearchRootPath(value)) return looksLikeExtensionlessAssetPath(value) || looksLikeKnownExtensionlessAsset(value);
141
+ return includeExtensionlessAssetPaths && looksLikeExtensionlessAssetPath(value);
142
+ }
143
+ function extractAsciiStrings(data) {
144
+ const strings = [];
145
+ let current = "";
146
+ for (const byte of data) if (byte >= 32 && byte <= 126) current += String.fromCharCode(byte);
147
+ else {
148
+ if (current.length >= 4) strings.push(current);
149
+ current = "";
150
+ }
151
+ if (current.length >= 4) strings.push(current);
152
+ return strings;
153
+ }
154
+ function discoverAssetReferences(data, searchExtensions, includeExtensionlessAssetPaths = false) {
155
+ const out = /* @__PURE__ */ new Set();
156
+ for (const text of extractAsciiStrings(data)) {
157
+ const whole = trimAssetReference(text);
158
+ if (whole && looksLikeAssetReference(whole, searchExtensions, includeExtensionlessAssetPaths, true)) out.add(whole);
159
+ for (const match of text.matchAll(/@([^@\n\r]+)@/g)) {
160
+ const ref = trimAssetReference(match[1] ?? "");
161
+ if (ref && looksLikeAssetReference(ref, searchExtensions, includeExtensionlessAssetPaths, false)) out.add(ref);
162
+ }
163
+ for (const match of text.matchAll(/["']([^"'\n\r]+\.(?:usd|usda|usdc|usdz|png|jpe?g|webp|ktx2|exr|hdr|mdl))["']/gi)) {
164
+ const ref = trimAssetReference(match[1] ?? "");
165
+ if (ref && looksLikeAssetReference(ref, searchExtensions, includeExtensionlessAssetPaths, false)) out.add(ref);
166
+ }
167
+ }
168
+ return [...out];
169
+ }
170
+ function discoverAssetSearchRoots(data, searchRoots) {
171
+ const out = /* @__PURE__ */ new Set();
172
+ const rootSet = new Set(searchRoots);
173
+ for (const text of extractAsciiStrings(data)) {
174
+ const ref = trimAssetReference(text);
175
+ if (!ref) continue;
176
+ const segment = firstPathSegment(ref);
177
+ if (rootSet.has(segment) && normalizeFsRelativePath(ref).includes("/")) out.add(segment);
178
+ }
179
+ return [...out];
180
+ }
181
+ function buildExtensionVariants(path, searchExtensions) {
182
+ const normalized = normalizeFsRelativePath(path);
183
+ if (!normalized) return [];
184
+ if (extensionOf(normalized)) return [normalized];
185
+ return [...searchExtensions.map((extension) => `${normalized}${extension}`), normalized];
186
+ }
187
+ function buildReferencePathVariants(relativePath) {
188
+ const normalized = normalizeFsRelativePath(relativePath);
189
+ if (!normalized) return [];
190
+ return [{
191
+ path: normalized,
192
+ aliases: [normalized]
193
+ }];
194
+ }
195
+ function unique(values) {
196
+ return [...new Set(values)];
197
+ }
198
+ function uniqueFileCount(files) {
199
+ return new Set(Object.values(files)).size;
200
+ }
201
+ function firstPathSegment(path) {
202
+ return normalizeFsRelativePath(path).split("/")[0] ?? "";
203
+ }
204
+ function normalizeSearchRoots(roots) {
205
+ return unique((roots ?? DEFAULT_ASSET_SEARCH_ROOTS).map((root) => normalizeFsRelativePath(root)).filter(Boolean));
206
+ }
207
+ function inferReferencedSearchRoots(refs, searchRoots) {
208
+ const rootSet = new Set(searchRoots);
209
+ return unique(refs.map((ref) => firstPathSegment(ref)).filter((segment) => rootSet.has(segment)));
210
+ }
211
+ function buildFetchAttempts(ref, sourceUrl, sourceRelativePath, searchExtensions, rootSourceUrl, searchRoots) {
212
+ const sourceDir = dirname(sourceRelativePath);
213
+ const attempts = [];
214
+ const seen = /* @__PURE__ */ new Set();
215
+ const normalizedRef = normalizeFsRelativePath(ref);
216
+ const isDotRelative = /^\.{1,2}\//.test(ref);
217
+ const candidates = [];
218
+ function addCandidate(fsPath, fetchPath, baseUrl, aliases = []) {
219
+ const normalizedFsPath = normalizeFsRelativePath(fsPath);
220
+ if (!normalizedFsPath) return;
221
+ candidates.push({
222
+ fsPath: normalizedFsPath,
223
+ fetchPath,
224
+ baseUrl,
225
+ aliases: unique([
226
+ normalizedRef,
227
+ normalizedFsPath,
228
+ ...aliases
229
+ ].filter(Boolean))
230
+ });
231
+ }
232
+ if (!isDotRelative) {
233
+ for (const root of searchRoots) if (normalizedRef !== root && !normalizedRef.startsWith(`${root}/`)) addCandidate(`${root}/${normalizedRef}`, `${root}/${normalizedRef}`, rootSourceUrl);
234
+ }
235
+ addCandidate(joinRelativePath(sourceDir, ref), ref, sourceUrl);
236
+ if (!isDotRelative && sourceDir) addCandidate(normalizedRef, normalizedRef, rootSourceUrl);
237
+ for (const candidate of candidates) for (const variant of buildReferencePathVariants(candidate.fsPath)) {
238
+ const fsAliases = new Set([...candidate.aliases, ...variant.aliases]);
239
+ for (const pathWithExtension of buildExtensionVariants(variant.path, searchExtensions)) {
240
+ const extension = extensionOf(pathWithExtension);
241
+ fsAliases.add(pathWithExtension);
242
+ const fetchPath = extensionOf(candidate.fetchPath) ? candidate.fetchPath : `${candidate.fetchPath}${extension}`;
243
+ try {
244
+ const url = new URL(fetchPath, candidate.baseUrl).href;
245
+ if (seen.has(url)) continue;
246
+ seen.add(url);
247
+ attempts.push({
248
+ url,
249
+ fsPaths: [...fsAliases]
250
+ });
251
+ } catch {}
252
+ }
253
+ }
254
+ return attempts;
255
+ }
256
+ async function fetchBinary(url) {
257
+ try {
258
+ const response = await fetch(url);
259
+ if (!response.ok) return null;
260
+ return new Uint8Array(await response.arrayBuffer());
261
+ } catch {
262
+ return null;
263
+ }
264
+ }
265
+ async function autoResolveAssetFiles(rootData, options) {
266
+ const sourcePath = options.sourcePath ?? "";
267
+ if (options.autoResolveAssets === false || !sourcePath) return {};
268
+ const sourceUrl = toFetchableUrl(sourcePath);
269
+ if (!sourceUrl) return {};
270
+ const searchExtensions = options.assetSearchExtensions ?? DEFAULT_ASSET_SEARCH_EXTENSIONS;
271
+ const configuredSearchRoots = normalizeSearchRoots(options.assetSearchRoots);
272
+ const maxFiles = options.maxAssetReferences ?? DEFAULT_MAX_ASSET_REFERENCES;
273
+ const maxDepth = options.maxAssetReferenceDepth ?? DEFAULT_MAX_ASSET_REFERENCE_DEPTH;
274
+ const rootRelativePath = normalizeFsRelativePath(options.fileName ?? fileNameForSource$1(sourcePath));
275
+ const files = {};
276
+ const fetchedUrls = /* @__PURE__ */ new Set();
277
+ const queued = /* @__PURE__ */ new Set();
278
+ const rootRefs = discoverAssetReferences(rootData, searchExtensions, true);
279
+ const inferredSearchRoots = unique([...inferReferencedSearchRoots(rootRefs, configuredSearchRoots), ...discoverAssetSearchRoots(rootData, configuredSearchRoots)]);
280
+ const queue = rootRefs.map((ref) => ({
281
+ ref,
282
+ sourceUrl,
283
+ rootSourceUrl: sourceUrl,
284
+ sourceRelativePath: rootRelativePath,
285
+ searchRoots: inferredSearchRoots,
286
+ depth: 0
287
+ }));
288
+ for (const item of queue) queued.add(`${item.sourceUrl}\n${item.ref}`);
289
+ while (queue.length > 0 && uniqueFileCount(files) < maxFiles) {
290
+ const item = queue.shift();
291
+ if (!item || item.depth > maxDepth) continue;
292
+ for (const attempt of buildFetchAttempts(item.ref, item.sourceUrl, item.sourceRelativePath, searchExtensions, item.rootSourceUrl, item.searchRoots)) {
293
+ if (fetchedUrls.has(attempt.url)) continue;
294
+ fetchedUrls.add(attempt.url);
295
+ const data = await fetchBinary(attempt.url);
296
+ if (!data) continue;
297
+ for (const fsPath of attempt.fsPaths) files[fsPath] = data;
298
+ if (item.depth < maxDepth) {
299
+ const sourceRelativePath = attempt.fsPaths.find((path) => extensionOf(path)) ?? attempt.fsPaths[0];
300
+ if (sourceRelativePath) for (const ref of discoverAssetReferences(data, searchExtensions, !isUsdCrate(data))) {
301
+ const queueKey = `${attempt.url}\n${ref}`;
302
+ if (queued.has(queueKey)) continue;
303
+ queued.add(queueKey);
304
+ const nestedSearchRoots = unique([...item.searchRoots, ...inferReferencedSearchRoots([sourceRelativePath], configuredSearchRoots)]);
305
+ queue.push({
306
+ ref,
307
+ sourceUrl: attempt.url,
308
+ rootSourceUrl: item.rootSourceUrl,
309
+ sourceRelativePath,
310
+ searchRoots: nestedSearchRoots,
311
+ depth: item.depth + 1
312
+ });
313
+ }
314
+ }
315
+ break;
316
+ }
317
+ }
318
+ return files;
319
+ }
320
+ function isTextureAssetReference(value) {
321
+ if (/^(blob|data):/i.test(value)) return false;
322
+ if (/^[A-Za-z][A-Za-z0-9+.-]*:/.test(value)) return false;
323
+ if (value.startsWith("/")) return false;
324
+ return [
325
+ ".png",
326
+ ".jpg",
327
+ ".jpeg",
328
+ ".webp"
329
+ ].includes(extensionOf(value));
330
+ }
331
+ function collectTextureAssetReferences(value, refs = /* @__PURE__ */ new Set()) {
332
+ if (!value) return refs;
333
+ if (Array.isArray(value)) {
334
+ for (const item of value) collectTextureAssetReferences(item, refs);
335
+ return refs;
336
+ }
337
+ if (typeof value !== "object") return refs;
338
+ const asset = value;
339
+ for (const item of [asset.path, asset.url]) if (typeof item === "string" && isTextureAssetReference(item)) refs.add(item);
340
+ for (const item of Object.values(value)) collectTextureAssetReferences(item, refs);
341
+ return refs;
342
+ }
343
+ async function autoResolveTextureFiles(metadata, existingFiles, options) {
344
+ const sourcePath = options.sourcePath ?? "";
345
+ if (options.autoResolveAssets === false || !sourcePath) return {};
346
+ const sourceUrl = toFetchableUrl(sourcePath);
347
+ if (!sourceUrl) return {};
348
+ const searchExtensions = options.assetSearchExtensions ?? DEFAULT_ASSET_SEARCH_EXTENSIONS;
349
+ const configuredSearchRoots = normalizeSearchRoots(options.assetSearchRoots);
350
+ const textureRefs = [...collectTextureAssetReferences(metadata)];
351
+ const searchRoots = unique([...inferReferencedSearchRoots(Object.keys(existingFiles), configuredSearchRoots), ...inferReferencedSearchRoots(textureRefs, configuredSearchRoots)]);
352
+ const maxFiles = options.maxAssetReferences ?? DEFAULT_MAX_ASSET_REFERENCES;
353
+ const remainingFiles = Math.max(maxFiles - uniqueFileCount(existingFiles), 0);
354
+ if (remainingFiles === 0) return {};
355
+ const rootRelativePath = normalizeFsRelativePath(options.fileName ?? fileNameForSource$1(sourcePath));
356
+ const files = {};
357
+ const fetchedUrls = /* @__PURE__ */ new Set();
358
+ let resolvedFiles = 0;
359
+ for (const ref of textureRefs) {
360
+ if (resolvedFiles >= remainingFiles) break;
361
+ for (const attempt of buildFetchAttempts(ref, sourceUrl, rootRelativePath, searchExtensions, sourceUrl, searchRoots)) {
362
+ if (attempt.fsPaths.some((path) => existingFiles[path] || files[path])) break;
363
+ if (fetchedUrls.has(attempt.url)) continue;
364
+ fetchedUrls.add(attempt.url);
365
+ const data = await fetchBinary(attempt.url);
366
+ if (!data) continue;
367
+ for (const fsPath of attempt.fsPaths) files[fsPath] = data;
368
+ resolvedFiles += 1;
369
+ break;
370
+ }
371
+ }
372
+ return files;
373
+ }
374
+ function readZipEntryName(data, start, length) {
375
+ return new TextDecoder().decode(data.subarray(start, start + length));
376
+ }
377
+ function extractStoredZipEntries(data) {
378
+ const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
379
+ const entries = /* @__PURE__ */ new Map();
380
+ let offset = 0;
381
+ while (offset + 30 <= data.byteLength) {
382
+ if (view.getUint32(offset, true) !== 67324752) break;
383
+ const method = view.getUint16(offset + 8, true);
384
+ const compressedSize = view.getUint32(offset + 18, true);
385
+ const fileNameLength = view.getUint16(offset + 26, true);
386
+ const extraLength = view.getUint16(offset + 28, true);
387
+ const nameStart = offset + 30;
388
+ const dataStart = nameStart + fileNameLength + extraLength;
389
+ const dataEnd = dataStart + compressedSize;
390
+ if (dataEnd > data.byteLength) break;
391
+ const name = normalizeFsRelativePath(readZipEntryName(data, nameStart, fileNameLength));
392
+ if (name && method === 0) entries.set(name, data.slice(dataStart, dataEnd));
393
+ offset = dataEnd;
394
+ }
395
+ return entries;
396
+ }
397
+ function createTextureResolverFromEntries(entries) {
398
+ if (typeof Blob === "undefined" || typeof URL === "undefined" || typeof URL.createObjectURL !== "function") return null;
399
+ const imageEntries = new Map([...entries].filter(([path]) => [
400
+ ".png",
401
+ ".jpg",
402
+ ".jpeg",
403
+ ".webp"
404
+ ].includes(extensionOf(path))));
405
+ if (imageEntries.size === 0) return null;
406
+ const byBaseName = /* @__PURE__ */ new Map();
407
+ for (const path of imageEntries.keys()) {
408
+ const name = baseName(path);
409
+ byBaseName.set(name, byBaseName.has(name) ? null : path);
410
+ }
411
+ const urls = /* @__PURE__ */ new Map();
412
+ const urlForEntry = (path) => {
413
+ const entry = imageEntries.get(path);
414
+ if (!entry) return null;
415
+ let url = urls.get(path);
416
+ if (!url) {
417
+ url = URL.createObjectURL(new Blob([toArrayBuffer(entry)], { type: contentTypeForPath(path) }));
418
+ urls.set(path, url);
419
+ }
420
+ return url;
421
+ };
422
+ const resolvePath = (value) => {
423
+ if (!value) return null;
424
+ const normalized = normalizeFsRelativePath(value.split(/[?#]/)[0] ?? value);
425
+ if (!normalized) return null;
426
+ if (imageEntries.has(normalized)) return urlForEntry(normalized);
427
+ const suffixMatches = [...imageEntries.keys()].filter((path) => path.endsWith(`/${normalized}`));
428
+ if (suffixMatches.length === 1 && suffixMatches[0]) return urlForEntry(suffixMatches[0]);
429
+ const virtualFsMatches = [...imageEntries.keys()].filter((path) => normalized.endsWith(`/${path}`));
430
+ if (virtualFsMatches.length === 1 && virtualFsMatches[0]) return urlForEntry(virtualFsMatches[0]);
431
+ const uniqueBaseName = byBaseName.get(baseName(normalized));
432
+ return uniqueBaseName ? urlForEntry(uniqueBaseName) : null;
433
+ };
434
+ return {
435
+ urls,
436
+ resolve(asset) {
437
+ return resolvePath(asset.resolvedPath) ?? resolvePath(asset.path) ?? resolvePath(asset.url) ?? null;
438
+ }
439
+ };
440
+ }
441
+ function createPackageTextureResolver(data) {
442
+ return createTextureResolverFromEntries(extractStoredZipEntries(data));
443
+ }
444
+ //#endregion
445
+ //#region src/metadata.ts
446
+ const MATERIAL_INPUT_SUFFIXES = [
447
+ "diffuseColor",
448
+ "base_color",
449
+ "baseColor",
450
+ "emissiveColor",
451
+ "normal",
452
+ "occlusion",
453
+ "opacity",
454
+ "alpha",
455
+ "roughness",
456
+ "metallic",
457
+ "metallicFactor",
458
+ "file",
459
+ "wrapS",
460
+ "wrapT",
461
+ "scale",
462
+ "translation",
463
+ "rotation"
464
+ ];
465
+ const JOINT_TYPES = {
466
+ PhysicsFixedJoint: "fixed",
467
+ PhysicsRevoluteJoint: "revolute",
468
+ PhysicsPrismaticJoint: "prismatic",
469
+ PhysicsSphericalJoint: "spherical",
470
+ PhysicsDistanceJoint: "distance",
471
+ PhysicsJoint: "joint"
472
+ };
473
+ function dispose$1(value) {
474
+ if (value && typeof value === "object" && "delete" in value && typeof value.delete === "function") value.delete();
475
+ }
476
+ function disposeAll(values) {
477
+ for (let i = values.length - 1; i >= 0; --i) dispose$1(values[i]);
478
+ }
479
+ function isValid$1(value) {
480
+ if (!value) return false;
481
+ if (typeof value.IsValid === "function") return Boolean(value.IsValid());
482
+ if (typeof value.IsDefined === "function") return Boolean(value.IsDefined());
483
+ return true;
484
+ }
485
+ function asFiniteNumber(value) {
486
+ return typeof value === "number" && Number.isFinite(value) ? value : null;
487
+ }
488
+ function normalizeValue(value) {
489
+ if (typeof value === "number") return Number.isFinite(value) ? value : null;
490
+ if (Array.isArray(value)) return value.map((item) => normalizeValue(item));
491
+ if (value && typeof value === "object") {
492
+ const out = {};
493
+ for (const [key, item] of Object.entries(value)) out[key] = normalizeValue(item);
494
+ return out;
495
+ }
496
+ return value;
497
+ }
498
+ function toArray(value) {
499
+ return Array.isArray(value) ? value : [];
500
+ }
501
+ function toNumberArray(value) {
502
+ return toArray(value).map((item) => Number(item)).filter((item) => Number.isFinite(item));
503
+ }
504
+ function toVector3(value, fallback) {
505
+ const numbers = toNumberArray(value);
506
+ if (numbers.length < 3) return fallback;
507
+ return [
508
+ numbers[0] ?? fallback[0],
509
+ numbers[1] ?? fallback[1],
510
+ numbers[2] ?? fallback[2]
511
+ ];
512
+ }
513
+ function toVector4(value) {
514
+ if (Array.isArray(value)) {
515
+ const numbers = toNumberArray(value);
516
+ if (numbers.length >= 4) return [
517
+ numbers[0] ?? 1,
518
+ numbers[1] ?? 0,
519
+ numbers[2] ?? 0,
520
+ numbers[3] ?? 0
521
+ ];
522
+ }
523
+ if (value && typeof value === "object") {
524
+ const { real, imaginary } = value;
525
+ const imaginaryNumbers = toNumberArray(imaginary);
526
+ const r = asFiniteNumber(real);
527
+ if (r !== null && imaginaryNumbers.length >= 3) return [
528
+ r,
529
+ imaginaryNumbers[0] ?? 0,
530
+ imaginaryNumbers[1] ?? 0,
531
+ imaginaryNumbers[2] ?? 0
532
+ ];
533
+ }
534
+ return null;
535
+ }
536
+ function identityMatrix() {
537
+ return [
538
+ 1,
539
+ 0,
540
+ 0,
541
+ 0,
542
+ 0,
543
+ 1,
544
+ 0,
545
+ 0,
546
+ 0,
547
+ 0,
548
+ 1,
549
+ 0,
550
+ 0,
551
+ 0,
552
+ 0,
553
+ 1
554
+ ];
555
+ }
556
+ function multiplyMatrices(a, b) {
557
+ const out = new Array(16).fill(0);
558
+ for (let row = 0; row < 4; row += 1) for (let column = 0; column < 4; column += 1) out[row * 4 + column] = (a[row * 4] ?? 0) * (b[column] ?? 0) + (a[row * 4 + 1] ?? 0) * (b[4 + column] ?? 0) + (a[row * 4 + 2] ?? 0) * (b[8 + column] ?? 0) + (a[row * 4 + 3] ?? 0) * (b[12 + column] ?? 0);
559
+ return out;
560
+ }
561
+ function matricesNearlyEqual(a, b) {
562
+ if (!a || !b || a.length < 16 || b.length < 16) return false;
563
+ return a.every((value, index) => Math.abs(value - (b[index] ?? 0)) < 1e-8);
564
+ }
565
+ function matrixFromXformOp(opName, value) {
566
+ const numbers = toNumberArray(value);
567
+ if (numbers.length === 0) return null;
568
+ if (opName.startsWith("xformOp:transform")) return numbers.length >= 16 ? numbers.slice(0, 16) : null;
569
+ if (opName.startsWith("xformOp:translate")) return [
570
+ 1,
571
+ 0,
572
+ 0,
573
+ 0,
574
+ 0,
575
+ 1,
576
+ 0,
577
+ 0,
578
+ 0,
579
+ 0,
580
+ 1,
581
+ 0,
582
+ numbers[0] ?? 0,
583
+ numbers[1] ?? 0,
584
+ numbers[2] ?? 0,
585
+ 1
586
+ ];
587
+ if (opName.startsWith("xformOp:scale")) return [
588
+ numbers[0] ?? 1,
589
+ 0,
590
+ 0,
591
+ 0,
592
+ 0,
593
+ numbers[1] ?? numbers[0] ?? 1,
594
+ 0,
595
+ 0,
596
+ 0,
597
+ 0,
598
+ numbers[2] ?? numbers[0] ?? 1,
599
+ 0,
600
+ 0,
601
+ 0,
602
+ 0,
603
+ 1
604
+ ];
605
+ if (opName.startsWith("xformOp:rotate")) return rotationMatrixFromXformOp(opName, numbers);
606
+ return null;
607
+ }
608
+ function rotationMatrixFromXformOp(opName, degrees) {
609
+ const rotateX = (degree) => {
610
+ const radians = degree * Math.PI / 180;
611
+ const cos = Math.cos(radians);
612
+ const sin = Math.sin(radians);
613
+ return [
614
+ 1,
615
+ 0,
616
+ 0,
617
+ 0,
618
+ 0,
619
+ cos,
620
+ sin,
621
+ 0,
622
+ 0,
623
+ -sin,
624
+ cos,
625
+ 0,
626
+ 0,
627
+ 0,
628
+ 0,
629
+ 1
630
+ ];
631
+ };
632
+ const rotateY = (degree) => {
633
+ const radians = degree * Math.PI / 180;
634
+ const cos = Math.cos(radians);
635
+ const sin = Math.sin(radians);
636
+ return [
637
+ cos,
638
+ 0,
639
+ -sin,
640
+ 0,
641
+ 0,
642
+ 1,
643
+ 0,
644
+ 0,
645
+ sin,
646
+ 0,
647
+ cos,
648
+ 0,
649
+ 0,
650
+ 0,
651
+ 0,
652
+ 1
653
+ ];
654
+ };
655
+ const rotateZ = (degree) => {
656
+ const radians = degree * Math.PI / 180;
657
+ const cos = Math.cos(radians);
658
+ const sin = Math.sin(radians);
659
+ return [
660
+ cos,
661
+ sin,
662
+ 0,
663
+ 0,
664
+ -sin,
665
+ cos,
666
+ 0,
667
+ 0,
668
+ 0,
669
+ 0,
670
+ 1,
671
+ 0,
672
+ 0,
673
+ 0,
674
+ 0,
675
+ 1
676
+ ];
677
+ };
678
+ const suffix = opName.split(":")[1] ?? "";
679
+ if (suffix === "rotateX") return rotateX(degrees[0] ?? 0);
680
+ if (suffix === "rotateY") return rotateY(degrees[0] ?? 0);
681
+ if (suffix === "rotateZ") return rotateZ(degrees[0] ?? 0);
682
+ const axes = suffix.replace(/^rotate/, "").split("");
683
+ if (axes.length === 0) return null;
684
+ return axes.reduce((matrix, axis, index) => {
685
+ const degree = degrees[index] ?? 0;
686
+ const next = axis === "X" ? rotateX(degree) : axis === "Y" ? rotateY(degree) : axis === "Z" ? rotateZ(degree) : null;
687
+ return next ? multiplyMatrices(matrix, next) : matrix;
688
+ }, identityMatrix());
689
+ }
690
+ function attr(prim, name, time, defaultValue = null) {
691
+ const attribute = prim.GetAttribute(name);
692
+ try {
693
+ if (!isValid$1(attribute)) return defaultValue;
694
+ const value = attribute.Get(time);
695
+ return value === null || value === void 0 ? defaultValue : normalizeValue(value);
696
+ } finally {
697
+ dispose$1(attribute);
698
+ }
699
+ }
700
+ function attrMetadata(prim, name, key, defaultValue = null) {
701
+ const attribute = prim.GetAttribute(name);
702
+ try {
703
+ if (!isValid$1(attribute)) return defaultValue;
704
+ const value = attribute.GetMetadata(key);
705
+ return value === null || value === void 0 ? defaultValue : normalizeValue(value);
706
+ } finally {
707
+ dispose$1(attribute);
708
+ }
709
+ }
710
+ function relationshipTargets(prim, name) {
711
+ const relationship = prim.GetRelationship(name);
712
+ try {
713
+ if (!isValid$1(relationship)) return [];
714
+ const targets = relationship.GetTargets();
715
+ return Array.isArray(targets) ? targets.map(String) : [];
716
+ } finally {
717
+ dispose$1(relationship);
718
+ }
719
+ }
720
+ function connectionSourcePath(attribute) {
721
+ const connections = attribute.GetConnections();
722
+ if (!Array.isArray(connections) || connections.length === 0) return null;
723
+ return String(connections[0]).split(".outputs:")[0] ?? null;
724
+ }
725
+ function primChildren(prim) {
726
+ const children = prim.GetChildren();
727
+ return Array.isArray(children) ? children : [];
728
+ }
729
+ function primAtPath(pxr, stage, path) {
730
+ const sdfPath = new pxr.Sdf.Path(path);
731
+ try {
732
+ return stage.GetPrimAtPath(sdfPath);
733
+ } finally {
734
+ dispose$1(sdfPath);
735
+ }
736
+ }
737
+ function materialBinding(prim) {
738
+ let current = prim;
739
+ let ownsCurrent = false;
740
+ try {
741
+ while (current && !current.IsPseudoRoot()) {
742
+ const targets = relationshipTargets(current, "material:binding");
743
+ if (targets.length > 0) return targets[0] ?? null;
744
+ const parent = current.GetParent();
745
+ if (ownsCurrent) dispose$1(current);
746
+ current = parent;
747
+ ownsCurrent = true;
748
+ }
749
+ return null;
750
+ } finally {
751
+ if (ownsCurrent) dispose$1(current);
752
+ }
753
+ }
754
+ function localTransformForPrim(pxr, prim) {
755
+ if (!prim.IsA("Xformable")) return {
756
+ localMatrix: null,
757
+ resetsXformStack: false
758
+ };
759
+ const xformable = new pxr.UsdGeom.Xformable(prim);
760
+ try {
761
+ if (!isValid$1(xformable)) return {
762
+ localMatrix: null,
763
+ resetsXformStack: false
764
+ };
765
+ const transform = xformable.GetLocalTransformation();
766
+ return {
767
+ localMatrix: toNumberArray(transform?.matrix),
768
+ resetsXformStack: Boolean(transform?.resetsXformStack)
769
+ };
770
+ } finally {
771
+ dispose$1(xformable);
772
+ }
773
+ }
774
+ function parentPathForPrim(prim) {
775
+ const parent = prim.GetParent();
776
+ try {
777
+ if (!isValid$1(parent) || parent.IsPseudoRoot()) return null;
778
+ return String(parent.GetPath());
779
+ } finally {
780
+ dispose$1(parent);
781
+ }
782
+ }
783
+ function getModelView(pxr, prims, time) {
784
+ return { prims: prims.map((prim) => {
785
+ const transform = localTransformForPrim(pxr, prim);
786
+ return {
787
+ path: String(prim.GetPath()),
788
+ parentPath: parentPathForPrim(prim),
789
+ name: String(prim.GetName()),
790
+ typeName: String(prim.GetTypeName()),
791
+ visibility: String(attr(prim, "visibility", time, "inherited")),
792
+ ...transform
793
+ };
794
+ }) };
795
+ }
796
+ function valueAt(values, index) {
797
+ return index >= 0 && index < values.length ? values[index] : null;
798
+ }
799
+ function samplePrimvar(values, indices, interpolation, pointIndex, faceIndex, faceVertexIndex, pointCount, faceCount, faceVertexCount) {
800
+ if (values.length === 0) return null;
801
+ let domainIndex;
802
+ if (values.length === faceVertexCount) domainIndex = faceVertexIndex;
803
+ else if (values.length === pointCount) domainIndex = pointIndex;
804
+ else if (values.length === faceCount) domainIndex = faceIndex;
805
+ else if (values.length === 1) domainIndex = 0;
806
+ else if (interpolation === "faceVarying") domainIndex = faceVertexIndex;
807
+ else if (interpolation === "vertex" || interpolation === "varying") domainIndex = pointIndex;
808
+ else if (interpolation === "uniform") domainIndex = faceIndex;
809
+ else domainIndex = pointIndex;
810
+ let valueIndex = domainIndex;
811
+ if (indices.length > 0) {
812
+ const indexed = indices[domainIndex];
813
+ if (indexed === void 0) return null;
814
+ valueIndex = indexed;
815
+ }
816
+ return valueAt(values, valueIndex);
817
+ }
818
+ function meshPrimvar(prim, name, time) {
819
+ return {
820
+ values: toArray(attr(prim, name, time, [])),
821
+ indices: toNumberArray(attr(prim, `${name}:indices`, time, [])),
822
+ interpolation: String(attrMetadata(prim, name, "interpolation", attr(prim, `${name}:interpolation`, time, "")) || "") || null
823
+ };
824
+ }
825
+ function asVec2(value, fallback) {
826
+ const numbers = toNumberArray(value);
827
+ if (numbers.length < 2) return fallback;
828
+ return [numbers[0] ?? fallback[0], numbers[1] ?? fallback[1]];
829
+ }
830
+ function asFloat(value, fallback = 0) {
831
+ const number = asFiniteNumber(value);
832
+ return number === null ? fallback : number;
833
+ }
834
+ function shaderInputConnectionPath(shader, inputName) {
835
+ const attribute = shader.GetAttribute(inputName);
836
+ try {
837
+ if (!isValid$1(attribute)) return null;
838
+ return connectionSourcePath(attribute);
839
+ } finally {
840
+ dispose$1(attribute);
841
+ }
842
+ }
843
+ function shaderChildByPath(material, path) {
844
+ const children = primChildren(material);
845
+ try {
846
+ for (const child of children) if (String(child.GetPath()) === path) return child;
847
+ return null;
848
+ } finally {
849
+ for (const child of children) if (String(child.GetPath()) !== path) dispose$1(child);
850
+ }
851
+ }
852
+ function primvarNameForTextureShader(pxr, stage, material, textureShader, time) {
853
+ const stSourcePath = shaderInputConnectionPath(textureShader, "inputs:st");
854
+ if (!stSourcePath) return null;
855
+ const stSource = shaderChildByPath(material, stSourcePath) ?? primAtPath(pxr, stage, stSourcePath);
856
+ try {
857
+ if (!isValid$1(stSource)) return null;
858
+ const id = attr(stSource, "info:id", time);
859
+ if (id === "UsdPrimvarReader_float2") {
860
+ const varname = attr(stSource, "inputs:varname", time);
861
+ return typeof varname === "string" && varname ? varname : null;
862
+ }
863
+ if (id === "UsdTransform2d") {
864
+ const nestedSourcePath = shaderInputConnectionPath(stSource, "inputs:in") ?? shaderInputConnectionPath(stSource, "inputs:st");
865
+ if (!nestedSourcePath) return null;
866
+ const nestedSource = shaderChildByPath(material, nestedSourcePath) ?? primAtPath(pxr, stage, nestedSourcePath);
867
+ try {
868
+ if (!isValid$1(nestedSource) || attr(nestedSource, "info:id", time) !== "UsdPrimvarReader_float2") return null;
869
+ const varname = attr(nestedSource, "inputs:varname", time);
870
+ return typeof varname === "string" && varname ? varname : null;
871
+ } finally {
872
+ dispose$1(nestedSource);
873
+ }
874
+ }
875
+ return null;
876
+ } finally {
877
+ dispose$1(stSource);
878
+ }
879
+ }
880
+ function texturePrimvarForMaterial(pxr, stage, materialPath, time) {
881
+ if (!materialPath) return "primvars:st";
882
+ const material = primAtPath(pxr, stage, materialPath);
883
+ try {
884
+ if (!isValid$1(material)) return "primvars:st";
885
+ const children = primChildren(material);
886
+ try {
887
+ for (const shader of children) {
888
+ if (shader.GetTypeName() !== "Shader" || attr(shader, "info:id", time) !== "UsdUVTexture") continue;
889
+ const varname = primvarNameForTextureShader(pxr, stage, material, shader, time);
890
+ if (varname) return `primvars:${varname}`;
891
+ }
892
+ } finally {
893
+ disposeAll(children);
894
+ }
895
+ return "primvars:st";
896
+ } finally {
897
+ dispose$1(material);
898
+ }
899
+ }
900
+ function textureTransformForMaterial(pxr, stage, materialPath, time) {
901
+ if (!materialPath) return null;
902
+ const material = primAtPath(pxr, stage, materialPath);
903
+ try {
904
+ if (!isValid$1(material)) return null;
905
+ const children = primChildren(material);
906
+ try {
907
+ for (const shader of children) {
908
+ if (shader.GetTypeName() !== "Shader" || attr(shader, "info:id", time) !== "UsdUVTexture") continue;
909
+ const stAttribute = shader.GetAttribute("inputs:st");
910
+ try {
911
+ if (!isValid$1(stAttribute)) continue;
912
+ const sourcePath = connectionSourcePath(stAttribute);
913
+ if (!sourcePath) return null;
914
+ const transform = primAtPath(pxr, stage, sourcePath);
915
+ try {
916
+ if (!isValid$1(transform) || attr(transform, "info:id", time) !== "UsdTransform2d") return null;
917
+ const scale = asVec2(attr(transform, "inputs:scale", time), [1, 1]);
918
+ const translation = asVec2(attr(transform, "inputs:translation", time), [0, 0]);
919
+ return {
920
+ scaleX: scale[0],
921
+ scaleY: scale[1],
922
+ translateX: translation[0],
923
+ translateY: translation[1],
924
+ rotation: asFloat(attr(transform, "inputs:rotation", time))
925
+ };
926
+ } finally {
927
+ dispose$1(transform);
928
+ }
929
+ } finally {
930
+ dispose$1(stAttribute);
931
+ }
932
+ }
933
+ } finally {
934
+ disposeAll(children);
935
+ }
936
+ return null;
937
+ } finally {
938
+ dispose$1(material);
939
+ }
940
+ }
941
+ function transformUv(uv, transform) {
942
+ const numbers = toNumberArray(uv);
943
+ if (numbers.length < 2) return null;
944
+ let u = numbers[0] ?? 0;
945
+ let v = numbers[1] ?? 0;
946
+ if (!transform) return [u, v];
947
+ u *= transform.scaleX;
948
+ v *= transform.scaleY;
949
+ const rotation = transform.rotation * Math.PI / 180;
950
+ if (rotation !== 0) {
951
+ const cos = Math.cos(rotation);
952
+ const sin = Math.sin(rotation);
953
+ const nextU = u * cos - v * sin;
954
+ const nextV = u * sin + v * cos;
955
+ u = nextU;
956
+ v = nextV;
957
+ }
958
+ return [u + transform.translateX, v + transform.translateY];
959
+ }
960
+ function triangulateMesh(pxr, stage, prim, time) {
961
+ const points = toArray(attr(prim, "points", time, []));
962
+ const faceCounts = toNumberArray(attr(prim, "faceVertexCounts", time, []));
963
+ const faceIndices = toNumberArray(attr(prim, "faceVertexIndices", time, []));
964
+ const normals = toArray(attr(prim, "normals", time, []));
965
+ const normalInterpolation = String(attrMetadata(prim, "normals", "interpolation", attr(prim, "normals:interpolation", time, "")) || "") || null;
966
+ const boundMaterial = materialBinding(prim);
967
+ const uv = meshPrimvar(prim, texturePrimvarForMaterial(pxr, stage, boundMaterial, time), time);
968
+ const colors = toArray(attr(prim, "primvars:displayColor", time, []));
969
+ const opacities = toArray(attr(prim, "primvars:displayOpacity", time, []));
970
+ const uvTransform = textureTransformForMaterial(pxr, stage, boundMaterial, time);
971
+ const triangleCorners = [];
972
+ let cursor = 0;
973
+ for (let faceIndex = 0; faceIndex < faceCounts.length; ++faceIndex) {
974
+ const count = faceCounts[faceIndex] ?? 0;
975
+ const face = faceIndices.slice(cursor, cursor + count);
976
+ for (let index = 1; index < count - 1; ++index) triangleCorners.push([
977
+ face[0] ?? -1,
978
+ faceIndex,
979
+ cursor
980
+ ], [
981
+ face[index] ?? -1,
982
+ faceIndex,
983
+ cursor + index
984
+ ], [
985
+ face[index + 1] ?? -1,
986
+ faceIndex,
987
+ cursor + index + 1
988
+ ]);
989
+ cursor += count;
990
+ }
991
+ const positions = [];
992
+ const flatNormals = [];
993
+ const uvs = [];
994
+ for (const [pointIndex, faceIndex, faceVertexIndex] of triangleCorners) {
995
+ const point = toNumberArray(points[pointIndex]);
996
+ if (point.length >= 3) positions.push(point[0] ?? 0, point[1] ?? 0, point[2] ?? 0);
997
+ if (normals.length > 0) {
998
+ const normal = toNumberArray(samplePrimvar(normals, [], normalInterpolation, pointIndex, faceIndex, faceVertexIndex, points.length, faceCounts.length, faceIndices.length));
999
+ if (normal.length >= 3) flatNormals.push(normal[0] ?? 0, normal[1] ?? 0, normal[2] ?? 0);
1000
+ }
1001
+ if (uv.values.length > 0) {
1002
+ const transformedUv = transformUv(samplePrimvar(uv.values, uv.indices, uv.interpolation, pointIndex, faceIndex, faceVertexIndex, points.length, faceCounts.length, faceIndices.length), uvTransform);
1003
+ if (transformedUv) uvs.push(transformedUv[0], transformedUv[1]);
1004
+ }
1005
+ }
1006
+ return {
1007
+ positions,
1008
+ normals: flatNormals,
1009
+ uvs,
1010
+ displayColor: colors.length > 0 ? toVector3(colors[0], [
1011
+ 1,
1012
+ 1,
1013
+ 1
1014
+ ]) : null,
1015
+ displayOpacity: opacities.length > 0 ? asFiniteNumber(opacities[0]) : null
1016
+ };
1017
+ }
1018
+ function getModelElements(pxr, stage, prims, time) {
1019
+ const elements = [];
1020
+ for (const prim of prims) {
1021
+ if (prim.GetTypeName() !== "Mesh") continue;
1022
+ elements.push({
1023
+ path: String(prim.GetPath()),
1024
+ doubleSided: Boolean(attr(prim, "doubleSided", time, false)),
1025
+ material: materialBinding(prim),
1026
+ geometry: triangulateMesh(pxr, stage, prim, time)
1027
+ });
1028
+ }
1029
+ return elements;
1030
+ }
1031
+ function collectShaderInputs(prim, time) {
1032
+ const inputs = {};
1033
+ const attributes = prim.GetAttributes();
1034
+ try {
1035
+ for (const attribute of attributes) {
1036
+ const name = String(attribute.GetName());
1037
+ if (!name.includes("inputs:")) continue;
1038
+ if (!MATERIAL_INPUT_SUFFIXES.some((suffix) => name.endsWith(suffix))) continue;
1039
+ const value = attribute.Get(time);
1040
+ const connections = attribute.GetConnections();
1041
+ if (value !== null && value !== void 0) inputs[name] = normalizeValue(value);
1042
+ if (Array.isArray(connections) && connections.length > 0) inputs[`${name}.connect`] = connections.map(String);
1043
+ }
1044
+ } finally {
1045
+ disposeAll(attributes);
1046
+ }
1047
+ return inputs;
1048
+ }
1049
+ function getModelMaterials(prims, time) {
1050
+ const materials = [];
1051
+ for (const prim of prims) {
1052
+ if (prim.GetTypeName() !== "Material") continue;
1053
+ const children = primChildren(prim);
1054
+ const shaders = [];
1055
+ try {
1056
+ for (const child of children) {
1057
+ if (child.GetTypeName() !== "Shader") continue;
1058
+ const idValue = attr(child, "info:id", time);
1059
+ const id = typeof idValue === "string" ? idValue : null;
1060
+ const inputs = collectShaderInputs(child, time);
1061
+ if (Object.keys(inputs).length === 0 && id !== "UsdPreviewSurface" && id !== "UsdUVTexture") continue;
1062
+ shaders.push({
1063
+ path: String(child.GetPath()),
1064
+ id,
1065
+ inputs
1066
+ });
1067
+ }
1068
+ } finally {
1069
+ disposeAll(children);
1070
+ }
1071
+ materials.push({
1072
+ path: String(prim.GetPath()),
1073
+ inputs: collectShaderInputs(prim, time),
1074
+ shaders
1075
+ });
1076
+ }
1077
+ return materials;
1078
+ }
1079
+ function driveForNamespace(prim, namespace, time) {
1080
+ const drive = {
1081
+ targetPosition: asFiniteNumber(attr(prim, `drive:${namespace}:physics:targetPosition`, time)),
1082
+ targetVelocity: asFiniteNumber(attr(prim, `drive:${namespace}:physics:targetVelocity`, time)),
1083
+ stiffness: asFiniteNumber(attr(prim, `drive:${namespace}:physics:stiffness`, time)),
1084
+ damping: asFiniteNumber(attr(prim, `drive:${namespace}:physics:damping`, time)),
1085
+ maxForce: asFiniteNumber(attr(prim, `drive:${namespace}:physics:maxForce`, time))
1086
+ };
1087
+ return Object.values(drive).some((value) => value !== null) ? drive : null;
1088
+ }
1089
+ function jointTypeForPrim(typeName) {
1090
+ if (JOINT_TYPES[typeName]) return JOINT_TYPES[typeName];
1091
+ if (typeName.startsWith("Physics") && typeName.endsWith("Joint")) return typeName.slice(7, -5).toLowerCase() || "joint";
1092
+ return null;
1093
+ }
1094
+ function getModelJoints(prims, time) {
1095
+ const joints = [];
1096
+ for (const prim of prims) {
1097
+ const typeName = String(prim.GetTypeName());
1098
+ const jointType = jointTypeForPrim(typeName);
1099
+ if (!jointType) continue;
1100
+ const angularDrive = driveForNamespace(prim, "angular", time);
1101
+ const linearDrive = driveForNamespace(prim, "linear", time);
1102
+ const drives = {};
1103
+ if (angularDrive) drives.angular = angularDrive;
1104
+ if (linearDrive) drives.linear = linearDrive;
1105
+ const body0 = relationshipTargets(prim, "physics:body0");
1106
+ const body1 = relationshipTargets(prim, "physics:body1");
1107
+ const drive = jointType === "prismatic" ? linearDrive ?? angularDrive : angularDrive ?? linearDrive;
1108
+ joints.push({
1109
+ path: String(prim.GetPath()),
1110
+ name: String(prim.GetName()),
1111
+ typeName,
1112
+ jointType,
1113
+ body0: body0[0] ?? null,
1114
+ body1: body1[0] ?? null,
1115
+ axis: typeof attr(prim, "physics:axis", time) === "string" ? String(attr(prim, "physics:axis", time)) : null,
1116
+ localPos0: toVector3(attr(prim, "physics:localPos0", time, [
1117
+ 0,
1118
+ 0,
1119
+ 0
1120
+ ]), [
1121
+ 0,
1122
+ 0,
1123
+ 0
1124
+ ]),
1125
+ localPos1: toVector3(attr(prim, "physics:localPos1", time, [
1126
+ 0,
1127
+ 0,
1128
+ 0
1129
+ ]), [
1130
+ 0,
1131
+ 0,
1132
+ 0
1133
+ ]),
1134
+ localRot0: toVector4(attr(prim, "physics:localRot0", time)),
1135
+ localRot1: toVector4(attr(prim, "physics:localRot1", time)),
1136
+ lowerLimit: asFiniteNumber(attr(prim, "physics:lowerLimit", time)),
1137
+ upperLimit: asFiniteNumber(attr(prim, "physics:upperLimit", time)),
1138
+ enabled: Boolean(attr(prim, "physics:jointEnabled", time, true)),
1139
+ drive,
1140
+ drives
1141
+ });
1142
+ }
1143
+ return joints;
1144
+ }
1145
+ function stageTimingFromLayer(rootLayer) {
1146
+ const text = String(rootLayer.ExportToString?.() ?? "");
1147
+ const numberFor = (name) => {
1148
+ const match = new RegExp(`${name}\\s*=\\s*(-?\\d+(?:\\.\\d+)?)`).exec(text);
1149
+ if (!match?.[1]) return null;
1150
+ const value = Number(match[1]);
1151
+ return Number.isFinite(value) ? value : null;
1152
+ };
1153
+ return {
1154
+ startTimeCode: numberFor("startTimeCode"),
1155
+ endTimeCode: numberFor("endTimeCode"),
1156
+ timeCodesPerSecond: numberFor("timeCodesPerSecond") ?? 24
1157
+ };
1158
+ }
1159
+ function getStageInfo(pxr, stage, options) {
1160
+ const defaultPrim = stage.GetDefaultPrim();
1161
+ const rootLayer = stage.GetRootLayer();
1162
+ try {
1163
+ const timing = stageTimingFromLayer(rootLayer);
1164
+ return {
1165
+ sourcePath: options.sourcePath,
1166
+ rootLayerIdentifier: options.rootLayerIdentifier,
1167
+ defaultPrim: isValid$1(defaultPrim) ? String(defaultPrim.GetPath()) : null,
1168
+ upAxis: String(pxr.UsdGeom.GetStageUpAxis(stage)),
1169
+ ...timing
1170
+ };
1171
+ } finally {
1172
+ dispose$1(rootLayer);
1173
+ dispose$1(defaultPrim);
1174
+ }
1175
+ }
1176
+ function localMatrixFromXformOps(prim, time) {
1177
+ const opOrder = toArray(attr(prim, "xformOpOrder", time, [])).map(String);
1178
+ if (opOrder.length === 0) return null;
1179
+ let matrix = identityMatrix();
1180
+ let foundOp = false;
1181
+ for (const opName of opOrder) {
1182
+ if (opName.startsWith("!")) {
1183
+ matrix = identityMatrix();
1184
+ continue;
1185
+ }
1186
+ const opMatrix = matrixFromXformOp(opName, attr(prim, opName, time));
1187
+ if (!opMatrix) continue;
1188
+ matrix = multiplyMatrices(matrix, opMatrix);
1189
+ foundOp = true;
1190
+ }
1191
+ return foundOp ? matrix : null;
1192
+ }
1193
+ function sampleLocalMatrix(pxr, prim, frame) {
1194
+ const time = new pxr.Usd.TimeCode(frame);
1195
+ try {
1196
+ return localMatrixFromXformOps(prim, time);
1197
+ } finally {
1198
+ dispose$1(time);
1199
+ }
1200
+ }
1201
+ function animationSampleFrames(start, end) {
1202
+ const maxSamples = 1e3;
1203
+ const span = end - start;
1204
+ if (span <= 0) return [];
1205
+ const wholeFrameCount = Number.isInteger(start) && Number.isInteger(end) ? Math.floor(span) + 1 : 0;
1206
+ if (wholeFrameCount > 1 && wholeFrameCount <= maxSamples) return Array.from({ length: wholeFrameCount }, (_, index) => start + index);
1207
+ const sampleCount = Math.min(maxSamples, Math.max(2, Math.ceil(span) + 1));
1208
+ return Array.from({ length: sampleCount }, (_, index) => {
1209
+ return start + span * index / (sampleCount - 1);
1210
+ });
1211
+ }
1212
+ function getModelAnimations(pxr, prims, stageInfo) {
1213
+ const start = stageInfo.startTimeCode ?? null;
1214
+ const end = stageInfo.endTimeCode ?? null;
1215
+ const timeCodesPerSecond = stageInfo.timeCodesPerSecond ?? 24;
1216
+ if (start === null || end === null || end <= start) return [];
1217
+ const frames = animationSampleFrames(start, end);
1218
+ if (frames.length < 2) return [];
1219
+ const transforms = [];
1220
+ for (const prim of prims) {
1221
+ if (!prim.IsA("Xformable")) continue;
1222
+ const defaultTime = pxr.Usd.TimeCode.Default();
1223
+ try {
1224
+ if (toArray(attr(prim, "xformOpOrder", defaultTime, [])).length === 0) continue;
1225
+ } finally {
1226
+ dispose$1(defaultTime);
1227
+ }
1228
+ const samples = frames.map((frame) => {
1229
+ const localMatrix = sampleLocalMatrix(pxr, prim, frame);
1230
+ return localMatrix ? {
1231
+ time: (frame - start) / timeCodesPerSecond,
1232
+ localMatrix
1233
+ } : null;
1234
+ }).filter((sample) => sample !== null);
1235
+ if (samples.length < 2) continue;
1236
+ const first = samples[0]?.localMatrix ?? null;
1237
+ if (!samples.some((sample) => !matricesNearlyEqual(first, sample.localMatrix))) continue;
1238
+ transforms.push({
1239
+ primPath: String(prim.GetPath()),
1240
+ samples
1241
+ });
1242
+ }
1243
+ return transforms.length > 0 ? [{
1244
+ startTimeCode: start,
1245
+ endTimeCode: end,
1246
+ timeCodesPerSecond,
1247
+ transforms
1248
+ }] : [];
1249
+ }
1250
+ function extractUSDModelData(pxr, stage, options = {}) {
1251
+ const resolvedOptions = {
1252
+ sourcePath: options.sourcePath ?? "",
1253
+ rootLayerIdentifier: options.rootLayerIdentifier ?? ""
1254
+ };
1255
+ const time = pxr.Usd.TimeCode.Default();
1256
+ const prims = stage.Traverse();
1257
+ try {
1258
+ const stageInfo = getStageInfo(pxr, stage, resolvedOptions);
1259
+ return {
1260
+ stage: stageInfo,
1261
+ view: getModelView(pxr, prims, time),
1262
+ elements: getModelElements(pxr, stage, prims, time),
1263
+ materials: getModelMaterials(prims, time),
1264
+ joints: getModelJoints(prims, time),
1265
+ animations: getModelAnimations(pxr, prims, stageInfo)
1266
+ };
1267
+ } finally {
1268
+ disposeAll(prims);
1269
+ dispose$1(time);
1270
+ }
1271
+ }
1272
+ //#endregion
1273
+ //#region src/three.ts
1274
+ const sharedTextureCache = /* @__PURE__ */ new Map();
1275
+ function matrixFromUsd(values) {
1276
+ const matrix = new THREE.Matrix4();
1277
+ matrix.set(values[0] ?? 1, values[4] ?? 0, values[8] ?? 0, values[12] ?? 0, values[1] ?? 0, values[5] ?? 1, values[9] ?? 0, values[13] ?? 0, values[2] ?? 0, values[6] ?? 0, values[10] ?? 1, values[14] ?? 0, values[3] ?? 0, values[7] ?? 0, values[11] ?? 0, values[15] ?? 1);
1278
+ return matrix;
1279
+ }
1280
+ function applyLocalMatrix(object, values) {
1281
+ if (!values || values.length < 16) return;
1282
+ matrixFromUsd(values).decompose(object.position, object.quaternion, object.scale);
1283
+ }
1284
+ function vectorColor(value) {
1285
+ if (!Array.isArray(value) || value.length < 3) return null;
1286
+ const r = Number(value[0]);
1287
+ const g = Number(value[1]);
1288
+ const b = Number(value[2]);
1289
+ if (![
1290
+ r,
1291
+ g,
1292
+ b
1293
+ ].every(Number.isFinite)) return null;
1294
+ return new THREE.Color(r, g, b);
1295
+ }
1296
+ function firstInput(material, suffixes) {
1297
+ if (!material) return null;
1298
+ const inputSources = [material.inputs, ...material.shaders.map((shader) => shader.inputs)];
1299
+ for (const inputs of inputSources) for (const [key, value] of Object.entries(inputs)) if (suffixes.some((suffix) => key.endsWith(suffix))) return value;
1300
+ return null;
1301
+ }
1302
+ function publicUrlFromAsset(asset, options, material, shader) {
1303
+ if (!asset || typeof asset !== "object") return null;
1304
+ const assetValue = asset;
1305
+ const resolved = options.textureResolver?.(assetValue, {
1306
+ sourcePath: options.sourcePath ?? "",
1307
+ material,
1308
+ shader
1309
+ });
1310
+ if (resolved) return resolved;
1311
+ if (assetValue.url && looksLikeTextureURL(assetValue.url)) return assetValue.url;
1312
+ if (assetValue.path) {
1313
+ if (!looksLikeTextureURL(assetValue.path)) return null;
1314
+ try {
1315
+ return new URL(assetValue.path, options.sourcePath || globalThis.location?.href).href;
1316
+ } catch {
1317
+ return assetValue.path;
1318
+ }
1319
+ }
1320
+ return null;
1321
+ }
1322
+ function looksLikeTextureURL(value) {
1323
+ if (/^(blob|data):/i.test(value)) return true;
1324
+ return /\.(png|jpe?g|webp)(?:[?#].*)?$/i.test(value);
1325
+ }
1326
+ function textureWrap(value) {
1327
+ if (value === "repeat") return THREE.RepeatWrapping;
1328
+ if (value === "mirror") return THREE.MirroredRepeatWrapping;
1329
+ return THREE.ClampToEdgeWrapping;
1330
+ }
1331
+ function shaderPathFromConnection(value) {
1332
+ return Array.isArray(value) && typeof value[0] === "string" ? value[0].split(".outputs:")[0] ?? null : null;
1333
+ }
1334
+ function findTexture(material, options, connectionSuffixes, fallbackToAnyTexture = false) {
1335
+ if (!material) return null;
1336
+ const connectedPath = shaderPathFromConnection(firstInput(material, connectionSuffixes));
1337
+ const textureShader = material.shaders.find((shader) => shader.id === "UsdUVTexture" && (!connectedPath || shader.path === connectedPath)) ?? (fallbackToAnyTexture ? material.shaders.find((shader) => shader.id === "UsdUVTexture") : null);
1338
+ if (!textureShader) return null;
1339
+ const url = publicUrlFromAsset(textureShader.inputs["inputs:file"], options, material, textureShader);
1340
+ if (!url) return null;
1341
+ return {
1342
+ url,
1343
+ wrapS: textureWrap(textureShader.inputs["inputs:wrapS"]),
1344
+ wrapT: textureWrap(textureShader.inputs["inputs:wrapT"])
1345
+ };
1346
+ }
1347
+ function loadTexture(url, options, colorSpace) {
1348
+ const cache = options.textureCache ?? sharedTextureCache;
1349
+ const cacheKey = `${colorSpace}:${url}`;
1350
+ const cached = cache.get(cacheKey);
1351
+ if (cached) return cached;
1352
+ const texture = (options.textureLoader ?? new THREE.TextureLoader()).load(url);
1353
+ texture.colorSpace = colorSpace;
1354
+ texture.flipY = true;
1355
+ cache.set(cacheKey, texture);
1356
+ return texture;
1357
+ }
1358
+ function applyTextureWrap(texture, info) {
1359
+ texture.wrapS = info.wrapS;
1360
+ texture.wrapT = info.wrapT;
1361
+ }
1362
+ function buildMaterial(material, element, options) {
1363
+ const displayColor = element.geometry.displayColor ? new THREE.Color(...element.geometry.displayColor) : null;
1364
+ const baseColor = vectorColor(firstInput(material, [
1365
+ "diffuseColor",
1366
+ "base_color",
1367
+ "baseColor"
1368
+ ])) ?? displayColor ?? new THREE.Color(13158600);
1369
+ const opacityInput = firstInput(material, ["opacity", "alpha"]);
1370
+ const opacity = typeof opacityInput === "number" ? opacityInput : element.geometry.displayOpacity ?? 1;
1371
+ const roughness = firstInput(material, ["roughness"]);
1372
+ const metallic = firstInput(material, ["metallic", "metallicFactor"]);
1373
+ const diffuseTexture = options.loadTextures === false ? null : findTexture(material, options, ["diffuseColor.connect", "baseColor.connect"], true);
1374
+ const normalTexture = options.loadTextures === false ? null : findTexture(material, options, ["normal.connect"]);
1375
+ const metallicTexture = options.loadTextures === false ? null : findTexture(material, options, ["metallic.connect", "metallicFactor.connect"]);
1376
+ const roughnessTexture = options.loadTextures === false ? null : findTexture(material, options, ["roughness.connect"]);
1377
+ const map = diffuseTexture ? loadTexture(diffuseTexture.url, options, THREE.SRGBColorSpace) : null;
1378
+ const normalMap = normalTexture ? loadTexture(normalTexture.url, options, THREE.NoColorSpace) : null;
1379
+ const metalnessMap = metallicTexture ? loadTexture(metallicTexture.url, options, THREE.NoColorSpace) : null;
1380
+ const roughnessMap = roughnessTexture ? loadTexture(roughnessTexture.url, options, THREE.NoColorSpace) : null;
1381
+ if (map && diffuseTexture) applyTextureWrap(map, diffuseTexture);
1382
+ if (normalMap && normalTexture) applyTextureWrap(normalMap, normalTexture);
1383
+ if (metalnessMap && metallicTexture) applyTextureWrap(metalnessMap, metallicTexture);
1384
+ if (roughnessMap && roughnessTexture) applyTextureWrap(roughnessMap, roughnessTexture);
1385
+ return new THREE.MeshStandardMaterial({
1386
+ color: baseColor,
1387
+ map,
1388
+ normalMap,
1389
+ metalnessMap,
1390
+ roughnessMap,
1391
+ roughness: typeof roughness === "number" ? roughness : .55,
1392
+ metalness: typeof metallic === "number" ? metallic : 0,
1393
+ opacity,
1394
+ transparent: opacity < 1,
1395
+ side: element.doubleSided ? THREE.DoubleSide : THREE.FrontSide
1396
+ });
1397
+ }
1398
+ function buildGeometry(element) {
1399
+ const geometry = new THREE.BufferGeometry();
1400
+ geometry.setAttribute("position", new THREE.Float32BufferAttribute(element.geometry.positions, 3));
1401
+ if (element.geometry.normals.length > 0 && element.geometry.normals.length === element.geometry.positions.length) geometry.setAttribute("normal", new THREE.Float32BufferAttribute(element.geometry.normals, 3));
1402
+ else geometry.computeVertexNormals();
1403
+ if (element.geometry.uvs.length > 0) geometry.setAttribute("uv", new THREE.Float32BufferAttribute(element.geometry.uvs, 2));
1404
+ geometry.computeBoundingBox();
1405
+ geometry.computeBoundingSphere();
1406
+ return geometry;
1407
+ }
1408
+ function makeObjectForPrim(primPath, elementsByPath, materialsByPath, data, options) {
1409
+ const viewPrim = data.view.prims.find((item) => item.path === primPath);
1410
+ const element = elementsByPath.get(primPath);
1411
+ const object = element ? new THREE.Mesh(buildGeometry(element), buildMaterial(element.material ? materialsByPath.get(element.material) : void 0, element, options)) : new THREE.Group();
1412
+ object.name = viewPrim?.name ?? primPath.split("/").pop() ?? primPath;
1413
+ object.visible = viewPrim?.visibility !== "invisible";
1414
+ object.userData.usdPath = primPath;
1415
+ object.userData.usdTypeName = viewPrim?.typeName ?? "";
1416
+ applyLocalMatrix(object, viewPrim?.localMatrix ?? null);
1417
+ return object;
1418
+ }
1419
+ function attachJointUserData(data, objectsByPath) {
1420
+ for (const joint of data.joints) {
1421
+ const jointObject = objectsByPath.get(joint.path);
1422
+ if (jointObject) jointObject.userData.usdJoint = joint;
1423
+ for (const bodyPath of [joint.body0, joint.body1]) {
1424
+ if (!bodyPath) continue;
1425
+ const bodyObject = objectsByPath.get(bodyPath);
1426
+ if (!bodyObject) continue;
1427
+ const joints = bodyObject.userData.usdJoints;
1428
+ if (Array.isArray(joints)) joints.push(joint);
1429
+ else bodyObject.userData.usdJoints = [joint];
1430
+ }
1431
+ }
1432
+ }
1433
+ function createTransformTracks(animation, objectsByPath) {
1434
+ const tracks = [];
1435
+ for (const transform of animation.transforms) {
1436
+ const object = objectsByPath.get(transform.primPath);
1437
+ if (!object || transform.samples.length < 2) continue;
1438
+ const times = [];
1439
+ const positions = [];
1440
+ const quaternions = [];
1441
+ const scales = [];
1442
+ let previousQuaternion = null;
1443
+ for (const sample of transform.samples) {
1444
+ const matrix = matrixFromUsd(sample.localMatrix);
1445
+ const position = new THREE.Vector3();
1446
+ const quaternion = new THREE.Quaternion();
1447
+ const scale = new THREE.Vector3();
1448
+ matrix.decompose(position, quaternion, scale);
1449
+ if (previousQuaternion && previousQuaternion.dot(quaternion) < 0) quaternion.set(-quaternion.x, -quaternion.y, -quaternion.z, -quaternion.w);
1450
+ previousQuaternion = quaternion.clone();
1451
+ times.push(sample.time);
1452
+ positions.push(position.x, position.y, position.z);
1453
+ quaternions.push(quaternion.x, quaternion.y, quaternion.z, quaternion.w);
1454
+ scales.push(scale.x, scale.y, scale.z);
1455
+ }
1456
+ tracks.push(new THREE.VectorKeyframeTrack(`${object.uuid}.position`, times, positions), new THREE.QuaternionKeyframeTrack(`${object.uuid}.quaternion`, times, quaternions), new THREE.VectorKeyframeTrack(`${object.uuid}.scale`, times, scales));
1457
+ }
1458
+ return tracks;
1459
+ }
1460
+ function createAnimationClips(animations, objectsByPath) {
1461
+ return animations.map((animation, index) => {
1462
+ const tracks = createTransformTracks(animation, objectsByPath);
1463
+ if (tracks.length === 0) return null;
1464
+ return new THREE.AnimationClip(`USDAnimation_${index + 1}`, -1, tracks);
1465
+ }).filter((clip) => clip !== null);
1466
+ }
1467
+ function buildUSDObject(data, options = {}) {
1468
+ const sourcePath = options.sourcePath ?? data.stage.sourcePath;
1469
+ const wrapper = new THREE.Group();
1470
+ wrapper.name = `USD:${sourcePath}`;
1471
+ wrapper.userData.usdMetadata = data;
1472
+ wrapper.userData.usdJoints = data.joints;
1473
+ wrapper.userData.usdPath = data.stage.defaultPrim ?? "/";
1474
+ wrapper.userData.usdSource = sourcePath;
1475
+ const elementsByPath = new Map(data.elements.map((element) => [element.path, element]));
1476
+ const materialsByPath = new Map(data.materials.map((material) => [material.path, material]));
1477
+ const objectsByPath = /* @__PURE__ */ new Map();
1478
+ for (const prim of data.view.prims) objectsByPath.set(prim.path, makeObjectForPrim(prim.path, elementsByPath, materialsByPath, data, {
1479
+ ...options,
1480
+ sourcePath
1481
+ }));
1482
+ for (const prim of data.view.prims) {
1483
+ const object = objectsByPath.get(prim.path);
1484
+ if (!object) continue;
1485
+ const parent = prim.parentPath ? objectsByPath.get(prim.parentPath) : null;
1486
+ if (parent) parent.add(object);
1487
+ else wrapper.add(object);
1488
+ }
1489
+ if (options.convertZUp !== false && data.stage.upAxis === "Z") wrapper.rotation.x = -Math.PI / 2;
1490
+ attachJointUserData(data, objectsByPath);
1491
+ wrapper.userData.usdObjectsByPath = objectsByPath;
1492
+ wrapper.userData.usdAnimations = data.animations;
1493
+ wrapper.animations = createAnimationClips(data.animations, objectsByPath);
1494
+ return wrapper;
1495
+ }
1496
+ function createUSDLoadedModel(data, pxr, rootLayerIdentifier, options = {}, stage) {
1497
+ const sourcePath = options.sourcePath ?? data.stage.sourcePath;
1498
+ return {
1499
+ scene: buildUSDObject(data, {
1500
+ ...options,
1501
+ sourcePath
1502
+ }),
1503
+ metadata: data,
1504
+ stage,
1505
+ pxr,
1506
+ sourcePath,
1507
+ rootLayerIdentifier
1508
+ };
1509
+ }
1510
+ //#endregion
1511
+ //#region src/loader.ts
1512
+ const DEFAULT_WORKING_DIRECTORY = "/tmp/openusd-three-loader";
1513
+ let nextLoadId = 0;
1514
+ function isLoadingManager(value) {
1515
+ return !!value && typeof value === "object" && "itemStart" in value && "itemEnd" in value;
1516
+ }
1517
+ function dispose(value) {
1518
+ if (value && typeof value === "object" && "delete" in value && typeof value.delete === "function") value.delete();
1519
+ }
1520
+ function isValid(value) {
1521
+ return Boolean(value && (typeof value.IsValid !== "function" || value.IsValid()));
1522
+ }
1523
+ function isBlobLike(value) {
1524
+ return typeof Blob !== "undefined" && value instanceof Blob;
1525
+ }
1526
+ function extensionForPath(path) {
1527
+ const withoutQuery = path.split(/[?#]/)[0] ?? path;
1528
+ const extension = /\.([a-z0-9]+)$/i.exec(withoutQuery)?.[1]?.toLowerCase();
1529
+ if (extension === "usd" || extension === "usda" || extension === "usdc" || extension === "usdz") return extension;
1530
+ return "usda";
1531
+ }
1532
+ function fileNameForSource(sourcePath, fallbackExtension) {
1533
+ const name = (sourcePath.split(/[?#]/)[0] ?? sourcePath).split("/").filter(Boolean).pop();
1534
+ if (name && /\.[a-z0-9]+$/i.test(name)) return sanitizeFileName(name);
1535
+ return `scene.${fallbackExtension}`;
1536
+ }
1537
+ function sanitizeFileName(name) {
1538
+ return name.replace(/[^A-Za-z0-9._-]+/g, "_").replace(/^_+/, "") || "scene.usda";
1539
+ }
1540
+ function joinFsPath(base, name) {
1541
+ return `${base.replace(/\/+$/, "")}/${name.replace(/^\/+/, "")}`;
1542
+ }
1543
+ function normalizeBytes(input) {
1544
+ if (input instanceof ArrayBuffer) return new Uint8Array(input);
1545
+ return new Uint8Array(input.buffer, input.byteOffset, input.byteLength);
1546
+ }
1547
+ async function sourceToWritableData(input) {
1548
+ if (typeof input === "string") return input;
1549
+ if (isBlobLike(input)) return new Uint8Array(await input.arrayBuffer());
1550
+ return normalizeBytes(input);
1551
+ }
1552
+ function maybeResolveURL(path, basePath) {
1553
+ if (!basePath) return path;
1554
+ try {
1555
+ return new URL(path, basePath).href;
1556
+ } catch {
1557
+ return `${basePath.replace(/\/+$/, "")}/${path.replace(/^\/+/, "")}`;
1558
+ }
1559
+ }
1560
+ function composeTextureResolver(userResolver, packageResolver) {
1561
+ if (!packageResolver) return userResolver;
1562
+ return (asset, context) => userResolver?.(asset, context) ?? packageResolver(asset, context);
1563
+ }
1564
+ function deleteFileIfExists(pxr, path) {
1565
+ try {
1566
+ if (pxr.FS.exists(path)) pxr.FS.deleteFile(path);
1567
+ } catch {}
1568
+ }
1569
+ async function writeUSDInput(pxr, input, options) {
1570
+ const directory = joinFsPath(options.workingDirectory, String(++nextLoadId));
1571
+ pxr.FS.createDir(directory);
1572
+ for (const [relativePath, file] of Object.entries(options.files ?? {})) {
1573
+ const filePath = joinFsPath(directory, relativePath);
1574
+ pxr.FS.writeFile(filePath, await sourceToWritableData(file));
1575
+ }
1576
+ const path = joinFsPath(directory, sanitizeFileName(options.fileName));
1577
+ pxr.FS.writeFile(path, input);
1578
+ return path;
1579
+ }
1580
+ function openStage(pxr, filePath, extension, usdzLayer) {
1581
+ const identifiers = extension === "usdz" && usdzLayer ? [`${filePath}[${usdzLayer}]`, filePath] : [filePath];
1582
+ for (const identifier of identifiers) {
1583
+ const stage = pxr.Usd.Stage.Open(identifier);
1584
+ if (isValid(stage)) return {
1585
+ stage,
1586
+ identifier
1587
+ };
1588
+ dispose(stage);
1589
+ }
1590
+ throw new Error(`Failed to open USD stage from ${filePath}`);
1591
+ }
1592
+ function mergeParseOptions(loaderOptions, parseOptions) {
1593
+ const sourcePath = parseOptions.sourcePath ?? parseOptions.fileName ?? "scene.usda";
1594
+ const extension = extensionForPath(parseOptions.fileName ?? sourcePath);
1595
+ return {
1596
+ ...loaderOptions,
1597
+ ...parseOptions,
1598
+ sourcePath,
1599
+ workingDirectory: parseOptions.workingDirectory ?? loaderOptions.workingDirectory ?? DEFAULT_WORKING_DIRECTORY,
1600
+ fileName: parseOptions.fileName ?? fileNameForSource(sourcePath, extension),
1601
+ usdzLayer: parseOptions.usdzLayer ?? loaderOptions.usdzLayer,
1602
+ preserveStage: parseOptions.preserveStage ?? loaderOptions.preserveStage,
1603
+ cleanupAfterParse: parseOptions.cleanupAfterParse ?? loaderOptions.cleanupAfterParse ?? false
1604
+ };
1605
+ }
1606
+ var USDLoader = class extends Loader {
1607
+ options;
1608
+ pxrPromise;
1609
+ constructor(optionsOrManager, manager) {
1610
+ if (isLoadingManager(optionsOrManager)) {
1611
+ super(optionsOrManager);
1612
+ this.options = {};
1613
+ } else {
1614
+ super(manager);
1615
+ this.options = optionsOrManager ?? {};
1616
+ }
1617
+ this.pxrPromise = this.options.pxr ? Promise.resolve(this.options.pxr) : null;
1618
+ }
1619
+ setPxr(pxr) {
1620
+ this.pxrPromise = Promise.resolve(pxr);
1621
+ return this;
1622
+ }
1623
+ setUSDZLayer(layer) {
1624
+ this.options.usdzLayer = layer;
1625
+ return this;
1626
+ }
1627
+ setWorkingDirectory(path) {
1628
+ this.options.workingDirectory = path;
1629
+ return this;
1630
+ }
1631
+ load(url, onLoad, onProgress, onError) {
1632
+ const loader = new FileLoader(this.manager);
1633
+ loader.setPath(this.path);
1634
+ loader.setResponseType("arraybuffer");
1635
+ loader.setRequestHeader(this.requestHeader);
1636
+ loader.setWithCredentials(this.withCredentials);
1637
+ loader.load(url, (data) => {
1638
+ const sourcePath = maybeResolveURL(url, this.path);
1639
+ this.parseAsync(data, { sourcePath }).then(onLoad).catch((error) => {
1640
+ if (onError) onError(error);
1641
+ else console.error(error);
1642
+ this.manager.itemError(url);
1643
+ });
1644
+ }, onProgress, onError);
1645
+ }
1646
+ parse(input, onLoad, onError, options = {}) {
1647
+ this.parseAsync(input, options).then(onLoad).catch((error) => {
1648
+ if (onError) onError(error);
1649
+ else console.error(error);
1650
+ });
1651
+ }
1652
+ async parseAsync(input, options = {}) {
1653
+ const pxr = await this.getPxr();
1654
+ const mergedOptions = mergeParseOptions(this.options, options);
1655
+ const extension = extensionForPath(mergedOptions.fileName);
1656
+ const writableData = await sourceToWritableData(input);
1657
+ const packageTextureResolver = extension === "usdz" && writableData instanceof Uint8Array ? createPackageTextureResolver(writableData) : null;
1658
+ let autoFiles = writableData instanceof Uint8Array && extension !== "usdz" ? await autoResolveAssetFiles(writableData, mergedOptions) : {};
1659
+ const filePath = await writeUSDInput(pxr, writableData, {
1660
+ ...mergedOptions,
1661
+ files: {
1662
+ ...autoFiles,
1663
+ ...mergedOptions.files
1664
+ }
1665
+ });
1666
+ const { stage, identifier } = openStage(pxr, filePath, extension, mergedOptions.usdzLayer);
1667
+ try {
1668
+ const metadata = extractUSDModelData(pxr, stage, {
1669
+ sourcePath: mergedOptions.sourcePath,
1670
+ rootLayerIdentifier: identifier
1671
+ });
1672
+ if (extension !== "usdz") autoFiles = {
1673
+ ...autoFiles,
1674
+ ...await autoResolveTextureFiles(metadata, autoFiles, mergedOptions)
1675
+ };
1676
+ const autoTextureResolver = createTextureResolverFromEntries(new Map(Object.entries(autoFiles).map(([path, data]) => [path, normalizeBytes(data)])));
1677
+ const model = createUSDLoadedModel(metadata, pxr, identifier, {
1678
+ ...mergedOptions,
1679
+ textureResolver: composeTextureResolver(composeTextureResolver(mergedOptions.textureResolver, packageTextureResolver?.resolve), autoTextureResolver?.resolve)
1680
+ }, mergedOptions.preserveStage ? stage : void 0);
1681
+ if (packageTextureResolver) model.scene.userData.usdzTextureURLs = packageTextureResolver.urls;
1682
+ if (autoTextureResolver) model.scene.userData.autoTextureURLs = autoTextureResolver.urls;
1683
+ return model;
1684
+ } finally {
1685
+ if (!mergedOptions.preserveStage) dispose(stage);
1686
+ if (mergedOptions.cleanupAfterParse && !mergedOptions.preserveStage) deleteFileIfExists(pxr, filePath);
1687
+ }
1688
+ }
1689
+ async getPxr() {
1690
+ if (!this.pxrPromise) {
1691
+ if (!this.options.pxrOptions) throw new Error("USDLoader requires either a pxr instance or pxrOptions");
1692
+ this.pxrPromise = createPxr(this.options.pxrOptions);
1693
+ }
1694
+ return this.pxrPromise;
1695
+ }
1696
+ };
1697
+ //#endregion
1698
+ //#region src/manipulation-controls.ts
1699
+ const axisVectors = {
1700
+ X: new THREE.Vector3(1, 0, 0),
1701
+ Y: new THREE.Vector3(0, 1, 0),
1702
+ Z: new THREE.Vector3(0, 0, 1)
1703
+ };
1704
+ function clamp(value, min, max) {
1705
+ return Math.min(max, Math.max(min, value));
1706
+ }
1707
+ function limitsForJoint(joint) {
1708
+ if (joint.lowerLimit !== null && joint.upperLimit !== null) return {
1709
+ lower: joint.lowerLimit,
1710
+ upper: joint.upperLimit
1711
+ };
1712
+ return joint.jointType === "revolute" ? {
1713
+ lower: -180,
1714
+ upper: 180
1715
+ } : {
1716
+ lower: -1,
1717
+ upper: 1
1718
+ };
1719
+ }
1720
+ function initialValueForJoint(joint) {
1721
+ const { lower, upper } = limitsForJoint(joint);
1722
+ return clamp(joint.drive?.targetPosition ?? lower, lower, upper);
1723
+ }
1724
+ function isManipulableJoint(joint) {
1725
+ return joint.enabled && (joint.jointType === "revolute" || joint.jointType === "prismatic");
1726
+ }
1727
+ function objectsByPathForRoot(root) {
1728
+ const userDataMap = root.userData.usdObjectsByPath;
1729
+ if (userDataMap instanceof Map) return new Map([...userDataMap.entries()].filter((entry) => {
1730
+ return typeof entry[0] === "string" && entry[1] instanceof THREE.Object3D;
1731
+ }));
1732
+ const objectsByPath = /* @__PURE__ */ new Map();
1733
+ root.traverse((child) => {
1734
+ const usdPath = child.userData.usdPath;
1735
+ if (typeof usdPath === "string") objectsByPath.set(usdPath, child);
1736
+ });
1737
+ return objectsByPath;
1738
+ }
1739
+ function findUSDModels(scene) {
1740
+ const models = [];
1741
+ scene.traverse((object) => {
1742
+ const metadata = object.userData.usdMetadata;
1743
+ if (!metadata) return;
1744
+ object.updateMatrixWorld(true);
1745
+ models.push({
1746
+ root: object,
1747
+ metadata,
1748
+ objectsByPath: objectsByPathForRoot(object)
1749
+ });
1750
+ });
1751
+ return models;
1752
+ }
1753
+ function modelStateSignature(models) {
1754
+ return models.map((model) => {
1755
+ const matrix = model.root.matrixWorld.elements.map((value) => value.toFixed(5)).join(",");
1756
+ const stage = model.metadata.stage;
1757
+ return [
1758
+ model.root.uuid,
1759
+ stage.rootLayerIdentifier,
1760
+ stage.defaultPrim ?? "",
1761
+ model.metadata.joints.length,
1762
+ matrix
1763
+ ].join(":");
1764
+ }).join("|");
1765
+ }
1766
+ function jointForObject(models, object) {
1767
+ for (const model of models) for (const joint of model.metadata.joints) {
1768
+ if (!isManipulableJoint(joint) || !joint.body1) continue;
1769
+ const bodyObject = model.objectsByPath.get(joint.body1);
1770
+ if (!bodyObject) continue;
1771
+ let current = object;
1772
+ while (current) {
1773
+ if (current === bodyObject) return {
1774
+ model,
1775
+ joint,
1776
+ object: bodyObject,
1777
+ body0: joint.body0 ? model.objectsByPath.get(joint.body0) ?? null : null
1778
+ };
1779
+ current = current.parent;
1780
+ }
1781
+ }
1782
+ return null;
1783
+ }
1784
+ function jointValueKey(model, joint) {
1785
+ return `${model.root.uuid}:${joint.path}`;
1786
+ }
1787
+ function localAxisForJoint(joint) {
1788
+ return axisVectors[joint.axis ?? ""]?.clone() ?? axisVectors.X.clone();
1789
+ }
1790
+ function localPivotForJoint(joint) {
1791
+ return new THREE.Vector3(joint.localPos0[0] ?? 0, joint.localPos0[1] ?? 0, joint.localPos0[2] ?? 0);
1792
+ }
1793
+ function parentLocalPoint(object, body0, point) {
1794
+ if (!body0) return point.clone();
1795
+ const worldPoint = body0.localToWorld(point.clone());
1796
+ return object.parent ? object.parent.worldToLocal(worldPoint) : worldPoint;
1797
+ }
1798
+ function parentLocalVectorForJoint(joint, object, body0, delta) {
1799
+ const pivot = localPivotForJoint(joint);
1800
+ const start = parentLocalPoint(object, body0, pivot);
1801
+ return parentLocalPoint(object, body0, pivot.clone().add(localAxisForJoint(joint).multiplyScalar(delta))).sub(start);
1802
+ }
1803
+ function parentLocalAxisForJoint(joint, object, body0) {
1804
+ const axis = parentLocalVectorForJoint(joint, object, body0, 1);
1805
+ if (axis.lengthSq() < 1e-10) return localAxisForJoint(joint).normalize();
1806
+ return axis.normalize();
1807
+ }
1808
+ function parentLocalPivotForJoint(joint, object, body0) {
1809
+ return parentLocalPoint(object, body0, localPivotForJoint(joint));
1810
+ }
1811
+ function parentLocalToWorld(object, point) {
1812
+ return object.parent ? object.parent.localToWorld(point.clone()) : point.clone();
1813
+ }
1814
+ function projectToClient(point, camera, rect) {
1815
+ const ndc = point.clone().project(camera);
1816
+ return new THREE.Vector2((ndc.x * .5 + .5) * rect.width, (-ndc.y * .5 + .5) * rect.height);
1817
+ }
1818
+ function prismaticDragMapping(joint, object, body0, camera, rect) {
1819
+ const { lower, upper } = limitsForJoint(joint);
1820
+ const referenceDelta = Math.max(Math.abs(upper - lower), .1);
1821
+ const startPivot = parentLocalPivotForJoint(joint, object, body0);
1822
+ const start = parentLocalToWorld(object, startPivot);
1823
+ const screenVector = projectToClient(parentLocalToWorld(object, startPivot.clone().add(parentLocalVectorForJoint(joint, object, body0, referenceDelta))), camera, rect).sub(projectToClient(start, camera, rect));
1824
+ const screenLength = screenVector.length();
1825
+ if (screenLength < 1) return {
1826
+ screenAxis: new THREE.Vector2(1, 0),
1827
+ valuePerPixel: referenceDelta / 180
1828
+ };
1829
+ return {
1830
+ screenAxis: screenVector.normalize(),
1831
+ valuePerPixel: referenceDelta / screenLength
1832
+ };
1833
+ }
1834
+ function fallbackRevoluteScreenAxis(pivot, axis, camera, rect) {
1835
+ const projectedAxis = projectToClient(pivot.clone().add(axis), camera, rect).sub(projectToClient(pivot, camera, rect));
1836
+ if (projectedAxis.length() < 1) return new THREE.Vector2(1, -1).normalize();
1837
+ return new THREE.Vector2(-projectedAxis.y, projectedAxis.x).normalize();
1838
+ }
1839
+ function revoluteDragMapping(joint, object, body0, camera, rect) {
1840
+ const axis = parentLocalAxisForJoint(joint, object, body0);
1841
+ const pivot = parentLocalPivotForJoint(joint, object, body0);
1842
+ const referenceDegrees = 12;
1843
+ const referenceRadians = THREE.MathUtils.degToRad(referenceDegrees);
1844
+ const bounds = new THREE.Box3().setFromObject(object);
1845
+ const center = object.parent ? object.parent.worldToLocal(bounds.getCenter(new THREE.Vector3())) : bounds.getCenter(new THREE.Vector3());
1846
+ const size = bounds.getSize(new THREE.Vector3());
1847
+ const radius = Math.max(size.length() * .25, .05);
1848
+ const radiusVector = center.sub(pivot);
1849
+ radiusVector.addScaledVector(axis, -radiusVector.dot(axis));
1850
+ if (radiusVector.lengthSq() < 1e-8) {
1851
+ const fallback = Math.abs(axis.dot(new THREE.Vector3(0, 1, 0))) > .9 ? new THREE.Vector3(1, 0, 0) : new THREE.Vector3(0, 1, 0);
1852
+ radiusVector.copy(fallback.cross(axis));
1853
+ }
1854
+ radiusVector.setLength(Math.max(radiusVector.length(), radius));
1855
+ const start = pivot.clone().add(radiusVector);
1856
+ const end = pivot.clone().add(radiusVector.clone().applyAxisAngle(axis, referenceRadians));
1857
+ const startWorld = parentLocalToWorld(object, start);
1858
+ const screenVector = projectToClient(parentLocalToWorld(object, end), camera, rect).sub(projectToClient(startWorld, camera, rect));
1859
+ const screenLength = screenVector.length();
1860
+ if (screenLength < 1) return {
1861
+ screenAxis: fallbackRevoluteScreenAxis(parentLocalToWorld(object, pivot), parentLocalToWorld(object, pivot.clone().add(axis)).sub(parentLocalToWorld(object, pivot)), camera, rect),
1862
+ valuePerPixel: referenceDegrees / 140
1863
+ };
1864
+ return {
1865
+ screenAxis: screenVector.normalize(),
1866
+ valuePerPixel: referenceDegrees / screenLength
1867
+ };
1868
+ }
1869
+ function updateJointTransform(drag, value) {
1870
+ const delta = value - drag.startValue;
1871
+ const axis = parentLocalAxisForJoint(drag.joint, drag.object, drag.body0);
1872
+ const nextLocal = new THREE.Matrix4();
1873
+ if (drag.joint.jointType === "revolute") {
1874
+ const pivot = parentLocalPivotForJoint(drag.joint, drag.object, drag.body0);
1875
+ const rotation = new THREE.Matrix4().makeRotationAxis(axis, THREE.MathUtils.degToRad(delta));
1876
+ nextLocal.makeTranslation(pivot.x, pivot.y, pivot.z).multiply(rotation).multiply(new THREE.Matrix4().makeTranslation(-pivot.x, -pivot.y, -pivot.z)).multiply(drag.initialLocalMatrix);
1877
+ nextLocal.decompose(drag.object.position, drag.object.quaternion, drag.object.scale);
1878
+ drag.object.updateMatrixWorld(true);
1879
+ return;
1880
+ }
1881
+ const translation = parentLocalVectorForJoint(drag.joint, drag.object, drag.body0, delta);
1882
+ nextLocal.makeTranslation(translation.x, translation.y, translation.z).multiply(drag.initialLocalMatrix);
1883
+ nextLocal.decompose(drag.object.position, drag.object.quaternion, drag.object.scale);
1884
+ drag.object.updateMatrixWorld(true);
1885
+ }
1886
+ function highlightedMaterials(material, color) {
1887
+ const highlighted = (Array.isArray(material) ? material : [material]).map((item) => {
1888
+ const cloned = item.clone();
1889
+ if ("emissive" in cloned) {
1890
+ const emissiveMaterial = cloned;
1891
+ emissiveMaterial.emissive = color.clone();
1892
+ emissiveMaterial.emissiveIntensity = Math.max(emissiveMaterial.emissiveIntensity, .28);
1893
+ }
1894
+ return cloned;
1895
+ });
1896
+ return Array.isArray(material) ? highlighted : highlighted[0];
1897
+ }
1898
+ function disposeMaterial(material) {
1899
+ const materials = Array.isArray(material) ? material : [material];
1900
+ for (const item of materials) item.dispose();
1901
+ }
1902
+ var USDManipulationControls = class {
1903
+ raycaster = new THREE.Raycaster();
1904
+ pointer = new THREE.Vector2();
1905
+ jointValues = /* @__PURE__ */ new Map();
1906
+ highlighted = /* @__PURE__ */ new Map();
1907
+ activeDrag = null;
1908
+ hovered = null;
1909
+ modelStateSignature = null;
1910
+ _enabled;
1911
+ disposed = false;
1912
+ scene;
1913
+ camera;
1914
+ domElement;
1915
+ controls;
1916
+ highlightColor;
1917
+ onChange;
1918
+ onHoverChange;
1919
+ constructor(options) {
1920
+ this.scene = options.scene;
1921
+ this.camera = options.camera;
1922
+ this.domElement = options.domElement;
1923
+ this.controls = options.controls ?? null;
1924
+ this.highlightColor = new THREE.Color(options.highlightColor ?? 16777215);
1925
+ this._enabled = options.enabled ?? true;
1926
+ this.onChange = options.onChange;
1927
+ this.onHoverChange = options.onHoverChange;
1928
+ this.domElement.addEventListener("pointermove", this.onPointerMove);
1929
+ this.domElement.addEventListener("pointerdown", this.onPointerDown);
1930
+ this.domElement.addEventListener("pointerup", this.onPointerUp);
1931
+ this.domElement.addEventListener("pointercancel", this.onPointerUp);
1932
+ }
1933
+ get enabled() {
1934
+ return this._enabled;
1935
+ }
1936
+ set enabled(value) {
1937
+ if (this._enabled === value) return;
1938
+ this._enabled = value;
1939
+ if (!value) {
1940
+ this.endDrag(null);
1941
+ this.clearSelection();
1942
+ }
1943
+ }
1944
+ dispose() {
1945
+ if (this.disposed) return;
1946
+ this.disposed = true;
1947
+ this.domElement.removeEventListener("pointermove", this.onPointerMove);
1948
+ this.domElement.removeEventListener("pointerdown", this.onPointerDown);
1949
+ this.domElement.removeEventListener("pointerup", this.onPointerUp);
1950
+ this.domElement.removeEventListener("pointercancel", this.onPointerUp);
1951
+ this.endDrag(null);
1952
+ this.clearSelection();
1953
+ for (const [mesh, state] of this.highlighted) {
1954
+ disposeMaterial(state.highlighted);
1955
+ mesh.material = state.original;
1956
+ }
1957
+ this.highlighted.clear();
1958
+ }
1959
+ update() {
1960
+ this.syncModelState(findUSDModels(this.scene));
1961
+ }
1962
+ clearSelection() {
1963
+ if (!this.hovered) return;
1964
+ this.restoreHighlight(this.hovered.object);
1965
+ this.hovered = null;
1966
+ this.onHoverChange?.({
1967
+ joint: null,
1968
+ object: null
1969
+ });
1970
+ }
1971
+ getJointValue(joint, root) {
1972
+ if (root) return this.jointValues.get(`${root.uuid}:${joint.path}`);
1973
+ for (const [key, value] of this.jointValues) if (key.endsWith(`:${joint.path}`)) return value;
1974
+ }
1975
+ setJointValue(joint, value, root) {
1976
+ const models = findUSDModels(this.scene);
1977
+ this.syncModelState(models);
1978
+ for (const model of models) {
1979
+ if (root && model.root !== root) continue;
1980
+ const modelJoint = model.metadata.joints.find((item) => item.path === joint.path);
1981
+ if (!modelJoint || !isManipulableJoint(modelJoint) || !modelJoint.body1) continue;
1982
+ const object = model.objectsByPath.get(modelJoint.body1);
1983
+ if (!object) continue;
1984
+ object.updateMatrix();
1985
+ object.updateMatrixWorld(true);
1986
+ const body0 = modelJoint.body0 ? model.objectsByPath.get(modelJoint.body0) ?? null : null;
1987
+ body0?.updateMatrixWorld(true);
1988
+ const jointKey = jointValueKey(model, modelJoint);
1989
+ const currentValue = this.jointValues.get(jointKey) ?? initialValueForJoint(modelJoint);
1990
+ const { lower, upper } = limitsForJoint(modelJoint);
1991
+ const nextValue = clamp(value, lower, upper);
1992
+ const drag = {
1993
+ model,
1994
+ joint: modelJoint,
1995
+ object,
1996
+ body0,
1997
+ startX: 0,
1998
+ startY: 0,
1999
+ startValue: currentValue,
2000
+ initialLocalMatrix: object.matrix.clone(),
2001
+ jointKey,
2002
+ controlsWasEnabled: null
2003
+ };
2004
+ this.jointValues.set(jointKey, nextValue);
2005
+ updateJointTransform(drag, nextValue);
2006
+ this.onChange?.({
2007
+ joint: modelJoint,
2008
+ object,
2009
+ body0,
2010
+ value: nextValue,
2011
+ previousValue: currentValue
2012
+ });
2013
+ return true;
2014
+ }
2015
+ return false;
2016
+ }
2017
+ onPointerMove = (event) => {
2018
+ if (!this._enabled || this.disposed) return;
2019
+ const activeDrag = this.activeDrag;
2020
+ if (activeDrag) {
2021
+ const pointerDelta = new THREE.Vector2(event.clientX - activeDrag.startX, event.clientY - activeDrag.startY);
2022
+ let mappedValueDelta;
2023
+ if (activeDrag.joint.jointType === "prismatic" && activeDrag.prismaticScreenAxis && activeDrag.prismaticValuePerPixel !== void 0) mappedValueDelta = pointerDelta.dot(activeDrag.prismaticScreenAxis) * activeDrag.prismaticValuePerPixel;
2024
+ else if (activeDrag.joint.jointType === "revolute" && activeDrag.revoluteScreenAxis && activeDrag.revoluteValuePerPixel !== void 0) mappedValueDelta = pointerDelta.dot(activeDrag.revoluteScreenAxis) * activeDrag.revoluteValuePerPixel;
2025
+ else {
2026
+ const { lower, upper } = limitsForJoint(activeDrag.joint);
2027
+ const range = Math.max(upper - lower, activeDrag.joint.jointType === "revolute" ? 90 : .5);
2028
+ mappedValueDelta = (pointerDelta.x - pointerDelta.y) / 280 * range;
2029
+ }
2030
+ const { lower, upper } = limitsForJoint(activeDrag.joint);
2031
+ const previousValue = this.jointValues.get(activeDrag.jointKey) ?? activeDrag.startValue;
2032
+ const nextValue = clamp(activeDrag.startValue + mappedValueDelta, lower, upper);
2033
+ if (nextValue === previousValue) return;
2034
+ this.jointValues.set(activeDrag.jointKey, nextValue);
2035
+ updateJointTransform(activeDrag, nextValue);
2036
+ this.onChange?.({
2037
+ joint: activeDrag.joint,
2038
+ object: activeDrag.object,
2039
+ body0: activeDrag.body0,
2040
+ value: nextValue,
2041
+ previousValue
2042
+ });
2043
+ return;
2044
+ }
2045
+ const hit = this.pickJoint(event);
2046
+ if (this.hovered && this.hovered.object !== hit?.object) this.clearSelection();
2047
+ if (hit && this.hovered?.object !== hit.object) {
2048
+ this.applyHighlight(hit.object);
2049
+ this.hovered = {
2050
+ joint: hit.joint,
2051
+ object: hit.object
2052
+ };
2053
+ this.onHoverChange?.({
2054
+ joint: hit.joint,
2055
+ object: hit.object
2056
+ });
2057
+ }
2058
+ };
2059
+ onPointerDown = (event) => {
2060
+ if (!this._enabled || this.disposed) return;
2061
+ const hit = this.pickJoint(event);
2062
+ if (!hit) return;
2063
+ event.preventDefault();
2064
+ this.domElement.setPointerCapture?.(event.pointerId);
2065
+ const controlsWasEnabled = this.controls?.enabled ?? null;
2066
+ if (this.controls) this.controls.enabled = false;
2067
+ const jointKey = jointValueKey(hit.model, hit.joint);
2068
+ hit.object.updateMatrix();
2069
+ hit.object.updateMatrixWorld(true);
2070
+ hit.body0?.updateMatrixWorld(true);
2071
+ const currentValue = this.jointValues.get(jointKey) ?? initialValueForJoint(hit.joint);
2072
+ const rect = this.domElement.getBoundingClientRect();
2073
+ const prismaticMapping = hit.joint.jointType === "prismatic" ? prismaticDragMapping(hit.joint, hit.object, hit.body0, this.camera, rect) : null;
2074
+ const revoluteMapping = hit.joint.jointType === "revolute" ? revoluteDragMapping(hit.joint, hit.object, hit.body0, this.camera, rect) : null;
2075
+ this.activeDrag = {
2076
+ ...hit,
2077
+ startX: event.clientX,
2078
+ startY: event.clientY,
2079
+ startValue: currentValue,
2080
+ initialLocalMatrix: hit.object.matrix.clone(),
2081
+ jointKey,
2082
+ controlsWasEnabled,
2083
+ revoluteScreenAxis: revoluteMapping?.screenAxis,
2084
+ revoluteValuePerPixel: revoluteMapping?.valuePerPixel,
2085
+ prismaticScreenAxis: prismaticMapping?.screenAxis,
2086
+ prismaticValuePerPixel: prismaticMapping?.valuePerPixel
2087
+ };
2088
+ };
2089
+ onPointerUp = (event) => {
2090
+ this.endDrag(event);
2091
+ };
2092
+ pickJoint(event) {
2093
+ this.updatePointer(event);
2094
+ const models = findUSDModels(this.scene);
2095
+ if (models.length === 0) return null;
2096
+ this.syncModelState(models);
2097
+ this.raycaster.setFromCamera(this.pointer, this.camera);
2098
+ const intersections = this.raycaster.intersectObjects(models.map((model) => model.root), true);
2099
+ for (const intersection of intersections) {
2100
+ const hit = jointForObject(models, intersection.object);
2101
+ if (hit) return hit;
2102
+ }
2103
+ return null;
2104
+ }
2105
+ updatePointer(event) {
2106
+ const rect = this.domElement.getBoundingClientRect();
2107
+ this.pointer.x = (event.clientX - rect.left) / rect.width * 2 - 1;
2108
+ this.pointer.y = -((event.clientY - rect.top) / rect.height) * 2 + 1;
2109
+ }
2110
+ syncModelState(models) {
2111
+ const nextSignature = modelStateSignature(models);
2112
+ if (this.modelStateSignature === nextSignature) return;
2113
+ this.endDrag(null);
2114
+ this.clearSelection();
2115
+ this.jointValues.clear();
2116
+ this.modelStateSignature = nextSignature;
2117
+ }
2118
+ endDrag(event) {
2119
+ const activeDrag = this.activeDrag;
2120
+ if (!activeDrag) return;
2121
+ this.activeDrag = null;
2122
+ if (event && this.domElement.hasPointerCapture?.(event.pointerId)) this.domElement.releasePointerCapture(event.pointerId);
2123
+ if (this.controls && activeDrag.controlsWasEnabled !== null) this.controls.enabled = activeDrag.controlsWasEnabled;
2124
+ }
2125
+ applyHighlight(object) {
2126
+ object.traverse((child) => {
2127
+ if (!(child instanceof THREE.Mesh) || this.highlighted.has(child)) return;
2128
+ const highlighted = highlightedMaterials(child.material, this.highlightColor);
2129
+ this.highlighted.set(child, {
2130
+ original: child.material,
2131
+ highlighted
2132
+ });
2133
+ child.material = highlighted;
2134
+ });
2135
+ }
2136
+ restoreHighlight(object) {
2137
+ object.traverse((child) => {
2138
+ if (!(child instanceof THREE.Mesh)) return;
2139
+ const state = this.highlighted.get(child);
2140
+ if (!state) return;
2141
+ child.material = state.original;
2142
+ disposeMaterial(state.highlighted);
2143
+ this.highlighted.delete(child);
2144
+ });
2145
+ }
2146
+ };
2147
+ //#endregion
2148
+ export { USDLoader, USDManipulationControls, buildUSDObject, createUSDLoadedModel, extractUSDModelData };