Невозможно повернуть 3D камеру вертикально, вместо этого переводит изображение - PullRequest
0 голосов
/ 03 октября 2019

Недавно я написал простую программу для проецирования точек в трехмерном пространстве на экран и создал класс полигонов, чтобы продемонстрировать это. И, кроме пары визуальных глюков, я вполне доволен этим.

Он работает, вычисляя разницу в углах каждой точки относительно камеры и угла, на который смотрит камера. Это делается в горизонтальной и вертикальной плоскостях отдельно. При расчете расстояния по горизонтали от камеры до точки (которая используется для вычисления вертикального угла) я умножаю на cos(abs(self.angle[0] - self.camera.angle[0])) расчетное проекционное расстояние, а не евклидово расстояние.

Однако естьодна вопиющая проблема, которая заключается в том, что при повороте камеры вверх или вниз она вместо этого перемещает весь экран вверх или вниз, не изменяя перспективу. Это означает, что все выглядит так, как должно, только когда камера идеально выровнена. В противном случае, все немного искажается (это лучше всего увидеть, поднявшись и опустив взгляд).

Вот изображение многоугольника сбоку (выглядит нормально), а затем видно прямо сверху, смотря вниз (очень искажено). Это должно быть так, как если бы оно просматривалось прямо, но это не то, что происходит.

Нормальный: Normal Искаженный: Distorted

Я уже несколько часов пытаюсь выяснить, что я могу сделать, чтобы решить эту проблему, но на самом деле не уверен, и я хотел бы помочь. Я включил свой код ниже, там не так много работы, поэтому я надеюсь, что вы сможете понять его.

Вы можете перемещаться с помощью WASD и вращать камеру с помощью клавиш со стрелками. Пробел и LSHIFT перемещаются вверх и вниз соответственно.

import sys
import pygame
import math
from pygame.locals import *

pygame.init()

WIDTH = 800
HEIGHT = 600
RATIO = WIDTH / HEIGHT
SCREEN = pygame.display.set_mode((WIDTH,HEIGHT))
CLOCK = pygame.time.Clock()
FPS = 60

pygame.display.set_caption('3D Test')

#  #------Colours------#  #
BLACK = (0, 0, 0)
BLUE = (0, 0, 255)
CYAN = (0, 255, 255)
DARKGREEN = (0, 64, 0)
DARKGREY = (64, 64, 64)
GREY = (128, 128, 128)
GREEN = (0, 128, 0)
LIME = (0, 255, 0)
MAGENTA = (255, 0, 255)
MAROON = (128, 0, 0)
NAVYBLUE = (0, 0, 128)
OLIVE = (128, 128, 0)
PURPLE = (128, 0, 128)
RED = (255, 0, 0)
SILVER = (192, 192, 192)
TEAL = (0, 128, 128)
WHITE = (255, 255, 255)
YELLOW = (255, 255, 0)
#  #-------------------#  #


class Camera:
    def __init__(self):
        self.position = [0, 5, -20]
        self.angle = [90, -15]

        self.fov = 45

        self.move_speed = 0.2
        self.look_speed = 2

    def look_around(self):
        keys = pygame.key.get_pressed()

        if keys[K_RIGHT]:
            self.angle[0] -= self.look_speed

        if keys[K_LEFT]:
            self.angle[0] += self.look_speed

        if keys[K_UP]:
            self.angle[1] += self.look_speed

        if keys[K_DOWN]:
            self.angle[1] -= self.look_speed

        if self.angle[0] < -180:
            self.angle[0] += 360

        elif self.angle[0] > 180:
            self.angle[0] -= 360

        self.angle[1] = min(max(self.angle[1], -90), 90)

    def move(self):
        keys = pygame.key.get_pressed()

        if keys[K_w]:  # Move Forwards
            self.position[0] += math.cos(math.radians(self.angle[0])) * self.move_speed
            self.position[2] += math.sin(math.radians(self.angle[0])) * self.move_speed

        if keys[K_s]:  # Move Backwards
            self.position[0] -= math.cos(math.radians(self.angle[0])) * self.move_speed
            self.position[2] -= math.sin(math.radians(self.angle[0])) * self.move_speed

        if keys[K_a]:  # Move Left
            self.position[2] += math.cos(math.radians(self.angle[0])) * self.move_speed
            self.position[0] -= math.sin(math.radians(self.angle[0])) * self.move_speed

        if keys[K_d]:  # Move Right
            self.position[2] -= math.cos(math.radians(self.angle[0])) * self.move_speed
            self.position[0] += math.sin(math.radians(self.angle[0])) * self.move_speed

        if keys[K_SPACE]:  # Move Up
            self.position[1] += self.move_speed

        if keys[K_LSHIFT]:  # Move Down
            self.position[1] -= self.move_speed

    def update(self):
        self.move()
        self.look_around()


