Mouse picking demo¶
click cubes to change their colour.
▶ Run in browserTags: 3d
Uses the engine’s GPU object-picking buffer: set shape.pickable = True and
override on_picked. For the manual approach (casting a world-space ray with
screen_to_ray and intersecting a CollisionWorld yourself) see raycast.py.
Usage: uv run python examples/features/3d/picking.py
Source¶
1"""Mouse picking demo: click cubes to change their colour.
2
3Uses the engine's GPU object-picking buffer: set ``shape.pickable = True`` and
4override ``on_picked``. For the manual approach (casting a world-space ray with
5``screen_to_ray`` and intersecting a ``CollisionWorld`` yourself) see ``raycast.py``.
6
7Usage:
8 uv run python examples/features/3d/picking.py
9"""
10
11from simvx.core import (
12 Camera3D,
13 CollisionShape3D,
14 Material,
15 Mesh,
16 MeshInstance3D,
17 Node,
18 Text2D,
19)
20from simvx.graphics import App
21
22
23class PickableCube(MeshInstance3D):
24 """A cube that responds to mouse clicks."""
25
26 _COLOURS = [
27 (0.9, 0.1, 0.1, 1),
28 (0.1, 0.9, 0.1, 1),
29 (0.1, 0.1, 0.9, 1),
30 (0.9, 0.9, 0.1, 1),
31 (0.9, 0.1, 0.9, 1),
32 (0.1, 0.9, 0.9, 1),
33 ]
34
35 def __init__(self, label="Cube", colour_idx=0, **kwargs):
36 mat = Material(colour=self._COLOURS[colour_idx % len(self._COLOURS)])
37 super().__init__(material=mat, **kwargs)
38 self.label = label
39 self._colour_idx = colour_idx
40
41 def on_ready(self):
42 # Inscribed sphere (touches face centers) for tight picking
43 shape = CollisionShape3D(radius=0.5)
44 shape.pickable = True
45 self.add_child(shape)
46
47 def on_picked(self, event):
48 """Called when this node is picked by mouse ray."""
49 self._colour_idx = (self._colour_idx + 1) % len(self._COLOURS)
50 new_colour = self._COLOURS[self._colour_idx]
51 self.material.colour = new_colour
52 print(f"PICKED {self.label} -> colour {self._colour_idx}", flush=True)
53
54
55class PickingScene(Node):
56 def on_ready(self):
57 # Camera: looking down Y axis
58 cam = Camera3D(position=(0, -15, 0))
59 cam.look_at((0, 0, 0), up=(0, 0, 1))
60 self.add_child(cam)
61
62 # Shared mesh
63 cube_mesh = Mesh.cube()
64
65 # Grid of pickable cubes
66 positions = [
67 (-3, 0, -2),
68 (0, 0, -2),
69 (3, 0, -2),
70 (-3, 0, 2),
71 (0, 0, 2),
72 (3, 0, 2),
73 ]
74 for i, pos in enumerate(positions):
75 cube = PickableCube(
76 label=f"Cube_{i}",
77 colour_idx=i,
78 mesh=cube_mesh,
79 position=pos,
80 )
81 self.add_child(cube)
82
83 # HUD
84 self.add_child(Text2D(text="Click cubes to change colour", x=10, y=10, font_scale=1.5))
85
86 def on_process(self, dt):
87 pass
88
89
90if __name__ == "__main__":
91 app = App(title="Picking Demo", width=1280, height=720)
92 app.run(PickingScene())