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))