web-dev-qa-db-fra.com

Méthode directe de calcul de l'angle horaire entre 2 vecteurs

Je veux connaître l'angle dans le sens des aiguilles d'une montre entre 2 vecteurs (2D, 3D). 

La méthode classique avec le produit scalaire me donne l'angle interne (0-180 degrés) et je dois utiliser certaines instructions if pour déterminer si le résultat est l'angle dont j'ai besoin ou son complément. 

Connaissez-vous un moyen direct de calculer l'angle dans le sens des aiguilles d'une montre?

56
Felics

Cas 2D

Tout comme le produit scalaire est proportionnel au cosinus de l'angle, le déterminant est proportionnel à son sinus. Donc, vous pouvez calculer l'angle comme ceci:

dot = x1*x2 + y1*y2      # dot product between [x1, y1] and [x2, y2]
det = x1*y2 - y1*x2      # determinant
angle = atan2(det, dot)  # atan2(y, x) or atan2(sin, cos)

L'orientation de cet angle correspond à celle du système de coordonnées. Dans un système de coordonnées gaucher _, c'est-à-dire x pointant vers la droite et y vers le bas, comme cela est courant pour les graphiques informatiques, cela signifie que vous obtenez un signe positif pour les angles dans le sens des aiguilles d'une montre. Si l'orientation du système de coordonnées est mathématique avec y vers le haut, vous obtenez des angles dans le sens inverse des aiguilles d'une montre, comme c'est le cas dans la convention en mathématiques. Changer l'ordre des entrées changera le signe, donc si vous n'êtes pas satisfait des signes, échangez simplement les entrées.

Cas 3D

En 3D, deux vecteurs placés arbitrairement définissent leur propre axe de rotation, perpendiculaire aux deux. Cet axe de rotation ne vient pas avec une orientation fixe, ce qui signifie que vous ne pouvez pas non plus fixer la direction de l'angle de rotation. Une convention commune consiste à laisser les angles toujours positifs et à orienter l'axe de manière à ce qu'il corresponde à un angle positif. Dans ce cas, le produit scalaire des vecteurs normalisés suffit à calculer les angles.

dot = x1*x2 + y1*y2 + z1*z2    #between [x1, y1, z1] and [x2, y2, z2]
lenSq1 = x1*x1 + y1*y1 + z1*z1
lenSq2 = x2*x2 + y2*y2 + z2*z2
angle = acos(dot/sqrt(lenSq1 * lenSq2))

Plan intégré en 3D

Un cas particulier est le cas où vos vecteurs ne sont pas placés arbitrairement, mais se situent dans un plan avec un vecteur normal connu n. Ensuite, l'axe de rotation sera également dans la direction n, et l'orientation de n fixera une orientation pour cet axe. Dans ce cas, vous pouvez adapter le calcul 2D ci-dessus, en incluant n dans le déterminant pour obtenir une taille de 3 × 3.

dot = x1*x2 + y1*y2 + z1*z2
det = x1*y2*zn + x2*yn*z1 + xn*y1*z2 - z1*y2*xn - z2*yn*x1 - zn*y1*x2
angle = atan2(det, dot)

Une des conditions pour que cela fonctionne est que le vecteur normal n ait une longueur unitaire. Sinon, vous devrez le normaliser.

Comme triple produit

Ce déterminant pourrait également être exprimé par le triple produit , ainsi que le @Excrubulent indiqué dans une modification suggérée.

det = n · (v1 × v2)

Cela peut être plus facile à implémenter dans certaines API et donne une perspective différente de ce qui se passe ici: Le produit croisé est proportionnel au sinus de l'angle et se situera perpendiculairement au plan, donc un multiple de n. Le produit scalaire mesurera donc essentiellement la longueur de ce vecteur, mais avec le signe correct qui y est attaché.

131
MvG

Pour calculer l'angle, il suffit d'appeler atan2(v1.s_cross(v2), v1.dot(v2)) pour le cas 2D . Où s_cross est un analogue scalaire de la production croisée (zone signalisée du parallélogramme) . Pour un cas 2D qui serait une production de coin . Pour un cas 3D, vous nécessité de définir la rotation dans le sens des aiguilles d'une montre car d'un côté du plan, le sens des aiguilles d'une montre correspond à une direction, de l'autre côté du plan, une autre direction =)

Edit: c'est l'angle dans le sens inverse des aiguilles d'une montre, l'angle dans le sens des aiguilles d'une montre est juste opposé

5
kassak

Cette réponse est la même que celle de MvG, mais l'explique différemment (c'est le résultat de mes efforts pour essayer de comprendre pourquoi la solution de MvG fonctionne). Je le publie au hasard que d'autres le trouvent utile.

L'angle anti-horaire theta de x à y, en ce qui concerne le point de vue de leur valeur normale n (||n|| = 1), est donné par

atan2 (point (n, croix (x, y)), point (x, y))

(1) = atan2 (|| x || || y || sin (thêta), || x || || y || cos (thêta))

(2) = atan2 (sin (thêta), cos (thêta))

