3D オブジェクトのアニメーション ( Android OpenGL フレームワーク “Rajawali” と戯れる #08 )

2013.03.19

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

Android OpenGL フレームワーク "Rajawali" と戯れるシリーズ
第 08 回目は、3D オブジェクトのアニメーションについて解説します。

Rajawali がサポートするアニメーション可能な 3D フォーマット

Rajawali では下記フォーマットをサポートしています。

  • MD2 (*.md2)
  • MD5 (*.md5mesh, *.md5anim) (*.md5camera は非サポート )

MD2 は QuakeII という FPS(First Person shooter) ゲームで使用されている頂点アニメーション情報を持った 3D オブジェクトフォーマットで、MD5 は DOOM3 という同じく FPS ゲームで使用されているボーンやウェイトなどの情報を保持した 3D オブジェクトフォーマットです。いずれも id Software 社によって開発されています。

MD5 の読み込み

MD5 は Blender やその他の 3D ツールで書き出せるらしいのですが、手持ちの LightWave 3D では上手く書き出せなかったので、DOOM3 を買ってインストールしてゲームのデータを拝借してみました。

出力結果

動きますね。

3D オブジェクトおよびアニメーション情報のパースとデータ取得

RajawaliRenderer 内で以下のように記述します。

//第一引数の文字列は任意のアニメーション名、R.raw.idle は idle.md5anim の ID
MD5AnimParser animParser = new MD5AnimParser("test", this, R.raw.idle);
animParser.parse();

//R.raw.skeleton は skeleton.md5mesh の ID
MD5MeshParser meshParser = new MD5MeshParser(this, R.raw.skeleton);
meshParser.parse();

BoneAnimationSequence md5anim = (BoneAnimationSequence) animParser.getParsedAnimationSequence();
BoneAnimationObject3D md5mesh = (BoneAnimationObject3D) meshParser.getParsedAnimationObject();

md5mesh.setAnimationSequence(md5anim);

md5mesh.play();
addChild(md5mesh);

シンプルですね。
ちなみに、テクスチャは .md5mesh 内に含まれている情報を参照する作りになっています。

MD2 の読み込み

RajawaliRenderer 内で以下のように記述します。MD5 よりシンプルです。

MD2Parser parser = new MD2Parser(mContext.getResources(), mTextureManager, R.raw.r_miku_md2);
parser.parse();

VertexAnimationObject3D mObj = (VertexAnimationObject3D) parser.getParsedAnimationObject();

mObj.play();
addChild(mObj);

LightWave 3D で MD2 ファイル書き出し

MD5 を自前で書き出すことは失敗しましたが MD2 は書き出せたので、備忘録を兼ねて手順を残しておきます。

下準備

前回に引き続き、三次元CG@七葉にて保管されているズサ氏制作のはちゅねミク 3D オブジェクト+テクスチャ データ を元に作業を進めます。

「モデラー」でボーンを入れて…

「レイアウト」でボーンを動かし、キーフレームをセットします。

これらの作業で 1 点注意しなければならないのは座標系の違いです。
Android の座標系と LightWave 3D の座標系は異なるので、モデリング作業が終わった段階で X 方向に鏡面コピーした 3D オブジェクトを「レイアウト」に送っています。

使用プラグイン

アニメーション情報を保持した MD2 ファイルは「レイアウト」を使用して書きだしますが、デフォルトの機能では MD2 を書き出すことはできないので、有志による MD2 書出しプラグインを使用します。( 32bit 版でのみ動作します )

MD2 Import/Export Tools

MD2 Import/Export Tools の設定項目

「レイアウト」に送ったオブジェクトのアイテムプロパティから「変位」タブ内にある「変位プラグイン追加」を選択した後、「MRB::Export::Quake2」を選択します。

選択した後、ダイアログの下端に表示される「Edit」ボタンをクリックすると「Construction Panel」が表示されるので、ディレクトリ情報やテクスチャ情報、キーフレームのアニメーション情報を入力します。

設定が完了したら「レイアウト」の「ユーティリティ」タブ→「プラグイン」から「MRB::ExportControl」を選択してダイアログを表示します。

データグリッドの Save 項目にチェックを入れ、「Save Checked Models」をクリックすると、「プレビュー作成」のダイアログが表示されるので、必要に応じてフレームを調整したうえで「OK」をクリックします。

その後「Preview Playback Controls」ダイアログが表示されるので、一度再生させてプレビューを終了させた段階で MD2 ファイルが書き出されます。

出力結果

ムービー


ねんがんのねぎふりができたぞ!

ソース

MainActivity.java

package jp.classmethod.sample;

import java.util.ArrayList; import javax.microedition.khronos.opengles.GL10; import rajawali.BaseObject3D; import rajawali.RajawaliFragmentActivity; import rajawali.animation.mesh.BoneAnimationObject3D; import rajawali.animation.mesh.BoneAnimationSequence; import rajawali.animation.mesh.VertexAnimationObject3D; import rajawali.lights.DirectionalLight; import rajawali.parser.MD2Parser; import rajawali.parser.md5.MD5AnimParser; import rajawali.parser.md5.MD5MeshParser; import rajawali.renderer.RajawaliRenderer; import android.content.Context; import android.content.res.Resources; import android.os.Bundle;

public class MainActivity extends RajawaliFragmentActivity {

private RajawaliRenderer mRenderer;

@Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); mRenderer = new Lesson06_01Renderer(this); mRenderer.setSurfaceView(mSurfaceView); setRenderer(mRenderer); }

/** * RajawaliRenderer のサブクラス */ private class Lesson06_01Renderer extends RajawaliRenderer { /** パースされた 3D オブジェクトデータ */ private VertexAnimationObject3D mObj;

/** パースされた 3D オブジェクトデータのリスト */ private ArrayList mList = new ArrayList();

/** コンストラクタ */ public Lesson06_01Renderer(Context context) { super(context); setFrameRate(60); setBackgroundColor(0x999999); }

@Override /** シーン初期化 */ protected void initScene() {

mCamera.setX(6.5f); mCamera.setY(1.7f); mCamera.setZ(-5f); mCamera.setRotX(10); mCamera.setRotY(-55); mCamera.setRotZ(10);

Resources r = mContext.getResources();

DirectionalLight light = new DirectionalLight(); light.setPower(1.5f); light.setColor(0xff9900); light.setPosition(0f, 3f, -1f);

MD2Parser parser = new MD2Parser(r, mTextureManager, R.raw.r_miku_md2); parser.parse();

mObj = (VertexAnimationObject3D)parser.getParsedAnimationObject(); mObj.addLight(light);

int i, j, l = 6, m = 6; float startPos = -5f; float posX; float posZ; float offset = 2f; VertexAnimationObject3D tmp; posX = startPos; for(i = 0; i < l; i++) { posZ = startPos; for(j = 0; j < m; j++) { tmp = mObj.clone(); tmp.setPosition(posX, 0, posZ); mList.add(tmp); posZ += offset; } posX += offset; } for(i = 0; i < mList.size(); i++) { tmp = mList.get(i); addChild(tmp); tmp.play(true); } } @Override /** フレーム描画 */ public void onDrawFrame(GL10 glUnused) { super.onDrawFrame(glUnused); int i; for(i = 0; i < mList.size(); i++) { rotateObject(mList.get(i)); } } /** * 3D オブジェクトの回転 * @param obj 任意の BaseObject3D オブジェクト */ protected void rotateObject(BaseObject3D obj) { obj.setRotation(0, obj.getRotY() + 1f, 0); } } } [/java]

次回

RajawaliRenderer と UI について解説します。