From f05ccbe166fd2501be3502f81f0562e73291fa04 Mon Sep 17 00:00:00 2001 From: Aaron Perley Date: Sun, 9 Oct 2016 18:00:10 -0400 Subject: Refactor bridge, fish show, and sunrise show code --- bridge.py | 79 +++++++++++++----------------------- brige.py | 100 ---------------------------------------------- color.py | 50 ++++++++++++----------- fish_render.py | 123 --------------------------------------------------------- fish_show.py | 117 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ show.py | 39 ++++++++++++++++++ sunrise.py | 28 ++++++------- 7 files changed, 223 insertions(+), 313 deletions(-) mode change 100755 => 100644 bridge.py delete mode 100755 brige.py delete mode 100644 fish_render.py create mode 100644 fish_show.py create mode 100644 show.py diff --git a/bridge.py b/bridge.py old mode 100755 new mode 100644 index 4d5d817..70adb99 --- a/bridge.py +++ b/bridge.py @@ -1,12 +1,7 @@ -#!/usr/bin/python2 -"""Simple demo of oscillating colors.""" -from __future__ import division, print_function -import color -import constants -import fish_render -import sunrise -import time -import random +from fish_show import FishShow +from sunrise import SunriseShow +from constants import * + try: import lumiversepython except ImportError: @@ -15,55 +10,35 @@ except ImportError: else: have_lumiversepython = True +class Bridge(object): + WIDTH = 200 + HEIGHT = 2 + def __init__(self): + if have_lumiversepython: + self.rig = lumiversepython.Rig('/home/teacher/Lumiverse/PBridge.rig.json') + else: + self.rig = DummyRig(self.WIDTH, self.HEIGHT) + self.rig.init() + self.lights = self.get_lights() + def update(self): + self.rig.updateOnce() -#fish_render = sunrise - - - - - - -def get_all_lights(rig): - """Return a list of all panels.""" - return [ - [ - fish_render.Light( - x, int(y == 'top'), - rig.select('$side={y}[$sequence={x}]'.format(y=y, x=x)) - ) for y in ('top', 'bot') - ] for x in xrange(1, 200) - ] + def get_light(self, x, y): + return self.lights[x][y] + def get_lights(self): + return [ [ self.rig.select('$side={y}[$sequence={x}]'.format(y=y, x=x)) + for y in ('bot', 'top') ] + for x in xrange(1, self.WIDTH+1)] -def demo(lights, rig): - """Do some light things.""" - #fish = fish_render.Fish(0, 0, 15, 1.5, color.black, 0.05 / constants.FRAME_RATE) - #fish = fish_render.Fish(0, 0, 23, 1.5, color.RGB(193 / 255, 68 / 255, 227 / 255), 0.05 / constants.FRAME_RATE) - fish = fish_render.Fish(0, 0, 23, 1.5, color.black, 0.05 / constants.FRAME_RATE) - bridge = fish_render.Bridge(lights, fish, rig) - current_time = time.time() - count = 0 - while True: - bridge.update() - if count % constants.FRAME_RATE == 0: - time_delta = time.time() - current_time - print(time_delta) - current_time = time.time() - #time.sleep(1 / constants.FRAME_RATE) - count += 1 def main(): - """Run show.""" - if have_lumiversepython: - rig = lumiversepython.Rig('/home/teacher/Lumiverse/PBridge.rig.json') - else: - rig = DummyRig(200, 2) - rig.init() - lights = get_all_lights(rig) - demo(lights, rig) + bridge = Bridge() + show = FishShow(bridge) + #show = SunriseShow(bridge) + show.run(framerate=FRAME_RATE) -if __name__ == '__main__': - main() +main() \ No newline at end of file diff --git a/brige.py b/brige.py deleted file mode 100755 index 314f9db..0000000 --- a/brige.py +++ /dev/null @@ -1,100 +0,0 @@ -#!/usr/bin/python2 -from __future__ import division, print_function -import color -import constants -import curses -import fish_render -import time -import random -try: - import lumiversepython -except ImportError: - have_lumiversepython = False - from DummyRig import DummyRig -else: - have_lumiversepython = True - -def get_all_lights(rig): - """Return a list of all panels.""" - return [ - [ - fish_render.Light( - x, int(y == 'top'), - rig.select('$side={y}[$sequence={x}]'.format(y=y, x=x)) - ) for y in ('top', 'bot') - ] for x in xrange(1, 200) - ] - - -def colorSection(lights, color, *ranges): - for start, end in ranges: - map(lambda l: map(lambda x: x.light.setRGBRaw(*color), l), lights[start:end]) - -def onSection(lights, *ranges): - colorSection(lights, (1, 1, 1), *ranges) - -def offSection(lights, *ranges): - colorSection(lights, (0, 0, 0), *ranges) - -def mapKey(key): - if key == ord('a'): - return (0, 20) - if key == ord('o'): - return (20, 40) - if key == ord('e'): - return (40, 60) - if key == ord('u'): - return (60, 80) - if key == ord('i'): - return (80, 100) - if key == ord('d'): - return (100, 120) - if key == ord('h'): - return (120, 140) - if key == ord('t'): - return (140, 160) - if key == ord('n'): - return (160, 180) - if key == ord('s'): - return (180, 200) - return (0, 0) - -def demo(lights, rig, screen): - """Do some light things.""" - sections = [] - while True: - k = screen.getch() - offSection(lights, *sections) - r = mapKey(k) - try: - i = sections.index(r) - except ValueError: - sections.append(r) - else: - del sections[i] - onSection(lights, *sections) - rig.updateOnce() - -def main(): - """Run show.""" - if have_lumiversepython: - rig = lumiversepython.Rig('/home/teacher/Lumiverse/PBridge.rig.json') - else: - rig = DummyRig(200, 2) - rig.init() - stdscr = curses.initscr() - stdscr.keypad(1) - curses.noecho() - - lights = get_all_lights(rig) - try: - demo(lights, rig, stdscr) - except BaseException: - curses.nocbreak() - stdscr.keypad(0) - curses.echo() - curses.endwin() - raise - -if __name__ == '__main__': - main() diff --git a/color.py b/color.py index a38c28b..058abb4 100644 --- a/color.py +++ b/color.py @@ -4,41 +4,45 @@ import colorsys import math class Vector(object): - """N-dimensional vector class.""" - def __init__(self, *components): + __slots__ = ('x', 'y', 'z') + + """3-dimensional vector class.""" + def __init__(self, x, y, z): """Create a vector from its components.""" - self.components = components + self.x = x + self.y = y + self.z = z def __add__(self, other): """Add two vectors.""" - return self.__class__( - *map(lambda (x, y): x + y, zip(self.components, other.components)) - ) + return self.__class__(self.x + other.x, self.y + other.y, self.z + other.z) def __sub__(self, other): """Subtract two vectors.""" - return self.__class__( - *map(lambda (x, y): x - y, zip(self.components, other.components)) - ) + return self.__class__(self.x - other.x, self.y - other.y, self.z - other.z) - def __mul__(self, other): + def __mul__(self, scalar): """Multiply a vector by a constant.""" - return self.__class__(*map(lambda x: x * other, self.components)) + return self.__class__(self.x * scalar, self.y * scalar, self.z * scalar) - def __rmul__(self, other): + def __rmul__(self, scalar): """Multiply a constant by a vector.""" - return self * other + return self * scalar def __abs__(self): """Return the 2-norm of the vector.""" - return math.sqrt(sum(map(lambda x: x * x, self.components))) + return math.sqrt(self.x * self.x + self.y * self.y + self.z * self.z) def __truediv__(self, scalar): """Return self * 1 / scalar.""" return self * (1 / scalar) def __eq__(self, other): - return self.components == other.components + return self.x == other.x and self.y == other.y and self.z == other.z + + @property + def components(self): + return (self.x, self.y, self.z) class HSV(Vector): @@ -49,19 +53,19 @@ class HSV(Vector): def to_RGB(self): """Convert to an RGB vector.""" - return RGB(*colorsys.hsv_to_rgb(*self.components)) + return RGB(*colorsys.hsv_to_rgb(self.x, self.y, self.z)) def h(self): """Return hue.""" - return self.components[0] + return self.x def s(self): """Return saturation.""" - return self.components[1] + return self.y def v(self): """Return value.""" - return self.components[2] + return self.z class RGB(Vector): @@ -72,19 +76,19 @@ class RGB(Vector): def to_HSV(self): """Convert to HSV vector.""" - return HSV(*colorsys.rgb_to_hsv(*self.components)) + return HSV(*colorsys.rgb_to_hsv(self.x, self.y, self.z)) def r(self): """Return red value.""" - return self.components[0] + return self.x def g(self): """Return green value.""" - return self.components[1] + return self.y def b(self): """Return blue value.""" - return self.components[2] + return self.z red = RGB(1, 0, 0) green = RGB(0, 1, 0) diff --git a/fish_render.py b/fish_render.py deleted file mode 100644 index 18aecac..0000000 --- a/fish_render.py +++ /dev/null @@ -1,123 +0,0 @@ -"""Rendering classes and helper functions.""" -from __future__ import division, unicode_literals -import color -import random -import constants -from constants import * -import math - -class Fish(object): - """Object displayed on the bridge, probably a fish.""" - def __init__(self, x, y, width, height, color, step): - """Create fish with default parameters.""" - self.x = x - self.y = y - self.width = width - self.height = height - self.color = color - self.speed = step - self.step = step - - def update(self): - """Swim gently to the other side of the bridge.""" - self.x += self.step - if self.x > 200: - self.x = 0 - self.y = 0.25 + 0.25 * math.sin(math.pi * self.x / 10) - #theta = random.random() * math.pi / 2 - math.pi / 4 - #self.y += 0.01 * math.sin(theta) - #print(self.y) - - -class Light(object): - """Represent a bridge light and its state.""" - def __init__(self, x, y, light): - """Store all facts about a light.""" - self.light = light - self.color = color.black - self.transitioning_to = self.color - self.step = 0 - self.x = x - self.y = y - - def update(self, fish): - """Change color depending on where the fish is.""" - if abs(self.transitioning_to - self.color) <= self.step: - self.transitioning_to = pick_HSV().to_RGB() - self.step = (random_change() - * abs(self.color - self.transitioning_to)) - new_background = transition( - self.color, self.transitioning_to, self.step - ) - coverage = compute_coverage(fish, self.x, self.y) - #coverage *= coverage - new_color = coverage * fish.color + (1 - coverage) * new_background - #new_color = new_background - self.color = new_background - if self.x == 30 and self.y == 0: - pass - #print(self.color.r(), self.color.g(), self.color.b()) - self.light.setRGBRaw( - *map(lambda x: clamp(0, x, 1), new_color.components) - ) - - -class Bridge(object): - """Store objects on the bridge.""" - def __init__(self, lights, fish, rig): - """Initialize with lights and fish.""" - self.lights = lights - self.fish = fish - self.rig = rig - - def update(self): - """Compute fish coverages and push lights to bridge.""" - for x, light_col in enumerate(self.lights): - for y, light in enumerate(light_col): - self.fish.update() - light.update(self.fish) - self.rig.updateOnce() - - -def compute_coverage(fish, x, y): - """Calculate the coverage of a fish over a box.""" - middle_of_fish = fish.x + fish.width / 2 - middle_of_panel = x + 0.5 - diff = abs(middle_of_fish - middle_of_panel) / (fish.width / 2) - scale = 1 - diff - min_x = min(max(x, fish.x), x + 1) - max_x = max(min(x+1, fish.x + fish.width), x) - min_y = min(max(y, fish.y), y + 1) - max_y = max(min(y+1, fish.y + fish.height), y) - coverage = (max_x - min_x) * (max_y - min_y) - return coverage * scale - - -def pick_HSV(): - """Return a random color in an appropriate interval.""" - hue = random.random() * (constants.MAX_HUE - constants.MIN_HUE) + constants.MIN_HUE - saturation = random.random() * (MAX_SATURATION - MIN_SATURATION) + MIN_SATURATION - value = random.random() * (MAX_VALUE - MIN_VALUE) + MIN_VALUE - return color.HSV(hue, saturation, value) - - -def transition(start, end, step): - """Return a vector equal to (end - start) * step + start.""" - if start == end: - return start - diff_vector = end - start - step_vector = diff_vector / abs(diff_vector) * step - result = step_vector + start - return result - - -def random_change(): - """Return a random amount that a light should change each frame.""" - min_time = int(constants.MIN_TIME * FRAME_RATE) - max_time = int(MAX_TIME * FRAME_RATE) - return 1 / random.randint(min_time, max_time) - - -def clamp(low, x, high): - """Return v such that low <= v <= high and |x - v| is minimal.""" - return max(low, min(x, high)) diff --git a/fish_show.py b/fish_show.py new file mode 100644 index 0000000..cda4174 --- /dev/null +++ b/fish_show.py @@ -0,0 +1,117 @@ +from __future__ import division +import color +import random +import constants +from constants import * +import math +from show import Show + +class Fish(object): + """Object displayed on the bridge, probably a fish.""" + def __init__(self, x, y, width, height, color, step): + """Create fish with default parameters.""" + self.x = x + self.y = y + self.width = width + self.height = height + self.color = color + self.speed = step + self.step = step + + def update(self): + """Swim gently to the other side of the bridge.""" + self.x += self.step + if self.x > 200: + self.x = 0 + self.y = 0.25 + 0.25 * math.sin(math.pi * self.x / 10) + #theta = random.random() * math.pi / 2 - math.pi / 4 + #self.y += 0.01 * math.sin(theta) + #print(self.y) + +class Light(object): + """Represent a bridge light and its state.""" + def __init__(self, x, y, light): + """Store all facts about a light.""" + self.light = light + self.color = color.black + self.transitioning_to = self.color + self.step = 0 + self.x = x + self.y = y + + def update(self, fish): + """Change color depending on where the fish is.""" + if abs(self.transitioning_to - self.color) <= self.step: + self.transitioning_to = pick_HSV().to_RGB() + self.step = (random_change() + * abs(self.color - self.transitioning_to)) + new_background = transition( + self.color, self.transitioning_to, self.step + ) + coverage = compute_coverage(fish, self.x, self.y) + #coverage *= coverage + new_color = coverage * fish.color + (1 - coverage) * new_background + #new_color = new_background + self.color = new_background + if self.x == 30 and self.y == 0: + pass + #print(self.color.r(), self.color.g(), self.color.b()) + self.light.setRGBRaw( + *map(lambda x: clamp(0, x, 1), new_color.components) + ) + +def compute_coverage(fish, x, y): + """Calculate the coverage of a fish over a box.""" + middle_of_fish = fish.x + fish.width / 2 + middle_of_panel = x + 0.5 + diff = abs(middle_of_fish - middle_of_panel) / (fish.width / 2) + scale = 1 - diff + min_x = min(max(x, fish.x), x + 1) + max_x = max(min(x+1, fish.x + fish.width), x) + min_y = min(max(y, fish.y), y + 1) + max_y = max(min(y+1, fish.y + fish.height), y) + coverage = (max_x - min_x) * (max_y - min_y) + return coverage * scale + + +def pick_HSV(): + """Return a random color in an appropriate interval.""" + hue = random.random() * (constants.MAX_HUE - constants.MIN_HUE) + constants.MIN_HUE + saturation = random.random() * (MAX_SATURATION - MIN_SATURATION) + MIN_SATURATION + value = random.random() * (MAX_VALUE - MIN_VALUE) + MIN_VALUE + return color.HSV(hue, saturation, value) + + +def transition(start, end, step): + """Return a vector equal to (end - start) * step + start.""" + if start == end: + return start + diff_vector = end - start + step_vector = diff_vector / abs(diff_vector) * step + result = step_vector + start + return result + + +def random_change(): + """Return a random amount that a light should change each frame.""" + min_time = int(constants.MIN_TIME * FRAME_RATE) + max_time = int(MAX_TIME * FRAME_RATE) + return 1 / random.randint(min_time, max_time) + + +def clamp(low, x, high): + """Return v such that low <= v <= high and |x - v| is minimal.""" + return max(low, min(x, high)) + +class FishShow(Show): + def init(self): + self.lights = [Light(x, y, self.bridge.get_light(x, y)) + for y in xrange(self.bridge.HEIGHT) + for x in xrange(self.bridge.WIDTH)] + self.fish = Fish(0, 0, 23, 1.5, color.black, 1) + + + def update(self): + self.fish.update() + for light in self.lights: + light.update(self.fish) \ No newline at end of file diff --git a/show.py b/show.py new file mode 100644 index 0000000..f8544c9 --- /dev/null +++ b/show.py @@ -0,0 +1,39 @@ +from __future__ import division, print_function +import time + +class Clock(object): + def __init__(self): + self.last_tick = time.time() + + def tick(self, framerate): + sec_per_frame = 1.0 / framerate + diff = time.time() - self.last_tick + while diff < sec_per_frame: + diff = time.time() - self.last_tick + + if diff > (1.05 * sec_per_frame): + print("WARN: tick is not being called fast enough\n", + "target {}, actual {}".format(sec_per_frame, diff)) + self.last_tick = time.time() + + +class Show(object): + def __init__(self, bridge): + self.bridge = bridge + self.clock = Clock() + self.running = True + self.init() + + def run(self, framerate=40): + while self.running: + self.update() + self.bridge.update() + self.clock.tick(framerate) + + def init(self): + """Override me""" + raise NotImplementedError + + def update(self): + """Override me""" + raise NotImplementedError diff --git a/sunrise.py b/sunrise.py index 68a423c..7e11ee3 100644 --- a/sunrise.py +++ b/sunrise.py @@ -5,8 +5,9 @@ import random import constants from constants import * import math +from show import Show -class Fish(object): +class Sun(object): """Object displayed on the bridge, probably a fish.""" def __init__(self, x, y, width, height, c, step): """Create fish with default parameters.""" @@ -33,7 +34,7 @@ class Fish(object): #theta = random.random() * math.pi / 2 - math.pi / 4 #self.y += 0.01 * math.sin(theta) #print(self.y) - self.y += 0.000005 + self.y += 0.005 if self.y >= 1: self.y = 1 self.wait += 1 @@ -66,21 +67,18 @@ class Light(object): ) -class Bridge(object): - """Store objects on the bridge.""" - def __init__(self, lights, fish, rig): - """Initialize with lights and fish.""" - self.lights = lights - self.fish = fish - self.rig = rig +class SunriseShow(Show): + def init(self): + self.lights = [Light(x, y, self.bridge.get_light(x, y)) + for y in xrange(self.bridge.HEIGHT) + for x in xrange(self.bridge.WIDTH)] + self.sun = Sun(0, 0, 23, 1.5, color.black, 1) + def update(self): - """Compute fish coverages and push lights to bridge.""" - for x, light_col in enumerate(self.lights): - for y, light in enumerate(light_col): - self.fish.update() - light.update(self.fish) - self.rig.updateOnce() + self.sun.update() + for light in self.lights: + light.update(self.sun) def compute_coverage(fish, x, y): -- cgit v1.3.1