Module pyxyz.camera

Camera class definition

Expand source code
"""Camera class definition"""

import math
import numpy as np
from quaternion import as_rotation_matrix
from pyxyz.vector3 import Vector3
from pyxyz.object3d import Object3d

class Camera(Object3d):
    """Camera class.
    It allows us to have a viewport into the scene. Each scene has a camera set."""

    def __init__(self, ortho, res_x, res_y):
        """
        Arguments:
            ortho {bool} - True if this is an ortographic camera, false otherwise.

            res_x {int} - Horizontal resolution of the window. This will be the size of the window
            in ortographic view

            res_y {int} - Vertical resolution of the window. This will be the size of the window in
            ortographic view
        """
        super().__init__(self)

        self.ortho = ortho
        """{bool} True if this is an ortographic camera, false otherwise"""
        self.res_x = res_x
        """{int} Horizontal resolution of the window. This will be the size of the window in
        ortographic view"""
        self.res_y = res_y
        """{int} Vertical resolution of the window. This will be the size of the window in
        ortographic view"""
        self.near_plane = 1
        """{number} Distance from the camera to the near plane. Defaults to 1"""
        self.far_plane = 100
        """{number} Distance from the camera to the far plane. Defaults to 100"""
        self.fov = math.radians(60)
        """{number} Field of view angle (in radians) of the camera (if in perspective mode)"""
        self.proj_matrix = np.identity(4)
        """{np.array} Projection matrix. This is only set after get_projection_matrix is called
        once"""

    def get_projection_matrix(self):
        """Retrieves the projection matrix of this camera.
        This function sets up the proj_matrix attribute as well.

        Returns:
            np.array - Projection matrix of this camera
        """
        self.proj_matrix = np.zeros((4, 4))
        if self.ortho:
            self.proj_matrix[0, 0] = self.res_x * 0.5
            self.proj_matrix[1, 1] = self.res_y * 0.5
            self.proj_matrix[3, 0] = 0
            self.proj_matrix[3, 1] = 0
            self.proj_matrix[3, 3] = 1
        else:
            t = math.tan(self.fov * 0.5)
            a = self.res_y / self.res_x
            self.proj_matrix[0, 0] = 0.5 * self.res_x / t
            self.proj_matrix[1, 1] = 0.5 * self.res_y / (a * t)
            self.proj_matrix[2, 3] = 1
            self.proj_matrix[2, 2] = self.far_plane / (self.far_plane - self.near_plane)
            self.proj_matrix[3, 2] = self.proj_matrix[2, 2] * self.near_plane

        return self.proj_matrix

    def get_camera_matrix(self):
        """Retrieves the view matrix of this camera. This is basically the same as a PRS matrix
        without scalling, and with the position and rotation negated.

        Returns:
            np.array - View matrix of this camera
        """
        trans = np.identity(4)
        trans[3, 0] = -self.position.x
        trans[3, 1] = -self.position.y
        trans[3, 2] = -self.position.z

        qrot = as_rotation_matrix(self.rotation.inverse())
        rotation_matrix = np.identity(4)
        rotation_matrix[0][0] = qrot[0][0]
        rotation_matrix[0][1] = qrot[0][1]
        rotation_matrix[0][2] = qrot[0][2]
        rotation_matrix[1][0] = qrot[1][0]
        rotation_matrix[1][1] = qrot[1][1]
        rotation_matrix[1][2] = qrot[1][2]
        rotation_matrix[2][0] = qrot[2][0]
        rotation_matrix[2][1] = qrot[2][1]
        rotation_matrix[2][2] = qrot[2][2]
        rotation_matrix[3][3] = 1

        return trans @ rotation_matrix

    def ray_from_ndc(self, pos):
        """Retrieves a ray (origin, direction) corresponding to the given position on screen.
        This function takes the coordinates as NDC (normalized device coordinates), in which the
        lower-left corner of the screen corresponds to (-1,-1) and the upper-right corresponds to
        (1,1).
        For example, to convert mouse coordinates to NDC, you could do something like:

        >>> mouse_pos = pygame.mouse.get_pos()
        >>> mouse_pos = ((mouse_pos[0] / res_x) * 2 - 1, (mouse_pos[1] / res_y) * 2 - 1)
        >>> origin, dir = camera.RayFromNDC(mouse_pos)

        Arguments:
            pos {2-tuple} -- Screen position in NDC (normalized device coordinates)

        Returns:
            Vector3, Vector3 - Origin and direction of the ray corresponding to that screen
            positions
        """

        vpos = Vector3(pos[0], pos[1], self.near_plane)
        vpos.x = vpos.x * self.res_x * 0.5
        vpos.y = -vpos.y * self.res_y * 0.5

        inv_view_proj_matrix = self.get_camera_matrix() @ self.get_projection_matrix()
        inv_view_proj_matrix = np.linalg.inv(inv_view_proj_matrix)

        direction = inv_view_proj_matrix @ vpos.to_np4(1)
        direction = Vector3.from_np(direction).normalized()

        return self.position + direction * self.near_plane, direction

Classes

class Camera (ortho, res_x, res_y)

Camera class. It allows us to have a viewport into the scene. Each scene has a camera set.

Arguments

ortho {bool} - True if this is an ortographic camera, false otherwise.

res_x {int} - Horizontal resolution of the window. This will be the size of the window in ortographic view

res_y {int} - Vertical resolution of the window. This will be the size of the window in ortographic view

Expand source code
class Camera(Object3d):
    """Camera class.
    It allows us to have a viewport into the scene. Each scene has a camera set."""

    def __init__(self, ortho, res_x, res_y):
        """
        Arguments:
            ortho {bool} - True if this is an ortographic camera, false otherwise.

            res_x {int} - Horizontal resolution of the window. This will be the size of the window
            in ortographic view

            res_y {int} - Vertical resolution of the window. This will be the size of the window in
            ortographic view
        """
        super().__init__(self)

        self.ortho = ortho
        """{bool} True if this is an ortographic camera, false otherwise"""
        self.res_x = res_x
        """{int} Horizontal resolution of the window. This will be the size of the window in
        ortographic view"""
        self.res_y = res_y
        """{int} Vertical resolution of the window. This will be the size of the window in
        ortographic view"""
        self.near_plane = 1
        """{number} Distance from the camera to the near plane. Defaults to 1"""
        self.far_plane = 100
        """{number} Distance from the camera to the far plane. Defaults to 100"""
        self.fov = math.radians(60)
        """{number} Field of view angle (in radians) of the camera (if in perspective mode)"""
        self.proj_matrix = np.identity(4)
        """{np.array} Projection matrix. This is only set after get_projection_matrix is called
        once"""

    def get_projection_matrix(self):
        """Retrieves the projection matrix of this camera.
        This function sets up the proj_matrix attribute as well.

        Returns:
            np.array - Projection matrix of this camera
        """
        self.proj_matrix = np.zeros((4, 4))
        if self.ortho:
            self.proj_matrix[0, 0] = self.res_x * 0.5
            self.proj_matrix[1, 1] = self.res_y * 0.5
            self.proj_matrix[3, 0] = 0
            self.proj_matrix[3, 1] = 0
            self.proj_matrix[3, 3] = 1
        else:
            t = math.tan(self.fov * 0.5)
            a = self.res_y / self.res_x
            self.proj_matrix[0, 0] = 0.5 * self.res_x / t
            self.proj_matrix[1, 1] = 0.5 * self.res_y / (a * t)
            self.proj_matrix[2, 3] = 1
            self.proj_matrix[2, 2] = self.far_plane / (self.far_plane - self.near_plane)
            self.proj_matrix[3, 2] = self.proj_matrix[2, 2] * self.near_plane

        return self.proj_matrix

    def get_camera_matrix(self):
        """Retrieves the view matrix of this camera. This is basically the same as a PRS matrix
        without scalling, and with the position and rotation negated.

        Returns:
            np.array - View matrix of this camera
        """
        trans = np.identity(4)
        trans[3, 0] = -self.position.x
        trans[3, 1] = -self.position.y
        trans[3, 2] = -self.position.z

        qrot = as_rotation_matrix(self.rotation.inverse())
        rotation_matrix = np.identity(4)
        rotation_matrix[0][0] = qrot[0][0]
        rotation_matrix[0][1] = qrot[0][1]
        rotation_matrix[0][2] = qrot[0][2]
        rotation_matrix[1][0] = qrot[1][0]
        rotation_matrix[1][1] = qrot[1][1]
        rotation_matrix[1][2] = qrot[1][2]
        rotation_matrix[2][0] = qrot[2][0]
        rotation_matrix[2][1] = qrot[2][1]
        rotation_matrix[2][2] = qrot[2][2]
        rotation_matrix[3][3] = 1

        return trans @ rotation_matrix

    def ray_from_ndc(self, pos):
        """Retrieves a ray (origin, direction) corresponding to the given position on screen.
        This function takes the coordinates as NDC (normalized device coordinates), in which the
        lower-left corner of the screen corresponds to (-1,-1) and the upper-right corresponds to
        (1,1).
        For example, to convert mouse coordinates to NDC, you could do something like:

        >>> mouse_pos = pygame.mouse.get_pos()
        >>> mouse_pos = ((mouse_pos[0] / res_x) * 2 - 1, (mouse_pos[1] / res_y) * 2 - 1)
        >>> origin, dir = camera.RayFromNDC(mouse_pos)

        Arguments:
            pos {2-tuple} -- Screen position in NDC (normalized device coordinates)

        Returns:
            Vector3, Vector3 - Origin and direction of the ray corresponding to that screen
            positions
        """

        vpos = Vector3(pos[0], pos[1], self.near_plane)
        vpos.x = vpos.x * self.res_x * 0.5
        vpos.y = -vpos.y * self.res_y * 0.5

        inv_view_proj_matrix = self.get_camera_matrix() @ self.get_projection_matrix()
        inv_view_proj_matrix = np.linalg.inv(inv_view_proj_matrix)

        direction = inv_view_proj_matrix @ vpos.to_np4(1)
        direction = Vector3.from_np(direction).normalized()

        return self.position + direction * self.near_plane, direction

