You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

223 lines
6.5 KiB

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