四元数のおべんきょログ

今やってるやつの向きの指定に四元数を使おうと思う。外部から使うことはできるものの、いまだに内部がどうなってるのかは詳しくないので、あらためて勉強しなおし。そのログ。

四元数の目的

Quaternionとも呼ばれ、「向き」を決定するのに使う。もう少し厳密に言えば「姿勢」を一意に決定するために使う。「向き」と「姿勢」の違いはあとで出てくる。はず。

四元数の構造

「Quaternion」でググる。見つけたやつをもとにスタディ開始。


参考ここらへん。


まず、「Quaternion」とは「複素数」の拡張であるらしい。
「実数」だけなら、それは数直線で表現できる。直線。つまり一次元。
複素数」は「実数」+「虚数」で、軸が一本増える。平面。つまり二次元。
であるならば、三次元を表現するためにはもう一つ軸が増えればいい。つまり、「実数+虚数+何か」で表現すればいい。で、「Quaternion」の構造はどうなっているかというと、

Q = t + xi + yj + zk

多いー!一個多いー!kって何だkって。


落ち着け。素数以外の数を数えて落ち着くんだ。「1,4,6,8,9,10...」。「素数以外の数」はあまり注目されない孤独な数字。わたしに勇気を与えてくれる。


つまりこれが「向き」と「姿勢」の違いなわけだな。「向き」とは方向。ベクトル。いわば「どっちに向かって指差すか」を表すもの。「姿勢」は「向き」に加えて「本体の傾き」も決定する。「どっちを指差すか」に「腕をどれくらいコークスクリューさせているか」を加えたもの。つまり、同じ向きを指していても「どれくらい回転をかけているか」は決まらないので、それを決めてしまおうということだ。きっとそうだ。そうに違いない。


で、確認してみると、(x, y, z)が3次元ベクトル(腕の向き)を表し、tが回転量(腕の回転)を表しているらしい。これだけだとだいぶ誤解を招くな(tは2PIとか書くの?みたいな)。厳密に書けば「(x, y, z)を軸にθだけ回転する」は「cos(θ/2) + x*sin(θ/2)*i + y*sin(θ/2)*j + z*sin(θ/2)*k」と表現される。


さらに、「指差し」を例えに使ってしまうと、「Quaternionってのは"どっちを向いてるか"+"回転量"なんだぁ」みたいに誤解されてしまうな。実際には「最初の姿勢をもとに、"この軸で""これだけ回転したもの"」を表すのがQuaternionだ。と思う(→これもちょっと違うっぽいな。基本はそうなんだけど)。「指差し」を例えに使ったのは「3次元だけじゃ足りない」というのを表現するためのものなのだ。(でも、「大きさ」というものを考慮しなければ3次元だけでもいけんじゃね?とか考えつつ、ややこしくなるので置いておく)

四元数の使い方

この先は知ってるので、ここらへんで終了。使い方はさっきのリンク先とかにあるし。
ざっとメモると、

  • 逆Quaternionというのがある。(かけると「1」になるアレ)
  • 実際の計算では、もとのQuaternionと逆Quaternionではさんで計算する。

あたり。

その他

「球面線型補間」とかは「柱が倒れる」とかに使えるんだよねぇ。これを使わずに単純な方法でやると「柱の根元が変な風に移動する」という現象が起こるのだ。もっと早目に知りたかったな。


Quaternion×Quaternionで出てくるのも「軸と回転量」かなぁと思ったけど、そうじゃないよなぁ。頭の中で「90度回転」とか組み合わせてみたやつは、一つの「軸と回転量」では表現できない。だからたぶん「軸と回転量の掛け合わせた結果」みたいなものを表現してるんだろう。ちゃんと計算してみるべきか。

ちゃんと計算してみる

概念だけの状態と計算で身に付けることの差を実感したく、実際に計算する所存なり。


