Search code examples
pythonpygamegame-physics

Adding gravity to a bouncing ball using vectors


I have a gravity vector (in the form [r, theta]) which I add to my ball's velocity vector. For some reason, the ball doesn't return to the same height after bouncing, but instead slowly loses height sporadically. I am guessing there's some rounding error or something in a calculation I'm using, but I can't isolate the issue.

Here is my code. You need both files and pygame to run it. Sorry if it's a little confusing. I can comment anything some more if you want.

I added a marker whenever the ball reaches its max height so you guys what I mean. I want the ball to return to exactly the same height every time it bounces.

I took a little bit of unnecessary code out. The full program is under the pastebin links.


https://pastebin.com/FyejMCmg - PhysicsSim

import pygame, sys, math, tools, random, time
from pygame.locals import *

clock = pygame.time.Clock()
lines = []

class Particle:
    def __init__(self,screen,colour, mass, loc, vel):
        self.screen = screen
        self.colour = colour

        self.mass = mass

        self.x = loc[0]
        self.y = loc[1]
        self.location = self.x,self.y
        self.speed = vel[0]
        self.angle = vel[1]


    def update(self):
        global lines

        # add gravity
        self.speed,self.angle = tools.add_vectors2([self.speed,self.angle], tools.GRAVITY)

        # update position
        dt = clock.tick(60)
        self.x += self.speed * tools.SCALE * math.cos(self.angle) * dt
        self.y -= self.speed * tools.SCALE * math.sin(self.angle) * dt
        self.location = int(self.x),int(self.y)

        # border checking
        do = False
        n=[]
        if ((self.y+self.mass) > tools.SCREEN_HEIGHT):
            self.y = tools.SCREEN_HEIGHT-self.mass
            n = [0,1]
            do = True

        # adds position to array so max height so max height can be recorded
        if (self.speed==0):
            lines.append([self.screen, self.location, self.mass])

        # bounce
        if do:
            #init, convert everything to cartesian
            v = tools.polarToCartesian([self.speed, self.angle])

            #final -> initial minus twice the projection onto n, where n is the normal to the surface
            a = tools.scalarP(2*abs(tools.dotP(v,n)),n) #vector to be added to v
            v = tools.add_vectors(v,a)

            self.angle = tools.cartesianToPolar(v)[1] # does not set magnitude


        # drawing
        pygame.draw.circle(self.screen, self.colour, self.location, self.mass, 0)

# draws max height line
def draw_line(l):
    screen = l[0]
    location = l[1]
    radius = l[2]
    pygame.draw.line(screen, tools.BLACK, [location[0] + 15, location[1]-radius],[location[0] - 15, location[1]-radius])

def main():
    pygame.init()

    DISPLAY = pygame.display.set_mode(tools.SCREEN_SIZE,0,32)
    DISPLAY.fill(tools.WHITE)

    particles = []
    particles.append(Particle(DISPLAY, tools.GREEN, 10, [100,100], [0,0]))

    done = False
    while not done:
        global lines
        for event in pygame.event.get():
            if event.type == QUIT:
                pygame.quit()
                sys.exit()

        DISPLAY.fill(tools.WHITE)
        for i in particles:
            i.update()

        for l in lines:
            draw_line(l)

        pygame.display.update()

main()

https://pastebin.com/Epgqka31 - tools

import math

#colours
WHITE =     (255, 255, 255)
BLUE =      (  0,   0, 255)
GREEN =     (  0, 255,   0)
RED =       ( 255,  0,   0)
BLACK =     (   0,  0,   0)

COLOURS = [WHITE,BLUE,GREEN,RED,BLACK]

#screen
SCREEN_SIZE = SCREEN_WIDTH,SCREEN_HEIGHT = 1000,700

#vectors
GRAVITY = [5.0, 3*math.pi/2] # not 9.8 because it seems too high
SCALE = 0.01

# converts polar coordinates to cartesian coordinates in R2
def polarToCartesian(v):
    return [v[0]*math.cos(v[1]), v[0]*math.sin(v[1])]

# converts cartesian coordinates to polar coordinates in R2
def cartesianToPolar(v):
    return [math.sqrt(v[0]**2 + v[1]**2), math.atan2(v[1],v[0])]

# dots two cartesian vectors in R2
def dotP(v1, v2):
    return v1[0]*v2[0] + v1[1]*v2[1]

# multiplies cartesian vector v by scalar s in Rn
def scalarP(s,v):
    v_=[]
    for i in v:
        v_.append(s*i)
    return v_

# returns the sum of two cartesian vectors in R2
def add_vectors(v1, v2):
    return [v1[0]+v2[0], v1[1]+v2[1]]

# returns the sum of two polar vectors in R2, equations from https://math.stackexchange.com/questions/1365622/adding-two-polar-vectors
def add_vectors2(v1,v2):
    r1,r2,t1,t2 = v1[0],v2[0],v1[1],v2[1]
    return [math.sqrt(r1**2 + r2**2 + 2*r1*r2*math.cos(t2-t1)), t1 + math.atan2(r2*math.sin(t2 - t1), r1 + r2*math.cos(t2 - t1))]

Solution

  • Your time interval, dt = clock.tick(60), is not a constant. If you change it to dt = 60 your program runs as expected.

    Have a look a the Verlet Algorithm and implement it in your code. You are on the right track!