はじめに
JavaScript
で3Dベクトルの回転を扱う機会が現れたので、使い慣れたUnityEngine.Quaternion
をフルスクラッチで再現しました。
レポジトリ:konbraphat51/UnityQuaternion_js
これまで英語圏含めフルスクラッチ再現のコード例が公開されていませんでしたので、世界のフルスクラッチャーにとって有益なソースコードになるかと思われます。
ここで記すのは、この作業によって得られたクォータニオンの 感覚的理解 です。
あくまでも「感覚」なので数学的厳密性が欠けた記述、ないし誤った記述が多発するものかとおもわれますが、ご容赦ください。
TL; DR: プログラマーが認識すればいいこと
論理をいっぱい隠蔽して過剰書きすると
-
クォータニオンは
- 一つの回転軸と
- その回転軸で何度回るか
の情報をもつ
-
クォータニオン同士の掛け算で回転を合成できる
- 左右どちらが先かで考え方がことなる:[Unity]Quaternionの回転、左が先か?右が先か?
-
ベクトルを回転させるときは$Q\vec v Q^{-1}$
- (ライブラリによっては
*
がオーバーライドでquaternion * vector
で普通に回転できる)
- (ライブラリによっては
クオータニオンとは?
「四元数」自体の説明
数学的な定義は2通り存在するようですが(後述)、いずれも抽象的に・プログラミング的に言えば、 特殊な演算が設定されているw, x, y, z
の4フィールドを持つクラス といったところでしょうか。
要するに 4次元の値 です。
虚数単位的な基底による定義
こちらがよく見かける方だと思います
\boldsymbol Q(x,y,z,w) = xi + yj + zk + w
と表されます。
$i, j, k$が虚数単位的な 基底 です。
「基底」とは?
簡単に言うとグラフの軸のことです。
直交座標ではx軸、y軸があったように、1軸、i軸などを考えます。
ちゃんとした説明:線形代数における基底ってなに?
お互いの基底に対し、このような掛け算が 定義 されています。(→導かれたのではなく、「定義」されているものなので、そのまま受け止める必要があります)
Wikipedia より引用
この定義に従って、クォータニオン同士を掛け算すると、中学数学と同じ数式展開でこの結果が得られます
CS184: Using Quaternions to Represent Rotationより引用
二つ目、三つ目の虚数単位ってなに?
高校で習う虚数単位は
\sqrt -1 = i
という定義で、一つ目の虚数単位$i$と出会いました。
しかし、二つ目$j$と三つ目$k$の虚数単位って一体何者?
両方
j^2 = k ^2 = -1
なら、まあどっちかが$-\sqrt -1$になるとしても、さすがにこれ以外ないんじゃないの?
僕が辿り着いた結論は 複素数の延長で考えるのはやめよう でした。(数学出来る人はもっといい理解があるでしょうけど、、、)
というのは、$i,j,k$の定義は「虚数単位」ではなく「基底」として与えられています。
そもそものモチベーションが違うのです。
さらには、四元数を導入すると、実は$\sqrt -1$は 無限に 存在することが証明できます。 (Wikipedia「-1の平方根」を参照)
すなわち、四元数は高校数学の虚数の理解では達しえない論理ワールドが展開されています。
僕は「四元数は複素数の拡張」ではなく、「複素数は四元数の特殊化」であると考え方を変えました。
要するに、
- 四元数というのを、
- 次元を削減($w+xi+yj+zk$⇒$x+yi$)することによって、
- なんと$\sqrt -1 = i$ に限定され、
- 高校数学の「複素数」で習った事項が実現される
という風に。
簡単に言うと、複素数で習った性質を四元数に求めるのはやめました。
まあ歴史的経緯では複素数→四元数らしいんですけどね。
「スカラー+ベクトル」による定義
別の定義もあります。
Q = w + x
\left(
\begin{array}{c}
1 \\
0 \\
0
\end{array}
\right)
+ y
\left(
\begin{array}{c}
0 \\
1 \\
0
\end{array}
\right)
+ z
\left(
\begin{array}{c}
0 \\
0 \\
1
\end{array}
\right)
という、スカラーとベクトルのキメラさんにする定義もあります。
掛け算の方法などはこちらが詳しいです:
クォータニオン計算便利ノート
クォータニオン同士の掛け算の結果は先述の定義と同じになります。
となると、どちらの定義を採用しても結果的な$w, x, y, z$の値は一致するので 、お好みで大丈夫です。
「回転させ器」としての理解
みなさんがこちらにいらした動機は、おそらく「回転させ器」としての機能目的であると思います。
単位クォータニオンは、掛け算 によってベクトルやクォータニオンを回転させる 性質を持ちます。
単位クォータニオン
単位ベクトルと同じノリで、
w^2 + x^2 + y^2 + z^2 = 1
になるクォータニオンです。
4パラメーターの意味づけ
$w,x,y,z$の4パラメーターありますが、実はそれぞれに意味付けをすることができます。
回転軸(原点を通る)を設定して、その軸の周りを 回転する角度 として意味づけをすることができます。
その回転軸のベクトルを$\vec n$、回転する角度を$\theta$とすると、
\left(
\begin{array}{c}
w \\
x \\
y \\
z
\end{array}
\right)
=
\left(
\begin{array}{c}
cos \frac{\theta}{2}\\
\vec n_x sin \frac{\theta}{2} \\
\vec n_y sin \frac{\theta}{2} \\
\vec n_z sin \frac{\theta}{2}
\end{array}
\right)
と表現できます。
$\theta$の回転方向
- 右手座標系なら$\vec n$について右ネジ、
- 左手座標系なら$\vec n$について左ネジ
の方向です
要するに、$x,y,z$は 回転軸ベクトル を表し、$w$は回転した角度を表します。
クォータニオンを回転させる(回転の合成)
単位クォータニオンは一つの回転を表しますが、単位クォータニオン同士の掛け算で、両者の回転を合成できます
単純な掛け算で合成できるわけですが、 順番 に罠があります。
順番の考え方については、この記事がとても良いです: [Unity]Quaternionの回転、左が先か?右が先か?
すなわち、
「左から先」と考える場合
クォータニオンは「回転軸と回転角」の表現でした。
その回転軸についてですが、
Q_1 Q_2
について、まずは$Q_1$については我々の想像通り普通に回転します。
しかし、同時に$Q_2$の回転軸も回転します
そこに注意しながら、つまり回転されてしまう回転軸を熟慮しつつ計算を設計しなければいけません。
「右から先」考える場合
Q_1 Q_2
について、まずは$Q_2$(右側)については我々の想像通り普通に回転します。
次の$Q_1$の回転軸は 回転されず、表現通りそのまま として、回転をします。
こちらは回転後の回転軸を考慮しなくて良いので、「クォータニオンの掛け算は右が先」と考える人の方が多いです。僕も。
どちらもあくまで「我々人間がどう解釈するか」であり、最終的な結果は同じなので不思議ですよね。
ちょっと考えれば必然かもしれませんが、ちょっと考えてないのでこの点の説明は控えます。
ベクトルを回転させる
ベクトルの回転こそがクォータニオンの本懐と言えるでしょう。
まずは結論から。
もしベクトル$\vec v$をクォータニオン$Q$で回転させたいならば、
\vec v_{rotated} = Q \vec v Q^{-1}
です。
ただし、計算上はベクトルをクォータニオンに直して計算してください。
すなわち、
V = 0 + \vec v_x i + \vec v_y j + \vec v_z k
というクォータニオンにして、クォータニオン同士の掛け算に持ち込んでください。
逆に、結果として得られる$\vec v_{rotated}$も、同じノリで$x,y,z$成分を
抽出して並べると、欲しかったベクトルになります。
逆クォータニオン$Q^{-1}$
逆クォータニオンは元々のクォータニオンの 逆回転 を表現します。
その計算方法は、単位クォータニオン の場合、
\begin{eqnarray}
Q = w+xi+yj+zk \\
\Rightarrow Q^{-1} = w-xi-yj-zk
\end{eqnarray}
複素数との連想でいえば、「共役」ですね。
なぜ逆クォータニオンもかける?
イメージ的には、クォータニオンが回転を表すのであれば、シンプルに掛け算だけでベクトルの回転が完遂できてほしいところですよね。
逆クォータニオンが余計に見えちゃう。
英語圏含めかなり調べたのですが、みんな天下り的に$QVQ^{-1}$を計算して、計算結果が偶然回転後のベクトルと一致したね、と述べるだけであまり嬉しくなりませんでした。
まずクォータニオン$V = 0 + \vec v_x i + \vec v_y j + \vec v_z k$の意味を考えましょう。
先述の「意味づけ」のもとで計算すると、クォータニオン$V$は、
- 回転軸が$\vec v$
- 回転角が$cos \frac{\theta}{2}=0 \Rightarrow \theta = 180^\circ$
を表現しています。
いわば、$\vec v$を軸とした対称移動 (反転)ですね。
$QVQ^{-1}$の計算は、けっこう面白いテクニックのもとで考えられています。
まず、算出したい目的のクォータニオン
V_{rotated} = 0 + \vec v_{x, rotated} i + \vec v_{y, rotated} j + \vec v_{z, rotated} k
すなわち、$\vec v_{rotated}$軸の対象移動を表すクォータニオンを出したい、というお気持ちがあります。
つまり、頑張って「クォータニオンの対称移動の軸を 回転させる($V \rightarrow V_{rotated}$)」ことを考えねばならないのです。
回りくどくない?
直接クォータニオンとベクトル合成できないの?という疑問を抱くかもしれません。
しかし、我々が持っている武器は「クォータニオン同士の掛け算」であり、「クォータニオンvsベクトルの掛け算」はまだやっていません。
というよりも、今まさにその武器を作ろうとしているところです。
「右が先」の掛け算で考えてみましょう
-
まずは$Q^{-1}$
物体が$-\theta$回転します。 -
次に$V$
$-\theta$回転された世界で、$\vec v$軸で対称移動します。
$-\theta$回転された世界で、$\vec v$は回転されずそのままなので(先述「右が先」の説明)、相対的に$\vec v$が$\theta$回転している 状態での対称移動になっています。 -
最後に$Q$
$-\theta$の回転していたところを$\theta$回転することで、$Q^{-1}$のズレを戻します
こういうのは長々と説明し始めると余計ややこしなるので、この箇条書きと睨めっこしていただければと思います。
「まず共役を掛け算してから対称移動させ、元々のものを掛け算して元に戻す」は、イメージ的には高校数学でやった複素数平面の線対称移動に似ています:【標準】複素数平面と直線に対する対称移動
「姿勢」としての理解
クォータニオンは「姿勢」を表すことができます。
ここにおける「姿勢」とは、
- どんな角度で
- どこを向いて
いるのかを表します。
クォータニオンは「 回転させ器 」でしたね。
物体のデフォルトの位置にある原子(ピクセル)の一つ一つ(の位置ベクトル)を、全てこのクォータニオンを使って回転させると、最終的に物体全体がクォータニオンに従って回転することになりますね。
このように、「 デフォルト状態からどう回転したか 」でもって「姿勢」を表します。
姿勢を回転させる
物体の外から見てもしクォータニオンQ
で姿勢クォータニオンP
を回転させたい場合、
P = Q * P;
と計算するのがほとんどです。
「右が先」の考え方でいくと、P
回転して、そのままQ
回転(Q
の回転軸そのまま)ですからね。
物体の主観始視点から見てもしクォータニオンQ
で姿勢クォータニオンP
を回転させたい場合、
P = P * Q;
今回は都合よく「左が先」の考え方でいくと、最初のP
回転でQ
の回転軸も同時に回転され、物体の主観視点にあわさってくれますね。
実装上注意しないといけないこと
座標系の方向、ネジの方向
オイラー角から変換する場合に重要になります。
座標系には「右手座標系」と「左手座標系」があります:右手系と左手系が違うと何が問題か
高校物理で「フレミングの左手」をやった人なら懐かしいかもしれません。
Wikipediaより引用
フレームワークによってまちまちです。
Freya the strayさんのツイートより引用
計算過程上の理由で(説明を放棄)、どちらの座標系を用いているのかで、オイラー角⇔クォータニオン の数式が少し違います。
変換はけっこう簡単です: 左手座標系と右手座標系の壁を超えよう。
また、ネジの方向の重要です。
回転軸を表すベクトル方向について、「回転」が右ねじ方向なのか左ねじ方向なのかで、「角度」の正負が変わります。
基本的に、右手座標系は右ねじ、左手座標系は左ねじなのですが、極たまに逆張りがいます。(例: Web APIは左手座標系の右ねじ)
(オイラー角と共有する場合) 回転の順番
これも、オイラー角と連携する場合に重要です。
オイラー角を見るとき、「どの順番で回転するか」を念頭に入れなければいけません。
こちらに右手座標系における全パターンの計算結果が掲載されています:回転行列、クォータニオン(四元数)、オイラー角の相互変換
最後に
回転、しましょう
いいねくださると泣きながら喜びます><