diff --git a/include/collision.hpp b/include/collision.hpp index d611b51..9784997 100644 --- a/include/collision.hpp +++ b/include/collision.hpp @@ -5,11 +5,13 @@ #include "collision_data.hpp" #include #include +#include namespace Collision { - typedef bool (*collision_detect)(CollisionData&); - typedef std::map, collision_detect> collision_dispatcher; - extern collision_dispatcher dispatch; + extern std::map< + std::pair, + std::function + > dispatch; bool playerToBlock(CollisionData& data); bool blockToPlayer(CollisionData& data); diff --git a/src/collision.cpp b/src/collision.cpp index 5773b27..282539a 100644 --- a/src/collision.cpp +++ b/src/collision.cpp @@ -5,172 +5,173 @@ #include "object.hpp" #include -namespace Collision { - // initialisation du dictionnaire associant les types - // impliqués dans une collision à leur fonction de résolution - collision_dispatcher dispatch = { - {std::make_pair(Player::TYPE_ID, Block::TYPE_ID), &playerToBlock}, - {std::make_pair(Block::TYPE_ID, Player::TYPE_ID), &blockToPlayer}, - {std::make_pair(Player::TYPE_ID, Player::TYPE_ID), &playerToPlayer}, - {std::make_pair(Block::TYPE_ID, Block::TYPE_ID), &blockToBlock} - }; +// initialisation du dictionnaire associant les types +// impliqués dans une collision à leur fonction de résolution +std::map< + std::pair, + std::function +> Collision::dispatch = { + {std::make_pair(Player::TYPE_ID, Block::TYPE_ID), Collision::playerToBlock}, + {std::make_pair(Block::TYPE_ID, Player::TYPE_ID), Collision::blockToPlayer}, + {std::make_pair(Player::TYPE_ID, Player::TYPE_ID), Collision::playerToPlayer}, + {std::make_pair(Block::TYPE_ID, Block::TYPE_ID), Collision::blockToBlock} +}; - bool playerToBlock(CollisionData& data) { - Player player = dynamic_cast(data.objA); - Block block = dynamic_cast(data.objB); +bool Collision::playerToBlock(CollisionData& data) { + Player player = dynamic_cast(data.objA); + Block block = dynamic_cast(data.objB); - // recherche du point le plus proche du centre de la - // balle sur le bloc. On regarde la position relative - // du cercle par rapport au bloc - std::unique_ptr aabb = block.getAABB(); - sf::Vector2f relpos = block.getPosition() - player.getPosition(); - sf::Vector2f closest = relpos; + // recherche du point le plus proche du centre de la + // balle sur le bloc. On regarde la position relative + // du cercle par rapport au bloc + std::unique_ptr aabb = block.getAABB(); + sf::Vector2f relpos = block.getPosition() - player.getPosition(); + sf::Vector2f closest = relpos; - // on restreint la position relative pour rester - // à l'intérieur du bloc - if (closest.x < -aabb->width / 2) { - closest.x = -aabb->width / 2; - } + // on restreint la position relative pour rester + // à l'intérieur du bloc + if (closest.x < -aabb->width / 2) { + closest.x = -aabb->width / 2; + } - if (closest.x > aabb->width / 2) { - closest.x = aabb->width / 2; - } + if (closest.x > aabb->width / 2) { + closest.x = aabb->width / 2; + } - if (closest.y < -aabb->height / 2) { - closest.y = -aabb->height / 2; - } + if (closest.y < -aabb->height / 2) { + closest.y = -aabb->height / 2; + } - if (closest.y > aabb->height / 2) { - closest.y = aabb->height / 2; - } + if (closest.y > aabb->height / 2) { + closest.y = aabb->height / 2; + } - // si la position n'a pas été changée, elle - // était déjà à l'intérieur du cercle : le cercle - // est dans le bloc - float isInside = false; + // si la position n'a pas été changée, elle + // était déjà à l'intérieur du cercle : le cercle + // est dans le bloc + float isInside = false; - if (relpos == closest) { - isInside = true; + if (relpos == closest) { + isInside = true; - // on se colle au bord le plus proche du bloc - if (std::abs(relpos.x) > std::abs(relpos.y)) { - if (closest.x > 0) { - closest.x = aabb->width / 2; - } else { - closest.x = -aabb->width / 2; - } + // on se colle au bord le plus proche du bloc + if (std::abs(relpos.x) > std::abs(relpos.y)) { + if (closest.x > 0) { + closest.x = aabb->width / 2; } else { - if (closest.y > 0) { - closest.y = aabb->height / 2; - } else { - closest.y = -aabb->height / 2; - } + closest.x = -aabb->width / 2; } - } - - // la normale est portée par la direction - // du point le plus proche au centre de la balle - sf::Vector2f prenormal = relpos - closest; - float squaredLength = prenormal.x * prenormal.x + prenormal.y * prenormal.y; - - // si la balle est à l'extérieur et que - // la normale est plus longue que son rayon, - // il n'y a pas collision - if (!isInside && squaredLength >= player.getRadius() * player.getRadius()) { - return false; - } - - float length = std::sqrt(squaredLength); - data.depth = player.getRadius() - length; - - if (length != 0) { - data.normal = prenormal / length; - } - - if (isInside) { - data.normal *= -1.f; - } - - return true; - } - - bool blockToPlayer(CollisionData& data) { - // la collision Block -> Player est la collision Player -> Block - Object& objT = data.objB; - data.objB = data.objA; - data.objA = objT; - - return playerToBlock(data); - } - - bool playerToPlayer(CollisionData& data) { - Player playerA = dynamic_cast(data.objA); - Player playerB = dynamic_cast(data.objB); - - sf::Vector2f dir = playerB.getPosition() - playerA.getPosition(); - float squaredLength = dir.x * dir.x + dir.y * dir.y; - float totalRadius = playerB.getRadius() + playerA.getRadius(); - - // si les deux balles sont à une distance supérieure - // à la somme de leurs deux rayons, il n'y a pas eu collision - if (squaredLength > totalRadius * totalRadius) { - return false; - } - - float length = std::sqrt(squaredLength); - - // les balles sont sur la même position. - // Renvoie une normale apte à séparer les deux balles - if (length == 0) { - data.depth = totalRadius; - data.normal.x = 0; - data.normal.y = -1; - return true; - } - - // il y a eu collision - data.depth = totalRadius - length; - data.normal = dir / length; - return true; - } - - bool blockToBlock(CollisionData& data) { - Block blockA = dynamic_cast(data.objA); - Block blockB = dynamic_cast(data.objB); - - std::unique_ptr aabb = blockA.getAABB(); - std::unique_ptr obj_aabb = blockB.getAABB(); - sf::Vector2f relpos = blockB.getPosition() - blockA.getPosition(); - - float overlap_x = aabb->width / 2 + obj_aabb->width / 2 - std::abs(relpos.x); - float overlap_y = aabb->height / 2 + obj_aabb->height / 2 - std::abs(relpos.y); - - // si il n'y a pas de chauvauchement sur l'axe X et Y, pas de collision - if (overlap_x <= 0 || overlap_y <= 0) { - return false; - } - - // on choisit l'axe de pénétration maximale pour calculer la normale - if (overlap_x < overlap_y) { - if (relpos.x < 0) { - data.normal.x = -1; - } else { - data.normal.x = 1; - } - - data.normal.y = 0; - data.depth = overlap_x; } else { - if (relpos.y < 0) { - data.normal.y = -1; + if (closest.y > 0) { + closest.y = aabb->height / 2; } else { - data.normal.y = 1; + closest.y = -aabb->height / 2; } - - data.normal.x = 0; - data.depth = overlap_y; } + } + // la normale est portée par la direction + // du point le plus proche au centre de la balle + sf::Vector2f prenormal = relpos - closest; + float squaredLength = prenormal.x * prenormal.x + prenormal.y * prenormal.y; + + // si la balle est à l'extérieur et que + // la normale est plus longue que son rayon, + // il n'y a pas collision + if (!isInside && squaredLength >= player.getRadius() * player.getRadius()) { + return false; + } + + float length = std::sqrt(squaredLength); + data.depth = player.getRadius() - length; + + if (length != 0) { + data.normal = prenormal / length; + } + + if (isInside) { + data.normal *= -1.f; + } + + return true; +} + +bool Collision::blockToPlayer(CollisionData& data) { + // la collision Block -> Player est la collision Player -> Block + Object& objT = data.objB; + data.objB = data.objA; + data.objA = objT; + + return playerToBlock(data); +} + +bool Collision::playerToPlayer(CollisionData& data) { + Player playerA = dynamic_cast(data.objA); + Player playerB = dynamic_cast(data.objB); + + sf::Vector2f dir = playerB.getPosition() - playerA.getPosition(); + float squaredLength = dir.x * dir.x + dir.y * dir.y; + float totalRadius = playerB.getRadius() + playerA.getRadius(); + + // si les deux balles sont à une distance supérieure + // à la somme de leurs deux rayons, il n'y a pas eu collision + if (squaredLength > totalRadius * totalRadius) { + return false; + } + + float length = std::sqrt(squaredLength); + + // les balles sont sur la même position. + // Renvoie une normale apte à séparer les deux balles + if (length == 0) { + data.depth = totalRadius; + data.normal.x = 0; + data.normal.y = -1; return true; } + + // il y a eu collision + data.depth = totalRadius - length; + data.normal = dir / length; + return true; +} + +bool Collision::blockToBlock(CollisionData& data) { + Block blockA = dynamic_cast(data.objA); + Block blockB = dynamic_cast(data.objB); + + std::unique_ptr aabb = blockA.getAABB(); + std::unique_ptr obj_aabb = blockB.getAABB(); + sf::Vector2f relpos = blockB.getPosition() - blockA.getPosition(); + + float overlap_x = aabb->width / 2 + obj_aabb->width / 2 - std::abs(relpos.x); + float overlap_y = aabb->height / 2 + obj_aabb->height / 2 - std::abs(relpos.y); + + // si il n'y a pas de chauvauchement sur l'axe X et Y, pas de collision + if (overlap_x <= 0 || overlap_y <= 0) { + return false; + } + + // on choisit l'axe de pénétration maximale pour calculer la normale + if (overlap_x < overlap_y) { + if (relpos.x < 0) { + data.normal.x = -1; + } else { + data.normal.x = 1; + } + + data.normal.y = 0; + data.depth = overlap_x; + } else { + if (relpos.y < 0) { + data.normal.y = -1; + } else { + data.normal.y = 1; + } + + data.normal.x = 0; + data.depth = overlap_y; + } + + return true; }