package com.asha.vrlib; import android.opengl.GLES20; import android.opengl.Matrix; import com.asha.vrlib.common.VRUtil; import com.asha.vrlib.model.MDPosition; import com.asha.vrlib.model.MDQuaternion; import com.asha.vrlib.model.position.MDMutablePosition; /** * Created by hzqiujiadi on 16/1/22. * hzqiujiadi ashqalcn@gmail.com * * response for model * view * projection */ public class MD360Director { private static final String TAG = "MD360Director"; private static final float sNear = 0.7f; private float[] mViewMatrix = new float[16]; private float[] mProjectionMatrix = new float[16]; private float[] mMVMatrix = new float[16]; private float[] mMVPMatrix = new float[16]; private float[] mWorldRotationMatrix = new float[16]; private float[] mWorldRotationInvertMatrix = new float[16]; private float[] mCurrentRotationPost = new float[16]; private float[] mSensorMatrix = new float[16]; private float[] mTempMatrix = new float[16]; private float[] mCameraMatrix = new float[16]; private final MDDirectorCamera mCamera; private final MDDirectorCamUpdate mCameraUpdate = new MDDirectorCamUpdate(); private final MDMutablePosition mCameraRotation = MDMutablePosition.newInstance(); private final MDQuaternion mViewQuaternion = new MDQuaternion(); private MDDirectorFilter mDirectorFilter; private float mDeltaX; private float mDeltaY; private boolean mWorldRotationMatrixInvalidate = true; protected MD360Director(Builder builder) { this.mCamera = builder.mCamera; initModel(); } public float getDeltaY() { return mDeltaY; } public void setDeltaY(float mDeltaY) { this.mDeltaY = mDeltaY; mWorldRotationMatrixInvalidate = true; } public float getDeltaX() { return mDeltaX; } public void setDeltaX(float mDeltaX) { this.mDeltaX = mDeltaX; mWorldRotationMatrixInvalidate = true; } private void initModel(){ Matrix.setIdentityM(mViewMatrix, 0); Matrix.setIdentityM(mSensorMatrix, 0); mViewQuaternion.fromMatrix(mViewMatrix); } public void beforeShot(){ updateProjectionIfNeed(); updateViewMatrixIfNeed(); } public void shot(MD360Program program, MDPosition modelPosition) { // This multiplies the view matrix by the model matrix, and stores the result in the MVP matrix // (which currently contains model * view). Matrix.multiplyMM(mMVMatrix, 0, mViewMatrix, 0, modelPosition.getMatrix(), 0); // This multiplies the model view matrix by the projection matrix, and stores the result in the MVP matrix // (which now contains model * view * projection). Matrix.multiplyMM(mMVPMatrix, 0, mProjectionMatrix, 0, mMVMatrix, 0); // Pass in the model view matrix GLES20.glUniformMatrix4fv(program.getMVMatrixHandle(), 1, false, mMVMatrix, 0); // Pass in the combined matrix. GLES20.glUniformMatrix4fv(program.getMVPMatrixHandle(), 1, false, mMVPMatrix, 0); } private void updateViewMatrixIfNeed(){ boolean camera = mCamera.isPositionValidate() || mCameraUpdate.isPositionValidate(); boolean world = mWorldRotationMatrixInvalidate || mCamera.isRotationValidate() || mCameraUpdate.isRotationValidate(); if (camera){ updateCameraMatrix(); mCamera.consumePositionValidate(); mCameraUpdate.consumePositionValidate(); } if (world){ mCameraRotation.setPitch(mCamera.getPitch() + mCameraUpdate.getPitch()); mCameraRotation.setRoll(mCamera.getRoll() + mCameraUpdate.getRoll()); mCameraRotation.setYaw(mCamera.getYaw() + mCameraUpdate.getYaw()); // mCamera changed will be consumed after updateWorldRotationMatrix updateWorldRotationMatrix(); mWorldRotationMatrixInvalidate = false; mCamera.consumeRotationValidate(); mCameraUpdate.consumeRotationValidate(); } if (camera || world){ Matrix.multiplyMM(mViewMatrix, 0, mCameraMatrix, 0, mWorldRotationMatrix, 0); mViewQuaternion.fromMatrix(mViewMatrix); float pitch = mViewQuaternion.getPitch(); float yaw = mViewQuaternion.getYaw(); float roll = mViewQuaternion.getRoll(); float filterPitch = mDirectorFilter.onFilterPitch(pitch); float filterYaw = mDirectorFilter.onFilterYaw(yaw); float filterRoll = mDirectorFilter.onFilterRoll(roll); if (pitch != filterPitch || yaw != filterYaw || roll != filterRoll){ mViewQuaternion.setEulerAngles(filterPitch, filterYaw, filterRoll); mViewQuaternion.toMatrix(mViewMatrix); } } } public void setViewport(int width, int height){ // Projection Matrix mCamera.updateViewport(width, height); } public void setNearScale(float scale){ mCamera.setNearScale(scale); } private void updateProjectionIfNeed(){ if (mCamera.isProjectionValidate() || mCameraUpdate.isProjectionValidate()){ updateProjection(); mCamera.consumeProjectionValidate(); mCameraUpdate.consumeProjectionValidate(); } } protected void updateProjection(){ final float left = -mCamera.getRatio()/2; final float right = mCamera.getRatio()/2; final float bottom = -0.5f; final float top = 0.5f; final float far = 500; Matrix.frustumM(getProjectionMatrix(), 0, left, right, bottom, top, getNear(), far); } protected float getNear(){ return (mCamera.getNearScale() + mCameraUpdate.getNearScale()) * sNear; } protected float getRatio(){ return mCamera.getRatio(); } public float[] getProjectionMatrix(){ return mProjectionMatrix; } public int getViewportWidth() { return mCamera.getViewportWidth(); } public int getViewportHeight() { return mCamera.getViewportHeight(); } public float[] getViewMatrix() { return mViewMatrix; } public MDQuaternion getViewQuaternion() { return mViewQuaternion; } private void updateCameraMatrix() { final float eyeX = mCamera.getEyeX() + mCameraUpdate.getEyeX(); final float eyeY = mCamera.getEyeY() + mCameraUpdate.getEyeY(); final float eyeZ = mCamera.getEyeZ() + mCameraUpdate.getEyeZ(); final float lookX = mCamera.getLookX() + mCameraUpdate.getLookX(); final float lookY = mCamera.getLookY() + mCameraUpdate.getLookY(); final float lookZ = -1.0f; final float upX = 0.0f; final float upY = 1.0f; final float upZ = 0.0f; Matrix.setIdentityM(mCameraMatrix, 0); Matrix.setLookAtM(mCameraMatrix, 0, eyeX, eyeY, eyeZ, lookX, lookY, lookZ, upX, upY, upZ); } private void updateWorldRotationMatrix(){ Matrix.setIdentityM(mWorldRotationMatrix, 0); Matrix.rotateM(mWorldRotationMatrix, 0, -mDeltaY, 1.0f, 0.0f, 0.0f); Matrix.setIdentityM(mCurrentRotationPost, 0); Matrix.rotateM(mCurrentRotationPost, 0, -mDeltaX, 0.0f, 1.0f, 0.0f); Matrix.setIdentityM(mTempMatrix, 0); Matrix.multiplyMM(mTempMatrix, 0, mCurrentRotationPost, 0, mCameraRotation.getMatrix(), 0); Matrix.multiplyMM(mCurrentRotationPost, 0, mSensorMatrix, 0, mTempMatrix, 0); Matrix.multiplyMM(mTempMatrix, 0, mWorldRotationMatrix, 0, mCurrentRotationPost, 0); System.arraycopy(mTempMatrix, 0, mWorldRotationMatrix, 0, 16); boolean success = VRUtil.invertM(mWorldRotationInvertMatrix, mWorldRotationMatrix); if (!success){ Matrix.setIdentityM(mWorldRotationInvertMatrix, 0); } } // call in gl thread public void updateSensorMatrix(float[] sensorMatrix) { System.arraycopy(sensorMatrix, 0, mSensorMatrix, 0, 16); mWorldRotationMatrixInvalidate = true; } // call in gl thread public void reset(){ mDeltaX = mDeltaY = 0; Matrix.setIdentityM(mSensorMatrix,0); mWorldRotationMatrixInvalidate = true; } public static Builder builder(){ return new Builder(); } public float[] getWorldRotationInvert() { return mWorldRotationInvertMatrix; } public void applyUpdate(MDDirectorCamUpdate cameraUpdate) { mCameraUpdate.copy(cameraUpdate); } public void applyFilter(MDDirectorFilter directorFilter) { this.mDirectorFilter = directorFilter; } public static class Builder { private MDDirectorCamera mCamera = new MDDirectorCamera(); private MDDirectorCamera camera(){ return mCamera; } public Builder setLookX(float mLookX) { camera().setLookX(mLookX); return this; } public Builder setLookY(float mLookY) { camera().setLookY(mLookY); return this; } public Builder setEyeX(float mEyeX) { camera().setEyeX(mEyeX); return this; } public Builder setEyeY(float mEyeY) { camera().setEyeY(mEyeY); return this; } public Builder setEyeZ(float mEyeZ) { camera().setEyeZ(mEyeZ); return this; } public Builder setNearScale(float scale) { camera().setNearScale(scale); return this; } public Builder setRoll(float roll){ camera().setRoll(roll); return this; } public Builder setPitch(float pitch){ camera().setPitch(pitch); return this; } public Builder setYaw(float yaw){ camera().setYaw(yaw); return this; } public MD360Director build(){ return new MD360Director(this); } } }