diff --git a/generators/movingshapes.py b/generators/movingshapes.py new file mode 100755 index 0000000..35be714 --- /dev/null +++ b/generators/movingshapes.py @@ -0,0 +1,222 @@ +#!/usr/bin/env python3 + +import sys +import random +import argparse +import collections +import blup.frame +import blup.output +import blup.animation +import blup.writebml +import colorsys +import math + + +WIDTH = 22 +HEIGHT = 16 +DEPTH = 256 + +_ALLSHAPES = [ + [[0, 1, 0, 1, 0], + [1, 1, 1, 1, 1], + [1, 1, 1, 1, 1], + [0, 1, 1, 1, 0], + [0, 0, 1, 0, 0]], + + [[1, 0, 0, 0, 1], + [0, 0, 1, 0, 0], + [0, 0, 1, 0, 0], + [1, 0, 0, 0, 1], + [0, 1, 1, 1, 0]], + + [[1, 0, 1, 0, 1], + [0, 1, 1, 1, 0], + [0, 1, 1, 1, 0], + [0, 1, 0, 1, 0], + [1, 1, 0, 1, 1]], +] + + + +class Point(collections.namedtuple('Point', ['x', 'y'])): + def __add__(self, other): + return Point(self.x + other.x, self.y + other.y) + def __sub__(self, other): + return Point(self.x - other.x, self.y - other.y) + +ALLSHAPES = [] +for s in _ALLSHAPES: + ALLSHAPES.append(set()) + y = 0 + for l in s: + x = 0 + for i in l: + if i == 1: + ALLSHAPES[-1].add(Point(x, y)) + x += 1 + y += 1 + + + + +def convert_color(c): + return list(map(lambda x: int(round(x*(DEPTH-1))), c)) + + +def get_random_color(): + return colorsys.hsv_to_rgb(random.random(), 1, 1) + + +class Shape: + def __init__(self, shape, pos, color): + self.shape, self.pos, self.color = shape, pos, color + self.period = 2 + self.fadetime = 0.5 + random.random() / 5 + self.t = random.random() * self.period + self.t = 0 + self.oldv = 1 + self.v = 1 + self.newshape = None + + def update(self, t, dt): + self.t += dt + if self.t > self.period: + t = self.t - self.period + if t >= self.fadetime: + self.v = 1 + self.t = random.random() * self.period + else: + self.v = math.cos(t * (math.pi / self.fadetime)) ** 2 + if (self.newshape is not None \ + and (self.oldv < self.v or self.v == 0)): + self.shape = self.newshape + self.color = self.newcolor + self.newshape = None + self.oldv = self.v + + def change_shape_and_color(self, newshape, newcolor): + self.newshape = newshape + self.newcolor = newcolor + self.t = self.period + + def draw(self, frame, origin=Point(0, 0)): + h, s, v = colorsys.rgb_to_hsv(*self.color) + for p in self.shape: + try: + pos = self.pos + p - origin + c = colorsys.hsv_to_rgb(h, s, v * self.v) + frame.setPixel(pos.x, pos.y, convert_color(c)) + except ValueError: + pass + + +class World: + def __init__(self, size, allshapes, gap=2): + self.size, self.allshapes, self.gap = size, allshapes, gap + self.shapes = {} + self.pos = Point(0, 0) + self.cycletime = 60 + self.curshape = random.choice(allshapes) + self.curcolor = get_random_color() + + self.shapesize = Point(0, 0) + for s in allshapes: + assert min(map(lambda p: p.x, s)) >= 0 + assert min(map(lambda p: p.y, s)) >= 0 + shapesize = Point( + max(map(lambda p: p.x, s)) + 1, + max(map(lambda p: p.y, s)) + 1, + ) + if shapesize.x > self.shapesize.x: + self.shapesize = Point(shapesize.x, self.shapesize.y) + if shapesize.y > self.shapesize.y: + self.shapesize = Point(self.shapesize.x, shapesize.y) + print (self.shapesize) + + @property + def shape_space(self): + ret = set() + for x in range(self.size.x): + for y in range(self.size.y): + p = self.pos + Point(x, y) + if (p.x % (self.shapesize.x + self.gap) >= self.gap + and p.y % (self.shapesize.y + self.gap) >= self.gap): + ret.add(p) + return ret + + def get_shape_coverage(self, shape): + return { shape.pos + Point(x, y) for x in range(self.shapesize.x) \ + for y in range(self.shapesize.y) } + + def update(self, t, dt): + self.pos = Point( + int(math.sin((t * math.pi/self.cycletime)*5) * self.size.x), + int(math.cos((t * math.pi/self.cycletime)*5) * self.size.y) + ) + + space = self.shape_space + for s in self.shapes.values(): + space -= self.get_shape_coverage(s) + while len(space) > 0: # and len(self.shapes) < 1: + print('...') + p = space.pop() + newpos = Point( + (p.x // (self.shapesize.x + self.gap)) \ + * (self.shapesize.x + self.gap) + self.gap, + (p.y // (self.shapesize.y + self.gap)) \ + * (self.shapesize.y + self.gap) + self.gap + ) + print(p, newpos) + # TODO improve shape and color selection + newshape = Shape(self.curshape, newpos, self.curcolor) + assert newpos in self.get_shape_coverage(newshape) + print('covered', self.get_shape_coverage(newshape)) + print('space before', len(space)) + space -= self.get_shape_coverage(newshape) + print('space after ', len(space)) + assert newpos not in self.shapes + self.shapes[newpos] = newshape + print('new', len(self.shapes)) + #print(space) + + newshape = None + newcolor = None + if round(t*1000) % 3000 == 0: + newshape = random.choice(self.allshapes) + self.curshape = newshape + newcolor = get_random_color() + self.curcolor = newcolor + for s in self.shapes.values(): + if newshape is not None: + s.change_shape_and_color(newshape, newcolor) + s.update(t, dt) + + def draw(self, frame): + for s in self.shapes.values(): + s.draw(frame, self.pos) + + +if __name__ == '__main__': + parser = argparse.ArgumentParser(description='Generate animations') + parser.add_argument('-d', '--delay', type=int, default=50) + parser.add_argument('-t', '--time', type=int, default=15) + parser.add_argument('output_file') + args = parser.parse_args() + + dim = blup.frame.FrameDimension(WIDTH, HEIGHT, DEPTH, 3) + anim = blup.animation.Animation(dim) + anim.tags['description'] = ' '.join(sys.argv) + + w = World(Point(WIDTH, HEIGHT), ALLSHAPES) + t = 0 + dt = args.delay / 1000 + while t < args.time: + t += dt + frame = blup.animation.AnimationFrame(dim, args.delay) + + w.update(t, dt) + w.draw(frame) + + anim.addFrame(frame) + + blup.writebml.writeBml(anim, args.output_file)