Search code examples
androidmatrixopengl-esopengl-es-2.0touch-event

Rotate camera around scene with scrolling opengl ES


I am trying to rotate the camera around the scene using touch (scroll):

public class SurfaceView extends GLSurfaceView 
        implements GestureDetector.OnGestureListener {
    SceneRenderer renderer;        

    @Override
    public boolean onScroll(MotionEvent e1, MotionEvent e2,
                      float distanceX, float distanceY) {
        renderer.setMotion(distanceX, distanceY); // pass movement to render
        return true;
    }    
}

The camera moves in the render as follows:

public class SceneRenderer implements GLSurfaceView.Renderer {
    private float[] viewMatrix = new float[16];
    
    @Override
    public void onSurfaceCreated(GL10 gl, EGLConfig config) {
        Matrix.setLookAtM(viewMatrix, 0, 0f, 0f, 3f,
        0f, 0f, 0f, 0f, 1.0f, 0.0f);
    }

    ...
    // movement of camera
    public synchronized void setMotion(float xDistance, float yDistance) {
        Matrix.rotateM(viewMatrix, 0, -xDistance * 0.1f, 0, 1, 0);
        Matrix.rotateM(viewMatrix, 0, -yDistance * 0.1f, 1, 0, 0);
    }
}

This works well at first. But then the camera starts to rotate not as expected. How can I make the camera move correctly relative to the center of the scene (object) using scrolling? Maybe someone solved a similar problem?

Thank you for any answer/comment!

Workaround solution:

  private float kx = 0f;
  private float ky = 0f;
  private float radius = 3.0f;
  private float x, y, z = 0f;

  ...
  public synchronized void setMotion(float xDistance, float yDistance) {
      kx = kx + xDistance * 0.001f;
      x = (float) (radius * Math.sin(kx));
      z = (float) (radius * Math.cos(kx));

      ky = ky + yDistance * 0.001f;
      y = (float) (radius * Math.sin(ky));

      Matrix.setLookAtM(viewMatrix, 0, x, -y, z, 0f, 0f, 0f, 0f, 1.0f, 0.0f);
  }

But with this approach, the camera moves away from the scene when rotating along the Y-axis.


Solution

  • In matrix algebra, order does matter. Since incremental rotations interleave matrices and break order, it won't work as you expect. Thus you need to rebuild the view-matrix based on cumulative delta x/y on every setMotion() call so that rotations would always be applied in right order as follows.

    public class SceneRenderer implements GLSurfaceView.Renderer {
        private float[] viewMatrix = new float[16];
        private float cumulativeX = 0.0f;
        private float cumulativeY = 0.0f;
    
        @Override
        public void onSurfaceCreated(GL10 gl, EGLConfig config) {
            Matrix.setLookAtM(viewMatrix, 0, 0f, 0f, 3f,
            0f, 0f, 0f, 0f, 1.0f, 0.0f);
            cumulativeX = 0.0f;
            cumulativeY = 0.0f;
        }
    
        ...
        // movement of camera
        public synchronized void setMotion(float xDistance, float yDistance) {
    
            cumulativeX += xDistance;
            cumulativeY += yDistance;
    
            Matrix.setLookAtM(viewMatrix, 0, 0f, 0f, 3f,
                0f, 0f, 0f, 0f, 1.0f, 0.0f);
    
            //Matrix.rotateM(viewMatrix, 0, -cumulativeX * 0.1f, 0, 1, 0);
            //Matrix.rotateM(viewMatrix, 0, -cumulativeY * 0.1f, 1, 0, 0);
    
            Matrix.rotateM(viewMatrix, 0, -cumulativeY * 0.1f, 1, 0, 0);
            Matrix.rotateM(viewMatrix, 0, -cumulativeX * 0.1f, 0, 1, 0);
        }
    }
    

    Appendix: Composing view-matrix only by setLookAtM

    It's surely possible to achieve the same only with setLookAtM. To keep view-point in constant distance, move the view-point in spherical coordinates. And adjust up-direction too.

    public synchronized void setMotion(float xDistance, float yDistance) {
    
        kx = kx + xDistance * 0.001f;
        ky = ky + yDistance * 0.001f;
        
        x = (float) (radius * Math.cos(ky) * Math.sin(kx));
        z = (float) (radius * Math.cos(ky) * Math.cos(kx));
        y = (float) (radius * Math.sin(ky));
    
    
        float[] up =
        {
            (float) (Math.sin(ky) * Math.sin(kx)),
            (float) (Math.cos(ky)),
            (float) (Math.sin(ky) * Math.cos(kx)),
        };
        Matrix.setLookAtM(viewMatrix, 0,
                x, -y, z,
                0f, 0f, 0f,
                up[0], up[1], up[2]
        );
    
    }