Java tutorial
/******************************************************************************* Copyright 2014 Kent Displays, Inc. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ******************************************************************************/ package com.kentdisplays.synccardboarddemo; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.opengl.GLES20; import android.opengl.Matrix; import android.os.Bundle; import android.support.v4.content.LocalBroadcastManager; import android.util.Log; import com.google.vrtoolkit.cardboard.CardboardActivity; import com.google.vrtoolkit.cardboard.CardboardView; import com.google.vrtoolkit.cardboard.EyeTransform; import com.google.vrtoolkit.cardboard.HeadTransform; import com.google.vrtoolkit.cardboard.Viewport; import com.improvelectronics.sync.android.SyncFtpService; import com.improvelectronics.sync.android.SyncStreamingService; import java.io.BufferedReader; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.FloatBuffer; import java.util.Random; import javax.microedition.khronos.egl.EGLConfig; public class MainActivity extends CardboardActivity implements CardboardView.StereoRenderer { private static final String TAG = "MainActivity"; private static final float CAMERA_Z = 0.01f; // We keep the light always position just above the user. private final float[] mLightPosInWorldSpace = new float[] { 0.0f, 2.0f, 0.0f, 1.0f }; private final float[] mLightPosInEyeSpace = new float[4]; private static final int COORDS_PER_VERTEX = 3; private final WorldLayoutData DATA = new WorldLayoutData(); private Page[] mPages; private FloatBuffer mFloorVertices; private FloatBuffer mFloorColors; private FloatBuffer mFloorNormals; private int mGlProgram; private int mPositionParam; private int mNormalParam; private int mColorParam; private int mModelViewProjectionParam; private int mLightPosParam; private int mModelViewParam; private int mModelParam; private int mIsFloorParam; private float[] mCamera; private float[] mView; private float[] mHeadView; private float[] mModelViewProjection; private float[] mModelView; private float[] mModelFloor; private float mFloorDepth = 20f; private CardboardOverlayView mOverlayView; private static Random mRandom; /** * Converts a raw text file, saved as a resource, into an OpenGL ES shader * @param type The type of shader we will be creating. * @param resId The resource ID of the raw text file about to be turned into a shader. * @return */ private int loadGLShader(int type, int resId) { String code = readRawTextFile(resId); int shader = GLES20.glCreateShader(type); GLES20.glShaderSource(shader, code); GLES20.glCompileShader(shader); // Get the compilation status. final int[] compileStatus = new int[1]; GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, compileStatus, 0); // If the compilation failed, delete the shader. if (compileStatus[0] == 0) { Log.e(TAG, "Error compiling shader: " + GLES20.glGetShaderInfoLog(shader)); GLES20.glDeleteShader(shader); shader = 0; } if (shader == 0) { throw new RuntimeException("Error creating shader."); } return shader; } /** * Checks if we've had an error inside of OpenGL ES, and if so what that error is. * @param func */ private static void checkGLError(String func) { int error; while ((error = GLES20.glGetError()) != GLES20.GL_NO_ERROR) { Log.e(TAG, func + ": glError " + error); throw new RuntimeException(func + ": glError " + error); } } /** * Sets the view to our CardboardView and initializes the transformation matrices we will use * to render our scene. * @param savedInstanceState */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); Intent streamingIntent = new Intent(this, SyncStreamingService.class); startService(streamingIntent); Intent ftpIntent = new Intent(this, SyncFtpService.class); startService(ftpIntent); setContentView(R.layout.activity_main); CardboardView cardboardView = (CardboardView) findViewById(R.id.cardboard_view); cardboardView.setRenderer(this); setCardboardView(cardboardView); mCamera = new float[16]; mView = new float[16]; mModelViewProjection = new float[16]; mModelView = new float[16]; mModelFloor = new float[16]; mHeadView = new float[16]; mOverlayView = (CardboardOverlayView) findViewById(R.id.overlay); mRandom = new Random(); // Set up a local receiver to listen for new saved files. IntentFilter intentFilter = new IntentFilter(); intentFilter.addAction(FileDownloadService.SAVED_NEW_FILE); LocalBroadcastManager.getInstance(this).registerReceiver(mMessageReceiver, intentFilter); } @Override public void onDestroy() { super.onDestroy(); // Unregister from receiver. LocalBroadcastManager.getInstance(this).unregisterReceiver(mMessageReceiver); } /** * Listens for a broadcast from the FileDownloadService. */ private final BroadcastReceiver mMessageReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { // New file was saved, update a random page object in the world. String[] fileList = fileList(); String fileName = fileList[fileList.length - 1]; // Last file int random = mRandom.nextInt(4); // Random position try { FileInputStream fis = openFileInput(fileName); mPages[random] = new Page(fis, mGlProgram, random); mOverlayView.show3DToast("Displaying new saved page"); } catch (FileNotFoundException e) { e.printStackTrace(); } } }; @Override public void onRendererShutdown() { Log.i(TAG, "onRendererShutdown"); } @Override public void onSurfaceChanged(int width, int height) { Log.i(TAG, "onSurfaceChanged"); } /** * Creates the buffers we use to store information about the 3D world. OpenGL doesn't use Java * arrays, but rather needs data in a format it can understand. Hence we use ByteBuffers. * @param config The EGL configuration used when creating the surface. */ @Override public void onSurfaceCreated(EGLConfig config) { Log.i(TAG, "onSurfaceCreated"); // make a floor ByteBuffer bbFloorVertices = ByteBuffer.allocateDirect(DATA.FLOOR_COORDS.length * 4); bbFloorVertices.order(ByteOrder.nativeOrder()); mFloorVertices = bbFloorVertices.asFloatBuffer(); mFloorVertices.put(DATA.FLOOR_COORDS); mFloorVertices.position(0); ByteBuffer bbFloorNormals = ByteBuffer.allocateDirect(DATA.FLOOR_NORMALS.length * 4); bbFloorNormals.order(ByteOrder.nativeOrder()); mFloorNormals = bbFloorNormals.asFloatBuffer(); mFloorNormals.put(DATA.FLOOR_NORMALS); mFloorNormals.position(0); ByteBuffer bbFloorColors = ByteBuffer.allocateDirect(DATA.FLOOR_COLORS.length * 4); bbFloorColors.order(ByteOrder.nativeOrder()); mFloorColors = bbFloorColors.asFloatBuffer(); mFloorColors.put(DATA.FLOOR_COLORS); mFloorColors.position(0); int vertexShader = loadGLShader(GLES20.GL_VERTEX_SHADER, R.raw.light_vertex); int gridShader = loadGLShader(GLES20.GL_FRAGMENT_SHADER, R.raw.grid_fragment); mGlProgram = GLES20.glCreateProgram(); GLES20.glAttachShader(mGlProgram, vertexShader); GLES20.glAttachShader(mGlProgram, gridShader); GLES20.glLinkProgram(mGlProgram); GLES20.glEnable(GLES20.GL_DEPTH_TEST); Matrix.setIdentityM(mModelFloor, 0); Matrix.translateM(mModelFloor, 0, 0, -mFloorDepth, 0); // Floor appears below user // Create the placeholder pages. mPages = new Page[4]; mPages[0] = new Page(getResources().openRawResource(R.raw.boogie_board), mGlProgram, 0); mPages[1] = new Page(getResources().openRawResource(R.raw.house), mGlProgram, 1); mPages[2] = new Page(getResources().openRawResource(R.raw.placeholder), mGlProgram, 2); mPages[3] = new Page(getResources().openRawResource(R.raw.cylinder), mGlProgram, 3); checkGLError("onSurfaceCreated"); } /** * Converts a raw text file into a string. * @param resId The resource ID of the raw text file about to be turned into a shader. * @return */ private String readRawTextFile(int resId) { InputStream inputStream = getResources().openRawResource(resId); try { BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream)); StringBuilder sb = new StringBuilder(); String line; while ((line = reader.readLine()) != null) { sb.append(line).append("\n"); } reader.close(); return sb.toString(); } catch (IOException e) { e.printStackTrace(); } return ""; } /** * Prepares OpenGL ES before we draw a frame. * @param headTransform The head transformation in the new frame. */ @Override public void onNewFrame(HeadTransform headTransform) { GLES20.glUseProgram(mGlProgram); GLES20.glClearColor(0f, 0f, 0f, 1.0f); // Dark background so text shows up well mModelViewProjectionParam = GLES20.glGetUniformLocation(mGlProgram, "u_MVP"); mLightPosParam = GLES20.glGetUniformLocation(mGlProgram, "u_LightPos"); mModelViewParam = GLES20.glGetUniformLocation(mGlProgram, "u_MVMatrix"); mModelParam = GLES20.glGetUniformLocation(mGlProgram, "u_Model"); mIsFloorParam = GLES20.glGetUniformLocation(mGlProgram, "u_IsFloor"); // Build the camera matrix and apply it to the ModelView. Matrix.setLookAtM(mCamera, 0, 0.0f, 0.0f, CAMERA_Z, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f); headTransform.getHeadView(mHeadView, 0); checkGLError("onReadyToDraw"); } /** * Draws a frame for an eye. The transformation for that eye (from the camera) is passed in as * a parameter. * @param transform The transformations to apply to render this eye. */ @Override public void onDrawEye(EyeTransform transform) { GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT | GLES20.GL_DEPTH_BUFFER_BIT); GLES20.glClearColor(0f, 0f, 0f, 1.00f); // Dark background so text shows up well mPositionParam = GLES20.glGetAttribLocation(mGlProgram, "a_Position"); mNormalParam = GLES20.glGetAttribLocation(mGlProgram, "a_Normal"); mColorParam = GLES20.glGetAttribLocation(mGlProgram, "a_Color"); GLES20.glEnableVertexAttribArray(mPositionParam); GLES20.glEnableVertexAttribArray(mNormalParam); GLES20.glEnableVertexAttribArray(mColorParam); checkGLError("mColorParam"); // Apply the eye transformation to the camera. Matrix.multiplyMM(mView, 0, transform.getEyeView(), 0, mCamera, 0); // Set the position of the light Matrix.multiplyMV(mLightPosInEyeSpace, 0, mView, 0, mLightPosInWorldSpace, 0); GLES20.glUniform3f(mLightPosParam, mLightPosInEyeSpace[0], mLightPosInEyeSpace[1], mLightPosInEyeSpace[2]); // Draw the pages. for (Page page : mPages) { page.draw(transform.getPerspective(), mView); checkGLError("Drawing page"); } // Set mModelView for the floor, so we draw floor in the correct location Matrix.multiplyMM(mModelView, 0, mView, 0, mModelFloor, 0); Matrix.multiplyMM(mModelViewProjection, 0, transform.getPerspective(), 0, mModelView, 0); drawFloor(transform.getPerspective()); } @Override public void onFinishFrame(Viewport viewport) { } /** * Draw the floor. This feeds in data for the floor into the shader. Note that this doesn't * feed in data about position of the light, so if we rewrite our code to draw the floor first, * the lighting might look strange. */ public void drawFloor(float[] perspective) { // This is the floor! GLES20.glUniform1f(mIsFloorParam, 1f); // Set ModelView, MVP, position, normals, and color GLES20.glUniformMatrix4fv(mModelParam, 1, false, mModelFloor, 0); GLES20.glUniformMatrix4fv(mModelViewParam, 1, false, mModelView, 0); GLES20.glUniformMatrix4fv(mModelViewProjectionParam, 1, false, mModelViewProjection, 0); GLES20.glVertexAttribPointer(mPositionParam, COORDS_PER_VERTEX, GLES20.GL_FLOAT, false, 0, mFloorVertices); GLES20.glVertexAttribPointer(mNormalParam, 3, GLES20.GL_FLOAT, false, 0, mFloorNormals); GLES20.glVertexAttribPointer(mColorParam, 4, GLES20.GL_FLOAT, false, 0, mFloorColors); GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, 6); checkGLError("drawing floor"); } @Override public void onCardboardTrigger() { } }