quaternionによるローカル座標回転を考えた【Unity】【Quaternion】【Local Coordinate】【Rotation】

クォータニオンでのローカル軸回転に苦しめられたので書き残しておきます。

 

結論

ある物体の回転角がクォータニオン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)