com.google.vrtoolkit.cardboard.samples.treasurehunt.MainActivity.java Source code

Java tutorial

Introduction

Here is the source code for com.google.vrtoolkit.cardboard.samples.treasurehunt.MainActivity.java

Source

/* vim: set ts=2 sw=2: */

/*
 * Copyright 2014 Google Inc. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.google.vrtoolkit.cardboard.samples.treasurehunt;

import com.google.vrtoolkit.cardboard.CardboardActivity;
import com.google.vrtoolkit.cardboard.CardboardView;
import com.google.vrtoolkit.cardboard.Eye;
import com.google.vrtoolkit.cardboard.HeadTransform;
import com.google.vrtoolkit.cardboard.Viewport;
import com.google.vrtoolkit.cardboard.audio.CardboardAudioEngine;

import android.content.Context;
import android.opengl.GLES20;
import android.opengl.Matrix;
import android.os.Bundle;
import android.os.Vibrator;
import android.util.Log;

import java.io.BufferedReader;
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 javax.microedition.khronos.egl.EGLConfig;

import org.java_websocket.client.WebSocketClient;
import org.java_websocket.handshake.ServerHandshake;

import java.net.URI;
import java.net.URISyntaxException;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import java.net.Socket;
import java.io.OutputStream;

/**
 * A Cardboard sample application.
 */
public class MainActivity extends CardboardActivity implements CardboardView.StereoRenderer {
    private static final String TAG = "MainActivity";

    private static final float Z_NEAR = 0.1f;
    private static final float Z_FAR = 100.0f;

    private static final float CAMERA_Z = 0.01f;
    private static final float TIME_DELTA = 0.3f;

    private static final float YAW_LIMIT = 0.12f;
    private static final float PITCH_LIMIT = 0.12f;

    private static final int COORDS_PER_VERTEX = 3;

    // We keep the light always position just above the user.
    private static final float[] LIGHT_POS_IN_WORLD_SPACE = new float[] { 0.0f, 2.0f, 0.0f, 1.0f };

    private static final float MIN_MODEL_DISTANCE = 3.0f;
    private static final float MAX_MODEL_DISTANCE = 7.0f;

    private static final String SOUND_FILE = "cube_sound.wav";

    private final float[] lightPosInEyeSpace = new float[4];

    private FloatBuffer floorVertices;
    private FloatBuffer floorColors;
    private FloatBuffer floorNormals;

    private FloatBuffer cubeVertices;
    private FloatBuffer cubeColors;
    private FloatBuffer cubeFoundColors;
    private FloatBuffer cubeNormals;

    private FloatBuffer miniCubeVertices;
    private FloatBuffer miniCubeColors;
    private FloatBuffer miniCubeFoundColors;
    private FloatBuffer miniCubeNormals;

    private int cubeProgram;
    private int miniCubeProgram;
    private int floorProgram;

    private int cubePositionParam;
    private int cubeNormalParam;
    private int cubeColorParam;
    private int cubeModelParam;
    private int cubeModelViewParam;
    private int cubeModelViewProjectionParam;
    private int cubeLightPosParam;

    private int miniCubePositionParam;
    private int miniCubeNormalParam;
    private int miniCubeColorParam;
    private int miniCubeModelParam;
    private int miniCubeModelViewParam;
    private int miniCubeModelViewProjectionParam;
    private int miniCubeLightPosParam;

    private int floorPositionParam;
    private int floorNormalParam;
    private int floorColorParam;
    private int floorModelParam;
    private int floorModelViewParam;
    private int floorModelViewProjectionParam;
    private int floorLightPosParam;

    private float[] modelCube;
    private float[] modelMiniCube;
    private float[] camera;
    private float[] view;
    private float[] headView;
    private float[] modelViewProjection;
    private float[] modelView;
    private float[] modelFloor;

    private float[] modelPosition;
    private float[] headRotation;

    private int score = 0;
    private float objectDistance = MAX_MODEL_DISTANCE / 2.0f;
    private float floorDepth = 20f;

    private Vibrator vibrator;
    private CardboardOverlayView overlayView;

