はじめに
Rajawaliフレームワークを使っていて、3Dモデルの拡大 -> 回転 -> 移動の操作を同時に適用したときに意図した順番にならずにはまったときのメモです。
Rajawaliとは?
Android 用 OpenGLフレームワークです。
2016年11月現在0.9., 1.0., 1.1.系がリリース済みで、2.系を開発中みたいです。
0.9系は以下の記事が詳しいです。
http://dev.classmethod.jp/smartphone/android/android-rajawali-tutorials-01/
0.9と1.0以降は互換性はありません。
以下の記事は1.1についてのものです。
問題
Rajawaliでは移動・回転・拡縮可能なオブジェクトを表現するATransformable3Dという抽象クラスがあります。
(上記クラスを継承したObject3Dというクラスがあり、基本的にこのクラスを操作して3Dモデルの生成・描画を行う)
ATransformable3Dには
- setScale
- setOrientation/ setRotation
- setPosition
のメソッドがあるのですが、このメソッドをどの順番で呼び出しても回転 -> 拡縮 -> 移動の順番で適用されてしまいます。
※この順番が狂うとなぜ困るのかについては、以下の記事などを参照してください。
http://www.asahi-net.or.jp/~va5n-okmt/pov/tutorial/tra_rota_scl.html
ATransformable3Dクラスは内部的に
- mPosition ... 平行移動用の行列を格納するメンバ(setPositionで操作)
- mScale ... 拡縮用の行列を格納するメンバ(setScaleで操作)
- mOrientation ... 回転用の行列を格納するメンバ(setOrientation/ setRotationで操作)
- mMMatrix ... 上記3つを合成したモデル行列を格納するメンバ
の変数を持っているのですが、モデル行列の計算を行うcalculateModelMatrixメソッドの実装を覗いてみると
/**
* Calculates the model matrix for this {@link ATransformable3D} object.
*
* @param parentMatrix {@link Matrix4} The parent matrix, if any, to apply to this object.
*/
public void calculateModelMatrix(final Matrix4 parentMatrix) {
mMMatrix.setAll(mPosition, mScale, mOrientation);
if (parentMatrix != null) {
mMMatrix.leftMultiply(parentMatrix);
}
}
さらにcalculateModelMatrixから呼び出されるsetAllの内容は
/**
* Sets the values of this {@link Matrix4} to the values corresponding to a Translation x Scale x Rotation.
* This is useful for composing a model matrix as efficiently as possible, eliminating any extraneous calculations.
*
* @param position {@link Vector3} representing the translation.
* @param scale {@link Vector3} representing the scaling.
* @param rotation {@link Quaternion} representing the rotation.
* @return A reference to this {@link Matrix4} to facilitate chaining.
*/
public Matrix4 setAll(final Vector3 position, final Vector3 scale, final Quaternion rotation) {
// Precompute these factors for speed
final double x2 = rotation.x * rotation.x;
final double y2 = rotation.y * rotation.y;
final double z2 = rotation.z * rotation.z;
final double xy = rotation.x * rotation.y;
final double xz = rotation.x * rotation.z;
final double yz = rotation.y * rotation.z;
final double wx = rotation.w * rotation.x;
final double wy = rotation.w * rotation.y;
final double wz = rotation.w * rotation.z;
// Column 0
m[M00] = scale.x * (1.0 - 2.0 * (y2 + z2));
m[M10] = 2.0 * scale.y * (xy - wz);
m[M20] = 2.0 * scale.z * (xz + wy);
m[M30] = 0;
// Column 1
m[M01] = 2.0 * scale.x * (xy + wz);
m[M11] = scale.y * (1.0 - 2.0 * (x2 + z2));
m[M21] = 2.0 * scale.z * (yz - wx);
m[M31] = 0;
// Column 2
m[M02] = 2.0 * scale.x * (xz - wy);
m[M12] = 2.0 * scale.y * (yz + wx);
m[M22] = scale.z * (1.0 - 2.0 * (x2 + y2));
m[M32] = 0;
// Column 3
m[M03] = position.x;
m[M13] = position.y;
m[M23] = position.z;
m[M33] = 1.0;
return this;
}
のようになっています。
また、mMMatrixを直接操作するメソッドは公開されていません。
対応
// objを回転 -> 移動 -> 拡縮の順番で操作
Object3D obj = (略)...;
obj.setScale(1.0f, 2.0f, 1.5f); // 拡縮
Object3D parent= new Object3D(); // 空のオブジェクトparentを作成
Quaternion q = (略)...;
parent.setOrientation(q);
parent.setPosition(5.0f, 6.0f, 7.0f);
parent.addChild(obj);
のようにObject3Dの親子関係をつくり、先に適用したい操作を親のオブジェクトに対して行うことで解決しました。