web-dev-qa-db-ja.com

2つのベクトル間の時計回りの角度を直接計算する方法

2つのベクトル(2D、3D)間の時計回りの角度を見つけたい。

ドット積を使用した古典的な方法では、内側の角度(0-180度)が得られるため、ifステートメントを使用して、結果が必要な角度かその補数かを判断する必要があります。

時計回りの角度を直接計算する方法を知っていますか?

61
Felics

2Dケース

ドット積 が角度のコサインに比例するように、 行列式 はそのサインに比例します。したがって、次のように角度を計算できます。

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では、2つの任意に配置されたベクトルが、両方に垂直な独自の回転軸を定義します。その回転軸には固定方向がありません。つまり、回転角度の方向も一意に固定することはできません。一般的な規則の1つは、角度を常に正にし、正の角度に合うように軸を方向付けることです。この場合、角度を計算するには正規化されたベクトルのドット積で十分です。

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に埋め込まれた平面

1つの特殊なケースは、ベクトルが任意に配置されず、既知の法線ベクトルnを持つ平面内にある場合です。次に、回転軸はnの方向になり、nその軸の向きを修正します。この場合、nを含む2D計算を 行列式 に適合させて、サイズを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)

これが機能するための1つの条件は、法線ベクトルnに単位長があることです。そうでない場合は、正規化する必要があります。

トリプル製品として

この決定要因は、 @ Excrubulent が提案された編集で指摘されているように、 三重積 として表現することもできます。

det = n · (v1 × v2)

これはいくつかのAPIで実装する方が簡単かもしれませんし、ここで何が起こっているかについて異なる視点を提供します:クロス積は角度のサインに比例し、平面に垂直になるため、n。したがって、内積は基本的にそのベクトルの長さを測定しますが、それに正しい符号が付けられます。

146
MvG

角度を計算するには、2Dの場合にatan2(v1.s_cross(v2), v1.dot(v2))を呼び出すだけです。どこ s_crossは、クロス生成のスカラー類似物です(平行四辺形の符号付き領域)。 2Dケースの場合は、ウェッジプロダクションになります。 3Dの場合、平面の片側から時計回りに1つの方向、平面の反対側から別の方向に=)ので、時計回りの回転を定義する必要があります=)

編集:これは反時計回りの角度です、時計回りの角度はちょうど反対です

5
kassak

この答えはMvGのものと同じですが、説明が異なります(MvGのソリューションが機能する理由を理解しようとする私の努力の結果です)。私は、他の人が役に立つと思うオフチャンスに投稿しています。

反時計回りの角度thetaからxまでyは、与えられた通常のn(_||n|| = 1_)の視点に関して、によって与えられた

atan2(dot(n、cross(x、y))、dot(x、y))

(1)= atan2(|| x || || y || sin(θ)、|| x || || y || cos(θ))

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

(3)= x軸とベクトルの間の反時計回りの角度(cos(theta)、sin(theta))

(4)=シータ

ここで、_||x||_はxの大きさを示します。

ステップ(1)に続いて、

cross(x、y)= || x || || y || sin(theta)n、

など

dot(n、cross(x、y))

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

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

等しい

|| x || || y || sin(シータ)

_||n|| = 1_の場合。

ステップ(2)は_atan2_の定義から続き、atan2(cy, cx) = atan2(y,x)であることに注意してください。ここで、cはスカラーです。 _atan2_の定義からステップ(3)が続きます。ステップ(4)は、cosおよびsinの幾何学的定義から続きます。

3
sircolinton

2つのベクトルのスカラー(ドット)積により、それらの間の角度の余弦を取得できます。角度の「方向」を取得するには、外積も計算する必要があります(z座標を介して)角度が時計回りであるかどうかを確認できます(つまり、360度から抽出する必要があります)。

2

2Dメソッドの場合、余弦の法則と「方向」メソッドを使用できます。

セグメントP3:P2まで時計回りにスイープするセグメントP3:P1の角度を計算するには.

 
 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

上記の提案としての操作と、もう1つだけの浮動小数点操作。

使用するメソッドは次のとおりです。

 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

2つのベクトル、xa、yaとxb、ybの間の時計回りの角度、2Dの場合の式。

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

「直接的な方法」でifステートメントを回避することを意味する場合、本当に一般的な解決策はないと思います。

ただし、特定の問題により角度の離散化の精度がある程度失われ、型変換の時間をある程度失うことができる場合は、[-pi、pi)phi角度の許容範囲を符号付き整数型の許容範囲にマッピングできます。 。その後、無料で相補性を取得します。しかし、実際にはこのトリックを実際には使用しませんでした。最も可能性が高いのは、浮動小数点から整数への変換と整数から浮動小数点への変換の費用が、直接性の利点を上回ることです。この角度の計算が頻繁に行われる場合は、自動ベクトル化可能または並列化可能なコードの作成に優先順位を設定することをお勧めします。

また、角度の方向について明確な結果が得られるような問題の詳細がある場合、コンパイラーの組み込み関数を使用してこの情報をコンパイラーに提供できるため、分岐をより効率的に最適化できます。たとえば、gccの場合、__builtin_expect 関数。このようなlikelyおよびunlikelyマクロ(Linuxカーネルのように)にラップする場合は、多少便利です。

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

これをコピーして貼り付けてください

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

どういたしまして ;-)

0
Red