summaryrefslogtreecommitdiff
path: root/source
diff options
context:
space:
mode:
Diffstat (limited to 'source')
-rwxr-xr-xsource/main.cpp1113
-rwxr-xr-xsource/math.h463
-rwxr-xr-xsource/shaders/colored_quad.fs.glsl8
-rwxr-xr-xsource/shaders/colored_quad.vs.glsl10
-rwxr-xr-xsource/shaders/ui_text.fs.glsl15
-rwxr-xr-xsource/shaders/ui_text.vs.glsl16
6 files changed, 1625 insertions, 0 deletions
diff --git a/source/main.cpp b/source/main.cpp
new file mode 100755
index 0000000..50005df
--- /dev/null
+++ b/source/main.cpp
@@ -0,0 +1,1113 @@
+#include <SDL2/SDL.h>
+#include <glad/glad.h>
+
+#define STB_IMAGE_IMPLEMENTATION
+#include "stb_image.h"
+
+#include <ft2build.h>
+#include FT_FREETYPE_H
+
+/*
+* Project Estimation/Target ~ 1 - 2 months
+* Tasks:
+* DONE:
+* - gravity - very barebones version done
+* - horizontal motion on ground
+* - accelerate
+* - constant speed
+* - decelerate to give impression of sliding
+* - horizontal motion when falling
+* - inertia-like movement when accelerating prior to falling
+* - loss of inertia when player was at high speed before sliding off
+* - small movement when in free fall
+* - movement and deceleration when not moving in free fall
+* TODO:
+* - Platform collision
+* - Fix collision and make it work for each object
+* - Jumping
+* - Level completion
+* - Level Selection
+* - Level Creation
+*/
+
+typedef uint8_t u8;
+typedef uint16_t u16;
+typedef uint32_t u32;
+typedef uint64_t u64;
+
+typedef int8_t s8;
+typedef int16_t s16;
+typedef int32_t s32;
+typedef int64_t s64;
+
+typedef float r32;
+typedef double r64;
+
+typedef u8 b8;
+
+#define MIN(x,y) ((x) < (y) ? (x) : (y))
+#define R32_MAX ((r32)(size_t) - 1)
+#define R64_MAX ((r64)(size_t) - 1)
+
+#include "math.h"
+
+enum PMoveState {
+ NO_MOVE = 0,
+ ACCEL = 1,
+ KEEP = 2,
+ DECEL = 3,
+ FALL_FREE = 4,
+ FALL_NORM = 5,
+ FALL_KEEP = 6,
+ FALL_DECEL = 7
+};
+
+enum PlatformKey {
+ PK_NIL = 0,
+ PK_W = 1,
+ PK_A = 2,
+ PK_S = 3,
+ PK_D = 4,
+};
+
+struct TextChar {
+ s64 advance;
+ Vec2 size;
+ Vec2 bearing;
+};
+
+struct TextState {
+ u32 pixel_size;
+ u32 texture_atlas_id;
+ u32 sp;
+ u32 vao;
+ u32 vbo;
+ u32 chunk_size;
+ s32* char_indexes;
+ Mat4* transforms;
+ TextChar* char_map;
+};
+
+struct GLRenderer {
+ // colored quad
+ b8 cq_init;
+ u32 cq_sp;
+ u32 cq_vao;
+ // camera
+ b8 cam_update;
+ Vec3 cam_pos;
+ Vec3 cam_look;
+ Mat4 cam_view;
+ Mat4 cam_proj;
+ // ui text
+ TextState ui_text;
+};
+
+struct Controller {
+ b8 move_up;
+ b8 move_down;
+ b8 move_left;
+ b8 move_right;
+ b8 jump;
+};
+
+struct GameState {
+ // player
+ b8 p_jump_status;
+ r32 p_jump_period;
+ r32 p_jumpx;
+ r32 p_jumpy;
+ Vec3 player_position;
+ Vec2 player_size;
+ // floor
+ Vec3 floor_position;
+ Vec2 floor_size;
+ // wall
+ Vec3 wall_position;
+ Vec2 wall_size;
+};
+
+u32 gl_shader_program(char* vs, char* fs)
+{
+ int status;
+ char info_log[512];
+
+
+ // =============
+ // vertex shader
+ u32 vertex_shader = glCreateShader(GL_VERTEX_SHADER);
+ glShaderSource(vertex_shader, 1, &vs, NULL);
+ glCompileShader(vertex_shader);
+
+ glGetShaderiv(vertex_shader, GL_COMPILE_STATUS, &status);
+ if (status == 0)
+ {
+ glGetShaderInfoLog(vertex_shader, 512, NULL, info_log);
+ printf("== ERROR: Vertex Shader Compilation Failed ==\n");
+ printf("%s\n", info_log);
+ }
+
+
+ // ===============
+ // fragment shader
+ u32 fragment_shader = glCreateShader(GL_FRAGMENT_SHADER);
+ glShaderSource(fragment_shader, 1, &fs, NULL);
+ glCompileShader(fragment_shader);
+
+ glGetShaderiv(fragment_shader, GL_COMPILE_STATUS, &status);
+ if (status == 0)
+ {
+ glGetShaderInfoLog(fragment_shader, 512, NULL, info_log);
+ printf("== ERROR: Fragment Shader Compilation Failed ==\n");
+ printf("%s\n", info_log);
+ }
+
+
+ // ==============
+ // shader program
+ u32 shader_program = glCreateProgram();
+
+ glAttachShader(shader_program, vertex_shader);
+ glAttachShader(shader_program, fragment_shader);
+ glLinkProgram(shader_program);
+
+ glGetProgramiv(shader_program, GL_LINK_STATUS, &status);
+ if(status == 0)
+ {
+ glGetProgramInfoLog(shader_program, 512, NULL, info_log);
+ printf("== ERROR: Shader Program Linking Failed\n");
+ printf("%s\n", info_log);
+ }
+
+ glDeleteShader(vertex_shader);
+ glDeleteShader(fragment_shader);
+
+ return shader_program;
+}
+
+u32 gl_shader_program_from_path(const char* vspath, const char* fspath)
+{
+ size_t read_count;
+ char* vs = (char*)SDL_LoadFile(vspath, &read_count);
+ if (read_count == 0)
+ {
+ printf("Error! Failed to read vertex shader file at path %s\n", vspath);
+ return 0;
+ }
+
+ char* fs = (char*)SDL_LoadFile(fspath, &read_count);
+ if (read_count == 0)
+ {
+ printf("Error! Failed to read fragment shader file at path %s\n", vspath);
+ return 0;
+ }
+
+ u32 shader_program = gl_shader_program(vs, fs);
+ return shader_program;
+}
+
+u32 gl_setup_colored_quad(u32 sp)
+{
+ // @todo: make this use index buffer maybe?
+ r32 vertices[] = {
+ -1.0f, -1.0f, 0.0f, // bottom-left
+ 1.0f, -1.0f, 0.0f, // bottom-right
+ 1.0f, 1.0f, 0.0f, // top-right
+ 1.0f, 1.0f, 0.0f, // top-right
+ -1.0f, 1.0f, 0.0f, // top-left
+ -1.0f, -1.0f, 0.0f, // bottom-left
+ };
+ u32 vao, vbo;
+ glGenVertexArrays(1, &vao);
+ glGenBuffers(1, &vbo);
+
+ glBindVertexArray(vao);
+ glBindBuffer(GL_ARRAY_BUFFER, vbo);
+ glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), &vertices, GL_STATIC_DRAW);
+ glEnableVertexAttribArray(0);
+ glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(r32), (void*)0);
+
+ glBindVertexArray(0);
+
+ // now return or store the vao, vbo state somewhere
+ return vao;
+}
+
+void gl_draw_colored_quad(
+ GLRenderer* renderer,
+ Vec3 position,
+ Vec2 size,
+ Vec3 color
+) {
+ glEnable(GL_DEPTH_TEST);
+ glUseProgram(renderer->cq_sp);
+ if (renderer->cq_init == 0)
+ {
+ glUniformMatrix4fv(
+ glGetUniformLocation(renderer->cq_sp, "Projection"),
+ 1, GL_TRUE, (renderer->cam_proj).buffer
+ );
+ renderer->cq_init = 1;
+ }
+ // setting quad size
+ Mat4 model = init_value4m(1.0);
+ Mat4 scale = scaling_matrix4m(size.x, size.y, 0.0f);
+ model = multiply4m(scale, model);
+ // setting quad position
+ Mat4 translation = translation_matrix4m(position.x, position.y, position.z);
+ model = multiply4m(translation, model);
+ // setting color
+ glUniform3fv(glGetUniformLocation(renderer->cq_sp, "Color"), 1, color.data);
+
+ glUniformMatrix4fv(
+ glGetUniformLocation(renderer->cq_sp, "Model"),
+ 1, GL_TRUE, model.buffer
+ );
+ glUniformMatrix4fv(
+ glGetUniformLocation(renderer->cq_sp, "View"),
+ 1, GL_TRUE, (renderer->cam_view).buffer
+ );
+
+ glBindVertexArray(renderer->cq_vao);
+ glDrawArrays(GL_TRIANGLES, 0, 6);
+}
+
+void gl_setup_text(TextState* state, FT_Face font_face)
+{
+ FT_Set_Pixel_Sizes(font_face, state->pixel_size, state->pixel_size);
+ glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
+
+ glGenTextures(1, &(state->texture_atlas_id));
+ glBindTexture(GL_TEXTURE_2D_ARRAY, state->texture_atlas_id);
+
+ // generate texture
+ glTexImage3D(
+ GL_TEXTURE_2D_ARRAY,
+ 0,
+ GL_R8,
+ state->pixel_size,
+ state->pixel_size,
+ 128,
+ 0,
+ GL_RED,
+ GL_UNSIGNED_BYTE,
+ 0
+ );
+
+ // generate characters
+ for (u32 c = 0; c < 128; c++)
+ {
+ if (FT_Load_Char(font_face, c, FT_LOAD_RENDER))
+ {
+ printf("ERROR :: Freetype failed to load glyph: %c", c);
+ }
+ else
+ {
+ glTexSubImage3D(
+ GL_TEXTURE_2D_ARRAY,
+ 0,
+ 0, 0, // x, y offset
+ int(c),
+ font_face->glyph->bitmap.width,
+ font_face->glyph->bitmap.rows,
+ 1,
+ GL_RED,
+ GL_UNSIGNED_BYTE,
+ font_face->glyph->bitmap.buffer
+ );
+
+ // set texture options
+ glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+ glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+ glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+ glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+
+ TextChar tc;
+ tc.size = Vec2{
+ (r32)font_face->glyph->bitmap.width,
+ (r32)font_face->glyph->bitmap.rows
+ };
+ tc.bearing = Vec2{
+ (r32)font_face->glyph->bitmap_left,
+ (r32)font_face->glyph->bitmap_top
+ };
+ tc.advance = font_face->glyph->advance.x;
+
+ state->char_map[c] = tc;
+ }
+ }
+
+ glBindTexture(GL_TEXTURE_2D_ARRAY, 0);
+
+ // @note: this data is used for GL_TRIANGLE_STRIP
+ // as such the order for vertices for this is AntiCW -> CW -> AntiCW
+ // that can be seen in this array as it goes from ACW -> CW
+ r32 vertices[] = {
+ 0.0f, 1.0f,
+ 0.0f, 0.0f,
+ 1.0f, 1.0f,
+ 1.0f, 0.0f
+ };
+
+ glGenVertexArrays(1, &(state->vao));
+ glGenBuffers(1, &(state->vbo));
+
+ glBindVertexArray(state->vao);
+ glBindBuffer(GL_ARRAY_BUFFER, state->vbo);
+ glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
+ glEnableVertexAttribArray(0);
+ glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, 0);
+
+ glBindBuffer(GL_ARRAY_BUFFER, 0);
+ glBindVertexArray(0);
+}
+
+void gl_render_text(GLRenderer *renderer, char* text, Vec2 position, r32 size, Vec3 color)
+{
+ glDisable(GL_DEPTH_TEST);
+ glEnable(GL_BLEND);
+ glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+
+ glUseProgram(renderer->ui_text.sp);
+ glUniformMatrix4fv(glGetUniformLocation(renderer->ui_text.sp, "View"),
+ 1, GL_TRUE, renderer->cam_view.buffer);
+ glUniformMatrix4fv(glGetUniformLocation(renderer->ui_text.sp, "Projection"),
+ 1, GL_TRUE, renderer->cam_proj.buffer);
+ glUniform3fv(glGetUniformLocation(renderer->ui_text.sp, "TextColor"), 1, color.data);
+ glBindVertexArray(renderer->ui_text.vao);
+ glBindTexture(GL_TEXTURE_2D_ARRAY, renderer->ui_text.texture_atlas_id);
+ glBindBuffer(GL_ARRAY_BUFFER, renderer->ui_text.vbo);
+ glActiveTexture(GL_TEXTURE0);
+
+ u32 running_index = 0;
+ r32 startx = position.x;
+ r32 starty = position.y;
+ r32 linex = startx;
+ r32 scale = size/renderer->ui_text.pixel_size;
+ memset(renderer->ui_text.transforms, 0, renderer->ui_text.chunk_size);
+ memset(renderer->ui_text.char_indexes, 0, renderer->ui_text.chunk_size);
+
+ char *char_iter = text;
+ while (*char_iter != '\0')
+ {
+ TextChar render_char = renderer->ui_text.char_map[*char_iter];
+ if (*char_iter == '\n')
+ {
+ linex = startx;
+ starty = starty - (render_char.size.y * 1.5 * scale);
+ }
+ else if (*char_iter == ' ')
+ {
+ linex += (render_char.advance >> 6) * scale;
+ }
+ else
+ {
+ r32 xpos = linex + (scale * render_char.bearing.x);
+ r32 ypos = starty - (renderer->ui_text.pixel_size - render_char.bearing.y) * scale;
+
+ r32 w = scale * renderer->ui_text.pixel_size;
+ r32 h = scale * renderer->ui_text.pixel_size;
+
+ Mat4 sm = scaling_matrix4m(w, h, 0);
+ Mat4 tm = translation_matrix4m(xpos, ypos, 0);
+ Mat4 model = multiply4m(tm, sm);
+ renderer->ui_text.transforms[running_index] = model;
+ renderer->ui_text.char_indexes[running_index] = int(*char_iter);
+
+ linex += (render_char.advance >> 6) * scale;
+ running_index++;
+ if (running_index > renderer->ui_text.chunk_size - 1)
+ {
+ r32 transform_loc = glGetUniformLocation(renderer->ui_text.sp, "LetterTransforms");
+ glUniformMatrix4fv(transform_loc, renderer->ui_text.chunk_size,
+ GL_TRUE, &(renderer->ui_text.transforms[0].buffer[0]));
+ r32 texture_map_loc = glGetUniformLocation(renderer->ui_text.sp, "TextureMap");
+ glUniform1iv(texture_map_loc, renderer->ui_text.chunk_size, renderer->ui_text.char_indexes);
+ glDrawArraysInstanced(GL_TRIANGLE_STRIP, 0, 4, renderer->ui_text.chunk_size);
+ running_index = 0;
+ memset(renderer->ui_text.transforms, 0, renderer->ui_text.chunk_size);
+ memset(renderer->ui_text.char_indexes, 0, renderer->ui_text.chunk_size);
+ }
+ }
+ char_iter++;
+ }
+ if (running_index > 0)
+ {
+ u32 render_count = running_index < renderer->ui_text.chunk_size ? running_index : renderer->ui_text.chunk_size;
+ r32 transform_loc = glGetUniformLocation(renderer->ui_text.sp, "LetterTransforms");
+ glUniformMatrix4fv(transform_loc, render_count,
+ GL_TRUE, &(renderer->ui_text.transforms[0].buffer[0]));
+ r32 texture_map_loc = glGetUniformLocation(renderer->ui_text.sp, "TextureMap");
+ glUniform1iv(texture_map_loc, render_count, renderer->ui_text.char_indexes);
+ glDrawArraysInstanced(GL_TRIANGLE_STRIP, 0, 4, render_count);
+ running_index = 0;
+ memset(renderer->ui_text.transforms, 0, render_count);
+ memset(renderer->ui_text.char_indexes, 0, render_count);
+ }
+}
+
+int main(int argc, char* argv[])
+{
+ u32 scr_width = 1024;
+ u32 scr_height = 768;
+
+ if (SDL_Init(SDL_INIT_VIDEO) != 0)
+ {
+ printf("Error initialising SDL2: %s\n", SDL_GetError());
+ return -1;
+ }
+
+ 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);
+
+ SDL_Window* window = SDL_CreateWindow("simple platformer",
+ SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED,
+ scr_width, scr_height,
+ SDL_WINDOW_OPENGL);
+ SDL_GLContext context = SDL_GL_CreateContext(window);
+ if (!context)
+ {
+ printf("ERROR :: OpenGL context creation failed: %s\n", SDL_GetError());
+ return -1;
+ }
+
+ // load glad
+ if (!gladLoadGLLoader((GLADloadproc)SDL_GL_GetProcAddress)) {
+ printf("ERROR :: Failed to initialize Glad\n");
+ return -1;
+ }
+
+ // vsync controls: 0 = OFF | 1 = ON (Default)
+ SDL_GL_SetSwapInterval(1);
+
+ size_t read_count;
+ u32 quad_sp = gl_shader_program_from_path(
+ "./source/shaders/colored_quad.vs.glsl",
+ "./source/shaders/colored_quad.fs.glsl"
+ );
+ u32 ui_text_sp = gl_shader_program_from_path(
+ "./source/shaders/ui_text.vs.glsl",
+ "./source/shaders/ui_text.fs.glsl"
+ );
+ u32 quad_vao = gl_setup_colored_quad(quad_sp);
+
+ GLRenderer renderer;
+ renderer.cq_sp = quad_sp;
+ renderer.cq_vao = quad_vao;
+
+ // ==========
+ // setup text
+ // 1. setup free type library stuff
+ FT_Library ft_lib;
+ FT_Face roboto_font_face;
+ if (FT_Init_FreeType(&ft_lib))
+ {
+ printf("ERROR :: Could not init freetype library\n");
+ return -1;
+ }
+
+ FT_Error error = FT_New_Face(
+ ft_lib,
+ "assets/fonts/Roboto.ttf",
+ 0,
+ &roboto_font_face
+ );
+ if (error == FT_Err_Unknown_File_Format)
+ {
+ printf("ERROR :: Font Loading Failed. The font format is unsupported\n");
+ return -1;
+ }
+ else if (error)
+ {
+ printf("ERROR :: Font Loading Failed. Unknown error code: %d\n", error);
+ return -1;
+ }
+ // 2. setup gl text
+ // @note: we only support 128 characters, which is the basic ascii set
+ renderer.ui_text.chunk_size = 32;
+ renderer.ui_text.pixel_size = 512;
+ renderer.ui_text.sp = ui_text_sp;
+ renderer.ui_text.transforms = (Mat4*)malloc(
+ renderer.ui_text.chunk_size*sizeof(Mat4)
+ );
+ renderer.ui_text.char_indexes = (s32*)malloc(
+ renderer.ui_text.chunk_size*sizeof(s32)
+ );
+ renderer.ui_text.char_map = (TextChar*)malloc(
+ 128*sizeof(TextChar)
+ );
+ gl_setup_text(&(renderer.ui_text), roboto_font_face);
+
+
+ // ============
+ // setup camera
+ Vec3 preset_up_dir = Vec3{0.0f, 1.0f, 0.0f};
+ renderer.cam_update = 1;
+ renderer.cam_pos = Vec3{0.0f, 0.0f, 1.0f};
+ renderer.cam_look = camera_look_around(To_Radian(0.0f), -To_Radian(90.0f));
+ renderer.cam_view = camera_create4m(
+ renderer.cam_pos,
+ add3v(renderer.cam_pos, renderer.cam_look), preset_up_dir
+ );
+ renderer.cam_proj = orthographic_projection4m(
+ 0.0f, (r32)scr_width*1.5f,
+ 0.0f, (r32)scr_height*1.5f,
+ 0.1f, 10.0f
+ );
+
+ // ======
+ // player
+ r32 fall_velocity = 3.0f;
+ r32 fall_decel = -6.0f;
+ r32 move_velocity = 6.0f;
+ r32 move_accel = 6.0f;
+ r32 move_decel = -8.0f;
+ GameState state = {0};
+ state.p_jump_period = 1.0f;
+ state.player_position = Vec3{0.0f, 70.0f, -1.0f};
+ state.player_size = Vec2{40.0f, 40.0f};
+ state.floor_position = Vec3{600.0f, 800.0f, -2.0f};
+ state.floor_size = Vec2{400.0f, 20.0f};
+ state.wall_position = Vec3{170.0f, 100.0f, -1.0f};
+ state.wall_size = Vec2{20.0f, 80.0f};
+ Controller controller = {0};
+ r32 key_down_time[5] = {0.0f, 0.0f, 0.0f, 0.0f, 0.0f};
+
+ // gravity calculations
+ b8 collidex = 0;
+ b8 collidey = 0;
+ b8 activate_gravity = 0;
+ Vec2 player_move_t = Vec2{0.0f, 0.0f};
+ Vec2 player_decel_t = Vec2{0.0f, 0.0f};
+ b8 is_player_moving = false;
+
+ // player force variables
+ r32 gravity = -9.8f;
+ r32 player_mass = 1.0f;
+ Vec2 player_acceleration = Vec2{0.0f, 0.0f};
+ Vec2 player_velocity = Vec2{0.0f, 0.0f};
+ Vec2 position_displacement = Vec2{0.0f, 0.0f};
+ r32 force_factor = 1.0f;
+ PMoveState p_move_state = NO_MOVE;
+ Vec2 p_move_dir_old = Vec2{0.0f, 0.0f};
+ Vec2 p_move_dir = Vec2{0.0f, 0.0f};
+
+ b8 cndbrk = 0; // a conditional debug trick in code
+ b8 game_running = 1;
+ r32 time_prev = SDL_GetTicks64() / 1000.0f;
+ r32 time_curr = time_prev;
+ r32 time_delta = time_curr - time_prev;
+ while (game_running)
+ {
+ time_prev = time_curr;
+ time_curr = SDL_GetTicks64() / 1000.0f;
+ time_delta = time_curr - time_prev;
+ SDL_Event ev;
+ while(SDL_PollEvent(&ev))
+ {
+ switch(ev.type)
+ {
+ case (SDL_QUIT):
+ {
+ game_running = 0;
+ } break;
+ case (SDL_KEYDOWN):
+ {
+ if (ev.key.keysym.sym == SDLK_w)
+ {
+ controller.move_up = 1;
+ }
+ if (ev.key.keysym.sym == SDLK_a)
+ {
+ controller.move_left = 1;
+ key_down_time[PK_A] = time_curr;
+ }
+ if (ev.key.keysym.sym == SDLK_s)
+ {
+ controller.move_down = 1;
+ }
+ if (ev.key.keysym.sym == SDLK_d)
+ {
+ controller.move_right = 1;
+ key_down_time[PK_D] = time_curr;
+ }
+ if (ev.key.keysym.sym == SDLK_SPACE)
+ {
+ controller.jump = 1;
+ }
+ } break;
+ case (SDL_KEYUP):
+ {
+ if (ev.key.keysym.sym == SDLK_w)
+ {
+ controller.move_up = 0;
+ }
+ if (ev.key.keysym.sym == SDLK_a)
+ {
+ controller.move_left = 0;
+ key_down_time[PK_A] = 0.0f;
+ }
+ if (ev.key.keysym.sym == SDLK_s)
+ {
+ controller.move_down = 0;
+ }
+ if (ev.key.keysym.sym == SDLK_d)
+ {
+ controller.move_right = 0;
+ key_down_time[PK_D] = 0.0f;
+ }
+ } break;
+ default:
+ {
+ break;
+ }
+ }
+ }
+
+ // @section: input processing
+ // @note: because of how I am handling the different states
+ // of movement, there is a bug where if I change direction
+ // the wrong motion gets considered, that needs to be fixed
+ Vec2 player_force = Vec2{0.0f, 0.0f};
+ if (controller.move_up)
+ {
+ player_force.y = force_factor;
+ p_move_dir.y = 1.0f;
+ }
+ if (controller.move_down)
+ {
+ player_force.y = -force_factor;
+ p_move_dir.y = -1.0f;
+ }
+
+ PlatformKey horizontal_move = PK_NIL;
+ is_player_moving = false;
+ if (
+ key_down_time[PK_A] != 0.0f ||
+ key_down_time[PK_D] != 0.0f
+ ) {
+ horizontal_move = (
+ key_down_time[PK_A] > key_down_time[PK_D] ? PK_A : PK_D
+ );
+ }
+
+ if (horizontal_move == PK_A && controller.move_left)
+ {
+ player_force.x = -force_factor;
+ p_move_dir.x = -1.0f;
+ is_player_moving = true;
+ }
+ if (horizontal_move == PK_D && controller.move_right)
+ {
+ player_force.x = force_factor;
+ p_move_dir.x = 1.0f;
+ is_player_moving = true;
+ }
+
+ if (controller.jump)
+ {
+ // activate gravity
+ controller.jump = 0;
+ activate_gravity = !activate_gravity;
+ player_velocity = Vec2{0.0f, 0.0f};
+ p_move_dir.x = 0.0f;
+ }
+
+ b8 was_moving_on_ground = p_move_state == ACCEL || p_move_state == KEEP;
+ if (activate_gravity) {
+ b8 is_move_dir_changed = p_move_dir.x != p_move_dir_old.x;
+ if (!collidey) {
+ // fall states
+ if (is_player_moving) {
+ if (is_move_dir_changed) {
+ p_move_state = FALL_KEEP;
+ } else if (
+ was_moving_on_ground || p_move_state == FALL_NORM
+ ) {
+ p_move_state = FALL_NORM;
+ } else if (p_move_state != FALL_KEEP) {
+ p_move_state = FALL_KEEP;
+ }
+ } else {
+ if (p_move_state == NO_MOVE) {
+ p_move_state = FALL_FREE;
+ }
+ if (p_move_state != FALL_FREE) {
+ p_move_state = FALL_DECEL;
+ }
+ }
+ } else {
+ // standard motion states
+ if (is_player_moving) {
+ if (p_move_state != KEEP || is_move_dir_changed) {
+ p_move_state = ACCEL;
+ }
+ } else {
+ if (p_move_state == FALL_FREE) {
+ p_move_state = NO_MOVE;
+ }
+ if (p_move_state != NO_MOVE) {
+ p_move_state = DECEL;
+ }
+ }
+ }
+ }
+
+ // @section: gravity
+ p_move_dir_old = p_move_dir;
+ Vec2 pd_1 = Vec2{0.0f, 0.0f};
+ r32 accel_computed = 0.0f;
+ if (collidey)
+ {
+ player_move_t.y = 0.0f;
+ player_velocity.y = 0.0f;
+ }
+ if (collidex)
+ {
+ player_move_t.x = 0.0f;
+ player_velocity.x = 0.0f;
+ }
+ if (activate_gravity)
+ {
+ if (!collidey) {
+ // vertical motion when falling
+ r32 dy1 = player_velocity.y;
+ dy1 = dy1 + (-9.8f)*time_delta;
+ player_velocity.y = dy1;
+ pd_1.y = dy1;
+ }
+
+ // @note: define equations of motion for player horizontal movement
+ // phase 1: ramp up
+ // y = x => v = v0 + at (v0 = 0)
+ // y = 4 => v = 4 (a = 0, v0 = 4)
+ // 4 = -1.5x + 1.5
+
+ if (!collidex) {
+ r32 dx1 = player_velocity.x;
+ switch (p_move_state) {
+ case ACCEL:
+ {
+ dx1 += move_accel*time_delta*p_move_dir.x;
+ } break;
+ case KEEP:
+ {
+ dx1 = move_velocity*p_move_dir.x;
+ } break;
+ case DECEL:
+ {
+ dx1 += move_decel*time_delta*p_move_dir.x;
+ } break;
+ case FALL_NORM:
+ {
+ if (ABS(player_velocity.x) <= fall_velocity) {
+ dx1 = fall_velocity*p_move_dir.x;
+ } else {
+ dx1 -= 2.0f*time_delta*p_move_dir.x;
+ }
+ } break;
+ case FALL_KEEP:
+ {
+ dx1 = fall_velocity*p_move_dir.x;
+ } break;
+ case FALL_DECEL:
+ {
+ dx1 += fall_decel*time_delta*p_move_dir.x;
+ } break;
+ default:
+ {
+ } break;
+ }
+ // checks for motion on ground
+ if (ABS(dx1) <= 0.5f && p_move_state == DECEL) {
+ p_move_state = NO_MOVE;
+ p_move_dir.x = 0.0f;
+ dx1 = 0.0f;
+ }
+ if (ABS(dx1) >= move_velocity && was_moving_on_ground) {
+ p_move_state = KEEP;
+ }
+
+ // checks for motion in air
+ if (ABS(dx1) <= fall_velocity && p_move_state == FALL_NORM) {
+ p_move_state = FALL_KEEP;
+ }
+ if (ABS(dx1) <= 0.5f && p_move_state == FALL_DECEL) {
+ p_move_state = FALL_FREE;
+ p_move_dir.x = 0.0f;
+ dx1 = 0.0f;
+ }
+
+ accel_computed = (dx1 - player_velocity.x)/time_delta;
+ player_velocity.x = dx1;
+ pd_1.x = dx1;
+ }
+ }
+ else
+ {
+ pd_1 = mul2vf(player_force, 8.0f);
+ }
+
+ // @section: collision
+ // player
+
+ // @note:
+ //
+ // Approach 1
+ // 1. check if player colliding with floor using AABB collision
+ // 2. check if player sides colliding with left of floor
+ // 2.1. clamp the player next position with bounds
+ // (side-effect) => player horizontal velocity sets to 0
+ // 3. check if player sides colliding with right of floor
+ // 3.1. clamp the player next position with bounds
+ // (side-effect) => player horizontal velocity sets to 0
+ // 4. if neither sides collide then that means it was colliding vertically
+ // (with top or bottom)
+ // 4.1. clamp the player position
+ // 5 if no collision y, update player vertical position
+ // 5.1. if no y collision and also no x collision, update x position
+ //
+ // @verdict:
+ // This absolutely sucks, this is awful and downright insane, still it led
+ // me to the next discovery.
+ //
+ // @todo:
+ // Approach 2
+ // Use surface normals, and direction vectors. Figure out this math part
+ // and how exactly those would be computed or would matter, but the main
+ // point is, that would allow a far better approach to detect collisions than
+ // whatever I am doing right now.
+
+ Vec2 next_player_position = Vec2{
+ state.player_position.x,
+ state.player_position.y
+ };
+ next_player_position = next_player_position + pd_1;
+
+ // calculate_position_bounds player
+ r32 p_left = next_player_position.x - state.player_size.x;
+ r32 p_right = next_player_position.x + state.player_size.x;
+ r32 p_top = next_player_position.y + state.player_size.y;
+ r32 p_bottom = next_player_position.y - state.player_size.y;
+ // calculate_position_bounds floor
+ r32 f_left = state.floor_position.x - state.floor_size.x;
+ r32 f_right = state.floor_position.x + state.floor_size.x;
+ r32 f_top = state.floor_position.y + state.floor_size.y;
+ r32 f_bottom = state.floor_position.y - state.floor_size.y;
+
+#if leniant_platform_collision_check
+ b8 is_bottom_on_obj_top = (p_bottom <= f_top + 5.0f) &&
+ (p_bottom >= f_top - 5.0f);
+ b8 is_top_on_obj_bottom = (p_top <= f_bottom + 5.0f) &&
+ (p_top >= f_bottom - 5.0f);
+#endif
+ b8 is_bottom_on_obj_top = p_bottom == f_top;
+ b8 is_top_on_obj_bottom = p_top == f_bottom;
+ b8 can_slidex = (p_left < f_right || p_right > f_left) &&
+ (is_bottom_on_obj_top || is_top_on_obj_bottom);
+
+#if wall_collision_check
+ r32 w_left = state.wall_position.x - state.wall_size.x;
+ r32 w_right = state.wall_position.x + state.wall_size.x;
+ r32 w_top = state.wall_position.y + state.wall_size.y;
+ r32 w_bottom = state.wall_position.y - state.wall_size.y;
+ b8 wall_colliding = !(p_left > w_right || p_right < w_left || p_bottom > w_top || p_top < w_bottom);
+#endif
+
+ b8 floor_colliding = !(
+ p_left > f_right || p_right < f_left ||
+ p_bottom > f_top || p_top < f_bottom
+ );
+
+ b8 new_collidex = 0;
+ b8 new_collidey = 0;
+
+ if (floor_colliding) {
+ // check which side we are colliding with
+ b8 collide_left = p_right <= f_left + 5.0f;
+ b8 collide_right = p_left >= f_right - 5.0f;
+ if (collide_left) {
+ state.player_position.x = clampf(
+ next_player_position.x,
+ 0.0f,
+ f_left - state.player_size.x
+ );
+ new_collidex = 1;
+ } else if (collide_right) {
+ state.player_position.x = clampf(
+ next_player_position.x,
+ f_right + state.player_size.x,
+ 1.5*scr_width
+ );
+ new_collidex = 1;
+ } else {
+ new_collidey = 1;
+ }
+ state.player_position.y = clampf(
+ state.player_position.y,
+ f_bottom - state.player_size.y,
+ f_top + state.player_size.y
+ );
+ }
+ if (!new_collidey) {
+ state.player_position.y = next_player_position.y;
+ if (!new_collidex) {
+ state.player_position.x = next_player_position.x;
+ }
+ }
+
+ if (can_slidex) {
+ state.player_position.x = next_player_position.x;
+ }
+ collidex = new_collidex;
+ collidey = new_collidey;
+
+ // output
+ glClearColor(0.8f, 0.5f, 0.7f, 1.0f);
+ glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+
+ // floor
+ gl_draw_colored_quad(&renderer,
+ state.floor_position,
+ state.floor_size,
+ Vec3{1.0f, 1.0f, 1.0f});
+ // wall
+ gl_draw_colored_quad(&renderer,
+ state.wall_position,
+ state.wall_size,
+ Vec3{1.0f, 0.0f, 0.0f});
+ // player
+ gl_draw_colored_quad(&renderer,
+ state.player_position, // position
+ state.player_size, // size
+ Vec3{0.45f, 0.8f, 0.2f});
+
+ // render ui text
+ gl_render_text(&renderer,
+ "hello sailor!",
+ Vec2{30.0f, 700.0f}, // position
+ 28.0f, // size
+ Vec3{0.0f, 0.0f, 0.0f}); // color
+
+ if (new_collidex || new_collidey)
+ {
+ gl_render_text(&renderer,
+ "is colliding",
+ Vec2{500.0f, 700.0f}, // position
+ 28.0f, // size
+ Vec3{0.0f, 0.0f, 0.0f}); // color
+
+ char movedir_output[50];
+ sprintf(movedir_output, "move_dir = %f", p_move_dir.x);
+ gl_render_text(&renderer,
+ movedir_output,
+ Vec2{500.0f, 60.0f}, // position
+ 28.0f, // size
+ Vec3{0.0f, 0.0f, 0.0f}); // color
+
+ char speed_output[50];
+ sprintf(speed_output, "%f pps", player_velocity.x);
+ gl_render_text(&renderer,
+ speed_output,
+ Vec2{500.0f, 100.0f}, // position
+ 28.0f, // size
+ Vec3{0.0f, 0.0f, 0.0f}); // color
+
+ char accel_output[50];
+ sprintf(accel_output, "%f pps^2", accel_computed);
+ gl_render_text(&renderer,
+ accel_output,
+ Vec2{500.0f, 150.0f}, // position
+ 28.0f, // size
+ Vec3{0.0f, 0.0f, 0.0f}); // color
+ }
+ char move_state_output[50];
+ switch(p_move_state) {
+ case ACCEL:
+ {
+ sprintf(move_state_output, "move_dir = ACCEL");
+ } break;
+ case KEEP:
+ {
+ sprintf(move_state_output, "move_dir = KEEP");
+ } break;
+ case DECEL:
+ {
+ sprintf(move_state_output, "move_dir = DECEL");
+ } break;
+ case FALL_NORM:
+ {
+ sprintf(move_state_output, "move_dir = FALL_NORM");
+ } break;
+ case FALL_KEEP:
+ {
+ sprintf(move_state_output, "move_dir = FALL_KEEP");
+ } break;
+ case FALL_DECEL:
+ {
+ sprintf(move_state_output, "move_dir = FALL_DECEL");
+ } break;
+ case NO_MOVE:
+ {
+ sprintf(move_state_output, "move_dir = NO_MOVE");
+ } break;
+ case FALL_FREE:
+ {
+ sprintf(move_state_output, "move_dir = FALL_FREE");
+ } break;
+ default:
+ break;
+ }
+ gl_render_text(&renderer,
+ move_state_output,
+ Vec2{900.0f, 60.0f}, // position
+ 28.0f, // size
+ Vec3{0.0f, 0.0f, 0.0f}); // color
+
+ char fmt_buffer[50];
+ sprintf(fmt_buffer, "player moving? %d", is_player_moving);
+ gl_render_text(&renderer,
+ fmt_buffer,
+ Vec2{900.0f, 40.0f}, // position
+ 28.0f, // size
+ Vec3{0.0f, 0.0f, 0.0f}); // color
+
+ sprintf(fmt_buffer, "%f pixels", pd_1.x);
+ gl_render_text(&renderer,
+ fmt_buffer,
+ Vec2{500.0f, 200.0f}, // position
+ 28.0f, // size
+ Vec3{0.0f, 0.0f, 0.0f}); // color
+
+ sprintf(fmt_buffer, "can_slide = %d", can_slidex);
+ gl_render_text(&renderer,
+ fmt_buffer,
+ Vec2{200.0f, 200.0f}, // position
+ 28.0f, // size
+ Vec3{0.0f, 0.0f, 0.0f}); // color
+ sprintf(fmt_buffer, "collide x: %d, y: %d", collidex, collidey);
+ gl_render_text(&renderer,
+ fmt_buffer,
+ Vec2{200.0f, 600.0f}, // position
+ 28.0f, // size
+ Vec3{0.0f, 0.0f, 0.0f}); // color
+ if (activate_gravity)
+ {
+ gl_render_text(&renderer,
+ "gravity=1",
+ Vec2{650.0f, 700.0f},
+ 18.0f,
+ Vec3{0.2f, 0.8f, 0.0f});
+ }
+ SDL_GL_SwapWindow(window);
+ }
+
+ free(renderer.ui_text.transforms);
+ free(renderer.ui_text.char_indexes);
+ free(renderer.ui_text.char_map);
+ SDL_GL_DeleteContext(context);
+ SDL_DestroyWindow(window);
+ SDL_Quit();
+ return 0;
+}
diff --git a/source/math.h b/source/math.h
new file mode 100755
index 0000000..cf04d54
--- /dev/null
+++ b/source/math.h
@@ -0,0 +1,463 @@
+#ifndef MATH_H
+#define MATH_H
+
+#define PI 3.14159265358979323846264338327950288f
+#define Square(x) ((x)*(x))
+#define To_Radian(x) ((x) * PI / 180.0f)
+#define To_Degree(x) ((x) * 180.0f / PI)
+#define ABS(x) ((x) < 0 ? (-(x)) : (x))
+
+// @todo:
+// - convert these to column major calculations for the opengl path
+// - make everything simd
+
+r32 clampf(r32 x, r32 bottom, r32 top)
+{
+ if (x < bottom)
+ {
+ x = bottom;
+ }
+ else if (x > top)
+ {
+ x = top;
+ }
+
+ return x;
+}
+
+// ==== Vector Math ====
+
+union Vec2 {
+ struct {
+ r32 x;
+ r32 y;
+ };
+ r32 data[2];
+
+ Vec2 operator+(const r32& scaler) const {
+ Vec2 res;
+ res.x = this->x + scaler;
+ res.y = this->y + scaler;
+
+ return res;
+ }
+
+ Vec2 operator+(const Vec2& v) const {
+ Vec2 res;
+ res.x = this->x + v.x;
+ res.y = this->y + v.y;
+
+ return res;
+ }
+};
+
+union Vec3 {
+ struct {
+ r32 x;
+ r32 y;
+ r32 z;
+ };
+ r32 data[3];
+};
+
+union Vec4 {
+ struct {
+ r32 x;
+ r32 y;
+ r32 z;
+ r32 w;
+ };
+ r32 data[4];
+};
+
+union Mat4 {
+ Vec4 xyzw[4];
+ r32 data[4][4];
+ r32 buffer[16];
+};
+
+// ==== Vec2 ====
+
+Vec2 mul2vf(Vec2 vec, r32 scaler)
+{
+ Vec2 res;
+ res.x = vec.x * scaler;
+ res.y = vec.y * scaler;
+
+ return res;
+}
+
+Vec2 div2vf(Vec2 vec, r32 scaler)
+{
+ SDL_assert(scaler != 0);
+ Vec2 res;
+ res.x = vec.x / scaler;
+ res.y = vec.y / scaler;
+
+ return res;
+}
+
+// ========================================================== Vec3 ==========================================================
+
+Vec3 init3v(r32 x, r32 y, r32 z)
+{
+ Vec3 res;
+ res.x = x;
+ res.y = y;
+ res.z = z;
+
+ return res;
+}
+
+Vec3 scaler_add3v(Vec3 vec, r32 scaler)
+{
+ Vec3 res;
+ res.x = vec.x + scaler;
+ res.y = vec.y + scaler;
+ res.z = vec.z + scaler;
+
+ return res;
+}
+
+Vec3 scaler_multiply3v(Vec3 vec, r32 scaler)
+{
+ Vec3 res;
+ res.x = vec.x * scaler;
+ res.y = vec.y * scaler;
+ res.z = vec.z * scaler;
+
+ return res;
+}
+
+Vec3 scaler_divide3v(Vec3 vec, r32 scaler)
+{
+ Vec3 res;
+ res.x = vec.x / scaler;
+ res.y = vec.y / scaler;
+ res.z = vec.z / scaler;
+
+ return res;
+}
+
+
+Vec3 add3v(Vec3 a, Vec3 b)
+{
+ Vec3 res;
+ res.x = a.x + b.x;
+ res.y = a.y + b.y;
+ res.z = a.z + b.z;
+
+ return res;
+}
+
+Vec3 subtract3v(Vec3 a, Vec3 b)
+{
+ Vec3 res;
+ res.x = a.x - b.x;
+ res.y = a.y - b.y;
+ res.z = a.z - b.z;
+
+ return res;
+}
+
+r32 dot_multiply3v(Vec3 a, Vec3 b)
+{
+ r32 x = a.x * b.x;
+ r32 y = a.y * b.y;
+ r32 z = a.z * b.z;
+
+ r32 res = x + y + z;
+
+ return res;
+}
+
+r32 magnitude3v(Vec3 vec)
+{
+ r32 res = sqrtf(Square(vec.x) + Square(vec.y) + Square(vec.z));
+ return res;
+}
+
+Vec3 normalize3v(Vec3 vec)
+{
+ r32 magnitude = magnitude3v(vec);
+ Vec3 res = scaler_divide3v(vec, magnitude);
+ return res;
+}
+
+Vec3 cross_multiply3v(Vec3 a, Vec3 b)
+{
+ Vec3 res;
+ res.x = (a.y * b.z) - (a.z * b.y);
+ res.y = (a.z * b.x) - (a.x * b.z);
+ res.z = (a.x * b.y) - (a.y * b.x);
+
+ return res;
+}
+
+// ============================================== Vec4, Mat4 ==============================================
+
+Vec4 init4v(r32 x, r32 y, r32 z, r32 w)
+{
+ Vec4 res;
+ res.x = x;
+ res.y = y;
+ res.z = z;
+ res.w = w;
+
+ return res;
+}
+
+Mat4 init_value4m(r32 value)
+{
+ Mat4 res = {0};
+ res.data[0][0] = value;
+ res.data[1][1] = value;
+ res.data[2][2] = value;
+ res.data[3][3] = value;
+
+ return res;
+}
+
+// @note: These operations are just defined and not expressed. They are kept here for completeness sake BUT
+// since I have not had to do anything related to these, I have not created them.
+Vec4 scaler_add4v(Vec4 vec, r32 scaler);
+Vec4 scaler_subtract4v(Vec4 vec, r32 scaler);
+Vec4 scaler_multiply4v(Vec4 vec, r32 scaler);
+Vec4 scaler_divide4v(Vec4 vec, r32 scaler);
+Vec4 add4v(Vec4 a, Vec4 b);
+Vec4 subtract4v(Vec4 a, Vec4 b);
+Vec4 dot_multiply4v(Vec4 a, Vec4 b);
+
+Mat4 add4m(Mat4 a, Mat4 b)
+{
+ Mat4 res;
+ // row 0
+ res.data[0][0] = a.data[0][0] + b.data[0][0];
+ res.data[0][1] = a.data[0][1] + b.data[0][1];
+ res.data[0][2] = a.data[0][2] + b.data[0][2];
+ res.data[0][3] = a.data[0][3] + b.data[0][3];
+ // row 1
+ res.data[1][0] = a.data[1][0] + b.data[1][0];
+ res.data[1][1] = a.data[1][1] + b.data[1][1];
+ res.data[1][2] = a.data[1][2] + b.data[1][2];
+ res.data[1][3] = a.data[1][3] + b.data[1][3];
+ // row 2
+ res.data[2][0] = a.data[2][0] + b.data[2][0];
+ res.data[2][1] = a.data[2][1] + b.data[2][1];
+ res.data[2][2] = a.data[2][2] + b.data[2][2];
+ res.data[2][3] = a.data[2][3] + b.data[2][3];
+ // row 3
+ res.data[3][0] = a.data[3][0] + b.data[3][0];
+ res.data[3][1] = a.data[3][1] + b.data[3][1];
+ res.data[3][2] = a.data[3][2] + b.data[3][2];
+ res.data[3][3] = a.data[3][3] + b.data[3][3];
+
+ return res;
+}
+
+Mat4 subtract4m(Mat4 a, Mat4 b)
+{
+ Mat4 res;
+ // row 0
+ res.data[0][0] = a.data[0][0] - b.data[0][0];
+ res.data[0][1] = a.data[0][1] - b.data[0][1];
+ res.data[0][2] = a.data[0][2] - b.data[0][2];
+ res.data[0][3] = a.data[0][3] - b.data[0][3];
+ // row 1
+ res.data[1][0] = a.data[1][0] - b.data[1][0];
+ res.data[1][1] = a.data[1][1] - b.data[1][1];
+ res.data[1][2] = a.data[1][2] - b.data[1][2];
+ res.data[1][3] = a.data[1][3] - b.data[1][3];
+ // row 2
+ res.data[2][0] = a.data[2][0] - b.data[2][0];
+ res.data[2][1] = a.data[2][1] - b.data[2][1];
+ res.data[2][2] = a.data[2][2] - b.data[2][2];
+ res.data[2][3] = a.data[2][3] - b.data[2][3];
+ // row 3
+ res.data[3][0] = a.data[3][0] - b.data[3][0];
+ res.data[3][1] = a.data[3][1] - b.data[3][1];
+ res.data[3][2] = a.data[3][2] - b.data[3][2];
+ res.data[3][3] = a.data[3][3] - b.data[3][3];
+
+ return res;
+}
+
+Vec4 multiply4vm(Vec4 vec, Mat4 mat)
+{
+ /*
+ * @note: Incase I get confused about this in the future.
+ *
+ * Everything is row-order, which means that things in memory are laid out row first. So with a sample matrix
+ * we have this order in memory: r1c1 r1c2 r1c3 r1c4 r2c1 ... (r = row, c = column). The same holds true for
+ * vectors. (maybe move this explanation to the top)
+ *
+ * Now, multiply4vm will multiply a vector with a matrix. Conventionally that does not make any sense as
+ * a vector is usually 4x1 and a matrix ix 4x4.
+ * What this function considers a vector, while it is a vector, it is infact a row from a matrix, which
+ * means that the vector is 1x4 and the matrix is 4x4.
+ *
+ * The function is meant to supplement the matrix multiplication process to alleviate the multiple lines of code
+ * we have to write when multiplying the row of a left matrix to each column of the right matrix
+ */
+ Vec4 res = { 0 };
+ res.x = (mat.data[0][0] * vec.x) + (mat.data[0][1] * vec.y) + (mat.data[0][2] * vec.z) + (mat.data[0][3] * vec.w);
+ res.y = (mat.data[1][0] * vec.x) + (mat.data[1][1] * vec.y) + (mat.data[1][2] * vec.z) + (mat.data[1][3] * vec.w);
+ res.z = (mat.data[2][0] * vec.x) + (mat.data[2][1] * vec.y) + (mat.data[2][2] * vec.z) + (mat.data[2][3] * vec.w);
+ res.w = (mat.data[3][0] * vec.x) + (mat.data[3][1] * vec.y) + (mat.data[3][2] * vec.z) + (mat.data[3][3] * vec.w);
+
+ return res;
+}
+
+Mat4 multiply4m(Mat4 a, Mat4 b)
+{
+ Mat4 res = { 0 };
+
+ res.xyzw[0] = multiply4vm(a.xyzw[0], b);
+ res.xyzw[1] = multiply4vm(a.xyzw[1], b);
+ res.xyzw[2] = multiply4vm(a.xyzw[2], b);
+ res.xyzw[3] = multiply4vm(a.xyzw[3], b);
+
+ return res;
+}
+
+// ==== Matrix Transformation ====
+
+Mat4 scaling_matrix4m(r32 x, r32 y, r32 z) // generates a 4x4 scaling matrix for scaling each of the x,y,z axis
+{
+ Mat4 res = init_value4m(1.0f);
+ res.data[0][0] = x;
+ res.data[1][1] = y;
+ res.data[2][2] = z;
+
+ return res;
+}
+
+Mat4 translation_matrix4m(r32 x, r32 y, r32 z) // generates a 4x4 translation matrix for translation along each of the x,y,z axis
+{
+ Mat4 res = init_value4m(1.0f);
+ res.data[0][3] = x;
+ res.data[1][3] = y;
+ res.data[2][3] = z;
+
+ return res;
+}
+
+Mat4 rotation_matrix4m(r32 angle_radians, Vec3 axis) // generates a 4x4 rotation matrix for rotation along each of the x,y,z axis
+{
+ Mat4 res = init_value4m(1.0f);
+ axis = normalize3v(axis);
+
+ r32 cos_theta = cosf(angle_radians);
+ r32 sin_theta = sinf(angle_radians);
+ r32 cos_value = 1.0f - cos_theta;
+
+ res.data[0][0] = (axis.x * axis.x * cos_value) + cos_theta;
+ res.data[0][1] = (axis.x * axis.y * cos_value) + (axis.z * sin_theta);
+ res.data[0][2] = (axis.x * axis.z * cos_value) - (axis.y * sin_theta);
+
+ res.data[1][0] = (axis.x * axis.y * cos_value) - (axis.z * sin_theta);
+ res.data[1][1] = (axis.y * axis.y * cos_value) + cos_theta;
+ res.data[1][2] = (axis.y * axis.z * cos_value) + (axis.x * sin_theta);
+
+ res.data[2][0] = (axis.x * axis.z * cos_value) + (axis.y * sin_theta);
+ res.data[2][1] = (axis.z * axis.y * cos_value) - (axis.x * sin_theta);
+ res.data[2][2] = (axis.z * axis.z * cos_value) + cos_theta;
+
+ return res;
+}
+
+Mat4 perspective_projection4m(r32 left, r32 right, r32 bottom, r32 top, r32 near, r32 far)
+{
+ Mat4 res = { 0 };
+
+ res.data[0][0] = (2.0 * near)/(right - left);
+ res.data[0][2] = (right + left)/(right - left);
+
+ res.data[1][1] = (2.0 * near)/(top - bottom);
+ res.data[1][2] = (top + bottom)/(top - bottom);
+
+ res.data[2][2] = -(far + near)/(far - near);
+ res.data[2][3] = -2.0*far*near/(far - near);
+
+ res.data[3][2] = -1.0;
+
+ return res;
+}
+
+Mat4 perspective4m(r32 fov, r32 aspect_ratio, r32 near, r32 far)
+{
+ r32 cotangent = 1.0f / tanf(fov / 2.0f);
+
+ Mat4 res = { 0 };
+
+ res.data[0][0] = cotangent / aspect_ratio;
+
+ res.data[1][1] = cotangent;
+
+ res.data[2][2] = -(far + near) / (far - near);
+ res.data[2][3] = -2.0f * far * near / (far - near);
+
+ res.data[3][2] = -1.0f;
+
+ return res;
+}
+
+Mat4 orthographic_projection4m(r32 left, r32 right, r32 bottom, r32 top, r32 near, r32 far)
+{
+ // @todo: understand the derivation once I am done experimenting
+ Mat4 res = { 0 };
+ res.data[0][0] = 2.0f/(right - left); res.data[0][3] = -(right + left)/(right - left);
+ res.data[1][1] = 2.0f/(top - bottom); res.data[1][3] = -(top + bottom)/(top - bottom);
+ res.data[2][2] = -2.0f/(far - near); res.data[2][3] = -(far + near)/(far - near);
+ res.data[3][3] = 1.0f;
+
+ return res;
+}
+
+Mat4 lookat4m(Vec3 up, Vec3 forward, Vec3 right, Vec3 position)
+{
+ /*
+ * @note: The construction of the lookat matrix is not obvious. For that reason here is the supplemental matrial I have used to understand
+ * things while I maintain my elementary understanding of linear algebra.
+ * 1. This youtube video (https://www.youtube.com/watch?v=3ZmqJb7J5wE) helped me understand why we invert matrices.
+ * It is because, we are moving from the position matrix which is a global to the view matrix which
+ * is a local. It won't be very clear from this illustration alone, so you would be best served watching the video and recollecting and understanding from there.
+ * 2. This article (https://twodee.org/blog/17560) derives (or rather shows), in a very shallow way how we get to the look at matrix.
+ *
+ * I am guessing this is not useful for 2D stuff
+ */
+ Mat4 res = init_value4m(1.0);
+ res.xyzw[0] = Vec4{ right.x, right.y, right.z, -dot_multiply3v(right, position) };
+ res.xyzw[1] = Vec4{ up.x, up.y, up.z, -dot_multiply3v(up, position) };
+ res.xyzw[2] = Vec4{ forward.x, forward.y, forward.z, -dot_multiply3v(forward, position) };
+ res.xyzw[3] = Vec4{ 0.0f, 0.0f, 0.0f, 1.0f };
+
+ return res;
+}
+
+Vec3 camera_look_around(r32 angle_pitch, r32 angle_yaw)
+{
+ Vec3 camera_look = {0.0};
+ camera_look.x = cosf(angle_yaw) * cosf(angle_pitch);
+ camera_look.y = sinf(angle_pitch);
+ camera_look.z = sinf(angle_yaw) * cosf(angle_pitch);
+ camera_look = normalize3v(camera_look);
+
+ return camera_look;
+}
+
+Mat4 camera_create4m(Vec3 camera_pos, Vec3 camera_look, Vec3 camera_up)
+{
+ // @note: We do this because this allows the camera to have the axis it looks at
+ // inwards be the +z axis.
+ // If we did not do this, then the inward axis the camera looks at would be negative.
+ // I am still learning from learnopengl.com but I imagine that this was done for conveniences' sake.
+ Vec3 camera_forward_dir = normalize3v(subtract3v(camera_pos, camera_look));
+ Vec3 camera_right_dir = normalize3v(cross_multiply3v(camera_up, camera_forward_dir));
+ Vec3 camera_up_dir = normalize3v(cross_multiply3v(camera_forward_dir, camera_right_dir));
+
+ Mat4 res = lookat4m(camera_up_dir, camera_forward_dir, camera_right_dir, camera_pos);
+
+ return res;
+}
+#endif
diff --git a/source/shaders/colored_quad.fs.glsl b/source/shaders/colored_quad.fs.glsl
new file mode 100755
index 0000000..095a90e
--- /dev/null
+++ b/source/shaders/colored_quad.fs.glsl
@@ -0,0 +1,8 @@
+#version 330 core
+
+uniform vec3 Color;
+out vec4 FragColor;
+
+void main() {
+ FragColor = vec4(Color, 1.0);
+};
diff --git a/source/shaders/colored_quad.vs.glsl b/source/shaders/colored_quad.vs.glsl
new file mode 100755
index 0000000..ac64c49
--- /dev/null
+++ b/source/shaders/colored_quad.vs.glsl
@@ -0,0 +1,10 @@
+#version 330 core
+layout(location=0) in vec3 aPos;
+
+uniform mat4 Model;
+uniform mat4 View;
+uniform mat4 Projection;
+
+void main() {
+ gl_Position = Projection * View * Model * vec4(aPos, 1.0);
+}
diff --git a/source/shaders/ui_text.fs.glsl b/source/shaders/ui_text.fs.glsl
new file mode 100755
index 0000000..ca2c249
--- /dev/null
+++ b/source/shaders/ui_text.fs.glsl
@@ -0,0 +1,15 @@
+#version 330 core
+
+in vec2 TexCoords;
+flat in int Index;
+uniform sampler2DArray TextureAtlas;
+uniform int TextureMap[32];
+uniform vec3 TextColor;
+out vec4 FragColor;
+
+void main() {
+ int TextureId = TextureMap[Index];
+ vec3 TextureIndexCoords = vec3(TexCoords.xy, TextureId);
+ vec4 sampled = vec4(1.0, 1.0, 1.0, texture(TextureAtlas, TextureIndexCoords).r);
+ FragColor = sampled * vec4(TextColor, 1);
+};
diff --git a/source/shaders/ui_text.vs.glsl b/source/shaders/ui_text.vs.glsl
new file mode 100755
index 0000000..9bba904
--- /dev/null
+++ b/source/shaders/ui_text.vs.glsl
@@ -0,0 +1,16 @@
+#version 330 core
+layout(location=0) in vec2 aPos;
+
+uniform mat4 Projection;
+uniform mat4 View;
+uniform mat4 LetterTransforms[32];
+out vec2 TexCoords;
+flat out int Index;
+
+void main() {
+ gl_Position = Projection * View * LetterTransforms[gl_InstanceID] * vec4(aPos, 0.0, 1.0);
+ vec2 tex = aPos;
+ TexCoords = tex;
+ TexCoords.y = 1.0 - TexCoords.y;
+ Index = gl_InstanceID;
+}