Skip to content

Commit a273979

Browse files
noah-wardlowclaude
andcommitted
feat: add useBodyMeshes hook, remove SelectionHighlight component
useBodyMeshes(bodyId) returns Three.js meshes for a MuJoCo body — the low-level primitive for custom selection visuals, outlines, or postprocessing effects. useSelectionHighlight is now built on useBodyMeshes. SelectionHighlight component removed from public API. BREAKING CHANGE: <SelectionHighlight> component is removed. Use useSelectionHighlight(bodyId) hook or useBodyMeshes(bodyId) for custom visuals. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 5ed421b commit a273979

9 files changed

Lines changed: 232 additions & 130 deletions

File tree

README.md

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -365,10 +365,6 @@ Component wrapper for contact events:
365365
/>
366366
```
367367

368-
### `<SelectionHighlight />`
369-
370-
Emissive highlight on selected body meshes. Also available as `useSelectionHighlight(bodyId, options?)` hook.
371-
372368
### `<TrajectoryPlayer />`
373369

374370
Plays back recorded qpos trajectories with scrubbing.
@@ -555,9 +551,19 @@ Returns actuator metadata for building control UIs.
555551

556552
Ref-based site position/quaternion tracking.
557553

554+
### `useBodyMeshes(bodyId)`
555+
556+
Returns the Three.js meshes belonging to a MuJoCo body. Use for custom selection visuals, outlines, postprocessing, or any per-body mesh manipulation:
557+
558+
```tsx
559+
const meshes = useBodyMeshes(selectedBodyId);
560+
561+
// Use with drei Outline, or manipulate materials directly
562+
```
563+
558564
### `useSelectionHighlight(bodyId, options?)`
559565

560-
Hook form of `<SelectionHighlight>`. Apply emissive highlights imperatively:
566+
Convenience wrapper around `useBodyMeshes` that applies an emissive highlight:
561567

562568
```tsx
563569
useSelectionHighlight(selectedBodyId, { color: '#00ff00', emissiveIntensity: 0.5 });
@@ -659,12 +665,13 @@ Objects that need stable contact (grasping, stacking, etc.) require tuned MuJoCo
659665

660666
### Click-to-Select
661667

662-
Combine R3F raycasting with `<SelectionHighlight />` for body selection:
668+
Combine R3F raycasting with `useSelectionHighlight` for body selection:
663669

664670
```tsx
665671
function ClickSelectOverlay() {
666672
const selectedBodyId = useClickSelect(); // your raycasting hook
667-
return <SelectionHighlight bodyId={selectedBodyId} />;
673+
useSelectionHighlight(selectedBodyId);
674+
return null;
668675
}
669676
```
670677

docs/components/selection-highlight.mdx

Lines changed: 0 additions & 64 deletions
This file was deleted.

docs/docs.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,6 @@
5555
"components/tendon-renderer",
5656
"components/flex-renderer",
5757
"components/contact-listener",
58-
"components/selection-highlight",
5958
"components/trajectory-player"
6059
]
6160
},
@@ -82,6 +81,7 @@
8281
"hooks/use-video-recorder",
8382
"hooks/use-ctrl-noise",
8483
"hooks/use-gravity-compensation",
84+
"hooks/use-body-meshes",
8585
"hooks/use-selection-highlight",
8686
"hooks/use-scene-lights"
8787
]

docs/guides/click-to-select.mdx

Lines changed: 35 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,17 @@ description: "Implement body selection with raycasting and highlights"
44
icon: "mouse-pointer"
55
---
66

7-
A common interaction pattern: double-click a body to select it, highlight the selected body, and use the selection for further actions. This combines R3F raycasting with the library's `<SelectionHighlight />` component.
7+
A common interaction pattern: double-click a body to select it, highlight the selected body, and use the selection for further actions. This combines R3F raycasting with the library's `useSelectionHighlight` hook.
88

99
## The Pattern
1010

1111
1. A hook listens for double-clicks and raycasts to find the clicked body
12-
2. A component passes the selected body ID to `<SelectionHighlight />`
12+
2. The `useSelectionHighlight` hook applies a visual highlight to the selected body
1313

1414
```tsx
1515
import { useState, useCallback } from 'react';
1616
import { useThree } from '@react-three/fiber';
17-
import { SelectionHighlight } from 'mujoco-react';
17+
import { useSelectionHighlight } from 'mujoco-react';
1818

1919
function useClickSelect(): number | null {
2020
const [selectedBodyId, setSelectedBodyId] = useState<number | null>(null);
@@ -53,12 +53,13 @@ function useClickSelect(): number | null {
5353
}
5454
```
5555

56-
## Composing with SelectionHighlight
56+
## Composing with useSelectionHighlight
5757

