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.
- package/README.md +107 -81
- package/dist/index.d.ts +87 -5
- package/dist/index.js +399 -28
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/core/GenericIK.ts +13 -10
- package/src/core/MujocoProvider.tsx +91 -7
- package/src/core/MujocoSimProvider.tsx +48 -5
- package/src/core/SceneLoader.ts +343 -1
- package/src/hooks/useIkController.ts +30 -11
- package/src/index.ts +10 -0
- package/src/types.ts +72 -3
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
|
|
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
|
|
26
|
-
|
|
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
|
|
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
|
|
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:
|
|
1143
|
-
range
|
|
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
|
|
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
|
|
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,
|
|
2033
|
+
solve(model, data, siteId, qposAdr, targetPos, targetQuat, currentQ, opts) {
|
|
1679
2034
|
const o = { ...DEFAULTS, ...opts };
|
|
1680
|
-
const n =
|
|
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
|
|
1728
|
-
data.qpos[
|
|
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[
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
const solution =
|
|
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
|
-
|
|
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
|