mujoco-react 8.3.3 → 8.4.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.
@@ -9,8 +9,8 @@ import * as THREE from 'three';
9
9
  import { createControllerHook } from '../core/createController';
10
10
  import { useMujocoContext, useBeforePhysicsStep } from '../core/MujocoSimProvider';
11
11
  import { GenericIK } from '../core/GenericIK';
12
- import { findSiteByName } from '../core/SceneLoader';
13
- import type { IkConfig, IkContextValue, IKSolveFn, MujocoData } from '../types';
12
+ import { createContiguousControlGroup, findSiteByName, resolveControlGroup } from '../core/SceneLoader';
13
+ import type { ControlGroupInfo, IkConfig, IkContextValue, IKSolveFn, MujocoData } from '../types';
14
14
 
15
15
  // Preallocated temp for syncGizmoToSite
16
16
  const _syncMat4 = new THREE.Matrix4();
@@ -40,6 +40,7 @@ export const useIkController = createControllerHook<IkConfig, IkContextValue>(
40
40
  const ikCalculatingRef = useRef(false);
41
41
  const ikTargetRef = useRef<THREE.Group>(new THREE.Group());
42
42
  const siteIdRef = useRef(-1);
43
+ const controlGroupRef = useRef<ControlGroupInfo | null>(null);
43
44
  const genericIkRef = useRef<GenericIK>(new GenericIK(mujocoRef.current));
44
45
  const firstIkEnableRef = useRef(true);
45
46
  const needsInitialSync = useRef(true);
@@ -54,23 +55,32 @@ export const useIkController = createControllerHook<IkConfig, IkContextValue>(
54
55
  duration: 1000,
55
56
  });
56
57
 
57
- // Resolve site ID when model loads or config changes
58
+ // Resolve site ID and model-aware control group when model loads or config changes.
58
59
  useEffect(() => {
59
60
  if (!config) {
60
61
  siteIdRef.current = -1;
62
+ controlGroupRef.current = null;
61
63
  return;
62
64
  }
63
65
  const model = mjModelRef.current;
64
66
  if (!model || status !== 'ready') {
65
67
  siteIdRef.current = -1;
68
+ controlGroupRef.current = null;
66
69
  return;
67
70
  }
68
71
  siteIdRef.current = findSiteByName(model, config.siteName);
72
+ controlGroupRef.current = config.numJoints !== undefined
73
+ ? createContiguousControlGroup(model, config.numJoints)
74
+ : resolveControlGroup(model, {
75
+ siteName: config.siteName,
76
+ joints: config.joints,
77
+ actuators: config.actuators,
78
+ });
69
79
  const data = mjDataRef.current;
70
80
  if (data && ikTargetRef.current) {
71
81
  syncGizmoToSite(data, siteIdRef.current, ikTargetRef.current);
72
82
  }
73
- }, [config?.siteName, status, mjModelRef, mjDataRef, config]);
83
+ }, [config?.siteName, config?.numJoints, config?.joints, config?.actuators, status, mjModelRef, mjDataRef, config]);
74
84
 
75
85
  // IK solve function
