J'ai un ensemble de points. Je veux les séparer en 2 ensembles distincts. Pour ce faire, je choisis deux points ( a et b ) et trace une ligne imaginaire entre eux. Maintenant, je veux avoir tous les points qui restent de cette ligne dans un ensemble et ceux qui sont juste de cette ligne dans l'autre ensemble.
Comment puis-je savoir pour un point donné z s'il est placé à gauche ou à droite? J'ai essayé de calculer l'angle entre azb -, les angles inférieurs à 180 sont situés à droite, ceux supérieurs à 180 à gauche - mais, à cause de la définition d'ArcCos, les angles calculés sont toujours inférieurs à 180 °. . Existe-t-il une formule permettant de calculer des angles supérieurs à 180 ° (ou toute autre formule permettant de choisir le côté droit ou gauche)?
Utilisez le signe du déterminant de vecteurs (AB,AM)
, où M(X,Y)
est le point de requête:
position = sign((Bx - Ax) * (Y - Ay) - (By - Ay) * (X - Ax))
Il s'agit de 0
sur la ligne et +1
d'un côté, -1
de l'autre.
Essayez ce code qui utilise un produit croisé :
public bool isLeft(Point a, Point b, Point c){
return ((b.X - a.X)*(c.Y - a.Y) - (b.Y - a.Y)*(c.X - a.X)) > 0;
}
Où a = point 1 de la ligne; b = point 2 de la ligne; c = point contre lequel vérifier.
Si la formule est égale à 0, les points sont colinéaires.
Si la ligne est horizontale, cela retourne vrai si le point est au dessus de la ligne.
Vous regardez le signe du déterminant de
| x2-x1 x3-x1 |
| y2-y1 y3-y1 |
Ce sera positif pour les points d'un côté et négatif de l'autre (et nul pour les points de la ligne elle-même).
Le vecteur (y1 - y2, x2 - x1)
est perpendiculaire à la ligne et pointe toujours vers la droite (ou toujours vers la gauche si l'orientation du plan est différente de la mienne).
Vous pouvez ensuite calculer le produit scalaire de ce vecteur et de (x3 - x1, y3 - y1)
pour déterminer si le point se trouve du même côté de la ligne que le vecteur perpendiculaire (produit scalaire> 0
) ou non.
Je l'ai implémenté en Java et ai exécuté un test unitaire (source ci-dessous). Aucune des solutions ci-dessus ne fonctionne. Ce code passe le test unitaire. Si quelqu'un trouve un test unitaire qui ne passe pas, s'il vous plaît faites le moi savoir.
Code: REMARQUE: nearlyEqual(double,double)
renvoie vrai si les deux nombres sont très proches.
/*
* @return integer code for which side of the line ab c is on. 1 means
* left turn, -1 means right turn. Returns
* 0 if all three are on a line
*/
public static int findSide(
double ax, double ay,
double bx, double by,
double cx, double cy) {
if (nearlyEqual(bx-ax,0)) { // vertical line
if (cx < bx) {
return by > ay ? 1 : -1;
}
if (cx > bx) {
return by > ay ? -1 : 1;
}
return 0;
}
if (nearlyEqual(by-ay,0)) { // horizontal line
if (cy < by) {
return bx > ax ? -1 : 1;
}
if (cy > by) {
return bx > ax ? 1 : -1;
}
return 0;
}
double slope = (by - ay) / (bx - ax);
double yIntercept = ay - ax * slope;
double cSolution = (slope*cx) + yIntercept;
if (slope != 0) {
if (cy > cSolution) {
return bx > ax ? 1 : -1;
}
if (cy < cSolution) {
return bx > ax ? -1 : 1;
}
return 0;
}
return 0;
}
Voici le test unitaire:
@Test public void testFindSide() {
assertTrue("1", 1 == Utility.findSide(1, 0, 0, 0, -1, -1));
assertTrue("1.1", 1 == Utility.findSide(25, 0, 0, 0, -1, -14));
assertTrue("1.2", 1 == Utility.findSide(25, 20, 0, 20, -1, 6));
assertTrue("1.3", 1 == Utility.findSide(24, 20, -1, 20, -2, 6));
assertTrue("-1", -1 == Utility.findSide(1, 0, 0, 0, 1, 1));
assertTrue("-1.1", -1 == Utility.findSide(12, 0, 0, 0, 2, 1));
assertTrue("-1.2", -1 == Utility.findSide(-25, 0, 0, 0, -1, -14));
assertTrue("-1.3", -1 == Utility.findSide(1, 0.5, 0, 0, 1, 1));
assertTrue("2.1", -1 == Utility.findSide(0,5, 1,10, 10,20));
assertTrue("2.2", 1 == Utility.findSide(0,9.1, 1,10, 10,20));
assertTrue("2.3", -1 == Utility.findSide(0,5, 1,10, 20,10));
assertTrue("2.4", -1 == Utility.findSide(0,9.1, 1,10, 20,10));
assertTrue("vertical 1", 1 == Utility.findSide(1,1, 1,10, 0,0));
assertTrue("vertical 2", -1 == Utility.findSide(1,10, 1,1, 0,0));
assertTrue("vertical 3", -1 == Utility.findSide(1,1, 1,10, 5,0));
assertTrue("vertical 3", 1 == Utility.findSide(1,10, 1,1, 5,0));
assertTrue("horizontal 1", 1 == Utility.findSide(1,-1, 10,-1, 0,0));
assertTrue("horizontal 2", -1 == Utility.findSide(10,-1, 1,-1, 0,0));
assertTrue("horizontal 3", -1 == Utility.findSide(1,-1, 10,-1, 0,-9));
assertTrue("horizontal 4", 1 == Utility.findSide(10,-1, 1,-1, 0,-9));
assertTrue("positive slope 1", 1 == Utility.findSide(0,0, 10,10, 1,2));
assertTrue("positive slope 2", -1 == Utility.findSide(10,10, 0,0, 1,2));
assertTrue("positive slope 3", -1 == Utility.findSide(0,0, 10,10, 1,0));
assertTrue("positive slope 4", 1 == Utility.findSide(10,10, 0,0, 1,0));
assertTrue("negative slope 1", -1 == Utility.findSide(0,0, -10,10, 1,2));
assertTrue("negative slope 2", -1 == Utility.findSide(0,0, -10,10, 1,2));
assertTrue("negative slope 3", 1 == Utility.findSide(0,0, -10,10, -1,-2));
assertTrue("negative slope 4", -1 == Utility.findSide(-10,10, 0,0, -1,-2));
assertTrue("0", 0 == Utility.findSide(1, 0, 0, 0, -1, 0));
assertTrue("1", 0 == Utility.findSide(0,0, 0, 0, 0, 0));
assertTrue("2", 0 == Utility.findSide(0,0, 0,1, 0,2));
assertTrue("3", 0 == Utility.findSide(0,0, 2,0, 1,0));
assertTrue("4", 0 == Utility.findSide(1, -2, 0, 0, -1, 2));
}
En utilisant l'équation de la ligneab, obtenez la coordonnée x de la ligne à la même coordonnée y que le point à trier.
Commencez par vérifier si vous avez une ligne verticale:
if (x2-x1) == 0
if x3 < x2
it's on the left
if x3 > x2
it's on the right
else
it's on the line
Puis, calcule la pente: m = (y2-y1)/(x2-x1)
Créez ensuite une équation de la ligne en utilisant la forme de la pente du point: y - y1 = m*(x-x1) + y1
. Par souci d’explication, simplifiez-le pour l’intercepter sous forme de pente (non nécessaire dans votre algorithme): y = mx+b
.
Maintenant, connectez (x3, y3)
pour x
et y
. Voici un pseudocode détaillant ce qui devrait arriver:
if m > 0
if y3 > m*x3 + b
it's on the left
else if y3 < m*x3 + b
it's on the right
else
it's on the line
else if m < 0
if y3 < m*x3 + b
it's on the left
if y3 > m*x3+b
it's on the right
else
it's on the line
else
horizontal line; up to you what you do
en gros, je pense qu’il existe une solution beaucoup plus simple et simple, pour tout polygone donné, disons composée de quatre sommets (p1, p2, p3, p4), trouver les deux sommets extrêmes opposés dans le polygone, dans un autre mots, recherchez par exemple le sommet le plus en haut à gauche (disons p1) et le sommet opposé qui se situe au plus en bas à droite (disons). Par conséquent, étant donné votre point de test C (x, y), vous devez maintenant effectuer une double vérification entre C et p1 et C et p4:
si cx> p1x ET cy> p1y ==> signifie que C est inférieur et à droite de p1 next si cx <p2x ET cy <p2y ==> signifie que C est supérieur et à gauche de p4
conclusion, C est à l'intérieur du rectangle.
Merci :)
La réponse de AVB en Ruby
det = Matrix[
[(x2 - x1), (x3 - x1)],
[(y2 - y1), (y3 - y1)]
].determinant
Si det
est positif, son supérieur, si négatif, inférieur. Si 0, c'est sur la ligne.
Voici une version, utilisant à nouveau la logique de produits croisés, écrite en Clojure.
(defn is-left? [line point]
(let [[[x1 y1] [x2 y2]] (sort line)
[x-pt y-pt] point]
(> (* (- x2 x1) (- y-pt y1)) (* (- y2 y1) (- x-pt x1)))))
Exemple d'utilisation:
(is-left? [[-3 -1] [3 1]] [0 10])
true
Ce qui revient à dire que le point (0, 10) se situe à gauche de la droite déterminée par (-3, -1) et (3, 1).
REMARQUE: cette implémentation résout un problème qu'aucun autre (jusqu'à présent) ne fait! L'ordre compte lorsque vous attribuez les points qui déterminent la ligne. C'est-à-dire que c'est une "ligne dirigée", dans un certain sens. Donc, avec le code ci-dessus, cette invocation produit également le résultat de true
:
(is-left? [[3 1] [-3 -1]] [0 10])
true
C'est à cause de cet extrait de code:
(sort line)
Enfin, comme pour les autres solutions basées sur plusieurs produits, cette solution renvoie un booléen et ne donne pas un troisième résultat en termes de colinéarité. Mais cela donnera un résultat logique, par exemple:
(is-left? [[1 1] [3 1]] [10 1])
false
En supposant que les points sont (Ax, Ay) (Bx, By) et (Cx, Cy), vous devez calculer:
(Bx - Axe) * (Cy - Ay) - (Par - Ay) * (Cx - Axe)
Cela sera égal à zéro si le point C est sur la ligne formée par les points A et B et aura un signe différent en fonction du côté. Son côté dépend de l'orientation de vos coordonnées (x, y), mais vous pouvez insérer des valeurs de test pour A, B et C dans cette formule afin de déterminer si les valeurs négatives se trouvent à gauche ou à droite.
Je voulais proposer une solution inspirée de la physique.
Imaginez une force appliquée le long de la ligne et vous mesurez le couple de la force autour du point. Si le couple est positif (dans le sens inverse des aiguilles d'une montre), le point est situé à gauche de la ligne, mais si le couple est négatif, le point correspond à la droite de la ligne.
Donc, si le vecteur de force est égal à la portée des deux points définissant la ligne
fx = x_2 - x_1
fy = y_2 - y_1
vous testez le côté d'un point (px,py)
en fonction du signe du test suivant
var torque = fx*(py-y_1)-fy*(px-x_1)
if torque>0 then
"point on left side"
else if torque <0 then
"point on right side"
else
"point on line"
end if