例に出した「指差し」をもとに座標の計算テスト。まずは、そのために方向とかを決定しておく。
「前後」を「Z軸」にして、自分にとっての「前」をプラス方向にする。「上下」を「Y軸」にして、「上」をプラスにする。で、「左右」が「X軸」になるのだが、どっちをプラスにすべきか。いわゆる右手座標系と左手座標系の問題。ライブラリ作る場合は、両方対応して定数使ってプリコンパイルの段階で分岐させればいいんだろうけど、ここではODEの座標系に合わせることにする。で、確認したところ、「え〜!Zが上下なの〜!」ともう一度驚く。なんで忘れてんだ俺。まぁそれはともかく「右手座標系」なので「左」がX軸のプラスになる。


で、その座標系で「犯人はお前だ!」という感じの腕の位置を初期座標にしてみる。「肘」を原点。「人差し指の先」が前方向にあるので(0,0,10)ぐらいで。さらに親指は左を向いているので(2,0,8)あたり。
これを、まず時計回りに90度回転させて、「手で銃をつくって発射する前の態勢」にする。この段階で、人差し指の座標は変わらないけど親指の座標は(0,2,8)になってるはず。
さらに肘を中心に左に回転させて「死刑!」のポーズにする(古いよ。俺の世代でもビミョーだよ。でもミサミサがいるか)。そうすると、人差し指の座標は(10,0,0)で、親指の座標は(8,2,0)になるはず。
これを、実際に計算して確かめてみる。

「犯人はお前だ!」→「手で銃」

腕を軸に時計回りに90度回転する。つまり、Z軸を中心に90度だか−90度だか回転するわけだ。で、90度なのか−90度なのか。「腕を軸に時計回り」というのをXY平面での回転で考えると、「いわゆるXY平面」=「Xのプラスが右、Yのプラスが上」における「反時計回り」=「いわゆる普通の回転」なので、たぶん「プラス90度回転」なんだろうなぁと見当つけて、回転開始。
親指の座標が(2,0,8)から(0,2,8)になるはず。


まず、Quaternionは「Z軸:(0,0,1)に90度回転」から、定義より「cos(90/2) + 0*sin(90/2)*i + 0*sin(90/2)*j + 1*sin(90/2)*k」→「\frac{1}{\sqrt{2}} + \frac{1}{\sqrt{2}}*k
座標(2,0,8)は「2*i + 0*j + 8*k」→「2*i + 8*k」と表現される。これを回転させるには、「四元数×座標×共役四元数」とするので、
(\frac{1}{\sqrt{2}} + \frac{1}{\sqrt{2}}*k) * (2*i + 8*k) * (\frac{1}{\sqrt{2}} - \frac{1}{\sqrt{2}}*k)
= (\sqrt{2}*i + \sqrt{2}*k*i + 4\sqrt{2}*k + 4\sqrt{2}*k*k) * (\frac{1}{\sqrt{2}} - \frac{1}{\sqrt{2}}*k)
(i + k*i + 4*k + 4*k*k) - (i*k + k*i*k + 4*k*k + 4*k*k*k)
ここで、「i*i = -1, j*j = -1, k*k = -1」「i*j = k, j*k = i, k*i = j」「j*i = -k, k*j = -i, i*k = -j」を使うと、
(i + j + 4*k - 4) - (-j + i - 4 - 4*k)
2*j + 8*k
となって、「0*i + 2*j + 8*k」→座標(0,2,8)になった。目標通り。
計算途中で、「i, j, kが軸に対応するなら、右手座標系と左手座標系で計算方法違ったりする?」とか思ったりするが、ひとまずおいておこう。覚えてたら通勤中にでも確認するさ。

「手で銃」→「死刑!」

上の計算結果にさらに「Y軸方向に時計回りに90度」?さっきは当たり前のように「時計回り」とか使ったけど、ちゃんと「プラスの向き」を意識しないといかんね。指差しの時と同じく「プラス=奥方向」とした場合に「Y軸方向に時計回りに90度」=「Y軸プラス90度回転」だ。
で、普通に計算してももう面白くないので、「2つのQuaternionをかけたもの」を先に計算してから、「初期座標」にかけて最終地点を求めてみる。こっちの方が実際のコーディングの確認にもなるし。


