diff options
Diffstat (limited to 'source')
-rwxr-xr-x | source/main.cpp | 1113 | ||||
-rwxr-xr-x | source/math.h | 463 | ||||
-rwxr-xr-x | source/shaders/colored_quad.fs.glsl | 8 | ||||
-rwxr-xr-x | source/shaders/colored_quad.vs.glsl | 10 | ||||
-rwxr-xr-x | source/shaders/ui_text.fs.glsl | 15 | ||||
-rwxr-xr-x | source/shaders/ui_text.vs.glsl | 16 |
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; +} |