|
|
|
#!/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)*4) * 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)
|