Ancestors

Instance variables

var far_plane

{number} Distance from the camera to the far plane. Defaults to 100

var fov

{number} Field of view angle (in radians) of the camera (if in perspective mode)

var near_plane

{number} Distance from the camera to the near plane. Defaults to 1

var ortho

{bool} True if this is an ortographic camera, false otherwise

var proj_matrix

{np.array} Projection matrix. This is only set after get_projection_matrix is called once

var res_x

{int} Horizontal resolution of the window. This will be the size of the window in ortographic view

var res_y

{int} Vertical resolution of the window. This will be the size of the window in ortographic view

Methods

def get_camera_matrix(self)

Retrieves the view matrix of this camera. This is basically the same as a PRS matrix without scalling, and with the position and rotation negated.

Returns

np.array - View matrix of this camera

Expand source code
def get_camera_matrix(self):
    """Retrieves the view matrix of this camera. This is basically the same as a PRS matrix
    without scalling, and with the position and rotation negated.

    Returns:
        np.array - View matrix of this camera
    """
    trans = np.identity(4)
    trans[3, 0] = -self.position.x
    trans[3, 1] = -self.position.y
    trans[3, 2] = -self.position.z

    qrot = as_rotation_matrix(self.rotation.inverse())
    rotation_matrix = np.identity(4)
    rotation_matrix[0][0] = qrot[0][0]
    rotation_matrix[0][1] = qrot[0][1]
    rotation_matrix[0][2] = qrot[0][2]
    rotation_matrix[1][0] = qrot[1][0]
    rotation_matrix[1][1] = qrot[1][1]
    rotation_matrix[1][2] = qrot[1][2]
    rotation_matrix[2][0] = qrot[2][0]
    rotation_matrix[2][1] = qrot[2][1]
    rotation_matrix[2][2] = qrot[2][2]
    rotation_matrix[3][3] = 1

    return trans @ rotation_matrix
