@openusd-wasm/three-loader 0.0.3 → 0.0.5

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.
@@ -1,38 +1,8 @@
1
1
  import { createPxr } from "@openusd-wasm/pxr";
2
2
  import * as THREE from "three";
3
3
  import { FileLoader, Loader } from "three";
4
- import { autoResolveAssetFiles, autoResolveTextureFiles, createPackageTextureResolver, createTextureResolverFromEntries, findPackageRootLayer } from "@openusd-wasm/utils";
5
- //#region src/metadata.ts
6
- const MATERIAL_INPUT_SUFFIXES = [
7
- "diffuseColor",
8
- "base_color",
9
- "baseColor",
10
- "emissiveColor",
11
- "normal",
12
- "occlusion",
13
- "opacity",
14
- "alpha",
15
- "roughness",
16
- "metallic",
17
- "metallicFactor",
18
- "file",
19
- "wrapS",
20
- "wrapT",
21
- "st",
22
- "in",
23
- "varname",
24
- "scale",
25
- "translation",
26
- "rotation"
27
- ];
28
- const JOINT_TYPES = {
29
- PhysicsFixedJoint: "fixed",
30
- PhysicsRevoluteJoint: "revolute",
31
- PhysicsPrismaticJoint: "prismatic",
32
- PhysicsSphericalJoint: "spherical",
33
- PhysicsDistanceJoint: "distance",
34
- PhysicsJoint: "joint"
35
- };
4
+ import { autoResolveAssetFiles, autoResolveTextureFiles, createTextureResolverFromEntries, extractPackageEntries, findPackageRootLayer } from "@openusd-wasm/utils";
5
+ //#region src/scene-data/common.ts
36
6
  function dispose$1(value) {
37
7
  if (value && typeof value === "object" && "delete" in value && typeof value.delete === "function") value.delete();
38
8
  }
