#include "collision.hpp" #include "collision_data.hpp" #include "player.hpp" #include "block.hpp" #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} }; bool 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; // 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.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; 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; } } else { if (closest.y > 0) { closest.y = aabb->height / 2; } else { closest.y = -aabb->height / 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; } else { data.normal.y = 1; } data.normal.x = 0; data.depth = overlap_y; } return true; } }