From 4a513cf1f50f947090abdda8c7293842aa57c5c6 Mon Sep 17 00:00:00 2001 From: Lephenixnoir Date: Thu, 10 Mar 2022 23:31:31 +0000 Subject: [PATCH] better, teleport-based collision resolution --- src/comp/mechanical.c | 22 ++++------ src/fixed.h | 5 +++ src/map.c | 95 +++++++++++++++++++++++++++++++++++++++++++ src/map.h | 7 ++++ 4 files changed, 116 insertions(+), 13 deletions(-) diff --git a/src/comp/mechanical.c b/src/comp/mechanical.c index 546cb89..42d7e8a 100644 --- a/src/comp/mechanical.c +++ b/src/comp/mechanical.c @@ -68,22 +68,18 @@ void mechanical_move(entity_t *e, vec2 direction, fixed_t dt, map_t const *map) if(facing >= 0) p->facing = facing; - fixed_t new_x = p->x + fmul(m->vx, dt); - fixed_t new_y = p->y + fmul(m->vy, dt); - rect new_hitbox = rect_translate(p->hitbox, (vec2){ new_x, new_y }); + vec2 new_pos = { p->x + fmul(m->vx, dt), p->y + fmul(m->vy, dt) }; + new_pos = map_move(map, (vec2){ p->x, p->y }, new_pos, p->hitbox); - // TODO ECS: New collision/ejection system based on teleports - if(!map_collides(map, new_hitbox)) { - if(f && f->current_attack && f->attack_follows_movement) { - physical_t *attack = getcomp(f->current_attack, physical); - attack->x += (new_x - p->x); - attack->y += (new_y - p->y); - } - - p->x = new_x; - p->y = new_y; + if(f && f->current_attack && f->attack_follows_movement) { + physical_t *attack = getcomp(f->current_attack, physical); + attack->x += (new_pos.x - p->x); + attack->y += (new_pos.y - p->y); } + p->x = new_pos.x; + p->y = new_pos.y; + /* TODO: Without acceleration, the movement model is broken */ m->vdx = fmul(m->vdx, fix(0.8)); m->vdy = fmul(m->vdy, fix(0.8)); diff --git a/src/fixed.h b/src/fixed.h index 3d9651a..e89be69 100644 --- a/src/fixed.h +++ b/src/fixed.h @@ -86,3 +86,8 @@ static inline fixed_t fease(fixed_t x) return fix(1) - 2 * fmul(x, x); } } + +static inline fixed_t fclamp(fixed_t x, fixed_t min, fixed_t max) +{ + return (x <= min) ? min : (x >= max) ? max : x; +} diff --git a/src/map.c b/src/map.c index 6edec39..f03c8ea 100644 --- a/src/map.c +++ b/src/map.c @@ -1,5 +1,6 @@ #include "map.h" #include +#include rect tile_shape(tileset_t const *tileset, int id) { @@ -22,6 +23,18 @@ map_cell_t *map_cell(map_t const *m, int x, int y) return &m->cells[y * m->width + x]; } +bool map_cell_walkable(map_t const *m, map_cell_t const *cell) +{ + if(!cell) + return false; + if(m->tileset->tiles[cell->base].solid) + return false; + if(cell->decor && m->tileset->tiles[cell->decor].solid) + return false; + + return true; +} + bool map_collides(map_t const *m, rect hitbox) { int y_min = ffloor(hitbox.t); @@ -52,3 +65,85 @@ bool map_collides(map_t const *m, rect hitbox) return false; } + +vec2 map_move(map_t const *m, vec2 old_pos, vec2 new_pos, rect hitbox) +{ + int x = ffloor(new_pos.x); + int y = ffloor(new_pos.y); + + /* If there is no collision, accept immediately */ + if(!map_collides(m, rect_translate(hitbox, new_pos))) + return new_pos; + + /* Find the nearest walkable cell */ + fixed_t best_d2 = fix(999); + fixed_t best_x = fix(-1), best_y = fix(-1); + + for(int cy = y-1; cy <= y+1; cy++) + for(int cx = x-1; cx <= x+1; cx++) { + map_cell_t *cell = map_cell(m, cx, cy); + if(!map_cell_walkable(m, cell)) + continue; + + fixed_t closest_x = fclamp(new_pos.x, fix(cx), fix(cx+1)-1); + fixed_t closest_y = fclamp(new_pos.y, fix(cy), fix(cy+1)-1); + + fixed_t d2 = dist2(new_pos, (vec2){ closest_x, closest_y }); + if(d2 < best_d2) { + best_d2 = d2; + best_x = closest_x; + best_y = closest_y; + } + } + + /* If there is no walkable cell, prevent movement */ + if(best_x < 0 || best_y < 0) + return old_pos; + + /* In that cell, clamp to the limits imposed by neighboring cells */ + x = ffloor(best_x); + y = ffloor(best_y); + fixed_t nx = fdec(best_x); + fixed_t ny = fdec(best_y); + + /* Limits at edges */ + if(!map_cell_walkable(m, map_cell(m, x-1, y))) + nx = max(nx, -hitbox.l); + if(!map_cell_walkable(m, map_cell(m, x+1, y))) + nx = min(nx, fix(1) - hitbox.r); + if(!map_cell_walkable(m, map_cell(m, x, y-1))) + ny = max(ny, -hitbox.t); + if(!map_cell_walkable(m, map_cell(m, x, y+1))) + ny = min(ny, fix(1) - hitbox.b); + + /* Limits at corners */ + if(!map_cell_walkable(m, map_cell(m, x-1, y-1)) + && nx < -hitbox.l && ny < -hitbox.t) { + nx = -hitbox.l; + ny = -hitbox.t; + } + if(!map_cell_walkable(m, map_cell(m, x+1, y-1)) + && nx > fix(1) - hitbox.r && ny < -hitbox.t) { + nx = fix(1) - hitbox.r; + ny = -hitbox.t; + } + if(!map_cell_walkable(m, map_cell(m, x-1, y+1)) + && nx < -hitbox.l && ny > fix(1) - hitbox.b) { + nx = -hitbox.l; + ny = fix(1) - hitbox.b; + } + if(!map_cell_walkable(m, map_cell(m, x+1, y+1)) + && nx > fix(1) - hitbox.r && ny > fix(1) - hitbox.b) { + nx = fix(1) - hitbox.r; + ny = fix(1) - hitbox.b; + } + + /* If the new position is valid, use it, otherwise refuse the movement. We + catch here some potential bugs, plus a whole lot of unhandled cases when + the hitbox is larger than a tile */ + new_pos = (vec2){ fix(x) + nx, fix(y) + ny }; + if(!map_collides(m, rect_translate(hitbox, new_pos))) + return new_pos; + + return old_pos; +} diff --git a/src/map.h b/src/map.h index fe37e05..644a2e3 100644 --- a/src/map.h +++ b/src/map.h @@ -96,5 +96,12 @@ typedef struct /* Get a pointer to the cell at (x,y) in map; NULL if out of bounds. */ map_cell_t *map_cell(map_t const *m, int x, int y); +/* Check whether a map cell is walkable, only accounting for full hitboxes. */ +bool map_cell_walkable(map_t const *m, map_cell_t const *cell); + /* Check whether a hitbox collides with the map. */ bool map_collides(map_t const *m, rect hitbox); + +/* Returns the new position of an object with a hitbox trying to move from + [old_pos] to [new_pos]. */ +vec2 map_move(map_t const *m, vec2 old_pos, vec2 new_pos, rect hitbox);