連載
» 2011年12月21日 00時00分 公開

モバイルARアプリ開発“超”入門(4):3DモデルがアニメーションするARをOpenGL ESで作るには (2/3)

[松井暢之,TIS株式会社]

AndAR上でアニメーションするための準備

 長い前置きが、やっと終わりました。ここからは具体的なサンプルコードを交え、AndAR上でユーザーの操作に対応してアニメーションするARを構築する手法を紹介していきます。

動作確認検証済み環境

 なお今回のサンプルコードは、ユーザーのマルチタッチ操作をハンドリングするためにAndroid 2.2以降を前提としています。動作確認はAndroid 2.3.6搭載のGoogle Nexus SとAndroid 2.2搭載のHTC Desireで行いました。

ベースとなるソースコードの準備とAndroid APIレベルの変更

 これから紹介するサンプルは、前回「3DモデルがアニメーションするARをOpenGL ESで作るには」で紹介した「オリジナルの3Dモデルを表示する」ソースコードをベースにしています。前回紹介した手順に従い、ソファの3Dモデルが表示されるところまで準備してください。

 次に、AndroidのAPIレベルを変更します。Eclipseのプロパティ画面を開き、左側ペインで「Android」を選択し、Targetを「Android 2.2」に変更します。

テニスボールの3DモデルをAndAR上に表示

 自作した、もしくはダウンロードしたテニスボールの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モデルの拡大・縮小

 筆者が作った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)」へ通知することで、ユーザーの操作に連動してテニスボールが拡大・縮小します。

3Dモデルの拡大・縮小

 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.

RSSについて

アイティメディアIDについて

メールマガジン登録

@ITのメールマガジンは、 もちろん、すべて無料です。ぜひメールマガジンをご購読ください。