76
86
  const ikSolveFn = useCallback(
@@ -79,9 +89,10 @@ export const useIkController = createControllerHook<IkConfig, IkContextValue>(
79
89
  if (config.ikSolveFn) return config.ikSolveFn(pos, quat, currentQ);
80
90
  const model = mjModelRef.current;
81
91
  const data = mjDataRef.current;
82
- if (!model || !data || siteIdRef.current === -1) return null;
92
+ const controlGroup = controlGroupRef.current;
93
+ if (!model || !data || !controlGroup || siteIdRef.current === -1) return null;
83
94
  return genericIkRef.current.solve(
84
- model, data, siteIdRef.current, config.numJoints,
95
+ model, data, siteIdRef.current, controlGroup.qposAdr,
85
96
  pos, quat, currentQ,
86
97
  { damping: config.damping, maxIterations: config.maxIterations },
87
98
  );
@@ -126,12 +137,20 @@ export const useIkController = createControllerHook<IkConfig, IkContextValue>(
126
137
  if (!target) return;
127
138
 
128
139
  ikCalculatingRef.current = true;
129
- const numJoints = config.numJoints;
130
- const currentQ: number[] = [];
131
- for (let i = 0; i < numJoints; i++) currentQ.push(data.qpos[i]);
132
- const solution = ikSolveFnRef.current(target.position, target.quaternion, currentQ);
140
+ const controlGroup = controlGroupRef.current;
141
+ if (!controlGroup) return;
142
+
143
+ const currentQ = Array.from(controlGroup.readQpos(data));
144
+ const solution = config.ikSolveFn
145
+ ? config.ikSolveFn(target.position, target.quaternion, currentQ, {
146
+ model,
147
+ data,
148
+ siteId: siteIdRef.current,
149
+ controlGroup,
150
+ })
151
+ : ikSolveFnRef.current(target.position, target.quaternion, currentQ);
133
152
  if (solution) {
134
- for (let i = 0; i < numJoints; i++) data.ctrl[i] = solution[i];
153
+ controlGroup.writeCtrl(data, solution);
135
154
  }
136
155
  });
137
156
 
package/src/index.ts CHANGED
@@ -5,6 +5,7 @@
5
5
 
6
6
  // Core
7
7
  export { MujocoProvider, useMujocoWasm } from './core/MujocoProvider';
8
+ export type { MujocoLoader, MujocoLoaderOptions, MujocoProviderProps, MujocoWasmVariant } from './core/MujocoProvider';
8
9
  export { MujocoCanvas } from './core/MujocoCanvas';
9
10
  export { MujocoPhysics } from './core/MujocoPhysics';
10
11
  export type { MujocoPhysicsProps } from './core/MujocoPhysics';
@@ -20,6 +21,10 @@ export {
20
21
  findGeomByName,
21
22
  findSensorByName,
22
23
  findTendonByName,
24
+ getActuatedJoints,
25
+ getControlMap,
26
+ resolveControlGroup,
27
+ createContiguousControlGroup,
23
28
  } from './core/SceneLoader';
24
29
 
25
30
  // Controller factory
@@ -82,6 +87,11 @@ export type {
82
87
  // Model introspection
83
88
  BodyInfo,
84
89
  JointInfo,
90
+ ActuatedJointInfo,
91
+ ControlJointInfo,
92
+ ControlGroupInfo,
93
+ ControlGroupSelector,
94
+ ResourceSelector,
85
95
  GeomInfo,
86
96
  SiteInfo,
87
97
  ActuatorInfo,
package/src/types.ts CHANGED
@@ -344,11 +344,27 @@ export interface SceneConfig {
344
344
 
345
345
  // ---- IK Controller Config ----
346
346
 
347
+ export type ResourceSelector<TInfo, TName extends string = string> =
348
+ | TName
349
+ | readonly TName[]
350
+ | RegExp
351
+ | ((info: TInfo) => boolean);
352
+
347
353
  export interface IkConfig {
348
354
  /** MuJoCo site name for IK target. */
349
355
  siteName: Sites;
350
- /** Number of joints to solve for. */
351
- numJoints: number;
356
+ /**
357
+ * Explicit joints for IK. When omitted, the controller infers scalar hinge/slide
358
+ * joints by walking from the site body to the model root.
359
+ */
360
+ joints?: ResourceSelector<JointInfo, Joints>;
361
+ /** Explicit actuators for IK control output. */
362
+ actuators?: ResourceSelector<ActuatorInfo, Actuators>;
363
+ /**
364
+ * Number of joints to solve for, assuming legacy contiguous qpos/ctrl layout
365
+ * starting at index 0. Prefer inferred IK or `joints`/`actuators`.
366
+ */
367
+ numJoints?: number;
352
368
  /** Custom IK solver. When omitted, uses built-in Damped Least-Squares solver. */
353
369
  ikSolveFn?: IKSolveFn;
354
370
  /** DLS damping. Default: 0.01. */
@@ -390,9 +406,17 @@ export interface PhysicsConfig {
390
406
  export type IKSolveFn = (
391
407
  pos: THREE.Vector3,
392
408
  quat: THREE.Quaternion,
393
- currentQ: number[]
409
+ currentQ: number[],
410
+ context?: IKSolveContext
394
411
  ) => number[] | null;
395
412
 
413
+ export interface IKSolveContext {
414
+ model: MujocoModel;
415
+ data: MujocoData;
416
+ siteId: number;
417
+ controlGroup: ControlGroupInfo;
418
+ }
419
+
396
420
  // ---- Callbacks ----
397
421
 
398
422
  export type PhysicsStepCallback = (
@@ -453,6 +477,48 @@ export interface ActuatorInfo {
453
477
  range: [number, number];
454
478
  }
455
479
 
480
+ export interface ActuatedJointInfo extends JointInfo {
481
+ actuatorId: number;
482
+ actuatorName: string;
483
+ ctrlAdr: number;
484
+ ctrlRange: [number, number];
485
+ }
486
+
487
+ export interface ControlJointInfo extends JointInfo {
488
+ actuatorId: number | null;
489
+ actuatorName: string | null;
490
+ ctrlAdr: number | null;
491
+ ctrlRange: [number, number] | null;
492
+ }
493
+
494
+ export interface ControlGroupSelector {
495
+ /** Infer a kinematic chain from a MuJoCo site. */
496
+ siteName?: Sites;
497
+ /** Infer a kinematic chain from a body. */
498
+ bodyName?: Bodies;
499
+ /** Select joints by name, names, regex, or predicate. */
500
+ joints?: ResourceSelector<JointInfo, Joints>;
501
+ /** Select actuators by name, names, regex, or predicate. */
502
+ actuators?: ResourceSelector<ActuatorInfo, Actuators>;
503
+ }
504
+
505
+ export interface ControlGroupInfo {
506
+ /** Joints in solve/control order. */
507
+ joints: ControlJointInfo[];
508
+ /** Actuators in control output order. */
509
+ actuators: ActuatorInfo[];
510
+ /** qpos addresses for scalar hinge/slide joints. */
511
+ qposAdr: number[];
512
+ /** dof addresses for scalar hinge/slide joints. */
513
+ dofAdr: number[];
514
+ /** ctrl addresses matching writable actuators. */
515
+ ctrlAdr: number[];
516
+ readQpos(data: MujocoData): Float64Array;
517
+ readCtrl(data: MujocoData): Float64Array;
518
+ writeQpos(data: MujocoData, values: ArrayLike<number>): void;
519
+ writeCtrl(data: MujocoData, values: ArrayLike<number>): void;
520
+ }
521
+
456
522
  export interface SensorInfo {
457
523
  id: number;
458
524
  name: string;
@@ -629,6 +695,9 @@ export interface MujocoSimAPI {
629
695
  // Actuator / control (spec 3.1)
630
696
  setCtrl(nameOrValues: Actuators | Record<Actuators, number>, value?: number): void;
631
697
  getCtrl(): Float64Array;
698
+ getControlMap(): ControlGroupInfo;
699
+ getActuatedJoints(): ActuatedJointInfo[];
700
+ resolveControlGroup(selector: ControlGroupSelector): ControlGroupInfo | null;
632
701
 
633
702
  // Force application (spec 8.1)
634
703
  applyForce(bodyName: Bodies, force: THREE.Vector3, point?: THREE.Vector3): void;