Compare commits

1 Commits

Author SHA1 Message Date
a22702df4d feat(patterns): add radiate animation 2026-04-20 23:37:43 +12:00

89
src/patterns/radiate.py Normal file
View File

@@ -0,0 +1,89 @@
import utime
class Radiate:
def __init__(self, driver):
self.driver = driver
def run(self, preset):
"""Radiate from nodes every n1 LEDs, retriggering every delay (d).
- n1: node spacing in LEDs
- n2: outbound travel time in ms
- n3: return travel time in ms
- d: retrigger interval in ms
"""
colors = preset.c if preset.c else [(255, 255, 255)]
base_on = colors[0]
base_off = colors[1] if len(colors) > 1 else (0, 0, 0)
spacing = max(1, int(preset.n1))
outward_ms = max(1, int(preset.n2))
return_ms = max(1, int(preset.n3))
max_dist = spacing // 2
lit_color = self.driver.apply_brightness(base_on, preset.b)
off_color = self.driver.apply_brightness(base_off, preset.b)
now = utime.ticks_ms()
last_trigger = now
active_pulses = [now]
if not preset.a:
# Single-step render uses only the first instant pulse.
active_pulses = [utime.ticks_ms()]
while True:
now = utime.ticks_ms()
delay_ms = max(1, int(preset.d))
spacing = max(1, int(preset.n1))
outward_ms = max(1, int(preset.n2))
return_ms = max(1, int(preset.n3))
max_dist = spacing // 2
lit_color = self.driver.apply_brightness(base_on, preset.b)
off_color = self.driver.apply_brightness(base_off, preset.b)
if preset.a and utime.ticks_diff(now, last_trigger) >= delay_ms:
active_pulses.append(now)
last_trigger = utime.ticks_add(last_trigger, delay_ms)
# Drop pulses once their out-and-back lifetime ends.
pulse_lifetime = outward_ms + return_ms
kept = []
for start in active_pulses:
age = utime.ticks_diff(now, start)
if age <= pulse_lifetime:
kept.append(start)
active_pulses = kept
for i in range(self.driver.num_leds):
# Nearest node distance for a repeating node grid every `spacing` LEDs.
offset = i % spacing
dist = min(offset, spacing - offset)
lit = False
for start in active_pulses:
age = utime.ticks_diff(now, start)
if age < 0:
continue
if age <= outward_ms:
front = (age * max_dist) / outward_ms
elif age <= outward_ms + return_ms:
back_age = age - outward_ms
front = ((return_ms - back_age) * max_dist) / return_ms
else:
continue
if dist <= front:
lit = True
break
self.driver.n[i] = lit_color if lit else off_color
self.driver.n.write()
if not preset.a:
yield
return
yield