web-dev-qa-db-ja.com

あるベクトルから別のベクトルへの回転を表すクォータニオンを見つける

2つのベクトルuとvがあります。uからvへの回転を表すクォータニオンを見つける方法はありますか?

97
sdfqwerqaz1
Quaternion q;
vector a = crossproduct(v1, v2);
q.xyz = a;
q.w = sqrt((v1.Length ^ 2) * (v2.Length ^ 2)) + dotproduct(v1, v2);

Qを正規化することを忘れないでください。

リチャードは、ユニークな回転がないことについては正しいですが、上記は「最短アーク」を与えるはずです。これはおそらく必要なものです。

106
Polaris878

半方向ベクトルソリューション

私は、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(ベクトル間の角度)で回転することで実現できます。 、ドットと外積の結果からこのような回転を表すクォータニオンを直接構築できるように見えます。ただし、現状では、theta = angle/2です。つまり、そうすると、目的の回転が2倍になります。

1つの解決策は、uvの中間のベクトルを計算し、uおよびhalf-wayベクトルは、 twice 角度の回転を表すクォータニオンを構築しますuhalf-wayベクトルの間で、v

u == -vと特殊な中間ベクトルの計算が不可能になる特別なケースがあります。これは、uからvに至る無限に多くの「最短アーク」回転を考えると予想されます。特殊なケースのソリューションとして、u(またはv)に直交するベクトルを中心に180度回転します。これは、uと他のベクトル not u

擬似コードが続きます(明らかに、実際には、特殊なケースでは浮動小数点の不正確さを考慮する必要があります-おそらく絶対値ではなく何らかのしきい値に対してドット積をチェックすることによって)。

また、u == v(恒等式四元数が生成される場合、特殊なケース no があることに注意してください自分で確認してください)。

_// 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%高速ですが、Wordを使用しません)。私のような人が説明に興味がある場合に備えて、ここに追加します。

基本的に、ハーフウェイベクトルを使用してクォータニオンを計算する代わりに、必要な回転の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)));
}
_
58
Joseph Thomson

前述の問題は明確に定義されていません。ベクトルの特定のペアに固有の回転はありません。たとえば、u = <1、0、0>およびv = <0、1、0>の場合を考えます。 uからvへの1つの回転は、pi/2 z軸を中心とした回転になります。 uからvへの別の回転は、piベクトルの周りの回転<1、1、0>です。

5
Richard Dunlap

純粋な四元数を使用してベクトルを表現しないのはなぜですか?おそらく最初にそれらを正規化する方が良いでしょう。
q1 =(0 uバツ あなたはy あなたはz) '
q2 =(0 vバツ vy vz) '
q1 q腐敗 = q2
qで事前乗算1-1
q腐敗 = q1-1 q2
where q1-1 = q1conj / q規範
これは「左部門」と考えることができます。あなたが望んでいるものではない正しい分割:
q腐敗、右 = q2-1 q1

4
madratman

いくつかの答えは、外積が0になる可能性を考慮していないようです。以下のスニペットは、角度軸表現を使用しています。

//v1, v2 are assumed to be normalized
Vector3 axis = v1.cross(v2);
if (axis == Vector3::Zero())
    axis = up();
else
    axis = axis.normalized();

return toQuaternion(axis, ang);

toQuaternionは次のように実装できます。

static Quaternion toQuaternion(const Vector3& axis, float angle)
{
    auto s = std::sin(angle / 2);
    auto u = axis.normalized();
    return Quaternion(std::cos(angle / 2), u.x() * s, u.y() * s, u.z() * s);
}

Eigenライブラリを使用している場合は、次のこともできます:

Quaternion::FromTwoVectors(from, to)
2
Shital Shah

クォータニオンはあまり得意ではありません。しかし、私はこれに何時間も苦労し、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度回転)

1
Imbrondir

アルゴリズムの観点から見ると、最速のソリューションは擬似コードに見えます

 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
 }

ユニットクォータニオンが必要であることを確認してください(通常、補間に必要です)。

注:非ユニットクォータニオンは、ユニットよりも高速な操作で使用できます。

0
minorlogic