Généralisation des collisions

This commit is contained in:
Mattéo Delabre 2016-04-09 15:32:42 +02:00
parent c5e48cbc1c
commit affcc09abb
8 changed files with 136 additions and 102 deletions

View File

@ -30,6 +30,11 @@ public:
*/ */
virtual std::unique_ptr<sf::FloatRect> getAABB() const; virtual std::unique_ptr<sf::FloatRect> getAABB() const;
/**
* Récupère le rayon du bloc
*/
virtual float getRadius() const;
/** /**
* Opérations de préparation de la texture du bloc * Opérations de préparation de la texture du bloc
*/ */
@ -50,6 +55,11 @@ public:
*/ */
virtual unsigned int getTypeId() const; virtual unsigned int getTypeId() const;
/**
* Récupère le type de collision des blocs
*/
virtual CollisionType getCollisionType() const;
/** /**
* Chargement du bloc depuis le fichier donné * Chargement du bloc depuis le fichier donné
*/ */

View File

@ -6,23 +6,29 @@
class Object; class Object;
/** /**
* Structure qui retient des informations * Type de collision : entre deux rectangles ou
* entre deux cercles
*/
enum class CollisionType {AABB, CIRCLE};
/**
* Structure qui retient les informations
* sur les collisions * sur les collisions
*/ */
struct CollisionData { struct CollisionData {
sf::Vector2f normal; sf::Vector2f normal;
float depth; float depth;
Object& objA; Object& obj_a;
Object& objB; Object& obj_b;
CollisionData(Object& objA, Object& objB); CollisionData(Object& obj_a, Object& obj_b);
}; };
/** /**
* Détermine les informations sur la collision * Détermine les informations sur la collision
* entre les deux objets donnés dans data et * entre les deux objets donnés dans data et stocke
* les stocke dans data * ces informations dans data
*/ */
bool getCollisionData(CollisionData& data); bool getCollisionData(CollisionData& data);

View File

@ -58,6 +58,11 @@ public:
*/ */
virtual std::unique_ptr<sf::FloatRect> getAABB() const = 0; virtual std::unique_ptr<sf::FloatRect> getAABB() const = 0;
/**
* Récupère le rayon de l'objet
*/
virtual float getRadius() const = 0;
/** /**
* Dessine l'objet dans la fenêtre donnée * Dessine l'objet dans la fenêtre donnée
*/ */
@ -73,6 +78,11 @@ public:
*/ */
virtual unsigned int getTypeId() const = 0; virtual unsigned int getTypeId() const = 0;
/**
* Récupère le type de collision de cet objet
*/
virtual CollisionType getCollisionType() const = 0;
/** /**
* Charge les propriétés communes à tous les objets * Charge les propriétés communes à tous les objets
* depuis le fichier donné dans l'objet donné * depuis le fichier donné dans l'objet donné

View File

@ -34,6 +34,11 @@ public:
*/ */
virtual std::unique_ptr<sf::FloatRect> getAABB() const; virtual std::unique_ptr<sf::FloatRect> getAABB() const;
/**
* Récupère le rayon du joueur
*/
virtual float getRadius() const;
/** /**
* Dessine le joueur dans la fenêtre donnée * Dessine le joueur dans la fenêtre donnée
*/ */
@ -49,6 +54,11 @@ public:
*/ */
virtual unsigned int getTypeId() const; virtual unsigned int getTypeId() const;
/**
* Récupère le type de collision des joueurs
*/
virtual CollisionType getCollisionType() const;
/** /**
* Chargement du joueur depuis le fichier donné * Chargement du joueur depuis le fichier donné
*/ */
@ -60,11 +70,6 @@ public:
*/ */
virtual void updatePosition(); virtual void updatePosition();
/**
* Renvoie le rayon de la balle du joueur
*/
float getRadius() const;
/** /**
* Renvoie le numéro du joueur * Renvoie le numéro du joueur
*/ */

View File

@ -79,6 +79,14 @@ std::unique_ptr<sf::FloatRect> Block::getAABB() const {
)); ));
} }
float Block::getRadius() const {
return Constants::GRID / 2;
}
unsigned int Block::getTypeId() const { unsigned int Block::getTypeId() const {
return TYPE_ID; return TYPE_ID;
} }
CollisionType Block::getCollisionType() const {
return CollisionType::AABB;
}

View File

