web-dev-qa-db-fra.com

Comment tester si un segment de ligne croise un rectangle aligné sur l'axe en 2D?

Comment tester si un segment de ligne croise un rectangle aligné sur l'axe en 2D? Le segment est défini avec ses deux extrémités: p1, p2. Le rectangle est défini avec des points en haut à gauche et en bas à droite.

38
metamal

L'affiche originale voulait DÉTECTER une intersection entre un segment de ligne et un polygone. Il n'était pas nécessaire de LOCALISER l'intersection, s'il y en a une. Si c'est ce que vous vouliez dire, vous pouvez faire moins de travail que Liang-Barsky ou Cohen-Sutherland:

Soit les extrémités du segment p1 = (x1 y1) et p2 = (x2 y2).
Soit les coins du rectangle (xBL yBL) et (xTR yTR).

Il ne vous reste plus qu'à

A. Vérifiez si les quatre coins du rectangle sont du même côté de la ligne. L'équation implicite pour une ligne passant par p1 et p2 est:

F(x y) = (y2-y1)*x + (x1-x2)*y + (x2*y1-x1*y2)

Si F (x y) = 0, (x y) est sur la ligne.
Si F (x y)> 0, (x y) est "au-dessus" de la ligne.
Si F (x y) <0, (x y) est "en dessous" de la ligne.

Remplacez les quatre coins par F (x y). S'ils sont tous négatifs ou tous positifs, il n'y a pas d'intersection. Si certains sont positifs et certains négatifs, passez à l'étape B.

B. Projetez l'extrémité sur l'axe des x et vérifiez si l'ombre du segment croise l'ombre du polygone. Répétez sur l'axe y:

Si (x1> xTR et x2> xTR), aucune intersection (la ligne est à droite du rectangle).
Si (x1 <xBL et x2 <xBL), aucune intersection (la ligne est à gauche du rectangle).
Si (y1> yTR et y2> yTR), pas d'intersection (la ligne est au-dessus du rectangle).
Si (y1 <yBL et y2 <yBL), pas d'intersection (la ligne est sous le rectangle).
sinon, il y a une intersection. Faites Cohen-Sutherland ou tout autre code mentionné dans les autres réponses à votre question.

Vous pouvez, bien sûr, faire d'abord B, puis A.

Alejo

51
user37968

A écrit une solution assez simple et fonctionnelle:

      bool SegmentIntersectRectangle(double a_rectangleMinX,
                                 double a_rectangleMinY,
                                 double a_rectangleMaxX,
                                 double a_rectangleMaxY,
                                 double a_p1x,
                                 double a_p1y,
                                 double a_p2x,
                                 double a_p2y)
  {
    // Find min and max X for the segment

    double minX = a_p1x;
    double maxX = a_p2x;

    if(a_p1x > a_p2x)
    {
      minX = a_p2x;
      maxX = a_p1x;
    }

    // Find the intersection of the segment's and rectangle's x-projections

    if(maxX > a_rectangleMaxX)
    {
      maxX = a_rectangleMaxX;
    }

    if(minX < a_rectangleMinX)
    {
      minX = a_rectangleMinX;
    }

    if(minX > maxX) // If their projections do not intersect return false
    {
      return false;
    }

    // Find corresponding min and max Y for min and max X we found before

    double minY = a_p1y;
    double maxY = a_p2y;

    double dx = a_p2x - a_p1x;

    if(Math::Abs(dx) > 0.0000001)
    {
      double a = (a_p2y - a_p1y) / dx;
      double b = a_p1y - a * a_p1x;
      minY = a * minX + b;
      maxY = a * maxX + b;
    }

    if(minY > maxY)
    {
      double tmp = maxY;
      maxY = minY;
      minY = tmp;
    }

    // Find the intersection of the segment's and rectangle's y-projections

    if(maxY > a_rectangleMaxY)
    {
      maxY = a_rectangleMaxY;
    }

    if(minY < a_rectangleMinY)
    {
      minY = a_rectangleMinY;
    }

    if(minY > maxY) // If Y-projections do not intersect return false
    {
      return false;
    }

    return true;
  }
26
metamal

Vous pouvez également créer un rectangle à partir du segment et tester si l'autre rectangle entre en collision avec lui, car il ne s'agit que d'une série de comparaisons. De la source pygame:

def _rect_collide(a, b):
    return a.x + a.w > b.x and b.x + b.w > a.x and \
           a.y + a.h > b.y and b.y + b.h > a.y
7
asolano

Puisque votre rectangle est aligné, Liang-Barsky pourrait être une bonne solution. Il est plus rapide que Cohen-Sutherland, si la vitesse est importante ici.

explication Siggraph
ne autre bonne description
Et bien sûr, Wikipedia

7
ryan_s

Utilisez algorithme de Cohen-Sutherland .

