Comment puis-je vérifier si 2 segments s'intersectent?
J'ai les données suivantes:
Segment1 [ {x1,y1}, {x2,y2} ]
Segment2 [ {x1,y1}, {x2,y2} ]
Je dois écrire un petit algorithme en Python pour détecter si les 2 lignes s'intersectent.
Comment puis-je vérifier si 2 segments s'intersectent?
J'ai les données suivantes:
Segment1 [ {x1,y1}, {x2,y2} ]
Segment2 [ {x1,y1}, {x2,y2} ]
Je dois écrire un petit algorithme en Python pour détecter si les 2 lignes s'intersectent.
L'utilisateur @i_4_got pointe vers cette page avec une solution très efficace en Python. Je le reproduis ici pour plus de commodité (car cela m'aurait rendu heureux de l'avoir ici) :
def ccw(A,B,C):
return (C.y-A.y) * (B.x-A.x) > (B.y-A.y) * (C.x-A.x)
# Renvoie vrai si les segments de droite AB et CD s'intersectent
def intersect(A,B,C,D):
return ccw(A,C,D) != ccw(B,C,D) and ccw(A,B,C) != ccw(A,B,D)
Très simple et élégant, mais il ne gère pas bien la colinéarité, et donc plus de code est nécessaire à cet effet.
Pour une solution qui gère également la colinéarité, consultez geeksforgeeks.org/check-if-two-given-line-segments-intersect
L'équation d'une droite est :
f(x) = A*x + b = y
Pour un segment, c'est exactement la même chose, sauf que x est inclus dans un intervalle I.
Si vous avez deux segments, définis comme suit :
Segment1 = {(X1, Y1), (X2, Y2)}
Segment2 = {(X3, Y3), (X4, Y4)}
L'abscisse Xa du point d'intersection potentiel (Xa,Ya) doit être contenue dans les deux intervalles I1 et I2, définis comme suit :
I1 = [min(X1,X2), max(X1,X2)]
I2 = [min(X3,X4), max(X3,X4)]
Et on pourrait dire que Xa est inclus dans :
Ia = [max( min(X1,X2), min(X3,X4) ),
min( max(X1,X2), max(X3,X4) )]
Maintenant, nous devons vérifier que cet intervalle Ia existe :
if (max(X1,X2) < min(X3,X4)):
return False # Il n'y a pas d'abscisses mutuelles
Ainsi, nous avons deux formules de droite et un intervalle commun. Vos formules de droite sont :
f1(x) = A1*x + b1 = y
f2(x) = A2*x + b2 = y
Comme nous avons deux points par segment, nous sommes capables de déterminer A1, A2, b1 et b2 :
A1 = (Y1-Y2)/(X1-X2) # Faites attention à ne pas diviser par zéro
A2 = (Y3-Y4)/(X3-X4) # Faites attention à ne pas diviser par zéro
b1 = Y1-A1*X1 = Y2-A1*X2
b2 = Y3-A2*X3 = Y4-A2*X4
Si les segments sont parallèles, alors A1 == A2 :
if (A1 == A2):
return False # Segments parallèles
Un point (Xa,Ya) appartenant aux deux droites doit vérifier les deux formules f1 et f2 :
Ya = A1 * Xa + b1
Ya = A2 * Xa + b2
A1 * Xa + b1 = A2 * Xa + b2
Xa = (b2 - b1) / (A1 - A2) # Une fois de plus, faites attention à ne pas diviser par zéro
La dernière chose à faire est de vérifier que Xa est inclus dans Ia :
if ( (Xa < max( min(X1,X2), min(X3,X4) )) or
(Xa > min( max(X1,X2), max(X3,X4) )) ):
return False # L'intersection est hors limites
else:
return True
En plus de cela, vous pouvez vérifier au démarrage que deux des quatre points fournis ne sont pas égaux pour éviter tous ces tests.
Segments, ce sont des segments, désolé. Pourriez-vous mettre à jour votre réponse en tenant compte des segments ?
Ok merci je vais examiner le code. Wow n'est pas compliqué ? Je pensais que ce n'était pas une solution si longue. Mais si c'est la seule... eh bien allons-y.
Ceci n'est pas si compliqué, j'ai écrit beaucoup de étapes intermédiaires (non essentielles ?) dans le but de faciliter la compréhension. Les points principaux à implémenter sont juste : Vérifier l'existence de l'intervalle mutuel, calculer A1, A2, b1, b2, et Xa, puis vérifier que Xa est inclus dans l'intervalle mutuel. C'est tout :)
Vous n'avez pas besoin de calculer exactement où les segments s'intersectent, mais seulement de comprendre s'ils s'intersectent. Cela simplifiera la solution.
L'idée est de traiter un segment comme l'"ancre" et de séparer le deuxième segment en 2 points.
Maintenant, vous devrez trouver la position relative de chaque point par rapport au segment "ancré" (À gauche, À droite ou Colinéaire).
Après avoir fait cela pour les deux points, vérifiez qu'un des points est À gauche et l'autre est À droite (ou peut-être inclure la position colinéaire, si vous souhaitez inclure des intersections impropres également).
Vous devez ensuite répéter le processus avec les rôles des segments ancré et séparé.
Une intersection existe si, et seulement si, un des points est À gauche et l'autre est À droite. Voir ce lien pour une explication plus détaillée avec des images d'exemple pour chaque cas possible.
Mettre en œuvre une telle méthode sera bien plus facile que d'implémenter réellement une méthode qui trouve le point d'intersection (étant donné les nombreux cas exceptionnels que vous devrez également gérer).
Mise à jour
Les fonctions suivantes devraient illustrer l'idée (source : Géométrie Computationnelle en C).
Remarque : Cet exemple suppose l'utilisation de nombres entiers. Si vous utilisez une représentation en virgule flottante à la place (ce qui pourrait évidemment compliquer les choses), alors vous devriez déterminer une valeur epsilon pour indiquer l'"égalité" (surtout pour l'évaluation de IsCollinear
).
// les points "a" et "b" forment le segment ancré.
// le point "c" est le point évalué
bool IsOnLeft(Point a, Point b, Point c)
{
return Area2(a, b, c) > 0;
}
bool IsOnRight(Point a, Point b, Point c)
{
return Area2(a, b, c) < 0;
}
bool IsCollinear(Point a, Point b, Point c)
{
return Area2(a, b, c) == 0;
}
// calcule la taille du triangle (formé par le segment "ancré" et le point supplémentaire)
int Area2(Point a, Point b, Point c)
{
return (b.X - a.X) * (c.Y - a.Y) -
(c.X - a.X) * (b.Y - a.Y);
}
Bien sûr, lors de l'utilisation de ces fonctions, il faut se rappeler de vérifier que chaque segment se situe "entre" l'autre segment (puisqu'il s'agit de segments finis, et non de lignes infinies).
De plus, en utilisant ces fonctions, vous pouvez comprendre si vous avez une intersection correcte ou incorrecte.
+1 Pretty much my idea too. If you just think about where the points are in relation to each other, you can decide if their segments must to intersect or not, without calculating anything.
Et @THC4k Euh, ce n'est pas clair en fait. Par exemple, regardez l'image que j'ai ajoutée à la question: les 2 points sont "OnLeft" et "OnRight" mais les 2 segments ne se croisent pas.
@Patrick, en fait non. En fonction de quel segment est l' "ancre", alors les deux points sont soit OnLeft soit OnRight dans ce cas. (Voir ma réponse mise à jour).
Supposons que les deux segments aient les extrémités A, B et C, D. La manière numériquement robuste de déterminer l'intersection est de vérifier le signe des quatre déterminants :
| Ax-Cx Bx-Cx | | Ax-Dx Bx-Dx |
| Ay-Cy By-Cy | | Ay-Dy By-Dy |
| Cx-Ax Dx-Ax | | Cx-Bx Dx-Bx |
| Cy-Ay Dy-Ay | | Cy-By Dy-By |
Pour l'intersection, chaque déterminant à gauche doit avoir le signe opposé de celui à droite, mais il n'y a pas besoin qu'il y ait de relation entre les deux droites. Vous vérifiez essentiellement chaque point d'un segment par rapport à l'autre segment pour vous assurer qu'ils se trouvent de chaque côté de la droite définie par l'autre segment.
Voir ici : http://www.cs.cmu.edu/~quake/robust.html
Est-ce que cela fonctionne pour les intersections incorrectes, c'est-à-dire lorsque le point d'intersection se trouve sur un segment de ligne ?
@SayamQazi Il semble échouer à l'intersection si vous passez le point final d'un segment de ligne, du moins. Car si vous êtes sur le segment : je suppose que ce serait une comparaison 0 vs 1/-1, donc il ne détecterait aucune intersection.
Au fait, pour expliquer ceci : chaque déterminant calcule le produit vectoriel des extrémités de deux segments de droite. En haut à gauche, on a CA x CB contre en haut à droite DA x DB, par exemple. Cela permet essentiellement de tester de quel côté se trouve un sommet (sens de rotation). J'essaie toujours de comprendre comment cela fonctionne pour des segments de droite qui ne s'étendent pas à l'infini.
Basé sur Liran et Grumdrig d'excellentes réponses voici un code Python complet pour vérifier si des segments fermés s'intersectent. Fonctionne pour des segments colinéaires, des segments parallèles à l'axe Y, des segments dégénérés (le diable est dans les détails). Suppose des coordonnées entières. Les coordonnées en nombres flottants nécessitent une modification du test d'égalité des points.
def côté(a,b,c):
""" Renvoie une position du point c par rapport à la ligne passant par a et b
Il est attendu que les points a, b soient différents
"""
d = (c[1]-a[1])*(b[0]-a[0]) - (b[1]-a[1])*(c[0]-a[0])
return 1 if d > 0 else (-1 if d < 0 else 0)
def est_point_dans_segment_fermé(a, b, c):
""" Renvoie True si c est à l'intérieur du segment fermé, False sinon.
Il est attendu que a, b, c soient colinéaires
"""
if a[0] < b[0]:
return a[0] <= c[0] and c[0] <= b[0]
if b[0] < a[0]:
return b[0] <= c[0] and c[0] <= a[0]
if a[1] < b[1]:
return a[1] <= c[1] and c[1] <= b[1]
if b[1] < a[1]:
return b[1] <= c[1] and c[1] <= a[1]
return a[0] == c[0] and a[1] == c[1]
#
def segment_fermé_s'intersectent(a,b,c,d):
""" Vérifie si les segments fermés a, b, c, d s'intersectent.
"""
if a == b:
return a == c or a == d
if c == d:
return c == a or c == b
s1 = côté(a,b,c)
s2 = côté(a,b,d)
# Tous les points sont colinéaires
if s1 == 0 and s2 == 0:
return \
est_point_dans_segment_fermé(a, b, c) or est_point_dans_segment_fermé(a, b, d) or \
est_point_dans_segment_fermé(c, d, a) or est_point_dans_segment_fermé(c, d, b)
# Pas de contact et du même côté
if s1 and s1 == s2:
return False
s1 = côté(c,d,a)
s2 = côté(c,d,b)
# Pas de contact et du même côté
if s1 and s1 == s2:
return False
return True
Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.
2 votes
Voir topcoder.com/tc?module=Static&d1=tutorials&d2=geometry2.