    private CardboardAudioEngine cardboardAudioEngine;
    private volatile int soundId = CardboardAudioEngine.INVALID_ID;

    //Websocket things
    private WebSocketClient mWebSocketClient;

    private float[] handPos;
    private float WALL_DIST = 4.25f;

    /**
     * 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 The shader object handler.
     */
    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 label Label to report in case of error.
     */
    private static void checkGLError(String label) {
        int error;
        while ((error = GLES20.glGetError()) != GLES20.GL_NO_ERROR) {
            Log.e(TAG, label + ": glError " + error);
            throw new RuntimeException(label + ": glError " + error);
        }
    }

    /**
     * Sets the view to our CardboardView and initializes the transformation matrices we will use
     * to render our scene.
     */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setContentView(R.layout.common_ui);
        CardboardView cardboardView = (CardboardView) findViewById(R.id.cardboard_view);
        cardboardView.setRestoreGLStateEnabled(false);
        cardboardView.setRenderer(this);
        setCardboardView(cardboardView);

        modelCube = new float[16];
        modelMiniCube = new float[16];
        camera = new float[16];
        view = new float[16];
        modelViewProjection = new float[16];
        modelView = new float[16];
        modelFloor = new float[16];
        // Model first appears directly in front of user.
        //modelPosition = new float[] {0.0f, 0.0f, -MAX_MODEL_DISTANCE / 0.5f};
        modelPosition = new float[] { 0.0f, 0.0f, -WALL_DIST };
        headRotation = new float[4];
        headView = new float[16];
        vibrator = (Vibrator) getSystemService(Context.VIBRATOR_SERVICE);

        overlayView = (CardboardOverlayView) findViewById(R.id.overlay);
        overlayView.show3DToast("Pull the magnet when you find an object.");

        // Initialize 3D audio engine.
        cardboardAudioEngine = new CardboardAudioEngine(getAssets(), CardboardAudioEngine.RenderingQuality.HIGH);

