Mugichoko's blog

Mugichoko’s blog

プログラミングを中心としたメモ書き.

MOVERIO + OpenGL ES 2.0 + ジャイロセンサ

目標

MOVERIOのジャイロセンサから得られた回転のパラメータを使って回転行列を得て,OpenGL ESに与え,頭の回転に合わせて三角形のCGを描画する.

OpenGL ESだけを動かすためには,前回の記事を参照のこと.つまり,今回は,前回からのジャイロセンサに関する拡張版だ.

mugichoko.hatenablog.com

開発環境

サンプルプログラム

OpenGLES20Activity

本体のプログラム,前回とほとんど変わらないが,新たにMoverioGyroクラスのインスタンス化とそれに付随したonChanged関数の定義が追加されている.これによって,ジャイロセンサが反応したときにonChanged関数が呼び出される仕様だ.

package com.example.XXXXX.XXXXX;
// XXXXXにはユーザ名やプロジェクト名が入っているはずです.

import android.support.v7.app.AppCompatActivity;
import android.app.ActivityManager;
import android.content.Context;
import android.content.pm.ConfigurationInfo;
import android.os.Bundle;

public class OpenGLES20Activity extends AppCompatActivity implements MoverioGyro.IGyroListener {

    private MyGLSurfaceView mGLView;
    private MoverioGyro mGyro;

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

        mGLView = new MyGLSurfaceView(this);

        final ActivityManager activityManager = (ActivityManager)getSystemService(Context.ACTIVITY_SERVICE);
        final ConfigurationInfo configurationInfo = activityManager.getDeviceConfigurationInfo();
        final boolean supportsES2 = configurationInfo.reqGlEsVersion >= 0x20000;
        if (supportsES2)
        {
            setContentView(mGLView);
        }
        else
        {
            return;
        }

        mGyro = new MoverioGyro(this);
    }

    @Override
    protected void onPause()
    {
        super.onPause();
        if (mGLView != null)
        {
            mGLView.onPause();
        }
        if (mGyro != null)
        {
            mGyro.stopSensor();
        }
    }

    @Override
    protected void onResume()
    {
        super.onResume();
        if (mGLView != null)
        {
            mGLView.onResume();
        }
        if (mGyro != null)
        {
            mGyro.startSensor();
        }
    }

    @Override
    public void onChanged(float gx, float gy, float gz)
    {
        float[] angles = { gx, gy, gz };
        mGLView.setGyroValues(angles);
    }
}

MyGLSurfaceView

ここも,前回とほとんど変わらない.新たにジャイロの値をmRendererに渡すための関数が追加されたくらいだ.

package com.example.XXXXX.XXXXX;
// XXXXXにはユーザ名やプロジェクト名が入っているはずです.

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

class MyGLSurfaceView extends GLSurfaceView
{
    private final MyGLRenderer mRenderer;

    public MyGLSurfaceView(Context context)
    {
        super(context);
        setEGLContextClientVersion(2);

        mRenderer = new MyGLRenderer();
        setRenderer(mRenderer);
        setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);
    }

    public void setGyroValues(float[] angles)
    {
        float[] Angles = mRenderer.getGyroValues();
        for (int i = 0; i < angles.length; ++i)
        {
            Angles[i] += angles[i];
        }
        mRenderer.setGyroValues(Angles);

        requestRender();
    }
}

MyGLRenderer

onDrawFrameにて,ジャイロで得られた値を用いて回転行列を計算しています.

package com.example.XXXXX.XXXXX;
// XXXXXにはユーザ名やプロジェクト名が入っているはずです.

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

import android.opengl.GLES20;
import android.opengl.GLSurfaceView;
import android.opengl.Matrix;

public class MyGLRenderer implements GLSurfaceView.Renderer
{
    private Triangle mTriangle;

    private final float[] mMVPMatrix = new float[16];
    public volatile float[] mGyroValues = new float[3];
    public volatile long mGyroTimestamp;

    @Override
    public void onSurfaceCreated(GL10 unused, EGLConfig config)
    {
        GLES20.glClearColor(0.0f, 0.0f, 0.0f, 1.0f);

        mTriangle = new Triangle();
    }

    @Override
    public void onDrawFrame(GL10 unused)
    {
        GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);

        // FoV Ref: https://tech.moverio.epson.com/en/pdf/comparison.pdf
        float[] projectionMatrix = new float[16];
        float fovy = 23.0f * (3.0f / 5.0f);
        float aspect = (float)1280 / 720;
        Matrix.perspectiveM(projectionMatrix, 0, fovy, aspect, 10.0f, 3000.0f);

        float[] rotationMatrixX = new float[16];
        float[] rotationMatrixY = new float[16];
        float[] rotationMatrixZ = new float[16];
        float[] rotationMatrix = new float[16];
        Matrix.setRotateM(rotationMatrixX, 0, mGyroValues[0], 1.0f, 0.0f, 0.0f);
        Matrix.setRotateM(rotationMatrixY, 0, mGyroValues[1], 0.0f, 1.0f, 0.0f);
        Matrix.setRotateM(rotationMatrixZ, 0, mGyroValues[2], 0.0f, 0.0f, 1.0f);
        Matrix.multiplyMM(rotationMatrix, 0, rotationMatrixY, 0, rotationMatrixX, 0);
        Matrix.multiplyMM(rotationMatrix, 0, rotationMatrixZ, 0, rotationMatrix, 0);
        float[] position = { 0.0f, 0.0f, 500.0f };

        float[] viewMatrix = new float[16];
        Matrix.setLookAtM(viewMatrix, 0,
                position[0], position[1], position[2],
                -rotationMatrix[8], -rotationMatrix[9], position[2]-rotationMatrix[10],
                rotationMatrix[4], rotationMatrix[5], rotationMatrix[6]
        );

        Matrix.multiplyMM(mMVPMatrix, 0, projectionMatrix, 0, viewMatrix, 0);
        mTriangle.draw(mMVPMatrix);
    }

    @Override
    public void onSurfaceChanged(GL10 unused, int width, int height)
    {
        GLES20.glViewport(0, 0, width, height);
        //float ratio = (float) width / height;
        //Matrix.frustumM(mProjectionMatrix, 0, -ratio, ratio, -1, 1, 3, 7);
    }

    public static int loadShader(int type, String shaderCode)
    {
        int shader = GLES20.glCreateShader(type);
        GLES20.glShaderSource(shader, shaderCode);
        GLES20.glCompileShader(shader);

        return shader;
    }

    public float[] getGyroValues()
    {
        return mGyroValues;
    }

    public void setGyroValues(float[] gyroValues)
    {
        mGyroValues = gyroValues;
    }

    public long getmGyroTimestamp()
    {
        return mGyroTimestamp;
    }

    public void setmGyroTimestamp(long ts)
    {
        mGyroTimestamp = ts;
    }
}

MoverioGyro

ジャイロセンサの値を取得する大本です.

package com.example.XXXXX.XXXXX;
// XXXXXにはユーザ名やプロジェクト名が入っているはずです.

import android.content.Context;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;

class MoverioGyro implements SensorEventListener
{
    private boolean mIsSensorStarted;
    private SensorManager mSensorManager;
    private IGyroListener mListener;

    interface IGyroListener
    {
        void onChanged(float gx, float gy, float gz);
    }

    MoverioGyro(Context context)
    {
        mIsSensorStarted = false;

        if (context instanceof IGyroListener)
        {
            mListener = (IGyroListener)context;
            mSensorManager = (SensorManager)context.getSystemService((Context.SENSOR_SERVICE));
        }
        else
        {
            throw new ClassCastException("Must implement IGyroListener");
        }
    }

    @Override
    public void onSensorChanged(SensorEvent e)
    {
        if (e.sensor.getType() == Sensor.TYPE_GYROSCOPE)
        {
            if (mListener != null)
            {
                mListener.onChanged(e.values[0], e.values[1], e.values[2]);
            }
        }
    }

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

    }

    void startSensor()
    {
        if (!mIsSensorStarted)
        {
            if (mSensorManager != null)
            {
                Sensor gyro = mSensorManager.getDefaultSensor(Sensor.TYPE_GYROSCOPE);

                if (gyro != null)
                {
                    mIsSensorStarted = mSensorManager.registerListener(this, gyro, SensorManager.SENSOR_DELAY_NORMAL);
                }
            }
        }
    }

    void stopSensor()
    {
        if (mIsSensorStarted)
        {
            if (mSensorManager != null)
            {
                mSensorManager.unregisterListener(this);
                mIsSensorStarted = false;
            }
        }
    }
}

尚,Triangle.javaactivity_main.xml前回と同じです.

結果

youtu.be

動画にある通り,カメラの動きに合わせて(CGの三角形がその場に留まるように)表示されています.目標達成です!