Merge pull request #9 from Sakimori/sprited-renders
merging sprited-renders
1
.gitignore
vendored
|
@ -367,3 +367,4 @@ ConfigFiles/
|
||||||
env/
|
env/
|
||||||
GroundControlFiles/
|
GroundControlFiles/
|
||||||
SatFiles/
|
SatFiles/
|
||||||
|
/test.png
|
||||||
|
|
BIN
Assets/Maps/rect_color.png
Normal file
After Width: | Height: | Size: 2.2 MiB |
BIN
Assets/Maps/rect_color_small.png
Normal file
After Width: | Height: | Size: 383 KiB |
BIN
Assets/Sphere/0001.png
Normal file
After Width: | Height: | Size: 1.4 MiB |
BIN
Assets/Sphere/0002.png
Normal file
After Width: | Height: | Size: 1.4 MiB |
BIN
Assets/Sphere/0003.png
Normal file
After Width: | Height: | Size: 1.4 MiB |
BIN
Assets/Sphere/0004.png
Normal file
After Width: | Height: | Size: 1.4 MiB |
BIN
Assets/Sphere/0005.png
Normal file
After Width: | Height: | Size: 1.4 MiB |
BIN
Assets/Sphere/0006.png
Normal file
After Width: | Height: | Size: 1.4 MiB |
BIN
Assets/Sphere/0007.png
Normal file
After Width: | Height: | Size: 1.4 MiB |
BIN
Assets/Sphere/0008.png
Normal file
After Width: | Height: | Size: 1.4 MiB |
BIN
Assets/Sphere/0009.png
Normal file
After Width: | Height: | Size: 1.4 MiB |
BIN
Assets/Sphere/0010.png
Normal file
After Width: | Height: | Size: 1.4 MiB |
BIN
Assets/Sphere/0011.png
Normal file
After Width: | Height: | Size: 1.4 MiB |
BIN
Assets/Sphere/0012.png
Normal file
After Width: | Height: | Size: 1.4 MiB |
BIN
Assets/Sphere/0013.png
Normal file
After Width: | Height: | Size: 1.4 MiB |
BIN
Assets/Sphere/0014.png
Normal file
After Width: | Height: | Size: 1.4 MiB |
BIN
Assets/Sphere/0015.png
Normal file
After Width: | Height: | Size: 1.4 MiB |
BIN
Assets/Sphere/0016.png
Normal file
After Width: | Height: | Size: 1.4 MiB |
BIN
Assets/Sphere/0017.png
Normal file
After Width: | Height: | Size: 1.4 MiB |
BIN
Assets/Sphere/0018.png
Normal file
After Width: | Height: | Size: 1.4 MiB |
BIN
Assets/Sphere/0019.png
Normal file
After Width: | Height: | Size: 1.4 MiB |
BIN
Assets/Sphere/0020.png
Normal file
After Width: | Height: | Size: 1.4 MiB |
BIN
Assets/Sphere/0021.png
Normal file
After Width: | Height: | Size: 1.4 MiB |
BIN
Assets/Sphere/0022.png
Normal file
After Width: | Height: | Size: 1.4 MiB |
BIN
Assets/Sphere/0023.png
Normal file
After Width: | Height: | Size: 1.4 MiB |
BIN
Assets/Sphere/0024.png
Normal file
After Width: | Height: | Size: 1.4 MiB |
BIN
Assets/Sphere/0025.png
Normal file
After Width: | Height: | Size: 1.4 MiB |
BIN
Assets/Sphere/0026.png
Normal file
After Width: | Height: | Size: 1.4 MiB |
BIN
Assets/Sphere/0027.png
Normal file
After Width: | Height: | Size: 1.4 MiB |
BIN
Assets/Sphere/0028.png
Normal file
After Width: | Height: | Size: 1.4 MiB |
BIN
Assets/Sphere/0029.png
Normal file
After Width: | Height: | Size: 1.4 MiB |
BIN
Assets/Sphere/0030.png
Normal file
After Width: | Height: | Size: 1.4 MiB |
BIN
Assets/Sphere/0031.png
Normal file
After Width: | Height: | Size: 1.4 MiB |
BIN
Assets/Sphere/0032.png
Normal file
After Width: | Height: | Size: 1.4 MiB |
BIN
Assets/Sphere/0033.png
Normal file
After Width: | Height: | Size: 1.4 MiB |
BIN
Assets/Sphere/0034.png
Normal file
After Width: | Height: | Size: 1.4 MiB |
BIN
Assets/Sphere/0035.png
Normal file
After Width: | Height: | Size: 1.4 MiB |
BIN
Assets/Sphere/0036.png
Normal file
After Width: | Height: | Size: 1.4 MiB |
BIN
Assets/Sphere/0037.png
Normal file
After Width: | Height: | Size: 1.4 MiB |
BIN
Assets/Sphere/0038.png
Normal file
After Width: | Height: | Size: 1.4 MiB |
BIN
Assets/Sphere/0039.png
Normal file
After Width: | Height: | Size: 1.4 MiB |
BIN
Assets/Sphere/0040.png
Normal file
After Width: | Height: | Size: 1.4 MiB |
BIN
Assets/Sphere/0041.png
Normal file
After Width: | Height: | Size: 1.4 MiB |
BIN
Assets/Sphere/0042.png
Normal file
After Width: | Height: | Size: 1.4 MiB |
BIN
Assets/Sphere/0043.png
Normal file
After Width: | Height: | Size: 1.4 MiB |
BIN
Assets/Sphere/0044.png
Normal file
After Width: | Height: | Size: 1.4 MiB |
BIN
Assets/Sphere/0045.png
Normal file
After Width: | Height: | Size: 1.4 MiB |
BIN
Assets/Sphere/0046.png
Normal file
After Width: | Height: | Size: 1.4 MiB |
BIN
Assets/Sphere/0047.png
Normal file
After Width: | Height: | Size: 1.4 MiB |
BIN
Assets/Sphere/0048.png
Normal file
After Width: | Height: | Size: 1.4 MiB |
BIN
Assets/Sphere/0049.png
Normal file
After Width: | Height: | Size: 1.4 MiB |
BIN
Assets/Sphere/0050.png
Normal file
After Width: | Height: | Size: 1.4 MiB |
|
@ -38,6 +38,62 @@
|
||||||
<Architecture>X86</Architecture>
|
<Architecture>X86</Architecture>
|
||||||
</Interpreter>
|
</Interpreter>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<Folder Include="Assets\" />
|
||||||
|
<Folder Include="Assets\Sphere\" />
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<Content Include="Assets\Sphere\0001.png" />
|
||||||
|
<Content Include="Assets\Sphere\0002.png" />
|
||||||
|
<Content Include="Assets\Sphere\0003.png" />
|
||||||
|
<Content Include="Assets\Sphere\0004.png" />
|
||||||
|
<Content Include="Assets\Sphere\0005.png" />
|
||||||
|
<Content Include="Assets\Sphere\0006.png" />
|
||||||
|
<Content Include="Assets\Sphere\0007.png" />
|
||||||
|
<Content Include="Assets\Sphere\0008.png" />
|
||||||
|
<Content Include="Assets\Sphere\0009.png" />
|
||||||
|
<Content Include="Assets\Sphere\0010.png" />
|
||||||
|
<Content Include="Assets\Sphere\0011.png" />
|
||||||
|
<Content Include="Assets\Sphere\0012.png" />
|
||||||
|
<Content Include="Assets\Sphere\0013.png" />
|
||||||
|
<Content Include="Assets\Sphere\0014.png" />
|
||||||
|
<Content Include="Assets\Sphere\0015.png" />
|
||||||
|
<Content Include="Assets\Sphere\0016.png" />
|
||||||
|
<Content Include="Assets\Sphere\0017.png" />
|
||||||
|
<Content Include="Assets\Sphere\0018.png" />
|
||||||
|
<Content Include="Assets\Sphere\0019.png" />
|
||||||
|
<Content Include="Assets\Sphere\0020.png" />
|
||||||
|
<Content Include="Assets\Sphere\0021.png" />
|
||||||
|
<Content Include="Assets\Sphere\0022.png" />
|
||||||
|
<Content Include="Assets\Sphere\0023.png" />
|
||||||
|
<Content Include="Assets\Sphere\0024.png" />
|
||||||
|
<Content Include="Assets\Sphere\0025.png" />
|
||||||
|
<Content Include="Assets\Sphere\0026.png" />
|
||||||
|
<Content Include="Assets\Sphere\0027.png" />
|
||||||
|
<Content Include="Assets\Sphere\0028.png" />
|
||||||
|
<Content Include="Assets\Sphere\0029.png" />
|
||||||
|
<Content Include="Assets\Sphere\0030.png" />
|
||||||
|
<Content Include="Assets\Sphere\0031.png" />
|
||||||
|
<Content Include="Assets\Sphere\0032.png" />
|
||||||
|
<Content Include="Assets\Sphere\0033.png" />
|
||||||
|
<Content Include="Assets\Sphere\0034.png" />
|
||||||
|
<Content Include="Assets\Sphere\0035.png" />
|
||||||
|
<Content Include="Assets\Sphere\0036.png" />
|
||||||
|
<Content Include="Assets\Sphere\0037.png" />
|
||||||
|
<Content Include="Assets\Sphere\0038.png" />
|
||||||
|
<Content Include="Assets\Sphere\0039.png" />
|
||||||
|
<Content Include="Assets\Sphere\0040.png" />
|
||||||
|
<Content Include="Assets\Sphere\0041.png" />
|
||||||
|
<Content Include="Assets\Sphere\0042.png" />
|
||||||
|
<Content Include="Assets\Sphere\0043.png" />
|
||||||
|
<Content Include="Assets\Sphere\0044.png" />
|
||||||
|
<Content Include="Assets\Sphere\0045.png" />
|
||||||
|
<Content Include="Assets\Sphere\0046.png" />
|
||||||
|
<Content Include="Assets\Sphere\0047.png" />
|
||||||
|
<Content Include="Assets\Sphere\0048.png" />
|
||||||
|
<Content Include="Assets\Sphere\0049.png" />
|
||||||
|
<Content Include="Assets\Sphere\0050.png" />
|
||||||
|
</ItemGroup>
|
||||||
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\Python Tools\Microsoft.PythonTools.targets" />
|
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\Python Tools\Microsoft.PythonTools.targets" />
|
||||||
<!-- Uncomment the CoreCompile target to enable the Build command in
|
<!-- Uncomment the CoreCompile target to enable the Build command in
|
||||||
Visual Studio and specify your pre- and post-build commands in
|
Visual Studio and specify your pre- and post-build commands in
|
||||||
|
|
71
OrbitSim.py
|
@ -44,14 +44,25 @@ class Planet:
|
||||||
self.name = name
|
self.name = name
|
||||||
self.mass = mass
|
self.mass = mass
|
||||||
self.radius = radius
|
self.radius = radius
|
||||||
self.rotationPercentage = 0.04
|
self.rotationPercentage = 0.00
|
||||||
self.rotationPeriod = rotationPeriod
|
self.rotationPeriod = rotationPeriod
|
||||||
|
|
||||||
def rotate(self, timeDelta:"Seconds"):
|
def rotate(self, timeDelta):
|
||||||
self.rotationPercentage += timeDelta/self.rotationPeriod
|
self.rotationPercentage += timeDelta*100/self.rotationPeriod
|
||||||
if self.rotationPercentage >= 100.0:
|
if self.rotationPercentage >= 100.0:
|
||||||
self.rotationPercentage -= 100.0
|
self.rotationPercentage -= 100.0
|
||||||
|
|
||||||
|
def sphericalToLatLong(self, theta, phi):
|
||||||
|
"""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
|
||||||
|
if long < -math.pi:
|
||||||
|
long += math.pi*2
|
||||||
|
elif long > math.pi:
|
||||||
|
long -= math.pi*2
|
||||||
|
return (lat, math.degrees(long))
|
||||||
|
|
||||||
class DisplayPoint:
|
class DisplayPoint:
|
||||||
"""A single point of any color"""
|
"""A single point of any color"""
|
||||||
def __init__(self, location, color):
|
def __init__(self, location, color):
|
||||||
|
@ -60,14 +71,15 @@ class DisplayPoint:
|
||||||
|
|
||||||
class DecayPoint(DisplayPoint):
|
class DecayPoint(DisplayPoint):
|
||||||
"""A display point that slowly fades to black"""
|
"""A display point that slowly fades to black"""
|
||||||
decayTick = 10
|
decayTick = 1
|
||||||
currentDecayTick = 0
|
currentDecayTick = 0
|
||||||
|
color = (255,255,255,255)
|
||||||
|
|
||||||
def update(self):
|
def update(self):
|
||||||
self.currentDecayTick += 1
|
self.currentDecayTick += 1
|
||||||
if self.currentDecayTick > self.decayTick:
|
if self.currentDecayTick >= self.decayTick:
|
||||||
self.currentDecayTick = 0
|
self.currentDecayTick = 0
|
||||||
self.color = (max((self.color[0]-5, 0)), max((self.color[1]-5, 0)), max((self.color[2]-5, 0)))
|
self.color = (self.color[0], self.color[1], self.color[2], (max((self.color[3]-5, 0))))
|
||||||
|
|
||||||
def copy(self):
|
def copy(self):
|
||||||
"""returns a distinct copy of the point"""
|
"""returns a distinct copy of the point"""
|
||||||
|
@ -79,12 +91,14 @@ def physicsUpdate(objects, orbitlines, deltaTime):
|
||||||
"""updates the positions of all orbiting objects in [objects] with timestep deltaTime"""
|
"""updates the positions of all orbiting objects in [objects] with timestep deltaTime"""
|
||||||
for obj in objects:
|
for obj in objects:
|
||||||
if type(obj).__name__ == "OrbitingBody":
|
if type(obj).__name__ == "OrbitingBody":
|
||||||
orbitlines.append(DecayPoint(deepcopy(obj.location), (255,255,255)))
|
orbitlines.append(DecayPoint(deepcopy(obj.location), (255,255,255,255)))
|
||||||
if len(orbitlines) > 500:
|
if len(orbitlines) > 100:
|
||||||
orbitlines.pop(0)
|
orbitlines.pop(0)
|
||||||
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))
|
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.velocity = Point.add(obj.velocity, Point.scalarMult(accel, deltaTime))
|
||||||
obj.location = Point.add(obj.location, Point.scalarMult(obj.velocity, deltaTime))
|
obj.location = Point.add(obj.location, Point.scalarMult(obj.velocity, deltaTime))
|
||||||
|
elif type(obj).__name__ == "Planet":
|
||||||
|
obj.rotate(deltaTime)
|
||||||
for line in orbitlines:
|
for line in orbitlines:
|
||||||
line.update()
|
line.update()
|
||||||
|
|
||||||
|
@ -96,41 +110,48 @@ if __name__=="__main__":
|
||||||
resolutionDownscaling = 2
|
resolutionDownscaling = 2
|
||||||
pygame.display.flip()
|
pygame.display.flip()
|
||||||
|
|
||||||
frameTime = 1/30 #framerate
|
FPS = 144 #max framerate
|
||||||
|
frameTime = 1/144
|
||||||
|
|
||||||
running = True
|
running = True
|
||||||
display = False
|
display = False
|
||||||
thisEarth = deepcopy(Planet.Earth)
|
thisEarth = deepcopy(Planet.Earth)
|
||||||
sat = OrbitingBody(Point(config()["earthRadius"] * 1.5, 0, 0), Point(2000,6000,-2500), "BoSLOO", 3, thisEarth)
|
sat = OrbitingBody(Point(0, config()["earthRadius"], config()["earthRadius"] - 800000), Point(-8900,0,0), "BoSLOO", 5, thisEarth)
|
||||||
orbitlines = []
|
orbitlines = []
|
||||||
renderObjects = [thisEarth, sat, orbitlines]
|
renderObjects = [thisEarth, sat, orbitlines]
|
||||||
imageThread = threading.Thread()
|
clock = pygame.time.Clock()
|
||||||
|
mapThread = threading.Thread()
|
||||||
|
|
||||||
|
save = False
|
||||||
|
|
||||||
|
clock.tick(FPS)
|
||||||
|
|
||||||
while running:
|
while running:
|
||||||
|
clock.tick(FPS)
|
||||||
|
if display:
|
||||||
|
#deltaTime = frameTime * config()["timeScale"]
|
||||||
|
deltaTime = (clock.get_time()/1000) * config()["timeScale"]
|
||||||
|
physicsUpdate(renderObjects, orbitlines, deltaTime)
|
||||||
|
camera.renderFrame(save=save)
|
||||||
|
save=False
|
||||||
|
pygame.display.flip()
|
||||||
|
|
||||||
for event in pygame.event.get():
|
for event in pygame.event.get():
|
||||||
if event.type == pygame.QUIT:
|
if event.type == pygame.QUIT:
|
||||||
running = False
|
running = False
|
||||||
elif event.type == pygame.MOUSEBUTTONDOWN:
|
elif event.type == pygame.MOUSEBUTTONDOWN:
|
||||||
if not display:
|
if not display:
|
||||||
display = True
|
display = True
|
||||||
camera = Camera(window, Point(0, 0, 4 * config()["earthRadius"]), thisEarth, renderObjects)
|
camera = Camera(window, Point(10 * config()["earthRadius"], 0, 0), thisEarth, renderObjects)
|
||||||
pygame.draw.circle(window, (255,255,255), pygame.mouse.get_pos(), 100)
|
|
||||||
camera.renderFrame()
|
camera.renderFrame()
|
||||||
pygame.display.flip()
|
pygame.display.flip()
|
||||||
else:
|
else:
|
||||||
if not imageThread.is_alive():
|
save = True
|
||||||
imageThread = threading.Thread(target=camera.renderImage, args=(sat, thisEarth, orbitlines))
|
if not mapThread.is_alive():
|
||||||
imageThread.start()
|
mapThread = threading.Thread(target=camera.saveGroundTrack())
|
||||||
display = False
|
mapThread.start()
|
||||||
window.fill((0,0,0))
|
|
||||||
pygame.display.flip()
|
#time.sleep(frameTime)
|
||||||
if display:
|
|
||||||
deltaTime = frameTime * config()["timeScale"]
|
|
||||||
physicsUpdate(renderObjects, orbitlines, deltaTime)
|
|
||||||
camera.renderFrame()
|
|
||||||
pygame.display.flip()
|
|
||||||
time.sleep(frameTime)
|
|
||||||
|
|
||||||
pygame.quit()
|
pygame.quit()
|
||||||
|
|
||||||
|
|
271
renderer.py
|
@ -1,15 +1,30 @@
|
||||||
import numpy, pygame, math
|
import numpy, pygame, math, os
|
||||||
import pygame.freetype
|
import pygame.freetype
|
||||||
|
|
||||||
|
ASSET_DIR = "Assets"
|
||||||
|
SPHERE_FOLDER_NAME = "Sphere"
|
||||||
|
MAPS_FOLDER_NAME = "Maps"
|
||||||
|
|
||||||
|
|
||||||
class Point:
|
class Point:
|
||||||
"""Numpy 3-vec"""
|
"""Numpy 3-vec"""
|
||||||
def __init__(self, x, y, z):
|
def __init__(self, x, y, z):
|
||||||
self.vector = numpy.array([x, y, z])
|
self.vector = numpy.array([x, y, z])
|
||||||
|
|
||||||
def polar(self):
|
def polar(self):
|
||||||
rho = math.sqrt(self.vector[0] ** 2 + self.vector[1] ** 2 + self.vector[2] ** 2)
|
"""Converts the vector rectangular coordinates to polar coordinates."""
|
||||||
theta = math.atan(self.vector[2]/self.vector[0])
|
if self.vector[0] == 0:
|
||||||
phi = math.acos((self.vector[1])/(rho))
|
self.vector[0] = 0.1
|
||||||
|
if self.vector[2] == 0:
|
||||||
|
self.vector[2] = 0.1
|
||||||
|
rho = math.sqrt(int(self.vector[0]) ** 2 + int(self.vector[1]) ** 2 + int(self.vector[2]) ** 2)
|
||||||
|
theta = math.atan(self.vector[1]/self.vector[0]) #this has a range of -pi/2 to pi/2 but we need 0 to 2pi so more work needed
|
||||||
|
phi = math.acos(self.vector[2]/rho)
|
||||||
|
if self.vector[0] < 0:
|
||||||
|
if self.vector[1] >= 0: #if x is positive, atan is fine. need to check if x is negative, first.
|
||||||
|
theta += math.pi
|
||||||
|
else:
|
||||||
|
theta -= math.pi
|
||||||
return [rho, theta, phi]
|
return [rho, theta, phi]
|
||||||
|
|
||||||
def magnitude(self):
|
def magnitude(self):
|
||||||
|
@ -70,137 +85,187 @@ class Plane:
|
||||||
self.point = point
|
self.point = point
|
||||||
self.normal = normal
|
self.normal = normal
|
||||||
|
|
||||||
|
class PlanetSprite(pygame.sprite.Sprite):
|
||||||
|
def __init__(self, camera, parentPlanet:"Planet"):
|
||||||
|
pygame.sprite.Sprite.__init__(self)
|
||||||
|
#the rotation animation loops every 64th of a rotation, so determine and store the frame number.
|
||||||
|
self.frames = {}
|
||||||
|
for imgName in os.listdir(os.path.join(ASSET_DIR, SPHERE_FOLDER_NAME)):
|
||||||
|
if imgName.endswith(".png"):
|
||||||
|
self.frames[imgName.strip(".png")] = pygame.image.load(os.path.join(ASSET_DIR, SPHERE_FOLDER_NAME, imgName)).convert_alpha()
|
||||||
|
self.parentPlanet = parentPlanet
|
||||||
|
self.frameNumber = str(round(math.modf(self.parentPlanet.rotationPercentage/100 * 64)[0] * 49) + 1).zfill(4)
|
||||||
|
self.image = self.frames[self.frameNumber]
|
||||||
|
self.setSize(camera)
|
||||||
|
|
||||||
|
|
||||||
|
def setSize(self, camera):
|
||||||
|
winWidth, winHeight = camera.surface.get_size()
|
||||||
|
#distance = Point.subtract(camera.location, self.parentPlanet.location).magnitude()
|
||||||
|
#radius = self.parentPlanet.radius
|
||||||
|
#self.sideLength = int((1/math.tan(numpy.radians(camera.hFOV)/2))*radius/math.sqrt(distance**2 - radius**2)*winWidth/2)
|
||||||
|
|
||||||
|
lineToCam = Line(Point.add(self.parentPlanet.location, Point(0, self.parentPlanet.radius,0)), camera.location)
|
||||||
|
intersectPoint = lineToCam.intersectWithPlane(camera.screenPlane)
|
||||||
|
radius = intersectPoint.vector[1]
|
||||||
|
self.sideLength = int(radius*2*600/530)
|
||||||
|
|
||||||
|
self.image = pygame.transform.scale(self.image, (self.sideLength, self.sideLength))
|
||||||
|
self.rect = self.image.get_rect()
|
||||||
|
self.rect.center = (winWidth/2, winHeight/2)
|
||||||
|
|
||||||
|
|
||||||
|
def update(self):
|
||||||
|
self.frameNumber = str(round(math.modf(self.parentPlanet.rotationPercentage/100 * 64)[0] * 49) + 1).zfill(4)
|
||||||
|
self.image = pygame.image.load(os.path.join(ASSET_DIR, SPHERE_FOLDER_NAME, f"{self.frameNumber}.png")).convert_alpha()
|
||||||
|
if self.sideLength is not None:
|
||||||
|
self.image = pygame.transform.scale(self.image, (self.sideLength, self.sideLength))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class Camera:
|
class Camera:
|
||||||
"""Object which will be used to paint pixels on screen."""
|
"""Object in charge of rendering both the realtime 3D scene and a ground track map."""
|
||||||
def __init__(self, surface:pygame.Surface, location:Point, target:"Planet", objects, hFOV = 55, vFOV = 55):
|
def __init__(self, surface:pygame.Surface, location:Point, target:"Planet", objects, hFOV = 60):
|
||||||
self.surface = surface
|
self.surface = surface
|
||||||
self.objects = objects
|
self.objects = objects
|
||||||
self.location = location
|
self.location = location
|
||||||
self.target = target
|
self.target = target
|
||||||
self.hFOV = hFOV
|
self.hFOV = hFOV
|
||||||
self.vFOV = vFOV
|
self.spriteGroup = pygame.sprite.Group()
|
||||||
|
self.pastTrackPoints = []
|
||||||
|
self.trackSampleRate = 8
|
||||||
|
self.trackSampleCount = 0
|
||||||
|
|
||||||
|
self.mapSurface = pygame.image.load(os.path.join(ASSET_DIR, MAPS_FOLDER_NAME, "rect_color.png"))
|
||||||
|
self.mapWidth, self.mapHeight = self.mapSurface.get_size()
|
||||||
|
|
||||||
|
winWidth, winHeight = self.surface.get_size()
|
||||||
|
winDistance = winWidth / (2 * math.tan(numpy.radians(self.hFOV/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()
|
||||||
|
self.screenPlane = Plane(Point.add(self.location, Point.scalarMult(vecToCenter, winDistance)), vecToCenter)
|
||||||
|
|
||||||
|
self.spriteGroup.add(PlanetSprite(self, self.target))
|
||||||
|
|
||||||
|
|
||||||
def isInside(self, planet:"Planet"):
|
def isInside(self, planet:"Planet"):
|
||||||
"""returns True if camera is inside the planet."""
|
"""returns True if camera is inside the planet."""
|
||||||
return numpy.linalg.norm(self.location.magnitude) < planet.radius
|
return numpy.linalg.norm(self.location.magnitude) < planet.radius
|
||||||
|
|
||||||
def renderFrame(self):
|
def renderFrame(self, save=False):
|
||||||
"""generates a frame and draws it to the surface. Does not update screen; use pygame.display.flip()"""
|
"""generates a frame and draws it to the surface. Does not update screen; use pygame.display.flip()"""
|
||||||
font = pygame.freetype.SysFont("Comic Sans MS", 14)
|
font = pygame.freetype.SysFont("Comic Sans MS", 14)
|
||||||
winWidth, winHeight = self.surface.get_size()
|
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)
|
frontSurface = pygame.Surface((winWidth, winHeight), pygame.SRCALPHA)
|
||||||
vecToCenter.normalize()
|
backSurface = pygame.Surface((winWidth, winHeight), pygame.SRCALPHA)
|
||||||
screenPlane = Plane(Point.add(self.location, Point.scalarMult(vecToCenter, winDistance)), vecToCenter)
|
backgroundSurface = pygame.Surface((winWidth, winHeight))
|
||||||
screenSurface = pygame.Surface((winWidth, winHeight))
|
|
||||||
|
backgroundSurface.fill((15,15,15))
|
||||||
|
backSurface.fill((0,0,0,0))
|
||||||
|
frontSurface.fill((0,0,0,0))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#pygame uses 0,0 as the top left corner
|
#pygame uses 0,0 as the top left corner
|
||||||
for obj in self.objects:
|
for obj in self.objects:
|
||||||
if type(obj).__name__ == "OrbitingBody":
|
if type(obj).__name__ == "OrbitingBody":
|
||||||
sat = obj
|
sat = obj
|
||||||
lineToCamera = Line(obj.location, self.location)
|
lineToCamera = Line(obj.location, self.location)
|
||||||
intersectPoint = lineToCamera.intersectWithPlane(screenPlane)
|
intersectPoint = lineToCamera.intersectWithPlane(self.screenPlane)
|
||||||
|
intersectPoint.vector[2] = -intersectPoint.vector[2]
|
||||||
if intersectPoint is not None:
|
if intersectPoint is not None:
|
||||||
intersectPoint = Point.add(intersectPoint, Point(int(winWidth/2), int(winHeight/2), 0))
|
intersectPoint = Point.add(intersectPoint, Point(0, int(winWidth/2), int(winHeight/2))) #x is meaningless here
|
||||||
pygame.draw.circle(screenSurface, (255,255,150), (int(intersectPoint.vector[0]), int(intersectPoint.vector[1])), obj.displaySize)
|
if sat.location.vector[0] < 0:
|
||||||
elif type(obj).__name__ == "Planet":
|
drawSurface = backSurface
|
||||||
target = obj
|
else:
|
||||||
lineToCamera = Line(obj.location, self.location)
|
drawSurface = frontSurface
|
||||||
intersectPoint = lineToCamera.intersectWithPlane(screenPlane)
|
pygame.draw.circle(drawSurface, (255,255,150,255), (int(intersectPoint.vector[1]), int(intersectPoint.vector[2])), obj.displaySize)
|
||||||
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)
|
|
||||||
elif isinstance(obj, list):
|
elif isinstance(obj, list):
|
||||||
for orbitline in obj:
|
for orbitline in obj:
|
||||||
if orbitline.color != (0,0,0):
|
if orbitline.color != (0,0,0):
|
||||||
lineToCamera = Line(orbitline.location, self.location)
|
lineToCamera = Line(orbitline.location, self.location)
|
||||||
intersectPoint = lineToCamera.intersectWithPlane(screenPlane)
|
intersectPoint = lineToCamera.intersectWithPlane(self.screenPlane)
|
||||||
|
intersectPoint.vector[2] = -intersectPoint.vector[2]
|
||||||
if intersectPoint is not None:
|
if intersectPoint is not None:
|
||||||
intersectPoint = Point.add(intersectPoint, Point(int(winWidth/2), int(winHeight/2), 0))
|
intersectPoint = Point.add(intersectPoint, Point(0, int(winWidth/2), int(winHeight/2)))
|
||||||
pygame.draw.circle(screenSurface, orbitline.color, (int(intersectPoint.vector[0]), int(intersectPoint.vector[1])), 1)
|
if orbitline.color[3] != 0:
|
||||||
|
if orbitline.location.vector[0] < 0:
|
||||||
|
drawSurface = backSurface
|
||||||
|
else:
|
||||||
|
drawSurface = frontSurface
|
||||||
|
pygame.draw.circle(drawSurface, orbitline.color, (int(intersectPoint.vector[1]), int(intersectPoint.vector[2])), 1)
|
||||||
|
|
||||||
|
#DEBUG DOTS
|
||||||
|
#lineToCam = Line(Point.add(self.target.location, Point(0,self.target.radius,0)), self.location)
|
||||||
|
#intersectPoint = lineToCam.intersectWithPlane(self.screenPlane)
|
||||||
|
#intersectPoint = Point.add(intersectPoint, Point(0, int(winWidth/2), int(winHeight/2)))
|
||||||
|
#pygame.draw.circle(frontSurface, (255,150,150,255), (int(intersectPoint.vector[1]), int(intersectPoint.vector[2])), 5)
|
||||||
|
|
||||||
screenSurface = pygame.transform.flip(screenSurface, False, True)
|
#newLineToCam = Line(Point.add(self.screenPlane.point, Point(0,750,0)), self.location)
|
||||||
|
#intersectPoint = newLineToCam.intersectWithPlane(self.screenPlane)
|
||||||
|
#intersectPoint = Point.add(intersectPoint, Point(0, int(winWidth/2), int(winHeight/2)))
|
||||||
|
#pygame.draw.circle(screenSurface, (150,255,150), (int(intersectPoint.vector[1]), int(intersectPoint.vector[2])), 5)
|
||||||
|
|
||||||
#generate text
|
#generate text
|
||||||
rho, theta, phi = sat.location.polar()
|
rho, theta, phi = sat.location.polar()
|
||||||
theta = math.degrees(theta)
|
if rho < self.target.radius:
|
||||||
phi = math.degrees(phi)
|
0 == 0
|
||||||
|
|
||||||
#textSurface, rect = font.render(f"Speed: {round(sat.velocity.magnitude())} m/s \nAltitude: {round(rho - target.radius)} m", False, (255,255,255))
|
rawLat, rawLong = self.target.sphericalToLatLong(theta, phi)
|
||||||
font.render_to(screenSurface, (0,0), f"Speed: {round(sat.velocity.magnitude())} m/s \nAltitude: {round(rho - target.radius)} m", (255,255,255))
|
self.updateTrackList(rawLat, rawLong)
|
||||||
self.surface.blit(screenSurface, (0,0))
|
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,50), latString, (255,255,255))
|
||||||
|
font.render_to(backSurface, (0,70), longString, (255,255,255))
|
||||||
|
|
||||||
|
self.spriteGroup.update()
|
||||||
|
self.spriteGroup.draw(backSurface)
|
||||||
|
|
||||||
|
self.surface.blit(backgroundSurface, (0,0))
|
||||||
|
self.surface.blit(backSurface, (0,0))
|
||||||
|
self.surface.blit(frontSurface, (0,0))
|
||||||
|
|
||||||
|
if save:
|
||||||
|
pygame.image.save(self.surface, "test.png")
|
||||||
|
|
||||||
|
|
||||||
def renderImage(self, sat:"OrbitingBody", planet:"Planet", points):
|
|
||||||
"""generates a single image and saves it to disk"""
|
|
||||||
frozenSat = sat.location
|
|
||||||
rotValue = math.modf(planet.rotationPercentage * 12)[0] * 3.14159 / 6 #get percentage of 1/12 of a revolution
|
|
||||||
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)
|
|
||||||
screenPlaneOrigin = Point.subtract(screenPlane.point, Point(int(winWidth/2), int(winHeight/2), 0))
|
|
||||||
screenSurface = pygame.Surface((winWidth, winHeight))
|
|
||||||
#pygame uses 0,0 as the top left corner
|
|
||||||
|
|
||||||
satDistance = -1
|
def updateTrackList(self, lat, long):
|
||||||
|
"""Updates the ground track map list of points."""
|
||||||
|
if self.trackSampleCount != self.trackSampleRate:
|
||||||
|
self.trackSampleCount += 1
|
||||||
|
return
|
||||||
|
if len(self.pastTrackPoints) > 20000:
|
||||||
|
self.pastTrackPoints.pop(0)
|
||||||
|
#latitude is from -90 to 90; longitude is from -180 to 180.
|
||||||
|
latPercent = (lat + 90)/180
|
||||||
|
longPercent = (long + 180)/360
|
||||||
|
lat = self.mapHeight * latPercent
|
||||||
|
long = self.mapWidth * longPercent
|
||||||
|
self.pastTrackPoints.append((long, lat))
|
||||||
|
self.trackSampleCount = 0
|
||||||
|
|
||||||
curveCoeff = 1.1
|
def saveGroundTrack(self):
|
||||||
|
mapSurface = pygame.Surface.copy(self.mapSurface)
|
||||||
for column in range(0, winWidth):
|
sets = []
|
||||||
for row in range(0, winHeight):
|
currStart = 0
|
||||||
#get line in world going through this pixel
|
for i in range(1,len(self.pastTrackPoints)):
|
||||||
worldLine = Line(self.location, Point.add(screenPlaneOrigin, Point(column, row, 0)))
|
if abs(self.pastTrackPoints[i][0] - self.pastTrackPoints[i-1][0]) > 400:
|
||||||
#compare distance from center of planet to radius of planet to determine intersection
|
sets.append(self.pastTrackPoints[currStart:i])
|
||||||
|
currStart = i
|
||||||
dist = frozenSat.distanceFromLine(worldLine)
|
sets.append(self.pastTrackPoints[currStart:])
|
||||||
if satDistance < 0 or dist < satDistance:
|
colors = [(122,255,243), (211,122,255), (222,0,177)]
|
||||||
satDistance = dist
|
for i in range(0,len(sets)):
|
||||||
satPixel = (column, row)
|
try:
|
||||||
|
pygame.draw.lines(mapSurface, colors[i%3], False, sets[i], width=5)
|
||||||
if self.target.location.distanceFromLine(worldLine) < self.target.radius:
|
#pygame.draw.aalines(mapSurface, colors[i%3], False, [(long, lat+1) for long, lat in sets[i]])
|
||||||
epsilon = 0.1
|
#pygame.draw.aalines(mapSurface, colors[i%3], False, [(long+1, lat) for long, lat in sets[i]])
|
||||||
yPrime = min([abs((row + screenPlaneOrigin.vector[1]) * (self.location.vector[2] / winDistance)), self.target.radius])
|
#pygame.draw.aalines(mapSurface, colors[i%3], False, [(long, lat-1) for long, lat in sets[i]])
|
||||||
yPrimeCurve = yPrime / (self.target.radius * curveCoeff)
|
#pygame.draw.aalines(mapSurface, colors[i%3], False, [(long-1, lat) for long, lat in sets[i]])
|
||||||
xPrime = min([abs((column + screenPlaneOrigin.vector[0]) * (self.location.vector[2] / winDistance)), self.target.radius])
|
except:
|
||||||
xPrimeCurve = xPrime / (self.target.radius * curveCoeff)
|
pass
|
||||||
#treat yPrime like it's further from zero than it really is based on xPrime, and vice versa
|
pygame.image.save(mapSurface, "testMap.png")
|
||||||
yPrime /= math.sin(math.acos(xPrimeCurve))
|
|
||||||
xPrime /= math.sin(math.acos(yPrimeCurve))
|
|
||||||
|
|
||||||
try:
|
|
||||||
lat = math.modf((math.acos(yPrime / self.target.radius) / (3.141592/12.0)))[0] #pi/12 = 15 degrees
|
|
||||||
except:
|
|
||||||
screenSurface.set_at((column, row), (20,20,20))
|
|
||||||
continue
|
|
||||||
|
|
||||||
try:
|
|
||||||
long = math.modf(((math.acos((xPrime) / self.target.radius)) / (3.141592/6.0)))[0] #pi/6 = 30 degrees
|
|
||||||
except:
|
|
||||||
screenSurface.set_at((column, row), (20,20,20))
|
|
||||||
continue
|
|
||||||
|
|
||||||
if -epsilon < lat < epsilon or -epsilon < long < epsilon:
|
|
||||||
screenSurface.set_at((column, row), (180,180,180))
|
|
||||||
elif -epsilon < lat < epsilon and -epsilon < long < epsilon:
|
|
||||||
screenSurface.set_at((column, row), (255,255,255))
|
|
||||||
else:
|
|
||||||
screenSurface.set_at((column, row), (50,50,50))
|
|
||||||
|
|
||||||
#check if satellite is behind or in front of planet (or unobscured)
|
|
||||||
if screenSurface.get_at(satPixel) == (0,0,0):
|
|
||||||
circleBorder = 0
|
|
||||||
else:
|
|
||||||
if self.location.distanceFromPoint(frozenSat) > self.location.distanceFromPoint(self.target.location):
|
|
||||||
circleBorder = 2
|
|
||||||
else:
|
|
||||||
circleBorder = 0
|
|
||||||
pygame.draw.circle(screenSurface, (230, 227, 64), satPixel, 4, width = circleBorder)
|
|
||||||
screenSurface = pygame.transform.flip(screenSurface, False, True)
|
|
||||||
pygame.image.save(screenSurface, "test.png")
|
|
||||||
|
|
||||||
|
|
||||||
#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))
|
|
BIN
testMap.png
Normal file
After Width: | Height: | Size: 2.2 MiB |