import mobius, cairo, math, numpy, cmath, time
IMAGE_WIDTH = 1920
IMAGE_HEIGHT = 1080
SCALE = 225.0 # pixels_per_unit
FPS = 60
DURATION = 29
NUM_FRAMES = FPS*DURATION
def draw_frame(path, circles, depth):
# cairo stuff
surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, IMAGE_WIDTH, IMAGE_HEIGHT)
cr = cairo.Context(surface)
cr.set_line_width(1.0)
# define our transforms
generators = []
for circle in circles:
generators.append(mobius.circle_involution(circle[0], circle[1]))
# drawing routine
cr.set_source_rgb(0.0, 1.0, 0.0)
draw_word_action(cr, circles, generators, [], depth)
surface.write_to_png(path)
def draw_word_action(cr, circles, generators, word, depth):
"Apply the given word to circles and draw, then compose a generator and recur."
# apply current word to points and draw
circle_images = [x for x in circles]
for index in word:
circle_images = [generators[index].of_circle(x[0], x[1]) for x in circle_images]
circle_images = [x for x in circle_images if x[1] != 0]
for circle in circle_images:
cr.new_path()
center, radius = circle
x = IMAGE_WIDTH/2 + center.real*SCALE
y = IMAGE_HEIGHT/2 - center.imag*SCALE
cr.arc(x, y, radius*SCALE, 0.0, 2.0*math.pi)
cr.stroke()
if depth > 0:
# recur at smaller depth
for i in range(len(generators)):
gen = generators[i]
set_color_from_palette(cr, i)
if word == [] or word[-1] != i:
draw_word_action(cr, circles, generators, word + [i], depth - 1)
def set_color_from_palette(cr, i):
"Picks the current color based on a pre-set palette."
palette = [(1.0, 0.0, 0.0),
(0.0, 1.0, 0.0),
(0.0, 0.0, 1.0),
(1.0, 1.0, 0.0),
(1.0, 0.0, 1.0),
(0.0, 1.0, 1.0)]
i = i % len(palette)
(r, g, b) = palette[i]
cr.set_source_rgb(r, g, b)
def circles_converge(num_circles, radius, depth):
start_time = time.clock()
radius = 1.0
for frame in range(NUM_FRAMES):
# pick circle centers, radii
distance_from_origin = radius*1.5*(NUM_FRAMES - 2.0*frame)/float(NUM_FRAMES)
circles = []
for i in range(num_circles):
center = cmath.exp(float(i)/num_circles*2j*math.pi)*distance_from_origin
circles.append((center, radius))
report_progress(frame, start_time)
draw_frame(get_filename(frame), circles, depth)
def get_filename(t):
"Takes frame and names it appropriately."
full_digits = len(str(NUM_FRAMES))
digits = len(str(t))
leading_zeroes = ""
for i in range(full_digits - digits):
leading_zeroes = leading_zeroes + "0"
return "frame" + leading_zeroes + str(t) + ".png"
def report_progress(frame_index, start_time):
print "Rendering frame " + str(frame_index) + " of " + str(NUM_FRAMES)
elapsed = time.clock() - start_time
print "Time elapsed: " + str(elapsed)
if frame_index > 0:
seconds_remaining = int(float(NUM_FRAMES - frame_index)*elapsed/frame_index)
seconds = seconds_remaining%60
minutes = (seconds_remaining/60)%60
hours = seconds_remaining/3600
remaining = str(hours) + "h" + str(minutes) + "m" + str(seconds) + "s"
else:
remaining = "(unknown)"
print "Estimated time remaining: " + str(remaining)
circles_converge(3, 1.0, 9)