commit 4ad7d5bc16619bdc982ee8d59444346821b2a044
parent bdac377ab8fbef0dc831d1d87a51efbfcf5364ec
Author: Amin Mesbah <dev@aminmesbah.com>
Date:   Wed, 24 Apr 2019 21:54:15 -0700
Ignore internal walls when colliding
Finally! Smooth (and seemingly robust) movement along solid surfaces!!!
Diffstat:
| M | src/game.c |  |  | 175 | +++++++++++++++++++++++++++++++++++++++++++++++++++---------------------------- | 
| M | src/glmth.h |  |  | 37 | +++++++++++++++++++++++++++++++++++++ | 
2 files changed, 151 insertions(+), 61 deletions(-)
diff --git a/src/game.c b/src/game.c
@@ -2,6 +2,9 @@
 #include "glad.c"
 #include "shader.c"
 
+// One tile is one square meter
+#define TILE_SIZE 1.0f
+
 #ifdef PLATFORM_HOTLOAD_GAME_CODE
 void game_load_opengl_symbols(void)
 {
@@ -15,7 +18,6 @@ internal void game_init(struct GameMemory *game_memory, v2u framebuffer)
 
     // init player
     game_state->player.pos = (v2) {12.5f, 5.0f};
-    game_state->player.pos = (v2) {12.5f, 8.5f};
 
     // In Knytt, the player is 9 by 14 texels and a tile is 24 by 24 texels.
     // These dimensions are relative to a square 'meter', one tile
@@ -134,6 +136,55 @@ internal struct WallCollision get_wall_collision(v2 entity_p_initial, v2 entity_
     return result;
 }
 
+internal bool tile_is_solid(u32 tiles[][ROOM_TILE_DIM_X], v2 tile_pos)
+{
+    assert(tiles);
+
+    bool is_solid = false;
+
+    v2u tile_index = {
+        tile_pos.x,
+        ROOM_TILE_DIM_Y - 1.0f - tile_pos.y,
+    };
+
+    assert(tile_index.x >= 0);
+    assert(tile_index.x < ROOM_TILE_DIM_X);
+    assert(tile_index.y >= 0);
+    assert(tile_index.y < ROOM_TILE_DIM_X);
+
+    u32 tile_value = tiles[tile_index.y][tile_index.x];
+    is_solid = tile_value > 0;
+
+    return is_solid;
+}
+
+internal bool wall_is_internal(u32 tiles[][ROOM_TILE_DIM_X], segment wall)
+{
+    bool is_internal = false;
+    bool wall_is_horizontal = wall.min.y == wall.max.y;
+    bool wall_is_vertical = wall.min.x == wall.max.x;
+
+    assert(wall_is_vertical || wall_is_horizontal);
+
+    v2 lesser_neighbor = {0};
+    v2 greater_neighbor = {0};
+
+    if (wall_is_vertical)
+    {
+        lesser_neighbor = (v2) {wall.min.x - TILE_SIZE, wall.min.y};
+        greater_neighbor = wall.min;
+        is_internal = tile_is_solid(tiles, lesser_neighbor) && tile_is_solid(tiles, greater_neighbor);
+    }
+    else
+    {
+        lesser_neighbor = (v2) {wall.min.x, wall.min.y - TILE_SIZE};
+        greater_neighbor = wall.min;
+        is_internal = tile_is_solid(tiles, lesser_neighbor) && tile_is_solid(tiles, greater_neighbor);
+    }
+
+    return is_internal;
+}
+
 // NOTE(amin): For now updating and rendering are interleaved. We simulate the
 // world at the same rate we render it. Our timestep is not fixed. We may want
 // to change this in the future.
@@ -218,8 +269,6 @@ void game_update_and_render(struct GameMemory *game_memory, struct GameInput *ga
         { 0, 0, 0, 0, 0,  0, 0, 0, 0, 0,  0, 0, 0, 0, 0,  0, 0, 0, 0, 0,  0, 0, 0, 0, 0 },
     };
 
-    // One tile is one square meter
-    f32 tile_size = 1.0f;
 
     // render tiles
     {
@@ -240,9 +289,9 @@ void game_update_and_render(struct GameMemory *game_memory, struct GameInput *ga
                 // our square verts are anchored around the center point of the
                 // square, so we want to offset by 0.5 to instead have our
                 // anchor in the min corner
-                model = glmth_translate(model, (v3) {tile_size * 0.5f, tile_size * 0.5f, 0.0f});
+                model = glmth_translate(model, (v3) {TILE_SIZE * 0.5f, TILE_SIZE * 0.5f, 0.0f});
                 model = glmth_translate(model, (v3) {tile_pos.x, tile_pos.y, 0.0f});
-                model = glmth_scale(model, (v3) {tile_size, tile_size, 1.0f});
+                model = glmth_scale(model, (v3) {TILE_SIZE, TILE_SIZE, 1.0f});
 
                 v3 color;
 
@@ -265,7 +314,7 @@ void game_update_and_render(struct GameMemory *game_memory, struct GameInput *ga
                     };
                     rect tile_aabb = {
                         .min = tile_pos,
-                        .max = {tile_pos.x + tile_size, tile_pos.y + tile_size},
+                        .max = {tile_pos.x + TILE_SIZE, tile_pos.y + TILE_SIZE},
                     };
 
                     bool player_is_in_tile = glmth_intersect_aabb_aabb(player_aabb, tile_aabb);
@@ -276,7 +325,7 @@ void game_update_and_render(struct GameMemory *game_memory, struct GameInput *ga
                     if (player_is_in_tile && tile_id > 0)
                     {
                         color = (v3) {0.8f, 0.4f, 0.4f};
-                        //assert(false);
+                        assert(false);
                     }
                 }
 
@@ -368,7 +417,7 @@ void game_update_and_render(struct GameMemory *game_memory, struct GameInput *ga
 
                     rect tile_aabb = {
                         .min = {x, y},
-                        .max = {x + tile_size, y + tile_size},
+                        .max = {x + TILE_SIZE, y + TILE_SIZE},
                     };
                     rect player_aabb = {
                         .min = {next_player_pos.x - half_w, next_player_pos.y - half_h},
@@ -434,85 +483,89 @@ void game_update_and_render(struct GameMemory *game_memory, struct GameInput *ga
                         {
                             rect tile_aabb = {
                                 .min = {tile_x, tile_y},
-                                .max = {tile_x + tile_size, tile_y + tile_size},
+                                .max = {tile_x + TILE_SIZE, tile_y + TILE_SIZE},
                             };
 
                             rect tile_player_sum = glmth_minkowski_sum_rect_rect(tile_aabb, player->dimensions);
                             RENDER_COLLISION_DEBUG_QUAD(tile_player_sum, ((v3) {0.8f, 0.4f, 0.4f}));
 
-                            v2 tile_nw = {tile_player_sum.min.x, tile_player_sum.max.y};
-                            v2 tile_ne = tile_player_sum.max;
-                            v2 tile_sw = tile_player_sum.min;
-                            v2 tile_se = {tile_player_sum.max.x, tile_player_sum.min.y};
-
-                            segment tile_b = {tile_sw, tile_se};
-                            segment tile_t = {tile_nw, tile_ne};
-                            segment tile_l = {tile_sw, tile_nw};
-                            segment tile_r = {tile_se, tile_ne};
+                            segment tile_sum_b = glmth_rect_get_edge(tile_player_sum, RECT_EDGE_BOTTOM);
+                            segment tile_sum_t = glmth_rect_get_edge(tile_player_sum, RECT_EDGE_TOP);
+                            segment tile_sum_l = glmth_rect_get_edge(tile_player_sum, RECT_EDGE_LEFT);
+                            segment tile_sum_r = glmth_rect_get_edge(tile_player_sum, RECT_EDGE_RIGHT);
 
                             v2 norm_b = {0.0f, -1.0f};
                             v2 norm_t = {0.0f, 1.0f};
                             v2 norm_l = {-1.0f, 0.0f};
                             v2 norm_r = {1.0f, 0.0f};
 
-                            struct WallCollision bottom = get_wall_collision(player->pos, new_p, tile_b, norm_b);
-                            if (bottom.collision_occurred)
+                            if (!wall_is_internal(tiles, glmth_rect_get_edge(tile_aabb, RECT_EDGE_BOTTOM)))
                             {
-                                if (smallest_distance_scale_factor > bottom.distance_scale_factor)
+                                struct WallCollision bottom = get_wall_collision(player->pos, new_p, tile_sum_b, norm_b);
+                                if (bottom.collision_occurred)
                                 {
-                                    smallest_distance_scale_factor = bottom.distance_scale_factor;
+                                    if (smallest_distance_scale_factor > bottom.distance_scale_factor)
+                                    {
+                                        smallest_distance_scale_factor = bottom.distance_scale_factor;
+                                    }
+                                    wall_normal = norm_b;
+                                    rect collision = {
+                                        .min = tile_aabb.min,
+                                        .max = {tile_aabb.max.x, tile_aabb.min.y + 0.2f},
+                                    };
+                                    RENDER_COLLISION_DEBUG_QUAD(collision, ((v3) {1.0f, 0.0f, 0.0f}));
                                 }
-                                wall_normal = norm_b;
-                                rect collision = {
-                                    .min = tile_aabb.min,
-                                    .max = {tile_aabb.max.x, tile_aabb.min.y + 0.2f},
-                                };
-                                RENDER_COLLISION_DEBUG_QUAD(collision, ((v3) {1.0f, 0.0f, 0.0f}));
                             }
-
-                            struct WallCollision top = get_wall_collision(player->pos, new_p, tile_t, norm_t);
-                            if (top.collision_occurred)
+                            if (!wall_is_internal(tiles, glmth_rect_get_edge(tile_aabb, RECT_EDGE_TOP)))
                             {
-                                if (smallest_distance_scale_factor > top.distance_scale_factor)
+                                struct WallCollision top = get_wall_collision(player->pos, new_p, tile_sum_t, norm_t);
+                                if (top.collision_occurred)
                                 {
-                                    smallest_distance_scale_factor = top.distance_scale_factor;
+                                    if (smallest_distance_scale_factor > top.distance_scale_factor)
+                                    {
+                                        smallest_distance_scale_factor = top.distance_scale_factor;
+                                    }
+                                    wall_normal = norm_t;
+                                    rect collision = {
+                                        .min = {tile_aabb.min.x, tile_aabb.max.y - 0.2f},
+                                        .max = tile_aabb.max,
+                                    };
+                                    RENDER_COLLISION_DEBUG_QUAD(collision, ((v3) {0.0f, 1.0f, 0.0f}));
                                 }
-                                wall_normal = norm_t;
-                                rect collision = {
-                                    .min = {tile_aabb.min.x, tile_aabb.max.y - 0.2f},
-                                    .max = tile_aabb.max,
-                                };
-                                RENDER_COLLISION_DEBUG_QUAD(collision, ((v3) {0.0f, 1.0f, 0.0f}));
                             }
-
-                            struct WallCollision left = get_wall_collision(player->pos, new_p, tile_l, norm_l);
-                            if (left.collision_occurred)
+                            if (!wall_is_internal(tiles, glmth_rect_get_edge(tile_aabb, RECT_EDGE_LEFT)))
                             {
-                                if (smallest_distance_scale_factor > left.distance_scale_factor)
+                                struct WallCollision left = get_wall_collision(player->pos, new_p, tile_sum_l, norm_l);
+                                if (left.collision_occurred)
                                 {
-                                    smallest_distance_scale_factor = left.distance_scale_factor;
+                                    if (smallest_distance_scale_factor > left.distance_scale_factor)
+                                    {
+                                        smallest_distance_scale_factor = left.distance_scale_factor;
+                                    }
+                                    wall_normal = norm_l;
+                                    rect collision = {
+                                        .min = tile_aabb.min,
+                                        .max = {tile_aabb.min.x + 0.2f, tile_aabb.max.y},
+                                    };
+                                    RENDER_COLLISION_DEBUG_QUAD(collision, ((v3) {0.0f, 0.0f, 1.0f}));
                                 }
-                                wall_normal = norm_l;
-                                rect collision = {
-                                    .min = tile_aabb.min,
-                                    .max = {tile_aabb.min.x + 0.2f, tile_aabb.max.y},
-                                };
-                                RENDER_COLLISION_DEBUG_QUAD(collision, ((v3) {0.0f, 0.0f, 1.0f}));
                             }
-
-                            struct WallCollision right = get_wall_collision(player->pos, new_p, tile_r, norm_r);
-                            if (right.collision_occurred)
+                            if (!wall_is_internal(tiles, glmth_rect_get_edge(tile_aabb, RECT_EDGE_RIGHT)))
                             {
-                                if (smallest_distance_scale_factor > right.distance_scale_factor)
+                                struct WallCollision right = get_wall_collision(player->pos, new_p, tile_sum_r, norm_r);
+                                if (right.collision_occurred)
                                 {
-                                    smallest_distance_scale_factor = right.distance_scale_factor;
+                                    if (smallest_distance_scale_factor > right.distance_scale_factor)
+                                    {
+                                        smallest_distance_scale_factor = right.distance_scale_factor;
+                                    }
+                                    wall_normal = norm_r;
+                                    rect collision = {
+                                        .min = {tile_aabb.max.x - 0.2f, tile_aabb.min.y},
+                                        .max = tile_aabb.max,
+                                    };
+                                    RENDER_COLLISION_DEBUG_QUAD(collision, ((v3) {1.0f, 0.0f, 1.0f}));
                                 }
-                                wall_normal = norm_r;
-                                rect collision = {
-                                    .min = {tile_aabb.max.x - 0.2f, tile_aabb.min.y},
-                                    .max = tile_aabb.max,
-                                };
-                                RENDER_COLLISION_DEBUG_QUAD(collision, ((v3) {1.0f, 0.0f, 1.0f}));
                             }
                         }
                     }
diff --git a/src/glmth.h b/src/glmth.h
@@ -498,6 +498,43 @@ internal inline rect glmth_minkowski_sum_rect_rect(rect r1, v2 r2_dimensions)
     return sum;
 }
 
+enum GlmthRectEdge
+{
+    RECT_EDGE_BOTTOM,
+    RECT_EDGE_TOP,
+    RECT_EDGE_LEFT,
+    RECT_EDGE_RIGHT,
+};
+
+internal inline segment glmth_rect_get_edge(rect r, enum GlmthRectEdge e)
+{
+    v2 nw = {r.min.x, r.max.y};
+    v2 ne = r.max;
+    v2 sw = r.min;
+    v2 se = {r.max.x, r.min.y};
+
+    segment edge = {0};
+    switch(e)
+    {
+        case RECT_EDGE_BOTTOM:
+            edge = (segment) {sw, se};
+            break;
+        case RECT_EDGE_TOP:
+            edge = (segment) {nw, ne};
+            break;
+        case RECT_EDGE_LEFT:
+            edge = (segment) {sw, nw};
+            break;
+        case RECT_EDGE_RIGHT:
+            edge = (segment) {se, ne};
+            break;
+        default:
+            assert(false);
+            break;
+    }
+    return edge;
+}
+
 internal inline void glmth_v2_print(v2 v)
 {
     printf("( %f, %f )\n", v.x, v.y);