#include "collision_map.hpp" #include // Para std::ranges::any_of #ifdef _DEBUG #include "core/system/debug.hpp" // Para Debug #endif #include "utils/defines.hpp" // Para Collision // Constructor CollisionMap::CollisionMap(std::vector tile_map, int tile_set_width, int conveyor_belt_direction) : tile_map_(std::move(tile_map)), tile_set_width_(tile_set_width), conveyor_belt_direction_(conveyor_belt_direction) { // Inicializa todas las superficies de colisión initializeSurfaces(); } // Inicializa todas las superficies de colisión void CollisionMap::initializeSurfaces() { setBottomSurfaces(); setTopSurfaces(); setLeftSurfaces(); setRightSurfaces(); setLeftSlopes(); setRightSlopes(); setAutoSurfaces(); } // Devuelve el tipo de tile que hay en ese pixel auto CollisionMap::getTile(SDL_FPoint point) const -> Tile { const int row = static_cast(point.y / TILE_SIZE); const int col = static_cast(point.x / TILE_SIZE); const int POS = row * MAP_WIDTH + col; return getTile(POS); } // Devuelve el tipo de tile que hay en ese indice auto CollisionMap::getTile(int index) const -> Tile { const bool ON_RANGE = (index > -1) && (index < (int)tile_map_.size()); if (ON_RANGE) { // Las filas 0-8 son de tiles t_wall if ((tile_map_[index] >= 0) && (tile_map_[index] < 9 * tile_set_width_)) { return Tile::WALL; } // Las filas 9-17 son de tiles t_passable if ((tile_map_[index] >= 9 * tile_set_width_) && (tile_map_[index] < 18 * tile_set_width_)) { return Tile::PASSABLE; } // Las filas 18-20 es de tiles t_animated if ((tile_map_[index] >= 18 * tile_set_width_) && (tile_map_[index] < 21 * tile_set_width_)) { return Tile::ANIMATED; } // La fila 21 es de tiles t_slope_r if ((tile_map_[index] >= 21 * tile_set_width_) && (tile_map_[index] < 22 * tile_set_width_)) { return Tile::SLOPE_R; } // La fila 22 es de tiles t_slope_l if ((tile_map_[index] >= 22 * tile_set_width_) && (tile_map_[index] < 23 * tile_set_width_)) { return Tile::SLOPE_L; } // La fila 23 es de tiles t_kill if ((tile_map_[index] >= 23 * tile_set_width_) && (tile_map_[index] < 24 * tile_set_width_)) { return Tile::KILL; } } return Tile::EMPTY; } // Obten la coordenada de la cuesta a partir de un punto perteneciente a ese tile auto CollisionMap::getSlopeHeight(SDL_FPoint p, Tile slope) -> int { // Calcula la base del tile int base = ((p.y / TILE_SIZE) * TILE_SIZE) + TILE_SIZE; #ifdef _DEBUG Debug::get()->add("BASE = " + std::to_string(base)); #endif // Calcula cuanto se ha entrado en el tile horizontalmente const int POS = (static_cast(p.x) % TILE_SIZE); // Esto da un valor entre 0 y 7 #ifdef _DEBUG Debug::get()->add("POS = " + std::to_string(POS)); #endif // Se resta a la base la cantidad de pixeles pos en funcion de la rampa if (slope == Tile::SLOPE_R) { base -= POS + 1; #ifdef _DEBUG Debug::get()->add("BASE_R = " + std::to_string(base)); #endif } else { base -= (TILE_SIZE - POS); #ifdef _DEBUG Debug::get()->add("BASE_L = " + std::to_string(base)); #endif } return base; } // === Queries de colisión === // Comprueba las colisiones con paredes derechas auto CollisionMap::checkRightSurfaces(const SDL_FRect& rect) -> int { for (const auto& s : right_walls_) { if (checkCollision(s, rect)) { return s.x; } } return Collision::NONE; } // Comprueba las colisiones con paredes izquierdas auto CollisionMap::checkLeftSurfaces(const SDL_FRect& rect) -> int { for (const auto& s : left_walls_) { if (checkCollision(s, rect)) { return s.x; } } return Collision::NONE; } // Comprueba las colisiones con techos auto CollisionMap::checkTopSurfaces(const SDL_FRect& rect) -> int { for (const auto& s : top_floors_) { if (checkCollision(s, rect)) { return s.y; } } return Collision::NONE; } // Comprueba las colisiones punto con techos auto CollisionMap::checkTopSurfaces(const SDL_FPoint& p) -> bool { return std::ranges::any_of(top_floors_, [&](const auto& s) { return checkCollision(s, p); }); } // Comprueba las colisiones con suelos auto CollisionMap::checkBottomSurfaces(const SDL_FRect& rect) -> int { for (const auto& s : bottom_floors_) { if (checkCollision(s, rect)) { return s.y; } } return Collision::NONE; } // Comprueba las colisiones con conveyor belts auto CollisionMap::checkAutoSurfaces(const SDL_FRect& rect) -> int { for (const auto& s : conveyor_belt_floors_) { if (checkCollision(s, rect)) { return s.y; } } return Collision::NONE; } // Comprueba las colisiones punto con conveyor belts auto CollisionMap::checkConveyorBelts(const SDL_FPoint& p) -> bool { return std::ranges::any_of(conveyor_belt_floors_, [&](const auto& s) { return checkCollision(s, p); }); } // Comprueba las colisiones línea con rampas izquierdas auto CollisionMap::checkLeftSlopes(const LineVertical& line) -> int { for (const auto& slope : left_slopes_) { const auto P = checkCollision(slope, line); if (P.x != -1) { return P.y; } } return Collision::NONE; } // Comprueba las colisiones punto con rampas izquierdas auto CollisionMap::checkLeftSlopes(const SDL_FPoint& p) -> bool { return std::ranges::any_of(left_slopes_, [&](const auto& slope) { return checkCollision(p, slope); }); } // Comprueba las colisiones línea con rampas derechas auto CollisionMap::checkRightSlopes(const LineVertical& line) -> int { for (const auto& slope : right_slopes_) { const auto P = checkCollision(slope, line); if (P.x != -1) { return P.y; } } return Collision::NONE; } // Comprueba las colisiones punto con rampas derechas auto CollisionMap::checkRightSlopes(const SDL_FPoint& p) -> bool { return std::ranges::any_of(right_slopes_, [&](const auto& slope) { return checkCollision(p, slope); }); } // Obtiene puntero a slope en un punto (prioriza left_slopes_ sobre right_slopes_) auto CollisionMap::getSlopeAtPoint(const SDL_FPoint& p) const -> const LineDiagonal* { // Primero busca en rampas izquierdas for (const auto& slope : left_slopes_) { if (checkCollision(p, slope)) { return &slope; } } // Luego busca en rampas derechas for (const auto& slope : right_slopes_) { if (checkCollision(p, slope)) { return &slope; } } // No hay colisión con ninguna slope return nullptr; } // === Helpers para recopilar tiles === // Helper: recopila tiles inferiores (muros sin muro debajo) auto CollisionMap::collectBottomTiles() -> std::vector { std::vector tile; // Busca todos los tiles de tipo muro que no tengan debajo otro muro // Hay que recorrer la habitación por filas (excepto los de la última fila) for (int i = 0; i < (int)tile_map_.size() - MAP_WIDTH; ++i) { if (getTile(i) == Tile::WALL && getTile(i + MAP_WIDTH) != Tile::WALL) { tile.push_back(i); // Si llega al final de la fila, introduce un separador if (i % MAP_WIDTH == MAP_WIDTH - 1) { tile.push_back(-1); } } } // Añade un terminador tile.push_back(-1); return tile; } // Helper: recopila tiles superiores (muros o pasables sin muro encima) auto CollisionMap::collectTopTiles() -> std::vector { std::vector tile; // Busca todos los tiles de tipo muro o pasable que no tengan encima un muro // Hay que recorrer la habitación por filas (excepto los de la primera fila) for (int i = MAP_WIDTH; i < (int)tile_map_.size(); ++i) { if ((getTile(i) == Tile::WALL || getTile(i) == Tile::PASSABLE) && getTile(i - MAP_WIDTH) != Tile::WALL) { tile.push_back(i); // Si llega al final de la fila, introduce un separador if (i % MAP_WIDTH == MAP_WIDTH - 1) { tile.push_back(-1); } } } // Añade un terminador tile.push_back(-1); return tile; } // Helper: recopila tiles animados (para superficies automaticas/conveyor belts) auto CollisionMap::collectAnimatedTiles() -> std::vector { std::vector tile; // Busca todos los tiles de tipo animado // Hay que recorrer la habitación por filas (excepto los de la primera fila) for (int i = MAP_WIDTH; i < (int)tile_map_.size(); ++i) { if (getTile(i) == Tile::ANIMATED) { tile.push_back(i); // Si llega al final de la fila, introduce un separador if (i % MAP_WIDTH == MAP_WIDTH - 1) { tile.push_back(-1); } } } // Añade un terminador si hay tiles if (!tile.empty()) { tile.push_back(-1); } return tile; } // Helper: construye lineas horizontales a partir de tiles consecutivos void CollisionMap::buildHorizontalLines(const std::vector& tiles, std::vector& lines, bool is_bottom_surface) { if (tiles.size() <= 1) { return; } int i = 0; while (i < (int)tiles.size() - 1) { LineHorizontal line; line.x1 = (tiles[i] % MAP_WIDTH) * TILE_SIZE; // Calcula Y segun si es superficie inferior o superior if (is_bottom_surface) { line.y = ((tiles[i] / MAP_WIDTH) * TILE_SIZE) + TILE_SIZE - 1; } else { line.y = (tiles[i] / MAP_WIDTH) * TILE_SIZE; } int last_one = i; i++; // Encuentra tiles consecutivos if (i < (int)tiles.size()) { while (tiles[i] == tiles[i - 1] + 1) { last_one = i; i++; if (i >= (int)tiles.size()) { break; } } } line.x2 = ((tiles[last_one] % MAP_WIDTH) * TILE_SIZE) + TILE_SIZE - 1; lines.push_back(line); // Salta separadores if (i < (int)tiles.size() && tiles[i] == -1) { i++; } } } // === Métodos de generación de geometría === // Calcula las superficies inferiores void CollisionMap::setBottomSurfaces() { std::vector tile = collectBottomTiles(); buildHorizontalLines(tile, bottom_floors_, true); } // Calcula las superficies superiores void CollisionMap::setTopSurfaces() { std::vector tile = collectTopTiles(); buildHorizontalLines(tile, top_floors_, false); } // Calcula las superficies laterales izquierdas void CollisionMap::setLeftSurfaces() { std::vector tile; // Busca todos los tiles de tipo muro que no tienen a su izquierda un tile de tipo muro // Hay que recorrer la habitación por columnas (excepto los de la primera columna) for (int i = 1; i < MAP_WIDTH; ++i) { for (int j = 0; j < MAP_HEIGHT; ++j) { const int POS = ((j * MAP_WIDTH) + i); if (getTile(POS) == Tile::WALL && getTile(POS - 1) != Tile::WALL) { tile.push_back(POS); } } } // Añade un terminador tile.push_back(-1); // Recorre el vector de tiles buscando tiles consecutivos // (Los tiles de la misma columna, la diferencia entre ellos es de mapWidth) // para localizar las superficies if ((int)tile.size() > 1) { int i = 0; do { LineVertical line; line.x = (tile[i] % MAP_WIDTH) * TILE_SIZE; line.y1 = ((tile[i] / MAP_WIDTH) * TILE_SIZE); while (tile[i] + MAP_WIDTH == tile[i + 1]) { if (i == (int)tile.size() - 1) { break; } i++; } line.y2 = ((tile[i] / MAP_WIDTH) * TILE_SIZE) + TILE_SIZE - 1; left_walls_.push_back(line); i++; } while (i < (int)tile.size() - 1); } } // Calcula las superficies laterales derechas void CollisionMap::setRightSurfaces() { std::vector tile; // Busca todos los tiles de tipo muro que no tienen a su derecha un tile de tipo muro // Hay que recorrer la habitación por columnas (excepto los de la última columna) for (int i = 0; i < MAP_WIDTH - 1; ++i) { for (int j = 0; j < MAP_HEIGHT; ++j) { const int POS = ((j * MAP_WIDTH) + i); if (getTile(POS) == Tile::WALL && getTile(POS + 1) != Tile::WALL) { tile.push_back(POS); } } } // Añade un terminador tile.push_back(-1); // Recorre el vector de tiles buscando tiles consecutivos // (Los tiles de la misma columna, la diferencia entre ellos es de mapWidth) // para localizar las superficies if ((int)tile.size() > 1) { int i = 0; do { LineVertical line; line.x = ((tile[i] % MAP_WIDTH) * TILE_SIZE) + TILE_SIZE - 1; line.y1 = ((tile[i] / MAP_WIDTH) * TILE_SIZE); while (tile[i] + MAP_WIDTH == tile[i + 1]) { if (i == (int)tile.size() - 1) { break; } i++; } line.y2 = ((tile[i] / MAP_WIDTH) * TILE_SIZE) + TILE_SIZE - 1; right_walls_.push_back(line); i++; } while (i < (int)tile.size() - 1); } } // Encuentra todas las rampas que suben hacia la izquierda void CollisionMap::setLeftSlopes() { // Recorre la habitación entera por filas buscando tiles de tipo t_slope_l std::vector found; for (int i = 0; i < (int)tile_map_.size(); ++i) { if (getTile(i) == Tile::SLOPE_L) { found.push_back(i); } } // El primer elemento es el inicio de una rampa. Se añade ese elemento y se buscan los siguientes, // que seran i + mapWidth + 1. Conforme se añaden se eliminan y se vuelve a escudriñar el vector de // tiles encontrados hasta que esté vacío while (!found.empty()) { LineDiagonal line; line.x1 = (found[0] % MAP_WIDTH) * TILE_SIZE; line.y1 = (found[0] / MAP_WIDTH) * TILE_SIZE; int looking_for = found[0] + MAP_WIDTH + 1; int last_one_found = found[0]; found.erase(found.begin()); for (int i = 0; i < (int)found.size(); ++i) { if (found[i] == looking_for) { last_one_found = looking_for; looking_for += MAP_WIDTH + 1; found.erase(found.begin() + i); i--; } } line.x2 = ((last_one_found % MAP_WIDTH) * TILE_SIZE) + TILE_SIZE - 1; line.y2 = ((last_one_found / MAP_WIDTH) * TILE_SIZE) + TILE_SIZE - 1; left_slopes_.push_back(line); } } // Encuentra todas las rampas que suben hacia la derecha void CollisionMap::setRightSlopes() { // Recorre la habitación entera por filas buscando tiles de tipo t_slope_r std::vector found; for (int i = 0; i < (int)tile_map_.size(); ++i) { if (getTile(i) == Tile::SLOPE_R) { found.push_back(i); } } // El primer elemento es el inicio de una rampa. Se añade ese elemento y se buscan los siguientes, // que seran i + mapWidth - 1. Conforme se añaden se eliminan y se vuelve a escudriñar el vector de // tiles encontrados hasta que esté vacío while (!found.empty()) { LineDiagonal line; line.x1 = ((found[0] % MAP_WIDTH) * TILE_SIZE) + TILE_SIZE - 1; line.y1 = (found[0] / MAP_WIDTH) * TILE_SIZE; int looking_for = found[0] + MAP_WIDTH - 1; int last_one_found = found[0]; found.erase(found.begin()); for (int i = 0; i < (int)found.size(); ++i) { if (found[i] == looking_for) { last_one_found = looking_for; looking_for += MAP_WIDTH - 1; found.erase(found.begin() + i); i--; } } line.x2 = (last_one_found % MAP_WIDTH) * TILE_SIZE; line.y2 = ((last_one_found / MAP_WIDTH) * TILE_SIZE) + TILE_SIZE - 1; right_slopes_.push_back(line); } } // Calcula las superficies automaticas (conveyor belts) void CollisionMap::setAutoSurfaces() { std::vector tile = collectAnimatedTiles(); buildHorizontalLines(tile, conveyor_belt_floors_, false); }