mujoco-react 10.5.0 → 10.7.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/dist/onnx.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import * as ort from 'onnxruntime-web';
2
- import { bq as PolicyActionChunk } from './types-CViUme8D.js';
2
+ import { bq as PolicyActionChunk } from './types-Dvtm4I0o.js';
3
3
  import 'react';
4
4
  import '@react-three/fiber';
5
5
  import 'three';
package/dist/spark.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
2
  import * as _sparkjsdev_spark from '@sparkjsdev/spark';
3
- import { o as SplatEnvironmentProps, r as PairedSplatEnvironmentConfig, S as SceneConfig, u as SplatEnvironmentReadiness, p as VisualScenarioConfig } from './types-CViUme8D.js';
3
+ import { o as SplatEnvironmentProps, r as PairedSplatEnvironmentConfig, S as SceneConfig, u as SplatEnvironmentReadiness, p as VisualScenarioConfig } from './types-Dvtm4I0o.js';
4
4
  import 'react';
5
5
  import '@react-three/fiber';
6
6
  import 'three';
package/dist/spark.js CHANGED
@@ -1,4 +1,4 @@
1
- import { useSplatSceneConfig, useSplatEnvironment, SplatEnvironment, CAMERA_FRAME_CAPTURE_RENDER_USER_DATA_KEY } from './chunk-KHZ5U36J.js';
1
+ import { useSplatSceneConfig, useSplatEnvironment, SplatEnvironment, CAMERA_FRAME_CAPTURE_RENDER_USER_DATA_KEY } from './chunk-EN55TTGH.js';
2
2
  import { useThree } from '@react-three/fiber';
3
3
  import { useMemo, useState, useEffect, useCallback, useRef } from 'react';
4
4
  import * as THREE from 'three';
