#include #include "sys/sys.h" #include "game.h" #include "map/game_map.h" bool game_collision_is_occlusive(sys_maptile tile) { if (tile >= 0 && tile < 30) { return true; } if (tile >= 60 && tile < 90) { return true; } return false; } // c's modulus can produce a negative result. don't allow that! #define modulus(x, mod) ((x) % (mod) + (mod)) % (mod) #define round_down(x, incr) (x) - modulus(x, incr) #define round_up(x, incr) round_down((x) + (incr) - 1, incr) bool game_collision_can_move(game_bbox body, sys_i32 dx, sys_i32 dy) { // First: place the body in space. sys_i32 x = body.x - body.x_origin + dx; sys_i32 y = body.y - body.y_origin + dy; // this is extremely similar to the logic in move_to_contact // but has different rounding behavior // (because a body at a fractional position must not actually collide // with fractional pixels) sys_i32 body_x0 = round_down(x, PIXEL_SZ_MICROPIXEL); sys_i32 body_y0 = round_down(y, PIXEL_SZ_MICROPIXEL); sys_i32 body_x1 = body_x0 + body.w; sys_i32 body_y1 = body_y0 + body.h; sys_i32 tile_x0 = round_down(body_x0, TILE_SZ_MICROPIXEL); sys_i32 tile_y0 = round_down(body_y0, TILE_SZ_MICROPIXEL); sys_i32 tile_x1 = round_up(body_x1, TILE_SZ_MICROPIXEL); sys_i32 tile_y1 = round_up(body_y1, TILE_SZ_MICROPIXEL); // now convert to tile coords instead of pixels tile_x0 /= TILE_SZ_MICROPIXEL; tile_y0 /= TILE_SZ_MICROPIXEL; tile_x1 /= TILE_SZ_MICROPIXEL; tile_y1 /= TILE_SZ_MICROPIXEL; for (sys_i32 y = tile_y0; y < tile_y1; y++) { for (sys_i32 x = tile_x0; x < tile_x1; x++) { bool invalid; sys_maptile tile = sys_map_get(map_game_map, x, y, &invalid); if (invalid || game_collision_is_occlusive(tile)) { return false; } } } return true; } game_collision game_collision_move_to_contact( game_bbox* body, sys_i32 total_dx, sys_i32 total_dy ) { sys_i32 src_x = body->x; sys_i32 src_y = body->y; game_collision collision = { .collided_x=false, .collided_y=false, .distance_x=0, .distance_y=0, }; sys_i32 tar_x = body->x + total_dx; sys_i32 tar_y = body->y + total_dy; while (true) { // calculate how much we still need to move int32_t remaining_dx = tar_x - body->x; int32_t remaining_dy = tar_y - body->y; // if we don't need to move, stop if (remaining_dx == 0 && remaining_dy == 0) { collision.distance_x = body->x - src_x; collision.distance_y = body->x - src_y; return collision; } // figure out where we are sys_i32 body_x0 = body->x - body->x_origin; sys_i32 body_y0 = body->y - body->y_origin; sys_i32 body_x1 = body_x0 + body->w; sys_i32 body_y1 = body_y0 + body->h; // figure out what tile we're still in sys_i32 tile_x0 = round_down(body_x0, TILE_SZ_MICROPIXEL); sys_i32 tile_y0 = round_down(body_y0, TILE_SZ_MICROPIXEL); sys_i32 max_overlap = PIXEL_SZ_MICROPIXEL - 1; sys_i32 tile_x1 = round_up(body_x1 - max_overlap, TILE_SZ_MICROPIXEL) + max_overlap; sys_i32 tile_y1 = round_up(body_y1 - max_overlap, TILE_SZ_MICROPIXEL) + max_overlap; // but we want the _next_ tile if (tile_x0 == body_x0) { tile_x0 -= TILE_SZ_MICROPIXEL; } if (tile_y0 == body_y0) { tile_y0 -= TILE_SZ_MICROPIXEL; } if (tile_x1 == body_x1) { tile_x1 += TILE_SZ_MICROPIXEL; } if (tile_y1 == body_y1) { tile_y1 += TILE_SZ_MICROPIXEL; } // length of an x axis move int step_dx = remaining_dx < 0 ? // line up our left side with the tile to our left // not exceeding dx sys_max_i32(remaining_dx, tile_x0 - body_x0) : remaining_dx > 0 ? // line up our right side with the tile to our right // not exceeding dx sys_min_i32(remaining_dx, tile_x1 - body_x1) : // dx == 0 INT32_MAX ; int step_dy = remaining_dy < 0 ? // line up our top side with the tile above us // not exceeding dy sys_max_i32(remaining_dy, tile_y0 - body_y0) : remaining_dy > 0 ? // line up our bottom side with the tile below us // not exceeding dy sys_min_i32(remaining_dy, tile_y1 - body_y1) : INT32_MAX; // there must be a viable move in some direction assert(step_dx != INT32_MAX || step_dy != INT32_MAX); if (sys_abs_i32(step_dx) < sys_abs_i32(step_dy)) { if (game_collision_can_move(*body, step_dx, 0)) { body->x += step_dx; // take the tiny move } else { tar_x = body->x; // stop trying to move collision.collided_x = true; } } else { if (game_collision_can_move(*body, 0, step_dy)) { body->y += step_dy; // take the tiny move } else { tar_y = body->y; // stop trying to move collision.collided_y = true; } } } }