From 7c0ca92da0d2d0fd7e7f4da957ef7d1ea9f15e41 Mon Sep 17 00:00:00 2001
From: talha <sarcxd@gmail.com>
Date: Tue, 8 Oct 2024 21:15:38 +0500
Subject: Saving draft, added collision detection so far

---
 source/main.cpp                     | 1113 +++++++++++++++++++++++++++++++++++
 source/math.h                       |  463 +++++++++++++++
 source/shaders/colored_quad.fs.glsl |    8 +
 source/shaders/colored_quad.vs.glsl |   10 +
 source/shaders/ui_text.fs.glsl      |   15 +
 source/shaders/ui_text.vs.glsl      |   16 +
 6 files changed, 1625 insertions(+)
 create mode 100755 source/main.cpp
 create mode 100755 source/math.h
 create mode 100755 source/shaders/colored_quad.fs.glsl
 create mode 100755 source/shaders/colored_quad.vs.glsl
 create mode 100755 source/shaders/ui_text.fs.glsl
 create mode 100755 source/shaders/ui_text.vs.glsl

(limited to 'source')

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;
+}
-- 
cgit v1.2.3