@@ -540,7 +540,7 @@ interface IkContextValue {
540
540
  ikTargetRef: React__default.RefObject<THREE.Group>;
541
541
  siteIdRef: React__default.RefObject<number>;
542
542
  setIkEnabled: (enabled: boolean) => void;
543
- moveTarget: (pos: THREE.Vector3, duration?: number) => void;
543
+ moveTarget: (pos: IkTargetPosition, duration?: number) => void;
544
544
  syncTargetToSite: () => void;
545
545
  solveIK: (input: IkSolveInput) => number[] | null;
546
546
  getGizmoStats: () => {
@@ -561,9 +561,20 @@ interface PhysicsConfig {
561
561
  speed?: number;
562
562
  }
563
563
  type IKSolveFn = (input: IkSolveInput) => number[] | null;
564
+ type IkTargetPosition = THREE.Vector3 | readonly [number, number, number] | {
565
+ readonly x: number;
566
+ readonly y: number;
567
+ readonly z: number;
568
+ };
569
+ type IkTargetQuaternion = THREE.Quaternion | readonly [number, number, number, number] | {
570
+ readonly x: number;
571
+ readonly y: number;
572
+ readonly z: number;
573
+ readonly w: number;
574
+ };
564
575
  interface IkSolveInput {
565
- position: THREE.Vector3;
566
- quaternion: THREE.Quaternion;
576
+ position: IkTargetPosition;
577
+ quaternion: IkTargetQuaternion;
567
578
  currentQ: number[];
568
579
  context?: IKSolveContext;
569
580
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mujoco-react",
3
- "version": "10.5.0",
3
+ "version": "10.7.0",
4
4
  "description": "Composable React Three Fiber building blocks for MuJoCo WASM simulations",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -40,13 +40,6 @@ function resolveOptions(opts?: Partial<GenericIKOptions>): ResolvedGenericIKOpti
40
40
  };
41
41
  }
42
42
 
43
- function clampJoint(value: number, limit: readonly [number, number] | null | undefined) {
44
- if (!limit) return value;
45
- const [min, max] = limit;
46
- if (!Number.isFinite(min) || !Number.isFinite(max) || min >= max) return value;
47
- return Math.max(min, Math.min(max, value));
48
- }
49
-
50
43
  /**
51
44
  * Generic Damped Least-Squares IK solver.
52
45
  * Uses finite-difference Jacobian via MuJoCo's mj_forward.
@@ -93,7 +86,7 @@ export class GenericIK {
93
86
 
94
87
  // Working joint angles — start from current configuration
95
88
  const q = new Float64Array(n);
96
- for (let i = 0; i < n; i++) q[i] = clampJoint(currentQ[i], o.jointLimits?.[i]);
89
+ for (let i = 0; i < n; i++) q[i] = currentQ[i];
97
90
 
98
91
  // Pre-allocate work arrays
99
92
  const J = new Float64Array(6 * n); // 6×n Jacobian (row-major)
@@ -108,6 +101,14 @@ export class GenericIK {
108
101
 
109
102
  let bestQ: number[] | null = null;
110
103
  let bestErr = Infinity;
104
+ // Stop early once the error stops improving. The solver always returns the
105
+ // best configuration it has seen, so when it stalls or diverges (an
106
+ // unreachable target, e.g. a near-singular wrist orientation) further
107
+ // iterations can't beat bestQ — they only burn (1 + n) mj_forward calls
108
+ // each and feed jitter into ctrl. This is what bounds the cost of dragging
109
+ // the target into an unreachable region.
110
+ const patience = 4;
111
+ let noImprove = 0;
111
112
 
112
113
  if (n === 0) return null;
113
114
 
@@ -145,14 +146,20 @@ export class GenericIK {
145
146
  );
146
147
 
147
148
  // Track best solution
148
- if (errNorm < bestErr) {
149
+ if (errNorm < bestErr - 1e-9) {
149
150
  bestErr = errNorm;
150
151
  bestQ = Array.from(q);
152
+ noImprove = 0;
153
+ } else {
154
+ noImprove++;
151
155
  }
152
156
 
153
157
  // Converged
154
158
  if (errNorm < o.tolerance) break;
155
159
 
160
+ // Stalled or diverging — bestQ can no longer improve, so stop.
161
+ if (noImprove >= patience) break;
162
+
156
163
  // Compute Jacobian via finite differences
157
164
  for (let j = 0; j < n; j++) {
158
165
  const adr = qposAdr[j];
@@ -208,7 +215,7 @@ export class GenericIK {
208
215
  }
209
216
 
210
217
  // Update joints
211
- for (let i = 0; i < n; i++) q[i] = clampJoint(q[i] + dq[i], o.jointLimits?.[i]);
218
+ for (let i = 0; i < n; i++) q[i] += dq[i];
212
219
  }
213
220
 
214
221
  // Restore original qpos
Binary file
@@ -10,11 +10,31 @@ import { createControllerHook } from '../core/createController';
10
10
  import { useMujocoContext, useBeforePhysicsStep } from '../core/MujocoSimProvider';
11
11
  import { GenericIK } from '../core/GenericIK';
12
12
  import { createContiguousControlGroup, findSiteByName, resolveControlGroup } from '../core/SceneLoader';
13
- import type { ControlGroupInfo, IkConfig, IkContextValue, IKSolveFn, IkSolveInput, MujocoData } from '../types';
13
+ import type {
14
+ ControlGroupInfo,
15
+ IkConfig,
16
+ IkContextValue,
17
+ IKSolveFn,
18
+ IkSolveInput,
19
+ IkTargetPosition,
20
+ MujocoData,
21
+ } from '../types';
14
22
 
15
23
  // Preallocated temp for syncGizmoToSite
16
24
  const _syncMat4 = new THREE.Matrix4();
17
25
 
26
+ function toIkVector3(value: IkSolveInput['position']): THREE.Vector3 {
27
+ return 'x' in value
28
+ ? new THREE.Vector3(value.x, value.y, value.z)
29
+ : new THREE.Vector3(value[0], value[1], value[2]);
30
+ }
31
+
32
+ function toIkQuaternion(value: IkSolveInput['quaternion']): THREE.Quaternion {
33
+ return 'x' in value
34
+ ? new THREE.Quaternion(value.x, value.y, value.z, value.w)
35
+ : new THREE.Quaternion(value[0], value[1], value[2], value[3]);
36
+ }
37
+
18
38
  function syncGizmoToSite(data: MujocoData, siteId: number, target: THREE.Group) {
19
39
  if (siteId === -1) return;
20
40
  const sitePos = data.site_xpos.subarray(siteId * 3, siteId * 3 + 3);
@@ -86,14 +106,22 @@ export const useIkController = createControllerHook<IkConfig, IkContextValue>(
86
106
  const ikSolveFn = useCallback(
87
107
  ({ position, quaternion, currentQ, context }: IkSolveInput): number[] | null => {
88
108
  if (!config) return null;
89
- if (config.ikSolveFn) return config.ikSolveFn({ position, quaternion, currentQ, context });
109
+ const targetPosition = toIkVector3(position);
110
+ const targetQuaternion = toIkQuaternion(quaternion);
111
+ const normalizedInput: IkSolveInput = {
112
+ position: targetPosition,
113
+ quaternion: targetQuaternion,
114
+ currentQ,
115
+ context,
116
+ };
117
+ if (config.ikSolveFn) return config.ikSolveFn(normalizedInput);
90
118
  const model = mjModelRef.current;
91
119
  const data = mjDataRef.current;
92
120
  const controlGroup = controlGroupRef.current;
93
121
  if (!model || !data || !controlGroup || siteIdRef.current === -1) return null;
94
122
  return genericIkRef.current.solve(
95
123
  model, data, siteIdRef.current, controlGroup.qposAdr,
96
- position, quaternion, currentQ,
124
+ targetPosition, targetQuaternion, currentQ,
97
125
  {
98
126
  damping: config.damping,
99
127
  epsilon: config.epsilon,
@@ -218,12 +246,12 @@ export const useIkController = createControllerHook<IkConfig, IkContextValue>(
218
246
  );
219
247
 
220
248
  const moveTarget = useCallback(
221
- (pos: THREE.Vector3, duration = 0) => {
249
+ (pos: IkTargetPosition, duration = 0) => {
222
250
  if (!ikEnabledRef.current) setIkEnabled(true);
223
251
  const target = ikTargetRef.current;
224
252
  if (!target) return;
225
253
 
226
- const targetPos = pos.clone();
254
+ const targetPos = toIkVector3(pos);
227
255
  const targetRot = new THREE.Quaternion().setFromEuler(
228
256
  new THREE.Euler(Math.PI, 0, 0),
229
257
  );
package/src/index.ts CHANGED
@@ -97,11 +97,13 @@ export { useBodyState } from './hooks/useBodyState';
97
97
  export { useBodyPose, useGeomPose, useSitePose } from './hooks/usePose';
98
98
  export type { PoseReadout, PoseResourceKind } from './hooks/usePose';
99
99
  export { useCtrl } from './hooks/useCtrl';
100
- export { controlGroup, useControlGroup } from './hooks/useControlGroup';
100
+ export { controlGroup, defineControls, useControlGroup, useControls } from './hooks/useControlGroup';
101
101
  export type {
102
102
  ControlGroup,
103
103
  ControlGroupHandle,
104
104
  ControlGroupSetOptions,
105
+ ControlsHandle,
106
+ DefinedControls,
105
107
  UseControlGroupOptions,
106
108
  } from './hooks/useControlGroup';
107
109
  export { useControlWriter } from './hooks/useControlWriter';
package/src/types.ts CHANGED
@@ -564,7 +564,7 @@ export interface IkContextValue {
564
564
  ikTargetRef: React.RefObject<THREE.Group>;
565
565
  siteIdRef: React.RefObject<number>;
566
566
  setIkEnabled: (enabled: boolean) => void;
567
- moveTarget: (pos: THREE.Vector3, duration?: number) => void;
567
+ moveTarget: (pos: IkTargetPosition, duration?: number) => void;
568
568
  syncTargetToSite: () => void;
569
569
  solveIK: (input: IkSolveInput) => number[] | null;
570
570
  getGizmoStats: () => { pos: THREE.Vector3; rot: THREE.Euler } | null;
@@ -592,9 +592,28 @@ export type IKSolveFn = (
592
592
  input: IkSolveInput
593
593
  ) => number[] | null;
594
594
 
595
+ export type IkTargetPosition =
596
+ | THREE.Vector3
597
+ | readonly [number, number, number]
598
+ | {
599
+ readonly x: number;
600
+ readonly y: number;
601
+ readonly z: number;
602
+ };
603
+
604
+ export type IkTargetQuaternion =
605
+ | THREE.Quaternion
606
+ | readonly [number, number, number, number]
607
+ | {
608
+ readonly x: number;
609
+ readonly y: number;
610
+ readonly z: number;
611
+ readonly w: number;
612
+ };
613
+
595
614
  export interface IkSolveInput {
596
- position: THREE.Vector3;
597
- quaternion: THREE.Quaternion;
615
+ position: IkTargetPosition;
616
+ quaternion: IkTargetQuaternion;
598
617
  currentQ: number[];
599
618
  context?: IKSolveContext;
600
619
  }