じゃ、まずは「2つのQuaternionをかけたもの」を計算。
2つのQuaternionはそれぞれ下の通り。
「Z軸:(0,0,1)に90度回転」は「\frac{1}{\sqrt{2}} + \frac{1}{\sqrt{2}}*k
「Y軸:(0,1,0)に90度回転」は「\frac{1}{\sqrt{2}} + \frac{1}{\sqrt{2}}*j
で、どっちを右にしてかければいいのだろうか。やってみるとわかるが、順序を逆にしてしまうと「死刑!」じゃなくて「手で銃を発射した後のポーズ」になるのだ。
計算方法を確認してみよう。「四元数×座標×共役四元数」で計算する。記号に変換して、「Q * P * -Q」と表現してみる。Q1で回転させた後にQ2で回転させることを考えると、「Q1 * P * -Q1」の結果をQ2で回転させればいい。「Q1 * P * -Q1」の結果をP1としてみると、「Q2 * P1 * -Q2」が求めるものだ。で、まんま「P1 = Q1 * P * -Q1」だから、「Q2 * (Q1 * P * -Q1) * -Q2」となり、これは「(Q2 * Q1) * P * -(Q1 * Q2)」となるはず。ぶっちゃけ共役四元数(-(Q1 * Q2))の方は自信ないが。
まぁともかく、「P」にかけるべき共役数は「Q2 * Q1」なので、「先にかけるやつは右側」なのだと思う。なので、その方向で計算。
(\frac{1}{\sqrt{2}} + \frac{1}{\sqrt{2}}*j) * (\frac{1}{\sqrt{2}} + \frac{1}{\sqrt{2}}*k)
\frac{1}{2} + \frac{1}{2}*j + \frac{1}{2}*k + \frac{1}{2}*j*k
さっきの「j*k = i」を使って、
\frac{1}{2} + \frac{1}{2}*j + \frac{1}{2}*k + \frac{1}{2}*i
順番を整理して、
\frac{1}{2} + \frac{1}{2}*i + \frac{1}{2}*j + \frac{1}{2}*k
となる。ちなみに、「これの共役四元数」も「それぞれの共役四元数の積」も同じになることを確認した。「それぞれの共役四元数の積」の方はかける順序が逆になるので注意。一般化してちゃんと証明もできそうだ。じゃあそこらへんはもういいや。次、次。


人差し指の位置を計算する。
最初の位置が(0,0,10)=「10*k」で、これが(10,0,0)=「10*i」になるはず。
(\frac{1}{2} + \frac{1}{2}*i + \frac{1}{2}*j + \frac{1}{2}*k) * (10*k) * (\frac{1}{2} - \frac{1}{2}*i - \frac{1}{2}*j - \frac{1}{2}*k)
(\frac{10}{2}*k + \frac{10}{2}*i*k + \frac{10}{2}*j*k + \frac{10}{2}*k*k) * (\frac{1}{2} - \frac{1}{2}*i - \frac{1}{2}*j - \frac{1}{2}*k)
(\frac{10}{4}*k + \frac{10}{4}*i*k + \frac{10}{4}*j*k + \frac{10}{4}*k*k) - (\frac{10}{4}*k*i + \frac{10}{4}*i*k*i + \frac{10}{4}*j*k*i + \frac{10}{4}*k*k*i) - (\frac{10}{4}*k*j + \frac{10}{4}*i*k*j + \frac{10}{4}*j*k*j + \frac{10}{4}*k*k*j) - (\frac{10}{4}*k*k + \frac{10}{4}*i*k*k + \frac{10}{4}*j*k*k + \frac{10}{4}*k*k*k)
「i*i = -1, j*j = -1, k*k = -1」「i*j = k, j*k = i, k*i = j」「j*i = -k, k*j = -i, i*k = -j」を使って整理。
(\frac{10}{4}*k - \frac{10}{4}*j + \frac{10}{4}*i - \frac{10}{4}) - (\frac{10}{4}*j + \frac{10}{4}*k - \frac{10}{4} - \frac{10}{4}*i) - (-\frac{10}{4}*i + \frac{10}{4} + \frac{10}{4}*k - \frac{10}{4}*j) - (-\frac{10}{4} - \frac{10}{4}*i - \frac{10}{4}*j - \frac{10}{4}*k)
10*i
ん。上手くいった。てか、そろそろ寝る時間なんだけど。コーディングしてないんだけど。結構「感じ」はつかめたから、もう親指の位置とかどうでもいいんだけど。てことで終了。