web-dev-qa-db-fra.com

Algorithme mathématique efficace pour calculer les intersections

Pour un jeu que je développe, j'ai besoin d'un algorithme capable de calculer les intersections. J'ai résolu le problème, mais la façon dont je l'ai fait est vraiment désagréable et j'espère que quelqu'un ici pourrait avoir une solution plus élégante.

Une paire de points représente les points d'extrémité d'une ligne tracée entre eux. Étant donné deux paires de points, les lignes tracées se coupent-elles et si oui, à quel point?

Ainsi, par exemple, appelez les lignes (A.x, A.y) - (B.x, B.y) et (C.x, C.y) - (D.x, D.y)

Quelqu'un peut-il penser à une solution? Une solution dans n'importe quelle langue fera l'affaire.

Éditer: Un point que j'aurais dû clarifier, l'algorithme doit retourner faux si le point d'intersection est au-delà des longueurs des segments de ligne.

37
Mitch

La plupart des réponses déjà ici semblent suivre l'idée générale que:

  1. trouver l'intersection de deux droites passant par les points donnés.
  2. déterminer si l'intersection appartient aux deux segments de ligne.

Mais lorsque l'intersection ne se produit pas souvent, une meilleure façon consiste probablement à inverser ces étapes:

  1. exprimer les lignes droites sous la forme de y = ax + b (ligne passant A, B) et y = cx + d (ligne passant C, D)
  2. voir si C et D sont du même côté sur y = ax + b
  3. voir si A et B sont du même côté sur y = cx + d
  4. si la réponse à ce qui précède est à la fois non, alors il y a is une intersection. sinon il n'y a pas d'intersection.
  5. trouver l'intersection s'il y en a une.

Remarque: pour effectuer l'étape 2, vérifiez simplement si (Cy - a(C.x) - b) et (Dy - a(D.x) - b) ont le même signe. L'étape 3 est similaire. L'étape 5 est juste un calcul standard à partir des deux équations.

De plus, si vous devez comparer chaque segment de ligne avec (n-1) autres segments de ligne, le précalcul de l'étape 1 pour toutes les lignes vous fait gagner du temps.

62
PolyThinker

Si vous en avez l'occasion, vous devriez vraiment consulter la Bible sur la détection des collisions, "Détection des collisions en temps réel" si vous prévoyez de faire quelque chose de non trivial. Je ne suis pas un programmeur de jeux professionnel et j'ai compris et je pouvais appliquer les concepts avec peu de difficulté.

enter image description here

Amazon - Détection de collision en temps réel

Fondamentalement, faire un ensemble de tests d'intersection de lignes coûte cher quoi qu'il arrive. Ce que vous faites, c'est utiliser des éléments tels que les boîtes englobantes (axe aligné ou orienté) sur vos polygones complexes. Cela vous permettra de faire rapidement une vérification O (N ^ 2) du pire des cas de collision entre chaque "objet". Vous pouvez ensuite accélérer encore plus les choses en utilisant des arbres spatiaux (partitionnement binaire ou QuadTrees) en vérifiant uniquement les intersections d'objets proches les uns des autres.

Cela vous permet d'élaguer de nombreux tests de collision. La meilleure optimisation ne fait rien du tout. Ce n'est qu'une fois que vous avez une collision entre des boîtes englobantes que vous effectuez vos intersections de lignes coûteuses pour déterminer si les objets se croisent vraiment ou non. Cela vous permet d'augmenter le nombre d'objets tout en conservant une vitesse raisonnable.

17
mmcdole
float x12 = x1 - x2;
float x34 = x3 - x4;
float y12 = y1 - y2;
float y34 = y3 - y4;

float c = x12 * y34 - y12 * x34;

if (fabs(c) < 0.01)
{
  // No intersection
  return false;
}
else
{
  // Intersection
  float a = x1 * y2 - y1 * x2;
  float b = x3 * y4 - y3 * x4;

  float x = (a * x34 - b * x12) / c;
  float y = (a * y34 - b * y12) / c;

  return true;
}

Formules tirées de:
Intersection ligne-ligne - Wikipedia

13
Tamara Wijsman
public struct PointD
{
    public double X { get; set; }
    public double Y { get; set; }
}

/// <summary>
/// Find the intersection point between two lines.
/// </summary>
/// <param name="IntersectPoint">The intersection point. A <see cref="Esteem.Geometry.PointD">PointD</see> structure.</param>
/// <param name="L1StartPoint">The starting point of first line. A PointD structure.</param>
/// <param name="L1EndPoint">The end point of first line. A PointD structure.</param>
/// <param name="L2StartPoint">The starting point of second line. A PointD structure.</param>
/// <param name="L2EndPoint">The end point of second line. A PointD structure.</param>
/// <param name="L1IntersectPos">The intersection position at first line.</param>
/// <param name="L2IntersectPos">The intersection position at second line.</param>
/// <returns>Returns a boolean. True if there is intersection, false otherwise.</returns>
/// <remarks>The formula is taken from comp.graphics.algorithms Frequently Asked Questions.</remarks>
public static bool LineIntersect(out PointD IntersectPoint, PointD L1StartPoint, PointD L1EndPoint, PointD L2StartPoint, PointD L2EndPoint, out double L1IntersectPos, out double L2IntersectPos) 
{
    IntersectPoint = new PointD();
    double ay_cy, ax_cx, px, py;
    double dx_cx = L2EndPoint.X - L2StartPoint.X,
        dy_cy = L2EndPoint.Y - L2StartPoint.Y,
        bx_ax = L1EndPoint.X - L1StartPoint.X,
        by_ay = L1EndPoint.Y - L1StartPoint.Y;

    double de = (bx_ax) * (dy_cy) - (by_ay) * (dx_cx);
    //double tor = 1.0E-10;     //tolerance


    L1IntersectPos = 0;      L2IntersectPos = 0;
    if (Math.Abs(de)<0.01)
        return false;
    //if (de > -tor && de < tor) return false; //line is in parallel

    ax_cx = L1StartPoint.X - L2StartPoint.X;
    ay_cy = L1StartPoint.Y - L2StartPoint.Y;
    double r = ((ay_cy) * (dx_cx) - (ax_cx) * (dy_cy)) / de;
    double s = ((ay_cy) * (bx_ax) - (ax_cx) * (by_ay)) / de;
    px = L1StartPoint.X + r * (bx_ax);
    py = L1StartPoint.Y + r * (by_ay);

    IntersectPoint.X = px;  //return the intersection point
    IntersectPoint.Y = py;  //return the intersection position
    L1IntersectPos = r;      L2IntersectPos = s;

    return true; //indicate there is intersection
}

Pour vérifier si le point d'intersection ne dépasse pas la longueur d'origine de la ligne, assurez-vous simplement que 0<r<1 et 0<s<1

3
Graviton

Une optimisation simple qui peut vous faire gagner beaucoup de temps consiste à cocher les cases de délimitation alignées sur l'axe des lignes avant d'entrer dans le calcul de l'intersection.
Si les boîtes englobantes sont complètement disjointes, vous pouvez être certain qu'il n'y a pas d'intersection.
Bien sûr, cela dépend des données dont vous disposez. si une intersection est très probable dans chaque vérification que vous effectuez, vous pourriez vous retrouver à perdre du temps sur une vérification qui est toujours vraie.

3
shoosh

Ci-dessous se trouve mon intersection ligne-ligne comme décrit dans MathWorld . Pour la détection/intersection de collision générale, vous pouvez consulter le Théorème de l'axe de séparation . J'ai trouvé ce tutoriel sur SAT très instructif.

    /// <summary>
    /// Returns the intersection point of the given lines. 
    /// Returns Empty if the lines do not intersect.
    /// Source: http://mathworld.wolfram.com/Line-LineIntersection.html
    /// </summary>
    public static PointF LineIntersection(PointF v1, PointF v2, PointF v3, PointF v4)
    {
        float tolerance = 0.000001f;

        float a = Det2(v1.X - v2.X, v1.Y - v2.Y, v3.X - v4.X, v3.Y - v4.Y);
        if (Math.Abs(a) < float.Epsilon) return PointF.Empty; // Lines are parallel

        float d1 = Det2(v1.X, v1.Y, v2.X, v2.Y);
        float d2 = Det2(v3.X, v3.Y, v4.X, v4.Y);
        float x = Det2(d1, v1.X - v2.X, d2, v3.X - v4.X) / a;
        float y = Det2(d1, v1.Y - v2.Y, d2, v3.Y - v4.Y) / a;

        if (x < Math.Min(v1.X, v2.X) - tolerance || x > Math.Max(v1.X, v2.X) + tolerance) return PointF.Empty;
        if (y < Math.Min(v1.Y, v2.Y) - tolerance || y > Math.Max(v1.Y, v2.Y) + tolerance) return PointF.Empty;
        if (x < Math.Min(v3.X, v4.X) - tolerance || x > Math.Max(v3.X, v4.X) + tolerance) return PointF.Empty;
        if (y < Math.Min(v3.Y, v4.Y) - tolerance || y > Math.Max(v3.Y, v4.Y) + tolerance) return PointF.Empty;

        return new PointF(x, y);
    }

    /// <summary>
    /// Returns the determinant of the 2x2 matrix defined as
    /// <list>
    /// <item>| x1 x2 |</item>
    /// <item>| y1 y2 |</item>
    /// </list>
    /// </summary>
    public static float Det2(float x1, float x2, float y1, float y2)
    {
        return (x1 * y2 - y1 * x2);
    }
