mujoco-react 6.0.1 → 7.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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
- );
@@ -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
- }