@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.
- package/README.md +53 -13
- package/dist/openusd_three_loader.d.ts +231 -77
- package/dist/openusd_three_loader.js +746 -1239
- package/package.json +3 -3
|
@@ -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,
|
|
5
|
-
//#region src/
|
|
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
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
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
|
|
206
|
-
const
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
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
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
if (
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
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
|
|
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
|
-
|
|
215
|
+
//#endregion
|
|
216
|
+
//#region src/scene-data/materials.ts
|
|
217
|
+
function asMaterialTextureInfo(value) {
|
|
218
|
+
const info = value;
|
|
301
219
|
return {
|
|
302
|
-
|
|
303
|
-
|
|
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
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
const
|
|
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
|
-
|
|
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(
|
|
234
|
+
dispose$1(materialPrim);
|
|
235
|
+
dispose$1(path);
|
|
328
236
|
}
|
|
329
237
|
}
|
|
330
|
-
function
|
|
331
|
-
const
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
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
|
|
340
|
-
const
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
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
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
const
|
|
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
|
-
|
|
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(
|
|
267
|
+
dispose$1(mesh);
|
|
386
268
|
}
|
|
387
269
|
}
|
|
388
|
-
function
|
|
389
|
-
|
|
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
|
-
|
|
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(
|
|
275
|
+
dispose$1(sphere);
|
|
412
276
|
}
|
|
413
277
|
}
|
|
414
|
-
function
|
|
415
|
-
|
|
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
|
-
|
|
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(
|
|
283
|
+
dispose$1(cylinder);
|
|
459
284
|
}
|
|
460
285
|
}
|
|
461
|
-
function
|
|
462
|
-
const
|
|
463
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
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
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
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
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
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
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
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
|
|
788
|
-
|
|
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
|
|
799
|
-
const
|
|
800
|
-
const
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
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
|
-
|
|
815
|
-
|
|
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
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
851
|
-
|
|
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
|
|
918
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
981
|
-
|
|
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
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
if (
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
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
|
|
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.
|
|
1001
|
-
|
|
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
|
|
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
|
-
|
|
1077
|
-
wrapper.
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
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
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
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
|
|
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
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
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 (!
|
|
1282
|
-
if (mergedOptions.cleanupAfterParse && !
|
|
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
|
-
|
|
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 };
|