2
Ozgur Ozcitak

(Je laisserais cela comme un commentaire, sauf que je n'ai pas encore compris comment le faire.)

Je voudrais juste ajouter, comme alternative/complément à un test de boîte englobante, vous pouvez également tester pour voir si la distance entre les points médians des lignes est supérieure à la moitié de la longueur combinée des lignes. Si c'est le cas, les lignes ne peuvent pas se croiser.

Imaginez un cercle englobant minimal pour chaque ligne, puis testez l'intersection des cercles. Cela permet un pré-calcul (au moins pour les lignes statiques et les lignes qui préservent leur longueur) et un moyen efficace d'exclure un grand nombre d'intersections potentielles.

2
Jo Are By

Eh bien, les mathématiques et les produits scalaires peuvent y aider.
- Supposons que vous souhaitiez croiser les segments [AB] et [CD]
- Supposons que l'intersection des lignes soit M

M est à l'intérieur du segment [ÅB] si et seulement si
Vecteur (AB). Vecteur (AM)> = 0
et
Vecteur (AB). Vecteur (Mo)> = 0

Où le point "." désigne un produit scalaire: u. v = ux * vx + uy * vy

Donc, votre algo est:
- trouver M
- M est à l'intérieur des deux segments si et seulement si les 4 éq ci-dessous sont vrais
Vecteur (AB). Vecteur (AM)> = 0
Vecteur (AB). Vecteur (Mo)> = 0
Vecteur (CD). Vecteur (CM)> = 0
Vecteur (CD). Vecteur (MD)> = 0

J'espère que cela t'aides

1
Pascal T.
private function Loop(e:Event):void
{
    var x12:Number = Ball1.x - Ball2.x;
    var x34:Number = Ball3.x - Ball4.x;
    var y12:Number = Ball1.y - Ball2.y;
    var y34:Number = Ball3.y - Ball4.y;

    // Det
    var c:Number = x12 * y34 - y12 * x34;

    if (Math.abs(c) < 0.01)
    {
        Circle.visible = false;
    }
    else
    {
        var a:Number = Ball1.x * Ball2.y - Ball1.y * Ball2.x;
        var b:Number = Ball3.x * Ball4.y - Ball3.y * Ball4.x;
        var px:Number = (a * x34 - b * x12) / c;
        var py:Number = (a * y34 - b * y12) / c;

        var Btwn12x:Boolean = (px >= Math.min(Ball1.x, Ball2.x)) && (px <= Math.max(Ball1.x, Ball2.x));
        var Btwn12y:Boolean = (py >= Math.min(Ball1.y, Ball2.y)) && (py <= Math.max(Ball1.y, Ball2.y));
        var Btwn34x:Boolean = (px >= Math.min(Ball3.x, Ball4.x)) && (px <= Math.max(Ball3.x, Ball4.x));
        var Btwn34y:Boolean = (py >= Math.min(Ball3.y, Ball4.y)) && (py <= Math.max(Ball3.y, Ball4.y));

        var Btwn12:Boolean = Btwn12x && Btwn12y;
        var Btwn34:Boolean = Btwn34x && Btwn34y;

        if(Btwn12 && Btwn34)
        {
            Circle.visible = true;
            Circle.x = px;
            Circle.y = py;
        }
        else
        {
            Circle.visible = false;
        }
    }
}
0
Galindo