Going 3D

Camera

Previously, I described a simple view matrix that is fixed in one position. But I would like to make this a bit more interesting by introducing the Camera struct.

The Camera is an entity that moves around the screen and can rotate in 3 axes to view the scene. For example, how far objects look like. I have taken this camera out of LearnOpenGL

struct Camera {
    // camera Attributes
    Vec3 position;
    Vec3 front;
    Vec3 up;
    Vec3 right;
    Vec3 worldUp;
    // euler Angles
    float yaw;
    float pitch;
    // camera options
    float movementSpeed;
    float mouseSensitivity;
    float zoom;
};

I should be able to control this camera with the keyboard and mouse. Finally, I want to be able to get the view matrix of the camera at any point. These are the functions of this camera:

Camera Camera_Create(Vec3 position, Vec3 up, float yaw, float pitch);
Mat4 Camera_GetViewMatrix(Camera *camera);
// processes input received from any keyboard-like input system. Accepts input
// parameter in the form of camera defined ENUM (to abstract it from windowing
// systems)
void Camera_ProcessKeyboard(Camera *camera, CameraMovement direction,
                            float deltaTime);
// processes input received from a mouse input system. Expects the offset
// value in both the x and y direction.
void Camera_ProcessMouseMovement(Camera *camera, float offsetX, float offsetY,
                                 bool constrainPitch);
void Camera_ProcessMouseScroll(Camera *camera, float offsetY);
void Camera_UpdateVectors(Camera *camera);

I have also added some constants for some of the camera values:

// Defines several possible options for camera movement. Used as abstraction to
// stay away from window-system specific input methods
enum CameraMovement { FORWARD, BACKWARD, LEFT, RIGHT, UP, DOWN };

// Default camera values
const float YAW = -90.0f;
const float PITCH = 0.0f;
const float SPEED = 2.5f;
const float SENSITIVITY = 0.1f;
const float ZOOM = 45.0f;

I need to add the camera to the renderer entity to be able to access it inside the renderer functions. In the future I could create a different struct called Scene that holds information of all the entities that will be renderer, the lights, and the camera.

struct Renderer {
    ...

    Camera camera;
};

...

Mat4 view = Camera_GetViewMatrix(&r->camera);

float aspect = (float)r->width / r->height;
Mat4 projection = Mat4_Perspective(DegToRadians(r->camera.zoom), aspect, 0.1f, 100.0f);

Now I can use the Camera_GetViewMatrix function to get the view matrix and transform the object.

Here is the entire implementation of the camera:

#include "camera.h"
#include "math.h"
#include <cmath>

Camera Camera_Create(Vec3 position, Vec3 up, float yaw, float pitch) {
    Camera camera = {};
    camera.front = Vec3{0.0f, 0.0f, -1.0f};
    camera.movementSpeed = SPEED;
    camera.mouseSensitivity = SENSITIVITY;
    camera.zoom = ZOOM;
    camera.position = position;
    camera.worldUp = up;
    camera.yaw = yaw;
    camera.pitch = pitch;

    Camera_UpdateVectors(&camera);

    return camera;
}

Mat4 Camera_GetViewMatrix(Camera *camera) {
    return Mat4_LookAt(camera->position,
                       Vec3_Add(camera->position, camera->front), camera->up);
}

void Camera_ProcessKeyboard(Camera *camera, CameraMovement direction,
                            float deltaTime) {
    float velocity = camera->movementSpeed * deltaTime;

    if (direction == FORWARD) {
        camera->position = Vec3_Add(camera->position,
                                    Vec3_ScalarMult(camera->front, velocity));
    }
    if (direction == BACKWARD) {
        camera->position = Vec3_Subtract(
            camera->position, Vec3_ScalarMult(camera->front, velocity));
    }
    if (direction == LEFT) {
        camera->position = Vec3_Subtract(
            camera->position, Vec3_ScalarMult(camera->right, velocity));
    }
    if (direction == RIGHT) {
        camera->position = Vec3_Add(camera->position,
                                    Vec3_ScalarMult(camera->right, velocity));
    }
    if (direction == UP) {
        camera->position =
            Vec3_Add(camera->position, Vec3_ScalarMult(camera->up, velocity));
    }
    if (direction == DOWN) {
        camera->position = Vec3_Subtract(camera->position,
                                         Vec3_ScalarMult(camera->up, velocity));
    }
}

void Camera_ProcessMouseMovement(Camera *camera, float offsetX, float offsetY,
                                 bool constrainPitch) {
    offsetX *= camera->mouseSensitivity;
    offsetY *= camera->mouseSensitivity;

    camera->yaw += offsetX;
    camera->pitch += offsetY;

    // make sure that when pitch is out of bounds, screen doesn't get flipped
    if (constrainPitch) {
        if (camera->pitch > 89.0f) {
            camera->pitch = 89.0f;
        }
        if (camera->pitch < -89.0f) {
            camera->pitch = -89.0f;
        }
    }

    Camera_UpdateVectors(camera);
}

void Camera_ProcessMouseScroll(Camera *camera, float offsetY) {
    camera->zoom -= (float)offsetY;
    if (camera->zoom < 1.0f) {
        camera->zoom = 1.0f;
    }
    if (camera->zoom > 45.0f) {
        camera->zoom = 45.0f;
    }
}

void Camera_UpdateVectors(Camera *camera) {
    // calculate the new Front vector
    Vec3 front;
    front.x = cos(DegToRadians(camera->yaw)) * cos(DegToRadians(camera->pitch));
    front.y = sin(DegToRadians(camera->pitch));
    front.z = sin(DegToRadians(camera->yaw)) * cos(DegToRadians(camera->pitch));
    camera->front = Vec3_Normalize(front);

    // also re-calculate the Right and Up vector
    // normalize the vectors, because their length gets
    // closer to 0 the more you look up or down which
    // results in slower movement.
    camera->right = Vec3_Normalize(Vec3_Cross(camera->front, camera->worldUp));
    camera->up = Vec3_Normalize(Vec3_Cross(camera->right, camera->front));
}
Squared Wave SVG