Creating the Solar System - Part 1

May 25, 2017

 

In this series, I will explain how to create realistic orbital physics in a 2-dimensional solar system. We will use the key formula:

 

Where F is the force of gravity between two object, G is the gravitational constant (6.67x10^-11), M and m are the masses of the objects and r is the distance between their centers of mass.

 

First, we'll import some libraries.

 

import pygame
import time
import os
from random import randint
from math import sqrt, atan, atan2, sin as sine, cos as cosine, radians, degrees

 

Now, we create some basic math functions so we can deal in degrees.

 

def sin(deg): return sine(radians(deg))
def cos(deg): return cosine(radians(deg))

def pythag(a,b): return sqrt(a*a + b*b)

 

This function will calculate the horizontal and vertical resultant force given a force and its angle.

 

def apply_force(magnitude,angle,hf,vf):
    hf += magnitude*sin(angle)
    vf += -magnitude*cos(angle)

    return hf, vf

 

This next function will add celestial bodies to our system. The bodies are stored in a dictionary where 'num' is the number of bodies and 'hv' and 'vv' are horizontal and vertical velocities. In dictionary notation, '"variable{0}".format(x)' will read '"variable5"' if 'x' is equal to 5.

 

def add_body(name,col,mass,pos,hv,vv):
    bodies[
"name{0}".format(bodies["num"])] = name
    bodies[
"mass{0}".format(bodies["num"])] = mass
    bodies[
"color{0}".format(bodies["num"])] = col
    bodies[
"pos{0}".format(bodies["num"])] = pos
    bodies[
"hv{0}".format(bodies["num"])] = hv
    bodies[
"vv{0}".format(bodies["num"])] = vv
    bodies[
"num"] += 1

 

This function will calculate the coordinates of a body from a top down perspective, taking zoom and panning into consideration.

 

def conv_z(pos,panx,pany,zoom):
    return (int((pos[0] + panx - xSize/2)*zoom + xSize/2),int((pos[1] + pany - ySize/2)*zoom + ySize/2))

 

Now we initiate pygame and make it full screen.

 

pygame.init()
infoObject = pygame.display.Info()
xSize, ySize = infoObject.current_w, infoObject.current_h
screen = pygame.display.set_mode((xSize, ySize), pygame.NOFRAME | pygame.FULLSCREEN)
pygame.display.set_caption(
"Gravity")

 

Here are the variables. The variable 'zoom_d' is the direct zoom value that can be changed by the user. Next, 'focus' is the number of the body that the camera is centered on. Then, 'view_mode' is the viewing mode, which for part one of this series will only be top-down. In the next part of the series, we will introduce a first person viewing mode. I have reduced the gravitational constant 'G' so everything doesn't have to be so astronomically far apart. Then we define 'bodies' and set 'num' to zero.

 

#----------------------Main Loop----------------------#

zoom_d = 3*sqrt(xSize) #zoom
focus = 0
view_mode = 0

 

G = pow(10,-21)

 

bodies = {}
bodies["num"] = 0

 

Next, we add the bodies. The first line shows a template. I have given Earth an initial horizontal velocity such that it has a circular orbit around the Sun. The formula for the speed of a circular orbit is v = G*M/r. I have done the same for the Moon but then added Earth's velocity meaning the Moon will orbit the Earth. None of the values are to scale.

 

#add_body(NAME, COLOUR, MASS, POSITION, HV, VV)
add_body("Spaceship",[255,255,255],100,[2000,2000],0,0)
add_body(
"The Sun",[254,229,117],pow(10,25),[0,0],0,0)
add_body(
"Earth",[92,135,45],pow(10,22),[0,500],sqrt(G*bodies["mass1"]/500),0)
add_body(
"The Moon",[155,155,155],pow(10,20),[0,520],-sqrt(G*bodies["mass2"]/20) + bodies["hv2"],0)
add_body(
"Mercury",[189,122,33],pow(10,19),[0,50],sqrt(G*bodies["mass1"]/50),0)

 

Then we begin our main loop.

 

fps = 60
clock = pygame.time.Clock()
frameCount = 0
done =
False
while not done:
    frameCount += 1
    keys = pygame.key.get_pressed()
   
for event in pygame.event.get():
       
if event.type == pygame.QUIT:
            done = True

 

Next, we allow the user to change the camera focus using the number keys. The last line prevents the error of having a focus number higher than the number of  bodies.


    if keys[pygame.K_0]: focus = 0
   