Il est utilisé pour l'écrêtage mais peut être légèrement modifié pour cette tâche. Il divise l'espace 2D en une planche tic-tac-toe avec votre rectangle comme "carré central".
puis il vérifie dans laquelle des neuf régions chacun des deux points de votre ligne se trouve.

  • Si les deux points sont à gauche, à droite, en haut ou en bas, vous rejetez trivialement.
  • Si l'un ou l'autre point est à l'intérieur, vous acceptez trivialement.
  • Dans les rares cas restants, vous pouvez faire le calcul pour couper avec les côtés du rectangle avec lesquels vous pouvez vous croiser, en fonction des régions dans lesquelles ils se trouvent.
5
Kevin Conner

Ou utilisez/copiez simplement le code déjà dans la méthode Java

Java.awt.geom.Rectangle2D.intersectsLine(double x1, double y1, double x2, double y2)

Voici la méthode après avoir été convertie en statique pour plus de commodité:

/**
 * Code copied from {@link Java.awt.geom.Rectangle2D#intersectsLine(double, double, double, double)}
 */
public class RectangleLineIntersectTest {
    private static final int OUT_LEFT = 1;
    private static final int OUT_TOP = 2;
    private static final int OUT_RIGHT = 4;
    private static final int OUT_BOTTOM = 8;

    private static int outcode(double pX, double pY, double rectX, double rectY, double rectWidth, double rectHeight) {
        int out = 0;
        if (rectWidth <= 0) {
            out |= OUT_LEFT | OUT_RIGHT;
        } else if (pX < rectX) {
            out |= OUT_LEFT;
        } else if (pX > rectX + rectWidth) {
            out |= OUT_RIGHT;
        }
        if (rectHeight <= 0) {
            out |= OUT_TOP | OUT_BOTTOM;
        } else if (pY < rectY) {
            out |= OUT_TOP;
        } else if (pY > rectY + rectHeight) {
            out |= OUT_BOTTOM;
        }
        return out;
    }

    public static boolean intersectsLine(double lineX1, double lineY1, double lineX2, double lineY2, double rectX, double rectY, double rectWidth, double rectHeight) {
        int out1, out2;
        if ((out2 = outcode(lineX2, lineY2, rectX, rectY, rectWidth, rectHeight)) == 0) {
            return true;
        }
        while ((out1 = outcode(lineX1, lineY1, rectX, rectY, rectWidth, rectHeight)) != 0) {
            if ((out1 & out2) != 0) {
                return false;
            }
            if ((out1 & (OUT_LEFT | OUT_RIGHT)) != 0) {
                double x = rectX;
                if ((out1 & OUT_RIGHT) != 0) {
                    x += rectWidth;
                }
                lineY1 = lineY1 + (x - lineX1) * (lineY2 - lineY1) / (lineX2 - lineX1);
                lineX1 = x;
            } else {
                double y = rectY;
                if ((out1 & OUT_BOTTOM) != 0) {
                    y += rectHeight;
                }
                lineX1 = lineX1 + (y - lineY1) * (lineX2 - lineX1) / (lineY2 - lineY1);
                lineY1 = y;
            }
        }
        return true;
    }
}
3
Craigo

Une recherche rapide sur Google a fait apparaître une page avec du code C++ pour tester l'intersection.

Fondamentalement, il teste l'intersection entre la ligne et chaque bordure ou rectangle.

Code d'intersection rectangle et ligne

1
Vincent McNabb

