Skip to content

Commit c1736c5

Browse files
committed
Dec 29 daily effect
1 parent 5ee62e1 commit c1736c5

File tree

4 files changed

+207
-16
lines changed

4 files changed

+207
-16
lines changed

setup.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
__version__ = '0.1.29'
1+
__version__ = '0.1.30'
22

33
_classifiers = [
44
'Development Status :: 4 - Beta',

xled_plus/samples/day28.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ def blend_colors(self, colors):
113113

114114
if __name__ == '__main__':
115115
ctr = setup_control()
116-
ctr.adjust_layout_aspect(1.4) # How many times wider than high is the led installation?
116+
ctr.adjust_layout_aspect(1.0) # How many times wider than high is the led installation?
117117
eff = FlowerScene(ctr, 6)
118118
oldmode = ctr.get_mode()["mode"]
119119
eff.launch_rt()

xled_plus/samples/day29.py

+69
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
"""
2+
Day 29 - Bouncing Balls
3+
4+
Balls of slowly changing colors, bouncing over the leds.
5+
Unlike the previous effects, this should work reasonably for both 2D and 3D layouts.
6+
Works best for fairly dense layouts though.
7+
A continuous effect. You can upload it as a movie instead if you prefer, but it will jerk in the transition.
8+
Remember to adjust aspect ratio in the call to adjust_layout_aspect in the code below.
9+
"""
10+
11+
from xled_plus.samples.sample_setup import *
12+
13+
class MeanderBouncingScene(BouncingScene):
14+
def __init__(self, ctr, num, speed=0.25, size=0.3):
15+
super(MeanderBouncingScene, self).__init__(ctr, num, speed, size, False)
16+
self.bgcol = hsl_color(0.0, 0.0, -0.5)
17+
self.bgeffect = BreathEffect(ctr, [(0.0, 0.0, 0.0)], 1.0, 0.75, [12, 30])
18+
self.colfunc = False
19+
self.horizon = 0
20+
self.preferred_frames = 640
21+
22+
def create(self):
23+
sh = super(MeanderBouncingScene, self).create()
24+
sh.cm = ColorMeander("surface", start=(random(), 1.0, 0.0))
25+
return sh
26+
27+
def update(self, step):
28+
for sh in self.shapes:
29+
sh.cm.step()
30+
(h, s, l) = sh.cm.get_hsl()
31+
sh.color = hsl_color(h, 1.0, min(0.3, max(-0.3, l)))
32+
super(MeanderBouncingScene, self).update(step)
33+
34+
def blend_colors(self, colors):
35+
cols = colors
36+
br = list(map(lambda rgb: color_brightness(*rgb), colors))
37+
maxbr = max(br) if br else 0.0
38+
sumbr = sum(br) if br else 0.0
39+
bgbr = color_brightness(*self.bgcol)
40+
if maxbr < bgbr:
41+
delta = (bgbr - maxbr) / bgbr
42+
sumbr += (bgbr - maxbr)
43+
maxbr = bgbr
44+
cols = cols + [tuple(map(lambda x: x*delta, self.bgcol))]
45+
sumcol = tuple(map(lambda *args: max(0, min(255, int(round(sum(args) * maxbr / sumbr)))), *cols))
46+
return sumcol
47+
48+
def reset(self, numframes):
49+
super(MeanderBouncingScene, self).reset(numframes)
50+
self.bgeffect.reset(numframes)
51+
52+
def getnext(self):
53+
pat1 = super(MeanderBouncingScene, self).getnext()
54+
patbg = self.bgeffect.getnext()
55+
vec = self.getoccupancy()
56+
pat = [pat1[i] if vec[i] else patbg[i] for i in range(self.ctr.num_leds)]
57+
return pat
58+
59+
60+
if __name__ == '__main__':
61+
ctr = setup_control()
62+
ctr.adjust_layout_aspect(1.0) # How many times wider than high is the led installation?
63+
eff = MeanderBouncingScene(ctr, 5, size=0.4)
64+
oldmode = ctr.get_mode()["mode"]
65+
eff.launch_rt()
66+
print("Started continuous effect - press Return to stop it")
67+
input()
68+
eff.stop_rt()
69+
ctr.set_mode(oldmode)

xled_plus/shapes.py

+136-14
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111

1212

1313
import math as m
14-
from random import random, gauss
14+
from random import random, gauss, uniform
1515
from copy import copy
1616

1717
from xled_plus.ledcolor import hsl_color
@@ -86,6 +86,29 @@ def getnext(self):
8686
def getoccupancy(self):
8787
return self.occvec
8888

89+
def get_scene_bounds(self):
90+
bounds = self.ctr.get_layout_bounds()
91+
if bounds["dim"] == 3:
92+
if self.proj2D3D == "cylbase":
93+
fact = self.ctr.get_layout_bounds()["radius"] / self.ctr.get_layout_bounds()["cylradius"]
94+
return [(fact * bounds["bounds"][0][0], fact * bounds["bounds"][0][1]),
95+
(fact * bounds["bounds"][2][0], fact * bounds["bounds"][2][1])]
96+
elif self.proj2D3D == "cylshell":
97+
hyp = ((m.pi * bounds["cylradius"]) ** 2 + 0.25) ** 0.5
98+
xfact = m.pi * bounds["cylradius"] / hyp
99+
yfact = 0.5 / hyp
100+
return [(-xfact, xfact), (-yfact, yfact)]
101+
elif self.proj2D3D == "halfsphere":
102+
return [(-1.0, 1.0), (-1.0, 1.0)]
103+
else:
104+
return bounds["bounds"]
105+
elif bounds["dim"] == 2:
106+
fact = 1.0 / bounds["radius"]
107+
return [(fact * bounds["bounds"][0][0], fact * bounds["bounds"][0][1]),
108+
(fact * bounds["bounds"][1][0], fact * bounds["bounds"][1][1])]
109+
else:
110+
return bounds["bounds"]
111+
89112

90113
class Shape(object):
91114
def __init__(self, cent, angle):
@@ -226,6 +249,7 @@ def is_inside(self, coord):
226249
#
227250
class Lineart2D(Shape):
228251
def __init__(self):
252+
super(Lineart2D, self).__init__((0.0, 0.0), 0.0)
229253
self.points = []
230254
self.lines = []
231255
self.arcs = []
@@ -574,11 +598,27 @@ def __init__(self, char, pos, angle, size, color):
574598
for seg in segs:
575599
self.add_segment(*seg)
576600
self.color = color
577-
self.off = [-pos[0], -pos[1]]
578-
ca = m.cos(angle * m.pi / 180.0) / size
579-
sa = m.sin(angle * m.pi / 180.0) / size
601+
self.size = size
602+
self.off = [0.0, 0.0]
603+
self.angle = 0.0
604+
self.speed = (0.0, 0.0)
605+
self.torque = 0.0
606+
self.change_pos(pos)
607+
self.change_angle(angle)
608+
609+
def change_angle(self, deltaangle):
610+
self.angle += deltaangle
611+
ca = m.cos(self.angle * m.pi / 180.0) / self.size
612+
sa = m.sin(self.angle * m.pi / 180.0) / self.size
580613
self.mat = [[ca, sa], [-sa, ca]]
581614

615+
def change_pos(self, delta):
616+
self.off = [self.off[0] - delta[0], self.off[1] - delta[1]]
617+
618+
def update(self, step):
619+
self.change_pos((step * self.speed[0], step * self.speed[1]))
620+
self.change_angle(step * self.torque)
621+
582622

583623
# Example scenes
584624

@@ -719,6 +759,61 @@ def update(self, step):
719759
self.time += step
720760

721761

762+
class RunningText(MovingShapesScene):
763+
def __init__(self, ctr, txt, color, linewidth=0.1, size=0.6, speed=1.0):
764+
super(RunningText, self).__init__(ctr)
765+
self.textcolor = color
766+
self.lw = linewidth
767+
self.txt = txt
768+
self.size = size
769+
self.speed = speed
770+
self.horizon = 0
771+
self.proj2D3D = "cylshell"
772+
self.place_text()
773+
self.preferred_frames = self.nsteps
774+
775+
def set_fps(self, fps):
776+
self.preferred_fps = fps
777+
self.place_text()
778+
self.preferred_frames = self.nsteps
779+
780+
def place_text(self):
781+
bounds = self.get_scene_bounds()
782+
# Size is relative the total height of leds, convert to relative radius
783+
size = self.size * (bounds[1][1] - bounds[1][0])
784+
# Speed is in letter heights per second, convert to length per step
785+
speed = size * self.speed / self.preferred_fps
786+
self.currx = bounds[0][1]
787+
self.endx = bounds[0][0]
788+
self.liney = -size / 2.0
789+
self.shapes = []
790+
for ind, ch in enumerate(self.txt):
791+
if isinstance(self.textcolor, list):
792+
col = self.textcolor[ind % len(self.textcolor)]
793+
elif callable(self.textcolor):
794+
col = self.textcolor(ind)
795+
else:
796+
col = self.textcolor
797+
sh = Letter(ch, (0, self.liney), 0, size, col)
798+
sh.lw = self.lw
799+
sh.off[0] = -self.currx + (sh.extent[0] - sh.lw * 0.5) * size
800+
sh.set_speed(-speed, 0.0)
801+
wdt = max(0.4, sh.extent[2] - sh.extent[0] + 2 * sh.lw)
802+
self.currx += wdt * size
803+
self.add_shape(sh)
804+
self.nsteps = int(round((self.currx - self.endx) / speed + 0.5))
805+
self.time = 0
806+
807+
def reset(self, numframes):
808+
if self.time != 0:
809+
self.place_text()
810+
811+
def update(self, step):
812+
for sh in self.shapes:
813+
sh.update(step)
814+
self.time += step
815+
816+
722817
class CaleidoScene(MovingShapesScene):
723818
def __init__(self, ctr, sym):
724819
super(CaleidoScene, self).__init__(ctr)
@@ -766,15 +861,22 @@ def get_color(self, coord, ind):
766861

767862

768863
class BouncingScene(MovingShapesScene):
769-
def __init__(self, ctr, num):
864+
def __init__(self, ctr, num, speed=0.25, size=0.3, colorfunc=False):
770865
super(BouncingScene, self).__init__(ctr)
866+
self.bounds = ctr.get_layout_bounds()
867+
self.size = size
868+
self.speed = speed
869+
self.colfunc = colorfunc or random_hsl_color_func(light=0.0)
771870
for i in range(num):
772-
self.create()
871+
self.ind = i
872+
self.add_shape(self.create())
773873

774874
def create(self):
775-
cent = (gauss(0.0, 0.2), gauss(0.0, 0.2))
776-
shape = Blob(cent, 0.12, random_hsl_color_func(light=0.0)())
777-
shape.speed = (gauss(0.0, 0.1), gauss(0.0, 0.1))
875+
cent = tuple(uniform(*self.bounds["bounds"][d]) for d in range(self.bounds["dim"]))
876+
vec = tuple(gauss(0.0, 0.1) for d in range(self.bounds["dim"]))
877+
vlen = self.dot(vec, vec) ** 0.5
878+
shape = Blob(cent, self.size/2.0, self.colfunc(self.ind))
879+
shape.speed = tuple(ele/vlen * self.speed/self.preferred_fps for ele in vec)
778880
return shape
779881

780882
def dot(self, v1, v2):
@@ -786,13 +888,33 @@ def scale(self, v1, sc):
786888
def add(self, v1, v2):
787889
return tuple(map(lambda x1, x2: x1 + x2, v1, v2))
788890

891+
def bounces(self, sh):
892+
rad = self.dot(sh.cent, sh.cent)
893+
dir = self.dot(sh.cent, sh.speed)
894+
if rad > 1.0 and dir > 0.0:
895+
return True
896+
for d in range(self.bounds["dim"]):
897+
c = sh.cent[d] * self.bounds["radius"] + self.bounds["center"][d]
898+
if ((c < self.bounds["bounds"][d][0] and sh.speed[d] < 0.0) or
899+
(c > self.bounds["bounds"][d][1] and sh.speed[d] > 0.0)):
900+
return True
901+
if self.bounds["dim"] == 3:
902+
vec = (sh.cent[0], sh.cent[2])
903+
sp = (sh.speed[0], sh.speed[2])
904+
rad = self.dot(vec, vec) ** 0.5
905+
dir = self.dot(vec, sp)
906+
if rad * self.bounds["radius"] > self.bounds["cylradius"] and dir > 0.0:
907+
return True
908+
return False
909+
789910
def update(self, step):
790-
# om utanför, vänd håll med slumpbrus
791911
for sh in self.shapes:
792-
if self.dot(sh.cent, sh.cent) > 1.0 and self.dot(sh.cent, sh.speed) > 0.0:
793-
delta = self.dot(sh.cent, sh.speed) / self.dot(sh.cent, sh.cent)
794-
sh.speed = self.add(sh.speed, self.scale(sh.cent, -2 * delta))
795-
super(BouncingScene, self).update(step)
912+
if self.bounces(sh):
913+
vec = tuple(c + gauss(0.0, 0.1) for c in sh.cent)
914+
vlen2 = self.dot(vec, vec)
915+
delta = self.dot(vec, sh.speed) / vlen2
916+
sh.speed = self.add(sh.speed, self.scale(vec, -2 * delta))
917+
sh.update(step)
796918

797919

798920
# ---------------------------------------------------------

0 commit comments

Comments
 (0)