if keys[pygame.K_1]: focus = 1
   
if keys[pygame.K_2]: focus = 2
   
if keys[pygame.K_3]: focus = 3
   
if keys[pygame.K_4]: focus = 4
   
if keys[pygame.K_5]: focus = 5
   
if keys[pygame.K_6]: focus = 6
   
if keys[pygame.K_7]: focus = 7
   
if keys[pygame.K_8]: focus = 8
   
if keys[pygame.K_9]: focus = 9
    focus =
min(bodies["num"] - 1,focus)

 

Then, we allow the user to adjust the zoom and fill the screen black, using the page up and page down buttons.

 

    if keys[pygame.K_PAGEUP]: zoom_d -= 1
   
if keys[pygame.K_PAGEDOWN]: zoom_d += 1
    zoom_d =
max(zoom_d,1)
    zoom = xSize/(zoom_d*zoom_d)
        
    screen.fill([0,0,0])

 

Each frame we recalculate the spaceship's velocity, hence needing to set it to zero. These variables are used in representing the force on screen with lines.

 

    phf, pvf = 0, 0 #player horizontal/vertical velocity

 

Now we introduce the physics. Since every body feels the gravity of every other body, we need two for loops, where 'a' is the body we are calculating the physics for and 'b' is the body that is affecting 'a'. We do not want the mass of a body affecting itself, hence 'if b != a:'

 

    for a in range(bodies["num"]):
       
for b in range(bodies["num"]):
           
if b != a:

 

Next we give shorter names to the positions and calculate the angle between the two bodies in question. Then we get the distance between them and we use the main formula to get the force between them.


                px1, py1 = bodies["pos{0}".format(b)]
                px2, py2 = bodies[
"pos{0}".format(a)]
                th = degrees(atan2((py2 - py1),(px2 - px1))) - 90
#theta, in degrees
                
                d = pythag(py1 - py2,px1 - px2) + 0.00000001
#true distance

                f = G*bodies["mass{0}".format(a)]*bodies["mass{0}".format(b)]/(d*d) #gravitational force
   

Now, we set the horizontal and vertical force to zero. When 'a' is zero, we are calculating the physics of the spaceship. The user can use WASD to control the spaceship. If shift is held, the force will be greater and if space is held, the force will be smaller but more precise. I've used 'if b == 1:' just so it isn't repeated many times each frame.


                hf = 0 #horizontal force
                vf = 0 #vertical force

 

                if a == 0:
                   
if keys[pygame.K_LSHIFT]: thrust = 2
                   
elif keys[pygame.K_SPACE]: thrust = 0.3
                   
else: thrust = 1


                    if b == 1:
                       
if keys[pygame.K_w]:
                            hf, vf = apply_force(15*
pow(10,thrust),0,hf,vf)
                       
if keys[pygame.K_a]:
                            hf, vf = apply_force(15*
pow(10,thrust),270,hf,vf)
                       
if keys[pygame.K_s]:
                            hf, vf = apply_force(15*
pow(10,thrust),180,hf,vf)
                       
if keys[pygame.K_d]:
                            hf, vf = apply_force(15*pow(10,thrust),90,hf,vf)

 

This next part will detect a collision between the spaceship and another body.

 

                    if d < int(pow(bodies["mass{0}".format(b)]/pow(10,22),1/3.5)) + 2:
                        print(
"You crashed into",bodies["name{0}".format(b)] + ".")
                        done = True

                

Now, we apply the force of gravity to the body and add to the spaceship's force.


                hf, vf = apply_force(f,th,hf,vf)

 

                if a == 0:
                    phf += hf
                    pvf += vf

 

Every frame the acceleration increases by force divided by mass from the equation F = ma.

 

                ha = hf/bodies["mass{0}".format(a)] #horizontal acceleration
                va = vf/bodies["mass{0}".format(a)]

 

Every frame the velocity increases by the acceleration times the time. '5/fps' is a constant that I've made to have the time progress at a reasonable rate.

 

                bodies["hv{0}".format(a)] += ha*5/fps #horizontal velocity
                bodies["vv{0}".format(a)] += va*5/fps
 

Now we change the positions of the bodies given their velocities. I have excluded this from our double for loop above so that the change in position does not affect the physics of bodies calculated afterwards.


    #Move
    for a in range(bodies["num"]):
        bodies[
"pos{0}".format(a)][0] += bodies["hv{0}".format(a)]*5/fps
        bodies[
"pos{0}".format(a)][1] += bodies["vv{0}".format(a)]*5/fps
    

