Last active
August 2, 2020 16:27
-
-
Save PardhavMaradani/f229b4f36a95978779f12db558ca89f0 to your computer and use it in GitHub Desktop.
Blog: Bezier Curve Animations
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# http://pardhav-m.blogspot.com/2020/06/bezier-curve-animations.html | |
# https://trinket.io/embed/python/99a4c5990a | |
# https://pardhav-m.blogspot.com/2020/06/bezier-curve-animations.html | |
# Bezier Curve Animation | |
# Bezier Curves | |
import turtle | |
import random | |
import math | |
import time | |
# screen setup | |
g_screen = turtle.Screen() | |
g_wh = g_screen.window_height() | |
g_ww = 1.5 * g_wh | |
g_screen.setup(g_ww, g_wh) | |
g_screen_radius = g_wh / 2 | |
# other globals | |
g_b_side = g_screen_radius * 0.1 | |
g_b_side2 = g_b_side / 2 | |
g_playing = True | |
g_pen_colors = ['black', 'blue', 'firebrick', 'green', 'saddle brown', 'orange red', 'indian red', 'medium violet red'] | |
g_min_cps = 2 | |
g_max_cps = 8 | |
g_n_cps = 3 | |
g_control_points = [] | |
g_control_points_coords = [] | |
g_n_steps = 50 # number of steps of t between [0, 1] | |
g_t = 0 | |
g_t_step = 1 / g_n_steps | |
# turtle to draw connecting lines | |
g_dt = turtle.Turtle() | |
g_dt.speed(0) | |
g_dt.ht() | |
# turtle to draw bezier points | |
g_bt = turtle.Turtle() | |
g_bt.speed(0) | |
g_bt.ht() | |
g_bt.pensize(3) | |
g_bt.color('red') | |
# draw borders | |
def draw_borders(): | |
t = turtle.Turtle() | |
t.speed(0) | |
t.pu() | |
t.goto(-g_ww/2, -g_wh/2) | |
t.pd() | |
for i in range(4): | |
t.fd(g_ww if i % 2 == 0 else g_wh) | |
t.left(90) | |
t.pu() | |
t.goto(-g_ww/2, g_wh/2) | |
t.pd() | |
for i in range(4): | |
t.fd(g_b_side*8 if i % 2 == 0 else g_b_side*3) | |
t.right(90) | |
# handle play/pause button click | |
def handle_pp_click(x, y): | |
global g_playing | |
g_playing = not g_playing | |
if g_playing: | |
start_loop() | |
# handle inc button click | |
def handle_inc_click(x, y): | |
global g_n_cps | |
if g_n_cps < g_max_cps: | |
create_control_point(g_n_cps) | |
g_n_cps += 1 | |
update_control_points() | |
# handle dec button click | |
def handle_dec_click(x, y): | |
global g_n_cps | |
if g_n_cps > g_min_cps: | |
g_n_cps -= 1 | |
cp = g_control_points.pop() | |
cp.clear() | |
cp.ht() | |
del cp | |
update_control_points() | |
# create a button | |
def create_button(name, shape, handler, x, y, angle = 0): | |
t = turtle.Turtle() | |
t.speed(0) | |
t.shape(shape) | |
t.pu() | |
if shape == 'b_square': | |
t.goto(x - g_b_side2 - 15, y - g_b_side) | |
else: | |
t.goto(x - g_b_side2, y - g_b_side) | |
t.write(name) | |
t.goto(x, y) | |
t.seth(angle) | |
t.onclick(handler) | |
return t | |
# create buttons on top left | |
def create_buttons(): | |
# custom shapes | |
triangle_shape = [(-g_b_side2, 0), (g_b_side2, 0), (0, g_b_side2)] | |
g_screen.register_shape('b_triangle', triangle_shape) | |
square_shape = [(-g_b_side2, -g_b_side2), (g_b_side2, -g_b_side2), (g_b_side2, g_b_side2), (-g_b_side2, g_b_side2)] | |
g_screen.register_shape('b_square', square_shape) | |
x = -g_ww/2 + g_b_side + 20 | |
y = g_wh/2 - g_b_side - 10 | |
create_button('play / pause', 'b_square', handle_pp_click, x, y) | |
create_button('inc', 'b_triangle', handle_inc_click, x + g_b_side*2.5, y, 90) | |
create_button('dec', 'b_triangle', handle_dec_click, x + g_b_side*5, y, -90) | |
# control point class | |
class ControlPoint(turtle.Turtle): | |
def __init__(self, num = 0, x = 0, y = 0): | |
turtle.Turtle.__init__(self) | |
self.num = num | |
self.clicked = False | |
self.dragging = False | |
self.is_playing = None | |
self.speed(0) | |
self.pu() | |
self.goto(x, y) | |
self.fd(10) | |
self.write("p" + str(self.num)) | |
self.bk(10) | |
# click/release/drag handlers | |
self.onclick(self.onclick_handler) | |
self.onrelease(self.onrelease_handler) | |
self.ondrag(self.ondrag_handler) | |
# handle click | |
def onclick_handler(self, x, y): | |
global g_playing | |
self.clicked = True | |
self.is_playing = g_playing | |
g_playing = False | |
# handle release | |
def onrelease_handler(self, x, y): | |
global g_playing | |
self.clicked = False | |
if self.is_playing: | |
g_playing = True | |
start_loop() | |
# handle drag | |
def ondrag_handler(self, x, y): | |
if not self.clicked or self.dragging: | |
return | |
self.dragging = True | |
self.clear() | |
self.goto(x, y) | |
self.fd(10) | |
self.write("p" + str(self.num)) | |
self.bk(10) | |
update_control_points() | |
self.dragging = False | |
# create a control point | |
# i - control point number | |
def create_control_point(i): | |
mh = int(g_wh * 0.8) | |
x = random.randrange(0, mh) - g_wh/2 | |
y = random.randrange(0, mh) - g_wh/2 | |
cp = ControlPoint(i, x, y) | |
cp.shape('cp') | |
g_control_points.append(cp) | |
# create initial control points | |
def create_initial_control_points(): | |
global g_control_points | |
# create control point shape | |
s = g_screen_radius * 0.025 | |
g_screen.register_shape("cp", ((-s, -s), (s, -s), (s, s), (-s, s))) | |
# create control points | |
for i in range(g_n_cps): | |
create_control_point(i) | |
# update control point coordinates (after drag) | |
def update_control_points(): | |
global g_control_points_coords | |
g_control_points_coords = [] | |
for cp in g_control_points: | |
g_control_points_coords.append(cp.pos()) | |
draw_frame(True) | |
# recursive functiont to get bezier point | |
# cps - control points | |
# t - t value [0, 1] | |
# level - recursion level | |
# draw - whether to draw connecting lines | |
# dt - turtle to draw connecting lines | |
def get_bezier_point(cps, t, level, draw = False, dt = None): | |
# terminating condition | |
if len(cps) == 1: | |
return cps[0] | |
# create new control points (n - 1) | |
n_cps = [] | |
for i, p0 in enumerate(cps): | |
if i == len(cps) - 1: | |
break | |
p1 = cps[i + 1] | |
x0, y0 = p0 | |
x1, y1 = p1 | |
x2 = x0 + t * (x1 - x0) | |
y2 = y0 + t * (y1 - y0) | |
n_cps.append((x2, y2)) | |
# draw connecting lines if asked | |
if draw: | |
dt.pensize(2 - (0.5*level)) | |
for i, v in enumerate(cps): | |
if i == 0: | |
dt.pu() | |
dt.color(g_pen_colors[level % len(g_pen_colors)]) | |
dt.goto(v) | |
dt.dot(5) | |
if i == 0: | |
dt.pd() | |
# recursively call for n - 1 control points | |
return get_bezier_point(n_cps, t, level + 1, draw, dt) | |
# draw a frame | |
# redraw_bt - whether to redraw bezier curve up to current t | |
def draw_frame(redraw_bt = False): | |
g_dt.clear() | |
bp = get_bezier_point(g_control_points_coords, g_t, 0, True, g_dt) | |
# redraw bezier curve if needed | |
if redraw_bt: | |
g_bt.clear() | |
t = 0 | |
for i in range(g_n_steps + 1): | |
if t > g_t: | |
break | |
bp = get_bezier_point(g_control_points_coords, t, 0) | |
if t == 0: | |
g_bt.pu() | |
g_bt.goto(bp) | |
if t == 0: | |
g_bt.pd() | |
t += g_t_step | |
else: | |
if g_t == 0: | |
g_bt.clear() | |
g_bt.pu() | |
g_bt.goto(bp) | |
if g_t == 0: | |
g_bt.pd() | |
# update screen | |
g_screen.update() | |
# loop to draw animation | |
def start_loop(): | |
global g_t | |
while True: | |
if not g_playing: | |
break | |
g_t += g_t_step | |
if g_t > 1: | |
g_t = 0 | |
draw_frame() | |
time.sleep(0.01) | |
# initial setup | |
def setup(): | |
g_screen.tracer(0) | |
draw_borders() | |
create_buttons() | |
create_initial_control_points() | |
update_control_points() | |
# main | |
setup() | |
start_loop() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# http://pardhav-m.blogspot.com/2020/06/bezier-curve-animations.html | |
# https://trinket.io/embed/python/cb88f35b30 | |
# Bezier Curve Animation | |
# Pursuit curves | |
import turtle | |
import random | |
import math | |
import time | |
# screen setup | |
g_screen = turtle.Screen() | |
g_wh = g_screen.window_height() | |
g_ww = 1.5 * g_wh | |
g_screen.setup(g_ww, g_wh) | |
g_screen_radius = g_wh / 2 | |
# other globals | |
g_b_side = g_screen_radius * 0.1 | |
g_b_side2 = g_b_side / 2 | |
g_playing = True | |
g_pen_colors = ['black', 'blue', 'firebrick', 'green', 'saddle brown', 'orange red', 'indian red', 'medium violet red'] | |
g_min_cps = 2 | |
g_max_cps = 8 | |
g_n_cps = 3 | |
g_control_points = [] | |
g_control_points_coords = [] | |
g_n_steps = 50 # number of steps of t between [0, 1] | |
g_t = 0 | |
g_t_step = 1 / g_n_steps | |
g_dt = [None] * g_max_cps | |
# draw borders | |
def draw_borders(): | |
t = turtle.Turtle() | |
t.speed(0) | |
t.pu() | |
t.goto(-g_ww/2, -g_wh/2) | |
t.pd() | |
for i in range(4): | |
t.fd(g_ww if i % 2 == 0 else g_wh) | |
t.left(90) | |
t.pu() | |
t.goto(-g_ww/2, g_wh/2) | |
t.pd() | |
for i in range(4): | |
t.fd(g_b_side*8 if i % 2 == 0 else g_b_side*3) | |
t.right(90) | |
# handle play/pause button click | |
def handle_pp_click(x, y): | |
global g_playing | |
g_playing = not g_playing | |
if g_playing: | |
start_loop() | |
# handle inc button click | |
def handle_inc_click(x, y): | |
global g_n_cps | |
if g_n_cps < g_max_cps: | |
create_control_point(g_n_cps) | |
g_n_cps += 1 | |
update_control_points() | |
# handle dec button click | |
def handle_dec_click(x, y): | |
global g_n_cps | |
if g_n_cps > g_min_cps: | |
g_n_cps -= 1 | |
cp = g_control_points.pop() | |
cp.clear() | |
cp.ht() | |
del cp | |
update_control_points() | |
# clear, hide and penup a turtle | |
def clear_turtle(t): | |
t.clear() | |
t.ht() | |
t.pu() | |
# create a button | |
def create_button(name, shape, handler, x, y, angle = 0): | |
t = turtle.Turtle() | |
t.speed(0) | |
t.shape(shape) | |
t.pu() | |
if shape == 'b_square': | |
t.goto(x - g_b_side2 - 15, y - g_b_side) | |
else: | |
t.goto(x - g_b_side2, y - g_b_side) | |
t.write(name) | |
t.goto(x, y) | |
t.seth(angle) | |
t.onclick(handler) | |
return t | |
# create buttons on top left | |
def create_buttons(): | |
# custom shapes | |
triangle_shape = [(-g_b_side2, 0), (g_b_side2, 0), (0, g_b_side2)] | |
g_screen.register_shape('b_triangle', triangle_shape) | |
square_shape = [(-g_b_side2, -g_b_side2), (g_b_side2, -g_b_side2), (g_b_side2, g_b_side2), (-g_b_side2, g_b_side2)] | |
g_screen.register_shape('b_square', square_shape) | |
x = -g_ww/2 + g_b_side + 20 | |
y = g_wh/2 - g_b_side - 10 | |
create_button('play / pause', 'b_square', handle_pp_click, x, y) | |
create_button('inc', 'b_triangle', handle_inc_click, x + g_b_side*2.5, y, 90) | |
create_button('dec', 'b_triangle', handle_dec_click, x + g_b_side*5, y, -90) | |
# control point class | |
class ControlPoint(turtle.Turtle): | |
def __init__(self, num = 0, x = 0, y = 0): | |
turtle.Turtle.__init__(self) | |
self.num = num | |
self.clicked = False | |
self.dragging = False | |
self.is_playing = None | |
self.speed(0) | |
self.pu() | |
self.goto(x, y) | |
self.fd(10) | |
self.write("p" + str(self.num)) | |
self.bk(10) | |
# click/release/drag handlers | |
self.onclick(self.onclick_handler) | |
self.onrelease(self.onrelease_handler) | |
self.ondrag(self.ondrag_handler) | |
# handle click | |
def onclick_handler(self, x, y): | |
global g_playing | |
self.clicked = True | |
self.is_playing = g_playing | |
g_playing = False | |
# handle release | |
def onrelease_handler(self, x, y): | |
global g_playing | |
self.clicked = False | |
if self.is_playing: | |
g_playing = True | |
start_loop() | |
# handle drag | |
def ondrag_handler(self, x, y): | |
if not self.clicked or self.dragging: | |
return | |
self.dragging = True | |
self.clear() | |
self.goto(x, y) | |
self.fd(10) | |
self.write("p" + str(self.num)) | |
self.bk(10) | |
update_control_points() | |
self.dragging = False | |
# create a control point | |
# i - control point number | |
def create_control_point(i): | |
mh = int(g_wh * 0.8) | |
x = random.randrange(0, mh) - g_wh/2 | |
y = random.randrange(0, mh) - g_wh/2 | |
cp = ControlPoint(i, x, y) | |
cp.shape('cp') | |
g_control_points.append(cp) | |
# create initial control points | |
def create_initial_control_points(): | |
global g_control_points | |
# create control point shape | |
s = g_screen_radius * 0.025 | |
g_screen.register_shape("cp", ((-s, -s), (s, -s), (s, s), (-s, s))) | |
# create control points | |
for i in range(g_n_cps): | |
create_control_point(i) | |
# update control point coordinates (after drag) | |
def update_control_points(): | |
global g_control_points_coords | |
g_control_points_coords = [] | |
for cp in g_control_points: | |
g_control_points_coords.append(cp.pos()) | |
draw_frame(True) | |
# recursive functiont to get bezier point | |
# cps - control points | |
# t - t value [0, 1] | |
# level - recursion level | |
def get_bezier_point(cps, t, level): | |
# terminating condition | |
if len(cps) == 1: | |
return cps[0] | |
# create new control points (n - 1) | |
n_cps = [] | |
for i, p0 in enumerate(cps): | |
if i == len(cps) - 1: | |
break | |
p1 = cps[i + 1] | |
x0, y0 = p0 | |
x1, y1 = p1 | |
x2 = x0 + t * (x1 - x0) | |
y2 = y0 + t * (y1 - y0) | |
n_cps.append((x2, y2)) | |
# recursively call for n - 1 control points | |
bp = get_bezier_point(n_cps, t, level + 1) | |
pt = cps[len(cps) - 1] | |
if level == 0: | |
pt = bp | |
dt = g_dt[0] | |
else: | |
dt = g_dt[g_n_cps - 1 - level] | |
dt.seth(dt.towards(pt)) | |
dt.goto(pt) | |
if not dt.isvisible(): | |
dt.st() | |
dt.pd() | |
return bp | |
# draw a frame | |
# redraw_bt - whether to redraw bezier curve up to current t | |
def draw_frame(redraw_bt = False): | |
if g_t == 0 or redraw_bt: | |
for i in range(g_max_cps): | |
clear_turtle(g_dt[i]) | |
# redraw bezier curve if needed | |
if redraw_bt: | |
t = 0 | |
for i in range(g_n_steps + 1): | |
if t > g_t: | |
break | |
get_bezier_point(g_control_points_coords, t, 0) | |
t += g_t_step | |
else: | |
get_bezier_point(g_control_points_coords, g_t, 0) | |
# update screen | |
g_screen.update() | |
# loop to draw animation | |
def start_loop(): | |
global g_t | |
while True: | |
if not g_playing: | |
break | |
g_t += g_t_step | |
if g_t > 1: | |
g_t = 0 | |
draw_frame() | |
time.sleep(0.01) | |
# initial setup | |
def setup(): | |
global g_dt | |
g_screen.tracer(0) | |
draw_borders() | |
create_buttons() | |
for i in range(g_max_cps): | |
g_dt[i] = turtle.Turtle() | |
g_dt[i].speed(0) | |
if i == 0: | |
g_dt[i].color('red') | |
g_dt[i].pensize(3) | |
else: | |
g_dt[i].color(g_pen_colors[i % len(g_pen_colors)]) | |
g_dt[i].pensize(2 - (0.5*i)) | |
create_initial_control_points() | |
update_control_points() | |
# main | |
setup() | |
start_loop() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# http://pardhav-m.blogspot.com/2020/06/bezier-curve-animations.html | |
# https://trinket.io/embed/python/b20c187b0f | |
# Bezier Curve Animation | |
# Diverging Bezier Curves | |
import turtle | |
import random | |
import math | |
import time | |
# screen setup | |
g_screen = turtle.Screen() | |
g_wh = g_screen.window_height() | |
g_ww = 1.5 * g_wh | |
g_screen.setup(g_ww, g_wh) | |
g_screen_radius = g_wh / 2 | |
# other globals | |
g_b_side = g_screen_radius * 0.1 | |
g_b_side2 = g_b_side / 2 | |
g_playing = True | |
g_pen_colors = ['black', 'blue', 'firebrick', 'green', 'saddle brown', 'orange red', 'indian red', 'medium violet red'] | |
g_min_cps = 2 | |
g_max_cps = 8 | |
g_n_cps = 3 | |
g_control_points = [] | |
g_control_points_coords = [] | |
g_n_steps = 50 # number of steps of t between [0, 1] | |
g_t = 1 | |
g_t_step = 1 / g_n_steps | |
g_dt = [None] * g_max_cps | |
g_shuffle = False | |
# draw borders | |
def draw_borders(): | |
t = turtle.Turtle() | |
t.speed(0) | |
t.pu() | |
t.goto(-g_ww/2, -g_wh/2) | |
t.pd() | |
for i in range(4): | |
t.fd(g_ww if i % 2 == 0 else g_wh) | |
t.left(90) | |
t.pu() | |
t.goto(-g_ww/2, g_wh/2) | |
t.pd() | |
for i in range(4): | |
t.fd(g_b_side*8 if i % 2 == 0 else g_b_side*3) | |
t.right(90) | |
# handle play/pause button click | |
def handle_pp_click(x, y): | |
global g_playing | |
g_playing = not g_playing | |
if g_playing: | |
start_loop() | |
# handle inc button click | |
def handle_inc_click(x, y): | |
global g_n_cps | |
if g_n_cps < g_max_cps: | |
create_control_point(g_n_cps) | |
g_n_cps += 1 | |
update_control_points() | |
# handle dec button click | |
def handle_dec_click(x, y): | |
global g_n_cps | |
if g_n_cps > g_min_cps: | |
g_n_cps -= 1 | |
cp = g_control_points.pop() | |
cp.clear() | |
cp.ht() | |
del cp | |
update_control_points() | |
# clear, hide and penup a turtle | |
def clear_turtle(t): | |
t.clear() | |
t.ht() | |
t.pu() | |
# create a button | |
def create_button(name, shape, handler, x, y, angle = 0): | |
t = turtle.Turtle() | |
t.speed(0) | |
t.shape(shape) | |
t.pu() | |
if shape == 'b_square': | |
t.goto(x - g_b_side2 - 15, y - g_b_side) | |
else: | |
t.goto(x - g_b_side2, y - g_b_side) | |
t.write(name) | |
t.goto(x, y) | |
t.seth(angle) | |
t.onclick(handler) | |
return t | |
# create buttons on top left | |
def create_buttons(): | |
# custom shapes | |
triangle_shape = [(-g_b_side2, 0), (g_b_side2, 0), (0, g_b_side2)] | |
g_screen.register_shape('b_triangle', triangle_shape) | |
square_shape = [(-g_b_side2, -g_b_side2), (g_b_side2, -g_b_side2), (g_b_side2, g_b_side2), (-g_b_side2, g_b_side2)] | |
g_screen.register_shape('b_square', square_shape) | |
x = -g_ww/2 + g_b_side + 20 | |
y = g_wh/2 - g_b_side - 10 | |
create_button('play / pause', 'b_square', handle_pp_click, x, y) | |
create_button('inc', 'b_triangle', handle_inc_click, x + g_b_side*2.5, y, 90) | |
create_button('dec', 'b_triangle', handle_dec_click, x + g_b_side*5, y, -90) | |
# control point class | |
class ControlPoint(turtle.Turtle): | |
def __init__(self, num = 0, x = 0, y = 0): | |
turtle.Turtle.__init__(self) | |
self.num = num | |
self.clicked = False | |
self.dragging = False | |
self.is_playing = None | |
self.speed(0) | |
self.pu() | |
self.goto(x, y) | |
self.fd(10) | |
self.write("p" + str(self.num)) | |
self.bk(10) | |
# click/release/drag handlers | |
self.onclick(self.onclick_handler) | |
self.onrelease(self.onrelease_handler) | |
self.ondrag(self.ondrag_handler) | |
# handle click | |
def onclick_handler(self, x, y): | |
global g_playing | |
self.clicked = True | |
self.is_playing = g_playing | |
g_playing = False | |
# handle release | |
def onrelease_handler(self, x, y): | |
global g_playing | |
self.clicked = False | |
if self.is_playing: | |
g_playing = True | |
start_loop() | |
# handle drag | |
def ondrag_handler(self, x, y): | |
if not self.clicked or self.dragging: | |
return | |
self.dragging = True | |
self.clear() | |
self.goto(x, y) | |
self.fd(10) | |
self.write("p" + str(self.num)) | |
self.bk(10) | |
update_control_points() | |
self.dragging = False | |
# create a control point | |
# i - control point number | |
def create_control_point(i): | |
mh = int(g_wh * 0.8) | |
x = random.randrange(0, mh) - g_wh/2 | |
y = random.randrange(0, mh) - g_wh/2 | |
cp = ControlPoint(i, x, y) | |
cp.shape('cp') | |
g_control_points.append(cp) | |
# create initial control points | |
def create_initial_control_points(): | |
global g_control_points | |
# create control point shape | |
s = g_screen_radius * 0.025 | |
g_screen.register_shape("cp", ((-s, -s), (s, -s), (s, s), (-s, s))) | |
# create control points | |
for i in range(g_n_cps): | |
create_control_point(i) | |
# update control point coordinates (after drag) | |
def update_control_points(): | |
global g_control_points_coords | |
g_control_points_coords = [] | |
for cp in g_control_points: | |
g_control_points_coords.append(cp.pos()) | |
draw_frame(True) | |
# recursive functiont to get bezier point | |
# cps - control points | |
# t - t value [0, 1] | |
# level - recursion level | |
def get_bezier_point(cps, t, level): | |
# terminating condition | |
if len(cps) == 1: | |
return cps[0] | |
# create new control points (n - 1) | |
n_cps = [] | |
for i, p0 in enumerate(cps): | |
if i == len(cps) - 1: | |
break | |
p1 = cps[i + 1] | |
x0, y0 = p0 | |
x1, y1 = p1 | |
x2 = x0 + t * (x1 - x0) | |
y2 = y0 + t * (y1 - y0) | |
n_cps.append((x2, y2)) | |
# recursively call for n - 1 control points | |
bp = get_bezier_point(n_cps, t, level + 1) | |
pt = cps[len(cps) - 1] | |
if level == 0: | |
pt = bp | |
dt = g_dt[0] | |
else: | |
dt = g_dt[g_n_cps - 1 - level] | |
dt.seth(dt.towards(pt)) | |
dt.goto(pt) | |
if not dt.isvisible(): | |
dt.st() | |
dt.pd() | |
return bp | |
# draw a frame | |
# redraw_bt - whether to redraw bezier curve up to current t | |
def draw_frame(redraw_bt = False): | |
if g_t == 1 or redraw_bt: | |
for i in range(g_max_cps): | |
clear_turtle(g_dt[i]) | |
# redraw bezier curve if needed | |
if redraw_bt: | |
t = 1 | |
for i in range(g_n_steps + 1): | |
if t < g_t: | |
break | |
get_bezier_point(g_control_points_coords, t, 0) | |
t -= g_t_step | |
else: | |
get_bezier_point(g_control_points_coords, g_t, 0) | |
# update screen | |
g_screen.update() | |
# loop to draw animation | |
def start_loop(): | |
global g_t | |
global g_control_points_coords | |
while True: | |
if not g_playing: | |
break | |
g_t -= g_t_step | |
if g_t < 0: | |
g_t = 1 | |
if g_shuffle: | |
last = g_control_points_coords[g_n_cps - 1] | |
sa = g_control_points_coords[0:g_n_cps - 1] | |
random.shuffle(sa) | |
na = sa | |
na.append(last) | |
g_control_points_coords = na | |
draw_frame() | |
time.sleep(0.01) | |
# initial setup | |
def setup(): | |
global g_dt | |
g_screen.tracer(0) | |
draw_borders() | |
create_buttons() | |
for i in range(g_max_cps): | |
g_dt[i] = turtle.Turtle() | |
g_dt[i].speed(0) | |
if i == 0: | |
g_dt[i].color('red') | |
g_dt[i].pensize(3) | |
else: | |
g_dt[i].color(g_pen_colors[i % len(g_pen_colors)]) | |
g_dt[i].pensize(2 - (0.5*i)) | |
create_initial_control_points() | |
update_control_points() | |
# main | |
setup() | |
start_loop() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# http://pardhav-m.blogspot.com/2020/06/bezier-curve-animations.html | |
# https://trinket.io/embed/python/731c1fbac1 | |
# Bezier Curve Animations | |
# Rotating spirals | |
import turtle | |
import random | |
import math | |
import time | |
# screen setup | |
g_screen = turtle.Screen() | |
g_wh = g_screen.window_height() | |
g_ww = 1.5 * g_wh | |
g_screen.setup(g_ww, g_wh) | |
g_screen_radius = g_wh / 2 | |
# other globals | |
g_b_side = g_screen_radius * 0.1 | |
g_b_side2 = g_b_side / 2 | |
g_playing = True | |
g_pen_colors = ['black', 'blue', 'firebrick', 'green', 'saddle brown', 'orange red', 'indian red', 'medium violet red'] | |
g_min_cps = 2 | |
g_max_cps = 8 | |
g_n_cps = 3 | |
g_control_points = [] | |
g_control_points_coords = [] | |
g_n_steps = 50 # number of steps of t between [0, 1] | |
g_t = 0 | |
g_t_step = 1 / g_n_steps | |
g_levels = 20 | |
# turtle to draw connecting lines | |
g_dt = turtle.Turtle() | |
g_dt.speed(0) | |
g_dt.ht() | |
# draw borders | |
def draw_borders(): | |
t = turtle.Turtle() | |
t.speed(0) | |
t.pu() | |
t.goto(-g_ww/2, -g_wh/2) | |
t.pd() | |
for i in range(4): | |
t.fd(g_ww if i % 2 == 0 else g_wh) | |
t.left(90) | |
t.pu() | |
t.goto(-g_ww/2, g_wh/2) | |
t.pd() | |
for i in range(4): | |
t.fd(g_b_side*8 if i % 2 == 0 else g_b_side*3) | |
t.right(90) | |
# handle play/pause button click | |
def handle_pp_click(x, y): | |
global g_playing | |
g_playing = not g_playing | |
if g_playing: | |
start_loop() | |
# handle inc button click | |
def handle_inc_click(x, y): | |
global g_n_cps | |
if g_n_cps < g_max_cps: | |
create_control_point(g_n_cps) | |
g_n_cps += 1 | |
update_control_points() | |
# handle dec button click | |
def handle_dec_click(x, y): | |
global g_n_cps | |
if g_n_cps > g_min_cps: | |
g_n_cps -= 1 | |
cp = g_control_points.pop() | |
cp.clear() | |
cp.ht() | |
del cp | |
update_control_points() | |
# create a button | |
def create_button(name, shape, handler, x, y, angle = 0): | |
t = turtle.Turtle() | |
t.speed(0) | |
t.shape(shape) | |
t.pu() | |
if shape == 'b_square': | |
t.goto(x - g_b_side2 - 15, y - g_b_side) | |
else: | |
t.goto(x - g_b_side2, y - g_b_side) | |
t.write(name) | |
t.goto(x, y) | |
t.seth(angle) | |
t.onclick(handler) | |
return t | |
# create buttons on top left | |
def create_buttons(): | |
# custom shapes | |
triangle_shape = [(-g_b_side2, 0), (g_b_side2, 0), (0, g_b_side2)] | |
g_screen.register_shape('b_triangle', triangle_shape) | |
square_shape = [(-g_b_side2, -g_b_side2), (g_b_side2, -g_b_side2), (g_b_side2, g_b_side2), (-g_b_side2, g_b_side2)] | |
g_screen.register_shape('b_square', square_shape) | |
x = -g_ww/2 + g_b_side + 20 | |
y = g_wh/2 - g_b_side - 10 | |
create_button('play / pause', 'b_square', handle_pp_click, x, y) | |
create_button('inc', 'b_triangle', handle_inc_click, x + g_b_side*2.5, y, 90) | |
create_button('dec', 'b_triangle', handle_dec_click, x + g_b_side*5, y, -90) | |
# control point class | |
class ControlPoint(turtle.Turtle): | |
def __init__(self, num = 0, x = 0, y = 0): | |
turtle.Turtle.__init__(self) | |
self.num = num | |
self.clicked = False | |
self.dragging = False | |
self.is_playing = None | |
self.speed(0) | |
self.pu() | |
self.goto(x, y) | |
self.fd(10) | |
self.write("p" + str(self.num)) | |
self.bk(10) | |
# click/release/drag handlers | |
self.onclick(self.onclick_handler) | |
self.onrelease(self.onrelease_handler) | |
self.ondrag(self.ondrag_handler) | |
# handle click | |
def onclick_handler(self, x, y): | |
global g_playing | |
self.clicked = True | |
self.is_playing = g_playing | |
g_playing = False | |
# handle release | |
def onrelease_handler(self, x, y): | |
global g_playing | |
self.clicked = False | |
if self.is_playing: | |
g_playing = True | |
start_loop() | |
# handle drag | |
def ondrag_handler(self, x, y): | |
if not self.clicked or self.dragging: | |
return | |
self.dragging = True | |
self.clear() | |
self.goto(x, y) | |
self.fd(10) | |
self.write("p" + str(self.num)) | |
self.bk(10) | |
update_control_points() | |
self.dragging = False | |
# create a control point | |
# i - control point number | |
def create_control_point(i): | |
mh = int(g_wh * 0.8) | |
x = random.randrange(0, mh) - g_wh/2 | |
y = random.randrange(0, mh) - g_wh/2 | |
cp = ControlPoint(i, x, y) | |
cp.shape('cp') | |
g_control_points.append(cp) | |
# create initial control points | |
def create_initial_control_points(): | |
global g_control_points | |
# create control point shape | |
s = g_screen_radius * 0.025 | |
g_screen.register_shape("cp", ((-s, -s), (s, -s), (s, s), (-s, s))) | |
# create control points | |
for i in range(g_n_cps): | |
create_control_point(i) | |
# update control point coordinates (after drag) | |
def update_control_points(): | |
global g_control_points_coords | |
g_control_points_coords = [] | |
for cp in g_control_points: | |
g_control_points_coords.append(cp.pos()) | |
g_control_points_coords.append(g_control_points_coords[0]) | |
draw_frame() | |
# recursive functiont to get bezier point | |
# cps - control points | |
# t - t value [0, 1] | |
# level - recursion level | |
# draw - whether to draw connecting lines | |
# dt - turtle to draw connecting lines | |
def get_bezier_point(cps, t, level, draw = False, dt = None): | |
# terminating condition | |
if level > g_levels: | |
return | |
# create new control points (n - 1) | |
n_cps = [] | |
for i, p0 in enumerate(cps): | |
if i == len(cps) - 1: | |
i = 0 | |
p1 = cps[i + 1] | |
x0, y0 = p0 | |
x1, y1 = p1 | |
x2 = x0 + t * (x1 - x0) | |
y2 = y0 + t * (y1 - y0) | |
n_cps.append((x2, y2)) | |
# draw connecting lines if asked | |
if draw: | |
dt.pensize(2 - (0.5*level)) | |
for i, v in enumerate(cps): | |
if i == 0: | |
dt.pu() | |
dt.color(g_pen_colors[level % len(g_pen_colors)]) | |
dt.goto(v) | |
dt.dot(5) | |
if i == 0: | |
dt.pd() | |
# recursively call for n - 1 control points | |
get_bezier_point(n_cps, t, level + 1, draw, dt) | |
# draw a frame | |
# redraw_bt - whether to redraw bezier curve up to current t | |
def draw_frame(): | |
g_dt.clear() | |
get_bezier_point(g_control_points_coords, g_t, 0, True, g_dt) | |
# update screen | |
g_screen.update() | |
# loop to draw animation | |
def start_loop(): | |
global g_t | |
while True: | |
if not g_playing: | |
break | |
g_t += g_t_step | |
if g_t > 1: | |
g_t = 0 | |
draw_frame() | |
time.sleep(0.01) | |
# initial setup | |
def setup(): | |
g_screen.tracer(0) | |
draw_borders() | |
create_buttons() | |
create_initial_control_points() | |
update_control_points() | |
# main | |
setup() | |
start_loop() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment