ユースケース
例えば関節をいくつかもつ3Dモデルを作成し、それに動作をつけることを考えます。
この際、動作のつけかたは2種類考えられます。
a. 絶対的な角度を利用する場合
- 動作を定義する際、3Dモデルの関節角度をそのまま記録する。
- 再生時、記録した関節角度をそのまま各関節に適用する。
b. 相対的な角度を利用する場合
- 動作を定義する際、3Dモデルの初期関節角度からの角度差を記録する。
- 再生時、**「初期関節角度 + 初期関節角度からの角度差」**を各関節に適用する。
**a.の方法は、3Dモデルの初期姿勢が変わった際に全ての動作を作り直す必要があります。
対してb.**の方法は、そうした変更による影響をまったく受けません。
**b.**の方法を実現するために、実際にどのようにして角度差を求めるかを、次節から解説します。
アジェンダ & ソースコード
// 1. 初期Quaternionを保存する
var home_quaternion = ANY_OBJECT.quaternion.clone();
// 2. 現在のQuaternionを取得する
var current_quaternion = ANY_OBJECT.quaternion;
// 3. 差分Quaternionを導出する
var diff_quaternion = home_quaternion.inverse().multiply(current_quaternion);
// 4. 角度情報に落とし込む
var axis = ANY_NORMALIZED_VECTOR3;
var half_thetas = [
(axis.x)? Math.atan2(diff_quaternion.x / axis.x, diff_quaternion.w) : null,
(axis.y)? Math.atan2(diff_quaternion.y / axis.y, diff_quaternion.w) : null,
(axis.z)? Math.atan2(diff_quaternion.z / axis.z, diff_quaternion.w) : null
];
var half_theta = half_thetas.find(function(half_theta){ return half_theta; });
var theta = (function(half_theta)
{
// 1/2回転の2倍の絶対値がπより大きい場合、[0, ±π]の領域に写す。
if (Math.abs(half_theta * 2) > Math.PI)
{
var theta = 2 * Math.PI - Math.abs(half_theta * 2);
// 1/2回転の2倍が+π以上ならば、回転は負,そうでなければ回転は正。
// (先のif文で前提条件が緩くなっているため、1/2回転の符号だけで回転の符号が求まる。)
return (half_theta > 0)? -thata : theta;
}
else
{
return half_theta * 2;
}
})(half_theta);
1. 初期Quaternionを保存する
var home_quaternion = ANY_OBJECT.quaternion.clone();
とすることで、ANY_OBJECTのQuaternionを保存できます。
参照を保存するのではなく、.clone()
メソッドを使用して新規にオブジェクトを作成することがポイントです。
2. 現在のQuaternionを取得する
var current_quaternion = ANY_OBJECT.quaternion;
とすることで、ANY_OBJECTのQuaternionを取得できます。
3. 差分Quaternionを導出する
var diff_quaternion = home_quaternion.inverse().multiply(current_quaternion);
とすることで、初期Quaternionから現在のQuaternionへ変換するためのQuaternion(= 差分Quaternion)を導出できます。
補足) 簡単な証明
\begin{eqnarray}
c :&=& {\rm current\_quaternion}\\
h :&=& {\rm home\_quaternion}\\
d :&=& {\rm diff\_quaternion}
\end{eqnarray}
と置いたとき、$c=dhd^\ast$であり、これにより$\boldsymbol{\theta_c} = \boldsymbol{\theta_d} + \boldsymbol{\theta_h}$なる角度部分での演算が行われます。
よって、$\boldsymbol{\theta_d} = \boldsymbol{\theta_c} + (-\boldsymbol{\theta_h})$であり、式の対称性より$d=ch^{-1}c^\ast$と求まります。
4. 角度情報に落とし込む
\begin{eqnarray}
quaternion :&=& \cos \frac{\theta}{2} + (ai + bj + ck) \sin \frac{\theta}{2}\\
:&=& w + xi + yj + zk
\end{eqnarray}
であるので、回転軸となる単位ベクトル$\boldsymbol{e_{axis}} := (a, b, c)$が事前にわかっていれば、$\theta$は一意に求まります。
3Dモデルを動かす際はローカル座標の回転のみを扱うことが多いため、その場合$\boldsymbol{e_{axis}}$は既知です。
(参考 -> http://qiita.com/Guvalif/items/ab0c847390adbb8e4a06)
var axis = ANY_NORMALIZED_VECTOR3;
// 回転軸の内0となる軸を除外する。
var half_thetas = [
(axis.x)? Math.atan2(diff_quaternion.x / axis.x, diff_quaternion.w) : null,
(axis.y)? Math.atan2(diff_quaternion.y / axis.y, diff_quaternion.w) : null,
(axis.z)? Math.atan2(diff_quaternion.z / axis.z, diff_quaternion.w) : null
];
var half_theta = half_thetas.find(function(half_theta){ return half_theta; });
とすることで、1/2回転をラジアンで取得することができます。
最後にこれを2倍し、$\pm \pi$の範囲におさまるように変形を行うことで、角度情報に落とし込むことができます。
var theta = (function(half_theta)
{
// 1/2回転の2倍の絶対値がπより大きい場合、[0, ±π]の領域に写す。
if (Math.abs(half_theta * 2) > Math.PI)
{
var theta = 2 * Math.PI - Math.abs(half_theta * 2);
// 1/2回転の2倍が+π以上ならば、回転は負,そうでなければ回転は正。
// (先のif文で前提条件が緩くなっているため、1/2回転の符号だけで回転の符号が求まる。)
return (half_theta > 0)? -thata : theta;
}
else
{
return half_theta * 2;
}
})(half_theta);
5. まとめ
以上の処理を、自分はロボットシミュレータの実装に利用しています。併せて参考になれば幸いです。
→ http://plen.jp/playground/motion-editor/