Finally, we draw the bodies on the screen, taking into account the panning around the focus body and the zoom. The lines drawn represent the forces acting on the spaceship. The value of the radius is the same value used for the collision detection.


    #Draw
    if view_mode == 0:
        px, py = bodies[
"pos0"][0], bodies["pos0"][1]
        panx = xSize/2 - bodies[
"pos{0}".format(focus)][0]
        pany = ySize/2 - bodies[
"pos{0}".format(focus)][1]
        
       
for a in range(1,bodies["num"]):
            rad =
int(pow(bodies["mass{0}".format(a)]/pow(10,22),1/3.5)) + 2

            x, y = conv_z((bodies["pos{0}".format(a)][0],bodies["pos{0}".format(a)][1]),panx,pany,zoom)
            pygame.draw.circle(screen, [255,255,255], (x,y),
int(rad*zoom))
            
        pygame.draw.circle(screen, [255,255,255], conv_z((px,py),panx,pany,zoom), 1)
        pygame.draw.line(screen, [255,0,0], conv_z((px + phf*
pow(10,-1),py),panx,pany,zoom),conv_z((px,py),panx,pany,zoom),1)
        pygame.draw.line(screen, [255,0,0], conv_z((px,py + pvf*
pow(10,-1)),panx,pany,zoom),conv_z((px,py),panx,pany,zoom),1)
        
    pygame.display.flip()
    clock.tick(fps)

pygame.quit()
 

This concludes the basic physics and top down viewing of the solar system. Here's all the code so far:

 

import pygame
import time
import os
from random import randint
from math import sqrt, atan, atan2, sin as sine, cos as cosine, radians, degrees

 

def sin(deg): return sine(radians(deg))
def cos(deg): return cosine(radians(deg))

def pythag(a,b): return sqrt(a*a + b*b)

 

def apply_force(magnitude,angle,hf,vf):
    hf += magnitude*sin(angle)
    vf += -magnitude*cos(angle)

    return hf, vf

 

def add_body(name,col,mass,pos,hv,vv):
    bodies[
"name{0}".format(bodies["num"])] = name
    bodies[
"mass{0}".format(bodies["num"])] = mass
    bodies[
"color{0}".format(bodies["num"])] = col
    bodies[
"pos{0}".format(bodies["num"])] = pos
    bodies[
"hv{0}".format(bodies["num"])] = hv
    bodies[
"vv{0}".format(bodies["num"])] = vv
    bodies[
"num"] += 1

 

def conv_z(pos,panx,pany,zoom):
    return (int((pos[0] + panx - xSize/2)*zoom + xSize/2),int((pos[1] + pany - ySize/2)*zoom + ySize/2))

 

dir_path = os.path.dirname(os.path.realpath(__file__))

pygame.init()
infoObject = pygame.display.Info()
xSize, ySize = infoObject.current_w, infoObject.current_h
screen = pygame.display.set_mode((xSize, ySize), pygame.NOFRAME | pygame.FULLSCREEN)
pygame.display.set_caption(
"Gravity")

 

#----------------------Main Loop----------------------#

zoom_d = 3*sqrt(xSize) #zoom
focus = 0
view_mode = 0

 

G = pow(10,-21)

 

bodies = {}
bodies["num"] = 0

 

#add_body(NAME, COLOUR, MASS, POSITION, HV, VV)
add_body("Spaceship",[255,255,255],100,[2000,2000],0,0)
add_body(
"The Sun",[254,229,117],pow(10,25),[0,0],0,0)
add_body(
"Earth",[92,135,45],pow(10,22),[0,500],sqrt(G*bodies["mass1"]/500),0)
add_body(
"The Moon",[155,155,155],pow(10,20),[0,520],-sqrt(G*bodies["mass2"]/20) + bodies["hv2"],0)
add_body(
"Mercury",[189,122,33],pow(10,19),[0,50],sqrt(G*bodies["mass1"]/50),0)

 

fps = 60
clock = pygame.time.Clock()
frameCount = 0
done =
False
while not done:
    frameCount += 1
    keys = pygame.key.get_pressed()
   
for event in pygame.event.get():
       
if event.type == pygame.QUIT:
            done = True


    if keys[pygame.K_0]: focus = 0
   
if keys[pygame.K_1]: focus = 1
   
if keys[pygame.K_2]: focus = 2
   
if keys[pygame.K_3]: focus = 3
   
if keys[pygame.K_4]: focus = 4
   
