1

I've been trying to make a code with pygame to simulate simple gravity. At the moment, there is only one object (HOM) which is orbiting the sun. However, for reasons unknown to me, whenever I run the code, HOM travels round the sun in an orbit at the start, but then accelerates away from the sun when it reaches ~135 degrees from vertical.

Does anyone know why this is happening and how I can fix it? I have been printing some variables to try and source the problem, but have had no luck so far.

Code:

import pygame,sys,time
from math import *

screen=pygame.display.set_mode((800,600))

G = 5

class Object: #Just an object, like a moon or planet
    def __init__(self,mass,init_cds,init_vel,orbit_obj='Sun'):
        self.mass = mass
        self.cds = init_cds
        self.velocity = init_vel
        self.accel = [0,0]
        self.angle = 0
        self.orb_obj = orbit_obj

    def display(self):
        int_cds = (round(self.cds[0]),round(self.cds[1]))#Stores its co-ordinates as floats, has to convert to integers for draw function
        pygame.draw.circle(screen,(255,0,0),int_cds,10)

    def calc_gravity(self):
        if self.orb_obj == 'Sun':
            c_x,c_y = 400,300
            c_mass = 10000
        else:
            c_x,c_y = self.orb_obj.cds
            c_mass = self.orb_obj.mass
        d_x = self.cds[0]-c_x
        d_y = self.cds[1]-c_y
        dist = sqrt(d_x**2+d_y**2) #Find direct distance
        angle = atan(d_x/d_y) #Find angle
        print(d_x,d_y)
        print(dist,degrees(angle))
        if dist == 0:
            acc = 0
        else:
            acc = G*c_mass/(dist**2) #F=G(Mm)/r^2, a=F/m -> a=GM/r^2
        print(acc)
        acc_x = acc*sin(angle) #Convert acceleration from magnitude+angle -> x and y components
        acc_y = acc*cos(angle)
        self.accel = [acc_x,acc_y]
        print(self.accel)
        self.velocity = [self.velocity[0]+self.accel[0],self.velocity[1]+self.accel[1]] #Add acceleration to velocity
        print(self.velocity)
        self.cds = (self.cds[0]+self.velocity[0],self.cds[1]+self.velocity[1]) #Change co-ordinates by velocity
        print(self.cds)
        print('-------------------') #For seperating each run of the function when printing variables

HOM = Object(1000000,(400,100),[10,0]) #The problem planet

clock = pygame.time.Clock()

while True:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            pygame.quit()
            sys.exit()

    screen.fill((0,0,0))
    pygame.draw.circle(screen,(255,255,0),(400,300),25)
    HOM.display()
    HOM.calc_gravity()

    clock.tick(30)

    pygame.display.flip()
