Skip to main content

BezierCurve — wiki

This script contains a function, calculate_bezier to calculate a bezier curve and a short demo to display how it can be used. The calculate_bezier function is a direct port of the forward differencing bezier curve algorithm described here.

It depends on the 2d vector class here in the pygame cookbook: 2DVectorClass

Controls for the demo:
Right mouse button - Add a control point
Left mouse button - Move a control point

Update:

The original code didn't work for me. I found an implementation of forward differentiating bezier algorithm at DrDobbs and pretty much copy-pasted it to python. I also removed adding new points with RMB since I couldn't figure out how to make this to work, but for the purpose of demonstrating the algorithm it isn't really that important. 2DVectorClass dependency was also removed.

"""
bezier.py - Calculates a bezier curve from control points. 
 
2007 Victor Blomqvist
Released to the Public Domain
"""
import pygame
from pygame.locals import *
 
class vec2d(object):
    def __init__(self, x, y):
        self.x = x
        self.y = y

 
gray = (100,100,100)
lightgray = (200,200,200)
red = (255,0,0)
green = (0,255,0)
blue = (0,0,255)
X,Y,Z = 0,1,2

def compute_bezier_points(vertices, numPoints=None):
    if numPoints is None:
        numPoints = 30
    if numPoints < 2 or len(vertices) != 4:
        return None

    result = []

    b0x = vertices[0][0]
    b0y = vertices[0][1]
    b1x = vertices[1][0]
    b1y = vertices[1][1]
    b2x = vertices[2][0]
    b2y = vertices[2][1]
    b3x = vertices[3][0]
    b3y = vertices[3][1]



    # Compute polynomial coefficients from Bezier points
    ax = -b0x + 3 * b1x + -3 * b2x + b3x
    ay = -b0y + 3 * b1y + -3 * b2y + b3y

    bx = 3 * b0x + -6 * b1x + 3 * b2x
    by = 3 * b0y + -6 * b1y + 3 * b2y

    cx = -3 * b0x + 3 * b1x
    cy = -3 * b0y + 3 * b1y

    dx = b0x
    dy = b0y

    # Set up the number of steps and step size
    numSteps = numPoints - 1 # arbitrary choice
    h = 1.0 / numSteps # compute our step size

    # Compute forward differences from Bezier points and "h"
    pointX = dx
    pointY = dy

    firstFDX = ax * (h * h * h) + bx * (h * h) + cx * h
    firstFDY = ay * (h * h * h) + by * (h * h) + cy * h


    secondFDX = 6 * ax * (h * h * h) + 2 * bx * (h * h)
    secondFDY = 6 * ay * (h * h * h) + 2 * by * (h * h)

    thirdFDX = 6 * ax * (h * h * h)
    thirdFDY = 6 * ay * (h * h * h)

    # Compute points at each step
    result.append((int(pointX), int(pointY)))

    for i in range(numSteps):
        pointX += firstFDX
        pointY += firstFDY

        firstFDX += secondFDX
        firstFDY += secondFDY

        secondFDX += thirdFDX
        secondFDY += thirdFDY

        result.append((int(pointX), int(pointY)))

    return result
    
def main():
    pygame.init()
    screen = pygame.display.set_mode((1024, 768))
 
    ### Control points that are later used to calculate the curve
    control_points = [vec2d(100,100), vec2d(150,500), vec2d(450,500), vec2d(500,150)]
 
    ### The currently selected point
    selected = None
    
    clock = pygame.time.Clock()
    
    running = True
    while running:
        for event in pygame.event.get():
            if event.type in (QUIT, KEYDOWN):
                running = False
            elif event.type == MOUSEBUTTONDOWN and event.button == 1:
                for p in control_points:
                    if abs(p.x - event.pos[X]) < 10 and abs(p.y - event.pos[Y]) < 10 :
                        selected = p
            # elif event.type == MOUSEBUTTONDOWN and event.button == 3:
            #     x,y = pygame.mouse.get_pos()
            #     control_points.append(vec2d(x,y))
            elif event.type == MOUSEBUTTONUP and event.button == 1:
                selected = None
        
        ### Draw stuff
        screen.fill(gray)
                
        if selected is not None:
            selected.x, selected.y = pygame.mouse.get_pos()
            pygame.draw.circle(screen, green, (selected.x, selected.y), 10)
        
        ### Draw control points
        for p in control_points:
            pygame.draw.circle(screen, blue, (int(p.x), int(p.y)), 4)

        ### Draw control "lines"
        pygame.draw.lines(screen, lightgray, False, [(x.x, x.y) for x in control_points])

        ### Draw bezier curve
        b_points = compute_bezier_points([(x.x, x.y) for x in control_points])
        pygame.draw.lines(screen, pygame.Color("red"), False, b_points, 2)

        ### Flip screen
        pygame.display.flip()
        clock.tick(100)
        #print clock.get_fps()
    
if __name__ == '__main__':
    main()