2022-07-06 01:59:29 +00:00
import os , json , numpy , pygame , time , threading , jsonpickle
2021-05-02 21:42:54 +00:00
from renderer import *
from copy import deepcopy
2021-04-22 16:22:59 +00:00
2022-07-06 01:59:29 +00:00
groundControlPath = " GroundControl "
stateFilePath = os . path . join ( " SatState.json " )
2021-04-22 19:10:57 +00:00
configPath = os . path . join ( " ConfigFiles " , " OrbitSim " )
configFilename = os . path . join ( configPath , " Universe.cfg " )
2022-07-06 01:59:29 +00:00
satSavePath = os . path . join ( configPath , " Orbit.cfg " )
2021-04-22 19:10:57 +00:00
mapFilename = os . path . join ( configPath , " Map.png " )
2022-07-06 01:59:29 +00:00
STATE_EVENT = pygame . event . custom_type ( )
2021-04-22 19:10:57 +00:00
def config ( ) :
""" Returns the config dictionary. Generates with default values if no config dictionary exists. """
if not os . path . exists ( configPath ) :
os . makedirs ( configPath )
if not os . path . exists ( configFilename ) :
#generate default
config_dic = {
2021-05-02 21:42:54 +00:00
" G " : 6.674e-11 ,
" earthMass " : 5.972e24 , #in kg
2021-04-22 19:10:57 +00:00
" earthRadius " : 6378000 , #meters
2022-07-06 01:59:29 +00:00
" timeScale " : 1 , #higher number go faster wheeeeee
" updateTick " : 300 #seconds to wait between save to file
2021-04-22 19:10:57 +00:00
}
with open ( configFilename , " w " ) as file :
json . dump ( config_dic , file , indent = 4 )
return config_dic
else :
with open ( configFilename ) as file :
return json . load ( file )
2022-07-06 01:59:29 +00:00
2021-04-22 19:10:57 +00:00
class OrbitingBody :
""" a zero-mass point object parented to a planet """
def __init__ ( self , location : Point , velocity : Point , name , displaySize , parentPlanet ) :
self . location = location
2022-07-06 01:59:29 +00:00
self . resetLocation = location . copy ( )
2021-04-22 19:10:57 +00:00
self . velocity = velocity
2022-07-06 01:59:29 +00:00
self . resetVelocity = velocity . copy ( )
2021-04-22 19:10:57 +00:00
self . name = name
2021-05-02 21:42:54 +00:00
self . displaySize = displaySize #the size of the object on camera in pixels, for visibility reasons
2021-04-22 19:10:57 +00:00
self . parentPlanet = parentPlanet
2022-07-06 01:59:29 +00:00
self . lastDelta = 0
self . lastSecondDelta = 0
self . keepFreeze = 3
def stationKeep ( self ) :
currDelta = Point . subtract ( self . resetLocation , self . location ) . magnitude ( )
currSecondDelta = currDelta - self . lastDelta
if ( currSecondDelta > 0 ) and ( self . lastSecondDelta < = 0 ) and self . keepFreeze < = 0 :
self . location = self . resetLocation . copy ( )
self . velocity = self . resetVelocity . copy ( )
self . keepFreeze = 3
elif self . keepFreeze > 0 :
self . keepFreeze - = 1
self . lastDelta = currDelta
self . lastSecondDelta = currSecondDelta
def latLongAlt ( self ) :
rho , theta , phi = self . location . polar ( )
rawLat , rawLong = self . parentPlanet . sphericalToLatLong ( theta , phi ) #negative lat is north, positive lat is south, positive long is east, negative long is west
return ( rho - self . parentPlanet . radius ) , rawLat , rawLong
def writeStateReadable ( self ) :
alt , lat , long = self . latLongAlt ( )
stateDic = {
" notes " : " lat: pos S, neg N; long: pos E, neg W " ,
" latitude " : lat ,
" longitude " : long ,
" altitude " : alt ,
" velocity " : self . velocity . magnitude ( )
}
with open ( stateFilePath , " w " ) as file :
json . dump ( stateDic , file , indent = 4 )
def saveState ( self ) :
stateDic = {
" location " : jsonpickle . encode ( self . location ) ,
" velocity " : jsonpickle . encode ( self . velocity ) ,
}
def loadState ( self ) :
if os . path . exists ( satSavePath ) :
with open ( satSavePath ) as file :
state = json . load ( file )
self . location = jsonpickle . decode ( state [ " location " ] )
self . velocity = jsonpickle . decode ( state [ " velocity " ] )
return True
else :
return False
2021-04-22 19:10:57 +00:00
class Planet :
""" A massive body at 0,0,0 and a given radius. """
2021-05-02 21:42:54 +00:00
def __init__ ( self , name , mass , radius , rotationPeriod , location : Point = deepcopy ( Point . zero ) ) :
2021-04-22 19:10:57 +00:00
""" Rotation period given in seconds. """
2021-05-02 21:42:54 +00:00
self . location = location
2021-04-22 19:10:57 +00:00
self . name = name
self . mass = mass
self . radius = radius
2022-01-19 22:41:06 +00:00
self . rotationPercentage = 0.00
2021-04-22 19:10:57 +00:00
self . rotationPeriod = rotationPeriod
2022-01-20 22:05:07 +00:00
def rotate ( self , timeDelta ) :
self . rotationPercentage + = timeDelta * 100 / self . rotationPeriod
2021-05-02 21:42:54 +00:00
if self . rotationPercentage > = 100.0 :
self . rotationPercentage - = 100.0
2022-01-20 20:19:02 +00:00
def sphericalToLatLong ( self , theta , phi ) :
""" Converts theta and phi spherical coordinates to latitude and longitude. -> lat, long """
2022-01-20 22:05:07 +00:00
rotRadian = self . rotationPercentage / 100 * 2 * math . pi
lat = math . degrees ( phi - ( math . pi / 2 ) ) #negative lat is north, positive is south
2022-07-06 01:59:29 +00:00
long = rotRadian - theta #positive long is east, negative is west
2022-01-20 20:19:02 +00:00
if long < - math . pi :
long + = math . pi * 2
elif long > math . pi :
long - = math . pi * 2
return ( lat , math . degrees ( long ) )
2021-05-03 01:38:42 +00:00
class DisplayPoint :
""" A single point of any color """
def __init__ ( self , location , color ) :
self . location = location
self . color = color
class DecayPoint ( DisplayPoint ) :
""" A display point that slowly fades to black """
2022-01-20 02:01:00 +00:00
decayTick = 1
2021-05-03 01:38:42 +00:00
currentDecayTick = 0
2022-01-20 02:01:00 +00:00
color = ( 255 , 255 , 255 , 255 )
2021-05-03 01:38:42 +00:00
def update ( self ) :
self . currentDecayTick + = 1
2022-01-20 02:01:00 +00:00
if self . currentDecayTick > = self . decayTick :
2021-05-03 01:38:42 +00:00
self . currentDecayTick = 0
2022-01-20 02:01:00 +00:00
self . color = ( self . color [ 0 ] , self . color [ 1 ] , self . color [ 2 ] , ( max ( ( self . color [ 3 ] - 5 , 0 ) ) ) )
2021-05-03 01:38:42 +00:00
2022-01-19 20:19:30 +00:00
def copy ( self ) :
""" returns a distinct copy of the point """
return DecayPoint ( self . location , self . color )
2021-05-02 21:42:54 +00:00
Planet . Earth = Planet ( " Earth " , config ( ) [ " earthMass " ] , config ( ) [ " earthRadius " ] , 86400 )
2021-05-03 01:38:42 +00:00
def physicsUpdate ( objects , orbitlines , deltaTime ) :
2021-05-02 21:42:54 +00:00
""" updates the positions of all orbiting objects in [objects] with timestep deltaTime """
for obj in objects :
if type ( obj ) . __name__ == " OrbitingBody " :
2022-01-20 02:01:00 +00:00
orbitlines . append ( DecayPoint ( deepcopy ( obj . location ) , ( 255 , 255 , 255 , 255 ) ) )
if len ( orbitlines ) > 100 :
2021-05-03 01:38:42 +00:00
orbitlines . pop ( 0 )
2021-05-02 21:42:54 +00:00
accel = Point . scalarMult ( Point . subtract ( obj . location , obj . parentPlanet . location ) . normalize ( ) , - ( config ( ) [ " G " ] * obj . parentPlanet . mass ) / ( Point . subtract ( obj . location , obj . parentPlanet . location ) . magnitude ( ) * * 2 ) )
obj . velocity = Point . add ( obj . velocity , Point . scalarMult ( accel , deltaTime ) )
obj . location = Point . add ( obj . location , Point . scalarMult ( obj . velocity , deltaTime ) )
2022-07-06 01:59:29 +00:00
obj . stationKeep ( )
2022-01-19 22:41:06 +00:00
elif type ( obj ) . __name__ == " Planet " :
obj . rotate ( deltaTime )
2021-05-03 01:38:42 +00:00
for line in orbitlines :
line . update ( )
2021-04-22 19:10:57 +00:00
if __name__ == " __main__ " :
pygame . init ( )
pygame . display . set_caption ( " Spinny " )
2022-01-20 05:06:37 +00:00
window = pygame . display . set_mode ( ( 900 , 900 ) )
2021-04-22 19:10:57 +00:00
resolutionDownscaling = 2
pygame . display . flip ( )
2022-01-20 19:51:17 +00:00
FPS = 144 #max framerate
frameTime = 1 / 144
2021-05-02 21:42:54 +00:00
2021-04-22 19:10:57 +00:00
running = True
display = False
2021-05-02 21:42:54 +00:00
thisEarth = deepcopy ( Planet . Earth )
2024-02-20 04:12:53 +00:00
sat = OrbitingBody ( Point ( 0 , config ( ) [ " earthRadius " ] * 5 , config ( ) [ " earthRadius " ] * 2 ) , Point ( - 2500 , 0 , 0 ) , " BoSLOO " , 5 , thisEarth )
2021-05-03 01:38:42 +00:00
orbitlines = [ ]
renderObjects = [ thisEarth , sat , orbitlines ]
2022-07-06 01:59:29 +00:00
configFile = config ( )
2022-01-20 19:51:17 +00:00
clock = pygame . time . Clock ( )
2022-07-06 01:59:29 +00:00
stateTimer = pygame . time . set_timer ( STATE_EVENT , configFile [ " updateTick " ] * 1000 )
2022-01-20 22:05:07 +00:00
mapThread = threading . Thread ( )
2022-01-20 19:51:17 +00:00
save = False
2021-06-27 02:19:44 +00:00
2022-01-20 19:51:17 +00:00
clock . tick ( FPS )
2021-04-22 19:10:57 +00:00
while running :
2022-01-20 19:51:17 +00:00
clock . tick ( FPS )
if display :
#deltaTime = frameTime * config()["timeScale"]
2022-07-06 01:59:29 +00:00
deltaTime = ( clock . get_time ( ) / 1000 ) * configFile [ " timeScale " ]
2022-01-20 19:51:17 +00:00
physicsUpdate ( renderObjects , orbitlines , deltaTime )
camera . renderFrame ( save = save )
save = False
pygame . display . flip ( )
2021-04-22 19:10:57 +00:00
for event in pygame . event . get ( ) :
if event . type == pygame . QUIT :
running = False
elif event . type == pygame . MOUSEBUTTONDOWN :
if not display :
display = True
2022-07-06 01:59:29 +00:00
camera = Camera ( window , Point ( 10 * configFile [ " earthRadius " ] , 0 , 0 ) , thisEarth , renderObjects )
2021-05-02 21:42:54 +00:00
camera . renderFrame ( )
pygame . display . flip ( )
else :
2022-01-20 19:51:17 +00:00
save = True
2022-01-20 22:05:07 +00:00
if not mapThread . is_alive ( ) :
mapThread = threading . Thread ( target = camera . saveGroundTrack ( ) )
mapThread . start ( )
2022-07-06 01:59:29 +00:00
elif event . type == STATE_EVENT :
sat . writeStateReadable ( )
configFile = config ( )
2022-01-20 19:51:17 +00:00
#time.sleep(frameTime)
2021-05-02 21:42:54 +00:00
2021-04-22 19:10:57 +00:00
pygame . quit ( )
print ( " Bye! " )