exemple de codage dans PHP (j'utilise un modèle d'objet qui a des méthodes pour des choses comme getLeft (), getRight (), getTop (), getBottom () pour obtenir les coordonnées externes d'un polygone et a également un getWidth () et getHeight () - en fonction des paramètres qui l'ont alimenté, il calculera et mettra en cache les inconnues - c'est-à-dire que je peux créer un polygone avec x1, y1 et ... w, h ou x2, y2 et il peut calculer les autres)

J'utilise 'n' pour désigner le 'nouvel' élément dont le chevauchement est vérifié ($ nItem est une instance de mon objet polygone) - les éléments à tester à nouveau [il s'agit d'un programme de sac à dos bin/sort] sont dans un tableau composé de plusieurs instances du (même) objet polygone.

public function checkForOverlaps(BinPack_Polygon $nItem) {
  // grab some local variables for the stuff re-used over and over in loop
  $nX = $nItem->getLeft();
  $nY = $nItem->getTop();
  $nW = $nItem->getWidth();
  $nH = $nItem->getHeight();
  // loop through the stored polygons checking for overlaps
  foreach($this->packed as $_i => $pI) {
    if(((($pI->getLeft()  - $nW) < $nX) && ($nX < $pI->getRight())) &&
       ((($pI->getTop()  - $nH) < $nY) && ($nY < $pI->getBottom()))) {
      return false;
    }
  }
  return true;
}
0
Scott

Je regardais un problème similaire et voici ce que j'ai trouvé. J'ai d'abord comparé les bords et réalisé quelque chose. Si le point médian d'un bord qui tombait dans l'axe opposé de la première case est dans la moitié de la longueur de ce bord des points extérieurs sur le premier dans le même axe, alors il y a une intersection de ce côté quelque part. Mais c'était penser 1 dimensionnellement et il fallait regarder de chaque côté de la deuxième boîte pour comprendre.

Il m'est soudain venu à l'esprit que si vous trouvez le "point médian" de la deuxième case et comparez les coordonnées du point médian pour voir si elles tombent à moins de 1/2 longueur d'un côté (de la deuxième case) des dimensions extérieures de la première , puis il y a une intersection quelque part.

i.e. box 1 is bounded by x1,y1 to x2,y2
box 2 is bounded by a1,b1 to a2,b2

the width and height of box 2 is:
w2 = a2 - a1   (half of that is w2/2)
h2 = b2 - b1   (half of that is h2/2)
the midpoints of box 2 are:
am = a1 + w2/2
bm = b1 + h2/2

So now you just check if
(x1 - w2/2) < am < (x2 + w2/2) and (y1 - h2/2) < bm < (y2 + h2/2) 
then the two overlap somewhere.
If you want to check also for edges intersecting to count as 'overlap' then
 change the < to <=

Bien sûr, vous pouvez tout aussi facilement comparer l'inverse (en vérifiant que les points médians de la boîte 1 se trouvent à moins de la moitié des dimensions extérieures de la boîte 2)

Et encore plus de simplification - déplacez le milieu par vos demi-longueurs et il est identique au point d'origine de cette boîte. Ce qui signifie que vous pouvez maintenant vérifier juste ce point pour tomber dans votre plage de délimitation et en déplaçant la plaine vers le haut et vers la gauche, le coin inférieur est maintenant le coin inférieur de la première case. Beaucoup moins de mathématiques:

(x1 - w2) < a1 < x2
&&
(y1 - h2) < b1 < y2
[overlap exists]

ou non substitué:

( (x1-(a2-a1)) < a1 < x2 ) && ( (y1-(b2-b1)) < b1 < y2 ) [overlap exists]
( (x1-(a2-a1)) <= a1 <= x2 ) && ( (y1-(b2-b1)) <= b1 <= y2 ) [overlap or intersect exists]
0
Scott

Quelques exemples de code pour ma solution (en php):

// returns 'true' on overlap checking against an array of similar objects in $this->packed
public function checkForOverlaps(BinPack_Polygon $nItem) {
  $nX = $nItem->getLeft();
  $nY = $nItem->getTop();
  $nW = $nItem->getWidth();
  $nH = $nItem->getHeight();
  // loop through the stored polygons checking for overlaps
  foreach($this->packed as $_i => $pI) {
    if(((($pI->getLeft() - $nW) < $nX) && ($nX < $pI->getRight())) && ((($pI->getTop() - $nH) < $nY) && ($nY < $pI->getBottom()))) {
      return true;
    }
  }
  return false;
}
0
Scott

Voici une version javascript de la réponse de @ metamal

var isRectangleIntersectedByLine = function (
  a_rectangleMinX,
  a_rectangleMinY,
  a_rectangleMaxX,
  a_rectangleMaxY,
  a_p1x,
  a_p1y,
  a_p2x,
  a_p2y) {

  // Find min and max X for the segment
  var minX = a_p1x
  var maxX = a_p2x

  if (a_p1x > a_p2x) {
    minX = a_p2x
    maxX = a_p1x
  }

  // Find the intersection of the segment's and rectangle's x-projections
  if (maxX > a_rectangleMaxX)
    maxX = a_rectangleMaxX

  if (minX < a_rectangleMinX)
    minX = a_rectangleMinX

  // If their projections do not intersect return false
  if (minX > maxX)
    return false

  // Find corresponding min and max Y for min and max X we found before
  var minY = a_p1y
  var maxY = a_p2y

  var dx = a_p2x - a_p1x

  if (Math.abs(dx) > 0.0000001) {
    var a = (a_p2y - a_p1y) / dx
    var b = a_p1y - a * a_p1x
    minY = a * minX + b
    maxY = a * maxX + b
  }

  if (minY > maxY) {
    var tmp = maxY
    maxY = minY
    minY = tmp
  }

  // Find the intersection of the segment's and rectangle's y-projections
  if(maxY > a_rectangleMaxY)
    maxY = a_rectangleMaxY

  if (minY < a_rectangleMinY)
    minY = a_rectangleMinY

  // If Y-projections do not intersect return false
  if(minY > maxY)
    return false

  return true
}
0
Breck

J'ai fait une petite solution de serviette ..

Trouvez ensuite m et c et donc l'équation y = mx + c

y = (Point2.Y - Point1.Y) / (Point2.X - Point1.X)

Remplacer les coordonnées P1 pour trouver maintenant c

Maintenant, pour un sommet de rectangle, mettez la valeur X dans l'équation de la ligne, obtenez la valeur Y et voyez si la valeur Y se situe dans les limites du rectangle indiquées ci-dessous

(vous pouvez trouver les valeurs constantes X1, X2, Y1, Y2 pour le rectangle telles que)

X1 <= x <= X2 & 
Y1 <= y <= Y2

Si la valeur Y satisfait la condition ci-dessus et se situe entre (Point1.Y, Point2.Y) - nous avons une intersection. Essayez chaque sommet si celui-ci échoue.

0
Gishu