A Bezier curve is a curve defined by three points, a start, an end and one the line curves toward.
But can we programmatically draw one using Pygame? The answer is Yes! And this is what I wrote to do it!
Here is an online version you can interact with: https://replit.com/@d1ddle/Bezier-Curve
Animating Curve Toggling Coordinates Movable Points + Pausing Animation
And here is the python code. Make sure you have python 3.9 and pygame 2.0.1 + installed:
# BEZIER CURVE SIMULATION WITH PYGAME - https://d1ddle.com #
import pygame, sys
#global variable initialisations.
pygame.init()
pygame.display.set_caption("Bezier Curve - d1ddle")
WIDTH, HEIGHT = 500, 500
if WIDTH - HEIGHT > 0: HEIGHT = WIDTH
else: WIDTH = HEIGHT
screen = pygame.display.set_mode((WIDTH, HEIGHT))
clock = pygame.time.Clock()
font = pygame.font.Font(None, WIDTH//16)
paused = False
done = False
drag = False
coord_show = False
counter = 0
glob_counter = 0
#co-ordinates of first line
P0 = [50 * WIDTH//400, 300* WIDTH//400]
P1 = [200* WIDTH//400, 50* WIDTH//400]
line_1 = P0, P1
#co-ordinates of second line
#P1 = [200, 50] isn't needed since both lines join at a shared point.
P2 = [300* WIDTH//400, 350* WIDTH//400]
line_2 = P1, P2
co_ords = P0, P1, P2
#these look like matrix transformations, but they're actually
#calculations for the X and Y co-ordinates for any point on a curve/ y = mx + c / ax^2 + bx + c.
#this first one uses P(t) = (1-t)*P0 + t*P1
#twice: once for X, once for Y.
#t is the percentage of the length of the line that you request coordinates for.
#so it varies from 0 to 1, gathering and plotting pixels across the screen.
def P(t, line):
PtX = (1-t)*line[0][0] + t*line[1][0]
PtY = (1-t)*line[0][1] + t*line[1][1]
return PtX, PtY
#this is the quadratic part. Unfortunately this is hard
#coded so we can't really have more than one curve on screen at once.
# Qt = (1-t)^2 * P0 + 2*(1-t)*t*P1 + t^2 * P2
def Q(t):
QtX = ((1-t)**2)*P0[0] + 2*(1-t)*t*P1[0] + (t**2)*P2[0]
QtY = ((1-t)**2)*P0[1] + 2*(1-t)*t*P1[1] + (t**2)*P2[1]
return QtX, QtY
#main loop
while not done:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_SPACE:
paused = not paused
if event.key == pygame.K_TAB:
coord_show = not coord_show
if event.key == pygame.K_ESCAPE:
pygame.quit()
sys.exit()
elif event.type == pygame.MOUSEBUTTONDOWN:
if event.button == 1:
drag = True
mouse_x, mouse_y = event.pos
elif event.type == pygame.MOUSEBUTTONUP:
if event.button == 1:
drag = False
#this whole block checks for mouse click to move the points.
elif event.type == pygame.MOUSEMOTION:
if drag:
mouse_x, mouse_y = event.pos
if abs(P0[0] - mouse_x) <= WIDTH//20 and abs(P0[1] - mouse_y) <= WIDTH//20:
line_1 = [mouse_x, mouse_y], line_1[1]
P0 = mouse_x, mouse_y
if abs(P1[0] - mouse_x) <= WIDTH//20 and abs(P1[1] - mouse_y) <= WIDTH//20:
line_1 = line_1[0], [mouse_x, mouse_y]
P1 = mouse_x, mouse_y
line_2 = [mouse_x, mouse_y], line_2[1]
if abs(P2[0] - mouse_x) <= WIDTH//20 and abs(P2[1] - mouse_y) <= WIDTH//20:
line_2 = line_2[0], [mouse_x, mouse_y]
P2 = mouse_x, mouse_y
#drawing grid
screen.fill((255,255,255))
for i in range(0, WIDTH//10):
pygame.draw.line(screen, (211,211,211), (i*10, WIDTH), (i*10,0))
pygame.draw.line(screen, (211,211,211), (0, i*10), (HEIGHT, i*10))
pygame.draw.line(screen, (105,105,105), (WIDTH//2,0), (WIDTH//2, HEIGHT))
pygame.draw.line(screen, (105,105,105), (0, WIDTH//2), (HEIGHT, WIDTH//2))
#important loop for drawing curved lines.
for i in range(1, 1000):
i*=0.001
#straight line - inefficient. They're drawn above.
## Pi = (int(P(i, line_1)[0]), int(P(i, line_1)[1]))
## screen.set_at(Pi, (0,0,0)")
##
## Pi2 = (int(P(i, line_2)[0]), int(P(i, line_2)[1]))
## screen.set_at(Pi2, (0,0,0)")
#curved quadratic line
Qi = (int(Q(i)[0]), int(Q(i)[1]))
screen.set_at(Qi, (0,0,255))
#calculating red points
Red = (int(P(counter*0.005, line_1)[0]), int(P(counter*0.005, line_1)[1]))
Red2 = (int(P(counter*0.005, line_2)[0]), int(P(counter*0.005, line_2)[1]))
#drawing Blue point
Qi = (int(Q(counter*0.005)[0]), int(Q(counter*0.005)[1]))
pygame.draw.circle(screen, (0,0,255), Qi, WIDTH//80)
#efficient way of drawing the black & red line/s
widt = WIDTH//400
if widt < 1: widt = 1
pygame.draw.lines(screen, (0,0,0), False, [(int(P(0, line_1)[0]), int(P(0, line_1)[1])), (int(P(1, line_1)[0]), int(P(1, line_1)[1])), (int(P(1, line_2)[0]), int(P(1, line_2)[1]))] , width = widt)
pygame.draw.line(screen, (255,0,0), Red, Red2, width = widt)
#drawing P points & red points
pygame.draw.circle(screen, (105,105,105), P0, WIDTH//80)
pygame.draw.circle(screen, (105,105,105), P1, WIDTH//80)
pygame.draw.circle(screen, (105,105,105), P2, WIDTH//80)
pygame.draw.circle(screen, (255,0,0), Red2, WIDTH//80)
pygame.draw.circle(screen, (255,0,0), Red, WIDTH//80)
#drawing text co-ords. Not enough to make me systematically draw them.
textsurf = font.render(str((P0[0]-WIDTH//2, (P0[1]-HEIGHT//2)*-1)), False, (105,105,105))
textsurf2 = font.render(str((P1[0]-WIDTH//2, (P1[1]-HEIGHT//2)*-1)), False, (105,105,105))
textsurf3 = font.render(str((P2[0]-WIDTH//2, (P2[1]-HEIGHT//2)*-1)), False, (105,105,105))
textsurf4 = font.render(str((Red[0]-WIDTH//2, (Red[1]-HEIGHT//2)*-1)), False, (255,0,0))
textsurf5 = font.render(str((Red2[0]-WIDTH//2, (Red2[1]-HEIGHT//2)*-1)), False, (255,0,0))
textsurf6 = font.render(str((Qi[0]-WIDTH//2, (Qi[1]-HEIGHT//2)*-1)), False, (0,0,255))
textsurf7 = font.render("TAB toggle coord", False, (50,50,50))
screen.blit(textsurf7, (WIDTH//2 + 60, WIDTH//4 * 3 + WIDTH//8 - 25))
textsurf8 = font.render("SPACE to Pause", False, (50,50,50))
screen.blit(textsurf8, (WIDTH//2 + 60, WIDTH//4 * 3 + WIDTH//8 + WIDTH//80 * 3 - 25))
textsurf9 = font.render("Scale 1:10", False, (50,50,50))
screen.blit(textsurf9, (WIDTH//2 + 60, WIDTH//4 * 3 + WIDTH//8 + WIDTH//80 * 6 - 25))
if coord_show:
screen.blit(textsurf, (P0))
screen.blit(textsurf2, (P1))
screen.blit(textsurf3, (P2))
screen.blit(textsurf4, (Red))
screen.blit(textsurf5, (Red2))
screen.blit(textsurf6, (Qi))
else:
screen.blit(textsurf, (0, 0))
screen.blit(textsurf2, (0, WIDTH // 16))
screen.blit(textsurf3, (0, WIDTH // 8))
screen.blit(textsurf4, (0, WIDTH // 5.3))
screen.blit(textsurf5, (0, WIDTH // 4))
screen.blit(textsurf6, (0, WIDTH // 3.2))
#counter and update.
if counter > 199:
counter = -1
if not paused:
counter += 1
pygame.display.set_caption("Bezier Curve - d1ddle - Speed: " + str(int(clock.get_fps())))
glob_counter += 1
pygame.display.flip()
clock.tick(60)
Recent Comments