2D Trail

Colour-gradient trails behind moving objects.

▶ Run in browser

Tags: 2d

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 examples/features/2d/trail.py

Controls: Space - Toggle trails on/off Escape - Quit

Source

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