Séparation des trois phases de collisionage

This commit is contained in:
Mattéo Delabre 2016-03-28 02:03:56 +02:00
parent c5a4402369
commit b9e50580b6
7 changed files with 142 additions and 78 deletions

View File

@ -2,19 +2,19 @@
#define __PTF_COLLISION_HPP__ #define __PTF_COLLISION_HPP__
#include "object.hpp" #include "object.hpp"
#include "collision_data.hpp"
#include <SFML/Graphics.hpp> #include <SFML/Graphics.hpp>
#include <utility> #include <utility>
#include <string>
namespace Collision { namespace Collision {
typedef bool (*collision_data)(Object&, Object&, sf::Vector2f&, float&); typedef bool (*collision_detect)(CollisionData&);
typedef std::map<std::pair<unsigned int, unsigned int>, collision_data> collision_dispatcher; typedef std::map<std::pair<unsigned int, unsigned int>, collision_detect> collision_dispatcher;
extern collision_dispatcher dispatch; extern collision_dispatcher dispatch;
bool playerToBlock(Object& objA, Object& objB, sf::Vector2f& normal, float& depth); bool playerToBlock(CollisionData& data);
bool blockToPlayer(Object& objA, Object& objB, sf::Vector2f& normal, float& depth); bool blockToPlayer(CollisionData& data);
bool playerToPlayer(Object& objA, Object& objB, sf::Vector2f& normal, float& depth); bool playerToPlayer(CollisionData& data);
bool blockToBlock(Object& objA, Object& objB, sf::Vector2f& normal, float& depth); bool blockToBlock(CollisionData& data);
} }
#endif #endif

View File

@ -0,0 +1,22 @@
#ifndef __PTF_COLLISION_DATA_HPP__
#define __PTF_COLLISION_DATA_HPP__
#include <SFML/Graphics.hpp>
class Object;
/**
* Structure qui retient des informations sur les
* collisions
*/
struct CollisionData {
sf::Vector2f normal;
float depth;
Object& objA;
Object& objB;
CollisionData(Object& objA, Object& objB) : objA(objA), objB(objB) {}
};
#endif

View File

