ある平面に対象な回転を求めるにはどうすればいいの?
スケールによる反転ではなく、回転だけを逆向きにしたいの。
回転軸とその軸周りの回転角度を反射させれば良いわ。
ある面を境に反転したようなクォータニオンを求める方法の解説記事です。
次のように、面に対象な回転(クォータニオン)を求めることを目標とします。
これは、平面を定義する法線ベクトルから高速に計算できます。
本記事では、このように指定された面を境に反射したようなクォータニオンを求める方法を使用例とともに解説していきます。
- Unity 6000.0.32f1
完成系のコード
まず初めに、指定された面に反射したようなクォータニオンを算出するコードの実装例を示します。
以下はQuaternionの拡張メソッドとして実装した例です。
上記をQuaternionExtensions.csなどという名前でUnityプロジェクトに保存すると、Quaternionの拡張メソッドとしてQuaternion.GetReflected、Quaternion.Reflectメソッドが使用可能になります。
使用例(必要ならば)
前述の拡張メソッドの使用例です。
指定されたオブジェクトの回転(rotation)を入力として、反射したクォータニオンを計算し、別オブジェクトの回転に指定しています。
上記をUseExample.csという名前でUnityプロジェクトに保存しておきます。
これを適当なゲームオブジェクトに追加し、以下項目を設定すればリアルタイムに計算前と計算後の結果を確認することができるようになります。
- Source – 計算前のクォータニオンとして使用するオブジェクト
- Target – 計算後の結果を反映するオブジェクト
- Normal – 面を定義する法線ベクトル
実行結果
Sourceに指定したオブジェクトを回転させると、出力先のTargetオブジェクトがNormalで定義した面を対象に回転していることを確認できます。
反射したクォータニオンの計算方法
ここから先は、前述の処理でクォータニオンの反射が計算できる理由について解説していきます。
本章は計算式の証明がメインとなるため、不要な方は次の章まで読み飛ばしていただいて問題ありません。
本記事では、クォータニオンを用いた数式の説明および導出過程のみ示すものとし、クォータニオン自体の基礎的な解説は省略いたします。
計算式
計算元となる入力クォータニオンを\bm{q}、面を定義する法線ベクトルを\bm{n}とすると、\bm{q}を\bm{n}方向で反射させたクォータニオン\bm{q^{\prime}}は次式のようになります。
\bm{q^{\prime}} = \left( \begin{array}{c} 2 (\bm{v} \cdot \bm{n}) n_x - q_x \\ 2 (\bm{v} \cdot \bm{n}) n_y - q_y \\ 2 (\bm{v} \cdot \bm{n}) n_z - q_z \\ q_w \end{array} \right)
ただし、
\bm{q} = \left( \begin{array}{c} q_x \\ q_y \\ q_z \\ q_w \end{array} \right), \bm{n} = \left( \begin{array}{c} n_x \\ n_y \\ n_z \end{array} \right) , \bm{v} = \left( \begin{array}{c} q_x \\ q_y \\ q_z \end{array} \right)
\|\bm{n}\| = 1
計算式の導出過程
得られるクォータニオン\bm{q^{\prime}}は、\bm{q}に対して次の操作を行った結果となります。
- 回転軸を面を定義する法線ベクトル\bm{n}に対して反射させる
- 回転軸回りの回転角度の符号を反転させる
クォータニオン自体は、回転軸\bm{a}と回転角度\thetaから次式のように求められます。
\bm{q} = \left( \begin{array}{c} a_x \displaystyle\sin{\frac{\theta}{2}} \\ \\ a_y \displaystyle\sin{\frac{\theta}{2}}\\ \\ a_z \displaystyle\sin{ \frac{\theta}{2}} \\ \\ \displaystyle\cos{ \frac{\theta}{2}}\end{array} \right)
ただし、
\bm{a} = \left( \begin{array}{c} a_x \\ a_y \\ a_z \end{array} \right)
この時、反射した回転軸\bm{a^{\prime}}、符号反転した回転角度\theta^{\prime}は次式のようになります。
\bm{a^{\prime}} = \bm{a} - 2 (\bm{a} \cdot \bm{n}) \bm{n}
\theta^{\prime} = -\theta
反射ベクトルを求める際、法線ベクトルとの内積を2倍して引く理由は、1倍でちょうど面に投影したベクトルとなり、2倍すると面と反対方向に移動できるためです。
このような反射ベクトルは、Vector2.Reflect、Vector3.Reflectメソッドで計算することも可能です。
内部的には同様の計算で求めています。
クォータニオン\bm{q}を法線ベクトル\bm{n}方向で反射したクォータニオン\bm{q^{\prime}}を
\bm{q^{\prime}} = \left( \begin{array}{c} a_x^{\prime} \displaystyle\sin{\frac{\theta^{\prime}}{2}} \\ \\ a_y^{\prime} \displaystyle\sin{\frac{\theta^{\prime}}{2}}\\ \\ a_z^{\prime} \displaystyle\sin{ \frac{\theta^{\prime}}{2}} \\ \\ \displaystyle\cos{ \frac{\theta^{\prime}}{2}}\end{array} \right)
とすると、次式のように変形できます。
\bm{q^{\prime}} = \left( \begin{array}{c} \{a_x - 2(\bm{a} \cdot \bm{n}) n_x\} \displaystyle\sin{\frac{-\theta}{2}} \\ \\ \{a_y - 2(\bm{a} \cdot \bm{n}) n_y\} \displaystyle\sin{\frac{-\theta}{2}}\\ \\ \{a_z - 2(\bm{a} \cdot \bm{n}) n_z\} \displaystyle\sin{\frac{-\theta}{2}} \\ \\ \displaystyle\cos{ \frac{-\theta}{2}}\end{array} \right)
ここで、
\sin({-\theta}) = -\sin{\theta}, \cos({-\theta}) = \cos{\theta}
なる性質を用いると、次式のようになります。
\bm{q^{\prime}} = \left( \begin{array}{c} -\{a_x - 2(\bm{a} \cdot \bm{n}) n_x\} \displaystyle\sin{\frac{\theta}{2}} \\ \\ -\{a_y - 2(\bm{a} \cdot \bm{n}) n_y\} \displaystyle\sin{\frac{\theta}{2}}\\ \\ -\{a_z - 2(\bm{a} \cdot \bm{n}) n_z\} \displaystyle\sin{\frac{\theta}{2}} \\ \\ \displaystyle\cos{ \frac{\theta}{2}}\end{array} \right)
ここで、
q_x = a_x \displaystyle\sin{\frac{\theta}{2}}, q_y = a_y \displaystyle\sin{\frac{\theta}{2}}
q_z = a_z \displaystyle\sin{\frac{\theta}{2}}, q_w = \displaystyle\cos{\frac{\theta}{2}}
であるため、これらを用いて\bm{q^{\prime}}は次式のように表現できます。
\bm{q^{\prime}} = \left( \begin{array}{c} -q_x + 2(\bm{a} \cdot \bm{n}) n_x \displaystyle\sin{\frac{\theta}{2}} \\ \\ -q_y + 2(\bm{a} \cdot \bm{n}) n_y \displaystyle\sin{\frac{\theta}{2}} \\ \\ -q_z + 2(\bm{a} \cdot \bm{n}) n_z \displaystyle\sin{\frac{\theta}{2}} \\ \\ q_w \end{array} \right)
また、
\bm{v} = \left( \begin{array}{c} q_x \\ q_y \\ q_z \end{array} \right) = \left( \begin{array}{c} a_x \displaystyle\sin{\frac{\theta}{2}} \\\\ a_y \displaystyle\sin{\frac{\theta}{2}} \\\\ a_z \displaystyle\sin{\frac{\theta}{2}} \end{array} \right)
とすると、
(\bm{a} \cdot \bm{n}) \displaystyle\sin{\frac{\theta}{2}} = \bm{v} \cdot \bm{n}
と置き換えられるため、最終的な\bm{q^{\prime}}は次式のように変形できます。
\bm{q^{\prime}} = \left( \begin{array}{c} 2(\bm{v} \cdot \bm{n}) n_x - q_x \\ 2(\bm{v} \cdot \bm{n}) n_y - q_y \\ 2(\bm{v} \cdot \bm{n}) n_z - q_z \\ q_w \end{array} \right)
ノルムの変化
ここまで面で反射したクォータニオンを求める導出過程を解説しましたが、ノルムがどう変化するか気になる方がいるかもしれません。
そこで、計算前のクォータニオン\bm{q}と計算後のクォータニオン\bm{q^{\prime}}のノルムの関係についても触れておきます。
クォータニオン\bm{q}、\bm{q^{\prime}}のノルムをそれぞれ\|\bm{q}\|、\|\bm{q^{\prime}}\|とすると、次式のように各要素の総和の平方根として表されます。
\|\bm{q}\| = \sqrt{q_x^2 + q_y^2 + q_z^2 + q_w^2}
\|\bm{q^{\prime}}\| = \sqrt{q_x^{\prime2} + q_y^{\prime2} + q_z^{\prime2} + q_w^{\prime2}}
ここで、
\bm{q^{\prime}} = \left( \begin{array}{c} q_x^{\prime} \\ q_y^{\prime} \\ q_z^{\prime} \\ q_w^{\prime} \end{array} \right) = \left( \begin{array}{c} 2(\bm{v} \cdot \bm{n}) n_x - q_x \\ 2(\bm{v} \cdot \bm{n}) n_y - q_y \\ 2(\bm{v} \cdot \bm{n}) n_z - q_z \\ q_w \end{array} \right)
であるため、ノルム\|\bm{q^{\prime}}\|は次式のように変形できます。
\begin{aligned} \|\bm{q^{\prime}}\| &= \sqrt{ (2(\bm{v} \cdot \bm{n}) n_x - q_x)^2 + (2(\bm{v} \cdot \bm{n}) n_y - q_y)^2 + (2(\bm{v} \cdot \bm{n}) n_z - q_z)^2 + q_w^2 } \\ &= \sqrt{4 (\bm{v} \cdot \bm{n})^2 ( n_x^2 + n_y^2 + n_x^z ) - 4 (\bm{v} \cdot \bm{n})(n_x q_x + n_y q_y + n_z q_z) + q_x^2 + q_y^2 + q_z^2 + q_w^2} \\ \end{aligned}
ここで法線ベクトル\bm{n}の長さは1としているため、次式の関係が成り立ちます。
\|\bm{n}\|^2 = n_x^2 + n_y^2 + n_z^2 = 1
また、内積\bm{v} \cdot \bm{n}は
\bm{v} \cdot \bm{n} = q_x n_x + q_y n_y + q_z n_z
であることから、最終的に計算後のノルム\|\bm{q^{\prime}}\|は次式のようになります。
\begin{aligned} \|\bm{q^{\prime}}\| &= \sqrt{ 4 (\bm{v} \cdot \bm{n})^2 - 4 (\bm{v} \cdot \bm{n})(\bm{v} \cdot \bm{n}) + q_x^2 + q_y^2 + q_z^2 + q_w^2 } \\ &= \sqrt{ 4 (\bm{v} \cdot \bm{n})^2 - 4 (\bm{v} \cdot \bm{n})^2 + q_x^2 + q_y^2 + q_z^2 + q_w^2 } \\ &= \sqrt{q_x^2 + q_y^2 + q_z^2 + q_w^2} \\ &= \|\bm{q}\| \end{aligned}
これは、ノルム\|\bm{q}\|と\|\bm{q^{\prime}}\|は等しい、すなわちクォータニオンの反射計算でノルムが変化しないことを意味します。
ただし、計算誤差によってノルムに誤差が蓄積する可能性はあるため、それが不都合になる場合はノルムが1になるように正規化しておくとよいでしょう。
拡張メソッドにするメリット
実装例では、Quaternionの拡張メソッドとして反射ベクトルを求めるメソッドを定義しましたが、拡張メソッド化することで、Quaternionのメソッドであるかのように使用することができます。
本来はQuaternion.GetReflectedやQuaternion.Reflectは存在しないメソッドですが、これらを元のQuaternionのソースを変更することなくメソッドを追加できます。
さいごに
指定した面に反射するようなクォータニオンは、ベクトルの反射を活用して求められます。
反射ベクトルは内積を用いて計算できるため、比較的軽い計算で済むといったメリットがあります。
拡張メソッドとして使いまわせるようにしておくと、様々な箇所で手軽に使えるようになって便利でしょう。