diff --git a/CMakeLists.txt b/CMakeLists.txt index 86c1b65..cb2f191 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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) diff --git a/src/engine/renderer/camera/camera.c b/src/engine/renderer/camera/camera.c index 4687ed5..63da677 100644 --- a/src/engine/renderer/camera/camera.c +++ b/src/engine/renderer/camera/camera.c @@ -4,6 +4,8 @@ #include "camera.h" #include "../..//core/logger/log.h" +#include +#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); } \ No newline at end of file diff --git a/src/engine/renderer/camera/camera.h b/src/engine/renderer/camera/camera.h index 433477d..9dc608d 100644 --- a/src/engine/renderer/camera/camera.h +++ b/src/engine/renderer/camera/camera.h @@ -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 diff --git a/src/engine/renderer/data/engine_data.h b/src/engine/renderer/data/engine_data.h index 99fa7f6..4288f09 100644 --- a/src/engine/renderer/data/engine_data.h +++ b/src/engine/renderer/data/engine_data.h @@ -7,6 +7,7 @@ #include #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; diff --git a/src/engine/renderer/mesh/mesh.c b/src/engine/renderer/mesh/mesh.c new file mode 100644 index 0000000..1a672b2 --- /dev/null +++ b/src/engine/renderer/mesh/mesh.c @@ -0,0 +1,93 @@ +// +// Created by Tristan on 10/17/2025. +// + +#include "mesh.h" +#include "../../core/logger/log.h" +#include + +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; +} \ No newline at end of file diff --git a/src/engine/renderer/mesh/mesh.h b/src/engine/renderer/mesh/mesh.h new file mode 100644 index 0000000..ceab2be --- /dev/null +++ b/src/engine/renderer/mesh/mesh.h @@ -0,0 +1,43 @@ +// +// Created by Tristan on 10/17/2025. +// + +#ifndef MESH_H +#define MESH_H + +#include +#include + +// 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 diff --git a/src/engine/renderer/shaders/shader.c b/src/engine/renderer/shaders/shader.c index d2ef3df..ea4b844 100644 --- a/src/engine/renderer/shaders/shader.c +++ b/src/engine/renderer/shaders/shader.c @@ -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; } diff --git a/src/engine/renderer/shaders/shader.h b/src/engine/renderer/shaders/shader.h index a139df9..ccdee53 100644 --- a/src/engine/renderer/shaders/shader.h +++ b/src/engine/renderer/shaders/shader.h @@ -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); diff --git a/src/main.c b/src/main.c index 19a58a9..61730dd 100644 --- a/src/main.c +++ b/src/main.c @@ -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) { - shader_delete(&state->shader); + mesh_delete(&state->triangle_mesh); + + if (state->shader.ID != 0) { + shader_delete(&state->shader); + } + free(state); }