(17) OpenGLと加速度センサーでボールを転がす。

投稿者: | 2018年10月3日

この記事は最終更新から 1150日 が経過しています。

1. やりたいこと

(13) OpenGLで画像を表示する。 では実機上にボールを表示した。
(15) 加速度センサーを使ってみる。 では加速度センサーの出力値を見た。

よって、今回は…
実機上に OpenGLで表示したボールを、実機を傾けて転がしてみたい。

2. やってみる!

(1) 実装

能書きを抜きにしてソースコードを貼り付けておく。
センサーからの入力値を Activity → View → Renderer とずるずると引き渡している。
何かスマートな方法はないものか?

1) MainActivity

import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.os.Handler;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;

public class MainActivity extends AppCompatActivity implements SensorEventListener {
    MyGLView m_glView;
    private SensorManager m_sensorManager;
    private float m_sensor_val_x, m_sensor_val_y;
    private Runnable m_runnable;
    private final Handler m_handler = new Handler();

    @Override protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        // OpenGL View生成
        m_glView = new MyGLView(this);
        setContentView(m_glView);

        // センサー初期化
        m_sensor_val_x = m_sensor_val_y = 0.0f;
        m_sensorManager = (SensorManager)getSystemService(SENSOR_SERVICE);
        StartCyclicHandler();       // 周期ハンドラ開始
    }

    @Override protected void onResume() {
        super.onResume();
        m_glView.onResume();

        // Event Listener登録
        Sensor accel = m_sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
        m_sensorManager.registerListener(this, accel, SensorManager.SENSOR_DELAY_NORMAL);
        StartCyclicHandler();       // 周期ハンドラ開始
    }

    @Override protected void onPause() {
        super.onPause();
        m_glView.onPause();

        // Event Listener登録解除
        m_sensorManager.unregisterListener(this);
        StoptCyclicHandler();       // 周期ハンドラ停止
    }

    protected void StartCyclicHandler(){
        m_runnable = new Runnable() {
            @Override public void run() {
                m_glView.AccelerometerNotify(m_sensor_val_x, m_sensor_val_y);
                m_handler.postDelayed(this, 30);    // 30msスリープ
            }
        };
        m_handler.post(m_runnable);     // スレッド起動
    }
    protected void StoptCyclicHandler() {
        m_handler.removeCallbacks(m_runnable);
    }

    @Override public void onSensorChanged(SensorEvent event) {      // センサー通知
        if(event.sensor.getType() == Sensor.TYPE_ACCELEROMETER){
            m_sensor_val_x = event.values[0];
            m_sensor_val_y = event.values[1];
        }
    }

    @Override public void onAccuracyChanged(Sensor sensor, int accuracy) {
    }
}

2) MyGLView

import android.content.Context;
import android.opengl.GLSurfaceView;

public class MyGLView extends GLSurfaceView {
    private MyGLRenderer m_renderer;

    public MyGLView(Context context){
        super(context);

        // レンダラー生成
        m_renderer = new MyGLRenderer(context);
        setRenderer(m_renderer);
    }

    public void AccelerometerNotify( float x, float y ){    // 加速度センサー通知
        m_renderer.AccelerometerNotify(x, y);
    }
}

3) MyGLRenderer

import android.content.Context;
import android.opengl.GLSurfaceView;
import android.util.Log;

import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;

public class MyGLRenderer implements GLSurfaceView.Renderer {
    private Context m_context;          // アプリのコンテキスト
    private MyGLPic m_pic = null;       // 表示画像
    private int m_view_w, m_view_h;     // View表示領域の幅と高さ

    public MyGLRenderer( Context context ){
        m_context = context;
    }

    @Override public void onSurfaceCreated(GL10 gl, EGLConfig config) {
        gl.glDisable(GL10.GL_DITHER);       // 機能無効化 : ディザリング
        gl.glEnable(GL10.GL_DEPTH_TEST);    // 機能有効化 : 隠面消去用 Depth Buffer更新
        gl.glEnable(GL10.GL_TEXTURE_2D);    // 機能有効化 : 2Dテクスチャ
        gl.glEnable(GL10.GL_ALPHA_TEST);    // 機能有効化 : アルファテスト(αチャネルによる画素の有効/無効判定)
        gl.glEnable(GL10.GL_BLEND);         // 機能有効化 : ブレンド(画像の重ね合わせ)
        gl.glBlendFunc(GL10.GL_SRC_ALPHA, GL10.GL_ONE_MINUS_SRC_ALPHA);     // カラーブレンド(画像重ね合わせ)モード :  アルファブレンド
        // 描画する画像をリソースから取得
        m_pic = new MyGLPic();
        m_pic.SetTexture(gl, m_context.getResources(), R.drawable.ball);
    }

    @Override public void onSurfaceChanged(GL10 gl, int width, int height) {
        m_view_w = width;
        m_view_h = height;
    }

    @Override public void onDrawFrame(GL10 gl) {
        // 描画用バッファをクリア
        gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT);
        m_pic.Draw(gl);     // 画像を描画
    }

    public void AccelerometerNotify( float x, float y ){    // 加速度センサー通知
        if(m_pic == null){
            return;
        }
        int iX = (int)(x * 3.0);      // X方向移動量 : センサー出力値 x 3[pixel]動かす
        int iY = (int)(y * 3.0);      // Y方向移動量 : センサー出力値 x 3[pixel]動かす
        // 制御対象画像の位置とサイズを取得
        int pic_w = m_pic.get_w();
        int pic_h = m_pic.get_h();
        int pic_x = m_pic.get_pos_x();
        int pic_y = m_pic.get_pos_y();
        // 移動後の位置を算出
        int new_x = pic_x;
        int new_y = pic_y;
        // X軸
        new_x = pic_x - iX;
        if(iX > 0) {         // 左回転?
            if(new_x < 0){
                new_x = 0;
            }
        }
        else if(iX < 0) {   // 右回転?
            int lim_x = m_view_w - pic_w;
            if(new_x > lim_x){
                new_x = lim_x;
            }
        }
        // Y軸
        new_y = pic_y - iY;
        if(iY > 0) {         // 手前回転? ※画面下方向(-方向)へ移動
            if(new_y < 0){
                new_y = 0;
            }
        }
        else if(iY < 0){    // 奥回転 ※画面上方向(+方向)へ移動
            int lim_y = m_view_h - pic_h;
            if(new_y > lim_y){
                new_y = lim_y;
            }
        }
        // 画像の位置を変更
        m_pic.SetPosXY(new_x, new_y);
    }
}

4) MyGLPic

import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.opengl.GLUtils;
import android.util.Log;

import java.io.IOException;
import java.io.InputStream;

import javax.microedition.khronos.opengles.GL10;
import javax.microedition.khronos.opengles.GL11;
import javax.microedition.khronos.opengles.GL11Ext;

public class MyGLPic {
    protected int m_textureNo;
    protected float m_width, m_height;          // 画像の表示幅と高さ
    protected int m_crop_x, m_crop_y;           // テクスチャ切り出し左上オフセット座標
    protected int m_crop_w, m_crop_h;           // テクスチャ切り出し幅と高さ
    protected float m_pos_x, m_pos_y, m_pos_z;  // テクスチャ表示位置

    public MyGLPic(){
        m_textureNo = 0;
        m_pos_x = m_pos_y = m_pos_z = 0.0f;
        m_width = m_height = 0.0f;
    }

    // 画像情報問い合わせ
    public int get_pos_x(){ return (int)m_pos_x; }
    public int get_pos_y(){ return (int)m_pos_y; }
    public int get_w(){ return (int)m_width; }
    public int get_h(){ return (int)m_height; }

