Skip to content

Instantly share code, notes, and snippets.

@lennyferguson
Last active April 18, 2018 15:17
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save lennyferguson/779ff4c15b17ba6d99226027977e4cbd to your computer and use it in GitHub Desktop.
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
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