Skip to content

Instantly share code, notes, and snippets.

@drhodes
Last active December 6, 2022 23:19
Show Gist options
  • Save drhodes/cb0e4a5fa9c50bc40cff8b624a309d5d to your computer and use it in GitHub Desktop.
Save drhodes/cb0e4a5fa9c50bc40cff8b624a309d5d to your computer and use it in GitHub Desktop.
import math
from PIL import Image
from PIL import ImageDraw
'''
to run, need python3
$ python3 -m venv venv
$ source ./venv/bin/activate
$ pip install pillow
$ python main.py
'''
def equal(x, y):
return math.isclose(x, y, rel_tol=1e-10)
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def as_tuple(self):
return self.x, self.y
def __repr__(self):
return str((self.x, self.y))
def __eq__(self, other):
return equal(self.x, other.x) and equal(self.y, other.y)
class Vector:
def __init__(self, p):
self.x = p.x
self.y = p.y
@staticmethod
def from_to(p1, p2):
x = p2.x - p1.x
y = p2.y - p1.y
return Vector(Point(x, y))
def dot(self, other):
return self.x*other.x + self.y*other.y
def mag(self):
return math.sqrt(self.x**2 + self.y**2)
def angle_to(self, other):
return math.acos(self.dot(other)/(self.mag() * other.mag()))
def right_angle_with(self, other):
return equal(self.angle_to(other), math.pi/2)
def normalize(self):
return Vector(Point(self.x/self.mag(), self.y/self.mag()))
def scale(self, s):
return Vector(Point(s * self.x, s * self.y))
def resize(self, s):
return self.normalize().scale(s)
class RightTriangle:
def __init__(self, p1, p2, p3):
self.p1 = p1
self.p2 = p2
self.p3 = p3
self.rp = self.right_corner()
if not self.rp:
raise Exception("this is not a right triangle")
def right_corner(self):
'''the point at the right angle'''
# at p1?
if Vector.from_to(self.p1, self.p2).right_angle_with(Vector.from_to(self.p1, self.p3)):
return self.p1
elif Vector.from_to(self.p2, self.p1).right_angle_with(Vector.from_to(self.p2, self.p3)):
return self.p2
elif Vector.from_to(self.p3, self.p1).right_angle_with(Vector.from_to(self.p3, self.p2)):
return self.p3
return None
def acute_corners(self):
# get the points at the acute corners
rc = self.right_corner()
return [p for p in [self.p1, self.p2, self.p3] if p is not rc]
def draw(self, ctx, depth):
ctx.polygon([
self.p1.as_tuple(),
self.p2.as_tuple(),
self.p3.as_tuple(),
], width=int(depth/2))
def altitude(self):
# https://en.wikipedia.org/wiki/Right_triangle
# find the altitude point on hypotenuse
p1, p2 = self.acute_corners()
rc = self.right_corner()
hypv = Vector.from_to(p1, p2)
legv = Vector.from_to(p1, rc)
theta = legv.angle_to(hypv)
newmag = legv.mag() * math.cos(theta)
altv = hypv.resize(newmag)
return Point(p1.x + altv.x, p1.y+ altv.y)
def split(self):
# return two smaller triangles by splitting on the altitude
p1, p2 = self.acute_corners()
rc = self.right_corner()
pa = self.altitude()
return (RightTriangle(p2, rc, pa),
RightTriangle(p1, rc, pa))
def recurse(self, ctx, depth):
self.draw(ctx, depth)
if depth == 0: return
t1, t2 = self.split()
t1.recurse(ctx, depth-1)
t2.recurse(ctx, depth-1)
def main():
im = Image.new("RGB", (1000, 1000))
ctx = ImageDraw.Draw(im)
ctx.rectangle([(0,0),im.size], fill = (80,80,80))
rt = RightTriangle(Point(100, 100), Point(900, 700), Point(900,100))
rt.recurse(ctx, 12)
im.save("recursive_triangle.png")
if __name__ == "__main__":
main()
@drhodes
Copy link
Author

drhodes commented Dec 1, 2022

image

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment