Test de collision "pixel-perfect"

Création pour la SFML

/*!
* \brief Gestion des collisions entre deux sf::Sprites en gérant la transparence ( pixel perfect ).
* \author 
* \note Utilise la version pre-1.3.
* \warning Les deux Sprites ne doivent pas avoir subis de rotation!!
* \param s1 : premier sf::Sprite.
* \param s2 : deuxième sf::Sprite.
* \param AlphaMax : seuil de transparence : -1 par defaut (PixelPerfect désactivé)
* \return true si collision il y a, false en cas contraire.
*/
inline bool AreColliding(const sf::Sprite& s1, const sf::Sprite& s2, const int AlphaMax = -1)
{
    // Définition deux objets représentant les dimensions de s1 et s2.
    // Si les Sprites ont été tournés, les calculs ne fonctionneront pas!
    sf::Vector2f pos = s1.GetPosition() - s1.GetCenter();
    const sf::FloatRect r1(pos.x, pos.y, pos.x + s1.GetSize().x, pos.y + s1.GetSize().y);
    pos = s2.GetPosition() - s2.GetCenter();
    const sf::FloatRect r2(pos.x, pos.y, pos.x + s2.GetSize().x, pos.y + s2.GetSize().y);
 
    // Espace de collision potentiel.
    sf::FloatRect zone;
 
    // Testons si les Sprites se superposent.
    if (r1.Intersects(r2, &zone))
    {
        if (AlphaMax >= 0)
        {
            int left1 = static_cast<int>(zone.Left - r1.Left);
            int top1 = static_cast<int>(zone.Top - r1.Top);
 
            int left2 = static_cast<int>(zone.Left - r2.Left);
            int top2 = static_cast<int>(zone.Top - r2.Top);
 
            int width = static_cast<int>(zone.GetWidth());
            int height = static_cast<int>(zone.GetHeight());
 
            for (int y = 0; y < height; y++)
            {
                for (int x = 0; x < width; x++)
                {
                    if (s1.GetPixel(x+left1, y+top1).a > AlphaMax && s2.GetPixel(x+left2, y+top2).a > AlphaMax)
                        return true;
                }
            }
 
            return false;
        }
 
        return true;
    }
 
    return false;
}

Adaptation de la SDL

/**
 *  Partie de Game Develop
 *
 *  Par Florian "4ian" Rival
 *
 */
 
#define SDL_COLLIDE_MAX(a,b)	((a > b) ? a : b)
#define SDL_COLLIDE_MIN(a,b)	((a < b) ? a : b)
 
////////////////////////////////////////////////////////////
/// Vérifie une collision entre deux objets ( pixel perfect )
/// Code adapaté depuis un code pour la SDL
////////////////////////////////////////////////////////////
bool CheckCollision(sf::Sprite *SpriteObjet1, sf::Sprite *SpriteObjet2)
{
 
    if (SpriteObjet1->GetTop() + SpriteObjet1->GetHeight() > SpriteObjet2->GetTop() &&
        SpriteObjet1->GetLeft() + SpriteObjet1->GetWidth() > SpriteObjet2->GetLeft() &&
        SpriteObjet1->GetLeft() < SpriteObjet2->GetLeft() + SpriteObjet2->GetWidth() &&
        SpriteObjet1->GetTop() < SpriteObjet2->GetTop() + SpriteObjet2->GetHeight())
    {
 
        float ax = SpriteObjet1->GetLeft();
        float ay = SpriteObjet1->GetTop();
        float bx = SpriteObjet2->GetLeft();
        float by = SpriteObjet2->GetTop();
        /*a - bottom right co-ordinates*/
        float ax1 = ax + SpriteObjet1->GetWidth() - 1;
        float ay1 = ay + SpriteObjet1->GetHeight() - 1;
 
        /*b - bottom right co-ordinates*/
        float bx1 = bx + SpriteObjet2->GetWidth() - 1;
        float by1 = by +  SpriteObjet2->GetHeight() - 1;
 
        /*check if bounding boxes intersect*/
        if((bx1 < ax) || (ax1 < bx))
            return false;
        if((by1 < ay) || (ay1 < by))
            return false;
 
 
       	float inter_x0 = SDL_COLLIDE_MAX(ax,bx);
        float inter_x1 = SDL_COLLIDE_MIN(ax1,bx1);
 
        float inter_y0 = SDL_COLLIDE_MAX(ay,by);
        float inter_y1 = SDL_COLLIDE_MIN(ay1,by1);
 
        for(int y = inter_y0 ; y <= inter_y1 ; y++)
        {
            for(int x = inter_x0 ; x <= inter_x1 ; x++)
            {
                if((SpriteObjet1.GetPixel(x-ax , y-ay).a!=0)/*compute offsets for surface*/
                && (SpriteObjet2.GetPixel( x-bx , y-by).a!=0))/*before pass to SDL_TransparentPixel*/
                    return true;
            }
        }
        return false;
    }
    return false;
}