def get_projection_matrix(self)

Retrieves the projection matrix of this camera. This function sets up the proj_matrix attribute as well.

Returns

np.array - Projection matrix of this camera

Expand source code
def get_projection_matrix(self):
    """Retrieves the projection matrix of this camera.
    This function sets up the proj_matrix attribute as well.

    Returns:
        np.array - Projection matrix of this camera
    """
    self.proj_matrix = np.zeros((4, 4))
    if self.ortho:
        self.proj_matrix[0, 0] = self.res_x * 0.5
        self.proj_matrix[1, 1] = self.res_y * 0.5
        self.proj_matrix[3, 0] = 0
        self.proj_matrix[3, 1] = 0
        self.proj_matrix[3, 3] = 1
    else:
        t = math.tan(self.fov * 0.5)
        a = self.res_y / self.res_x
        self.proj_matrix[0, 0] = 0.5 * self.res_x / t
        self.proj_matrix[1, 1] = 0.5 * self.res_y / (a * t)
        self.proj_matrix[2, 3] = 1
        self.proj_matrix[2, 2] = self.far_plane / (self.far_plane - self.near_plane)
        self.proj_matrix[3, 2] = self.proj_matrix[2, 2] * self.near_plane

    return self.proj_matrix
def ray_from_ndc(self, pos)

Retrieves a ray (origin, direction) corresponding to the given position on screen. This function takes the coordinates as NDC (normalized device coordinates), in which the lower-left corner of the screen corresponds to (-1,-1) and the upper-right corresponds to (1,1). For example, to convert mouse coordinates to NDC, you could do something like:

>>> mouse_pos = pygame.mouse.get_pos()
>>> mouse_pos = ((mouse_pos[0] / res_x) * 2 - 1, (mouse_pos[1] / res_y) * 2 - 1)
>>> origin, dir = camera.RayFromNDC(mouse_pos)

Arguments

pos {2-tuple} – Screen position in NDC (normalized device coordinates)

Returns

Vector3, Vector3 - Origin and direction of the ray corresponding to that screen positions

Expand source code
def ray_from_ndc(self, pos):
    """Retrieves a ray (origin, direction) corresponding to the given position on screen.
    This function takes the coordinates as NDC (normalized device coordinates), in which the
    lower-left corner of the screen corresponds to (-1,-1) and the upper-right corresponds to
    (1,1).
    For example, to convert mouse coordinates to NDC, you could do something like:

    >>> mouse_pos = pygame.mouse.get_pos()
    >>> mouse_pos = ((mouse_pos[0] / res_x) * 2 - 1, (mouse_pos[1] / res_y) * 2 - 1)
    >>> origin, dir = camera.RayFromNDC(mouse_pos)

    Arguments:
        pos {2-tuple} -- Screen position in NDC (normalized device coordinates)

    Returns:
        Vector3, Vector3 - Origin and direction of the ray corresponding to that screen
        positions
    """

    vpos = Vector3(pos[0], pos[1], self.near_plane)
    vpos.x = vpos.x * self.res_x * 0.5
    vpos.y = -vpos.y * self.res_y * 0.5

    inv_view_proj_matrix = self.get_camera_matrix() @ self.get_projection_matrix()
    inv_view_proj_matrix = np.linalg.inv(inv_view_proj_matrix)

    direction = inv_view_proj_matrix @ vpos.to_np4(1)
    direction = Vector3.from_np(direction).normalized()

    return self.position + direction * self.near_plane, direction

Inherited members