OpenGL ES 2.0 Android - cube rotation bug












1















I implemented the visualization of a colored 3d cube with OpenGL ES 2.0 that is used for an android app. My goal: the cube should react to swipe events (left, right, up, down), then the cube should be rotated in the corresponding direction.



The rotation:




  • stops if (current_angle % 90 == 0) -> so you swipe from one face to another

  • only one rotation at a time (and only around x- or y-axis)

  • should also be done in steps (ex.: 5 degrees), so it is not done instantly -> can be seen by the user


My code:



public class Cube20 {

private volatile int angleX;
private volatile int angleY;

private volatile Cube.RotateDirection rotateDirection;

public Cube.RotateDirection getRotateDirection() {
return rotateDirection;
}


public void setRotation(Cube.RotateDirection rotateDirection) {
this.rotateDirection = Cube.RotateDirection.getDirectionForID(rotateDirection.getId());
}

public int getAngleX() {
return angleX;
}

public void setAngleX(int angleX) {
this.angleX = angleX;
}

public int getAngleY() {
return angleY;
}

public void setAngleY(int angleY) {
this.angleY = angleY;
}

private final String vertexShaderCode =
// This matrix member variable provides a hook to manipulate
// the coordinates of the objects that use this vertex shader
"uniform mat4 uMVPMatrix;" +
"attribute vec4 vPosition;" +
"void main() {" +
// The matrix must be included as a modifier of gl_Position.
// Note that the uMVPMatrix factor *must be first* in order
// for the matrix multiplication product to be correct.
" gl_Position = uMVPMatrix * vPosition;" +
"}";

private final String fragmentShaderCode =
"precision mediump float;" +
"uniform vec4 vColor;" +
"void main() {" +
" gl_FragColor = vColor;" +
"}";

private final FloatBuffer vertexBuffer;
//private final ShortBuffer drawListBuffer;
private final int mProgram;
private final ShortBuffer indexBuffer;
private int mPositionHandle;
private int mColorHandle;
private int mMVPMatrixHandle;

// number of coordinates per vertex in this array
static final int COORDS_PER_VERTEX = 3;

private float vertices = { // Vertices of the 6 faces
// FRONT
-1.0f, -1.0f, 1.0f, // 0. left-bottom-front (0)
1.0f, -1.0f, 1.0f, // 1. right-bottom-front
-1.0f, 1.0f, 1.0f, // 2. left-top-front
1.0f, 1.0f, 1.0f, // 3. right-top-front
// BACK
1.0f, -1.0f, -1.0f, // 6. right-bottom-back (4)
-1.0f, -1.0f, -1.0f, // 4. left-bottom-back
1.0f, 1.0f, -1.0f, // 7. right-top-back
-1.0f, 1.0f, -1.0f, // 5. left-top-back
// LEFT
-1.0f, -1.0f, -1.0f, // 4. left-bottom-back (8)
-1.0f, -1.0f, 1.0f, // 0. left-bottom-front
-1.0f, 1.0f, -1.0f, // 5. left-top-back
-1.0f, 1.0f, 1.0f, // 2. left-top-front
// RIGHT
1.0f, -1.0f, 1.0f, // 1. right-bottom-front (12)
1.0f, -1.0f, -1.0f, // 6. right-bottom-back
1.0f, 1.0f, 1.0f, // 3. right-top-front
1.0f, 1.0f, -1.0f, // 7. right-top-back
// TOP
-1.0f, 1.0f, 1.0f, // 2. left-top-front
1.0f, 1.0f, 1.0f, // 3. right-top-front
-1.0f, 1.0f, -1.0f, // 5. left-top-back
1.0f, 1.0f, -1.0f, // 7. right-top-back
// BOTTOM
-1.0f, -1.0f, -1.0f, // 4. left-bottom-back
1.0f, -1.0f, -1.0f, // 6. right-bottom-back
-1.0f, -1.0f, 1.0f, // 0. left-bottom-front
1.0f, -1.0f, 1.0f // 1. right-bottom-front
};

private final int vertexStride = COORDS_PER_VERTEX * 4; // 4 bytes per vertex

private float colors = { // Colors of the 6 faces
{1.0f, 0.5f, 0.0f, 1.0f}, // 0. orange
{1.0f, 0.0f, 1.0f, 1.0f}, // 1. violet
{0.0f, 1.0f, 0.0f, 1.0f}, // 2. green
{0.0f, 0.0f, 1.0f, 1.0f}, // 3. blue
{1.0f, 0.0f, 0.0f, 1.0f}, // 4. red
{1.0f, 1.0f, 0.0f, 1.0f} // 5. yellow
};

short indices = {
0, 1, 2, 2, 1, 3, // FRONT
4, 5, 6, 6, 5, 7, // BACK
8, 9, 10, 10, 9, 11, // LEFT
12, 13, 14, 14, 13, 15, // RIGHT
16, 17, 18, 18, 17, 19, // TOP
20, 21, 22, 22, 21, 23, // BOTTOM

};
private int numFaces = 6;

/**
* Sets up the drawing object data for use in an OpenGL ES context.
*/
public Cube20() {
// initialize vertex byte buffer for shape coordinates
ByteBuffer bb = ByteBuffer.allocateDirect(
// (# of coordinate values * 4 bytes per float)
vertices.length * 4);
bb.order(ByteOrder.nativeOrder());
vertexBuffer = bb.asFloatBuffer();
vertexBuffer.put(vertices);
vertexBuffer.position(0);

// initialize byte buffer for the draw list
indexBuffer = ByteBuffer.allocateDirect(indices.length * 2).order(ByteOrder.nativeOrder()).asShortBuffer();
indexBuffer.put(indices).position(0);

// prepare shaders and OpenGL program
int vertexShader = RenderUtils.loadShader(
GLES20.GL_VERTEX_SHADER,
vertexShaderCode);
int fragmentShader = RenderUtils.loadShader(
GLES20.GL_FRAGMENT_SHADER,
fragmentShaderCode);

mProgram = GLES20.glCreateProgram(); // create empty OpenGL Program
GLES20.glAttachShader(mProgram, vertexShader); // add the vertex shader to program
GLES20.glAttachShader(mProgram, fragmentShader); // add the fragment shader to program
GLES20.glLinkProgram(mProgram); // create OpenGL program executables

this.rotateDirection = Cube.RotateDirection.NONE;
}

/**
* Encapsulates the OpenGL ES instructions for drawing this shape.
*
* @param mvpMatrix - The Model View Project matrix in which to draw
* this shape.
*/
public void draw(float mvpMatrix) {
// Add program to OpenGL environment
GLES20.glUseProgram(mProgram);

GLES20.glFrontFace(GLES20.GL_CCW);
GLES20.glEnable(GLES20.GL_CULL_FACE);
GLES20.glCullFace(GLES20.GL_BACK);

// scale
float scale_matrix = new float[16];
Matrix.setIdentityM(scale_matrix, 0);
Matrix.scaleM(scale_matrix, 0, 0.5f, 0.5f, 1);
Matrix.multiplyMM(mvpMatrix, 0, scale_matrix, 0, mvpMatrix, 0);

// get handle to vertex shader's vPosition member
mPositionHandle = GLES20.glGetAttribLocation(mProgram, "vPosition");

// Enable a handle to the triangle vertices
GLES20.glEnableVertexAttribArray(mPositionHandle);

// Prepare the triangle coordinate data
GLES20.glVertexAttribPointer(
mPositionHandle, COORDS_PER_VERTEX,
GLES20.GL_FLOAT, false,
vertexStride, vertexBuffer);

// get handle to fragment shader's vColor member
mColorHandle = GLES20.glGetUniformLocation(mProgram, "vColor");

// Set color for drawing the triangle
//GLES20.glUniform4fv(mColorHandle, 1, color, 0);

// get handle to shape's transformation matrix
mMVPMatrixHandle = GLES20.glGetUniformLocation(mProgram, "uMVPMatrix");
RenderUtils.checkGlError("glGetUniformLocation");

// Apply the projection and view transformation
GLES20.glUniformMatrix4fv(mMVPMatrixHandle, 1, false, mvpMatrix, 0);
RenderUtils.checkGlError("glUniformMatrix4fv");

// Render all the faces
for (int face = 0; face < numFaces; face++) {
// Set the color for each of the faces
GLES20.glUniform4fv(mColorHandle, 1, colors[face], 0);
indexBuffer.position(face * 6);
GLES20.glDrawElements(GLES20.GL_TRIANGLES, 6, GLES20.GL_UNSIGNED_SHORT, indexBuffer);
}

// Disable vertex array
GLES20.glDisableVertexAttribArray(mPositionHandle);
GLES20.glDisable(GLES20.GL_CULL_FACE);
}
}

public class MyGLSurfaceView extends GLSurfaceView {
private volatile MyGLRenderer myGLRenderer;

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

// Create an OpenGL ES 2.0 context
setEGLContextClientVersion(2);

myGLRenderer = new MyGLRenderer(context);
setRenderer(myGLRenderer); // Use a custom renderer
setOnTouchListener(new OnSwipeListener(context));

setRenderMode(GLSurfaceView.RENDERMODE_CONTINUOUSLY);
}

class OnSwipeListener implements View.OnTouchListener {

private final GestureDetector gestureDetector;

public OnSwipeListener(Context context) {
this.gestureDetector = new GestureDetector(context, new OnFlingListener());
}

@Override
public boolean onTouch(View v, MotionEvent event) {
return this.gestureDetector.onTouchEvent(event);
}

private class OnFlingListener extends GestureDetector.SimpleOnGestureListener {
private final Object LOCK = new Object();

@Override
public boolean onDown(MotionEvent e) {
return true;
}

/**
* @return true if the event is consumed, else false
*/
@Override
public boolean onFling(MotionEvent down, MotionEvent up, float velocityX, float velocityY) {
//super.onFling(down, up, velocityX, velocityY);

float distanceX = up.getX() - down.getX();
float distanceY = up.getY() - down.getY();

final Cube20 cube = myGLRenderer.getCube();
if (!cube.getRotateDirection().equals(Cube.RotateDirection.NONE)) {
return false;
}

if (Math.abs(distanceX) > Math.abs(distanceY)) {
if (distanceX > 0) {
// RIGHT
cube.setRotation(Cube.RotateDirection.RIGHT);
} else {
// LEFT
cube.setRotation(Cube.RotateDirection.LEFT);
}
} else {
if (distanceY < 0) {
// TOP
cube.setRotation(Cube.RotateDirection.UP);
} else {
// DOWN
cube.setRotation(Cube.RotateDirection.DOWN);
}
}
//requestRender();
return true;
}
}
}
}

public class MyGLRenderer implements GLSurfaceView.Renderer {
Context context; // Application's context
private volatile int rotationAngle;
private volatile float rotateX;
private volatile float rotateY;
private boolean firstRotation = true;
private Cube20 cube;

// mMVPMatrix is an abbreviation for "Model View Projection Matrix"
private final float mMVPMatrix = new float[16];
private final float mProjectionMatrix = new float[16];
private final float mViewMatrix = new float[16];

// Constructor with global application context
public MyGLRenderer(Context context) {
this.context = context;
}


public float getRotateX() {
return rotateX;
}

public void setRotateX(float rotateX) {
this.rotateX = rotateX;
}

public float getRotateY() {
return rotateY;
}

public void setRotateY(float rotateY) {
this.rotateY = rotateY;
}

public float getAngle() {
return rotationAngle;
}

public void setAngle(int angle) {
rotationAngle = angle;
}


// Call back when the surface is first created or re-created
@Override
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
// Set the background frame color
GLES20.glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
this.cube = new Cube20();
}