class Point:
    def __init__(self, camera, position):
        self.camera = camera
        self.position = position

        self.angle = [0, 0]
        self.update_angle()

    def update_angle(self):
        # Get the horizontal angle between the camera and the point
        self.angle[0] = math.degrees(math.atan2(self.position[2] - self.camera.position[2],
                                                self.position[0] - self.camera.position[0]))

        horizontal_distance = math.hypot(self.position[0] - self.camera.position[0],
                                         self.position[2] - self.camera.position[2])
        vertical_distance = self.position[1] - self.camera.position[1]

        # Get the vertical angle between the camera and the point
        self.angle[1] = math.degrees(math.atan2(vertical_distance,
                                                math.cos(math.radians(abs(self.angle[0] - self.camera.angle[0]))) * horizontal_distance))

    def update(self):
        self.update_angle()

    def get_screen_pos(self):
        # Calculate the difference in the angle of the camera and the point
        view_angle = [self.camera.angle[0] - self.angle[0],
                      self.camera.angle[1] - self.angle[1]]

        # Calculate the relative position of the point to the camera (on the screen)
        relative_position = [view_angle[0] / self.camera.fov,
                             view_angle[1] / (self.camera.fov / RATIO)]

        # Scale the relative position to the size of the screen
        position = [int((relative_position[0] + 0.5) * WIDTH), int((relative_position[1] + 0.5) * HEIGHT)]

        return position


class Polygon:
    def __init__(self, camera, point_list, colour, line_colour, line_width=1):
        """
        Creates a polygon out of a list of points
        :param camera: Reference to the camera
        :param point_list:  List of points
        :param colour: Fill colour of the polygon
        :param line_colour: Line colour of the polygon
        :param line_width: With of the edges of the polygon
        """
        self.points = [Point(camera, point) for point in point_list]

        self.colour = colour
        self.line_colour = line_colour
        self.line_width = line_width

    def update(self):
        for point in self.points:
            point.update()

    def render(self):
        screen_points = [point.get_screen_pos() for point in self.points]

        pygame.draw.polygon(SCREEN, self.colour, screen_points)
        pygame.draw.polygon(SCREEN, self.line_colour, screen_points, self.line_width)


def main():
    camera = Camera()

    poly = Polygon(camera, [(0, 0, 5), (3, 0, 4), (4, 0, 3), (5, 0, 0), (4, 0, -3), (3, 0, -4), (0, 0, -5), (-3, 0, -4),
                            (-4, 0, -3), (-5, 0, 0), (-4, 0, 3), (-3, 0, 4)], RED, WHITE, 3)

    while True:
        SCREEN.fill(BLACK)

        events = pygame.event.get()
        for event in events:
            if event.type == QUIT:
                pygame.quit()
                sys.exit()

            elif event.type == KEYDOWN:
                if event.key == K_ESCAPE:
                    pygame.quit()
                    sys.exit()

        camera.update()

        poly.update()
        poly.render()

        pygame.display.update()
        CLOCK.tick(FPS)


if __name__ == '__main__':
    main()
...