5858
```tsx
5959
function ClickSelectOverlay() {
6060
const selectedBodyId = useClickSelect();
61-
return <SelectionHighlight bodyId={selectedBodyId} />;
61+
useSelectionHighlight(selectedBodyId);
62+
return null;
6263
}
6364
```
6465

@@ -75,24 +76,38 @@ Then drop it into your scene:
7576
`<MujocoCanvas>` also has a built-in `onSelection` callback that fires on double-click:
7677

7778
```tsx
78-
<MujocoCanvas
79-
config={config}
80-
onSelection={(bodyId, name) => {
81-
console.log(`Selected body ${name} (id: ${bodyId})`);
82-
}}
83-
>
84-
<SelectionHighlight bodyId={selectedBodyId} />
85-
</MujocoCanvas>
79+
function SelectionHandler({ bodyId }: { bodyId: number | null }) {
80+
useSelectionHighlight(bodyId);
81+
return null;
82+
}
83+
84+
function App() {
85+
const [selectedBodyId, setSelectedBodyId] = useState<number | null>(null);
86+
87+
return (
88+
<MujocoCanvas
89+
config={config}
90+
onSelection={(bodyId, name) => {
91+
console.log(`Selected body ${name} (id: ${bodyId})`);
92+
setSelectedBodyId(bodyId);
93+
}}
94+
>
95+
<SelectionHandler bodyId={selectedBodyId} />
96+
</MujocoCanvas>
97+
);
98+
}
8699
```
87100

88101
## How SceneRenderer Stores Body IDs
89102

90-
`<SceneRenderer />` sets `userData.bodyID` on every mesh it creates. When raycasting, walk up the object tree with `obj.parent` until you find a node with `userData.bodyID` — child meshes (e.g. geom sub-meshes) don't have it directly, but their parent body group does.
103+
`<SceneRenderer />` sets `userData.bodyID` on every mesh it creates. When raycasting, walk up the object tree with `obj.parent` until you find a node with `userData.bodyID` -- child meshes (e.g. geom sub-meshes) don't have it directly, but their parent body group does.
104+
105+
## useSelectionHighlight Options
91106

92-
## SelectionHighlight Props
107+
| Option | Type | Default | Description |
108+
|--------|------|---------|-------------|
109+
| `bodyId` (first argument) | `number \| null` | -- | Body to highlight, or `null` to clear |
110+
| `options.color` | `string` | `'#ff4444'` | Emissive highlight color |
111+
| `options.emissiveIntensity` | `number` | `0.3` | Strength of the emissive glow |
93112

94-
| Prop | Type | Default | Description |
95-
|------|------|---------|-------------|
96-
| `bodyId` | `number \| null` || Body to highlight, or `null` to clear |
97-
| `color` | `string` | `'#ff4444'` | Emissive highlight color |
98-
| `emissiveIntensity` | `number` | `0.3` | Strength of the emissive glow |
113+
For more advanced selection visuals (outlines, postprocessing, custom shaders), use [`useBodyMeshes`](/hooks/use-body-meshes) to get direct access to the body's Three.js meshes.