@ -9,81 +9,79 @@
/** /**
* Détermination des informations sur une collision entre * Détermination des informations sur une collision entre
* un joueur et un bloc (normale et profondeur de collision) * un cercle et un rectangle
*/ */
bool playerToBlock(CollisionData& data) { bool circleToAABB(CollisionData& data) {
Player player = dynamic_cast<Player&>(data.objA); Object& circle = data.obj_a;
Block block = dynamic_cast<Block&>(data.objB); Object& aabb = data.obj_b;
// recherche du point le plus proche du centre de la // recherche du point le plus proche du centre du cercle
// balle sur le bloc. On regarde la position relative // sur le rectangle. On regarde la position relative du cercle
// du cercle par rapport au bloc // par rapport au rectangle
std::unique_ptr<sf::FloatRect> aabb = block.getAABB(); std::unique_ptr<sf::FloatRect> box = aabb.getAABB();
sf::Vector2f relpos = block.getPosition() - player.getPosition(); sf::Vector2f relpos = aabb.getPosition() - circle.getPosition();
sf::Vector2f closest = relpos; sf::Vector2f closest = relpos;
// on restreint la position relative pour rester // on restreint la position relative pour rester
// à l'intérieur du bloc // à l'intérieur du rectangle
if (closest.x < -aabb->width / 2) { if (closest.x < -box->width / 2) {
closest.x = -aabb->width / 2; closest.x = -box->width / 2;
} }
if (closest.x > aabb->width / 2) { if (closest.x > box->width / 2) {
closest.x = aabb->width / 2; closest.x = box->width / 2;
} }
if (closest.y < -aabb->height / 2) { if (closest.y < -box->height / 2) {
closest.y = -aabb->height / 2; closest.y = -box->height / 2;
} }
if (closest.y > aabb->height / 2) { if (closest.y > box->height / 2) {
closest.y = aabb->height / 2; closest.y = box->height / 2;
} }
// si la position n'a pas été changée, elle // si la position n'a pas été changée, elle était déjà
// était déjà à l'intérieur du cercle : le cercle // à l'intérieur du cercle : le cercle est dans le rectangle
// est dans le bloc float is_inside = false;
float isInside = false;
if (relpos == closest) { if (relpos == closest) {
isInside = true; is_inside = true;
// on se colle au bord le plus proche du bloc // on se colle au bord le plus proche du rectangle
if (std::abs(relpos.x) > std::abs(relpos.y)) { if (std::abs(relpos.x) > std::abs(relpos.y)) {
if (closest.x > 0) { if (closest.x > 0) {
closest.x = aabb->width / 2; closest.x = box->width / 2;
} else { } else {
closest.x = -aabb->width / 2; closest.x = -box->width / 2;
} }
} else { } else {
if (closest.y > 0) { if (closest.y > 0) {
closest.y = aabb->height / 2; closest.y = box->height / 2;
} else { } else {
closest.y = -aabb->height / 2; closest.y = -box->height / 2;
} }
} }
} }
// la normale est portée par la direction // la normale est portée par la direction
// du point le plus proche au centre de la balle // du point le plus proche au centre du cercle
sf::Vector2f prenormal = relpos - closest; sf::Vector2f prenormal = relpos - closest;
float squaredLength = prenormal.x * prenormal.x + prenormal.y * prenormal.y; float squared_length = prenormal.x * prenormal.x + prenormal.y * prenormal.y;
// si la balle est à l'extérieur et que // si le cercle est à l'extérieur et que la normale est plus
// la normale est plus longue que son rayon, // longue que son rayon, il n'y a pas collision
// il n'y a pas collision if (!is_inside && squared_length >= circle.getRadius() * circle.getRadius()) {
if (!isInside && squaredLength >= player.getRadius() * player.getRadius()) {
return false; return false;
} }
float length = std::sqrt(squaredLength); float length = std::sqrt(squared_length);
data.depth = player.getRadius() - length; data.depth = circle.getRadius() - length;
if (length != 0) { if (length != 0) {
data.normal = prenormal / length; data.normal = prenormal / length;
} }
if (isInside) { if (is_inside) {
data.normal *= -1.f; data.normal *= -1.f;
} }
@ -92,66 +90,66 @@ bool playerToBlock(CollisionData& data) {
/** /**
* Détermination des informations sur une collision entre * Détermination des informations sur une collision entre
* un bloc et un joueur (normale et profondeur de collision) * un rectangle et un cercle
*/ */
bool blockToPlayer(CollisionData& data) { bool AABBToCircle(CollisionData& data) {
// la collision Block -> Player est la collision Player -> Block // la collision rectangle -> cercle est la collision cercle -> rectangle
Object& objT = data.objB; Object& transfer = data.obj_b;
data.objB = data.objA; data.obj_b = data.obj_a;
data.objA = objT; data.obj_a = transfer;
return playerToBlock(data); return circleToAABB(data);
} }
/** /**
* Détermination des informations sur une collision entre * Détermination des informations sur une collision entre
* deux joueurs (normale et profondeur de collision) * deux cercles
*/ */
bool playerToPlayer(CollisionData& data) { bool circleToCircle(CollisionData& data) {
Player playerA = dynamic_cast<Player&>(data.objA); Object& circle_a = data.obj_a;
Player playerB = dynamic_cast<Player&>(data.objB); Object& circle_b = data.obj_b;
sf::Vector2f dir = playerB.getPosition() - playerA.getPosition(); sf::Vector2f dir = circle_b.getPosition() - circle_a.getPosition();
float squaredLength = dir.x * dir.x + dir.y * dir.y; float squared_length = dir.x * dir.x + dir.y * dir.y;
float totalRadius = playerB.getRadius() + playerA.getRadius(); float total_radius = circle_b.getRadius() + circle_a.getRadius();
// si les deux balles sont à une distance supérieure // si les deux cercles sont à une distance supérieure
// à la somme de leurs deux rayons, il n'y a pas eu collision // à la somme de leurs deux rayons, il n'y a pas eu collision
if (squaredLength > totalRadius * totalRadius) { if (squared_length > total_radius * total_radius) {
return false; return false;
} }
float length = std::sqrt(squaredLength); float length = std::sqrt(squared_length);
// les balles sont sur la même position. // les cercles sont sur la même position.
// Renvoie une normale apte à séparer les deux balles // Renvoie une normale apte à séparer les deux cercles
if (length == 0) { if (length == 0) {
data.depth = totalRadius; data.depth = total_radius;
data.normal.x = 0; data.normal.x = 0;
data.normal.y = -1; data.normal.y = -1;
return true; return true;
} }
// il y a eu collision // il y a eu collision
data.depth = totalRadius - length; data.depth = total_radius - length;
data.normal = dir / length; data.normal = dir / length;
return true; return true;
} }
/** /**
* Détermination des informations sur une collision entre * Détermination des informations sur une collision entre
* deux blocs (normale et profondeur de collision) * deux rectangles
*/ */
bool blockToBlock(CollisionData& data) { bool AABBToAABB(CollisionData& data) {
Block blockA = dynamic_cast<Block&>(data.objA); Object& aabb_a = data.obj_a;
Block blockB = dynamic_cast<Block&>(data.objB); Object& aabb_b = data.obj_b;
std::unique_ptr<sf::FloatRect> aabb = blockA.getAABB(); std::unique_ptr<sf::FloatRect> box_a = aabb_a.getAABB();
std::unique_ptr<sf::FloatRect> obj_aabb = blockB.getAABB(); std::unique_ptr<sf::FloatRect> box_b = aabb_b.getAABB();
sf::Vector2f relpos = blockB.getPosition() - blockA.getPosition(); sf::Vector2f relpos = aabb_b.getPosition() - aabb_a.getPosition();
float overlap_x = aabb->width / 2 + obj_aabb->width / 2 - std::abs(relpos.x); float overlap_x = box_a->width / 2 + box_b->width / 2 - std::abs(relpos.x);
float overlap_y = aabb->height / 2 + obj_aabb->height / 2 - std::abs(relpos.y); float overlap_y = box_a->height / 2 + box_b->height / 2 - std::abs(relpos.y);
// si il n'y a pas de chauvauchement sur l'axe X et Y, pas de collision // si il n'y a pas de chauvauchement sur l'axe X et Y, pas de collision
if (overlap_x <= 0 || overlap_y <= 0) { if (overlap_x <= 0 || overlap_y <= 0) {
@ -187,26 +185,19 @@ bool blockToBlock(CollisionData& data) {
* dans une collision à leur fonction de résolution * dans une collision à leur fonction de résolution
*/ */
std::map< std::map<
std::pair<unsigned int, unsigned int>, std::pair<CollisionType, CollisionType>,
std::function<bool(CollisionData&)> std::function<bool(CollisionData&)>
> collision_map = { > collision_map = {
{std::make_pair(Player::TYPE_ID, Player::TYPE_ID), playerToPlayer}, {std::make_pair(CollisionType::CIRCLE, CollisionType::CIRCLE), circleToCircle},
{std::make_pair(Player::TYPE_ID, Block::TYPE_ID), playerToBlock}, {std::make_pair(CollisionType::CIRCLE, CollisionType::AABB), circleToAABB},
{std::make_pair(Player::TYPE_ID, GravityBlock::TYPE_ID), playerToBlock}, {std::make_pair(CollisionType::AABB, CollisionType::AABB), AABBToAABB},
{std::make_pair(Block::TYPE_ID, Block::TYPE_ID), blockToBlock}, {std::make_pair(CollisionType::AABB, CollisionType::CIRCLE), AABBToCircle}
{std::make_pair(Block::TYPE_ID, GravityBlock::TYPE_ID), blockToBlock},
{std::make_pair(Block::TYPE_ID, Player::TYPE_ID), blockToPlayer},
{std::make_pair(GravityBlock::TYPE_ID, Block::TYPE_ID), blockToBlock},
{std::make_pair(GravityBlock::TYPE_ID, GravityBlock::TYPE_ID), blockToBlock},
{std::make_pair(GravityBlock::TYPE_ID, Player::TYPE_ID), blockToPlayer}
}; };
CollisionData::CollisionData(Object& objA, Object& objB) : CollisionData::CollisionData(Object& obj_a, Object& obj_b) : obj_a(obj_a), obj_b(obj_b) {}
objA(objA), objB(objB) {}
bool getCollisionData(CollisionData& data) { bool getCollisionData(CollisionData& data) {
return collision_map[std::make_pair( return collision_map[std::make_pair(
data.objA.getTypeId(), data.obj_a.getCollisionType(),
data.objB.getTypeId() data.obj_b.getCollisionType()
)](data); )](data);
} }

View File

@ -91,13 +91,13 @@ void Game::update() {
// détection des objets en collision // détection des objets en collision
for (unsigned int i = 0; i < getObjects().size(); i++) { for (unsigned int i = 0; i < getObjects().size(); i++) {
ObjectPtr objA = getObjects()[i]; ObjectPtr obj_a = getObjects()[i];
for (unsigned int j = i + 1; j < getObjects().size(); j++) { for (unsigned int j = i + 1; j < getObjects().size(); j++) {
ObjectPtr objB = getObjects()[j]; ObjectPtr obj_b = getObjects()[j];
CollisionData data(*objA, *objB); CollisionData data(*obj_a, *obj_b);
if (objA->detectCollision(*objB, data)) { if (obj_a->detectCollision(*obj_b, data)) {
colliding.push_back(data); colliding.push_back(data);
} }
} }
@ -111,7 +111,7 @@ void Game::update() {
// résolution des collisions détectées // résolution des collisions détectées
for (unsigned int i = 0; i < colliding.size(); i++) { for (unsigned int i = 0; i < colliding.size(); i++) {
CollisionData& collided = colliding[i]; CollisionData& collided = colliding[i];
collided.objA.solveCollision(*this, collided.objB, collided.normal); collided.obj_a.solveCollision(*this, collided.obj_b, collided.normal);
} }
// intégration de la vitesse dans la position // intégration de la vitesse dans la position
@ -122,8 +122,8 @@ void Game::update() {
// application de la correction positionnelle // application de la correction positionnelle
for (unsigned int i = 0; i < colliding.size(); i++) { for (unsigned int i = 0; i < colliding.size(); i++) {
CollisionData& collided = colliding[i]; CollisionData& collided = colliding[i];
collided.objA.positionalCorrection( collided.obj_a.positionalCorrection(
collided.objB, collided.normal, collided.depth collided.obj_b, collided.normal, collided.depth
); );
} }
} }

View File

@ -108,12 +108,16 @@ std::unique_ptr<sf::FloatRect> Player::getAABB() const {
)); ));
} }
float Player::getRadius() const {
return 10 * getMass();
}
unsigned int Player::getTypeId() const { unsigned int Player::getTypeId() const {
return TYPE_ID; return TYPE_ID;
} }
float Player::getRadius() const { CollisionType Player::getCollisionType() const {
return 10 * getMass(); return CollisionType::CIRCLE;
} }
unsigned int Player::getPlayerNumber() const { unsigned int Player::getPlayerNumber() const {