0xCursor
0xCursor

Reputation: 2268

How to pinch zoom into a certain location in OpenGL ES 2?

There are other posts on Stack Overflow on pinch zooming, but I haven't found any helpful ones for OpenGL that do what I'm looking for. I am currently using the orthoM function to change the camera position and to do scaling in OpenGL. I have gotten the camera to move around, and have gotten pinch zooming to work, but the zooming always zooms into the center of the OpenGL surface view coordinate system at 0,0. After trying different things, I haven't found a way yet that allows the camera to move around, while also allowing pinch zooming to the user's touch point (as an example, the touch controls in Clash of Clans is similar to what I am trying to make).

(The method I'm currently using to get the scale value is based on this post.)

My first attempt:

// mX and mY are the movement offsets based on the user's touch movements,
// and can be positive or negative
Matrix.orthoM(mProjectionMatrix, 0, ((-WIDTH/2f)+mX)*scale, ((WIDTH/2f)+mX)*scale,
                                    ((-HEIGHT/2f)+mY)*scale, ((HEIGHT/2f)+mY)*scale, 1f, 2f);

In the above code, I realize that the camera moves towards the coordinate 0,0 because as scale gets increasingly smaller, the values for the camera edges decrease towards 0. So although the zoom goes towards the coordinate system center, the movement of the camera moves at the right speeds at any scale level.

So, I then edited the code to this:

Matrix.orthoM(mProjectionMatrix, 0, (-WIDTH/2f)*scale+mX, (WIDTH/2f)*scale+mX,
                                    (-HEIGHT/2f)*scale+mY, (HEIGHT/2f)*scale+mY, 1f, 2f);

The edited code now makes the zoom go toward the center of the screen no matter where in the surface view coordinate system the camera is (although that isn't the full goal), but the camera movement is off, as the offset isn't adjusted for the different scale levels.

I'm still working to find a solution myself, but if anyone has any advice or ideas on how this could be implemented, I would be glad to hear.

Note, I don't think it matters, but I'm doing this in Android and using Java.

EDIT:

Since I first posted this question, I have made some changes to my code. I found this post, which explains the logic of how to pan the camera to the correct position based on the scale, so that the zoompoint remains in the same position.

My updated attempt:

// Only do the following if-block if two fingers are on the screen
if (zooming) {
    // midPoint is a PointF object that stores the coordinate of the midpoint between
    //two fingers
    float scaleChange = scale - prevScale;  // scale is the same as in my previous code
    float offsetX = -(midPoint.x*scaleChange);
    float offsetY = -(midPoint.y*scaleChange);
    cameraPos.x += offsetX;
    cameraPos.y += offsetY;
}

// cameraPos is a PointF object that stores the coordinate at the center of the screen, 
// and replaces the previous values mX and mY
left = cameraPos.x-(WIDTH/2f)*scale;
right = cameraPos.x+(WIDTH/2f)*scale;
bottom = cameraPos.y-(HEIGHT/2f)*scale;
top = cameraPos.y+(HEIGHT/2f)*scale;

Matrix.orthoM(mProjectionMatrix, 0, left, right, bottom, top, 1f, 2f);

The code does work quite a bit better now, but it still isn't completely accurate. I tested how the code worked when panning was disabled, and the zooming worked sort of better. However, when the panning is enabled, the zooming doesn't focus in on the zoompoint at all.

Upvotes: 0

Views: 905

Answers (1)

0xCursor
0xCursor

Reputation: 2268

I finally found a solution while working on another project, so I'll post (in simplest form possible) what worked for me in case this could help anyone by chance.

final float currentPointersDistance = this.calculateDistance(pointer1CurrentX, pointer1CurrentY, pointer2CurrentX, pointer2CurrentY);
final float zoomFactorMultiplier = currentPointersDistance/initialPointerDistance; //> Get an initial distance between two pointers before calling this

final float newZoomFactor = previousZoomFactor*zoomFactorMultiplier;
final float zoomFactorChange = newZoomFactor-previousZoomFactor; //> previousZoomFactor is the current value of the zoom

//> The x and y values of the variables are in scene coordinate form (not surface)
final float distanceFromCenterToMidpointX = camera.getCenterX()-currentPointersMidpointX;
final float distanceFromCenterToMidpointY = camera.getCenterY()-currentPointersMidpointY;

final float offsetX = -(distanceFromCenterToMidpointX*zoomFactorChange/newZoomFactor);
final float offsetY = -(distanceFromCenterToMidpointY*zoomFactorChange/newZoomFactor);

camera.setZoomFactor(newZoomFactor);
camera.translate(offsetX, offsetY);

initialPointerDistance = currentPointersDistance; //> Make sure to do this

Method used to calculate the distance between two pointers:

public float calculateDistance(float pX1, float pY1, float pX2, float pY2) {
    float x = pX2-pX1;
    float y = pY2-pY1;
    return (float)Math.sqrt((x*x)+(y*y));
}

Camera class methods used above:

public float getXMin() {
    return centerX-((centerX-xMin)/zoomFactor);
}

public float getYMin() {
    return centerY-((centerY-yMin)/zoomFactor);
}

public float getXMax() {
    return centerX+((xMax-centerX)/zoomFactor);
}

public float getYMax() {
    return centerY+((yMax-centerY)/zoomFactor);
}

public void setZoomFactor(float pZoomFactor) {
    zoomFactor = pZoomFactor;
}

public void translate(float pX, float pY) {
    xMin += pX;
    yMin += pY;
    xMax += pX;
    yMax += pY;
}

The orthoM() function is called like the following:

Matrix.orthoM(projectionMatrix, 0, camera.getXMin(), camera.getXMax(), camera.getYMin(), camera.getYMax(), near, far);

Upvotes: 0

Related Questions