N3RDIUM
N3RDIUM

Reputation: 366

How to make camera movement in a pygame 3d engine from scratch?

I am new to matrices and matrix transformations and other things. I am making a 3d engine of my own in pygame using 2d, but I ran into a problem. I can't get my engine to implement camera movement and it only does camera rotation. Here is my code:

window.py: The custom window class

from .general_imports import *

class Py3dWindow:
    def __init__(self, title=None, width=800, height=600,):
        self.title = title
        self.window = None
        self.width = width
        self.height = height
        self.meshes = []
        self.events_assinged = False
    
        self.projection_matrix = np.matrix([
            [1, 0, 0, 0],
            [0, 1, 0, 0],
            [0, 0, 1, 0],
            [0, 0, 0, 1]
        ])
        print(self.projection_matrix)

        self.camera = [
            [0, 0, 0, 0],
            [0, 0, 0],
            75, # fov
            1, # near
            1000 # far
        ]

    def add_mesh(self, mesh):
        self.meshes.append(mesh)

    def translate_matrix(self):
        angle_x = math.radians(self.camera[1][0])
        angle_y = math.radians(self.camera[1][1])
        angle_z = math.radians(self.camera[1][2])

        pos_x = self.camera[0][0]
        pos_y = self.camera[0][1]
        pos_z = self.camera[0][2]

        rotation_z = np.matrix([
            [int(np.cos(angle_z)), -int(np.sin(angle_z)), 0, 0],
            [int(np.sin(angle_z)), int(np.cos(angle_z)), 0, 0],
            [0, 0, 1, 0],
            [0, 0, 0, 1]
        ],)

        rotation_y = np.matrix([
            [int(np.cos(angle_y)), 0, int(np.sin(angle_y))],
            [0, 1, 0, 0],
            [-int(np.sin(angle_y)), 0, int(np.cos(angle_y))],
            [0, 0, 0, 1]
        ])

        rotation_x = np.matrix([
            [1, 0, 0, 0],
            [0, int(np.cos(angle_x)), -int(np.sin(angle_x)), 0],
            [0, int(np.sin(angle_x)), int(np.cos(angle_x)), 0],
            [0, 0, 0, 1],
        ])

        position_matrix = np.matrix([
            [1, 0, 0, pos_x],
            [0, 1, 0, pos_y],
            [0, 0, 1, pos_z],
            [0, 0, 0, 1],
        ])
        
        self.projection_matrix = np.dot(rotation_z, np.dot(rotation_y, rotation_x)) * position_matrix

        print(self.projection_matrix)

    def init_window(self):
        pygame.init()
        self.window = pygame.display.set_mode((self.width, self.height), pygame.RESIZABLE)
        pygame.display.set_caption(self.title)
        self.running = False

    def run(self,):
        self.running = True
        while self.running:
            self.draw()
            self.events(pygame.event.get())
            self.update()
            self.translate_matrix()
        pygame.quit()

    def events(self, events):
        for event in events:
            if event.type == pygame.QUIT:
                self.running = False
        if self.events_assinged:
            self._events(events)

    def update(self,):
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                self.running = False

    def draw(self,):
        self.window.fill((0, 0, 0))
        for mesh in self.meshes:
            mesh.draw(self.window)
        pygame.display.update()
        time.sleep(0.001)

    def on_events(self, func):
        self._events = func
        self.events_assinged = True
    
    def on_update(self, func):
        self.update = lambda: func()

    def on_draw(self, func):
        self.draw = lambda: func()

mesh.py: Mesh class

from .general_imports import *

class Mesh:
    def __init__(self, parnet):
        self.points = []
        self.parent = parnet

    def add_point(self, position):
        position = [position[0] * 100, position[1] * 100, position[2] * 100, 1]
        self.points.append(np.matrix(position))

    def draw(self, window):
        for point in self.points:
            p = (point - self.parent.camera[0])
            p = np.reshape(p, (4, 1))
            projection = np.dot(self.parent.projection_matrix, p)
            projection = projection.tolist()
            x = projection[0][0]
            y = projection[1][0]
            pygame.draw.circle(window, (255, 255, 255), (int(x) + window.get_width() / 2 - 100, int(y) + window.get_width() / 2- 100) , 1)

demo.py: The demo

# Py3D demo

from Py3D.__init__ import *
import random

win = Py3dWindow(title="Py3D")
win.init_window()

mesh_ = Mesh(win)

side = 1

for i in range(-side, side):
    for j in range(-side, side):
        for k in range(-side, side):
            mesh_.add_point((i, j, k))

win.add_mesh(mesh_)

def move(events):
    for i in events:
        if i.type == 771:
            if i.text == 'w':
                win.camera[0][2] -= 1
            if i.text == 's':
                win.camera[0][2] += 1
            if i.text == 'a':
                win.camera[0][0] -= 1
            if i.text == 'd':
                win.camera[0][0] += 1

def rotate():
    win.camera[1][0] = 45
    win.camera[1][1] = 45
    win.camera[1][2] = 45

win.on_events(move)
win.on_update(rotate)
win.run()

Errors

ValueError: shapes (4,4) and (1,4) not aligned: 4 (dim 1) != 1 (dim 0)

Upvotes: 0

Views: 172

Answers (2)

user17242583
user17242583

Reputation:

As you pointed out, in the translate_matrix function, your rotation_y variable is declared incorrectly:

rotation_y = np.matrix([
    [int(np.cos(angle_y)), 0, int(np.sin(angle_y))], # <--- missing an item
    [0, 1, 0, 0],
    [-int(np.sin(angle_y)), 0, int(np.cos(angle_y))], # <--- missing an item
    [0, 0, 0, 1]
])

The first and third items are declared with only 3 items, while the 2nd and third items, and all of the items of rotation_x and rotation_z, are made with 4 items. Obviously that can't work, as a matrix is supposed to be rectangular.

Upvotes: 1

N3RDIUM
N3RDIUM

Reputation: 366

The solution I found

In the translate_matrix function, I forgot to add four indexes to the whole array in rotation_y. That's it! It worked fine!

Upvotes: 0

Related Questions