Going 3D

Camera

Previously, we described a simple view matrix that is fixed in one position. But we 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. We 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;
};

We should be able to control this camera with the keyboard and mouse. Finally, we 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);

We 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;

We need to add the camera to the renderer entity to be able to access it inside the renderer functions. In the future we 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 we 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