summaryrefslogtreecommitdiff
path: root/source/main.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'source/main.cpp')
-rwxr-xr-xsource/main.cpp1113
1 files changed, 1113 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;
+}