(3) = angle dans le sens anti-horaire entre l'axe des x et le vecteur (cos (thêta), sin (thêta))

(4) = thêta

||x|| désigne la magnitude de x

L'étape (1) suit en notant que

croix (x, y) = || x || || y || péché (thêta) n,

et donc

point (n, croix (x, y))

= point (n, || x || || y || sin (thêta) n)

= || x || || y || péché (thêta) point (n, n)

qui est égal à 

|| x || || y || péché (thêta)

si ||n|| = 1.

L'étape (2) découle de la définition de atan2, en notant que atan2(cy, cx) = atan2(y,x), où c est un scalaire. L'étape (3) découle de la définition de atan2. L'étape (4) découle des définitions géométriques de cos et sin.

3
sircolinton

Le produit scalaire (point) de deux vecteurs vous permet d'obtenir le cosinus de l'angle entre eux . Pour obtenir la "direction" de l'angle, vous devez également calculer le produit croisé, il vous permettra de vérifier (via la coordonnée z) Cet angle est dans le sens horaire ou non (c.-à-d. si vous l'extrayez à 360 degrés ou non).

2
Nickolay Olshevsky

Pour une méthode 2D, vous pouvez utiliser la loi des cosinus .__ et la méthode "direction".

Pour calculer l'angle du segment P3: P1 Balayez dans le sens des aiguilles d'une montre pour segmenter P3: P2. 

  
 P1 P2 

 P3 
    double d = direction(x3, y3, x2, y2, x1, y1);

    // c
    int d1d3 = distanceSqEucl(x1, y1, x3, y3);

    // b
    int d2d3 = distanceSqEucl(x2, y2, x3, y3);

    // a
    int d1d2 = distanceSqEucl(x1, y1, x2, y2);

    //cosine A = (b^2 + c^2 - a^2)/2bc
    double cosA = (d1d3 + d2d3 - d1d2)
        / (2 * Math.sqrt(d1d3 * d2d3));

    double angleA = Math.acos(cosA);

    if (d > 0) {
        angleA = 2.*Math.PI - angleA;
    }

This has the same number of transcendental

opérations telles que suggérées ci-dessus et une seule opération plus ou moins à virgule flottante.

les méthodes utilisées sont:

 public int distanceSqEucl(int x1, int y1, 
    int x2, int y2) {

    int diffX = x1 - x2;
    int diffY = y1 - y2;
    return (diffX * diffX + diffY * diffY);
}

public int direction(int x1, int y1, int x2, int y2, 
    int x3, int y3) {

    int d = ((x2 - x1)*(y3 - y1)) - ((y2 - y1)*(x3 - x1));

    return d;
}
1
nichole

Une formule pour un angle horaire, cas 2D, entre 2 vecteurs, xa, ya et xb, yb.

Angle (vec.a-vec, b) = pi ()/2 * ((1 + signe (ya)) * * (1-signe (xa ^ 2)) - (1 + signe (yb)) * (1- signe (xb ^ 2)))

                        +pi()/4*((2+sign(ya))*sign(xa)-(2+sign(yb))*sign(xb))

                        +sign(xa*ya)*atan((abs(ya)-abs(xa))/(abs(ya)+abs(xa)))

                        -sign(xb*yb)*atan((abs(yb)-abs(xb))/(abs(yb)+abs(xb)))
0
theodore panagos

Si, par "voie directe", vous entendez éviter la déclaration if, alors je ne pense pas qu'il existe une solution vraiment générale.

Toutefois, si votre problème spécifique permet de perdre de la précision dans la discrétisation des angles et que vous êtes prêt à perdre du temps dans les conversions de types, vous pouvez mapper la plage [-pi, pi) autorisée de l'angle phi sur la plage autorisée de certains types entiers signés. . Vous obtiendrez alors la complémentarité gratuitement. Cependant, je n'ai pas vraiment utilisé cette astuce dans la pratique. Il est fort probable que les coûts des conversions float-to-integer et integer-to-float l'emporteraient sur les avantages du caractère direct. Il est préférable de définir vos priorités en matière d’écriture de code autovectorisable ou parallélisable lorsque le calcul de cet angle est souvent fait.

En outre, si les détails de votre problème sont tels qu'il existe un résultat plus probable pour la direction de l'angle, vous pouvez utiliser les fonctions intégrées des compilateurs pour fournir ces informations au compilateur, afin qu'il puisse optimiser la création de branches plus efficacement. Par exemple, dans le cas de gcc, c'est la fonction __builtin_expect. C'est un peu plus pratique à utiliser lorsque vous l'enveloppez dans de telles macros likely et unlikely (comme dans le noyau linux):

#define likely(x)      __builtin_expect(!!(x), 1)
#define unlikely(x)    __builtin_expect(!!(x), 0)
0
Vadim Khotilovich

il suffit de copier et coller ceci.

angle = (acos((v1.x * v2.x + v1.y * v2.y)/((sqrt(v1.x*v1.x + v1.y*v1.y) * sqrt(v2.x*v2.x + v2.y*v2.y))))/pi*180);

vous êtes les bienvenus ;-)

0
Red