diff --git a/BoSLOO.pyproj b/BoSLOO.pyproj index 42b1fa4..4b7c2e8 100644 --- a/BoSLOO.pyproj +++ b/BoSLOO.pyproj @@ -23,6 +23,9 @@ + + Code + diff --git a/OrbitSim.py b/OrbitSim.py index cf39a07..f72af42 100644 --- a/OrbitSim.py +++ b/OrbitSim.py @@ -1,4 +1,6 @@ -import os, json, numpy, pygame +import os, json, numpy, pygame, time +from renderer import * +from copy import deepcopy groundControlPath = "GroundControlFiles" configPath = os.path.join("ConfigFiles", "OrbitSim") @@ -13,10 +15,8 @@ def config(): if not os.path.exists(configFilename): #generate default config_dic = { - "g": 6674, - "gExp": -14, #G = g * 10^gExp - "earthMass": 5972, - "earthMassExp": 21, #Me = earthMass * 10^earthMassExp; in kg + "G": 6.674e-11, + "earthMass": 5.972e24, #in kg "earthRadius": 6378000, #meters "timeScale": 1 #higher number go faster wheeeeee } @@ -27,61 +27,56 @@ def config(): with open(configFilename) as file: return json.load(file) -class Point: - """Numpy 3-vec""" - def __init__(self, x, y, z): - self.vector = numpy.array([x, y, z]) - - def magnitude(self): - return numpy.linalg.norm(self.vector) - - def distanceFrom(self, otherPoint:"Point"): - return numpy.linalg.norm(self.vector - otherPoint.vector) - -Point.zero = Point(0, 0, 0) - -class Camera: - """Object which will be used to paint pixels on screen.""" - def __init__(self, location:Point, target:Point = Point.zero, FOV = 75): - self.location = location - self.target = target - self.FOV = FOV - - def isInside(self, planet:"Planet"): - """returns True if camera is inside the planet.""" - return numpy.linalg.norm(self.location.magnitude) < planet.radius - 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.velocity = velocity self.name = name - self.displaySize = displaySize #the size of the object on camera, for visibility reasons + self.displaySize = displaySize #the size of the object on camera in pixels, for visibility reasons self.parentPlanet = parentPlanet class Planet: """A massive body at 0,0,0 and a given radius.""" - def __init__(self, name, mass, radius, rotationPeriod): + def __init__(self, name, mass, radius, rotationPeriod, location:Point = deepcopy(Point.zero)): """Rotation period given in seconds.""" + self.location = location self.name = name self.mass = mass self.radius = radius - self.rotationPercentage = 0 + self.rotationPercentage = 0.00 self.rotationPeriod = rotationPeriod -Planet.Earth = Planet("Earth", (config()["earthMass"] * 10**config()["earthMassExp"]), config()["earthRadius"], 86400) + def rotate(self, timeDelta:"Seconds"): + self.rotationPercentage += timeDelta/self.rotationPeriod + if self.rotationPercentage >= 100.0: + self.rotationPercentage -= 100.0 + +Planet.Earth = Planet("Earth", config()["earthMass"], config()["earthRadius"], 86400) + +def physicsUpdate(objects, deltaTime): + """updates the positions of all orbiting objects in [objects] with timestep deltaTime""" + for obj in objects: + if type(obj).__name__ == "OrbitingBody": + 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)) if __name__=="__main__": pygame.init() pygame.display.set_caption("Spinny") - window = pygame.display.set_mode((400, 400)) + window = pygame.display.set_mode((600, 600)) resolutionDownscaling = 2 pygame.display.flip() + frameTime = 1/30 #framerate + running = True display = False + thisEarth = deepcopy(Planet.Earth) + sat = OrbitingBody(Point(config()["earthRadius"] * 1.1, 0, 0), Point(0,1000,-6500), "BoSLOO", 3, thisEarth) + renderObjects = [thisEarth, sat] while running: for event in pygame.event.get(): @@ -90,7 +85,21 @@ if __name__=="__main__": elif event.type == pygame.MOUSEBUTTONDOWN: if not display: display = True - camera = Camera(Point(0, 0, 6378000*4)) + camera = Camera(window, Point(0, 0, 3 * config()["earthRadius"]), thisEarth, renderObjects) + pygame.draw.circle(window, (255,255,255), pygame.mouse.get_pos(), 100) + camera.renderFrame() + pygame.display.flip() + else: + display = False + window.fill((0,0,0)) + pygame.display.flip() + if display: + deltaTime = frameTime * config()["timeScale"] + physicsUpdate(renderObjects, deltaTime) + camera.renderFrame() + pygame.display.flip() + time.sleep(frameTime) + pygame.quit() print("Bye!") \ No newline at end of file diff --git a/renderer.py b/renderer.py new file mode 100644 index 0000000..87efc06 --- /dev/null +++ b/renderer.py @@ -0,0 +1,106 @@ +import numpy, pygame + +class Point: + """Numpy 3-vec""" + def __init__(self, x, y, z): + self.vector = numpy.array([x, y, z]) + + def magnitude(self): + return numpy.linalg.norm(self.vector) + + def normalize(self): + self.vector = self.vector/self.magnitude() + return self + + def distanceFrom(self, otherPoint:"Point"): + return numpy.linalg.norm(self.vector - otherPoint.vector) + + def add(p1, p2): + sum = numpy.add(p1.vector, p2.vector) + return Point(sum[0], sum[1], sum[2]) + + def subtract(p1, p2): + diff = numpy.subtract(p1.vector, p2.vector) + return Point(diff[0], diff[1], diff[2]) + + def dot(p1, p2): + return numpy.dot(p1.vector, p2.vector) + + def scalarMult(p1, scalar): + mult = p1.vector * scalar + return Point(mult[0], mult[1], mult[2]) + + +Point.zero = Point(0, 0, 0) + +class Ray: + def __init__(self, origin:Point, direction:Point): + self.origin = origin + self.direction = direction + +class Line: + def __init__(self, p1:Point, p2:Point): + self.p1 = p1 + self.p2 = p2 + + def intersectWithPlane(self, plane): + lineVec = Point.subtract(self.p2, self.p1) + dot = Point.dot(plane.normal, lineVec) + + if abs(dot) > 1e-6: + w = Point.subtract(self.p1, plane.point) + fac = -Point.dot(plane.normal, w) / dot + u = Point.scalarMult(lineVec, fac) + return Point.add(self.p1, u) + else: + return None + +class Plane: + def __init__(self, point:Point, normal:Point): + self.point = point + self.normal = normal + +class Camera: + """Object which will be used to paint pixels on screen.""" + def __init__(self, surface:pygame.Surface, location:Point, target:"Planet", objects, hFOV = 55, vFOV = 55): + self.surface = surface + self.objects = objects + self.location = location + self.target = target + self.hFOV = hFOV + self.vFOV = vFOV + + def isInside(self, planet:"Planet"): + """returns True if camera is inside the planet.""" + return numpy.linalg.norm(self.location.magnitude) < planet.radius + + def renderFrame(self): + """generates a frame and draws it to the surface. Does not update screen; use pygame.display.flip()""" + winWidth, winHeight = self.surface.get_size() + winDistance = winWidth * numpy.cos(numpy.radians(self.hFOV)/2) / 2 #distance for a virtual screen to exist in-space to give the correct FOV + vecToCenter = Point.subtract(self.target.location, self.location) + vecToCenter.normalize() + screenPlane = Plane(Point.add(self.location, Point.scalarMult(vecToCenter, winDistance)), vecToCenter) + screenSurface = pygame.Surface((winWidth, winHeight)) + #pygame uses 0,0 as the top left corner + for obj in self.objects: + if type(obj).__name__ == "OrbitingBody": + lineToCamera = Line(obj.location, self.location) + intersectPoint = lineToCamera.intersectWithPlane(screenPlane) + if intersectPoint is not None: + intersectPoint = Point.add(intersectPoint, Point(int(winWidth/2), int(winHeight/2), 0)) + pygame.draw.circle(screenSurface, (255,255,150), (int(intersectPoint.vector[0]), int(intersectPoint.vector[1])), obj.displaySize) + elif type(obj).__name__ == "Planet": + lineToCamera = Line(obj.location, self.location) + intersectPoint = lineToCamera.intersectWithPlane(screenPlane) + if intersectPoint is not None: + intersectPoint = Point.add(intersectPoint, Point(int(winWidth/2), int(winHeight/2), 0)) + pygame.draw.circle(screenSurface, (255,255,150), (int(intersectPoint.vector[0]), int(intersectPoint.vector[1])), 15) + + screenSurface = pygame.transform.flip(screenSurface, False, True) + self.surface.blit(screenSurface, (0,0)) + + + #for row in range(int(-winHeight/2), int(winHeight/2)): + # for column in range(int(-winWidth/2), int(winWidth/2)): + # line = Line(self.location, Point(self.location.x + column)) \ No newline at end of file