mujoco-react 0.2.0 → 0.3.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 +206 -42
- package/dist/index.d.ts +175 -95
- package/dist/index.js +1137 -771
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/components/ContactMarkers.tsx +12 -19
- package/src/components/Debug.tsx +168 -33
- package/src/components/DragInteraction.tsx +1 -1
- 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 +8 -6
- package/src/components/SelectionHighlight.tsx +2 -49
- package/src/components/TendonRenderer.tsx +90 -26
- package/src/components/TrajectoryPlayer.tsx +14 -10
- package/src/core/IkContext.tsx +40 -0
- package/src/core/MujocoProvider.tsx +12 -4
- package/src/core/MujocoSimProvider.tsx +55 -331
- package/src/core/SceneLoader.ts +44 -11
- 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 +16 -1
- package/src/types.ts +59 -22
package/dist/index.js
CHANGED
|
@@ -2,7 +2,7 @@ import loadMujoco from 'mujoco-js';
|
|
|
2
2
|
import { createContext, forwardRef, useEffect, useContext, useState, useRef, useCallback, useMemo } from 'react';
|
|
3
3
|
import { jsx, jsxs, Fragment } 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
|
}
|
|
@@ -420,14 +228,12 @@ async function loadScene(mujoco, config, onProgress) {
|
|
|
420
228
|
const siteId = findSiteByName(mjModel, config.tcpSiteName ?? "tcp");
|
|
421
229
|
const gripperId = findActuatorByName(mjModel, config.gripperActuatorName ?? "gripper");
|
|
422
230
|
if (config.homeJoints) {
|
|
423
|
-
|
|
231
|
+
const homeCount = Math.min(config.homeJoints.length, mjModel.nu);
|
|
232
|
+
for (let i = 0; i < homeCount; i++) {
|
|
424
233
|
mjData.ctrl[i] = config.homeJoints[i];
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
const qposAdr = mjModel.jnt_qposadr[jointId];
|
|
429
|
-
mjData.qpos[qposAdr] = config.homeJoints[i];
|
|
430
|
-
}
|
|
234
|
+
const qposAdr = getActuatedScalarQposAdr(mjModel, i);
|
|
235
|
+
if (qposAdr !== -1) {
|
|
236
|
+
mjData.qpos[qposAdr] = config.homeJoints[i];
|
|
431
237
|
}
|
|
432
238
|
}
|
|
433
239
|
}
|
|
@@ -437,8 +243,9 @@ async function loadScene(mujoco, config, onProgress) {
|
|
|
437
243
|
function scanDependencies(xmlString, currentFile, parser, downloaded, queue) {
|
|
438
244
|
const xmlDoc = parser.parseFromString(xmlString, "text/xml");
|
|
439
245
|
const compiler = xmlDoc.querySelector("compiler");
|
|
440
|
-
const
|
|
441
|
-
const
|
|
246
|
+
const assetDir = compiler?.getAttribute("assetdir") || "";
|
|
247
|
+
const meshDir = compiler?.getAttribute("meshdir") || assetDir;
|
|
248
|
+
const textureDir = compiler?.getAttribute("texturedir") || assetDir;
|
|
442
249
|
const currentDir = currentFile.includes("/") ? currentFile.substring(0, currentFile.lastIndexOf("/") + 1) : "";
|
|
443
250
|
xmlDoc.querySelectorAll("[file]").forEach((el) => {
|
|
444
251
|
const fileAttr = el.getAttribute("file");
|
|
@@ -519,6 +326,8 @@ var _applyPoint = new Float64Array(3);
|
|
|
519
326
|
var _rayPnt = new Float64Array(3);
|
|
520
327
|
var _rayVec = new Float64Array(3);
|
|
521
328
|
var _rayGeomId = new Int32Array(1);
|
|
329
|
+
var _projRaycaster = new THREE11.Raycaster();
|
|
330
|
+
var _projNdc = new THREE11.Vector2();
|
|
522
331
|
var MujocoSimContext = createContext(null);
|
|
523
332
|
function useMujocoSim() {
|
|
524
333
|
const ctx = useContext(MujocoSimContext);
|
|
@@ -572,16 +381,12 @@ function MujocoSimProvider({
|
|
|
572
381
|
const mjDataRef = useRef(null);
|
|
573
382
|
const mujocoRef = useRef(mujoco);
|
|
574
383
|
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
384
|
const pausedRef = useRef(paused ?? false);
|
|
580
385
|
const speedRef = useRef(speed ?? 1);
|
|
581
386
|
const substepsRef = useRef(substeps ?? 1);
|
|
582
387
|
const interpolateRef = useRef(interpolate ?? false);
|
|
583
|
-
const firstIkEnableRef = useRef(true);
|
|
584
388
|
const stepsToRunRef = useRef(0);
|
|
389
|
+
const loadGenRef = useRef(0);
|
|
585
390
|
useRef(null);
|
|
586
391
|
useRef(null);
|
|
587
392
|
useRef(0);
|
|
@@ -591,6 +396,7 @@ function MujocoSimProvider({
|
|
|
591
396
|
onStepRef.current = onStep;
|
|
592
397
|
const beforeStepCallbacks = useRef(/* @__PURE__ */ new Set());
|
|
593
398
|
const afterStepCallbacks = useRef(/* @__PURE__ */ new Set());
|
|
399
|
+
const resetCallbacks = useRef(/* @__PURE__ */ new Set());
|
|
594
400
|
configRef.current = config;
|
|
595
401
|
useEffect(() => {
|
|
596
402
|
pausedRef.current = paused ?? false;
|
|
@@ -618,74 +424,6 @@ function MujocoSimProvider({
|
|
|
618
424
|
if (!model?.opt) return;
|
|
619
425
|
model.opt.timestep = timestep;
|
|
620
426
|
}, [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
427
|
useEffect(() => {
|
|
690
428
|
let disposed = false;
|
|
691
429
|
(async () => {
|
|
@@ -698,8 +436,6 @@ function MujocoSimProvider({
|
|
|
698
436
|
}
|
|
699
437
|
mjModelRef.current = result.mjModel;
|
|
700
438
|
mjDataRef.current = result.mjData;
|
|
701
|
-
siteIdRef.current = result.siteId;
|
|
702
|
-
gripperIdRef.current = result.gripperId;
|
|
703
439
|
if (gravity && result.mjModel.opt?.gravity) {
|
|
704
440
|
result.mjModel.opt.gravity[0] = gravity[0];
|
|
705
441
|
result.mjModel.opt.gravity[1] = gravity[1];
|
|
@@ -708,9 +444,6 @@ function MujocoSimProvider({
|
|
|
708
444
|
if (timestep !== void 0 && result.mjModel.opt) {
|
|
709
445
|
result.mjModel.opt.timestep = timestep;
|
|
710
446
|
}
|
|
711
|
-
if (ikTargetRef.current) {
|
|
712
|
-
syncGizmoToSite(result.mjData, result.siteId, ikTargetRef.current);
|
|
713
|
-
}
|
|
714
447
|
setStatus("ready");
|
|
715
448
|
} catch (e) {
|
|
716
449
|
if (!disposed) {
|
|
@@ -744,42 +477,10 @@ function MujocoSimProvider({
|
|
|
744
477
|
}
|
|
745
478
|
}
|
|
746
479
|
}, [status]);
|
|
747
|
-
useFrame((
|
|
480
|
+
useFrame(() => {
|
|
748
481
|
const model = mjModelRef.current;
|
|
749
482
|
const data = mjDataRef.current;
|
|
750
483
|
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
484
|
const shouldStep = !pausedRef.current || stepsToRunRef.current > 0;
|
|
784
485
|
if (!shouldStep) return;
|
|
785
486
|
for (let i = 0; i < model.nv; i++) {
|
|
@@ -788,18 +489,6 @@ function MujocoSimProvider({
|
|
|
788
489
|
for (const cb of beforeStepCallbacks.current) {
|
|
789
490
|
cb(model, data);
|
|
790
491
|
}
|
|
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
492
|
const numSubsteps = substepsRef.current;
|
|
804
493
|
if (stepsToRunRef.current > 0) {
|
|
805
494
|
for (let s = 0; s < stepsToRunRef.current; s++) {
|
|
@@ -824,72 +513,24 @@ function MujocoSimProvider({
|
|
|
824
513
|
const model = mjModelRef.current;
|
|
825
514
|
const data = mjDataRef.current;
|
|
826
515
|
if (!model || !data) return;
|
|
827
|
-
gizmoAnimRef.current.active = false;
|
|
828
516
|
mujoco.mj_resetData(model, data);
|
|
829
517
|
const homeJoints = configRef.current.homeJoints;
|
|
830
518
|
if (homeJoints) {
|
|
831
|
-
|
|
519
|
+
const homeCount = Math.min(homeJoints.length, model.nu);
|
|
520
|
+
for (let i = 0; i < homeCount; i++) {
|
|
832
521
|
data.ctrl[i] = homeJoints[i];
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
const qposAdr = model.jnt_qposadr[jointId];
|
|
837
|
-
data.qpos[qposAdr] = homeJoints[i];
|
|
838
|
-
}
|
|
522
|
+
const qposAdr = getActuatedScalarQposAdr(model, i);
|
|
523
|
+
if (qposAdr !== -1) {
|
|
524
|
+
data.qpos[qposAdr] = homeJoints[i];
|
|
839
525
|
}
|
|
840
526
|
}
|
|
841
527
|
}
|
|
842
528
|
configRef.current.onReset?.(model, data);
|
|
843
529
|
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;
|
|
530
|
+
for (const cb of resetCallbacks.current) {
|
|
531
|
+
cb();
|
|
856
532
|
}
|
|
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
|
-
);
|
|
533
|
+
}, [mujoco]);
|
|
893
534
|
const setSpeed = useCallback((multiplier) => {
|
|
894
535
|
speedRef.current = multiplier;
|
|
895
536
|
}, []);
|
|
@@ -1051,19 +692,16 @@ function MujocoSimProvider({
|
|
|
1051
692
|
const contacts = [];
|
|
1052
693
|
const ncon = data.ncon;
|
|
1053
694
|
for (let i = 0; i < ncon; i++) {
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
} catch {
|
|
1065
|
-
break;
|
|
1066
|
-
}
|
|
695
|
+
const c = getContact(data, i);
|
|
696
|
+
if (!c) break;
|
|
697
|
+
contacts.push({
|
|
698
|
+
geom1: c.geom1,
|
|
699
|
+
geom1Name: getName(model, model.name_geomadr[c.geom1]),
|
|
700
|
+
geom2: c.geom2,
|
|
701
|
+
geom2Name: getName(model, model.name_geomadr[c.geom2]),
|
|
702
|
+
pos: [c.pos[0], c.pos[1], c.pos[2]],
|
|
703
|
+
depth: c.dist
|
|
704
|
+
});
|
|
1067
705
|
}
|
|
1068
706
|
return contacts;
|
|
1069
707
|
}, []);
|
|
@@ -1202,7 +840,7 @@ function MujocoSimProvider({
|
|
|
1202
840
|
const geomId = _rayGeomId[0];
|
|
1203
841
|
const bodyId = geomId >= 0 ? model.geom_bodyid[geomId] : -1;
|
|
1204
842
|
return {
|
|
1205
|
-
point: new
|
|
843
|
+
point: new THREE11.Vector3(
|
|
1206
844
|
origin.x + dir.x * dist,
|
|
1207
845
|
origin.y + dir.y * dist,
|
|
1208
846
|
origin.z + dir.z * dist
|
|
@@ -1240,10 +878,10 @@ function MujocoSimProvider({
|
|
|
1240
878
|
for (let i = 0; i < model.nv; i++) data.qvel[i] = model.key_qvel[qvelOffset + i];
|
|
1241
879
|
}
|
|
1242
880
|
mujoco.mj_forward(model, data);
|
|
1243
|
-
|
|
1244
|
-
|
|
881
|
+
for (const cb of resetCallbacks.current) {
|
|
882
|
+
cb();
|
|
1245
883
|
}
|
|
1246
|
-
}, [mujoco
|
|
884
|
+
}, [mujoco]);
|
|
1247
885
|
const getKeyframeNames = useCallback(() => {
|
|
1248
886
|
const model = mjModelRef.current;
|
|
1249
887
|
if (!model) return [];
|
|
@@ -1257,6 +895,7 @@ function MujocoSimProvider({
|
|
|
1257
895
|
return mjModelRef.current?.nkey ?? 0;
|
|
1258
896
|
}, []);
|
|
1259
897
|
const loadSceneApi = useCallback(async (newConfig) => {
|
|
898
|
+
const gen = ++loadGenRef.current;
|
|
1260
899
|
try {
|
|
1261
900
|
mjModelRef.current?.delete();
|
|
1262
901
|
mjDataRef.current?.delete();
|
|
@@ -1264,28 +903,21 @@ function MujocoSimProvider({
|
|
|
1264
903
|
mjDataRef.current = null;
|
|
1265
904
|
setStatus("loading");
|
|
1266
905
|
const result = await loadScene(mujoco, newConfig);
|
|
906
|
+
if (gen !== loadGenRef.current) {
|
|
907
|
+
result.mjModel.delete();
|
|
908
|
+
result.mjData.delete();
|
|
909
|
+
return;
|
|
910
|
+
}
|
|
1267
911
|
mjModelRef.current = result.mjModel;
|
|
1268
912
|
mjDataRef.current = result.mjData;
|
|
1269
|
-
siteIdRef.current = result.siteId;
|
|
1270
|
-
gripperIdRef.current = result.gripperId;
|
|
1271
913
|
configRef.current = newConfig;
|
|
1272
|
-
if (ikTargetRef.current) {
|
|
1273
|
-
syncGizmoToSite(result.mjData, result.siteId, ikTargetRef.current);
|
|
1274
|
-
}
|
|
1275
914
|
setStatus("ready");
|
|
1276
915
|
} catch (e) {
|
|
916
|
+
if (gen !== loadGenRef.current) return;
|
|
1277
917
|
setStatus("error");
|
|
1278
918
|
throw e;
|
|
1279
919
|
}
|
|
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
|
-
}, []);
|
|
920
|
+
}, [mujoco]);
|
|
1289
921
|
const getCanvasSnapshot = useCallback(
|
|
1290
922
|
(width, height, mimeType = "image/jpeg") => {
|
|
1291
923
|
if (width && height) {
|
|
@@ -1309,9 +941,8 @@ function MujocoSimProvider({
|
|
|
1309
941
|
virtCam.lookAt(lookAt);
|
|
1310
942
|
virtCam.updateMatrixWorld();
|
|
1311
943
|
virtCam.updateProjectionMatrix();
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
raycaster.setFromCamera(ndc, virtCam);
|
|
944
|
+
_projNdc.set(x * 2 - 1, -(y * 2 - 1));
|
|
945
|
+
_projRaycaster.setFromCamera(_projNdc, virtCam);
|
|
1315
946
|
const objects = [];
|
|
1316
947
|
const scene = camera.parent;
|
|
1317
948
|
if (scene) {
|
|
@@ -1319,7 +950,7 @@ function MujocoSimProvider({
|
|
|
1319
950
|
if (c.isMesh) objects.push(c);
|
|
1320
951
|
});
|
|
1321
952
|
}
|
|
1322
|
-
const hits =
|
|
953
|
+
const hits = _projRaycaster.intersectObjects(objects);
|
|
1323
954
|
if (hits.length > 0) {
|
|
1324
955
|
const hitObj = hits[0].object;
|
|
1325
956
|
const geomId = hitObj.userData.geomID !== void 0 ? hitObj.userData.geomID : -1;
|
|
@@ -1359,31 +990,6 @@ function MujocoSimProvider({
|
|
|
1359
990
|
model.geom_size[id * 3 + 1] = size[1];
|
|
1360
991
|
model.geom_size[id * 3 + 2] = size[2];
|
|
1361
992
|
}, []);
|
|
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
993
|
const api = useMemo(
|
|
1388
994
|
() => ({
|
|
1389
995
|
get status() {
|
|
@@ -1425,15 +1031,8 @@ function MujocoSimProvider({
|
|
|
1425
1031
|
getKeyframeNames,
|
|
1426
1032
|
getKeyframeCount,
|
|
1427
1033
|
loadScene: loadSceneApi,
|
|
1428
|
-
setIkEnabled,
|
|
1429
|
-
moveTarget,
|
|
1430
|
-
syncTargetToSite,
|
|
1431
|
-
solveIK,
|
|
1432
|
-
getGizmoStats,
|
|
1433
1034
|
getCanvasSnapshot,
|
|
1434
1035
|
project2DTo3D,
|
|
1435
|
-
getCameraState,
|
|
1436
|
-
moveCameraTo,
|
|
1437
1036
|
setBodyMass,
|
|
1438
1037
|
setGeomFriction,
|
|
1439
1038
|
setGeomSize,
|
|
@@ -1478,110 +1077,578 @@ function MujocoSimProvider({
|
|
|
1478
1077
|
getKeyframeNames,
|
|
1479
1078
|
getKeyframeCount,
|
|
1480
1079
|
loadSceneApi,
|
|
1481
|
-
setIkEnabled,
|
|
1482
|
-
moveTarget,
|
|
1483
|
-
syncTargetToSite,
|
|
1484
|
-
solveIK,
|
|
1485
|
-
getGizmoStats,
|
|
1486
1080
|
getCanvasSnapshot,
|
|
1487
1081
|
project2DTo3D,
|
|
1488
|
-
getCameraState,
|
|
1489
|
-
moveCameraTo,
|
|
1490
1082
|
setBodyMass,
|
|
1491
1083
|
setGeomFriction,
|
|
1492
1084
|
setGeomSize
|
|
1493
1085
|
]
|
|
1494
1086
|
);
|
|
1495
|
-
const apiRef = useRef(api);
|
|
1496
|
-
apiRef.current = api;
|
|
1087
|
+
const apiRef = useRef(api);
|
|
1088
|
+
apiRef.current = api;
|
|
1089
|
+
const contextValue = useMemo(
|
|
1090
|
+
() => ({
|
|
1091
|
+
api,
|
|
1092
|
+
mjModelRef,
|
|
1093
|
+
mjDataRef,
|
|
1094
|
+
mujocoRef,
|
|
1095
|
+
configRef,
|
|
1096
|
+
pausedRef,
|
|
1097
|
+
speedRef,
|
|
1098
|
+
substepsRef,
|
|
1099
|
+
onSelectionRef,
|
|
1100
|
+
beforeStepCallbacks,
|
|
1101
|
+
afterStepCallbacks,
|
|
1102
|
+
resetCallbacks,
|
|
1103
|
+
status
|
|
1104
|
+
}),
|
|
1105
|
+
[api, status]
|
|
1106
|
+
);
|
|
1107
|
+
return /* @__PURE__ */ jsx(MujocoSimContext.Provider, { value: contextValue, children });
|
|
1108
|
+
}
|
|
1109
|
+
var MujocoCanvas = forwardRef(
|
|
1110
|
+
function MujocoCanvas2({
|
|
1111
|
+
config,
|
|
1112
|
+
onReady,
|
|
1113
|
+
onError,
|
|
1114
|
+
onStep,
|
|
1115
|
+
onSelection,
|
|
1116
|
+
// Declarative physics config (spec 1.1)
|
|
1117
|
+
gravity,
|
|
1118
|
+
timestep,
|
|
1119
|
+
substeps,
|
|
1120
|
+
paused,
|
|
1121
|
+
speed,
|
|
1122
|
+
interpolate,
|
|
1123
|
+
gravityCompensation,
|
|
1124
|
+
mjcfLights,
|
|
1125
|
+
children,
|
|
1126
|
+
...canvasProps
|
|
1127
|
+
}, ref) {
|
|
1128
|
+
const { mujoco, status: wasmStatus, error: wasmError } = useMujoco();
|
|
1129
|
+
useEffect(() => {
|
|
1130
|
+
if (wasmStatus === "error" && onError) {
|
|
1131
|
+
onError(new Error(wasmError ?? "WASM load failed"));
|
|
1132
|
+
}
|
|
1133
|
+
}, [wasmStatus, wasmError, onError]);
|
|
1134
|
+
if (wasmStatus === "error" || wasmStatus === "loading" || !mujoco) {
|
|
1135
|
+
return null;
|
|
1136
|
+
}
|
|
1137
|
+
return /* @__PURE__ */ jsx(Canvas, { ...canvasProps, children: /* @__PURE__ */ jsx(
|
|
1138
|
+
MujocoSimProvider,
|
|
1139
|
+
{
|
|
1140
|
+
mujoco,
|
|
1141
|
+
config,
|
|
1142
|
+
apiRef: ref,
|
|
1143
|
+
onReady,
|
|
1144
|
+
onError,
|
|
1145
|
+
onStep,
|
|
1146
|
+
onSelection,
|
|
1147
|
+
gravity,
|
|
1148
|
+
timestep,
|
|
1149
|
+
substeps,
|
|
1150
|
+
paused,
|
|
1151
|
+
speed,
|
|
1152
|
+
interpolate,
|
|
1153
|
+
children
|
|
1154
|
+
}
|
|
1155
|
+
) });
|
|
1156
|
+
}
|
|
1157
|
+
);
|
|
1158
|
+
function shallowEqual(a, b) {
|
|
1159
|
+
const keysA = Object.keys(a);
|
|
1160
|
+
const keysB = Object.keys(b);
|
|
1161
|
+
if (keysA.length !== keysB.length) return false;
|
|
1162
|
+
for (const key of keysA) {
|
|
1163
|
+
if (a[key] !== b[key]) return false;
|
|
1164
|
+
}
|
|
1165
|
+
return true;
|
|
1166
|
+
}
|
|
1167
|
+
function createController(options, Impl) {
|
|
1168
|
+
function Controller({
|
|
1169
|
+
config,
|
|
1170
|
+
children
|
|
1171
|
+
}) {
|
|
1172
|
+
const configObj = config ?? {};
|
|
1173
|
+
const stableRef = useRef(configObj);
|
|
1174
|
+
if (!shallowEqual(stableRef.current, configObj)) {
|
|
1175
|
+
stableRef.current = configObj;
|
|
1176
|
+
}
|
|
1177
|
+
const stableConfig = stableRef.current;
|
|
1178
|
+
const mergedConfig = useMemo(
|
|
1179
|
+
() => ({ ...options.defaultConfig, ...stableConfig }),
|
|
1180
|
+
[stableConfig]
|
|
1181
|
+
);
|
|
1182
|
+
return /* @__PURE__ */ jsx(Impl, { config: mergedConfig, children });
|
|
1183
|
+
}
|
|
1184
|
+
Controller.displayName = options.name;
|
|
1185
|
+
Controller.controllerName = options.name;
|
|
1186
|
+
Controller.defaultConfig = options.defaultConfig ?? {};
|
|
1187
|
+
return Controller;
|
|
1188
|
+
}
|
|
1189
|
+
var IkContext = createContext(null);
|
|
1190
|
+
function useIk(options) {
|
|
1191
|
+
const ctx = useContext(IkContext);
|
|
1192
|
+
if (!ctx && !options?.optional) {
|
|
1193
|
+
throw new Error("useIk() must be used inside an <IkController>");
|
|
1194
|
+
}
|
|
1195
|
+
return ctx;
|
|
1196
|
+
}
|
|
1197
|
+
|
|
1198
|
+
// src/core/GenericIK.ts
|
|
1199
|
+
var DEFAULTS = {
|
|
1200
|
+
maxIterations: 50,
|
|
1201
|
+
damping: 0.01,
|
|
1202
|
+
tolerance: 1e-3,
|
|
1203
|
+
epsilon: 1e-6,
|
|
1204
|
+
posWeight: 1,
|
|
1205
|
+
rotWeight: 0.3
|
|
1206
|
+
};
|
|
1207
|
+
var GenericIK = class {
|
|
1208
|
+
mujoco;
|
|
1209
|
+
constructor(mujoco) {
|
|
1210
|
+
this.mujoco = mujoco;
|
|
1211
|
+
}
|
|
1212
|
+
/**
|
|
1213
|
+
* Solve IK for a target 6-DOF pose.
|
|
1214
|
+
* @param model MuJoCo model
|
|
1215
|
+
* @param data MuJoCo data (qpos will be temporarily modified, then restored)
|
|
1216
|
+
* @param siteId Index of the end-effector site to control
|
|
1217
|
+
* @param numJoints Number of arm joints (assumes qpos[0..numJoints-1])
|
|
1218
|
+
* @param targetPos Target position in world frame
|
|
1219
|
+
* @param targetQuat Target orientation in world frame
|
|
1220
|
+
* @param currentQ Current joint angles (length = numJoints)
|
|
1221
|
+
* @param opts Optional solver parameters
|
|
1222
|
+
* @returns Joint angles array, or null if solver diverged
|
|
1223
|
+
*/
|
|
1224
|
+
solve(model, data, siteId, numJoints, targetPos, targetQuat, currentQ, opts) {
|
|
1225
|
+
const o = { ...DEFAULTS, ...opts };
|
|
1226
|
+
const n = numJoints;
|
|
1227
|
+
const savedQpos = new Float64Array(data.qpos.length);
|
|
1228
|
+
savedQpos.set(data.qpos);
|
|
1229
|
+
const R_target = quatToMat3(targetQuat);
|
|
1230
|
+
const q = new Float64Array(n);
|
|
1231
|
+
for (let i = 0; i < n; i++) q[i] = currentQ[i];
|
|
1232
|
+
const J = new Float64Array(6 * n);
|
|
1233
|
+
const JJt = new Float64Array(36);
|
|
1234
|
+
const rhs = new Float64Array(6);
|
|
1235
|
+
const x = new Float64Array(6);
|
|
1236
|
+
const dq = new Float64Array(n);
|
|
1237
|
+
const baseSitePos = new Float64Array(3);
|
|
1238
|
+
const baseSiteMat = new Float64Array(9);
|
|
1239
|
+
const pertSitePos = new Float64Array(3);
|
|
1240
|
+
const pertSiteMat = new Float64Array(9);
|
|
1241
|
+
let bestQ = null;
|
|
1242
|
+
let bestErr = Infinity;
|
|
1243
|
+
for (let iter = 0; iter < o.maxIterations; iter++) {
|
|
1244
|
+
for (let i = 0; i < n; i++) data.qpos[i] = q[i];
|
|
1245
|
+
this.mujoco.mj_forward(model, data);
|
|
1246
|
+
const sp = data.site_xpos;
|
|
1247
|
+
const sm = data.site_xmat;
|
|
1248
|
+
const off3 = siteId * 3;
|
|
1249
|
+
const off9 = siteId * 9;
|
|
1250
|
+
for (let i = 0; i < 3; i++) baseSitePos[i] = sp[off3 + i];
|
|
1251
|
+
for (let i = 0; i < 9; i++) baseSiteMat[i] = sm[off9 + i];
|
|
1252
|
+
const posErr0 = targetPos.x - baseSitePos[0];
|
|
1253
|
+
const posErr1 = targetPos.y - baseSitePos[1];
|
|
1254
|
+
const posErr2 = targetPos.z - baseSitePos[2];
|
|
1255
|
+
const rotErr = orientationError(baseSiteMat, R_target);
|
|
1256
|
+
const error = [
|
|
1257
|
+
posErr0 * o.posWeight,
|
|
1258
|
+
posErr1 * o.posWeight,
|
|
1259
|
+
posErr2 * o.posWeight,
|
|
1260
|
+
rotErr[0] * o.rotWeight,
|
|
1261
|
+
rotErr[1] * o.rotWeight,
|
|
1262
|
+
rotErr[2] * o.rotWeight
|
|
1263
|
+
];
|
|
1264
|
+
const errNorm = Math.sqrt(
|
|
1265
|
+
error[0] * error[0] + error[1] * error[1] + error[2] * error[2] + error[3] * error[3] + error[4] * error[4] + error[5] * error[5]
|
|
1266
|
+
);
|
|
1267
|
+
if (errNorm < bestErr) {
|
|
1268
|
+
bestErr = errNorm;
|
|
1269
|
+
bestQ = Array.from(q);
|
|
1270
|
+
}
|
|
1271
|
+
if (errNorm < o.tolerance) break;
|
|
1272
|
+
for (let j = 0; j < n; j++) {
|
|
1273
|
+
const saved = data.qpos[j];
|
|
1274
|
+
data.qpos[j] = q[j] + o.epsilon;
|
|
1275
|
+
this.mujoco.mj_forward(model, data);
|
|
1276
|
+
for (let i = 0; i < 3; i++) pertSitePos[i] = sp[off3 + i];
|
|
1277
|
+
for (let i = 0; i < 9; i++) pertSiteMat[i] = sm[off9 + i];
|
|
1278
|
+
J[0 * n + j] = (pertSitePos[0] - baseSitePos[0]) / o.epsilon * o.posWeight;
|
|
1279
|
+
J[1 * n + j] = (pertSitePos[1] - baseSitePos[1]) / o.epsilon * o.posWeight;
|
|
1280
|
+
J[2 * n + j] = (pertSitePos[2] - baseSitePos[2]) / o.epsilon * o.posWeight;
|
|
1281
|
+
const dRot = angularDelta(baseSiteMat, pertSiteMat);
|
|
1282
|
+
J[3 * n + j] = dRot[0] / o.epsilon * o.rotWeight;
|
|
1283
|
+
J[4 * n + j] = dRot[1] / o.epsilon * o.rotWeight;
|
|
1284
|
+
J[5 * n + j] = dRot[2] / o.epsilon * o.rotWeight;
|
|
1285
|
+
data.qpos[j] = saved;
|
|
1286
|
+
}
|
|
1287
|
+
for (let i = 0; i < n; i++) data.qpos[i] = q[i];
|
|
1288
|
+
for (let r = 0; r < 6; r++) {
|
|
1289
|
+
for (let c = 0; c < 6; c++) {
|
|
1290
|
+
let sum = 0;
|
|
1291
|
+
for (let k = 0; k < n; k++) {
|
|
1292
|
+
sum += J[r * n + k] * J[c * n + k];
|
|
1293
|
+
}
|
|
1294
|
+
JJt[r * 6 + c] = sum + (r === c ? o.damping : 0);
|
|
1295
|
+
}
|
|
1296
|
+
}
|
|
1297
|
+
for (let i = 0; i < 6; i++) rhs[i] = error[i];
|
|
1298
|
+
solve6x6(JJt, rhs, x);
|
|
1299
|
+
for (let j = 0; j < n; j++) {
|
|
1300
|
+
let sum = 0;
|
|
1301
|
+
for (let r = 0; r < 6; r++) {
|
|
1302
|
+
sum += J[r * n + j] * x[r];
|
|
1303
|
+
}
|
|
1304
|
+
dq[j] = sum;
|
|
1305
|
+
}
|
|
1306
|
+
for (let i = 0; i < n; i++) q[i] += dq[i];
|
|
1307
|
+
}
|
|
1308
|
+
data.qpos.set(savedQpos);
|
|
1309
|
+
this.mujoco.mj_forward(model, data);
|
|
1310
|
+
return bestQ;
|
|
1311
|
+
}
|
|
1312
|
+
};
|
|
1313
|
+
function quatToMat3(q) {
|
|
1314
|
+
const m = new Float64Array(9);
|
|
1315
|
+
const x = q.x, y = q.y, z = q.z, w = q.w;
|
|
1316
|
+
const xx = x * x, yy = y * y, zz = z * z;
|
|
1317
|
+
const xy = x * y, xz = x * z, yz = y * z;
|
|
1318
|
+
const wx = w * x, wy = w * y, wz = w * z;
|
|
1319
|
+
m[0] = 1 - 2 * (yy + zz);
|
|
1320
|
+
m[1] = 2 * (xy - wz);
|
|
1321
|
+
m[2] = 2 * (xz + wy);
|
|
1322
|
+
m[3] = 2 * (xy + wz);
|
|
1323
|
+
m[4] = 1 - 2 * (xx + zz);
|
|
1324
|
+
m[5] = 2 * (yz - wx);
|
|
1325
|
+
m[6] = 2 * (xz - wy);
|
|
1326
|
+
m[7] = 2 * (yz + wx);
|
|
1327
|
+
m[8] = 1 - 2 * (xx + yy);
|
|
1328
|
+
return m;
|
|
1329
|
+
}
|
|
1330
|
+
function orientationError(R_cur, R_tgt) {
|
|
1331
|
+
const Re = new Float64Array(9);
|
|
1332
|
+
for (let i = 0; i < 3; i++) {
|
|
1333
|
+
for (let j = 0; j < 3; j++) {
|
|
1334
|
+
let s2 = 0;
|
|
1335
|
+
for (let k = 0; k < 3; k++) {
|
|
1336
|
+
s2 += R_tgt[i * 3 + k] * R_cur[j * 3 + k];
|
|
1337
|
+
}
|
|
1338
|
+
Re[i * 3 + j] = s2;
|
|
1339
|
+
}
|
|
1340
|
+
}
|
|
1341
|
+
const trace = Re[0] + Re[4] + Re[8];
|
|
1342
|
+
const cosAngle = Math.max(-1, Math.min(1, (trace - 1) * 0.5));
|
|
1343
|
+
const angle = Math.acos(cosAngle);
|
|
1344
|
+
if (angle < 1e-6) {
|
|
1345
|
+
return [0, 0, 0];
|
|
1346
|
+
}
|
|
1347
|
+
if (angle > Math.PI - 1e-6) {
|
|
1348
|
+
return [
|
|
1349
|
+
0.5 * (Re[7] - Re[5]),
|
|
1350
|
+
0.5 * (Re[2] - Re[6]),
|
|
1351
|
+
0.5 * (Re[3] - Re[1])
|
|
1352
|
+
];
|
|
1353
|
+
}
|
|
1354
|
+
const s = angle / (2 * Math.sin(angle));
|
|
1355
|
+
return [
|
|
1356
|
+
s * (Re[7] - Re[5]),
|
|
1357
|
+
s * (Re[2] - Re[6]),
|
|
1358
|
+
s * (Re[3] - Re[1])
|
|
1359
|
+
];
|
|
1360
|
+
}
|
|
1361
|
+
function angularDelta(R_base, R_pert) {
|
|
1362
|
+
const dR = new Float64Array(9);
|
|
1363
|
+
for (let i = 0; i < 3; i++) {
|
|
1364
|
+
for (let j = 0; j < 3; j++) {
|
|
1365
|
+
let s = 0;
|
|
1366
|
+
for (let k = 0; k < 3; k++) {
|
|
1367
|
+
s += R_pert[i * 3 + k] * R_base[j * 3 + k];
|
|
1368
|
+
}
|
|
1369
|
+
dR[i * 3 + j] = s;
|
|
1370
|
+
}
|
|
1371
|
+
}
|
|
1372
|
+
return [
|
|
1373
|
+
0.5 * (dR[7] - dR[5]),
|
|
1374
|
+
0.5 * (dR[2] - dR[6]),
|
|
1375
|
+
0.5 * (dR[3] - dR[1])
|
|
1376
|
+
];
|
|
1377
|
+
}
|
|
1378
|
+
function solve6x6(A, b, x) {
|
|
1379
|
+
const N = 6;
|
|
1380
|
+
const a = new Float64Array(A);
|
|
1381
|
+
const r = new Float64Array(b);
|
|
1382
|
+
for (let col = 0; col < N; col++) {
|
|
1383
|
+
let maxVal = Math.abs(a[col * N + col]);
|
|
1384
|
+
let maxRow = col;
|
|
1385
|
+
for (let row = col + 1; row < N; row++) {
|
|
1386
|
+
const val = Math.abs(a[row * N + col]);
|
|
1387
|
+
if (val > maxVal) {
|
|
1388
|
+
maxVal = val;
|
|
1389
|
+
maxRow = row;
|
|
1390
|
+
}
|
|
1391
|
+
}
|
|
1392
|
+
if (maxRow !== col) {
|
|
1393
|
+
for (let k = 0; k < N; k++) {
|
|
1394
|
+
const tmp2 = a[col * N + k];
|
|
1395
|
+
a[col * N + k] = a[maxRow * N + k];
|
|
1396
|
+
a[maxRow * N + k] = tmp2;
|
|
1397
|
+
}
|
|
1398
|
+
const tmp = r[col];
|
|
1399
|
+
r[col] = r[maxRow];
|
|
1400
|
+
r[maxRow] = tmp;
|
|
1401
|
+
}
|
|
1402
|
+
const pivot = a[col * N + col];
|
|
1403
|
+
if (Math.abs(pivot) < 1e-12) {
|
|
1404
|
+
x.fill(0);
|
|
1405
|
+
return;
|
|
1406
|
+
}
|
|
1407
|
+
for (let row = col + 1; row < N; row++) {
|
|
1408
|
+
const factor = a[row * N + col] / pivot;
|
|
1409
|
+
for (let k = col; k < N; k++) {
|
|
1410
|
+
a[row * N + k] -= factor * a[col * N + k];
|
|
1411
|
+
}
|
|
1412
|
+
r[row] -= factor * r[col];
|
|
1413
|
+
}
|
|
1414
|
+
}
|
|
1415
|
+
for (let row = N - 1; row >= 0; row--) {
|
|
1416
|
+
let sum = r[row];
|
|
1417
|
+
for (let k = row + 1; k < N; k++) {
|
|
1418
|
+
sum -= a[row * N + k] * x[k];
|
|
1419
|
+
}
|
|
1420
|
+
x[row] = sum / a[row * N + row];
|
|
1421
|
+
}
|
|
1422
|
+
}
|
|
1423
|
+
var _syncMat4 = new THREE11.Matrix4();
|
|
1424
|
+
function syncGizmoToSite(data, siteId, target) {
|
|
1425
|
+
if (siteId === -1) return;
|
|
1426
|
+
const sitePos = data.site_xpos.subarray(siteId * 3, siteId * 3 + 3);
|
|
1427
|
+
const siteMat = data.site_xmat.subarray(siteId * 9, siteId * 9 + 9);
|
|
1428
|
+
target.position.set(sitePos[0], sitePos[1], sitePos[2]);
|
|
1429
|
+
_syncMat4.set(
|
|
1430
|
+
siteMat[0],
|
|
1431
|
+
siteMat[1],
|
|
1432
|
+
siteMat[2],
|
|
1433
|
+
0,
|
|
1434
|
+
siteMat[3],
|
|
1435
|
+
siteMat[4],
|
|
1436
|
+
siteMat[5],
|
|
1437
|
+
0,
|
|
1438
|
+
siteMat[6],
|
|
1439
|
+
siteMat[7],
|
|
1440
|
+
siteMat[8],
|
|
1441
|
+
0,
|
|
1442
|
+
0,
|
|
1443
|
+
0,
|
|
1444
|
+
0,
|
|
1445
|
+
1
|
|
1446
|
+
);
|
|
1447
|
+
target.quaternion.setFromRotationMatrix(_syncMat4);
|
|
1448
|
+
}
|
|
1449
|
+
function IkControllerImpl({
|
|
1450
|
+
config,
|
|
1451
|
+
children
|
|
1452
|
+
}) {
|
|
1453
|
+
const { mjModelRef, mjDataRef, mujocoRef, configRef, resetCallbacks, status } = useMujocoSim();
|
|
1454
|
+
const ikEnabledRef = useRef(false);
|
|
1455
|
+
const ikCalculatingRef = useRef(false);
|
|
1456
|
+
const ikTargetRef = useRef(new THREE11.Group());
|
|
1457
|
+
const siteIdRef = useRef(-1);
|
|
1458
|
+
const genericIkRef = useRef(new GenericIK(mujocoRef.current));
|
|
1459
|
+
const firstIkEnableRef = useRef(true);
|
|
1460
|
+
const needsInitialSync = useRef(true);
|
|
1461
|
+
const gizmoAnimRef = useRef({
|
|
1462
|
+
active: false,
|
|
1463
|
+
startPos: new THREE11.Vector3(),
|
|
1464
|
+
endPos: new THREE11.Vector3(),
|
|
1465
|
+
startRot: new THREE11.Quaternion(),
|
|
1466
|
+
endRot: new THREE11.Quaternion(),
|
|
1467
|
+
startTime: 0,
|
|
1468
|
+
duration: 1e3
|
|
1469
|
+
});
|
|
1470
|
+
useEffect(() => {
|
|
1471
|
+
const model = mjModelRef.current;
|
|
1472
|
+
if (!model || status !== "ready") {
|
|
1473
|
+
siteIdRef.current = -1;
|
|
1474
|
+
return;
|
|
1475
|
+
}
|
|
1476
|
+
siteIdRef.current = findSiteByName(model, config.siteName);
|
|
1477
|
+
const data = mjDataRef.current;
|
|
1478
|
+
if (data && ikTargetRef.current) {
|
|
1479
|
+
syncGizmoToSite(data, siteIdRef.current, ikTargetRef.current);
|
|
1480
|
+
}
|
|
1481
|
+
}, [config.siteName, status, mjModelRef, mjDataRef]);
|
|
1482
|
+
const ikSolveFn = useCallback(
|
|
1483
|
+
(pos, quat, currentQ) => {
|
|
1484
|
+
if (config.ikSolveFn) return config.ikSolveFn(pos, quat, currentQ);
|
|
1485
|
+
const model = mjModelRef.current;
|
|
1486
|
+
const data = mjDataRef.current;
|
|
1487
|
+
if (!model || !data || siteIdRef.current === -1) return null;
|
|
1488
|
+
return genericIkRef.current.solve(
|
|
1489
|
+
model,
|
|
1490
|
+
data,
|
|
1491
|
+
siteIdRef.current,
|
|
1492
|
+
config.numJoints,
|
|
1493
|
+
pos,
|
|
1494
|
+
quat,
|
|
1495
|
+
currentQ,
|
|
1496
|
+
{
|
|
1497
|
+
damping: config.damping,
|
|
1498
|
+
maxIterations: config.maxIterations
|
|
1499
|
+
}
|
|
1500
|
+
);
|
|
1501
|
+
},
|
|
1502
|
+
[config.ikSolveFn, config.numJoints, config.damping, config.maxIterations, mjModelRef, mjDataRef]
|
|
1503
|
+
);
|
|
1504
|
+
const ikSolveFnRef = useRef(ikSolveFn);
|
|
1505
|
+
ikSolveFnRef.current = ikSolveFn;
|
|
1506
|
+
useFrame(() => {
|
|
1507
|
+
if (needsInitialSync.current && siteIdRef.current !== -1) {
|
|
1508
|
+
const data = mjDataRef.current;
|
|
1509
|
+
if (data && ikTargetRef.current) {
|
|
1510
|
+
syncGizmoToSite(data, siteIdRef.current, ikTargetRef.current);
|
|
1511
|
+
needsInitialSync.current = false;
|
|
1512
|
+
}
|
|
1513
|
+
}
|
|
1514
|
+
const ga = gizmoAnimRef.current;
|
|
1515
|
+
const target = ikTargetRef.current;
|
|
1516
|
+
if (!ga.active || !target) return;
|
|
1517
|
+
const now = performance.now();
|
|
1518
|
+
const elapsed = now - ga.startTime;
|
|
1519
|
+
const t = Math.min(elapsed / ga.duration, 1);
|
|
1520
|
+
const ease = 1 - Math.pow(1 - t, 3);
|
|
1521
|
+
target.position.lerpVectors(ga.startPos, ga.endPos, ease);
|
|
1522
|
+
target.quaternion.slerpQuaternions(ga.startRot, ga.endRot, ease);
|
|
1523
|
+
if (t >= 1) ga.active = false;
|
|
1524
|
+
});
|
|
1525
|
+
useBeforePhysicsStep((model, data) => {
|
|
1526
|
+
if (!ikEnabledRef.current) {
|
|
1527
|
+
ikCalculatingRef.current = false;
|
|
1528
|
+
return;
|
|
1529
|
+
}
|
|
1530
|
+
const target = ikTargetRef.current;
|
|
1531
|
+
if (!target) return;
|
|
1532
|
+
ikCalculatingRef.current = true;
|
|
1533
|
+
const numJoints = config.numJoints;
|
|
1534
|
+
const currentQ = [];
|
|
1535
|
+
for (let i = 0; i < numJoints; i++) currentQ.push(data.qpos[i]);
|
|
1536
|
+
const solution = ikSolveFnRef.current(target.position, target.quaternion, currentQ);
|
|
1537
|
+
if (solution) {
|
|
1538
|
+
for (let i = 0; i < numJoints; i++) data.ctrl[i] = solution[i];
|
|
1539
|
+
}
|
|
1540
|
+
});
|
|
1541
|
+
useEffect(() => {
|
|
1542
|
+
const cb = () => {
|
|
1543
|
+
const data = mjDataRef.current;
|
|
1544
|
+
if (data && ikTargetRef.current) {
|
|
1545
|
+
syncGizmoToSite(data, siteIdRef.current, ikTargetRef.current);
|
|
1546
|
+
}
|
|
1547
|
+
gizmoAnimRef.current.active = false;
|
|
1548
|
+
firstIkEnableRef.current = true;
|
|
1549
|
+
ikEnabledRef.current = false;
|
|
1550
|
+
needsInitialSync.current = true;
|
|
1551
|
+
};
|
|
1552
|
+
resetCallbacks.current.add(cb);
|
|
1553
|
+
return () => {
|
|
1554
|
+
resetCallbacks.current.delete(cb);
|
|
1555
|
+
};
|
|
1556
|
+
}, [resetCallbacks, mjDataRef]);
|
|
1557
|
+
const setIkEnabled = useCallback(
|
|
1558
|
+
(enabled) => {
|
|
1559
|
+
ikEnabledRef.current = enabled;
|
|
1560
|
+
const data = mjDataRef.current;
|
|
1561
|
+
if (enabled && data && !gizmoAnimRef.current.active && ikTargetRef.current) {
|
|
1562
|
+
syncGizmoToSite(data, siteIdRef.current, ikTargetRef.current);
|
|
1563
|
+
firstIkEnableRef.current = false;
|
|
1564
|
+
}
|
|
1565
|
+
},
|
|
1566
|
+
[mjDataRef]
|
|
1567
|
+
);
|
|
1568
|
+
const syncTargetToSiteApi = useCallback(() => {
|
|
1569
|
+
const data = mjDataRef.current;
|
|
1570
|
+
const target = ikTargetRef.current;
|
|
1571
|
+
if (data && target) syncGizmoToSite(data, siteIdRef.current, target);
|
|
1572
|
+
}, [mjDataRef]);
|
|
1573
|
+
const solveIK = useCallback(
|
|
1574
|
+
(pos, quat, currentQ) => {
|
|
1575
|
+
return ikSolveFnRef.current(pos, quat, currentQ);
|
|
1576
|
+
},
|
|
1577
|
+
[]
|
|
1578
|
+
);
|
|
1579
|
+
const moveTarget = useCallback(
|
|
1580
|
+
(pos, duration = 0) => {
|
|
1581
|
+
if (!ikEnabledRef.current) setIkEnabled(true);
|
|
1582
|
+
const target = ikTargetRef.current;
|
|
1583
|
+
if (!target) return;
|
|
1584
|
+
const targetPos = pos.clone();
|
|
1585
|
+
const targetRot = new THREE11.Quaternion().setFromEuler(
|
|
1586
|
+
new THREE11.Euler(Math.PI, 0, 0)
|
|
1587
|
+
);
|
|
1588
|
+
if (duration > 0) {
|
|
1589
|
+
const ga = gizmoAnimRef.current;
|
|
1590
|
+
ga.active = true;
|
|
1591
|
+
ga.startPos.copy(target.position);
|
|
1592
|
+
ga.endPos.copy(targetPos);
|
|
1593
|
+
ga.startRot.copy(target.quaternion);
|
|
1594
|
+
ga.endRot.copy(targetRot);
|
|
1595
|
+
ga.startTime = performance.now();
|
|
1596
|
+
ga.duration = duration;
|
|
1597
|
+
} else {
|
|
1598
|
+
gizmoAnimRef.current.active = false;
|
|
1599
|
+
target.position.copy(targetPos);
|
|
1600
|
+
target.quaternion.copy(targetRot);
|
|
1601
|
+
}
|
|
1602
|
+
},
|
|
1603
|
+
[setIkEnabled]
|
|
1604
|
+
);
|
|
1605
|
+
const getGizmoStats = useCallback(
|
|
1606
|
+
() => {
|
|
1607
|
+
const target = ikTargetRef.current;
|
|
1608
|
+
if (!ikCalculatingRef.current || !target) return null;
|
|
1609
|
+
return {
|
|
1610
|
+
pos: target.position.clone(),
|
|
1611
|
+
rot: new THREE11.Euler().setFromQuaternion(target.quaternion)
|
|
1612
|
+
};
|
|
1613
|
+
},
|
|
1614
|
+
[]
|
|
1615
|
+
);
|
|
1497
1616
|
const contextValue = useMemo(
|
|
1498
1617
|
() => ({
|
|
1499
|
-
api,
|
|
1500
|
-
mjModelRef,
|
|
1501
|
-
mjDataRef,
|
|
1502
|
-
mujocoRef,
|
|
1503
|
-
configRef,
|
|
1504
|
-
siteIdRef,
|
|
1505
|
-
gripperIdRef,
|
|
1506
1618
|
ikEnabledRef,
|
|
1507
1619
|
ikCalculatingRef,
|
|
1508
|
-
pausedRef,
|
|
1509
|
-
speedRef,
|
|
1510
|
-
substepsRef,
|
|
1511
1620
|
ikTargetRef,
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
beforeStepCallbacks,
|
|
1519
|
-
afterStepCallbacks,
|
|
1520
|
-
status
|
|
1621
|
+
siteIdRef,
|
|
1622
|
+
setIkEnabled,
|
|
1623
|
+
moveTarget,
|
|
1624
|
+
syncTargetToSite: syncTargetToSiteApi,
|
|
1625
|
+
solveIK,
|
|
1626
|
+
getGizmoStats
|
|
1521
1627
|
}),
|
|
1522
|
-
[
|
|
1628
|
+
[setIkEnabled, moveTarget, syncTargetToSiteApi, solveIK, getGizmoStats]
|
|
1523
1629
|
);
|
|
1524
|
-
return /* @__PURE__ */ jsx(
|
|
1630
|
+
return /* @__PURE__ */ jsx(IkContext.Provider, { value: contextValue, children });
|
|
1525
1631
|
}
|
|
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
|
-
}
|
|
1632
|
+
var IkController = createController(
|
|
1633
|
+
{
|
|
1634
|
+
name: "IkController",
|
|
1635
|
+
defaultConfig: {
|
|
1636
|
+
damping: 0.01,
|
|
1637
|
+
maxIterations: 50
|
|
1638
|
+
}
|
|
1639
|
+
},
|
|
1640
|
+
IkControllerImpl
|
|
1574
1641
|
);
|
|
1575
|
-
var CapsuleGeometry = class extends
|
|
1642
|
+
var CapsuleGeometry = class extends THREE11.BufferGeometry {
|
|
1576
1643
|
parameters;
|
|
1577
1644
|
constructor(radius = 1, length = 1, capSegments = 4, radialSegments = 8) {
|
|
1578
1645
|
super();
|
|
1579
1646
|
this.type = "CapsuleGeometry";
|
|
1580
1647
|
this.parameters = { radius, length, capSegments, radialSegments };
|
|
1581
|
-
const path = new
|
|
1648
|
+
const path = new THREE11.Path();
|
|
1582
1649
|
path.absarc(0, -length / 2, radius, Math.PI * 1.5, 0, false);
|
|
1583
1650
|
path.absarc(0, length / 2, radius, 0, Math.PI * 0.5, false);
|
|
1584
|
-
const latheGeometry = new
|
|
1651
|
+
const latheGeometry = new THREE11.LatheGeometry(path.getPoints(capSegments), radialSegments);
|
|
1585
1652
|
const self = this;
|
|
1586
1653
|
self.setIndex(latheGeometry.getIndex());
|
|
1587
1654
|
self.setAttribute("position", latheGeometry.getAttribute("position"));
|
|
@@ -1589,27 +1656,27 @@ var CapsuleGeometry = class extends THREE.BufferGeometry {
|
|
|
1589
1656
|
self.setAttribute("uv", latheGeometry.getAttribute("uv"));
|
|
1590
1657
|
}
|
|
1591
1658
|
};
|
|
1592
|
-
var Reflector = class extends
|
|
1659
|
+
var Reflector = class extends THREE11.Mesh {
|
|
1593
1660
|
isReflector = true;
|
|
1594
1661
|
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
|
|
1662
|
+
reflectorPlane = new THREE11.Plane();
|
|
1663
|
+
normal = new THREE11.Vector3();
|
|
1664
|
+
reflectorWorldPosition = new THREE11.Vector3();
|
|
1665
|
+
cameraWorldPosition = new THREE11.Vector3();
|
|
1666
|
+
rotationMatrix = new THREE11.Matrix4();
|
|
1667
|
+
lookAtPosition = new THREE11.Vector3(0, 0, -1);
|
|
1668
|
+
clipPlane = new THREE11.Vector4();
|
|
1669
|
+
view = new THREE11.Vector3();
|
|
1670
|
+
target = new THREE11.Vector3();
|
|
1671
|
+
q = new THREE11.Vector4();
|
|
1672
|
+
textureMatrix = new THREE11.Matrix4();
|
|
1606
1673
|
virtualCamera;
|
|
1607
1674
|
renderTarget;
|
|
1608
1675
|
constructor(geometry, options = {}) {
|
|
1609
1676
|
super(geometry);
|
|
1610
1677
|
this.type = "Reflector";
|
|
1611
|
-
this.camera = new
|
|
1612
|
-
const color = options.color !== void 0 ? new
|
|
1678
|
+
this.camera = new THREE11.PerspectiveCamera();
|
|
1679
|
+
const color = options.color !== void 0 ? new THREE11.Color(options.color) : new THREE11.Color(8355711);
|
|
1613
1680
|
const textureWidth = options.textureWidth || 512;
|
|
1614
1681
|
const textureHeight = options.textureHeight || 512;
|
|
1615
1682
|
const clipBias = options.clipBias || 0;
|
|
@@ -1617,11 +1684,11 @@ var Reflector = class extends THREE.Mesh {
|
|
|
1617
1684
|
const blendTexture = options.texture || void 0;
|
|
1618
1685
|
const mixStrength = options.mixStrength !== void 0 ? options.mixStrength : 0.25;
|
|
1619
1686
|
this.virtualCamera = this.camera;
|
|
1620
|
-
this.renderTarget = new
|
|
1687
|
+
this.renderTarget = new THREE11.WebGLRenderTarget(textureWidth, textureHeight, {
|
|
1621
1688
|
samples: multisample,
|
|
1622
|
-
type:
|
|
1689
|
+
type: THREE11.HalfFloatType
|
|
1623
1690
|
});
|
|
1624
|
-
this.material = new
|
|
1691
|
+
this.material = new THREE11.MeshPhysicalMaterial({
|
|
1625
1692
|
map: blendTexture,
|
|
1626
1693
|
color,
|
|
1627
1694
|
roughness: 0.5,
|
|
@@ -1747,7 +1814,7 @@ var GeomBuilder = class {
|
|
|
1747
1814
|
const pos = mjModel.geom_pos.subarray(g * 3, g * 3 + 3);
|
|
1748
1815
|
const quat = mjModel.geom_quat.subarray(g * 4, g * 4 + 4);
|
|
1749
1816
|
const matId = mjModel.geom_matid[g];
|
|
1750
|
-
const color = new
|
|
1817
|
+
const color = new THREE11.Color(16777215);
|
|
1751
1818
|
let opacity = 1;
|
|
1752
1819
|
if (matId >= 0) {
|
|
1753
1820
|
const rgba = mjModel.mat_rgba.subarray(matId * 4, matId * 4 + 4);
|
|
@@ -1762,16 +1829,16 @@ var GeomBuilder = class {
|
|
|
1762
1829
|
let geo = null;
|
|
1763
1830
|
const getVal = (v) => v?.value ?? v;
|
|
1764
1831
|
if (type === getVal(MG.mjGEOM_PLANE)) {
|
|
1765
|
-
geo = new
|
|
1832
|
+
geo = new THREE11.PlaneGeometry(size[0] * 2 || 5, size[1] * 2 || 5);
|
|
1766
1833
|
} else if (type === getVal(MG.mjGEOM_SPHERE)) {
|
|
1767
|
-
geo = new
|
|
1834
|
+
geo = new THREE11.SphereGeometry(size[0], 24, 24);
|
|
1768
1835
|
} else if (type === getVal(MG.mjGEOM_CAPSULE)) {
|
|
1769
1836
|
geo = new CapsuleGeometry(size[0], size[1] * 2, 24, 12);
|
|
1770
1837
|
geo.rotateX(Math.PI / 2);
|
|
1771
1838
|
} else if (type === getVal(MG.mjGEOM_BOX)) {
|
|
1772
|
-
geo = new
|
|
1839
|
+
geo = new THREE11.BoxGeometry(size[0] * 2, size[1] * 2, size[2] * 2);
|
|
1773
1840
|
} else if (type === getVal(MG.mjGEOM_CYLINDER)) {
|
|
1774
|
-
geo = new
|
|
1841
|
+
geo = new THREE11.CylinderGeometry(size[0], size[0], size[1] * 2, 24);
|
|
1775
1842
|
geo.rotateX(Math.PI / 2);
|
|
1776
1843
|
} else if (type === getVal(MG.mjGEOM_MESH)) {
|
|
1777
1844
|
const mId = mjModel.geom_dataid[g];
|
|
@@ -1779,8 +1846,8 @@ var GeomBuilder = class {
|
|
|
1779
1846
|
const vNum = mjModel.mesh_vertnum[mId];
|
|
1780
1847
|
const fAdr = mjModel.mesh_faceadr[mId];
|
|
1781
1848
|
const fNum = mjModel.mesh_facenum[mId];
|
|
1782
|
-
geo = new
|
|
1783
|
-
geo.setAttribute("position", new
|
|
1849
|
+
geo = new THREE11.BufferGeometry();
|
|
1850
|
+
geo.setAttribute("position", new THREE11.Float32BufferAttribute(mjModel.mesh_vert.subarray(vAdr * 3, (vAdr + vNum) * 3), 3));
|
|
1784
1851
|
geo.setIndex(Array.from(mjModel.mesh_face.subarray(fAdr * 3, (fAdr + fNum) * 3)));
|
|
1785
1852
|
geo.computeVertexNormals();
|
|
1786
1853
|
}
|
|
@@ -1795,7 +1862,7 @@ var GeomBuilder = class {
|
|
|
1795
1862
|
mixStrength: 0.25
|
|
1796
1863
|
});
|
|
1797
1864
|
} else {
|
|
1798
|
-
mesh = new
|
|
1865
|
+
mesh = new THREE11.Mesh(geo, new THREE11.MeshStandardMaterial({
|
|
1799
1866
|
color,
|
|
1800
1867
|
transparent: opacity < 1,
|
|
1801
1868
|
opacity,
|
|
@@ -1820,10 +1887,11 @@ function SceneRenderer() {
|
|
|
1820
1887
|
const bodyRefs = useRef([]);
|
|
1821
1888
|
const prevModelRef = useRef(null);
|
|
1822
1889
|
const geomBuilder = useMemo(() => {
|
|
1890
|
+
if (status !== "ready") return null;
|
|
1823
1891
|
return new GeomBuilder(mujocoRef.current);
|
|
1824
|
-
}, [mujocoRef
|
|
1892
|
+
}, [status, mujocoRef]);
|
|
1825
1893
|
useEffect(() => {
|
|
1826
|
-
if (status !== "ready") return;
|
|
1894
|
+
if (status !== "ready" || !geomBuilder) return;
|
|
1827
1895
|
const model = mjModelRef.current;
|
|
1828
1896
|
const group = groupRef.current;
|
|
1829
1897
|
if (!model || !group) return;
|
|
@@ -1834,7 +1902,7 @@ function SceneRenderer() {
|
|
|
1834
1902
|
}
|
|
1835
1903
|
const refs = [];
|
|
1836
1904
|
for (let i = 0; i < model.nbody; i++) {
|
|
1837
|
-
const bodyGroup = new
|
|
1905
|
+
const bodyGroup = new THREE11.Group();
|
|
1838
1906
|
bodyGroup.userData.bodyID = i;
|
|
1839
1907
|
for (let g = 0; g < model.ngeom; g++) {
|
|
1840
1908
|
if (model.geom_bodyid[g] === i) {
|
|
@@ -1877,31 +1945,25 @@ function SceneRenderer() {
|
|
|
1877
1945
|
while (obj && obj.userData.bodyID === void 0 && obj.parent) {
|
|
1878
1946
|
obj = obj.parent;
|
|
1879
1947
|
}
|
|
1880
|
-
|
|
1948
|
+
const bodyID = obj?.userData.bodyID;
|
|
1949
|
+
if (typeof bodyID === "number" && bodyID > 0) {
|
|
1881
1950
|
const model = mjModelRef.current;
|
|
1882
|
-
if (model && onSelectionRef.current) {
|
|
1883
|
-
const name = getName(model, model.name_bodyadr[
|
|
1884
|
-
onSelectionRef.current(
|
|
1951
|
+
if (model && bodyID < model.nbody && onSelectionRef.current) {
|
|
1952
|
+
const name = getName(model, model.name_bodyadr[bodyID]);
|
|
1953
|
+
onSelectionRef.current(bodyID, name);
|
|
1885
1954
|
}
|
|
1886
1955
|
}
|
|
1887
1956
|
}
|
|
1888
1957
|
}
|
|
1889
1958
|
);
|
|
1890
1959
|
}
|
|
1891
|
-
var _mat4 = new
|
|
1892
|
-
var _pos = new
|
|
1893
|
-
var _quat = new
|
|
1894
|
-
var _scale = new
|
|
1960
|
+
var _mat4 = new THREE11.Matrix4();
|
|
1961
|
+
var _pos = new THREE11.Vector3();
|
|
1962
|
+
var _quat = new THREE11.Quaternion();
|
|
1963
|
+
var _scale = new THREE11.Vector3(1, 1, 1);
|
|
1895
1964
|
function IkGizmo({ siteName, scale = 0.18, onDrag }) {
|
|
1896
|
-
const {
|
|
1897
|
-
|
|
1898
|
-
mjModelRef,
|
|
1899
|
-
mjDataRef,
|
|
1900
|
-
siteIdRef,
|
|
1901
|
-
api,
|
|
1902
|
-
ikEnabledRef,
|
|
1903
|
-
status
|
|
1904
|
-
} = useMujocoSim();
|
|
1965
|
+
const { mjModelRef, mjDataRef, status } = useMujocoSim();
|
|
1966
|
+
const { ikTargetRef, siteIdRef, ikEnabledRef, setIkEnabled } = useIk();
|
|
1905
1967
|
const wrapperRef = useRef(null);
|
|
1906
1968
|
const pivotRef = useRef(null);
|
|
1907
1969
|
const draggingRef = useRef(false);
|
|
@@ -1909,19 +1971,15 @@ function IkGizmo({ siteName, scale = 0.18, onDrag }) {
|
|
|
1909
1971
|
const { controls } = useThree();
|
|
1910
1972
|
useEffect(() => {
|
|
1911
1973
|
const model = mjModelRef.current;
|
|
1912
|
-
if (!model || status !== "ready") {
|
|
1974
|
+
if (!model || status !== "ready" || !siteName) {
|
|
1913
1975
|
localSiteIdRef.current = -1;
|
|
1914
1976
|
return;
|
|
1915
1977
|
}
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
} else {
|
|
1919
|
-
localSiteIdRef.current = siteIdRef.current;
|
|
1920
|
-
}
|
|
1921
|
-
}, [siteName, status, mjModelRef, siteIdRef]);
|
|
1978
|
+
localSiteIdRef.current = findSiteByName(model, siteName);
|
|
1979
|
+
}, [siteName, status, mjModelRef]);
|
|
1922
1980
|
useFrame(() => {
|
|
1923
1981
|
const data = mjDataRef.current;
|
|
1924
|
-
const sid = localSiteIdRef.current;
|
|
1982
|
+
const sid = siteName ? localSiteIdRef.current : siteIdRef.current;
|
|
1925
1983
|
if (!data || sid < 0 || !wrapperRef.current) return;
|
|
1926
1984
|
if (!draggingRef.current) {
|
|
1927
1985
|
const p = data.site_xpos;
|
|
@@ -1966,7 +2024,7 @@ function IkGizmo({ siteName, scale = 0.18, onDrag }) {
|
|
|
1966
2024
|
onDragStart: () => {
|
|
1967
2025
|
draggingRef.current = true;
|
|
1968
2026
|
if (!onDrag) {
|
|
1969
|
-
if (!ikEnabledRef.current)
|
|
2027
|
+
if (!ikEnabledRef.current) setIkEnabled(true);
|
|
1970
2028
|
}
|
|
1971
2029
|
if (controls) controls.enabled = false;
|
|
1972
2030
|
},
|
|
@@ -1994,11 +2052,11 @@ function IkGizmo({ siteName, scale = 0.18, onDrag }) {
|
|
|
1994
2052
|
}
|
|
1995
2053
|
) });
|
|
1996
2054
|
}
|
|
1997
|
-
var _dummy = new
|
|
2055
|
+
var _dummy = new THREE11.Object3D();
|
|
1998
2056
|
function ContactMarkers({
|
|
1999
2057
|
maxContacts = 100,
|
|
2000
|
-
radius =
|
|
2001
|
-
color = "#
|
|
2058
|
+
radius = 8e-3,
|
|
2059
|
+
color = "#22d3ee",
|
|
2002
2060
|
visible = true
|
|
2003
2061
|
} = {}) {
|
|
2004
2062
|
const { mjDataRef, status } = useMujocoSim();
|
|
@@ -2013,43 +2071,33 @@ function ContactMarkers({
|
|
|
2013
2071
|
const ncon = data.ncon;
|
|
2014
2072
|
const count = Math.min(ncon, maxContacts);
|
|
2015
2073
|
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 {
|
|
2074
|
+
const c = getContact(data, i);
|
|
2075
|
+
if (!c) {
|
|
2023
2076
|
mesh.count = i;
|
|
2024
2077
|
mesh.instanceMatrix.needsUpdate = true;
|
|
2025
2078
|
return;
|
|
2026
2079
|
}
|
|
2080
|
+
_dummy.position.set(c.pos[0], c.pos[1], c.pos[2]);
|
|
2081
|
+
_dummy.updateMatrix();
|
|
2082
|
+
mesh.setMatrixAt(i, _dummy.matrix);
|
|
2027
2083
|
}
|
|
2028
2084
|
mesh.count = count;
|
|
2029
2085
|
mesh.instanceMatrix.needsUpdate = true;
|
|
2030
2086
|
});
|
|
2031
2087
|
if (status !== "ready") return null;
|
|
2032
|
-
return /* @__PURE__ */ jsxs("instancedMesh", { ref: meshRef, args: [void 0, void 0, maxContacts], children: [
|
|
2088
|
+
return /* @__PURE__ */ jsxs("instancedMesh", { ref: meshRef, args: [void 0, void 0, maxContacts], frustumCulled: false, renderOrder: 999, children: [
|
|
2033
2089
|
/* @__PURE__ */ jsx("sphereGeometry", { args: [radius, 8, 8] }),
|
|
2034
|
-
/* @__PURE__ */ jsx(
|
|
2035
|
-
"meshStandardMaterial",
|
|
2036
|
-
{
|
|
2037
|
-
color,
|
|
2038
|
-
emissive: color,
|
|
2039
|
-
emissiveIntensity: 0.3,
|
|
2040
|
-
roughness: 0.5
|
|
2041
|
-
}
|
|
2042
|
-
)
|
|
2090
|
+
/* @__PURE__ */ jsx("meshBasicMaterial", { color, depthTest: false })
|
|
2043
2091
|
] });
|
|
2044
2092
|
}
|
|
2045
2093
|
var _force = new Float64Array(3);
|
|
2046
2094
|
var _torque = new Float64Array(3);
|
|
2047
2095
|
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
|
|
2096
|
+
var _bodyPos = new THREE11.Vector3();
|
|
2097
|
+
var _bodyQuat = new THREE11.Quaternion();
|
|
2098
|
+
var _worldHit = new THREE11.Vector3();
|
|
2099
|
+
var _raycaster = new THREE11.Raycaster();
|
|
2100
|
+
var _mouse = new THREE11.Vector2();
|
|
2053
2101
|
function DragInteraction({
|
|
2054
2102
|
stiffness = 250,
|
|
2055
2103
|
showArrow = true
|
|
@@ -2059,16 +2107,16 @@ function DragInteraction({
|
|
|
2059
2107
|
const draggingRef = useRef(false);
|
|
2060
2108
|
const bodyIdRef = useRef(-1);
|
|
2061
2109
|
const grabDistanceRef = useRef(0);
|
|
2062
|
-
const localHitRef = useRef(new
|
|
2063
|
-
const grabWorldRef = useRef(new
|
|
2064
|
-
const mouseWorldRef = useRef(new
|
|
2110
|
+
const localHitRef = useRef(new THREE11.Vector3());
|
|
2111
|
+
const grabWorldRef = useRef(new THREE11.Vector3());
|
|
2112
|
+
const mouseWorldRef = useRef(new THREE11.Vector3());
|
|
2065
2113
|
const arrowRef = useRef(null);
|
|
2066
2114
|
const groupRef = useRef(null);
|
|
2067
2115
|
useEffect(() => {
|
|
2068
2116
|
if (!showArrow || !groupRef.current) return;
|
|
2069
|
-
const arrow = new
|
|
2070
|
-
new
|
|
2071
|
-
new
|
|
2117
|
+
const arrow = new THREE11.ArrowHelper(
|
|
2118
|
+
new THREE11.Vector3(0, 1, 0),
|
|
2119
|
+
new THREE11.Vector3(),
|
|
2072
2120
|
0.1,
|
|
2073
2121
|
16729156
|
|
2074
2122
|
);
|
|
@@ -2196,7 +2244,7 @@ function DragInteraction({
|
|
|
2196
2244
|
if (!arrow) return;
|
|
2197
2245
|
if (draggingRef.current && bodyIdRef.current > 0) {
|
|
2198
2246
|
arrow.visible = true;
|
|
2199
|
-
const dir = mouseWorldRef.current
|
|
2247
|
+
const dir = _bodyPos.copy(mouseWorldRef.current).sub(grabWorldRef.current);
|
|
2200
2248
|
const len = dir.length();
|
|
2201
2249
|
if (len > 1e-3) {
|
|
2202
2250
|
dir.normalize();
|
|
@@ -2211,7 +2259,7 @@ function DragInteraction({
|
|
|
2211
2259
|
if (status !== "ready") return null;
|
|
2212
2260
|
return /* @__PURE__ */ jsx("group", { ref: groupRef });
|
|
2213
2261
|
}
|
|
2214
|
-
function
|
|
2262
|
+
function useSceneLights(intensity = 1) {
|
|
2215
2263
|
const { mjModelRef, status } = useMujocoSim();
|
|
2216
2264
|
const { scene } = useThree();
|
|
2217
2265
|
const lightsRef = useRef([]);
|
|
@@ -2239,7 +2287,7 @@ function SceneLights({ intensity = 1 }) {
|
|
|
2239
2287
|
const dr = model.light_diffuse ? model.light_diffuse[3 * i] : 1;
|
|
2240
2288
|
const dg = model.light_diffuse ? model.light_diffuse[3 * i + 1] : 1;
|
|
2241
2289
|
const db = model.light_diffuse ? model.light_diffuse[3 * i + 2] : 1;
|
|
2242
|
-
const color = new
|
|
2290
|
+
const color = new THREE11.Color(dr, dg, db);
|
|
2243
2291
|
const px = model.light_pos[3 * i];
|
|
2244
2292
|
const py = model.light_pos[3 * i + 1];
|
|
2245
2293
|
const pz = model.light_pos[3 * i + 2];
|
|
@@ -2247,7 +2295,7 @@ function SceneLights({ intensity = 1 }) {
|
|
|
2247
2295
|
const dy = model.light_dir[3 * i + 1];
|
|
2248
2296
|
const dz = model.light_dir[3 * i + 2];
|
|
2249
2297
|
if (isDirectional) {
|
|
2250
|
-
const light = new
|
|
2298
|
+
const light = new THREE11.DirectionalLight(color, finalIntensity);
|
|
2251
2299
|
light.position.set(px, py, pz);
|
|
2252
2300
|
light.target.position.set(px + dx, py + dy, pz + dz);
|
|
2253
2301
|
light.castShadow = castShadow;
|
|
@@ -2270,7 +2318,7 @@ function SceneLights({ intensity = 1 }) {
|
|
|
2270
2318
|
const cutoff = model.light_cutoff ? model.light_cutoff[i] : 45;
|
|
2271
2319
|
const exponent = model.light_exponent ? model.light_exponent[i] : 10;
|
|
2272
2320
|
const angle = cutoff * Math.PI / 180;
|
|
2273
|
-
const light = new
|
|
2321
|
+
const light = new THREE11.SpotLight(color, finalIntensity, 0, angle, exponent / 128);
|
|
2274
2322
|
light.position.set(px, py, pz);
|
|
2275
2323
|
light.target.position.set(px + dx, py + dy, pz + dz);
|
|
2276
2324
|
light.castShadow = castShadow;
|
|
@@ -2300,6 +2348,11 @@ function SceneLights({ intensity = 1 }) {
|
|
|
2300
2348
|
targetsRef.current = [];
|
|
2301
2349
|
};
|
|
2302
2350
|
}, [status, mjModelRef, scene, intensity]);
|
|
2351
|
+
}
|
|
2352
|
+
|
|
2353
|
+
// src/components/SceneLights.tsx
|
|
2354
|
+
function SceneLights({ intensity = 1 }) {
|
|
2355
|
+
useSceneLights(intensity);
|
|
2303
2356
|
return null;
|
|
2304
2357
|
}
|
|
2305
2358
|
var JOINT_COLORS = {
|
|
@@ -2312,6 +2365,12 @@ var JOINT_COLORS = {
|
|
|
2312
2365
|
3: 16776960
|
|
2313
2366
|
// hinge - yellow
|
|
2314
2367
|
};
|
|
2368
|
+
var _v3a = new THREE11.Vector3();
|
|
2369
|
+
new THREE11.Vector3();
|
|
2370
|
+
var _quat2 = new THREE11.Quaternion();
|
|
2371
|
+
var _contactPos = new THREE11.Vector3();
|
|
2372
|
+
var _contactNormal = new THREE11.Vector3();
|
|
2373
|
+
var MAX_CONTACT_ARROWS = 50;
|
|
2315
2374
|
function Debug({
|
|
2316
2375
|
showGeoms = false,
|
|
2317
2376
|
showSites = false,
|
|
@@ -2338,21 +2397,21 @@ function Debug({
|
|
|
2338
2397
|
let geometry = null;
|
|
2339
2398
|
switch (type) {
|
|
2340
2399
|
case 2:
|
|
2341
|
-
geometry = new
|
|
2400
|
+
geometry = new THREE11.SphereGeometry(s[3 * i], 12, 8);
|
|
2342
2401
|
break;
|
|
2343
2402
|
case 3:
|
|
2344
|
-
geometry = new
|
|
2403
|
+
geometry = new THREE11.CapsuleGeometry(s[3 * i], s[3 * i + 1] * 2, 6, 8);
|
|
2345
2404
|
break;
|
|
2346
2405
|
case 5:
|
|
2347
|
-
geometry = new
|
|
2406
|
+
geometry = new THREE11.CylinderGeometry(s[3 * i], s[3 * i], s[3 * i + 1] * 2, 12);
|
|
2348
2407
|
break;
|
|
2349
2408
|
case 6:
|
|
2350
|
-
geometry = new
|
|
2409
|
+
geometry = new THREE11.BoxGeometry(s[3 * i] * 2, s[3 * i + 1] * 2, s[3 * i + 2] * 2);
|
|
2351
2410
|
break;
|
|
2352
2411
|
}
|
|
2353
2412
|
if (geometry) {
|
|
2354
|
-
const mat = new
|
|
2355
|
-
const mesh = new
|
|
2413
|
+
const mat = new THREE11.MeshBasicMaterial({ color: 65280, wireframe: true, transparent: true, opacity: 0.3 });
|
|
2414
|
+
const mesh = new THREE11.Mesh(geometry, mat);
|
|
2356
2415
|
mesh.userData.geomId = i;
|
|
2357
2416
|
mesh.userData.bodyId = model.geom_bodyid[i];
|
|
2358
2417
|
geoms.push(mesh);
|
|
@@ -2360,35 +2419,88 @@ function Debug({
|
|
|
2360
2419
|
}
|
|
2361
2420
|
}
|
|
2362
2421
|
if (showSites) {
|
|
2422
|
+
const siteSize = model.site_size;
|
|
2363
2423
|
for (let i = 0; i < model.nsite; i++) {
|
|
2364
|
-
|
|
2365
|
-
|
|
2366
|
-
|
|
2424
|
+
let radius = 8e-3;
|
|
2425
|
+
if (siteSize) {
|
|
2426
|
+
radius = Math.max(siteSize[3 * i] * 0.5, 4e-3);
|
|
2427
|
+
} else {
|
|
2428
|
+
const bodyId = model.site_bodyid[i];
|
|
2429
|
+
let maxGeomSize = 0;
|
|
2430
|
+
for (let g = 0; g < model.ngeom; g++) {
|
|
2431
|
+
if (model.geom_bodyid[g] === bodyId) {
|
|
2432
|
+
maxGeomSize = Math.max(maxGeomSize, model.geom_size[3 * g]);
|
|
2433
|
+
}
|
|
2434
|
+
}
|
|
2435
|
+
if (maxGeomSize > 0) radius = maxGeomSize * 0.15;
|
|
2436
|
+
}
|
|
2437
|
+
const geometry = new THREE11.OctahedronGeometry(radius);
|
|
2438
|
+
const mat = new THREE11.MeshBasicMaterial({ color: 16711935, depthTest: false });
|
|
2439
|
+
const mesh = new THREE11.Mesh(geometry, mat);
|
|
2440
|
+
mesh.renderOrder = 999;
|
|
2441
|
+
mesh.frustumCulled = false;
|
|
2367
2442
|
mesh.userData.siteId = i;
|
|
2443
|
+
const canvas = document.createElement("canvas");
|
|
2444
|
+
canvas.width = 256;
|
|
2445
|
+
canvas.height = 64;
|
|
2446
|
+
const ctx = canvas.getContext("2d");
|
|
2447
|
+
ctx.fillStyle = "#ff00ff";
|
|
2448
|
+
ctx.font = "bold 36px monospace";
|
|
2449
|
+
ctx.textAlign = "center";
|
|
2450
|
+
ctx.fillText(getName(model, model.name_siteadr[i]), 128, 42);
|
|
2451
|
+
const tex = new THREE11.CanvasTexture(canvas);
|
|
2452
|
+
const spriteMat = new THREE11.SpriteMaterial({ map: tex, depthTest: false, transparent: true });
|
|
2453
|
+
const sprite = new THREE11.Sprite(spriteMat);
|
|
2454
|
+
const labelScale = radius * 15;
|
|
2455
|
+
sprite.scale.set(labelScale, labelScale * 0.25, 1);
|
|
2456
|
+
sprite.position.y = radius * 2;
|
|
2457
|
+
sprite.renderOrder = 999;
|
|
2458
|
+
mesh.add(sprite);
|
|
2368
2459
|
sites.push(mesh);
|
|
2369
2460
|
}
|
|
2370
2461
|
}
|
|
2371
2462
|
if (showJoints) {
|
|
2463
|
+
const jntPos = model.jnt_pos;
|
|
2464
|
+
const jntAxis = model.jnt_axis;
|
|
2372
2465
|
for (let i = 0; i < model.njnt; i++) {
|
|
2373
2466
|
const type = model.jnt_type[i];
|
|
2374
2467
|
const color = JOINT_COLORS[type] ?? 16777215;
|
|
2375
|
-
const
|
|
2376
|
-
|
|
2377
|
-
|
|
2378
|
-
|
|
2468
|
+
const bodyId = model.jnt_bodyid[i];
|
|
2469
|
+
let maxGeomSize = 0;
|
|
2470
|
+
for (let g = 0; g < model.ngeom; g++) {
|
|
2471
|
+
if (model.geom_bodyid[g] === bodyId) {
|
|
2472
|
+
maxGeomSize = Math.max(maxGeomSize, model.geom_size[3 * g]);
|
|
2473
|
+
}
|
|
2474
|
+
}
|
|
2475
|
+
const arrowLen = Math.max(maxGeomSize * 0.8, 0.05);
|
|
2476
|
+
const arrow = new THREE11.ArrowHelper(
|
|
2477
|
+
new THREE11.Vector3(0, 0, 1),
|
|
2478
|
+
new THREE11.Vector3(),
|
|
2479
|
+
arrowLen,
|
|
2379
2480
|
color,
|
|
2380
|
-
0.
|
|
2381
|
-
|
|
2481
|
+
arrowLen * 0.25,
|
|
2482
|
+
arrowLen * 0.12
|
|
2382
2483
|
);
|
|
2484
|
+
arrow.renderOrder = 999;
|
|
2485
|
+
arrow.frustumCulled = false;
|
|
2486
|
+
arrow.line.material = new THREE11.LineBasicMaterial({ color, depthTest: false });
|
|
2487
|
+
arrow.cone.material.depthTest = false;
|
|
2488
|
+
arrow.line.renderOrder = 999;
|
|
2489
|
+
arrow.line.frustumCulled = false;
|
|
2490
|
+
arrow.cone.renderOrder = 999;
|
|
2491
|
+
arrow.cone.frustumCulled = false;
|
|
2383
2492
|
arrow.userData.jointId = i;
|
|
2493
|
+
arrow.userData.bodyId = bodyId;
|
|
2494
|
+
arrow.userData.hasJntPos = !!jntPos;
|
|
2495
|
+
arrow.userData.hasJntAxis = !!jntAxis;
|
|
2384
2496
|
joints.push(arrow);
|
|
2385
2497
|
}
|
|
2386
2498
|
}
|
|
2387
2499
|
if (showCOM) {
|
|
2388
2500
|
for (let i = 1; i < model.nbody; i++) {
|
|
2389
|
-
const geometry = new
|
|
2390
|
-
const mat = new
|
|
2391
|
-
const mesh = new
|
|
2501
|
+
const geometry = new THREE11.SphereGeometry(5e-3, 6, 6);
|
|
2502
|
+
const mat = new THREE11.MeshBasicMaterial({ color: 16711680 });
|
|
2503
|
+
const mesh = new THREE11.Mesh(geometry, mat);
|
|
2392
2504
|
mesh.userData.bodyId = i;
|
|
2393
2505
|
comMarkers.push(mesh);
|
|
2394
2506
|
}
|
|
@@ -2416,6 +2528,8 @@ function Debug({
|
|
|
2416
2528
|
const model = mjModelRef.current;
|
|
2417
2529
|
const data = mjDataRef.current;
|
|
2418
2530
|
if (!model || !data || !debugGeometry) return;
|
|
2531
|
+
const jntPos = model.jnt_pos;
|
|
2532
|
+
const jntAxis = model.jnt_axis;
|
|
2419
2533
|
for (const mesh of debugGeometry.geoms) {
|
|
2420
2534
|
const bid = mesh.userData.bodyId;
|
|
2421
2535
|
const i3 = bid * 3;
|
|
@@ -2429,7 +2543,8 @@ function Debug({
|
|
|
2429
2543
|
);
|
|
2430
2544
|
const gid = mesh.userData.geomId;
|
|
2431
2545
|
const gp = model.geom_pos;
|
|
2432
|
-
|
|
2546
|
+
_v3a.set(gp[3 * gid], gp[3 * gid + 1], gp[3 * gid + 2]).applyQuaternion(mesh.quaternion);
|
|
2547
|
+
mesh.position.add(_v3a);
|
|
2433
2548
|
}
|
|
2434
2549
|
for (const mesh of debugGeometry.sites) {
|
|
2435
2550
|
const sid = mesh.userData.siteId;
|
|
@@ -2439,6 +2554,28 @@ function Debug({
|
|
|
2439
2554
|
data.site_xpos[3 * sid + 2]
|
|
2440
2555
|
);
|
|
2441
2556
|
}
|
|
2557
|
+
for (const obj of debugGeometry.joints) {
|
|
2558
|
+
const arrow = obj;
|
|
2559
|
+
const jid = arrow.userData.jointId;
|
|
2560
|
+
const bid = arrow.userData.bodyId;
|
|
2561
|
+
const i3 = bid * 3;
|
|
2562
|
+
const i4 = bid * 4;
|
|
2563
|
+
_quat2.set(
|
|
2564
|
+
data.xquat[i4 + 1],
|
|
2565
|
+
data.xquat[i4 + 2],
|
|
2566
|
+
data.xquat[i4 + 3],
|
|
2567
|
+
data.xquat[i4]
|
|
2568
|
+
);
|
|
2569
|
+
arrow.position.set(data.xpos[i3], data.xpos[i3 + 1], data.xpos[i3 + 2]);
|
|
2570
|
+
if (jntPos) {
|
|
2571
|
+
_v3a.set(jntPos[3 * jid], jntPos[3 * jid + 1], jntPos[3 * jid + 2]).applyQuaternion(_quat2);
|
|
2572
|
+
arrow.position.add(_v3a);
|
|
2573
|
+
}
|
|
2574
|
+
if (jntAxis) {
|
|
2575
|
+
_v3a.set(jntAxis[3 * jid], jntAxis[3 * jid + 1], jntAxis[3 * jid + 2]).applyQuaternion(_quat2).normalize();
|
|
2576
|
+
arrow.setDirection(_v3a);
|
|
2577
|
+
}
|
|
2578
|
+
}
|
|
2442
2579
|
for (const mesh of debugGeometry.comMarkers) {
|
|
2443
2580
|
const bid = mesh.userData.bodyId;
|
|
2444
2581
|
const i3 = bid * 3;
|
|
@@ -2446,34 +2583,61 @@ function Debug({
|
|
|
2446
2583
|
}
|
|
2447
2584
|
});
|
|
2448
2585
|
const contactGroupRef = useRef(null);
|
|
2449
|
-
const
|
|
2586
|
+
const contactPoolRef = useRef([]);
|
|
2587
|
+
const contactPoolInitRef = useRef(false);
|
|
2588
|
+
useEffect(() => {
|
|
2589
|
+
const group = contactGroupRef.current;
|
|
2590
|
+
if (!group || contactPoolInitRef.current) return;
|
|
2591
|
+
contactPoolInitRef.current = true;
|
|
2592
|
+
const pool = [];
|
|
2593
|
+
for (let i = 0; i < MAX_CONTACT_ARROWS; i++) {
|
|
2594
|
+
const arrow = new THREE11.ArrowHelper(
|
|
2595
|
+
new THREE11.Vector3(0, 1, 0),
|
|
2596
|
+
new THREE11.Vector3(),
|
|
2597
|
+
0.1,
|
|
2598
|
+
16729156,
|
|
2599
|
+
0.03,
|
|
2600
|
+
0.015
|
|
2601
|
+
);
|
|
2602
|
+
arrow.visible = false;
|
|
2603
|
+
group.add(arrow);
|
|
2604
|
+
pool.push(arrow);
|
|
2605
|
+
}
|
|
2606
|
+
contactPoolRef.current = pool;
|
|
2607
|
+
return () => {
|
|
2608
|
+
for (const arrow of pool) {
|
|
2609
|
+
group.remove(arrow);
|
|
2610
|
+
arrow.dispose();
|
|
2611
|
+
}
|
|
2612
|
+
contactPoolRef.current = [];
|
|
2613
|
+
contactPoolInitRef.current = false;
|
|
2614
|
+
};
|
|
2615
|
+
}, [showContacts]);
|
|
2450
2616
|
useFrame(() => {
|
|
2451
2617
|
if (!showContacts) return;
|
|
2452
|
-
const model = mjModelRef.current;
|
|
2453
2618
|
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 = [];
|
|
2619
|
+
const pool = contactPoolRef.current;
|
|
2620
|
+
if (!data || pool.length === 0) return;
|
|
2461
2621
|
const ncon = data.ncon;
|
|
2462
|
-
|
|
2463
|
-
|
|
2464
|
-
|
|
2465
|
-
|
|
2466
|
-
|
|
2467
|
-
|
|
2468
|
-
|
|
2469
|
-
|
|
2470
|
-
|
|
2471
|
-
|
|
2472
|
-
|
|
2473
|
-
|
|
2474
|
-
|
|
2475
|
-
|
|
2476
|
-
|
|
2622
|
+
let arrowIdx = 0;
|
|
2623
|
+
for (let i = 0; i < Math.min(ncon, MAX_CONTACT_ARROWS); i++) {
|
|
2624
|
+
const c = getContact(data, i);
|
|
2625
|
+
if (!c) break;
|
|
2626
|
+
_contactPos.set(c.pos[0], c.pos[1], c.pos[2]);
|
|
2627
|
+
_contactNormal.set(c.frame[0], c.frame[1], c.frame[2]);
|
|
2628
|
+
const force = Math.abs(c.dist) * 100;
|
|
2629
|
+
const length = Math.min(force * 0.01, 0.1);
|
|
2630
|
+
if (length > 1e-3 && arrowIdx < pool.length) {
|
|
2631
|
+
const arrow = pool[arrowIdx];
|
|
2632
|
+
arrow.position.copy(_contactPos);
|
|
2633
|
+
arrow.setDirection(_contactNormal);
|
|
2634
|
+
arrow.setLength(length, length * 0.3, length * 0.15);
|
|
2635
|
+
arrow.visible = true;
|
|
2636
|
+
arrowIdx++;
|
|
2637
|
+
}
|
|
2638
|
+
}
|
|
2639
|
+
for (let i = arrowIdx; i < pool.length; i++) {
|
|
2640
|
+
pool[i].visible = false;
|
|
2477
2641
|
}
|
|
2478
2642
|
});
|
|
2479
2643
|
if (status !== "ready") return null;
|
|
@@ -2482,30 +2646,75 @@ function Debug({
|
|
|
2482
2646
|
showContacts && /* @__PURE__ */ jsx("group", { ref: contactGroupRef })
|
|
2483
2647
|
] });
|
|
2484
2648
|
}
|
|
2485
|
-
var DEFAULT_TENDON_COLOR = new
|
|
2649
|
+
var DEFAULT_TENDON_COLOR = new THREE11.Color(0.3, 0.3, 0.8);
|
|
2486
2650
|
var DEFAULT_TENDON_WIDTH = 2e-3;
|
|
2651
|
+
new THREE11.Vector3();
|
|
2487
2652
|
function TendonRenderer() {
|
|
2488
2653
|
const { mjModelRef, mjDataRef, status } = useMujocoSim();
|
|
2489
2654
|
const groupRef = useRef(null);
|
|
2490
2655
|
const meshesRef = useRef([]);
|
|
2491
|
-
|
|
2656
|
+
const curvesRef = useRef([]);
|
|
2657
|
+
const materialRef = useRef(null);
|
|
2658
|
+
useEffect(() => {
|
|
2492
2659
|
const model = mjModelRef.current;
|
|
2493
2660
|
const data = mjDataRef.current;
|
|
2494
2661
|
const group = groupRef.current;
|
|
2495
2662
|
if (!model || !data || !group) return;
|
|
2496
2663
|
const ntendon = model.ntendon ?? 0;
|
|
2497
2664
|
if (ntendon === 0) return;
|
|
2498
|
-
|
|
2499
|
-
|
|
2500
|
-
|
|
2501
|
-
|
|
2665
|
+
const material = new THREE11.MeshStandardMaterial({
|
|
2666
|
+
color: DEFAULT_TENDON_COLOR,
|
|
2667
|
+
roughness: 0.6,
|
|
2668
|
+
metalness: 0.1
|
|
2669
|
+
});
|
|
2670
|
+
materialRef.current = material;
|
|
2671
|
+
const meshes = [];
|
|
2672
|
+
const curves = [];
|
|
2673
|
+
for (let t = 0; t < ntendon; t++) {
|
|
2674
|
+
const wrapNum = model.ten_wrapnum[t];
|
|
2675
|
+
if (wrapNum < 2) {
|
|
2676
|
+
meshes.push(null);
|
|
2677
|
+
curves.push(null);
|
|
2678
|
+
continue;
|
|
2679
|
+
}
|
|
2680
|
+
const points = Array.from({ length: wrapNum }, () => new THREE11.Vector3());
|
|
2681
|
+
const curve = new THREE11.CatmullRomCurve3(points, false);
|
|
2682
|
+
const segments = Math.max(wrapNum * 2, 4);
|
|
2683
|
+
const geometry = new THREE11.TubeGeometry(curve, segments, DEFAULT_TENDON_WIDTH, 6, false);
|
|
2684
|
+
const mesh = new THREE11.Mesh(geometry, material);
|
|
2685
|
+
mesh.frustumCulled = false;
|
|
2686
|
+
group.add(mesh);
|
|
2687
|
+
meshes.push(mesh);
|
|
2688
|
+
curves.push(curve);
|
|
2502
2689
|
}
|
|
2503
|
-
meshesRef.current =
|
|
2690
|
+
meshesRef.current = meshes;
|
|
2691
|
+
curvesRef.current = curves;
|
|
2692
|
+
return () => {
|
|
2693
|
+
for (const mesh of meshes) {
|
|
2694
|
+
if (!mesh) continue;
|
|
2695
|
+
group.remove(mesh);
|
|
2696
|
+
mesh.geometry.dispose();
|
|
2697
|
+
}
|
|
2698
|
+
material.dispose();
|
|
2699
|
+
meshesRef.current = [];
|
|
2700
|
+
curvesRef.current = [];
|
|
2701
|
+
materialRef.current = null;
|
|
2702
|
+
};
|
|
2703
|
+
}, [status, mjModelRef, mjDataRef]);
|
|
2704
|
+
useFrame(() => {
|
|
2705
|
+
const model = mjModelRef.current;
|
|
2706
|
+
const data = mjDataRef.current;
|
|
2707
|
+
if (!model || !data) return;
|
|
2708
|
+
const ntendon = model.ntendon ?? 0;
|
|
2709
|
+
const meshes = meshesRef.current;
|
|
2710
|
+
const curves = curvesRef.current;
|
|
2504
2711
|
for (let t = 0; t < ntendon; t++) {
|
|
2712
|
+
const mesh = meshes[t];
|
|
2713
|
+
const curve = curves[t];
|
|
2714
|
+
if (!mesh || !curve) continue;
|
|
2505
2715
|
const wrapAdr = model.ten_wrapadr[t];
|
|
2506
2716
|
const wrapNum = model.ten_wrapnum[t];
|
|
2507
|
-
|
|
2508
|
-
const points = [];
|
|
2717
|
+
let validCount = 0;
|
|
2509
2718
|
for (let w = 0; w < wrapNum; w++) {
|
|
2510
2719
|
const idx = (wrapAdr + w) * 3;
|
|
2511
2720
|
if (data.wrap_xpos && idx + 2 < data.wrap_xpos.length) {
|
|
@@ -2513,27 +2722,32 @@ function TendonRenderer() {
|
|
|
2513
2722
|
const y = data.wrap_xpos[idx + 1];
|
|
2514
2723
|
const z = data.wrap_xpos[idx + 2];
|
|
2515
2724
|
if (x !== 0 || y !== 0 || z !== 0) {
|
|
2516
|
-
|
|
2725
|
+
if (validCount < curve.points.length) {
|
|
2726
|
+
curve.points[validCount].set(x, y, z);
|
|
2727
|
+
}
|
|
2728
|
+
validCount++;
|
|
2517
2729
|
}
|
|
2518
2730
|
}
|
|
2519
2731
|
}
|
|
2520
|
-
if (
|
|
2521
|
-
|
|
2522
|
-
|
|
2732
|
+
if (validCount < 2) {
|
|
2733
|
+
mesh.visible = false;
|
|
2734
|
+
continue;
|
|
2735
|
+
}
|
|
2736
|
+
if (curve.points.length !== validCount) {
|
|
2737
|
+
curve.points.length = validCount;
|
|
2738
|
+
while (curve.points.length < validCount) {
|
|
2739
|
+
curve.points.push(new THREE11.Vector3());
|
|
2740
|
+
}
|
|
2741
|
+
}
|
|
2742
|
+
mesh.geometry.dispose();
|
|
2743
|
+
mesh.geometry = new THREE11.TubeGeometry(
|
|
2523
2744
|
curve,
|
|
2524
|
-
Math.max(
|
|
2745
|
+
Math.max(validCount * 2, 4),
|
|
2525
2746
|
DEFAULT_TENDON_WIDTH,
|
|
2526
2747
|
6,
|
|
2527
2748
|
false
|
|
2528
2749
|
);
|
|
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);
|
|
2750
|
+
mesh.visible = true;
|
|
2537
2751
|
}
|
|
2538
2752
|
});
|
|
2539
2753
|
if (status !== "ready") return null;
|
|
@@ -2553,24 +2767,24 @@ function FlexRenderer() {
|
|
|
2553
2767
|
const vertAdr = model.flex_vertadr[f];
|
|
2554
2768
|
const vertNum = model.flex_vertnum[f];
|
|
2555
2769
|
if (vertNum === 0) continue;
|
|
2556
|
-
const geometry = new
|
|
2770
|
+
const geometry = new THREE11.BufferGeometry();
|
|
2557
2771
|
const positions = new Float32Array(vertNum * 3);
|
|
2558
|
-
geometry.setAttribute("position", new
|
|
2772
|
+
geometry.setAttribute("position", new THREE11.BufferAttribute(positions, 3));
|
|
2559
2773
|
geometry.computeVertexNormals();
|
|
2560
|
-
let color = new
|
|
2774
|
+
let color = new THREE11.Color(0.5, 0.5, 0.5);
|
|
2561
2775
|
if (model.flex_rgba) {
|
|
2562
|
-
color = new
|
|
2776
|
+
color = new THREE11.Color(
|
|
2563
2777
|
model.flex_rgba[4 * f],
|
|
2564
2778
|
model.flex_rgba[4 * f + 1],
|
|
2565
2779
|
model.flex_rgba[4 * f + 2]
|
|
2566
2780
|
);
|
|
2567
2781
|
}
|
|
2568
|
-
const material = new
|
|
2782
|
+
const material = new THREE11.MeshStandardMaterial({
|
|
2569
2783
|
color,
|
|
2570
2784
|
roughness: 0.7,
|
|
2571
|
-
side:
|
|
2785
|
+
side: THREE11.DoubleSide
|
|
2572
2786
|
});
|
|
2573
|
-
const mesh = new
|
|
2787
|
+
const mesh = new THREE11.Mesh(geometry, material);
|
|
2574
2788
|
mesh.userData.flexId = f;
|
|
2575
2789
|
mesh.userData.vertAdr = vertAdr;
|
|
2576
2790
|
mesh.userData.vertNum = vertNum;
|
|
@@ -2605,22 +2819,45 @@ function FlexRenderer() {
|
|
|
2605
2819
|
if (status !== "ready") return null;
|
|
2606
2820
|
return /* @__PURE__ */ jsx("group", { ref: groupRef });
|
|
2607
2821
|
}
|
|
2822
|
+
var geomNameCacheByModel = /* @__PURE__ */ new WeakMap();
|
|
2823
|
+
function getGeomNameCached(model, geomId) {
|
|
2824
|
+
let perModel = geomNameCacheByModel.get(model);
|
|
2825
|
+
if (!perModel) {
|
|
2826
|
+
perModel = /* @__PURE__ */ new Map();
|
|
2827
|
+
geomNameCacheByModel.set(model, perModel);
|
|
2828
|
+
}
|
|
2829
|
+
let name = perModel.get(geomId);
|
|
2830
|
+
if (name === void 0) {
|
|
2831
|
+
name = getName(model, model.name_geomadr[geomId]);
|
|
2832
|
+
perModel.set(geomId, name);
|
|
2833
|
+
}
|
|
2834
|
+
return name;
|
|
2835
|
+
}
|
|
2608
2836
|
function useContacts(bodyName, callback) {
|
|
2609
|
-
const { mjModelRef } = useMujocoSim();
|
|
2837
|
+
const { mjModelRef, status } = useMujocoSim();
|
|
2610
2838
|
const contactsRef = useRef([]);
|
|
2611
2839
|
const bodyIdRef = useRef(-1);
|
|
2840
|
+
const bodyResolvedRef = useRef(false);
|
|
2612
2841
|
const callbackRef = useRef(callback);
|
|
2613
2842
|
callbackRef.current = callback;
|
|
2614
2843
|
useEffect(() => {
|
|
2615
2844
|
if (!bodyName) {
|
|
2616
2845
|
bodyIdRef.current = -1;
|
|
2846
|
+
bodyResolvedRef.current = true;
|
|
2617
2847
|
return;
|
|
2618
2848
|
}
|
|
2849
|
+
bodyResolvedRef.current = false;
|
|
2850
|
+
if (status !== "ready") return;
|
|
2619
2851
|
const model = mjModelRef.current;
|
|
2620
2852
|
if (!model) return;
|
|
2621
2853
|
bodyIdRef.current = findBodyByName(model, bodyName);
|
|
2622
|
-
|
|
2854
|
+
bodyResolvedRef.current = true;
|
|
2855
|
+
}, [bodyName, status, mjModelRef]);
|
|
2623
2856
|
useAfterPhysicsStep((model, data) => {
|
|
2857
|
+
if (bodyName && !bodyResolvedRef.current) {
|
|
2858
|
+
bodyIdRef.current = findBodyByName(model, bodyName);
|
|
2859
|
+
bodyResolvedRef.current = true;
|
|
2860
|
+
}
|
|
2624
2861
|
const ncon = data.ncon;
|
|
2625
2862
|
if (ncon === 0) {
|
|
2626
2863
|
if (contactsRef.current.length > 0) contactsRef.current = [];
|
|
@@ -2630,24 +2867,21 @@ function useContacts(bodyName, callback) {
|
|
|
2630
2867
|
const contacts = [];
|
|
2631
2868
|
const filterBody = bodyIdRef.current;
|
|
2632
2869
|
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
|
-
}
|
|
2870
|
+
const c = getContact(data, i);
|
|
2871
|
+
if (!c) break;
|
|
2872
|
+
if (filterBody >= 0) {
|
|
2873
|
+
const b1 = model.geom_bodyid[c.geom1];
|
|
2874
|
+
const b2 = model.geom_bodyid[c.geom2];
|
|
2875
|
+
if (b1 !== filterBody && b2 !== filterBody) continue;
|
|
2876
|
+
}
|
|
2877
|
+
contacts.push({
|
|
2878
|
+
geom1: c.geom1,
|
|
2879
|
+
geom1Name: getGeomNameCached(model, c.geom1),
|
|
2880
|
+
geom2: c.geom2,
|
|
2881
|
+
geom2Name: getGeomNameCached(model, c.geom2),
|
|
2882
|
+
pos: [c.pos[0], c.pos[1], c.pos[2]],
|
|
2883
|
+
depth: c.dist
|
|
2884
|
+
});
|
|
2651
2885
|
}
|
|
2652
2886
|
contactsRef.current = contacts;
|
|
2653
2887
|
callbackRef.current?.(contacts);
|
|
@@ -2781,28 +3015,28 @@ function TrajectoryPlayer({
|
|
|
2781
3015
|
onFrame
|
|
2782
3016
|
}) {
|
|
2783
3017
|
const player = useTrajectoryPlayer(trajectory, { fps, loop });
|
|
3018
|
+
const onFrameRef = useRef(onFrame);
|
|
3019
|
+
onFrameRef.current = onFrame;
|
|
3020
|
+
const lastReportedFrameRef = useRef(-1);
|
|
2784
3021
|
useEffect(() => {
|
|
2785
3022
|
if (playing) {
|
|
2786
3023
|
player.play();
|
|
2787
3024
|
} else {
|
|
2788
3025
|
player.pause();
|
|
2789
3026
|
}
|
|
2790
|
-
}, [playing]);
|
|
2791
|
-
|
|
2792
|
-
if (
|
|
2793
|
-
|
|
2794
|
-
|
|
2795
|
-
|
|
2796
|
-
|
|
3027
|
+
}, [playing, player]);
|
|
3028
|
+
useFrame(() => {
|
|
3029
|
+
if (!onFrameRef.current) return;
|
|
3030
|
+
const currentFrame = player.frame;
|
|
3031
|
+
if (currentFrame !== lastReportedFrameRef.current && player.playing) {
|
|
3032
|
+
lastReportedFrameRef.current = currentFrame;
|
|
3033
|
+
onFrameRef.current(currentFrame);
|
|
2797
3034
|
}
|
|
2798
|
-
}
|
|
3035
|
+
});
|
|
2799
3036
|
return null;
|
|
2800
3037
|
}
|
|
2801
|
-
function
|
|
2802
|
-
|
|
2803
|
-
color = "#ff4444",
|
|
2804
|
-
emissiveIntensity = 0.3
|
|
2805
|
-
}) {
|
|
3038
|
+
function useSelectionHighlight(bodyId, options = {}) {
|
|
3039
|
+
const { color = "#ff4444", emissiveIntensity = 0.3 } = options;
|
|
2806
3040
|
const { scene } = useThree();
|
|
2807
3041
|
const prevMeshesRef = useRef([]);
|
|
2808
3042
|
useEffect(() => {
|
|
@@ -2815,7 +3049,7 @@ function SelectionHighlight({
|
|
|
2815
3049
|
}
|
|
2816
3050
|
prevMeshesRef.current = [];
|
|
2817
3051
|
if (bodyId === null || bodyId < 0) return;
|
|
2818
|
-
const highlightColor = new
|
|
3052
|
+
const highlightColor = new THREE11.Color(color);
|
|
2819
3053
|
scene.traverse((obj) => {
|
|
2820
3054
|
if (obj.userData.bodyID === bodyId && obj.isMesh) {
|
|
2821
3055
|
const mesh = obj;
|
|
@@ -2842,6 +3076,15 @@ function SelectionHighlight({
|
|
|
2842
3076
|
prevMeshesRef.current = [];
|
|
2843
3077
|
};
|
|
2844
3078
|
}, [bodyId, color, emissiveIntensity, scene]);
|
|
3079
|
+
}
|
|
3080
|
+
|
|
3081
|
+
// src/components/SelectionHighlight.tsx
|
|
3082
|
+
function SelectionHighlight({
|
|
3083
|
+
bodyId,
|
|
3084
|
+
color = "#ff4444",
|
|
3085
|
+
emissiveIntensity = 0.3
|
|
3086
|
+
}) {
|
|
3087
|
+
useSelectionHighlight(bodyId, { color, emissiveIntensity });
|
|
2845
3088
|
return null;
|
|
2846
3089
|
}
|
|
2847
3090
|
function useActuators() {
|
|
@@ -2862,12 +3105,12 @@ function useActuators() {
|
|
|
2862
3105
|
return actuators;
|
|
2863
3106
|
}, [status, mjModelRef]);
|
|
2864
3107
|
}
|
|
2865
|
-
var _mat42 = new
|
|
3108
|
+
var _mat42 = new THREE11.Matrix4();
|
|
2866
3109
|
function useSitePosition(siteName) {
|
|
2867
3110
|
const { mjModelRef, mjDataRef, status } = useMujocoSim();
|
|
2868
3111
|
const siteIdRef = useRef(-1);
|
|
2869
|
-
const positionRef = useRef(new
|
|
2870
|
-
const quaternionRef = useRef(new
|
|
3112
|
+
const positionRef = useRef(new THREE11.Vector3());
|
|
3113
|
+
const quaternionRef = useRef(new THREE11.Quaternion());
|
|
2871
3114
|
useEffect(() => {
|
|
2872
3115
|
const model = mjModelRef.current;
|
|
2873
3116
|
if (!model || status !== "ready") {
|
|
@@ -2996,6 +3239,8 @@ function useJointState(name) {
|
|
|
2996
3239
|
const dofDimRef = useRef(1);
|
|
2997
3240
|
const positionRef = useRef(0);
|
|
2998
3241
|
const velocityRef = useRef(0);
|
|
3242
|
+
const posBufferRef = useRef(null);
|
|
3243
|
+
const velBufferRef = useRef(null);
|
|
2999
3244
|
useEffect(() => {
|
|
3000
3245
|
const model = mjModelRef.current;
|
|
3001
3246
|
if (!model || status !== "ready") return;
|
|
@@ -3015,6 +3260,13 @@ function useJointState(name) {
|
|
|
3015
3260
|
qposDimRef.current = 1;
|
|
3016
3261
|
dofDimRef.current = 1;
|
|
3017
3262
|
}
|
|
3263
|
+
if (qposDimRef.current > 1) {
|
|
3264
|
+
posBufferRef.current = new Float64Array(qposDimRef.current);
|
|
3265
|
+
velBufferRef.current = new Float64Array(dofDimRef.current);
|
|
3266
|
+
} else {
|
|
3267
|
+
posBufferRef.current = null;
|
|
3268
|
+
velBufferRef.current = null;
|
|
3269
|
+
}
|
|
3018
3270
|
return;
|
|
3019
3271
|
}
|
|
3020
3272
|
}
|
|
@@ -3028,8 +3280,12 @@ function useJointState(name) {
|
|
|
3028
3280
|
positionRef.current = data.qpos[qa];
|
|
3029
3281
|
velocityRef.current = data.qvel[da];
|
|
3030
3282
|
} else {
|
|
3031
|
-
|
|
3032
|
-
|
|
3283
|
+
const posBuf = posBufferRef.current;
|
|
3284
|
+
const velBuf = velBufferRef.current;
|
|
3285
|
+
posBuf.set(data.qpos.subarray(qa, qa + qposDimRef.current));
|
|
3286
|
+
velBuf.set(data.qvel.subarray(da, da + dofDimRef.current));
|
|
3287
|
+
positionRef.current = posBuf;
|
|
3288
|
+
velocityRef.current = velBuf;
|
|
3033
3289
|
}
|
|
3034
3290
|
});
|
|
3035
3291
|
return { position: positionRef, velocity: velocityRef };
|
|
@@ -3037,10 +3293,10 @@ function useJointState(name) {
|
|
|
3037
3293
|
function useBodyState(name) {
|
|
3038
3294
|
const { mjModelRef, status } = useMujocoSim();
|
|
3039
3295
|
const bodyIdRef = useRef(-1);
|
|
3040
|
-
const position = useRef(new
|
|
3041
|
-
const quaternion = useRef(new
|
|
3042
|
-
const linearVelocity = useRef(new
|
|
3043
|
-
const angularVelocity = useRef(new
|
|
3296
|
+
const position = useRef(new THREE11.Vector3());
|
|
3297
|
+
const quaternion = useRef(new THREE11.Quaternion());
|
|
3298
|
+
const linearVelocity = useRef(new THREE11.Vector3());
|
|
3299
|
+
const angularVelocity = useRef(new THREE11.Vector3());
|
|
3044
3300
|
useEffect(() => {
|
|
3045
3301
|
const model = mjModelRef.current;
|
|
3046
3302
|
if (!model || status !== "ready") return;
|
|
@@ -3377,10 +3633,97 @@ function useCtrlNoise(config = {}) {
|
|
|
3377
3633
|
}
|
|
3378
3634
|
});
|
|
3379
3635
|
}
|
|
3636
|
+
function useCameraAnimation() {
|
|
3637
|
+
const { camera } = useThree();
|
|
3638
|
+
const orbitTargetRef = useRef(new THREE11.Vector3(0, 0, 0));
|
|
3639
|
+
const cameraAnimRef = useRef({
|
|
3640
|
+
active: false,
|
|
3641
|
+
startPos: new THREE11.Vector3(),
|
|
3642
|
+
endPos: new THREE11.Vector3(),
|
|
3643
|
+
startRot: new THREE11.Quaternion(),
|
|
3644
|
+
endRot: new THREE11.Quaternion(),
|
|
3645
|
+
startTarget: new THREE11.Vector3(),
|
|
3646
|
+
endTarget: new THREE11.Vector3(),
|
|
3647
|
+
startTime: 0,
|
|
3648
|
+
duration: 0,
|
|
3649
|
+
resolve: null
|
|
3650
|
+
});
|
|
3651
|
+
useFrame((state) => {
|
|
3652
|
+
const ca = cameraAnimRef.current;
|
|
3653
|
+
if (!ca.active) return;
|
|
3654
|
+
const now = performance.now();
|
|
3655
|
+
const progress = Math.min((now - ca.startTime) / ca.duration, 1);
|
|
3656
|
+
const ease = progress < 0.5 ? 4 * progress * progress * progress : 1 - Math.pow(-2 * progress + 2, 3) / 2;
|
|
3657
|
+
camera.position.lerpVectors(ca.startPos, ca.endPos, ease);
|
|
3658
|
+
camera.quaternion.slerpQuaternions(ca.startRot, ca.endRot, ease);
|
|
3659
|
+
orbitTargetRef.current.lerpVectors(ca.startTarget, ca.endTarget, ease);
|
|
3660
|
+
const orbitControls = state.controls;
|
|
3661
|
+
if (orbitControls?.target) {
|
|
3662
|
+
orbitControls.target.copy(orbitTargetRef.current);
|
|
3663
|
+
}
|
|
3664
|
+
if (progress >= 1) {
|
|
3665
|
+
ca.active = false;
|
|
3666
|
+
camera.position.copy(ca.endPos);
|
|
3667
|
+
camera.quaternion.copy(ca.endRot);
|
|
3668
|
+
orbitTargetRef.current.copy(ca.endTarget);
|
|
3669
|
+
ca.resolve?.();
|
|
3670
|
+
ca.resolve = null;
|
|
3671
|
+
}
|
|
3672
|
+
});
|
|
3673
|
+
const getCameraState = useCallback(
|
|
3674
|
+
() => ({
|
|
3675
|
+
position: camera.position.clone(),
|
|
3676
|
+
target: orbitTargetRef.current.clone()
|
|
3677
|
+
}),
|
|
3678
|
+
[camera]
|
|
3679
|
+
);
|
|
3680
|
+
const moveCameraTo = useCallback(
|
|
3681
|
+
(position, target, durationMs) => {
|
|
3682
|
+
return new Promise((resolve) => {
|
|
3683
|
+
const ca = cameraAnimRef.current;
|
|
3684
|
+
ca.active = true;
|
|
3685
|
+
ca.startTime = performance.now();
|
|
3686
|
+
ca.duration = durationMs;
|
|
3687
|
+
ca.startPos.copy(camera.position);
|
|
3688
|
+
ca.startRot.copy(camera.quaternion);
|
|
3689
|
+
ca.startTarget.copy(orbitTargetRef.current);
|
|
3690
|
+
ca.endPos.copy(position);
|
|
3691
|
+
ca.endTarget.copy(target);
|
|
3692
|
+
const dummyCam = camera.clone();
|
|
3693
|
+
dummyCam.position.copy(position);
|
|
3694
|
+
dummyCam.lookAt(target);
|
|
3695
|
+
ca.endRot.copy(dummyCam.quaternion);
|
|
3696
|
+
ca.resolve = resolve;
|
|
3697
|
+
setTimeout(resolve, durationMs + 100);
|
|
3698
|
+
});
|
|
3699
|
+
},
|
|
3700
|
+
[camera]
|
|
3701
|
+
);
|
|
3702
|
+
return { getCameraState, moveCameraTo };
|
|
3703
|
+
}
|
|
3380
3704
|
/**
|
|
3381
3705
|
* @license
|
|
3382
3706
|
* SPDX-License-Identifier: Apache-2.0
|
|
3383
3707
|
*/
|
|
3708
|
+
/**
|
|
3709
|
+
* @license
|
|
3710
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
3711
|
+
*
|
|
3712
|
+
* createController — typed factory for BYOC (Bring Your Own Controller) plugins.
|
|
3713
|
+
*/
|
|
3714
|
+
/**
|
|
3715
|
+
* @license
|
|
3716
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
3717
|
+
*
|
|
3718
|
+
* IkContext — React context for the IK controller plugin.
|
|
3719
|
+
*/
|
|
3720
|
+
/**
|
|
3721
|
+
* @license
|
|
3722
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
3723
|
+
*
|
|
3724
|
+
* IkController — composable IK controller plugin.
|
|
3725
|
+
* Extracts all IK logic from MujocoSimProvider into an opt-in component.
|
|
3726
|
+
*/
|
|
3384
3727
|
/**
|
|
3385
3728
|
* @license
|
|
3386
3729
|
* SPDX-License-Identifier: Apache-2.0
|
|
@@ -3394,6 +3737,14 @@ function useCtrlNoise(config = {}) {
|
|
|
3394
3737
|
* Fixed from original: reads data.ncon first, accesses contact via .get(i),
|
|
3395
3738
|
* limits to maxContacts to avoid WASM heap OOM.
|
|
3396
3739
|
*/
|
|
3740
|
+
/**
|
|
3741
|
+
* @license
|
|
3742
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
3743
|
+
*
|
|
3744
|
+
* useSceneLights — hook form of SceneLights (spec 6.3)
|
|
3745
|
+
*
|
|
3746
|
+
* Auto-creates Three.js lights from MJCF <light> elements.
|
|
3747
|
+
*/
|
|
3397
3748
|
/**
|
|
3398
3749
|
* @license
|
|
3399
3750
|
* SPDX-License-Identifier: Apache-2.0
|
|
@@ -3456,6 +3807,15 @@ function useCtrlNoise(config = {}) {
|
|
|
3456
3807
|
*
|
|
3457
3808
|
* TrajectoryPlayer — component form of trajectory playback (spec 13.2)
|
|
3458
3809
|
*/
|
|
3810
|
+
/**
|
|
3811
|
+
* @license
|
|
3812
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
3813
|
+
*
|
|
3814
|
+
* useSelectionHighlight — hook form of SelectionHighlight (spec 6.5)
|
|
3815
|
+
*
|
|
3816
|
+
* Applies emissive highlight to all meshes belonging to a body.
|
|
3817
|
+
* Restores original emissive when bodyId changes or hook unmounts.
|
|
3818
|
+
*/
|
|
3459
3819
|
/**
|
|
3460
3820
|
* @license
|
|
3461
3821
|
* SPDX-License-Identifier: Apache-2.0
|
|
@@ -3522,7 +3882,13 @@ function useCtrlNoise(config = {}) {
|
|
|
3522
3882
|
*
|
|
3523
3883
|
* useCtrlNoise — control noise / perturbation hook (spec 3.2)
|
|
3524
3884
|
*/
|
|
3885
|
+
/**
|
|
3886
|
+
* @license
|
|
3887
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
3888
|
+
*
|
|
3889
|
+
* useCameraAnimation — composable camera animation hook.
|
|
3890
|
+
*/
|
|
3525
3891
|
|
|
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 };
|
|
3892
|
+
export { ContactListener, ContactMarkers, Debug, DragInteraction, FlexRenderer, IkController, IkGizmo, MujocoCanvas, 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
3893
|
//# sourceMappingURL=index.js.map
|
|
3528
3894
|
//# sourceMappingURL=index.js.map
|