Monday, April 8, 2013

Sooo, I've been playing around today with Bézier curves, and trying to implement those in Python and Pygame.  I finally got a piece of code that works, with much help from wikipedia's article on Bézier curves.  It still only has very basic functionality, but I had fun while doing it.  The curve follows the cursor.

Here's a screenshot of the script running:





Here's my code:


import pygame, sys
from pygame.locals import *
##Globals
BLACK = (  0,   0,   0)
WHITE = (255, 255, 255)
BLUE =  (  0,   0, 255)
GREEN = (  0, 255,   0)
RED =   (255,   0,   0)

FPS = 30
lx = 0
ly = 1


def main():
    global FPSCLOCK, SCREEN, circleList, mousex, mousey, screen_dims
    screen_dims = (900, 600)
    FPSCLOCK = pygame.time.Clock()
    pygame.display.init()
    SCREEN = pygame.display.set_mode(screen_dims)
    pygame.display.set_caption("BezierCurves")
    mousex, mousey = (0,0)
    SCREEN.fill(BLACK)
    drawBezier([(0,0), (100,200),(200,0)])
    x = 0
    while True:
        SCREEN.fill(BLACK)
        #print mousex, mousey
        drawBezier([(300,400), (mousex,mousey),(400, 100)])
        draw()
        checkForQuit()
        updateGameState()        
        pygame.display.update()
        FPSCLOCK.tick(FPS)

def drawBezier(pointList, color = WHITE):
    global SCREEN
    if len(pointList) < 2:
        raise ValueError

    d = listDistance(pointList)
    if d == 0:
        d=1
    #print d

    curve = pygame.PixelArray(SCREEN)
    for t in range(int(d + 1)):
        p = bezierPoint(pointList, t/d)
        #print p
        if inside((0,0), screen_dims, p):
            curve[int(p[lx])][int(p[ly])] = color
    del curve




def bezierPoint(pointList, t):
    """
    Recursive function to draw high order bezier curves.
    """
    if len(pointList) == 2:
        return tBetween(pointList[0], pointList[1], t )
    else:
        qPoints = [tBetween(pointList[p], pointList[p+1], t)
                   for p in range(len(pointList)-1)]
        return bezierPoint(qPoints, t)
    

def draw():
    pass

def tBetween(p1, p2, t):
    """
    Returns a point at a certain percentage t on a line between p1 and p2
    Acceptable values for t can be from 0 to 1.
    """
    if t > 1 or t < 0:
        raise ValueError
    return (p1[lx]+(p2[lx] - p1[lx])*t, p1[ly]+(p2[ly]-p1[ly])*t)

def listDistance(pointList):
    """
    this returns the distance of straight lines drawn
    between all points in pointList. For the purpose of
    estimating how many pixels need to be drawn to form
    a bezier curve without spaces
    """
    s = sum([distance(pointList[p], pointList[p+1])
                   for p in range(len(pointList)-1)])
    return s

def distance(p1, p2):
    dx = p1[lx] - p2[lx]
    dy = p1[ly] - p2[ly]
    return abs(dx**2 + dy**2)**.5

def inside(dimsMin, dimsMax, point):
    if point[lx] < dimsMin[lx] or point[lx] > dimsMax[lx]:
        return False
    if point[ly] < dimsMin[ly] or point[ly] > dimsMax[ly]:
        return False
    return True

def updateGameState():
    global mousex, mousey
    for event in pygame.event.get(MOUSEMOTION):
        mousex, mousey = event.pos
        #print mousex, mousey

def terminate():
    pygame.quit()
    sys.exit()

def checkForQuit():
    for event in pygame.event.get(QUIT):
        terminate()
    for event in pygame.event.get(KEYUP):
        if event.key == K_ESCAPE:
            terminate()
        pygame.event.post(event)


    

if __name__ == "__main__":
    main()