u と v の 2 つのベクトルがあります。u から v への回転を表す四元数を見つける方法はありますか?
9 に答える
Quaternion q;
vector a = crossproduct(v1, v2);
q.xyz = a;
q.w = sqrt((v1.Length ^ 2) * (v2.Length ^ 2)) + dotproduct(v1, v2);
q を正規化することを忘れないでください。
リチャードは、一意の回転がないことについては正しいですが、上記は「最短の弧」を与えるはずです。これはおそらくあなたが必要とするものです.
中間ベクトル解
私は、Imbrondir が提示しようとしていたと思われる解決策を思いつきました (小さな間違いがありましたが、おそらくそれが、sinisterchipmunk が検証に苦労した理由です)。
次のように、軸の周りの回転を表す四元数を構築できるとします。
q.w == cos(angle / 2)
q.x == sin(angle / 2) * axis.x
q.y == sin(angle / 2) * axis.y
q.z == sin(angle / 2) * axis.z
また、正規化された 2 つのベクトルの内積と外積は次のようになります。
dot == cos(theta)
cross.x == sin(theta) * perpendicular.x
cross.y == sin(theta) * perpendicular.y
cross.z == sin(theta) * perpendicular.z
uからvへの回転は、垂直ベクトルを中心にシータ (ベクトル間の角度) だけ回転することで実現できるため、内積と外積の結果から、そのような回転を表す四元数を直接構築できるように見えます。 ; ただし、そのままでは、theta = angle / 2であり、これを行うと目的の回転の 2 倍になることを意味します。
1 つの解決策は、 uとvの間の中間ベクトルを計算し、uと中間ベクトルの間の角度の2 倍の回転を表すクォータニオンを構築するために、 uと中間ベクトルの内積と外積を使用することです。これでvまでたどり着きます!
u == -vで、固有の中間ベクトルを計算できなくなる特殊なケースがあります。これは、 uからvに移動できる「最短円弧」回転が無限にある場合に予想され、特別な場合の解決策として、u (またはv )に直交する任意のベクトルを中心に 180 度回転する必要があります。これは、 uに平行でない他のベクトルとuの正規化された外積を取ることによって行われます。
疑似コードが続きます (明らかに、実際には特殊なケースでは、浮動小数点の不正確さを説明する必要があります。おそらく、内積を絶対値ではなく何らかのしきい値に対してチェックすることによって)。
また、 u == vの場合に特別なケースはないことに注意してください(恒等四元数が生成されます -- 自分で確認してください)。
// N.B. the arguments are _not_ axis and angle, but rather the
// raw scalar-vector components.
Quaternion(float w, Vector3 xyz);
Quaternion get_rotation_between(Vector3 u, Vector3 v)
{
// It is important that the inputs are of equal length when
// calculating the half-way vector.
u = normalized(u);
v = normalized(v);
// Unfortunately, we have to check for when u == -v, as u + v
// in this case will be (0, 0, 0), which cannot be normalized.
if (u == -v)
{
// 180 degree rotation around any orthogonal vector
return Quaternion(0, normalized(orthogonal(u)));
}
Vector3 half = normalized(u + v);
return Quaternion(dot(u, half), cross(u, half));
}
このorthogonal
関数は、指定されたベクトルに直交する任意のベクトルを返します。この実装では、最も直交する基底ベクトルとの外積を使用します。
Vector3 orthogonal(Vector3 v)
{
float x = abs(v.x);
float y = abs(v.y);
float z = abs(v.z);
Vector3 other = x < y ? (x < z ? X_AXIS : Z_AXIS) : (y < z ? Y_AXIS : Z_AXIS);
return cross(v, other);
}
中途四元数解
これは実際に受け入れられた回答で提示されたソリューションであり、中途半端なベクトルソリューションよりもわずかに高速であるように見えます(私の測定では約20%高速ですが、私の言葉を信じてはいけません)。私のような他の人が説明に興味がある場合に備えて、ここに追加します。
基本的に、中間ベクトルを使用してクォータニオンを計算する代わりに、必要な回転の 2 倍になるクォータニオンを計算し (他のソリューションで詳しく説明されています)、それとゼロ度の中間にあるクォータニオンを見つけることができます。
前に説明したように、必要な回転を 2 倍にするクォータニオンは次のとおりです。
q.w == dot(u, v)
q.xyz == cross(u, v)
ゼロ回転のクォータニオンは次のとおりです。
q.w == 1
q.xyz == (0, 0, 0)
中間クォータニオンの計算は、ベクトルの場合と同様に、クォータニオンを合計して結果を正規化するだけです。ただし、ベクトルの場合と同様に、四元数は同じ大きさでなければなりません。そうでない場合、結果はより大きな大きさの四元数に偏ってしまいます。
2 つのベクトルの内積と外積から構成される四元数は、これらの積と同じ大きさになりますlength(u) * length(v)
。4 つの成分すべてをこの係数で割る代わりに、恒等四元数をスケールアップできます。そして、受け入れられた答えが を使用するsqrt(length(u) ^ 2 * length(v) ^ 2)
ことで問題を複雑にしているように見える理由を疑問に思っている場合は、ベクトルの長さの 2 乗は長さよりも計算が速いため、計算を 1 つ節約できますsqrt
。結果は次のとおりです。
q.w = dot(u, v) + sqrt(length_2(u) * length_2(v))
q.xyz = cross(u, v)
そして、結果を正規化します。疑似コードは次のとおりです。
Quaternion get_rotation_between(Vector3 u, Vector3 v)
{
float k_cos_theta = dot(u, v);
float k = sqrt(length_2(u) * length_2(v));
if (k_cos_theta / k == -1)
{
// 180 degree rotation around any orthogonal vector
return Quaternion(0, normalized(orthogonal(u)));
}
return normalized(Quaternion(k_cos_theta + k, cross(u, v)));
}
述べた問題は明確に定義されていません。指定されたベクトルのペアに対して一意の回転はありません。たとえば、 u = <1, 0, 0>および v = <0, 1, 0>の場合を考えてみましょう。u から v への 1 回転は、z 軸を中心としたpi / 2回転になります。u から v への別の回転は、ベクトル<1, 1, 0> を中心としたpi回転になります。
私はクォータニオンがあまり得意ではありません。しかし、私はこれに何時間も苦労し、Polaris878 ソリューションを機能させることができませんでした。v1とv2を事前に正規化してみました。q の正規化。q.xyz の正規化。それでも、私はそれを理解していません。結果はまだ私に正しい結果を与えませんでした。
結局、解決策を見つけました。それが他の誰かを助けるなら、ここに私の作業(python)コードがあります:
def diffVectors(v1, v2):
""" Get rotation Quaternion between 2 vectors """
v1.normalize(), v2.normalize()
v = v1+v2
v.normalize()
angle = v.dot(v2)
axis = v.cross(v2)
return Quaternion( angle, *axis )
v1 と v2 が v1 == v2 または v1 == -v2 のように平行である場合 (ある程度の許容誤差あり)、特別なケースを作成する必要があります。ここで、解決策は Quaternion(1, 0,0,0) (回転なし) である必要があります。または Quaternion(0, *v1) (180 度回転)
アルゴリズムの観点から、最速のソリューションは疑似コードに見えます
Quaternion shortest_arc(const vector3& v1, const vector3& v2 )
{
// input vectors NOT unit
Quaternion q( cross(v1, v2), dot(v1, v2) );
// reducing to half angle
q.w += q.magnitude(); // 4 multiplication instead of 6 and more numerical stable
// handling close to 180 degree case
//... code skipped
return q.normalized(); // normalize if you need UNIT quaternion
}
単位四元数が必要であることを確認してください (通常、補間には必要です)。
注: 非単位クォータニオンは、単位よりも高速な一部の操作で使用できます。