mujoco-react 6.0.1 → 7.0.1
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 +80 -45
- package/dist/index.d.ts +88 -57
- package/dist/index.js +372 -243
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/components/Body.tsx +102 -0
- package/src/components/ContactMarkers.tsx +2 -2
- package/src/components/Debug.tsx +2 -2
- package/src/components/DragInteraction.tsx +2 -2
- package/src/components/FlexRenderer.tsx +2 -2
- package/src/components/IkGizmo.tsx +7 -11
- package/src/components/SceneRenderer.tsx +9 -6
- package/src/components/TendonRenderer.tsx +2 -2
- package/src/core/MujocoSimProvider.tsx +87 -6
- package/src/core/createController.tsx +54 -2
- package/src/hooks/useActuators.ts +2 -2
- package/src/hooks/useBodyState.ts +2 -2
- package/src/hooks/useContacts.ts +2 -2
- package/src/hooks/useCtrl.ts +2 -2
- package/src/hooks/useCtrlNoise.ts +2 -2
- package/src/hooks/useGamepad.ts +2 -2
- package/src/hooks/useIkController.ts +242 -0
- package/src/hooks/useJointState.ts +2 -2
- package/src/hooks/useKeyboardTeleop.ts +2 -2
- package/src/hooks/usePolicy.ts +2 -2
- package/src/hooks/useSceneLights.ts +2 -2
- package/src/hooks/useSensor.ts +3 -3
- package/src/hooks/useSitePosition.ts +2 -2
- package/src/hooks/useTrajectoryPlayer.ts +2 -2
- package/src/hooks/useTrajectoryRecorder.ts +2 -2
- package/src/index.ts +5 -4
- package/src/types.ts +30 -0
- package/src/components/IkController.tsx +0 -262
- package/src/core/IkContext.tsx +0 -40
|
@@ -1,262 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @license
|
|
3
|
-
* SPDX-License-Identifier: Apache-2.0
|
|
4
|
-
*
|
|
5
|
-
* IkController — composable IK controller plugin.
|
|
6
|
-
* Extracts all IK logic from MujocoSimProvider into an opt-in component.
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
import { useCallback, useEffect, useMemo, useRef } from 'react';
|
|
10
|
-
import { useFrame } from '@react-three/fiber';
|
|
11
|
-
import * as THREE from 'three';
|
|
12
|
-
import { createController } from '../core/createController';
|
|
13
|
-
import { IkContext, type IkContextValue } from '../core/IkContext';
|
|
14
|
-
import { useMujoco, useBeforePhysicsStep } from '../core/MujocoSimProvider';
|
|
15
|
-
import { GenericIK } from '../core/GenericIK';
|
|
16
|
-
import { findSiteByName } from '../core/SceneLoader';
|
|
17
|
-
import type { IkConfig, IKSolveFn, MujocoData } from '../types';
|
|
18
|
-
|
|
19
|
-
// Preallocated temp for syncGizmoToSite
|
|
20
|
-
const _syncMat4 = new THREE.Matrix4();
|
|
21
|
-
|
|
22
|
-
function syncGizmoToSite(data: MujocoData, siteId: number, target: THREE.Group) {
|
|
23
|
-
if (siteId === -1) return;
|
|
24
|
-
const sitePos = data.site_xpos.subarray(siteId * 3, siteId * 3 + 3);
|
|
25
|
-
const siteMat = data.site_xmat.subarray(siteId * 9, siteId * 9 + 9);
|
|
26
|
-
target.position.set(sitePos[0], sitePos[1], sitePos[2]);
|
|
27
|
-
_syncMat4.set(
|
|
28
|
-
siteMat[0], siteMat[1], siteMat[2], 0,
|
|
29
|
-
siteMat[3], siteMat[4], siteMat[5], 0,
|
|
30
|
-
siteMat[6], siteMat[7], siteMat[8], 0,
|
|
31
|
-
0, 0, 0, 1,
|
|
32
|
-
);
|
|
33
|
-
target.quaternion.setFromRotationMatrix(_syncMat4);
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
function IkControllerImpl({
|
|
37
|
-
config,
|
|
38
|
-
children,
|
|
39
|
-
}: {
|
|
40
|
-
config: IkConfig;
|
|
41
|
-
children?: React.ReactNode;
|
|
42
|
-
}) {
|
|
43
|
-
const { mjModelRef, mjDataRef, mujocoRef, configRef, resetCallbacks, status } =
|
|
44
|
-
useMujoco();
|
|
45
|
-
|
|
46
|
-
// All IK state lives here, NOT in the provider
|
|
47
|
-
const ikEnabledRef = useRef(false);
|
|
48
|
-
const ikCalculatingRef = useRef(false);
|
|
49
|
-
const ikTargetRef = useRef<THREE.Group>(new THREE.Group());
|
|
50
|
-
const siteIdRef = useRef(-1);
|
|
51
|
-
const genericIkRef = useRef<GenericIK>(new GenericIK(mujocoRef.current));
|
|
52
|
-
const firstIkEnableRef = useRef(true);
|
|
53
|
-
|
|
54
|
-
const needsInitialSync = useRef(true);
|
|
55
|
-
|
|
56
|
-
const gizmoAnimRef = useRef({
|
|
57
|
-
active: false,
|
|
58
|
-
startPos: new THREE.Vector3(),
|
|
59
|
-
endPos: new THREE.Vector3(),
|
|
60
|
-
startRot: new THREE.Quaternion(),
|
|
61
|
-
endRot: new THREE.Quaternion(),
|
|
62
|
-
startTime: 0,
|
|
63
|
-
duration: 1000,
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
// Resolve site ID when model loads or config changes
|
|
67
|
-
useEffect(() => {
|
|
68
|
-
const model = mjModelRef.current;
|
|
69
|
-
if (!model || status !== 'ready') {
|
|
70
|
-
siteIdRef.current = -1;
|
|
71
|
-
return;
|
|
72
|
-
}
|
|
73
|
-
siteIdRef.current = findSiteByName(model, config.siteName);
|
|
74
|
-
const data = mjDataRef.current;
|
|
75
|
-
if (data && ikTargetRef.current) {
|
|
76
|
-
syncGizmoToSite(data, siteIdRef.current, ikTargetRef.current);
|
|
77
|
-
}
|
|
78
|
-
}, [config.siteName, status, mjModelRef, mjDataRef]);
|
|
79
|
-
|
|
80
|
-
// IK solve function — use custom solver if provided, otherwise built-in GenericIK
|
|
81
|
-
const ikSolveFn = useCallback(
|
|
82
|
-
(pos: THREE.Vector3, quat: THREE.Quaternion, currentQ: number[]): number[] | null => {
|
|
83
|
-
if (config.ikSolveFn) return config.ikSolveFn(pos, quat, currentQ);
|
|
84
|
-
const model = mjModelRef.current;
|
|
85
|
-
const data = mjDataRef.current;
|
|
86
|
-
if (!model || !data || siteIdRef.current === -1) return null;
|
|
87
|
-
return genericIkRef.current.solve(
|
|
88
|
-
model,
|
|
89
|
-
data,
|
|
90
|
-
siteIdRef.current,
|
|
91
|
-
config.numJoints,
|
|
92
|
-
pos,
|
|
93
|
-
quat,
|
|
94
|
-
currentQ,
|
|
95
|
-
{
|
|
96
|
-
damping: config.damping,
|
|
97
|
-
maxIterations: config.maxIterations,
|
|
98
|
-
},
|
|
99
|
-
);
|
|
100
|
-
},
|
|
101
|
-
[config.ikSolveFn, config.numJoints, config.damping, config.maxIterations, mjModelRef, mjDataRef],
|
|
102
|
-
);
|
|
103
|
-
const ikSolveFnRef = useRef<IKSolveFn>(ikSolveFn);
|
|
104
|
-
ikSolveFnRef.current = ikSolveFn;
|
|
105
|
-
|
|
106
|
-
// Gizmo animation + one-time initial sync in useFrame
|
|
107
|
-
useFrame(() => {
|
|
108
|
-
// Ensure the gizmo is positioned at the site after the first physics step
|
|
109
|
-
if (needsInitialSync.current && siteIdRef.current !== -1) {
|
|
110
|
-
const data = mjDataRef.current;
|
|
111
|
-
if (data && ikTargetRef.current) {
|
|
112
|
-
syncGizmoToSite(data, siteIdRef.current, ikTargetRef.current);
|
|
113
|
-
needsInitialSync.current = false;
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
const ga = gizmoAnimRef.current;
|
|
118
|
-
const target = ikTargetRef.current;
|
|
119
|
-
if (!ga.active || !target) return;
|
|
120
|
-
|
|
121
|
-
const now = performance.now();
|
|
122
|
-
const elapsed = now - ga.startTime;
|
|
123
|
-
const t = Math.min(elapsed / ga.duration, 1.0);
|
|
124
|
-
const ease = 1 - Math.pow(1 - t, 3);
|
|
125
|
-
target.position.lerpVectors(ga.startPos, ga.endPos, ease);
|
|
126
|
-
target.quaternion.slerpQuaternions(ga.startRot, ga.endRot, ease);
|
|
127
|
-
if (t >= 1.0) ga.active = false;
|
|
128
|
-
});
|
|
129
|
-
|
|
130
|
-
// IK solve in physics loop
|
|
131
|
-
useBeforePhysicsStep((model, data) => {
|
|
132
|
-
if (!ikEnabledRef.current) {
|
|
133
|
-
ikCalculatingRef.current = false;
|
|
134
|
-
return;
|
|
135
|
-
}
|
|
136
|
-
const target = ikTargetRef.current;
|
|
137
|
-
if (!target) return;
|
|
138
|
-
|
|
139
|
-
ikCalculatingRef.current = true;
|
|
140
|
-
const numJoints = config.numJoints;
|
|
141
|
-
const currentQ: number[] = [];
|
|
142
|
-
for (let i = 0; i < numJoints; i++) currentQ.push(data.qpos[i]);
|
|
143
|
-
const solution = ikSolveFnRef.current(target.position, target.quaternion, currentQ);
|
|
144
|
-
if (solution) {
|
|
145
|
-
for (let i = 0; i < numJoints; i++) data.ctrl[i] = solution[i];
|
|
146
|
-
}
|
|
147
|
-
});
|
|
148
|
-
|
|
149
|
-
// Reset callback — sync gizmo and reset IK state
|
|
150
|
-
useEffect(() => {
|
|
151
|
-
const cb = () => {
|
|
152
|
-
const data = mjDataRef.current;
|
|
153
|
-
if (data && ikTargetRef.current) {
|
|
154
|
-
syncGizmoToSite(data, siteIdRef.current, ikTargetRef.current);
|
|
155
|
-
}
|
|
156
|
-
gizmoAnimRef.current.active = false;
|
|
157
|
-
firstIkEnableRef.current = true;
|
|
158
|
-
ikEnabledRef.current = false;
|
|
159
|
-
needsInitialSync.current = true;
|
|
160
|
-
};
|
|
161
|
-
resetCallbacks.current.add(cb);
|
|
162
|
-
return () => {
|
|
163
|
-
resetCallbacks.current.delete(cb);
|
|
164
|
-
};
|
|
165
|
-
}, [resetCallbacks, mjDataRef]);
|
|
166
|
-
|
|
167
|
-
// --- API methods ---
|
|
168
|
-
|
|
169
|
-
const setIkEnabled = useCallback(
|
|
170
|
-
(enabled: boolean) => {
|
|
171
|
-
ikEnabledRef.current = enabled;
|
|
172
|
-
const data = mjDataRef.current;
|
|
173
|
-
if (enabled && data && !gizmoAnimRef.current.active && ikTargetRef.current) {
|
|
174
|
-
syncGizmoToSite(data, siteIdRef.current, ikTargetRef.current);
|
|
175
|
-
firstIkEnableRef.current = false;
|
|
176
|
-
}
|
|
177
|
-
},
|
|
178
|
-
[mjDataRef],
|
|
179
|
-
);
|
|
180
|
-
|
|
181
|
-
const syncTargetToSiteApi = useCallback(() => {
|
|
182
|
-
const data = mjDataRef.current;
|
|
183
|
-
const target = ikTargetRef.current;
|
|
184
|
-
if (data && target) syncGizmoToSite(data, siteIdRef.current, target);
|
|
185
|
-
}, [mjDataRef]);
|
|
186
|
-
|
|
187
|
-
const solveIK = useCallback(
|
|
188
|
-
(pos: THREE.Vector3, quat: THREE.Quaternion, currentQ: number[]): number[] | null => {
|
|
189
|
-
return ikSolveFnRef.current(pos, quat, currentQ);
|
|
190
|
-
},
|
|
191
|
-
[],
|
|
192
|
-
);
|
|
193
|
-
|
|
194
|
-
const moveTarget = useCallback(
|
|
195
|
-
(pos: THREE.Vector3, duration = 0) => {
|
|
196
|
-
if (!ikEnabledRef.current) setIkEnabled(true);
|
|
197
|
-
const target = ikTargetRef.current;
|
|
198
|
-
if (!target) return;
|
|
199
|
-
|
|
200
|
-
const targetPos = pos.clone();
|
|
201
|
-
const targetRot = new THREE.Quaternion().setFromEuler(
|
|
202
|
-
new THREE.Euler(Math.PI, 0, 0),
|
|
203
|
-
);
|
|
204
|
-
|
|
205
|
-
if (duration > 0) {
|
|
206
|
-
const ga = gizmoAnimRef.current;
|
|
207
|
-
ga.active = true;
|
|
208
|
-
ga.startPos.copy(target.position);
|
|
209
|
-
ga.endPos.copy(targetPos);
|
|
210
|
-
ga.startRot.copy(target.quaternion);
|
|
211
|
-
ga.endRot.copy(targetRot);
|
|
212
|
-
ga.startTime = performance.now();
|
|
213
|
-
ga.duration = duration;
|
|
214
|
-
} else {
|
|
215
|
-
gizmoAnimRef.current.active = false;
|
|
216
|
-
target.position.copy(targetPos);
|
|
217
|
-
target.quaternion.copy(targetRot);
|
|
218
|
-
}
|
|
219
|
-
},
|
|
220
|
-
[setIkEnabled],
|
|
221
|
-
);
|
|
222
|
-
|
|
223
|
-
const getGizmoStats = useCallback(
|
|
224
|
-
(): { pos: THREE.Vector3; rot: THREE.Euler } | null => {
|
|
225
|
-
const target = ikTargetRef.current;
|
|
226
|
-
if (!ikCalculatingRef.current || !target) return null;
|
|
227
|
-
return {
|
|
228
|
-
pos: target.position.clone(),
|
|
229
|
-
rot: new THREE.Euler().setFromQuaternion(target.quaternion),
|
|
230
|
-
};
|
|
231
|
-
},
|
|
232
|
-
[],
|
|
233
|
-
);
|
|
234
|
-
|
|
235
|
-
const contextValue = useMemo<IkContextValue>(
|
|
236
|
-
() => ({
|
|
237
|
-
ikEnabledRef,
|
|
238
|
-
ikCalculatingRef,
|
|
239
|
-
ikTargetRef,
|
|
240
|
-
siteIdRef,
|
|
241
|
-
setIkEnabled,
|
|
242
|
-
moveTarget,
|
|
243
|
-
syncTargetToSite: syncTargetToSiteApi,
|
|
244
|
-
solveIK,
|
|
245
|
-
getGizmoStats,
|
|
246
|
-
}),
|
|
247
|
-
[setIkEnabled, moveTarget, syncTargetToSiteApi, solveIK, getGizmoStats],
|
|
248
|
-
);
|
|
249
|
-
|
|
250
|
-
return <IkContext.Provider value={contextValue}>{children}</IkContext.Provider>;
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
export const IkController = createController<IkConfig>(
|
|
254
|
-
{
|
|
255
|
-
name: 'IkController',
|
|
256
|
-
defaultConfig: {
|
|
257
|
-
damping: 0.01,
|
|
258
|
-
maxIterations: 50,
|
|
259
|
-
},
|
|
260
|
-
},
|
|
261
|
-
IkControllerImpl,
|
|
262
|
-
);
|
package/src/core/IkContext.tsx
DELETED
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @license
|
|
3
|
-
* SPDX-License-Identifier: Apache-2.0
|
|
4
|
-
*
|
|
5
|
-
* IkContext — React context for the IK controller plugin.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import { createContext, useContext } from 'react';
|
|
9
|
-
import * as THREE from 'three';
|
|
10
|
-
|
|
11
|
-
export interface IkContextValue {
|
|
12
|
-
ikEnabledRef: React.RefObject<boolean>;
|
|
13
|
-
ikCalculatingRef: React.RefObject<boolean>;
|
|
14
|
-
ikTargetRef: React.RefObject<THREE.Group>;
|
|
15
|
-
siteIdRef: React.RefObject<number>;
|
|
16
|
-
setIkEnabled(enabled: boolean): void;
|
|
17
|
-
moveTarget(pos: THREE.Vector3, duration?: number): void;
|
|
18
|
-
syncTargetToSite(): void;
|
|
19
|
-
solveIK(pos: THREE.Vector3, quat: THREE.Quaternion, currentQ: number[]): number[] | null;
|
|
20
|
-
getGizmoStats(): { pos: THREE.Vector3; rot: THREE.Euler } | null;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
export const IkContext = createContext<IkContextValue | null>(null);
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* Access the IK controller context.
|
|
27
|
-
*
|
|
28
|
-
* - `useIk()` — throws if no `<IkController>` ancestor (use inside `<IkController>`)
|
|
29
|
-
* - `useIk({ optional: true })` — returns `null` if no ancestor (use in components
|
|
30
|
-
* that optionally interact with IK, e.g. keyboard controllers that disable IK)
|
|
31
|
-
*/
|
|
32
|
-
export function useIk(): IkContextValue;
|
|
33
|
-
export function useIk(options: { optional: true }): IkContextValue | null;
|
|
34
|
-
export function useIk(options?: { optional?: boolean }): IkContextValue | null {
|
|
35
|
-
const ctx = useContext(IkContext);
|
|
36
|
-
if (!ctx && !options?.optional) {
|
|
37
|
-
throw new Error('useIk() must be used inside an <IkController>');
|
|
38
|
-
}
|
|
39
|
-
return ctx;
|
|
40
|
-
}
|