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