OpenGL ES 2.0 Android - cube rotation bug
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
successful swipe reaction and rotation about y-axis
With this setup i encounter the following problems:
- Sometimes the rotation is done about the z-axis
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 atMyGLSurfaceView
andMyGLRenderer
(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
.
- 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
add a comment |
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
successful swipe reaction and rotation about y-axis
With this setup i encounter the following problems:
- Sometimes the rotation is done about the z-axis
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 atMyGLSurfaceView
andMyGLRenderer
(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
.
- 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
add a comment |
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
successful swipe reaction and rotation about y-axis
With this setup i encounter the following problems:
- Sometimes the rotation is done about the z-axis
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 atMyGLSurfaceView
andMyGLRenderer
(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
.
- 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
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
successful swipe reaction and rotation about y-axis
With this setup i encounter the following problems:
- Sometimes the rotation is done about the z-axis
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 atMyGLSurfaceView
andMyGLRenderer
(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
.
- 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
java android opengl-es opengl-es-2.0
edited Nov 17 '18 at 12:32
Rabbid76
36.7k113247
36.7k113247
asked Nov 17 '18 at 9:28
Matthias TietzMatthias Tietz
104
104
add a comment |
add a comment |
1 Answer
1
active
oldest
votes
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);
}
}
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 doesrotateX * 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 dorotateY * rotateX
. If you want 1 rotation around X than around Y and a 3rd again around X, then you would have to dorotateX2*rotateY*rotateX1
. This is what the code of the answer does, because all previous rotations are collected inmRotationMatrix
.
– Rabbid76
Nov 17 '18 at 20:20
add a comment |
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
});
}
});
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
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
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);
}
}
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 doesrotateX * 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 dorotateY * rotateX
. If you want 1 rotation around X than around Y and a 3rd again around X, then you would have to dorotateX2*rotateY*rotateX1
. This is what the code of the answer does, because all previous rotations are collected inmRotationMatrix
.
– Rabbid76
Nov 17 '18 at 20:20
add a comment |
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);
}
}
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 doesrotateX * 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 dorotateY * rotateX
. If you want 1 rotation around X than around Y and a 3rd again around X, then you would have to dorotateX2*rotateY*rotateX1
. This is what the code of the answer does, because all previous rotations are collected inmRotationMatrix
.
– Rabbid76
Nov 17 '18 at 20:20
add a comment |
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);
}
}
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);
}
}
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 doesrotateX * 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 dorotateY * rotateX
. If you want 1 rotation around X than around Y and a 3rd again around X, then you would have to dorotateX2*rotateY*rotateX1
. This is what the code of the answer does, because all previous rotations are collected inmRotationMatrix
.
– Rabbid76
Nov 17 '18 at 20:20
add a comment |
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 doesrotateX * 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 dorotateY * rotateX
. If you want 1 rotation around X than around Y and a 3rd again around X, then you would have to dorotateX2*rotateY*rotateX1
. This is what the code of the answer does, because all previous rotations are collected inmRotationMatrix
.
– 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
add a comment |
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.
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
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
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
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