簡體   English   中英

計算2個向量之間順時針角度的直接方法

[英]Direct way of computing clockwise angle between 2 vectors

我想找出 2 個向量(2D,3D)之間的順時針角度。

點積的經典方法給出了內角(0-180 度),我需要使用一些 if 語句來確定結果是我需要的角度還是它的補角。

你知道計算順時針角度的直接方法嗎?

二維案例

就像點積與角度的余弦成正比一樣,行列式與其正弦成正比。 所以你可以像這樣計算角度:

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)

該角度的方向與坐標系的方向相匹配。 左手坐標系中,即x指向右而y向下,這在計算機圖形中很常見,這意味着順時針角度為正號。 如果坐標系的方向是數學上的, y向上,則按照數學慣例,您會得到逆時針角度。 改變輸入的順序會改變符號,所以如果你對符號不滿意,只需交換輸入。

3D案例

在 3D 中,兩個任意放置的向量定義了它們自己的旋轉軸,垂直於兩者。 該旋轉軸沒有固定方向,這意味着您也無法唯一確定旋轉角度的方向。 一個常見的約定是讓角度始終為正,並以適合正角度的方式定位軸。 在這種情況下,歸一化向量的點積足以計算角度。

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))

嵌入 3D 的平面

一種特殊情況是您的向量不是任意放置的,而是位於具有已知法向量n的平面內。 然后旋轉軸也將在方向n 上,並且n的方向將固定該軸的方向。 在這種情況下,您可以調整上面的 2D 計算,將n包含在行列式中以使其大小為 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)

使其起作用的一個條件是法向量n具有單位長度。 如果沒有,則必須對其進行規范化。

作為三重產品

正如@Excrubulent在建議的編輯中指出的那樣,這個決定因素也可以表示為三重積

det = n · (v1 × v2)

這在某些 API 中可能更容易實現,並且對這里發生的事情給出了不同的觀點:叉積與角度的正弦成正比,並且垂直於平面,因此是n的倍數。 因此,點積將基本上測量該向量的長度,但附加了正確的符號。

要計算角度,您只需要為 2D 情況調用atan2(v1.s_cross(v2), v1.dot(v2)) 其中s_cross是交叉產生的標量模擬(平行四邊形的有符號面積)。 對於將是楔形生產的 2D 情況。 對於 3D 情況,您需要定義順時針旋轉,因為從平面的一側順時針是一個方向,從平面的另一側是另一個方向 =)

編輯:這是逆時針角度,順時針角度剛好相反

這個答案與 MvG 的相同,但解釋不同(這是我努力理解 MvG 解決方案為何有效的結果)。 我發布它是為了其他人覺得它有幫助。

相對於給定法線n ( ||n|| = 1 ) 的視點,從xy的逆時針角度theta由下式給出

atan2(點(n,交叉(x,y)),點(x,y))

(1) = atan2( ||x|| ||y|| sin(theta), ||x|| ||y|| cos(theta) )

(2) = atan2( sin(theta), cos(theta) )

(3) = x 軸與向量之間的逆時針角度(cos(theta), sin(theta))

(4) = θ

其中||x|| 表示x的大小。

步驟 (1) 之后注意到

交叉(x,y)= ||x|| ||y|| 罪(θ)n,

所以

點(n,交叉(x,y))

= dot(n, ||x|| ||y|| sin(theta) n)

= ||x|| ||y|| 罪(θ)點(n,n)

等於

||x|| ||y|| 罪(θ)

如果||n|| = 1 ||n|| = 1

步驟 (2) 遵循atan2的定義,注意atan2(cy, cx) = atan2(y,x) ,其中c是標量。 步驟 (3) 遵循atan2的定義。 步驟 (4) 遵循cossin的幾何定義。

兩個向量的標量(點)積可讓您獲得它們之間角度的余弦。 要獲得角度的“方向”,您還應該計算叉積,它會讓您檢查(通過 z 坐標)角度是否為順時針(即您是否應該從 360 度中提取它)。

對於 2D 方法,您可以使用余弦定律和“方向”方法。

計算線段 P3:P1 順時針掃過線段 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

以上建議的操作,只有一個或多個浮點運算。

它使用的方法是:

 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;
}

如果“直接方式”是指避免使用if語句,那么我認為沒有真正通用的解決方案。

但是,如果您的特定問題會導致角度離散化的精度降低,並且您可以在類型轉換中浪費一些時間,則可以將 phi 角度的 [-pi,pi) 允許范圍映射到某些有符號整數類型的允許范圍. 然后您將免費獲得互補性。 但是,我在實踐中並沒有真正使用這個技巧。 最有可能的是,浮點數到整數和整數到浮點數轉換的費用​​將超過直接性的任何好處。 當這個角度計算完成很多時,最好設置你編寫自動向量化或並行化代碼的優先級。

此外,如果您的問題細節使得角度方向的結果更有可能,那么您可以使用編譯器的內置函數將此信息提供給編譯器,以便它可以更有效地優化分支。 例如,在 gcc 的情況下,就是__builtin_expect函數。 當你把它包裝成這樣likelyunlikely宏(比如在 linux 內核中)時,使用起來會更方便:

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

由於最簡單和最優雅的解決方案之一隱藏在評論中,我認為將其作為單獨的答案發布可能會很有用。 acos可能會導致非常小的角度不准確,因此通常首選atan2 對於 3D 情況:

dot = x1 * x2 + y1 * y2 + z1 * z2
cross_x = (y1 * z2 – z1 * y2)
cross_y = (z1 * x2 – x1 * z2)
cross_z = (x1 * y2 – y1 * x2)
det = sqrt(cross_x * cross_x + cross_y * cross_y + cross_z * cross_z)
angle = atan2(det, dot)

對於2D 情況,atan2 可以輕松計算 (1, 0) 向量(X 軸)與您的一個向量之間的角度。 公式為:

Atan2(y, x)

所以你可以很容易地計算出兩個角度相對於 X 軸的差異

angle = -(atan2(y2, x2) - atan2(y1, x1))

為什么不將其用作默認解決方案? atan2 效率不夠。 最佳答案的解決方案更好。 對 C# 的測試表明,該方法的性能降低了 19.6% (100 000 000 次迭代)。 這並不重要,但令人不快。

所以,另一個有用的信息:

外角和內角之間的最小角度(以度為單位):

abs(angle * 180 / PI)

全角度數:

angle = angle * 180 / PI
angle = angle > 0 ? angle : 360 - angle

或者

angle = angle * 180 / PI
if (angle < 0) angle = 360 - angle;

順時針角度的公式,二維情況,在 2 個向量 xa,ya 和 xb,yb 之間。

Angle(vec.a-vec,b)=
  pi()/2*((1+sign(ya))* 
  (1-sign(xa^2))-(1+sign(yb))*
  (1-sign(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)))


                     
  

只需復制並粘貼這個。

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);

別客氣 ;-)

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM