クォータニオンでのローカル軸回転に苦しめられたので書き残しておきます。
結論
ある物体の回転角がクォータニオンpであり、それをワールド座標でクォータニオンqだけ回転させる場合は
q⊗p=(qwpx−qzpy+qypz+qxpw,qzpx+qwpy−qxpz+qypw,−qypx+qxpy+qwpz+qzpw,−qxpx−qypy−qzpz+qwpw)
が変換後の回転角となる。
一方、同じ物体(回転角p)を、ローカル座標でクォータニオンqだけ回転させる場合は
p⊗q
が変換後の回転角となる。
これは、ワールド座標でq回転している物体にpを作用させたと理解できる。すなわち、回転角pをローカル座標でq回転させた結果の回転角は、回転角qをワールド座標でp回転させた結果の回転角と等しくなる。
初めに
ある物体の回転(姿勢)はquaternionを用いて一意に記述することができます。
q = (q_x,q_y,q_z,q_w) (ただし、q_x^2 + q_y^2 + q_z^2 = 1)
剛体の回転自由度は三自由度であり、Unityのinspector上では直感的に分かりやすいオイラー角を用いて表されています。しかしながら、オイラー角は計算機にとって計算がしにくく、またジンバルロックなどの問題もあるため、実際に計算される時にはquaternionが用いられています。
ワールド座標での回転
ある物体の姿勢がクォータニオンpであり、それを(ワールド座標で)クォータニオンqだけ回転させるとき、
q⊗p=(qwpx−qzpy+qypz+qxpw,qzpx+qwpy−qxpz+qypw,−qypx+qxpy+qwpz+qzpw,−qxpx−qypy−qzpz+qwpw)
が回転後の物体の姿勢を表すクォータニオンになります。
例えばワールド座標x軸回りの回転は
r = (λ_x sinΘ/2, λ_y sinΘ/2,λ_z sinΘ/2,cosΘ/2 )
= (1,0,0,0)
となるので、
p = (p_x, p_y, p_z, p_w)をx軸回りに180度回転させると
r⊗p = (p_w, -p_z, p_y, -p_x)
となります。
Unityにおいては、簡単に
gameObject.transform.Rotate(180,0,0,Space.World);
と記述できます。
ローカル座標での回転
ワールド座標における回転は以上の通りですが、ローカル座標で回転させるにはどうすればよいでしょうか。
ローカルx軸回り180度回転は、Unityにおいては、簡単に
gameObject.transform.Rotate(180,0,0,Space.Local);
と記述できます。
ところで、pをローカル座標でq回転させるのは、qをワールド座標でp回転させるのと等しくなります。
よって、クォータニオンpをローカル座標でクォータニオンq回転させたときは、
p⊗q
が回転後の物体の姿勢を表すクォータニオンになります。
例えばローカル座標x軸回りの回転は
r = (λ_x sinΘ/2, λ_y sinΘ/2,λ_z sinΘ/2,cosΘ/2 )
= (1,0,0,0)
となるので、
p = (p_x, p_y, p_z, p_w)をx軸回りに180度回転させると
p⊗r = (p_w, p_z, -p_y, -p_x)
となります。
余談:Quaternionの計算をUnity上で態々書かなくても、transform.Rotateで良くないか?この記事のモチベは?
トラッキングデータなど、外部ソフトから回転角をUnityに持ってきて、Unityの座標系・モデルの座標系に変換し、モデルを動かすことがあります。
自分の場合は、外部ソフトから持ってきた回転角が右手座標系であったため、それをUnityの左手座標系にして、更にボーンの初期回転角に依る角度ずれを補正し、Unity側モデルの座標系に合わせる必要が生じました。一先ず書いてみると、以下のようになりました。
r = (外部から持ってきたrotation);
r = new Quaternion(r.z,-r.x,r.y,-r.w); //座標変換
this.transform.rotation = r;
this.transform.Rotate(180, 0, 0, Space.Self); //180度ずれを補正
この2行目と4行目の処理を(計算速度の観点から)一行に減らしたかったため、transform.Rotate(180,0,0 Space.self)をクォータニオンの計算で何とか表せないかと考えたためです。
上で書いたことから、transform.Rotate(180, 0, 0, Space.Self)はr ⊗ (1,0,0,0)と考えられるので、コードは以下のようになりました。
r = (外部から持ってきたrotation);
r = new Quaternion(-r.w,r.y,r.x,-r.z); //座標変換
this.transform.rotation = r;
シンプルな実装ではありますが、自分は「クォータニオンpをローカル座標でクォータニオンq回転させた結果はクォータニオンqをワールド座標でクォータニオンp回転させた結果(p⊗q)と等しくなる」ということに中々気がつきませんでした。
pを(ローカル座標で)q回転させるためのクォータニオンを得ようと考え、q'⊗p = p⊗qとなるq'を無駄に考えていました。残念。
クォータニオンによるローカル座標回転はググっても出てこなかったので、一応書き物として残しておきます。この記事を見つけた人の助けとなりますよう。
ちなみに、回転を反転させる方法は以下の通りです。
全方向反転:
(q_x,q_y,q_z,-q_w)
x軸方向のみ反転:
(q_x,-q_y,-q_z,q_w)
y軸方向のみ反転:
(-q_x,q_y,-q_z,q_w)
z軸方向のみ反転:
(-q_x,-q_y,q_z,q_w)