three-usd-robot 0.4.0 → 0.6.0
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 +136 -35
- package/dist/AssetResolver-CpIJNgWZ.d.ts +29 -0
- package/dist/{ThreeUsdRobot-Deoh7zam.d.ts → ThreeUsdRobot-CajXJqYU.d.ts} +1 -1
- package/dist/ThreeUsdRobotLoader-DrMMLzo-.d.ts +67 -0
- package/dist/{buildKinematicTree-CZjBMA1I.d.ts → buildKinematicTree-wtZPzy0B.d.ts} +53 -16
- package/dist/{chunk-FYVZ7YPW.js → chunk-4MEZM763.js} +3153 -2618
- package/dist/chunk-4MEZM763.js.map +1 -0
- package/dist/chunk-JZMCABQO.js +864 -0
- package/dist/chunk-JZMCABQO.js.map +1 -0
- package/dist/chunk-YVWSO27W.js +709 -0
- package/dist/chunk-YVWSO27W.js.map +1 -0
- package/dist/core.d.ts +297 -134
- package/dist/core.js +2 -1
- package/dist/extras.d.ts +2 -2
- package/dist/helpers.d.ts +2 -2
- package/dist/index.d.ts +229 -83
- package/dist/index.js +271 -631
- package/dist/index.js.map +1 -1
- package/dist/react.d.ts +72 -0
- package/dist/react.js +92 -0
- package/dist/react.js.map +1 -0
- package/package.json +22 -1
- package/dist/chunk-FYVZ7YPW.js.map +0 -1
|
@@ -0,0 +1,709 @@
|
|
|
1
|
+
import { resolveBoundMaterial, COLLISION_API, isNonVisualPurpose, computeWorldTransform, identity4, multiply, computeLocalTransform, invert, interpolate, DefaultAssetResolver, CrateReader, openUsdz, extractRobotDescription, buildKinematicTree, Stage, composeLayer, composeFile, crateToUsdaFile } from './chunk-4MEZM763.js';
|
|
2
|
+
import * as THREE4 from 'three';
|
|
3
|
+
|
|
4
|
+
function axisVector(axis) {
|
|
5
|
+
switch (axis) {
|
|
6
|
+
case "X":
|
|
7
|
+
return new THREE4.Vector3(1, 0, 0);
|
|
8
|
+
case "Y":
|
|
9
|
+
return new THREE4.Vector3(0, 1, 0);
|
|
10
|
+
case "Z":
|
|
11
|
+
return new THREE4.Vector3(0, 0, 1);
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
var JointObject = class extends THREE4.Object3D {
|
|
15
|
+
isJointObject = true;
|
|
16
|
+
jointName;
|
|
17
|
+
jointType;
|
|
18
|
+
axisToken;
|
|
19
|
+
axis;
|
|
20
|
+
lower;
|
|
21
|
+
upper;
|
|
22
|
+
_value = 0;
|
|
23
|
+
constructor(joint) {
|
|
24
|
+
super();
|
|
25
|
+
this.name = joint.name;
|
|
26
|
+
this.jointName = joint.name;
|
|
27
|
+
this.jointType = joint.type;
|
|
28
|
+
this.axisToken = joint.axis;
|
|
29
|
+
this.axis = axisVector(joint.axis);
|
|
30
|
+
this.lower = joint.lower;
|
|
31
|
+
this.upper = joint.upper;
|
|
32
|
+
}
|
|
33
|
+
get value() {
|
|
34
|
+
return this._value;
|
|
35
|
+
}
|
|
36
|
+
get articulated() {
|
|
37
|
+
return this.jointType !== "fixed";
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Set the joint value (radians for revolute/continuous, length for prismatic).
|
|
41
|
+
* Optionally clamps to authored limits. Returns the value actually applied.
|
|
42
|
+
*/
|
|
43
|
+
setValue(value, clampToLimits = true) {
|
|
44
|
+
if (!this.articulated) return this._value;
|
|
45
|
+
let v = value;
|
|
46
|
+
if (clampToLimits) {
|
|
47
|
+
if (this.lower !== void 0 && v < this.lower) v = this.lower;
|
|
48
|
+
if (this.upper !== void 0 && v > this.upper) v = this.upper;
|
|
49
|
+
}
|
|
50
|
+
this._value = v;
|
|
51
|
+
if (this.jointType === "prismatic") {
|
|
52
|
+
this.position.copy(this.axis).multiplyScalar(v);
|
|
53
|
+
this.quaternion.identity();
|
|
54
|
+
} else {
|
|
55
|
+
this.quaternion.setFromAxisAngle(this.axis, v);
|
|
56
|
+
this.position.set(0, 0, 0);
|
|
57
|
+
}
|
|
58
|
+
return v;
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
var LinkObject = class extends THREE4.Object3D {
|
|
62
|
+
isLinkObject = true;
|
|
63
|
+
linkName;
|
|
64
|
+
primPath;
|
|
65
|
+
constructor(link) {
|
|
66
|
+
super();
|
|
67
|
+
this.name = link.name;
|
|
68
|
+
this.linkName = link.name;
|
|
69
|
+
this.primPath = link.primPath;
|
|
70
|
+
this.matrixAutoUpdate = false;
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
var DEFAULT_COLOR = 10132122;
|
|
74
|
+
function buildMeshGeometry(meshPrim) {
|
|
75
|
+
const points = meshPrim.GetAttribute("points").Get();
|
|
76
|
+
if (!isVec3Array(points) || points.length === 0) return null;
|
|
77
|
+
const geometry = new THREE4.BufferGeometry();
|
|
78
|
+
geometry.setAttribute("position", new THREE4.Float32BufferAttribute(flat3(points), 3));
|
|
79
|
+
const counts = meshPrim.GetAttribute("faceVertexCounts").Get();
|
|
80
|
+
const indices = meshPrim.GetAttribute("faceVertexIndices").Get();
|
|
81
|
+
if (isNumberArray(counts) && isNumberArray(indices)) {
|
|
82
|
+
geometry.setIndex(triangulate(counts, indices));
|
|
83
|
+
} else if (isNumberArray(indices)) {
|
|
84
|
+
geometry.setIndex(indices.slice());
|
|
85
|
+
}
|
|
86
|
+
const normals = meshPrim.GetAttribute("normals").Get();
|
|
87
|
+
if (isVec3Array(normals) && normals.length === points.length) {
|
|
88
|
+
geometry.setAttribute("normal", new THREE4.Float32BufferAttribute(flat3(normals), 3));
|
|
89
|
+
} else {
|
|
90
|
+
geometry.computeVertexNormals();
|
|
91
|
+
}
|
|
92
|
+
const st = meshPrim.GetAttribute("primvars:st").Get();
|
|
93
|
+
if (isVec2Array(st) && st.length === points.length) {
|
|
94
|
+
geometry.setAttribute("uv", new THREE4.Float32BufferAttribute(flat2(st), 2));
|
|
95
|
+
}
|
|
96
|
+
return geometry;
|
|
97
|
+
}
|
|
98
|
+
function buildMeshMaterial(meshPrim, stage, textures) {
|
|
99
|
+
const color = new THREE4.Color(DEFAULT_COLOR);
|
|
100
|
+
let opacity = 1;
|
|
101
|
+
const bound = stage ? resolveBoundMaterial(stage, meshPrim) : void 0;
|
|
102
|
+
if (bound?.color) {
|
|
103
|
+
color.setRGB(bound.color[0], bound.color[1], bound.color[2]);
|
|
104
|
+
} else {
|
|
105
|
+
const displayColor = meshPrim.GetAttribute("primvars:displayColor").Get();
|
|
106
|
+
if (isVec3Array(displayColor) && displayColor[0]) {
|
|
107
|
+
const [r, g, b] = displayColor[0];
|
|
108
|
+
color.setRGB(r, g, b);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
if (bound?.opacity !== void 0) opacity = bound.opacity;
|
|
112
|
+
const tex = (rt, cs) => rt && textures ? textures(rt.path, {
|
|
113
|
+
colorSpace: cs,
|
|
114
|
+
...rt.wrapS ? { wrapS: rt.wrapS } : {},
|
|
115
|
+
...rt.wrapT ? { wrapT: rt.wrapT } : {},
|
|
116
|
+
...rt.transform ? { transform: rt.transform } : {}
|
|
117
|
+
}) : null;
|
|
118
|
+
const map = tex(bound?.colorTexture, "srgb");
|
|
119
|
+
if (map) {
|
|
120
|
+
const s = bound?.colorTexture?.scale;
|
|
121
|
+
if (s) color.setRGB(s[0], s[1], s[2]);
|
|
122
|
+
else color.setRGB(1, 1, 1);
|
|
123
|
+
}
|
|
124
|
+
const normalMap = tex(bound?.normalTexture, "linear");
|
|
125
|
+
const roughnessMap = tex(bound?.roughnessTexture, "linear");
|
|
126
|
+
const metalnessMap = tex(bound?.metalnessTexture, "linear");
|
|
127
|
+
const aoMap = tex(bound?.occlusionTexture, "linear");
|
|
128
|
+
const emissiveMap = tex(bound?.emissiveTexture, "srgb");
|
|
129
|
+
const metalness = bound?.metalness ?? bound?.metalnessTexture?.scale?.[0] ?? (metalnessMap ? 1 : 0.1);
|
|
130
|
+
const roughness = bound?.roughness ?? bound?.roughnessTexture?.scale?.[0] ?? (roughnessMap ? 1 : 0.8);
|
|
131
|
+
const emissive = new THREE4.Color(0);
|
|
132
|
+
if (bound?.emissiveColor) {
|
|
133
|
+
emissive.setRGB(bound.emissiveColor[0], bound.emissiveColor[1], bound.emissiveColor[2]);
|
|
134
|
+
} else if (emissiveMap) {
|
|
135
|
+
emissive.setRGB(1, 1, 1);
|
|
136
|
+
}
|
|
137
|
+
const opacityTex = bound?.opacityTexture;
|
|
138
|
+
const sharesColorMap = !!(opacityTex && opacityTex.path === bound?.colorTexture?.path);
|
|
139
|
+
const alphaMap = sharesColorMap ? null : tex(opacityTex, "linear");
|
|
140
|
+
const hasAlphaSource = opacity < 1 || sharesColorMap || !!alphaMap;
|
|
141
|
+
const threshold = bound?.opacityThreshold;
|
|
142
|
+
const alphaTest = threshold !== void 0 && threshold > 0 ? threshold : 0;
|
|
143
|
+
const transparent = alphaTest === 0 && hasAlphaSource;
|
|
144
|
+
const doubleSided = meshPrim.GetAttribute("doubleSided").Get() === true;
|
|
145
|
+
return new THREE4.MeshStandardMaterial({
|
|
146
|
+
color,
|
|
147
|
+
metalness,
|
|
148
|
+
roughness,
|
|
149
|
+
emissive,
|
|
150
|
+
transparent,
|
|
151
|
+
opacity,
|
|
152
|
+
...alphaTest > 0 ? { alphaTest } : {},
|
|
153
|
+
side: doubleSided ? THREE4.DoubleSide : THREE4.FrontSide,
|
|
154
|
+
...map ? { map } : {},
|
|
155
|
+
...alphaMap ? { alphaMap } : {},
|
|
156
|
+
...normalMap ? { normalMap } : {},
|
|
157
|
+
...roughnessMap ? { roughnessMap } : {},
|
|
158
|
+
...metalnessMap ? { metalnessMap } : {},
|
|
159
|
+
...aoMap ? { aoMap } : {},
|
|
160
|
+
...emissiveMap ? { emissiveMap } : {}
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
function bindRobotMeshes(stage, robot3d, desc, options = {}) {
|
|
164
|
+
const loadVisuals = options.loadVisuals ?? true;
|
|
165
|
+
const loadCollisions = options.loadCollisions ?? false;
|
|
166
|
+
const textures = options.textureProvider;
|
|
167
|
+
for (const [key, link] of Object.entries(desc.links)) {
|
|
168
|
+
const linkObj = robot3d.getLinkObject(key);
|
|
169
|
+
const linkPrim = stage.GetPrimAtPath(link.primPath);
|
|
170
|
+
if (!linkObj || !linkPrim) continue;
|
|
171
|
+
const collisionSet = new Set(link.collisionPrims ?? []);
|
|
172
|
+
if (loadVisuals) {
|
|
173
|
+
for (const meshPath of link.visualPrims) {
|
|
174
|
+
if (collisionSet.has(meshPath)) continue;
|
|
175
|
+
attachMesh(stage, linkPrim, meshPath, linkObj, "visual", textures);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
if (loadCollisions) {
|
|
179
|
+
for (const meshPath of link.collisionPrims ?? []) {
|
|
180
|
+
attachMesh(stage, linkPrim, meshPath, linkObj, "collision", textures);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
function bindSceneMeshes(stage, robot3d, desc, options = {}) {
|
|
186
|
+
const owned = /* @__PURE__ */ new Set();
|
|
187
|
+
for (const link of Object.values(desc.links)) {
|
|
188
|
+
for (const path of link.visualPrims) owned.add(path);
|
|
189
|
+
for (const path of link.collisionPrims ?? []) owned.add(path);
|
|
190
|
+
owned.add(link.primPath);
|
|
191
|
+
}
|
|
192
|
+
let attached = 0;
|
|
193
|
+
for (const prim of stage.Traverse()) {
|
|
194
|
+
if (prim.GetTypeName() !== "Mesh" || owned.has(prim.GetPath())) continue;
|
|
195
|
+
if (prim.HasAPI(COLLISION_API) || isNonVisualPurpose(prim)) continue;
|
|
196
|
+
if ([...owned].some((path) => prim.GetPath().startsWith(`${path}/`))) continue;
|
|
197
|
+
const geometry = buildMeshGeometry(prim);
|
|
198
|
+
if (!geometry) continue;
|
|
199
|
+
const mesh = new THREE4.Mesh(geometry, buildMeshMaterial(prim, stage, options.textureProvider));
|
|
200
|
+
mesh.name = prim.GetName();
|
|
201
|
+
mesh.userData.kind = "scene";
|
|
202
|
+
mesh.userData.primPath = prim.GetPath();
|
|
203
|
+
mesh.matrixAutoUpdate = false;
|
|
204
|
+
mesh.matrix.fromArray(computeWorldTransform(prim));
|
|
205
|
+
mesh.matrixWorldNeedsUpdate = true;
|
|
206
|
+
robot3d.add(mesh);
|
|
207
|
+
attached++;
|
|
208
|
+
}
|
|
209
|
+
return attached;
|
|
210
|
+
}
|
|
211
|
+
function attachMesh(stage, linkPrim, meshPath, parent, kind, textures) {
|
|
212
|
+
const meshPrim = stage.GetPrimAtPath(meshPath);
|
|
213
|
+
if (!meshPrim) return;
|
|
214
|
+
const geometry = buildMeshGeometry(meshPrim);
|
|
215
|
+
if (!geometry) return;
|
|
216
|
+
const mesh = new THREE4.Mesh(geometry, buildMeshMaterial(meshPrim, stage, textures));
|
|
217
|
+
mesh.name = meshPrim.GetName();
|
|
218
|
+
mesh.userData.kind = kind;
|
|
219
|
+
mesh.userData.primPath = meshPath;
|
|
220
|
+
if (kind === "collision") mesh.visible = false;
|
|
221
|
+
mesh.matrixAutoUpdate = false;
|
|
222
|
+
mesh.matrix.fromArray(relativeTransform(linkPrim, meshPrim));
|
|
223
|
+
mesh.matrixWorldNeedsUpdate = true;
|
|
224
|
+
parent.add(mesh);
|
|
225
|
+
}
|
|
226
|
+
function relativeTransform(linkPrim, meshPrim) {
|
|
227
|
+
const chain = [];
|
|
228
|
+
let p = meshPrim;
|
|
229
|
+
const stop = linkPrim.GetPath();
|
|
230
|
+
while (p && p.GetPath() !== stop) {
|
|
231
|
+
chain.push(p);
|
|
232
|
+
p = p.GetParent();
|
|
233
|
+
}
|
|
234
|
+
chain.reverse();
|
|
235
|
+
let m = identity4();
|
|
236
|
+
for (const prim of chain) {
|
|
237
|
+
m = multiply(m, computeLocalTransform(prim).matrix);
|
|
238
|
+
}
|
|
239
|
+
return m;
|
|
240
|
+
}
|
|
241
|
+
function triangulate(faceVertexCounts, faceVertexIndices) {
|
|
242
|
+
const tris = [];
|
|
243
|
+
let offset = 0;
|
|
244
|
+
for (const count of faceVertexCounts) {
|
|
245
|
+
for (let k = 2; k < count; k++) {
|
|
246
|
+
tris.push(
|
|
247
|
+
faceVertexIndices[offset],
|
|
248
|
+
faceVertexIndices[offset + k - 1],
|
|
249
|
+
faceVertexIndices[offset + k]
|
|
250
|
+
);
|
|
251
|
+
}
|
|
252
|
+
offset += count;
|
|
253
|
+
}
|
|
254
|
+
return tris;
|
|
255
|
+
}
|
|
256
|
+
function flat3(v) {
|
|
257
|
+
const out = new Array(v.length * 3);
|
|
258
|
+
for (let i = 0; i < v.length; i++) {
|
|
259
|
+
out[i * 3] = v[i][0];
|
|
260
|
+
out[i * 3 + 1] = v[i][1];
|
|
261
|
+
out[i * 3 + 2] = v[i][2];
|
|
262
|
+
}
|
|
263
|
+
return out;
|
|
264
|
+
}
|
|
265
|
+
function flat2(v) {
|
|
266
|
+
const out = new Array(v.length * 2);
|
|
267
|
+
for (let i = 0; i < v.length; i++) {
|
|
268
|
+
out[i * 2] = v[i][0];
|
|
269
|
+
out[i * 2 + 1] = v[i][1];
|
|
270
|
+
}
|
|
271
|
+
return out;
|
|
272
|
+
}
|
|
273
|
+
function isNumberArray(v) {
|
|
274
|
+
return Array.isArray(v) && v.every((n) => typeof n === "number");
|
|
275
|
+
}
|
|
276
|
+
function isVec3Array(v) {
|
|
277
|
+
return Array.isArray(v) && v.every((e) => Array.isArray(e) && e.length === 3);
|
|
278
|
+
}
|
|
279
|
+
function isVec2Array(v) {
|
|
280
|
+
return Array.isArray(v) && v.every((e) => Array.isArray(e) && e.length === 2);
|
|
281
|
+
}
|
|
282
|
+
var DEG2RAD = Math.PI / 180;
|
|
283
|
+
function createTextureProvider(resolver, baseUrl) {
|
|
284
|
+
const images = /* @__PURE__ */ new Map();
|
|
285
|
+
const imageFor = (url) => {
|
|
286
|
+
let p = images.get(url);
|
|
287
|
+
if (!p) {
|
|
288
|
+
p = loadImage(resolver, url);
|
|
289
|
+
images.set(url, p);
|
|
290
|
+
}
|
|
291
|
+
return p;
|
|
292
|
+
};
|
|
293
|
+
return (assetPath, options = {}) => {
|
|
294
|
+
let url;
|
|
295
|
+
try {
|
|
296
|
+
url = resolver.resolve(assetPath, baseUrl);
|
|
297
|
+
} catch {
|
|
298
|
+
return null;
|
|
299
|
+
}
|
|
300
|
+
const texture = new THREE4.Texture();
|
|
301
|
+
texture.colorSpace = options.colorSpace === "linear" ? THREE4.NoColorSpace : THREE4.SRGBColorSpace;
|
|
302
|
+
texture.wrapS = toThreeWrap(options.wrapS);
|
|
303
|
+
texture.wrapT = toThreeWrap(options.wrapT);
|
|
304
|
+
applyTransform(texture, options.transform);
|
|
305
|
+
imageFor(url).then((image) => {
|
|
306
|
+
texture.image = image;
|
|
307
|
+
texture.needsUpdate = true;
|
|
308
|
+
}).catch(() => {
|
|
309
|
+
});
|
|
310
|
+
return texture;
|
|
311
|
+
};
|
|
312
|
+
}
|
|
313
|
+
async function loadImage(resolver, url) {
|
|
314
|
+
const bytes = resolver.fetchBytes ? await resolver.fetchBytes(url) : new TextEncoder().encode(await resolver.fetchText(url));
|
|
315
|
+
const blob = new Blob([bytes], { type: mimeOf(url) });
|
|
316
|
+
const objectUrl = URL.createObjectURL(blob);
|
|
317
|
+
try {
|
|
318
|
+
return await new Promise((resolve, reject) => {
|
|
319
|
+
const img = new Image();
|
|
320
|
+
img.onload = () => resolve(img);
|
|
321
|
+
img.onerror = () => reject(new Error(`failed to decode texture: ${url}`));
|
|
322
|
+
img.src = objectUrl;
|
|
323
|
+
});
|
|
324
|
+
} finally {
|
|
325
|
+
URL.revokeObjectURL(objectUrl);
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
function toThreeWrap(wrap) {
|
|
329
|
+
switch (wrap) {
|
|
330
|
+
case "clamp":
|
|
331
|
+
case "black":
|
|
332
|
+
return THREE4.ClampToEdgeWrapping;
|
|
333
|
+
case "mirror":
|
|
334
|
+
return THREE4.MirroredRepeatWrapping;
|
|
335
|
+
default:
|
|
336
|
+
return THREE4.RepeatWrapping;
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
function applyTransform(texture, t) {
|
|
340
|
+
if (!t) return;
|
|
341
|
+
if (t.scale) texture.repeat.set(t.scale[0], t.scale[1]);
|
|
342
|
+
if (t.translation) texture.offset.set(t.translation[0], t.translation[1]);
|
|
343
|
+
if (t.rotation !== void 0) {
|
|
344
|
+
texture.rotation = t.rotation * DEG2RAD;
|
|
345
|
+
texture.center.set(0, 0);
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
function mimeOf(url) {
|
|
349
|
+
if (/\.jpe?g$/i.test(url)) return "image/jpeg";
|
|
350
|
+
if (/\.webp$/i.test(url)) return "image/webp";
|
|
351
|
+
return "image/png";
|
|
352
|
+
}
|
|
353
|
+
var ThreeUsdRobot = class extends THREE4.Object3D {
|
|
354
|
+
isThreeUsdRobot = true;
|
|
355
|
+
robot;
|
|
356
|
+
tree;
|
|
357
|
+
clampJointLimits;
|
|
358
|
+
linkObjects = /* @__PURE__ */ new Map();
|
|
359
|
+
jointObjects = /* @__PURE__ */ new Map();
|
|
360
|
+
dirty = true;
|
|
361
|
+
helperSize;
|
|
362
|
+
_showVisual = true;
|
|
363
|
+
_showCollision = false;
|
|
364
|
+
_showJointAxes = false;
|
|
365
|
+
_showLinkFrames = false;
|
|
366
|
+
jointAxesHelpers = [];
|
|
367
|
+
linkFrameHelpers = [];
|
|
368
|
+
constructor(robot, tree, options = {}) {
|
|
369
|
+
super();
|
|
370
|
+
this.name = robot.name;
|
|
371
|
+
this.robot = robot;
|
|
372
|
+
this.tree = tree;
|
|
373
|
+
this.clampJointLimits = options.clampJointLimits ?? true;
|
|
374
|
+
this.helperSize = options.helperSize ?? 0.15;
|
|
375
|
+
for (const [key, link] of Object.entries(robot.links)) {
|
|
376
|
+
this.linkObjects.set(key, new LinkObject(link));
|
|
377
|
+
}
|
|
378
|
+
this.attachRoot();
|
|
379
|
+
this.attachTreeEdges();
|
|
380
|
+
this.attachIsolatedLinks();
|
|
381
|
+
this.applyStageNormalization(robot, options);
|
|
382
|
+
if (options.applyInitialPose ?? true) this.applyInitialPose(robot);
|
|
383
|
+
}
|
|
384
|
+
/** Orient (Z-up → Y-up) and scale (metersPerUnit × unitScale) the robot root. */
|
|
385
|
+
applyStageNormalization(robot, options) {
|
|
386
|
+
const scale = (robot.metersPerUnit || 1) * (options.unitScale ?? 1);
|
|
387
|
+
if (scale !== 1) this.scale.setScalar(scale);
|
|
388
|
+
const conv = options.upAxisConversion ?? "none";
|
|
389
|
+
const toY = conv === "Z" || conv === "auto" && robot.upAxis === "Z";
|
|
390
|
+
if (toY) this.quaternion.setFromAxisAngle(new THREE4.Vector3(1, 0, 0), -Math.PI / 2);
|
|
391
|
+
}
|
|
392
|
+
/** Apply each joint's authored initial value, if any. */
|
|
393
|
+
applyInitialPose(robot) {
|
|
394
|
+
for (const [key, joint] of Object.entries(robot.joints)) {
|
|
395
|
+
if (joint.initialValue === void 0) continue;
|
|
396
|
+
this.jointObjects.get(key)?.setValue(joint.initialValue, this.clampJointLimits);
|
|
397
|
+
}
|
|
398
|
+
this.dirty = true;
|
|
399
|
+
}
|
|
400
|
+
attachRoot() {
|
|
401
|
+
const rootObj = this.linkObjects.get(this.tree.root);
|
|
402
|
+
if (!rootObj) return;
|
|
403
|
+
const rootJointKey = this.tree.rootJoint;
|
|
404
|
+
const rootLink = this.robot.links[this.tree.root];
|
|
405
|
+
if (rootJointKey) {
|
|
406
|
+
const j = this.robot.joints[rootJointKey];
|
|
407
|
+
if (j) setMatrix(rootObj, multiply(j.jointFrame0, invert(j.jointFrame1)));
|
|
408
|
+
} else if (rootLink?.worldTransform) {
|
|
409
|
+
setMatrix(rootObj, rootLink.worldTransform);
|
|
410
|
+
}
|
|
411
|
+
this.add(rootObj);
|
|
412
|
+
}
|
|
413
|
+
attachTreeEdges() {
|
|
414
|
+
for (const linkKey of this.tree.order) {
|
|
415
|
+
const node = this.tree.nodes[linkKey];
|
|
416
|
+
if (!node || node.parent === null || node.jointToParent === null) continue;
|
|
417
|
+
const parentObj = this.linkObjects.get(node.parent);
|
|
418
|
+
const childObj = this.linkObjects.get(linkKey);
|
|
419
|
+
const joint = this.robot.joints[node.jointToParent];
|
|
420
|
+
if (!parentObj || !childObj || !joint) continue;
|
|
421
|
+
this.attachJointChain(parentObj, childObj, node.jointToParent, joint);
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
/** Build `parent → frame0 → motion → frame1⁻¹ → child` for one joint. */
|
|
425
|
+
attachJointChain(parent, child, jointKey, joint) {
|
|
426
|
+
const frame0 = new THREE4.Group();
|
|
427
|
+
frame0.name = `${joint.name}:frame0`;
|
|
428
|
+
setMatrix(frame0, joint.jointFrame0);
|
|
429
|
+
const motion = new JointObject(joint);
|
|
430
|
+
const frame1Inv = new THREE4.Group();
|
|
431
|
+
frame1Inv.name = `${joint.name}:frame1Inv`;
|
|
432
|
+
setMatrix(frame1Inv, invert(joint.jointFrame1));
|
|
433
|
+
parent.add(frame0);
|
|
434
|
+
frame0.add(motion);
|
|
435
|
+
motion.add(frame1Inv);
|
|
436
|
+
frame1Inv.add(child);
|
|
437
|
+
this.jointObjects.set(jointKey, motion);
|
|
438
|
+
}
|
|
439
|
+
attachIsolatedLinks() {
|
|
440
|
+
for (const key of this.tree.isolatedLinks) {
|
|
441
|
+
const obj = this.linkObjects.get(key);
|
|
442
|
+
if (!obj || obj.parent) continue;
|
|
443
|
+
const worldTransform = this.robot.links[key]?.worldTransform;
|
|
444
|
+
if (worldTransform) setMatrix(obj, worldTransform);
|
|
445
|
+
this.add(obj);
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
// -- Joint control -------------------------------------------------------
|
|
449
|
+
/** Set one joint value. Unknown joints are ignored. Returns whether it applied. */
|
|
450
|
+
setJointValue(name, value) {
|
|
451
|
+
const joint = this.jointObjects.get(name);
|
|
452
|
+
if (!joint) return false;
|
|
453
|
+
joint.setValue(value, this.clampJointLimits);
|
|
454
|
+
this.dirty = true;
|
|
455
|
+
return true;
|
|
456
|
+
}
|
|
457
|
+
/** Set several joint values at once (matrix update is coalesced). */
|
|
458
|
+
setJointValues(values) {
|
|
459
|
+
for (const [name, value] of Object.entries(values)) {
|
|
460
|
+
const joint = this.jointObjects.get(name);
|
|
461
|
+
if (joint) {
|
|
462
|
+
joint.setValue(value, this.clampJointLimits);
|
|
463
|
+
this.dirty = true;
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
getJointValue(name) {
|
|
468
|
+
return this.jointObjects.get(name)?.value;
|
|
469
|
+
}
|
|
470
|
+
/** Recompute world matrices. Called lazily by the getters; safe to call directly. */
|
|
471
|
+
updateKinematics() {
|
|
472
|
+
this.updateMatrixWorld(true);
|
|
473
|
+
this.dirty = false;
|
|
474
|
+
}
|
|
475
|
+
ensureUpdated() {
|
|
476
|
+
if (this.dirty) this.updateKinematics();
|
|
477
|
+
}
|
|
478
|
+
// -- Queries -------------------------------------------------------------
|
|
479
|
+
getLinkWorldMatrix(name) {
|
|
480
|
+
const obj = this.linkObjects.get(name);
|
|
481
|
+
if (!obj) throw new Error(`unknown link "${name}"`);
|
|
482
|
+
this.ensureUpdated();
|
|
483
|
+
return obj.matrixWorld.clone();
|
|
484
|
+
}
|
|
485
|
+
getLinkWorldPosition(name) {
|
|
486
|
+
return new THREE4.Vector3().setFromMatrixPosition(this.getLinkWorldMatrix(name));
|
|
487
|
+
}
|
|
488
|
+
getLinkObject(name) {
|
|
489
|
+
return this.linkObjects.get(name);
|
|
490
|
+
}
|
|
491
|
+
getJointObject(name) {
|
|
492
|
+
return this.jointObjects.get(name);
|
|
493
|
+
}
|
|
494
|
+
getJoints() {
|
|
495
|
+
return Object.values(this.robot.joints);
|
|
496
|
+
}
|
|
497
|
+
getLinks() {
|
|
498
|
+
return Object.values(this.robot.links);
|
|
499
|
+
}
|
|
500
|
+
/** Names of the articulated (controllable) joints. */
|
|
501
|
+
getJointNames() {
|
|
502
|
+
return [...this.jointObjects.keys()];
|
|
503
|
+
}
|
|
504
|
+
getLinkNames() {
|
|
505
|
+
return [...this.linkObjects.keys()];
|
|
506
|
+
}
|
|
507
|
+
getKinematicTree() {
|
|
508
|
+
return this.tree;
|
|
509
|
+
}
|
|
510
|
+
// -- Animation playback --------------------------------------------------
|
|
511
|
+
/** Playback rate in time codes per second (from the stage; default 24). */
|
|
512
|
+
getTimeCodesPerSecond() {
|
|
513
|
+
return this.robot.timeCodesPerSecond ?? 24;
|
|
514
|
+
}
|
|
515
|
+
/** Whether any joint has a time-sampled trajectory. */
|
|
516
|
+
hasAnimation() {
|
|
517
|
+
return Object.values(this.robot.joints).some((j) => j.valueSamples !== void 0);
|
|
518
|
+
}
|
|
519
|
+
/**
|
|
520
|
+
* Animation range in time codes: the union of authored joint sample ranges,
|
|
521
|
+
* falling back to the stage `startTimeCode`/`endTimeCode`. `null` if neither.
|
|
522
|
+
*/
|
|
523
|
+
getTimeRange() {
|
|
524
|
+
let start = Number.POSITIVE_INFINITY;
|
|
525
|
+
let end = Number.NEGATIVE_INFINITY;
|
|
526
|
+
for (const joint of Object.values(this.robot.joints)) {
|
|
527
|
+
const times = joint.valueSamples?.times;
|
|
528
|
+
if (!times || times.length === 0) continue;
|
|
529
|
+
start = Math.min(start, times[0]);
|
|
530
|
+
end = Math.max(end, times[times.length - 1]);
|
|
531
|
+
}
|
|
532
|
+
if (start <= end) return { start, end };
|
|
533
|
+
const { startTimeCode, endTimeCode } = this.robot;
|
|
534
|
+
if (startTimeCode !== void 0 && endTimeCode !== void 0) {
|
|
535
|
+
return { start: startTimeCode, end: endTimeCode };
|
|
536
|
+
}
|
|
537
|
+
return null;
|
|
538
|
+
}
|
|
539
|
+
/** Sample every animated joint at time code `t` and apply the values. */
|
|
540
|
+
setTime(t) {
|
|
541
|
+
for (const [key, joint] of Object.entries(this.robot.joints)) {
|
|
542
|
+
if (joint.valueSamples) this.setJointValue(key, interpolate(joint.valueSamples, t));
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
// -- Display toggles -----------------------------------------------------
|
|
546
|
+
get showVisual() {
|
|
547
|
+
return this._showVisual;
|
|
548
|
+
}
|
|
549
|
+
set showVisual(v) {
|
|
550
|
+
this._showVisual = v;
|
|
551
|
+
this.setKindVisibility("visual", v);
|
|
552
|
+
}
|
|
553
|
+
get showCollision() {
|
|
554
|
+
return this._showCollision;
|
|
555
|
+
}
|
|
556
|
+
set showCollision(v) {
|
|
557
|
+
this._showCollision = v;
|
|
558
|
+
this.setKindVisibility("collision", v);
|
|
559
|
+
}
|
|
560
|
+
get showJointAxes() {
|
|
561
|
+
return this._showJointAxes;
|
|
562
|
+
}
|
|
563
|
+
set showJointAxes(v) {
|
|
564
|
+
this._showJointAxes = v;
|
|
565
|
+
if (v && this.jointAxesHelpers.length === 0) {
|
|
566
|
+
for (const joint of this.jointObjects.values()) {
|
|
567
|
+
const h = new THREE4.AxesHelper(this.helperSize);
|
|
568
|
+
h.name = `${joint.jointName}:axes`;
|
|
569
|
+
joint.add(h);
|
|
570
|
+
this.jointAxesHelpers.push(h);
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
for (const h of this.jointAxesHelpers) h.visible = v;
|
|
574
|
+
}
|
|
575
|
+
get showLinkFrames() {
|
|
576
|
+
return this._showLinkFrames;
|
|
577
|
+
}
|
|
578
|
+
set showLinkFrames(v) {
|
|
579
|
+
this._showLinkFrames = v;
|
|
580
|
+
if (v && this.linkFrameHelpers.length === 0) {
|
|
581
|
+
for (const link of this.linkObjects.values()) {
|
|
582
|
+
const h = new THREE4.AxesHelper(this.helperSize);
|
|
583
|
+
h.name = `${link.linkName}:frame`;
|
|
584
|
+
link.add(h);
|
|
585
|
+
this.linkFrameHelpers.push(h);
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
for (const h of this.linkFrameHelpers) h.visible = v;
|
|
589
|
+
}
|
|
590
|
+
setKindVisibility(kind, visible) {
|
|
591
|
+
this.traverse((o) => {
|
|
592
|
+
if (o.userData.kind === kind) o.visible = visible;
|
|
593
|
+
});
|
|
594
|
+
}
|
|
595
|
+
};
|
|
596
|
+
function setMatrix(obj, m) {
|
|
597
|
+
obj.matrixAutoUpdate = false;
|
|
598
|
+
obj.matrix.fromArray(m);
|
|
599
|
+
obj.matrixWorldNeedsUpdate = true;
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
// src/three/ThreeUsdRobotLoader.ts
|
|
603
|
+
var ThreeUsdRobotLoader = class {
|
|
604
|
+
options;
|
|
605
|
+
constructor(options = {}) {
|
|
606
|
+
this.options = options;
|
|
607
|
+
}
|
|
608
|
+
get resolver() {
|
|
609
|
+
return this.options.assetResolver ?? new DefaultAssetResolver();
|
|
610
|
+
}
|
|
611
|
+
/**
|
|
612
|
+
* Fetch an asset by URL and build the robot. `.usdz` packages are unzipped and
|
|
613
|
+
* composed from their entries; everything else is sniffed for the crate magic
|
|
614
|
+
* and parsed as binary USDC or USDA text.
|
|
615
|
+
*/
|
|
616
|
+
async loadAsync(url) {
|
|
617
|
+
const bytes = await this.fetchRootBytes(url);
|
|
618
|
+
if (/\.usdz$/i.test(url)) return this.parseUsdz(bytes);
|
|
619
|
+
if (CrateReader.isCrate(bytes)) return this.parseCrate(bytes, url);
|
|
620
|
+
return this.parse(new TextDecoder().decode(bytes), url);
|
|
621
|
+
}
|
|
622
|
+
async fetchRootBytes(url) {
|
|
623
|
+
const resolver = this.resolver;
|
|
624
|
+
if (resolver.fetchBytes) return resolver.fetchBytes(url);
|
|
625
|
+
return new TextEncoder().encode(await resolver.fetchText(url));
|
|
626
|
+
}
|
|
627
|
+
/** Build a robot (composed, with meshes) from USDA source text. */
|
|
628
|
+
async parse(text, baseUrl = "") {
|
|
629
|
+
return this.buildFromStage(
|
|
630
|
+
await this.composeStage(text, baseUrl, this.resolver),
|
|
631
|
+
baseUrl,
|
|
632
|
+
this.resolver
|
|
633
|
+
);
|
|
634
|
+
}
|
|
635
|
+
/** Build a robot from the bytes of a `.usdz` package. */
|
|
636
|
+
async parseUsdz(bytes) {
|
|
637
|
+
const pkg = openUsdz(bytes);
|
|
638
|
+
const rootBytes = await pkg.resolver.fetchBytes(pkg.rootEntry);
|
|
639
|
+
const stage = await this.composeStageFromBytes(rootBytes, pkg.rootEntry, pkg.resolver);
|
|
640
|
+
return this.buildFromStage(stage, pkg.rootEntry, pkg.resolver);
|
|
641
|
+
}
|
|
642
|
+
/** Build a robot from the bytes of a binary crate (`.usdc` / binary `.usd`). */
|
|
643
|
+
async parseCrate(bytes, baseUrl = "") {
|
|
644
|
+
const stage = await this.composeStageFromBytes(bytes, baseUrl, this.resolver);
|
|
645
|
+
return this.buildFromStage(stage, baseUrl, this.resolver);
|
|
646
|
+
}
|
|
647
|
+
/** Parse + compose USDA source into the Three.js-independent robot IR. */
|
|
648
|
+
async parseRobotDescription(text, baseUrl = "") {
|
|
649
|
+
const stage = await this.composeStage(text, baseUrl, this.resolver);
|
|
650
|
+
return extractRobotDescription(stage, this.extractOptions());
|
|
651
|
+
}
|
|
652
|
+
buildFromStage(stage, baseUrl, resolver) {
|
|
653
|
+
const robot = extractRobotDescription(stage, this.extractOptions());
|
|
654
|
+
const tree = buildKinematicTree(robot);
|
|
655
|
+
const robot3d = new ThreeUsdRobot(robot, tree, this.robotOptions());
|
|
656
|
+
const loadVisuals = this.options.loadVisuals ?? true;
|
|
657
|
+
const loadCollisions = this.options.loadCollisions ?? false;
|
|
658
|
+
const loadScene = this.options.loadSceneGeometry ?? false;
|
|
659
|
+
if (loadVisuals || loadCollisions || loadScene) {
|
|
660
|
+
const textureProvider = this.options.loadTextures ?? true ? createTextureProvider(resolver, baseUrl) : void 0;
|
|
661
|
+
if (loadVisuals || loadCollisions) {
|
|
662
|
+
bindRobotMeshes(stage, robot3d, robot, {
|
|
663
|
+
loadVisuals,
|
|
664
|
+
loadCollisions,
|
|
665
|
+
...textureProvider ? { textureProvider } : {}
|
|
666
|
+
});
|
|
667
|
+
}
|
|
668
|
+
if (loadScene) {
|
|
669
|
+
bindSceneMeshes(stage, robot3d, robot, {
|
|
670
|
+
...textureProvider ? { textureProvider } : {}
|
|
671
|
+
});
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
return robot3d;
|
|
675
|
+
}
|
|
676
|
+
async composeStage(text, baseUrl, resolver) {
|
|
677
|
+
const composeOptions = this.options.onWarn ? { onWarn: this.options.onWarn } : {};
|
|
678
|
+
return Stage.OpenFromFile(await composeLayer(text, baseUrl, resolver, composeOptions));
|
|
679
|
+
}
|
|
680
|
+
/** Compose a layer from raw bytes, sniffing binary crate vs USDA text. */
|
|
681
|
+
async composeStageFromBytes(bytes, baseUrl, resolver) {
|
|
682
|
+
const composeOptions = this.options.onWarn ? { onWarn: this.options.onWarn } : {};
|
|
683
|
+
const composed = CrateReader.isCrate(bytes) ? await composeFile(
|
|
684
|
+
crateToUsdaFile(new CrateReader(bytes)),
|
|
685
|
+
baseUrl,
|
|
686
|
+
resolver,
|
|
687
|
+
composeOptions
|
|
688
|
+
) : await composeLayer(new TextDecoder().decode(bytes), baseUrl, resolver, composeOptions);
|
|
689
|
+
return Stage.OpenFromFile(composed);
|
|
690
|
+
}
|
|
691
|
+
robotOptions() {
|
|
692
|
+
return {
|
|
693
|
+
upAxisConversion: this.options.upAxisConversion ?? "auto",
|
|
694
|
+
...this.options.clampJointLimits !== void 0 ? { clampJointLimits: this.options.clampJointLimits } : {},
|
|
695
|
+
...this.options.unitScale !== void 0 ? { unitScale: this.options.unitScale } : {},
|
|
696
|
+
...this.options.applyDriveTargetsAsInitialPose !== void 0 ? { applyInitialPose: this.options.applyDriveTargetsAsInitialPose } : {}
|
|
697
|
+
};
|
|
698
|
+
}
|
|
699
|
+
extractOptions() {
|
|
700
|
+
return {
|
|
701
|
+
...this.options.robotName !== void 0 ? { robotName: this.options.robotName } : {},
|
|
702
|
+
...this.options.onWarn !== void 0 ? { onWarn: this.options.onWarn } : {}
|
|
703
|
+
};
|
|
704
|
+
}
|
|
705
|
+
};
|
|
706
|
+
|
|
707
|
+
export { JointObject, LinkObject, ThreeUsdRobot, ThreeUsdRobotLoader, axisVector, bindRobotMeshes, bindSceneMeshes, buildMeshGeometry, buildMeshMaterial, createTextureProvider };
|
|
708
|
+
//# sourceMappingURL=chunk-YVWSO27W.js.map
|
|
709
|
+
//# sourceMappingURL=chunk-YVWSO27W.js.map
|