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では、任意に配置された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))
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の倍数になります。したがって、内積は基本的にそのベクトルの長さを測定しますが、正しい符号が付けられています。
atan2(v1.s_cross(v2), v1.dot(v2))
角度を計算するには、2Dケースを呼び出す必要があります。s_cross
クロスプロダクションのスカラーアナログ(平行四辺形の符号付き領域)はどこにありますか。2Dの場合、ウェッジの作成になります。3Dの場合、時計回りの回転を定義する必要があります。これは、平面の一方の側から時計回りが1つの方向であり、平面のもう一方の側から別の方向であるためです=)
編集:これは反時計回りの角度で、時計回りの角度は正反対です
この答えはMvGの答えと同じですが、説明が異なります(MvGのソリューションが機能する理由を理解しようとした私の努力の結果です)。他の人が役立つと思うオフチャンスに投稿しています。
与えられた法線()の視点に対するtheta
からx
への反時計回りの角度は、次の式で与えられます。y
n
||n|| = 1
atan2(dot(n、cross(x、y))、dot(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)は、次のことに注意してください。
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
で、はスカラーです。ステップ(3)は、の定義に従います。ステップ(4)は、との幾何学的定義に従います。atan2(cy, cx) = atan2(y,x)
c
atan2
cos
sin
2つのベクトルのスカラー(ドット)積を使用すると、それらの間の角度の余弦を取得できます。角度の「方向」を取得するには、外積も計算する必要があります。これにより、角度が時計回りであるかどうか(つまり、360度から抽出するかどうか)を確認できます(z座標を介して)。
最もシンプルでエレガントな解決策の1つがコメントの中に隠されているので、別の回答として投稿すると便利かもしれません。
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法の場合、余弦定理と「方向」法を使用できます。
セグメント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;
}
「直接的な方法」でif
ステートメントを回避することを意味する場合、実際に一般的な解決策はないと思います。
ただし、特定の問題によって角度の離散化である程度の精度が失われ、型変換である程度の時間が失われても問題がない場合は、[-pi、pi)許容範囲のファイ角度をいくつかの符号付き整数型の許容範囲にマッピングできます。 。そうすれば、無料で相補性を得ることができます。しかし、私は実際にはこのトリックを実際には使用しませんでした。ほとんどの場合、floatからintegerおよびintegerからfloatへの変換の費用は、直接性の利点を上回ります。この角度の計算が頻繁に行われる場合は、自動ベクトル化または並列化可能なコードの記述に優先順位を設定することをお勧めします。
また、問題の詳細が角度方向に明確でより可能性の高い結果をもたらすようなものである場合は、コンパイラの組み込み関数を使用してこの情報をコンパイラに提供できるため、分岐をより効率的に最適化できます。たとえば、gccの場合、それは__builtin_expect
関数です。(Linuxカーネルのように)そのようなマクロlikely
にラップするときに使用する方がやや便利です。unlikely
#define likely(x) __builtin_expect(!!(x), 1)
#define unlikely(x) __builtin_expect(!!(x), 0)
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)))
これをコピーして貼り付けるだけです。
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);
どういたしまして ;-)