2D Trail¶
Demonstrates:
Trail2D as a child of a moving Node2D
Configurable length, width, colour gradient, and lifetime
Multiple trails with different visual settings
Toggle emission on/off with Space
Run: uv run python packages/graphics/examples/2d_trail.py
Controls: Space - Toggle trails on/off Escape - Quit
Source Code¶
1"""Trail2D demo -- colour-gradient trails behind moving objects.
2
3Demonstrates:
4 - Trail2D as a child of a moving Node2D
5 - Configurable length, width, colour gradient, and lifetime
6 - Multiple trails with different visual settings
7 - Toggle emission on/off with Space
8
9Run: uv run python packages/graphics/examples/2d_trail.py
10
11Controls:
12 Space - Toggle trails on/off
13 Escape - Quit
14"""
15
16import math
17
18from simvx.core import Input, InputMap, Key, Node2D, Property, Trail2D, Vec2
19from simvx.graphics import App
20
21WIDTH, HEIGHT = 1024, 768
22
23
24class FigureEightMover(Node2D):
25 """Moves in a figure-8 (lemniscate) pattern."""
26
27 speed = Property(2.0)
28
29 def __init__(self, cx: float, cy: float, rx: float, ry: float, **kwargs):
30 super().__init__(**kwargs)
31 self._cx, self._cy = cx, cy
32 self._rx, self._ry = rx, ry
33 self._time = 0.0
34
35 def process(self, dt: float):
36 self._time += dt * self.speed
37 t = self._time
38 x = self._cx + math.sin(t) * self._rx
39 y = self._cy + math.sin(t * 2) * self._ry * 0.5
40 self.position = Vec2(x, y)
41
42
43class TrailDemo(Node2D):
44 """Root scene with two moving objects, each with a Trail2D child."""
45
46 def ready(self):
47 InputMap.add_action("toggle_trails", [Key.SPACE])
48 InputMap.add_action("quit", [Key.ESCAPE])
49
50 # --- Cyan figure-8 mover with a thin, long trail ---
51 self._mover1 = self.add_child(
52 FigureEightMover(WIDTH * 0.5, HEIGHT * 0.35, 200, 180, name="Mover1")
53 )
54 trail1 = self._mover1.add_child(Trail2D(name="CyanTrail"))
55 trail1.length = 40
56 trail1.width = 8.0
57 trail1.colour = (0.2, 0.8, 1.0, 1.0)
58 trail1.colour_end = (0.2, 0.8, 1.0, 0.0)
59 trail1.lifetime = 1.0
60
61 # --- Red-to-yellow mover on a circular orbit ---
62 self._mover2 = self.add_child(Node2D(name="Mover2"))
63 trail2 = self._mover2.add_child(Trail2D(name="FireTrail"))
64 trail2.length = 25
65 trail2.width = 16.0
66 trail2.colour = (1.0, 0.2, 0.1, 1.0)
67 trail2.colour_end = (1.0, 0.9, 0.1, 0.0)
68 trail2.lifetime = 0.6
69
70 self._trails = [trail1, trail2]
71 self._time = 0.0
72
73 def process(self, dt: float):
74 self._time += dt
75
76 # Circular orbit for the second mover
77 angle = self._time * 1.8
78 self._mover2.position = Vec2(
79 WIDTH * 0.5 + math.cos(angle) * 160,
80 HEIGHT * 0.7 + math.sin(angle) * 80,
81 )
82
83 # Toggle trails with Space
84 if Input.is_action_just_pressed("toggle_trails"):
85 for trail in self._trails:
86 trail.emit = not trail.emit
87 if Input.is_action_just_pressed("quit"):
88 self.app.quit()
89
90 def draw(self, renderer):
91 # Draw each trail as connected line segments with colour gradient
92 for trail in self._trails:
93 points = trail.trail_points
94 for i in range(len(points) - 1):
95 a, b = points[i], points[i + 1]
96 renderer.draw_line(a["position"], b["position"], colour=a["colour"])
97
98 # Draw mover positions as small dots
99 white = (1.0, 1.0, 1.0, 1.0)
100 renderer.draw_circle(self._mover1.position, 5, colour=white)
101 renderer.draw_circle(self._mover2.position, 5, colour=white)
102
103 # HUD
104 renderer.draw_text("TRAIL2D DEMO", (10, 10), colour=(0.78, 0.78, 0.78), scale=2)
105 state = "ON" if self._trails[0].emit else "OFF"
106 renderer.draw_text(
107 f"Space = toggle [{state}] | Cyan: figure-8 | Red: orbit",
108 (10, 35),
109 colour=(0.59, 0.59, 0.59),
110 )
111
112
113if __name__ == "__main__":
114 App(width=WIDTH, height=HEIGHT, title="Trail2D Demo").run(TrailDemo())