Note : Normalement, mon code utilise des objets. Ici, j'ai fait directement un test avec des sf::Sprite. 4ian

Test de collision par diamètre

Une autre solution, simple et peu gourmande, consiste a attribuer un cercle a nos objets, puis a téster la collision entre ces cercles.

Mais il nous faudra avant tout une petite fonction de calcul de distance entre deux points : distance_entre_deux_points .

Puis, tout simplement :

Test de Collision

/*!
* \brief Gestion des collisions entre deux cercles
* \param p1 : Centre du premier cercle
* \param r1 : Rayon du premier cercle
* \param p2 : Centre du second cercle
* \param r2 : Rayon du second cercle
* \return true si collision il y a, false en cas contraire.
*/
inline bool AreColliding(const sf::Vector2f& p1, const float r1, const sf::Vector2f& p2, const float r2)
{
    if (Distance(p1, p2) <= r1+r2)
        return true;
 
    return false;
}

Test de collision inter-sprites "droite-gauche-haut-bas"

/*!
* \brief Gestion des collisions entre deux sf::Sprites, qui donne la direction du choc pour le sprite 1.
* \author Scaerloc
* \note on peut la mixer avec la fonction "pixel-perfect" ci-dessus, pour faire du pixel perfect.
* \param sprite1 : premier sf::Sprite, qui est la cible (typiquement, un personnage qui veut savoir s'il peut se déplacer dans telle ou telle         direction)
* \param sprite2 : deuxième sf::Sprite, qui est l'obstacle.
* \return un vector de 5 booléens (toutes directions, haut, bas, gauche et droite), qui valent true si le sprite 2 rentre dans le sprite 1 par la     direction définie.
*/
 
// énumération des directions
enum {DIR_DROITE = 0, DIR_GAUCHE = 1, DIR_HAUTE = 2, DIR_BAS = 3, TOUTES_DIR = 4};
 
std::vector < bool > collisionSprites(sf::Sprite sprite1, sf::Sprite sprite2)
{
 
    std::vector < bool > collisionsV(5, false);
 
 
 
       /*Creation des rectangles représentants les surfaces (noms honteusements pompés à la fonction de pixel perfect ;)*/
       sf::FloatRect r1(sprite1.GetLeft(), sprite1.GetTop(), sprite1.GetLeft() + sprite1.GetWidth(), sprite1.GetTop() + sprite1.GetHeight()),
                r2(sprite2.GetLeft(), sprite2.GetTop(), sprite2.GetLeft() + sprite2.GetWidth(), sprite2.GetTop() + sprite2.GetHeight()),
                zone;
 
 
 
       if (r1.Intersects(r2, &zone))
       {
           collisionsV[TOUTES_DIR] = true;
 
        if (zone.Left < r1.Left + r1.GetWidth()/2 && zone.GetHeight() > MARGE_COLLISION_BAS) // si cela arrive par la gauche
            collisionsV[DIR_GAUCHE] = true;                                                  // on met le bool "gauche" à true, etc...
        if (zone.Right > r1.Left + r1.GetWidth()/2 && zone.GetHeight() > MARGE_COLLISION_BAS)
            collisionsV[DIR_DROITE] = true;
 
        if (zone.Top > r1.Top + r1.GetHeight()/2)
            collisionsV[DIR_BAS] = true;
        if (zone.Bottom < r1.Top + r1.GetHeight()/2)
            collisionsV[DIR_HAUTE] = true;
    }
 
return collisionsV; // on renvoie le vector de booléens
}

Test de collision inter-sprites localisé (avec nouvelle version de sfml)

