# Bezier Curves – Can we Draw one With Pygame?

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

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 + t*line
PtY = (1-t)*line + t*line
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 + 2*(1-t)*t*P1 + (t**2)*P2
QtY = ((1-t)**2)*P0 + 2*(1-t)*t*P1 + (t**2)*P2
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 - mouse_x) <= WIDTH//20 and abs(P0 - mouse_y) <= WIDTH//20:
line_1 = [mouse_x, mouse_y], line_1
P0 = mouse_x, mouse_y
if abs(P1 - mouse_x) <= WIDTH//20 and abs(P1 - mouse_y) <= WIDTH//20:
line_1 = line_1, [mouse_x, mouse_y]
P1 = mouse_x, mouse_y
line_2 = [mouse_x, mouse_y], line_2
if abs(P2 - mouse_x) <= WIDTH//20 and abs(P2 - mouse_y) <= WIDTH//20:
line_2 = line_2, [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)), int(P(i, line_1)))
##        screen.set_at(Pi, (0,0,0)")
##
##        Pi2 = (int(P(i, line_2)), int(P(i, line_2)))
##        screen.set_at(Pi2, (0,0,0)")

Qi = (int(Q(i)), int(Q(i)))
screen.set_at(Qi, (0,0,255))

#calculating red points
Red = (int(P(counter*0.005, line_1)), int(P(counter*0.005, line_1)))
Red2 = (int(P(counter*0.005, line_2)), int(P(counter*0.005, line_2)))

#drawing Blue point
Qi = (int(Q(counter*0.005)), int(Q(counter*0.005)))
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)), int(P(0, line_1))), (int(P(1, line_1)), int(P(1, line_1))), (int(P(1, line_2)), int(P(1, line_2)))] , 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-WIDTH//2, (P0-HEIGHT//2)*-1)), False, (105,105,105))

textsurf2 = font.render(str((P1-WIDTH//2, (P1-HEIGHT//2)*-1)), False, (105,105,105))

textsurf3 = font.render(str((P2-WIDTH//2, (P2-HEIGHT//2)*-1)), False, (105,105,105))

textsurf4 = font.render(str((Red-WIDTH//2, (Red-HEIGHT//2)*-1)), False, (255,0,0))

textsurf5 = font.render(str((Red2-WIDTH//2, (Red2-HEIGHT//2)*-1)), False, (255,0,0))

textsurf6 = font.render(str((Qi-WIDTH//2, (Qi-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)
``````