Oliver
  • 51
  • 7
  • Can you post a snippet of the output values please. – turnip Oct 20 '17 at 08:02
  • Sure, I've isolated the values from around the time where the problem occurs. The print statements tell you the order of the variables as printed. – Oliver Oct 20 '17 at 08:05
  • `------------------- 63.844549149787156 21.125165327178536 67.24878486813137 71.6914260165494 11.056078702397329 [10.496403191570936, 3.4730960703071965] [-6.9922567082937785, 29.012459884108917] (456.8522924414934, 350.13762521128746) ------------------- 56.852292441493375 50.13762521128746 75.80214124733293 48.59116240316936 8.701759117372143 [6.526398145958425, 5.755583287313254] [-0.4658585623353533, 34.76804317142217] (456.386433879158, 384.9056683827096) -------------------` – Oliver Oct 20 '17 at 08:07
  • I haven't looked closely at your code, but it appears that you're measuring angles from the vertical axis. FWIW, it's conventional to measure angles from the horizontal axis, so it's a little confusing reading your code. ;) But anyway, rather than doing `atan(d_x/d_y)` you should use `atan2(d_x, d_y)` since that handles all 4 quadrants correctly. – PM 2Ring Oct 20 '17 at 08:09
  • 1
    You are more likely to get help if you present these in the body of your question, and format them to include the heading – turnip Oct 20 '17 at 08:09
  • You need to be careful that your _y_ coordinates have the correct sign. I don't know Pygame, but if your _y_ coordinates increase as you go down the screen you'll need to take that into account, eg `d_y = c_y - self.cds[1]`. – PM 2Ring Oct 20 '17 at 08:16
  • 1
    The algorithm that you're using to calculate velocity and displacement is called Euler integration. It is technically correct, but it's notorious for accumulating arithmetic errors, so your orbits will quickly become invalid unless the time step is very small. I recommend using a different integrator. For orbit sims I like the Leapfrog algorithm, you can see the formulas in [this answer](https://stackoverflow.com/a/29010741/4014959). – PM 2Ring Oct 20 '17 at 08:18
  • To add to what @PM2Ring said about Euler Integration, take a look at Verlet Integration. The reason Euler itegration is not suitable for such cases is that it does not handle things like conservation of energy. – turnip Oct 20 '17 at 08:30
  • 1
    In addition to integration issues, your problem is most likely related to use of the `atan` function. You should probably use `atan2` instead, since that can handle angles in any quadrant of the circle. Or better yet, don't use angles at all, and just use vector math. – Blckknght Oct 20 '17 at 08:41
  • @Petar Sure, Verlet is good, and there are some links to it in my old answer that I linked earlier. But I find Leapfrog simpler, and easier to understand why it works. – PM 2Ring Oct 20 '17 at 08:45
  • 1
    Another thing I forgot to mention earlier: check that your acceleration vector is directed towards the sun, not away from it! – PM 2Ring Oct 20 '17 at 08:47

1 Answers1

1

Your main issue has to do with this line:

angle = atan(d_x/d_y) #Find angle

The atan function is very limited in its ability to compute angles because it can't tell the signs of the coordinates you combined in your division. For instance, it will give the same result for atan(1/1) and atan(-1/-1), since both divisions compute the same slope (1).

Instead you should use atan2, and pass the coordinates separately. Since this will let the code see both coordinates, it can pick an angle in the right quadrant of the circle every time.

But there's an even better fix. Instead of computing an angle and then immediately converting it back to a unit vector (by calling sin and cos on it), why not compute the unit vector directly? You already have the original vector's length! Instead of:

acc_x = acc*sin(angle) #Convert acceleration from magnitude+angle -> x and y components
acc_y = acc*cos(angle)

Use:

acc_x = acc * d_x / distance
acc_y = acc * d_y / distance

The d_x / distance and d_y / distance values are the same as the sin and cos values you were getting before (for the angles when they were working correctly), but there's no need for the trigonometry. You can get rid of the line I quoted up top completely!

Note that you might need to reverse the way you're computing d_x and d_y, so that you get a vector that points from the orbiting object towards the object it's orbiting around (instead of pointing the other way, from the center of the orbit towards the orbiting object). I'm not sure if I'm reading your code correctly, but it looks to me like you have it the other way around right now. That means that you were actually getting the wrong results from atan in the cases where your current code was working the way you expected, and the bad behavior (flying off into nowhere) is the code working "correctly" (from a mathematical point of view). Alternatively, you could compute acc to be negative, rather than positive.

As several commenters mentioned, you may have other issues related to your choice of integration algorithm, but those errors are not going to be as large as the main issue with the acceleration angle. They'll crop up as you run your simulation over longer time periods (and try to use larger time steps to make the simulation go faster). Your current algorithm is good enough for an orbit or two, but if you're simulating dozens or hundreds of orbits, you'll start seeing errors accumulate and so you should pick a better integrator.

Blckknght
  • 100,903
  • 11
  • 120
  • 169
  • Thanks, I completely forgot I could just use `d_x/distance` and `d_y/distance`! I just thought I had to work out the angle and use this to get the acceleration, it didn't even occur to me. Also, I will reverse the acceleration to go towards the sun. – Oliver Oct 20 '17 at 09:59