mujoco-react 8.2.1 → 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/README.md +129 -7
- package/dist/index.d.ts +93 -8
- package/dist/index.js +484 -87
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
- package/src/components/ContactMarkers.tsx +14 -12
- package/src/components/Debug.tsx +18 -16
- package/src/components/FlexRenderer.tsx +1 -1
- package/src/components/TendonRenderer.tsx +1 -1
- package/src/core/GenericIK.ts +13 -10
- package/src/core/MujocoProvider.tsx +93 -8
- package/src/core/MujocoSimProvider.tsx +63 -18
- package/src/core/SceneLoader.ts +354 -2
- package/src/hooks/useContacts.ts +20 -18
- package/src/hooks/useIkController.ts +30 -11
- package/src/index.ts +10 -0
- package/src/rendering/GeomBuilder.ts +1 -1
- package/src/types.ts +94 -7
- package/src/wasm-url.d.ts +4 -0
package/dist/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import loadMujoco from 'mujoco
|
|
1
|
+
import loadMujoco from '@mujoco/mujoco';
|
|
2
|
+
import defaultMujocoWasmUrl from '@mujoco/mujoco/mujoco.wasm?url';
|
|
2
3
|
import { createContext, forwardRef, useEffect, useContext, useState, useRef, useCallback, useMemo, useLayoutEffect } from 'react';
|
|
3
4
|
import { jsx, jsxs } from 'react/jsx-runtime';
|
|
4
5
|
import { Canvas, useThree, useFrame } from '@react-three/fiber';
|
|
@@ -14,15 +15,57 @@ var MujocoContext = createContext({
|
|
|
14
15
|
function useMujocoWasm() {
|
|
15
16
|
return useContext(MujocoContext);
|
|
16
17
|
}
|
|
17
|
-
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
|
+
}) {
|
|
18
41
|
const [status, setStatus] = useState("loading");
|
|
19
42
|
const [error, setError] = useState(null);
|
|
20
43
|
const moduleRef = useRef(null);
|
|
21
44
|
const isMounted = useRef(true);
|
|
22
45
|
useEffect(() => {
|
|
23
46
|
isMounted.current = true;
|
|
24
|
-
const
|
|
25
|
-
|
|
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,
|
|
26
69
|
printErr: (text) => {
|
|
27
70
|
if (text.includes("Aborted") && isMounted.current) {
|
|
28
71
|
setError("Simulation crashed. Reload page.");
|
|
@@ -35,6 +78,9 @@ function MujocoProvider({ wasmUrl, timeout = 3e4, children, onError }) {
|
|
|
35
78
|
);
|
|
36
79
|
Promise.race([wasmPromise, timeoutPromise]).then((inst) => {
|
|
37
80
|
if (isMounted.current) {
|
|
81
|
+
if (!isMujocoModule(inst)) {
|
|
82
|
+
throw new Error("MuJoCo WASM module initialized with an unexpected shape");
|
|
83
|
+
}
|
|
38
84
|
moduleRef.current = inst;
|
|
39
85
|
setStatus("ready");
|
|
40
86
|
}
|
|
@@ -49,7 +95,7 @@ function MujocoProvider({ wasmUrl, timeout = 3e4, children, onError }) {
|
|
|
49
95
|
return () => {
|
|
50
96
|
isMounted.current = false;
|
|
51
97
|
};
|
|
52
|
-
}, [wasmUrl, timeout]);
|
|
98
|
+
}, [wasmUrl, mtWasmUrl, threadedLoader, wasmVariant, timeout, onError]);
|
|
53
99
|
return /* @__PURE__ */ jsx(
|
|
54
100
|
MujocoContext.Provider,
|
|
55
101
|
{
|
|
@@ -60,13 +106,21 @@ function MujocoProvider({ wasmUrl, timeout = 3e4, children, onError }) {
|
|
|
60
106
|
}
|
|
61
107
|
|
|
62
108
|
// src/types.ts
|
|
63
|
-
function getContact(
|
|
109
|
+
function getContact(contacts, i) {
|
|
64
110
|
try {
|
|
65
|
-
return
|
|
111
|
+
return contacts.get(i);
|
|
66
112
|
} catch {
|
|
67
113
|
return void 0;
|
|
68
114
|
}
|
|
69
115
|
}
|
|
116
|
+
function withContacts(data, read) {
|
|
117
|
+
const contacts = data.contact;
|
|
118
|
+
try {
|
|
119
|
+
return read(contacts);
|
|
120
|
+
} finally {
|
|
121
|
+
contacts.delete?.();
|
|
122
|
+
}
|
|
123
|
+
}
|
|
70
124
|
var CapsuleGeometry = class extends THREE11.BufferGeometry {
|
|
71
125
|
parameters;
|
|
72
126
|
constructor(radius = 1, length = 1, capSegments = 4, radialSegments = 8) {
|
|
@@ -311,6 +365,12 @@ var GeomBuilder = class {
|
|
|
311
365
|
};
|
|
312
366
|
|
|
313
367
|
// src/core/SceneLoader.ts
|
|
368
|
+
var JOINT_TYPE_NAMES = {
|
|
369
|
+
0: "free",
|
|
370
|
+
1: "ball",
|
|
371
|
+
2: "slide",
|
|
372
|
+
3: "hinge"
|
|
373
|
+
};
|
|
314
374
|
function getName(mjModel, address) {
|
|
315
375
|
let name = "";
|
|
316
376
|
let idx = address;
|
|
@@ -379,6 +439,276 @@ function getActuatedScalarQposAdr(mjModel, actuatorId) {
|
|
|
379
439
|
if (jntType !== 2 && jntType !== 3) return -1;
|
|
380
440
|
return mjModel.jnt_qposadr[jointId];
|
|
381
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
|
+
}
|
|
382
712
|
function sceneObjectToXml(obj) {
|
|
383
713
|
const joint = obj.freejoint ? "<freejoint/>" : "";
|
|
384
714
|
const pos = obj.position.map((v) => v.toFixed(3)).join(" ");
|
|
@@ -403,6 +733,15 @@ function ensureDir(mujoco, fname) {
|
|
|
403
733
|
}
|
|
404
734
|
}
|
|
405
735
|
}
|
|
736
|
+
function loadModelFromPath(mujoco, path) {
|
|
737
|
+
if (mujoco.MjModel.from_xml_path) {
|
|
738
|
+
return mujoco.MjModel.from_xml_path(path);
|
|
739
|
+
}
|
|
740
|
+
if (mujoco.MjModel.loadFromXML) {
|
|
741
|
+
return mujoco.MjModel.loadFromXML(path);
|
|
742
|
+
}
|
|
743
|
+
throw new Error("MuJoCo WASM module does not expose an XML path loader");
|
|
744
|
+
}
|
|
406
745
|
async function loadScene(mujoco, config, onProgress) {
|
|
407
746
|
try {
|
|
408
747
|
mujoco.FS.unmount("/working");
|
|
@@ -486,7 +825,7 @@ async function loadScene(mujoco, config, onProgress) {
|
|
|
486
825
|
}
|
|
487
826
|
}
|
|
488
827
|
onProgress?.("Loading model...");
|
|
489
|
-
const mjModel = mujoco
|
|
828
|
+
const mjModel = loadModelFromPath(mujoco, `/working/${config.sceneFile}`);
|
|
490
829
|
const mjData = new mujoco.MjData(mjModel);
|
|
491
830
|
if (config.homeJoints) {
|
|
492
831
|
const homeCount = Math.min(config.homeJoints.length, mjModel.nu);
|
|
@@ -609,7 +948,7 @@ function SceneRenderer(props) {
|
|
|
609
948
|
}
|
|
610
949
|
);
|
|
611
950
|
}
|
|
612
|
-
var
|
|
951
|
+
var JOINT_TYPE_NAMES2 = ["free", "ball", "slide", "hinge"];
|
|
613
952
|
var GEOM_TYPE_NAMES = ["plane", "hfield", "sphere", "capsule", "ellipsoid", "cylinder", "box", "mesh"];
|
|
614
953
|
var SENSOR_TYPE_NAMES = {
|
|
615
954
|
0: "touch",
|
|
@@ -662,6 +1001,22 @@ var SENSOR_TYPE_NAMES = {
|
|
|
662
1001
|
47: "plugin",
|
|
663
1002
|
48: "user"
|
|
664
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
|
+
}
|
|
665
1020
|
var _applyForce = new Float64Array(3);
|
|
666
1021
|
var _applyTorque = new Float64Array(3);
|
|
667
1022
|
var _applyPoint = new Float64Array(3);
|
|
@@ -859,7 +1214,7 @@ function MujocoSimProvider({
|
|
|
859
1214
|
if (externalApiRef) {
|
|
860
1215
|
if (typeof externalApiRef === "function") {
|
|
861
1216
|
externalApiRef(api2);
|
|
862
|
-
} else {
|
|
1217
|
+
} else if (isMutableApiRef(externalApiRef)) {
|
|
863
1218
|
externalApiRef.current = api2;
|
|
864
1219
|
}
|
|
865
1220
|
}
|
|
@@ -1080,18 +1435,20 @@ function MujocoSimProvider({
|
|
|
1080
1435
|
if (!model || !data) return [];
|
|
1081
1436
|
const contacts = [];
|
|
1082
1437
|
const ncon = data.ncon;
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1438
|
+
withContacts(data, (contactArray) => {
|
|
1439
|
+
for (let i = 0; i < ncon; i++) {
|
|
1440
|
+
const c = getContact(contactArray, i);
|
|
1441
|
+
if (!c) break;
|
|
1442
|
+
contacts.push({
|
|
1443
|
+
geom1: c.geom1,
|
|
1444
|
+
geom1Name: getName(model, model.name_geomadr[c.geom1]),
|
|
1445
|
+
geom2: c.geom2,
|
|
1446
|
+
geom2Name: getName(model, model.name_geomadr[c.geom2]),
|
|
1447
|
+
pos: [c.pos[0], c.pos[1], c.pos[2]],
|
|
1448
|
+
depth: c.dist
|
|
1449
|
+
});
|
|
1450
|
+
}
|
|
1451
|
+
});
|
|
1095
1452
|
return contacts;
|
|
1096
1453
|
}, []);
|
|
1097
1454
|
const getBodies = useCallback(() => {
|
|
@@ -1114,14 +1471,14 @@ function MujocoSimProvider({
|
|
|
1114
1471
|
const result = [];
|
|
1115
1472
|
for (let i = 0; i < model.njnt; i++) {
|
|
1116
1473
|
const type = model.jnt_type[i];
|
|
1117
|
-
const
|
|
1474
|
+
const range = [model.jnt_range[2 * i], model.jnt_range[2 * i + 1]];
|
|
1118
1475
|
result.push({
|
|
1119
1476
|
id: i,
|
|
1120
1477
|
name: getName(model, model.name_jntadr[i]),
|
|
1121
1478
|
type,
|
|
1122
|
-
typeName:
|
|
1123
|
-
range
|
|
1124
|
-
limited,
|
|
1479
|
+
typeName: JOINT_TYPE_NAMES2[type] ?? `unknown(${type})`,
|
|
1480
|
+
range,
|
|
1481
|
+
limited: range[0] < range[1],
|
|
1125
1482
|
bodyId: model.jnt_bodyid[i],
|
|
1126
1483
|
qposAdr: model.jnt_qposadr[i],
|
|
1127
1484
|
dofAdr: model.jnt_dofadr[i]
|
|
@@ -1173,6 +1530,18 @@ function MujocoSimProvider({
|
|
|
1173
1530
|
}
|
|
1174
1531
|
return result;
|
|
1175
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
|
+
}, []);
|
|
1176
1545
|
const getSensors = useCallback(() => {
|
|
1177
1546
|
const model = mjModelRef.current;
|
|
1178
1547
|
if (!model) return [];
|
|
@@ -1409,6 +1778,9 @@ function MujocoSimProvider({
|
|
|
1409
1778
|
getQvel,
|
|
1410
1779
|
setCtrl,
|
|
1411
1780
|
getCtrl: getCtrl2,
|
|
1781
|
+
getControlMap: getControlMapApi,
|
|
1782
|
+
getActuatedJoints: getActuatedJointsApi,
|
|
1783
|
+
resolveControlGroup: resolveControlGroupApi,
|
|
1412
1784
|
applyForce,
|
|
1413
1785
|
applyTorque: applyTorqueApi,
|
|
1414
1786
|
setExternalForce,
|
|
@@ -1455,6 +1827,9 @@ function MujocoSimProvider({
|
|
|
1455
1827
|
getQvel,
|
|
1456
1828
|
setCtrl,
|
|
1457
1829
|
getCtrl2,
|
|
1830
|
+
getControlMapApi,
|
|
1831
|
+
getActuatedJointsApi,
|
|
1832
|
+
resolveControlGroupApi,
|
|
1458
1833
|
applyForce,
|
|
1459
1834
|
applyTorqueApi,
|
|
1460
1835
|
setExternalForce,
|
|
@@ -1648,16 +2023,16 @@ var GenericIK = class {
|
|
|
1648
2023
|
* @param model MuJoCo model
|
|
1649
2024
|
* @param data MuJoCo data (qpos will be temporarily modified, then restored)
|
|
1650
2025
|
* @param siteId Index of the end-effector site to control
|
|
1651
|
-
* @param
|
|
2026
|
+
* @param qposAdr qpos addresses for scalar joints in solve order
|
|
1652
2027
|
* @param targetPos Target position in world frame
|
|
1653
2028
|
* @param targetQuat Target orientation in world frame
|
|
1654
|
-
* @param currentQ Current joint angles
|
|
2029
|
+
* @param currentQ Current joint angles matching qposAdr order
|
|
1655
2030
|
* @param opts Optional solver parameters
|
|
1656
2031
|
* @returns Joint angles array, or null if solver diverged
|
|
1657
2032
|
*/
|
|
1658
|
-
solve(model, data, siteId,
|
|
2033
|
+
solve(model, data, siteId, qposAdr, targetPos, targetQuat, currentQ, opts) {
|
|
1659
2034
|
const o = { ...DEFAULTS, ...opts };
|
|
1660
|
-
const n =
|
|
2035
|
+
const n = qposAdr.length;
|
|
1661
2036
|
const savedQpos = new Float64Array(data.qpos.length);
|
|
1662
2037
|
savedQpos.set(data.qpos);
|
|
1663
2038
|
const R_target = quatToMat3(targetQuat);
|
|
@@ -1674,8 +2049,9 @@ var GenericIK = class {
|
|
|
1674
2049
|
const pertSiteMat = new Float64Array(9);
|
|
1675
2050
|
let bestQ = null;
|
|
1676
2051
|
let bestErr = Infinity;
|
|
2052
|
+
if (n === 0) return null;
|
|
1677
2053
|
for (let iter = 0; iter < o.maxIterations; iter++) {
|
|
1678
|
-
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];
|
|
1679
2055
|
this.mujoco.mj_forward(model, data);
|
|
1680
2056
|
const sp = data.site_xpos;
|
|
1681
2057
|
const sm = data.site_xmat;
|
|
@@ -1704,8 +2080,9 @@ var GenericIK = class {
|
|
|
1704
2080
|
}
|
|
1705
2081
|
if (errNorm < o.tolerance) break;
|
|
1706
2082
|
for (let j = 0; j < n; j++) {
|
|
1707
|
-
const
|
|
1708
|
-
data.qpos[
|
|
2083
|
+
const adr = qposAdr[j];
|
|
2084
|
+
const saved = data.qpos[adr];
|
|
2085
|
+
data.qpos[adr] = q[j] + o.epsilon;
|
|
1709
2086
|
this.mujoco.mj_forward(model, data);
|
|
1710
2087
|
for (let i = 0; i < 3; i++) pertSitePos[i] = sp[off3 + i];
|
|
1711
2088
|
for (let i = 0; i < 9; i++) pertSiteMat[i] = sm[off9 + i];
|
|
@@ -1716,9 +2093,9 @@ var GenericIK = class {
|
|
|
1716
2093
|
J[3 * n + j] = dRot[0] / o.epsilon * o.rotWeight;
|
|
1717
2094
|
J[4 * n + j] = dRot[1] / o.epsilon * o.rotWeight;
|
|
1718
2095
|
J[5 * n + j] = dRot[2] / o.epsilon * o.rotWeight;
|
|
1719
|
-
data.qpos[
|
|
2096
|
+
data.qpos[adr] = saved;
|
|
1720
2097
|
}
|
|
1721
|
-
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];
|
|
1722
2099
|
for (let r = 0; r < 6; r++) {
|
|
1723
2100
|
for (let c = 0; c < 6; c++) {
|
|
1724
2101
|
let sum = 0;
|
|
@@ -1890,6 +2267,7 @@ var useIkController = createControllerHook(
|
|
|
1890
2267
|
const ikCalculatingRef = useRef(false);
|
|
1891
2268
|
const ikTargetRef = useRef(new THREE11.Group());
|
|
1892
2269
|
const siteIdRef = useRef(-1);
|
|
2270
|
+
const controlGroupRef = useRef(null);
|
|
1893
2271
|
const genericIkRef = useRef(new GenericIK(mujocoRef.current));
|
|
1894
2272
|
const firstIkEnableRef = useRef(true);
|
|
1895
2273
|
const needsInitialSync = useRef(true);
|
|
@@ -1905,31 +2283,39 @@ var useIkController = createControllerHook(
|
|
|
1905
2283
|
useEffect(() => {
|
|
1906
2284
|
if (!config) {
|
|
1907
2285
|
siteIdRef.current = -1;
|
|
2286
|
+
controlGroupRef.current = null;
|
|
1908
2287
|
return;
|
|
1909
2288
|
}
|
|
1910
2289
|
const model = mjModelRef.current;
|
|
1911
2290
|
if (!model || status !== "ready") {
|
|
1912
2291
|
siteIdRef.current = -1;
|
|
2292
|
+
controlGroupRef.current = null;
|
|
1913
2293
|
return;
|
|
1914
2294
|
}
|
|
1915
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
|
+
});
|
|
1916
2301
|
const data = mjDataRef.current;
|
|
1917
2302
|
if (data && ikTargetRef.current) {
|
|
1918
2303
|
syncGizmoToSite(data, siteIdRef.current, ikTargetRef.current);
|
|
1919
2304
|
}
|
|
1920
|
-
}, [config?.siteName, status, mjModelRef, mjDataRef, config]);
|
|
2305
|
+
}, [config?.siteName, config?.numJoints, config?.joints, config?.actuators, status, mjModelRef, mjDataRef, config]);
|
|
1921
2306
|
const ikSolveFn = useCallback(
|
|
1922
2307
|
(pos, quat, currentQ) => {
|
|
1923
2308
|
if (!config) return null;
|
|
1924
2309
|
if (config.ikSolveFn) return config.ikSolveFn(pos, quat, currentQ);
|
|
1925
2310
|
const model = mjModelRef.current;
|
|
1926
2311
|
const data = mjDataRef.current;
|
|
1927
|
-
|
|
2312
|
+
const controlGroup = controlGroupRef.current;
|
|
2313
|
+
if (!model || !data || !controlGroup || siteIdRef.current === -1) return null;
|
|
1928
2314
|
return genericIkRef.current.solve(
|
|
1929
2315
|
model,
|
|
1930
2316
|
data,
|
|
1931
2317
|
siteIdRef.current,
|
|
1932
|
-
|
|
2318
|
+
controlGroup.qposAdr,
|
|
1933
2319
|
pos,
|
|
1934
2320
|
quat,
|
|
1935
2321
|
currentQ,
|
|
@@ -1968,12 +2354,17 @@ var useIkController = createControllerHook(
|
|
|
1968
2354
|
const target = ikTargetRef.current;
|
|
1969
2355
|
if (!target) return;
|
|
1970
2356
|
ikCalculatingRef.current = true;
|
|
1971
|
-
const
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
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);
|
|
1975
2366
|
if (solution) {
|
|
1976
|
-
|
|
2367
|
+
controlGroup.writeCtrl(data, solution);
|
|
1977
2368
|
}
|
|
1978
2369
|
});
|
|
1979
2370
|
useEffect(() => {
|
|
@@ -2255,18 +2646,20 @@ function ContactMarkers({
|
|
|
2255
2646
|
}
|
|
2256
2647
|
const ncon = data.ncon;
|
|
2257
2648
|
const count = Math.min(ncon, maxContacts);
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
|
|
2649
|
+
let resolvedCount = count;
|
|
2650
|
+
withContacts(data, (contactArray) => {
|
|
2651
|
+
for (let i = 0; i < count; i++) {
|
|
2652
|
+
const c = getContact(contactArray, i);
|
|
2653
|
+
if (!c) {
|
|
2654
|
+
resolvedCount = i;
|
|
2655
|
+
return;
|
|
2656
|
+
}
|
|
2657
|
+
_dummy.position.set(c.pos[0], c.pos[1], c.pos[2]);
|
|
2658
|
+
_dummy.updateMatrix();
|
|
2659
|
+
mesh.setMatrixAt(i, _dummy.matrix);
|
|
2264
2660
|
}
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
mesh.setMatrixAt(i, _dummy.matrix);
|
|
2268
|
-
}
|
|
2269
|
-
mesh.count = count;
|
|
2661
|
+
});
|
|
2662
|
+
mesh.count = resolvedCount;
|
|
2270
2663
|
mesh.instanceMatrix.needsUpdate = true;
|
|
2271
2664
|
});
|
|
2272
2665
|
if (status !== "ready") return null;
|
|
@@ -2807,22 +3200,24 @@ function Debug({
|
|
|
2807
3200
|
if (!data || pool.length === 0) return;
|
|
2808
3201
|
const ncon = data.ncon;
|
|
2809
3202
|
let arrowIdx = 0;
|
|
2810
|
-
|
|
2811
|
-
|
|
2812
|
-
|
|
2813
|
-
|
|
2814
|
-
|
|
2815
|
-
|
|
2816
|
-
|
|
2817
|
-
|
|
2818
|
-
|
|
2819
|
-
|
|
2820
|
-
|
|
2821
|
-
|
|
2822
|
-
|
|
2823
|
-
|
|
3203
|
+
withContacts(data, (contactArray) => {
|
|
3204
|
+
for (let i = 0; i < Math.min(ncon, MAX_CONTACT_ARROWS); i++) {
|
|
3205
|
+
const c = getContact(contactArray, i);
|
|
3206
|
+
if (!c) break;
|
|
3207
|
+
_contactPos.set(c.pos[0], c.pos[1], c.pos[2]);
|
|
3208
|
+
_contactNormal.set(c.frame[0], c.frame[1], c.frame[2]);
|
|
3209
|
+
const force = Math.abs(c.dist) * 100;
|
|
3210
|
+
const length = Math.min(force * 0.01, 0.1);
|
|
3211
|
+
if (length > 1e-3 && arrowIdx < pool.length) {
|
|
3212
|
+
const arrow = pool[arrowIdx];
|
|
3213
|
+
arrow.position.copy(_contactPos);
|
|
3214
|
+
arrow.setDirection(_contactNormal);
|
|
3215
|
+
arrow.setLength(length, length * 0.3, length * 0.15);
|
|
3216
|
+
arrow.visible = true;
|
|
3217
|
+
arrowIdx++;
|
|
3218
|
+
}
|
|
2824
3219
|
}
|
|
2825
|
-
}
|
|
3220
|
+
});
|
|
2826
3221
|
for (let i = arrowIdx; i < pool.length; i++) {
|
|
2827
3222
|
pool[i].visible = false;
|
|
2828
3223
|
}
|
|
@@ -3053,23 +3448,25 @@ function useContacts(bodyName, callback) {
|
|
|
3053
3448
|
}
|
|
3054
3449
|
const contacts = [];
|
|
3055
3450
|
const filterBody = bodyIdRef.current;
|
|
3056
|
-
|
|
3057
|
-
|
|
3058
|
-
|
|
3059
|
-
|
|
3060
|
-
|
|
3061
|
-
|
|
3062
|
-
|
|
3451
|
+
withContacts(data, (contactArray) => {
|
|
3452
|
+
for (let i = 0; i < ncon; i++) {
|
|
3453
|
+
const c = getContact(contactArray, i);
|
|
3454
|
+
if (!c) break;
|
|
3455
|
+
if (filterBody >= 0) {
|
|
3456
|
+
const b1 = model.geom_bodyid[c.geom1];
|
|
3457
|
+
const b2 = model.geom_bodyid[c.geom2];
|
|
3458
|
+
if (b1 !== filterBody && b2 !== filterBody) continue;
|
|
3459
|
+
}
|
|
3460
|
+
contacts.push({
|
|
3461
|
+
geom1: c.geom1,
|
|
3462
|
+
geom1Name: getGeomNameCached(model, c.geom1),
|
|
3463
|
+
geom2: c.geom2,
|
|
3464
|
+
geom2Name: getGeomNameCached(model, c.geom2),
|
|
3465
|
+
pos: [c.pos[0], c.pos[1], c.pos[2]],
|
|
3466
|
+
depth: c.dist
|
|
3467
|
+
});
|
|
3063
3468
|
}
|
|
3064
|
-
|
|
3065
|
-
geom1: c.geom1,
|
|
3066
|
-
geom1Name: getGeomNameCached(model, c.geom1),
|
|
3067
|
-
geom2: c.geom2,
|
|
3068
|
-
geom2Name: getGeomNameCached(model, c.geom2),
|
|
3069
|
-
pos: [c.pos[0], c.pos[1], c.pos[2]],
|
|
3070
|
-
depth: c.dist
|
|
3071
|
-
});
|
|
3072
|
-
}
|
|
3469
|
+
});
|
|
3073
3470
|
contactsRef.current = contacts;
|
|
3074
3471
|
callbackRef.current?.(contacts);
|
|
3075
3472
|
});
|
|
@@ -4058,7 +4455,7 @@ function useCameraAnimation() {
|
|
|
4058
4455
|
* WASM fields used: model.ntendon, model.ten_wrapadr, model.ten_wrapnum
|
|
4059
4456
|
* data.wrap_xpos, data.ten_wrapadr (runtime)
|
|
4060
4457
|
*
|
|
4061
|
-
* Note: ten_rgba and ten_width
|
|
4458
|
+
* Note: ten_rgba and ten_width may not be available in all MuJoCo WASM builds.
|
|
4062
4459
|
* Tendons use a default color and width.
|
|
4063
4460
|
*/
|
|
4064
4461
|
/**
|
|
@@ -4176,6 +4573,6 @@ function useCameraAnimation() {
|
|
|
4176
4573
|
* useCameraAnimation — composable camera animation hook.
|
|
4177
4574
|
*/
|
|
4178
4575
|
|
|
4179
|
-
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 };
|
|
4180
4577
|
//# sourceMappingURL=index.js.map
|
|
4181
4578
|
//# sourceMappingURL=index.js.map
|