        connectWebSocket();
        handPos = new float[3];
    }

    @Override
    public void onPause() {
        cardboardAudioEngine.pause();
        super.onPause();
    }

    @Override
    public void onResume() {
        super.onResume();
        cardboardAudioEngine.resume();
    }

    @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.
     *
     * <p>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");
        GLES20.glClearColor(0.1f, 0.1f, 0.1f, 0.5f); // Dark background so text shows up well.

        ByteBuffer bbVertices = ByteBuffer.allocateDirect(WorldLayoutData.CUBE_COORDS.length * 4);
        bbVertices.order(ByteOrder.nativeOrder());
        cubeVertices = bbVertices.asFloatBuffer();
        cubeVertices.put(WorldLayoutData.CUBE_COORDS);
        cubeVertices.position(0);

        ByteBuffer bbColors = ByteBuffer.allocateDirect(WorldLayoutData.CUBE_COLORS.length * 4);
        bbColors.order(ByteOrder.nativeOrder());
        cubeColors = bbColors.asFloatBuffer();
        cubeColors.put(WorldLayoutData.CUBE_COLORS);
        cubeColors.position(0);

        ByteBuffer bbFoundColors = ByteBuffer.allocateDirect(WorldLayoutData.CUBE_FOUND_COLORS.length * 4);
        bbFoundColors.order(ByteOrder.nativeOrder());
        cubeFoundColors = bbFoundColors.asFloatBuffer();
        cubeFoundColors.put(WorldLayoutData.CUBE_FOUND_COLORS);
        cubeFoundColors.position(0);

        ByteBuffer bbNormals = ByteBuffer.allocateDirect(WorldLayoutData.CUBE_NORMALS.length * 4);
        bbNormals.order(ByteOrder.nativeOrder());
        cubeNormals = bbNormals.asFloatBuffer();
        cubeNormals.put(WorldLayoutData.CUBE_NORMALS);
        cubeNormals.position(0);

        ByteBuffer mcbbVertices = ByteBuffer.allocateDirect(WorldLayoutData.MINI_CUBE_COORDS.length * 4);
        mcbbVertices.order(ByteOrder.nativeOrder());
        miniCubeVertices = mcbbVertices.asFloatBuffer();
        miniCubeVertices.put(WorldLayoutData.MINI_CUBE_COORDS);
        miniCubeVertices.position(0);

        ByteBuffer mcbbColors = ByteBuffer.allocateDirect(WorldLayoutData.MINI_CUBE_COLORS.length * 4);
        mcbbColors.order(ByteOrder.nativeOrder());
        miniCubeColors = mcbbColors.asFloatBuffer();
        miniCubeColors.put(WorldLayoutData.MINI_CUBE_COLORS);
        miniCubeColors.position(0);

        ByteBuffer mcbbNormals = ByteBuffer.allocateDirect(WorldLayoutData.CUBE_NORMALS.length * 4);
        mcbbNormals.order(ByteOrder.nativeOrder());
        miniCubeNormals = mcbbNormals.asFloatBuffer();
        miniCubeNormals.put(WorldLayoutData.CUBE_NORMALS);
        miniCubeNormals.position(0);

        // make a floor
        ByteBuffer bbFloorVertices = ByteBuffer.allocateDirect(WorldLayoutData.FLOOR_COORDS.length * 4);
        bbFloorVertices.order(ByteOrder.nativeOrder());
        floorVertices = bbFloorVertices.asFloatBuffer();
        floorVertices.put(WorldLayoutData.FLOOR_COORDS);
        floorVertices.position(0);

        ByteBuffer bbFloorNormals = ByteBuffer.allocateDirect(WorldLayoutData.FLOOR_NORMALS.length * 4);
        bbFloorNormals.order(ByteOrder.nativeOrder());
        floorNormals = bbFloorNormals.asFloatBuffer();
        floorNormals.put(WorldLayoutData.FLOOR_NORMALS);
        floorNormals.position(0);

        ByteBuffer bbFloorColors = ByteBuffer.allocateDirect(WorldLayoutData.FLOOR_COLORS.length * 4);
        bbFloorColors.order(ByteOrder.nativeOrder());
        floorColors = bbFloorColors.asFloatBuffer();
        floorColors.put(WorldLayoutData.FLOOR_COLORS);
        floorColors.position(0);

        int vertexShader = loadGLShader(GLES20.GL_VERTEX_SHADER, R.raw.light_vertex);
        int gridShader = loadGLShader(GLES20.GL_FRAGMENT_SHADER, R.raw.grid_fragment);
        int passthroughShader = loadGLShader(GLES20.GL_FRAGMENT_SHADER, R.raw.passthrough_fragment);

        cubeProgram = GLES20.glCreateProgram();
        GLES20.glAttachShader(cubeProgram, vertexShader);
        GLES20.glAttachShader(cubeProgram, passthroughShader);
        GLES20.glLinkProgram(cubeProgram);
        GLES20.glUseProgram(cubeProgram);

        checkGLError("Cube program");

        cubePositionParam = GLES20.glGetAttribLocation(cubeProgram, "a_Position");
        cubeNormalParam = GLES20.glGetAttribLocation(cubeProgram, "a_Normal");
        cubeColorParam = GLES20.glGetAttribLocation(cubeProgram, "a_Color");

        cubeModelParam = GLES20.glGetUniformLocation(cubeProgram, "u_Model");
        cubeModelViewParam = GLES20.glGetUniformLocation(cubeProgram, "u_MVMatrix");
        cubeModelViewProjectionParam = GLES20.glGetUniformLocation(cubeProgram, "u_MVP");
        cubeLightPosParam = GLES20.glGetUniformLocation(cubeProgram, "u_LightPos");

        GLES20.glEnableVertexAttribArray(cubePositionParam);
        GLES20.glEnableVertexAttribArray(cubeNormalParam);
        GLES20.glEnableVertexAttribArray(cubeColorParam);

        checkGLError("Cube program params");

        //Minicube
        miniCubeProgram = GLES20.glCreateProgram();
        GLES20.glAttachShader(miniCubeProgram, vertexShader);
        GLES20.glAttachShader(miniCubeProgram, passthroughShader);
        GLES20.glLinkProgram(miniCubeProgram);
        GLES20.glUseProgram(miniCubeProgram);

        checkGLError("Cube program");

        miniCubePositionParam = GLES20.glGetAttribLocation(miniCubeProgram, "a_Position");
        miniCubeNormalParam = GLES20.glGetAttribLocation(miniCubeProgram, "a_Normal");
        miniCubeColorParam = GLES20.glGetAttribLocation(miniCubeProgram, "a_Color");

        miniCubeModelParam = GLES20.glGetUniformLocation(miniCubeProgram, "u_Model");
        miniCubeModelViewParam = GLES20.glGetUniformLocation(miniCubeProgram, "u_MVMatrix");
        miniCubeModelViewProjectionParam = GLES20.glGetUniformLocation(miniCubeProgram, "u_MVP");
        miniCubeLightPosParam = GLES20.glGetUniformLocation(miniCubeProgram, "u_LightPos");

        GLES20.glEnableVertexAttribArray(miniCubePositionParam);
        GLES20.glEnableVertexAttribArray(miniCubeNormalParam);
        GLES20.glEnableVertexAttribArray(miniCubeColorParam);

        checkGLError("Cube program params");

        floorProgram = GLES20.glCreateProgram();
        GLES20.glAttachShader(floorProgram, vertexShader);
        GLES20.glAttachShader(floorProgram, gridShader);
        GLES20.glLinkProgram(floorProgram);
        GLES20.glUseProgram(floorProgram);

        checkGLError("Floor program");

        floorModelParam = GLES20.glGetUniformLocation(floorProgram, "u_Model");
        floorModelViewParam = GLES20.glGetUniformLocation(floorProgram, "u_MVMatrix");
        floorModelViewProjectionParam = GLES20.glGetUniformLocation(floorProgram, "u_MVP");
        floorLightPosParam = GLES20.glGetUniformLocation(floorProgram, "u_LightPos");

        floorPositionParam = GLES20.glGetAttribLocation(floorProgram, "a_Position");
        floorNormalParam = GLES20.glGetAttribLocation(floorProgram, "a_Normal");
        floorColorParam = GLES20.glGetAttribLocation(floorProgram, "a_Color");

        GLES20.glEnableVertexAttribArray(floorPositionParam);
        GLES20.glEnableVertexAttribArray(floorNormalParam);
        GLES20.glEnableVertexAttribArray(floorColorParam);

        checkGLError("Floor program params");

        Matrix.setIdentityM(modelFloor, 0);
        Matrix.translateM(modelFloor, 0, 0, -floorDepth, 0); // Floor appears below user.

        // Avoid any delays during start-up due to decoding of sound files.
        new Thread(new Runnable() {
            public void run() {
                // Start spatial audio playback of SOUND_FILE at the model postion. The returned
                //soundId handle is stored and allows for repositioning the sound object whenever
                // the cube position changes.
                cardboardAudioEngine.preloadSoundFile(SOUND_FILE);
                soundId = cardboardAudioEngine.createSoundObject(SOUND_FILE);
                cardboardAudioEngine.setSoundObjectPosition(soundId, modelPosition[0], modelPosition[1],
                        modelPosition[2]);
                cardboardAudioEngine.playSound(soundId, true /* looped playback */);
            }
        }).start();

        updateModelPosition();

        checkGLError("onSurfaceCreated");
    }

    /**
     * Updates the cube model position.
     */
    private void updateModelPosition() {
        Matrix.setIdentityM(modelCube, 0);
        Matrix.translateM(modelCube, 0, modelPosition[0], modelPosition[1], modelPosition[2]);

        // Update the sound location to match it with the new cube position.
        if (soundId != CardboardAudioEngine.INVALID_ID) {
            cardboardAudioEngine.setSoundObjectPosition(soundId, modelPosition[0], modelPosition[1],
                    modelPosition[2]);
        }
        checkGLError("updateCubePosition");
    }

    private float[] quaternionToMatrix(float[] _data, float x, float y, float z, float w) {
        float x2, y2, z2, xx, xy, xz, yy, yz, zz, wx, wy, wz;
        //if(null == _data)
        //_data = new float[16];
        // calculate coefficients
        x2 = x + x;
        y2 = y + y;
        z2 = z + z;

        xx = x * x2;
        xy = x * y2;
        xz = x * z2;
        yy = y * y2;
        yz = y * z2;
        zz = z * z2;
        wx = w * x2;
        wy = w * y2;
        wz = w * z2;

        _data[0] = 1.0f - (yy + zz);
        _data[1] = xy - wz;
        _data[2] = xz + wy;
        _data[3] = 0.0f;

        _data[4] = xy + wz;
        _data[5] = 1.0f - (xx + zz);
        _data[6] = yz - wx;
        _data[7] = 0.0f;

        _data[8] = xz - wy;
        _data[9] = yz + wx;
        _data[10] = 1.0f - (xx + yy);
        _data[11] = 0.0f;

        _data[12] = 0.0f;
        _data[13] = 0.0f;
        _data[14] = 0.0f;
        _data[15] = 1.0f;

        return _data;
    }

    //Takes quaternion
    private void updateMiniCubePosition(float x, float y, float z, float w) {
        //Matrix.setIdentityM(modelMiniCube, 0);
        //We normalize the distance as well
        //Matrix.translateM(modelMiniCube, 0, handPos[0]/(float)50.0, handPos[1]/(float)50.0, handPos[2]/(float)50.0);
        //This accounts for the reversing
        //Matrix.translateM(modelMiniCube, 0, -handPos[0]/(float)50.0, -handPos[2]/(float)50.0, -handPos[1]/(float)50.0);

        float[] temp_modelMiniCube = new float[16];
        float[] temp_mRotate = new float[16];
        Matrix.setIdentityM(temp_modelMiniCube, 0);
        Matrix.translateM(temp_modelMiniCube, 0, -handPos[0] / (float) 50.0, -handPos[2] / (float) 50.0,
                -handPos[1] / (float) 50.0);
        //Matrix.setIdentityM(temp_mRotate, 0);
        //Matrix.rotateM(temp_mRotate, 0, 45, 1, 0, 0); //This rotates the cube
        quaternionToMatrix(temp_mRotate, -x, -y, -z, w);

        Matrix.multiplyMM(modelMiniCube, 0, temp_mRotate, 0, temp_modelMiniCube, 0);

        Log.i("Armo", String.format("%f", -handPos[1] / (float) 50));
        if (-handPos[1] / (float) 50 < -WALL_DIST + 1) { //offset because this is cube center, we want the wall position
            sendArmoRequest(true);
            Log.i("Armo", "Sending_lock");
        } else {
            sendArmoRequest(false);
            Log.i("Armo", "Sending_unlock");
        }

    }

    private void updateMiniCubePosition(float pitch, float yaw, float roll) {
        float[] temp_modelMiniCube = new float[16];
        float[] temp_mRotate = new float[16];
        Matrix.setIdentityM(temp_modelMiniCube, 0);
        Matrix.translateM(temp_modelMiniCube, 0, -handPos[0] / (float) 50.0, -handPos[2] / (float) 50.0,
                -handPos[1] / (float) 50.0);
        Matrix.setIdentityM(temp_mRotate, 0);
        Matrix.rotateM(temp_mRotate, 0, 45, 1, 0, 0); //This rotates the cube
        //quaternionToMatrix(temp_mRotate, x, y, z, w);

        Matrix.multiplyMM(modelMiniCube, 0, temp_mRotate, 0, temp_modelMiniCube, 0);
    }

    /**
     * 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 The context of the text file, or null in case of error.
     */
    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 null;
    }

    /**
     * Prepares OpenGL ES before we draw a frame.
     *
     * @param headTransform The head transformation in the new frame.
     */
    @Override
    public void onNewFrame(HeadTransform headTransform) {
        // Build the Model part of the ModelView matrix.
        //Matrix.rotateM(modelCube, 0, TIME_DELTA, 0.5f, 0.5f, 1.0f);

        // Build the camera matrix and apply it to the ModelView.
        Matrix.setLookAtM(camera, 0, 0.0f, 0.0f, CAMERA_Z, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f);

        headTransform.getHeadView(headView, 0);

        // Update the 3d audio engine with the most recent head rotation.
        headTransform.getQuaternion(headRotation, 0);
        cardboardAudioEngine.setHeadRotation(headRotation[0], headRotation[1], headRotation[2], headRotation[3]);

        checkGLError("onReadyToDraw");

        //float[] headAngles = new float[3];
        //headTransform.getEulerAngles(headAngles, 0)

        //float[] quat = new float[4];
        //headTransform.getQuaternion(quat, 0);
        updateMiniCubePosition(headRotation[0], headRotation[1], headRotation[2], headRotation[3]);
    }

    /**
     * Draws a frame for an eye.
     *
     * @param eye The eye to render. Includes all required transformations.
     */
    @Override
    public void onDrawEye(Eye eye) {
        GLES20.glEnable(GLES20.GL_DEPTH_TEST);
        GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT | GLES20.GL_DEPTH_BUFFER_BIT);

        checkGLError("colorParam");

        // Apply the eye transformation to the camera.
        Matrix.multiplyMM(view, 0, eye.getEyeView(), 0, camera, 0);

        // Set the position of the light
        Matrix.multiplyMV(lightPosInEyeSpace, 0, view, 0, LIGHT_POS_IN_WORLD_SPACE, 0);

        // Build the ModelView and ModelViewProjection matrices
        // for calculating cube position and light.
        float[] perspective = eye.getPerspective(Z_NEAR, Z_FAR);
        Matrix.multiplyMM(modelView, 0, view, 0, modelCube, 0);
        Matrix.multiplyMM(modelViewProjection, 0, perspective, 0, modelView, 0);
        drawCube();

        float[] mRotationCube = new float[16];
        //Not sure if we can just reuse modelView like this
        Matrix.multiplyMM(modelView, 0, view, 0, modelMiniCube, 0);
        //Matrix.multiplyMM(mRotationCube, 0, view, 0, modelView, 0);

        //Not working 
        //float[] ident = new Matrix(eye.getPerspective(Z_NEAR, Z_FAR));
        //Matrix.setIdentityM(ident, 0); //Cheating so that we have relative hand movement
        //end not working

        Matrix.multiplyMM(modelViewProjection, 0, perspective, 0, modelView, 0);
        //Matrix.multiplyMM(modelViewProjection, 0, perspective, 0, mRotationCube, 0);

        //doesn't work :(
        //Matrix.multiplyMM(modelViewProjection, 0, perspective, 0, modelMiniCube, 0);

        drawMiniCube();

        // Set modelView for the floor, so we draw floor in the correct location
        Matrix.multiplyMM(modelView, 0, view, 0, modelFloor, 0);
        Matrix.multiplyMM(modelViewProjection, 0, perspective, 0, modelView, 0);
        drawFloor();
    }

    @Override
    public void onFinishFrame(Viewport viewport) {
    }

    /**
     * Draw the cube.
     *
     * <p>We've set all of our transformation matrices. Now we simply pass them into the shader.
     */
    public void drawCube() {
        GLES20.glUseProgram(cubeProgram);

        GLES20.glUniform3fv(cubeLightPosParam, 1, lightPosInEyeSpace, 0);

        // Set the Model in the shader, used to calculate lighting
        GLES20.glUniformMatrix4fv(cubeModelParam, 1, false, modelCube, 0);

        // Set the ModelView in the shader, used to calculate lighting
        GLES20.glUniformMatrix4fv(cubeModelViewParam, 1, false, modelView, 0);

        // Set the position of the cube
        GLES20.glVertexAttribPointer(cubePositionParam, COORDS_PER_VERTEX, GLES20.GL_FLOAT, false, 0, cubeVertices);

        // Set the ModelViewProjection matrix in the shader.
        GLES20.glUniformMatrix4fv(cubeModelViewProjectionParam, 1, false, modelViewProjection, 0);

        // Set the normal positions of the cube, again for shading
        GLES20.glVertexAttribPointer(cubeNormalParam, 3, GLES20.GL_FLOAT, false, 0, cubeNormals);
        GLES20.glVertexAttribPointer(cubeColorParam, 4, GLES20.GL_FLOAT, false, 0,
                isLookingAtObject() ? cubeFoundColors : cubeColors);

        GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, 36);
        checkGLError("Drawing cube");
    }

    public void drawMiniCube() {
        GLES20.glUseProgram(miniCubeProgram);

        GLES20.glUniform3fv(miniCubeLightPosParam, 1, lightPosInEyeSpace, 0);

        // Set the Model in the shader, used to calculate lighting
        GLES20.glUniformMatrix4fv(miniCubeModelParam, 1, false, modelMiniCube, 0);

        // Set the ModelView in the shader, used to calculate lighting
        GLES20.glUniformMatrix4fv(miniCubeModelViewParam, 1, false, modelView, 0);

        // Set the position of the miniCube
        GLES20.glVertexAttribPointer(miniCubePositionParam, COORDS_PER_VERTEX, GLES20.GL_FLOAT, false, 0,
                miniCubeVertices);

        // Set the ModelViewProjection matrix in the shader.
        GLES20.glUniformMatrix4fv(miniCubeModelViewProjectionParam, 1, false, modelViewProjection, 0);

        // Set the normal positions of the miniCube, again for shading
        GLES20.glVertexAttribPointer(miniCubeNormalParam, 3, GLES20.GL_FLOAT, false, 0, miniCubeNormals);
        GLES20.glVertexAttribPointer(miniCubeColorParam, 4, GLES20.GL_FLOAT, false, 0, miniCubeColors);

        GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, 36);
        checkGLError("Drawing miniCube");
    }

    /**
     * Draw the floor.
     *
     * <p>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() {
        GLES20.glUseProgram(floorProgram);

        // Set ModelView, MVP, position, normals, and color.
        GLES20.glUniform3fv(floorLightPosParam, 1, lightPosInEyeSpace, 0);
        GLES20.glUniformMatrix4fv(floorModelParam, 1, false, modelFloor, 0);
        GLES20.glUniformMatrix4fv(floorModelViewParam, 1, false, modelView, 0);
        GLES20.glUniformMatrix4fv(floorModelViewProjectionParam, 1, false, modelViewProjection, 0);
        GLES20.glVertexAttribPointer(floorPositionParam, COORDS_PER_VERTEX, GLES20.GL_FLOAT, false, 0,
                floorVertices);
        GLES20.glVertexAttribPointer(floorNormalParam, 3, GLES20.GL_FLOAT, false, 0, floorNormals);
        GLES20.glVertexAttribPointer(floorColorParam, 4, GLES20.GL_FLOAT, false, 0, floorColors);

        GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, 6);

        checkGLError("drawing floor");
    }

    /**
     * Called when the Cardboard trigger is pulled.
     */
    @Override
    public void onCardboardTrigger() {
        Log.i(TAG, "onCardboardTrigger");

        if (isLookingAtObject()) {
            score++;
            overlayView.show3DToast("Found it! Look around for another one.\nScore = " + score);
            //hideObject();
        } else {
            overlayView.show3DToast("Look around to find the object!");
        }

        // Always give user feedback.
        vibrator.vibrate(50);
    }

    /**
     * Find a new random position for the object.
     *
     * <p>We'll rotate it around the Y-axis so it's out of sight, and then up or down by a little bit.
     */
    private void hideObject() {
        float[] rotationMatrix = new float[16];
        float[] posVec = new float[4];

        // First rotate in XZ plane, between 90 and 270 deg away, and scale so that we vary
        // the object's distance from the user.
        float angleXZ = (float) Math.random() * 180 + 90;
        Matrix.setRotateM(rotationMatrix, 0, angleXZ, 0f, 1f, 0f);
        float oldObjectDistance = objectDistance;
        objectDistance = (float) Math.random() * (MAX_MODEL_DISTANCE - MIN_MODEL_DISTANCE) + MIN_MODEL_DISTANCE;
        float objectScalingFactor = objectDistance / oldObjectDistance;
        Matrix.scaleM(rotationMatrix, 0, objectScalingFactor, objectScalingFactor, objectScalingFactor);
        Matrix.multiplyMV(posVec, 0, rotationMatrix, 0, modelCube, 12);

        float angleY = (float) Math.random() * 80 - 40; // Angle in Y plane, between -40 and 40.
        angleY = (float) Math.toRadians(angleY);
        float newY = (float) Math.tan(angleY) * objectDistance;

        modelPosition[0] = posVec[0];
        modelPosition[1] = newY;
        modelPosition[2] = posVec[2];

        updateModelPosition();
    }

    /**
     * Check if user is looking at object by calculating where the object is in eye-space.
     *
     * @return true if the user is looking at the object.
     */
    private boolean isLookingAtObject() {
        float[] initVec = { 0, 0, 0, 1.0f };
        float[] objPositionVec = new float[4];

        // Convert object space to camera space. Use the headView from onNewFrame.
        Matrix.multiplyMM(modelView, 0, headView, 0, modelCube, 0);
        Matrix.multiplyMV(objPositionVec, 0, modelView, 0, initVec, 0);

        float pitch = (float) Math.atan2(objPositionVec[1], -objPositionVec[2]);
        float yaw = (float) Math.atan2(objPositionVec[0], -objPositionVec[2]);

        return Math.abs(pitch) < PITCH_LIMIT && Math.abs(yaw) < YAW_LIMIT;
    }

    private void connectWebSocket() {
        URI uri;
        try {
            //uri = new URI("ws://192.168.0.105:6437/");
            uri = new URI("ws://172.20.10.4:6437/");
        } catch (URISyntaxException e) {
            e.printStackTrace();
            return;
        }

        mWebSocketClient = new WebSocketClient(uri) {
            @Override
            public void onOpen(ServerHandshake serverHandshake) {
                Log.i("Websocket", "Opened");
            }

            @Override
            public void onMessage(String s) {
                final String message = s;
                //Log.i("Websocket_message", s);
                try {
                    JSONObject obj = new JSONObject(s);
                    JSONArray pos = obj.getJSONArray("hands").getJSONObject(0).getJSONArray("palmPosition");
                    handPos[0] = (float) pos.getDouble(0);
                    handPos[1] = (float) pos.getDouble(1);
                    handPos[2] = (float) pos.getDouble(2);
                    Log.i("Websocket_json", String.format("(%f, %f, %f)", handPos[0], handPos[1], handPos[2]));
                } catch (JSONException e) {
                    Log.i("Websocket_json", "Not the right json obj");
                }
            }

            @Override
            public void onClose(int i, String s, boolean b) {
                Log.i("Websocket", "Closed " + s);
            }

            @Override
            public void onError(Exception e) {
                Log.i("Websocket", "Error " + e.getMessage());
            }
        };
        mWebSocketClient.connect();
    }

    //private String ARMO_HOST = "192.168.0.107";
    private String ARMO_HOST = "172.20.10.5";
    private boolean armo_locked = false;

    private void sendArmoRequest(boolean _lock) {
        final boolean lock = _lock;

        if (lock == false) {
            if (armo_locked == false) {
                return;
            }
        }
        if (lock) {
            if (armo_locked == true) {
                return;
            }
        }
        new Thread(new Runnable() {
            public void run() {
                try {
                    String host = ARMO_HOST;
                    Socket socket = new Socket(host, 5000);
                    String request = "GET /position/180 HTTP/1.0\r\n\r\n";
                    if (lock == false) {
                        if (armo_locked == false) {
                            return;
                        }
                        armo_locked = false;
                    }
                    if (lock) {
                        if (armo_locked == true) {
                            return;
                        }
                        request = "GET /position/85 HTTP/1.0\r\n\r\n";
                        armo_locked = true;
                    }
                    OutputStream os = socket.getOutputStream();
                    os.write(request.getBytes());
                    os.flush();

                    InputStream is = socket.getInputStream();
                    int ch;
                    while ((ch = is.read()) != -1)
                        System.out.print((char) ch);
                    socket.close();
                } catch (IOException e) {
                    Log.e("Lock_request", "Something went wrong: " + e.getMessage());
                }
            }
        }).start();
    }
}