I am making a 2d OpenGL app on iPad. And i need to implement a pinch/zoom.
I want to move the camera in (x,y)
plane and control camera x,y
and z
value with pinch gesture.
Each frame in update
method i make view matrix (camera) like this
lookAt = GLKMatrix4MakeLookAt(view_x, view_y, view_z, view_x, view_y, 0.0f, 0.0f, 1.0f, 0.0f);
where view_x, view_y and view_z are defined at program start like this:
view_x = view_y = 0.0f; view_z = kStartZoom;
kStartZoom is 3000.
So the camera is at (0,0,3000) and looks to (0,0,0)
The almost-working solution for handling pinch events is
- (IBAction) handlePinch:(UIPinchGestureRecognizer*) recognizer {
switch (recognizer.state)
{
case UIGestureRecognizerStateBegan:
{
if (recognizer.numberOfTouches == 2)
{
prevTouchOrigin1 = [recognizer locationOfTouch:0 inView:self.view];
prevTouchOrigin2 = [recognizer locationOfTouch:1 inView:self.view];
}
} break;
case UIGestureRecognizerStateChanged:
{
if (recognizer.numberOfTouches == 2)
{
CGFloat newDistance, oldDistance;
oldDistance = distanceBetweenTwoCGPoints(&prevTouchOrigin1, &prevTouchOrigin2);
currTouchOrigin1 = [recognizer locationOfTouch:0 inView:self.view];
currTouchOrigin2 = [recognizer locationOfTouch:1 inView:self.view];
newDistance = distanceBetweenTwoCGPoints(&currTouchOrigin1, &currTouchOrigin2);
if (newDistance == 0 || oldDistance == 0)
{
scaleFactor = 1;
} else {
scaleFactor = oldDistance / newDistance;
}
GLfloat check = view_z * scaleFactor;
if (check < kMinZoom || check > kMaxZoom)
return;
view_z *= scaleFactor;
// translate
// formula: newPos = currTouchOrigin + (objectOrigin - prevTouchOrigin) * scaleFactor
static CGPoint translationDelta;
GLfloat z_ratio = view_z_old / view_z;
newPos1.x = currTouchOrigin1.x - ((prevTouchOrigin1.x - view_x) * scaleFactor);
newPos1.y = currTouchOrigin1.y - ((prevTouchOrigin1.y - view_y) * scaleFactor);
newPos2.x = currTouchOrigin2.x - ((prevTouchOrigin2.x - view_x) * scaleFactor);
newPos2.y = currTouchOrigin2.y - ((prevTouchOrigin2.y - view_y) * scaleFactor);
midpoint = CGPointMidpoint(&newPos1, &newPos2);
translationDelta = CGPointMake(midpoint.x - view_x, midpoint.y - view_y);
view_x += translationDelta.x;
view_y -= translationDelta.y;
prevTouchOrigin1 = currTouchOrigin1;
prevTouchOrigin2 = currTouchOrigin2;
}
} break;
case UIGestureRecognizerStateEnded:
{
} break;
default :
{
}
}}
Allmost working.
I have more movement on x,y then i need so camera is weaving around.
Is the problem i am not applying some transforms from screen coord to world coords?
What can be the issue? Other examples i was looking into only modify camera position depending on distance between previous and last finger positions, which is what i am doing.
This is my solution:
I have two classes, one that takes care of all the OpenGL stuff (RenderViewController) and another one that takes care of all the gesture recognizers and communication between the OpenGL part and other parts of the app (EditViewController).
This is the code regarding the gestures:
EdtorViewController
It captures the gestures and send the info about them to the RenderViewController. You have to be careful because of the different coordinate systems.
- (void) generateGestureRecognizers {
//Setup gesture recognizers
UIRotationGestureRecognizer *twoFingersRotate = [[UIRotationGestureRecognizer alloc] initWithTarget:self action:@selector(twoFingersRotate:)];
[self.hitView addGestureRecognizer:twoFingersRotate];
UIPinchGestureRecognizer *twoFingersScale = [[UIPinchGestureRecognizer alloc] initWithTarget:self action:@selector(twoFingersScale:)];
[self.hitView addGestureRecognizer:twoFingersScale];
UIPanGestureRecognizer *oneFingerPan = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(oneFingerPan:)];
[self.hitView addGestureRecognizer:oneFingerPan];
[twoFingersRotate setDelegate:self];
[twoFingersScale setDelegate:self];
[oneFingerPan setDelegate:self];
}
- (void) oneFingerPan:(UIPanGestureRecognizer *) recognizer {
//Handle pan gesture
CGPoint translation = [recognizer translationInView:self.hitView];
CGPoint location = [recognizer locationInView:self.hitView];
//Send info to renderViewController
[self.renderViewController translate:traslation];
//Reset recognizer so change doesn't accumulate
[recognizer setTranslation:CGPointZero inView:self.hitView];
}
- (void) twoFingersRotate:(UIRotationGestureRecognizer *) recognizer {
//Handle rotation gesture
CGPoint locationInView = [recognizer locationInView:self.hitView];
locationInView = CGPointMake(locationInView.x - self.hitView.bounds.size.width/2, locationInView.y - self.hitView.bounds.size.height/2);
if ([recognizer state] == UIGestureRecognizerStateBegan || [recognizer state] == UIGestureRecognizerStateChanged) {
//Send info to renderViewController
[self.renderViewController rotate:locationInView degrees:recognizer.rotation];
//Reset recognizer
[recognizer setRotation:0.0];
}
}
- (void) twoFingersScale:(UIPinchGestureRecognizer *) recognizer {
//Handle scale gesture
CGPoint locationInView = [recognizer locationInView:self.hitView];
locationInView = CGPointMake(locationInView.x - self.hitView.bounds.size.width/2, locationInView.y - self.hitView.bounds.size.height/2);
if ([recognizer state] == UIGestureRecognizerStateBegan || [recognizer state] == UIGestureRecognizerStateChanged) {
//Send info to renderViewController
[self.renderViewController scale:locationInView ammount:recognizer.scale];
//reset recognizer
[recognizer setScale:1.0];
}
}
//This allows gestures recognizers to happen simultaniously
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer {
if (gestureRecognizer.view != otherGestureRecognizer.view)
return NO;
if ([gestureRecognizer isKindOfClass:[UILongPressGestureRecognizer class]] || [otherGestureRecognizer isKindOfClass:[UILongPressGestureRecognizer class]])
return NO;
return YES;
}
RenderViewController
For every frame, the modelViewMatrix is calculated from three other temporary matrices (translation, scale and rotation)
- (void) setup {
//Creates the modelViewMatrix from the initial position, rotation and scale
translatemt = GLKMatrix4Translate(GLKMatrix4Identity, initialPosition.x, initialPosition.y, 0.0);
scalemt = GLKMatrix4Scale(GLKMatrix4Identity, initialScale, initialScale, 1.0);
rotatemt = GLKMatrix4Rotate(GLKMatrix4Identity, initialRotation, 0.0, 0.0, 1.0);
self.modelViewMatrix = GLKMatrix4Multiply(GLKMatrix4Multiply(GLKMatrix4Multiply(translatemt, rotatemt), scalemt), GLKMatrix4Identity);
//set these back to identities to take further modifications (they'll update the modelViewMatrix)
scalemt = GLKMatrix4Identity;
rotatemt = GLKMatrix4Identity;
translatemt = GLKMatrix4Identity;
//rest of the OpenGL setup
[self setupOpengGL];
}
//public interface
- (void) translate:(CGPoint) location {
//Update the translation temporary matrix
translatemt = GLKMatrix4Translate(translatemt, location.x, -location.y, 0.0);
}
//public interface
- (void) rotate:(CGPoint) location degrees:(CGFloat) degrees {
//Update the rotation temporary matrix
rotatemt = GLKMatrix4Translate(GLKMatrix4Identity, location.x, -location.y, 0.0);
rotatemt = GLKMatrix4Rotate(rotatemt, -degrees, 0.0, 0.0, 1.0);
rotatemt = GLKMatrix4Translate(rotatemt, -location.x, location.y, 0.0);
}
//public interface
- (void) scale:(CGPoint) location ammount:(CGFloat) ammount {
//Update the scale temporary matrix
scalemt = GLKMatrix4Translate(GLKMatrix4Identity, location.x, -location.y, 0.0);
scalemt = GLKMatrix4Scale(scalemt, ammount, ammount, 1.0);
scalemt = GLKMatrix4Translate(scalemt, -location.x, location.y, 0.0);
}
- (void)update {
//this is done before every render update. It generates the modelViewMatrix from the temporary matrices
self.modelViewMatrix = GLKMatrix4Multiply(GLKMatrix4Multiply(GLKMatrix4Multiply(rotatemt, translatemt), scalemt), self.modelViewMatrix);
//And then set them back to identities
translatemt = GLKMatrix4Identity;
rotatemt = GLKMatrix4Identity;
scalemt = GLKMatrix4Identity;
//set the modelViewMatrix for the effect (this is assuming you are using OpenGL es 2.0, but it would be similar for previous versions
self.effect.transform.modelviewMatrix = self.modelViewMatrix;
}