Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6c6f831353 | ||
|
|
65e7d1b401 | ||
|
|
c9c5fc428f | ||
|
|
a428be256c |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,3 +1,4 @@
|
||||
cmake-build-debug
|
||||
SDL
|
||||
.idea/
|
||||
external/
|
||||
|
||||
@@ -8,10 +8,26 @@ set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/$<CONFIGURATION>")
|
||||
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/$<CONFIGURATION>")
|
||||
|
||||
# This assumes the SDL source is available in SDL
|
||||
add_subdirectory(SDL EXCLUDE_FROM_ALL)
|
||||
add_subdirectory(external/SDL EXCLUDE_FROM_ALL)
|
||||
add_subdirectory(external/cglm EXCLUDE_FROM_ALL)
|
||||
#add_subdirectory(glad EXCLUDE_FROM_ALL)
|
||||
|
||||
# Create your game executable target as usual
|
||||
add_executable(BeyondDepth WIN32 main.c)
|
||||
add_executable(BeyondDepth src/main.c
|
||||
src/engine/core/logger/log.c
|
||||
src/engine/renderer/camera/camera.c
|
||||
src/engine/renderer/camera/camera.h
|
||||
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/mesh/mesh.c
|
||||
src/engine/renderer/mesh/mesh.h)
|
||||
|
||||
# Link to the actual SDL3 library.
|
||||
target_link_libraries(BeyondDepth PRIVATE SDL3::SDL3)
|
||||
set_target_properties(BeyondDepth PROPERTIES WIN32_EXECUTABLE FALSE)
|
||||
|
||||
target_include_directories(BeyondDepth PRIVATE external/glad/include)
|
||||
|
||||
target_link_libraries(BeyondDepth PRIVATE
|
||||
SDL3::SDL3
|
||||
cglm)
|
||||
67
main.c
67
main.c
@@ -1,67 +0,0 @@
|
||||
/*
|
||||
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely.
|
||||
*/
|
||||
#define SDL_MAIN_USE_CALLBACKS 1 /* use the callbacks instead of main() */
|
||||
#include <SDL3/SDL.h>
|
||||
#include <SDL3/SDL_main.h>
|
||||
|
||||
static SDL_Window *window = NULL;
|
||||
static SDL_Renderer *renderer = NULL;
|
||||
|
||||
/* This function runs once at startup. */
|
||||
SDL_AppResult SDL_AppInit(void **appstate, int argc, char *argv[])
|
||||
{
|
||||
/* Create the window */
|
||||
if (!SDL_CreateWindowAndRenderer("Hello World", 800, 600, SDL_WINDOW_FULLSCREEN, &window, &renderer)) {
|
||||
SDL_Log("Couldn't create window and renderer: %s", SDL_GetError());
|
||||
return SDL_APP_FAILURE;
|
||||
}
|
||||
return SDL_APP_CONTINUE;
|
||||
}
|
||||
|
||||
/* 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) {
|
||||
return SDL_APP_SUCCESS; /* end the program, reporting success to the OS. */
|
||||
}
|
||||
return SDL_APP_CONTINUE;
|
||||
}
|
||||
|
||||
/* This function runs once per frame, and is the heart of the program. */
|
||||
SDL_AppResult SDL_AppIterate(void *appstate)
|
||||
{
|
||||
const char *message = "Hello World!";
|
||||
int w = 0, h = 0;
|
||||
float x, y;
|
||||
const float scale = 4.0f;
|
||||
|
||||
/* Center the message and scale it up */
|
||||
SDL_GetRenderOutputSize(renderer, &w, &h);
|
||||
SDL_SetRenderScale(renderer, scale, scale);
|
||||
x = ((w / scale) - SDL_DEBUG_TEXT_FONT_CHARACTER_SIZE * SDL_strlen(message)) / 2;
|
||||
y = ((h / scale) - SDL_DEBUG_TEXT_FONT_CHARACTER_SIZE) / 2;
|
||||
|
||||
/* Draw the message */
|
||||
SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255);
|
||||
SDL_RenderClear(renderer);
|
||||
SDL_SetRenderDrawColor(renderer, 255, 255, 255, 255);
|
||||
SDL_RenderDebugText(renderer, x, y, message);
|
||||
SDL_RenderPresent(renderer);
|
||||
|
||||
return SDL_APP_CONTINUE;
|
||||
}
|
||||
|
||||
/* This function runs once at shutdown. */
|
||||
void SDL_AppQuit(void *appstate, SDL_AppResult result)
|
||||
{
|
||||
}
|
||||
168
src/engine/core/logger/log.c
Normal file
168
src/engine/core/logger/log.c
Normal file
@@ -0,0 +1,168 @@
|
||||
/*
|
||||
* Copyright (c) 2020 rxi
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to
|
||||
* deal in the Software without restriction, including without limitation the
|
||||
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
* sell copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
* IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#include "log.h"
|
||||
|
||||
#define MAX_CALLBACKS 32
|
||||
|
||||
typedef struct {
|
||||
log_LogFn fn;
|
||||
void *udata;
|
||||
int level;
|
||||
} Callback;
|
||||
|
||||
static struct {
|
||||
void *udata;
|
||||
log_LockFn lock;
|
||||
int level;
|
||||
bool quiet;
|
||||
Callback callbacks[MAX_CALLBACKS];
|
||||
} L;
|
||||
|
||||
|
||||
static const char *level_strings[] = {
|
||||
"TRACE", "DEBUG", "INFO", "WARN", "ERROR", "FATAL"
|
||||
};
|
||||
|
||||
#ifdef LOG_USE_COLOR
|
||||
static const char *level_colors[] = {
|
||||
"\x1b[94m", "\x1b[36m", "\x1b[32m", "\x1b[33m", "\x1b[31m", "\x1b[35m"
|
||||
};
|
||||
#endif
|
||||
|
||||
|
||||
static void stdout_callback(log_Event *ev) {
|
||||
char buf[16];
|
||||
buf[strftime(buf, sizeof(buf), "%H:%M:%S", ev->time)] = '\0';
|
||||
#ifdef LOG_USE_COLOR
|
||||
fprintf(
|
||||
ev->udata, "%s %s%-5s\x1b[0m \x1b[90m%s:%d:\x1b[0m ",
|
||||
buf, level_colors[ev->level], level_strings[ev->level],
|
||||
ev->file, ev->line);
|
||||
#else
|
||||
fprintf(
|
||||
ev->udata, "%s %-5s %s:%d: ",
|
||||
buf, level_strings[ev->level], ev->file, ev->line);
|
||||
#endif
|
||||
vfprintf(ev->udata, ev->fmt, ev->ap);
|
||||
fprintf(ev->udata, "\n");
|
||||
fflush(ev->udata);
|
||||
}
|
||||
|
||||
|
||||
static void file_callback(log_Event *ev) {
|
||||
char buf[64];
|
||||
buf[strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S", ev->time)] = '\0';
|
||||
fprintf(
|
||||
ev->udata, "%s %-5s %s:%d: ",
|
||||
buf, level_strings[ev->level], ev->file, ev->line);
|
||||
vfprintf(ev->udata, ev->fmt, ev->ap);
|
||||
fprintf(ev->udata, "\n");
|
||||
fflush(ev->udata);
|
||||
}
|
||||
|
||||
|
||||
static void lock(void) {
|
||||
if (L.lock) { L.lock(true, L.udata); }
|
||||
}
|
||||
|
||||
|
||||
static void unlock(void) {
|
||||
if (L.lock) { L.lock(false, L.udata); }
|
||||
}
|
||||
|
||||
|
||||
const char* log_level_string(int level) {
|
||||
return level_strings[level];
|
||||
}
|
||||
|
||||
|
||||
void log_set_lock(log_LockFn fn, void *udata) {
|
||||
L.lock = fn;
|
||||
L.udata = udata;
|
||||
}
|
||||
|
||||
|
||||
void log_set_level(int level) {
|
||||
L.level = level;
|
||||
}
|
||||
|
||||
|
||||
void log_set_quiet(bool enable) {
|
||||
L.quiet = enable;
|
||||
}
|
||||
|
||||
|
||||
int log_add_callback(log_LogFn fn, void *udata, int level) {
|
||||
for (int i = 0; i < MAX_CALLBACKS; i++) {
|
||||
if (!L.callbacks[i].fn) {
|
||||
L.callbacks[i] = (Callback) { fn, udata, level };
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
int log_add_fp(FILE *fp, int level) {
|
||||
return log_add_callback(file_callback, fp, level);
|
||||
}
|
||||
|
||||
|
||||
static void init_event(log_Event *ev, void *udata) {
|
||||
if (!ev->time) {
|
||||
time_t t = time(NULL);
|
||||
ev->time = localtime(&t);
|
||||
}
|
||||
ev->udata = udata;
|
||||
}
|
||||
|
||||
|
||||
void log_log(int level, const char *file, int line, const char *fmt, ...) {
|
||||
log_Event ev = {
|
||||
.fmt = fmt,
|
||||
.file = file,
|
||||
.line = line,
|
||||
.level = level,
|
||||
};
|
||||
|
||||
lock();
|
||||
|
||||
if (!L.quiet && level >= L.level) {
|
||||
init_event(&ev, stderr);
|
||||
va_start(ev.ap, fmt);
|
||||
stdout_callback(&ev);
|
||||
va_end(ev.ap);
|
||||
}
|
||||
|
||||
for (int i = 0; i < MAX_CALLBACKS && L.callbacks[i].fn; i++) {
|
||||
Callback *cb = &L.callbacks[i];
|
||||
if (level >= cb->level) {
|
||||
init_event(&ev, cb->udata);
|
||||
va_start(ev.ap, fmt);
|
||||
cb->fn(&ev);
|
||||
va_end(ev.ap);
|
||||
}
|
||||
}
|
||||
|
||||
unlock();
|
||||
}
|
||||
49
src/engine/core/logger/log.h
Normal file
49
src/engine/core/logger/log.h
Normal file
@@ -0,0 +1,49 @@
|
||||
/**
|
||||
* Copyright (c) 2020 rxi
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the MIT license. See `log.c` for details.
|
||||
*/
|
||||
|
||||
#ifndef LOG_H
|
||||
#define LOG_H
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdbool.h>
|
||||
#include <time.h>
|
||||
|
||||
#define LOG_VERSION "0.1.0"
|
||||
|
||||
typedef struct {
|
||||
va_list ap;
|
||||
const char *fmt;
|
||||
const char *file;
|
||||
struct tm *time;
|
||||
void *udata;
|
||||
int line;
|
||||
int level;
|
||||
} log_Event;
|
||||
|
||||
typedef void (*log_LogFn)(log_Event *ev);
|
||||
typedef void (*log_LockFn)(bool lock, void *udata);
|
||||
|
||||
enum { LOG_TRACE, LOG_DEBUG, LOG_INFO, LOG_WARN, LOG_ERROR, LOG_FATAL };
|
||||
|
||||
#define log_trace(...) log_log(LOG_TRACE, __FILE__, __LINE__, __VA_ARGS__)
|
||||
#define log_debug(...) log_log(LOG_DEBUG, __FILE__, __LINE__, __VA_ARGS__)
|
||||
#define log_info(...) log_log(LOG_INFO, __FILE__, __LINE__, __VA_ARGS__)
|
||||
#define log_warn(...) log_log(LOG_WARN, __FILE__, __LINE__, __VA_ARGS__)
|
||||
#define log_error(...) log_log(LOG_ERROR, __FILE__, __LINE__, __VA_ARGS__)
|
||||
#define log_fatal(...) log_log(LOG_FATAL, __FILE__, __LINE__, __VA_ARGS__)
|
||||
|
||||
const char* log_level_string(int level);
|
||||
void log_set_lock(log_LockFn fn, void *udata);
|
||||
void log_set_level(int level);
|
||||
void log_set_quiet(bool enable);
|
||||
int log_add_callback(log_LogFn fn, void *udata, int level);
|
||||
int log_add_fp(FILE *fp, int level);
|
||||
|
||||
void log_log(int level, const char *file, int line, const char *fmt, ...);
|
||||
|
||||
#endif
|
||||
127
src/engine/renderer/camera/camera.c
Normal file
127
src/engine/renderer/camera/camera.c
Normal file
@@ -0,0 +1,127 @@
|
||||
//
|
||||
// Created by Tristan on 10/16/2025.
|
||||
//
|
||||
|
||||
#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
|
||||
glm_vec3_copy(initial_position, self->Position);
|
||||
|
||||
// initialize orientation vector
|
||||
glm_vec3_copy((vec3){0.0f, 0.0f, -1.0f}, self->Orientation);
|
||||
|
||||
// initialize up vector
|
||||
glm_vec3_copy((vec3){0.0f, 1.0f, 0.0f}, self->Up);
|
||||
|
||||
// initialize controls and viewport
|
||||
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;
|
||||
|
||||
// Init view matrix to identity (for first frame)
|
||||
glm_mat4_identity(self->View);
|
||||
}
|
||||
|
||||
void camera_update_view_matrix(Camera *self) {
|
||||
// calc the center or target point (Pos + Orientation)
|
||||
vec3 target;
|
||||
glm_vec3_add(self->Position, self->Orientation, target);
|
||||
|
||||
// create view matrix
|
||||
glm_lookat(self->Position, target, self->Up, self->View);
|
||||
}
|
||||
|
||||
void camera_update_projection(Camera *self, float nearPlane, float farPlane, mat4 dest) {
|
||||
float aspect = (float)self->width / (float)self->height;
|
||||
glm_perspective(glm_rad(self->FOV_deg), aspect, nearPlane, farPlane, dest);
|
||||
}
|
||||
|
||||
// TODO: Update matrix function signature to accept camera entity?
|
||||
// change name to camera_upload_matrix for clarity?
|
||||
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.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 = shader->camMatrixLocation;
|
||||
|
||||
if (uniform_location == -1) {
|
||||
// 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);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
45
src/engine/renderer/camera/camera.h
Normal file
45
src/engine/renderer/camera/camera.h
Normal file
@@ -0,0 +1,45 @@
|
||||
//
|
||||
// Created by Tristan on 10/16/2025.
|
||||
//
|
||||
|
||||
#ifndef CAMERA_H
|
||||
#define CAMERA_H
|
||||
|
||||
#include <SDL3/SDL.h>
|
||||
#include <cglm/cglm.h>
|
||||
#include "../shaders/shader.h"
|
||||
|
||||
typedef struct {
|
||||
vec3 Position;
|
||||
vec3 Orientation;
|
||||
vec3 Up;
|
||||
float yaw;
|
||||
float pitch;
|
||||
float speed;
|
||||
float sensitivity;
|
||||
float FOV_deg;
|
||||
int width;
|
||||
int height;
|
||||
mat4 View; // cache for the calculated View Matrix
|
||||
} Camera;
|
||||
|
||||
// initializer
|
||||
void camera_init(Camera *self, vec3 initial_position, int width, int height);
|
||||
|
||||
/* --- Matrix calculation functions --- */
|
||||
// Updates self->View matrix based on pos, orientation and up
|
||||
void camera_update_view_matrix(Camera *self);
|
||||
|
||||
// calculates the projection matrix for a given set of planes
|
||||
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, 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
|
||||
33
src/engine/renderer/data/engine_data.h
Normal file
33
src/engine/renderer/data/engine_data.h
Normal file
@@ -0,0 +1,33 @@
|
||||
//
|
||||
// Created by Tristan on 10/17/2025.
|
||||
//
|
||||
|
||||
#ifndef ENGINE_DATA_H
|
||||
#define ENGINE_DATA_H
|
||||
|
||||
#include <SDL3/SDL.h>
|
||||
#include "../camera/camera.h"
|
||||
#include "../mesh/mesh.h"
|
||||
|
||||
typedef struct {
|
||||
// Camera instance
|
||||
Camera camera;
|
||||
|
||||
// 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;
|
||||
|
||||
#endif //ENGINE_DATA_H
|
||||
93
src/engine/renderer/mesh/mesh.c
Normal file
93
src/engine/renderer/mesh/mesh.c
Normal 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;
|
||||
}
|
||||
43
src/engine/renderer/mesh/mesh.h
Normal file
43
src/engine/renderer/mesh/mesh.h
Normal 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
|
||||
18
src/engine/renderer/shaders/default.frag
Normal file
18
src/engine/renderer/shaders/default.frag
Normal file
@@ -0,0 +1,18 @@
|
||||
#version 330 core
|
||||
|
||||
// Outputs colors in RGBA
|
||||
out vec4 FragColor;
|
||||
|
||||
// Inputs the color from the vertex shader
|
||||
in vec3 color;
|
||||
|
||||
// Inputs the texture coordinates from the vertex shader
|
||||
in vec2 texCoord;
|
||||
|
||||
// get the texture unit from the main function
|
||||
uniform sampler2D tex0;
|
||||
|
||||
void main()
|
||||
{
|
||||
FragColor = texture(tex0, texCoord);
|
||||
}
|
||||
22
src/engine/renderer/shaders/default.vert
Normal file
22
src/engine/renderer/shaders/default.vert
Normal file
@@ -0,0 +1,22 @@
|
||||
#version 330 core
|
||||
layout (location = 0) in vec3 aPos; // Vertex Position
|
||||
layout (location = 1) in vec3 aColor; // Vertex Color
|
||||
layout (location = 2) in vec2 aTex; // Texture coordinates
|
||||
|
||||
// Outputs the color for the Fragment shader
|
||||
out vec3 color;
|
||||
// Outputs the texture coordinates for the Fragment shader
|
||||
out vec2 texCoord;
|
||||
|
||||
// Imports the camera matrix from the main function
|
||||
uniform mat4 camMatrix;
|
||||
|
||||
void main()
|
||||
{
|
||||
// Apply the combined P*V matrix to transform the vertex position.
|
||||
gl_Position = camMatrix * vec4(aPos, 1.0);
|
||||
// Assigns the colors from the Vertex Data to "color"
|
||||
color = aColor;
|
||||
// Assigns the texture coordinates from the Vertex Data to "texCoord"
|
||||
texCoord = aTex;
|
||||
}
|
||||
129
src/engine/renderer/shaders/shader.c
Normal file
129
src/engine/renderer/shaders/shader.c
Normal file
@@ -0,0 +1,129 @@
|
||||
//
|
||||
// Created by Tristan on 10/16/2025.
|
||||
//
|
||||
|
||||
#include "shader.h"
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
#include "../../core/logger/log.h"
|
||||
|
||||
// reads entire file contents into a string
|
||||
char *get_file_contents(const char *filename) {
|
||||
FILE *fp;
|
||||
long length;
|
||||
char *buffer = NULL;
|
||||
|
||||
fp = fopen(filename, "rb");
|
||||
if (!fp) {
|
||||
log_error("Could not open file '%s'. Reason: %s\n", filename, strerror(errno));
|
||||
return NULL;
|
||||
}
|
||||
|
||||
fseek(fp, 0, SEEK_END);
|
||||
length = ftell(fp);
|
||||
fseek(fp, 0, SEEK_SET);
|
||||
|
||||
buffer = (char *)malloc(length + 1);
|
||||
if (!buffer) {
|
||||
log_error("Memory allocation failed for file '%s'.\n", filename);
|
||||
fclose(fp);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
fread(buffer, 1, length, fp);
|
||||
buffer[length] = '\0';
|
||||
fclose(fp);
|
||||
return buffer;
|
||||
}
|
||||
|
||||
// check if the different shaders have compiled properly
|
||||
void compile_errors(unsigned int shader, const char *type) {
|
||||
GLint hasCompiled;
|
||||
char infoLog[1024];
|
||||
|
||||
if (strcmp(type, "PROGRAM") != 0) {
|
||||
glGetShaderiv(shader, GL_COMPILE_STATUS, &hasCompiled);
|
||||
if (hasCompiled == GL_FALSE) {
|
||||
glGetShaderInfoLog(shader, 1024, NULL, infoLog);
|
||||
log_error("SHADER_COMPILATION_ERROR for: %s\n%s", type, infoLog);
|
||||
}
|
||||
} else {
|
||||
glGetProgramiv(shader, GL_LINK_STATUS, &hasCompiled);
|
||||
if (hasCompiled == GL_FALSE) {
|
||||
glGetShaderInfoLog(shader, 1024, NULL, infoLog);
|
||||
log_error("SHADER_LINKING_ERROR for %s\n%s", type, infoLog);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// build the shader program from 2 different shaders
|
||||
Shader shader_create(const char *vertexFile, const char *fragmentFile) {
|
||||
// read shader source files
|
||||
char *vertexSource = get_file_contents(vertexFile);
|
||||
char *fragmentSource = get_file_contents(fragmentFile);
|
||||
|
||||
if (vertexSource == NULL || fragmentSource == NULL) {
|
||||
log_error("Failed to load shader files. Returning empty shader\n");
|
||||
Shader emptyShader = { .ID = 0, .camMatrixLocation = -1};
|
||||
if (vertexSource)
|
||||
free(vertexSource);
|
||||
if (fragmentSource)
|
||||
free(fragmentSource);
|
||||
return emptyShader;
|
||||
}
|
||||
|
||||
// compile vertex shader
|
||||
GLuint vertexShader = glCreateShader(GL_VERTEX_SHADER);
|
||||
glShaderSource(vertexShader, 1, (const char *const *)&vertexSource, NULL);
|
||||
glCompileShader(vertexShader);
|
||||
compile_errors(vertexShader, "VERTEX");
|
||||
|
||||
// compile fragment shader
|
||||
GLuint fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
|
||||
glShaderSource(fragmentShader, 1, (const char *const *)&fragmentSource, NULL);
|
||||
glCompileShader(fragmentShader);
|
||||
compile_errors(fragmentShader, "FRAGMENT");
|
||||
|
||||
// cleanup strings after compilation
|
||||
free(vertexSource);
|
||||
free(fragmentSource);
|
||||
|
||||
// link shaders into a program
|
||||
GLuint programID = glCreateProgram();
|
||||
glAttachShader(programID, vertexShader);
|
||||
glAttachShader(programID, fragmentShader);
|
||||
glLinkProgram(programID);
|
||||
compile_errors(programID, "PROGRAM");
|
||||
|
||||
// cleanup temporary shaders now that we've linked into the program
|
||||
glDeleteShader(vertexShader);
|
||||
glDeleteShader(fragmentShader);
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
// activates the shader program
|
||||
void shader_activate(Shader *self) {
|
||||
glUseProgram(self->ID);
|
||||
}
|
||||
|
||||
// deletes the shader program
|
||||
void shader_delete(Shader *self) {
|
||||
glDeleteProgram(self->ID);
|
||||
self->ID = 0; // just in-case
|
||||
}
|
||||
31
src/engine/renderer/shaders/shader.h
Normal file
31
src/engine/renderer/shaders/shader.h
Normal file
@@ -0,0 +1,31 @@
|
||||
//
|
||||
// Created by Tristan on 10/16/2025.
|
||||
//
|
||||
|
||||
#ifndef SHADER_H
|
||||
#define SHADER_H
|
||||
|
||||
#include <glad/glad.h>
|
||||
#include <string.h>
|
||||
|
||||
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);
|
||||
|
||||
Shader shader_create(const char *vertexFile, const char *fragmentFile);
|
||||
|
||||
// Takes a pointer to the instance
|
||||
void shader_activate(Shader *self);
|
||||
void shader_delete(Shader *self);
|
||||
|
||||
// Private helper on shaderClass.h C++ header -- no private in C :)
|
||||
void compile_errors(unsigned int shader, const char *type);
|
||||
|
||||
|
||||
#endif //SHADER_H
|
||||
213
src/main.c
Normal file
213
src/main.c
Normal file
@@ -0,0 +1,213 @@
|
||||
/*
|
||||
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely.
|
||||
*/
|
||||
#define SDL_MAIN_USE_CALLBACKS 1 /* use the callbacks instead of main() */
|
||||
#include <SDL3/SDL.h>
|
||||
#include <SDL3/SDL_main.h>
|
||||
#include <cglm/cglm.h>
|
||||
#include "engine/core/logger/log.h"
|
||||
#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;
|
||||
|
||||
/* This function runs once at startup. */
|
||||
SDL_AppResult SDL_AppInit(void **appstate, int argc, char *argv[])
|
||||
{
|
||||
//log_set_level(2);
|
||||
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) {
|
||||
log_error("Failed to allocate engine_data state, %s", SDL_GetError());
|
||||
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());
|
||||
SDL_DestroyWindow(window);
|
||||
free(state);
|
||||
return SDL_APP_FAILURE;
|
||||
}
|
||||
|
||||
// initialize GLAD
|
||||
// loads all the OpenGL function pointers
|
||||
if (!gladLoadGLLoader((GLADloadproc)SDL_GL_GetProcAddress)) {
|
||||
log_error("Failed to initialize GLAD");
|
||||
SDL_GL_DestroyContext(gl_context);
|
||||
SDL_DestroyWindow(window);
|
||||
free(state);
|
||||
return SDL_APP_FAILURE;
|
||||
}
|
||||
|
||||
// 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");
|
||||
|
||||
return SDL_APP_CONTINUE;
|
||||
}
|
||||
|
||||
/* This function runs when a new event (mouse input, keypresses, etc) occurs. */
|
||||
SDL_AppResult SDL_AppEvent(void *appstate, SDL_Event *event)
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
/* This function runs once per frame, and is the heart of the program. */
|
||||
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);
|
||||
|
||||
// clear the screen before drawing
|
||||
// TODO: macros for colours so its easier to read? 0.07f does nothing for me context wise lol
|
||||
glClearColor(0.07f, 0.13f, 0.17f, 1.0f);
|
||||
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
|
||||
|
||||
// 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);
|
||||
|
||||
return SDL_APP_CONTINUE;
|
||||
}
|
||||
|
||||
/* This function runs once at shutdown. */
|
||||
void SDL_AppQuit(void *appstate, SDL_AppResult result)
|
||||
{
|
||||
Engine_Data *state = (Engine_Data *)appstate;
|
||||
|
||||
// cleanup all resources
|
||||
if (state) {
|
||||
mesh_delete(&state->triangle_mesh);
|
||||
|
||||
if (state->shader.ID != 0) {
|
||||
shader_delete(&state->shader);
|
||||
}
|
||||
|
||||
free(state);
|
||||
}
|
||||
|
||||
if (gl_context) {
|
||||
SDL_GL_DestroyContext(gl_context);
|
||||
}
|
||||
|
||||
if (window) {
|
||||
SDL_DestroyWindow(window);
|
||||
}
|
||||
|
||||
log_info("Application quit and resources cleaned up");
|
||||
}
|
||||
Reference in New Issue
Block a user