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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mujoco-react",
|
|
3
|
-
"version": "8.
|
|
3
|
+
"version": "8.4.0",
|
|
4
4
|
"description": "Composable React Three Fiber building blocks for MuJoCo WASM simulations",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
"license": "Apache-2.0",
|
|
35
35
|
"repository": {
|
|
36
36
|
"type": "git",
|
|
37
|
-
"url": "https://github.com/noah-wardlow/mujoco-react"
|
|
37
|
+
"url": "git+https://github.com/noah-wardlow/mujoco-react.git"
|
|
38
38
|
},
|
|
39
39
|
"scripts": {
|
|
40
40
|
"build": "tsup",
|
|
@@ -48,7 +48,7 @@
|
|
|
48
48
|
"three": ">=0.160.0"
|
|
49
49
|
},
|
|
50
50
|
"dependencies": {
|
|
51
|
-
"mujoco
|
|
51
|
+
"@mujoco/mujoco": "^3.9.0"
|
|
52
52
|
},
|
|
53
53
|
"devDependencies": {
|
|
54
54
|
"@react-three/drei": "^10.7.7",
|
|
@@ -13,7 +13,7 @@ import { useFrame } from '@react-three/fiber';
|
|
|
13
13
|
import type { ThreeElements } from '@react-three/fiber';
|
|
14
14
|
import * as THREE from 'three';
|
|
15
15
|
import { useMujocoContext } from '../core/MujocoSimProvider';
|
|
16
|
-
import { getContact } from '../types';
|
|
16
|
+
import { getContact, withContacts } from '../types';
|
|
17
17
|
|
|
18
18
|
const _dummy = new THREE.Object3D();
|
|
19
19
|
|
|
@@ -49,19 +49,21 @@ export function ContactMarkers({
|
|
|
49
49
|
const ncon = data.ncon;
|
|
50
50
|
const count = Math.min(ncon, maxContacts);
|
|
51
51
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
52
|
+
let resolvedCount = count;
|
|
53
|
+
withContacts(data, (contactArray) => {
|
|
54
|
+
for (let i = 0; i < count; i++) {
|
|
55
|
+
const c = getContact(contactArray, i);
|
|
56
|
+
if (!c) {
|
|
57
|
+
resolvedCount = i;
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
_dummy.position.set(c.pos[0], c.pos[1], c.pos[2]);
|
|
61
|
+
_dummy.updateMatrix();
|
|
62
|
+
mesh.setMatrixAt(i, _dummy.matrix);
|
|
58
63
|
}
|
|
59
|
-
|
|
60
|
-
_dummy.updateMatrix();
|
|
61
|
-
mesh.setMatrixAt(i, _dummy.matrix);
|
|
62
|
-
}
|
|
64
|
+
});
|
|
63
65
|
|
|
64
|
-
mesh.count =
|
|
66
|
+
mesh.count = resolvedCount;
|
|
65
67
|
mesh.instanceMatrix.needsUpdate = true;
|
|
66
68
|
});
|
|
67
69
|
|
package/src/components/Debug.tsx
CHANGED
|
@@ -11,7 +11,7 @@ import type { ThreeElements } from '@react-three/fiber';
|
|
|
11
11
|
import * as THREE from 'three';
|
|
12
12
|
import { useMujocoContext } from '../core/MujocoSimProvider';
|
|
13
13
|
import { getName } from '../core/SceneLoader';
|
|
14
|
-
import { getContact } from '../types';
|
|
14
|
+
import { getContact, withContacts } from '../types';
|
|
15
15
|
import type { DebugProps } from '../types';
|
|
16
16
|
|
|
17
17
|
const JOINT_COLORS: Record<number, number> = {
|
|
@@ -330,22 +330,24 @@ export function Debug({
|
|
|
330
330
|
const ncon = data.ncon;
|
|
331
331
|
let arrowIdx = 0;
|
|
332
332
|
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
333
|
+
withContacts(data, (contactArray) => {
|
|
334
|
+
for (let i = 0; i < Math.min(ncon, MAX_CONTACT_ARROWS); i++) {
|
|
335
|
+
const c = getContact(contactArray, i);
|
|
336
|
+
if (!c) break;
|
|
337
|
+
_contactPos.set(c.pos[0], c.pos[1], c.pos[2]);
|
|
338
|
+
_contactNormal.set(c.frame[0], c.frame[1], c.frame[2]);
|
|
339
|
+
const force = Math.abs(c.dist) * 100;
|
|
340
|
+
const length = Math.min(force * 0.01, 0.1);
|
|
341
|
+
if (length > 0.001 && arrowIdx < pool.length) {
|
|
342
|
+
const arrow = pool[arrowIdx];
|
|
343
|
+
arrow.position.copy(_contactPos);
|
|
344
|
+
arrow.setDirection(_contactNormal);
|
|
345
|
+
arrow.setLength(length, length * 0.3, length * 0.15);
|
|
346
|
+
arrow.visible = true;
|
|
347
|
+
arrowIdx++;
|
|
348
|
+
}
|
|
347
349
|
}
|
|
348
|
-
}
|
|
350
|
+
});
|
|
349
351
|
|
|
350
352
|
// Hide unused arrows
|
|
351
353
|
for (let i = arrowIdx; i < pool.length; i++) {
|
|
@@ -39,7 +39,7 @@ export function FlexRenderer(props: Omit<ThreeElements['group'], 'ref'>) {
|
|
|
39
39
|
const positions = new Float32Array(vertNum * 3);
|
|
40
40
|
geometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));
|
|
41
41
|
|
|
42
|
-
// Note: flex_faceadr/flex_facenum/flex_face
|
|
42
|
+
// Note: flex_faceadr/flex_facenum/flex_face may not be available in all MuJoCo WASM builds.
|
|
43
43
|
// Without face data we render as a point cloud. If future WASM versions expose
|
|
44
44
|
// face arrays, index-based triangle rendering can be added here.
|
|
45
45
|
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* WASM fields used: model.ntendon, model.ten_wrapadr, model.ten_wrapnum
|
|
8
8
|
* data.wrap_xpos, data.ten_wrapadr (runtime)
|
|
9
9
|
*
|
|
10
|
-
* Note: ten_rgba and ten_width
|
|
10
|
+
* Note: ten_rgba and ten_width may not be available in all MuJoCo WASM builds.
|
|
11
11
|
* Tendons use a default color and width.
|
|
12
12
|
*/
|
|
13
13
|
|
package/src/core/GenericIK.ts
CHANGED
|
@@ -41,10 +41,10 @@ export class GenericIK {
|
|
|
41
41
|
* @param model MuJoCo model
|
|
42
42
|
* @param data MuJoCo data (qpos will be temporarily modified, then restored)
|
|
43
43
|
* @param siteId Index of the end-effector site to control
|
|
44
|
-
* @param
|
|
44
|
+
* @param qposAdr qpos addresses for scalar joints in solve order
|
|
45
45
|
* @param targetPos Target position in world frame
|
|
46
46
|
* @param targetQuat Target orientation in world frame
|
|
47
|
-
* @param currentQ Current joint angles
|
|
47
|
+
* @param currentQ Current joint angles matching qposAdr order
|
|
48
48
|
* @param opts Optional solver parameters
|
|
49
49
|
* @returns Joint angles array, or null if solver diverged
|
|
50
50
|
*/
|
|
@@ -52,14 +52,14 @@ export class GenericIK {
|
|
|
52
52
|
model: MujocoModel,
|
|
53
53
|
data: MujocoData,
|
|
54
54
|
siteId: number,
|
|
55
|
-
|
|
55
|
+
qposAdr: ArrayLike<number>,
|
|
56
56
|
targetPos: THREE.Vector3,
|
|
57
57
|
targetQuat: THREE.Quaternion,
|
|
58
|
-
currentQ: number
|
|
58
|
+
currentQ: ArrayLike<number>,
|
|
59
59
|
opts?: Partial<GenericIKOptions>
|
|
60
60
|
): number[] | null {
|
|
61
61
|
const o = { ...DEFAULTS, ...opts };
|
|
62
|
-
const n =
|
|
62
|
+
const n = qposAdr.length;
|
|
63
63
|
|
|
64
64
|
// Save full qpos so we can restore after solving
|
|
65
65
|
const savedQpos = new Float64Array(data.qpos.length);
|
|
@@ -86,9 +86,11 @@ export class GenericIK {
|
|
|
86
86
|
let bestQ: number[] | null = null;
|
|
87
87
|
let bestErr = Infinity;
|
|
88
88
|
|
|
89
|
+
if (n === 0) return null;
|
|
90
|
+
|
|
89
91
|
for (let iter = 0; iter < o.maxIterations; iter++) {
|
|
90
92
|
// Set joints and run FK
|
|
91
|
-
for (let i = 0; i < n; i++) data.qpos[i] = q[i];
|
|
93
|
+
for (let i = 0; i < n; i++) data.qpos[qposAdr[i]] = q[i];
|
|
92
94
|
this.mujoco.mj_forward(model, data);
|
|
93
95
|
|
|
94
96
|
// Read current site pose
|
|
@@ -130,8 +132,9 @@ export class GenericIK {
|
|
|
130
132
|
|
|
131
133
|
// Compute Jacobian via finite differences
|
|
132
134
|
for (let j = 0; j < n; j++) {
|
|
133
|
-
const
|
|
134
|
-
data.qpos[
|
|
135
|
+
const adr = qposAdr[j];
|
|
136
|
+
const saved = data.qpos[adr];
|
|
137
|
+
data.qpos[adr] = q[j] + o.epsilon;
|
|
135
138
|
this.mujoco.mj_forward(model, data);
|
|
136
139
|
|
|
137
140
|
for (let i = 0; i < 3; i++) pertSitePos[i] = sp[off3 + i];
|
|
@@ -150,11 +153,11 @@ export class GenericIK {
|
|
|
150
153
|
J[5 * n + j] = (dRot[2] / o.epsilon) * o.rotWeight;
|
|
151
154
|
|
|
152
155
|
// Restore joint
|
|
153
|
-
data.qpos[
|
|
156
|
+
data.qpos[adr] = saved;
|
|
154
157
|
}
|
|
155
158
|
|
|
156
159
|
// Restore base FK state for next iteration
|
|
157
|
-
for (let i = 0; i < n; i++) data.qpos[i] = q[i];
|
|
160
|
+
for (let i = 0; i < n; i++) data.qpos[qposAdr[i]] = q[i];
|
|
158
161
|
|
|
159
162
|
// Damped least squares: Δq = Jᵀ (J Jᵀ + λI)⁻¹ error
|
|
160
163
|
// 1. Compute JJᵀ (6×6)
|
|
@@ -3,9 +3,10 @@
|
|
|
3
3
|
* SPDX-License-Identifier: Apache-2.0
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import loadMujoco from 'mujoco
|
|
6
|
+
import loadMujoco from '@mujoco/mujoco';
|
|
7
|
+
import defaultMujocoWasmUrl from '@mujoco/mujoco/mujoco.wasm?url';
|
|
7
8
|
import { createContext, useContext, useEffect, useRef, useState } from 'react';
|
|
8
|
-
import { MujocoModule, MujocoContextValue } from '../types';
|
|
9
|
+
import type { MujocoModule, MujocoContextValue } from '../types';
|
|
9
10
|
|
|
10
11
|
const MujocoContext = createContext<MujocoContextValue>({
|
|
11
12
|
mujoco: null,
|
|
@@ -20,19 +21,77 @@ export function useMujocoWasm(): MujocoContextValue {
|
|
|
20
21
|
return useContext(MujocoContext);
|
|
21
22
|
}
|
|
22
23
|
|
|
23
|
-
|
|
24
|
+
export type MujocoWasmVariant = 'single' | 'threaded' | 'auto';
|
|
25
|
+
|
|
26
|
+
export interface MujocoLoaderOptions {
|
|
27
|
+
locateFile?: (path: string) => string;
|
|
28
|
+
printErr?: (text: string) => void;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export type MujocoLoader = (options?: MujocoLoaderOptions) => Promise<unknown>;
|
|
32
|
+
|
|
33
|
+
export interface MujocoProviderProps {
|
|
24
34
|
wasmUrl?: string;
|
|
35
|
+
/** Optional URL for the multi-threaded WASM asset. */
|
|
36
|
+
mtWasmUrl?: string;
|
|
37
|
+
/**
|
|
38
|
+
* Optional official multi-threaded loader, usually imported from
|
|
39
|
+
* `@mujoco/mujoco/mt`. It is supplied by the app so the default package path
|
|
40
|
+
* does not force every bundler to process the threaded Emscripten build.
|
|
41
|
+
*/
|
|
42
|
+
threadedLoader?: MujocoLoader;
|
|
43
|
+
/**
|
|
44
|
+
* MuJoCo WASM build to load. `single` is the default and works everywhere.
|
|
45
|
+
* `threaded` requires `threadedLoader` and cross-origin isolation. `auto`
|
|
46
|
+
* uses threaded only when both conditions are satisfied.
|
|
47
|
+
*/
|
|
48
|
+
wasmVariant?: MujocoWasmVariant;
|
|
25
49
|
/** Timeout in ms for WASM module load. Default: 30000. */
|
|
26
50
|
timeout?: number;
|
|
27
51
|
children: React.ReactNode;
|
|
28
52
|
onError?: (error: Error) => void;
|
|
29
53
|
}
|
|
30
54
|
|
|
55
|
+
function canUseThreadedWasm(): boolean {
|
|
56
|
+
return typeof globalThis !== 'undefined' && globalThis.crossOriginIsolated === true;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function isMujocoModule(value: unknown): value is MujocoModule {
|
|
60
|
+
return typeof value === 'object'
|
|
61
|
+
&& value !== null
|
|
62
|
+
&& 'FS' in value
|
|
63
|
+
&& 'MjModel' in value
|
|
64
|
+
&& 'MjData' in value
|
|
65
|
+
&& 'mj_step' in value;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function hasWasmUrl(value: string | undefined): value is string {
|
|
69
|
+
return typeof value === 'string' && value.length > 0;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function resolveWasmVariant(
|
|
73
|
+
variant: MujocoWasmVariant | undefined,
|
|
74
|
+
threadedLoader: MujocoLoader | undefined,
|
|
75
|
+
mtWasmUrl: string | undefined
|
|
76
|
+
): 'single' | 'threaded' {
|
|
77
|
+
if (variant === 'threaded') return 'threaded';
|
|
78
|
+
if (variant === 'auto' && threadedLoader && mtWasmUrl && canUseThreadedWasm()) return 'threaded';
|
|
79
|
+
return 'single';
|
|
80
|
+
}
|
|
81
|
+
|
|
31
82
|
/**
|
|
32
83
|
* MujocoProvider — WASM / module lifecycle.
|
|
33
84
|
* Loads the MuJoCo WASM module on mount and provides it to children via context.
|
|
34
85
|
*/
|
|
35
|
-
export function MujocoProvider({
|
|
86
|
+
export function MujocoProvider({
|
|
87
|
+
wasmUrl,
|
|
88
|
+
mtWasmUrl,
|
|
89
|
+
threadedLoader,
|
|
90
|
+
wasmVariant = 'single',
|
|
91
|
+
timeout = 30000,
|
|
92
|
+
children,
|
|
93
|
+
onError,
|
|
94
|
+
}: MujocoProviderProps) {
|
|
36
95
|
const [status, setStatus] = useState<'loading' | 'ready' | 'error'>('loading');
|
|
37
96
|
const [error, setError] = useState<string | null>(null);
|
|
38
97
|
const moduleRef = useRef<MujocoModule | null>(null);
|
|
@@ -41,8 +100,31 @@ export function MujocoProvider({ wasmUrl, timeout = 30000, children, onError }:
|
|
|
41
100
|
useEffect(() => {
|
|
42
101
|
isMounted.current = true;
|
|
43
102
|
|
|
44
|
-
const
|
|
45
|
-
|
|
103
|
+
const variant = resolveWasmVariant(wasmVariant, threadedLoader, mtWasmUrl);
|
|
104
|
+
if (variant === 'threaded' && !threadedLoader) {
|
|
105
|
+
const err = new Error('MujocoProvider wasmVariant="threaded" requires a threadedLoader from @mujoco/mujoco/mt');
|
|
106
|
+
setError(err.message);
|
|
107
|
+
setStatus('error');
|
|
108
|
+
onError?.(err);
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
let selectedWasmUrl = wasmUrl ?? defaultMujocoWasmUrl;
|
|
112
|
+
|
|
113
|
+
if (variant === 'threaded') {
|
|
114
|
+
if (!hasWasmUrl(mtWasmUrl)) {
|
|
115
|
+
const err = new Error('MujocoProvider wasmVariant="threaded" requires mtWasmUrl from @mujoco/mujoco/mt/mujoco.wasm?url');
|
|
116
|
+
setError(err.message);
|
|
117
|
+
setStatus('error');
|
|
118
|
+
onError?.(err);
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
selectedWasmUrl = mtWasmUrl;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const load: MujocoLoader = variant === 'threaded' && threadedLoader ? threadedLoader : loadMujoco;
|
|
125
|
+
|
|
126
|
+
const wasmPromise = load({
|
|
127
|
+
locateFile: (path: string) => path.endsWith('.wasm') ? selectedWasmUrl : path,
|
|
46
128
|
printErr: (text: string) => {
|
|
47
129
|
if (text.includes('Aborted') && isMounted.current) {
|
|
48
130
|
setError('Simulation crashed. Reload page.');
|
|
@@ -58,7 +140,10 @@ export function MujocoProvider({ wasmUrl, timeout = 30000, children, onError }:
|
|
|
58
140
|
Promise.race([wasmPromise, timeoutPromise])
|
|
59
141
|
.then((inst: unknown) => {
|
|
60
142
|
if (isMounted.current) {
|
|
61
|
-
|
|
143
|
+
if (!isMujocoModule(inst)) {
|
|
144
|
+
throw new Error('MuJoCo WASM module initialized with an unexpected shape');
|
|
145
|
+
}
|
|
146
|
+
moduleRef.current = inst;
|
|
62
147
|
setStatus('ready');
|
|
63
148
|
}
|
|
64
149
|
})
|
|
@@ -74,7 +159,7 @@ export function MujocoProvider({ wasmUrl, timeout = 30000, children, onError }:
|
|
|
74
159
|
return () => {
|
|
75
160
|
isMounted.current = false;
|
|
76
161
|
};
|
|
77
|
-
}, [wasmUrl, timeout]);
|
|
162
|
+
}, [wasmUrl, mtWasmUrl, threadedLoader, wasmVariant, timeout, onError]);
|
|
78
163
|
|
|
79
164
|
return (
|
|
80
165
|
<MujocoContext.Provider
|
|
@@ -14,11 +14,14 @@ import {
|
|
|
14
14
|
useState,
|
|
15
15
|
} from 'react';
|
|
16
16
|
import * as THREE from 'three';
|
|
17
|
-
import { MujocoData, MujocoModel, MujocoModule, getContact } from '../types';
|
|
17
|
+
import { MujocoData, MujocoModel, MujocoModule, getContact, withContacts } from '../types';
|
|
18
18
|
import { SceneRenderer } from '../components/SceneRenderer';
|
|
19
19
|
import {
|
|
20
|
+
ActuatedJointInfo,
|
|
20
21
|
ActuatorInfo,
|
|
21
22
|
BodyInfo,
|
|
23
|
+
ControlGroupInfo,
|
|
24
|
+
ControlGroupSelector,
|
|
22
25
|
ContactInfo,
|
|
23
26
|
GeomInfo,
|
|
24
27
|
JointInfo,
|
|
@@ -40,7 +43,10 @@ import {
|
|
|
40
43
|
findSensorByName,
|
|
41
44
|
findActuatorByName,
|
|
42
45
|
getActuatedScalarQposAdr,
|
|
46
|
+
getActuatedJoints as getActuatedJointsFromModel,
|
|
47
|
+
getControlMap as getControlMapFromModel,
|
|
43
48
|
getName,
|
|
49
|
+
resolveControlGroup as resolveControlGroupFromModel,
|
|
44
50
|
} from './SceneLoader';
|
|
45
51
|
|
|
46
52
|
// ---- Joint type names ----
|
|
@@ -65,6 +71,24 @@ const SENSOR_TYPE_NAMES: Record<number, string> = {
|
|
|
65
71
|
45: 'clock', 46: 'tactile', 47: 'plugin', 48: 'user',
|
|
66
72
|
};
|
|
67
73
|
|
|
74
|
+
const EMPTY_CONTROL_GROUP: ControlGroupInfo = {
|
|
75
|
+
joints: [],
|
|
76
|
+
actuators: [],
|
|
77
|
+
qposAdr: [],
|
|
78
|
+
dofAdr: [],
|
|
79
|
+
ctrlAdr: [],
|
|
80
|
+
readQpos: () => new Float64Array(0),
|
|
81
|
+
readCtrl: () => new Float64Array(0),
|
|
82
|
+
writeQpos: () => {},
|
|
83
|
+
writeCtrl: () => {},
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
function isMutableApiRef(
|
|
87
|
+
ref: React.ForwardedRef<MujocoSimAPI>
|
|
88
|
+
): ref is React.MutableRefObject<MujocoSimAPI | null> {
|
|
89
|
+
return typeof ref === 'object' && ref !== null && 'current' in ref;
|
|
90
|
+
}
|
|
91
|
+
|
|
68
92
|
// Preallocated force/torque temps for applyForce/applyTorque
|
|
69
93
|
const _applyForce = new Float64Array(3);
|
|
70
94
|
const _applyTorque = new Float64Array(3);
|
|
@@ -329,8 +353,8 @@ export function MujocoSimProvider({
|
|
|
329
353
|
if (externalApiRef) {
|
|
330
354
|
if (typeof externalApiRef === 'function') {
|
|
331
355
|
externalApiRef(api);
|
|
332
|
-
} else {
|
|
333
|
-
|
|
356
|
+
} else if (isMutableApiRef(externalApiRef)) {
|
|
357
|
+
externalApiRef.current = api;
|
|
334
358
|
}
|
|
335
359
|
}
|
|
336
360
|
}
|
|
@@ -576,18 +600,20 @@ export function MujocoSimProvider({
|
|
|
576
600
|
if (!model || !data) return [];
|
|
577
601
|
const contacts: ContactInfo[] = [];
|
|
578
602
|
const ncon = data.ncon;
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
603
|
+
withContacts(data, (contactArray) => {
|
|
604
|
+
for (let i = 0; i < ncon; i++) {
|
|
605
|
+
const c = getContact(contactArray, i);
|
|
606
|
+
if (!c) break;
|
|
607
|
+
contacts.push({
|
|
608
|
+
geom1: c.geom1,
|
|
609
|
+
geom1Name: getName(model, model.name_geomadr[c.geom1]),
|
|
610
|
+
geom2: c.geom2,
|
|
611
|
+
geom2Name: getName(model, model.name_geomadr[c.geom2]),
|
|
612
|
+
pos: [c.pos[0], c.pos[1], c.pos[2]],
|
|
613
|
+
depth: c.dist,
|
|
614
|
+
});
|
|
615
|
+
}
|
|
616
|
+
});
|
|
591
617
|
return contacts;
|
|
592
618
|
}, []);
|
|
593
619
|
|
|
@@ -612,14 +638,14 @@ export function MujocoSimProvider({
|
|
|
612
638
|
const result: JointInfo[] = [];
|
|
613
639
|
for (let i = 0; i < model.njnt; i++) {
|
|
614
640
|
const type = model.jnt_type[i];
|
|
615
|
-
const
|
|
641
|
+
const range: [number, number] = [model.jnt_range[2 * i], model.jnt_range[2 * i + 1]];
|
|
616
642
|
result.push({
|
|
617
643
|
id: i,
|
|
618
644
|
name: getName(model, model.name_jntadr[i]),
|
|
619
645
|
type,
|
|
620
646
|
typeName: JOINT_TYPE_NAMES[type] ?? `unknown(${type})`,
|
|
621
|
-
range
|
|
622
|
-
limited,
|
|
647
|
+
range,
|
|
648
|
+
limited: range[0] < range[1],
|
|
623
649
|
bodyId: model.jnt_bodyid[i],
|
|
624
650
|
qposAdr: model.jnt_qposadr[i],
|
|
625
651
|
dofAdr: model.jnt_dofadr[i],
|
|
@@ -677,6 +703,21 @@ export function MujocoSimProvider({
|
|
|
677
703
|
return result;
|
|
678
704
|
}, []);
|
|
679
705
|
|
|
706
|
+
const getControlMapApi = useCallback((): ControlGroupInfo => {
|
|
707
|
+
const model = mjModelRef.current;
|
|
708
|
+
return model ? getControlMapFromModel(model) : EMPTY_CONTROL_GROUP;
|
|
709
|
+
}, []);
|
|
710
|
+
|
|
711
|
+
const getActuatedJointsApi = useCallback((): ActuatedJointInfo[] => {
|
|
712
|
+
const model = mjModelRef.current;
|
|
713
|
+
return model ? getActuatedJointsFromModel(model) : [];
|
|
714
|
+
}, []);
|
|
715
|
+
|
|
716
|
+
const resolveControlGroupApi = useCallback((selector: ControlGroupSelector): ControlGroupInfo | null => {
|
|
717
|
+
const model = mjModelRef.current;
|
|
718
|
+
return model ? resolveControlGroupFromModel(model, selector) : null;
|
|
719
|
+
}, []);
|
|
720
|
+
|
|
680
721
|
const getSensors = useCallback((): SensorInfo[] => {
|
|
681
722
|
const model = mjModelRef.current;
|
|
682
723
|
if (!model) return [];
|
|
@@ -937,6 +978,9 @@ export function MujocoSimProvider({
|
|
|
937
978
|
getQvel,
|
|
938
979
|
setCtrl,
|
|
939
980
|
getCtrl,
|
|
981
|
+
getControlMap: getControlMapApi,
|
|
982
|
+
getActuatedJoints: getActuatedJointsApi,
|
|
983
|
+
resolveControlGroup: resolveControlGroupApi,
|
|
940
984
|
applyForce,
|
|
941
985
|
applyTorque: applyTorqueApi,
|
|
942
986
|
setExternalForce,
|
|
@@ -968,6 +1012,7 @@ export function MujocoSimProvider({
|
|
|
968
1012
|
status, config, reset, setSpeed, togglePause, setPaused, step,
|
|
969
1013
|
getTime, getTimestep, applyKeyframe, saveState, restoreState,
|
|
970
1014
|
setQpos, setQvel, getQpos, getQvel, setCtrl, getCtrl,
|
|
1015
|
+
getControlMapApi, getActuatedJointsApi, resolveControlGroupApi,
|
|
971
1016
|
applyForce, applyTorqueApi, setExternalForce, applyGeneralizedForce,
|
|
972
1017
|
getSensorData, getContacts, getBodies, getJoints, getGeoms, getSites,
|
|
973
1018
|
getActuatorsApi, getSensors, getModelOption, setGravity, setTimestepApi,
|