generated from mwc/project_drawing
113 lines
3.3 KiB
Python
113 lines
3.3 KiB
Python
from math import pi, sqrt, sin, cos, atan
|
|
from turtle import penup, pendown, forward, left, radians, degrees
|
|
|
|
π = pi
|
|
|
|
def ellipse(rx, ry, num_points, points_to_draw=None):
|
|
"""
|
|
Draws an ellipse with x-radius rx and y-radius ry,
|
|
with num_points control points. Optionally specify
|
|
points_to_draw if you want only a partial ellipse.
|
|
Assumes the turtle starts at the center, pointed in
|
|
the +x direction, and ends in the same position and heading.
|
|
"""
|
|
radians()
|
|
φstart = get_angle(rx, ry, num_points, 0) / 2
|
|
fly(rx)
|
|
left(π/2 - φstart)
|
|
for i in range(points_to_draw or num_points):
|
|
φi = get_angle(rx, ry, num_points, i)
|
|
di = get_distance(rx, ry, num_points, i)
|
|
print(i, φi, di)
|
|
left(φi)
|
|
forward(di)
|
|
left(-π/2 + φstart)
|
|
fly(-rx)
|
|
degrees()
|
|
|
|
def fly(distance):
|
|
"""Like forward, but with the pen up.
|
|
"""
|
|
penup()
|
|
forward(distance)
|
|
pendown()
|
|
|
|
def get_angle(rx, ry, n, i):
|
|
"""Returns the angle φi, or how far left the turtle should
|
|
turn at point i of n.
|
|
"""
|
|
coords_prev = get_coordinates(rx, ry, n, i - 1)
|
|
coords = get_coordinates(rx, ry, n, i)
|
|
coords_next = get_coordinates(rx, ry, n, i + 1)
|
|
vec_prev = get_vector(coords_prev, coords)
|
|
vec_next = get_vector(coords, coords_next)
|
|
vec_parallel, vec_perpendicular = project_onto_basis(vec_next, vec_prev)
|
|
φi = atan(mag(vec_perpendicular) / mag(vec_parallel))
|
|
return φi
|
|
|
|
def get_distance(rx, ry, n, i):
|
|
"""
|
|
Returns the distance from point i to point i + 1.
|
|
"""
|
|
coords = get_coordinates(rx, ry, n, i)
|
|
coords_next = get_coordinates(rx, ry, n, i + 1)
|
|
vec_next = get_vector(coords, coords_next)
|
|
return mag(vec_next)
|
|
|
|
def get_coordinates(rx, ry, n, i):
|
|
"""Returns the (x, y) coordinates of point i for the ellipse
|
|
specified by rx, ry, and n.
|
|
|
|
Normally, we can get the coordinates of a point on the circle
|
|
at angle θ as (r * cos(θ), r * sin(θ)). An ellipse is a circle
|
|
scaled differently along the x- and y- axes, so we need to
|
|
apply different scaling factors rx and ry.
|
|
"""
|
|
θi = 2*π*i/n
|
|
xi = rx * cos(θi)
|
|
yi = ry * sin(θi)
|
|
return xi, yi
|
|
|
|
def get_vector(start, end):
|
|
"""Returns a vector from the start point to the end point.
|
|
"""
|
|
x0, y0 = start
|
|
x1, y1 = end
|
|
return (x1 - x0, y1 - y0)
|
|
|
|
def project_onto_basis(vec, basis):
|
|
"""Projects a vector onto a basis vector, returning two vectors:
|
|
the component which is parallel to basis (vec_a) and the component which
|
|
is perpendicular (vec_b).
|
|
"""
|
|
v0, v1 = vec
|
|
unit_basis = normalize(basis)
|
|
b0, b1 = unit_basis
|
|
len_a = dot(vec, unit_basis)
|
|
vec_a = (len_a * b0, len_a * b1)
|
|
vec_b = get_vector(vec_a, vec)
|
|
return vec_a, vec_b
|
|
|
|
def dot(vec0, vec1):
|
|
"""Computes the dot product of vec0 and vec1.
|
|
The result is the magnitude of vec0 projected onto vec1.
|
|
"""
|
|
x0, y0 = vec0
|
|
x1, y1 = vec1
|
|
return x0 * x1 + y0 * y1
|
|
|
|
def normalize(vec):
|
|
"""Scales vec to magnitude 1.
|
|
"""
|
|
x, y = vec
|
|
len_vec = mag(vec)
|
|
return x / len_vec, y / len_vec
|
|
|
|
def mag(vec):
|
|
"""Returns the magnitude, or length, of a vector.
|
|
We can simplify the familiar formula sqrt(x*x + y*y)
|
|
using the dot product.
|
|
"""
|
|
return sqrt(dot(vec, vec))
|
|
|