長い前置きが、やっと終わりました。ここからは具体的なサンプルコードを交え、AndAR上でユーザーの操作に対応してアニメーションするARを構築する手法を紹介していきます。
なお今回のサンプルコードは、ユーザーのマルチタッチ操作をハンドリングするためにAndroid 2.2以降を前提としています。動作確認はAndroid 2.3.6搭載のGoogle Nexus SとAndroid 2.2搭載のHTC Desireで行いました。
これから紹介するサンプルは、前回「3DモデルがアニメーションするARをOpenGL ESで作るには」で紹介した「オリジナルの3Dモデルを表示する」ソースコードをベースにしています。前回紹介した手順に従い、ソファの3Dモデルが表示されるところまで準備してください。
次に、AndroidのAPIレベルを変更します。Eclipseのプロパティ画面を開き、左側ペインで「Android」を選択し、Targetを「Android 2.2」に変更します。
自作した、もしくはダウンロードしたテニスボールの3Dモデル「tennis_ball.mtl」「tennis_ball.obj」を、「assets/models」フォルダ以下にコピーします(今回作成した3Dモデルはテキスチャを使っていないため、「tennis_ball.png」はありません)。
次に、ソファではなくテニスボールを表示するようにCustomActivityのModelLoaderを修正します。
public class CustomActivity extends AndARActivity implements SurfaceHolder.Callback { // 省略 @Override public void surfaceCreated(SurfaceHolder holder) { super.surfaceCreated(holder); if(model == null) { waitDialog = ProgressDialog.show(this, "", getResources().getText(R.string.loading), true); waitDialog.show(); new ModelLoader().execute("tennis_ball.obj"); // sofa.objからtennis_ball.objへ修正 } } // 省略 }
ここでビルドと実機へのデプロイを行い、マーカー上にテニスボールが表示されることを確認してください。
筆者が作った3Dモデルを用いた方は、「小さな黄色い球体があるだけで、テニスボールなのか分からない!」と思っていることでしょう。そこで次に、デバイスのタッチスクリーンのピンチアウト・ピンチイン操作をハンドリングしてテニスボールが拡大・縮小するようにしましょう。
CustomActivityに、ピンチアウト・ピンチイン操作を検知するための「ScaleGestureDetector」と、ピンチアウト・ピンチイン操作を検知した際に実施すべきメソッドを定義する「AndARSimpleOnScaleGestureListener」を実装します。
※このマルチタッチイベントを簡単にハンドリングできるクラス群がAndroid 2.2から追加されたため、本稿のサンプルはAndroid 2.2以上を前提としています。
public class CustomActivity extends AndARActivity implements SurfaceHolder.Callback { // 省略 private ScaleGestureDetector scaleGestureDetector; // ピンチイン・ピンチアウトの検出(Android2.2以降) // 省略 @Override public void onCreate(Bundle savedInstanceState) { // 省略 // 自作のListenerを登録してピンチイン・ピンチアウトを検出するDetectorを生成 scaleGestureDetector = new ScaleGestureDetector(this, new AndARSimpleOnScaleGestureListener()); //追加 } // 省略 // タッチイベントをハンドリングし、ピンチイン・ピンチアウト、タップやフリックを各Detectorへ委譲 @Override public boolean onTouchEvent(MotionEvent event) { scaleGestureDetector.onTouchEvent(event); return true; } // ピンチイン・ピンチアウトのListener private class AndARSimpleOnScaleGestureListener extends SimpleOnScaleGestureListener { @Override public boolean onScale(ScaleGestureDetector detector) { // マルチタッチした2本の指の間隔の、前回イベント発生時よりの比率 // 1.0fより大きい場合は、指と指の間隔が広がった = ピンチアウト // 1.0fより小さい場合は、指と指の間隔が狭まった = ピンチイン float scaleFactor = detector.getScaleFactor(); // ピンチアウト時 if (scaleFactor > 1.0f) { model3d.stretch(scaleFactor); // 3Dモデルへ「拡大」を通知 } // ピンチイン時 if (scaleFactor < 1.0f) { model3d.shrink(scaleFactor); // 3Dモデルへ「縮小」を通知 } return true; } } // 省略 }
CustomActivityのonTouchEventでデバイスが検知した生のタッチイベントをScaleGestureDetectorに委譲することで、「2本の指の間隔の増減率」の計算処理などをScaleGestureDetectorに任せられます。
ScaleGestureDetectorは生のタッチイベントが「ピンチアウト・ピンチイン操作」だと判断できた場合、登録されているListenerのonScaleが呼び出されます。このonScaleで「現実世界のユーザーが行ったピンチアウト・ピンチイン操作」を「仮想世界の3Dモデル(model3d)」へ通知することで、ユーザーの操作に連動してテニスボールが拡大・縮小します。
Model3Dに、あまりに大きくなり過ぎないように注意しつつ自らを拡大する「stretchメソッド」と、あまりに小さくなり過ぎないように注意しつつ、自らを縮小する「shrinkメソッド」を定義します。
ただし、Model3Dクラスが自分自身をレンダリングするdrawメソッドには、glScalefを用いた「形を変えずに自らを拡大・縮小する」アフィン変換が、すでに実装されています。そのため「stretchメソッド」では「最大スケールより小さいならば、与えられた拡大率でスケールを拡大する」ように、「shrinkメソッド」では「最小スケールより大きいならば、与えられた縮小率でスケールを縮小する」処理だけを実装すればいいことになります。
public class Model3D extends ARObject implements Serializable{ private static final float MIN_SCALE = 1.0f; // 最小スケール private static final float MAX_SCALE = 100.0f; // 最大スケール public void stretch(float factor) { // 最大スケールより小さい場合は、拡大する if (model.scale < MAX_SCALE) { model.scale = model.scale * factor; // 現在のスケールに1以上のscaleFactorを乗算することで、拡大して表示される } } public void shrink(float factor) { // 最小スケールより大きい場合は、縮小する if (model.scale > MIN_SCALE) { model.scale = model.scale * factor; // 現在のスケールに1以下のscaleFactorを乗算することで、縮小して表示される } } // 省略 @Override public void draw(GL10 gl) { super.draw(gl); //gl = (GL10) GLDebugHelper.wrap(gl, GLDebugHelper.CONFIG_CHECK_GL_ERROR, log); // マーカーに対して3Dモデルを回転、移動、拡大・縮小するアフィン変換(AndARに最初から実装済み) gl.glScalef(model.scale, model.scale, model.scale); // 省略 } // 省略 }
ビルドと実機へのデプロイを行ってください。マーカー上に表示されたテニスボールを拡大・縮小できましたか?
では本命の、タップによってテニスボールが回転&バウンドするアニメーションを行う処理を実装します。
AndroidのGestureDetector周りの実装はイマイチで、タップやロングプレス、ドラッグ操作といった1本指で行う操作はGestureDetector、2本指で行うピンチ操作はScaleGestureDetectorと使い分けなければなりません。そのため、CustomActivityに「GestureDetector」「AndARSimpleOnGestureListener」を追加実装するだけでなく、「GestureDetector」「ScaleGestureDetector」のどちらにタッチイベント処理を委譲するかを選択するようにonTouchEventメソッドを変更する必要があります。
public class CustomActivity extends AndARActivity implements SurfaceHolder.Callback { // 省略 private ScaleGestureDetector scaleGestureDetector; // ピンチイン・ピンチアウトの検出(Android 2.2以降) private GestureDetector gestureDetector; // タップやフリックを検出 // 省略 @Override public void onCreate(Bundle savedInstanceState) { // 省略 // 自作のListenerを登録してピンチイン・ピンチアウトを検出するDetectorを生成 scaleGestureDetector = new ScaleGestureDetector(this, new AndARSimpleOnScaleGestureListener()); // 追加 // 自作のListenerを登録してタップやフリックを検出するDetectorを生成 gestureDetector = new GestureDetector(this, new AndARSimpleOnGestureListener()); // 追加 } // 省略 // タッチイベントをハンドリングし、ピンチイン・ピンチアウト、タップやフリックを各Detectorへ委譲 // 指が一本の場合はgestureDetectorへ、二本以上の場合はscaleGestureDetectorへ処理を委譲する @Override public boolean onTouchEvent(MotionEvent event) { if (event.getPointerCount() == 1) { // タッチした指が一本の場合 gestureDetector.onTouchEvent(event); } else { // タッチした指が二本以上の場合 scaleGestureDetector.onTouchEvent(event); } return true; } // ピンチイン・ピンチアウトのListener private class AndARSimpleOnScaleGestureListener extends SimpleOnScaleGestureListener { // 省略 } // タップのListener private class AndARSimpleOnGestureListener extends SimpleOnGestureListener { // シングルタップ @Override public boolean onSingleTapUp(MotionEvent e) { model3d.awakeOrAsleep(); // 3Dモデルが寝ている場合は起こし、起きている場合は眠らせる return true; } } // 省略 }
onSingleTapUpで「現実世界のユーザーが行ったタップ操作」を「仮想世界の3Dモデル(model3d)」へ通知することで、テニスボールが動き出すことになります。
次ページでは、いよいよ3Dモデルの回転して移動する方法と、フリック操作によってテニスボールを加速する方法を解説します。
Copyright © ITmedia, Inc. All Rights Reserved.