diff --git a/include/ball.hpp b/include/ball.hpp index d47332c..e1aba7b 100644 --- a/include/ball.hpp +++ b/include/ball.hpp @@ -30,12 +30,10 @@ public: std::unique_ptr getAABB(); /** - * Calcule les informations sur une éventuelle collision de - * cet objet avec un autre : la normale et la profondeur + * Récupère l'identifiant de type de cet objet */ - virtual bool getCollisionInfo(Object& obj, sf::Vector2f& normal, float& depth); - virtual bool getCollisionInfo(Ball& obj, sf::Vector2f& normal, float& depth); - virtual bool getCollisionInfo(Block& obj, sf::Vector2f& normal, float& depth); + static constexpr unsigned int TYPE_ID = 0; + unsigned int getTypeId(); /** * Renvoie le rayon de la balle diff --git a/include/block.hpp b/include/block.hpp index 0f83240..d742317 100644 --- a/include/block.hpp +++ b/include/block.hpp @@ -2,7 +2,6 @@ #define __PTF_BLOCK_HPP__ #include -#include #include "object.hpp" #include "engine_state.hpp" @@ -24,12 +23,10 @@ public: std::unique_ptr getAABB(); /** - * Calcule les informations sur une éventuelle collision de - * cet objet avec un autre : la normale et la profondeur + * Récupère l'identifiant de type de cet objet */ - virtual bool getCollisionInfo(Object& obj, sf::Vector2f& normal, float& depth); - virtual bool getCollisionInfo(Ball& obj, sf::Vector2f& normal, float& depth); - virtual bool getCollisionInfo(Block& obj, sf::Vector2f& normal, float& depth); + static constexpr unsigned int TYPE_ID = 1; + unsigned int getTypeId(); }; #endif diff --git a/include/collision.hpp b/include/collision.hpp new file mode 100644 index 0000000..e671903 --- /dev/null +++ b/include/collision.hpp @@ -0,0 +1,20 @@ +#ifndef __PTF_COLLISION_HPP__ +#define __PTF_COLLISION_HPP__ + +#include "object.hpp" +#include +#include +#include + +namespace Collision { + typedef bool (*collision_data)(Object&, Object&, sf::Vector2f&, float&); + typedef std::map, collision_data> collision_dispatcher; + extern collision_dispatcher dispatch; + + bool ballToBlock(Object& objA, Object& objB, sf::Vector2f& normal, float& depth); + bool blockToBall(Object& objA, Object& objB, sf::Vector2f& normal, float& depth); + bool ballToBall(Object& objA, Object& objB, sf::Vector2f& normal, float& depth); + bool blockToBlock(Object& objA, Object& objB, sf::Vector2f& normal, float& depth); +} + +#endif diff --git a/include/object.hpp b/include/object.hpp index fb41581..0d14471 100644 --- a/include/object.hpp +++ b/include/object.hpp @@ -52,12 +52,9 @@ public: virtual std::unique_ptr getAABB() = 0; /** - * Calcule les informations sur une éventuelle collision de - * cet objet avec un autre : la normale et la profondeur + * Récupère l'identifiant de type de cet objet */ - virtual bool getCollisionInfo(Object& obj, sf::Vector2f& normal, float& depth); - virtual bool getCollisionInfo(Ball& obj, sf::Vector2f& normal, float& depth) = 0; - virtual bool getCollisionInfo(Block& obj, sf::Vector2f& normal, float& depth) = 0; + virtual unsigned int getTypeId() = 0; /** * Détecte s'il y a collision entre cet objet diff --git a/src/ball.cpp b/src/ball.cpp index 6f6cd2b..f96efbb 100644 --- a/src/ball.cpp +++ b/src/ball.cpp @@ -44,112 +44,8 @@ std::unique_ptr Ball::getAABB() { )); } -bool Ball::getCollisionInfo(Object& obj, sf::Vector2f& normal, float& depth) { - return obj.getCollisionInfo(*this, normal, depth); -} - -bool Ball::getCollisionInfo(Ball& obj, sf::Vector2f& normal, float& depth) { - sf::Vector2f dir = getPosition() - obj.getPosition(); - float squaredLength = dir.x * dir.x + dir.y * dir.y; - float totalRadius = getRadius() + obj.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) { - depth = totalRadius; - normal.x = 0; - normal.y = -1; - return true; - } - - // il y a eu collision - depth = totalRadius - length; - normal = dir / length; - return true; -} - -bool Ball::getCollisionInfo(Block& obj, sf::Vector2f& normal, float& depth) { - // 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 = obj.getAABB(); - sf::Vector2f relpos = getPosition() - obj.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 >= getRadius() * getRadius()) { - return false; - } - - float length = std::sqrt(squaredLength); - depth = getRadius() - length; - - if (length != 0) { - normal = prenormal / length; - } - - if (isInside) { - normal *= -1.f; - } - - return true; +unsigned int Ball::getTypeId() { + return Ball::TYPE_ID; } float Ball::getRadius() { diff --git a/src/block.cpp b/src/block.cpp index c146ab9..98b98f0 100644 --- a/src/block.cpp +++ b/src/block.cpp @@ -39,51 +39,6 @@ std::unique_ptr Block::getAABB() { )); } -bool Block::getCollisionInfo(Object& obj, sf::Vector2f& normal, float& depth) { - return obj.getCollisionInfo(*this, normal, depth); -} - -bool Block::getCollisionInfo(Ball& obj, sf::Vector2f& normal, float& depth) { - // la collision Block -> Ball est la collision Ball -> Block - // avec une normale de collision retournée - bool result = obj.getCollisionInfo(*this, normal, depth); - normal *= -1.f; - return result; -} - -bool Block::getCollisionInfo(Block& obj, sf::Vector2f& normal, float& depth) { - std::unique_ptr aabb = getAABB(); - std::unique_ptr obj_aabb = obj.getAABB(); - sf::Vector2f relpos = getPosition() - obj.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) { - normal.x = -1; - } else { - normal.x = 1; - } - - normal.y = 0; - depth = overlap_x; - } else { - if (relpos.y < 0) { - normal.y = -1; - } else { - normal.y = 1; - } - - normal.x = 0; - depth = overlap_y; - } - - return true; +unsigned int Block::getTypeId() { + return Block::TYPE_ID; } diff --git a/src/collision.cpp b/src/collision.cpp new file mode 100644 index 0000000..839101b --- /dev/null +++ b/src/collision.cpp @@ -0,0 +1,175 @@ +#include "collision.hpp" +#include "ball.hpp" +#include "block.hpp" +#include "object.hpp" +#include +#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(Ball::TYPE_ID, Block::TYPE_ID), &ballToBlock}, + {std::make_pair(Block::TYPE_ID, Ball::TYPE_ID), &blockToBall}, + {std::make_pair(Ball::TYPE_ID, Ball::TYPE_ID), &ballToBall}, + {std::make_pair(Block::TYPE_ID, Block::TYPE_ID), &blockToBlock} + }; + + bool ballToBlock(Object& objA, Object& objB, sf::Vector2f& normal, float& depth) { + Ball ball = dynamic_cast(objA); + Block block = dynamic_cast(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() - ball.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 >= ball.getRadius() * ball.getRadius()) { + return false; + } + + float length = std::sqrt(squaredLength); + depth = ball.getRadius() - length; + + if (length != 0) { + normal = prenormal / length; + } + + if (isInside) { + normal *= -1.f; + } + + return true; + } + + bool blockToBall(Object& objA, Object& objB, sf::Vector2f& normal, float& depth) { + // la collision Block -> Ball est la collision Ball -> Block + // avec une normale de collision retournée + bool result = ballToBlock(objB, objA, normal, depth); + normal *= -1.f; + return result; + } + + bool ballToBall(Object& objA, Object& objB, sf::Vector2f& normal, float& depth) { + Ball ballA = dynamic_cast(objA); + Ball ballB = dynamic_cast(objB); + + sf::Vector2f dir = ballB.getPosition() - ballA.getPosition(); + float squaredLength = dir.x * dir.x + dir.y * dir.y; + float totalRadius = ballB.getRadius() + ballA.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) { + depth = totalRadius; + normal.x = 0; + normal.y = -1; + return true; + } + + // il y a eu collision + depth = totalRadius - length; + normal = dir / length; + return true; + } + + bool blockToBlock(Object& objA, Object& objB, sf::Vector2f& normal, float& depth) { + Block blockA = dynamic_cast(objA); + Block blockB = dynamic_cast(objB); + + std::unique_ptr aabb = blockA.getAABB(); + std::unique_ptr obj_aabb = blockB.getAABB(); + sf::Vector2f relpos = blockA.getPosition() - blockB.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) { + normal.x = -1; + } else { + normal.x = 1; + } + + normal.y = 0; + depth = overlap_x; + } else { + if (relpos.y < 0) { + normal.y = -1; + } else { + normal.y = 1; + } + + normal.x = 0; + depth = overlap_y; + } + + return true; + } +} diff --git a/src/object.cpp b/src/object.cpp index 7943fec..8e879d3 100644 --- a/src/object.cpp +++ b/src/object.cpp @@ -1,5 +1,6 @@ #include "object.hpp" #include "constants.hpp" +#include "collision.hpp" #include Object::Object(float x, float y) : @@ -74,16 +75,12 @@ void Object::draw(sf::RenderWindow& window, ResourceManager& resources) { } void Object::update(EngineState& state) { - // intégration de la vitesse dans la position - position += velocity * Constants::PHYSICS_TIME; - // intégration des forces appliquées sur l'objet dans la vitesse acceleration = getForces(state) * getMassInvert(); velocity += acceleration * Constants::PHYSICS_TIME; -} -bool Object::getCollisionInfo(Object& obj, sf::Vector2f& normal, float& depth) { - return obj.getCollisionInfo(*this, normal, depth); + // intégration de la vitesse dans la position + position += velocity * Constants::PHYSICS_TIME; } void Object::collide(Object& obj) { @@ -104,7 +101,7 @@ void Object::collide(Object& obj) { // vérifie plus finement s'il y a collision et récupère // les informations sur la collision (normale et profondeur) - if (!getCollisionInfo(obj, normal, depth)) { + if (!Collision::dispatch[std::make_pair(getTypeId(), obj.getTypeId())](*this, obj, normal, depth)) { return; }