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)