143 lines
5.1 KiB
C
143 lines
5.1 KiB
C
#include <assert.h>
|
|
#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.
|
|
// Align its top-left corner to the pixel boundary
|
|
sys_i32 x = body.x + dx;
|
|
sys_i32 y = body.y + 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;
|
|
sys_i32 body_y0 = body->y;
|
|
sys_i32 body_x1 = body->x + body->w;
|
|
sys_i32 body_y1 = body->y + 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 tile_x1 = round_up(body_x1, TILE_SZ_MICROPIXEL);
|
|
sys_i32 tile_y1 = round_up(body_y1, TILE_SZ_MICROPIXEL);
|
|
|
|
// 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;
|
|
}
|
|
}
|
|
}
|
|
} |