回転行列からオイラー角を求める

カメラをWrangleで計算したベクトルで動かそうとしたらオイラー角の入力しか見当たらなかったので、しかたがなく行列を作ってオイラー角を求めることにしました。

この軸のベクトルに合わせるためのオイラー角を考えていきます。行列の計算は頭の体操だと思ってたまには手動でやってみるのもいいものですね。

回転行列を掛け合わせた行列をつくる

XYZ軸すべての回転行列を掛けた結果の行列をつくります。Z回転、X回転、Y回転の順で掛けていきます。この順番をしっかり決めておくことが大事で、順番が変わると結果も変わってしまいます。

// Z軸でz(ラジアン)回転させる行列
Rz = {cos(z),  sin(z), 0,
      -sin(z), cos(z), 0,
      0,       0,      1}

この行列にX軸の回転行列を掛けます。

// X軸方向にx(ラジアン)回転させる行列
Rx = {1, 0,       0,
      0, cos(x),  sin(x),
      0, -sin(x), cos(x)}

結果こうなります

RzRx = {cos(z),  sin(z)*cos(x), sin(z)*sin(x),
        -sin(z), cos(z)*cos(x), cos(z)*sin(x),
        0,       -sin(x),       cos(x)}

さらにY軸方向の回転行列を掛けます。

// Y軸方向にy(ラジアン)回転させる行列
Ry = {cos(y), 0, -sin(y),
      0,      1, 0,
      sin(y), 0, cos(y)}

結果になります。だいぶ複雑な感じになりました。

RzRxRy = {cos(z)*cos(y)+sin(z)*sin(x)*sin(y),  sin(z)*cos(x), -cos(z)sin(y)+sin(z)*sin(x)*cos(y),
          -sin(z)*cos(y)+cos(z)*sin(x)*sin(y), cos(z)*cos(x), sin(z)*sin(y)+cos(z)*sin(x)*cos(y),
          cos(x)*sin(y),                       -sin(x),       cos(x)*cos(y)}

角度x,y,zを当てはめれば行列(各軸のベクトル)ができるわけです。

ベクトルから行列をつくる

次にベクトルから行列をつくります。Houdiniでは基本的にZ軸は@N、Y軸は@upなので

// @upと@Nの外積からx軸のベクトルをつくる
vector L = cross(@up, @N)

RzRxRy = {L.x,  L.y,  L.z,
          up.x, up.y, up.z,
          N.x,  N.y,  N.z}

となります。このベクトルで構成した行列とZXY回転をした行列が同じ結果になるための角度x, y, zは何かを考えます。

L.x = cos(z)*cos(y)+sin(z)*sin(x)*sin(y)
L.y = sin(z)*cos(x)
L.z = -cos(z)sin(y)+sin(z)*sin(x)*cos(y)

up.x = -sin(z)*cos(y)+cos(z)*sin(x)*sin(y)
up.y = cos(z)*cos(x)
up.z = sin(z)*sin(y)+cos(z)*sin(x)*cos(y)

N.x = cos(x)*sin(y)
N.y = -sin(x)
N.z = cos(x)*cos(y)

中身を比較しながら方程式を解く要領で片方の変数を求めていきます。

角度x

N.yが-sin(x)に当たるので
N.y = -sin(x)
という等式が成り立つ
x = -asin(N.y)
となります。

角度z

zはL.yをup.yで割ることで求めることができます。
L.y / up.y = sin(z)*cos(x) / cos(z)*cos(x) = sin(z)/cos(z) = tan(z)
つまり
z = atan(L.y, up.y)
となります。

角度y

yも同じ要領でNxをNzで割ることで求まります。
Nx / Nz = cos(x)*sin(y) / cos(x)*cos(y) = sin(y) / cos(y) = tan(y)
y = atan(Nx, Nz)

モデルを回転して確認

// 行列を構成する3つの軸ベクトルをつくる
@N = normalize(set(1, 0, 1));
@up = normalize(set(-1, 1, 0));
@left = cross(@up, @N);
@up = cross(@N, @left); // 90度で交わるようにupベクトルを補正する

// オイラー角を計算する
v@rot.x = degrees(-asin(@N.y));
v@rot.y = degrees(atan2(@N.x, @N.z));
v@rot.z = degrees(atan2(v@left.y, @up.y));

printf('@rot:' + sprintf('%g', @rot) + '\n');
//@rot:{-0,45,35.2644}

適当にZ軸ベクトル(1, 0, 1)、Y軸ベクトル(-1, 1, 0)をもとに作った軸ベクトルから計算すると、角度はX軸が0度、Y軸が45度、Z軸が35.26度になりました。

Transform SOPに角度を入力したところ一致しました。

Transform OrderはRzRxRyの順にすることを忘れずに。

カメラに角度を割り当てる

同じようにWrangleでv@rotという角度のアトリビュートをつくり、

これをカメラのRotateにリンクさせます。

カメラの正面の向きがZマイナス方向なので、@Nはカメラに値を渡すときにー1を掛けて反転させておきましょう。

Translateにも座標をリンクさせればカメラが移動してくれます。

コメントを残す