mujoco-react 8.3.3 → 8.4.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/index.js CHANGED
@@ -15,15 +15,57 @@ var MujocoContext = createContext({
15
15
  function useMujocoWasm() {
16
16
  return useContext(MujocoContext);
17
17
  }
18
- function MujocoProvider({ wasmUrl, timeout = 3e4, children, onError }) {
18
+ function canUseThreadedWasm() {
19
+ return typeof globalThis !== "undefined" && globalThis.crossOriginIsolated === true;
20
+ }
21
+ function isMujocoModule(value) {
22
+ return typeof value === "object" && value !== null && "FS" in value && "MjModel" in value && "MjData" in value && "mj_step" in value;
23
+ }
24
+ function hasWasmUrl(value) {
25
+ return typeof value === "string" && value.length > 0;
26
+ }
27
+ function resolveWasmVariant(variant, threadedLoader, mtWasmUrl) {
28
+ if (variant === "threaded") return "threaded";
29
+ if (variant === "auto" && threadedLoader && mtWasmUrl && canUseThreadedWasm()) return "threaded";
30
+ return "single";
31
+ }
32
+ function MujocoProvider({
33
+ wasmUrl,
34
+ mtWasmUrl,
35
+ threadedLoader,
36
+ wasmVariant = "single",
37
+ timeout = 3e4,
38
+ children,
39
+ onError
40
+ }) {
19
41
  const [status, setStatus] = useState("loading");
20
42
  const [error, setError] = useState(null);
21
43
  const moduleRef = useRef(null);
22
44
  const isMounted = useRef(true);
23
45
  useEffect(() => {
24
46
  isMounted.current = true;
25
- const wasmPromise = loadMujoco({
26
- locateFile: (path) => path.endsWith(".wasm") ? wasmUrl ?? defaultMujocoWasmUrl : path,
47
+ const variant = resolveWasmVariant(wasmVariant, threadedLoader, mtWasmUrl);
48
+ if (variant === "threaded" && !threadedLoader) {
49
+ const err = new Error('MujocoProvider wasmVariant="threaded" requires a threadedLoader from @mujoco/mujoco/mt');
50
+ setError(err.message);
51
+ setStatus("error");
52
+ onError?.(err);
53
+ return;
54
+ }
55
+ let selectedWasmUrl = wasmUrl ?? defaultMujocoWasmUrl;
56
+ if (variant === "threaded") {
57
+ if (!hasWasmUrl(mtWasmUrl)) {
58
+ const err = new Error('MujocoProvider wasmVariant="threaded" requires mtWasmUrl from @mujoco/mujoco/mt/mujoco.wasm?url');
59
+ setError(err.message);
60
+ setStatus("error");
61
+ onError?.(err);
62
+ return;
63
+ }
64
+ selectedWasmUrl = mtWasmUrl;
65
+ }
66
+ const load = variant === "threaded" && threadedLoader ? threadedLoader : loadMujoco;
67
+ const wasmPromise = load({
68
+ locateFile: (path) => path.endsWith(".wasm") ? selectedWasmUrl : path,
27
69
  printErr: (text) => {
28
70
  if (text.includes("Aborted") && isMounted.current) {
29
71
  setError("Simulation crashed. Reload page.");
@@ -36,6 +78,9 @@ function MujocoProvider({ wasmUrl, timeout = 3e4, children, onError }) {
36
78
  );
37
79
  Promise.race([wasmPromise, timeoutPromise]).then((inst) => {
38
80
  if (isMounted.current) {
81
+ if (!isMujocoModule(inst)) {
82
+ throw new Error("MuJoCo WASM module initialized with an unexpected shape");
83
+ }
39
84
  moduleRef.current = inst;
40
85
  setStatus("ready");
41
86
  }
@@ -50,7 +95,7 @@ function MujocoProvider({ wasmUrl, timeout = 3e4, children, onError }) {
50
95
  return () => {
51
96
  isMounted.current = false;
52
97
  };
53
- }, [wasmUrl, timeout]);
98
+ }, [wasmUrl, mtWasmUrl, threadedLoader, wasmVariant, timeout, onError]);
54
99
  return /* @__PURE__ */ jsx(
55
100
  MujocoContext.Provider,
56
101
  {
@@ -320,6 +365,12 @@ var GeomBuilder = class {
320
365
  };
321
366
 
322
367
  // src/core/SceneLoader.ts
368
+ var JOINT_TYPE_NAMES = {
369
+ 0: "free",
370
+ 1: "ball",
371
+ 2: "slide",
372
+ 3: "hinge"
373
+ };
323
374
  function getName(mjModel, address) {
324
375
  let name = "";
325
376
  let idx = address;
@@ -388,6 +439,276 @@ function getActuatedScalarQposAdr(mjModel, actuatorId) {
388
439
  if (jntType !== 2 && jntType !== 3) return -1;
389
440
  return mjModel.jnt_qposadr[jointId];
390
441
  }
442
+ function getScalarJointDim(jointType) {
443
+ return jointType === 2 || jointType === 3 ? 1 : 0;
444
+ }
445
+ function unlimitedRange() {
446
+ return [-Infinity, Infinity];
447
+ }
448
+ function isScalarJoint(mjModel, jointId) {
449
+ return jointId >= 0 && jointId < mjModel.njnt && getScalarJointDim(mjModel.jnt_type[jointId]) === 1;
450
+ }
451
+ function getActuatorJointId(mjModel, actuatorId) {
452
+ if (actuatorId < 0 || actuatorId >= mjModel.nu) return -1;
453
+ const trnType = mjModel.actuator_trntype?.[actuatorId];
454
+ if (trnType !== void 0 && trnType !== 0 && trnType !== 1) return -1;
455
+ const jointId = mjModel.actuator_trnid[2 * actuatorId];
456
+ return isScalarJoint(mjModel, jointId) ? jointId : -1;
457
+ }
458
+ function getJointInfo(mjModel, jointId) {
459
+ const type = mjModel.jnt_type[jointId];
460
+ const range = [mjModel.jnt_range[2 * jointId], mjModel.jnt_range[2 * jointId + 1]];
461
+ return {
462
+ id: jointId,
463
+ name: getName(mjModel, mjModel.name_jntadr[jointId]),
464
+ type,
465
+ typeName: JOINT_TYPE_NAMES[type] ?? `unknown(${type})`,
466
+ range,
467
+ limited: range[0] < range[1],
468
+ bodyId: mjModel.jnt_bodyid[jointId],
469
+ qposAdr: mjModel.jnt_qposadr[jointId],
470
+ dofAdr: mjModel.jnt_dofadr[jointId]
471
+ };
472
+ }
473
+ function getActuatorInfo(mjModel, actuatorId) {
474
+ const hasRange = mjModel.actuator_ctrlrange[2 * actuatorId] < mjModel.actuator_ctrlrange[2 * actuatorId + 1];
475
+ return {
476
+ id: actuatorId,
477
+ name: getName(mjModel, mjModel.name_actuatoradr[actuatorId]),
478
+ range: hasRange ? [mjModel.actuator_ctrlrange[2 * actuatorId], mjModel.actuator_ctrlrange[2 * actuatorId + 1]] : unlimitedRange()
479
+ };
480
+ }
481
+ function includesResourceName(names, name) {
482
+ return names.includes(name);
483
+ }
484
+ function matchesSelector(info, selector) {
485
+ if (typeof selector === "string") return info.name === selector;
486
+ if (selector instanceof RegExp) return selector.test(info.name);
487
+ if (Array.isArray(selector)) return includesResourceName(selector, info.name);
488
+ if (typeof selector === "function") return selector(info);
489
+ return false;
490
+ }
491
+ function orderedJointIdsFromSelector(mjModel, selector) {
492
+ if (typeof selector === "string") {
493
+ const id = findJointByName(mjModel, selector);
494
+ return id >= 0 && isScalarJoint(mjModel, id) ? [id] : [];
495
+ }
496
+ if (Array.isArray(selector)) {
497
+ return selector.map((name) => findJointByName(mjModel, name)).filter((id) => id >= 0 && isScalarJoint(mjModel, id));
498
+ }
499
+ const ids = [];
500
+ for (let i = 0; i < mjModel.njnt; i++) {
501
+ if (!isScalarJoint(mjModel, i)) continue;
502
+ const info = getJointInfo(mjModel, i);
503
+ if (matchesSelector(info, selector)) ids.push(i);
504
+ }
505
+ return ids;
506
+ }
507
+ function orderedActuatorIdsFromSelector(mjModel, selector) {
508
+ if (typeof selector === "string") {
509
+ const id = findActuatorByName(mjModel, selector);
510
+ return id >= 0 && getActuatorJointId(mjModel, id) >= 0 ? [id] : [];
511
+ }
512
+ if (Array.isArray(selector)) {
513
+ return selector.map((name) => findActuatorByName(mjModel, name)).filter((id) => id >= 0 && getActuatorJointId(mjModel, id) >= 0);
514
+ }
515
+ const ids = [];
516
+ for (let i = 0; i < mjModel.nu; i++) {
517
+ if (getActuatorJointId(mjModel, i) < 0) continue;
518
+ const info = getActuatorInfo(mjModel, i);
519
+ if (matchesSelector(info, selector)) ids.push(i);
520
+ }
521
+ return ids;
522
+ }
523
+ function inferScalarJointChain(mjModel, bodyId) {
524
+ if (bodyId < 0 || bodyId >= mjModel.nbody) return [];
525
+ const chainByBody = [];
526
+ let current = bodyId;
527
+ const seen = /* @__PURE__ */ new Set();
528
+ while (current >= 0 && current < mjModel.nbody && !seen.has(current)) {
529
+ seen.add(current);
530
+ const joints = [];
531
+ const jointCount = mjModel.body_jntnum[current] ?? 0;
532
+ const jointStart = mjModel.body_jntadr[current] ?? -1;
533
+ for (let i = 0; i < jointCount; i++) {
534
+ const jointId = jointStart + i;
535
+ if (isScalarJoint(mjModel, jointId)) joints.push(jointId);
536
+ }
537
+ if (joints.length) chainByBody.push(joints);
538
+ const parent = mjModel.body_parentid[current];
539
+ if (parent === current) break;
540
+ current = parent;
541
+ }
542
+ return chainByBody.reverse().flat();
543
+ }
544
+ function unique(values) {
545
+ const seen = /* @__PURE__ */ new Set();
546
+ const result = [];
547
+ for (const value of values) {
548
+ if (seen.has(value)) continue;
549
+ seen.add(value);
550
+ result.push(value);
551
+ }
552
+ return result;
553
+ }
554
+ function findActuatorForJoint(mjModel, jointId, preferredActuatorIds) {
555
+ const search = preferredActuatorIds ?? Array.from({ length: mjModel.nu }, (_, i) => i);
556
+ for (const actuatorId of search) {
557
+ if (getActuatorJointId(mjModel, actuatorId) === jointId) return actuatorId;
558
+ }
559
+ return -1;
560
+ }
561
+ function buildControlGroup(mjModel, jointIds, preferredActuatorIds) {
562
+ const ids = unique(jointIds).filter((id) => isScalarJoint(mjModel, id));
563
+ if (!ids.length) return null;
564
+ const joints = [];
565
+ const actuators = [];
566
+ const qposAdr = [];
567
+ const dofAdr = [];
568
+ const ctrlAdr = [];
569
+ for (const jointId of ids) {
570
+ const actuatorId = findActuatorForJoint(mjModel, jointId, preferredActuatorIds);
571
+ const joint = getJointInfo(mjModel, jointId);
572
+ qposAdr.push(joint.qposAdr);
573
+ dofAdr.push(joint.dofAdr);
574
+ if (actuatorId >= 0) {
575
+ const actuator = getActuatorInfo(mjModel, actuatorId);
576
+ actuators.push(actuator);
577
+ ctrlAdr.push(actuatorId);
578
+ joints.push({
579
+ ...joint,
580
+ actuatorId,
581
+ actuatorName: actuator.name,
582
+ ctrlAdr: actuatorId,
583
+ ctrlRange: actuator.range
584
+ });
585
+ } else {
586
+ joints.push({
587
+ ...joint,
588
+ actuatorId: null,
589
+ actuatorName: null,
590
+ ctrlAdr: null,
591
+ ctrlRange: null
592
+ });
593
+ }
594
+ }
595
+ return {
596
+ joints,
597
+ actuators,
598
+ qposAdr,
599
+ dofAdr,
600
+ ctrlAdr,
601
+ readQpos(data) {
602
+ return new Float64Array(qposAdr.map((adr) => data.qpos[adr] ?? 0));
603
+ },
604
+ readCtrl(data) {
605
+ return new Float64Array(joints.map((joint) => joint.ctrlAdr === null ? 0 : data.ctrl[joint.ctrlAdr] ?? 0));
606
+ },
607
+ writeQpos(data, values) {
608
+ for (let i = 0; i < Math.min(values.length, qposAdr.length); i++) {
609
+ data.qpos[qposAdr[i]] = values[i];
610
+ }
611
+ },
612
+ writeCtrl(data, values) {
613
+ for (let i = 0; i < Math.min(values.length, joints.length); i++) {
614
+ const adr = joints[i].ctrlAdr;
615
+ if (adr !== null) data.ctrl[adr] = values[i];
616
+ }
617
+ }
618
+ };
619
+ }
620
+ function getActuatedJoints(mjModel) {
621
+ const result = [];
622
+ for (let actuatorId = 0; actuatorId < mjModel.nu; actuatorId++) {
623
+ const jointId = getActuatorJointId(mjModel, actuatorId);
624
+ if (jointId < 0) continue;
625
+ const actuator = getActuatorInfo(mjModel, actuatorId);
626
+ result.push({
627
+ ...getJointInfo(mjModel, jointId),
628
+ actuatorId,
629
+ actuatorName: actuator.name,
630
+ ctrlAdr: actuatorId,
631
+ ctrlRange: actuator.range
632
+ });
633
+ }
634
+ return result;
635
+ }
636
+ function getControlMap(mjModel) {
637
+ const actuatorIds = Array.from({ length: mjModel.nu }, (_, i) => i).filter((id) => getActuatorJointId(mjModel, id) >= 0);
638
+ const jointIds = actuatorIds.map((id) => getActuatorJointId(mjModel, id));
639
+ return buildControlGroup(mjModel, jointIds, actuatorIds) ?? createContiguousControlGroup(mjModel, 0);
640
+ }
641
+ function resolveControlGroup(mjModel, selector) {
642
+ if (selector.actuators) {
643
+ const actuatorIds = orderedActuatorIdsFromSelector(mjModel, selector.actuators);
644
+ const jointIds = actuatorIds.map((id) => getActuatorJointId(mjModel, id));
645
+ return buildControlGroup(mjModel, jointIds, actuatorIds);
646
+ }
647
+ if (selector.joints) {
648
+ return buildControlGroup(mjModel, orderedJointIdsFromSelector(mjModel, selector.joints));
649
+ }
650
+ if (selector.siteName) {
651
+ const siteId = findSiteByName(mjModel, selector.siteName);
652
+ const bodyId = siteId >= 0 ? mjModel.site_bodyid?.[siteId] ?? -1 : -1;
653
+ return buildControlGroup(mjModel, inferScalarJointChain(mjModel, bodyId));
654
+ }
655
+ if (selector.bodyName) {
656
+ return buildControlGroup(mjModel, inferScalarJointChain(mjModel, findBodyByName(mjModel, selector.bodyName)));
657
+ }
658
+ return getControlMap(mjModel);
659
+ }
660
+ function createContiguousControlGroup(mjModel, count) {
661
+ const n = Math.max(0, Math.min(count, mjModel.nq, mjModel.nu));
662
+ const joints = [];
663
+ const actuators = [];
664
+ const qposAdr = [];
665
+ const dofAdr = [];
666
+ const ctrlAdr = [];
667
+ for (let i = 0; i < n; i++) {
668
+ qposAdr.push(i);
669
+ dofAdr.push(i);
670
+ ctrlAdr.push(i);
671
+ const jointId = Array.from({ length: mjModel.njnt }, (_, id) => id).find((id) => mjModel.jnt_qposadr[id] === i);
672
+ const actuator = getActuatorInfo(mjModel, i);
673
+ actuators.push(actuator);
674
+ joints.push({
675
+ ...jointId !== void 0 ? getJointInfo(mjModel, jointId) : {
676
+ id: i,
677
+ name: `qpos${i}`,
678
+ type: 3,
679
+ typeName: "hinge",
680
+ range: unlimitedRange(),
681
+ limited: false,
682
+ bodyId: -1,
683
+ qposAdr: i,
684
+ dofAdr: i
685
+ },
686
+ actuatorId: i,
687
+ actuatorName: actuator.name,
688
+ ctrlAdr: i,
689
+ ctrlRange: actuator.range
690
+ });
691
+ }
692
+ return {
693
+ joints,
694
+ actuators,
695
+ qposAdr,
696
+ dofAdr,
697
+ ctrlAdr,
698
+ readQpos(data) {
699
+ return new Float64Array(qposAdr.map((adr) => data.qpos[adr] ?? 0));
700
+ },
701
+ readCtrl(data) {
702
+ return new Float64Array(ctrlAdr.map((adr) => data.ctrl[adr] ?? 0));
703
+ },
704
+ writeQpos(data, values) {
705
+ for (let i = 0; i < Math.min(values.length, qposAdr.length); i++) data.qpos[qposAdr[i]] = values[i];
706
+ },
707
+ writeCtrl(data, values) {
708
+ for (let i = 0; i < Math.min(values.length, ctrlAdr.length); i++) data.ctrl[ctrlAdr[i]] = values[i];
709
+ }
710
+ };
711
+ }
391
712
  function sceneObjectToXml(obj) {
392
713
  const joint = obj.freejoint ? "<freejoint/>" : "";
393
714
  const pos = obj.position.map((v) => v.toFixed(3)).join(" ");
@@ -627,7 +948,7 @@ function SceneRenderer(props) {
627
948
  }
628
949
  );
629
950
  }
630
- var JOINT_TYPE_NAMES = ["free", "ball", "slide", "hinge"];
951
+ var JOINT_TYPE_NAMES2 = ["free", "ball", "slide", "hinge"];
631
952
  var GEOM_TYPE_NAMES = ["plane", "hfield", "sphere", "capsule", "ellipsoid", "cylinder", "box", "mesh"];
632
953
  var SENSOR_TYPE_NAMES = {
633
954
  0: "touch",
@@ -680,6 +1001,22 @@ var SENSOR_TYPE_NAMES = {
680
1001
  47: "plugin",
681
1002
  48: "user"
682
1003
  };
1004
+ var EMPTY_CONTROL_GROUP = {
1005
+ joints: [],
1006
+ actuators: [],
1007
+ qposAdr: [],
1008
+ dofAdr: [],
1009
+ ctrlAdr: [],
1010
+ readQpos: () => new Float64Array(0),
1011
+ readCtrl: () => new Float64Array(0),
1012
+ writeQpos: () => {
1013
+ },
1014
+ writeCtrl: () => {
1015
+ }
1016
+ };
1017
+ function isMutableApiRef(ref) {
1018
+ return typeof ref === "object" && ref !== null && "current" in ref;
1019
+ }
683
1020
  var _applyForce = new Float64Array(3);
684
1021
  var _applyTorque = new Float64Array(3);
685
1022
  var _applyPoint = new Float64Array(3);
@@ -877,7 +1214,7 @@ function MujocoSimProvider({
877
1214
  if (externalApiRef) {
878
1215
  if (typeof externalApiRef === "function") {
879
1216
  externalApiRef(api2);
880
- } else {
1217
+ } else if (isMutableApiRef(externalApiRef)) {
881
1218
  externalApiRef.current = api2;
882
1219
  }
883
1220
  }
@@ -1134,14 +1471,14 @@ function MujocoSimProvider({
1134
1471
  const result = [];
1135
1472
  for (let i = 0; i < model.njnt; i++) {
1136
1473
  const type = model.jnt_type[i];
1137
- const limited = model.jnt_limited ? model.jnt_limited[i] !== 0 : false;
1474
+ const range = [model.jnt_range[2 * i], model.jnt_range[2 * i + 1]];
1138
1475
  result.push({
1139
1476
  id: i,
1140
1477
  name: getName(model, model.name_jntadr[i]),
1141
1478
  type,
1142
- typeName: JOINT_TYPE_NAMES[type] ?? `unknown(${type})`,
1143
- range: [model.jnt_range[2 * i], model.jnt_range[2 * i + 1]],
1144
- limited,
1479
+ typeName: JOINT_TYPE_NAMES2[type] ?? `unknown(${type})`,
1480
+ range,
1481
+ limited: range[0] < range[1],
1145
1482
  bodyId: model.jnt_bodyid[i],
1146
1483
  qposAdr: model.jnt_qposadr[i],
1147
1484
  dofAdr: model.jnt_dofadr[i]
@@ -1193,6 +1530,18 @@ function MujocoSimProvider({
1193
1530
  }
1194
1531
  return result;
1195
1532
  }, []);
1533
+ const getControlMapApi = useCallback(() => {
1534
+ const model = mjModelRef.current;
1535
+ return model ? getControlMap(model) : EMPTY_CONTROL_GROUP;
1536
+ }, []);
1537
+ const getActuatedJointsApi = useCallback(() => {
1538
+ const model = mjModelRef.current;
1539
+ return model ? getActuatedJoints(model) : [];
1540
+ }, []);
1541
+ const resolveControlGroupApi = useCallback((selector) => {
1542
+ const model = mjModelRef.current;
1543
+ return model ? resolveControlGroup(model, selector) : null;
1544
+ }, []);
1196
1545
  const getSensors = useCallback(() => {
1197
1546
  const model = mjModelRef.current;
1198
1547
  if (!model) return [];
@@ -1429,6 +1778,9 @@ function MujocoSimProvider({
1429
1778
  getQvel,
1430
1779
  setCtrl,
1431
1780
  getCtrl: getCtrl2,
1781
+ getControlMap: getControlMapApi,
1782
+ getActuatedJoints: getActuatedJointsApi,
1783
+ resolveControlGroup: resolveControlGroupApi,
1432
1784
  applyForce,
1433
1785
  applyTorque: applyTorqueApi,
1434
1786
  setExternalForce,
@@ -1475,6 +1827,9 @@ function MujocoSimProvider({
1475
1827
  getQvel,
1476
1828
  setCtrl,
1477
1829
  getCtrl2,
1830
+ getControlMapApi,
1831
+ getActuatedJointsApi,
1832
+ resolveControlGroupApi,
1478
1833
  applyForce,
1479
1834
  applyTorqueApi,
1480
1835
  setExternalForce,
@@ -1668,16 +2023,16 @@ var GenericIK = class {
1668
2023
  * @param model MuJoCo model
1669
2024
  * @param data MuJoCo data (qpos will be temporarily modified, then restored)
1670
2025
  * @param siteId Index of the end-effector site to control
1671
- * @param numJoints Number of arm joints (assumes qpos[0..numJoints-1])
2026
+ * @param qposAdr qpos addresses for scalar joints in solve order
1672
2027
  * @param targetPos Target position in world frame
1673
2028
  * @param targetQuat Target orientation in world frame
1674
- * @param currentQ Current joint angles (length = numJoints)
2029
+ * @param currentQ Current joint angles matching qposAdr order
1675
2030
  * @param opts Optional solver parameters
1676
2031
  * @returns Joint angles array, or null if solver diverged
1677
2032
  */
1678
- solve(model, data, siteId, numJoints, targetPos, targetQuat, currentQ, opts) {
2033
+ solve(model, data, siteId, qposAdr, targetPos, targetQuat, currentQ, opts) {
1679
2034
  const o = { ...DEFAULTS, ...opts };
1680
- const n = numJoints;
2035
+ const n = qposAdr.length;
1681
2036
  const savedQpos = new Float64Array(data.qpos.length);
1682
2037
  savedQpos.set(data.qpos);
1683
2038
  const R_target = quatToMat3(targetQuat);
@@ -1694,8 +2049,9 @@ var GenericIK = class {
1694
2049
  const pertSiteMat = new Float64Array(9);
1695
2050
  let bestQ = null;
1696
2051
  let bestErr = Infinity;
2052
+ if (n === 0) return null;
1697
2053
  for (let iter = 0; iter < o.maxIterations; iter++) {
1698
- for (let i = 0; i < n; i++) data.qpos[i] = q[i];
2054
+ for (let i = 0; i < n; i++) data.qpos[qposAdr[i]] = q[i];
1699
2055
  this.mujoco.mj_forward(model, data);
1700
2056
  const sp = data.site_xpos;
1701
2057
  const sm = data.site_xmat;
@@ -1724,8 +2080,9 @@ var GenericIK = class {
1724
2080
  }
1725
2081
  if (errNorm < o.tolerance) break;
1726
2082
  for (let j = 0; j < n; j++) {
1727
- const saved = data.qpos[j];
1728
- data.qpos[j] = q[j] + o.epsilon;
2083
+ const adr = qposAdr[j];
2084
+ const saved = data.qpos[adr];
2085
+ data.qpos[adr] = q[j] + o.epsilon;
1729
2086
  this.mujoco.mj_forward(model, data);
1730
2087
  for (let i = 0; i < 3; i++) pertSitePos[i] = sp[off3 + i];
1731
2088
  for (let i = 0; i < 9; i++) pertSiteMat[i] = sm[off9 + i];
@@ -1736,9 +2093,9 @@ var GenericIK = class {
1736
2093
  J[3 * n + j] = dRot[0] / o.epsilon * o.rotWeight;
1737
2094
  J[4 * n + j] = dRot[1] / o.epsilon * o.rotWeight;
1738
2095
  J[5 * n + j] = dRot[2] / o.epsilon * o.rotWeight;
1739
- data.qpos[j] = saved;
2096
+ data.qpos[adr] = saved;
1740
2097
  }
1741
- for (let i = 0; i < n; i++) data.qpos[i] = q[i];
2098
+ for (let i = 0; i < n; i++) data.qpos[qposAdr[i]] = q[i];
1742
2099
  for (let r = 0; r < 6; r++) {
1743
2100
  for (let c = 0; c < 6; c++) {
1744
2101
  let sum = 0;
@@ -1910,6 +2267,7 @@ var useIkController = createControllerHook(
1910
2267
  const ikCalculatingRef = useRef(false);
1911
2268
  const ikTargetRef = useRef(new THREE11.Group());
1912
2269
  const siteIdRef = useRef(-1);
2270
+ const controlGroupRef = useRef(null);
1913
2271
  const genericIkRef = useRef(new GenericIK(mujocoRef.current));
1914
2272
  const firstIkEnableRef = useRef(true);
1915
2273
  const needsInitialSync = useRef(true);
@@ -1925,31 +2283,39 @@ var useIkController = createControllerHook(
1925
2283
  useEffect(() => {
1926
2284
  if (!config) {
1927
2285
  siteIdRef.current = -1;
2286
+ controlGroupRef.current = null;
1928
2287
  return;
1929
2288
  }
1930
2289
  const model = mjModelRef.current;
1931
2290
  if (!model || status !== "ready") {
1932
2291
  siteIdRef.current = -1;
2292
+ controlGroupRef.current = null;
1933
2293
  return;
1934
2294
  }
1935
2295
  siteIdRef.current = findSiteByName(model, config.siteName);
2296
+ controlGroupRef.current = config.numJoints !== void 0 ? createContiguousControlGroup(model, config.numJoints) : resolveControlGroup(model, {
2297
+ siteName: config.siteName,
2298
+ joints: config.joints,
2299
+ actuators: config.actuators
2300
+ });
1936
2301
  const data = mjDataRef.current;
1937
2302
  if (data && ikTargetRef.current) {
1938
2303
  syncGizmoToSite(data, siteIdRef.current, ikTargetRef.current);
1939
2304
  }
1940
- }, [config?.siteName, status, mjModelRef, mjDataRef, config]);
2305
+ }, [config?.siteName, config?.numJoints, config?.joints, config?.actuators, status, mjModelRef, mjDataRef, config]);
1941
2306
  const ikSolveFn = useCallback(
1942
2307
  (pos, quat, currentQ) => {
1943
2308
  if (!config) return null;
1944
2309
  if (config.ikSolveFn) return config.ikSolveFn(pos, quat, currentQ);
1945
2310
  const model = mjModelRef.current;
1946
2311
  const data = mjDataRef.current;
1947
- if (!model || !data || siteIdRef.current === -1) return null;
2312
+ const controlGroup = controlGroupRef.current;
2313
+ if (!model || !data || !controlGroup || siteIdRef.current === -1) return null;
1948
2314
  return genericIkRef.current.solve(
1949
2315
  model,
1950
2316
  data,
1951
2317
  siteIdRef.current,
1952
- config.numJoints,
2318
+ controlGroup.qposAdr,
1953
2319
  pos,
1954
2320
  quat,
1955
2321
  currentQ,
@@ -1988,12 +2354,17 @@ var useIkController = createControllerHook(
1988
2354
  const target = ikTargetRef.current;
1989
2355
  if (!target) return;
1990
2356
  ikCalculatingRef.current = true;
1991
- const numJoints = config.numJoints;
1992
- const currentQ = [];
1993
- for (let i = 0; i < numJoints; i++) currentQ.push(data.qpos[i]);
1994
- const solution = ikSolveFnRef.current(target.position, target.quaternion, currentQ);
2357
+ const controlGroup = controlGroupRef.current;
2358
+ if (!controlGroup) return;
2359
+ const currentQ = Array.from(controlGroup.readQpos(data));
2360
+ const solution = config.ikSolveFn ? config.ikSolveFn(target.position, target.quaternion, currentQ, {
2361
+ model,
2362
+ data,
2363
+ siteId: siteIdRef.current,
2364
+ controlGroup
2365
+ }) : ikSolveFnRef.current(target.position, target.quaternion, currentQ);
1995
2366
  if (solution) {
1996
- for (let i = 0; i < numJoints; i++) data.ctrl[i] = solution[i];
2367
+ controlGroup.writeCtrl(data, solution);
1997
2368
  }
1998
2369
  });
1999
2370
  useEffect(() => {
@@ -4202,6 +4573,6 @@ function useCameraAnimation() {
4202
4573
  * useCameraAnimation — composable camera animation hook.
4203
4574
  */
4204
4575
 
4205
- export { Body, ContactListener, ContactMarkers, Debug, DragInteraction, FlexRenderer, IkGizmo, MujocoCanvas, MujocoPhysics, MujocoProvider, MujocoSimProvider, SceneLights, TendonRenderer, TrajectoryPlayer, createController, createControllerHook, findActuatorByName, findBodyByName, findGeomByName, findJointByName, findKeyframeByName, findSensorByName, findSiteByName, findTendonByName, getContact, getName, loadScene, useActuators, useAfterPhysicsStep, useBeforePhysicsStep, useBodyMeshes, useBodyState, useCameraAnimation, useContactEvents, useContacts, useCtrl, useCtrlNoise, useGamepad, useGravityCompensation, useIkController, useJointState, useKeyboardTeleop, useMujoco, useMujocoWasm, usePolicy, useSceneLights, useSelectionHighlight, useSensor, useSensors, useSitePosition, useTrajectoryPlayer, useTrajectoryRecorder, useVideoRecorder };
4576
+ export { Body, ContactListener, ContactMarkers, Debug, DragInteraction, FlexRenderer, IkGizmo, MujocoCanvas, MujocoPhysics, MujocoProvider, MujocoSimProvider, SceneLights, TendonRenderer, TrajectoryPlayer, createContiguousControlGroup, createController, createControllerHook, findActuatorByName, findBodyByName, findGeomByName, findJointByName, findKeyframeByName, findSensorByName, findSiteByName, findTendonByName, getActuatedJoints, getContact, getControlMap, getName, loadScene, resolveControlGroup, useActuators, useAfterPhysicsStep, useBeforePhysicsStep, useBodyMeshes, useBodyState, useCameraAnimation, useContactEvents, useContacts, useCtrl, useCtrlNoise, useGamepad, useGravityCompensation, useIkController, useJointState, useKeyboardTeleop, useMujoco, useMujocoWasm, usePolicy, useSceneLights, useSelectionHighlight, useSensor, useSensors, useSitePosition, useTrajectoryPlayer, useTrajectoryRecorder, useVideoRecorder };
4206
4577
  //# sourceMappingURL=index.js.map
4207
4578
  //# sourceMappingURL=index.js.map