mujoco-react 0.2.0 → 1.0.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 +287 -48
- package/dist/index.d.ts +215 -135
- package/dist/index.js +1176 -795
- package/dist/index.js.map +1 -1
- package/package.json +6 -4
- package/src/components/ContactMarkers.tsx +19 -22
- package/src/components/Debug.tsx +173 -36
- package/src/components/DragInteraction.tsx +5 -3
- package/src/components/FlexRenderer.tsx +3 -2
- package/src/components/IkController.tsx +262 -0
- package/src/components/IkGizmo.tsx +17 -25
- package/src/components/SceneLights.tsx +2 -112
- package/src/components/SceneRenderer.tsx +13 -8
- package/src/components/SelectionHighlight.tsx +2 -49
- package/src/components/TendonRenderer.tsx +93 -28
- package/src/components/TrajectoryPlayer.tsx +14 -10
- package/src/core/IkContext.tsx +40 -0
- package/src/core/MujocoCanvas.tsx +1 -5
- package/src/core/MujocoPhysics.tsx +79 -0
- package/src/core/MujocoProvider.tsx +12 -4
- package/src/core/MujocoSimProvider.tsx +56 -340
- package/src/core/SceneLoader.ts +45 -18
- package/src/core/createController.tsx +91 -0
- package/src/hooks/useCameraAnimation.ts +102 -0
- package/src/hooks/useContacts.ts +52 -22
- package/src/hooks/useJointState.ts +18 -2
- package/src/hooks/useSceneLights.ts +117 -0
- package/src/hooks/useSelectionHighlight.ts +65 -0
- package/src/index.ts +18 -1
- package/src/types.ts +53 -26
package/dist/index.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import loadMujoco from 'mujoco-js';
|
|
2
2
|
import { createContext, forwardRef, useEffect, useContext, useState, useRef, useCallback, useMemo } from 'react';
|
|
3
|
-
import { jsx, jsxs
|
|
3
|
+
import { jsx, jsxs } from 'react/jsx-runtime';
|
|
4
4
|
import { Canvas, useThree, useFrame } from '@react-three/fiber';
|
|
5
|
-
import * as
|
|
5
|
+
import * as THREE11 from 'three';
|
|
6
6
|
import { PivotControls } from '@react-three/drei';
|
|
7
7
|
|
|
8
8
|
// src/core/MujocoProvider.tsx
|
|
@@ -14,14 +14,14 @@ var MujocoContext = createContext({
|
|
|
14
14
|
function useMujoco() {
|
|
15
15
|
return useContext(MujocoContext);
|
|
16
16
|
}
|
|
17
|
-
function MujocoProvider({ wasmUrl, children, onError }) {
|
|
17
|
+
function MujocoProvider({ wasmUrl, timeout = 3e4, children, onError }) {
|
|
18
18
|
const [status, setStatus] = useState("loading");
|
|
19
19
|
const [error, setError] = useState(null);
|
|
20
20
|
const moduleRef = useRef(null);
|
|
21
21
|
const isMounted = useRef(true);
|
|
22
22
|
useEffect(() => {
|
|
23
23
|
isMounted.current = true;
|
|
24
|
-
loadMujoco({
|
|
24
|
+
const wasmPromise = loadMujoco({
|
|
25
25
|
...wasmUrl ? { locateFile: (path) => path.endsWith(".wasm") ? wasmUrl : path } : {},
|
|
26
26
|
printErr: (text) => {
|
|
27
27
|
if (text.includes("Aborted") && isMounted.current) {
|
|
@@ -29,7 +29,11 @@ function MujocoProvider({ wasmUrl, children, onError }) {
|
|
|
29
29
|
setStatus("error");
|
|
30
30
|
}
|
|
31
31
|
}
|
|
32
|
-
})
|
|
32
|
+
});
|
|
33
|
+
const timeoutPromise = new Promise(
|
|
34
|
+
(_, reject) => setTimeout(() => reject(new Error(`WASM module load timed out after ${timeout}ms`)), timeout)
|
|
35
|
+
);
|
|
36
|
+
Promise.race([wasmPromise, timeoutPromise]).then((inst) => {
|
|
33
37
|
if (isMounted.current) {
|
|
34
38
|
moduleRef.current = inst;
|
|
35
39
|
setStatus("ready");
|
|
@@ -45,7 +49,7 @@ function MujocoProvider({ wasmUrl, children, onError }) {
|
|
|
45
49
|
return () => {
|
|
46
50
|
isMounted.current = false;
|
|
47
51
|
};
|
|
48
|
-
}, [wasmUrl]);
|
|
52
|
+
}, [wasmUrl, timeout]);
|
|
49
53
|
return /* @__PURE__ */ jsx(
|
|
50
54
|
MujocoContext.Provider,
|
|
51
55
|
{
|
|
@@ -55,229 +59,12 @@ function MujocoProvider({ wasmUrl, children, onError }) {
|
|
|
55
59
|
);
|
|
56
60
|
}
|
|
57
61
|
|
|
58
|
-
// src/
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
posWeight: 1,
|
|
65
|
-
rotWeight: 0.3
|
|
66
|
-
};
|
|
67
|
-
var GenericIK = class {
|
|
68
|
-
mujoco;
|
|
69
|
-
constructor(mujoco) {
|
|
70
|
-
this.mujoco = mujoco;
|
|
71
|
-
}
|
|
72
|
-
/**
|
|
73
|
-
* Solve IK for a target 6-DOF pose.
|
|
74
|
-
* @param model MuJoCo model
|
|
75
|
-
* @param data MuJoCo data (qpos will be temporarily modified, then restored)
|
|
76
|
-
* @param siteId Index of the end-effector site to control
|
|
77
|
-
* @param numJoints Number of arm joints (assumes qpos[0..numJoints-1])
|
|
78
|
-
* @param targetPos Target position in world frame
|
|
79
|
-
* @param targetQuat Target orientation in world frame
|
|
80
|
-
* @param currentQ Current joint angles (length = numJoints)
|
|
81
|
-
* @param opts Optional solver parameters
|
|
82
|
-
* @returns Joint angles array, or null if solver diverged
|
|
83
|
-
*/
|
|
84
|
-
solve(model, data, siteId, numJoints, targetPos, targetQuat, currentQ, opts) {
|
|
85
|
-
const o = { ...DEFAULTS, ...opts };
|
|
86
|
-
const n = numJoints;
|
|
87
|
-
const savedQpos = new Float64Array(data.qpos.length);
|
|
88
|
-
savedQpos.set(data.qpos);
|
|
89
|
-
const R_target = quatToMat3(targetQuat);
|
|
90
|
-
const q = new Float64Array(n);
|
|
91
|
-
for (let i = 0; i < n; i++) q[i] = currentQ[i];
|
|
92
|
-
const J = new Float64Array(6 * n);
|
|
93
|
-
const JJt = new Float64Array(36);
|
|
94
|
-
const rhs = new Float64Array(6);
|
|
95
|
-
const x = new Float64Array(6);
|
|
96
|
-
const dq = new Float64Array(n);
|
|
97
|
-
const baseSitePos = new Float64Array(3);
|
|
98
|
-
const baseSiteMat = new Float64Array(9);
|
|
99
|
-
const pertSitePos = new Float64Array(3);
|
|
100
|
-
const pertSiteMat = new Float64Array(9);
|
|
101
|
-
let bestQ = null;
|
|
102
|
-
let bestErr = Infinity;
|
|
103
|
-
for (let iter = 0; iter < o.maxIterations; iter++) {
|
|
104
|
-
for (let i = 0; i < n; i++) data.qpos[i] = q[i];
|
|
105
|
-
this.mujoco.mj_forward(model, data);
|
|
106
|
-
const sp = data.site_xpos;
|
|
107
|
-
const sm = data.site_xmat;
|
|
108
|
-
const off3 = siteId * 3;
|
|
109
|
-
const off9 = siteId * 9;
|
|
110
|
-
for (let i = 0; i < 3; i++) baseSitePos[i] = sp[off3 + i];
|
|
111
|
-
for (let i = 0; i < 9; i++) baseSiteMat[i] = sm[off9 + i];
|
|
112
|
-
const posErr0 = targetPos.x - baseSitePos[0];
|
|
113
|
-
const posErr1 = targetPos.y - baseSitePos[1];
|
|
114
|
-
const posErr2 = targetPos.z - baseSitePos[2];
|
|
115
|
-
const rotErr = orientationError(baseSiteMat, R_target);
|
|
116
|
-
const error = [
|
|
117
|
-
posErr0 * o.posWeight,
|
|
118
|
-
posErr1 * o.posWeight,
|
|
119
|
-
posErr2 * o.posWeight,
|
|
120
|
-
rotErr[0] * o.rotWeight,
|
|
121
|
-
rotErr[1] * o.rotWeight,
|
|
122
|
-
rotErr[2] * o.rotWeight
|
|
123
|
-
];
|
|
124
|
-
const errNorm = Math.sqrt(
|
|
125
|
-
error[0] * error[0] + error[1] * error[1] + error[2] * error[2] + error[3] * error[3] + error[4] * error[4] + error[5] * error[5]
|
|
126
|
-
);
|
|
127
|
-
if (errNorm < bestErr) {
|
|
128
|
-
bestErr = errNorm;
|
|
129
|
-
bestQ = Array.from(q);
|
|
130
|
-
}
|
|
131
|
-
if (errNorm < o.tolerance) break;
|
|
132
|
-
for (let j = 0; j < n; j++) {
|
|
133
|
-
const saved = data.qpos[j];
|
|
134
|
-
data.qpos[j] = q[j] + o.epsilon;
|
|
135
|
-
this.mujoco.mj_forward(model, data);
|
|
136
|
-
for (let i = 0; i < 3; i++) pertSitePos[i] = sp[off3 + i];
|
|
137
|
-
for (let i = 0; i < 9; i++) pertSiteMat[i] = sm[off9 + i];
|
|
138
|
-
J[0 * n + j] = (pertSitePos[0] - baseSitePos[0]) / o.epsilon * o.posWeight;
|
|
139
|
-
J[1 * n + j] = (pertSitePos[1] - baseSitePos[1]) / o.epsilon * o.posWeight;
|
|
140
|
-
J[2 * n + j] = (pertSitePos[2] - baseSitePos[2]) / o.epsilon * o.posWeight;
|
|
141
|
-
const dRot = angularDelta(baseSiteMat, pertSiteMat);
|
|
142
|
-
J[3 * n + j] = dRot[0] / o.epsilon * o.rotWeight;
|
|
143
|
-
J[4 * n + j] = dRot[1] / o.epsilon * o.rotWeight;
|
|
144
|
-
J[5 * n + j] = dRot[2] / o.epsilon * o.rotWeight;
|
|
145
|
-
data.qpos[j] = saved;
|
|
146
|
-
}
|
|
147
|
-
for (let i = 0; i < n; i++) data.qpos[i] = q[i];
|
|
148
|
-
for (let r = 0; r < 6; r++) {
|
|
149
|
-
for (let c = 0; c < 6; c++) {
|
|
150
|
-
let sum = 0;
|
|
151
|
-
for (let k = 0; k < n; k++) {
|
|
152
|
-
sum += J[r * n + k] * J[c * n + k];
|
|
153
|
-
}
|
|
154
|
-
JJt[r * 6 + c] = sum + (r === c ? o.damping : 0);
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
for (let i = 0; i < 6; i++) rhs[i] = error[i];
|
|
158
|
-
solve6x6(JJt, rhs, x);
|
|
159
|
-
for (let j = 0; j < n; j++) {
|
|
160
|
-
let sum = 0;
|
|
161
|
-
for (let r = 0; r < 6; r++) {
|
|
162
|
-
sum += J[r * n + j] * x[r];
|
|
163
|
-
}
|
|
164
|
-
dq[j] = sum;
|
|
165
|
-
}
|
|
166
|
-
for (let i = 0; i < n; i++) q[i] += dq[i];
|
|
167
|
-
}
|
|
168
|
-
data.qpos.set(savedQpos);
|
|
169
|
-
this.mujoco.mj_forward(model, data);
|
|
170
|
-
return bestQ;
|
|
171
|
-
}
|
|
172
|
-
};
|
|
173
|
-
function quatToMat3(q) {
|
|
174
|
-
const m = new Float64Array(9);
|
|
175
|
-
const x = q.x, y = q.y, z = q.z, w = q.w;
|
|
176
|
-
const xx = x * x, yy = y * y, zz = z * z;
|
|
177
|
-
const xy = x * y, xz = x * z, yz = y * z;
|
|
178
|
-
const wx = w * x, wy = w * y, wz = w * z;
|
|
179
|
-
m[0] = 1 - 2 * (yy + zz);
|
|
180
|
-
m[1] = 2 * (xy - wz);
|
|
181
|
-
m[2] = 2 * (xz + wy);
|
|
182
|
-
m[3] = 2 * (xy + wz);
|
|
183
|
-
m[4] = 1 - 2 * (xx + zz);
|
|
184
|
-
m[5] = 2 * (yz - wx);
|
|
185
|
-
m[6] = 2 * (xz - wy);
|
|
186
|
-
m[7] = 2 * (yz + wx);
|
|
187
|
-
m[8] = 1 - 2 * (xx + yy);
|
|
188
|
-
return m;
|
|
189
|
-
}
|
|
190
|
-
function orientationError(R_cur, R_tgt) {
|
|
191
|
-
const Re = new Float64Array(9);
|
|
192
|
-
for (let i = 0; i < 3; i++) {
|
|
193
|
-
for (let j = 0; j < 3; j++) {
|
|
194
|
-
let s2 = 0;
|
|
195
|
-
for (let k = 0; k < 3; k++) {
|
|
196
|
-
s2 += R_tgt[i * 3 + k] * R_cur[j * 3 + k];
|
|
197
|
-
}
|
|
198
|
-
Re[i * 3 + j] = s2;
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
const trace = Re[0] + Re[4] + Re[8];
|
|
202
|
-
const cosAngle = Math.max(-1, Math.min(1, (trace - 1) * 0.5));
|
|
203
|
-
const angle = Math.acos(cosAngle);
|
|
204
|
-
if (angle < 1e-6) {
|
|
205
|
-
return [0, 0, 0];
|
|
206
|
-
}
|
|
207
|
-
if (angle > Math.PI - 1e-6) {
|
|
208
|
-
return [
|
|
209
|
-
0.5 * (Re[7] - Re[5]),
|
|
210
|
-
0.5 * (Re[2] - Re[6]),
|
|
211
|
-
0.5 * (Re[3] - Re[1])
|
|
212
|
-
];
|
|
213
|
-
}
|
|
214
|
-
const s = angle / (2 * Math.sin(angle));
|
|
215
|
-
return [
|
|
216
|
-
s * (Re[7] - Re[5]),
|
|
217
|
-
s * (Re[2] - Re[6]),
|
|
218
|
-
s * (Re[3] - Re[1])
|
|
219
|
-
];
|
|
220
|
-
}
|
|
221
|
-
function angularDelta(R_base, R_pert) {
|
|
222
|
-
const dR = new Float64Array(9);
|
|
223
|
-
for (let i = 0; i < 3; i++) {
|
|
224
|
-
for (let j = 0; j < 3; j++) {
|
|
225
|
-
let s = 0;
|
|
226
|
-
for (let k = 0; k < 3; k++) {
|
|
227
|
-
s += R_pert[i * 3 + k] * R_base[j * 3 + k];
|
|
228
|
-
}
|
|
229
|
-
dR[i * 3 + j] = s;
|
|
230
|
-
}
|
|
231
|
-
}
|
|
232
|
-
return [
|
|
233
|
-
0.5 * (dR[7] - dR[5]),
|
|
234
|
-
0.5 * (dR[2] - dR[6]),
|
|
235
|
-
0.5 * (dR[3] - dR[1])
|
|
236
|
-
];
|
|
237
|
-
}
|
|
238
|
-
function solve6x6(A, b, x) {
|
|
239
|
-
const N = 6;
|
|
240
|
-
const a = new Float64Array(A);
|
|
241
|
-
const r = new Float64Array(b);
|
|
242
|
-
for (let col = 0; col < N; col++) {
|
|
243
|
-
let maxVal = Math.abs(a[col * N + col]);
|
|
244
|
-
let maxRow = col;
|
|
245
|
-
for (let row = col + 1; row < N; row++) {
|
|
246
|
-
const val = Math.abs(a[row * N + col]);
|
|
247
|
-
if (val > maxVal) {
|
|
248
|
-
maxVal = val;
|
|
249
|
-
maxRow = row;
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
|
-
if (maxRow !== col) {
|
|
253
|
-
for (let k = 0; k < N; k++) {
|
|
254
|
-
const tmp2 = a[col * N + k];
|
|
255
|
-
a[col * N + k] = a[maxRow * N + k];
|
|
256
|
-
a[maxRow * N + k] = tmp2;
|
|
257
|
-
}
|
|
258
|
-
const tmp = r[col];
|
|
259
|
-
r[col] = r[maxRow];
|
|
260
|
-
r[maxRow] = tmp;
|
|
261
|
-
}
|
|
262
|
-
const pivot = a[col * N + col];
|
|
263
|
-
if (Math.abs(pivot) < 1e-12) {
|
|
264
|
-
x.fill(0);
|
|
265
|
-
return;
|
|
266
|
-
}
|
|
267
|
-
for (let row = col + 1; row < N; row++) {
|
|
268
|
-
const factor = a[row * N + col] / pivot;
|
|
269
|
-
for (let k = col; k < N; k++) {
|
|
270
|
-
a[row * N + k] -= factor * a[col * N + k];
|
|
271
|
-
}
|
|
272
|
-
r[row] -= factor * r[col];
|
|
273
|
-
}
|
|
274
|
-
}
|
|
275
|
-
for (let row = N - 1; row >= 0; row--) {
|
|
276
|
-
let sum = r[row];
|
|
277
|
-
for (let k = row + 1; k < N; k++) {
|
|
278
|
-
sum -= a[row * N + k] * x[k];
|
|
279
|
-
}
|
|
280
|
-
x[row] = sum / a[row * N + row];
|
|
62
|
+
// src/types.ts
|
|
63
|
+
function getContact(data, i) {
|
|
64
|
+
try {
|
|
65
|
+
return data.contact.get(i);
|
|
66
|
+
} catch {
|
|
67
|
+
return void 0;
|
|
281
68
|
}
|
|
282
69
|
}
|
|
283
70
|
|
|
@@ -340,6 +127,16 @@ function findTendonByName(mjModel, name) {
|
|
|
340
127
|
}
|
|
341
128
|
return -1;
|
|
342
129
|
}
|
|
130
|
+
function getActuatedScalarQposAdr(mjModel, actuatorId) {
|
|
131
|
+
if (actuatorId < 0 || actuatorId >= mjModel.nu) return -1;
|
|
132
|
+
const trnType = mjModel.actuator_trntype?.[actuatorId];
|
|
133
|
+
if (trnType !== void 0 && trnType !== 0 && trnType !== 1) return -1;
|
|
134
|
+
const jointId = mjModel.actuator_trnid[2 * actuatorId];
|
|
135
|
+
if (jointId < 0 || jointId >= mjModel.njnt) return -1;
|
|
136
|
+
const jntType = mjModel.jnt_type[jointId];
|
|
137
|
+
if (jntType !== 2 && jntType !== 3) return -1;
|
|
138
|
+
return mjModel.jnt_qposadr[jointId];
|
|
139
|
+
}
|
|
343
140
|
function sceneObjectToXml(obj) {
|
|
344
141
|
const joint = obj.freejoint ? "<freejoint/>" : "";
|
|
345
142
|
const pos = obj.position.map((v) => v.toFixed(3)).join(" ");
|
|
@@ -390,7 +187,13 @@ async function loadScene(mujoco, config, onProgress) {
|
|
|
390
187
|
for (const patch of config.xmlPatches ?? []) {
|
|
391
188
|
if (fname.endsWith(patch.target) || fname === patch.target) {
|
|
392
189
|
if (patch.replace) {
|
|
393
|
-
|
|
190
|
+
const [from, to] = patch.replace;
|
|
191
|
+
if (text.includes(from)) {
|
|
192
|
+
text = text.replace(from, to);
|
|
193
|
+
} else {
|
|
194
|
+
const preview = from.length > 80 ? `${from.slice(0, 80)}...` : from;
|
|
195
|
+
console.warn(`XML patch replace pattern not found in ${fname}: "${preview}"`);
|
|
196
|
+
}
|
|
394
197
|
}
|
|
395
198
|
if (patch.inject && patch.injectAfter) {
|
|
396
199
|
const idx = text.indexOf(patch.injectAfter);
|
|
@@ -398,7 +201,12 @@ async function loadScene(mujoco, config, onProgress) {
|
|
|
398
201
|
const tagEnd = text.indexOf(">", idx + patch.injectAfter.length);
|
|
399
202
|
if (tagEnd !== -1) {
|
|
400
203
|
text = text.slice(0, tagEnd + 1) + patch.inject + text.slice(tagEnd + 1);
|
|
204
|
+
} else {
|
|
205
|
+
console.warn(`XML patch inject failed in ${fname}: could not find tag end after "${patch.injectAfter}"`);
|
|
401
206
|
}
|
|
207
|
+
} else {
|
|
208
|
+
const preview = patch.injectAfter.length > 80 ? `${patch.injectAfter.slice(0, 80)}...` : patch.injectAfter;
|
|
209
|
+
console.warn(`XML patch inject anchor not found in ${fname}: "${preview}"`);
|
|
402
210
|
}
|
|
403
211
|
}
|
|
404
212
|
}
|
|
@@ -417,28 +225,25 @@ async function loadScene(mujoco, config, onProgress) {
|
|
|
417
225
|
onProgress?.("Loading model...");
|
|
418
226
|
const mjModel = mujoco.MjModel.loadFromXML(`/working/${config.sceneFile}`);
|
|
419
227
|
const mjData = new mujoco.MjData(mjModel);
|
|
420
|
-
const siteId = findSiteByName(mjModel, config.tcpSiteName ?? "tcp");
|
|
421
|
-
const gripperId = findActuatorByName(mjModel, config.gripperActuatorName ?? "gripper");
|
|
422
228
|
if (config.homeJoints) {
|
|
423
|
-
|
|
229
|
+
const homeCount = Math.min(config.homeJoints.length, mjModel.nu);
|
|
230
|
+
for (let i = 0; i < homeCount; i++) {
|
|
424
231
|
mjData.ctrl[i] = config.homeJoints[i];
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
const qposAdr = mjModel.jnt_qposadr[jointId];
|
|
429
|
-
mjData.qpos[qposAdr] = config.homeJoints[i];
|
|
430
|
-
}
|
|
232
|
+
const qposAdr = getActuatedScalarQposAdr(mjModel, i);
|
|
233
|
+
if (qposAdr !== -1) {
|
|
234
|
+
mjData.qpos[qposAdr] = config.homeJoints[i];
|
|
431
235
|
}
|
|
432
236
|
}
|
|
433
237
|
}
|
|
434
238
|
mujoco.mj_forward(mjModel, mjData);
|
|
435
|
-
return { mjModel, mjData
|
|
239
|
+
return { mjModel, mjData };
|
|
436
240
|
}
|
|
437
241
|
function scanDependencies(xmlString, currentFile, parser, downloaded, queue) {
|
|
438
242
|
const xmlDoc = parser.parseFromString(xmlString, "text/xml");
|
|
439
243
|
const compiler = xmlDoc.querySelector("compiler");
|
|
440
|
-
const
|
|
441
|
-
const
|
|
244
|
+
const assetDir = compiler?.getAttribute("assetdir") || "";
|
|
245
|
+
const meshDir = compiler?.getAttribute("meshdir") || assetDir;
|
|
246
|
+
const textureDir = compiler?.getAttribute("texturedir") || assetDir;
|
|
442
247
|
const currentDir = currentFile.includes("/") ? currentFile.substring(0, currentFile.lastIndexOf("/") + 1) : "";
|
|
443
248
|
xmlDoc.querySelectorAll("[file]").forEach((el) => {
|
|
444
249
|
const fileAttr = el.getAttribute("file");
|
|
@@ -519,6 +324,8 @@ var _applyPoint = new Float64Array(3);
|
|
|
519
324
|
var _rayPnt = new Float64Array(3);
|
|
520
325
|
var _rayVec = new Float64Array(3);
|
|
521
326
|
var _rayGeomId = new Int32Array(1);
|
|
327
|
+
var _projRaycaster = new THREE11.Raycaster();
|
|
328
|
+
var _projNdc = new THREE11.Vector2();
|
|
522
329
|
var MujocoSimContext = createContext(null);
|
|
523
330
|
function useMujocoSim() {
|
|
524
331
|
const ctx = useContext(MujocoSimContext);
|
|
@@ -563,7 +370,6 @@ function MujocoSimProvider({
|
|
|
563
370
|
substeps,
|
|
564
371
|
paused,
|
|
565
372
|
speed,
|
|
566
|
-
interpolate,
|
|
567
373
|
children
|
|
568
374
|
}) {
|
|
569
375
|
const { gl, camera } = useThree();
|
|
@@ -572,25 +378,18 @@ function MujocoSimProvider({
|
|
|
572
378
|
const mjDataRef = useRef(null);
|
|
573
379
|
const mujocoRef = useRef(mujoco);
|
|
574
380
|
const configRef = useRef(config);
|
|
575
|
-
const siteIdRef = useRef(-1);
|
|
576
|
-
const gripperIdRef = useRef(-1);
|
|
577
|
-
const ikEnabledRef = useRef(false);
|
|
578
|
-
const ikCalculatingRef = useRef(false);
|
|
579
381
|
const pausedRef = useRef(paused ?? false);
|
|
580
382
|
const speedRef = useRef(speed ?? 1);
|
|
581
383
|
const substepsRef = useRef(substeps ?? 1);
|
|
582
|
-
const interpolateRef = useRef(interpolate ?? false);
|
|
583
|
-
const firstIkEnableRef = useRef(true);
|
|
584
384
|
const stepsToRunRef = useRef(0);
|
|
585
|
-
useRef(
|
|
586
|
-
useRef(null);
|
|
587
|
-
useRef(0);
|
|
385
|
+
const loadGenRef = useRef(0);
|
|
588
386
|
const onSelectionRef = useRef(onSelection);
|
|
589
387
|
onSelectionRef.current = onSelection;
|
|
590
388
|
const onStepRef = useRef(onStep);
|
|
591
389
|
onStepRef.current = onStep;
|
|
592
390
|
const beforeStepCallbacks = useRef(/* @__PURE__ */ new Set());
|
|
593
391
|
const afterStepCallbacks = useRef(/* @__PURE__ */ new Set());
|
|
392
|
+
const resetCallbacks = useRef(/* @__PURE__ */ new Set());
|
|
594
393
|
configRef.current = config;
|
|
595
394
|
useEffect(() => {
|
|
596
395
|
pausedRef.current = paused ?? false;
|
|
@@ -601,9 +400,6 @@ function MujocoSimProvider({
|
|
|
601
400
|
useEffect(() => {
|
|
602
401
|
substepsRef.current = substeps ?? 1;
|
|
603
402
|
}, [substeps]);
|
|
604
|
-
useEffect(() => {
|
|
605
|
-
interpolateRef.current = interpolate ?? false;
|
|
606
|
-
}, [interpolate]);
|
|
607
403
|
useEffect(() => {
|
|
608
404
|
if (!gravity) return;
|
|
609
405
|
const model = mjModelRef.current;
|
|
@@ -618,74 +414,6 @@ function MujocoSimProvider({
|
|
|
618
414
|
if (!model?.opt) return;
|
|
619
415
|
model.opt.timestep = timestep;
|
|
620
416
|
}, [timestep]);
|
|
621
|
-
const ikTargetRef = useRef(new THREE.Group());
|
|
622
|
-
const genericIkRef = useRef(new GenericIK(mujoco));
|
|
623
|
-
const gizmoAnimRef = useRef({
|
|
624
|
-
active: false,
|
|
625
|
-
startPos: new THREE.Vector3(),
|
|
626
|
-
endPos: new THREE.Vector3(),
|
|
627
|
-
startRot: new THREE.Quaternion(),
|
|
628
|
-
endRot: new THREE.Quaternion(),
|
|
629
|
-
startTime: 0,
|
|
630
|
-
duration: 1e3
|
|
631
|
-
});
|
|
632
|
-
const cameraAnimRef = useRef({
|
|
633
|
-
active: false,
|
|
634
|
-
startPos: new THREE.Vector3(),
|
|
635
|
-
endPos: new THREE.Vector3(),
|
|
636
|
-
startRot: new THREE.Quaternion(),
|
|
637
|
-
endRot: new THREE.Quaternion(),
|
|
638
|
-
startTarget: new THREE.Vector3(),
|
|
639
|
-
endTarget: new THREE.Vector3(),
|
|
640
|
-
startTime: 0,
|
|
641
|
-
duration: 0,
|
|
642
|
-
resolve: null
|
|
643
|
-
});
|
|
644
|
-
const orbitTargetRef = useRef(new THREE.Vector3(0, 0, 0));
|
|
645
|
-
const syncGizmoToSite = useCallback((data, siteId, target) => {
|
|
646
|
-
if (siteId === -1) return;
|
|
647
|
-
const sitePos = data.site_xpos.subarray(siteId * 3, siteId * 3 + 3);
|
|
648
|
-
const siteMat = data.site_xmat.subarray(siteId * 9, siteId * 9 + 9);
|
|
649
|
-
target.position.set(sitePos[0], sitePos[1], sitePos[2]);
|
|
650
|
-
const m = new THREE.Matrix4().set(
|
|
651
|
-
siteMat[0],
|
|
652
|
-
siteMat[1],
|
|
653
|
-
siteMat[2],
|
|
654
|
-
0,
|
|
655
|
-
siteMat[3],
|
|
656
|
-
siteMat[4],
|
|
657
|
-
siteMat[5],
|
|
658
|
-
0,
|
|
659
|
-
siteMat[6],
|
|
660
|
-
siteMat[7],
|
|
661
|
-
siteMat[8],
|
|
662
|
-
0,
|
|
663
|
-
0,
|
|
664
|
-
0,
|
|
665
|
-
0,
|
|
666
|
-
1
|
|
667
|
-
);
|
|
668
|
-
target.quaternion.setFromRotationMatrix(m);
|
|
669
|
-
}, []);
|
|
670
|
-
const ikSolveFn = useCallback(
|
|
671
|
-
(pos, quat, currentQ) => {
|
|
672
|
-
const model = mjModelRef.current;
|
|
673
|
-
const data = mjDataRef.current;
|
|
674
|
-
if (!model || !data || siteIdRef.current === -1) return null;
|
|
675
|
-
return genericIkRef.current.solve(
|
|
676
|
-
model,
|
|
677
|
-
data,
|
|
678
|
-
siteIdRef.current,
|
|
679
|
-
configRef.current.numArmJoints ?? 7,
|
|
680
|
-
pos,
|
|
681
|
-
quat,
|
|
682
|
-
currentQ
|
|
683
|
-
);
|
|
684
|
-
},
|
|
685
|
-
[]
|
|
686
|
-
);
|
|
687
|
-
const ikSolveFnRef = useRef(ikSolveFn);
|
|
688
|
-
ikSolveFnRef.current = ikSolveFn;
|
|
689
417
|
useEffect(() => {
|
|
690
418
|
let disposed = false;
|
|
691
419
|
(async () => {
|
|
@@ -698,8 +426,6 @@ function MujocoSimProvider({
|
|
|
698
426
|
}
|
|
699
427
|
mjModelRef.current = result.mjModel;
|
|
700
428
|
mjDataRef.current = result.mjData;
|
|
701
|
-
siteIdRef.current = result.siteId;
|
|
702
|
-
gripperIdRef.current = result.gripperId;
|
|
703
429
|
if (gravity && result.mjModel.opt?.gravity) {
|
|
704
430
|
result.mjModel.opt.gravity[0] = gravity[0];
|
|
705
431
|
result.mjModel.opt.gravity[1] = gravity[1];
|
|
@@ -708,9 +434,6 @@ function MujocoSimProvider({
|
|
|
708
434
|
if (timestep !== void 0 && result.mjModel.opt) {
|
|
709
435
|
result.mjModel.opt.timestep = timestep;
|
|
710
436
|
}
|
|
711
|
-
if (ikTargetRef.current) {
|
|
712
|
-
syncGizmoToSite(result.mjData, result.siteId, ikTargetRef.current);
|
|
713
|
-
}
|
|
714
437
|
setStatus("ready");
|
|
715
438
|
} catch (e) {
|
|
716
439
|
if (!disposed) {
|
|
@@ -744,42 +467,10 @@ function MujocoSimProvider({
|
|
|
744
467
|
}
|
|
745
468
|
}
|
|
746
469
|
}, [status]);
|
|
747
|
-
useFrame((
|
|
470
|
+
useFrame((_state, delta) => {
|
|
748
471
|
const model = mjModelRef.current;
|
|
749
472
|
const data = mjDataRef.current;
|
|
750
473
|
if (!model || !data) return;
|
|
751
|
-
const ga = gizmoAnimRef.current;
|
|
752
|
-
const target = ikTargetRef.current;
|
|
753
|
-
if (ga.active && target) {
|
|
754
|
-
const now = performance.now();
|
|
755
|
-
const elapsed = now - ga.startTime;
|
|
756
|
-
const t = Math.min(elapsed / ga.duration, 1);
|
|
757
|
-
const ease = 1 - Math.pow(1 - t, 3);
|
|
758
|
-
target.position.lerpVectors(ga.startPos, ga.endPos, ease);
|
|
759
|
-
target.quaternion.slerpQuaternions(ga.startRot, ga.endRot, ease);
|
|
760
|
-
if (t >= 1) ga.active = false;
|
|
761
|
-
}
|
|
762
|
-
const ca = cameraAnimRef.current;
|
|
763
|
-
if (ca.active) {
|
|
764
|
-
const now = performance.now();
|
|
765
|
-
const progress = Math.min((now - ca.startTime) / ca.duration, 1);
|
|
766
|
-
const ease = progress < 0.5 ? 4 * progress * progress * progress : 1 - Math.pow(-2 * progress + 2, 3) / 2;
|
|
767
|
-
camera.position.lerpVectors(ca.startPos, ca.endPos, ease);
|
|
768
|
-
camera.quaternion.slerpQuaternions(ca.startRot, ca.endRot, ease);
|
|
769
|
-
orbitTargetRef.current.lerpVectors(ca.startTarget, ca.endTarget, ease);
|
|
770
|
-
const orbitControls = state.controls;
|
|
771
|
-
if (orbitControls?.target) {
|
|
772
|
-
orbitControls.target.copy(orbitTargetRef.current);
|
|
773
|
-
}
|
|
774
|
-
if (progress >= 1) {
|
|
775
|
-
ca.active = false;
|
|
776
|
-
camera.position.copy(ca.endPos);
|
|
777
|
-
camera.quaternion.copy(ca.endRot);
|
|
778
|
-
orbitTargetRef.current.copy(ca.endTarget);
|
|
779
|
-
ca.resolve?.();
|
|
780
|
-
ca.resolve = null;
|
|
781
|
-
}
|
|
782
|
-
}
|
|
783
474
|
const shouldStep = !pausedRef.current || stepsToRunRef.current > 0;
|
|
784
475
|
if (!shouldStep) return;
|
|
785
476
|
for (let i = 0; i < model.nv; i++) {
|
|
@@ -788,18 +479,6 @@ function MujocoSimProvider({
|
|
|
788
479
|
for (const cb of beforeStepCallbacks.current) {
|
|
789
480
|
cb(model, data);
|
|
790
481
|
}
|
|
791
|
-
if (ikEnabledRef.current && target) {
|
|
792
|
-
ikCalculatingRef.current = true;
|
|
793
|
-
const numArm = configRef.current.numArmJoints ?? 7;
|
|
794
|
-
const currentQ = [];
|
|
795
|
-
for (let i = 0; i < numArm; i++) currentQ.push(data.qpos[i]);
|
|
796
|
-
const solution = ikSolveFnRef.current(target.position, target.quaternion, currentQ);
|
|
797
|
-
if (solution) {
|
|
798
|
-
for (let i = 0; i < numArm; i++) data.ctrl[i] = solution[i];
|
|
799
|
-
}
|
|
800
|
-
} else {
|
|
801
|
-
ikCalculatingRef.current = false;
|
|
802
|
-
}
|
|
803
482
|
const numSubsteps = substepsRef.current;
|
|
804
483
|
if (stepsToRunRef.current > 0) {
|
|
805
484
|
for (let s = 0; s < stepsToRunRef.current; s++) {
|
|
@@ -808,7 +487,8 @@ function MujocoSimProvider({
|
|
|
808
487
|
stepsToRunRef.current = 0;
|
|
809
488
|
} else {
|
|
810
489
|
const startSimTime = data.time;
|
|
811
|
-
const
|
|
490
|
+
const clampedDelta = Math.min(delta, 1 / 15);
|
|
491
|
+
const frameTime = clampedDelta * speedRef.current;
|
|
812
492
|
while (data.time - startSimTime < frameTime) {
|
|
813
493
|
for (let s = 0; s < numSubsteps; s++) {
|
|
814
494
|
mujoco.mj_step(model, data);
|
|
@@ -824,72 +504,24 @@ function MujocoSimProvider({
|
|
|
824
504
|
const model = mjModelRef.current;
|
|
825
505
|
const data = mjDataRef.current;
|
|
826
506
|
if (!model || !data) return;
|
|
827
|
-
gizmoAnimRef.current.active = false;
|
|
828
507
|
mujoco.mj_resetData(model, data);
|
|
829
508
|
const homeJoints = configRef.current.homeJoints;
|
|
830
509
|
if (homeJoints) {
|
|
831
|
-
|
|
510
|
+
const homeCount = Math.min(homeJoints.length, model.nu);
|
|
511
|
+
for (let i = 0; i < homeCount; i++) {
|
|
832
512
|
data.ctrl[i] = homeJoints[i];
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
const qposAdr = model.jnt_qposadr[jointId];
|
|
837
|
-
data.qpos[qposAdr] = homeJoints[i];
|
|
838
|
-
}
|
|
513
|
+
const qposAdr = getActuatedScalarQposAdr(model, i);
|
|
514
|
+
if (qposAdr !== -1) {
|
|
515
|
+
data.qpos[qposAdr] = homeJoints[i];
|
|
839
516
|
}
|
|
840
517
|
}
|
|
841
518
|
}
|
|
842
519
|
configRef.current.onReset?.(model, data);
|
|
843
520
|
mujoco.mj_forward(model, data);
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
}
|
|
847
|
-
firstIkEnableRef.current = true;
|
|
848
|
-
ikEnabledRef.current = false;
|
|
849
|
-
}, [mujoco, syncGizmoToSite]);
|
|
850
|
-
const setIkEnabled = useCallback((enabled) => {
|
|
851
|
-
ikEnabledRef.current = enabled;
|
|
852
|
-
const data = mjDataRef.current;
|
|
853
|
-
if (enabled && data && !gizmoAnimRef.current.active && ikTargetRef.current) {
|
|
854
|
-
syncGizmoToSite(data, siteIdRef.current, ikTargetRef.current);
|
|
855
|
-
firstIkEnableRef.current = false;
|
|
521
|
+
for (const cb of resetCallbacks.current) {
|
|
522
|
+
cb();
|
|
856
523
|
}
|
|
857
|
-
}, [
|
|
858
|
-
const syncTargetToSite = useCallback(() => {
|
|
859
|
-
const data = mjDataRef.current;
|
|
860
|
-
const target = ikTargetRef.current;
|
|
861
|
-
if (data && target) syncGizmoToSite(data, siteIdRef.current, target);
|
|
862
|
-
}, [syncGizmoToSite]);
|
|
863
|
-
const solveIK = useCallback(
|
|
864
|
-
(pos, quat, currentQ) => {
|
|
865
|
-
return ikSolveFnRef.current(pos, quat, currentQ);
|
|
866
|
-
},
|
|
867
|
-
[]
|
|
868
|
-
);
|
|
869
|
-
const moveTarget = useCallback(
|
|
870
|
-
(pos, duration = 0) => {
|
|
871
|
-
if (!ikEnabledRef.current) setIkEnabled(true);
|
|
872
|
-
const target = ikTargetRef.current;
|
|
873
|
-
if (!target) return;
|
|
874
|
-
const targetPos = pos.clone();
|
|
875
|
-
const targetRot = new THREE.Quaternion().setFromEuler(new THREE.Euler(Math.PI, 0, 0));
|
|
876
|
-
if (duration > 0) {
|
|
877
|
-
const ga = gizmoAnimRef.current;
|
|
878
|
-
ga.active = true;
|
|
879
|
-
ga.startPos.copy(target.position);
|
|
880
|
-
ga.endPos.copy(targetPos);
|
|
881
|
-
ga.startRot.copy(target.quaternion);
|
|
882
|
-
ga.endRot.copy(targetRot);
|
|
883
|
-
ga.startTime = performance.now();
|
|
884
|
-
ga.duration = duration;
|
|
885
|
-
} else {
|
|
886
|
-
gizmoAnimRef.current.active = false;
|
|
887
|
-
target.position.copy(targetPos);
|
|
888
|
-
target.quaternion.copy(targetRot);
|
|
889
|
-
}
|
|
890
|
-
},
|
|
891
|
-
[setIkEnabled]
|
|
892
|
-
);
|
|
524
|
+
}, [mujoco]);
|
|
893
525
|
const setSpeed = useCallback((multiplier) => {
|
|
894
526
|
speedRef.current = multiplier;
|
|
895
527
|
}, []);
|
|
@@ -1051,19 +683,16 @@ function MujocoSimProvider({
|
|
|
1051
683
|
const contacts = [];
|
|
1052
684
|
const ncon = data.ncon;
|
|
1053
685
|
for (let i = 0; i < ncon; i++) {
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
} catch {
|
|
1065
|
-
break;
|
|
1066
|
-
}
|
|
686
|
+
const c = getContact(data, i);
|
|
687
|
+
if (!c) break;
|
|
688
|
+
contacts.push({
|
|
689
|
+
geom1: c.geom1,
|
|
690
|
+
geom1Name: getName(model, model.name_geomadr[c.geom1]),
|
|
691
|
+
geom2: c.geom2,
|
|
692
|
+
geom2Name: getName(model, model.name_geomadr[c.geom2]),
|
|
693
|
+
pos: [c.pos[0], c.pos[1], c.pos[2]],
|
|
694
|
+
depth: c.dist
|
|
695
|
+
});
|
|
1067
696
|
}
|
|
1068
697
|
return contacts;
|
|
1069
698
|
}, []);
|
|
@@ -1202,7 +831,7 @@ function MujocoSimProvider({
|
|
|
1202
831
|
const geomId = _rayGeomId[0];
|
|
1203
832
|
const bodyId = geomId >= 0 ? model.geom_bodyid[geomId] : -1;
|
|
1204
833
|
return {
|
|
1205
|
-
point: new
|
|
834
|
+
point: new THREE11.Vector3(
|
|
1206
835
|
origin.x + dir.x * dist,
|
|
1207
836
|
origin.y + dir.y * dist,
|
|
1208
837
|
origin.z + dir.z * dist
|
|
@@ -1240,10 +869,10 @@ function MujocoSimProvider({
|
|
|
1240
869
|
for (let i = 0; i < model.nv; i++) data.qvel[i] = model.key_qvel[qvelOffset + i];
|
|
1241
870
|
}
|
|
1242
871
|
mujoco.mj_forward(model, data);
|
|
1243
|
-
|
|
1244
|
-
|
|
872
|
+
for (const cb of resetCallbacks.current) {
|
|
873
|
+
cb();
|
|
1245
874
|
}
|
|
1246
|
-
}, [mujoco
|
|
875
|
+
}, [mujoco]);
|
|
1247
876
|
const getKeyframeNames = useCallback(() => {
|
|
1248
877
|
const model = mjModelRef.current;
|
|
1249
878
|
if (!model) return [];
|
|
@@ -1257,6 +886,7 @@ function MujocoSimProvider({
|
|
|
1257
886
|
return mjModelRef.current?.nkey ?? 0;
|
|
1258
887
|
}, []);
|
|
1259
888
|
const loadSceneApi = useCallback(async (newConfig) => {
|
|
889
|
+
const gen = ++loadGenRef.current;
|
|
1260
890
|
try {
|
|
1261
891
|
mjModelRef.current?.delete();
|
|
1262
892
|
mjDataRef.current?.delete();
|
|
@@ -1264,28 +894,21 @@ function MujocoSimProvider({
|
|
|
1264
894
|
mjDataRef.current = null;
|
|
1265
895
|
setStatus("loading");
|
|
1266
896
|
const result = await loadScene(mujoco, newConfig);
|
|
897
|
+
if (gen !== loadGenRef.current) {
|
|
898
|
+
result.mjModel.delete();
|
|
899
|
+
result.mjData.delete();
|
|
900
|
+
return;
|
|
901
|
+
}
|
|
1267
902
|
mjModelRef.current = result.mjModel;
|
|
1268
903
|
mjDataRef.current = result.mjData;
|
|
1269
|
-
siteIdRef.current = result.siteId;
|
|
1270
|
-
gripperIdRef.current = result.gripperId;
|
|
1271
904
|
configRef.current = newConfig;
|
|
1272
|
-
if (ikTargetRef.current) {
|
|
1273
|
-
syncGizmoToSite(result.mjData, result.siteId, ikTargetRef.current);
|
|
1274
|
-
}
|
|
1275
905
|
setStatus("ready");
|
|
1276
906
|
} catch (e) {
|
|
907
|
+
if (gen !== loadGenRef.current) return;
|
|
1277
908
|
setStatus("error");
|
|
1278
909
|
throw e;
|
|
1279
910
|
}
|
|
1280
|
-
}, [mujoco
|
|
1281
|
-
const getGizmoStats = useCallback(() => {
|
|
1282
|
-
const target = ikTargetRef.current;
|
|
1283
|
-
if (!ikCalculatingRef.current || !target) return null;
|
|
1284
|
-
return {
|
|
1285
|
-
pos: target.position.clone(),
|
|
1286
|
-
rot: new THREE.Euler().setFromQuaternion(target.quaternion)
|
|
1287
|
-
};
|
|
1288
|
-
}, []);
|
|
911
|
+
}, [mujoco]);
|
|
1289
912
|
const getCanvasSnapshot = useCallback(
|
|
1290
913
|
(width, height, mimeType = "image/jpeg") => {
|
|
1291
914
|
if (width && height) {
|
|
@@ -1309,9 +932,8 @@ function MujocoSimProvider({
|
|
|
1309
932
|
virtCam.lookAt(lookAt);
|
|
1310
933
|
virtCam.updateMatrixWorld();
|
|
1311
934
|
virtCam.updateProjectionMatrix();
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
raycaster.setFromCamera(ndc, virtCam);
|
|
935
|
+
_projNdc.set(x * 2 - 1, -(y * 2 - 1));
|
|
936
|
+
_projRaycaster.setFromCamera(_projNdc, virtCam);
|
|
1315
937
|
const objects = [];
|
|
1316
938
|
const scene = camera.parent;
|
|
1317
939
|
if (scene) {
|
|
@@ -1319,7 +941,7 @@ function MujocoSimProvider({
|
|
|
1319
941
|
if (c.isMesh) objects.push(c);
|
|
1320
942
|
});
|
|
1321
943
|
}
|
|
1322
|
-
const hits =
|
|
944
|
+
const hits = _projRaycaster.intersectObjects(objects);
|
|
1323
945
|
if (hits.length > 0) {
|
|
1324
946
|
const hitObj = hits[0].object;
|
|
1325
947
|
const geomId = hitObj.userData.geomID !== void 0 ? hitObj.userData.geomID : -1;
|
|
@@ -1359,31 +981,6 @@ function MujocoSimProvider({
|
|
|
1359
981
|
model.geom_size[id * 3 + 1] = size[1];
|
|
1360
982
|
model.geom_size[id * 3 + 2] = size[2];
|
|
1361
983
|
}, []);
|
|
1362
|
-
const getCameraState = useCallback(() => {
|
|
1363
|
-
return { position: camera.position.clone(), target: orbitTargetRef.current.clone() };
|
|
1364
|
-
}, [camera]);
|
|
1365
|
-
const moveCameraTo = useCallback(
|
|
1366
|
-
(position, target, durationMs) => {
|
|
1367
|
-
return new Promise((resolve) => {
|
|
1368
|
-
const ca = cameraAnimRef.current;
|
|
1369
|
-
ca.active = true;
|
|
1370
|
-
ca.startTime = performance.now();
|
|
1371
|
-
ca.duration = durationMs;
|
|
1372
|
-
ca.startPos.copy(camera.position);
|
|
1373
|
-
ca.startRot.copy(camera.quaternion);
|
|
1374
|
-
ca.startTarget.copy(orbitTargetRef.current);
|
|
1375
|
-
ca.endPos.copy(position);
|
|
1376
|
-
ca.endTarget.copy(target);
|
|
1377
|
-
const dummyCam = camera.clone();
|
|
1378
|
-
dummyCam.position.copy(position);
|
|
1379
|
-
dummyCam.lookAt(target);
|
|
1380
|
-
ca.endRot.copy(dummyCam.quaternion);
|
|
1381
|
-
ca.resolve = resolve;
|
|
1382
|
-
setTimeout(resolve, durationMs + 100);
|
|
1383
|
-
});
|
|
1384
|
-
},
|
|
1385
|
-
[camera]
|
|
1386
|
-
);
|
|
1387
984
|
const api = useMemo(
|
|
1388
985
|
() => ({
|
|
1389
986
|
get status() {
|
|
@@ -1425,15 +1022,8 @@ function MujocoSimProvider({
|
|
|
1425
1022
|
getKeyframeNames,
|
|
1426
1023
|
getKeyframeCount,
|
|
1427
1024
|
loadScene: loadSceneApi,
|
|
1428
|
-
setIkEnabled,
|
|
1429
|
-
moveTarget,
|
|
1430
|
-
syncTargetToSite,
|
|
1431
|
-
solveIK,
|
|
1432
|
-
getGizmoStats,
|
|
1433
1025
|
getCanvasSnapshot,
|
|
1434
1026
|
project2DTo3D,
|
|
1435
|
-
getCameraState,
|
|
1436
|
-
moveCameraTo,
|
|
1437
1027
|
setBodyMass,
|
|
1438
1028
|
setGeomFriction,
|
|
1439
1029
|
setGeomSize,
|
|
@@ -1478,110 +1068,597 @@ function MujocoSimProvider({
|
|
|
1478
1068
|
getKeyframeNames,
|
|
1479
1069
|
getKeyframeCount,
|
|
1480
1070
|
loadSceneApi,
|
|
1481
|
-
setIkEnabled,
|
|
1482
|
-
moveTarget,
|
|
1483
|
-
syncTargetToSite,
|
|
1484
|
-
solveIK,
|
|
1485
|
-
getGizmoStats,
|
|
1486
1071
|
getCanvasSnapshot,
|
|
1487
1072
|
project2DTo3D,
|
|
1488
|
-
getCameraState,
|
|
1489
|
-
moveCameraTo,
|
|
1490
1073
|
setBodyMass,
|
|
1491
1074
|
setGeomFriction,
|
|
1492
1075
|
setGeomSize
|
|
1493
1076
|
]
|
|
1494
1077
|
);
|
|
1495
|
-
const apiRef = useRef(api);
|
|
1496
|
-
apiRef.current = api;
|
|
1078
|
+
const apiRef = useRef(api);
|
|
1079
|
+
apiRef.current = api;
|
|
1080
|
+
const contextValue = useMemo(
|
|
1081
|
+
() => ({
|
|
1082
|
+
api,
|
|
1083
|
+
mjModelRef,
|
|
1084
|
+
mjDataRef,
|
|
1085
|
+
mujocoRef,
|
|
1086
|
+
configRef,
|
|
1087
|
+
pausedRef,
|
|
1088
|
+
speedRef,
|
|
1089
|
+
substepsRef,
|
|
1090
|
+
onSelectionRef,
|
|
1091
|
+
beforeStepCallbacks,
|
|
1092
|
+
afterStepCallbacks,
|
|
1093
|
+
resetCallbacks,
|
|
1094
|
+
status
|
|
1095
|
+
}),
|
|
1096
|
+
[api, status]
|
|
1097
|
+
);
|
|
1098
|
+
return /* @__PURE__ */ jsx(MujocoSimContext.Provider, { value: contextValue, children });
|
|
1099
|
+
}
|
|
1100
|
+
var MujocoCanvas = forwardRef(
|
|
1101
|
+
function MujocoCanvas2({
|
|
1102
|
+
config,
|
|
1103
|
+
onReady,
|
|
1104
|
+
onError,
|
|
1105
|
+
onStep,
|
|
1106
|
+
onSelection,
|
|
1107
|
+
// Declarative physics config
|
|
1108
|
+
gravity,
|
|
1109
|
+
timestep,
|
|
1110
|
+
substeps,
|
|
1111
|
+
paused,
|
|
1112
|
+
speed,
|
|
1113
|
+
children,
|
|
1114
|
+
...canvasProps
|
|
1115
|
+
}, ref) {
|
|
1116
|
+
const { mujoco, status: wasmStatus, error: wasmError } = useMujoco();
|
|
1117
|
+
useEffect(() => {
|
|
1118
|
+
if (wasmStatus === "error" && onError) {
|
|
1119
|
+
onError(new Error(wasmError ?? "WASM load failed"));
|
|
1120
|
+
}
|
|
1121
|
+
}, [wasmStatus, wasmError, onError]);
|
|
1122
|
+
if (wasmStatus === "error" || wasmStatus === "loading" || !mujoco) {
|
|
1123
|
+
return null;
|
|
1124
|
+
}
|
|
1125
|
+
return /* @__PURE__ */ jsx(Canvas, { ...canvasProps, children: /* @__PURE__ */ jsx(
|
|
1126
|
+
MujocoSimProvider,
|
|
1127
|
+
{
|
|
1128
|
+
mujoco,
|
|
1129
|
+
config,
|
|
1130
|
+
apiRef: ref,
|
|
1131
|
+
onReady,
|
|
1132
|
+
onError,
|
|
1133
|
+
onStep,
|
|
1134
|
+
onSelection,
|
|
1135
|
+
gravity,
|
|
1136
|
+
timestep,
|
|
1137
|
+
substeps,
|
|
1138
|
+
paused,
|
|
1139
|
+
speed,
|
|
1140
|
+
children
|
|
1141
|
+
}
|
|
1142
|
+
) });
|
|
1143
|
+
}
|
|
1144
|
+
);
|
|
1145
|
+
var MujocoPhysics = forwardRef(
|
|
1146
|
+
function MujocoPhysics2({ onError, children, ...props }, ref) {
|
|
1147
|
+
const { mujoco, status: wasmStatus, error: wasmError } = useMujoco();
|
|
1148
|
+
useEffect(() => {
|
|
1149
|
+
if (wasmStatus === "error" && onError) {
|
|
1150
|
+
onError(new Error(wasmError ?? "WASM load failed"));
|
|
1151
|
+
}
|
|
1152
|
+
}, [wasmStatus, wasmError, onError]);
|
|
1153
|
+
if (wasmStatus === "error" || wasmStatus === "loading" || !mujoco) {
|
|
1154
|
+
return null;
|
|
1155
|
+
}
|
|
1156
|
+
return /* @__PURE__ */ jsx(
|
|
1157
|
+
MujocoSimProvider,
|
|
1158
|
+
{
|
|
1159
|
+
mujoco,
|
|
1160
|
+
apiRef: ref,
|
|
1161
|
+
onError,
|
|
1162
|
+
...props,
|
|
1163
|
+
children
|
|
1164
|
+
}
|
|
1165
|
+
);
|
|
1166
|
+
}
|
|
1167
|
+
);
|
|
1168
|
+
function shallowEqual(a, b) {
|
|
1169
|
+
const keysA = Object.keys(a);
|
|
1170
|
+
const keysB = Object.keys(b);
|
|
1171
|
+
if (keysA.length !== keysB.length) return false;
|
|
1172
|
+
for (const key of keysA) {
|
|
1173
|
+
if (a[key] !== b[key]) return false;
|
|
1174
|
+
}
|
|
1175
|
+
return true;
|
|
1176
|
+
}
|
|
1177
|
+
function createController(options, Impl) {
|
|
1178
|
+
function Controller({
|
|
1179
|
+
config,
|
|
1180
|
+
children
|
|
1181
|
+
}) {
|
|
1182
|
+
const configObj = config ?? {};
|
|
1183
|
+
const stableRef = useRef(configObj);
|
|
1184
|
+
if (!shallowEqual(stableRef.current, configObj)) {
|
|
1185
|
+
stableRef.current = configObj;
|
|
1186
|
+
}
|
|
1187
|
+
const stableConfig = stableRef.current;
|
|
1188
|
+
const mergedConfig = useMemo(
|
|
1189
|
+
() => ({ ...options.defaultConfig, ...stableConfig }),
|
|
1190
|
+
[stableConfig]
|
|
1191
|
+
);
|
|
1192
|
+
return /* @__PURE__ */ jsx(Impl, { config: mergedConfig, children });
|
|
1193
|
+
}
|
|
1194
|
+
Controller.displayName = options.name;
|
|
1195
|
+
Controller.controllerName = options.name;
|
|
1196
|
+
Controller.defaultConfig = options.defaultConfig ?? {};
|
|
1197
|
+
return Controller;
|
|
1198
|
+
}
|
|
1199
|
+
var IkContext = createContext(null);
|
|
1200
|
+
function useIk(options) {
|
|
1201
|
+
const ctx = useContext(IkContext);
|
|
1202
|
+
if (!ctx && !options?.optional) {
|
|
1203
|
+
throw new Error("useIk() must be used inside an <IkController>");
|
|
1204
|
+
}
|
|
1205
|
+
return ctx;
|
|
1206
|
+
}
|
|
1207
|
+
|
|
1208
|
+
// src/core/GenericIK.ts
|
|
1209
|
+
var DEFAULTS = {
|
|
1210
|
+
maxIterations: 50,
|
|
1211
|
+
damping: 0.01,
|
|
1212
|
+
tolerance: 1e-3,
|
|
1213
|
+
epsilon: 1e-6,
|
|
1214
|
+
posWeight: 1,
|
|
1215
|
+
rotWeight: 0.3
|
|
1216
|
+
};
|
|
1217
|
+
var GenericIK = class {
|
|
1218
|
+
mujoco;
|
|
1219
|
+
constructor(mujoco) {
|
|
1220
|
+
this.mujoco = mujoco;
|
|
1221
|
+
}
|
|
1222
|
+
/**
|
|
1223
|
+
* Solve IK for a target 6-DOF pose.
|
|
1224
|
+
* @param model MuJoCo model
|
|
1225
|
+
* @param data MuJoCo data (qpos will be temporarily modified, then restored)
|
|
1226
|
+
* @param siteId Index of the end-effector site to control
|
|
1227
|
+
* @param numJoints Number of arm joints (assumes qpos[0..numJoints-1])
|
|
1228
|
+
* @param targetPos Target position in world frame
|
|
1229
|
+
* @param targetQuat Target orientation in world frame
|
|
1230
|
+
* @param currentQ Current joint angles (length = numJoints)
|
|
1231
|
+
* @param opts Optional solver parameters
|
|
1232
|
+
* @returns Joint angles array, or null if solver diverged
|
|
1233
|
+
*/
|
|
1234
|
+
solve(model, data, siteId, numJoints, targetPos, targetQuat, currentQ, opts) {
|
|
1235
|
+
const o = { ...DEFAULTS, ...opts };
|
|
1236
|
+
const n = numJoints;
|
|
1237
|
+
const savedQpos = new Float64Array(data.qpos.length);
|
|
1238
|
+
savedQpos.set(data.qpos);
|
|
1239
|
+
const R_target = quatToMat3(targetQuat);
|
|
1240
|
+
const q = new Float64Array(n);
|
|
1241
|
+
for (let i = 0; i < n; i++) q[i] = currentQ[i];
|
|
1242
|
+
const J = new Float64Array(6 * n);
|
|
1243
|
+
const JJt = new Float64Array(36);
|
|
1244
|
+
const rhs = new Float64Array(6);
|
|
1245
|
+
const x = new Float64Array(6);
|
|
1246
|
+
const dq = new Float64Array(n);
|
|
1247
|
+
const baseSitePos = new Float64Array(3);
|
|
1248
|
+
const baseSiteMat = new Float64Array(9);
|
|
1249
|
+
const pertSitePos = new Float64Array(3);
|
|
1250
|
+
const pertSiteMat = new Float64Array(9);
|
|
1251
|
+
let bestQ = null;
|
|
1252
|
+
let bestErr = Infinity;
|
|
1253
|
+
for (let iter = 0; iter < o.maxIterations; iter++) {
|
|
1254
|
+
for (let i = 0; i < n; i++) data.qpos[i] = q[i];
|
|
1255
|
+
this.mujoco.mj_forward(model, data);
|
|
1256
|
+
const sp = data.site_xpos;
|
|
1257
|
+
const sm = data.site_xmat;
|
|
1258
|
+
const off3 = siteId * 3;
|
|
1259
|
+
const off9 = siteId * 9;
|
|
1260
|
+
for (let i = 0; i < 3; i++) baseSitePos[i] = sp[off3 + i];
|
|
1261
|
+
for (let i = 0; i < 9; i++) baseSiteMat[i] = sm[off9 + i];
|
|
1262
|
+
const posErr0 = targetPos.x - baseSitePos[0];
|
|
1263
|
+
const posErr1 = targetPos.y - baseSitePos[1];
|
|
1264
|
+
const posErr2 = targetPos.z - baseSitePos[2];
|
|
1265
|
+
const rotErr = orientationError(baseSiteMat, R_target);
|
|
1266
|
+
const error = [
|
|
1267
|
+
posErr0 * o.posWeight,
|
|
1268
|
+
posErr1 * o.posWeight,
|
|
1269
|
+
posErr2 * o.posWeight,
|
|
1270
|
+
rotErr[0] * o.rotWeight,
|
|
1271
|
+
rotErr[1] * o.rotWeight,
|
|
1272
|
+
rotErr[2] * o.rotWeight
|
|
1273
|
+
];
|
|
1274
|
+
const errNorm = Math.sqrt(
|
|
1275
|
+
error[0] * error[0] + error[1] * error[1] + error[2] * error[2] + error[3] * error[3] + error[4] * error[4] + error[5] * error[5]
|
|
1276
|
+
);
|
|
1277
|
+
if (errNorm < bestErr) {
|
|
1278
|
+
bestErr = errNorm;
|
|
1279
|
+
bestQ = Array.from(q);
|
|
1280
|
+
}
|
|
1281
|
+
if (errNorm < o.tolerance) break;
|
|
1282
|
+
for (let j = 0; j < n; j++) {
|
|
1283
|
+
const saved = data.qpos[j];
|
|
1284
|
+
data.qpos[j] = q[j] + o.epsilon;
|
|
1285
|
+
this.mujoco.mj_forward(model, data);
|
|
1286
|
+
for (let i = 0; i < 3; i++) pertSitePos[i] = sp[off3 + i];
|
|
1287
|
+
for (let i = 0; i < 9; i++) pertSiteMat[i] = sm[off9 + i];
|
|
1288
|
+
J[0 * n + j] = (pertSitePos[0] - baseSitePos[0]) / o.epsilon * o.posWeight;
|
|
1289
|
+
J[1 * n + j] = (pertSitePos[1] - baseSitePos[1]) / o.epsilon * o.posWeight;
|
|
1290
|
+
J[2 * n + j] = (pertSitePos[2] - baseSitePos[2]) / o.epsilon * o.posWeight;
|
|
1291
|
+
const dRot = angularDelta(baseSiteMat, pertSiteMat);
|
|
1292
|
+
J[3 * n + j] = dRot[0] / o.epsilon * o.rotWeight;
|
|
1293
|
+
J[4 * n + j] = dRot[1] / o.epsilon * o.rotWeight;
|
|
1294
|
+
J[5 * n + j] = dRot[2] / o.epsilon * o.rotWeight;
|
|
1295
|
+
data.qpos[j] = saved;
|
|
1296
|
+
}
|
|
1297
|
+
for (let i = 0; i < n; i++) data.qpos[i] = q[i];
|
|
1298
|
+
for (let r = 0; r < 6; r++) {
|
|
1299
|
+
for (let c = 0; c < 6; c++) {
|
|
1300
|
+
let sum = 0;
|
|
1301
|
+
for (let k = 0; k < n; k++) {
|
|
1302
|
+
sum += J[r * n + k] * J[c * n + k];
|
|
1303
|
+
}
|
|
1304
|
+
JJt[r * 6 + c] = sum + (r === c ? o.damping : 0);
|
|
1305
|
+
}
|
|
1306
|
+
}
|
|
1307
|
+
for (let i = 0; i < 6; i++) rhs[i] = error[i];
|
|
1308
|
+
solve6x6(JJt, rhs, x);
|
|
1309
|
+
for (let j = 0; j < n; j++) {
|
|
1310
|
+
let sum = 0;
|
|
1311
|
+
for (let r = 0; r < 6; r++) {
|
|
1312
|
+
sum += J[r * n + j] * x[r];
|
|
1313
|
+
}
|
|
1314
|
+
dq[j] = sum;
|
|
1315
|
+
}
|
|
1316
|
+
for (let i = 0; i < n; i++) q[i] += dq[i];
|
|
1317
|
+
}
|
|
1318
|
+
data.qpos.set(savedQpos);
|
|
1319
|
+
this.mujoco.mj_forward(model, data);
|
|
1320
|
+
return bestQ;
|
|
1321
|
+
}
|
|
1322
|
+
};
|
|
1323
|
+
function quatToMat3(q) {
|
|
1324
|
+
const m = new Float64Array(9);
|
|
1325
|
+
const x = q.x, y = q.y, z = q.z, w = q.w;
|
|
1326
|
+
const xx = x * x, yy = y * y, zz = z * z;
|
|
1327
|
+
const xy = x * y, xz = x * z, yz = y * z;
|
|
1328
|
+
const wx = w * x, wy = w * y, wz = w * z;
|
|
1329
|
+
m[0] = 1 - 2 * (yy + zz);
|
|
1330
|
+
m[1] = 2 * (xy - wz);
|
|
1331
|
+
m[2] = 2 * (xz + wy);
|
|
1332
|
+
m[3] = 2 * (xy + wz);
|
|
1333
|
+
m[4] = 1 - 2 * (xx + zz);
|
|
1334
|
+
m[5] = 2 * (yz - wx);
|
|
1335
|
+
m[6] = 2 * (xz - wy);
|
|
1336
|
+
m[7] = 2 * (yz + wx);
|
|
1337
|
+
m[8] = 1 - 2 * (xx + yy);
|
|
1338
|
+
return m;
|
|
1339
|
+
}
|
|
1340
|
+
function orientationError(R_cur, R_tgt) {
|
|
1341
|
+
const Re = new Float64Array(9);
|
|
1342
|
+
for (let i = 0; i < 3; i++) {
|
|
1343
|
+
for (let j = 0; j < 3; j++) {
|
|
1344
|
+
let s2 = 0;
|
|
1345
|
+
for (let k = 0; k < 3; k++) {
|
|
1346
|
+
s2 += R_tgt[i * 3 + k] * R_cur[j * 3 + k];
|
|
1347
|
+
}
|
|
1348
|
+
Re[i * 3 + j] = s2;
|
|
1349
|
+
}
|
|
1350
|
+
}
|
|
1351
|
+
const trace = Re[0] + Re[4] + Re[8];
|
|
1352
|
+
const cosAngle = Math.max(-1, Math.min(1, (trace - 1) * 0.5));
|
|
1353
|
+
const angle = Math.acos(cosAngle);
|
|
1354
|
+
if (angle < 1e-6) {
|
|
1355
|
+
return [0, 0, 0];
|
|
1356
|
+
}
|
|
1357
|
+
if (angle > Math.PI - 1e-6) {
|
|
1358
|
+
return [
|
|
1359
|
+
0.5 * (Re[7] - Re[5]),
|
|
1360
|
+
0.5 * (Re[2] - Re[6]),
|
|
1361
|
+
0.5 * (Re[3] - Re[1])
|
|
1362
|
+
];
|
|
1363
|
+
}
|
|
1364
|
+
const s = angle / (2 * Math.sin(angle));
|
|
1365
|
+
return [
|
|
1366
|
+
s * (Re[7] - Re[5]),
|
|
1367
|
+
s * (Re[2] - Re[6]),
|
|
1368
|
+
s * (Re[3] - Re[1])
|
|
1369
|
+
];
|
|
1370
|
+
}
|
|
1371
|
+
function angularDelta(R_base, R_pert) {
|
|
1372
|
+
const dR = new Float64Array(9);
|
|
1373
|
+
for (let i = 0; i < 3; i++) {
|
|
1374
|
+
for (let j = 0; j < 3; j++) {
|
|
1375
|
+
let s = 0;
|
|
1376
|
+
for (let k = 0; k < 3; k++) {
|
|
1377
|
+
s += R_pert[i * 3 + k] * R_base[j * 3 + k];
|
|
1378
|
+
}
|
|
1379
|
+
dR[i * 3 + j] = s;
|
|
1380
|
+
}
|
|
1381
|
+
}
|
|
1382
|
+
return [
|
|
1383
|
+
0.5 * (dR[7] - dR[5]),
|
|
1384
|
+
0.5 * (dR[2] - dR[6]),
|
|
1385
|
+
0.5 * (dR[3] - dR[1])
|
|
1386
|
+
];
|
|
1387
|
+
}
|
|
1388
|
+
function solve6x6(A, b, x) {
|
|
1389
|
+
const N = 6;
|
|
1390
|
+
const a = new Float64Array(A);
|
|
1391
|
+
const r = new Float64Array(b);
|
|
1392
|
+
for (let col = 0; col < N; col++) {
|
|
1393
|
+
let maxVal = Math.abs(a[col * N + col]);
|
|
1394
|
+
let maxRow = col;
|
|
1395
|
+
for (let row = col + 1; row < N; row++) {
|
|
1396
|
+
const val = Math.abs(a[row * N + col]);
|
|
1397
|
+
if (val > maxVal) {
|
|
1398
|
+
maxVal = val;
|
|
1399
|
+
maxRow = row;
|
|
1400
|
+
}
|
|
1401
|
+
}
|
|
1402
|
+
if (maxRow !== col) {
|
|
1403
|
+
for (let k = 0; k < N; k++) {
|
|
1404
|
+
const tmp2 = a[col * N + k];
|
|
1405
|
+
a[col * N + k] = a[maxRow * N + k];
|
|
1406
|
+
a[maxRow * N + k] = tmp2;
|
|
1407
|
+
}
|
|
1408
|
+
const tmp = r[col];
|
|
1409
|
+
r[col] = r[maxRow];
|
|
1410
|
+
r[maxRow] = tmp;
|
|
1411
|
+
}
|
|
1412
|
+
const pivot = a[col * N + col];
|
|
1413
|
+
if (Math.abs(pivot) < 1e-12) {
|
|
1414
|
+
x.fill(0);
|
|
1415
|
+
return;
|
|
1416
|
+
}
|
|
1417
|
+
for (let row = col + 1; row < N; row++) {
|
|
1418
|
+
const factor = a[row * N + col] / pivot;
|
|
1419
|
+
for (let k = col; k < N; k++) {
|
|
1420
|
+
a[row * N + k] -= factor * a[col * N + k];
|
|
1421
|
+
}
|
|
1422
|
+
r[row] -= factor * r[col];
|
|
1423
|
+
}
|
|
1424
|
+
}
|
|
1425
|
+
for (let row = N - 1; row >= 0; row--) {
|
|
1426
|
+
let sum = r[row];
|
|
1427
|
+
for (let k = row + 1; k < N; k++) {
|
|
1428
|
+
sum -= a[row * N + k] * x[k];
|
|
1429
|
+
}
|
|
1430
|
+
x[row] = sum / a[row * N + row];
|
|
1431
|
+
}
|
|
1432
|
+
}
|
|
1433
|
+
var _syncMat4 = new THREE11.Matrix4();
|
|
1434
|
+
function syncGizmoToSite(data, siteId, target) {
|
|
1435
|
+
if (siteId === -1) return;
|
|
1436
|
+
const sitePos = data.site_xpos.subarray(siteId * 3, siteId * 3 + 3);
|
|
1437
|
+
const siteMat = data.site_xmat.subarray(siteId * 9, siteId * 9 + 9);
|
|
1438
|
+
target.position.set(sitePos[0], sitePos[1], sitePos[2]);
|
|
1439
|
+
_syncMat4.set(
|
|
1440
|
+
siteMat[0],
|
|
1441
|
+
siteMat[1],
|
|
1442
|
+
siteMat[2],
|
|
1443
|
+
0,
|
|
1444
|
+
siteMat[3],
|
|
1445
|
+
siteMat[4],
|
|
1446
|
+
siteMat[5],
|
|
1447
|
+
0,
|
|
1448
|
+
siteMat[6],
|
|
1449
|
+
siteMat[7],
|
|
1450
|
+
siteMat[8],
|
|
1451
|
+
0,
|
|
1452
|
+
0,
|
|
1453
|
+
0,
|
|
1454
|
+
0,
|
|
1455
|
+
1
|
|
1456
|
+
);
|
|
1457
|
+
target.quaternion.setFromRotationMatrix(_syncMat4);
|
|
1458
|
+
}
|
|
1459
|
+
function IkControllerImpl({
|
|
1460
|
+
config,
|
|
1461
|
+
children
|
|
1462
|
+
}) {
|
|
1463
|
+
const { mjModelRef, mjDataRef, mujocoRef, configRef, resetCallbacks, status } = useMujocoSim();
|
|
1464
|
+
const ikEnabledRef = useRef(false);
|
|
1465
|
+
const ikCalculatingRef = useRef(false);
|
|
1466
|
+
const ikTargetRef = useRef(new THREE11.Group());
|
|
1467
|
+
const siteIdRef = useRef(-1);
|
|
1468
|
+
const genericIkRef = useRef(new GenericIK(mujocoRef.current));
|
|
1469
|
+
const firstIkEnableRef = useRef(true);
|
|
1470
|
+
const needsInitialSync = useRef(true);
|
|
1471
|
+
const gizmoAnimRef = useRef({
|
|
1472
|
+
active: false,
|
|
1473
|
+
startPos: new THREE11.Vector3(),
|
|
1474
|
+
endPos: new THREE11.Vector3(),
|
|
1475
|
+
startRot: new THREE11.Quaternion(),
|
|
1476
|
+
endRot: new THREE11.Quaternion(),
|
|
1477
|
+
startTime: 0,
|
|
1478
|
+
duration: 1e3
|
|
1479
|
+
});
|
|
1480
|
+
useEffect(() => {
|
|
1481
|
+
const model = mjModelRef.current;
|
|
1482
|
+
if (!model || status !== "ready") {
|
|
1483
|
+
siteIdRef.current = -1;
|
|
1484
|
+
return;
|
|
1485
|
+
}
|
|
1486
|
+
siteIdRef.current = findSiteByName(model, config.siteName);
|
|
1487
|
+
const data = mjDataRef.current;
|
|
1488
|
+
if (data && ikTargetRef.current) {
|
|
1489
|
+
syncGizmoToSite(data, siteIdRef.current, ikTargetRef.current);
|
|
1490
|
+
}
|
|
1491
|
+
}, [config.siteName, status, mjModelRef, mjDataRef]);
|
|
1492
|
+
const ikSolveFn = useCallback(
|
|
1493
|
+
(pos, quat, currentQ) => {
|
|
1494
|
+
if (config.ikSolveFn) return config.ikSolveFn(pos, quat, currentQ);
|
|
1495
|
+
const model = mjModelRef.current;
|
|
1496
|
+
const data = mjDataRef.current;
|
|
1497
|
+
if (!model || !data || siteIdRef.current === -1) return null;
|
|
1498
|
+
return genericIkRef.current.solve(
|
|
1499
|
+
model,
|
|
1500
|
+
data,
|
|
1501
|
+
siteIdRef.current,
|
|
1502
|
+
config.numJoints,
|
|
1503
|
+
pos,
|
|
1504
|
+
quat,
|
|
1505
|
+
currentQ,
|
|
1506
|
+
{
|
|
1507
|
+
damping: config.damping,
|
|
1508
|
+
maxIterations: config.maxIterations
|
|
1509
|
+
}
|
|
1510
|
+
);
|
|
1511
|
+
},
|
|
1512
|
+
[config.ikSolveFn, config.numJoints, config.damping, config.maxIterations, mjModelRef, mjDataRef]
|
|
1513
|
+
);
|
|
1514
|
+
const ikSolveFnRef = useRef(ikSolveFn);
|
|
1515
|
+
ikSolveFnRef.current = ikSolveFn;
|
|
1516
|
+
useFrame(() => {
|
|
1517
|
+
if (needsInitialSync.current && siteIdRef.current !== -1) {
|
|
1518
|
+
const data = mjDataRef.current;
|
|
1519
|
+
if (data && ikTargetRef.current) {
|
|
1520
|
+
syncGizmoToSite(data, siteIdRef.current, ikTargetRef.current);
|
|
1521
|
+
needsInitialSync.current = false;
|
|
1522
|
+
}
|
|
1523
|
+
}
|
|
1524
|
+
const ga = gizmoAnimRef.current;
|
|
1525
|
+
const target = ikTargetRef.current;
|
|
1526
|
+
if (!ga.active || !target) return;
|
|
1527
|
+
const now = performance.now();
|
|
1528
|
+
const elapsed = now - ga.startTime;
|
|
1529
|
+
const t = Math.min(elapsed / ga.duration, 1);
|
|
1530
|
+
const ease = 1 - Math.pow(1 - t, 3);
|
|
1531
|
+
target.position.lerpVectors(ga.startPos, ga.endPos, ease);
|
|
1532
|
+
target.quaternion.slerpQuaternions(ga.startRot, ga.endRot, ease);
|
|
1533
|
+
if (t >= 1) ga.active = false;
|
|
1534
|
+
});
|
|
1535
|
+
useBeforePhysicsStep((model, data) => {
|
|
1536
|
+
if (!ikEnabledRef.current) {
|
|
1537
|
+
ikCalculatingRef.current = false;
|
|
1538
|
+
return;
|
|
1539
|
+
}
|
|
1540
|
+
const target = ikTargetRef.current;
|
|
1541
|
+
if (!target) return;
|
|
1542
|
+
ikCalculatingRef.current = true;
|
|
1543
|
+
const numJoints = config.numJoints;
|
|
1544
|
+
const currentQ = [];
|
|
1545
|
+
for (let i = 0; i < numJoints; i++) currentQ.push(data.qpos[i]);
|
|
1546
|
+
const solution = ikSolveFnRef.current(target.position, target.quaternion, currentQ);
|
|
1547
|
+
if (solution) {
|
|
1548
|
+
for (let i = 0; i < numJoints; i++) data.ctrl[i] = solution[i];
|
|
1549
|
+
}
|
|
1550
|
+
});
|
|
1551
|
+
useEffect(() => {
|
|
1552
|
+
const cb = () => {
|
|
1553
|
+
const data = mjDataRef.current;
|
|
1554
|
+
if (data && ikTargetRef.current) {
|
|
1555
|
+
syncGizmoToSite(data, siteIdRef.current, ikTargetRef.current);
|
|
1556
|
+
}
|
|
1557
|
+
gizmoAnimRef.current.active = false;
|
|
1558
|
+
firstIkEnableRef.current = true;
|
|
1559
|
+
ikEnabledRef.current = false;
|
|
1560
|
+
needsInitialSync.current = true;
|
|
1561
|
+
};
|
|
1562
|
+
resetCallbacks.current.add(cb);
|
|
1563
|
+
return () => {
|
|
1564
|
+
resetCallbacks.current.delete(cb);
|
|
1565
|
+
};
|
|
1566
|
+
}, [resetCallbacks, mjDataRef]);
|
|
1567
|
+
const setIkEnabled = useCallback(
|
|
1568
|
+
(enabled) => {
|
|
1569
|
+
ikEnabledRef.current = enabled;
|
|
1570
|
+
const data = mjDataRef.current;
|
|
1571
|
+
if (enabled && data && !gizmoAnimRef.current.active && ikTargetRef.current) {
|
|
1572
|
+
syncGizmoToSite(data, siteIdRef.current, ikTargetRef.current);
|
|
1573
|
+
firstIkEnableRef.current = false;
|
|
1574
|
+
}
|
|
1575
|
+
},
|
|
1576
|
+
[mjDataRef]
|
|
1577
|
+
);
|
|
1578
|
+
const syncTargetToSiteApi = useCallback(() => {
|
|
1579
|
+
const data = mjDataRef.current;
|
|
1580
|
+
const target = ikTargetRef.current;
|
|
1581
|
+
if (data && target) syncGizmoToSite(data, siteIdRef.current, target);
|
|
1582
|
+
}, [mjDataRef]);
|
|
1583
|
+
const solveIK = useCallback(
|
|
1584
|
+
(pos, quat, currentQ) => {
|
|
1585
|
+
return ikSolveFnRef.current(pos, quat, currentQ);
|
|
1586
|
+
},
|
|
1587
|
+
[]
|
|
1588
|
+
);
|
|
1589
|
+
const moveTarget = useCallback(
|
|
1590
|
+
(pos, duration = 0) => {
|
|
1591
|
+
if (!ikEnabledRef.current) setIkEnabled(true);
|
|
1592
|
+
const target = ikTargetRef.current;
|
|
1593
|
+
if (!target) return;
|
|
1594
|
+
const targetPos = pos.clone();
|
|
1595
|
+
const targetRot = new THREE11.Quaternion().setFromEuler(
|
|
1596
|
+
new THREE11.Euler(Math.PI, 0, 0)
|
|
1597
|
+
);
|
|
1598
|
+
if (duration > 0) {
|
|
1599
|
+
const ga = gizmoAnimRef.current;
|
|
1600
|
+
ga.active = true;
|
|
1601
|
+
ga.startPos.copy(target.position);
|
|
1602
|
+
ga.endPos.copy(targetPos);
|
|
1603
|
+
ga.startRot.copy(target.quaternion);
|
|
1604
|
+
ga.endRot.copy(targetRot);
|
|
1605
|
+
ga.startTime = performance.now();
|
|
1606
|
+
ga.duration = duration;
|
|
1607
|
+
} else {
|
|
1608
|
+
gizmoAnimRef.current.active = false;
|
|
1609
|
+
target.position.copy(targetPos);
|
|
1610
|
+
target.quaternion.copy(targetRot);
|
|
1611
|
+
}
|
|
1612
|
+
},
|
|
1613
|
+
[setIkEnabled]
|
|
1614
|
+
);
|
|
1615
|
+
const getGizmoStats = useCallback(
|
|
1616
|
+
() => {
|
|
1617
|
+
const target = ikTargetRef.current;
|
|
1618
|
+
if (!ikCalculatingRef.current || !target) return null;
|
|
1619
|
+
return {
|
|
1620
|
+
pos: target.position.clone(),
|
|
1621
|
+
rot: new THREE11.Euler().setFromQuaternion(target.quaternion)
|
|
1622
|
+
};
|
|
1623
|
+
},
|
|
1624
|
+
[]
|
|
1625
|
+
);
|
|
1497
1626
|
const contextValue = useMemo(
|
|
1498
1627
|
() => ({
|
|
1499
|
-
api,
|
|
1500
|
-
mjModelRef,
|
|
1501
|
-
mjDataRef,
|
|
1502
|
-
mujocoRef,
|
|
1503
|
-
configRef,
|
|
1504
|
-
siteIdRef,
|
|
1505
|
-
gripperIdRef,
|
|
1506
1628
|
ikEnabledRef,
|
|
1507
1629
|
ikCalculatingRef,
|
|
1508
|
-
pausedRef,
|
|
1509
|
-
speedRef,
|
|
1510
|
-
substepsRef,
|
|
1511
1630
|
ikTargetRef,
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
beforeStepCallbacks,
|
|
1519
|
-
afterStepCallbacks,
|
|
1520
|
-
status
|
|
1631
|
+
siteIdRef,
|
|
1632
|
+
setIkEnabled,
|
|
1633
|
+
moveTarget,
|
|
1634
|
+
syncTargetToSite: syncTargetToSiteApi,
|
|
1635
|
+
solveIK,
|
|
1636
|
+
getGizmoStats
|
|
1521
1637
|
}),
|
|
1522
|
-
[
|
|
1638
|
+
[setIkEnabled, moveTarget, syncTargetToSiteApi, solveIK, getGizmoStats]
|
|
1523
1639
|
);
|
|
1524
|
-
return /* @__PURE__ */ jsx(
|
|
1640
|
+
return /* @__PURE__ */ jsx(IkContext.Provider, { value: contextValue, children });
|
|
1525
1641
|
}
|
|
1526
|
-
var
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
timestep,
|
|
1536
|
-
substeps,
|
|
1537
|
-
paused,
|
|
1538
|
-
speed,
|
|
1539
|
-
interpolate,
|
|
1540
|
-
gravityCompensation,
|
|
1541
|
-
mjcfLights,
|
|
1542
|
-
children,
|
|
1543
|
-
...canvasProps
|
|
1544
|
-
}, ref) {
|
|
1545
|
-
const { mujoco, status: wasmStatus, error: wasmError } = useMujoco();
|
|
1546
|
-
useEffect(() => {
|
|
1547
|
-
if (wasmStatus === "error" && onError) {
|
|
1548
|
-
onError(new Error(wasmError ?? "WASM load failed"));
|
|
1549
|
-
}
|
|
1550
|
-
}, [wasmStatus, wasmError, onError]);
|
|
1551
|
-
if (wasmStatus === "error" || wasmStatus === "loading" || !mujoco) {
|
|
1552
|
-
return null;
|
|
1553
|
-
}
|
|
1554
|
-
return /* @__PURE__ */ jsx(Canvas, { ...canvasProps, children: /* @__PURE__ */ jsx(
|
|
1555
|
-
MujocoSimProvider,
|
|
1556
|
-
{
|
|
1557
|
-
mujoco,
|
|
1558
|
-
config,
|
|
1559
|
-
apiRef: ref,
|
|
1560
|
-
onReady,
|
|
1561
|
-
onError,
|
|
1562
|
-
onStep,
|
|
1563
|
-
onSelection,
|
|
1564
|
-
gravity,
|
|
1565
|
-
timestep,
|
|
1566
|
-
substeps,
|
|
1567
|
-
paused,
|
|
1568
|
-
speed,
|
|
1569
|
-
interpolate,
|
|
1570
|
-
children
|
|
1571
|
-
}
|
|
1572
|
-
) });
|
|
1573
|
-
}
|
|
1642
|
+
var IkController = createController(
|
|
1643
|
+
{
|
|
1644
|
+
name: "IkController",
|
|
1645
|
+
defaultConfig: {
|
|
1646
|
+
damping: 0.01,
|
|
1647
|
+
maxIterations: 50
|
|
1648
|
+
}
|
|
1649
|
+
},
|
|
1650
|
+
IkControllerImpl
|
|
1574
1651
|
);
|
|
1575
|
-
var CapsuleGeometry = class extends
|
|
1652
|
+
var CapsuleGeometry = class extends THREE11.BufferGeometry {
|
|
1576
1653
|
parameters;
|
|
1577
1654
|
constructor(radius = 1, length = 1, capSegments = 4, radialSegments = 8) {
|
|
1578
1655
|
super();
|
|
1579
1656
|
this.type = "CapsuleGeometry";
|
|
1580
1657
|
this.parameters = { radius, length, capSegments, radialSegments };
|
|
1581
|
-
const path = new
|
|
1658
|
+
const path = new THREE11.Path();
|
|
1582
1659
|
path.absarc(0, -length / 2, radius, Math.PI * 1.5, 0, false);
|
|
1583
1660
|
path.absarc(0, length / 2, radius, 0, Math.PI * 0.5, false);
|
|
1584
|
-
const latheGeometry = new
|
|
1661
|
+
const latheGeometry = new THREE11.LatheGeometry(path.getPoints(capSegments), radialSegments);
|
|
1585
1662
|
const self = this;
|
|
1586
1663
|
self.setIndex(latheGeometry.getIndex());
|
|
1587
1664
|
self.setAttribute("position", latheGeometry.getAttribute("position"));
|
|
@@ -1589,27 +1666,27 @@ var CapsuleGeometry = class extends THREE.BufferGeometry {
|
|
|
1589
1666
|
self.setAttribute("uv", latheGeometry.getAttribute("uv"));
|
|
1590
1667
|
}
|
|
1591
1668
|
};
|
|
1592
|
-
var Reflector = class extends
|
|
1669
|
+
var Reflector = class extends THREE11.Mesh {
|
|
1593
1670
|
isReflector = true;
|
|
1594
1671
|
camera;
|
|
1595
|
-
reflectorPlane = new
|
|
1596
|
-
normal = new
|
|
1597
|
-
reflectorWorldPosition = new
|
|
1598
|
-
cameraWorldPosition = new
|
|
1599
|
-
rotationMatrix = new
|
|
1600
|
-
lookAtPosition = new
|
|
1601
|
-
clipPlane = new
|
|
1602
|
-
view = new
|
|
1603
|
-
target = new
|
|
1604
|
-
q = new
|
|
1605
|
-
textureMatrix = new
|
|
1672
|
+
reflectorPlane = new THREE11.Plane();
|
|
1673
|
+
normal = new THREE11.Vector3();
|
|
1674
|
+
reflectorWorldPosition = new THREE11.Vector3();
|
|
1675
|
+
cameraWorldPosition = new THREE11.Vector3();
|
|
1676
|
+
rotationMatrix = new THREE11.Matrix4();
|
|
1677
|
+
lookAtPosition = new THREE11.Vector3(0, 0, -1);
|
|
1678
|
+
clipPlane = new THREE11.Vector4();
|
|
1679
|
+
view = new THREE11.Vector3();
|
|
1680
|
+
target = new THREE11.Vector3();
|
|
1681
|
+
q = new THREE11.Vector4();
|
|
1682
|
+
textureMatrix = new THREE11.Matrix4();
|
|
1606
1683
|
virtualCamera;
|
|
1607
1684
|
renderTarget;
|
|
1608
1685
|
constructor(geometry, options = {}) {
|
|
1609
1686
|
super(geometry);
|
|
1610
1687
|
this.type = "Reflector";
|
|
1611
|
-
this.camera = new
|
|
1612
|
-
const color = options.color !== void 0 ? new
|
|
1688
|
+
this.camera = new THREE11.PerspectiveCamera();
|
|
1689
|
+
const color = options.color !== void 0 ? new THREE11.Color(options.color) : new THREE11.Color(8355711);
|
|
1613
1690
|
const textureWidth = options.textureWidth || 512;
|
|
1614
1691
|
const textureHeight = options.textureHeight || 512;
|
|
1615
1692
|
const clipBias = options.clipBias || 0;
|
|
@@ -1617,11 +1694,11 @@ var Reflector = class extends THREE.Mesh {
|
|
|
1617
1694
|
const blendTexture = options.texture || void 0;
|
|
1618
1695
|
const mixStrength = options.mixStrength !== void 0 ? options.mixStrength : 0.25;
|
|
1619
1696
|
this.virtualCamera = this.camera;
|
|
1620
|
-
this.renderTarget = new
|
|
1697
|
+
this.renderTarget = new THREE11.WebGLRenderTarget(textureWidth, textureHeight, {
|
|
1621
1698
|
samples: multisample,
|
|
1622
|
-
type:
|
|
1699
|
+
type: THREE11.HalfFloatType
|
|
1623
1700
|
});
|
|
1624
|
-
this.material = new
|
|
1701
|
+
this.material = new THREE11.MeshPhysicalMaterial({
|
|
1625
1702
|
map: blendTexture,
|
|
1626
1703
|
color,
|
|
1627
1704
|
roughness: 0.5,
|
|
@@ -1747,7 +1824,7 @@ var GeomBuilder = class {
|
|
|
1747
1824
|
const pos = mjModel.geom_pos.subarray(g * 3, g * 3 + 3);
|
|
1748
1825
|
const quat = mjModel.geom_quat.subarray(g * 4, g * 4 + 4);
|
|
1749
1826
|
const matId = mjModel.geom_matid[g];
|
|
1750
|
-
const color = new
|
|
1827
|
+
const color = new THREE11.Color(16777215);
|
|
1751
1828
|
let opacity = 1;
|
|
1752
1829
|
if (matId >= 0) {
|
|
1753
1830
|
const rgba = mjModel.mat_rgba.subarray(matId * 4, matId * 4 + 4);
|
|
@@ -1762,16 +1839,16 @@ var GeomBuilder = class {
|
|
|
1762
1839
|
let geo = null;
|
|
1763
1840
|
const getVal = (v) => v?.value ?? v;
|
|
1764
1841
|
if (type === getVal(MG.mjGEOM_PLANE)) {
|
|
1765
|
-
geo = new
|
|
1842
|
+
geo = new THREE11.PlaneGeometry(size[0] * 2 || 5, size[1] * 2 || 5);
|
|
1766
1843
|
} else if (type === getVal(MG.mjGEOM_SPHERE)) {
|
|
1767
|
-
geo = new
|
|
1844
|
+
geo = new THREE11.SphereGeometry(size[0], 24, 24);
|
|
1768
1845
|
} else if (type === getVal(MG.mjGEOM_CAPSULE)) {
|
|
1769
1846
|
geo = new CapsuleGeometry(size[0], size[1] * 2, 24, 12);
|
|
1770
1847
|
geo.rotateX(Math.PI / 2);
|
|
1771
1848
|
} else if (type === getVal(MG.mjGEOM_BOX)) {
|
|
1772
|
-
geo = new
|
|
1849
|
+
geo = new THREE11.BoxGeometry(size[0] * 2, size[1] * 2, size[2] * 2);
|
|
1773
1850
|
} else if (type === getVal(MG.mjGEOM_CYLINDER)) {
|
|
1774
|
-
geo = new
|
|
1851
|
+
geo = new THREE11.CylinderGeometry(size[0], size[0], size[1] * 2, 24);
|
|
1775
1852
|
geo.rotateX(Math.PI / 2);
|
|
1776
1853
|
} else if (type === getVal(MG.mjGEOM_MESH)) {
|
|
1777
1854
|
const mId = mjModel.geom_dataid[g];
|
|
@@ -1779,8 +1856,8 @@ var GeomBuilder = class {
|
|
|
1779
1856
|
const vNum = mjModel.mesh_vertnum[mId];
|
|
1780
1857
|
const fAdr = mjModel.mesh_faceadr[mId];
|
|
1781
1858
|
const fNum = mjModel.mesh_facenum[mId];
|
|
1782
|
-
geo = new
|
|
1783
|
-
geo.setAttribute("position", new
|
|
1859
|
+
geo = new THREE11.BufferGeometry();
|
|
1860
|
+
geo.setAttribute("position", new THREE11.Float32BufferAttribute(mjModel.mesh_vert.subarray(vAdr * 3, (vAdr + vNum) * 3), 3));
|
|
1784
1861
|
geo.setIndex(Array.from(mjModel.mesh_face.subarray(fAdr * 3, (fAdr + fNum) * 3)));
|
|
1785
1862
|
geo.computeVertexNormals();
|
|
1786
1863
|
}
|
|
@@ -1795,7 +1872,7 @@ var GeomBuilder = class {
|
|
|
1795
1872
|
mixStrength: 0.25
|
|
1796
1873
|
});
|
|
1797
1874
|
} else {
|
|
1798
|
-
mesh = new
|
|
1875
|
+
mesh = new THREE11.Mesh(geo, new THREE11.MeshStandardMaterial({
|
|
1799
1876
|
color,
|
|
1800
1877
|
transparent: opacity < 1,
|
|
1801
1878
|
opacity,
|
|
@@ -1814,16 +1891,17 @@ var GeomBuilder = class {
|
|
|
1814
1891
|
return null;
|
|
1815
1892
|
}
|
|
1816
1893
|
};
|
|
1817
|
-
function SceneRenderer() {
|
|
1894
|
+
function SceneRenderer(props) {
|
|
1818
1895
|
const { mjModelRef, mjDataRef, mujocoRef, onSelectionRef, status } = useMujocoSim();
|
|
1819
1896
|
const groupRef = useRef(null);
|
|
1820
1897
|
const bodyRefs = useRef([]);
|
|
1821
1898
|
const prevModelRef = useRef(null);
|
|
1822
1899
|
const geomBuilder = useMemo(() => {
|
|
1900
|
+
if (status !== "ready") return null;
|
|
1823
1901
|
return new GeomBuilder(mujocoRef.current);
|
|
1824
|
-
}, [mujocoRef
|
|
1902
|
+
}, [status, mujocoRef]);
|
|
1825
1903
|
useEffect(() => {
|
|
1826
|
-
if (status !== "ready") return;
|
|
1904
|
+
if (status !== "ready" || !geomBuilder) return;
|
|
1827
1905
|
const model = mjModelRef.current;
|
|
1828
1906
|
const group = groupRef.current;
|
|
1829
1907
|
if (!model || !group) return;
|
|
@@ -1834,7 +1912,7 @@ function SceneRenderer() {
|
|
|
1834
1912
|
}
|
|
1835
1913
|
const refs = [];
|
|
1836
1914
|
for (let i = 0; i < model.nbody; i++) {
|
|
1837
|
-
const bodyGroup = new
|
|
1915
|
+
const bodyGroup = new THREE11.Group();
|
|
1838
1916
|
bodyGroup.userData.bodyID = i;
|
|
1839
1917
|
for (let g = 0; g < model.ngeom; g++) {
|
|
1840
1918
|
if (model.geom_bodyid[g] === i) {
|
|
@@ -1870,38 +1948,34 @@ function SceneRenderer() {
|
|
|
1870
1948
|
return /* @__PURE__ */ jsx(
|
|
1871
1949
|
"group",
|
|
1872
1950
|
{
|
|
1951
|
+
...props,
|
|
1873
1952
|
ref: groupRef,
|
|
1874
1953
|
onDoubleClick: (e) => {
|
|
1954
|
+
if (typeof props.onDoubleClick === "function") props.onDoubleClick(e);
|
|
1875
1955
|
e.stopPropagation();
|
|
1876
1956
|
let obj = e.object;
|
|
1877
1957
|
while (obj && obj.userData.bodyID === void 0 && obj.parent) {
|
|
1878
1958
|
obj = obj.parent;
|
|
1879
1959
|
}
|
|
1880
|
-
|
|
1960
|
+
const bodyID = obj?.userData.bodyID;
|
|
1961
|
+
if (typeof bodyID === "number" && bodyID > 0) {
|
|
1881
1962
|
const model = mjModelRef.current;
|
|
1882
|
-
if (model && onSelectionRef.current) {
|
|
1883
|
-
const name = getName(model, model.name_bodyadr[
|
|
1884
|
-
onSelectionRef.current(
|
|
1963
|
+
if (model && bodyID < model.nbody && onSelectionRef.current) {
|
|
1964
|
+
const name = getName(model, model.name_bodyadr[bodyID]);
|
|
1965
|
+
onSelectionRef.current(bodyID, name);
|
|
1885
1966
|
}
|
|
1886
1967
|
}
|
|
1887
1968
|
}
|
|
1888
1969
|
}
|
|
1889
1970
|
);
|
|
1890
1971
|
}
|
|
1891
|
-
var _mat4 = new
|
|
1892
|
-
var _pos = new
|
|
1893
|
-
var _quat = new
|
|
1894
|
-
var _scale = new
|
|
1972
|
+
var _mat4 = new THREE11.Matrix4();
|
|
1973
|
+
var _pos = new THREE11.Vector3();
|
|
1974
|
+
var _quat = new THREE11.Quaternion();
|
|
1975
|
+
var _scale = new THREE11.Vector3(1, 1, 1);
|
|
1895
1976
|
function IkGizmo({ siteName, scale = 0.18, onDrag }) {
|
|
1896
|
-
const {
|
|
1897
|
-
|
|
1898
|
-
mjModelRef,
|
|
1899
|
-
mjDataRef,
|
|
1900
|
-
siteIdRef,
|
|
1901
|
-
api,
|
|
1902
|
-
ikEnabledRef,
|
|
1903
|
-
status
|
|
1904
|
-
} = useMujocoSim();
|
|
1977
|
+
const { mjModelRef, mjDataRef, status } = useMujocoSim();
|
|
1978
|
+
const { ikTargetRef, siteIdRef, ikEnabledRef, setIkEnabled } = useIk();
|
|
1905
1979
|
const wrapperRef = useRef(null);
|
|
1906
1980
|
const pivotRef = useRef(null);
|
|
1907
1981
|
const draggingRef = useRef(false);
|
|
@@ -1909,19 +1983,15 @@ function IkGizmo({ siteName, scale = 0.18, onDrag }) {
|
|
|
1909
1983
|
const { controls } = useThree();
|
|
1910
1984
|
useEffect(() => {
|
|
1911
1985
|
const model = mjModelRef.current;
|
|
1912
|
-
if (!model || status !== "ready") {
|
|
1986
|
+
if (!model || status !== "ready" || !siteName) {
|
|
1913
1987
|
localSiteIdRef.current = -1;
|
|
1914
1988
|
return;
|
|
1915
1989
|
}
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
} else {
|
|
1919
|
-
localSiteIdRef.current = siteIdRef.current;
|
|
1920
|
-
}
|
|
1921
|
-
}, [siteName, status, mjModelRef, siteIdRef]);
|
|
1990
|
+
localSiteIdRef.current = findSiteByName(model, siteName);
|
|
1991
|
+
}, [siteName, status, mjModelRef]);
|
|
1922
1992
|
useFrame(() => {
|
|
1923
1993
|
const data = mjDataRef.current;
|
|
1924
|
-
const sid = localSiteIdRef.current;
|
|
1994
|
+
const sid = siteName ? localSiteIdRef.current : siteIdRef.current;
|
|
1925
1995
|
if (!data || sid < 0 || !wrapperRef.current) return;
|
|
1926
1996
|
if (!draggingRef.current) {
|
|
1927
1997
|
const p = data.site_xpos;
|
|
@@ -1966,7 +2036,7 @@ function IkGizmo({ siteName, scale = 0.18, onDrag }) {
|
|
|
1966
2036
|
onDragStart: () => {
|
|
1967
2037
|
draggingRef.current = true;
|
|
1968
2038
|
if (!onDrag) {
|
|
1969
|
-
if (!ikEnabledRef.current)
|
|
2039
|
+
if (!ikEnabledRef.current) setIkEnabled(true);
|
|
1970
2040
|
}
|
|
1971
2041
|
if (controls) controls.enabled = false;
|
|
1972
2042
|
},
|
|
@@ -1994,12 +2064,13 @@ function IkGizmo({ siteName, scale = 0.18, onDrag }) {
|
|
|
1994
2064
|
}
|
|
1995
2065
|
) });
|
|
1996
2066
|
}
|
|
1997
|
-
var _dummy = new
|
|
2067
|
+
var _dummy = new THREE11.Object3D();
|
|
1998
2068
|
function ContactMarkers({
|
|
1999
2069
|
maxContacts = 100,
|
|
2000
|
-
radius =
|
|
2001
|
-
color = "#
|
|
2002
|
-
visible = true
|
|
2070
|
+
radius = 8e-3,
|
|
2071
|
+
color = "#22d3ee",
|
|
2072
|
+
visible = true,
|
|
2073
|
+
...groupProps
|
|
2003
2074
|
} = {}) {
|
|
2004
2075
|
const { mjDataRef, status } = useMujocoSim();
|
|
2005
2076
|
const meshRef = useRef(null);
|
|
@@ -2013,62 +2084,53 @@ function ContactMarkers({
|
|
|
2013
2084
|
const ncon = data.ncon;
|
|
2014
2085
|
const count = Math.min(ncon, maxContacts);
|
|
2015
2086
|
for (let i = 0; i < count; i++) {
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
if (!c) break;
|
|
2019
|
-
_dummy.position.set(c.pos[0], c.pos[1], c.pos[2]);
|
|
2020
|
-
_dummy.updateMatrix();
|
|
2021
|
-
mesh.setMatrixAt(i, _dummy.matrix);
|
|
2022
|
-
} catch {
|
|
2087
|
+
const c = getContact(data, i);
|
|
2088
|
+
if (!c) {
|
|
2023
2089
|
mesh.count = i;
|
|
2024
2090
|
mesh.instanceMatrix.needsUpdate = true;
|
|
2025
2091
|
return;
|
|
2026
2092
|
}
|
|
2093
|
+
_dummy.position.set(c.pos[0], c.pos[1], c.pos[2]);
|
|
2094
|
+
_dummy.updateMatrix();
|
|
2095
|
+
mesh.setMatrixAt(i, _dummy.matrix);
|
|
2027
2096
|
}
|
|
2028
2097
|
mesh.count = count;
|
|
2029
2098
|
mesh.instanceMatrix.needsUpdate = true;
|
|
2030
2099
|
});
|
|
2031
2100
|
if (status !== "ready") return null;
|
|
2032
|
-
return /* @__PURE__ */ jsxs("instancedMesh", { ref: meshRef, args: [void 0, void 0, maxContacts], children: [
|
|
2101
|
+
return /* @__PURE__ */ jsx("group", { ...groupProps, children: /* @__PURE__ */ jsxs("instancedMesh", { ref: meshRef, args: [void 0, void 0, maxContacts], frustumCulled: false, renderOrder: 999, children: [
|
|
2033
2102
|
/* @__PURE__ */ jsx("sphereGeometry", { args: [radius, 8, 8] }),
|
|
2034
|
-
/* @__PURE__ */ jsx(
|
|
2035
|
-
|
|
2036
|
-
{
|
|
2037
|
-
color,
|
|
2038
|
-
emissive: color,
|
|
2039
|
-
emissiveIntensity: 0.3,
|
|
2040
|
-
roughness: 0.5
|
|
2041
|
-
}
|
|
2042
|
-
)
|
|
2043
|
-
] });
|
|
2103
|
+
/* @__PURE__ */ jsx("meshBasicMaterial", { color, depthTest: false })
|
|
2104
|
+
] }) });
|
|
2044
2105
|
}
|
|
2045
2106
|
var _force = new Float64Array(3);
|
|
2046
2107
|
var _torque = new Float64Array(3);
|
|
2047
2108
|
var _point = new Float64Array(3);
|
|
2048
|
-
var _bodyPos = new
|
|
2049
|
-
var _bodyQuat = new
|
|
2050
|
-
var _worldHit = new
|
|
2051
|
-
var _raycaster = new
|
|
2052
|
-
var _mouse = new
|
|
2109
|
+
var _bodyPos = new THREE11.Vector3();
|
|
2110
|
+
var _bodyQuat = new THREE11.Quaternion();
|
|
2111
|
+
var _worldHit = new THREE11.Vector3();
|
|
2112
|
+
var _raycaster = new THREE11.Raycaster();
|
|
2113
|
+
var _mouse = new THREE11.Vector2();
|
|
2053
2114
|
function DragInteraction({
|
|
2054
2115
|
stiffness = 250,
|
|
2055
|
-
showArrow = true
|
|
2116
|
+
showArrow = true,
|
|
2117
|
+
...groupProps
|
|
2056
2118
|
}) {
|
|
2057
2119
|
const { mjDataRef, mujocoRef, mjModelRef, status } = useMujocoSim();
|
|
2058
2120
|
const { gl, camera, scene, controls } = useThree();
|
|
2059
2121
|
const draggingRef = useRef(false);
|
|
2060
2122
|
const bodyIdRef = useRef(-1);
|
|
2061
2123
|
const grabDistanceRef = useRef(0);
|
|
2062
|
-
const localHitRef = useRef(new
|
|
2063
|
-
const grabWorldRef = useRef(new
|
|
2064
|
-
const mouseWorldRef = useRef(new
|
|
2124
|
+
const localHitRef = useRef(new THREE11.Vector3());
|
|
2125
|
+
const grabWorldRef = useRef(new THREE11.Vector3());
|
|
2126
|
+
const mouseWorldRef = useRef(new THREE11.Vector3());
|
|
2065
2127
|
const arrowRef = useRef(null);
|
|
2066
2128
|
const groupRef = useRef(null);
|
|
2067
2129
|
useEffect(() => {
|
|
2068
2130
|
if (!showArrow || !groupRef.current) return;
|
|
2069
|
-
const arrow = new
|
|
2070
|
-
new
|
|
2071
|
-
new
|
|
2131
|
+
const arrow = new THREE11.ArrowHelper(
|
|
2132
|
+
new THREE11.Vector3(0, 1, 0),
|
|
2133
|
+
new THREE11.Vector3(),
|
|
2072
2134
|
0.1,
|
|
2073
2135
|
16729156
|
|
2074
2136
|
);
|
|
@@ -2196,7 +2258,7 @@ function DragInteraction({
|
|
|
2196
2258
|
if (!arrow) return;
|
|
2197
2259
|
if (draggingRef.current && bodyIdRef.current > 0) {
|
|
2198
2260
|
arrow.visible = true;
|
|
2199
|
-
const dir = mouseWorldRef.current
|
|
2261
|
+
const dir = _bodyPos.copy(mouseWorldRef.current).sub(grabWorldRef.current);
|
|
2200
2262
|
const len = dir.length();
|
|
2201
2263
|
if (len > 1e-3) {
|
|
2202
2264
|
dir.normalize();
|
|
@@ -2209,9 +2271,9 @@ function DragInteraction({
|
|
|
2209
2271
|
}
|
|
2210
2272
|
});
|
|
2211
2273
|
if (status !== "ready") return null;
|
|
2212
|
-
return /* @__PURE__ */ jsx("group", { ref: groupRef });
|
|
2274
|
+
return /* @__PURE__ */ jsx("group", { ...groupProps, ref: groupRef });
|
|
2213
2275
|
}
|
|
2214
|
-
function
|
|
2276
|
+
function useSceneLights(intensity = 1) {
|
|
2215
2277
|
const { mjModelRef, status } = useMujocoSim();
|
|
2216
2278
|
const { scene } = useThree();
|
|
2217
2279
|
const lightsRef = useRef([]);
|
|
@@ -2239,7 +2301,7 @@ function SceneLights({ intensity = 1 }) {
|
|
|
2239
2301
|
const dr = model.light_diffuse ? model.light_diffuse[3 * i] : 1;
|
|
2240
2302
|
const dg = model.light_diffuse ? model.light_diffuse[3 * i + 1] : 1;
|
|
2241
2303
|
const db = model.light_diffuse ? model.light_diffuse[3 * i + 2] : 1;
|
|
2242
|
-
const color = new
|
|
2304
|
+
const color = new THREE11.Color(dr, dg, db);
|
|
2243
2305
|
const px = model.light_pos[3 * i];
|
|
2244
2306
|
const py = model.light_pos[3 * i + 1];
|
|
2245
2307
|
const pz = model.light_pos[3 * i + 2];
|
|
@@ -2247,7 +2309,7 @@ function SceneLights({ intensity = 1 }) {
|
|
|
2247
2309
|
const dy = model.light_dir[3 * i + 1];
|
|
2248
2310
|
const dz = model.light_dir[3 * i + 2];
|
|
2249
2311
|
if (isDirectional) {
|
|
2250
|
-
const light = new
|
|
2312
|
+
const light = new THREE11.DirectionalLight(color, finalIntensity);
|
|
2251
2313
|
light.position.set(px, py, pz);
|
|
2252
2314
|
light.target.position.set(px + dx, py + dy, pz + dz);
|
|
2253
2315
|
light.castShadow = castShadow;
|
|
@@ -2270,7 +2332,7 @@ function SceneLights({ intensity = 1 }) {
|
|
|
2270
2332
|
const cutoff = model.light_cutoff ? model.light_cutoff[i] : 45;
|
|
2271
2333
|
const exponent = model.light_exponent ? model.light_exponent[i] : 10;
|
|
2272
2334
|
const angle = cutoff * Math.PI / 180;
|
|
2273
|
-
const light = new
|
|
2335
|
+
const light = new THREE11.SpotLight(color, finalIntensity, 0, angle, exponent / 128);
|
|
2274
2336
|
light.position.set(px, py, pz);
|
|
2275
2337
|
light.target.position.set(px + dx, py + dy, pz + dz);
|
|
2276
2338
|
light.castShadow = castShadow;
|
|
@@ -2300,6 +2362,11 @@ function SceneLights({ intensity = 1 }) {
|
|
|
2300
2362
|
targetsRef.current = [];
|
|
2301
2363
|
};
|
|
2302
2364
|
}, [status, mjModelRef, scene, intensity]);
|
|
2365
|
+
}
|
|
2366
|
+
|
|
2367
|
+
// src/components/SceneLights.tsx
|
|
2368
|
+
function SceneLights({ intensity = 1 }) {
|
|
2369
|
+
useSceneLights(intensity);
|
|
2303
2370
|
return null;
|
|
2304
2371
|
}
|
|
2305
2372
|
var JOINT_COLORS = {
|
|
@@ -2312,6 +2379,12 @@ var JOINT_COLORS = {
|
|
|
2312
2379
|
3: 16776960
|
|
2313
2380
|
// hinge - yellow
|
|
2314
2381
|
};
|
|
2382
|
+
var _v3a = new THREE11.Vector3();
|
|
2383
|
+
new THREE11.Vector3();
|
|
2384
|
+
var _quat2 = new THREE11.Quaternion();
|
|
2385
|
+
var _contactPos = new THREE11.Vector3();
|
|
2386
|
+
var _contactNormal = new THREE11.Vector3();
|
|
2387
|
+
var MAX_CONTACT_ARROWS = 50;
|
|
2315
2388
|
function Debug({
|
|
2316
2389
|
showGeoms = false,
|
|
2317
2390
|
showSites = false,
|
|
@@ -2319,7 +2392,8 @@ function Debug({
|
|
|
2319
2392
|
showContacts = false,
|
|
2320
2393
|
showCOM = false,
|
|
2321
2394
|
showInertia = false,
|
|
2322
|
-
showTendons = false
|
|
2395
|
+
showTendons = false,
|
|
2396
|
+
...groupProps
|
|
2323
2397
|
}) {
|
|
2324
2398
|
const { mjModelRef, mjDataRef, status } = useMujocoSim();
|
|
2325
2399
|
const { scene } = useThree();
|
|
@@ -2338,21 +2412,21 @@ function Debug({
|
|
|
2338
2412
|
let geometry = null;
|
|
2339
2413
|
switch (type) {
|
|
2340
2414
|
case 2:
|
|
2341
|
-
geometry = new
|
|
2415
|
+
geometry = new THREE11.SphereGeometry(s[3 * i], 12, 8);
|
|
2342
2416
|
break;
|
|
2343
2417
|
case 3:
|
|
2344
|
-
geometry = new
|
|
2418
|
+
geometry = new THREE11.CapsuleGeometry(s[3 * i], s[3 * i + 1] * 2, 6, 8);
|
|
2345
2419
|
break;
|
|
2346
2420
|
case 5:
|
|
2347
|
-
geometry = new
|
|
2421
|
+
geometry = new THREE11.CylinderGeometry(s[3 * i], s[3 * i], s[3 * i + 1] * 2, 12);
|
|
2348
2422
|
break;
|
|
2349
2423
|
case 6:
|
|
2350
|
-
geometry = new
|
|
2424
|
+
geometry = new THREE11.BoxGeometry(s[3 * i] * 2, s[3 * i + 1] * 2, s[3 * i + 2] * 2);
|
|
2351
2425
|
break;
|
|
2352
2426
|
}
|
|
2353
2427
|
if (geometry) {
|
|
2354
|
-
const mat = new
|
|
2355
|
-
const mesh = new
|
|
2428
|
+
const mat = new THREE11.MeshBasicMaterial({ color: 65280, wireframe: true, transparent: true, opacity: 0.3 });
|
|
2429
|
+
const mesh = new THREE11.Mesh(geometry, mat);
|
|
2356
2430
|
mesh.userData.geomId = i;
|
|
2357
2431
|
mesh.userData.bodyId = model.geom_bodyid[i];
|
|
2358
2432
|
geoms.push(mesh);
|
|
@@ -2360,35 +2434,88 @@ function Debug({
|
|
|
2360
2434
|
}
|
|
2361
2435
|
}
|
|
2362
2436
|
if (showSites) {
|
|
2437
|
+
const siteSize = model.site_size;
|
|
2363
2438
|
for (let i = 0; i < model.nsite; i++) {
|
|
2364
|
-
|
|
2365
|
-
|
|
2366
|
-
|
|
2439
|
+
let radius = 8e-3;
|
|
2440
|
+
if (siteSize) {
|
|
2441
|
+
radius = Math.max(siteSize[3 * i] * 0.5, 4e-3);
|
|
2442
|
+
} else {
|
|
2443
|
+
const bodyId = model.site_bodyid[i];
|
|
2444
|
+
let maxGeomSize = 0;
|
|
2445
|
+
for (let g = 0; g < model.ngeom; g++) {
|
|
2446
|
+
if (model.geom_bodyid[g] === bodyId) {
|
|
2447
|
+
maxGeomSize = Math.max(maxGeomSize, model.geom_size[3 * g]);
|
|
2448
|
+
}
|
|
2449
|
+
}
|
|
2450
|
+
if (maxGeomSize > 0) radius = maxGeomSize * 0.15;
|
|
2451
|
+
}
|
|
2452
|
+
const geometry = new THREE11.OctahedronGeometry(radius);
|
|
2453
|
+
const mat = new THREE11.MeshBasicMaterial({ color: 16711935, depthTest: false });
|
|
2454
|
+
const mesh = new THREE11.Mesh(geometry, mat);
|
|
2455
|
+
mesh.renderOrder = 999;
|
|
2456
|
+
mesh.frustumCulled = false;
|
|
2367
2457
|
mesh.userData.siteId = i;
|
|
2458
|
+
const canvas = document.createElement("canvas");
|
|
2459
|
+
canvas.width = 256;
|
|
2460
|
+
canvas.height = 64;
|
|
2461
|
+
const ctx = canvas.getContext("2d");
|
|
2462
|
+
ctx.fillStyle = "#ff00ff";
|
|
2463
|
+
ctx.font = "bold 36px monospace";
|
|
2464
|
+
ctx.textAlign = "center";
|
|
2465
|
+
ctx.fillText(getName(model, model.name_siteadr[i]), 128, 42);
|
|
2466
|
+
const tex = new THREE11.CanvasTexture(canvas);
|
|
2467
|
+
const spriteMat = new THREE11.SpriteMaterial({ map: tex, depthTest: false, transparent: true });
|
|
2468
|
+
const sprite = new THREE11.Sprite(spriteMat);
|
|
2469
|
+
const labelScale = radius * 15;
|
|
2470
|
+
sprite.scale.set(labelScale, labelScale * 0.25, 1);
|
|
2471
|
+
sprite.position.y = radius * 2;
|
|
2472
|
+
sprite.renderOrder = 999;
|
|
2473
|
+
mesh.add(sprite);
|
|
2368
2474
|
sites.push(mesh);
|
|
2369
2475
|
}
|
|
2370
2476
|
}
|
|
2371
2477
|
if (showJoints) {
|
|
2478
|
+
const jntPos = model.jnt_pos;
|
|
2479
|
+
const jntAxis = model.jnt_axis;
|
|
2372
2480
|
for (let i = 0; i < model.njnt; i++) {
|
|
2373
2481
|
const type = model.jnt_type[i];
|
|
2374
2482
|
const color = JOINT_COLORS[type] ?? 16777215;
|
|
2375
|
-
const
|
|
2376
|
-
|
|
2377
|
-
|
|
2378
|
-
|
|
2483
|
+
const bodyId = model.jnt_bodyid[i];
|
|
2484
|
+
let maxGeomSize = 0;
|
|
2485
|
+
for (let g = 0; g < model.ngeom; g++) {
|
|
2486
|
+
if (model.geom_bodyid[g] === bodyId) {
|
|
2487
|
+
maxGeomSize = Math.max(maxGeomSize, model.geom_size[3 * g]);
|
|
2488
|
+
}
|
|
2489
|
+
}
|
|
2490
|
+
const arrowLen = Math.max(maxGeomSize * 0.8, 0.05);
|
|
2491
|
+
const arrow = new THREE11.ArrowHelper(
|
|
2492
|
+
new THREE11.Vector3(0, 0, 1),
|
|
2493
|
+
new THREE11.Vector3(),
|
|
2494
|
+
arrowLen,
|
|
2379
2495
|
color,
|
|
2380
|
-
0.
|
|
2381
|
-
|
|
2496
|
+
arrowLen * 0.25,
|
|
2497
|
+
arrowLen * 0.12
|
|
2382
2498
|
);
|
|
2499
|
+
arrow.renderOrder = 999;
|
|
2500
|
+
arrow.frustumCulled = false;
|
|
2501
|
+
arrow.line.material = new THREE11.LineBasicMaterial({ color, depthTest: false });
|
|
2502
|
+
arrow.cone.material.depthTest = false;
|
|
2503
|
+
arrow.line.renderOrder = 999;
|
|
2504
|
+
arrow.line.frustumCulled = false;
|
|
2505
|
+
arrow.cone.renderOrder = 999;
|
|
2506
|
+
arrow.cone.frustumCulled = false;
|
|
2383
2507
|
arrow.userData.jointId = i;
|
|
2508
|
+
arrow.userData.bodyId = bodyId;
|
|
2509
|
+
arrow.userData.hasJntPos = !!jntPos;
|
|
2510
|
+
arrow.userData.hasJntAxis = !!jntAxis;
|
|
2384
2511
|
joints.push(arrow);
|
|
2385
2512
|
}
|
|
2386
2513
|
}
|
|
2387
2514
|
if (showCOM) {
|
|
2388
2515
|
for (let i = 1; i < model.nbody; i++) {
|
|
2389
|
-
const geometry = new
|
|
2390
|
-
const mat = new
|
|
2391
|
-
const mesh = new
|
|
2516
|
+
const geometry = new THREE11.SphereGeometry(5e-3, 6, 6);
|
|
2517
|
+
const mat = new THREE11.MeshBasicMaterial({ color: 16711680 });
|
|
2518
|
+
const mesh = new THREE11.Mesh(geometry, mat);
|
|
2392
2519
|
mesh.userData.bodyId = i;
|
|
2393
2520
|
comMarkers.push(mesh);
|
|
2394
2521
|
}
|
|
@@ -2416,6 +2543,8 @@ function Debug({
|
|
|
2416
2543
|
const model = mjModelRef.current;
|
|
2417
2544
|
const data = mjDataRef.current;
|
|
2418
2545
|
if (!model || !data || !debugGeometry) return;
|
|
2546
|
+
const jntPos = model.jnt_pos;
|
|
2547
|
+
const jntAxis = model.jnt_axis;
|
|
2419
2548
|
for (const mesh of debugGeometry.geoms) {
|
|
2420
2549
|
const bid = mesh.userData.bodyId;
|
|
2421
2550
|
const i3 = bid * 3;
|
|
@@ -2429,7 +2558,8 @@ function Debug({
|
|
|
2429
2558
|
);
|
|
2430
2559
|
const gid = mesh.userData.geomId;
|
|
2431
2560
|
const gp = model.geom_pos;
|
|
2432
|
-
|
|
2561
|
+
_v3a.set(gp[3 * gid], gp[3 * gid + 1], gp[3 * gid + 2]).applyQuaternion(mesh.quaternion);
|
|
2562
|
+
mesh.position.add(_v3a);
|
|
2433
2563
|
}
|
|
2434
2564
|
for (const mesh of debugGeometry.sites) {
|
|
2435
2565
|
const sid = mesh.userData.siteId;
|
|
@@ -2439,6 +2569,28 @@ function Debug({
|
|
|
2439
2569
|
data.site_xpos[3 * sid + 2]
|
|
2440
2570
|
);
|
|
2441
2571
|
}
|
|
2572
|
+
for (const obj of debugGeometry.joints) {
|
|
2573
|
+
const arrow = obj;
|
|
2574
|
+
const jid = arrow.userData.jointId;
|
|
2575
|
+
const bid = arrow.userData.bodyId;
|
|
2576
|
+
const i3 = bid * 3;
|
|
2577
|
+
const i4 = bid * 4;
|
|
2578
|
+
_quat2.set(
|
|
2579
|
+
data.xquat[i4 + 1],
|
|
2580
|
+
data.xquat[i4 + 2],
|
|
2581
|
+
data.xquat[i4 + 3],
|
|
2582
|
+
data.xquat[i4]
|
|
2583
|
+
);
|
|
2584
|
+
arrow.position.set(data.xpos[i3], data.xpos[i3 + 1], data.xpos[i3 + 2]);
|
|
2585
|
+
if (jntPos) {
|
|
2586
|
+
_v3a.set(jntPos[3 * jid], jntPos[3 * jid + 1], jntPos[3 * jid + 2]).applyQuaternion(_quat2);
|
|
2587
|
+
arrow.position.add(_v3a);
|
|
2588
|
+
}
|
|
2589
|
+
if (jntAxis) {
|
|
2590
|
+
_v3a.set(jntAxis[3 * jid], jntAxis[3 * jid + 1], jntAxis[3 * jid + 2]).applyQuaternion(_quat2).normalize();
|
|
2591
|
+
arrow.setDirection(_v3a);
|
|
2592
|
+
}
|
|
2593
|
+
}
|
|
2442
2594
|
for (const mesh of debugGeometry.comMarkers) {
|
|
2443
2595
|
const bid = mesh.userData.bodyId;
|
|
2444
2596
|
const i3 = bid * 3;
|
|
@@ -2446,66 +2598,138 @@ function Debug({
|
|
|
2446
2598
|
}
|
|
2447
2599
|
});
|
|
2448
2600
|
const contactGroupRef = useRef(null);
|
|
2449
|
-
const
|
|
2601
|
+
const contactPoolRef = useRef([]);
|
|
2602
|
+
const contactPoolInitRef = useRef(false);
|
|
2603
|
+
useEffect(() => {
|
|
2604
|
+
const group = contactGroupRef.current;
|
|
2605
|
+
if (!group || contactPoolInitRef.current) return;
|
|
2606
|
+
contactPoolInitRef.current = true;
|
|
2607
|
+
const pool = [];
|
|
2608
|
+
for (let i = 0; i < MAX_CONTACT_ARROWS; i++) {
|
|
2609
|
+
const arrow = new THREE11.ArrowHelper(
|
|
2610
|
+
new THREE11.Vector3(0, 1, 0),
|
|
2611
|
+
new THREE11.Vector3(),
|
|
2612
|
+
0.1,
|
|
2613
|
+
16729156,
|
|
2614
|
+
0.03,
|
|
2615
|
+
0.015
|
|
2616
|
+
);
|
|
2617
|
+
arrow.visible = false;
|
|
2618
|
+
group.add(arrow);
|
|
2619
|
+
pool.push(arrow);
|
|
2620
|
+
}
|
|
2621
|
+
contactPoolRef.current = pool;
|
|
2622
|
+
return () => {
|
|
2623
|
+
for (const arrow of pool) {
|
|
2624
|
+
group.remove(arrow);
|
|
2625
|
+
arrow.dispose();
|
|
2626
|
+
}
|
|
2627
|
+
contactPoolRef.current = [];
|
|
2628
|
+
contactPoolInitRef.current = false;
|
|
2629
|
+
};
|
|
2630
|
+
}, [showContacts]);
|
|
2450
2631
|
useFrame(() => {
|
|
2451
2632
|
if (!showContacts) return;
|
|
2452
|
-
const model = mjModelRef.current;
|
|
2453
2633
|
const data = mjDataRef.current;
|
|
2454
|
-
const
|
|
2455
|
-
if (!
|
|
2456
|
-
for (const arrow of contactArrowsRef.current) {
|
|
2457
|
-
group.remove(arrow);
|
|
2458
|
-
arrow.dispose();
|
|
2459
|
-
}
|
|
2460
|
-
contactArrowsRef.current = [];
|
|
2634
|
+
const pool = contactPoolRef.current;
|
|
2635
|
+
if (!data || pool.length === 0) return;
|
|
2461
2636
|
const ncon = data.ncon;
|
|
2462
|
-
|
|
2463
|
-
|
|
2464
|
-
|
|
2465
|
-
|
|
2466
|
-
|
|
2467
|
-
|
|
2468
|
-
|
|
2469
|
-
|
|
2470
|
-
|
|
2471
|
-
|
|
2472
|
-
|
|
2473
|
-
|
|
2474
|
-
|
|
2475
|
-
|
|
2476
|
-
|
|
2637
|
+
let arrowIdx = 0;
|
|
2638
|
+
for (let i = 0; i < Math.min(ncon, MAX_CONTACT_ARROWS); i++) {
|
|
2639
|
+
const c = getContact(data, i);
|
|
2640
|
+
if (!c) break;
|
|
2641
|
+
_contactPos.set(c.pos[0], c.pos[1], c.pos[2]);
|
|
2642
|
+
_contactNormal.set(c.frame[0], c.frame[1], c.frame[2]);
|
|
2643
|
+
const force = Math.abs(c.dist) * 100;
|
|
2644
|
+
const length = Math.min(force * 0.01, 0.1);
|
|
2645
|
+
if (length > 1e-3 && arrowIdx < pool.length) {
|
|
2646
|
+
const arrow = pool[arrowIdx];
|
|
2647
|
+
arrow.position.copy(_contactPos);
|
|
2648
|
+
arrow.setDirection(_contactNormal);
|
|
2649
|
+
arrow.setLength(length, length * 0.3, length * 0.15);
|
|
2650
|
+
arrow.visible = true;
|
|
2651
|
+
arrowIdx++;
|
|
2652
|
+
}
|
|
2653
|
+
}
|
|
2654
|
+
for (let i = arrowIdx; i < pool.length; i++) {
|
|
2655
|
+
pool[i].visible = false;
|
|
2477
2656
|
}
|
|
2478
2657
|
});
|
|
2479
2658
|
if (status !== "ready") return null;
|
|
2480
|
-
return /* @__PURE__ */ jsxs(
|
|
2659
|
+
return /* @__PURE__ */ jsxs("group", { ...groupProps, children: [
|
|
2481
2660
|
/* @__PURE__ */ jsx("group", { ref: groupRef }),
|
|
2482
2661
|
showContacts && /* @__PURE__ */ jsx("group", { ref: contactGroupRef })
|
|
2483
2662
|
] });
|
|
2484
2663
|
}
|
|
2485
|
-
var DEFAULT_TENDON_COLOR = new
|
|
2664
|
+
var DEFAULT_TENDON_COLOR = new THREE11.Color(0.3, 0.3, 0.8);
|
|
2486
2665
|
var DEFAULT_TENDON_WIDTH = 2e-3;
|
|
2487
|
-
|
|
2666
|
+
new THREE11.Vector3();
|
|
2667
|
+
function TendonRenderer(props) {
|
|
2488
2668
|
const { mjModelRef, mjDataRef, status } = useMujocoSim();
|
|
2489
2669
|
const groupRef = useRef(null);
|
|
2490
2670
|
const meshesRef = useRef([]);
|
|
2491
|
-
|
|
2671
|
+
const curvesRef = useRef([]);
|
|
2672
|
+
const materialRef = useRef(null);
|
|
2673
|
+
useEffect(() => {
|
|
2492
2674
|
const model = mjModelRef.current;
|
|
2493
2675
|
const data = mjDataRef.current;
|
|
2494
2676
|
const group = groupRef.current;
|
|
2495
2677
|
if (!model || !data || !group) return;
|
|
2496
2678
|
const ntendon = model.ntendon ?? 0;
|
|
2497
2679
|
if (ntendon === 0) return;
|
|
2498
|
-
|
|
2499
|
-
|
|
2500
|
-
|
|
2501
|
-
|
|
2680
|
+
const material = new THREE11.MeshStandardMaterial({
|
|
2681
|
+
color: DEFAULT_TENDON_COLOR,
|
|
2682
|
+
roughness: 0.6,
|
|
2683
|
+
metalness: 0.1
|
|
2684
|
+
});
|
|
2685
|
+
materialRef.current = material;
|
|
2686
|
+
const meshes = [];
|
|
2687
|
+
const curves = [];
|
|
2688
|
+
for (let t = 0; t < ntendon; t++) {
|
|
2689
|
+
const wrapNum = model.ten_wrapnum[t];
|
|
2690
|
+
if (wrapNum < 2) {
|
|
2691
|
+
meshes.push(null);
|
|
2692
|
+
curves.push(null);
|
|
2693
|
+
continue;
|
|
2694
|
+
}
|
|
2695
|
+
const points = Array.from({ length: wrapNum }, () => new THREE11.Vector3());
|
|
2696
|
+
const curve = new THREE11.CatmullRomCurve3(points, false);
|
|
2697
|
+
const segments = Math.max(wrapNum * 2, 4);
|
|
2698
|
+
const geometry = new THREE11.TubeGeometry(curve, segments, DEFAULT_TENDON_WIDTH, 6, false);
|
|
2699
|
+
const mesh = new THREE11.Mesh(geometry, material);
|
|
2700
|
+
mesh.frustumCulled = false;
|
|
2701
|
+
group.add(mesh);
|
|
2702
|
+
meshes.push(mesh);
|
|
2703
|
+
curves.push(curve);
|
|
2502
2704
|
}
|
|
2503
|
-
meshesRef.current =
|
|
2705
|
+
meshesRef.current = meshes;
|
|
2706
|
+
curvesRef.current = curves;
|
|
2707
|
+
return () => {
|
|
2708
|
+
for (const mesh of meshes) {
|
|
2709
|
+
if (!mesh) continue;
|
|
2710
|
+
group.remove(mesh);
|
|
2711
|
+
mesh.geometry.dispose();
|
|
2712
|
+
}
|
|
2713
|
+
material.dispose();
|
|
2714
|
+
meshesRef.current = [];
|
|
2715
|
+
curvesRef.current = [];
|
|
2716
|
+
materialRef.current = null;
|
|
2717
|
+
};
|
|
2718
|
+
}, [status, mjModelRef, mjDataRef]);
|
|
2719
|
+
useFrame(() => {
|
|
2720
|
+
const model = mjModelRef.current;
|
|
2721
|
+
const data = mjDataRef.current;
|
|
2722
|
+
if (!model || !data) return;
|
|
2723
|
+
const ntendon = model.ntendon ?? 0;
|
|
2724
|
+
const meshes = meshesRef.current;
|
|
2725
|
+
const curves = curvesRef.current;
|
|
2504
2726
|
for (let t = 0; t < ntendon; t++) {
|
|
2727
|
+
const mesh = meshes[t];
|
|
2728
|
+
const curve = curves[t];
|
|
2729
|
+
if (!mesh || !curve) continue;
|
|
2505
2730
|
const wrapAdr = model.ten_wrapadr[t];
|
|
2506
2731
|
const wrapNum = model.ten_wrapnum[t];
|
|
2507
|
-
|
|
2508
|
-
const points = [];
|
|
2732
|
+
let validCount = 0;
|
|
2509
2733
|
for (let w = 0; w < wrapNum; w++) {
|
|
2510
2734
|
const idx = (wrapAdr + w) * 3;
|
|
2511
2735
|
if (data.wrap_xpos && idx + 2 < data.wrap_xpos.length) {
|
|
@@ -2513,33 +2737,38 @@ function TendonRenderer() {
|
|
|
2513
2737
|
const y = data.wrap_xpos[idx + 1];
|
|
2514
2738
|
const z = data.wrap_xpos[idx + 2];
|
|
2515
2739
|
if (x !== 0 || y !== 0 || z !== 0) {
|
|
2516
|
-
|
|
2740
|
+
if (validCount < curve.points.length) {
|
|
2741
|
+
curve.points[validCount].set(x, y, z);
|
|
2742
|
+
}
|
|
2743
|
+
validCount++;
|
|
2517
2744
|
}
|
|
2518
2745
|
}
|
|
2519
2746
|
}
|
|
2520
|
-
if (
|
|
2521
|
-
|
|
2522
|
-
|
|
2747
|
+
if (validCount < 2) {
|
|
2748
|
+
mesh.visible = false;
|
|
2749
|
+
continue;
|
|
2750
|
+
}
|
|
2751
|
+
if (curve.points.length !== validCount) {
|
|
2752
|
+
curve.points.length = validCount;
|
|
2753
|
+
while (curve.points.length < validCount) {
|
|
2754
|
+
curve.points.push(new THREE11.Vector3());
|
|
2755
|
+
}
|
|
2756
|
+
}
|
|
2757
|
+
mesh.geometry.dispose();
|
|
2758
|
+
mesh.geometry = new THREE11.TubeGeometry(
|
|
2523
2759
|
curve,
|
|
2524
|
-
Math.max(
|
|
2760
|
+
Math.max(validCount * 2, 4),
|
|
2525
2761
|
DEFAULT_TENDON_WIDTH,
|
|
2526
2762
|
6,
|
|
2527
2763
|
false
|
|
2528
2764
|
);
|
|
2529
|
-
|
|
2530
|
-
color: DEFAULT_TENDON_COLOR,
|
|
2531
|
-
roughness: 0.6,
|
|
2532
|
-
metalness: 0.1
|
|
2533
|
-
});
|
|
2534
|
-
const mesh = new THREE.Mesh(geometry, material);
|
|
2535
|
-
group.add(mesh);
|
|
2536
|
-
meshesRef.current.push(mesh);
|
|
2765
|
+
mesh.visible = true;
|
|
2537
2766
|
}
|
|
2538
2767
|
});
|
|
2539
2768
|
if (status !== "ready") return null;
|
|
2540
|
-
return /* @__PURE__ */ jsx("group", { ref: groupRef });
|
|
2769
|
+
return /* @__PURE__ */ jsx("group", { ...props, ref: groupRef });
|
|
2541
2770
|
}
|
|
2542
|
-
function FlexRenderer() {
|
|
2771
|
+
function FlexRenderer(props) {
|
|
2543
2772
|
const { mjModelRef, mjDataRef, status } = useMujocoSim();
|
|
2544
2773
|
const groupRef = useRef(null);
|
|
2545
2774
|
const meshesRef = useRef([]);
|
|
@@ -2553,24 +2782,24 @@ function FlexRenderer() {
|
|
|
2553
2782
|
const vertAdr = model.flex_vertadr[f];
|
|
2554
2783
|
const vertNum = model.flex_vertnum[f];
|
|
2555
2784
|
if (vertNum === 0) continue;
|
|
2556
|
-
const geometry = new
|
|
2785
|
+
const geometry = new THREE11.BufferGeometry();
|
|
2557
2786
|
const positions = new Float32Array(vertNum * 3);
|
|
2558
|
-
geometry.setAttribute("position", new
|
|
2787
|
+
geometry.setAttribute("position", new THREE11.BufferAttribute(positions, 3));
|
|
2559
2788
|
geometry.computeVertexNormals();
|
|
2560
|
-
let color = new
|
|
2789
|
+
let color = new THREE11.Color(0.5, 0.5, 0.5);
|
|
2561
2790
|
if (model.flex_rgba) {
|
|
2562
|
-
color = new
|
|
2791
|
+
color = new THREE11.Color(
|
|
2563
2792
|
model.flex_rgba[4 * f],
|
|
2564
2793
|
model.flex_rgba[4 * f + 1],
|
|
2565
2794
|
model.flex_rgba[4 * f + 2]
|
|
2566
2795
|
);
|
|
2567
2796
|
}
|
|
2568
|
-
const material = new
|
|
2797
|
+
const material = new THREE11.MeshStandardMaterial({
|
|
2569
2798
|
color,
|
|
2570
2799
|
roughness: 0.7,
|
|
2571
|
-
side:
|
|
2800
|
+
side: THREE11.DoubleSide
|
|
2572
2801
|
});
|
|
2573
|
-
const mesh = new
|
|
2802
|
+
const mesh = new THREE11.Mesh(geometry, material);
|
|
2574
2803
|
mesh.userData.flexId = f;
|
|
2575
2804
|
mesh.userData.vertAdr = vertAdr;
|
|
2576
2805
|
mesh.userData.vertNum = vertNum;
|
|
@@ -2603,24 +2832,47 @@ function FlexRenderer() {
|
|
|
2603
2832
|
}
|
|
2604
2833
|
});
|
|
2605
2834
|
if (status !== "ready") return null;
|
|
2606
|
-
return /* @__PURE__ */ jsx("group", { ref: groupRef });
|
|
2835
|
+
return /* @__PURE__ */ jsx("group", { ...props, ref: groupRef });
|
|
2836
|
+
}
|
|
2837
|
+
var geomNameCacheByModel = /* @__PURE__ */ new WeakMap();
|
|
2838
|
+
function getGeomNameCached(model, geomId) {
|
|
2839
|
+
let perModel = geomNameCacheByModel.get(model);
|
|
2840
|
+
if (!perModel) {
|
|
2841
|
+
perModel = /* @__PURE__ */ new Map();
|
|
2842
|
+
geomNameCacheByModel.set(model, perModel);
|
|
2843
|
+
}
|
|
2844
|
+
let name = perModel.get(geomId);
|
|
2845
|
+
if (name === void 0) {
|
|
2846
|
+
name = getName(model, model.name_geomadr[geomId]);
|
|
2847
|
+
perModel.set(geomId, name);
|
|
2848
|
+
}
|
|
2849
|
+
return name;
|
|
2607
2850
|
}
|
|
2608
2851
|
function useContacts(bodyName, callback) {
|
|
2609
|
-
const { mjModelRef } = useMujocoSim();
|
|
2852
|
+
const { mjModelRef, status } = useMujocoSim();
|
|
2610
2853
|
const contactsRef = useRef([]);
|
|
2611
2854
|
const bodyIdRef = useRef(-1);
|
|
2855
|
+
const bodyResolvedRef = useRef(false);
|
|
2612
2856
|
const callbackRef = useRef(callback);
|
|
2613
2857
|
callbackRef.current = callback;
|
|
2614
2858
|
useEffect(() => {
|
|
2615
2859
|
if (!bodyName) {
|
|
2616
2860
|
bodyIdRef.current = -1;
|
|
2861
|
+
bodyResolvedRef.current = true;
|
|
2617
2862
|
return;
|
|
2618
2863
|
}
|
|
2864
|
+
bodyResolvedRef.current = false;
|
|
2865
|
+
if (status !== "ready") return;
|
|
2619
2866
|
const model = mjModelRef.current;
|
|
2620
2867
|
if (!model) return;
|
|
2621
2868
|
bodyIdRef.current = findBodyByName(model, bodyName);
|
|
2622
|
-
|
|
2869
|
+
bodyResolvedRef.current = true;
|
|
2870
|
+
}, [bodyName, status, mjModelRef]);
|
|
2623
2871
|
useAfterPhysicsStep((model, data) => {
|
|
2872
|
+
if (bodyName && !bodyResolvedRef.current) {
|
|
2873
|
+
bodyIdRef.current = findBodyByName(model, bodyName);
|
|
2874
|
+
bodyResolvedRef.current = true;
|
|
2875
|
+
}
|
|
2624
2876
|
const ncon = data.ncon;
|
|
2625
2877
|
if (ncon === 0) {
|
|
2626
2878
|
if (contactsRef.current.length > 0) contactsRef.current = [];
|
|
@@ -2630,24 +2882,21 @@ function useContacts(bodyName, callback) {
|
|
|
2630
2882
|
const contacts = [];
|
|
2631
2883
|
const filterBody = bodyIdRef.current;
|
|
2632
2884
|
for (let i = 0; i < ncon; i++) {
|
|
2633
|
-
|
|
2634
|
-
|
|
2635
|
-
|
|
2636
|
-
|
|
2637
|
-
|
|
2638
|
-
|
|
2639
|
-
|
|
2640
|
-
|
|
2641
|
-
|
|
2642
|
-
|
|
2643
|
-
|
|
2644
|
-
|
|
2645
|
-
|
|
2646
|
-
|
|
2647
|
-
|
|
2648
|
-
} catch {
|
|
2649
|
-
break;
|
|
2650
|
-
}
|
|
2885
|
+
const c = getContact(data, i);
|
|
2886
|
+
if (!c) break;
|
|
2887
|
+
if (filterBody >= 0) {
|
|
2888
|
+
const b1 = model.geom_bodyid[c.geom1];
|
|
2889
|
+
const b2 = model.geom_bodyid[c.geom2];
|
|
2890
|
+
if (b1 !== filterBody && b2 !== filterBody) continue;
|
|
2891
|
+
}
|
|
2892
|
+
contacts.push({
|
|
2893
|
+
geom1: c.geom1,
|
|
2894
|
+
geom1Name: getGeomNameCached(model, c.geom1),
|
|
2895
|
+
geom2: c.geom2,
|
|
2896
|
+
geom2Name: getGeomNameCached(model, c.geom2),
|
|
2897
|
+
pos: [c.pos[0], c.pos[1], c.pos[2]],
|
|
2898
|
+
depth: c.dist
|
|
2899
|
+
});
|
|
2651
2900
|
}
|
|
2652
2901
|
contactsRef.current = contacts;
|
|
2653
2902
|
callbackRef.current?.(contacts);
|
|
@@ -2781,28 +3030,28 @@ function TrajectoryPlayer({
|
|
|
2781
3030
|
onFrame
|
|
2782
3031
|
}) {
|
|
2783
3032
|
const player = useTrajectoryPlayer(trajectory, { fps, loop });
|
|
3033
|
+
const onFrameRef = useRef(onFrame);
|
|
3034
|
+
onFrameRef.current = onFrame;
|
|
3035
|
+
const lastReportedFrameRef = useRef(-1);
|
|
2784
3036
|
useEffect(() => {
|
|
2785
3037
|
if (playing) {
|
|
2786
3038
|
player.play();
|
|
2787
3039
|
} else {
|
|
2788
3040
|
player.pause();
|
|
2789
3041
|
}
|
|
2790
|
-
}, [playing]);
|
|
2791
|
-
|
|
2792
|
-
if (
|
|
2793
|
-
|
|
2794
|
-
|
|
2795
|
-
|
|
2796
|
-
|
|
3042
|
+
}, [playing, player]);
|
|
3043
|
+
useFrame(() => {
|
|
3044
|
+
if (!onFrameRef.current) return;
|
|
3045
|
+
const currentFrame = player.frame;
|
|
3046
|
+
if (currentFrame !== lastReportedFrameRef.current && player.playing) {
|
|
3047
|
+
lastReportedFrameRef.current = currentFrame;
|
|
3048
|
+
onFrameRef.current(currentFrame);
|
|
2797
3049
|
}
|
|
2798
|
-
}
|
|
3050
|
+
});
|
|
2799
3051
|
return null;
|
|
2800
3052
|
}
|
|
2801
|
-
function
|
|
2802
|
-
|
|
2803
|
-
color = "#ff4444",
|
|
2804
|
-
emissiveIntensity = 0.3
|
|
2805
|
-
}) {
|
|
3053
|
+
function useSelectionHighlight(bodyId, options = {}) {
|
|
3054
|
+
const { color = "#ff4444", emissiveIntensity = 0.3 } = options;
|
|
2806
3055
|
const { scene } = useThree();
|
|
2807
3056
|
const prevMeshesRef = useRef([]);
|
|
2808
3057
|
useEffect(() => {
|
|
@@ -2815,7 +3064,7 @@ function SelectionHighlight({
|
|
|
2815
3064
|
}
|
|
2816
3065
|
prevMeshesRef.current = [];
|
|
2817
3066
|
if (bodyId === null || bodyId < 0) return;
|
|
2818
|
-
const highlightColor = new
|
|
3067
|
+
const highlightColor = new THREE11.Color(color);
|
|
2819
3068
|
scene.traverse((obj) => {
|
|
2820
3069
|
if (obj.userData.bodyID === bodyId && obj.isMesh) {
|
|
2821
3070
|
const mesh = obj;
|
|
@@ -2842,6 +3091,15 @@ function SelectionHighlight({
|
|
|
2842
3091
|
prevMeshesRef.current = [];
|
|
2843
3092
|
};
|
|
2844
3093
|
}, [bodyId, color, emissiveIntensity, scene]);
|
|
3094
|
+
}
|
|
3095
|
+
|
|
3096
|
+
// src/components/SelectionHighlight.tsx
|
|
3097
|
+
function SelectionHighlight({
|
|
3098
|
+
bodyId,
|
|
3099
|
+
color = "#ff4444",
|
|
3100
|
+
emissiveIntensity = 0.3
|
|
3101
|
+
}) {
|
|
3102
|
+
useSelectionHighlight(bodyId, { color, emissiveIntensity });
|
|
2845
3103
|
return null;
|
|
2846
3104
|
}
|
|
2847
3105
|
function useActuators() {
|
|
@@ -2862,12 +3120,12 @@ function useActuators() {
|
|
|
2862
3120
|
return actuators;
|
|
2863
3121
|
}, [status, mjModelRef]);
|
|
2864
3122
|
}
|
|
2865
|
-
var _mat42 = new
|
|
3123
|
+
var _mat42 = new THREE11.Matrix4();
|
|
2866
3124
|
function useSitePosition(siteName) {
|
|
2867
3125
|
const { mjModelRef, mjDataRef, status } = useMujocoSim();
|
|
2868
3126
|
const siteIdRef = useRef(-1);
|
|
2869
|
-
const positionRef = useRef(new
|
|
2870
|
-
const quaternionRef = useRef(new
|
|
3127
|
+
const positionRef = useRef(new THREE11.Vector3());
|
|
3128
|
+
const quaternionRef = useRef(new THREE11.Quaternion());
|
|
2871
3129
|
useEffect(() => {
|
|
2872
3130
|
const model = mjModelRef.current;
|
|
2873
3131
|
if (!model || status !== "ready") {
|
|
@@ -2996,6 +3254,8 @@ function useJointState(name) {
|
|
|
2996
3254
|
const dofDimRef = useRef(1);
|
|
2997
3255
|
const positionRef = useRef(0);
|
|
2998
3256
|
const velocityRef = useRef(0);
|
|
3257
|
+
const posBufferRef = useRef(null);
|
|
3258
|
+
const velBufferRef = useRef(null);
|
|
2999
3259
|
useEffect(() => {
|
|
3000
3260
|
const model = mjModelRef.current;
|
|
3001
3261
|
if (!model || status !== "ready") return;
|
|
@@ -3015,6 +3275,13 @@ function useJointState(name) {
|
|
|
3015
3275
|
qposDimRef.current = 1;
|
|
3016
3276
|
dofDimRef.current = 1;
|
|
3017
3277
|
}
|
|
3278
|
+
if (qposDimRef.current > 1) {
|
|
3279
|
+
posBufferRef.current = new Float64Array(qposDimRef.current);
|
|
3280
|
+
velBufferRef.current = new Float64Array(dofDimRef.current);
|
|
3281
|
+
} else {
|
|
3282
|
+
posBufferRef.current = null;
|
|
3283
|
+
velBufferRef.current = null;
|
|
3284
|
+
}
|
|
3018
3285
|
return;
|
|
3019
3286
|
}
|
|
3020
3287
|
}
|
|
@@ -3028,8 +3295,12 @@ function useJointState(name) {
|
|
|
3028
3295
|
positionRef.current = data.qpos[qa];
|
|
3029
3296
|
velocityRef.current = data.qvel[da];
|
|
3030
3297
|
} else {
|
|
3031
|
-
|
|
3032
|
-
|
|
3298
|
+
const posBuf = posBufferRef.current;
|
|
3299
|
+
const velBuf = velBufferRef.current;
|
|
3300
|
+
posBuf.set(data.qpos.subarray(qa, qa + qposDimRef.current));
|
|
3301
|
+
velBuf.set(data.qvel.subarray(da, da + dofDimRef.current));
|
|
3302
|
+
positionRef.current = posBuf;
|
|
3303
|
+
velocityRef.current = velBuf;
|
|
3033
3304
|
}
|
|
3034
3305
|
});
|
|
3035
3306
|
return { position: positionRef, velocity: velocityRef };
|
|
@@ -3037,10 +3308,10 @@ function useJointState(name) {
|
|
|
3037
3308
|
function useBodyState(name) {
|
|
3038
3309
|
const { mjModelRef, status } = useMujocoSim();
|
|
3039
3310
|
const bodyIdRef = useRef(-1);
|
|
3040
|
-
const position = useRef(new
|
|
3041
|
-
const quaternion = useRef(new
|
|
3042
|
-
const linearVelocity = useRef(new
|
|
3043
|
-
const angularVelocity = useRef(new
|
|
3311
|
+
const position = useRef(new THREE11.Vector3());
|
|
3312
|
+
const quaternion = useRef(new THREE11.Quaternion());
|
|
3313
|
+
const linearVelocity = useRef(new THREE11.Vector3());
|
|
3314
|
+
const angularVelocity = useRef(new THREE11.Vector3());
|
|
3044
3315
|
useEffect(() => {
|
|
3045
3316
|
const model = mjModelRef.current;
|
|
3046
3317
|
if (!model || status !== "ready") return;
|
|
@@ -3377,10 +3648,97 @@ function useCtrlNoise(config = {}) {
|
|
|
3377
3648
|
}
|
|
3378
3649
|
});
|
|
3379
3650
|
}
|
|
3651
|
+
function useCameraAnimation() {
|
|
3652
|
+
const { camera } = useThree();
|
|
3653
|
+
const orbitTargetRef = useRef(new THREE11.Vector3(0, 0, 0));
|
|
3654
|
+
const cameraAnimRef = useRef({
|
|
3655
|
+
active: false,
|
|
3656
|
+
startPos: new THREE11.Vector3(),
|
|
3657
|
+
endPos: new THREE11.Vector3(),
|
|
3658
|
+
startRot: new THREE11.Quaternion(),
|
|
3659
|
+
endRot: new THREE11.Quaternion(),
|
|
3660
|
+
startTarget: new THREE11.Vector3(),
|
|
3661
|
+
endTarget: new THREE11.Vector3(),
|
|
3662
|
+
startTime: 0,
|
|
3663
|
+
duration: 0,
|
|
3664
|
+
resolve: null
|
|
3665
|
+
});
|
|
3666
|
+
useFrame((state) => {
|
|
3667
|
+
const ca = cameraAnimRef.current;
|
|
3668
|
+
if (!ca.active) return;
|
|
3669
|
+
const now = performance.now();
|
|
3670
|
+
const progress = Math.min((now - ca.startTime) / ca.duration, 1);
|
|
3671
|
+
const ease = progress < 0.5 ? 4 * progress * progress * progress : 1 - Math.pow(-2 * progress + 2, 3) / 2;
|
|
3672
|
+
camera.position.lerpVectors(ca.startPos, ca.endPos, ease);
|
|
3673
|
+
camera.quaternion.slerpQuaternions(ca.startRot, ca.endRot, ease);
|
|
3674
|
+
orbitTargetRef.current.lerpVectors(ca.startTarget, ca.endTarget, ease);
|
|
3675
|
+
const orbitControls = state.controls;
|
|
3676
|
+
if (orbitControls?.target) {
|
|
3677
|
+
orbitControls.target.copy(orbitTargetRef.current);
|
|
3678
|
+
}
|
|
3679
|
+
if (progress >= 1) {
|
|
3680
|
+
ca.active = false;
|
|
3681
|
+
camera.position.copy(ca.endPos);
|
|
3682
|
+
camera.quaternion.copy(ca.endRot);
|
|
3683
|
+
orbitTargetRef.current.copy(ca.endTarget);
|
|
3684
|
+
ca.resolve?.();
|
|
3685
|
+
ca.resolve = null;
|
|
3686
|
+
}
|
|
3687
|
+
});
|
|
3688
|
+
const getCameraState = useCallback(
|
|
3689
|
+
() => ({
|
|
3690
|
+
position: camera.position.clone(),
|
|
3691
|
+
target: orbitTargetRef.current.clone()
|
|
3692
|
+
}),
|
|
3693
|
+
[camera]
|
|
3694
|
+
);
|
|
3695
|
+
const moveCameraTo = useCallback(
|
|
3696
|
+
(position, target, durationMs) => {
|
|
3697
|
+
return new Promise((resolve) => {
|
|
3698
|
+
const ca = cameraAnimRef.current;
|
|
3699
|
+
ca.active = true;
|
|
3700
|
+
ca.startTime = performance.now();
|
|
3701
|
+
ca.duration = durationMs;
|
|
3702
|
+
ca.startPos.copy(camera.position);
|
|
3703
|
+
ca.startRot.copy(camera.quaternion);
|
|
3704
|
+
ca.startTarget.copy(orbitTargetRef.current);
|
|
3705
|
+
ca.endPos.copy(position);
|
|
3706
|
+
ca.endTarget.copy(target);
|
|
3707
|
+
const dummyCam = camera.clone();
|
|
3708
|
+
dummyCam.position.copy(position);
|
|
3709
|
+
dummyCam.lookAt(target);
|
|
3710
|
+
ca.endRot.copy(dummyCam.quaternion);
|
|
3711
|
+
ca.resolve = resolve;
|
|
3712
|
+
setTimeout(resolve, durationMs + 100);
|
|
3713
|
+
});
|
|
3714
|
+
},
|
|
3715
|
+
[camera]
|
|
3716
|
+
);
|
|
3717
|
+
return { getCameraState, moveCameraTo };
|
|
3718
|
+
}
|
|
3380
3719
|
/**
|
|
3381
3720
|
* @license
|
|
3382
3721
|
* SPDX-License-Identifier: Apache-2.0
|
|
3383
3722
|
*/
|
|
3723
|
+
/**
|
|
3724
|
+
* @license
|
|
3725
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
3726
|
+
*
|
|
3727
|
+
* createController — typed factory for BYOC (Bring Your Own Controller) plugins.
|
|
3728
|
+
*/
|
|
3729
|
+
/**
|
|
3730
|
+
* @license
|
|
3731
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
3732
|
+
*
|
|
3733
|
+
* IkContext — React context for the IK controller plugin.
|
|
3734
|
+
*/
|
|
3735
|
+
/**
|
|
3736
|
+
* @license
|
|
3737
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
3738
|
+
*
|
|
3739
|
+
* IkController — composable IK controller plugin.
|
|
3740
|
+
* Extracts all IK logic from MujocoSimProvider into an opt-in component.
|
|
3741
|
+
*/
|
|
3384
3742
|
/**
|
|
3385
3743
|
* @license
|
|
3386
3744
|
* SPDX-License-Identifier: Apache-2.0
|
|
@@ -3394,6 +3752,14 @@ function useCtrlNoise(config = {}) {
|
|
|
3394
3752
|
* Fixed from original: reads data.ncon first, accesses contact via .get(i),
|
|
3395
3753
|
* limits to maxContacts to avoid WASM heap OOM.
|
|
3396
3754
|
*/
|
|
3755
|
+
/**
|
|
3756
|
+
* @license
|
|
3757
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
3758
|
+
*
|
|
3759
|
+
* useSceneLights — hook form of SceneLights (spec 6.3)
|
|
3760
|
+
*
|
|
3761
|
+
* Auto-creates Three.js lights from MJCF <light> elements.
|
|
3762
|
+
*/
|
|
3397
3763
|
/**
|
|
3398
3764
|
* @license
|
|
3399
3765
|
* SPDX-License-Identifier: Apache-2.0
|
|
@@ -3456,6 +3822,15 @@ function useCtrlNoise(config = {}) {
|
|
|
3456
3822
|
*
|
|
3457
3823
|
* TrajectoryPlayer — component form of trajectory playback (spec 13.2)
|
|
3458
3824
|
*/
|
|
3825
|
+
/**
|
|
3826
|
+
* @license
|
|
3827
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
3828
|
+
*
|
|
3829
|
+
* useSelectionHighlight — hook form of SelectionHighlight (spec 6.5)
|
|
3830
|
+
*
|
|
3831
|
+
* Applies emissive highlight to all meshes belonging to a body.
|
|
3832
|
+
* Restores original emissive when bodyId changes or hook unmounts.
|
|
3833
|
+
*/
|
|
3459
3834
|
/**
|
|
3460
3835
|
* @license
|
|
3461
3836
|
* SPDX-License-Identifier: Apache-2.0
|
|
@@ -3522,7 +3897,13 @@ function useCtrlNoise(config = {}) {
|
|
|
3522
3897
|
*
|
|
3523
3898
|
* useCtrlNoise — control noise / perturbation hook (spec 3.2)
|
|
3524
3899
|
*/
|
|
3900
|
+
/**
|
|
3901
|
+
* @license
|
|
3902
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
3903
|
+
*
|
|
3904
|
+
* useCameraAnimation — composable camera animation hook.
|
|
3905
|
+
*/
|
|
3525
3906
|
|
|
3526
|
-
export { ContactListener, ContactMarkers, Debug, DragInteraction, FlexRenderer, IkGizmo, MujocoCanvas, MujocoProvider, MujocoSimProvider, SceneLights, SceneRenderer, SelectionHighlight, TendonRenderer, TrajectoryPlayer, findActuatorByName, findBodyByName, findGeomByName, findJointByName, findKeyframeByName, findSensorByName, findSiteByName, findTendonByName, getName, loadScene, useActuators, useAfterPhysicsStep, useBeforePhysicsStep, useBodyState, useContactEvents, useContacts, useCtrl, useCtrlNoise, useGamepad, useGravityCompensation, useJointState, useKeyboardTeleop, useMujoco, useMujocoSim, usePolicy, useSensor, useSensors, useSitePosition, useTrajectoryPlayer, useTrajectoryRecorder, useVideoRecorder };
|
|
3907
|
+
export { ContactListener, ContactMarkers, Debug, DragInteraction, FlexRenderer, IkController, IkGizmo, MujocoCanvas, MujocoPhysics, MujocoProvider, MujocoSimProvider, SceneLights, SceneRenderer, SelectionHighlight, TendonRenderer, TrajectoryPlayer, createController, findActuatorByName, findBodyByName, findGeomByName, findJointByName, findKeyframeByName, findSensorByName, findSiteByName, findTendonByName, getContact, getName, loadScene, useActuators, useAfterPhysicsStep, useBeforePhysicsStep, useBodyState, useCameraAnimation, useContactEvents, useContacts, useCtrl, useCtrlNoise, useGamepad, useGravityCompensation, useIk, useJointState, useKeyboardTeleop, useMujoco, useMujocoSim, usePolicy, useSceneLights, useSelectionHighlight, useSensor, useSensors, useSitePosition, useTrajectoryPlayer, useTrajectoryRecorder, useVideoRecorder };
|
|
3527
3908
|
//# sourceMappingURL=index.js.map
|
|
3528
3909
|
//# sourceMappingURL=index.js.map
|