mujoco-react 0.2.0 → 1.0.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 +287 -48
- package/dist/index.d.ts +215 -135
- package/dist/index.js +1176 -795
- package/dist/index.js.map +1 -1
- package/package.json +6 -4
- package/src/components/ContactMarkers.tsx +19 -22
- package/src/components/Debug.tsx +173 -36
- package/src/components/DragInteraction.tsx +5 -3
- package/src/components/FlexRenderer.tsx +3 -2
- package/src/components/IkController.tsx +262 -0
- package/src/components/IkGizmo.tsx +17 -25
- package/src/components/SceneLights.tsx +2 -112
- package/src/components/SceneRenderer.tsx +13 -8
- package/src/components/SelectionHighlight.tsx +2 -49
- package/src/components/TendonRenderer.tsx +93 -28
- package/src/components/TrajectoryPlayer.tsx +14 -10
- package/src/core/IkContext.tsx +40 -0
- package/src/core/MujocoCanvas.tsx +1 -5
- package/src/core/MujocoPhysics.tsx +79 -0
- package/src/core/MujocoProvider.tsx +12 -4
- package/src/core/MujocoSimProvider.tsx +56 -340
- package/src/core/SceneLoader.ts +45 -18
- package/src/core/createController.tsx +91 -0
- package/src/hooks/useCameraAnimation.ts +102 -0
- package/src/hooks/useContacts.ts +52 -22
- package/src/hooks/useJointState.ts +18 -2
- package/src/hooks/useSceneLights.ts +117 -0
- package/src/hooks/useSelectionHighlight.ts +65 -0
- package/src/index.ts +18 -1
- package/src/types.ts +53 -26
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mujoco-react",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "1.0.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,13 +34,12 @@
|
|
|
34
34
|
"license": "Apache-2.0",
|
|
35
35
|
"repository": {
|
|
36
36
|
"type": "git",
|
|
37
|
-
"url": "https://github.com/noah/mujoco-react"
|
|
37
|
+
"url": "https://github.com/noah-wardlow/mujoco-react"
|
|
38
38
|
},
|
|
39
39
|
"scripts": {
|
|
40
40
|
"build": "tsup",
|
|
41
41
|
"dev": "tsup --watch",
|
|
42
|
-
"typecheck": "tsc --noEmit"
|
|
43
|
-
"prepublishOnly": "npm run build"
|
|
42
|
+
"typecheck": "tsc --noEmit"
|
|
44
43
|
},
|
|
45
44
|
"peerDependencies": {
|
|
46
45
|
"@react-three/drei": ">=9",
|
|
@@ -54,9 +53,12 @@
|
|
|
54
53
|
"devDependencies": {
|
|
55
54
|
"@react-three/drei": "^10.7.7",
|
|
56
55
|
"@react-three/fiber": "^9.5.0",
|
|
56
|
+
"@semantic-release/changelog": "^6.0.3",
|
|
57
|
+
"@semantic-release/git": "^10.0.1",
|
|
57
58
|
"@types/react": "^19.0.0",
|
|
58
59
|
"@types/three": "^0.181.0",
|
|
59
60
|
"react": "^19.2.0",
|
|
61
|
+
"semantic-release": "^25.0.3",
|
|
60
62
|
"three": "^0.181.0",
|
|
61
63
|
"tsup": "^8.4.0",
|
|
62
64
|
"typescript": "~5.8.2"
|
|
@@ -10,17 +10,19 @@
|
|
|
10
10
|
|
|
11
11
|
import { useRef } from 'react';
|
|
12
12
|
import { useFrame } from '@react-three/fiber';
|
|
13
|
+
import type { ThreeElements } from '@react-three/fiber';
|
|
13
14
|
import * as THREE from 'three';
|
|
14
15
|
import { useMujocoSim } from '../core/MujocoSimProvider';
|
|
16
|
+
import { getContact } from '../types';
|
|
15
17
|
|
|
16
18
|
const _dummy = new THREE.Object3D();
|
|
17
19
|
|
|
18
20
|
interface ContactMarkersProps {
|
|
19
21
|
/** Maximum contacts to render. Default: 100. */
|
|
20
22
|
maxContacts?: number;
|
|
21
|
-
/** Sphere radius. Default: 0.
|
|
23
|
+
/** Sphere radius. Default: 0.008. */
|
|
22
24
|
radius?: number;
|
|
23
|
-
/** Color. Default: '#
|
|
25
|
+
/** Color. Default: '#22d3ee'. */
|
|
24
26
|
color?: string;
|
|
25
27
|
/** Show markers. Default: true. */
|
|
26
28
|
visible?: boolean;
|
|
@@ -28,10 +30,11 @@ interface ContactMarkersProps {
|
|
|
28
30
|
|
|
29
31
|
export function ContactMarkers({
|
|
30
32
|
maxContacts = 100,
|
|
31
|
-
radius = 0.
|
|
32
|
-
color = '#
|
|
33
|
+
radius = 0.008,
|
|
34
|
+
color = '#22d3ee',
|
|
33
35
|
visible = true,
|
|
34
|
-
|
|
36
|
+
...groupProps
|
|
37
|
+
}: ContactMarkersProps & Omit<ThreeElements['group'], 'ref' | 'visible'> = {}) {
|
|
35
38
|
const { mjDataRef, status } = useMujocoSim();
|
|
36
39
|
const meshRef = useRef<THREE.InstancedMesh>(null);
|
|
37
40
|
|
|
@@ -47,18 +50,15 @@ export function ContactMarkers({
|
|
|
47
50
|
const count = Math.min(ncon, maxContacts);
|
|
48
51
|
|
|
49
52
|
for (let i = 0; i < count; i++) {
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
if (!c) break;
|
|
53
|
-
_dummy.position.set(c.pos[0], c.pos[1], c.pos[2]);
|
|
54
|
-
_dummy.updateMatrix();
|
|
55
|
-
mesh.setMatrixAt(i, _dummy.matrix);
|
|
56
|
-
} catch {
|
|
57
|
-
// Contact access failed — stop here
|
|
53
|
+
const c = getContact(data, i);
|
|
54
|
+
if (!c) {
|
|
58
55
|
mesh.count = i;
|
|
59
56
|
mesh.instanceMatrix.needsUpdate = true;
|
|
60
57
|
return;
|
|
61
58
|
}
|
|
59
|
+
_dummy.position.set(c.pos[0], c.pos[1], c.pos[2]);
|
|
60
|
+
_dummy.updateMatrix();
|
|
61
|
+
mesh.setMatrixAt(i, _dummy.matrix);
|
|
62
62
|
}
|
|
63
63
|
|
|
64
64
|
mesh.count = count;
|
|
@@ -68,14 +68,11 @@ export function ContactMarkers({
|
|
|
68
68
|
if (status !== 'ready') return null;
|
|
69
69
|
|
|
70
70
|
return (
|
|
71
|
-
<
|
|
72
|
-
<
|
|
73
|
-
|
|
74
|
-
color={color}
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
roughness={0.5}
|
|
78
|
-
/>
|
|
79
|
-
</instancedMesh>
|
|
71
|
+
<group {...groupProps}>
|
|
72
|
+
<instancedMesh ref={meshRef} args={[undefined, undefined, maxContacts]} frustumCulled={false} renderOrder={999}>
|
|
73
|
+
<sphereGeometry args={[radius, 8, 8]} />
|
|
74
|
+
<meshBasicMaterial color={color} depthTest={false} />
|
|
75
|
+
</instancedMesh>
|
|
76
|
+
</group>
|
|
80
77
|
);
|
|
81
78
|
}
|
package/src/components/Debug.tsx
CHANGED
|
@@ -7,9 +7,11 @@
|
|
|
7
7
|
|
|
8
8
|
import { useEffect, useMemo, useRef } from 'react';
|
|
9
9
|
import { useFrame, useThree } from '@react-three/fiber';
|
|
10
|
+
import type { ThreeElements } from '@react-three/fiber';
|
|
10
11
|
import * as THREE from 'three';
|
|
11
12
|
import { useMujocoSim } from '../core/MujocoSimProvider';
|
|
12
13
|
import { getName } from '../core/SceneLoader';
|
|
14
|
+
import { getContact } from '../types';
|
|
13
15
|
import type { DebugProps } from '../types';
|
|
14
16
|
|
|
15
17
|
const JOINT_COLORS: Record<number, number> = {
|
|
@@ -19,6 +21,14 @@ const JOINT_COLORS: Record<number, number> = {
|
|
|
19
21
|
3: 0xffff00, // hinge - yellow
|
|
20
22
|
};
|
|
21
23
|
|
|
24
|
+
// Preallocated temps to avoid per-frame GC pressure
|
|
25
|
+
const _v3a = new THREE.Vector3();
|
|
26
|
+
const _v3b = new THREE.Vector3();
|
|
27
|
+
const _quat = new THREE.Quaternion();
|
|
28
|
+
const _contactPos = new THREE.Vector3();
|
|
29
|
+
const _contactNormal = new THREE.Vector3();
|
|
30
|
+
const MAX_CONTACT_ARROWS = 50;
|
|
31
|
+
|
|
22
32
|
/**
|
|
23
33
|
* Declarative debug visualization component.
|
|
24
34
|
* Renders wireframe geoms, site markers, joint axes, contact forces, COM markers, etc.
|
|
@@ -31,7 +41,8 @@ export function Debug({
|
|
|
31
41
|
showCOM = false,
|
|
32
42
|
showInertia = false,
|
|
33
43
|
showTendons = false,
|
|
34
|
-
|
|
44
|
+
...groupProps
|
|
45
|
+
}: DebugProps & Omit<ThreeElements['group'], 'ref'>) {
|
|
35
46
|
const { mjModelRef, mjDataRef, status } = useMujocoSim();
|
|
36
47
|
const { scene } = useThree();
|
|
37
48
|
const groupRef = useRef<THREE.Group>(null);
|
|
@@ -78,27 +89,92 @@ export function Debug({
|
|
|
78
89
|
}
|
|
79
90
|
}
|
|
80
91
|
|
|
81
|
-
// Site markers
|
|
92
|
+
// Site markers — scale based on site_size if available, else use geom_size of parent body
|
|
82
93
|
if (showSites) {
|
|
94
|
+
const siteSize = (model as Record<string, unknown>).site_size as Float64Array | undefined;
|
|
83
95
|
for (let i = 0; i < model.nsite; i++) {
|
|
84
|
-
|
|
85
|
-
|
|
96
|
+
// Determine marker radius: use site_size[3*i] if available, else estimate from parent body's geoms
|
|
97
|
+
let radius = 0.008;
|
|
98
|
+
if (siteSize) {
|
|
99
|
+
radius = Math.max(siteSize[3 * i] * 0.5, 0.004);
|
|
100
|
+
} else {
|
|
101
|
+
// Estimate from parent body's geom sizes
|
|
102
|
+
const bodyId = model.site_bodyid[i];
|
|
103
|
+
let maxGeomSize = 0;
|
|
104
|
+
for (let g = 0; g < model.ngeom; g++) {
|
|
105
|
+
if (model.geom_bodyid[g] === bodyId) {
|
|
106
|
+
maxGeomSize = Math.max(maxGeomSize, model.geom_size[3 * g]);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
if (maxGeomSize > 0) radius = maxGeomSize * 0.15;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const geometry = new THREE.OctahedronGeometry(radius);
|
|
113
|
+
const mat = new THREE.MeshBasicMaterial({ color: 0xff00ff, depthTest: false });
|
|
86
114
|
const mesh = new THREE.Mesh(geometry, mat);
|
|
115
|
+
mesh.renderOrder = 999;
|
|
116
|
+
mesh.frustumCulled = false;
|
|
87
117
|
mesh.userData.siteId = i;
|
|
118
|
+
|
|
119
|
+
// Label
|
|
120
|
+
const canvas = document.createElement('canvas');
|
|
121
|
+
canvas.width = 256;
|
|
122
|
+
canvas.height = 64;
|
|
123
|
+
const ctx = canvas.getContext('2d')!;
|
|
124
|
+
ctx.fillStyle = '#ff00ff';
|
|
125
|
+
ctx.font = 'bold 36px monospace';
|
|
126
|
+
ctx.textAlign = 'center';
|
|
127
|
+
ctx.fillText(getName(model, model.name_siteadr[i]), 128, 42);
|
|
128
|
+
const tex = new THREE.CanvasTexture(canvas);
|
|
129
|
+
const spriteMat = new THREE.SpriteMaterial({ map: tex, depthTest: false, transparent: true });
|
|
130
|
+
const sprite = new THREE.Sprite(spriteMat);
|
|
131
|
+
const labelScale = radius * 15;
|
|
132
|
+
sprite.scale.set(labelScale, labelScale * 0.25, 1);
|
|
133
|
+
sprite.position.y = radius * 2;
|
|
134
|
+
sprite.renderOrder = 999;
|
|
135
|
+
mesh.add(sprite);
|
|
136
|
+
|
|
88
137
|
sites.push(mesh);
|
|
89
138
|
}
|
|
90
139
|
}
|
|
91
140
|
|
|
92
|
-
// Joint axes
|
|
141
|
+
// Joint axes — scale arrow length based on parent body's geom sizes
|
|
93
142
|
if (showJoints) {
|
|
143
|
+
// Safely check for jnt_pos/jnt_axis on the WASM model
|
|
144
|
+
const jntPos = (model as Record<string, unknown>).jnt_pos as Float64Array | undefined;
|
|
145
|
+
const jntAxis = (model as Record<string, unknown>).jnt_axis as Float64Array | undefined;
|
|
146
|
+
|
|
94
147
|
for (let i = 0; i < model.njnt; i++) {
|
|
95
148
|
const type = model.jnt_type[i];
|
|
96
149
|
const color = JOINT_COLORS[type] ?? 0xffffff;
|
|
150
|
+
|
|
151
|
+
// Scale based on parent body geom size
|
|
152
|
+
const bodyId = model.jnt_bodyid[i];
|
|
153
|
+
let maxGeomSize = 0;
|
|
154
|
+
for (let g = 0; g < model.ngeom; g++) {
|
|
155
|
+
if (model.geom_bodyid[g] === bodyId) {
|
|
156
|
+
maxGeomSize = Math.max(maxGeomSize, model.geom_size[3 * g]);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
const arrowLen = Math.max(maxGeomSize * 0.8, 0.05);
|
|
160
|
+
|
|
97
161
|
const arrow = new THREE.ArrowHelper(
|
|
98
162
|
new THREE.Vector3(0, 0, 1), new THREE.Vector3(),
|
|
99
|
-
|
|
163
|
+
arrowLen, color, arrowLen * 0.25, arrowLen * 0.12
|
|
100
164
|
);
|
|
165
|
+
// Render on top so arrows show through geometry
|
|
166
|
+
arrow.renderOrder = 999;
|
|
167
|
+
arrow.frustumCulled = false;
|
|
168
|
+
arrow.line.material = new THREE.LineBasicMaterial({ color, depthTest: false });
|
|
169
|
+
(arrow.cone.material as THREE.MeshBasicMaterial).depthTest = false;
|
|
170
|
+
arrow.line.renderOrder = 999;
|
|
171
|
+
arrow.line.frustumCulled = false;
|
|
172
|
+
arrow.cone.renderOrder = 999;
|
|
173
|
+
arrow.cone.frustumCulled = false;
|
|
101
174
|
arrow.userData.jointId = i;
|
|
175
|
+
arrow.userData.bodyId = bodyId;
|
|
176
|
+
arrow.userData.hasJntPos = !!jntPos;
|
|
177
|
+
arrow.userData.hasJntAxis = !!jntAxis;
|
|
102
178
|
joints.push(arrow);
|
|
103
179
|
}
|
|
104
180
|
}
|
|
@@ -144,6 +220,10 @@ export function Debug({
|
|
|
144
220
|
const data = mjDataRef.current;
|
|
145
221
|
if (!model || !data || !debugGeometry) return;
|
|
146
222
|
|
|
223
|
+
// Safely grab optional arrays once
|
|
224
|
+
const jntPos = (model as Record<string, unknown>).jnt_pos as Float64Array | undefined;
|
|
225
|
+
const jntAxis = (model as Record<string, unknown>).jnt_axis as Float64Array | undefined;
|
|
226
|
+
|
|
147
227
|
// Update geom wireframes
|
|
148
228
|
for (const mesh of debugGeometry.geoms) {
|
|
149
229
|
const bid = mesh.userData.bodyId;
|
|
@@ -157,8 +237,9 @@ export function Debug({
|
|
|
157
237
|
// Apply local geom offset
|
|
158
238
|
const gid = mesh.userData.geomId;
|
|
159
239
|
const gp = model.geom_pos;
|
|
160
|
-
|
|
161
|
-
.applyQuaternion(mesh.quaternion)
|
|
240
|
+
_v3a.set(gp[3 * gid], gp[3 * gid + 1], gp[3 * gid + 2])
|
|
241
|
+
.applyQuaternion(mesh.quaternion);
|
|
242
|
+
mesh.position.add(_v3a);
|
|
162
243
|
}
|
|
163
244
|
|
|
164
245
|
// Update site markers
|
|
@@ -171,6 +252,35 @@ export function Debug({
|
|
|
171
252
|
);
|
|
172
253
|
}
|
|
173
254
|
|
|
255
|
+
// Update joint axes
|
|
256
|
+
for (const obj of debugGeometry.joints) {
|
|
257
|
+
const arrow = obj as THREE.ArrowHelper;
|
|
258
|
+
const jid = arrow.userData.jointId;
|
|
259
|
+
const bid = arrow.userData.bodyId;
|
|
260
|
+
const i3 = bid * 3;
|
|
261
|
+
const i4 = bid * 4;
|
|
262
|
+
|
|
263
|
+
_quat.set(
|
|
264
|
+
data.xquat[i4 + 1], data.xquat[i4 + 2],
|
|
265
|
+
data.xquat[i4 + 3], data.xquat[i4]
|
|
266
|
+
);
|
|
267
|
+
|
|
268
|
+
// Position: body origin + local joint anchor (if available)
|
|
269
|
+
arrow.position.set(data.xpos[i3], data.xpos[i3 + 1], data.xpos[i3 + 2]);
|
|
270
|
+
if (jntPos) {
|
|
271
|
+
_v3a.set(jntPos[3 * jid], jntPos[3 * jid + 1], jntPos[3 * jid + 2])
|
|
272
|
+
.applyQuaternion(_quat);
|
|
273
|
+
arrow.position.add(_v3a);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// Orient along joint axis in world frame (if available)
|
|
277
|
+
if (jntAxis) {
|
|
278
|
+
_v3a.set(jntAxis[3 * jid], jntAxis[3 * jid + 1], jntAxis[3 * jid + 2])
|
|
279
|
+
.applyQuaternion(_quat).normalize();
|
|
280
|
+
arrow.setDirection(_v3a);
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
174
284
|
// Update COM markers
|
|
175
285
|
for (const mesh of debugGeometry.comMarkers) {
|
|
176
286
|
const bid = mesh.userData.bodyId;
|
|
@@ -179,49 +289,76 @@ export function Debug({
|
|
|
179
289
|
}
|
|
180
290
|
});
|
|
181
291
|
|
|
182
|
-
// Contact force vectors
|
|
292
|
+
// Contact force vectors — pre-created pool to avoid per-frame allocation
|
|
183
293
|
const contactGroupRef = useRef<THREE.Group>(null);
|
|
184
|
-
const
|
|
294
|
+
const contactPoolRef = useRef<THREE.ArrowHelper[]>([]);
|
|
295
|
+
const contactPoolInitRef = useRef(false);
|
|
185
296
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
const model = mjModelRef.current;
|
|
189
|
-
const data = mjDataRef.current;
|
|
297
|
+
// Initialize arrow pool once
|
|
298
|
+
useEffect(() => {
|
|
190
299
|
const group = contactGroupRef.current;
|
|
191
|
-
if (!
|
|
300
|
+
if (!group || contactPoolInitRef.current) return;
|
|
301
|
+
contactPoolInitRef.current = true;
|
|
192
302
|
|
|
193
|
-
|
|
194
|
-
for (
|
|
195
|
-
|
|
196
|
-
|
|
303
|
+
const pool: THREE.ArrowHelper[] = [];
|
|
304
|
+
for (let i = 0; i < MAX_CONTACT_ARROWS; i++) {
|
|
305
|
+
const arrow = new THREE.ArrowHelper(
|
|
306
|
+
new THREE.Vector3(0, 1, 0), new THREE.Vector3(), 0.1, 0xff4444, 0.03, 0.015
|
|
307
|
+
);
|
|
308
|
+
arrow.visible = false;
|
|
309
|
+
group.add(arrow);
|
|
310
|
+
pool.push(arrow);
|
|
197
311
|
}
|
|
198
|
-
|
|
312
|
+
contactPoolRef.current = pool;
|
|
313
|
+
|
|
314
|
+
return () => {
|
|
315
|
+
for (const arrow of pool) {
|
|
316
|
+
group.remove(arrow);
|
|
317
|
+
arrow.dispose();
|
|
318
|
+
}
|
|
319
|
+
contactPoolRef.current = [];
|
|
320
|
+
contactPoolInitRef.current = false;
|
|
321
|
+
};
|
|
322
|
+
}, [showContacts]);
|
|
323
|
+
|
|
324
|
+
useFrame(() => {
|
|
325
|
+
if (!showContacts) return;
|
|
326
|
+
const data = mjDataRef.current;
|
|
327
|
+
const pool = contactPoolRef.current;
|
|
328
|
+
if (!data || pool.length === 0) return;
|
|
199
329
|
|
|
200
330
|
const ncon = data.ncon;
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
331
|
+
let arrowIdx = 0;
|
|
332
|
+
|
|
333
|
+
for (let i = 0; i < Math.min(ncon, MAX_CONTACT_ARROWS); i++) {
|
|
334
|
+
const c = getContact(data, i);
|
|
335
|
+
if (!c) break;
|
|
336
|
+
_contactPos.set(c.pos[0], c.pos[1], c.pos[2]);
|
|
337
|
+
_contactNormal.set(c.frame[0], c.frame[1], c.frame[2]);
|
|
338
|
+
const force = Math.abs(c.dist) * 100;
|
|
339
|
+
const length = Math.min(force * 0.01, 0.1);
|
|
340
|
+
if (length > 0.001 && arrowIdx < pool.length) {
|
|
341
|
+
const arrow = pool[arrowIdx];
|
|
342
|
+
arrow.position.copy(_contactPos);
|
|
343
|
+
arrow.setDirection(_contactNormal);
|
|
344
|
+
arrow.setLength(length, length * 0.3, length * 0.15);
|
|
345
|
+
arrow.visible = true;
|
|
346
|
+
arrowIdx++;
|
|
215
347
|
}
|
|
216
348
|
}
|
|
349
|
+
|
|
350
|
+
// Hide unused arrows
|
|
351
|
+
for (let i = arrowIdx; i < pool.length; i++) {
|
|
352
|
+
pool[i].visible = false;
|
|
353
|
+
}
|
|
217
354
|
});
|
|
218
355
|
|
|
219
356
|
if (status !== 'ready') return null;
|
|
220
357
|
|
|
221
358
|
return (
|
|
222
|
-
|
|
359
|
+
<group {...groupProps}>
|
|
223
360
|
<group ref={groupRef} />
|
|
224
361
|
{showContacts && <group ref={contactGroupRef} />}
|
|
225
|
-
|
|
362
|
+
</group>
|
|
226
363
|
);
|
|
227
364
|
}
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import { useFrame, useThree } from '@react-three/fiber';
|
|
7
|
+
import type { ThreeElements } from '@react-three/fiber';
|
|
7
8
|
import { useEffect, useRef } from 'react';
|
|
8
9
|
import * as THREE from 'three';
|
|
9
10
|
import { useMujocoSim, useBeforePhysicsStep } from '../core/MujocoSimProvider';
|
|
@@ -35,7 +36,8 @@ const _mouse = new THREE.Vector2();
|
|
|
35
36
|
export function DragInteraction({
|
|
36
37
|
stiffness = 250,
|
|
37
38
|
showArrow = true,
|
|
38
|
-
|
|
39
|
+
...groupProps
|
|
40
|
+
}: DragInteractionProps & Omit<ThreeElements['group'], 'ref'>) {
|
|
39
41
|
const { mjDataRef, mujocoRef, mjModelRef, status } = useMujocoSim();
|
|
40
42
|
const { gl, camera, scene, controls } = useThree();
|
|
41
43
|
|
|
@@ -208,7 +210,7 @@ export function DragInteraction({
|
|
|
208
210
|
|
|
209
211
|
if (draggingRef.current && bodyIdRef.current > 0) {
|
|
210
212
|
arrow.visible = true;
|
|
211
|
-
const dir = mouseWorldRef.current
|
|
213
|
+
const dir = _bodyPos.copy(mouseWorldRef.current).sub(grabWorldRef.current);
|
|
212
214
|
const len = dir.length();
|
|
213
215
|
if (len > 0.001) {
|
|
214
216
|
dir.normalize();
|
|
@@ -223,5 +225,5 @@ export function DragInteraction({
|
|
|
223
225
|
|
|
224
226
|
if (status !== 'ready') return null;
|
|
225
227
|
|
|
226
|
-
return <group ref={groupRef} />;
|
|
228
|
+
return <group {...groupProps} ref={groupRef} />;
|
|
227
229
|
}
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
import { useEffect, useRef } from 'react';
|
|
9
9
|
import { useFrame } from '@react-three/fiber';
|
|
10
|
+
import type { ThreeElements } from '@react-three/fiber';
|
|
10
11
|
import * as THREE from 'three';
|
|
11
12
|
import { useMujocoSim } from '../core/MujocoSimProvider';
|
|
12
13
|
|
|
@@ -14,7 +15,7 @@ import { useMujocoSim } from '../core/MujocoSimProvider';
|
|
|
14
15
|
* Renders MuJoCo flex (deformable) bodies as dynamic meshes.
|
|
15
16
|
* Vertices are updated every frame from flexvert_xpos.
|
|
16
17
|
*/
|
|
17
|
-
export function FlexRenderer() {
|
|
18
|
+
export function FlexRenderer(props: Omit<ThreeElements['group'], 'ref'>) {
|
|
18
19
|
const { mjModelRef, mjDataRef, status } = useMujocoSim();
|
|
19
20
|
const groupRef = useRef<THREE.Group>(null);
|
|
20
21
|
const meshesRef = useRef<THREE.Mesh[]>([]);
|
|
@@ -98,5 +99,5 @@ export function FlexRenderer() {
|
|
|
98
99
|
});
|
|
99
100
|
|
|
100
101
|
if (status !== 'ready') return null;
|
|
101
|
-
return <group ref={groupRef} />;
|
|
102
|
+
return <group {...props} ref={groupRef} />;
|
|
102
103
|
}
|