// Call back to draw the current frame.
@Override
public void onDrawFrame(GL10 gl) {
// Redraw background color
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT | GLES20.GL_DEPTH_BUFFER_BIT);

// Set the camera position (View matrix)
Matrix.setLookAtM(mViewMatrix, 0, 0, 0, -1, 0f, 0f, 0f, 0f, 1.0f, 0.0f);

// Calculate the projection and view transformation
Matrix.multiplyMM(mMVPMatrix, 0, mProjectionMatrix, 0, mViewMatrix, 0);

// zoom out a bit
Matrix.translateM(mMVPMatrix, 0, 0, 0, 4.5f);


int angleOffset = 5;
// update the angles for the x and y rotation
if (cube.getRotateDirection().equals(Cube.RotateDirection.LEFT)) {
cube.setAngleY(cube.getAngleY() - angleOffset);
} else if (cube.getRotateDirection().equals(Cube.RotateDirection.RIGHT)) {
cube.setAngleY(cube.getAngleY() + angleOffset);
} else if (cube.getRotateDirection().equals(Cube.RotateDirection.UP)) {
cube.setAngleX(cube.getAngleX() + angleOffset);
} else if (cube.getRotateDirection().equals(Cube.RotateDirection.DOWN)) {
cube.setAngleX(cube.getAngleX() - angleOffset);
}
firstRotation = false;

// rotate and draw
rotate();
cube.draw(mMVPMatrix);


// test if rotation should be stopped (lock in each 90° step)
if (cube.getRotateDirection().equals(Cube.RotateDirection.LEFT) ||
cube.getRotateDirection().equals(Cube.RotateDirection.RIGHT)) {
if (!firstRotation && cube.getAngleY() % 90 == 0) {
cube.setRotation(Cube.RotateDirection.NONE);
}
}
if (cube.getRotateDirection().equals(Cube.RotateDirection.UP) ||
cube.getRotateDirection().equals(Cube.RotateDirection.DOWN)) {
if (!firstRotation && cube.getAngleX() % 90 == 0) {
cube.setRotation(Cube.RotateDirection.NONE);
}
}

Log.i("MyGLRENDER~ ", cube.getRotateDirection().toString());
}

private void rotate() {
float rotationMatrix = new float[16];
Matrix.setIdentityM(rotationMatrix, 0);

// rotate in x and y direction, apply that to the intermediate matrix
Matrix.rotateM(rotationMatrix, 0, cube.getAngleX(), 1, 0, 0);
Matrix.rotateM(rotationMatrix, 0, cube.getAngleY(), 0, 1, 0);
Matrix.multiplyMM(mMVPMatrix, 0, mMVPMatrix, 0, rotationMatrix, 0);
}

// Call back after onSurfaceCreated() or whenever the window's size changes
@Override
public void onSurfaceChanged(GL10 gl, int width, int height) {
GLES20.glViewport(0, 0, width, height);
float ratio = (float) width / height;

// this projection matrix is applied to object coordinates
// in the onDrawFrame() method
Matrix.frustumM(mProjectionMatrix, 0, -ratio, ratio, -1, 1, 3, 7);
}

public Cube20 getCube() {
return cube;
}
}

public class MyGLActivity extends Activity {

private GLSurfaceView glView; // Use GLSurfaceView

// Call back when the activity is started, to initialize the view
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
glView = new MyGLSurfaceView(this); // Allocate a GLSurfaceView
this.setContentView(glView); // This activity sets to GLSurfaceView
}

// Call back when the activity is going into the background
@Override
protected void onPause() {
super.onPause();
glView.onPause();
}

// Call back after onPause()
@Override
protected void onResume() {
super.onResume();
glView.onResume();
}
}


The behaviour im expecting:




  • successful swipe reaction and rotation about x-axis
    (intermediate screenshot)


  • successful swipe reaction and rotation about y-axis
    (intermediate screenshot)



With this setup i encounter the following problems:




  1. Sometimes the rotation is done about the z-axis
    (intermediate screenshot)

    I never implemented a rotation about the z-axis for the cube, i am not sure why this rotation is done. I could only imagine that there is maybe a threading problem between the swipe listener at MyGLSurfaceView and MyGLRenderer (which holds the reference to the cube).


The rotation is done at MyGLRenderer.rotate. The cube uses separate angles (x/y) and also there is a property for which rotation is currently done (LEFT, RIGHT, UP, DOWN, NONE), which is updated in OnSwipeListener.




  1. Also if the bug with the z-rotation (1.) happened, the rotation about the x or y axis is done in the wrong direction (left/right, up/down swapped)


My guess is that either the angles or the RotateDirection is not updated correctly.
The app is tested on a OnePlus 3T @ Android 8.0, IDE is Android Studio 3.2.1.










