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