Last active
April 18, 2018 15:17
-
-
Save lennyferguson/779ff4c15b17ba6d99226027977e4cbd to your computer and use it in GitHub Desktop.
Draw Bot script that creates and renders an approximation of a circle by subdividing a polyhedron
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import math | |
from functools import reduce | |
""" | |
Vector Class to handle 2D transformations and operations | |
""" | |
class Vec2(object): | |
def __init__(self, x,y): | |
self.x = x | |
self.y = y | |
self.len = None | |
# Define higher order functions | |
# for various transformations | |
def map(self, fn): | |
return Vec2(fn(self.x), fn(self.y)) | |
def map2(self, other, fn): | |
return Vec2(fn(self.x, other.x), fn(self.y, other.y)) | |
def reduce(self,fn,default=0): | |
return fn(fn(default, self.x), self.y) | |
# Implement arithmetic operation overloads | |
def __add__(self,other): | |
return self.map2(other, lambda a,b: a + b) | |
def __sub__(self, other): | |
return self.map2(other, lambda a,b: a - b) | |
def __mul__(self, other): | |
if isinstance(other, (int, float)): | |
return self.map(lambda a: a * other) | |
else: | |
return self.map2(other, lambda a,b: a * b) | |
def __div__(self, other): | |
if isinstance(other, (int, float)): | |
return self.map(lambda a: a / other) | |
else: | |
return self.map2(other, lambda a,b: a / b) | |
# Implement iterator interface function | |
def __iter__(self): | |
return iter([self.x, self.y]) | |
#Various additional useful vector operations | |
def mix(self,other,amt): | |
return self.map2(other, lambda a,b: a * (1.0 - amt) + b * amt) | |
def dot(self, other): | |
return (self * other).reduce(lambda a,b: a + b) | |
def length(self): | |
return math.sqrt(self.dot(self)) | |
def normalize(self): | |
len = self.length() | |
return Vec2(self.x / len, self.y / len) | |
# Define pairs of verts for base square template for circle | |
# | |
# C | |
# | |
# B D | |
# | |
# A | |
# | |
square = [ | |
(Vec2( 0,-1), Vec2(-1, 0)), #A -> B | |
(Vec2(-1, 0), Vec2( 0, 1)), #B -> C | |
(Vec2( 0, 1), Vec2( 1, 0)), #C -> D | |
(Vec2( 1, 0), Vec2( 0,-1))] #D -> A | |
""" | |
Recursively Interpolate Points to specified depth | |
""" | |
def interp(start, end, depth): | |
if depth == 0: | |
return [start] | |
else: | |
mid = start.mix(end,0.5).normalize() | |
return interp(start,mid, depth - 1) + interp(mid,end, depth - 1) | |
""" | |
Interpolate points between Vert pairs | |
""" | |
def sphere(depth): | |
return reduce(lambda pts, pair: pts + interp(*pair,depth), square, []) | |
visualize = True | |
""" | |
Draws the sphere at (origin, origin) with | |
the argument radius and color. | |
The Verts should be the normalized to [-1,1] in x and y | |
and are translated and scaled by the render function to the center | |
of the window. | |
""" | |
def draw(origin, radius, color, verts): | |
newPath() | |
fill(*color) | |
sphere = list(map(lambda p: (p * radius) + Vec2(origin,origin), verts)) | |
first, rest = sphere[0], sphere[1:] | |
moveTo(first) | |
for pt in rest: | |
lineTo(pt) | |
closePath() | |
drawPath() | |
fill(1.0, 0.0, 0.0) | |
if visualize: | |
for pt in sphere: | |
rect(pt.x - 2, pt.y - 2, 4, 4) | |
""" | |
Setup multiple render calls for spheres | |
rendered at multiple interpolation depths | |
Render n = depth + 1 shrinking spheres per | |
image to visualy represent the 'depth' | |
""" | |
DIM = 800 | |
CENTER = DIM / 2 | |
size(DIM,DIM) | |
colors = [ | |
(85.0/255, 143.0/255, 110.0/255), | |
(149.0/255,188.0/255,167.0/255)] | |
for depth in range(11): | |
newPage(DIM,DIM) | |
frameDuration(0.5) | |
radius = CENTER - 15 | |
verts = sphere(depth) | |
for r in range(depth + 1): | |
draw(CENTER,radius, colors[r % 2], verts) | |
radius *= 0.75 | |
saveImage("~/Desktop/sphere-ify.gif") | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment