Sign in to follow this  
tau_neutrino

Android, OpenGl ES, panning + zooming

Recommended Posts

Good day my fellow developers,

 

I'm a new here and this is my first post on GD. My background is 10 years of programming in different platforms and languages including C#, Java, Linux Kernel Dev, Java Card, ... Android is something new to me (4 months so far). Some month ago I started to use OpenGl ES.

 

So what I'm trying to write is OpenGl ES app which shows map. The map is rotated using magnetometer (map rotates in opposite direction of the device's rotation). For zooming I'm using ScaleGestureDetector and passing scale factor to Matrix.scaleM. My current problem is panning. 

 

GlSurfaceView side:

private void handlePanAndZoom(MotionEvent event) {
    int action = MotionEventCompat.getActionMasked(event);
    // Get the index of the pointer associated with the action.
    int index = MotionEventCompat.getActionIndex(event);
    int xPos = (int) MotionEventCompat.getX(event, index);
    int yPos = (int) MotionEventCompat.getY(event, index);

    mScaleDetector.onTouchEvent(event);

    switch (action) {
        case MotionEvent.ACTION_DOWN:
            mRenderer.handleStartPan(xPos, yPos);
            break;
        case MotionEvent.ACTION_MOVE:
            if (!mScaleDetector.isInProgress()) {
                mRenderer.handlePan(xPos, yPos);
            }
            break;
    }
}

Renderer side:

private static final PointF mPanStart = new PointF();
public void handleStartPan(final int x, final int y) {
    runOnGlThread(new Runnable() {
        @Override
        public void run() {
            windowToWorld(x, y, mPanStart);
        }
    });
}

private static final PointF mCurrentPan = new PointF();
public void handlePan(final int x, final int y) {
    runOnGlThread(new Runnable() {
        @Override
        public void run() {
            windowToWorld(x, y, mCurrentPan);
            float dx = mCurrentPan.x - mPanStart.x;
            float dy = mCurrentPan.y - mPanStart.y;
            mOffsetX += dx;
            mOffsetY += dy;
            updateModelMatrix();
            mPanStart.set(mCurrentPan);
        }
    });
}

windowToWorld function uses gluUnProject and I know it works because I'm using it for other tasks. updateModelMatrix() func:

private void updateModelMatrix() {
    Matrix.setIdentityM(mScaleMatrix,0);
    Matrix.scaleM(mScaleMatrix, 0, mScale, mScale, mScale);

    Matrix.setRotateM(mRotationMatrix, 0, mAngle, 0, 0, 1.0f);

    Matrix.setIdentityM(mTranslationMatrix,0);
    Matrix.translateM(mTranslationMatrix, 0, mOffsetX, mOffsetY, 0);

    // Model = Scale * Rotate * Translate
    Matrix.multiplyMM(mIntermediateMatrix, 0, mScaleMatrix, 0, mRotationMatrix, 0);
    Matrix.multiplyMM(mModelMatrix, 0, mIntermediateMatrix, 0, mTranslationMatrix, 0);
}

Same mModelMatrix is used in gluUnproject of windowToWorld function for point translation.

So my problem two-fold:

  1. The panning occurs twice slower than the movement of finger on the device's screen
  2. At some point when panning continuously for a few seconds (making circles on screen, for example) the map starts to 'shake'. The amplitude of this shaking is getting bigger and bigger. Looks like some value adds up in handlePan iterations and causes this effect.

Any idea why these happen?

Thank you in advance, Greg.

P.S> I didn't want to overwhelm the post with code, so I tried to put only relevant functions. Please let me know if there is anything missing to understand the question.

Share this post


Link to post
Share on other sites

I had the same issue with regards to panning/zooming. What I found was that it tends to be caused by resetting values while applying them. Your problem is this bit:

mPanStart.set(mCurrentPan);

 

The issue is caused by resetting the 'down' position (mPanStart). To explain what's going on a little, when you put your finger down on a location you are kinda pinning your finger to that location, when you move your finger, your finger is still on that location so you don't need to reset mPanStart because your finger is still on mPanStart (since you are working in world space). Where ever your finger moves it will always be in the exact same world space location because you have pinned it to the world so you never need to reset mPanStart. That's the idea anyway. 

 

Currently you reset the position to the position you move to. Imagine you put your finger down on world position (0, 0), you then more right 1 unit. You work out the difference (-1x) and move your offset.x by -1. Then you reset it as if your finger was on (1, 0) but really it is still on 0, 0. So the instant you move again (ever so slightly) the code works out that your finger is now on some value that is ever so close to (0, 0) and thinks you have moved -1x (since it thinks it was on (1, 0)) so it does a jump. Of course your code is working very fast so those big jumps don't happen, only very tiny ones but over time as you say it really starts to become noticeable.

 

In short, just remove that line. It might magically just work. If not, well hopefully you know what's causing the problem now so you at least have something to start with. This gets even more fun when you start allowing people to zoom and pan at the same time :/.

 

It's been quite some time since I worked on this, I took a look at my code to refresh what was going on but hopefully my answer will aid you.

Edited by Nanoha

Share this post


Link to post
Share on other sites

Oh. Two things:

0) I cannot express the happiness and my gratitude. I invested 3 full days in debugging this and I'm now amazed...

1) ... amazed how dumb I am! 

 

I'm sorry no other words. Thank you so much!!!

 

On a side note, do you have that zooming+panning code to learn from? Looks like I'm still amateur in this field.

 

Regards, Greg.

Share this post


Link to post
Share on other sites

Now to achieve zooming and panning at the same time I have removed the condition of:

if (!mScaleDetector.isInProgress()) {

 

This basically works, but until scale factor of 2. Further scaling up will cause same effect of 'shaking' I described in 2). Maybe I'm doing it wrong way?

Share this post


Link to post
Share on other sites

Oh. Two things:

0) I cannot express the happiness and my gratitude. I invested 3 full days in debugging this and I'm now amazed...

1) ... amazed how dumb I am! 

 

I'm sorry no other words. Thank you so much!!!

 

On a side note, do you have that zooming+panning code to learn from? Looks like I'm still amateur in this field.

 

Regards, Greg.

 

I also wasted a huge amount of time on this same problem!

 

I can't share my code as such because it is a bit sprawled and I am using NDK so I have to do all the motion stuff myself but when I have a few minutes I will have a look and give some pointers. It is only a few simple things like you have to change where the 'mPanStart' is when a user starts touching a second point or stops touching a second point.

 

This basically works, but until scale factor of 2. Further scaling up will cause same effect of 'shaking' I described in 2). Maybe I'm doing it wrong way?

 

I am not sure what is causing this particular issue. It could be a similar thing. I'd have to see the code where you zoom.

Share this post


Link to post
Share on other sites

Hi,

 

Well scaling is pretty much straight-forward:

    private class ScaleListener extends ScaleGestureDetector.SimpleOnScaleGestureListener {
        @Override
        public boolean onScale(ScaleGestureDetector detector) {
            mScaleFactor *= detector.getScaleFactor();

            // Don't let the object get too small or too large.
            mScaleFactor = Math.max(0.1f, Math.min(mScaleFactor, 5.0f));
            Log.d("SCALE", Float.toString(mScaleFactor));

            // Update scale
            mRenderer.setScale(mScaleFactor);
            requestRender();
            return true;
        }
    }

Renderer side of things:

    public void setScale(float scale) {
        mScale = scale;
        updateModelMatrix();
    }

And updateModelMatrix is the same as in first post.

 

If I neutralize the panning, scaling works perfectly. So the issue is still in panning I suppose.  If I try to pan with one finger (no scaling is activated) after scaled to factor of 2 or more, shaking starts again.

Share this post


Link to post
Share on other sites

Found the bug. I should remove the scale factor from calculation of dx/dy because in world coordinates it is already been taken into account. Strange it is not introduced in the code here. I git-stashed the panning - related code and when pop it back it merged incorrectly.

 

So far so good.

 

Thanks for your help!


I'll put the code of windowToWorld just for the record. Maybe it's buggy too.

    public void windowToWorld(int x, int y, PointF worldPoint) {
        final float WINDOW_Z_NEAR = 0.0f;
        final float WINDOW_Z_FAR = 1.0f;

        float rayStartPos[] = new float[4];
        float rayEndPos[] = new float[4];
        float modelView[] = new float[16];
        int viewport[] = {0, 0, mViewPortWidth, mViewPortHeight};

        // Model matrix is scale + rotation + translation
        Matrix.multiplyMM(modelView, 0, mViewMatrix, 0, mModelMatrix, 0);

        int windowX = x;
        int windowY = mViewPortHeight - y;

        int result = GLU.gluUnProject(windowX, windowY, WINDOW_Z_NEAR, modelView, 0, mProjectionMatrix, 0,
                viewport, 0, rayStartPos, 0);

        if (result == GL10.GL_TRUE) {
            rayStartPos[0] /= rayStartPos[3];
            rayStartPos[1] /= rayStartPos[3];
            rayStartPos[2] /= rayStartPos[3];
            System.arraycopy(rayStartPos, 0, mRay, 0, 3);
        }

        result = GLU.gluUnProject(windowX, windowY, WINDOW_Z_FAR, modelView, 0, mProjectionMatrix, 0,
                viewport, 0, rayEndPos, 0);

        if (result == GL10.GL_TRUE) {
            rayEndPos[0] /= rayEndPos[3];
            rayEndPos[1] /= rayEndPos[3];
            rayEndPos[2] /= rayEndPos[3];
            System.arraycopy(rayEndPos, 0, mRay, 3, 3);
        }

        worldPoint.x = mRay[0];
        worldPoint.y = mRay[1];
    }

Although now I use only 2D, I'm planning to switch to 3D in the next release. That's why I use ray calculation.

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this