Search code examples
iphoneiosopengl-es-2.0glkitquaternions

iOS OpenGL ES 2.0 Quaternions Rotation with Momentum after Swipe


I am learning OpenGL ES 2.0 for iOS and with the help of several tutuorials can rotate and zoom a simple sphere object using quaternions. I'd like the globe to continue rotating with diminishing speed when the user lifts their finger from the screen after completing a swipe - so I'd like to give the sphere some momentum. I used this blog to learn about rotating: http://www.raywenderlich.com/12667/how-to-rotate-a-3d-object-using-touches-with-opengl. Can anybody provide some reading or examples of momentum? How can I implement momentum with quaternions? Thanks!

    // Set up the frustrum and projection matrix
float aspect = fabsf(self.view.bounds.size.width / self.view.bounds.size.height);
GLKMatrix4 projectionMatrix = GLKMatrix4MakePerspective(GLKMathDegreesToRadians(65.0f), aspect, 0.1f, 100.0f);
self.effect.transform.projectionMatrix = projectionMatrix;

// Now perform the interpolation step
//[self slerpToNewLocation];

// Move the globe back so we can see it
GLKMatrix4 modelViewMatrix = GLKMatrix4MakeTranslation(0.0f, 0.0f, GLOBAL_EARTH_Z_LOCATION);

// In update, we convert the quaternion into a rotation matrix, and apply it to the model view matrix as usual.
GLKMatrix4 rotation = GLKMatrix4MakeWithQuaternion(_quat);
GLKMatrix4 rotateMatrix = GLKMatrix4RotateWithVector3(rotation,momentumVar/200,GLKQuaternionAxis(_quat));
_quat = GLKQuaternionMakeWithMatrix4(rotateMatrix);

modelViewMatrix = GLKMatrix4Multiply(modelViewMatrix, rotateMatrix);

// Cap the zoom scale factor max and mins
// only if slerping so that the auto-zoom out and back
// in still works during auto-rotation
if ( !_slerping ) {
    if ( scale > 2.0 ) {
        scale = 2.0;
    }
    if ( scale < 1.15 ) {
        scale = 1.15;
    }
}

// Apply the zoom factors
if ( _slerping ) {
    //NSLog(@"final scale %f",scale);
}

modelViewMatrix = GLKMatrix4Scale(modelViewMatrix, scale, scale, scale);

// Assign the drawer modelViewMatrix
self.effect.transform.modelviewMatrix = modelViewMatrix;

I saw the following question: Obtaining momentum quaternion from two quaternions and timestep - but did not understand the answer. I'd really appreciate some hints here - thanks.

// Called when touches are ended
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event{

    // The momentum var is initialized to a value
    momentumVar = 0.025;

    // Now since we stopped touching, decay the rotation to simulate momentum
    momentumTimer = [NSTimer scheduledTimerWithTimeInterval:0.01
                                     target:self
                                   selector:@selector(decayGLKQuaternion)
                                   userInfo:nil
                                    repeats:YES];

}

// Slerp about the current axis after touches ended but for a greater angle ( radians )
- (void)decayGLKQuaternion {

    if ( momentumVar < 0.001 ) {

        // Stop the momentum and timer
        momentumVar = 1.0;
        [momentumTimer invalidate];

    }
    else {

        // What is the current angle for the quaternion?
        float currentQuatAngle = GLKQuaternionAngle(_quat);

        // Decay the value each time
        momentumVar = currentQuatAngle * 0.95;

    }

}

UPDATE: I am still trying to figure this out, I have tried converting the current quaternion in the update method to a Matrix4 and rotate that matrix around the quaternion axis for a value that is updated and decremented from a timer that starts firing once TouchesEnded within my app. The result does a rotation, but the axis doesn't look quite right ( it seems close ) and the angle creates the opposite rotation direction that I would expect - so if I swipe down and lift my finger the globe rotates upward with decreasing speed. Changing the sign of the angle value does not help. How can I extract the rotation axis correctly from the quaternion, rotate it with the proper angle, and do so with decrementing speed? Thanks -

// In update, we convert the quaternion into a rotation matrix, and apply it to the model view matrix as usual.
    GLKMatrix4 rotation = GLKMatrix4MakeWithQuaternion(_quat);
    GLKMatrix4 rotateMatrix = GLKMatrix4RotateWithVector3(rotation,momentumVar/200,GLKQuaternionAxis(_quat));
    _quat = GLKQuaternionMakeWithMatrix4(rotateMatrix);

    modelViewMatrix = GLKMatrix4Multiply(modelViewMatrix, rotateMatrix);

Solution

  • You can totally avoid having to implement momentum physics by making use of the UIScrollView class in UIKit. You can position a UIScrollView that doesn't actually draw any content over your OpenGL view and configure it to have the contentSize (scrollable area) and scrolling properties that you want. You can then make one of your classes conform to UIScrollViewDelegate and make an instance of that class the delegate of your UIScrollView. Then, when the user interacts with your invisible scroll view (on top of your OpenGL view) your scroll view delegate class will be notified of the user's interactions. The following delegate method may be particularly useful to implement.

    - (void)scrollViewDidScroll:(UIScrollView *)scrollView

    In there, you can get the newly updated contentOffset and use that to update your 3D model, which you can then render using OpenGL into your OpenGL view. So you're only using the scroll view as a mechanism of detecting the user's interaction, which will handle all the nice smooth updates and momentum physics that iOS users are familiar with.

    I'd strongly recommend watching the WWDC 2012 Session 223, "Enhancing User Experience with Scroll Views" with Josh Shaffer. In that talk he discusses using this technique in great detail and with examples.