docs/hooks/use-body-meshes.mdx

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
---
2+
title: "useBodyMeshes"
3+
description: "Hook that returns Three.js meshes for a MuJoCo body"
4+
icon: "cubes"
5+
---
6+
7+
Returns the `THREE.Mesh[]` array for a given MuJoCo body ID. This is the low-level primitive for building custom selection visuals, outlines, postprocessing effects, or any logic that needs direct access to the meshes belonging to a body.
8+
9+
## Signature
10+
11+
```tsx
12+
useBodyMeshes(bodyId: number | null): THREE.Mesh[]
13+
```
14+
15+
## Usage
16+
17+
```tsx
18+
import { useBodyMeshes } from 'mujoco-react';
19+
20+
function CustomOutline({ bodyId }: { bodyId: number | null }) {
21+
const meshes = useBodyMeshes(bodyId);
22+
23+
useEffect(() => {
24+
// Apply a custom outline effect to each mesh
25+
meshes.forEach((mesh) => {
26+
mesh.layers.enable(1); // e.g. assign to an outline layer
27+
});
28+
return () => {
29+
meshes.forEach((mesh) => {
30+
mesh.layers.disable(1);
31+
});
32+
};
33+
}, [meshes]);
34+
35+
return null;
36+
}
37+
```
38+
39+
### Custom Selection Visuals
40+
41+
Because `useBodyMeshes` gives you raw mesh references, you can implement any visual effect:
42+
43+
```tsx
44+
import { useBodyMeshes } from 'mujoco-react';
45+
import { useFrame } from '@react-three/fiber';
46+
47+
function PulsingHighlight({ bodyId }: { bodyId: number }) {
48+
const meshes = useBodyMeshes(bodyId);
49+
50+
useFrame(({ clock }) => {
51+
const intensity = (Math.sin(clock.elapsedTime * 4) + 1) / 2;
52+
meshes.forEach((mesh) => {
53+
const mat = mesh.material as THREE.MeshStandardMaterial;
54+
mat.emissiveIntensity = intensity * 0.5;
55+
mat.emissive.set('#ff8800');
56+
});
57+
});
58+
59+
return null;
60+
}
61+
```
62+
63+
### Postprocessing
64+
65+
Pair with `@react-three/postprocessing` to apply per-body effects:
66+
67+
```tsx
68+
import { useBodyMeshes } from 'mujoco-react';
69+
import { Selection, Select, EffectComposer, Outline } from '@react-three/postprocessing';
70+
71+
function OutlineSelectedBody({ bodyId }: { bodyId: number | null }) {
72+
const meshes = useBodyMeshes(bodyId);
73+
74+
return (
75+
<Selection>
76+
<EffectComposer>
77+
<Outline blur edgeStrength={3} />
78+
</EffectComposer>
79+
{meshes.map((mesh, i) => (
80+
<Select key={i} enabled>
81+
<primitive object={mesh} />
82+
</Select>
83+
))}
84+
</Selection>
85+
);
86+
}
87+
```
88+
89+
## Parameters
90+
91+
| Parameter | Type | Default | Description |
92+
|-----------|------|---------|-------------|
93+
| `bodyId` | `number \| null` | -- | ID of the MuJoCo body. Pass `null` to get an empty array. |
94+
95+
## Returns
96+
97+
| Type | Description |
98+
|------|-------------|
99+
| `THREE.Mesh[]` | Array of Three.js meshes belonging to the body. Empty array if `bodyId` is `null` or no meshes are found. |
100+
101+
## How It Works
102+
103+
Traverses the R3F scene graph and collects all meshes whose `userData.bodyID` matches the given `bodyId`. The `<SceneRenderer>` component sets `userData.bodyID` on every mesh it creates, so this hook works with any standard mujoco-react scene.
104+
105+
## Related
106+
107+
- [`useSelectionHighlight`](/hooks/use-selection-highlight) -- convenience hook built on `useBodyMeshes` for emissive highlights

docs/hooks/use-selection-highlight.mdx

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ description: "Hook for applying emissive highlights to body meshes"
44
icon: "highlighter"
55
---
66

7-
Applies an emissive glow to the meshes of a selected body. Hook form of [`<SelectionHighlight>`](/components/selection-highlight) for imperative usage inside your own components.
7+
Applies an emissive glow to the meshes of a selected body. This is a convenience hook built on top of [`useBodyMeshes`](/hooks/use-body-meshes) -- if you need more control over how meshes are styled, use `useBodyMeshes` directly.
88

99
## Signature
1010

@@ -38,7 +38,7 @@ function MyInteractiveScene() {
3838

3939
### Composing with Other Logic
4040

41-
The hook form is useful when you want to combine highlighting with other behavior in a single component:
41+
The hook is useful when you want to combine highlighting with other behavior in a single component:
4242

4343
```tsx
4444
function BodyInspector({ bodyId }: { bodyId: number }) {
@@ -58,17 +58,16 @@ function BodyInspector({ bodyId }: { bodyId: number }) {
5858

5959
| Parameter | Type | Default | Description |
6060
|-----------|------|---------|-------------|
61-
| `bodyId` | `number \| null` | | ID of the body to highlight. Pass `null` to clear. |
61+
| `bodyId` | `number \| null` | -- | ID of the body to highlight. Pass `null` to clear. |
6262
| `options.color` | `string` | `'#ff4444'` | Emissive highlight color. |
6363
| `options.emissiveIntensity` | `number` | `0.3` | Intensity of the emissive glow. |
6464

6565
## How It Works
6666

67-
1. Traverses the scene graph for meshes with matching `userData.bodyID`
67+
1. Uses [`useBodyMeshes`](/hooks/use-body-meshes) to get the meshes for the given body ID
6868
2. Sets the mesh material's `emissive` color and `emissiveIntensity`
6969
3. Restores original emissive values when `bodyId` changes or the component unmounts
7070

71-
## Notes
71+
## Related
7272

73-
- For a declarative JSX API, use [`<SelectionHighlight>`](/components/selection-highlight) instead
74-
- The component form is a thin wrapper around this hook
73+
- [`useBodyMeshes`](/hooks/use-body-meshes) -- the low-level primitive this hook is built on, for custom visuals and postprocessing

0 commit comments

Comments
 (0)