2D Trail

Play Demo

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())