generated from mwc/project_drawing
Compare commits
8 Commits
c82a594a05
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bf2d2b0774 | ||
|
|
02902dac51 | ||
|
|
c027ffbf9f | ||
|
|
edac317460 | ||
|
|
59ad818194 | ||
|
|
882cedb841 | ||
|
|
0b609de21e | ||
|
|
6bc9ef9f0b |
@@ -9,13 +9,7 @@ which allows basic formatting.
|
||||
|
||||
## Description
|
||||
|
||||
(I would like to create an animated swimming sea turtle with some bubbles.
|
||||
If you want to link to an
|
||||
image, move the image to this directory, and then use the following syntax:
|
||||
|
||||

|
||||
|
||||
)
|
||||
(I would like to create an animated swimming sea turtle with some bubbles.)
|
||||
|
||||
## Planning
|
||||
|
||||
|
||||
34
bubbles.py
Normal file
34
bubbles.py
Normal file
@@ -0,0 +1,34 @@
|
||||
from turtle import *
|
||||
from superturtle.animation import animate
|
||||
from random import random, seed
|
||||
|
||||
HOW_FAR_BUBBLES_GO = 400
|
||||
|
||||
def random_bubble_x():
|
||||
return -200 + 400 * random()
|
||||
|
||||
def random_bubble_y():
|
||||
return -200 + 400 * random()
|
||||
|
||||
def random_bubble_size():
|
||||
return 10 + 40 * random()
|
||||
|
||||
def generate_bubble_positions(n):
|
||||
bubble_positions = []
|
||||
for i in range(n):
|
||||
x = random_bubble_x()
|
||||
y = random_bubble_y()
|
||||
r = random_bubble_size()
|
||||
bubble_positions.append([x, y, r])
|
||||
return bubble_positions
|
||||
|
||||
def draw_bubbles(bubble_positions, frame):
|
||||
for x, y, r in bubble_positions:
|
||||
with frame.translate([x, y], [x, y + HOW_FAR_BUBBLES_GO]):
|
||||
circle(r)
|
||||
|
||||
number_of_bubbles = 25
|
||||
bubble_positions = generate_bubble_positions(number_of_bubbles)
|
||||
for frame in animate(100):
|
||||
draw_bubbles(bubble_positions, frame)
|
||||
|
||||
BIN
chris_ellipse_exploration/attempt_0.png
Normal file
BIN
chris_ellipse_exploration/attempt_0.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 12 KiB |
BIN
chris_ellipse_exploration/demo.gif
Normal file
BIN
chris_ellipse_exploration/demo.gif
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.8 MiB |
6
chris_ellipse_exploration/demo.html
Normal file
6
chris_ellipse_exploration/demo.html
Normal file
@@ -0,0 +1,6 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<body style="background-image: url(demo.gif); background-repeat: cover;">
|
||||
</body>
|
||||
</html>
|
||||
|
||||
26
chris_ellipse_exploration/demo.py
Normal file
26
chris_ellipse_exploration/demo.py
Normal file
@@ -0,0 +1,26 @@
|
||||
from ellipse import ellipse
|
||||
from superturtle.animation import animate
|
||||
from superturtle.easing import easeInOutCubic
|
||||
from turtle import color, fillcolor, begin_fill, end_fill
|
||||
|
||||
n_frames = 128
|
||||
|
||||
def is_even(n):
|
||||
return n % 2 == 0
|
||||
|
||||
def set_colors(n):
|
||||
"Sets the color and fill color to white when n is even, otherwise black."
|
||||
if is_even(n):
|
||||
color('white')
|
||||
fillcolor('white')
|
||||
else:
|
||||
color('black')
|
||||
fillcolor('black')
|
||||
|
||||
for frame in animate(n_frames, loop=True, gif_filename="demo.gif"):
|
||||
for r in reversed(range(2, 30)):
|
||||
set_colors(r)
|
||||
with frame.rotate(0, 360, r, n_frames - r, easing=easeInOutCubic):
|
||||
begin_fill()
|
||||
ellipse(5 * r, 10 * r, 128)
|
||||
end_fill()
|
||||
112
chris_ellipse_exploration/ellipse.py
Normal file
112
chris_ellipse_exploration/ellipse.py
Normal file
@@ -0,0 +1,112 @@
|
||||
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))
|
||||
|
||||
24
chris_ellipse_exploration/tests.py
Normal file
24
chris_ellipse_exploration/tests.py
Normal file
@@ -0,0 +1,24 @@
|
||||
# To run these tests run: python -m unittest tests
|
||||
|
||||
from unittest import TestCase
|
||||
from ellipse import *
|
||||
from math import sqrt
|
||||
|
||||
class TestEllipseFunctions(TestCase):
|
||||
def test_normalize(self):
|
||||
observed = normalize((0, 1))
|
||||
expected = (0, 1)
|
||||
self.assertEqual(observed, expected)
|
||||
|
||||
observed = normalize((2, 0))
|
||||
expected = (1, 0)
|
||||
self.assertEqual(observed, expected)
|
||||
|
||||
observed = normalize((1, 1))
|
||||
expected = (1/sqrt(2), 1/sqrt(2))
|
||||
self.assertEqual(observed, expected)
|
||||
|
||||
def test_project_onto_basis(self):
|
||||
observed = project_onto_basis((4, 3), (4, 0))
|
||||
expected = ((4, 0), (0, 3))
|
||||
self.assertEqual(observed, expected)
|
||||
116
drawing.py
116
drawing.py
@@ -9,11 +9,13 @@
|
||||
from turtle import *
|
||||
from superturtle.movement import fly
|
||||
from superturtle.animation import animate
|
||||
from superturtle.easing import easeInQuad
|
||||
import math
|
||||
import random
|
||||
|
||||
def draw_turtle(size):
|
||||
|
||||
|
||||
pencolor("green")
|
||||
#draw an oval for the shell
|
||||
height = size*2
|
||||
width = size*1.5
|
||||
@@ -52,10 +54,122 @@ def draw_turtle(size):
|
||||
forward(size*1.5)
|
||||
pendown()
|
||||
|
||||
draw_flipper(size, 1)
|
||||
draw_flipper(size, 4)
|
||||
|
||||
def draw_flipper(size, quad):
|
||||
|
||||
width = 2.5 * size
|
||||
height = 1.5 * size
|
||||
steps = 100 # Number of points to smooth the curve
|
||||
|
||||
penup()
|
||||
if quad == 1:
|
||||
angle_range = range(0, steps + 1) # Top half
|
||||
|
||||
for i in angle_range:
|
||||
angle = math.pi * i / steps # Only go from 0 to π radians
|
||||
x = ((width / 2) * math.cos(angle))+(size*2)
|
||||
y = ((height / 2) * math.sin(angle))+size
|
||||
if i == angle_range[0]:
|
||||
goto(x , y )
|
||||
pendown()
|
||||
else:
|
||||
goto(x, y)
|
||||
forward (size*2.5)
|
||||
|
||||
|
||||
elif quad == 4:
|
||||
angle_range = range(0, steps + 1) # Top half
|
||||
|
||||
for i in angle_range:
|
||||
angle = math.pi * i / steps # Only go from 0 to π radians
|
||||
x = ((width / 2) * math.cos(angle))-(size*2)
|
||||
y = ((height / 2) * math.sin(angle))+size
|
||||
if i == angle_range[0]:
|
||||
goto(x , y )
|
||||
pendown()
|
||||
else:
|
||||
goto(x, y)
|
||||
|
||||
forward (size*2.5)
|
||||
|
||||
|
||||
elif quad == 3:
|
||||
angle_range = range(0, steps + 1) # Top half
|
||||
|
||||
for i in angle_range:
|
||||
angle = math.pi * i / steps # Only go from 0 to π radians
|
||||
x = ((width / 2) * math.cos(angle))-(size*2)
|
||||
y = ((height / 2) * math.sin(angle))+size
|
||||
if i == angle_range[0]:
|
||||
goto(x , y )
|
||||
pendown()
|
||||
else:
|
||||
goto(x, y)
|
||||
forward (size*2.5)
|
||||
|
||||
|
||||
else:
|
||||
angle_range = range(0, steps + 1) # Top half
|
||||
|
||||
for i in angle_range:
|
||||
angle = math.pi * i / steps # Only go from 0 to π radians
|
||||
x = ((width / 2) * math.cos(angle))-(size*2)
|
||||
y = ((height / 2) * math.sin(angle))+size
|
||||
if i == angle_range[0]:
|
||||
goto(x , y )
|
||||
pendown()
|
||||
else:
|
||||
goto(x, y)
|
||||
forward (size*2.5)
|
||||
|
||||
|
||||
|
||||
|
||||
def draw_bubble(b_size):
|
||||
pencolor("blue")
|
||||
circle(b_size)
|
||||
|
||||
def place_bubbles(number):
|
||||
#draws bubbles of random sizes and locations in each of the 4 quadrants
|
||||
for i in range(number):
|
||||
x = random.random()*400
|
||||
y = random.random()*200
|
||||
fly(x,y)
|
||||
b_size = random.random()*15
|
||||
draw_bubble(b_size)
|
||||
for i in range(number):
|
||||
x = random.random()*400
|
||||
y = random.random()*200
|
||||
fly(-x,y)
|
||||
b_size = random.random()*15
|
||||
draw_bubble(b_size)
|
||||
for i in range(number):
|
||||
x = random.random()*400
|
||||
y = random.random()*200
|
||||
fly(x,-y)
|
||||
b_size = random.random()*15
|
||||
draw_bubble(b_size)
|
||||
for i in range(number):
|
||||
x = random.random()*400
|
||||
y = random.random()*200
|
||||
fly(-x,-y)
|
||||
b_size = random.random()*15
|
||||
draw_bubble(b_size)
|
||||
fly(0,0)
|
||||
|
||||
"""
|
||||
#pass the number of bubbles you want in each quadrant (so it will be 4 X what you pass)
|
||||
for frame in animate(50, loop=False):
|
||||
x = frame.interpolate(-10, 10)
|
||||
y = frame.interpolate(20, -10, easing=easeInQuad)
|
||||
fly(x, y)
|
||||
place_bubbles(1)
|
||||
"""
|
||||
place_bubbles(5)
|
||||
|
||||
draw_turtle(20)
|
||||
|
||||
done()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user