if keys[pygame.K_5]: focus = 5
   
if keys[pygame.K_6]: focus = 6
   
if keys[pygame.K_7]: focus = 7
   
if keys[pygame.K_8]: focus = 8
   
if keys[pygame.K_9]: focus = 9
    focus =
min(bodies["num"] - 1,focus)

 

    if keys[pygame.K_PAGEUP]: zoom_d -= 1
   
if keys[pygame.K_PAGEDOWN]: zoom_d += 1
    zoom_d =
max(zoom_d,1)
    zoom = xSize/(zoom_d*zoom_d)
        
    screen.fill([0,0,0])

 

    phf, pvf = 0, 0 #player horizontal/vertical velocity

 

    for a in range(bodies["num"]):
       
for b in range(bodies["num"]):
           
if b != a:
                px1, py1 = bodies[
"pos{0}".format(b)]
                px2, py2 = bodies[
"pos{0}".format(a)]
                th = degrees(atan2((py2 - py1),(px2 - px1))) - 90
#theta, in degrees
                
                d = pythag(py1 - py2,px1 - px2) + 0.00000001
#true distance

                f = G*bodies["mass{0}".format(a)]*bodies["mass{0}".format(b)]/(d*d) #gravitational force
    
                hf = 0
#horizontal force
                vf = 0 #vertical force

 

                if a == 0:
                   
if keys[pygame.K_LSHIFT]: thrust = 2
                   
elif keys[pygame.K_SPACE]: thrust = 0.3
                   
else: thrust = 1


                    if b == 1:
                       
if keys[pygame.K_w]:
                            hf, vf = apply_force(15*
pow(10,thrust),0,hf,vf)
                       
if keys[pygame.K_a]:
                            hf, vf = apply_force(15*
pow(10,thrust),270,hf,vf)
                       
if keys[pygame.K_s]:
                            hf, vf = apply_force(15*
pow(10,thrust),180,hf,vf)
                       
if keys[pygame.K_d]:
                            hf, vf = apply_force(15*pow(10,thrust),90,hf,vf)

 

                    if d < int(pow(bodies["mass{0}".format(b)]/pow(10,22),1/3.5)) + 2:
                        print(
"You crashed into",bodies["name{0}".format(b)] + ".")
                        done = True

                
                hf, vf = apply_force(f,th,hf,vf)

 

                if a == 0:
                    phf += hf
                    pvf += vf

 

                ha = hf/bodies["mass{0}".format(a)] #horizontal acceleration
                va = vf/bodies["mass{0}".format(a)]

 

                bodies["hv{0}".format(a)] += ha*5/fps #horizontal velocity
                bodies["vv{0}".format(a)] += va*5/fps
    
   
#Move
    for a in range(bodies["num"]):
        bodies[
"pos{0}".format(a)][0] += bodies["hv{0}".format(a)]*5/fps
        bodies[
"pos{0}".format(a)][1] += bodies["vv{0}".format(a)]*5/fps
    
   
#Draw
    if view_mode == 0:
        px, py = bodies[
"pos0"][0], bodies["pos0"][1]
        panx = xSize/2 - bodies[
"pos{0}".format(focus)][0]
        pany = ySize/2 - bodies[
"pos{0}".format(focus)][1]
        
       
for a in range(1,bodies["num"]):
            rad =
int(pow(bodies["mass{0}".format(a)]/pow(10,22),1/3.5)) + 2

            x, y = conv_z((bodies["pos{0}".format(a)][0],bodies["pos{0}".format(a)][1]),panx,pany,zoom)
            pygame.draw.circle(screen, [255,255,255], (x,y),
int(rad*zoom))
            
        pygame.draw.circle(screen, [255,255,255], conv_z((px,py),panx,pany,zoom), 1)
        pygame.draw.line(screen, [255,0,0], conv_z((px + phf*
pow(10,-1),py),panx,pany,zoom),conv_z((px,py),panx,pany,zoom),1)
        pygame.draw.line(screen, [255,0,0], conv_z((px,py + pvf*
pow(10,-1)),panx,pany,zoom),conv_z((px,py),panx,pany,zoom),1)
        
    pygame.display.flip()
    clock.tick(fps)

pygame.quit()

 

Please reload

Read more:

iOSDC Japan 2018

September 7, 2018

Making an iOS Newsletter App with Updating Content using JSON and Swift 4

July 25, 2018

1/14
Please reload

    © 2019 Tech Kingdom

    Contact us : official.techkingdom@gmail.com