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))