Suite au mise à jour de la SFML, la fonction membre sf::Sprite.Left n'est plus disponible; voici donc une nouvelle solution pour réaliser un test de collision localisé qui retourne la localisation sous forme angulaire (Le sprite2 est la référence au cercle trigonometrique);

- 0° est a droite du second sprite, 180° a gauche, 90° en haut, 270° en bas, (45°, 135°, 225°, 315°, sont également des valeurs pouvant être retourné)

- -1 signifie qu'il n'y a pas de collision

/*//////////////////////////////////////// 
   Code de localisation des Collision 
   Par Scheb 
*///////////////////////////////////////// 
 
#ifndef PI 
   #define PI (3.14159265358979323846) 
#endif 
 
//Fonction outil pour la rotation d'un point 
inline sf::Vector2f RotationPoint(const sf::Vector2f& Point, float Angle) 
{ 
   Angle = Angle * (PI/180.0); 
    sf::Vector2f RotatedPoint; 
    RotatedPoint.x = Point.x * cos(Angle) + Point.y * sin(Angle); 
    RotatedPoint.y = -Point.x * sin(Angle) + Point.y * cos(Angle); 
    return RotatedPoint; 
} 
 
int CollisionBoiteSensible( sf::Sprite& Image1,  sf::Sprite& Image2) 
{ 
 
   //Schema de la represensation trigonometrique 
   //       135°   90°  45° 
   //           B_____C 
   //      180° |     | 0° 
   //           |     | 
   //           A_____D 
   //      225°   270°  315° 
   //Cette fonction retourne une valeur angulaire suivant ce schema 
 
 
   sf::Rect<float> boite1; 
   boite1.Left   = Image1.GetPosition().x - Image1.GetCenter().x * Image1.GetScale().x; 
   boite1.Top    = Image1.GetPosition().y - Image1.GetCenter().y * Image1.GetScale().y; 
   boite1.Right  = Image1.GetPosition().x + Image1.GetSize().x - Image1.GetCenter().x * Image1.GetScale().x; 
   boite1.Bottom = Image1.GetPosition().y + Image1.GetSize().y - Image1.GetCenter().y * Image1.GetScale().y; 
 
   if(Image1.GetRotation() != 0.f) //Si l'angle est different, on doit recalculer les boites 
   { 
      sf::Vector2f pt_A, pt_B, pt_C, pt_D; 
 
      //Schema des points du carrée 
      // B_____C 
      // |     | 
      // |     | 
      // A_____D 
 
 
      pt_A.x = boite1.Left; 
      pt_A.y = boite1.Bottom; 
 
      pt_B.x = boite1.Left; 
      pt_B.y = boite1.Top; 
 
      pt_C.x = boite1.Right; 
      pt_C.y = boite1.Top; 
 
      pt_D.x = boite1.Right; 
      pt_D.y = boite1.Bottom; 
 
      //Rotation 
 
      sf::Vector2f pt_Ar, pt_Br, pt_Cr, pt_Dr; 
      pt_Ar.x = 0; 
      pt_Ar.y = Image1.GetSize().y; 
 
      pt_Br.x = 0; 
      pt_Br.y = 0; 
 
      pt_Cr.x = Image1.GetSize().x; 
      pt_Cr.y = 0; 
 
      pt_Dr.x = Image1.GetSize().x; 
      pt_Dr.y = Image1.GetSize().y; 
 
 
      pt_A = RotationPoint(pt_Ar, Image1.GetRotation()); 
      pt_B = RotationPoint(pt_Br, Image1.GetRotation()); 
      pt_C = RotationPoint(pt_Cr, Image1.GetRotation()); 
      pt_D = RotationPoint(pt_Dr, Image1.GetRotation()); 
 
      sf::Vector2f centre; 
      centre.x = Image1.GetCenter().x * Image1.GetScale().x; 
      centre.y = Image1.GetCenter().y * Image1.GetScale().y; 
      centre = RotationPoint(centre, Image1.GetRotation()); 
 
      pt_A += Image1.GetPosition() - centre; 
      pt_B += Image1.GetPosition() - centre; 
      pt_C += Image1.GetPosition() - centre; 
      pt_D += Image1.GetPosition() - centre; 
 
 
      sf::Vector2f pt_Af, pt_Bf, pt_Cf, pt_Df; 
 
      //Schema des points du carrée après rotation 
      /* 
      //     Bf 
      //    /  \ 
      //  Af    Cf 
      //    \  / 
      //     Df 
        */ 
      //Utilisation de l'operateur ? : condition ? valeur_vrai : valeur_faux; 
 
      pt_Af = pt_A; 
      pt_Af = ((pt_Af.x < pt_B.x)? pt_Af : pt_B); 
      pt_Af = ((pt_Af.x < pt_C.x)? pt_Af : pt_C); 
      pt_Af = ((pt_Af.x < pt_D.x)? pt_Af : pt_D); 
 
      pt_Bf = pt_A; 
      pt_Bf = ((pt_Bf.y < pt_B.y)? pt_Bf : pt_B); 
      pt_Bf = ((pt_Bf.y < pt_C.y)? pt_Bf : pt_C); 
      pt_Bf = ((pt_Bf.y < pt_D.y)? pt_Bf : pt_D); 
 
      pt_Cf = pt_A; 
      pt_Cf = ((pt_Cf.x > pt_B.x)? pt_Cf : pt_B); 
      pt_Cf = ((pt_Cf.x > pt_C.x)? pt_Cf : pt_C); 
      pt_Cf = ((pt_Cf.x > pt_D.x)? pt_Cf : pt_D); 
 
      pt_Df = pt_A; 
      pt_Df = ((pt_Df.y > pt_B.y)? pt_Df : pt_B); 
      pt_Df = ((pt_Df.y > pt_C.y)? pt_Df : pt_C); 
      pt_Df = ((pt_Df.y > pt_D.y)? pt_Df : pt_D); 
 
 
      //Schema des points du carrée après rotation 
      //   __Bf__ 
      //  | /  \ | 
      //  Af    Cf 
      //  | \  / | 
      //   __Df__ 
 
 
      boite1.Left   = pt_Af.x; 
      boite1.Top    = pt_Bf.y; 
      boite1.Right  = pt_Cf.x; 
      boite1.Bottom = pt_Df.y; 
   } 
 
   sf::Rect<float> boite2; 
   boite2.Left   = Image2.GetPosition().x - Image2.GetCenter().x * Image2.GetScale().x; 
   boite2.Top    = Image2.GetPosition().y - Image2.GetCenter().y * Image2.GetScale().y; 
   boite2.Right  = Image2.GetPosition().x + Image2.GetSize().x - Image2.GetCenter().x * Image2.GetScale().x; 
   boite2.Bottom = Image2.GetPosition().y + Image2.GetSize().y - Image2.GetCenter().y * Image2.GetScale().y; 
 
 
   if(Image2.GetRotation() != 0.f) //Si l'angle est different, on doit recalculer les boites 
   { 
      sf::Vector2f pt_A, pt_B, pt_C, pt_D; 
 
      //Schema des points du carrée 
      // B_____C 
      // |     | 
      // |     | 
      // A_____D 
 
 
      pt_A.x = boite2.Left; 
      pt_A.y = boite2.Bottom; 
 
      pt_B.x = boite2.Left; 
      pt_B.y = boite2.Top; 
 
      pt_C.x = boite2.Right; 
      pt_C.y = boite2.Top; 
 
      pt_D.x = boite2.Right; 
      pt_D.y = boite2.Bottom; 
 
      //Rotation 
 
      sf::Vector2f pt_Ar, pt_Br, pt_Cr, pt_Dr; 
      pt_Ar.x = 0; 
      pt_Ar.y = Image2.GetSize().y; 
 
      pt_Br.x = 0; 
      pt_Br.y = 0; 
 
      pt_Cr.x = Image2.GetSize().x; 
      pt_Cr.y = 0; 
 
      pt_Dr.x = Image2.GetSize().x; 
      pt_Dr.y = Image2.GetSize().y; 
 
 
      pt_A = RotationPoint(pt_Ar, Image2.GetRotation()); 
      pt_B = RotationPoint(pt_Br, Image2.GetRotation()); 
      pt_C = RotationPoint(pt_Cr, Image2.GetRotation()); 
      pt_D = RotationPoint(pt_Dr, Image2.GetRotation()); 
 
      sf::Vector2f centre; 
      centre.x = Image2.GetCenter().x * Image2.GetScale().x; 
      centre.y = Image2.GetCenter().y * Image2.GetScale().y; 
      centre = RotationPoint(centre, Image2.GetRotation()); 
 
      pt_A += Image2.GetPosition() - centre; 
      pt_B += Image2.GetPosition() - centre; 
      pt_C += Image2.GetPosition() - centre; 
      pt_D += Image2.GetPosition() - centre; 
 
 
      sf::Vector2f pt_Af, pt_Bf, pt_Cf, pt_Df; 
 
      //Schema des points du carrée après rotation 
      /* 
      //     Bf 
      //    /  \ 
      //  Af    Cf 
      //    \  / 
      //     Df 
        */ 
      //Utilisation de l'operateur ? : condition ? valeur_vrai : valeur_faux; 
 
      pt_Af = pt_A; 
      pt_Af = ((pt_Af.x < pt_B.x)? pt_Af : pt_B); 
      pt_Af = ((pt_Af.x < pt_C.x)? pt_Af : pt_C); 
      pt_Af = ((pt_Af.x < pt_D.x)? pt_Af : pt_D); 
 
      pt_Bf = pt_A; 
      pt_Bf = ((pt_Bf.y < pt_B.y)? pt_Bf : pt_B); 
      pt_Bf = ((pt_Bf.y < pt_C.y)? pt_Bf : pt_C); 
      pt_Bf = ((pt_Bf.y < pt_D.y)? pt_Bf : pt_D); 
 
      pt_Cf = pt_A; 
      pt_Cf = ((pt_Cf.x > pt_B.x)? pt_Cf : pt_B); 
      pt_Cf = ((pt_Cf.x > pt_C.x)? pt_Cf : pt_C); 
      pt_Cf = ((pt_Cf.x > pt_D.x)? pt_Cf : pt_D); 
 
      pt_Df = pt_A; 
      pt_Df = ((pt_Df.y > pt_B.y)? pt_Df : pt_B); 
      pt_Df = ((pt_Df.y > pt_C.y)? pt_Df : pt_C); 
      pt_Df = ((pt_Df.y > pt_D.y)? pt_Df : pt_D); 
 
 
      //Schema des points du carrée après rotation 
      //   __Bf__ 
      //  | /  \ | 
      //  Af    Cf 
      //  | \  / | 
      //   __Df__ 
 
 
      boite2.Left   = pt_Af.x; 
      boite2.Top    = pt_Bf.y; 
      boite2.Right  = pt_Cf.x; 
      boite2.Bottom = pt_Df.y; 
   } 
 
   if(boite1.Intersects(boite2)) //S il y a une intersection 
   { 
      sf::Vector2f Centre; 
 
      Centre.x = boite1.Left + (boite1.Right - boite1.Left)/2.f; 
      Centre.y = boite1.Top + (boite1.Bottom - boite1.Top)/2.f; 
 
      //On prend le centre de l'objet de réference 
 
      //Schema de la technique de visualisation 
      // 
      //      |     | 
      //  135°| 90° | 45° 
      //  ____B_____C____ 
      //      |     | 
      //  180°|     | 0° 
      //  ____A_____D____ 
      //      |     | 
      //  225°| 270°| 315° 
      // 
      //On cherche la zone où se trouve le centre de l'objet de reference 
 
      if(Centre.x <= boite2.Left) 
      { 
         if(Centre.y <= boite2.Top) 
         { 
            return 135; 
         } 
         else if(Centre.y >= boite2.Bottom) 
         { 
            return 225; 
         } 
         else 
         { 
            return 180; 
         } 
      } 
      else if(Centre.x >= boite2.Right) 
      { 
         if(Centre.y <= boite2.Top) 
         { 
            return 45; 
         } 
         else if(Centre.y >= boite2.Bottom) 
         { 
            return 315; 
         } 
         else 
         { 
            return 0; 
         } 
      } 
      else 
      { 
         if(Centre.y <= boite2.Top) 
         { 
            return 90; 
         } 
         else if(Centre.y >= boite2.Bottom) 
         { 
            return 270; 
         } 
         else 
         { 
            //Cas "parfait" -> toutes directions = 360 (cas normal de 0 à 359) 
            return 360; 
         } 
      } 
   } 
 
   //Si valeur négative, pas de collision 
   return -1; 
}
 
fr/sources/collisions.txt · Last modified: 2010/09/08 15:36 by Scheb
 
Recent changes RSS feed Creative Commons License Donate Powered by PHP Valid XHTML 1.0 Valid CSS Driven by DokuWiki