share|improve this question





























    1















    I implemented the visualization of a colored 3d cube with OpenGL ES 2.0 that is used for an android app. My goal: the cube should react to swipe events (left, right, up, down), then the cube should be rotated in the corresponding direction.



    The rotation:




    • stops if (current_angle % 90 == 0) -> so you swipe from one face to another

    • only one rotation at a time (and only around x- or y-axis)

    • should also be done in steps (ex.: 5 degrees), so it is not done instantly -> can be seen by the user


    My code:



    public class Cube20 {

    private volatile int angleX;
    private volatile int angleY;

    private volatile Cube.RotateDirection rotateDirection;

    public Cube.RotateDirection getRotateDirection() {
    return rotateDirection;
    }


    public void setRotation(Cube.RotateDirection rotateDirection) {
    this.rotateDirection = Cube.RotateDirection.getDirectionForID(rotateDirection.getId());
    }

    public int getAngleX() {
    return angleX;
    }

    public void setAngleX(int angleX) {
    this.angleX = angleX;
    }

    public int getAngleY() {
    return angleY;
    }

    public void setAngleY(int angleY) {
    this.angleY = angleY;
    }

    private final String vertexShaderCode =
    // This matrix member variable provides a hook to manipulate
    // the coordinates of the objects that use this vertex shader
    "uniform mat4 uMVPMatrix;" +
    "attribute vec4 vPosition;" +
    "void main() {" +
    // The matrix must be included as a modifier of gl_Position.
    // Note that the uMVPMatrix factor *must be first* in order
    // for the matrix multiplication product to be correct.
    " gl_Position = uMVPMatrix * vPosition;" +
    "}";

    private final String fragmentShaderCode =
    "precision mediump float;" +
    "uniform vec4 vColor;" +
    "void main() {" +
    " gl_FragColor = vColor;" +
    "}";

    private final FloatBuffer vertexBuffer;
    //private final ShortBuffer drawListBuffer;
    private final int mProgram;
    private final ShortBuffer indexBuffer;
    private int mPositionHandle;
    private int mColorHandle;
    private int mMVPMatrixHandle;

    // number of coordinates per vertex in this array
    static final int COORDS_PER_VERTEX = 3;

    private float vertices = { // Vertices of the 6 faces
    // FRONT
    -1.0f, -1.0f, 1.0f, // 0. left-bottom-front (0)
    1.0f, -1.0f, 1.0f, // 1. right-bottom-front
    -1.0f, 1.0f, 1.0f, // 2. left-top-front
    1.0f, 1.0f, 1.0f, // 3. right-top-front
    // BACK
    1.0f, -1.0f, -1.0f, // 6. right-bottom-back (4)
    -1.0f, -1.0f, -1.0f, // 4. left-bottom-back
    1.0f, 1.0f, -1.0f, // 7. right-top-back
    -1.0f, 1.0f, -1.0f, // 5. left-top-back
    // LEFT
    -1.0f, -1.0f, -1.0f, // 4. left-bottom-back (8)
    -1.0f, -1.0f, 1.0f, // 0. left-bottom-front
    -1.0f, 1.0f, -1.0f, // 5. left-top-back
    -1.0f, 1.0f, 1.0f, // 2. left-top-front
    // RIGHT
    1.0f, -1.0f, 1.0f, // 1. right-bottom-front (12)
    1.0f, -1.0f, -1.0f, // 6. right-bottom-back
    1.0f, 1.0f, 1.0f, // 3. right-top-front
    1.0f, 1.0f, -1.0f, // 7. right-top-back
    // TOP
    -1.0f, 1.0f, 1.0f, // 2. left-top-front
    1.0f, 1.0f, 1.0f, // 3. right-top-front
    -1.0f, 1.0f, -1.0f, // 5. left-top-back
    1.0f, 1.0f, -1.0f, // 7. right-top-back
    // BOTTOM
    -1.0f, -1.0f, -1.0f, // 4. left-bottom-back
    1.0f, -1.0f, -1.0f, // 6. right-bottom-back
    -1.0f, -1.0f, 1.0f, // 0. left-bottom-front
    1.0f, -1.0f, 1.0f // 1. right-bottom-front
    };

    private final int vertexStride = COORDS_PER_VERTEX * 4; // 4 bytes per vertex

    private float colors = { // Colors of the 6 faces
    {1.0f, 0.5f, 0.0f, 1.0f}, // 0. orange
    {1.0f, 0.0f, 1.0f, 1.0f}, // 1. violet
    {0.0f, 1.0f, 0.0f, 1.0f}, // 2. green
    {0.0f, 0.0f, 1.0f, 1.0f}, // 3. blue
    {1.0f, 0.0f, 0.0f, 1.0f}, // 4. red
    {1.0f, 1.0f, 0.0f, 1.0f} // 5. yellow
    };

    short indices = {
    0, 1, 2, 2, 1, 3, // FRONT
    4, 5, 6, 6, 5, 7, // BACK
    8, 9, 10, 10, 9, 11, // LEFT
    12, 13, 14, 14, 13, 15, // RIGHT
    16, 17, 18, 18, 17, 19, // TOP
    20, 21, 22, 22, 21, 23, // BOTTOM

    };
    private int numFaces = 6;

    /**
    * Sets up the drawing object data for use in an OpenGL ES context.
    */
    public Cube20() {
    // initialize vertex byte buffer for shape coordinates
    ByteBuffer bb = ByteBuffer.allocateDirect(
    // (# of coordinate values * 4 bytes per float)
    vertices.length * 4);
    bb.order(ByteOrder.nativeOrder());
    vertexBuffer = bb.asFloatBuffer();
    vertexBuffer.put(vertices);
    vertexBuffer.position(0);

    // initialize byte buffer for the draw list
    indexBuffer = ByteBuffer.allocateDirect(indices.length * 2).order(ByteOrder.nativeOrder()).asShortBuffer();
    indexBuffer.put(indices).position(0);

    // prepare shaders and OpenGL program
    int vertexShader = RenderUtils.loadShader(
    GLES20.GL_VERTEX_SHADER,
    vertexShaderCode);
    int fragmentShader = RenderUtils.loadShader(
    GLES20.GL_FRAGMENT_SHADER,
    fragmentShaderCode);

    mProgram = GLES20.glCreateProgram(); // create empty OpenGL Program
    GLES20.glAttachShader(mProgram, vertexShader); // add the vertex shader to program
    GLES20.glAttachShader(mProgram, fragmentShader); // add the fragment shader to program
    GLES20.glLinkProgram(mProgram); // create OpenGL program executables

    this.rotateDirection = Cube.RotateDirection.NONE;
    }

    /**
    * Encapsulates the OpenGL ES instructions for drawing this shape.
    *
    * @param mvpMatrix - The Model View Project matrix in which to draw
    * this shape.
    */
    public void draw(float mvpMatrix) {
    // Add program to OpenGL environment
    GLES20.glUseProgram(mProgram);

    GLES20.glFrontFace(GLES20.GL_CCW);
    GLES20.glEnable(GLES20.GL_CULL_FACE);
    GLES20.glCullFace(GLES20.GL_BACK);

    // scale
    float scale_matrix = new float[16];
    Matrix.setIdentityM(scale_matrix, 0);
    Matrix.scaleM(scale_matrix, 0, 0.5f, 0.5f, 1);
    Matrix.multiplyMM(mvpMatrix, 0, scale_matrix, 0, mvpMatrix, 0);

    // get handle to vertex shader's vPosition member
    mPositionHandle = GLES20.glGetAttribLocation(mProgram, "vPosition");

    // Enable a handle to the triangle vertices
    GLES20.glEnableVertexAttribArray(mPositionHandle);

    // Prepare the triangle coordinate data
    GLES20.glVertexAttribPointer(
    mPositionHandle, COORDS_PER_VERTEX,
    GLES20.GL_FLOAT, false,
    vertexStride, vertexBuffer);

    // get handle to fragment shader's vColor member
    mColorHandle = GLES20.glGetUniformLocation(mProgram, "vColor");

    // Set color for drawing the triangle
    //GLES20.glUniform4fv(mColorHandle, 1, color, 0);

    // get handle to shape's transformation matrix
    mMVPMatrixHandle = GLES20.glGetUniformLocation(mProgram, "uMVPMatrix");
    RenderUtils.checkGlError("glGetUniformLocation");

    // Apply the projection and view transformation
    GLES20.glUniformMatrix4fv(mMVPMatrixHandle, 1, false, mvpMatrix, 0);
    RenderUtils.checkGlError("glUniformMatrix4fv");

    // Render all the faces
    for (int face = 0; face < numFaces; face++) {
    // Set the color for each of the faces
    GLES20.glUniform4fv(mColorHandle, 1, colors[face], 0);
    indexBuffer.position(face * 6);
    GLES20.glDrawElements(GLES20.GL_TRIANGLES, 6, GLES20.GL_UNSIGNED_SHORT, indexBuffer);
    }

    // Disable vertex array
    GLES20.glDisableVertexAttribArray(mPositionHandle);
    GLES20.glDisable(GLES20.GL_CULL_FACE);
    }
    }

    public class MyGLSurfaceView extends GLSurfaceView {
    private volatile MyGLRenderer myGLRenderer;

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

    // Create an OpenGL ES 2.0 context
    setEGLContextClientVersion(2);

    myGLRenderer = new MyGLRenderer(context);
    setRenderer(myGLRenderer); // Use a custom renderer
    setOnTouchListener(new OnSwipeListener(context));

    setRenderMode(GLSurfaceView.RENDERMODE_CONTINUOUSLY);
    }

    class OnSwipeListener implements View.OnTouchListener {

    private final GestureDetector gestureDetector;

    public OnSwipeListener(Context context) {
    this.gestureDetector = new GestureDetector(context, new OnFlingListener());
    }

    @Override
    public boolean onTouch(View v, MotionEvent event) {
    return this.gestureDetector.onTouchEvent(event);
    }

    private class OnFlingListener extends GestureDetector.SimpleOnGestureListener {
    private final Object LOCK = new Object();

    @Override
    public boolean onDown(MotionEvent e) {
    return true;
    }

    /**
    * @return true if the event is consumed, else false
    */
    @Override
    public boolean onFling(MotionEvent down, MotionEvent up, float velocityX, float velocityY) {
    //super.onFling(down, up, velocityX, velocityY);

    float distanceX = up.getX() - down.getX();
    float distanceY = up.getY() - down.getY();

    final Cube20 cube = myGLRenderer.getCube();
    if (!cube.getRotateDirection().equals(Cube.RotateDirection.NONE)) {
    return false;
    }

    if (Math.abs(distanceX) > Math.abs(distanceY)) {
    if (distanceX > 0) {
    // RIGHT
    cube.setRotation(Cube.RotateDirection.RIGHT);
    } else {
    // LEFT
    cube.setRotation(Cube.RotateDirection.LEFT);
    }
    } else {
    if (distanceY < 0) {
    // TOP
    cube.setRotation(Cube.RotateDirection.UP);
    } else {
    // DOWN
    cube.setRotation(Cube.RotateDirection.DOWN);
    }
    }
    //requestRender();
    return true;
    }
    }
    }
    }

    public class MyGLRenderer implements GLSurfaceView.Renderer {
    Context context; // Application's context
    private volatile int rotationAngle;
    private volatile float rotateX;
    private volatile float rotateY;
    private boolean firstRotation = true;
    private Cube20 cube;

    // mMVPMatrix is an abbreviation for "Model View Projection Matrix"
    private final float mMVPMatrix = new float[16];
    private final float mProjectionMatrix = new float[16];
    private final float mViewMatrix = new float[16];

    // Constructor with global application context
    public MyGLRenderer(Context context) {
    this.context = context;
    }


    public float getRotateX() {
    return rotateX;
    }

    public void setRotateX(float rotateX) {
    this.rotateX = rotateX;
    }

    public float getRotateY() {
    return rotateY;
    }

    public void setRotateY(float rotateY) {
    this.rotateY = rotateY;
    }

    public float getAngle() {
    return rotationAngle;
    }

    public void setAngle(int angle) {
    rotationAngle = angle;
    }


    // Call back when the surface is first created or re-created
    @Override
    public void onSurfaceCreated(GL10 gl, EGLConfig config) {
    // Set the background frame color
    GLES20.glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
    this.cube = new Cube20();
    }

    // Call back to draw the current frame.
    @Override
    public void onDrawFrame(GL10 gl) {
    // Redraw background color
    GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT | GLES20.GL_DEPTH_BUFFER_BIT);

    // Set the camera position (View matrix)
    Matrix.setLookAtM(mViewMatrix, 0, 0, 0, -1, 0f, 0f, 0f, 0f, 1.0f, 0.0f);

    // Calculate the projection and view transformation
    Matrix.multiplyMM(mMVPMatrix, 0, mProjectionMatrix, 0, mViewMatrix, 0);

    // zoom out a bit
    Matrix.translateM(mMVPMatrix, 0, 0, 0, 4.5f);


    int angleOffset = 5;
    // update the angles for the x and y rotation
    if (cube.getRotateDirection().equals(Cube.RotateDirection.LEFT)) {
    cube.setAngleY(cube.getAngleY() - angleOffset);
    } else if (cube.getRotateDirection().equals(Cube.RotateDirection.RIGHT)) {
    cube.setAngleY(cube.getAngleY() + angleOffset);
    } else if (cube.getRotateDirection().equals(Cube.RotateDirection.UP)) {
    cube.setAngleX(cube.getAngleX() + angleOffset);
    } else if (cube.getRotateDirection().equals(Cube.RotateDirection.DOWN)) {
    cube.setAngleX(cube.getAngleX() - angleOffset);
    }
    firstRotation = false;

    // rotate and draw
    rotate();
    cube.draw(mMVPMatrix);


    // test if rotation should be stopped (lock in each 90° step)
    if (cube.getRotateDirection().equals(Cube.RotateDirection.LEFT) ||
    cube.getRotateDirection().equals(Cube.RotateDirection.RIGHT)) {
    if (!firstRotation && cube.getAngleY() % 90 == 0) {
    cube.setRotation(Cube.RotateDirection.NONE);
    }
    }
    if (cube.getRotateDirection().equals(Cube.RotateDirection.UP) ||
    cube.getRotateDirection().equals(Cube.RotateDirection.DOWN)) {
    if (!firstRotation && cube.getAngleX() % 90 == 0) {
    cube.setRotation(Cube.RotateDirection.NONE);
    }
    }

    Log.i("MyGLRENDER~ ", cube.getRotateDirection().toString());
    }

    private void rotate() {
    float rotationMatrix = new float[16];
    Matrix.setIdentityM(rotationMatrix, 0);

    // rotate in x and y direction, apply that to the intermediate matrix
    Matrix.rotateM(rotationMatrix, 0, cube.getAngleX(), 1, 0, 0);
    Matrix.rotateM(rotationMatrix, 0, cube.getAngleY(), 0, 1, 0);
    Matrix.multiplyMM(mMVPMatrix, 0, mMVPMatrix, 0, rotationMatrix, 0);
    }

    // Call back after onSurfaceCreated() or whenever the window's size changes
    @Override
    public void onSurfaceChanged(GL10 gl, int width, int height) {
    GLES20.glViewport(0, 0, width, height);
    float ratio = (float) width / height;

    // this projection matrix is applied to object coordinates
    // in the onDrawFrame() method
    Matrix.frustumM(mProjectionMatrix, 0, -ratio, ratio, -1, 1, 3, 7);
    }

    public Cube20 getCube() {
    return cube;
    }
    }

    public class MyGLActivity extends Activity {

    private GLSurfaceView glView; // Use GLSurfaceView

    // Call back when the activity is started, to initialize the view
    @Override
    protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    glView = new MyGLSurfaceView(this); // Allocate a GLSurfaceView
    this.setContentView(glView); // This activity sets to GLSurfaceView
    }

    // Call back when the activity is going into the background
    @Override
    protected void onPause() {
    super.onPause();
    glView.onPause();
    }

    // Call back after onPause()
    @Override
    protected void onResume() {
    super.onResume();
    glView.onResume();
    }
    }


    The behaviour im expecting:




    • successful swipe reaction and rotation about x-axis
      (intermediate screenshot)


    • successful swipe reaction and rotation about y-axis
      (intermediate screenshot)



    With this setup i encounter the following problems:




    1. Sometimes the rotation is done about the z-axis
      (intermediate screenshot)

      I never implemented a rotation about the z-axis for the cube, i am not sure why this rotation is done. I could only imagine that there is maybe a threading problem between the swipe listener at MyGLSurfaceView and MyGLRenderer (which holds the reference to the cube).


    The rotation is done at MyGLRenderer.rotate. The cube uses separate angles (x/y) and also there is a property for which rotation is currently done (LEFT, RIGHT, UP, DOWN, NONE), which is updated in OnSwipeListener.




    1. Also if the bug with the z-rotation (1.) happened, the rotation about the x or y axis is done in the wrong direction (left/right, up/down swapped)


    My guess is that either the angles or the RotateDirection is not updated correctly.
    The app is tested on a OnePlus 3T @ Android 8.0, IDE is Android Studio 3.2.1.










    share|improve this question



























      1












      1








      1








      I implemented the visualization of a colored 3d cube with OpenGL ES 2.0 that is used for an android app. My goal: the cube should react to swipe events (left, right, up, down), then the cube should be rotated in the corresponding direction.



      The rotation:




      • stops if (current_angle % 90 == 0) -> so you swipe from one face to another

      • only one rotation at a time (and only around x- or y-axis)

      • should also be done in steps (ex.: 5 degrees), so it is not done instantly -> can be seen by the user


      My code:



      public class Cube20 {

      private volatile int angleX;
      private volatile int angleY;

      private volatile Cube.RotateDirection rotateDirection;

      public Cube.RotateDirection getRotateDirection() {
      return rotateDirection;
      }


      public void setRotation(Cube.RotateDirection rotateDirection) {
      this.rotateDirection = Cube.RotateDirection.getDirectionForID(rotateDirection.getId());
      }

      public int getAngleX() {
      return angleX;
      }

      public void setAngleX(int angleX) {
      this.angleX = angleX;
      }

      public int getAngleY() {
      return angleY;
      }

      public void setAngleY(int angleY) {
      this.angleY = angleY;
      }

      private final String vertexShaderCode =
      // This matrix member variable provides a hook to manipulate
      // the coordinates of the objects that use this vertex shader
      "uniform mat4 uMVPMatrix;" +
      "attribute vec4 vPosition;" +
      "void main() {" +
      // The matrix must be included as a modifier of gl_Position.
      // Note that the uMVPMatrix factor *must be first* in order
      // for the matrix multiplication product to be correct.
      " gl_Position = uMVPMatrix * vPosition;" +
      "}";

      private final String fragmentShaderCode =
      "precision mediump float;" +
      "uniform vec4 vColor;" +
      "void main() {" +
      " gl_FragColor = vColor;" +
      "}";

      private final FloatBuffer vertexBuffer;
      //private final ShortBuffer drawListBuffer;
      private final int mProgram;
      private final ShortBuffer indexBuffer;
      private int mPositionHandle;
      private int mColorHandle;
      private int mMVPMatrixHandle;

      // number of coordinates per vertex in this array
      static final int COORDS_PER_VERTEX = 3;

      private float vertices = { // Vertices of the 6 faces
      // FRONT
      -1.0f, -1.0f, 1.0f, // 0. left-bottom-front (0)
      1.0f, -1.0f, 1.0f, // 1. right-bottom-front
      -1.0f, 1.0f, 1.0f, // 2. left-top-front
      1.0f, 1.0f, 1.0f, // 3. right-top-front
      // BACK
      1.0f, -1.0f, -1.0f, // 6. right-bottom-back (4)
      -1.0f, -1.0f, -1.0f, // 4. left-bottom-back
      1.0f, 1.0f, -1.0f, // 7. right-top-back
      -1.0f, 1.0f, -1.0f, // 5. left-top-back
      // LEFT
      -1.0f, -1.0f, -1.0f, // 4. left-bottom-back (8)
      -1.0f, -1.0f, 1.0f, // 0. left-bottom-front
      -1.0f, 1.0f, -1.0f, // 5. left-top-back
      -1.0f, 1.0f, 1.0f, // 2. left-top-front
      // RIGHT
      1.0f, -1.0f, 1.0f, // 1. right-bottom-front (12)
      1.0f, -1.0f, -1.0f, // 6. right-bottom-back
      1.0f, 1.0f, 1.0f, // 3. right-top-front
      1.0f, 1.0f, -1.0f, // 7. right-top-back
      // TOP
      -1.0f, 1.0f, 1.0f, // 2. left-top-front
      1.0f, 1.0f, 1.0f, // 3. right-top-front
      -1.0f, 1.0f, -1.0f, // 5. left-top-back
      1.0f, 1.0f, -1.0f, // 7. right-top-back
      // BOTTOM
      -1.0f, -1.0f, -1.0f, // 4. left-bottom-back
      1.0f, -1.0f, -1.0f, // 6. right-bottom-back
      -1.0f, -1.0f, 1.0f, // 0. left-bottom-front
      1.0f, -1.0f, 1.0f // 1. right-bottom-front
      };

      private final int vertexStride = COORDS_PER_VERTEX * 4; // 4 bytes per vertex

      private float colors = { // Colors of the 6 faces
      {1.0f, 0.5f, 0.0f, 1.0f}, // 0. orange
      {1.0f, 0.0f, 1.0f, 1.0f}, // 1. violet
      {0.0f, 1.0f, 0.0f, 1.0f}, // 2. green
      {0.0f, 0.0f, 1.0f, 1.0f}, // 3. blue
      {1.0f, 0.0f, 0.0f, 1.0f}, // 4. red
      {1.0f, 1.0f, 0.0f, 1.0f} // 5. yellow
      };

      short indices = {
      0, 1, 2, 2, 1, 3, // FRONT
      4, 5, 6, 6, 5, 7, // BACK
      8, 9, 10, 10, 9, 11, // LEFT
      12, 13, 14, 14, 13, 15, // RIGHT
      16, 17, 18, 18, 17, 19, // TOP
      20, 21, 22, 22, 21, 23, // BOTTOM

      };
      private int numFaces = 6;

      /**
      * Sets up the drawing object data for use in an OpenGL ES context.
      */
      public Cube20() {
      // initialize vertex byte buffer for shape coordinates
      ByteBuffer bb = ByteBuffer.allocateDirect(
      // (# of coordinate values * 4 bytes per float)
      vertices.length * 4);
      bb.order(ByteOrder.nativeOrder());
      vertexBuffer = bb.asFloatBuffer();
      vertexBuffer.put(vertices);
      vertexBuffer.position(0);

      // initialize byte buffer for the draw list
      indexBuffer = ByteBuffer.allocateDirect(indices.length * 2).order(ByteOrder.nativeOrder()).asShortBuffer();
      indexBuffer.put(indices).position(0);

      // prepare shaders and OpenGL program
      int vertexShader = RenderUtils.loadShader(
      GLES20.GL_VERTEX_SHADER,
      vertexShaderCode);
      int fragmentShader = RenderUtils.loadShader(
      GLES20.GL_FRAGMENT_SHADER,
      fragmentShaderCode);

      mProgram = GLES20.glCreateProgram(); // create empty OpenGL Program
      GLES20.glAttachShader(mProgram, vertexShader); // add the vertex shader to program
      GLES20.glAttachShader(mProgram, fragmentShader); // add the fragment shader to program
      GLES20.glLinkProgram(mProgram); // create OpenGL program executables

      this.rotateDirection = Cube.RotateDirection.NONE;
      }

      /**
      * Encapsulates the OpenGL ES instructions for drawing this shape.
      *
      * @param mvpMatrix - The Model View Project matrix in which to draw
      * this shape.
      */
      public void draw(float mvpMatrix) {
      // Add program to OpenGL environment
      GLES20.glUseProgram(mProgram);

      GLES20.glFrontFace(GLES20.GL_CCW);
      GLES20.glEnable(GLES20.GL_CULL_FACE);
      GLES20.glCullFace(GLES20.GL_BACK);

      // scale
      float scale_matrix = new float[16];
      Matrix.setIdentityM(scale_matrix, 0);
      Matrix.scaleM(scale_matrix, 0, 0.5f, 0.5f, 1);
      Matrix.multiplyMM(mvpMatrix, 0, scale_matrix, 0, mvpMatrix, 0);

      // get handle to vertex shader's vPosition member
      mPositionHandle = GLES20.glGetAttribLocation(mProgram, "vPosition");

      // Enable a handle to the triangle vertices
      GLES20.glEnableVertexAttribArray(mPositionHandle);

      // Prepare the triangle coordinate data
      GLES20.glVertexAttribPointer(
      mPositionHandle, COORDS_PER_VERTEX,
      GLES20.GL_FLOAT, false,
      vertexStride, vertexBuffer);

      // get handle to fragment shader's vColor member
      mColorHandle = GLES20.glGetUniformLocation(mProgram, "vColor");

      // Set color for drawing the triangle
      //GLES20.glUniform4fv(mColorHandle, 1, color, 0);

      // get handle to shape's transformation matrix
      mMVPMatrixHandle = GLES20.glGetUniformLocation(mProgram, "uMVPMatrix");
      RenderUtils.checkGlError("glGetUniformLocation");

      // Apply the projection and view transformation
      GLES20.glUniformMatrix4fv(mMVPMatrixHandle, 1, false, mvpMatrix, 0);
      RenderUtils.checkGlError("glUniformMatrix4fv");

      // Render all the faces
      for (int face = 0; face < numFaces; face++) {
      // Set the color for each of the faces
      GLES20.glUniform4fv(mColorHandle, 1, colors[face], 0);
      indexBuffer.position(face * 6);
      GLES20.glDrawElements(GLES20.GL_TRIANGLES, 6, GLES20.GL_UNSIGNED_SHORT, indexBuffer);
      }

      // Disable vertex array
      GLES20.glDisableVertexAttribArray(mPositionHandle);
      GLES20.glDisable(GLES20.GL_CULL_FACE);
      }
      }

      public class MyGLSurfaceView extends GLSurfaceView {
      private volatile MyGLRenderer myGLRenderer;

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

      // Create an OpenGL ES 2.0 context
      setEGLContextClientVersion(2);

      myGLRenderer = new MyGLRenderer(context);
      setRenderer(myGLRenderer); // Use a custom renderer
      setOnTouchListener(new OnSwipeListener(context));

      setRenderMode(GLSurfaceView.RENDERMODE_CONTINUOUSLY);
      }

      class OnSwipeListener implements View.OnTouchListener {

      private final GestureDetector gestureDetector;

      public OnSwipeListener(Context context) {
      this.gestureDetector = new GestureDetector(context, new OnFlingListener());
      }

      @Override
      public boolean onTouch(View v, MotionEvent event) {
      return this.gestureDetector.onTouchEvent(event);
      }

      private class OnFlingListener extends GestureDetector.SimpleOnGestureListener {
      private final Object LOCK = new Object();

      @Override
      public boolean onDown(MotionEvent e) {
      return true;
      }

      /**
      * @return true if the event is consumed, else false
      */
      @Override
      public boolean onFling(MotionEvent down, MotionEvent up, float velocityX, float velocityY) {
      //super.onFling(down, up, velocityX, velocityY);

      float distanceX = up.getX() - down.getX();
      float distanceY = up.getY() - down.getY();

      final Cube20 cube = myGLRenderer.getCube();
      if (!cube.getRotateDirection().equals(Cube.RotateDirection.NONE)) {
      return false;
      }

      if (Math.abs(distanceX) > Math.abs(distanceY)) {
      if (distanceX > 0) {
      // RIGHT
      cube.setRotation(Cube.RotateDirection.RIGHT);
      } else {
      // LEFT
      cube.setRotation(Cube.RotateDirection.LEFT);
      }
      } else {
      if (distanceY < 0) {
      // TOP
      cube.setRotation(Cube.RotateDirection.UP);
      } else {
      // DOWN
      cube.setRotation(Cube.RotateDirection.DOWN);
      }
      }
      //requestRender();
      return true;
      }
      }
      }
      }

      public class MyGLRenderer implements GLSurfaceView.Renderer {
      Context context; // Application's context
      private volatile int rotationAngle;
      private volatile float rotateX;
      private volatile float rotateY;
      private boolean firstRotation = true;
      private Cube20 cube;

      // mMVPMatrix is an abbreviation for "Model View Projection Matrix"
      private final float mMVPMatrix = new float[16];
      private final float mProjectionMatrix = new float[16];
      private final float mViewMatrix = new float[16];

      // Constructor with global application context
      public MyGLRenderer(Context context) {
      this.context = context;
      }


      public float getRotateX() {
      return rotateX;
      }

      public void setRotateX(float rotateX) {
      this.rotateX = rotateX;
      }

      public float getRotateY() {
      return rotateY;
      }

      public void setRotateY(float rotateY) {
      this.rotateY = rotateY;
      }

      public float getAngle() {
      return rotationAngle;
      }

      public void setAngle(int angle) {
      rotationAngle = angle;
      }


      // Call back when the surface is first created or re-created
      @Override
      public void onSurfaceCreated(GL10 gl, EGLConfig config) {
      // Set the background frame color
      GLES20.glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
      this.cube = new Cube20();
      }

      // Call back to draw the current frame.
      @Override
      public void onDrawFrame(GL10 gl) {
      // Redraw background color
      GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT | GLES20.GL_DEPTH_BUFFER_BIT);

      // Set the camera position (View matrix)
      Matrix.setLookAtM(mViewMatrix, 0, 0, 0, -1, 0f, 0f, 0f, 0f, 1.0f, 0.0f);

      // Calculate the projection and view transformation
      Matrix.multiplyMM(mMVPMatrix, 0, mProjectionMatrix, 0, mViewMatrix, 0);

      // zoom out a bit
      Matrix.translateM(mMVPMatrix, 0, 0, 0, 4.5f);


      int angleOffset = 5;
      // update the angles for the x and y rotation
      if (cube.getRotateDirection().equals(Cube.RotateDirection.LEFT)) {
      cube.setAngleY(cube.getAngleY() - angleOffset);
      } else if (cube.getRotateDirection().equals(Cube.RotateDirection.RIGHT)) {
      cube.setAngleY(cube.getAngleY() + angleOffset);
      } else if (cube.getRotateDirection().equals(Cube.RotateDirection.UP)) {
      cube.setAngleX(cube.getAngleX() + angleOffset);
      } else if (cube.getRotateDirection().equals(Cube.RotateDirection.DOWN)) {
      cube.setAngleX(cube.getAngleX() - angleOffset);
      }
      firstRotation = false;

      // rotate and draw
      rotate();
      cube.draw(mMVPMatrix);


      // test if rotation should be stopped (lock in each 90° step)
      if (cube.getRotateDirection().equals(Cube.RotateDirection.LEFT) ||
      cube.getRotateDirection().equals(Cube.RotateDirection.RIGHT)) {
      if (!firstRotation && cube.getAngleY() % 90 == 0) {
      cube.setRotation(Cube.RotateDirection.NONE);
      }
      }
      if (cube.getRotateDirection().equals(Cube.RotateDirection.UP) ||
      cube.getRotateDirection().equals(Cube.RotateDirection.DOWN)) {
      if (!firstRotation && cube.getAngleX() % 90 == 0) {
      cube.setRotation(Cube.RotateDirection.NONE);
      }
      }

      Log.i("MyGLRENDER~ ", cube.getRotateDirection().toString());
      }

      private void rotate() {
      float rotationMatrix = new float[16];
      Matrix.setIdentityM(rotationMatrix, 0);

      // rotate in x and y direction, apply that to the intermediate matrix
      Matrix.rotateM(rotationMatrix, 0, cube.getAngleX(), 1, 0, 0);
      Matrix.rotateM(rotationMatrix, 0, cube.getAngleY(), 0, 1, 0);
      Matrix.multiplyMM(mMVPMatrix, 0, mMVPMatrix, 0, rotationMatrix, 0);
      }

      // Call back after onSurfaceCreated() or whenever the window's size changes
      @Override
      public void onSurfaceChanged(GL10 gl, int width, int height) {
      GLES20.glViewport(0, 0, width, height);
      float ratio = (float) width / height;

      // this projection matrix is applied to object coordinates
      // in the onDrawFrame() method
      Matrix.frustumM(mProjectionMatrix, 0, -ratio, ratio, -1, 1, 3, 7);
      }

      public Cube20 getCube() {
      return cube;
      }
      }

      public class MyGLActivity extends Activity {

      private GLSurfaceView glView; // Use GLSurfaceView

      // Call back when the activity is started, to initialize the view
      @Override
      protected void onCreate(Bundle savedInstanceState) {
      super.onCreate(savedInstanceState);
      glView = new MyGLSurfaceView(this); // Allocate a GLSurfaceView
      this.setContentView(glView); // This activity sets to GLSurfaceView
      }

      // Call back when the activity is going into the background
      @Override
      protected void onPause() {
      super.onPause();
      glView.onPause();
      }

      // Call back after onPause()
      @Override
      protected void onResume() {
      super.onResume();
      glView.onResume();
      }
      }


      The behaviour im expecting:




      • successful swipe reaction and rotation about x-axis
        (intermediate screenshot)


      • successful swipe reaction and rotation about y-axis
        (intermediate screenshot)



      With this setup i encounter the following problems:




      1. Sometimes the rotation is done about the z-axis
        (intermediate screenshot)

        I never implemented a rotation about the z-axis for the cube, i am not sure why this rotation is done. I could only imagine that there is maybe a threading problem between the swipe listener at MyGLSurfaceView and MyGLRenderer (which holds the reference to the cube).


      The rotation is done at MyGLRenderer.rotate. The cube uses separate angles (x/y) and also there is a property for which rotation is currently done (LEFT, RIGHT, UP, DOWN, NONE), which is updated in OnSwipeListener.




      1. Also if the bug with the z-rotation (1.) happened, the rotation about the x or y axis is done in the wrong direction (left/right, up/down swapped)


      My guess is that either the angles or the RotateDirection is not updated correctly.
      The app is tested on a OnePlus 3T @ Android 8.0, IDE is Android Studio 3.2.1.










      share|improve this question
















      I implemented the visualization of a colored 3d cube with OpenGL ES 2.0 that is used for an android app. My goal: the cube should react to swipe events (left, right, up, down), then the cube should be rotated in the corresponding direction.



      The rotation:




      • stops if (current_angle % 90 == 0) -> so you swipe from one face to another

      • only one rotation at a time (and only around x- or y-axis)

      • should also be done in steps (ex.: 5 degrees), so it is not done instantly -> can be seen by the user


      My code:



      public class Cube20 {

      private volatile int angleX;
      private volatile int angleY;

      private volatile Cube.RotateDirection rotateDirection;

      public Cube.RotateDirection getRotateDirection() {
      return rotateDirection;
      }


      public void setRotation(Cube.RotateDirection rotateDirection) {
      this.rotateDirection = Cube.RotateDirection.getDirectionForID(rotateDirection.getId());
      }

      public int getAngleX() {
      return angleX;
      }

      public void setAngleX(int angleX) {
      this.angleX = angleX;
      }

      public int getAngleY() {
      return angleY;
      }

      public void setAngleY(int angleY) {
      this.angleY = angleY;
      }

      private final String vertexShaderCode =
      // This matrix member variable provides a hook to manipulate
      // the coordinates of the objects that use this vertex shader
      "uniform mat4 uMVPMatrix;" +
      "attribute vec4 vPosition;" +
      "void main() {" +
      // The matrix must be included as a modifier of gl_Position.
      // Note that the uMVPMatrix factor *must be first* in order
      // for the matrix multiplication product to be correct.
      " gl_Position = uMVPMatrix * vPosition;" +
      "}";

      private final String fragmentShaderCode =
      "precision mediump float;" +
      "uniform vec4 vColor;" +
      "void main() {" +
      " gl_FragColor = vColor;" +
      "}";

      private final FloatBuffer vertexBuffer;
      //private final ShortBuffer drawListBuffer;
      private final int mProgram;
      private final ShortBuffer indexBuffer;
      private int mPositionHandle;
      private int mColorHandle;
      private int mMVPMatrixHandle;

      // number of coordinates per vertex in this array
      static final int COORDS_PER_VERTEX = 3;

      private float vertices = { // Vertices of the 6 faces
      // FRONT
      -1.0f, -1.0f, 1.0f, // 0. left-bottom-front (0)
      1.0f, -1.0f, 1.0f, // 1. right-bottom-front
      -1.0f, 1.0f, 1.0f, // 2. left-top-front
      1.0f, 1.0f, 1.0f, // 3. right-top-front
      // BACK
      1.0f, -1.0f, -1.0f, // 6. right-bottom-back (4)
      -1.0f, -1.0f, -1.0f, // 4. left-bottom-back
      1.0f, 1.0f, -1.0f, // 7. right-top-back
      -1.0f, 1.0f, -1.0f, // 5. left-top-back
      // LEFT
      -1.0f, -1.0f, -1.0f, // 4. left-bottom-back (8)
      -1.0f, -1.0f, 1.0f, // 0. left-bottom-front
      -1.0f, 1.0f, -1.0f, // 5. left-top-back
      -1.0f, 1.0f, 1.0f, // 2. left-top-front
      // RIGHT
      1.0f, -1.0f, 1.0f, // 1. right-bottom-front (12)
      1.0f, -1.0f, -1.0f, // 6. right-bottom-back
      1.0f, 1.0f, 1.0f, // 3. right-top-front
      1.0f, 1.0f, -1.0f, // 7. right-top-back
      // TOP
      -1.0f, 1.0f, 1.0f, // 2. left-top-front
      1.0f, 1.0f, 1.0f, // 3. right-top-front
      -1.0f, 1.0f, -1.0f, // 5. left-top-back
      1.0f, 1.0f, -1.0f, // 7. right-top-back
      // BOTTOM
      -1.0f, -1.0f, -1.0f, // 4. left-bottom-back
      1.0f, -1.0f, -1.0f, // 6. right-bottom-back
      -1.0f, -1.0f, 1.0f, // 0. left-bottom-front
      1.0f, -1.0f, 1.0f // 1. right-bottom-front
      };

      private final int vertexStride = COORDS_PER_VERTEX * 4; // 4 bytes per vertex

      private float colors = { // Colors of the 6 faces
      {1.0f, 0.5f, 0.0f, 1.0f}, // 0. orange
      {1.0f, 0.0f, 1.0f, 1.0f}, // 1. violet
      {0.0f, 1.0f, 0.0f, 1.0f}, // 2. green
      {0.0f, 0.0f, 1.0f, 1.0f}, // 3. blue
      {1.0f, 0.0f, 0.0f, 1.0f}, // 4. red
      {1.0f, 1.0f, 0.0f, 1.0f} // 5. yellow
      };

      short indices = {
      0, 1, 2, 2, 1, 3, // FRONT
      4, 5, 6, 6, 5, 7, // BACK
      8, 9, 10, 10, 9, 11, // LEFT
      12, 13, 14, 14, 13, 15, // RIGHT
      16, 17, 18, 18, 17, 19, // TOP
      20, 21, 22, 22, 21, 23, // BOTTOM

      };
      private int numFaces = 6;

      /**
      * Sets up the drawing object data for use in an OpenGL ES context.
      */
      public Cube20() {
      // initialize vertex byte buffer for shape coordinates
      ByteBuffer bb = ByteBuffer.allocateDirect(
      // (# of coordinate values * 4 bytes per float)
      vertices.length * 4);
      bb.order(ByteOrder.nativeOrder());
      vertexBuffer = bb.asFloatBuffer();
      vertexBuffer.put(vertices);
      vertexBuffer.position(0);

      // initialize byte buffer for the draw list
      indexBuffer = ByteBuffer.allocateDirect(indices.length * 2).order(ByteOrder.nativeOrder()).asShortBuffer();
      indexBuffer.put(indices).position(0);

      // prepare shaders and OpenGL program
      int vertexShader = RenderUtils.loadShader(
      GLES20.GL_VERTEX_SHADER,
      vertexShaderCode);
      int fragmentShader = RenderUtils.loadShader(
      GLES20.GL_FRAGMENT_SHADER,
      fragmentShaderCode);

      mProgram = GLES20.glCreateProgram(); // create empty OpenGL Program
      GLES20.glAttachShader(mProgram, vertexShader); // add the vertex shader to program
      GLES20.glAttachShader(mProgram, fragmentShader); // add the fragment shader to program
      GLES20.glLinkProgram(mProgram); // create OpenGL program executables

      this.rotateDirection = Cube.RotateDirection.NONE;
      }

      /**
      * Encapsulates the OpenGL ES instructions for drawing this shape.
      *
      * @param mvpMatrix - The Model View Project matrix in which to draw
      * this shape.
      */
      public void draw(float mvpMatrix) {
      // Add program to OpenGL environment
      GLES20.glUseProgram(mProgram);

      GLES20.glFrontFace(GLES20.GL_CCW);
      GLES20.glEnable(GLES20.GL_CULL_FACE);
      GLES20.glCullFace(GLES20.GL_BACK);

      // scale
      float scale_matrix = new float[16];
      Matrix.setIdentityM(scale_matrix, 0);
      Matrix.scaleM(scale_matrix, 0, 0.5f, 0.5f, 1);
      Matrix.multiplyMM(mvpMatrix, 0, scale_matrix, 0, mvpMatrix, 0);

      // get handle to vertex shader's vPosition member
      mPositionHandle = GLES20.glGetAttribLocation(mProgram, "vPosition");

      // Enable a handle to the triangle vertices
      GLES20.glEnableVertexAttribArray(mPositionHandle);

      // Prepare the triangle coordinate data
      GLES20.glVertexAttribPointer(
      mPositionHandle, COORDS_PER_VERTEX,
      GLES20.GL_FLOAT, false,
      vertexStride, vertexBuffer);

      // get handle to fragment shader's vColor member
      mColorHandle = GLES20.glGetUniformLocation(mProgram, "vColor");

      // Set color for drawing the triangle
      //GLES20.glUniform4fv(mColorHandle, 1, color, 0);

      // get handle to shape's transformation matrix
      mMVPMatrixHandle = GLES20.glGetUniformLocation(mProgram, "uMVPMatrix");
      RenderUtils.checkGlError("glGetUniformLocation");

      // Apply the projection and view transformation
      GLES20.glUniformMatrix4fv(mMVPMatrixHandle, 1, false, mvpMatrix, 0);
      RenderUtils.checkGlError("glUniformMatrix4fv");

      // Render all the faces
      for (int face = 0; face < numFaces; face++) {
      // Set the color for each of the faces
      GLES20.glUniform4fv(mColorHandle, 1, colors[face], 0);
      indexBuffer.position(face * 6);
      GLES20.glDrawElements(GLES20.GL_TRIANGLES, 6, GLES20.GL_UNSIGNED_SHORT, indexBuffer);
      }

      // Disable vertex array
      GLES20.glDisableVertexAttribArray(mPositionHandle);
      GLES20.glDisable(GLES20.GL_CULL_FACE);
      }
      }

      public class MyGLSurfaceView extends GLSurfaceView {
      private volatile MyGLRenderer myGLRenderer;

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

      // Create an OpenGL ES 2.0 context
      setEGLContextClientVersion(2);

      myGLRenderer = new MyGLRenderer(context);
      setRenderer(myGLRenderer); // Use a custom renderer
      setOnTouchListener(new OnSwipeListener(context));

      setRenderMode(GLSurfaceView.RENDERMODE_CONTINUOUSLY);
      }

      class OnSwipeListener implements View.OnTouchListener {

      private final GestureDetector gestureDetector;

      public OnSwipeListener(Context context) {
      this.gestureDetector = new GestureDetector(context, new OnFlingListener());
      }

      @Override
      public boolean onTouch(View v, MotionEvent event) {
      return this.gestureDetector.onTouchEvent(event);
      }

      private class OnFlingListener extends GestureDetector.SimpleOnGestureListener {
      private final Object LOCK = new Object();

      @Override
      public boolean onDown(MotionEvent e) {
      return true;
      }

      /**
      * @return true if the event is consumed, else false
      */
      @Override
      public boolean onFling(MotionEvent down, MotionEvent up, float velocityX, float velocityY) {
      //super.onFling(down, up, velocityX, velocityY);

      float distanceX = up.getX() - down.getX();
      float distanceY = up.getY() - down.getY();

      final Cube20 cube = myGLRenderer.getCube();
      if (!cube.getRotateDirection().equals(Cube.RotateDirection.NONE)) {
      return false;
      }

      if (Math.abs(distanceX) > Math.abs(distanceY)) {
      if (distanceX > 0) {
      // RIGHT
      cube.setRotation(Cube.RotateDirection.RIGHT);
      } else {
      // LEFT
      cube.setRotation(Cube.RotateDirection.LEFT);
      }
      } else {
      if (distanceY < 0) {
      // TOP
      cube.setRotation(Cube.RotateDirection.UP);
      } else {
      // DOWN
      cube.setRotation(Cube.RotateDirection.DOWN);
      }
      }
      //requestRender();
      return true;
      }
      }
      }
      }

      public class MyGLRenderer implements GLSurfaceView.Renderer {
      Context context; // Application's context
      private volatile int rotationAngle;
      private volatile float rotateX;
      private volatile float rotateY;
      private boolean firstRotation = true;
      private Cube20 cube;

      // mMVPMatrix is an abbreviation for "Model View Projection Matrix"
      private final float mMVPMatrix = new float[16];
      private final float mProjectionMatrix = new float[16];
      private final float mViewMatrix = new float[16];

      // Constructor with global application context
      public MyGLRenderer(Context context) {
      this.context = context;
      }


      public float getRotateX() {
      return rotateX;
      }

      public void setRotateX(float rotateX) {
      this.rotateX = rotateX;
      }

      public float getRotateY() {
      return rotateY;
      }

      public void setRotateY(float rotateY) {
      this.rotateY = rotateY;
      }

      public float getAngle() {
      return rotationAngle;
      }

      public void setAngle(int angle) {
      rotationAngle = angle;
      }


      // Call back when the surface is first created or re-created
      @Override
      public void onSurfaceCreated(GL10 gl, EGLConfig config) {
      // Set the background frame color
      GLES20.glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
      this.cube = new Cube20();
      }

      // Call back to draw the current frame.
      @Override
      public void onDrawFrame(GL10 gl) {
      // Redraw background color
      GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT | GLES20.GL_DEPTH_BUFFER_BIT);

      // Set the camera position (View matrix)
      Matrix.setLookAtM(mViewMatrix, 0, 0, 0, -1, 0f, 0f, 0f, 0f, 1.0f, 0.0f);

      // Calculate the projection and view transformation
      Matrix.multiplyMM(mMVPMatrix, 0, mProjectionMatrix, 0, mViewMatrix, 0);

      // zoom out a bit
      Matrix.translateM(mMVPMatrix, 0, 0, 0, 4.5f);


      int angleOffset = 5;
      // update the angles for the x and y rotation
      if (cube.getRotateDirection().equals(Cube.RotateDirection.LEFT)) {
      cube.setAngleY(cube.getAngleY() - angleOffset);
      } else if (cube.getRotateDirection().equals(Cube.RotateDirection.RIGHT)) {
      cube.setAngleY(cube.getAngleY() + angleOffset);
      } else if (cube.getRotateDirection().equals(Cube.RotateDirection.UP)) {
      cube.setAngleX(cube.getAngleX() + angleOffset);
      } else if (cube.getRotateDirection().equals(Cube.RotateDirection.DOWN)) {
      cube.setAngleX(cube.getAngleX() - angleOffset);
      }
      firstRotation = false;

      // rotate and draw
      rotate();
      cube.draw(mMVPMatrix);


      // test if rotation should be stopped (lock in each 90° step)
      if (cube.getRotateDirection().equals(Cube.RotateDirection.LEFT) ||
      cube.getRotateDirection().equals(Cube.RotateDirection.RIGHT)) {
      if (!firstRotation && cube.getAngleY() % 90 == 0) {
      cube.setRotation(Cube.RotateDirection.NONE);
      }
      }
      if (cube.getRotateDirection().equals(Cube.RotateDirection.UP) ||
      cube.getRotateDirection().equals(Cube.RotateDirection.DOWN)) {
      if (!firstRotation && cube.getAngleX() % 90 == 0) {
      cube.setRotation(Cube.RotateDirection.NONE);
      }
      }

      Log.i("MyGLRENDER~ ", cube.getRotateDirection().toString());
      }

      private void rotate() {
      float rotationMatrix = new float[16];
      Matrix.setIdentityM(rotationMatrix, 0);

      // rotate in x and y direction, apply that to the intermediate matrix
      Matrix.rotateM(rotationMatrix, 0, cube.getAngleX(), 1, 0, 0);
      Matrix.rotateM(rotationMatrix, 0, cube.getAngleY(), 0, 1, 0);
      Matrix.multiplyMM(mMVPMatrix, 0, mMVPMatrix, 0, rotationMatrix, 0);
      }

      // Call back after onSurfaceCreated() or whenever the window's size changes
      @Override
      public void onSurfaceChanged(GL10 gl, int width, int height) {
      GLES20.glViewport(0, 0, width, height);
      float ratio = (float) width / height;

      // this projection matrix is applied to object coordinates
      // in the onDrawFrame() method
      Matrix.frustumM(mProjectionMatrix, 0, -ratio, ratio, -1, 1, 3, 7);
      }

      public Cube20 getCube() {
      return cube;
      }
      }

      public class MyGLActivity extends Activity {

      private GLSurfaceView glView; // Use GLSurfaceView

      // Call back when the activity is started, to initialize the view
      @Override
      protected void onCreate(Bundle savedInstanceState) {
      super.onCreate(savedInstanceState);
      glView = new MyGLSurfaceView(this); // Allocate a GLSurfaceView
      this.setContentView(glView); // This activity sets to GLSurfaceView
      }

      // Call back when the activity is going into the background
      @Override
      protected void onPause() {
      super.onPause();
      glView.onPause();
      }

      // Call back after onPause()
      @Override
      protected void onResume() {
      super.onResume();
      glView.onResume();
      }
      }


      The behaviour im expecting:




      • successful swipe reaction and rotation about x-axis
        (intermediate screenshot)


      • successful swipe reaction and rotation about y-axis
        (intermediate screenshot)



      With this setup i encounter the following problems:




      1. Sometimes the rotation is done about the z-axis
        (intermediate screenshot)

        I never implemented a rotation about the z-axis for the cube, i am not sure why this rotation is done. I could only imagine that there is maybe a threading problem between the swipe listener at MyGLSurfaceView and MyGLRenderer (which holds the reference to the cube).


      The rotation is done at MyGLRenderer.rotate. The cube uses separate angles (x/y) and also there is a property for which rotation is currently done (LEFT, RIGHT, UP, DOWN, NONE), which is updated in OnSwipeListener.




      1. Also if the bug with the z-rotation (1.) happened, the rotation about the x or y axis is done in the wrong direction (left/right, up/down swapped)


      My guess is that either the angles or the RotateDirection is not updated correctly.
      The app is tested on a OnePlus 3T @ Android 8.0, IDE is Android Studio 3.2.1.







      java android opengl-es opengl-es-2.0






      share|improve this question















      share|improve this question













      share|improve this question




      share|improve this question








      edited Nov 17 '18 at 12:32









      Rabbid76

      36.7k113247




      36.7k113247










      asked Nov 17 '18 at 9:28









      Matthias TietzMatthias Tietz

      104




      104
























          1 Answer
          1






          active

          oldest

          votes


















          0














          If you have rotated a cube by 90° around the X-axis, then of course a rotation on around the Y-axis of the cube is a rotation around the Z-axis in the world. You have to rotate around the Y-axis of the world, not around the Y-axis of the cube.

          To do so you have to store the concatenated rotations of the cube in a rotation matrix and to apply the new rotation to this matrix.



          Create a member for rotation matrix:



          private final float mRotationMatrix = new float[16];


          Initialize it by the identity matrix:



          Matrix.setIdentityM(mRotationMatrix, 0);


          In the method rotate you have to apply the current animation to the rotation matrix. The order has to be animationMatrix * mRotationMatrix. The matrix multiplication is not commutative. If you don't respect the order of the multiplication you will get the same result as before and the rotation would be around the axis of the cube and not around the axis of the world.



          private void rotate() {
          float animationMatrix = new float[16];
          Matrix.setIdentityM(animationMatrix, 0);

          // rotate in x and y direction, apply that to the intermediate matrix
          Matrix.rotateM(animationMatrix, 0, cube.getAngleX(), 1, 0, 0);
          Matrix.rotateM(animationMatrix, 0, cube.getAngleY(), 0, 1, 0);

          // concatenate the animation and the rotation matrix; the order is important
          Matrix.multiplyMM(animationMatrix, 0, animationMatrix, 0, mRotationMatrix, 0);

          Matrix.multiplyMM(mMVPMatrix, 0, mMVPMatrix, 0, animationMatrix, 0);
          }


          When the animation has reached full 90° then you have to change the rotation matrix and to reset the rotation angle:



          if (cube.getRotateDirection().equals(Cube.RotateDirection.LEFT) ||
          cube.getRotateDirection().equals(Cube.RotateDirection.RIGHT)) {
          if (!firstRotation && cube.getAngleY() % 90 == 0) {

          float newRotationMatrix = new float[16];
          Matrix.setIdentityM(newRotationMatrix, 0);
          Matrix.rotateM(newRotationMatrix, 0, cube.getAngleY(), 0, 1, 0);

          // concatenate the new 90 rotation to the rotation matrix
          Matrix.multiplyMM(mRotationMatrix, 0, newRotationMatrix, 0, mRotationMatrix, 0);

          // reset the angle
          cube.setAngleY(0);
          cube.setRotation(Cube.RotateDirection.NONE);
          }
          }
          if (cube.getRotateDirection().equals(Cube.RotateDirection.UP) ||
          cube.getRotateDirection().equals(Cube.RotateDirection.DOWN)) {
          if (!firstRotation && cube.getAngleX() % 90 == 0) {

          float newRotationMatrix = new float[16];
          Matrix.setIdentityM(newRotationMatrix, 0);
          Matrix.rotateM(newRotationMatrix, 0, cube.getAngleX(), 1, 0, 0);

          // concatenate the new 90 rotation to the rotation matrix
          Matrix.multiplyMM(mRotationMatrix, 0, newRotationMatrix, 0, mRotationMatrix, 0);

          // reset the angle
          cube.setAngleX(0);
          cube.setRotation(Cube.RotateDirection.NONE);
          }
          }





          share|improve this answer


























          • Thanks, works like a charm now! So basically, i already had this idea and wanted to rotate about the axis of the world. But meanwhile i was actually rotating the whole axis-system of the world and not correctly rotating the cube, right?

            – Matthias Tietz
            Nov 17 '18 at 20:01











          • @MatthiasTietz The issue is that your original code does rotateX * rotateY. This would work if the first rotation is around the Y-axis and the 2nd is around the X-axis, but it won't work if the first rotation is around the X and the 2nd is around Y. Then you would have to do rotateY * rotateX. If you want 1 rotation around X than around Y and a 3rd again around X, then you would have to do rotateX2*rotateY*rotateX1. This is what the code of the answer does, because all previous rotations are collected in mRotationMatrix.

            – Rabbid76
            Nov 17 '18 at 20:20













          Your Answer






          StackExchange.ifUsing("editor", function () {
          StackExchange.using("externalEditor", function () {
          StackExchange.using("snippets", function () {
          StackExchange.snippets.init();
          });
          });
          }, "code-snippets");

          StackExchange.ready(function() {
          var channelOptions = {
          tags: "".split(" "),
          id: "1"
          };
          initTagRenderer("".split(" "), "".split(" "), channelOptions);

          StackExchange.using("externalEditor", function() {
          // Have to fire editor after snippets, if snippets enabled
          if (StackExchange.settings.snippets.snippetsEnabled) {
          StackExchange.using("snippets", function() {
          createEditor();
          });
          }
          else {
          createEditor();
          }
          });

          function createEditor() {
          StackExchange.prepareEditor({
          heartbeatType: 'answer',
          autoActivateHeartbeat: false,
          convertImagesToLinks: true,
          noModals: true,
          showLowRepImageUploadWarning: true,
          reputationToPostImages: 10,
          bindNavPrevention: true,
          postfix: "",
          imageUploader: {
          brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
          contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
          allowUrls: true
          },
          onDemand: true,
          discardSelector: ".discard-answer"
          ,immediatelyShowMarkdownHelp:true
          });


          }
          });














          draft saved

          draft discarded


















          StackExchange.ready(
          function () {
          StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53349892%2fopengl-es-2-0-android-cube-rotation-bug%23new-answer', 'question_page');
          }
          );

          Post as a guest















          Required, but never shown

























          1 Answer
          1






          active

          oldest

          votes








          1 Answer
          1






          active

          oldest

          votes









          active

          oldest

          votes






          active

          oldest

          votes









          0














          If you have rotated a cube by 90° around the X-axis, then of course a rotation on around the Y-axis of the cube is a rotation around the Z-axis in the world. You have to rotate around the Y-axis of the world, not around the Y-axis of the cube.

          To do so you have to store the concatenated rotations of the cube in a rotation matrix and to apply the new rotation to this matrix.



          Create a member for rotation matrix:



          private final float mRotationMatrix = new float[16];


          Initialize it by the identity matrix:



          Matrix.setIdentityM(mRotationMatrix, 0);


          In the method rotate you have to apply the current animation to the rotation matrix. The order has to be animationMatrix * mRotationMatrix. The matrix multiplication is not commutative. If you don't respect the order of the multiplication you will get the same result as before and the rotation would be around the axis of the cube and not around the axis of the world.



          private void rotate() {
          float animationMatrix = new float[16];
          Matrix.setIdentityM(animationMatrix, 0);

          // rotate in x and y direction, apply that to the intermediate matrix
          Matrix.rotateM(animationMatrix, 0, cube.getAngleX(), 1, 0, 0);
          Matrix.rotateM(animationMatrix, 0, cube.getAngleY(), 0, 1, 0);

          // concatenate the animation and the rotation matrix; the order is important
          Matrix.multiplyMM(animationMatrix, 0, animationMatrix, 0, mRotationMatrix, 0);

          Matrix.multiplyMM(mMVPMatrix, 0, mMVPMatrix, 0, animationMatrix, 0);
          }


          When the animation has reached full 90° then you have to change the rotation matrix and to reset the rotation angle:



          if (cube.getRotateDirection().equals(Cube.RotateDirection.LEFT) ||
          cube.getRotateDirection().equals(Cube.RotateDirection.RIGHT)) {
          if (!firstRotation && cube.getAngleY() % 90 == 0) {

          float newRotationMatrix = new float[16];
          Matrix.setIdentityM(newRotationMatrix, 0);
          Matrix.rotateM(newRotationMatrix, 0, cube.getAngleY(), 0, 1, 0);

          // concatenate the new 90 rotation to the rotation matrix
          Matrix.multiplyMM(mRotationMatrix, 0, newRotationMatrix, 0, mRotationMatrix, 0);

          // reset the angle
          cube.setAngleY(0);
          cube.setRotation(Cube.RotateDirection.NONE);
          }
          }
          if (cube.getRotateDirection().equals(Cube.RotateDirection.UP) ||
          cube.getRotateDirection().equals(Cube.RotateDirection.DOWN)) {
          if (!firstRotation && cube.getAngleX() % 90 == 0) {

          float newRotationMatrix = new float[16];
          Matrix.setIdentityM(newRotationMatrix, 0);
          Matrix.rotateM(newRotationMatrix, 0, cube.getAngleX(), 1, 0, 0);

          // concatenate the new 90 rotation to the rotation matrix
          Matrix.multiplyMM(mRotationMatrix, 0, newRotationMatrix, 0, mRotationMatrix, 0);

          // reset the angle
          cube.setAngleX(0);
          cube.setRotation(Cube.RotateDirection.NONE);
          }
          }





          share|improve this answer


























          • Thanks, works like a charm now! So basically, i already had this idea and wanted to rotate about the axis of the world. But meanwhile i was actually rotating the whole axis-system of the world and not correctly rotating the cube, right?

            – Matthias Tietz
            Nov 17 '18 at 20:01











          • @MatthiasTietz The issue is that your original code does rotateX * rotateY. This would work if the first rotation is around the Y-axis and the 2nd is around the X-axis, but it won't work if the first rotation is around the X and the 2nd is around Y. Then you would have to do rotateY * rotateX. If you want 1 rotation around X than around Y and a 3rd again around X, then you would have to do rotateX2*rotateY*rotateX1. This is what the code of the answer does, because all previous rotations are collected in mRotationMatrix.

            – Rabbid76
            Nov 17 '18 at 20:20


















          0














          If you have rotated a cube by 90° around the X-axis, then of course a rotation on around the Y-axis of the cube is a rotation around the Z-axis in the world. You have to rotate around the Y-axis of the world, not around the Y-axis of the cube.

          To do so you have to store the concatenated rotations of the cube in a rotation matrix and to apply the new rotation to this matrix.



          Create a member for rotation matrix:



          private final float mRotationMatrix = new float[16];


          Initialize it by the identity matrix:



          Matrix.setIdentityM(mRotationMatrix, 0);


          In the method rotate you have to apply the current animation to the rotation matrix. The order has to be animationMatrix * mRotationMatrix. The matrix multiplication is not commutative. If you don't respect the order of the multiplication you will get the same result as before and the rotation would be around the axis of the cube and not around the axis of the world.



          private void rotate() {
          float animationMatrix = new float[16];
          Matrix.setIdentityM(animationMatrix, 0);

          // rotate in x and y direction, apply that to the intermediate matrix
          Matrix.rotateM(animationMatrix, 0, cube.getAngleX(), 1, 0, 0);
          Matrix.rotateM(animationMatrix, 0, cube.getAngleY(), 0, 1, 0);

          // concatenate the animation and the rotation matrix; the order is important
          Matrix.multiplyMM(animationMatrix, 0, animationMatrix, 0, mRotationMatrix, 0);

          Matrix.multiplyMM(mMVPMatrix, 0, mMVPMatrix, 0, animationMatrix, 0);
          }


          When the animation has reached full 90° then you have to change the rotation matrix and to reset the rotation angle:



          if (cube.getRotateDirection().equals(Cube.RotateDirection.LEFT) ||
          cube.getRotateDirection().equals(Cube.RotateDirection.RIGHT)) {
          if (!firstRotation && cube.getAngleY() % 90 == 0) {

          float newRotationMatrix = new float[16];
          Matrix.setIdentityM(newRotationMatrix, 0);
          Matrix.rotateM(newRotationMatrix, 0, cube.getAngleY(), 0, 1, 0);

          // concatenate the new 90 rotation to the rotation matrix
          Matrix.multiplyMM(mRotationMatrix, 0, newRotationMatrix, 0, mRotationMatrix, 0);

          // reset the angle
          cube.setAngleY(0);
          cube.setRotation(Cube.RotateDirection.NONE);
          }
          }
          if (cube.getRotateDirection().equals(Cube.RotateDirection.UP) ||
          cube.getRotateDirection().equals(Cube.RotateDirection.DOWN)) {
          if (!firstRotation && cube.getAngleX() % 90 == 0) {

          float newRotationMatrix = new float[16];
          Matrix.setIdentityM(newRotationMatrix, 0);
          Matrix.rotateM(newRotationMatrix, 0, cube.getAngleX(), 1, 0, 0);

          // concatenate the new 90 rotation to the rotation matrix
          Matrix.multiplyMM(mRotationMatrix, 0, newRotationMatrix, 0, mRotationMatrix, 0);

          // reset the angle
          cube.setAngleX(0);
          cube.setRotation(Cube.RotateDirection.NONE);
          }
          }





          share|improve this answer


























          • Thanks, works like a charm now! So basically, i already had this idea and wanted to rotate about the axis of the world. But meanwhile i was actually rotating the whole axis-system of the world and not correctly rotating the cube, right?

            – Matthias Tietz
            Nov 17 '18 at 20:01











          • @MatthiasTietz The issue is that your original code does rotateX * rotateY. This would work if the first rotation is around the Y-axis and the 2nd is around the X-axis, but it won't work if the first rotation is around the X and the 2nd is around Y. Then you would have to do rotateY * rotateX. If you want 1 rotation around X than around Y and a 3rd again around X, then you would have to do rotateX2*rotateY*rotateX1. This is what the code of the answer does, because all previous rotations are collected in mRotationMatrix.

            – Rabbid76
            Nov 17 '18 at 20:20
















          0












          0








          0







          If you have rotated a cube by 90° around the X-axis, then of course a rotation on around the Y-axis of the cube is a rotation around the Z-axis in the world. You have to rotate around the Y-axis of the world, not around the Y-axis of the cube.

          To do so you have to store the concatenated rotations of the cube in a rotation matrix and to apply the new rotation to this matrix.



          Create a member for rotation matrix:



          private final float mRotationMatrix = new float[16];


          Initialize it by the identity matrix:



          Matrix.setIdentityM(mRotationMatrix, 0);


          In the method rotate you have to apply the current animation to the rotation matrix. The order has to be animationMatrix * mRotationMatrix. The matrix multiplication is not commutative. If you don't respect the order of the multiplication you will get the same result as before and the rotation would be around the axis of the cube and not around the axis of the world.



          private void rotate() {
          float animationMatrix = new float[16];
          Matrix.setIdentityM(animationMatrix, 0);

          // rotate in x and y direction, apply that to the intermediate matrix
          Matrix.rotateM(animationMatrix, 0, cube.getAngleX(), 1, 0, 0);
          Matrix.rotateM(animationMatrix, 0, cube.getAngleY(), 0, 1, 0);

          // concatenate the animation and the rotation matrix; the order is important
          Matrix.multiplyMM(animationMatrix, 0, animationMatrix, 0, mRotationMatrix, 0);

          Matrix.multiplyMM(mMVPMatrix, 0, mMVPMatrix, 0, animationMatrix, 0);
          }


          When the animation has reached full 90° then you have to change the rotation matrix and to reset the rotation angle:



          if (cube.getRotateDirection().equals(Cube.RotateDirection.LEFT) ||
          cube.getRotateDirection().equals(Cube.RotateDirection.RIGHT)) {
          if (!firstRotation && cube.getAngleY() % 90 == 0) {

          float newRotationMatrix = new float[16];
          Matrix.setIdentityM(newRotationMatrix, 0);
          Matrix.rotateM(newRotationMatrix, 0, cube.getAngleY(), 0, 1, 0);

          // concatenate the new 90 rotation to the rotation matrix
          Matrix.multiplyMM(mRotationMatrix, 0, newRotationMatrix, 0, mRotationMatrix, 0);

          // reset the angle
          cube.setAngleY(0);
          cube.setRotation(Cube.RotateDirection.NONE);
          }
          }
          if (cube.getRotateDirection().equals(Cube.RotateDirection.UP) ||
          cube.getRotateDirection().equals(Cube.RotateDirection.DOWN)) {
          if (!firstRotation && cube.getAngleX() % 90 == 0) {

          float newRotationMatrix = new float[16];
          Matrix.setIdentityM(newRotationMatrix, 0);
          Matrix.rotateM(newRotationMatrix, 0, cube.getAngleX(), 1, 0, 0);

          // concatenate the new 90 rotation to the rotation matrix
          Matrix.multiplyMM(mRotationMatrix, 0, newRotationMatrix, 0, mRotationMatrix, 0);

          // reset the angle
          cube.setAngleX(0);
          cube.setRotation(Cube.RotateDirection.NONE);
          }
          }





          share|improve this answer















          If you have rotated a cube by 90° around the X-axis, then of course a rotation on around the Y-axis of the cube is a rotation around the Z-axis in the world. You have to rotate around the Y-axis of the world, not around the Y-axis of the cube.

          To do so you have to store the concatenated rotations of the cube in a rotation matrix and to apply the new rotation to this matrix.



          Create a member for rotation matrix:



          private final float mRotationMatrix = new float[16];


          Initialize it by the identity matrix:



          Matrix.setIdentityM(mRotationMatrix, 0);


          In the method rotate you have to apply the current animation to the rotation matrix. The order has to be animationMatrix * mRotationMatrix. The matrix multiplication is not commutative. If you don't respect the order of the multiplication you will get the same result as before and the rotation would be around the axis of the cube and not around the axis of the world.



          private void rotate() {
          float animationMatrix = new float[16];
          Matrix.setIdentityM(animationMatrix, 0);

          // rotate in x and y direction, apply that to the intermediate matrix
          Matrix.rotateM(animationMatrix, 0, cube.getAngleX(), 1, 0, 0);
          Matrix.rotateM(animationMatrix, 0, cube.getAngleY(), 0, 1, 0);

          // concatenate the animation and the rotation matrix; the order is important
          Matrix.multiplyMM(animationMatrix, 0, animationMatrix, 0, mRotationMatrix, 0);

          Matrix.multiplyMM(mMVPMatrix, 0, mMVPMatrix, 0, animationMatrix, 0);
          }


          When the animation has reached full 90° then you have to change the rotation matrix and to reset the rotation angle:



          if (cube.getRotateDirection().equals(Cube.RotateDirection.LEFT) ||
          cube.getRotateDirection().equals(Cube.RotateDirection.RIGHT)) {
          if (!firstRotation && cube.getAngleY() % 90 == 0) {

          float newRotationMatrix = new float[16];
          Matrix.setIdentityM(newRotationMatrix, 0);
          Matrix.rotateM(newRotationMatrix, 0, cube.getAngleY(), 0, 1, 0);

          // concatenate the new 90 rotation to the rotation matrix
          Matrix.multiplyMM(mRotationMatrix, 0, newRotationMatrix, 0, mRotationMatrix, 0);

          // reset the angle
          cube.setAngleY(0);
          cube.setRotation(Cube.RotateDirection.NONE);
          }
          }
          if (cube.getRotateDirection().equals(Cube.RotateDirection.UP) ||
          cube.getRotateDirection().equals(Cube.RotateDirection.DOWN)) {
          if (!firstRotation && cube.getAngleX() % 90 == 0) {

          float newRotationMatrix = new float[16];
          Matrix.setIdentityM(newRotationMatrix, 0);
          Matrix.rotateM(newRotationMatrix, 0, cube.getAngleX(), 1, 0, 0);

          // concatenate the new 90 rotation to the rotation matrix
          Matrix.multiplyMM(mRotationMatrix, 0, newRotationMatrix, 0, mRotationMatrix, 0);

          // reset the angle
          cube.setAngleX(0);
          cube.setRotation(Cube.RotateDirection.NONE);
          }
          }






          share|improve this answer














          share|improve this answer



          share|improve this answer








          edited Nov 18 '18 at 15:53









          Matthias Tietz

          104




          104










          answered Nov 17 '18 at 13:32









          Rabbid76Rabbid76

          36.7k113247




          36.7k113247













          • Thanks, works like a charm now! So basically, i already had this idea and wanted to rotate about the axis of the world. But meanwhile i was actually rotating the whole axis-system of the world and not correctly rotating the cube, right?

            – Matthias Tietz
            Nov 17 '18 at 20:01











          • @MatthiasTietz The issue is that your original code does rotateX * rotateY. This would work if the first rotation is around the Y-axis and the 2nd is around the X-axis, but it won't work if the first rotation is around the X and the 2nd is around Y. Then you would have to do rotateY * rotateX. If you want 1 rotation around X than around Y and a 3rd again around X, then you would have to do rotateX2*rotateY*rotateX1. This is what the code of the answer does, because all previous rotations are collected in mRotationMatrix.

            – Rabbid76
            Nov 17 '18 at 20:20





















          • Thanks, works like a charm now! So basically, i already had this idea and wanted to rotate about the axis of the world. But meanwhile i was actually rotating the whole axis-system of the world and not correctly rotating the cube, right?

            – Matthias Tietz
            Nov 17 '18 at 20:01











          • @MatthiasTietz The issue is that your original code does rotateX * rotateY. This would work if the first rotation is around the Y-axis and the 2nd is around the X-axis, but it won't work if the first rotation is around the X and the 2nd is around Y. Then you would have to do rotateY * rotateX. If you want 1 rotation around X than around Y and a 3rd again around X, then you would have to do rotateX2*rotateY*rotateX1. This is what the code of the answer does, because all previous rotations are collected in mRotationMatrix.

            – Rabbid76
            Nov 17 '18 at 20:20



















          Thanks, works like a charm now! So basically, i already had this idea and wanted to rotate about the axis of the world. But meanwhile i was actually rotating the whole axis-system of the world and not correctly rotating the cube, right?

          – Matthias Tietz
          Nov 17 '18 at 20:01





          Thanks, works like a charm now! So basically, i already had this idea and wanted to rotate about the axis of the world. But meanwhile i was actually rotating the whole axis-system of the world and not correctly rotating the cube, right?

          – Matthias Tietz
          Nov 17 '18 at 20:01













          @MatthiasTietz The issue is that your original code does rotateX * rotateY. This would work if the first rotation is around the Y-axis and the 2nd is around the X-axis, but it won't work if the first rotation is around the X and the 2nd is around Y. Then you would have to do rotateY * rotateX. If you want 1 rotation around X than around Y and a 3rd again around X, then you would have to do rotateX2*rotateY*rotateX1. This is what the code of the answer does, because all previous rotations are collected in mRotationMatrix.

          – Rabbid76
          Nov 17 '18 at 20:20







          @MatthiasTietz The issue is that your original code does rotateX * rotateY. This would work if the first rotation is around the Y-axis and the 2nd is around the X-axis, but it won't work if the first rotation is around the X and the 2nd is around Y. Then you would have to do rotateY * rotateX. If you want 1 rotation around X than around Y and a 3rd again around X, then you would have to do rotateX2*rotateY*rotateX1. This is what the code of the answer does, because all previous rotations are collected in mRotationMatrix.

          – Rabbid76
          Nov 17 '18 at 20:20




















          draft saved

          draft discarded




















































          Thanks for contributing an answer to Stack Overflow!


          • Please be sure to answer the question. Provide details and share your research!

          But avoid



          • Asking for help, clarification, or responding to other answers.

          • Making statements based on opinion; back them up with references or personal experience.


          To learn more, see our tips on writing great answers.




          draft saved


          draft discarded














          StackExchange.ready(
          function () {
          StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53349892%2fopengl-es-2-0-android-cube-rotation-bug%23new-answer', 'question_page');
          }
          );

          Post as a guest















          Required, but never shown





















































          Required, but never shown














          Required, but never shown












          Required, but never shown







          Required, but never shown

































          Required, but never shown














          Required, but never shown












          Required, but never shown







          Required, but never shown







          這個網誌中的熱門文章

          Xamarin.form Move up view when keyboard appear

          Post-Redirect-Get with Spring WebFlux and Thymeleaf

          Anylogic : not able to use stopDelay()