diff --git a/BoSLOO.pyproj b/BoSLOO.pyproj index 1e0b50d..8241d3e 100644 --- a/BoSLOO.pyproj +++ b/BoSLOO.pyproj @@ -11,7 +11,7 @@ . BoSLOO BoSLOO - MSBuild|bosloo_env|$(MSBuildProjectFullPath) + MSBuild|BoSLOOenv|$(MSBuildProjectFullPath) true diff --git a/OrbitSim.py b/OrbitSim.py index 2972ed5..565334c 100644 --- a/OrbitSim.py +++ b/OrbitSim.py @@ -1,12 +1,17 @@ -import os, json, numpy, pygame, time, threading +import os, json, numpy, pygame, time, threading, jsonpickle from renderer import * from copy import deepcopy -groundControlPath = "GroundControlFiles" +groundControlPath = "GroundControl" +stateFilePath = os.path.join("SatState.json") + configPath = os.path.join("ConfigFiles", "OrbitSim") configFilename = os.path.join(configPath, "Universe.cfg") +satSavePath = os.path.join(configPath, "Orbit.cfg") mapFilename = os.path.join(configPath, "Map.png") +STATE_EVENT = pygame.event.custom_type() + def config(): """Returns the config dictionary. Generates with default values if no config dictionary exists.""" @@ -18,7 +23,8 @@ def config(): "G": 6.674e-11, "earthMass": 5.972e24, #in kg "earthRadius": 6378000, #meters - "timeScale": 1 #higher number go faster wheeeeee + "timeScale": 1, #higher number go faster wheeeeee + "updateTick": 300 #seconds to wait between save to file } with open(configFilename, "w") as file: json.dump(config_dic, file, indent = 4) @@ -27,14 +33,67 @@ def config(): with open(configFilename) as file: return json.load(file) + + class OrbitingBody: """a zero-mass point object parented to a planet""" def __init__(self, location:Point, velocity:Point, name, displaySize, parentPlanet): self.location = location + self.resetLocation = location.copy() self.velocity = velocity + self.resetVelocity = velocity.copy() self.name = name self.displaySize = displaySize #the size of the object on camera in pixels, for visibility reasons self.parentPlanet = parentPlanet + 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 + class Planet: """A massive body at 0,0,0 and a given radius.""" @@ -56,7 +115,7 @@ class Planet: """Converts theta and phi spherical coordinates to latitude and longitude. -> lat, long""" rotRadian = self.rotationPercentage/100 * 2 * math.pi lat = math.degrees(phi - (math.pi/2)) #negative lat is north, positive is south - long = theta - rotRadian #positive long is east, negative is west + long = rotRadian - theta #positive long is east, negative is west if long < -math.pi: long += math.pi*2 elif long > math.pi: @@ -97,6 +156,7 @@ def physicsUpdate(objects, orbitlines, deltaTime): 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)) + obj.stationKeep() elif type(obj).__name__ == "Planet": obj.rotate(deltaTime) for line in orbitlines: @@ -116,10 +176,12 @@ if __name__=="__main__": running = True display = False thisEarth = deepcopy(Planet.Earth) - sat = OrbitingBody(Point(0, config()["earthRadius"], config()["earthRadius"] - 800000), Point(-8900,0,0), "BoSLOO", 5, thisEarth) + sat = OrbitingBody(Point(0, config()["earthRadius"], config()["earthRadius"] - 800000), Point(-6900,0,0), "BoSLOO", 5, thisEarth) orbitlines = [] renderObjects = [thisEarth, sat, orbitlines] + configFile = config() clock = pygame.time.Clock() + stateTimer = pygame.time.set_timer(STATE_EVENT, configFile["updateTick"]*1000) mapThread = threading.Thread() save = False @@ -130,7 +192,7 @@ if __name__=="__main__": clock.tick(FPS) if display: #deltaTime = frameTime * config()["timeScale"] - deltaTime = (clock.get_time()/1000) * config()["timeScale"] + deltaTime = (clock.get_time()/1000) * configFile["timeScale"] physicsUpdate(renderObjects, orbitlines, deltaTime) camera.renderFrame(save=save) save=False @@ -142,7 +204,7 @@ if __name__=="__main__": elif event.type == pygame.MOUSEBUTTONDOWN: if not display: display = True - camera = Camera(window, Point(10 * config()["earthRadius"], 0, 0), thisEarth, renderObjects) + camera = Camera(window, Point(10 * configFile["earthRadius"], 0, 0), thisEarth, renderObjects) camera.renderFrame() pygame.display.flip() else: @@ -150,6 +212,10 @@ if __name__=="__main__": if not mapThread.is_alive(): mapThread = threading.Thread(target=camera.saveGroundTrack()) mapThread.start() + + elif event.type == STATE_EVENT: + sat.writeStateReadable() + configFile = config() #time.sleep(frameTime) diff --git a/SatState.json b/SatState.json new file mode 100644 index 0000000..1773608 --- /dev/null +++ b/SatState.json @@ -0,0 +1,7 @@ +{ + "notes": "lat: pos S, neg N; long: pos E, neg W", + "latitude": -9.621613589753375, + "longitude": -135.404396876101, + "altitude": 2246794.2098555025, + "velocity": 6778.7033882673195 +} \ No newline at end of file diff --git a/renderer.py b/renderer.py index 51409c2..3d6841c 100644 --- a/renderer.py +++ b/renderer.py @@ -11,6 +11,9 @@ class Point: def __init__(self, x, y, z): self.vector = numpy.array([x, y, z]) + def copy(self): + return Point(self.vector[0], self.vector[1], self.vector[2]) + def polar(self): """Converts the vector rectangular coordinates to polar coordinates.""" if self.vector[0] == 0: @@ -28,7 +31,7 @@ class Point: return [rho, theta, phi] def magnitude(self): - return numpy.linalg.norm(self.vector) + return float(numpy.linalg.norm(self.vector)) def normalize(self): self.vector = self.vector/self.magnitude() @@ -209,16 +212,13 @@ class Camera: #pygame.draw.circle(screenSurface, (150,255,150), (int(intersectPoint.vector[1]), int(intersectPoint.vector[2])), 5) #generate text - rho, theta, phi = sat.location.polar() - if rho < self.target.radius: - 0 == 0 - rawLat, rawLong = self.target.sphericalToLatLong(theta, phi) + alt, rawLat, rawLong = sat.latLongAlt() self.updateTrackList(rawLat, rawLong) latString = f"Latitude: {round(rawLat,4)}⁰ S" if rawLat >= 0 else f"Latitude: {-round(rawLat,4)}⁰ N" longString = f"Longitude: {round(rawLong,4)}⁰ E" if rawLong >= 0 else f"Longitude: {-round(rawLong,4)}⁰ W" font.render_to(backSurface, (0,0), f"Speed: {round(sat.velocity.magnitude()/1000,3)} km/s", (255,255,255)) - font.render_to(backSurface, (0,20), f"Altitude: {round((rho - self.target.radius)/1000)} km", (255,255,255)) + font.render_to(backSurface, (0,20), f"Altitude: {round((alt)/1000)} km", (255,255,255)) font.render_to(backSurface, (0,50), latString, (255,255,255)) font.render_to(backSurface, (0,70), longString, (255,255,255)) diff --git a/testMap.png b/testMap.png index 2887863..d9d96c7 100644 Binary files a/testMap.png and b/testMap.png differ