@@ -71,42 +41,6 @@ function toArray(value) {
71
41
  function toNumberArray(value) {
72
42
  return toArray(value).map((item) => Number(item)).filter((item) => Number.isFinite(item));
73
43
  }
74
- function toVector3(value, fallback) {
75
- const numbers = toNumberArray(value);
76
- if (numbers.length < 3) return fallback;
77
- return [
78
- numbers[0] ?? fallback[0],
79
- numbers[1] ?? fallback[1],
80
- numbers[2] ?? fallback[2]
81
- ];
82
- }
83
- function toVector4(value) {
84
- if (Array.isArray(value)) {
85
- const numbers = toNumberArray(value);
86
- if (numbers.length >= 4) return [
87
- numbers[0] ?? 1,
88
- numbers[1] ?? 0,
89
- numbers[2] ?? 0,
90
- numbers[3] ?? 0
91
- ];
92
- }
93
- if (value && typeof value === "object") {
94
- const { real, imaginary } = value;
95
- const imaginaryNumbers = toNumberArray(imaginary);
96
- const r = asFiniteNumber(real);
97
- if (r !== null && imaginaryNumbers.length >= 3) return [
98
- r,
99
- imaginaryNumbers[0] ?? 0,
100
- imaginaryNumbers[1] ?? 0,
101
- imaginaryNumbers[2] ?? 0
102
- ];
103
- }
104
- return null;
105
- }
106
- function matricesNearlyEqual(a, b) {
107
- if (!a || !b || a.length < 16 || b.length < 16) return false;
108
- return a.every((value, index) => Math.abs(value - (b[index] ?? 0)) < 1e-8);
109
- }
110
44
  function attr(prim, name, time, defaultValue = null) {
111
45
  const attribute = prim.GetAttribute(name);
112
46
  try {
@@ -121,26 +55,6 @@ function attributeValue(attribute, time, defaultValue = null, authoredOnly = fal
121
55
  const value = attribute.Get(time);
122
56
  return value === null || value === void 0 ? defaultValue : normalizeValue(value);
123
57
  }
124
- function attrMetadata(prim, name, key, defaultValue = null) {
125
- const attribute = prim.GetAttribute(name);
126
- try {
127
- if (!isValid$1(attribute)) return defaultValue;
128
- const value = attribute.GetMetadata(key);
129
- return value === null || value === void 0 ? defaultValue : normalizeValue(value);
130
- } finally {
131
- dispose$1(attribute);
132
- }
133
- }
134
- function relationshipTargetPaths(relationship) {
135
- if (!isValid$1(relationship)) return [];
136
- const targets = relationship.GetTargets();
137
- if (!Array.isArray(targets)) return [];
138
- try {
139
- return targets.map(pathToString);
140
- } finally {
141
- disposeAll(targets);
142
- }
143
- }
144
58
  function schemaAttributeValue(schema, methodName, time, defaultValue = null, authoredOnly = false) {
145
59
  const attribute = schema[methodName]();
146
60
  try {
@@ -149,89 +63,71 @@ function schemaAttributeValue(schema, methodName, time, defaultValue = null, aut
149
63
  dispose$1(attribute);
150
64
  }
151
65
  }
152
- function schemaRelationshipTargets(schema, methodName) {
153
- const relationship = schema[methodName]();
154
- try {
155
- return relationshipTargetPaths(relationship);
156
- } finally {
157
- dispose$1(relationship);
158
- }
159
- }
160
- function assetPathValue(assetPath) {
161
- const path = typeof assetPath.GetAssetPath === "function" ? String(assetPath.GetAssetPath()) : "";
162
- if (!path) return null;
163
- const resolvedPath = typeof assetPath.GetResolvedPath === "function" ? String(assetPath.GetResolvedPath()) : "";
164
- return resolvedPath ? {
165
- path,
166
- resolvedPath
167
- } : { path };
168
- }
169
- function propertyPathPrimPath(path) {
170
- if (path && typeof path === "object") {
171
- const maybePath = path;
172
- if (typeof maybePath.GetPrimPath === "function") {
173
- const primPath = maybePath.GetPrimPath();
174
- try {
175
- return pathToString(primPath) || null;
176
- } finally {
177
- dispose$1(primPath);
178
- }
179
- }
180
- }
181
- return pathToString(path).split(/\.(?:outputs|inputs):/)[0] || null;
182
- }
183
- function inputSourcePrimPath(input) {
184
- const connections = input.GetRawConnectedSourcePaths();
185
- if (!Array.isArray(connections)) return null;
186
- try {
187
- if (connections.length === 0) return null;
188
- return propertyPathPrimPath(connections[0]);
189
- } finally {
190
- disposeAll(connections);
191
- }
192
- }
193
- function primChildren(prim) {
194
- const children = prim.GetChildren();
195
- return Array.isArray(children) ? children : [];
196
- }
197
- function primAtPath(pxr, stage, path) {
198
- const sdfPath = new pxr.Sdf.Path(path);
199
- try {
200
- return stage.GetPrimAtPath(sdfPath);
201
- } finally {
202
- dispose$1(sdfPath);
203
- }
66
+ //#endregion
67
+ //#region src/scene-data/animations.ts
68
+ function skelAnimationFrameRange(animation, stageInfo) {
69
+ const sampleTimes = [
70
+ ...animation.translations.map((sample) => sample.time),
71
+ ...animation.rotations.map((sample) => sample.time),
72
+ ...animation.scales.map((sample) => sample.time)
73
+ ];
74
+ if (sampleTimes.length === 0) return null;
75
+ if (stageInfo.endTimeCode > stageInfo.startTimeCode) return {
76
+ start: stageInfo.startTimeCode,
77
+ end: stageInfo.endTimeCode
78
+ };
79
+ const secondsStart = Math.min(...sampleTimes);
80
+ const secondsEnd = Math.max(...sampleTimes);
81
+ return {
82
+ start: stageInfo.startTimeCode + secondsStart * stageInfo.timeCodesPerSecond,
83
+ end: stageInfo.startTimeCode + secondsEnd * stageInfo.timeCodesPerSecond
84
+ };
204
85
  }
205
- function materialBinding(pxr, prim) {
206
- const binding = new pxr.UsdShade.MaterialBindingAPI(prim);
207
- try {
208
- if (!isValid$1(binding)) return null;
209
- const computed = binding.ComputeBoundMaterial("", false);
210
- if (!Array.isArray(computed)) return null;
211
- const [material, bindingRel] = computed;
212
- try {
213
- if (!isValid$1(bindingRel)) return null;
214
- const resolvedPath = pxr.UsdShade.MaterialBindingAPI.GetResolvedTargetPathFromBindingRel(bindingRel);
215
- try {
216
- const path = pathToString(resolvedPath);
217
- if (path) return path;
218
- } finally {
219
- dispose$1(resolvedPath);
220
- }
221
- if (!material || !isValid$1(material)) return null;
222
- const materialPrim = material.GetPrim();
223
- try {
224
- return isValid$1(materialPrim) ? String(materialPrim.GetPath()) : null;
225
- } finally {
226
- dispose$1(materialPrim);
227
- }
228
- } finally {
229
- disposeAll(computed);
86
+ function getModelAnimations(pxr, prims, stageInfo, skels) {
87
+ const timeCodesPerSecond = stageInfo.timeCodesPerSecond;
88
+ const skelAnimationTracks = [];
89
+ const xformInfo = pxr.UsdGeom.ComputeXformAnimationInfo(prims, stageInfo.startTimeCode, stageInfo.endTimeCode, timeCodesPerSecond);
90
+ let start = xformInfo.startTimeCode;
91
+ let end = xformInfo.endTimeCode;
92
+ const animationsByPath = new Map(skels.animations.map((animation) => [animation.path, animation]));
93
+ for (const skeleton of skels.skeletons) {
94
+ const bindingAnimationSource = skels.bindings.find((binding) => binding.skeletonPath === skeleton.path)?.animationSource;
95
+ const animation = bindingAnimationSource ? animationsByPath.get(bindingAnimationSource) : skels.animations[0];
96
+ if (!animation) continue;
97
+ const range = skelAnimationFrameRange(animation, stageInfo);
98
+ if (!range || range.end <= range.start) continue;
99
+ skelAnimationTracks.push({
100
+ skeletonPath: skeleton.path,
101
+ animationPath: animation.path,
102
+ samplesStartTimeCode: range.start,
103
+ samplesEndTimeCode: range.end
104
+ });
105
+ if (end <= start) {
106
+ start = range.start;
107
+ end = range.end;
108
+ } else {
109
+ start = Math.min(start, range.start);
110
+ end = Math.max(end, range.end);
230
111
  }
231
- } finally {
232
- dispose$1(binding);
233
112
  }
113
+ if (end <= start) return [];
114
+ const transforms = xformInfo.transforms ?? [];
115
+ if (transforms.length === 0 && skelAnimationTracks.length === 0) return [];
116
+ const firstSkelAnimation = skelAnimationTracks[0] ? animationsByPath.get(skelAnimationTracks[0].animationPath) : null;
117
+ return [{
118
+ id: firstSkelAnimation ? `skel:${firstSkelAnimation.path}` : "stage:default",
119
+ name: firstSkelAnimation?.name ?? "Stage Animation",
120
+ source: firstSkelAnimation ? "skel" : "stage",
121
+ variant: null,
122
+ startTimeCode: start,
123
+ endTimeCode: end,
124
+ timeCodesPerSecond,
125
+ transforms,
126
+ skelAnimations: skelAnimationTracks
127
+ }];
234
128
  }
129
+ //#endregion
130
+ //#region src/scene-data/view.ts
235
131
  function localTransformForPrim(pxr, prim, time) {
236
132
  if (!prim.IsA("Xformable")) return {
237
133
  localMatrix: null,
@@ -274,491 +170,156 @@ function getModelView(pxr, prims, time) {
274
170
  };
275
171
  }) };
276
172
  }
277
- function valueAt(values, index) {
278
- return index >= 0 && index < values.length ? values[index] : null;
279
- }
280
- function samplePrimvar(values, indices, interpolation, pointIndex, faceIndex, faceVertexIndex, pointCount, faceCount, faceVertexCount) {
281
- if (values.length === 0) return null;
282
- let domainIndex;
283
- if (values.length === faceVertexCount) domainIndex = faceVertexIndex;
284
- else if (values.length === pointCount) domainIndex = pointIndex;
285
- else if (values.length === faceCount) domainIndex = faceIndex;
286
- else if (values.length === 1) domainIndex = 0;
287
- else if (interpolation === "faceVarying") domainIndex = faceVertexIndex;
288
- else if (interpolation === "vertex" || interpolation === "varying") domainIndex = pointIndex;
289
- else if (interpolation === "uniform") domainIndex = faceIndex;
290
- else domainIndex = pointIndex;
291
- let valueIndex = domainIndex;
292
- if (indices.length > 0) {
293
- const indexed = indices[domainIndex];
294
- if (indexed === void 0) return null;
295
- valueIndex = indexed;
173
+ //#endregion
174
+ //#region src/scene-data/cameras.ts
175
+ function toClippingRange(value) {
176
+ const numbers = toNumberArray(value);
177
+ if (numbers.length < 2) return null;
178
+ const near = numbers[0];
179
+ const far = numbers[1];
180
+ return Number.isFinite(near) && Number.isFinite(far) ? [near, far] : null;
181
+ }
182
+ function getModelCameras(pxr, prims, time) {
183
+ const cameras = [];
184
+ for (const prim of prims) {
185
+ if (prim.GetTypeName() !== "Camera") continue;
186
+ const camera = new pxr.UsdGeom.Camera(prim);
187
+ try {
188
+ if (!isValid$1(camera)) continue;
189
+ const projection = String(schemaAttributeValue(camera, "GetProjectionAttr", time, "perspective") || "perspective");
190
+ cameras.push({
191
+ path: String(prim.GetPath()),
192
+ name: String(prim.GetName()),
193
+ projection: projection === "orthographic" ? "orthographic" : "perspective",
194
+ horizontalAperture: asFiniteNumber(schemaAttributeValue(camera, "GetHorizontalApertureAttr", time, null)),
195
+ verticalAperture: asFiniteNumber(schemaAttributeValue(camera, "GetVerticalApertureAttr", time, null)),
196
+ focalLength: asFiniteNumber(schemaAttributeValue(camera, "GetFocalLengthAttr", time, null)),
197
+ clippingRange: toClippingRange(schemaAttributeValue(camera, "GetClippingRangeAttr", time, null)),
198
+ localMatrix: localTransformForPrim(pxr, prim, time).localMatrix
199
+ });
200
+ } finally {
201
+ dispose$1(camera);
202
+ }
296
203
  }
297
- return valueAt(values, valueIndex);
204
+ return cameras;
298
205
  }
299
- function meshPrimvar(prim, name, time) {
206
+ //#endregion
207
+ //#region src/scene-data/material-binding.ts
208
+ function materialBinding(pxr, prim) {
209
+ const nativeBinding = pxr.UsdShade.ComputeBoundMaterialPath(prim);
210
+ if (!nativeBinding) return null;
211
+ const resolvedTargetPath = typeof nativeBinding.resolvedTargetPath === "string" ? nativeBinding.resolvedTargetPath : "";
212
+ const materialPath = typeof nativeBinding.materialPath === "string" ? nativeBinding.materialPath : "";
213
+ return resolvedTargetPath || materialPath || null;
214
+ }
215
+ //#endregion
216
+ //#region src/scene-data/materials.ts
217
+ function asMaterialTextureInfo(value) {
218
+ const info = value;
300
219
  return {
301
- values: toArray(attr(prim, name, time, [])),
302
- indices: toNumberArray(attr(prim, `${name}:indices`, time, [])),
303
- interpolation: String(attrMetadata(prim, name, "interpolation", attr(prim, `${name}:interpolation`, time, "")) || "") || null
220
+ uvPrimvar: typeof info?.uvPrimvar === "string" && info.uvPrimvar ? info.uvPrimvar : "primvars:st",
221
+ uvTransform: info?.uvTransform && typeof info.uvTransform === "object" ? info.uvTransform : null
304
222
  };
305
223
  }
306
- function asVec2(value, fallback) {
307
- const numbers = toNumberArray(value);
308
- if (numbers.length < 2) return fallback;
309
- return [numbers[0] ?? fallback[0], numbers[1] ?? fallback[1]];
310
- }
311
- function asFloat(value, fallback = 0) {
312
- const number = asFiniteNumber(value);
313
- return number === null ? fallback : number;
314
- }
315
- function shaderId(shader) {
316
- const id = shader.GetShaderId();
317
- return typeof id === "string" && id ? id : null;
318
- }
319
- function shaderInputValue(shader, inputName, time, defaultValue = null) {
320
- const input = shader.GetInput(inputName);
224
+ function textureInfoForMaterial(pxr, stage, materialPath, time) {
225
+ if (!materialPath) return {
226
+ uvPrimvar: "primvars:st",
227
+ uvTransform: null
228
+ };
229
+ const path = new pxr.Sdf.Path(materialPath);
230
+ const materialPrim = stage.GetPrimAtPath(path);
321
231
  try {
322
- if (!isValid$1(input)) return defaultValue;
323
- const value = input.Get(time);
324
- return value === null || value === void 0 ? defaultValue : normalizeValue(value);
232
+ return asMaterialTextureInfo(pxr.UsdShade.ComputeMaterialTextureInfo(materialPrim, time));
325
233
  } finally {
326
- dispose$1(input);
234
+ dispose$1(materialPrim);
235
+ dispose$1(path);
327
236
  }
328
237
  }
329
- function shaderInputConnectionPath(shader, inputName) {
330
- const input = shader.GetInput(inputName);
331
- try {
332
- if (!isValid$1(input)) return null;
333
- return inputSourcePrimPath(input);
334
- } finally {
335
- dispose$1(input);
336
- }
238
+ function cachedTextureInfoForMaterial(pxr, stage, materialPath, time, cache) {
239
+ const key = materialPath ?? "";
240
+ const cached = cache.get(key);
241
+ if (cached) return cached;
242
+ const value = textureInfoForMaterial(pxr, stage, materialPath, time);
243
+ cache.set(key, value);
244
+ return value;
337
245
  }
338
- function shaderChildByPath(material, path) {
339
- const children = primChildren(material);
340
- try {
341
- for (const child of children) if (String(child.GetPath()) === path) return child;
342
- return null;
343
- } finally {
344
- for (const child of children) if (String(child.GetPath()) !== path) dispose$1(child);
246
+ function getModelMaterials(pxr, prims, time) {
247
+ const materials = [];
248
+ for (const prim of prims) {
249
+ if (prim.GetTypeName() !== "Material") continue;
250
+ const material = pxr.UsdShade.ComputeMaterialInfo(prim, time);
251
+ materials.push({
252
+ path: material.path,
253
+ inputs: material.inputs,
254
+ shaders: material.shaders
255
+ });
345
256
  }
257
+ return materials;
346
258
  }
347
- function primvarNameForTextureShader(pxr, stage, material, textureShader, time) {
348
- const stSourcePath = shaderInputConnectionPath(textureShader, "st");
349
- if (!stSourcePath) return null;
350
- const stSource = shaderChildByPath(material, stSourcePath) ?? primAtPath(pxr, stage, stSourcePath);
259
+ //#endregion
260
+ //#region src/scene-data/mesh.ts
261
+ function triangulateMesh(pxr, stage, prim, time, boundMaterial, materialTextureCache) {
262
+ const textureInfo = cachedTextureInfoForMaterial(pxr, stage, boundMaterial, time, materialTextureCache);
263
+ const mesh = new pxr.UsdGeom.Mesh(prim);
351
264
  try {
352
- if (!isValid$1(stSource)) return null;
353
- const stShader = new pxr.UsdShade.Shader(stSource);
354
- try {
355
- if (!isValid$1(stShader)) return null;
356
- const id = shaderId(stShader);
357
- if (id === "UsdPrimvarReader_float2") {
358
- const varname = shaderInputValue(stShader, "varname", time);
359
- return typeof varname === "string" && varname ? varname : null;
360
- }
361
- if (id === "UsdTransform2d") {
362
- const nestedSourcePath = shaderInputConnectionPath(stShader, "in") ?? shaderInputConnectionPath(stShader, "st");
363
- if (!nestedSourcePath) return null;
364
- const nestedSource = shaderChildByPath(material, nestedSourcePath) ?? primAtPath(pxr, stage, nestedSourcePath);
365
- try {
366
- if (!isValid$1(nestedSource)) return null;
367
- const nestedShader = new pxr.UsdShade.Shader(nestedSource);
368
- try {
369
- if (!isValid$1(nestedShader) || shaderId(nestedShader) !== "UsdPrimvarReader_float2") return null;
370
- const varname = shaderInputValue(nestedShader, "varname", time);
371
- return typeof varname === "string" && varname ? varname : null;
372
- } finally {
373
- dispose$1(nestedShader);
374
- }
375
- } finally {
376
- dispose$1(nestedSource);
377
- }
378
- }
379
- return null;
380
- } finally {
381
- dispose$1(stShader);
382
- }
265
+ return mesh.ComputeTriangulatedGeometry(time, textureInfo.uvPrimvar, textureInfo.uvTransform);
383
266
  } finally {
384
- dispose$1(stSource);
267
+ dispose$1(mesh);
385
268
  }
386
269
  }
387
- function texturePrimvarForMaterial(pxr, stage, materialPath, time) {
388
- if (!materialPath) return "primvars:st";
389
- const material = primAtPath(pxr, stage, materialPath);
270
+ function triangulateSphere(pxr, prim, time) {
271
+ const sphere = new pxr.UsdGeom.Sphere(prim);
390
272
  try {
391
- if (!isValid$1(material)) return "primvars:st";
392
- const children = primChildren(material);
393
- try {
394
- for (const child of children) {
395
- if (child.GetTypeName() !== "Shader") continue;
396
- const shader = new pxr.UsdShade.Shader(child);
397
- try {
398
- if (!isValid$1(shader) || shaderId(shader) !== "UsdUVTexture") continue;
399
- const varname = primvarNameForTextureShader(pxr, stage, material, shader, time);
400
- if (varname) return `primvars:${varname}`;
401
- } finally {
402
- dispose$1(shader);
403
- }
404
- }
405
- } finally {
406
- disposeAll(children);
407
- }
408
- return "primvars:st";
273
+ return sphere.ComputeTriangulatedGeometry(time);
409
274
  } finally {
410
- dispose$1(material);
275
+ dispose$1(sphere);
411
276
  }
412
277
  }
413
- function textureTransformForMaterial(pxr, stage, materialPath, time) {
414
- if (!materialPath) return null;
415
- const material = primAtPath(pxr, stage, materialPath);
278
+ function triangulateCylinder(pxr, prim, time) {
279
+ const cylinder = new pxr.UsdGeom.Cylinder(prim);
416
280
  try {
417
- if (!isValid$1(material)) return null;
418
- const children = primChildren(material);
419
- try {
420
- for (const child of children) {
421
- if (child.GetTypeName() !== "Shader") continue;
422
- const shader = new pxr.UsdShade.Shader(child);
423
- try {
424
- if (!isValid$1(shader) || shaderId(shader) !== "UsdUVTexture") continue;
425
- const sourcePath = shaderInputConnectionPath(shader, "st");
426
- if (!sourcePath) return null;
427
- const transform = primAtPath(pxr, stage, sourcePath);
428
- try {
429
- if (!isValid$1(transform)) return null;
430
- const transformShader = new pxr.UsdShade.Shader(transform);
431
- try {
432
- if (!isValid$1(transformShader) || shaderId(transformShader) !== "UsdTransform2d") return null;
433
- const scale = asVec2(shaderInputValue(transformShader, "scale", time), [1, 1]);
434
- const translation = asVec2(shaderInputValue(transformShader, "translation", time), [0, 0]);
435
- return {
436
- scaleX: scale[0],
437
- scaleY: scale[1],
438
- translateX: translation[0],
439
- translateY: translation[1],
440
- rotation: asFloat(shaderInputValue(transformShader, "rotation", time))
441
- };
442
- } finally {
443
- dispose$1(transformShader);
444
- }
445
- } finally {
446
- dispose$1(transform);
447
- }
448
- } finally {
449
- dispose$1(shader);
450
- }
451
- }
452
- } finally {
453
- disposeAll(children);
454
- }
455
- return null;
281
+ return cylinder.ComputeTriangulatedGeometry(time);
456
282
  } finally {
457
- dispose$1(material);
283
+ dispose$1(cylinder);
458
284
  }
459
285
  }
460
- function transformUv(uv, transform) {
461
- const numbers = toNumberArray(uv);
462
- if (numbers.length < 2) return null;
463
- let u = numbers[0] ?? 0;
464
- let v = numbers[1] ?? 0;
465
- if (!transform) return [u, v];
466
- u *= transform.scaleX;
467
- v *= transform.scaleY;
468
- const rotation = transform.rotation * Math.PI / 180;
469
- if (rotation !== 0) {
470
- const cos = Math.cos(rotation);
471
- const sin = Math.sin(rotation);
472
- const nextU = u * cos - v * sin;
473
- const nextV = u * sin + v * cos;
474
- u = nextU;
475
- v = nextV;
476
- }
477
- return [u + transform.translateX, v + transform.translateY];
478
- }
479
- function triangulateMesh(pxr, stage, prim, time) {
480
- const points = toArray(attr(prim, "points", time, []));
481
- const faceCounts = toNumberArray(attr(prim, "faceVertexCounts", time, []));
482
- const faceIndices = toNumberArray(attr(prim, "faceVertexIndices", time, []));
483
- const normals = toArray(attr(prim, "normals", time, []));
484
- const normalInterpolation = String(attrMetadata(prim, "normals", "interpolation", attr(prim, "normals:interpolation", time, "")) || "") || null;
485
- const boundMaterial = materialBinding(pxr, prim);
486
- const uv = meshPrimvar(prim, texturePrimvarForMaterial(pxr, stage, boundMaterial, time), time);
487
- const colors = toArray(attr(prim, "primvars:displayColor", time, []));
488
- const opacities = toArray(attr(prim, "primvars:displayOpacity", time, []));
489
- const uvTransform = textureTransformForMaterial(pxr, stage, boundMaterial, time);
490
- const triangleCorners = [];
491
- let cursor = 0;
492
- for (let faceIndex = 0; faceIndex < faceCounts.length; ++faceIndex) {
493
- const count = faceCounts[faceIndex] ?? 0;
494
- const face = faceIndices.slice(cursor, cursor + count);
495
- for (let index = 1; index < count - 1; ++index) triangleCorners.push([
496
- face[0] ?? -1,
497
- faceIndex,
498
- cursor
499
- ], [
500
- face[index] ?? -1,
501
- faceIndex,
502
- cursor + index
503
- ], [
504
- face[index + 1] ?? -1,
505
- faceIndex,
506
- cursor + index + 1
507
- ]);
508
- cursor += count;
509
- }
510
- const positions = [];
511
- const flatNormals = [];
512
- const uvs = [];
513
- for (const [pointIndex, faceIndex, faceVertexIndex] of triangleCorners) {
514
- const point = toNumberArray(points[pointIndex]);
515
- if (point.length >= 3) positions.push(point[0] ?? 0, point[1] ?? 0, point[2] ?? 0);
516
- if (normals.length > 0) {
517
- const normal = toNumberArray(samplePrimvar(normals, [], normalInterpolation, pointIndex, faceIndex, faceVertexIndex, points.length, faceCounts.length, faceIndices.length));
518
- if (normal.length >= 3) flatNormals.push(normal[0] ?? 0, normal[1] ?? 0, normal[2] ?? 0);
519
- }
520
- if (uv.values.length > 0) {
521
- const transformedUv = transformUv(samplePrimvar(uv.values, uv.indices, uv.interpolation, pointIndex, faceIndex, faceVertexIndex, points.length, faceCounts.length, faceIndices.length), uvTransform);
522
- if (transformedUv) uvs.push(transformedUv[0], transformedUv[1]);
523
- }
524
- }
525
- return {
526
- positions,
527
- normals: flatNormals,
528
- uvs,
529
- displayColor: colors.length > 0 ? toVector3(colors[0], [
530
- 1,
531
- 1,
532
- 1
533
- ]) : null,
534
- displayOpacity: opacities.length > 0 ? asFiniteNumber(opacities[0]) : null
535
- };
286
+ function geomSubsetMaterialFallback(pxr, prim) {
287
+ const material = pxr.UsdShade.ComputeGeomSubsetMaterialFallback(prim);
288
+ return typeof material === "string" && material ? material : null;
536
289
  }
537
290
  function getModelElements(pxr, stage, prims, time) {
538
291
  const elements = [];
292
+ const materialTextureCache = /* @__PURE__ */ new Map();
539
293
  for (const prim of prims) {
540
- if (prim.GetTypeName() !== "Mesh") continue;
294
+ const typeName = String(prim.GetTypeName());
295
+ if (typeName !== "Mesh" && typeName !== "Sphere" && typeName !== "Cylinder") continue;
296
+ const material = materialBinding(pxr, prim) ?? geomSubsetMaterialFallback(pxr, prim);
541
297
  elements.push({
542
298
  path: String(prim.GetPath()),
543
299
  doubleSided: Boolean(attr(prim, "doubleSided", time, false)),
544
- material: materialBinding(pxr, prim),
545
- geometry: triangulateMesh(pxr, stage, prim, time)
300
+ material,
301
+ geometry: typeName === "Sphere" ? triangulateSphere(pxr, prim, time) : typeName === "Cylinder" ? triangulateCylinder(pxr, prim, time) : triangulateMesh(pxr, stage, prim, time, material, materialTextureCache)
546
302
  });
547
303
  }
548
304
  return elements;
549
305
  }
550
- function collectShadeInputs(connectable, time) {
551
- const inputs = {};
552
- const shadeInputs = connectable.GetInputs();
553
- try {
554
- for (const input of shadeInputs) {
555
- const baseName = String(input.GetBaseName());
556
- if (!MATERIAL_INPUT_SUFFIXES.some((suffix) => baseName.endsWith(suffix))) continue;
557
- const name = String(input.GetFullName());
558
- const value = input.Get(time);
559
- const connections = input.GetRawConnectedSourcePaths();
560
- if (value !== null && value !== void 0) inputs[name] = normalizeValue(value);
561
- if (Array.isArray(connections)) try {
562
- if (connections.length > 0) inputs[`${name}.connect`] = connections.map(pathToString);
563
- } finally {
564
- disposeAll(connections);
565
- }
566
- }
567
- } finally {
568
- disposeAll(shadeInputs);
569
- }
570
- return inputs;
571
- }
572
- function collectShaderSourceAssets(shader) {
573
- const inputs = {};
574
- const sourceTypes = shader.GetSourceTypes();
575
- if (!Array.isArray(sourceTypes)) return inputs;
576
- for (const sourceType of sourceTypes) {
577
- const sourceTypeName = String(sourceType);
578
- const sourceAsset = shader.GetSourceAsset(sourceTypeName);
579
- try {
580
- const value = assetPathValue(sourceAsset);
581
- if (!value) continue;
582
- const name = sourceTypeName ? `info:${sourceTypeName}:sourceAsset` : "info:sourceAsset";
583
- inputs[name] = value;
584
- } finally {
585
- dispose$1(sourceAsset);
586
- }
587
- }
588
- return inputs;
589
- }
590
- function getModelMaterials(pxr, prims, time) {
591
- const materials = [];
592
- for (const prim of prims) {
593
- if (prim.GetTypeName() !== "Material") continue;
594
- const material = new pxr.UsdShade.Material(prim);
595
- const children = primChildren(prim);
596
- const shaders = [];
597
- try {
598
- if (!isValid$1(material)) continue;
599
- for (const child of children) {
600
- if (child.GetTypeName() !== "Shader") continue;
601
- const shader = new pxr.UsdShade.Shader(child);
602
- try {
603
- if (!isValid$1(shader)) continue;
604
- const idValue = shader.GetShaderId();
605
- const id = typeof idValue === "string" && idValue ? idValue : null;
606
- const inputs = {
607
- ...collectShadeInputs(shader, time),
608
- ...collectShaderSourceAssets(shader)
609
- };
610
- if (Object.keys(inputs).length === 0 && id !== "UsdPreviewSurface" && id !== "UsdUVTexture") continue;
611
- shaders.push({
612
- path: String(child.GetPath()),
613
- id,
614
- inputs
615
- });
616
- } finally {
617
- dispose$1(shader);
618
- }
619
- }
620
- materials.push({
621
- path: String(prim.GetPath()),
622
- inputs: collectShadeInputs(material, time),
623
- shaders
624
- });
625
- } finally {
626
- disposeAll(children);
627
- dispose$1(material);
628
- }
629
- }
630
- return materials;
631
- }
632
- function driveForNamespace(pxr, prim, namespace, time) {
633
- const driveAPI = new pxr.UsdPhysics.DriveAPI(prim, namespace);
634
- const drive = {
635
- targetPosition: null,
636
- targetVelocity: null,
637
- stiffness: null,
638
- damping: null,
639
- maxForce: null
640
- };
641
- try {
642
- if (!isValid$1(driveAPI)) return null;
643
- drive.targetPosition = asFiniteNumber(schemaAttributeValue(driveAPI, "GetTargetPositionAttr", time, null, true));
644
- drive.targetVelocity = asFiniteNumber(schemaAttributeValue(driveAPI, "GetTargetVelocityAttr", time, null, true));
645
- drive.stiffness = asFiniteNumber(schemaAttributeValue(driveAPI, "GetStiffnessAttr", time, null, true));
646
- drive.damping = asFiniteNumber(schemaAttributeValue(driveAPI, "GetDampingAttr", time, null, true));
647
- drive.maxForce = asFiniteNumber(schemaAttributeValue(driveAPI, "GetMaxForceAttr", time, null, true));
648
- return Object.values(drive).some((value) => value !== null) ? drive : null;
649
- } finally {
650
- dispose$1(driveAPI);
651
- }
652
- }
653
- function jointTypeForPrim(typeName) {
654
- if (JOINT_TYPES[typeName]) return JOINT_TYPES[typeName];
655
- if (typeName.startsWith("Physics") && typeName.endsWith("Joint")) return typeName.slice(7, -5).toLowerCase() || "joint";
656
- return null;
657
- }
658
- function jointLimitSchema(pxr, prim, typeName) {
659
- switch (typeName) {
660
- case "PhysicsRevoluteJoint": return new pxr.UsdPhysics.RevoluteJoint(prim);
661
- case "PhysicsPrismaticJoint": return new pxr.UsdPhysics.PrismaticJoint(prim);
662
- case "PhysicsSphericalJoint": return new pxr.UsdPhysics.SphericalJoint(prim);
663
- case "PhysicsDistanceJoint": return new pxr.UsdPhysics.DistanceJoint(prim);
664
- default: return null;
665
- }
306
+ //#endregion
307
+ //#region src/scene-data/joints.ts
308
+ function getModelJoints(pxr, prims, time) {
309
+ return pxr.UsdPhysics.ComputeModelJoints(prims, time);
666
310
  }
667
- function jointAxisAndLimits(pxr, prim, typeName, time) {
668
- const schema = jointLimitSchema(pxr, prim, typeName);
669
- if (!schema) return {
670
- axis: null,
671
- lowerLimit: null,
672
- upperLimit: null
673
- };
674
- try {
675
- if (!isValid$1(schema)) return {
676
- axis: null,
677
- lowerLimit: null,
678
- upperLimit: null
679
- };
680
- if (typeName === "PhysicsSphericalJoint") {
681
- const axis = schemaAttributeValue(schema, "GetAxisAttr", time);
682
- return {
683
- axis: typeof axis === "string" ? axis : null,
684
- lowerLimit: asFiniteNumber(schemaAttributeValue(schema, "GetConeAngle0LimitAttr", time, null, true)),
685
- upperLimit: asFiniteNumber(schemaAttributeValue(schema, "GetConeAngle1LimitAttr", time, null, true))
686
- };
687
- }
688
- if (typeName === "PhysicsDistanceJoint") return {
689
- axis: null,
690
- lowerLimit: asFiniteNumber(schemaAttributeValue(schema, "GetMinDistanceAttr", time, null, true)),
691
- upperLimit: asFiniteNumber(schemaAttributeValue(schema, "GetMaxDistanceAttr", time, null, true))
692
- };
693
- const axis = schemaAttributeValue(schema, "GetAxisAttr", time);
694
- return {
695
- axis: typeof axis === "string" ? axis : null,
696
- lowerLimit: asFiniteNumber(schemaAttributeValue(schema, "GetLowerLimitAttr", time)),
697
- upperLimit: asFiniteNumber(schemaAttributeValue(schema, "GetUpperLimitAttr", time))
698
- };
699
- } finally {
700
- dispose$1(schema);
701
- }
311
+ //#endregion
312
+ //#region src/scene-data/physics.ts
313
+ function getModelPhysics(pxr, prims, time, joints) {
314
+ return pxr.UsdPhysics.ComputeModelPhysics(prims, time, joints);
702
315
  }
703
- function getModelJoints(pxr, prims, time) {
704
- const joints = [];
705
- for (const prim of prims) {
706
- const typeName = String(prim.GetTypeName());
707
- const jointType = jointTypeForPrim(typeName);
708
- if (!jointType) continue;
709
- const joint = new pxr.UsdPhysics.Joint(prim);
710
- if (!isValid$1(joint)) {
711
- dispose$1(joint);
712
- continue;
713
- }
714
- const angularDrive = driveForNamespace(pxr, prim, "angular", time);
715
- const linearDrive = driveForNamespace(pxr, prim, "linear", time);
716
- const drives = {};
717
- if (angularDrive) drives.angular = angularDrive;
718
- if (linearDrive) drives.linear = linearDrive;
719
- const body0 = schemaRelationshipTargets(joint, "GetBody0Rel");
720
- const body1 = schemaRelationshipTargets(joint, "GetBody1Rel");
721
- const drive = jointType === "prismatic" ? linearDrive ?? angularDrive : angularDrive ?? linearDrive;
722
- const axisAndLimits = jointAxisAndLimits(pxr, prim, typeName, time);
723
- try {
724
- joints.push({
725
- path: String(prim.GetPath()),
726
- name: String(prim.GetName()),
727
- typeName,
728
- jointType,
729
- body0: body0[0] ?? null,
730
- body1: body1[0] ?? null,
731
- ...axisAndLimits,
732
- localPos0: toVector3(schemaAttributeValue(joint, "GetLocalPos0Attr", time, [
733
- 0,
734
- 0,
735
- 0
736
- ]), [
737
- 0,
738
- 0,
739
- 0
740
- ]),
741
- localPos1: toVector3(schemaAttributeValue(joint, "GetLocalPos1Attr", time, [
742
- 0,
743
- 0,
744
- 0
745
- ]), [
746
- 0,
747
- 0,
748
- 0
749
- ]),
750
- localRot0: toVector4(schemaAttributeValue(joint, "GetLocalRot0Attr", time)),
751
- localRot1: toVector4(schemaAttributeValue(joint, "GetLocalRot1Attr", time)),
752
- enabled: Boolean(schemaAttributeValue(joint, "GetJointEnabledAttr", time, true)),
753
- drive,
754
- drives
755
- });
756
- } finally {
757
- dispose$1(joint);
758
- }
759
- }
760
- return joints;
316
+ //#endregion
317
+ //#region src/scene-data/skel.ts
318
+ function getModelSkels(pxr, prims, time, stageInfo) {
319
+ return pxr.UsdSkel.ComputeModelSkels(prims, time, stageInfo.startTimeCode, stageInfo.endTimeCode, stageInfo.timeCodesPerSecond || 24);
761
320
  }
321
+ //#endregion
322
+ //#region src/scene-data/stage.ts
762
323
  function getStageInfo(pxr, stage, options) {
763
324
  const defaultPrim = stage.GetDefaultPrim();
764
325
  try {
@@ -775,79 +336,99 @@ function getStageInfo(pxr, stage, options) {
775
336
  dispose$1(defaultPrim);
776
337
  }
777
338
  }
778
- function sampleLocalMatrix(pxr, prim, frame) {
779
- const time = new pxr.Usd.TimeCode(frame);
780
- try {
781
- return localTransformForPrim(pxr, prim, time).localMatrix;
782
- } finally {
783
- dispose$1(time);
339
+ //#endregion
340
+ //#region src/scene-data/variants.ts
341
+ function requireVariantSets(prim) {
342
+ if (typeof prim.GetVariantSets !== "function") throw new Error("USD wasm runtime is missing Usd.Prim.GetVariantSets; rebuild @openusd-wasm/core");
343
+ return prim.GetVariantSets();
344
+ }
345
+ function applyVariantSelections(pxr, stage, selections = []) {
346
+ if (selections.length === 0) return;
347
+ for (const selection of selections) {
348
+ const path = new pxr.Sdf.Path(selection.primPath);
349
+ const prim = stage.GetPrimAtPath(path);
350
+ let variantSets = null;
351
+ let variantSet = null;
352
+ try {
353
+ if (!isValid$1(prim)) continue;
354
+ variantSets = requireVariantSets(prim);
355
+ variantSet = variantSets.GetVariantSet(selection.setName);
356
+ if (!isValid$1(variantSet)) continue;
357
+ if (selection.selection) variantSet.SetVariantSelection(selection.selection);
358
+ else variantSet.ClearVariantSelection();
359
+ } finally {
360
+ dispose$1(variantSet);
361
+ dispose$1(variantSets);
362
+ dispose$1(prim);
363
+ dispose$1(path);
364
+ }
784
365
  }
785
366
  }
786
- function animationSampleFrames(start, end) {
787
- const maxSamples = 1e3;
788
- const span = end - start;
789
- if (span <= 0) return [];
790
- const wholeFrameCount = Number.isInteger(start) && Number.isInteger(end) ? Math.floor(span) + 1 : 0;
791
- if (wholeFrameCount > 1 && wholeFrameCount <= maxSamples) return Array.from({ length: wholeFrameCount }, (_, index) => start + index);
792
- const sampleCount = Math.min(maxSamples, Math.max(2, Math.ceil(span) + 1));
793
- return Array.from({ length: sampleCount }, (_, index) => {
794
- return start + span * index / (sampleCount - 1);
795
- });
367
+ function setVariantSelection(pxr, stage, selection) {
368
+ applyVariantSelections(pxr, stage, [selection]);
796
369
  }
797
- function getModelAnimations(pxr, prims, stageInfo) {
798
- const start = stageInfo.startTimeCode;
799
- const end = stageInfo.endTimeCode;
800
- const timeCodesPerSecond = stageInfo.timeCodesPerSecond;
801
- if (end <= start) return [];
802
- const frames = animationSampleFrames(start, end);
803
- if (frames.length < 2) return [];
804
- const transforms = [];
805
- for (const prim of prims) {
806
- if (!prim.IsA("Xformable")) continue;
807
- const defaultTime = pxr.Usd.TimeCode.Default();
808
- try {
809
- if (toArray(attr(prim, "xformOpOrder", defaultTime, [])).length === 0) continue;
810
- } finally {
811
- dispose$1(defaultTime);
370
+ function getModelVariants(stage) {
371
+ const stagePrims = typeof stage.Traverse === "function" ? stage.Traverse() : [];
372
+ const variants = [];
373
+ try {
374
+ for (const prim of stagePrims) {
375
+ const variantSets = requireVariantSets(prim);
376
+ try {
377
+ const setNames = variantSets.GetNames();
378
+ if (!Array.isArray(setNames)) continue;
379
+ for (const setName of setNames) {
380
+ const variantSet = variantSets.GetVariantSet(String(setName));
381
+ try {
382
+ if (!isValid$1(variantSet)) continue;
383
+ const variantNames = variantSet.GetVariantNames();
384
+ variants.push({
385
+ primPath: pathToString(prim.GetPath()),
386
+ primName: String(prim.GetName?.() ?? ""),
387
+ setName: String(setName),
388
+ variantNames: Array.isArray(variantNames) ? variantNames.map(String) : [],
389
+ selection: String(variantSet.GetVariantSelection?.() ?? "") || null
390
+ });
391
+ } finally {
392
+ dispose$1(variantSet);
393
+ }
394
+ }
395
+ } finally {
396
+ dispose$1(variantSets);
397
+ }
812
398
  }
813
- const samples = frames.map((frame) => {
814
- const localMatrix = sampleLocalMatrix(pxr, prim, frame);
815
- return localMatrix ? {
816
- time: (frame - start) / timeCodesPerSecond,
817
- localMatrix
818
- } : null;
819
- }).filter((sample) => sample !== null);
820
- if (samples.length < 2) continue;
821
- const first = samples[0]?.localMatrix ?? null;
822
- if (!samples.some((sample) => !matricesNearlyEqual(first, sample.localMatrix))) continue;
823
- transforms.push({
824
- primPath: String(prim.GetPath()),
825
- samples
826
- });
399
+ } finally {
400
+ disposeAll(stagePrims);
827
401
  }
828
- return transforms.length > 0 ? [{
829
- startTimeCode: start,
830
- endTimeCode: end,
831
- timeCodesPerSecond,
832
- transforms
833
- }] : [];
402
+ return variants.sort((a, b) => a.setName.localeCompare(b.setName) || a.primPath.localeCompare(b.primPath));
403
+ }
404
+ //#endregion
405
+ //#region src/scene-data.ts
406
+ function traverseRenderablePrims(stage) {
407
+ if (typeof stage.TraverseInstanceProxies !== "function") throw new Error("USD wasm runtime is missing Usd.Stage.TraverseInstanceProxies; rebuild @openusd-wasm/core");
408
+ return stage.TraverseInstanceProxies();
834
409
  }
835
- function extractUSDModelData(pxr, stage, options = {}) {
410
+ function extractUSDSceneData(pxr, stage, options = {}) {
836
411
  const resolvedOptions = {
837
412
  sourcePath: options.sourcePath ?? "",
838
413
  rootLayerIdentifier: options.rootLayerIdentifier ?? ""
839
414
  };
840
415
  const time = pxr.Usd.TimeCode.Default();
841
- const prims = stage.Traverse();
416
+ const prims = traverseRenderablePrims(stage);
842
417
  try {
843
418
  const stageInfo = getStageInfo(pxr, stage, resolvedOptions);
419
+ const skels = getModelSkels(pxr, prims, time, stageInfo);
420
+ const joints = getModelJoints(pxr, prims, time);
844
421
  return {
845
422
  stage: stageInfo,
846
423
  view: getModelView(pxr, prims, time),
847
424
  elements: getModelElements(pxr, stage, prims, time),
848
425
  materials: getModelMaterials(pxr, prims, time),
849
- joints: getModelJoints(pxr, prims, time),
850
- animations: getModelAnimations(pxr, prims, stageInfo)
426
+ physics: getModelPhysics(pxr, prims, time, joints),
427
+ joints,
428
+ cameras: getModelCameras(pxr, prims, time),
429
+ skels,
430
+ animations: getModelAnimations(pxr, prims, stageInfo, skels),
431
+ variants: getModelVariants(stage)
851
432
  };
852
433
  } finally {
853
434
  disposeAll(prims);
@@ -855,8 +436,7 @@ function extractUSDModelData(pxr, stage, options = {}) {
855
436
  }
856
437
  }
857
438
  //#endregion
858
- //#region src/three.ts
859
- const sharedTextureCache = /* @__PURE__ */ new Map();
439
+ //#region src/three/matrix.ts
860
440
  function matrixFromUsd(values) {
861
441
  const matrix = new THREE.Matrix4();
862
442
  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);
@@ -866,6 +446,246 @@ function applyLocalMatrix(object, values) {
866
446
  if (!values || values.length < 16) return;
867
447
  matrixFromUsd(values).decompose(object.position, object.quaternion, object.scale);
868
448
  }
449
+ //#endregion
450
+ //#region src/three/geometry.ts
451
+ function findJointIndex(joints, name) {
452
+ const exact = joints.indexOf(name);
453
+ if (exact >= 0) return exact;
454
+ return joints.findIndex((joint) => joint.split("/").pop() === name);
455
+ }
456
+ function skinAttributesForElement(element, binding, skeleton) {
457
+ if (!binding || !skeleton) return null;
458
+ const vertexCount = Math.floor(element.geometry.positions.length / 3);
459
+ if (vertexCount <= 0) return null;
460
+ const pointIndices = element.geometry.pointIndices;
461
+ const elementSize = Math.max(1, binding.elementSize || 1);
462
+ const skinIndices = [];
463
+ const skinWeights = [];
464
+ for (let vertexIndex = 0; vertexIndex < vertexCount; ++vertexIndex) {
465
+ const base = Number(pointIndices?.[vertexIndex] ?? vertexIndex) * elementSize;
466
+ const influences = [];
467
+ for (let influenceIndex = 0; influenceIndex < elementSize; ++influenceIndex) {
468
+ const weight = Number(binding.jointWeights[base + influenceIndex] ?? 0);
469
+ if (!Number.isFinite(weight) || weight <= 0) continue;
470
+ const authoredIndex = Math.trunc(Number(binding.jointIndices[base + influenceIndex] ?? -1));
471
+ const skeletonIndex = binding.joints.length > 0 ? findJointIndex(skeleton.joints, binding.joints[authoredIndex] ?? "") : authoredIndex;
472
+ if (skeletonIndex < 0 || skeletonIndex >= skeleton.joints.length) continue;
473
+ influences.push({
474
+ index: skeletonIndex,
475
+ weight
476
+ });
477
+ }
478
+ influences.sort((a, b) => b.weight - a.weight);
479
+ const top = influences.slice(0, 4);
480
+ const total = top.reduce((sum, item) => sum + item.weight, 0);
481
+ for (let slot = 0; slot < 4; ++slot) {
482
+ const influence = top[slot];
483
+ skinIndices.push(influence?.index ?? 0);
484
+ skinWeights.push(influence && total > 0 ? influence.weight / total : 0);
485
+ }
486
+ }
487
+ return {
488
+ indices: skinIndices,
489
+ weights: skinWeights
490
+ };
491
+ }
492
+ function buildGeometry(element, binding, skeleton) {
493
+ const geometry = new THREE.BufferGeometry();
494
+ geometry.setAttribute("position", new THREE.Float32BufferAttribute(element.geometry.positions, 3));
495
+ if (element.geometry.normals.length > 0 && element.geometry.normals.length === element.geometry.positions.length) geometry.setAttribute("normal", new THREE.Float32BufferAttribute(element.geometry.normals, 3));
496
+ else geometry.computeVertexNormals();
497
+ if (element.geometry.uvs.length > 0) geometry.setAttribute("uv", new THREE.Float32BufferAttribute(element.geometry.uvs, 2));
498
+ const skin = skinAttributesForElement(element, binding, skeleton);
499
+ if (skin) {
500
+ geometry.setAttribute("skinIndex", new THREE.Uint16BufferAttribute(skin.indices, 4));
501
+ geometry.setAttribute("skinWeight", new THREE.Float32BufferAttribute(skin.weights, 4));
502
+ }
503
+ if (geometry.index && geometry.hasAttribute("position") && geometry.hasAttribute("normal") && geometry.hasAttribute("uv")) geometry.computeTangents();
504
+ geometry.computeBoundingBox();
505
+ geometry.computeBoundingSphere();
506
+ return geometry;
507
+ }
508
+ function resolveBindingSkeleton(data, binding) {
509
+ if (!binding) return void 0;
510
+ if (binding.skeletonPath) {
511
+ const exact = data.skels.skeletons.find((skeleton) => skeleton.path === binding.skeletonPath);
512
+ if (exact) return exact;
513
+ }
514
+ return data.skels.skeletons[0];
515
+ }
516
+ function buildSkeletonBones(skeleton) {
517
+ return skeleton.joints.map((joint, index) => {
518
+ const bone = new THREE.Bone();
519
+ bone.name = joint.split("/").pop() || `joint_${index}`;
520
+ bone.userData.usd = {
521
+ kind: "skelJoint",
522
+ skeletonPath: skeleton.path,
523
+ jointPath: joint,
524
+ jointIndex: index
525
+ };
526
+ bone.matrixAutoUpdate = false;
527
+ const restMatrix = skeleton.restSkelTransforms?.[index] ?? skeleton.bindTransforms[index] ?? skeleton.restTransforms[index];
528
+ if (restMatrix && restMatrix.length >= 16) bone.matrix.copy(matrixFromUsd(restMatrix));
529
+ return bone;
530
+ });
531
+ }
532
+ function bindSkinnedMeshes(data, objectsByPath, wrapper) {
533
+ const bonesBySkeletonPath = /* @__PURE__ */ new Map();
534
+ const boneHostsBySkeletonPath = /* @__PURE__ */ new Map();
535
+ const threeSkeletons = /* @__PURE__ */ new Map();
536
+ for (const skeletonInfo of data.skels.skeletons) {
537
+ const bones = buildSkeletonBones(skeletonInfo);
538
+ bonesBySkeletonPath.set(skeletonInfo.path, bones);
539
+ const inverseBindMatrices = skeletonInfo.bindTransforms.map((matrix) => matrixFromUsd(matrix).invert());
540
+ const skeleton = new THREE.Skeleton(bones, inverseBindMatrices);
541
+ threeSkeletons.set(skeletonInfo.path, skeleton);
542
+ const skeletonObject = objectsByPath.get(skeletonInfo.path);
543
+ const boneHost = new THREE.Group();
544
+ boneHost.name = `${skeletonObject?.name ?? "Skeleton"}_BoneHost`;
545
+ boneHost.matrixAutoUpdate = false;
546
+ boneHostsBySkeletonPath.set(skeletonInfo.path, boneHost);
547
+ if (wrapper) {
548
+ wrapper.updateMatrixWorld(true);
549
+ boneHost.matrix.copy(wrapper.matrixWorld).invert();
550
+ wrapper.add(boneHost);
551
+ }
552
+ for (const bone of bones) boneHost.add(bone);
553
+ }
554
+ for (const binding of data.skels.bindings) {
555
+ const mesh = objectsByPath.get(binding.primPath);
556
+ const skeletonInfo = resolveBindingSkeleton(data, binding);
557
+ if (!(mesh instanceof THREE.SkinnedMesh) || !skeletonInfo) continue;
558
+ const skeleton = threeSkeletons.get(skeletonInfo.path);
559
+ if (!skeleton) continue;
560
+ const bindMatrix = binding.geomBindTransform ? matrixFromUsd(binding.geomBindTransform) : new THREE.Matrix4();
561
+ mesh.bindMode = THREE.DetachedBindMode;
562
+ mesh.bind(skeleton, bindMatrix);
563
+ mesh.frustumCulled = false;
564
+ }
565
+ return {
566
+ bonesBySkeletonPath,
567
+ boneHostsBySkeletonPath
568
+ };
569
+ }
570
+ //#endregion
571
+ //#region src/three/animations.ts
572
+ function createTransformTracks(animation, objectsByPath) {
573
+ const tracks = [];
574
+ for (const transform of animation.transforms) {
575
+ const object = objectsByPath.get(transform.primPath);
576
+ if (!object || transform.samples.length < 2) continue;
577
+ const times = [];
578
+ const positions = [];
579
+ const quaternions = [];
580
+ const scales = [];
581
+ let previousQuaternion = null;
582
+ for (const sample of transform.samples) {
583
+ const matrix = matrixFromUsd(sample.localMatrix);
584
+ const position = new THREE.Vector3();
585
+ const quaternion = new THREE.Quaternion();
586
+ const scale = new THREE.Vector3();
587
+ matrix.decompose(position, quaternion, scale);
588
+ if (previousQuaternion && previousQuaternion.dot(quaternion) < 0) quaternion.set(-quaternion.x, -quaternion.y, -quaternion.z, -quaternion.w);
589
+ previousQuaternion = quaternion.clone();
590
+ times.push(sample.time);
591
+ positions.push(position.x, position.y, position.z);
592
+ quaternions.push(quaternion.x, quaternion.y, quaternion.z, quaternion.w);
593
+ scales.push(scale.x, scale.y, scale.z);
594
+ }
595
+ 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));
596
+ }
597
+ return tracks;
598
+ }
599
+ function pushVec3Track(tracks, bone, property, samples, channelIndex) {
600
+ if (!bone || samples.length < 2) return;
601
+ const times = [];
602
+ const values = [];
603
+ for (const sample of samples) {
604
+ const value = sample.values[channelIndex];
605
+ if (!value) continue;
606
+ times.push(sample.time);
607
+ values.push(value[0], value[1], value[2]);
608
+ }
609
+ if (times.length < 2) return;
610
+ tracks.push(new THREE.VectorKeyframeTrack(`${bone.uuid}.${property}`, times, values));
611
+ }
612
+ function pushQuatTrack(tracks, bone, samples, channelIndex) {
613
+ if (!bone || samples.length < 2) return;
614
+ const times = [];
615
+ const values = [];
616
+ let previous = null;
617
+ for (const sample of samples) {
618
+ const value = sample.values[channelIndex];
619
+ if (!value) continue;
620
+ const quaternion = new THREE.Quaternion(value[1], value[2], value[3], value[0]);
621
+ if (previous && previous.dot(quaternion) < 0) quaternion.set(-quaternion.x, -quaternion.y, -quaternion.z, -quaternion.w);
622
+ previous = quaternion.clone();
623
+ times.push(sample.time);
624
+ values.push(quaternion.x, quaternion.y, quaternion.z, quaternion.w);
625
+ }
626
+ if (times.length < 2) return;
627
+ tracks.push(new THREE.QuaternionKeyframeTrack(`${bone.uuid}.quaternion`, times, values));
628
+ }
629
+ function pushMatrixTrack(tracks, bone, samples, jointIndex) {
630
+ if (!bone || samples.length < 2) return;
631
+ const times = [];
632
+ const values = [];
633
+ for (const sample of samples) {
634
+ const matrix = sample.values[jointIndex];
635
+ if (!matrix || matrix.length < 16) continue;
636
+ times.push(sample.time);
637
+ values.push(...matrixFromUsd(matrix).toArray());
638
+ }
639
+ if (times.length < 2) return;
640
+ tracks.push(new THREE.NumberKeyframeTrack(`${bone.uuid}.matrix`, times, values));
641
+ }
642
+ function createSkelTracks(animation, data, bonesBySkeletonPath) {
643
+ const tracks = [];
644
+ const animationsByPath = new Map(data.skels.animations.map((item) => [item.path, item]));
645
+ const skeletonsByPath = new Map(data.skels.skeletons.map((item) => [item.path, item]));
646
+ for (const skelTrack of animation.skelAnimations) {
647
+ const skeletonInfo = skeletonsByPath.get(skelTrack.skeletonPath);
648
+ const skelAnimation = animationsByPath.get(skelTrack.animationPath);
649
+ const bones = bonesBySkeletonPath.get(skelTrack.skeletonPath);
650
+ if (!skeletonInfo || !skelAnimation || !bones) continue;
651
+ const skelTransformSamples = skelAnimation.jointSkelTransformSamples?.find((item) => item.skeletonPath === skelTrack.skeletonPath)?.samples;
652
+ if (skelTransformSamples && skelTransformSamples.length >= 2) {
653
+ for (let jointIndex = 0; jointIndex < skeletonInfo.joints.length; ++jointIndex) pushMatrixTrack(tracks, bones[jointIndex], skelTransformSamples, jointIndex);
654
+ continue;
655
+ }
656
+ for (let channelIndex = 0; channelIndex < skelAnimation.joints.length; ++channelIndex) {
657
+ const joint = skelAnimation.joints[channelIndex];
658
+ const skeletonIndex = joint ? findJointIndex(skeletonInfo.joints, joint) : -1;
659
+ if (skeletonIndex < 0) continue;
660
+ const bone = bones[skeletonIndex];
661
+ pushVec3Track(tracks, bone, "position", skelAnimation.translations, channelIndex);
662
+ pushQuatTrack(tracks, bone, skelAnimation.rotations, channelIndex);
663
+ pushVec3Track(tracks, bone, "scale", skelAnimation.scales, channelIndex);
664
+ }
665
+ }
666
+ return tracks;
667
+ }
668
+ function createAnimationClips(animations, objectsByPath, data, bonesBySkeletonPath) {
669
+ return animations.map((animation, index) => {
670
+ const tracks = [...createTransformTracks(animation, objectsByPath), ...createSkelTracks(animation, data, bonesBySkeletonPath)];
671
+ if (tracks.length === 0) return null;
672
+ const clip = new THREE.AnimationClip(animation.name || `USDAnimation_${index + 1}`, -1, tracks);
673
+ clip.userData.usd = {
674
+ kind: "animation",
675
+ id: animation.id,
676
+ source: animation.source,
677
+ variant: animation.variant
678
+ };
679
+ return clip;
680
+ }).filter((clip) => clip !== null);
681
+ }
682
+ //#endregion
683
+ //#region src/three/materials.ts
684
+ const sharedTextureCache = /* @__PURE__ */ new Map();
685
+ function releaseTexturesFromSharedCache(textures) {
686
+ const released = new Set(textures);
687
+ for (const [key, texture] of sharedTextureCache) if (released.has(texture)) sharedTextureCache.delete(key);
688
+ }
869
689
  function vectorColor(value) {
870
690
  if (!Array.isArray(value) || value.length < 3) return null;
871
691
  const r = Number(value[0]);
@@ -913,22 +733,33 @@ function textureWrap(value) {
913
733
  if (value === "mirror") return THREE.MirroredRepeatWrapping;
914
734
  return THREE.ClampToEdgeWrapping;
915
735
  }
916
- function shaderPathFromConnection(value) {
917
- return Array.isArray(value) && typeof value[0] === "string" ? value[0].split(".outputs:")[0] ?? null : null;
736
+ function shaderConnectionInfo(value) {
737
+ if (!Array.isArray(value) || typeof value[0] !== "string") return null;
738
+ const [path, outputName] = value[0].split(".outputs:");
739
+ if (!path) return null;
740
+ return {
741
+ path,
742
+ outputName: outputName || null
743
+ };
918
744
  }
919
745
  function findTexture(material, options, connectionSuffixes, fallbackToAnyTexture = false) {
920
746
  if (!material) return null;
921
- const connectedPath = shaderPathFromConnection(firstInput(material, connectionSuffixes));
922
- const textureShader = material.shaders.find((shader) => shader.id === "UsdUVTexture" && (!connectedPath || shader.path === connectedPath)) ?? (fallbackToAnyTexture ? material.shaders.find((shader) => shader.id === "UsdUVTexture") : null);
747
+ const connection = shaderConnectionInfo(firstInput(material, connectionSuffixes.map((suffix) => suffix.replace(/\.connect$/, ".resolvedConnect"))) ?? firstInput(material, connectionSuffixes));
748
+ const connectedPath = connection?.path ?? null;
749
+ const textureShader = connectedPath ? material.shaders.find((shader) => shader.id === "UsdUVTexture" && shader.path === connectedPath) : fallbackToAnyTexture ? material.shaders.find((shader) => shader.id === "UsdUVTexture") : null;
923
750
  if (!textureShader) return null;
924
751
  const url = publicUrlFromAsset(textureShader.inputs["inputs:file"], options, material, textureShader);
925
752
  if (!url) return null;
926
753
  return {
927
754
  url,
755
+ outputName: connection?.outputName ?? null,
928
756
  wrapS: textureWrap(textureShader.inputs["inputs:wrapS"]),
929
757
  wrapT: textureWrap(textureShader.inputs["inputs:wrapT"])
930
758
  };
931
759
  }
760
+ function isHairLikeSurface(material, element) {
761
+ return /hair/i.test(element.path) || /hair/i.test(material?.path ?? "");
762
+ }
932
763
  function loadTexture(url, options, colorSpace) {
933
764
  const cache = options.textureCache ?? sharedTextureCache;
934
765
  const cacheKey = `${colorSpace}:${url}`;
@@ -945,118 +776,118 @@ function applyTextureWrap(texture, info) {
945
776
  texture.wrapT = info.wrapT;
946
777
  }
947
778
  function buildMaterial(material, element, options) {
779
+ const diffuseTexture = options.loadTextures === false ? null : findTexture(material, options, ["diffuseColor.connect", "baseColor.connect"], true);
948
780
  const displayColor = element.geometry.displayColor ? new THREE.Color(...element.geometry.displayColor) : null;
949
781
  const baseColor = vectorColor(firstInput(material, [
950
782
  "diffuseColor",
951
783
  "base_color",
952
784
  "baseColor"
953
- ])) ?? displayColor ?? new THREE.Color(13158600);
785
+ ])) ?? (diffuseTexture ? new THREE.Color(1, 1, 1) : null) ?? displayColor ?? new THREE.Color(13158600);
954
786
  const opacityInput = firstInput(material, ["opacity", "alpha"]);
955
787
  const opacity = typeof opacityInput === "number" ? opacityInput : element.geometry.displayOpacity ?? 1;
956
788
  const roughness = firstInput(material, ["roughness"]);
957
789
  const metallic = firstInput(material, ["metallic", "metallicFactor"]);
958
- const diffuseTexture = options.loadTextures === false ? null : findTexture(material, options, ["diffuseColor.connect", "baseColor.connect"], true);
790
+ const emissiveColor = vectorColor(firstInput(material, ["emissiveColor", "emission_color"]));
791
+ const ior = firstInput(material, ["ior"]);
792
+ const opacityThreshold = firstInput(material, ["opacityThreshold"]);
793
+ const alphaTexture = options.loadTextures === false ? null : findTexture(material, options, ["opacity.connect", "alpha.connect"]);
959
794
  const normalTexture = options.loadTextures === false ? null : findTexture(material, options, ["normal.connect"]);
960
795
  const metallicTexture = options.loadTextures === false ? null : findTexture(material, options, ["metallic.connect", "metallicFactor.connect"]);
961
796
  const roughnessTexture = options.loadTextures === false ? null : findTexture(material, options, ["roughness.connect"]);
797
+ const occlusionTexture = options.loadTextures === false ? null : findTexture(material, options, ["occlusion.connect"]);
798
+ const emissiveTexture = options.loadTextures === false ? null : findTexture(material, options, ["emissiveColor.connect", "emission_color.connect"]);
962
799
  const map = diffuseTexture ? loadTexture(diffuseTexture.url, options, THREE.SRGBColorSpace) : null;
800
+ const usesMapAlpha = Boolean(alphaTexture && diffuseTexture && alphaTexture.url === diffuseTexture.url && alphaTexture.outputName === "a");
801
+ const alphaMap = alphaTexture && !usesMapAlpha ? loadTexture(alphaTexture.url, options, THREE.NoColorSpace) : null;
963
802
  const normalMap = normalTexture ? loadTexture(normalTexture.url, options, THREE.NoColorSpace) : null;
964
803
  const metalnessMap = metallicTexture ? loadTexture(metallicTexture.url, options, THREE.NoColorSpace) : null;
965
804
  const roughnessMap = roughnessTexture ? loadTexture(roughnessTexture.url, options, THREE.NoColorSpace) : null;
805
+ const aoMap = occlusionTexture ? loadTexture(occlusionTexture.url, options, THREE.NoColorSpace) : null;
806
+ const emissiveMap = emissiveTexture ? loadTexture(emissiveTexture.url, options, THREE.SRGBColorSpace) : null;
966
807
  if (map && diffuseTexture) applyTextureWrap(map, diffuseTexture);
808
+ if (alphaMap && alphaTexture) applyTextureWrap(alphaMap, alphaTexture);
967
809
  if (normalMap && normalTexture) applyTextureWrap(normalMap, normalTexture);
968
810
  if (metalnessMap && metallicTexture) applyTextureWrap(metalnessMap, metallicTexture);
969
811
  if (roughnessMap && roughnessTexture) applyTextureWrap(roughnessMap, roughnessTexture);
970
- return new THREE.MeshStandardMaterial({
812
+ if (aoMap && occlusionTexture) applyTextureWrap(aoMap, occlusionTexture);
813
+ if (emissiveMap && emissiveTexture) applyTextureWrap(emissiveMap, emissiveTexture);
814
+ const materialParameters = {
971
815
  color: baseColor,
972
816
  map,
817
+ alphaMap,
973
818
  normalMap,
974
819
  metalnessMap,
975
820
  roughnessMap,
821
+ aoMap,
822
+ emissive: emissiveColor ?? new THREE.Color(0, 0, 0),
823
+ emissiveMap,
976
824
  roughness: typeof roughness === "number" ? roughness : .55,
977
825
  metalness: typeof metallic === "number" ? metallic : 0,
978
826
  opacity,
979
- transparent: opacity < 1,
980
- side: element.doubleSided ? THREE.DoubleSide : THREE.FrontSide
981
- });
827
+ alphaTest: typeof opacityThreshold === "number" ? opacityThreshold : 0,
828
+ transparent: opacity < 1 || Boolean(alphaTexture),
829
+ side: element.doubleSided || isHairLikeSurface(material, element) ? THREE.DoubleSide : THREE.FrontSide
830
+ };
831
+ return typeof ior === "number" ? new THREE.MeshPhysicalMaterial({
832
+ ...materialParameters,
833
+ ior
834
+ }) : new THREE.MeshStandardMaterial(materialParameters);
982
835
  }
983
- function buildGeometry(element) {
984
- const geometry = new THREE.BufferGeometry();
985
- geometry.setAttribute("position", new THREE.Float32BufferAttribute(element.geometry.positions, 3));
986
- if (element.geometry.normals.length > 0 && element.geometry.normals.length === element.geometry.positions.length) geometry.setAttribute("normal", new THREE.Float32BufferAttribute(element.geometry.normals, 3));
987
- else geometry.computeVertexNormals();
988
- if (element.geometry.uvs.length > 0) geometry.setAttribute("uv", new THREE.Float32BufferAttribute(element.geometry.uvs, 2));
989
- geometry.computeBoundingBox();
990
- geometry.computeBoundingSphere();
991
- return geometry;
836
+ //#endregion
837
+ //#region src/three/scene.ts
838
+ const disposedModels = /* @__PURE__ */ new WeakSet();
839
+ function appendMappedValue(map, key, value) {
840
+ const values = map.get(key);
841
+ if (values) values.push(value);
842
+ else map.set(key, [value]);
843
+ }
844
+ function createUSDSceneIndex(data, objectsByPath, bonesBySkeletonPath, boneHostsBySkeletonPath) {
845
+ const jointsByBodyPath = /* @__PURE__ */ new Map();
846
+ for (const joint of data.joints) {
847
+ if (joint.body0) appendMappedValue(jointsByBodyPath, joint.body0, joint);
848
+ if (joint.body1) appendMappedValue(jointsByBodyPath, joint.body1, joint);
849
+ }
850
+ const variantsByPrimPath = /* @__PURE__ */ new Map();
851
+ for (const variant of data.variants) appendMappedValue(variantsByPrimPath, variant.primPath, variant);
852
+ return {
853
+ objectsByPath,
854
+ bonesBySkeletonPath,
855
+ boneHostsBySkeletonPath,
856
+ primsByPath: new Map(data.view.prims.map((prim) => [prim.path, prim])),
857
+ meshesByPath: new Map(data.elements.map((element) => [element.path, element])),
858
+ materialsByPath: new Map(data.materials.map((material) => [material.path, material])),
859
+ rigidBodiesByPath: new Map(data.physics.rigidBodies.map((body) => [body.path, body])),
860
+ collidersByPath: new Map(data.physics.colliders.map((collider) => [collider.path, collider])),
861
+ jointsByPath: new Map(data.joints.map((joint) => [joint.path, joint])),
862
+ jointsByBodyPath,
863
+ skelBindingsByPrimPath: new Map(data.skels.bindings.map((binding) => [binding.primPath, binding])),
864
+ skeletonsByPath: new Map(data.skels.skeletons.map((skeleton) => [skeleton.path, skeleton])),
865
+ camerasByPath: new Map(data.cameras.map((camera) => [camera.path, camera])),
866
+ variantsByPrimPath,
867
+ animationsById: new Map(data.animations.map((animation) => [animation.id, animation]))
868
+ };
992
869
  }
993
870
  function makeObjectForPrim(primPath, elementsByPath, materialsByPath, data, options) {
994
871
  const viewPrim = data.view.prims.find((item) => item.path === primPath);
995
872
  const element = elementsByPath.get(primPath);
996
- const object = element ? new THREE.Mesh(buildGeometry(element), buildMaterial(element.material ? materialsByPath.get(element.material) : void 0, element, options)) : new THREE.Group();
873
+ const binding = data.skels.bindings.find((item) => item.primPath === primPath);
874
+ const skeleton = resolveBindingSkeleton(data, binding);
875
+ const material = element ? buildMaterial(element.material ? materialsByPath.get(element.material) : void 0, element, options) : void 0;
876
+ const object = element ? binding && skeleton ? new THREE.SkinnedMesh(buildGeometry(element, binding, skeleton), material) : new THREE.Mesh(buildGeometry(element), material) : new THREE.Group();
997
877
  object.name = viewPrim?.name ?? primPath.split("/").pop() ?? primPath;
998
878
  object.visible = viewPrim?.visibility !== "invisible";
999
- object.userData.usdPath = primPath;
1000
- object.userData.usdTypeName = viewPrim?.typeName ?? "";
879
+ object.userData.usd = {
880
+ kind: "prim",
881
+ path: primPath,
882
+ typeName: viewPrim?.typeName ?? ""
883
+ };
1001
884
  applyLocalMatrix(object, viewPrim?.localMatrix ?? null);
1002
885
  return object;
1003
886
  }
1004
- function attachJointUserData(data, objectsByPath) {
1005
- for (const joint of data.joints) {
1006
- const jointObject = objectsByPath.get(joint.path);
1007
- if (jointObject) jointObject.userData.usdJoint = joint;
1008
- for (const bodyPath of [joint.body0, joint.body1]) {
1009
- if (!bodyPath) continue;
1010
- const bodyObject = objectsByPath.get(bodyPath);
1011
- if (!bodyObject) continue;
1012
- const joints = bodyObject.userData.usdJoints;
1013
- if (Array.isArray(joints)) joints.push(joint);
1014
- else bodyObject.userData.usdJoints = [joint];
1015
- }
1016
- }
1017
- }
1018
- function createTransformTracks(animation, objectsByPath) {
1019
- const tracks = [];
1020
- for (const transform of animation.transforms) {
1021
- const object = objectsByPath.get(transform.primPath);
1022
- if (!object || transform.samples.length < 2) continue;
1023
- const times = [];
1024
- const positions = [];
1025
- const quaternions = [];
1026
- const scales = [];
1027
- let previousQuaternion = null;
1028
- for (const sample of transform.samples) {
1029
- const matrix = matrixFromUsd(sample.localMatrix);
1030
- const position = new THREE.Vector3();
1031
- const quaternion = new THREE.Quaternion();
1032
- const scale = new THREE.Vector3();
1033
- matrix.decompose(position, quaternion, scale);
1034
- if (previousQuaternion && previousQuaternion.dot(quaternion) < 0) quaternion.set(-quaternion.x, -quaternion.y, -quaternion.z, -quaternion.w);
1035
- previousQuaternion = quaternion.clone();
1036
- times.push(sample.time);
1037
- positions.push(position.x, position.y, position.z);
1038
- quaternions.push(quaternion.x, quaternion.y, quaternion.z, quaternion.w);
1039
- scales.push(scale.x, scale.y, scale.z);
1040
- }
1041
- 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));
1042
- }
1043
- return tracks;
1044
- }
1045
- function createAnimationClips(animations, objectsByPath) {
1046
- return animations.map((animation, index) => {
1047
- const tracks = createTransformTracks(animation, objectsByPath);
1048
- if (tracks.length === 0) return null;
1049
- return new THREE.AnimationClip(`USDAnimation_${index + 1}`, -1, tracks);
1050
- }).filter((clip) => clip !== null);
1051
- }
1052
- function buildUSDObject(data, options = {}) {
887
+ function buildUSDScene(data, options = {}) {
1053
888
  const sourcePath = options.sourcePath ?? data.stage.sourcePath;
1054
889
  const wrapper = new THREE.Group();
1055
890
  wrapper.name = `USD:${sourcePath}`;
1056
- wrapper.userData.usdMetadata = data;
1057
- wrapper.userData.usdJoints = data.joints;
1058
- wrapper.userData.usdPath = data.stage.defaultPrim ?? "/";
1059
- wrapper.userData.usdSource = sourcePath;
1060
891
  const elementsByPath = new Map(data.elements.map((element) => [element.path, element]));
1061
892
  const materialsByPath = new Map(data.materials.map((material) => [material.path, material]));
1062
893
  const objectsByPath = /* @__PURE__ */ new Map();
@@ -1072,25 +903,78 @@ function buildUSDObject(data, options = {}) {
1072
903
  else wrapper.add(object);
1073
904
  }
1074
905
  if (options.convertZUp !== false && data.stage.upAxis === "Z") wrapper.rotation.x = -Math.PI / 2;
1075
- attachJointUserData(data, objectsByPath);
1076
- wrapper.userData.usdObjectsByPath = objectsByPath;
1077
- wrapper.userData.usdAnimations = data.animations;
1078
- wrapper.animations = createAnimationClips(data.animations, objectsByPath);
1079
- return wrapper;
906
+ const skinning = bindSkinnedMeshes(data, objectsByPath, wrapper);
907
+ wrapper.animations = createAnimationClips(data.animations, objectsByPath, data, skinning.bonesBySkeletonPath);
908
+ return {
909
+ scene: wrapper,
910
+ index: createUSDSceneIndex(data, objectsByPath, skinning.bonesBySkeletonPath, skinning.boneHostsBySkeletonPath)
911
+ };
1080
912
  }
1081
- function createUSDLoadedModel(data, pxr, rootLayerIdentifier, options = {}, stage) {
913
+ function createUSDLoadedModel(data, pxr, rootLayerIdentifier, options = {}, stage, resources = {}) {
1082
914
  const sourcePath = options.sourcePath ?? data.stage.sourcePath;
1083
- return {
1084
- scene: buildUSDObject(data, {
1085
- ...options,
1086
- sourcePath
1087
- }),
1088
- metadata: data,
915
+ const built = buildUSDScene(data, {
916
+ ...options,
917
+ sourcePath
918
+ });
919
+ const model = {
920
+ scene: built.scene,
921
+ data,
922
+ index: built.index,
923
+ resources: {
924
+ usdzTextureURLs: resources.usdzTextureURLs ?? /* @__PURE__ */ new Map(),
925
+ autoTextureURLs: resources.autoTextureURLs ?? /* @__PURE__ */ new Map()
926
+ },
1089
927
  stage,
1090
928
  pxr,
1091
929
  sourcePath,
1092
- rootLayerIdentifier
930
+ rootLayerIdentifier,
931
+ dispose() {
932
+ disposeUSDLoadedModel(model);
933
+ }
1093
934
  };
935
+ return model;
936
+ }
937
+ function disposePxrObject(value) {
938
+ if (value && typeof value === "object" && "delete" in value && typeof value.delete === "function") value.delete();
939
+ }
940
+ function disposeTexture(texture, disposed) {
941
+ if (!texture || disposed.has(texture)) return;
942
+ disposed.add(texture);
943
+ texture.dispose();
944
+ }
945
+ function disposeMaterial(material, disposedTextures, disposedMaterials) {
946
+ if (disposedMaterials.has(material)) return;
947
+ disposedMaterials.add(material);
948
+ for (const value of Object.values(material)) if (value instanceof THREE.Texture) disposeTexture(value, disposedTextures);
949
+ material.dispose();
950
+ }
951
+ function revokeObjectURLs(urls) {
952
+ if (!(urls instanceof Map) || typeof URL === "undefined" || typeof URL.revokeObjectURL !== "function") return;
953
+ for (const url of urls.values()) if (typeof url === "string" && url.startsWith("blob:")) URL.revokeObjectURL(url);
954
+ urls.clear();
955
+ }
956
+ function disposeUSDLoadedModel(model) {
957
+ if (disposedModels.has(model)) return;
958
+ disposedModels.add(model);
959
+ const disposedGeometries = /* @__PURE__ */ new Set();
960
+ const disposedMaterials = /* @__PURE__ */ new Set();
961
+ const disposedTextures = /* @__PURE__ */ new Set();
962
+ model.scene.traverse((object) => {
963
+ const mesh = object;
964
+ const geometry = mesh.geometry;
965
+ if (geometry instanceof THREE.BufferGeometry && !disposedGeometries.has(geometry)) {
966
+ disposedGeometries.add(geometry);
967
+ geometry.dispose();
968
+ }
969
+ const material = mesh.material;
970
+ if (Array.isArray(material)) for (const item of material) disposeMaterial(item, disposedTextures, disposedMaterials);
971
+ else if (material instanceof THREE.Material) disposeMaterial(material, disposedTextures, disposedMaterials);
972
+ });
973
+ revokeObjectURLs(model.resources.usdzTextureURLs);
974
+ revokeObjectURLs(model.resources.autoTextureURLs);
975
+ releaseTexturesFromSharedCache(disposedTextures);
976
+ disposePxrObject(model.stage);
977
+ model.stage = void 0;
1094
978
  }
1095
979
  //#endregion
1096
980
  //#region src/loader.ts
@@ -1151,6 +1035,14 @@ function deleteFileIfExists(pxr, path) {
1151
1035
  if (pxr.FS.exists(path)) pxr.FS.deleteFile(path);
1152
1036
  } catch {}
1153
1037
  }
1038
+ function deleteDirRecursiveIfExists(pxr, path) {
1039
+ if (!pxr.FS.exists(path)) return;
1040
+ for (const entry of pxr.FS.listDir(path)) if (entry.isDir) deleteDirRecursiveIfExists(pxr, entry.path);
1041
+ else deleteFileIfExists(pxr, entry.path);
1042
+ try {
1043
+ pxr.FS.deleteDir(path);
1044
+ } catch {}
1045
+ }
1154
1046
  function createUSDInputDirectory(pxr, workingDirectory) {
1155
1047
  const directory = joinFsPath(workingDirectory, String(++nextLoadId));
1156
1048
  pxr.FS.createDir(directory);
@@ -1180,6 +1072,12 @@ function openStage(pxr, filePath, extension, usdzLayer) {
1180
1072
  }
1181
1073
  throw new Error(`Failed to open USD stage from ${filePath}`);
1182
1074
  }
1075
+ function isAnimationVariantSet(setName) {
1076
+ return setName.toLowerCase().includes("anim");
1077
+ }
1078
+ function selectionKey(selection) {
1079
+ return `${selection.primPath}\n${selection.setName}`;
1080
+ }
1183
1081
  function mergeParseOptions(loaderOptions, parseOptions) {
1184
1082
  const sourcePath = parseOptions.sourcePath ?? parseOptions.fileName ?? "scene.usda";
1185
1083
  const extension = extensionForPath(parseOptions.fileName ?? sourcePath);
@@ -1240,47 +1138,112 @@ var USDLoader = class extends Loader {
1240
1138
  else console.error(error);
1241
1139
  });
1242
1140
  }
1243
- async parseAsync(input, options = {}) {
1141
+ async parseDataAsync(input, options = {}) {
1244
1142
  const pxr = await this.getPxr();
1245
1143
  const mergedOptions = mergeParseOptions(this.options, options);
1246
1144
  const extension = extensionForPath(mergedOptions.fileName);
1247
1145
  const writableData = await sourceToWritableData(input);
1248
- const packageTextureResolver = extension === "usdz" && writableData instanceof Uint8Array ? createPackageTextureResolver(writableData) : null;
1249
- const packageRootLayer = extension === "usdz" && writableData instanceof Uint8Array ? mergedOptions.usdzLayer ?? findPackageRootLayer(writableData) ?? void 0 : mergedOptions.usdzLayer;
1250
1146
  const rootFileName = sanitizeFileName(mergedOptions.fileName);
1251
1147
  const directory = createUSDInputDirectory(pxr, mergedOptions.workingDirectory);
1252
- const filePath = await writeUSDInput(pxr, directory, writableData, {
1253
- fileName: rootFileName,
1254
- files: mergedOptions.files
1255
- });
1256
- let autoFiles = extension !== "usdz" ? await autoResolveAssetFiles(pxr, filePath, {
1257
- ...mergedOptions,
1258
- fileName: rootFileName
1259
- }) : {};
1260
- await writeUSDFiles(pxr, directory, autoFiles);
1261
- const { stage, identifier } = openStage(pxr, filePath, extension, packageRootLayer);
1148
+ let filePath = "";
1149
+ let stage = null;
1150
+ let stageReturned = false;
1262
1151
  try {
1263
- const metadata = extractUSDModelData(pxr, stage, {
1152
+ filePath = await writeUSDInput(pxr, directory, writableData, {
1153
+ fileName: rootFileName,
1154
+ files: mergedOptions.files
1155
+ });
1156
+ const packageEntries = extension === "usdz" ? extractPackageEntries(pxr, filePath, joinFsPath(directory, ".packages")) : null;
1157
+ const packageTextureResolver = packageEntries ? createTextureResolverFromEntries(packageEntries) : null;
1158
+ const packageRootLayer = extension === "usdz" ? mergedOptions.usdzLayer ?? findPackageRootLayer(pxr, filePath) ?? void 0 : mergedOptions.usdzLayer;
1159
+ if (packageEntries) await writeUSDFiles(pxr, directory, Object.fromEntries(packageEntries));
1160
+ let autoFiles = extension !== "usdz" ? await autoResolveAssetFiles(pxr, filePath, {
1161
+ ...mergedOptions,
1162
+ fileName: rootFileName
1163
+ }) : {};
1164
+ await writeUSDFiles(pxr, directory, autoFiles);
1165
+ const opened = openStage(pxr, filePath, extension, packageRootLayer);
1166
+ stage = opened.stage;
1167
+ const identifier = opened.identifier;
1168
+ applyVariantSelections(pxr, stage, mergedOptions.variantSelections);
1169
+ const sceneData = extractUSDSceneData(pxr, stage, {
1264
1170
  sourcePath: mergedOptions.sourcePath,
1265
1171
  rootLayerIdentifier: identifier
1266
1172
  });
1173
+ const originalSelections = new Map(sceneData.variants.map((variant) => [selectionKey(variant), variant.selection]));
1174
+ const currentSelectionByKey = new Map(originalSelections);
1175
+ const animationVariantClips = [];
1176
+ for (const variant of sceneData.variants.filter((item) => isAnimationVariantSet(item.setName))) for (const selection of variant.variantNames) {
1177
+ const key = selectionKey(variant);
1178
+ if (currentSelectionByKey.get(key) !== selection) {
1179
+ setVariantSelection(pxr, stage, {
1180
+ primPath: variant.primPath,
1181
+ setName: variant.setName,
1182
+ selection
1183
+ });
1184
+ currentSelectionByKey.set(key, selection);
1185
+ }
1186
+ const variantData = extractUSDSceneData(pxr, stage, {
1187
+ sourcePath: mergedOptions.sourcePath,
1188
+ rootLayerIdentifier: identifier
1189
+ });
1190
+ for (const animation of variantData.animations) {
1191
+ animation.id = `variant:${variant.primPath}:${variant.setName}:${selection}:${animation.id}`;
1192
+ animation.name = selection;
1193
+ animation.source = "variant";
1194
+ animation.variant = {
1195
+ primPath: variant.primPath,
1196
+ setName: variant.setName,
1197
+ selection
1198
+ };
1199
+ animationVariantClips.push(animation);
1200
+ }
1201
+ }
1202
+ for (const variant of sceneData.variants.filter((item) => isAnimationVariantSet(item.setName))) {
1203
+ const key = selectionKey(variant);
1204
+ const originalSelection = originalSelections.get(key) ?? null;
1205
+ if (currentSelectionByKey.get(key) !== originalSelection) {
1206
+ setVariantSelection(pxr, stage, {
1207
+ primPath: variant.primPath,
1208
+ setName: variant.setName,
1209
+ selection: originalSelection
1210
+ });
1211
+ currentSelectionByKey.set(key, originalSelection);
1212
+ }
1213
+ }
1214
+ if (animationVariantClips.length > 0) sceneData.animations = animationVariantClips;
1267
1215
  if (extension !== "usdz") autoFiles = {
1268
1216
  ...autoFiles,
1269
- ...await autoResolveTextureFiles(metadata, autoFiles, mergedOptions)
1217
+ ...await autoResolveTextureFiles(sceneData, autoFiles, mergedOptions)
1270
1218
  };
1271
1219
  const autoTextureResolver = createTextureResolverFromEntries(new Map(Object.entries(autoFiles).map(([path, data]) => [path, normalizeBytes(data)])));
1272
- const model = createUSDLoadedModel(metadata, pxr, identifier, {
1273
- ...mergedOptions,
1274
- textureResolver: composeTextureResolver(composeTextureResolver(mergedOptions.textureResolver, packageTextureResolver?.resolve), autoTextureResolver?.resolve)
1275
- }, mergedOptions.preserveStage ? stage : void 0);
1276
- if (packageTextureResolver) model.scene.userData.usdzTextureURLs = packageTextureResolver.urls;
1277
- if (autoTextureResolver) model.scene.userData.autoTextureURLs = autoTextureResolver.urls;
1278
- return model;
1220
+ const textureResolver = composeTextureResolver(composeTextureResolver(mergedOptions.textureResolver, packageTextureResolver?.resolve), autoTextureResolver?.resolve);
1221
+ stageReturned = Boolean(mergedOptions.preserveStage);
1222
+ return {
1223
+ data: sceneData,
1224
+ pxr,
1225
+ sourcePath: mergedOptions.sourcePath,
1226
+ rootLayerIdentifier: identifier,
1227
+ stage: mergedOptions.preserveStage ? stage ?? void 0 : void 0,
1228
+ textureResolver,
1229
+ usdzTextureURLs: packageTextureResolver?.urls,
1230
+ autoTextureURLs: autoTextureResolver?.urls
1231
+ };
1279
1232
  } finally {
1280
- if (!mergedOptions.preserveStage) dispose(stage);
1281
- if (mergedOptions.cleanupAfterParse && !mergedOptions.preserveStage) deleteFileIfExists(pxr, filePath);
1233
+ if (!stageReturned) dispose(stage);
1234
+ if (mergedOptions.cleanupAfterParse && !stageReturned) deleteDirRecursiveIfExists(pxr, directory);
1282
1235
  }
1283
1236
  }
1237
+ async parseAsync(input, options = {}) {
1238
+ const data = await this.parseDataAsync(input, options);
1239
+ return createUSDLoadedModel(data.data, data.pxr, data.rootLayerIdentifier, {
1240
+ ...mergeParseOptions(this.options, options),
1241
+ textureResolver: data.textureResolver
1242
+ }, data.stage, {
1243
+ usdzTextureURLs: data.usdzTextureURLs,
1244
+ autoTextureURLs: data.autoTextureURLs
1245
+ });
1246
+ }
1284
1247
  async getPxr() {
1285
1248
  if (!this.pxrPromise) {
1286
1249
  if (!this.options.pxrOptions) throw new Error("USDLoader requires either a pxr instance or pxrOptions");
@@ -1290,459 +1253,4 @@ var USDLoader = class extends Loader {
1290
1253
  }
1291
1254
  };
1292
1255
  //#endregion
1293
- //#region src/manipulation-controls.ts
1294
- const axisVectors = {
1295
- X: new THREE.Vector3(1, 0, 0),
1296
- Y: new THREE.Vector3(0, 1, 0),
1297
- Z: new THREE.Vector3(0, 0, 1)
1298
- };
1299
- function clamp(value, min, max) {
1300
- return Math.min(max, Math.max(min, value));
1301
- }
1302
- function limitsForJoint(joint) {
1303
- if (joint.lowerLimit !== null && joint.upperLimit !== null) return {
1304
- lower: joint.lowerLimit,
1305
- upper: joint.upperLimit
1306
- };
1307
- return joint.jointType === "revolute" ? {
1308
- lower: -180,
1309
- upper: 180
1310
- } : {
1311
- lower: -1,
1312
- upper: 1
1313
- };
1314
- }
1315
- function initialValueForJoint(joint) {
1316
- const { lower, upper } = limitsForJoint(joint);
1317
- return clamp(joint.drive?.targetPosition ?? 0, lower, upper);
1318
- }
1319
- function isManipulableJoint(joint) {
1320
- return joint.enabled && (joint.jointType === "revolute" || joint.jointType === "prismatic");
1321
- }
1322
- function objectsByPathForRoot(root) {
1323
- const userDataMap = root.userData.usdObjectsByPath;
1324
- if (userDataMap instanceof Map) return new Map([...userDataMap.entries()].filter((entry) => {
1325
- return typeof entry[0] === "string" && entry[1] instanceof THREE.Object3D;
1326
- }));
1327
- const objectsByPath = /* @__PURE__ */ new Map();
1328
- root.traverse((child) => {
1329
- const usdPath = child.userData.usdPath;
1330
- if (typeof usdPath === "string") objectsByPath.set(usdPath, child);
1331
- });
1332
- return objectsByPath;
1333
- }
1334
- function findUSDModels(scene) {
1335
- const models = [];
1336
- scene.traverse((object) => {
1337
- const metadata = object.userData.usdMetadata;
1338
- if (!metadata) return;
1339
- object.updateMatrixWorld(true);
1340
- models.push({
1341
- root: object,
1342
- metadata,
1343
- objectsByPath: objectsByPathForRoot(object)
1344
- });
1345
- });
1346
- return models;
1347
- }
1348
- function modelStateSignature(models) {
1349
- return models.map((model) => {
1350
- const matrix = model.root.matrixWorld.elements.map((value) => value.toFixed(5)).join(",");
1351
- const stage = model.metadata.stage;
1352
- return [
1353
- model.root.uuid,
1354
- stage.rootLayerIdentifier,
1355
- stage.defaultPrim ?? "",
1356
- model.metadata.joints.length,
1357
- matrix
1358
- ].join(":");
1359
- }).join("|");
1360
- }
1361
- function jointForObject(models, object) {
1362
- for (const model of models) for (const joint of model.metadata.joints) {
1363
- if (!isManipulableJoint(joint) || !joint.body1) continue;
1364
- const bodyObject = model.objectsByPath.get(joint.body1);
1365
- if (!bodyObject) continue;
1366
- let current = object;
1367
- while (current) {
1368
- if (current === bodyObject) return {
1369
- model,
1370
- joint,
1371
- object: bodyObject,
1372
- body0: joint.body0 ? model.objectsByPath.get(joint.body0) ?? null : null
1373
- };
1374
- current = current.parent;
1375
- }
1376
- }
1377
- return null;
1378
- }
1379
- function jointValueKey(model, joint) {
1380
- return `${model.root.uuid}:${joint.path}`;
1381
- }
1382
- function localAxisForJoint(joint) {
1383
- const axis = axisVectors[joint.axis ?? ""]?.clone() ?? axisVectors.X.clone();
1384
- const rotation = joint.localRot0;
1385
- if (!rotation) return axis;
1386
- const quaternion = new THREE.Quaternion(rotation[1] ?? 0, rotation[2] ?? 0, rotation[3] ?? 0, rotation[0] ?? 1);
1387
- if (quaternion.lengthSq() < 1e-10) return axis;
1388
- return axis.applyQuaternion(quaternion.normalize());
1389
- }
1390
- function localPivotForJoint(joint) {
1391
- return new THREE.Vector3(joint.localPos0[0] ?? 0, joint.localPos0[1] ?? 0, joint.localPos0[2] ?? 0);
1392
- }
1393
- function parentLocalPoint(object, body0, point) {
1394
- if (!body0) return point.clone();
1395
- const worldPoint = body0.localToWorld(point.clone());
1396
- return object.parent ? object.parent.worldToLocal(worldPoint) : worldPoint;
1397
- }
1398
- function parentLocalVectorForJoint(joint, object, body0, delta) {
1399
- const pivot = localPivotForJoint(joint);
1400
- const start = parentLocalPoint(object, body0, pivot);
1401
- return parentLocalPoint(object, body0, pivot.clone().add(localAxisForJoint(joint).multiplyScalar(delta))).sub(start);
1402
- }
1403
- function parentLocalAxisForJoint(joint, object, body0) {
1404
- const axis = parentLocalVectorForJoint(joint, object, body0, 1);
1405
- if (axis.lengthSq() < 1e-10) return localAxisForJoint(joint).normalize();
1406
- return axis.normalize();
1407
- }
1408
- function parentLocalPivotForJoint(joint, object, body0) {
1409
- return parentLocalPoint(object, body0, localPivotForJoint(joint));
1410
- }
1411
- function parentLocalToWorld(object, point) {
1412
- return object.parent ? object.parent.localToWorld(point.clone()) : point.clone();
1413
- }
1414
- function projectToClient(point, camera, rect) {
1415
- const ndc = point.clone().project(camera);
1416
- return new THREE.Vector2((ndc.x * .5 + .5) * rect.width, (-ndc.y * .5 + .5) * rect.height);
1417
- }
1418
- function prismaticDragMapping(joint, object, body0, camera, rect) {
1419
- const { lower, upper } = limitsForJoint(joint);
1420
- const referenceDelta = Math.max(Math.abs(upper - lower), .1);
1421
- const startPivot = parentLocalPivotForJoint(joint, object, body0);
1422
- const start = parentLocalToWorld(object, startPivot);
1423
- const screenVector = projectToClient(parentLocalToWorld(object, startPivot.clone().add(parentLocalVectorForJoint(joint, object, body0, referenceDelta))), camera, rect).sub(projectToClient(start, camera, rect));
1424
- const screenLength = screenVector.length();
1425
- if (screenLength < 1) return {
1426
- screenAxis: new THREE.Vector2(1, 0),
1427
- valuePerPixel: referenceDelta / 180
1428
- };
1429
- return {
1430
- screenAxis: screenVector.normalize(),
1431
- valuePerPixel: referenceDelta / screenLength
1432
- };
1433
- }
1434
- function fallbackRevoluteScreenAxis(pivot, axis, camera, rect) {
1435
- const projectedAxis = projectToClient(pivot.clone().add(axis), camera, rect).sub(projectToClient(pivot, camera, rect));
1436
- if (projectedAxis.length() < 1) return new THREE.Vector2(1, -1).normalize();
1437
- return new THREE.Vector2(-projectedAxis.y, projectedAxis.x).normalize();
1438
- }
1439
- function revoluteDragMapping(joint, object, body0, camera, rect) {
1440
- const axis = parentLocalAxisForJoint(joint, object, body0);
1441
- const pivot = parentLocalPivotForJoint(joint, object, body0);
1442
- const referenceDegrees = 12;
1443
- const referenceRadians = THREE.MathUtils.degToRad(referenceDegrees);
1444
- const bounds = new THREE.Box3().setFromObject(object);
1445
- const center = object.parent ? object.parent.worldToLocal(bounds.getCenter(new THREE.Vector3())) : bounds.getCenter(new THREE.Vector3());
1446
- const size = bounds.getSize(new THREE.Vector3());
1447
- const radius = Math.max(size.length() * .25, .05);
1448
- const radiusVector = center.sub(pivot);
1449
- radiusVector.addScaledVector(axis, -radiusVector.dot(axis));
1450
- if (radiusVector.lengthSq() < 1e-8) {
1451
- 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);
1452
- radiusVector.copy(fallback.cross(axis));
1453
- }
1454
- radiusVector.setLength(Math.max(radiusVector.length(), radius));
1455
- const start = pivot.clone().add(radiusVector);
1456
- const end = pivot.clone().add(radiusVector.clone().applyAxisAngle(axis, referenceRadians));
1457
- const startWorld = parentLocalToWorld(object, start);
1458
- const screenVector = projectToClient(parentLocalToWorld(object, end), camera, rect).sub(projectToClient(startWorld, camera, rect));
1459
- const screenLength = screenVector.length();
1460
- if (screenLength < 1) return {
1461
- screenAxis: fallbackRevoluteScreenAxis(parentLocalToWorld(object, pivot), parentLocalToWorld(object, pivot.clone().add(axis)).sub(parentLocalToWorld(object, pivot)), camera, rect),
1462
- valuePerPixel: referenceDegrees / 140
1463
- };
1464
- return {
1465
- screenAxis: screenVector.normalize(),
1466
- valuePerPixel: referenceDegrees / screenLength
1467
- };
1468
- }
1469
- function updateJointTransform(drag, value) {
1470
- const delta = value - drag.startValue;
1471
- const axis = parentLocalAxisForJoint(drag.joint, drag.object, drag.body0);
1472
- const nextLocal = new THREE.Matrix4();
1473
- if (drag.joint.jointType === "revolute") {
1474
- const pivot = parentLocalPivotForJoint(drag.joint, drag.object, drag.body0);
1475
- const rotation = new THREE.Matrix4().makeRotationAxis(axis, THREE.MathUtils.degToRad(delta));
1476
- nextLocal.makeTranslation(pivot.x, pivot.y, pivot.z).multiply(rotation).multiply(new THREE.Matrix4().makeTranslation(-pivot.x, -pivot.y, -pivot.z)).multiply(drag.initialLocalMatrix);
1477
- nextLocal.decompose(drag.object.position, drag.object.quaternion, drag.object.scale);
1478
- drag.object.updateMatrixWorld(true);
1479
- return;
1480
- }
1481
- const translation = parentLocalVectorForJoint(drag.joint, drag.object, drag.body0, delta);
1482
- nextLocal.makeTranslation(translation.x, translation.y, translation.z).multiply(drag.initialLocalMatrix);
1483
- nextLocal.decompose(drag.object.position, drag.object.quaternion, drag.object.scale);
1484
- drag.object.updateMatrixWorld(true);
1485
- }
1486
- function highlightedMaterials(material, color) {
1487
- const highlighted = (Array.isArray(material) ? material : [material]).map((item) => {
1488
- const cloned = item.clone();
1489
- if ("emissive" in cloned) {
1490
- const emissiveMaterial = cloned;
1491
- emissiveMaterial.emissive = color.clone();
1492
- emissiveMaterial.emissiveIntensity = Math.max(emissiveMaterial.emissiveIntensity, .28);
1493
- }
1494
- return cloned;
1495
- });
1496
- return Array.isArray(material) ? highlighted : highlighted[0];
1497
- }
1498
- function disposeMaterial(material) {
1499
- const materials = Array.isArray(material) ? material : [material];
1500
- for (const item of materials) item.dispose();
1501
- }
1502
- var USDManipulationControls = class {
1503
- raycaster = new THREE.Raycaster();
1504
- pointer = new THREE.Vector2();
1505
- jointValues = /* @__PURE__ */ new Map();
1506
- highlighted = /* @__PURE__ */ new Map();
1507
- activeDrag = null;
1508
- hovered = null;
1509
- modelStateSignature = null;
1510
- _enabled;
1511
- disposed = false;
1512
- scene;
1513
- camera;
1514
- domElement;
1515
- controls;
1516
- highlightColor;
1517
- onChange;
1518
- onHoverChange;
1519
- constructor(options) {
1520
- this.scene = options.scene;
1521
- this.camera = options.camera;
1522
- this.domElement = options.domElement;
1523
- this.controls = options.controls ?? null;
1524
- this.highlightColor = new THREE.Color(options.highlightColor ?? 16777215);
1525
- this._enabled = options.enabled ?? true;
1526
- this.onChange = options.onChange;
1527
- this.onHoverChange = options.onHoverChange;
1528
- this.domElement.addEventListener("pointermove", this.onPointerMove);
1529
- this.domElement.addEventListener("pointerdown", this.onPointerDown);
1530
- this.domElement.addEventListener("pointerup", this.onPointerUp);
1531
- this.domElement.addEventListener("pointercancel", this.onPointerUp);
1532
- }
1533
- get enabled() {
1534
- return this._enabled;
1535
- }
1536
- set enabled(value) {
1537
- if (this._enabled === value) return;
1538
- this._enabled = value;
1539
- if (!value) {
1540
- this.endDrag(null);
1541
- this.clearSelection();
1542
- }
1543
- }
1544
- dispose() {
1545
- if (this.disposed) return;
1546
- this.disposed = true;
1547
- this.domElement.removeEventListener("pointermove", this.onPointerMove);
1548
- this.domElement.removeEventListener("pointerdown", this.onPointerDown);
1549
- this.domElement.removeEventListener("pointerup", this.onPointerUp);
1550
- this.domElement.removeEventListener("pointercancel", this.onPointerUp);
1551
- this.endDrag(null);
1552
- this.clearSelection();
1553
- for (const [mesh, state] of this.highlighted) {
1554
- disposeMaterial(state.highlighted);
1555
- mesh.material = state.original;
1556
- }
1557
- this.highlighted.clear();
1558
- }
1559
- update() {
1560
- this.syncModelState(findUSDModels(this.scene));
1561
- }
1562
- clearSelection() {
1563
- if (!this.hovered) return;
1564
- this.restoreHighlight(this.hovered.object);
1565
- this.hovered = null;
1566
- this.onHoverChange?.({
1567
- joint: null,
1568
- object: null
1569
- });
1570
- }
1571
- getJointValue(joint, root) {
1572
- if (root) return this.jointValues.get(`${root.uuid}:${joint.path}`);
1573
- for (const [key, value] of this.jointValues) if (key.endsWith(`:${joint.path}`)) return value;
1574
- }
1575
- setJointValue(joint, value, root) {
1576
- const models = findUSDModels(this.scene);
1577
- this.syncModelState(models);
1578
- for (const model of models) {
1579
- if (root && model.root !== root) continue;
1580
- const modelJoint = model.metadata.joints.find((item) => item.path === joint.path);
1581
- if (!modelJoint || !isManipulableJoint(modelJoint) || !modelJoint.body1) continue;
1582
- const object = model.objectsByPath.get(modelJoint.body1);
1583
- if (!object) continue;
1584
- object.updateMatrix();
1585
- object.updateMatrixWorld(true);
1586
- const body0 = modelJoint.body0 ? model.objectsByPath.get(modelJoint.body0) ?? null : null;
1587
- body0?.updateMatrixWorld(true);
1588
- const jointKey = jointValueKey(model, modelJoint);
1589
- const currentValue = this.jointValues.get(jointKey) ?? initialValueForJoint(modelJoint);
1590
- const { lower, upper } = limitsForJoint(modelJoint);
1591
- const nextValue = clamp(value, lower, upper);
1592
- const drag = {
1593
- model,
1594
- joint: modelJoint,
1595
- object,
1596
- body0,
1597
- startX: 0,
1598
- startY: 0,
1599
- startValue: currentValue,
1600
- initialLocalMatrix: object.matrix.clone(),
1601
- jointKey,
1602
- controlsWasEnabled: null
1603
- };
1604
- this.jointValues.set(jointKey, nextValue);
1605
- updateJointTransform(drag, nextValue);
1606
- this.onChange?.({
1607
- joint: modelJoint,
1608
- object,
1609
- body0,
1610
- value: nextValue,
1611
- previousValue: currentValue
1612
- });
1613
- return true;
1614
- }
1615
- return false;
1616
- }
1617
- onPointerMove = (event) => {
1618
- if (!this._enabled || this.disposed) return;
1619
- const activeDrag = this.activeDrag;
1620
- if (activeDrag) {
1621
- const pointerDelta = new THREE.Vector2(event.clientX - activeDrag.startX, event.clientY - activeDrag.startY);
1622
- let mappedValueDelta;
1623
- if (activeDrag.joint.jointType === "prismatic" && activeDrag.prismaticScreenAxis && activeDrag.prismaticValuePerPixel !== void 0) mappedValueDelta = pointerDelta.dot(activeDrag.prismaticScreenAxis) * activeDrag.prismaticValuePerPixel;
1624
- else if (activeDrag.joint.jointType === "revolute" && activeDrag.revoluteScreenAxis && activeDrag.revoluteValuePerPixel !== void 0) mappedValueDelta = pointerDelta.dot(activeDrag.revoluteScreenAxis) * activeDrag.revoluteValuePerPixel;
1625
- else {
1626
- const { lower, upper } = limitsForJoint(activeDrag.joint);
1627
- const range = Math.max(upper - lower, activeDrag.joint.jointType === "revolute" ? 90 : .5);
1628
- mappedValueDelta = (pointerDelta.x - pointerDelta.y) / 280 * range;
1629
- }
1630
- const { lower, upper } = limitsForJoint(activeDrag.joint);
1631
- const previousValue = this.jointValues.get(activeDrag.jointKey) ?? activeDrag.startValue;
1632
- const nextValue = clamp(activeDrag.startValue + mappedValueDelta, lower, upper);
1633
- if (nextValue === previousValue) return;
1634
- this.jointValues.set(activeDrag.jointKey, nextValue);
1635
- updateJointTransform(activeDrag, nextValue);
1636
- this.onChange?.({
1637
- joint: activeDrag.joint,
1638
- object: activeDrag.object,
1639
- body0: activeDrag.body0,
1640
- value: nextValue,
1641
- previousValue
1642
- });
1643
- return;
1644
- }
1645
- const hit = this.pickJoint(event);
1646
- if (this.hovered && this.hovered.object !== hit?.object) this.clearSelection();
1647
- if (hit && this.hovered?.object !== hit.object) {
1648
- this.applyHighlight(hit.object);
1649
- this.hovered = {
1650
- joint: hit.joint,
1651
- object: hit.object
1652
- };
1653
- this.onHoverChange?.({
1654
- joint: hit.joint,
1655
- object: hit.object
1656
- });
1657
- }
1658
- };
1659
- onPointerDown = (event) => {
1660
- if (!this._enabled || this.disposed) return;
1661
- const hit = this.pickJoint(event);
1662
- if (!hit) return;
1663
- event.preventDefault();
1664
- this.domElement.setPointerCapture?.(event.pointerId);
1665
- const controlsWasEnabled = this.controls?.enabled ?? null;
1666
- if (this.controls) this.controls.enabled = false;
1667
- const jointKey = jointValueKey(hit.model, hit.joint);
1668
- hit.object.updateMatrix();
1669
- hit.object.updateMatrixWorld(true);
1670
- hit.body0?.updateMatrixWorld(true);
1671
- const currentValue = this.jointValues.get(jointKey) ?? initialValueForJoint(hit.joint);
1672
- const rect = this.domElement.getBoundingClientRect();
1673
- const prismaticMapping = hit.joint.jointType === "prismatic" ? prismaticDragMapping(hit.joint, hit.object, hit.body0, this.camera, rect) : null;
1674
- const revoluteMapping = hit.joint.jointType === "revolute" ? revoluteDragMapping(hit.joint, hit.object, hit.body0, this.camera, rect) : null;
1675
- this.activeDrag = {
1676
- ...hit,
1677
- startX: event.clientX,
1678
- startY: event.clientY,
1679
- startValue: currentValue,
1680
- initialLocalMatrix: hit.object.matrix.clone(),
1681
- jointKey,
1682
- controlsWasEnabled,
1683
- revoluteScreenAxis: revoluteMapping?.screenAxis,
1684
- revoluteValuePerPixel: revoluteMapping?.valuePerPixel,
1685
- prismaticScreenAxis: prismaticMapping?.screenAxis,
1686
- prismaticValuePerPixel: prismaticMapping?.valuePerPixel
1687
- };
1688
- };
1689
- onPointerUp = (event) => {
1690
- this.endDrag(event);
1691
- };
1692
- pickJoint(event) {
1693
- this.updatePointer(event);
1694
- const models = findUSDModels(this.scene);
1695
- if (models.length === 0) return null;
1696
- this.syncModelState(models);
1697
- this.raycaster.setFromCamera(this.pointer, this.camera);
1698
- const intersections = this.raycaster.intersectObjects(models.map((model) => model.root), true);
1699
- for (const intersection of intersections) {
1700
- const hit = jointForObject(models, intersection.object);
1701
- if (hit) return hit;
1702
- }
1703
- return null;
1704
- }
1705
- updatePointer(event) {
1706
- const rect = this.domElement.getBoundingClientRect();
1707
- this.pointer.x = (event.clientX - rect.left) / rect.width * 2 - 1;
1708
- this.pointer.y = -((event.clientY - rect.top) / rect.height) * 2 + 1;
1709
- }
1710
- syncModelState(models) {
1711
- const nextSignature = modelStateSignature(models);
1712
- if (this.modelStateSignature === nextSignature) return;
1713
- this.endDrag(null);
1714
- this.clearSelection();
1715
- this.jointValues.clear();
1716
- this.modelStateSignature = nextSignature;
1717
- }
1718
- endDrag(event) {
1719
- const activeDrag = this.activeDrag;
1720
- if (!activeDrag) return;
1721
- this.activeDrag = null;
1722
- if (event && this.domElement.hasPointerCapture?.(event.pointerId)) this.domElement.releasePointerCapture(event.pointerId);
1723
- if (this.controls && activeDrag.controlsWasEnabled !== null) this.controls.enabled = activeDrag.controlsWasEnabled;
1724
- }
1725
- applyHighlight(object) {
1726
- object.traverse((child) => {
1727
- if (!(child instanceof THREE.Mesh) || this.highlighted.has(child)) return;
1728
- const highlighted = highlightedMaterials(child.material, this.highlightColor);
1729
- this.highlighted.set(child, {
1730
- original: child.material,
1731
- highlighted
1732
- });
1733
- child.material = highlighted;
1734
- });
1735
- }
1736
- restoreHighlight(object) {
1737
- object.traverse((child) => {
1738
- if (!(child instanceof THREE.Mesh)) return;
1739
- const state = this.highlighted.get(child);
1740
- if (!state) return;
1741
- child.material = state.original;
1742
- disposeMaterial(state.highlighted);
1743
- this.highlighted.delete(child);
1744
- });
1745
- }
1746
- };
1747
- //#endregion
1748
- export { USDLoader, USDManipulationControls, buildUSDObject, createUSDLoadedModel, extractUSDModelData };
1256
+ export { USDLoader, buildUSDScene, createUSDLoadedModel, disposeUSDLoadedModel, extractUSDSceneData };