Comment détecter la direction des collisions d'objets rectangulaires 2D?

11

Après cette question , j'ai besoin d'aide.

Comment savoir de quel côté d'un rectangle provient une collision et réagir en conséquence?

rectangles en collision avec de tous les côtés

Les flèches bleues sont les chemins que certains objets circulaires suivraient si avant et après entrer en collision avec la boîte.

Comment puis-je calculer cela?

NemoStein
la source

Réponses:

8

Étant donné que cela est basé sur votre autre question, je vais donner une solution lorsque le rectangle est aligné sur l'axe.

Tout d'abord, vous créez le rectangle de votre objet actuel avec les valeurs suivantes:

int boxLeft = box.X;
int boxRight = boxLeft + box.Width;
int boxTop = box.Y;
int boxBottom = boxTop + box.Height;

Ensuite, vous devez avoir la position de l'ancien objet (que vous pouvez stocker sur chaque objet ou simplement passer à une fonction) pour créer le rectangle de l'ancien objet (lorsqu'il n'était pas en collision):

int oldBoxLeft = box.OldX;
int oldBoxRight = oldBoxLeft + box.Width;
int oldBoxTop = box.OldY;
int oldBoxBottom = oldBoxTop + box.Height;

Maintenant, pour savoir d'où vient la collision, vous devez trouver le côté où l'ancienne position n'était pas dans la zone de collision et où se trouve sa nouvelle position. Parce que, quand on y pense, c'est ce qui se passe quand on entre en collision: un côté qui n'entre pas en collision entre dans un autre rectangle.

Voici comment vous pouvez le faire (ces fonctions supposent qu'il y a une collision. Elles ne doivent pas être appelées s'il n'y a pas de collision):

 bool collidedFromLeft(Object otherObj)
{
    return oldBoxRight < otherObj.Left && // was not colliding
           boxRight >= otherObj.Left;
}

Rincez et répétez.

bool collidedFromRight(Object otherObj)
{
    return oldBoxLeft >= otherObj.Right && // was not colliding
           boxLeft < otherObj.Right;
}

bool collidedFromTop(Object otherObj)
{
    return oldBoxBottom < otherObj.Top && // was not colliding
           boxBottom >= otherObj.Top;
}

bool collidedFromBottom(Object otherObj)
{
    return oldBoxTop >= otherObj.Bottom && // was not colliding
           boxTop < otherObj.Bottom;
}

Maintenant, pour l'utilisation réelle avec la réponse de collision de l'autre question:

if (collidedFromTop(otherObj) || collidedFromBottom(otherObj))
    obj.Velocity.Y = -obj.Velocity.Y;
if (collidedFromLeft(otherObj) || collidedFromRight(otherObj))
    obj.Velocity.X = -obj.Velocity.X;

Encore une fois, ce n'est peut-être pas la meilleure solution, mais c'est la façon dont je procède habituellement pour la détection des collisions.

Jesse Emond
la source
Encore une fois, vous aviez raison! ; D Merci ... (envoyez-moi plus de cartes postales de votre forum la prochaine fois ... ^ ___ ^)
NemoStein
Ahhh malheureusement je ne savais pas à quoi je pouvais l'utiliser .. peut-être la prochaine fois!
Jesse Emond
7

Comme la question est partiellement identique à cette question , je vais réutiliser certaines parties de ma réponse afin d'essayer de répondre à votre question.


Permet de définir un contexte et certaines variables pour rendre l'explication suivante plus compréhensible. Le formulaire de représentation que nous utiliserons ici ne correspond probablement pas à la forme de vos propres données, mais il devrait être plus simple à comprendre de cette façon (en effet, vous pouvez utiliser les méthodes suivantes en utilisant d'autres types de représentations une fois que vous comprenez le principe)

Ainsi, nous considérerons une boîte englobante alignée sur l'axe (ou une boîte délimitée orientée ) et une entité en mouvement .

  • La boîte englobante est composée de 4 côtés, et nous définirons chacun comme:
    Côté1 = [x1, y1, x2, y2] (deux points [x1, y1] et [x2, y2])

  • L'entité en mouvement est définie comme un vecteur vitesse (position + vitesse):
    une position [posX, posY] et une vitesse [speedX, speedY] .


Vous pouvez déterminer quel côté d'un AABB / OBB est frappé par un vecteur en utilisant la méthode suivante:

  • 1 / Trouver les points d'intersection entre les lignes infinies passant par les quatre côtés de l'AABB et la ligne infinie passant par la position d'entité (précollision) qui utilisent le vecteur vitesse d'entité comme pente. (Vous pouvez trouver un point de collision ou un nombre indéfini, ce qui correspond à des parallèles ou des lignes qui se chevauchent)

  • 2 / Une fois que vous connaissez les points d'intersection (s'ils existent), vous pouvez rechercher ceux qui se trouvent dans les limites du segment.

  • 3 / Enfin, s'il y a encore plusieurs points sur la liste (le vecteur vitesse peut passer par plusieurs côtés), vous pouvez rechercher le point le plus proche de l'origine de l'entité en utilisant les grandeurs du vecteur de l'intersection à l'origine de l'entité.

Ensuite, vous pouvez déterminer l'angle de collision à l'aide d'un simple produit scalaire.

  • 4 / Trouvez l'angle entre la collision à l'aide d'un produit scalaire du vecteur entité (probablement une balle?) Avec le vecteur côté touché.

----------

Plus de détails:

  • 1 / Trouver des intersections

    • a / Déterminer les lignes infinies (Ax + Bx = D) en utilisant leurs formes paramétriques (P (t) = Po + tD).

      Origine du point: Po = [posX, posY]
      Vecteur de direction: D = [speedX, speedY]

      A = Dy = speedY
      B = -Dx = -speedX
      D = (Po.x * Dy) - (Po.y * Dx) = (posX speedY) - (posY speedX)

      Ax + By = D <====> (speedY x) + (-speedX y) = (posX speedY) - (posY speedX)

      J'ai utilisé les valeurs de points d'entité pour illustrer la méthode, mais c'est exactement la même méthode pour déterminer les 4 lignes infinies latérales de la boîte englobante (utilisez Po = [x1, y1] et D = [x2-x1; y2-y1] au lieu).

    • b / Ensuite, pour trouver l'intersection de deux droites infinies, nous pouvons résoudre le système suivant:

      A1x + B1x = D1 <== Ligne passant par le point d'entité avec le vecteur vitesse comme slop.
      A2x + B2x = D2 <== L'une des lignes passant par les côtés AABB.

      qui donne les coordonnées suivantes pour l'interception:

      Interception x = (( B 2 * D 1) - ( B 1 * D 2)) / (( A 1 * B 2) - ( A 2 * B 1))
      Interception y = (( A 1 * D 2) - ( A 2 * D 1)) / (( A 1 * B 2) - ( A 2 * B 1))

      Si le dénominateur ((A1 * B2) - (A2 * B1)) est égal à zéro, alors les deux lignes sont parallèles ou se chevauchent, sinon vous devriez trouver une intersection.

  • 2 / Testez les limites des segments. Comme cela est simple à vérifier, il n'y a pas besoin de plus de détails.

  • 3 / Recherchez le point le plus proche. S'il y a encore plusieurs points sur la liste, nous pouvons trouver quel côté est le plus proche du point d'origine de l'entité.

    • a / Déterminer le vecteur allant du point d'intersection au point d'origine de l'entité

      V = Po - Int = [Po.x - Int.x; Po.y - Int.y]

    • b / Calculer la magnitude du vecteur

      || V || = sqrt (V.x² + V.y²)

    • c / trouver le plus petit.
  • 4 / Maintenant que vous savez de quel côté vous allez frapper, vous pouvez déterminer l'angle à l'aide d'un produit scalaire.

    • a / Soit S = [x2-x1; y2-y1] est le vecteur latéral qui sera touché et E = [speedX; speedY] est le vecteur de vitesse d'entité.

      En utilisant la règle du produit scalaire vectoriel, nous savons que

      S · E = Sx Ex + Sy Ey
      et
      S · E = || S || || E || cos θ

      On peut donc déterminer θ en manipulant un peu cette équation ...

      cos θ = (S · E) / (|| S || || E ||)

      θ = acos ((S · E) / (|| S || || E ||))

      avec

      S · E = Sx * Ex + Sy * Ey
      || S || = sqrt (Sx² + Sy²)
      || E || = sqrt (Ex² + Ey²)


Remarque: comme je l'ai dit dans l'autre fil de questions, ce n'est probablement pas le moyen le plus efficace ni le plus simple de le faire, c'est juste ce qui est venu à l'esprit, et une partie des mathématiques pourrait peut-être aider.

Je n'ai pas vérifié avec un exemple concret d'OBB (je l'ai fait avec un AABB) mais cela devrait aussi fonctionner.

Valkea
la source
6

Un moyen simple consiste à résoudre la collision, puis à traduire tour à tour la zone de collision de l'objet en mouvement d'un pixel dans chaque direction et de voir celles qui provoquent la collision.

Si vous voulez le faire "correctement" et avec des formes de collision tournées ou des polygones arbitraires, je suggère de lire le théorème de l'axe de séparation. Le logiciel Metanet (les gens qui ont créé le jeu N), par exemple, a un super article de développement sur SAT . Ils discutent également de la physique impliquée.

Anko
la source
2

Une façon serait de faire tourner le monde autour de votre rectangle. "Le monde" dans ce cas n'est que les objets qui vous intéressent: le rectangle et la balle. Vous faites pivoter le rectangle autour de son centre jusqu'à ce que ses limites soient alignées avec les axes x / y, puis vous faites pivoter la balle de la même quantité.

Le point important ici est que vous faites tourner la balle autour du centre du rectangle, pas du sien.

Ensuite, vous pouvez facilement tester la collision comme vous le feriez avec n'importe quel autre rectangle non tourné.


Une autre option consiste à traiter le rectangle comme quatre segments de ligne distincts et à tester la collision avec chacun d'eux séparément. Cela vous permet de tester la collision et de déterminer quel côté a été heurté en même temps.

BlueRaja - Danny Pflughoeft
la source
1

J'ai utilisé des angles fixes dans mes calculs, mais cela devrait vous aider

void Bullet::Ricochet(C_Rect *r)
{
    C_Line Line;
    //the next two lines are because I detected 
    // a collision in my main loop so I need to take a step back.

    x = x + ceil(speed * ((double)fcos(itofix(angle)) / 65536));
    y = y + ceil(speed * ((double)fsin(itofix(angle)) / 65536));
    C_Point Prev(x,y);

    //the following checks our position to all the lines will give us
    // an answer which line we will hit due to no lines
    // with angles > 90 lines of a rect always shield the other lines.

    Line = r->Get_Closest_Line(Prev);    
    int langle = 0;
    if(!Line.Is_Horizontal())   //we need to rotate the line to a horizontal position
    {
        langle = Line.Get_Point1().Find_Fixed_Angle(Line.Get_Point2());
        angle = angle - langle;  //to give us the new angle of approach
    }
    //at this point the line is horizontal and the bullet is ready to be fixed.
    angle = 256 - angle;
    angle += langle;
}
David Sopala
la source