@ -43,8 +43,8 @@ namespace Constants {
* Correction positionnelle : pourcentage de correction * Correction positionnelle : pourcentage de correction
* et seuil de correction * et seuil de correction
*/ */
static constexpr float CORRECTION_PERCENTAGE = .8f; static constexpr float CORRECTION_PERCENTAGE = .2f;
static constexpr float CORRECTION_THRESHOLD = .01f; static constexpr float CORRECTION_THRESHOLD = .05f;
/** /**
* Masse par défaut d'un objet * Masse par défaut d'un objet

View File

@ -4,6 +4,7 @@
#include <SFML/Graphics.hpp> #include <SFML/Graphics.hpp>
#include <memory> #include <memory>
#include "engine_state.hpp" #include "engine_state.hpp"
#include "collision_data.hpp"
#include "resource_manager.hpp" #include "resource_manager.hpp"
class Block; class Block;
@ -54,10 +55,26 @@ public:
/** /**
* Détecte s'il y a collision entre cet objet * Détecte s'il y a collision entre cet objet
* et l'objet passé en paramètre et résoud la collision * et l'objet passé en paramètre
* si elle a lieu
*/ */
void collide(Object& obj); bool detectCollision(Object& obj, CollisionData& data);
/**
* Résolution de la collision entre cet objet
* et l'objet passé en paramètre selon la normale
* donnée
*/
void solveCollision(Object& obj, sf::Vector2f normal);
/**
* Application de la correction positionnelle sur
* cet objet et l'objet passé en paramètre après
* une résolution de collision de profondeur donnée.
* En raison de l'imprécision des flottants sur la machine,
* les objets peuvent accumuler une erreur de positionnement
* qui les fait "plonger" les uns dans les autres
*/
void positionalCorrection(Object& obj, sf::Vector2f normal, float depth);
/** /**
* Récupère la boîte englobante de l'objet * Récupère la boîte englobante de l'objet

View File

@ -1,4 +1,5 @@
#include "collision.hpp" #include "collision.hpp"
#include "collision_data.hpp"
#include "player.hpp" #include "player.hpp"
#include "block.hpp" #include "block.hpp"
#include "object.hpp" #include "object.hpp"
@ -14,9 +15,9 @@ namespace Collision {
{std::make_pair(Block::TYPE_ID, Block::TYPE_ID), &blockToBlock} {std::make_pair(Block::TYPE_ID, Block::TYPE_ID), &blockToBlock}
}; };
bool playerToBlock(Object& objA, Object& objB, sf::Vector2f& normal, float& depth) { bool playerToBlock(CollisionData& data) {
Player player = dynamic_cast<Player&>(objA); Player player = dynamic_cast<Player&>(data.objA);
Block block = dynamic_cast<Block&>(objB); Block block = dynamic_cast<Block&>(data.objB);
// recherche du point le plus proche du centre de la // recherche du point le plus proche du centre de la
// balle sur le bloc. On regarde la position relative // balle sur le bloc. On regarde la position relative
@ -80,30 +81,31 @@ namespace Collision {
} }
float length = std::sqrt(squaredLength); float length = std::sqrt(squaredLength);
depth = player.getRadius() - length; data.depth = player.getRadius() - length;
if (length != 0) { if (length != 0) {
normal = prenormal / length; data.normal = prenormal / length;
} }
if (isInside) { if (isInside) {
normal *= -1.f; data.normal *= -1.f;
} }
return true; return true;
} }
bool blockToPlayer(Object& objA, Object& objB, sf::Vector2f& normal, float& depth) { bool blockToPlayer(CollisionData& data) {
// la collision Block -> Player est la collision Player -> Block // la collision Block -> Player est la collision Player -> Block
// avec une normale de collision retournée Object& objT = data.objB;
bool result = playerToBlock(objB, objA, normal, depth); data.objB = data.objA;
normal *= -1.f; data.objA = objT;
return result;
return playerToBlock(data);
} }
bool playerToPlayer(Object& objA, Object& objB, sf::Vector2f& normal, float& depth) { bool playerToPlayer(CollisionData& data) {
Player playerA = dynamic_cast<Player&>(objA); Player playerA = dynamic_cast<Player&>(data.objA);
Player playerB = dynamic_cast<Player&>(objB); Player playerB = dynamic_cast<Player&>(data.objB);
sf::Vector2f dir = playerB.getPosition() - playerA.getPosition(); sf::Vector2f dir = playerB.getPosition() - playerA.getPosition();
float squaredLength = dir.x * dir.x + dir.y * dir.y; float squaredLength = dir.x * dir.x + dir.y * dir.y;
@ -120,21 +122,21 @@ namespace Collision {
// les balles sont sur la même position. // les balles sont sur la même position.
// Renvoie une normale apte à séparer les deux balles // Renvoie une normale apte à séparer les deux balles
if (length == 0) { if (length == 0) {
depth = totalRadius; data.depth = totalRadius;
normal.x = 0; data.normal.x = 0;
normal.y = -1; data.normal.y = -1;
return true; return true;
} }
// il y a eu collision // il y a eu collision
depth = totalRadius - length; data.depth = totalRadius - length;
normal = dir / length; data.normal = dir / length;
return true; return true;
} }
bool blockToBlock(Object& objA, Object& objB, sf::Vector2f& normal, float& depth) { bool blockToBlock(CollisionData& data) {
Block blockA = dynamic_cast<Block&>(objA); Block blockA = dynamic_cast<Block&>(data.objA);
Block blockB = dynamic_cast<Block&>(objB); Block blockB = dynamic_cast<Block&>(data.objB);
std::unique_ptr<sf::FloatRect> aabb = blockA.getAABB(); std::unique_ptr<sf::FloatRect> aabb = blockA.getAABB();
std::unique_ptr<sf::FloatRect> obj_aabb = blockB.getAABB(); std::unique_ptr<sf::FloatRect> obj_aabb = blockB.getAABB();
@ -151,22 +153,22 @@ namespace Collision {
// on choisit l'axe de pénétration maximale pour calculer la normale // on choisit l'axe de pénétration maximale pour calculer la normale
if (overlap_x < overlap_y) { if (overlap_x < overlap_y) {
if (relpos.x < 0) { if (relpos.x < 0) {
normal.x = -1; data.normal.x = -1;
} else { } else {
normal.x = 1; data.normal.x = 1;
} }
normal.y = 0; data.normal.y = 0;
depth = overlap_x; data.depth = overlap_x;
} else { } else {
if (relpos.y < 0) { if (relpos.y < 0) {
normal.y = -1; data.normal.y = -1;
} else { } else {
normal.y = 1; data.normal.y = 1;
} }
normal.x = 0; data.normal.x = 0;
depth = overlap_y; data.depth = overlap_y;
} }
return true; return true;

View File

@ -71,13 +71,19 @@ void Engine::addObject(Object& object) {
} }
void Engine::update() { void Engine::update() {
// gestion des collisions entre les objets std::vector<CollisionData> colliding;
// détection des objets en collision
for (unsigned int i = 0; i < state.objects.size(); i++) { for (unsigned int i = 0; i < state.objects.size(); i++) {
Object* objA = state.objects[i]; Object* objA = state.objects[i];
for (unsigned int j = i + 1; j < state.objects.size(); j++) { for (unsigned int j = i + 1; j < state.objects.size(); j++) {
Object* objB = state.objects[j]; Object* objB = state.objects[j];
objA->collide(*objB); CollisionData data(*objA, *objB);
if (objA->detectCollision(*objB, data)) {
colliding.push_back(data);
}
} }
} }
@ -86,11 +92,25 @@ void Engine::update() {
state.objects[i]->updateVelocity(state, Constants::PHYSICS_TIME / 2); state.objects[i]->updateVelocity(state, Constants::PHYSICS_TIME / 2);
} }
// résolution des collisions détectées
for (unsigned int i = 0; i < colliding.size(); i++) {
CollisionData& collided = colliding[i];
collided.objA.solveCollision(collided.objB, collided.normal);
}
// intégration de la vitesse dans la position // intégration de la vitesse dans la position
for (unsigned int i = 0; i < state.objects.size(); i++) { for (unsigned int i = 0; i < state.objects.size(); i++) {
state.objects[i]->updatePosition(state, Constants::PHYSICS_TIME); state.objects[i]->updatePosition(state, Constants::PHYSICS_TIME);
} }
// application de la correction positionnelle
for (unsigned int i = 0; i < colliding.size(); i++) {
CollisionData& collided = colliding[i];
collided.objA.positionalCorrection(
collided.objB, collided.normal, collided.depth
);
}
// intégration des forces dans la vitesse (seconde moitié) // intégration des forces dans la vitesse (seconde moitié)
for (unsigned int i = 0; i < state.objects.size(); i++) { for (unsigned int i = 0; i < state.objects.size(); i++) {
state.objects[i]->updateVelocity(state, Constants::PHYSICS_TIME / 2); state.objects[i]->updateVelocity(state, Constants::PHYSICS_TIME / 2);

View File

@ -1,6 +1,7 @@
#include "object.hpp" #include "object.hpp"
#include "constants.hpp" #include "constants.hpp"
#include "collision.hpp" #include "collision.hpp"
#include "collision_data.hpp"
Object::Object(float x, float y) : Object::Object(float x, float y) :
acceleration(0, 0), velocity(0, 0), position(x, y), acceleration(0, 0), velocity(0, 0), position(x, y),
@ -82,28 +83,27 @@ void Object::updatePosition(EngineState& state, float delta) {
position += velocity * delta; position += velocity * delta;
} }
void Object::collide(Object& obj) { bool Object::detectCollision(Object& obj, CollisionData& data) {
// si les objets ne sont pas sur la même couche, // si les objets ne sont pas sur la même couche,
// ils ne peuvent pas entrer en collision // ils ne peuvent pas entrer en collision
if (getLayer() != obj.getLayer()) { if (getLayer() != obj.getLayer()) {
return; return false;
} }
// si les deux boîtes englobantes des deux objets, ne // si les deux boîtes englobantes des deux objets ne
// s'intersectent pas, il ne risque pas d'y avoir de collision // s'intersectent pas, il ne risque pas d'y avoir de collision
if (!getAABB()->intersects(*obj.getAABB())) { if (!getAABB()->intersects(*obj.getAABB())) {
return; return false;
} }
sf::Vector2f normal; return Collision::dispatch[
float depth; // la fonction de détection fine de collision est choisie
// en fonction des types des deux objets en question
// vérifie plus finement s'il y a collision et récupère std::make_pair(getTypeId(), obj.getTypeId())
// les informations sur la collision (normale et profondeur) ](data);
if (!Collision::dispatch[std::make_pair(getTypeId(), obj.getTypeId())](*this, obj, normal, depth)) { }
return;
}
void Object::solveCollision(Object& obj, sf::Vector2f normal) {
// si les deux objets sont de masse infinie, réinitialisation // si les deux objets sont de masse infinie, réinitialisation
// des vitesses en tant que collision // des vitesses en tant que collision
if (getMassInvert() == 0 && obj.getMassInvert() == 0) { if (getMassInvert() == 0 && obj.getMassInvert() == 0) {
@ -140,30 +140,33 @@ void Object::collide(Object& obj) {
// la tangente est nulle : pas de frottement à générer // la tangente est nulle : pas de frottement à générer
// on évite ainsi une division par zéro // on évite ainsi une division par zéro
if (tangent_length != 0) { if (tangent_length == 0) {
tangent /= tangent_length; return;
float magnitude = -(rel_velo.x * tangent.x + rel_velo.y * tangent.y) /
(getMassInvert() + obj.getMassInvert());
float static_friction = (getStaticFriction() + obj.getStaticFriction()) / 2.f;
float dynamic_friction = (getDynamicFriction() + obj.getDynamicFriction()) / 2.f;
float friction_impulse;
// utilisation de la loi de Coulomb sur les frottements dynamiques/statiques
// cf https://fr.wikipedia.org/wiki/Loi_de_Coulomb_(m%C3%A9canique)
if (std::abs(magnitude) < collision_impulse * static_friction) {
friction_impulse = magnitude;
} else {
friction_impulse = -collision_impulse * dynamic_friction;
}
setVelocity(getVelocity() - getMassInvert() * friction_impulse * tangent);
obj.setVelocity(obj.getVelocity() + obj.getMassInvert() * friction_impulse * tangent);
} }
// correction de la position des objets. En raison de l'imprécision tangent /= tangent_length;
// des flottants sur la machine, les objets peuvent accumuler une
// erreur de positionnement qui les fait "plonger" les un dans les autres. float magnitude = -(rel_velo.x * tangent.x + rel_velo.y * tangent.y) /
(getMassInvert() + obj.getMassInvert());
float static_friction = (getStaticFriction() + obj.getStaticFriction()) / 2.f;
float dynamic_friction = (getDynamicFriction() + obj.getDynamicFriction()) / 2.f;
float friction_impulse;
// utilisation de la loi de Coulomb sur les frottements dynamiques/statiques
// cf https://fr.wikipedia.org/wiki/Loi_de_Coulomb_(m%C3%A9canique)
if (std::abs(magnitude) < collision_impulse * static_friction) {
friction_impulse = magnitude;
} else {
friction_impulse = -collision_impulse * dynamic_friction;
}
setVelocity(getVelocity() - getMassInvert() * friction_impulse * tangent);
obj.setVelocity(obj.getVelocity() + obj.getMassInvert() * friction_impulse * tangent);
}
void Object::positionalCorrection(Object& obj, sf::Vector2f normal, float depth) {
// ne pas corriger les petites erreurs de position
// pour éviter l'instabilité du moteur
if (depth <= Constants::CORRECTION_THRESHOLD) { if (depth <= Constants::CORRECTION_THRESHOLD) {
return; return;
} }