MOVERIO + OpenGL ES 2.0 + ジャイロセンサ
目標
MOVERIOのジャイロセンサから得られた回転のパラメータを使って回転行列を得て,OpenGL ESに与え,頭の回転に合わせて三角形のCGを描画する.
OpenGL ESだけを動かすためには,前回の記事を参照のこと.つまり,今回は,前回からのジャイロセンサに関する拡張版だ.
http://mugichoko.hatenablog.com/entry/2017/10/20/041704mugichoko.hatenablog.com
開発環境
- Windows 10 Home
- Android Studio 2.3.3
- Android SDK 5.1 (Lollipop)
- OpenGL ES 2.0
- BT300 SDK 1709
- Epson MOVERIO BT-300
サンプルプログラム
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.javaとactivity_main.xmlは前回と同じです.
結果
動画にある通り,カメラの動きに合わせて(CGの三角形がその場に留まるように)表示されています.目標達成です!