feat: add model matrix transformation hierarchy and delta time tracking

This commit is contained in:
Natsirt867
2025-10-17 20:44:22 -05:00
parent 65e7d1b401
commit 6c6f831353
9 changed files with 323 additions and 47 deletions

View File

@@ -20,7 +20,9 @@ add_executable(BeyondDepth src/main.c
external/glad/src/glad.c
src/engine/renderer/shaders/shader.c
src/engine/renderer/shaders/shader.h
src/engine/renderer/data/engine_data.h)
src/engine/renderer/data/engine_data.h
src/engine/renderer/mesh/mesh.c
src/engine/renderer/mesh/mesh.h)
set_target_properties(BeyondDepth PROPERTIES WIN32_EXECUTABLE FALSE)

View File

@@ -4,6 +4,8 @@
#include "camera.h"
#include "../..//core/logger/log.h"
#include <cglm/cglm.h>
#include "../shaders/shader.h"
void camera_init(Camera *self, vec3 initial_position, int width, int height) {
// copy initial_pos vector
@@ -16,9 +18,11 @@ void camera_init(Camera *self, vec3 initial_position, int width, int height) {
glm_vec3_copy((vec3){0.0f, 1.0f, 0.0f}, self->Up);
// initialize controls and viewport
self->speed = 0.1f;
self->sensitivity = 100.0f;
self->speed = 0.9f;
self->sensitivity = 3.5f;
self->FOV_deg = 45.0f;
self->yaw = -90.0f;
self->pitch = 0.0f;
self->width = width;
self->height = height;
@@ -27,13 +31,6 @@ void camera_init(Camera *self, vec3 initial_position, int width, int height) {
glm_mat4_identity(self->View);
}
/*
Camera l_camera = {
.Position = { 0.0f, 0.0f, 0.0f },
.Orientation = { 0.0f, 0.0f, -1.0f },
.Up = { 0.0f, 1.0f, 0.0f }
};*/
void camera_update_view_matrix(Camera *self) {
// calc the center or target point (Pos + Orientation)
vec3 target;
@@ -50,31 +47,81 @@ void camera_update_projection(Camera *self, float nearPlane, float farPlane, mat
// TODO: Update matrix function signature to accept camera entity?
// change name to camera_upload_matrix for clarity?
void camera_matrix_to_shader(Camera *self, GLuint shader_program_ID, const char *uniform_name) {
void camera_matrix_to_shader(Camera *self, Shader *shader, const char *uniform_name) {
// update the view matrix first, camera position might have moved
camera_update_view_matrix(self);
// calculate projection matrix (P) with temporary matrix 'projection' for the result
mat4 projection;
camera_update_projection(self, 0.1f, 100.0f, projection);
camera_update_projection(self, 0.001f, 100.0f, projection);
// combine the matrices -- P * V -> PV
mat4 view_projection;
glm_mat4_mul(projection, self->View, view_projection);
// get uniform location
GLint uniform_location = glGetUniformLocation(shader_program_ID, uniform_name);
GLint uniform_location = shader->camMatrixLocation;
if (uniform_location == -1) {
log_error("Warning: Uniform '%s' not found in shader program %u.\n", uniform_name, shader_program_ID);
// log_error("Warning: Uniform '%s' not found in shader program %u.\n", uniform_name, shader);
return;
}
// upload combined matrix to the shader
// view_projection[0] array pointer decays to first value
glUniformMatrix4fv(uniform_location, 1, GL_FALSE, view_projection[0]);
glUniformMatrix4fv(uniform_location, 1, GL_FALSE, *view_projection);
}
void camera_inputs(Camera *self, SDL_Event *event) {
// TODO: implementation for handling keyboard/mouse inputs for camera
void camera_process_keyboard(Camera *self, const Uint8 *keyState, float deltaTime) {
vec3 move;
float current_speed = self->speed * deltaTime;
// forward/back
if (keyState[SDL_SCANCODE_W]) {
glm_vec3_scale(self->Orientation, current_speed, move);
glm_vec3_add(self->Position, move, self->Position);
}
if (keyState[SDL_SCANCODE_S]) {
glm_vec3_scale(self->Orientation, current_speed, move);
glm_vec3_sub(self->Position, move, self->Position);
}
// strafe left/right
vec3 right;
glm_vec3_cross(self->Orientation, self->Up, right);
glm_vec3_normalize(right);
if (keyState[SDL_SCANCODE_A]) {
glm_vec3_scale(right, current_speed, move);
glm_vec3_sub(self->Position, move, self->Position);
}
if (keyState[SDL_SCANCODE_D]) {
glm_vec3_scale(right, current_speed, move);
glm_vec3_add(self->Position, move, self->Position);
}
}
// TODO: add deltaTime arg for smoother rotation
void camera_process_mouse(Camera *self, float xoffset, float yoffset, float deltaTime) {
float effective_sensitivity = self->sensitivity * deltaTime;
xoffset *= effective_sensitivity;
yoffset *= effective_sensitivity;
self->yaw += xoffset;
self->pitch += yoffset;
// clamps the pitch to prevent camera from flipping upside down
if (self->pitch > 89.0f)
self->pitch = 89.0f;
if (self->pitch < -89.0f)
self->pitch = -89.0f;
// calc new orientation vector from yaw and pitch
vec3 new_orientation;
new_orientation[0] = cos(glm_rad(self->yaw)) * cos(glm_rad(self->pitch));
new_orientation[1] = sin(glm_rad(self->pitch));
new_orientation[2] = sin(glm_rad(self->yaw)) * cos(glm_rad(self->pitch));
glm_vec3_normalize_to(new_orientation, self->Orientation);
}

View File

@@ -13,6 +13,8 @@ typedef struct {
vec3 Position;
vec3 Orientation;
vec3 Up;
float yaw;
float pitch;
float speed;
float sensitivity;
float FOV_deg;
@@ -32,9 +34,12 @@ void camera_update_view_matrix(Camera *self);
void camera_update_projection(Camera *self, float nearPlane, float forPlane, mat4 dest);
// generates (projection * view) and uploads to the shader program
void camera_matrix_to_shader(Camera *self, GLuint shader_program_ID, const char *uniform_name);
void camera_matrix_to_shader(Camera *self, Shader *shader, const char *uniform_name);
// TODO: Input handling (needs full SDL context later)
void camera_inputs(Camera *self, SDL_Event *event);
void camera_process_keyboard(Camera *self, const Uint8 *keystate, float deltaTime);
void camera_process_mouse(Camera *self, float xoffset, float yoffset, float deltaTime);
#endif //CAMERA_H

View File

@@ -7,6 +7,7 @@
#include <SDL3/SDL.h>
#include "../camera/camera.h"
#include "../mesh/mesh.h"
typedef struct {
// Camera instance
@@ -15,10 +16,17 @@ typedef struct {
// Shader program instance
Shader shader;
// Mesh instance
Mesh triangle_mesh;
mat4 pyramidModel;
// Keep track of window size
int window_width;
int window_height;
float lastFrameTime; // time of the previous frame
float deltaTime; // time elapsed since the last frame
// TODO: Add other components, SDL_Window* or Meshes, etc...
} Engine_Data;

View File

@@ -0,0 +1,93 @@
//
// Created by Tristan on 10/17/2025.
//
#include "mesh.h"
#include "../../core/logger/log.h"
#include <stdlib.h>
GLfloat vertices[] = {
// Position // Color
// X Y Z R G B // Index
-0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 0.0f, // 0: Apex
0.5f, -0.5f, 0.0f, 0.0f, 1.0f, 0.0f, // 1: Base Corner 1 (Red)
0.0f, 0.5f, 0.0f, 0.0f, 0.0f, 1.0f, // 2: Base Corner 2 (Green)
0.0f, -0.5f, -0.5f, 0.0f, 0.0f, 1.0f // 3: Base Corner 3 (Blue)
};
const size_t vertices_size = sizeof(vertices);
GLuint indices[] = {
// side 1: (Apex, C1, C2)
0, 1, 2, // vertices that form the triangle
// side2: (Apex, C2, C3)
0, 2, 3,
// side 3: (Apex, C3, C1)
0, 3, 1,
// base: (C1, C3, C2) - order is crucial for culling
1, 3, 2
};
void mesh_init(Mesh *self, GLfloat *vertices, GLsizei vertices_size, GLuint *indices, GLsizei num_indices) {
self->num_indices = num_indices;
// generate buffers, vao, vbo, ebo
glGenVertexArrays(1, &self->VAO);
glGenBuffers(1, &self->VBO);
glGenBuffers(1, &self->EBO);
// bind the VAO
glBindVertexArray(self->VAO);
// bind and upload VBO data
glBindBuffer(GL_ARRAY_BUFFER, self->VBO);
glBufferData(GL_ARRAY_BUFFER, vertices_size, vertices, GL_STATIC_DRAW);
// bind and upload ebo data
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, self->EBO);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(GLuint) * num_indices, indices, GL_STATIC_DRAW);
// configure vertex attributes
// a. Position attribute (layout = 0)
// 3 floats per pos, stride = 6 floats total (3 pos + 3 colour), offset is 0
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(GLfloat), (void*)0);
glEnableVertexAttribArray(0);
// b. Color attribute (layout = 1)
// 3 floats per color, stride = 6 floats total, offset is 3 floats (after pos)
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(GLfloat), (void*)(3 * sizeof(GLfloat)));
glEnableVertexAttribArray(1);
// TODO: hader also expects layout = 2 but lazy, pray no crash
// unbind the VAO (so it can't be modified)
glBindVertexArray(0);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
log_info("Mesh initialized with VAO: %u", self->VAO);
}
void mesh_draw(Mesh *self) {
// bind the vao
glBindVertexArray(self->VAO);
// draw the triangle
glDrawElements(GL_TRIANGLES, self->num_indices, GL_UNSIGNED_INT, 0);
// unbind
glBindVertexArray(0);
}
void mesh_delete(Mesh *self) {
glDeleteVertexArrays(1, &self->VAO);
glDeleteBuffers(1, &self->VBO);
glDeleteBuffers(1, &self->EBO);
self->VAO = 0;
self->VBO = 0;
self->EBO = 0;
}

View File

@@ -0,0 +1,43 @@
//
// Created by Tristan on 10/17/2025.
//
#ifndef MESH_H
#define MESH_H
#include <glad/glad.h>
#include <cglm/cglm.h>
// Vertices for a simple 2D triangle (x, y, z)
// Add (r, g, b) color data after each pos
// X, Y, Z, R, G, B
extern GLfloat vertices[];
extern const size_t vertices_size;
// Indices define the order vertices are drawn
extern GLuint indices[];
#define INDICES_COUNT 12
typedef struct {
// Vertex Array Object
GLuint VAO;
// Vertex Buffer Object
GLuint VBO;
// Element Buffer Object
GLuint EBO;
// Number of indices to draw
GLsizei num_indices;
} Mesh;
// initializes and uploads mesh data to gpu
void mesh_init(Mesh *self, GLfloat *vertices, GLsizei vertices_size, GLuint *indices, GLsizei num_indices);
// Draws the mesh
void mesh_draw(Mesh *self);
void mesh_delete(Mesh *self);
#endif //MESH_H

View File

@@ -64,9 +64,9 @@ Shader shader_create(const char *vertexFile, const char *fragmentFile) {
char *vertexSource = get_file_contents(vertexFile);
char *fragmentSource = get_file_contents(fragmentFile);
if (!vertexSource || !fragmentSource) {
if (vertexSource == NULL || fragmentSource == NULL) {
log_error("Failed to load shader files. Returning empty shader\n");
Shader emptyShader = { .ID = 0};
Shader emptyShader = { .ID = 0, .camMatrixLocation = -1};
if (vertexSource)
free(vertexSource);
if (fragmentSource)
@@ -101,7 +101,19 @@ Shader shader_create(const char *vertexFile, const char *fragmentFile) {
glDeleteShader(vertexShader);
glDeleteShader(fragmentShader);
Shader newShader = { .ID = programID };
GLint camMatrixLocation = glGetUniformLocation(programID, "camMatrix");
if (camMatrixLocation == -1) {
log_error("Uniform 'camMatrix' not found after linking shader program %u. Is it used in default.vert?", programID);
}
GLint modelMatrixLocation = glGetUniformLocation(programID, "modelMatrix");
if (modelMatrixLocation == -1) {
log_warn("Uniform 'modelMatrix' not found in shader program %u. Is it used in default.vert?", programID);
}
Shader newShader = { .ID = programID,
.camMatrixLocation = camMatrixLocation,
.modelMatrixLocation = modelMatrixLocation };
return newShader;
}

View File

@@ -11,6 +11,9 @@
typedef struct {
// Reference ID of the shader program
GLuint ID;
// Location of the camera matrix uniform, set at creation
GLint camMatrixLocation;
GLint modelMatrixLocation;
} Shader;
char *get_file_contents(const char *filename);

View File

@@ -17,6 +17,7 @@
#include "engine/renderer/camera/camera.h"
#include "engine/renderer/data/engine_data.h"
#include "engine/renderer/shaders/shader.h"
#include "engine/renderer/mesh/mesh.h"
static SDL_Window *window = NULL;
static SDL_GLContext gl_context = NULL;
@@ -28,6 +29,9 @@ SDL_AppResult SDL_AppInit(void **appstate, int argc, char *argv[])
log_info("Starting program and initializing SDL...");
/* Create the window */
int win_width = 1280;
int win_height = 720;
// TODO: make use of arena allocators potentially? see: digital grove repo
Engine_Data *state = (Engine_Data*)malloc(sizeof(Engine_Data));
if (state == NULL) {
@@ -35,25 +39,22 @@ SDL_AppResult SDL_AppInit(void **appstate, int argc, char *argv[])
return SDL_APP_FAILURE;
}
// pass allocated state back to the SDL loop
*appstate = state;
int win_width = 1280;
int win_height = 720;
// use of SDL_WINDOW_OPENGL flag to create a context-compatible window with shaders
window = SDL_CreateWindow("Beyond Depth", win_width, win_height, SDL_WINDOW_OPENGL);
if (!window) {
log_error("Couldn't create SDL window: %s", SDL_GetError());
free(state);
return SDL_APP_FAILURE;
}
state->window_width = win_width;
state->window_height = win_height;
// create openGL context with openGL 3.3 core profile
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
// use of SDL_WINDOW_OPENGL flag to create a context-compatible window with shaders
window = SDL_CreateWindow("Beyond Depth", win_width, win_height, SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE);
if (!window) {
log_error("Couldn't create SDL window: %s", SDL_GetError());
free(state);
return SDL_APP_FAILURE;
}
gl_context = SDL_GL_CreateContext(window);
if (!gl_context) {
log_error("Failed to create OpenGL context: %s", SDL_GetError());
@@ -72,22 +73,27 @@ SDL_AppResult SDL_AppInit(void **appstate, int argc, char *argv[])
return SDL_APP_FAILURE;
}
// TODO: if still crashing on initial startup, ensure all of OpenGL, GLAD is properly initialized
state->window_width = win_width;
state->window_height = win_height;
vec3 initial_pos = {0.0f, 0.0f, 3.0f};
camera_init(&state->camera, initial_pos, state->window_width, state->window_height);
// z-buffer
glEnable(GL_DEPTH_TEST);
state->shader = shader_create("default.vert", "default.frag");
if (state->shader.ID == 0) {
log_error("Failed to initialize shader program");
// TODO: cleanup memory
return SDL_APP_FAILURE;
}
vec3 initial_pos = {0.0f, 0.0f, 2.0f};
camera_init(&state->camera, initial_pos, state->window_width, state->window_height);
mesh_init(&state->triangle_mesh, vertices, (GLsizei)vertices_size, indices, INDICES_COUNT);
glm_mat4_identity(state->pyramidModel);
SDL_SetWindowRelativeMouseMode(window, true);
// pass allocated state back to the SDL loop
*appstate = state;
log_info("Camera, Shaders and App State initialized");
@@ -97,11 +103,24 @@ SDL_AppResult SDL_AppInit(void **appstate, int argc, char *argv[])
/* This function runs when a new event (mouse input, keypresses, etc) occurs. */
SDL_AppResult SDL_AppEvent(void *appstate, SDL_Event *event)
{
if (event->type == SDL_EVENT_KEY_DOWN ||
event->type == SDL_EVENT_QUIT) {
log_info("User pressed a key");
return SDL_APP_SUCCESS; /* end the program, reporting success to the OS. */
Engine_Data *state = (Engine_Data *)appstate;
// handles quitting
if (event->type == SDL_EVENT_QUIT) {
return SDL_APP_SUCCESS;
}
// handles esc key to quit
if (event->type == SDL_EVENT_KEY_DOWN && event->key.key == SDLK_ESCAPE) {
return SDL_APP_SUCCESS;
}
// handle mouse motion for camera look
if (event->type == SDL_EVENT_MOUSE_MOTION) {
// y movement inverted?
camera_process_mouse(&state->camera, event->motion.xrel, -event->motion.yrel, state->deltaTime);
}
return SDL_APP_CONTINUE;
}
@@ -111,6 +130,13 @@ SDL_AppResult SDL_AppIterate(void *appstate)
{
Engine_Data *state = (Engine_Data *)appstate; // good practice
float currentTime = (float)SDL_GetTicks() / 1000.0f;
state->deltaTime = currentTime - state->lastFrameTime;
state->lastFrameTime = currentTime;
const Uint8 *keyState = SDL_GetKeyboardState(NULL);
camera_process_keyboard(&state->camera, keyState, state->deltaTime);
// set viewport
glViewport(0, 0, 1280, 720);
@@ -119,7 +145,39 @@ SDL_AppResult SDL_AppIterate(void *appstate)
glClearColor(0.07f, 0.13f, 0.17f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
// TODO: add drawing logic
// calculate "orbit" angle for total rotation overtime
float rotation_speed_deg = 45.0f;
float rotation_amount = glm_rad(rotation_speed_deg * state->deltaTime);
// accumulate total rotation
static float total_rotation = 0.0f;
total_rotation += rotation_amount;
shader_activate(&state->shader);
// upload camera matrix (P*V) to the shader
camera_matrix_to_shader(&state->camera, &state->shader, "camMatrix");
float rotation = (float)SDL_GetTicks() / 1000.0f;
glm_mat4_identity(state->pyramidModel);
// rotate around the y-axis
glm_rotate(state->pyramidModel, total_rotation * 0.5f, (vec3){0.0f, 1.0f, 0.0f});
mat4 orbitModel; // temporary object
glm_mat4_identity(orbitModel);
glm_rotate(orbitModel, total_rotation, (vec3){0.0f, 1.0f, 0.0f});
glm_translate(orbitModel, (vec3){3.0f, 0.0f, 0.0f});
glm_rotate(orbitModel, total_rotation * 2.0f, (vec3){0.5f, 1.0f, 0.0f});
// upload model matrix to shader
glUniformMatrix4fv(state->shader.modelMatrixLocation, 1, GL_FALSE, *state->pyramidModel);
mesh_draw(&state->triangle_mesh);
glUniformMatrix4fv(state->shader.modelMatrixLocation, 1, GL_FALSE, *orbitModel);
mesh_draw(&state->triangle_mesh);
// swap front and back buffers to display rendered frame
SDL_GL_SwapWindow(window);
@@ -134,7 +192,12 @@ void SDL_AppQuit(void *appstate, SDL_AppResult result)
// cleanup all resources
if (state) {
mesh_delete(&state->triangle_mesh);
if (state->shader.ID != 0) {
shader_delete(&state->shader);
}
free(state);
}