    // 本インスタンスに画像をロード
    public void SetTexture( GL10 gl, Resources res, int id ) {
        InputStream is = res.openRawResource(id);
        Bitmap bitmap;
        try {
            bitmap = BitmapFactory.decodeStream(is);
        } finally {
        }
        try {
            is.close();
        } catch (IOException e) {
            Log.d("MyGLPic", "Can't load resource.");
        }
        gl.glEnable(GL10.GL_ALPHA_TEST);
        gl.glEnable(GL10.GL_BLEND);
        gl.glBlendFunc(GL10.GL_SRC_ALPHA, GL10.GL_ONE_MINUS_SRC_ALPHA);
        // 下地色とテクスチャ色との合成方法 : 乗算
        gl.glTexEnvf(GL10.GL_TEXTURE_ENV, GL10.GL_TEXTURE_ENV_MODE, GL10.GL_MODULATE);

        // テクスチャIDを割り当て
        int[] ary_textureNo = new int[1];
        gl.glGenTextures(1, ary_textureNo, 0);
        m_textureNo = ary_textureNo[0];

        // テクスチャIDをバインドし、データとIDを関連付ける。
        gl.glBindTexture(GL10.GL_TEXTURE_2D, m_textureNo);
        GLUtils.texImage2D(GL10.GL_TEXTURE_2D, 0, bitmap, 0);

        // ポリゴン内の画像の繰り返し指定 S軸、T軸ともに繰り返しなし。
        gl.glTexParameterx(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_WRAP_S, GL10.GL_CLAMP_TO_EDGE );
        gl.glTexParameterx(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_WRAP_T, GL10.GL_CLAMP_TO_EDGE );

        // テクスチャのリサンプリングアルゴリズム指定
        gl.glTexParameterx(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MAG_FILTER, GL10.GL_LINEAR );
        gl.glTexParameterx(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MIN_FILTER, GL10.GL_LINEAR );

        // 表示座標設定
        int img_w = bitmap.getWidth();
        int img_h = bitmap.getHeight();
        SetPosAll(0, img_h, img_w, -img_h,0, 0, img_w, img_h);
    }

    //テクスチャ表示サイズ、表示位置情報をセット
    public void SetPosAll( int crop_x,int crop_y,int crop_w,int crop_h, float pos_x, float pos_y, float disp_w, float disp_h ){
        m_crop_x = crop_x;
        m_crop_y = crop_y;
        m_crop_w = crop_w;
        m_crop_h = crop_h;
        m_pos_x = pos_x;
        m_pos_y = pos_y;
        m_width = disp_w;
        m_height = disp_h;
    }
    public void SetPosXY( float pos_x, float pos_y ) {
        m_pos_x = pos_x;
        m_pos_y = pos_y;
    }

    public void Draw( GL10 gl ){
        gl.glDisable(GL10.GL_DEPTH_TEST);   // デプステストOFF

        // 白色
        //    gl.glColor4x(0x10000, 0x10000, 0x10000, 0x10000);

        // 表示するテクスチャをバインド
        gl.glActiveTexture(GL10.GL_TEXTURE0);
        gl.glBindTexture(GL10.GL_TEXTURE_2D, m_textureNo);

        // テクスチャから指定部分を切り出して表示
        int rect[] = {m_crop_x, m_crop_y, m_crop_w, m_crop_h};
        ((GL11)gl).glTexParameteriv(GL10.GL_TEXTURE_2D, GL11Ext.GL_TEXTURE_CROP_RECT_OES, rect, 0);
        ((GL11Ext) gl).glDrawTexfOES(m_pos_x, m_pos_y, m_pos_z, m_width, m_height);

        gl.glEnable(GL10.GL_DEPTH_TEST);   // デプステストON
    }
}

(2) 実行結果

実機を傾けるとボールがコロコロと転がる。
ボール回転のアニメーションをしていないので、実際にはボールが画面上を滑っている。

3. 所感

・MyGLPic::SetTexture() 内の res.openRawResource(id) で落ちた原因は、画像リソースを drawable v24 の方に登録していたため。
・30fpsをもくろんで描画スレッドを 30ms周期で起動するようにした。
・実際にはそんなレートが出ていないのか、ボール移動時に少しカクカクして見える。
 ・ボール画像が 200 x 200[pixel]と大きかったのもカクカクした原因か?
 ・実機(NEC Lavie Tab PC-TE508S1W)の性能の問題でカクカクするのか?
・描画スレッドではなく onSensorChanged で描画してみたら耐えられない遅さになった。
・スマホでも動かしてみたい。うちには Android 2.3の古いスマホしかないので、ヤフオクで 6.xぐらいを買うか?


コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です


日本語が含まれない投稿は無視されますのでご注意ください。(スパム対策)