How To Rotate a 3D Object Using Touches with OpenGL

Ray Wenderlich
Learn how to rotate 3D objects using touches with GLKit!

Learn how to rotate 3D objects using touches with GLKit!

This is a blog post by site administrator Ray Wenderlich, an independent software developer and gamer.

In this tutorial, you will learn how to rotate a 3D object with touches on iOS with OpenGL ES 2.0 and GLKit.

We’ll start out simple and show you how you can rotate a 3D object by rotating a fixed amount along the x or y axis as the user drags. Then we’ll show you a more advanced technique using quaternions.

This tutorial will pick up with the sample project from the Beginning OpenGL ES 2.0 with GLKit Tutorial, so download it if you haven’t already.

Please keep in mind that I am learning this myself as I go and my math is quite rusty, so excuse me if I make any mistakes or don’t explain things quite right. If anyone has better or more correct ways to explain things, please chime in!

Without further ado, let’s get rotating!

Simple Rotation: Attempt 1

Start by running the sample project, and you’ll see that the cube is already rotating at a fixed amount:

Object rotating fixed amount per frame

If you open HelloGLKitViewController.m and find the update method, you can find the lines of code that are performing the fixed rotation:

_rotation += 90 * self.timeSinceLastUpdate;
modelViewMatrix = GLKMatrix4Rotate(modelViewMatrix, GLKMathDegreesToRadians(25), 1, 0, 0);
modelViewMatrix = GLKMatrix4Rotate(modelViewMatrix, GLKMathDegreesToRadians(_rotation), 0, 1, 0);

Our goal is to replace this so that the user can rotate the cube by dragging the mouse, instead of having the rotation fixed.

Let’s make an initial attempt by moving the rotation matrix to an instance variable, and modifying it as the user drags. Make the following changes to HelloGLKitViewController.m:

// Add to HelloGLKitViewController private variables
GLKMatrix4 _rotMatrix;
 
// Add to bottom of setupGL
_rotMatrix = GLKMatrix4Identity;
 
// In update, replace the 3 rotation lines shown in the previous snippet with this
modelViewMatrix = GLKMatrix4Multiply(modelViewMatrix, _rotMatrix);
 
// Remove everything inside touchesBegan
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
 
}
 
// Add new touchesMoved method
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
 
    UITouch * touch = [touches anyObject];
    CGPoint location = [touch locationInView:self.view];    
    CGPoint lastLoc = [touch previousLocationInView:self.view];
    CGPoint diff = CGPointMake(lastLoc.x - location.x, lastLoc.y - location.y);
 
    float rotX = -1 * GLKMathDegreesToRadians(diff.y / 2.0);
    float rotY = -1 * GLKMathDegreesToRadians(diff.x / 2.0);
 
    GLKVector3 xAxis = GLKVector3Make(1, 0, 0);
    _rotMatrix = GLKMatrix4Rotate(_rotMatrix, rotX, xAxis.x, xAxis.y, xAxis.z);
    GLKVector3 yAxis = GLKVector3Make(0, 1, 0);
    _rotMatrix = GLKMatrix4Rotate(_rotMatrix, rotY, yAxis.x, yAxis.y, yAxis.z);
 
}

OK, so here we’re initializing the rotation matrix to the identity (no change). As the user drags, we use GLKMatrix4Rotate to rotate the cube a number of degrees. For every pixel the user drags, we rotate the cube 1/2 degree.

Remember that the x-axis is horizontal across the screen, and the y-axis is vertical. So when the user drags from left to right, we actually want to rotate around the y axis (rotY), and vice versa.

Compile and run, and you’ll notice that the cube seems to rotate OK at the beginning, but after a while it doesn’t behave the way you expect and starts rotating according to strange diagonal directions. What’s going on?

Simple Rotation: Attempt 2

When you apply a transform to an object, you can think of it as moving the coordinate system of the object to a new space in the world.

To see what I mean, run the app and swipe from the upper right of the cube to about the middle of the cube. You have effectively modified the coordinate system of the object to be situated in a different world space as follows:

Coordinate system modification

The _rotMatrix is the transform that moves the object to this new space. Since our code is modifying the _rotMatrix as the user swipes, when we tell it to rotate around the x or y axis, it is rotating around the x or y axis in object space.

Try it out for yourself: make another swipe from right to left (careful not to move up or down), and you will see how it rotates around the new y-axis.

But what we really want to do is rotate around the world space x-axis and y-axis, not the object space x-axis and y-axis. But how can we figure out what the world space x-axis and y-axis will be in this new coordinate system?

The answer is simple: if _rotMatrix converts an object vector to where it should be in the world space, the inverse of _rotMatrix converts a world vector to where it should be in object space. You can see this through these equations:

_rotMatrix * object vector = world vector

Multiply both sides by (_rotMatrix)-1 (the inverse of _rotMatrix), and you get:

(_rotMatrix)-1 * _rotMatrix * object vector = (rotMatrix-1) * world vector

Since (_rotMatrix)-1 * _rotMatrix = 1, you get:

object vector = (_rotMatrix)-1 * world vector

Let’s try this out! Modify touchesMoved to the following:

- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
 
    UITouch * touch = [touches anyObject];
    CGPoint location = [touch locationInView:self.view];    
    CGPoint lastLoc = [touch previousLocationInView:self.view];
    CGPoint diff = CGPointMake(lastLoc.x - location.x, lastLoc.y - location.y);
 
    float rotX = -1 * GLKMathDegreesToRadians(diff.y / 2.0);
    float rotY = -1 * GLKMathDegreesToRadians(diff.x / 2.0);
 
    bool isInvertible;
    GLKVector3 xAxis = GLKMatrix4MultiplyVector3(GLKMatrix4Invert(_rotMatrix, &isInvertible), 
        GLKVector3Make(1, 0, 0));
    _rotMatrix = GLKMatrix4Rotate(_rotMatrix, rotX, xAxis.x, xAxis.y, xAxis.z);
    GLKVector3 yAxis = GLKMatrix4MultiplyVector3(GLKMatrix4Invert(_rotMatrix, &isInvertible), 
        GLKVector3Make(0, 1, 0));
    _rotMatrix = GLKMatrix4Rotate(_rotMatrix, rotY, yAxis.x, yAxis.y, yAxis.z);
 
}

Compile and run, and now it rotates as expected!

Arcball Rotation with Quaternions: Overview

Another popular way to handle rotating objects in 3D space is via the arcball rotation method popularized in a paper by Ken Shoemake.

Let me give you a brief overview of how arcball rotation works:

  1. Map to sphere. Create a virtual sphere around the object you are trying to rotate. When the user touches, you figure out the closest point on the sphere corresponding to the touch (start), and similarly then the touch moves (current).
  2. Calculate current rotation. To rotate a point on the sphere from start to current, you can think of it as a rotation of some degrees along some axis. Calculate this rotation/axis from the start/current positions.
  3. Update overall rotation. Update the overall rotation of the object with the current rotation in step 3.

Step 3 is where the magic of a mathematical concept called quaternions comes in. I’m not going to discuss the math behind them here, but I will point out three high level properties of quaternions:

  1. Quaternions = axis + angle of rotation. Quaternions can represent an axis and an angle of rotation around that axis. I.e. a quaternion can represent any rotation.
  2. Multiply quaternions = combine rotations. If you multiply two quaternions, they represent the combination of the rotations they represent.
  3. Can convert quaternions to matrices. Through some math (or a handy GLKit function) you can convert a quaternion to a rotation matrix.

One of the nice things about using GLKit is you can use quaternions without having to understand the mathematical equations behind how they work. You can use functions like:

  • GLKQuaternionMakeWithAngleAndVector3Axis: Create a quaternion given an axis and an angle of rotation
  • GLKQuaternionMultiply: Multiply one quaternion via another (combining the rotations)
  • GLKMatrix4MakeWithQuaternion: Convert quaternion to a rotation matrix

If you’re still a little confused to how this all works, don’t worry – we’re going to go through this step by step in the next few sections!

1) Map to Sphere.

Imagine we have a virtual sphere surrounding our object, with a radius of 1/3 the screen width. The center of the sphere is the center of the object we’re rotating. We want to let the user “grab and drag” this sphere to rotate the object.

So the first step is to figure out how to convert a 2D touch point to a point on a virtual 3D sphere surrounding the object.

Here’s the easiest way I found to visualize how to figure this out. First, let’s take a front view of where the user taps (from the center of the object):

Mapping touch point to sphere, part 1

We know the x position the user taps, and the y position the user taps, so we can figure out the length of the hypotenuse via the Pythagorean Theorem.

Now imagine we’re looking from top down instead, with the screen being the line at the bottom:

Mapping touch point to sphere, part 2

We now know the vector going from the center of the sphere to where the user tapped along the x and y axis, but we do not know the z coordinate.

However, we do know that the z coordinate needs to intersect with the sphere, and if you draw a line from the center of the sphere to any point, it has a length of the radius of the sphere (by definition).

Therefore, we have another right triangle and can use the pythagorean theorem to solve! We get:

r^2 = (sqrt(p.x^2 + p.y^2))^2 + z^2
r^2 = (p.x^2 + p.y^2) + z^2
r^2 - (p.x^2 + p.y^2) = z^2

The only tricky bit is if the user clicks outside the radius of the sphere. If this happens, we’ll use the closest point on the sphere we can find instead.

Let’s see what this looks like in code! Add the following method right before touchesBegan:

- (GLKVector3) projectOntoSurface:(GLKVector3) touchPoint
{
    float radius = self.view.bounds.size.width/3; 
    GLKVector3 center = GLKVector3Make(self.view.bounds.size.width/2, self.view.bounds.size.height/2, 0);
    GLKVector3 P = GLKVector3Subtract(touchPoint, center);
 
    // Flip the y-axis because pixel coords increase toward the bottom.
    P = GLKVector3Make(P.x, P.y * -1, P.z);
 
    float radius2 = radius * radius;
    float length2 = P.x*P.x + P.y*P.y;
 
    if (length2 <= radius2)
        P.z = sqrt(radius2 - length2);
    else
    {
        P.x *= radius / sqrt(length2);
        P.y *= radius / sqrt(length2);
        P.z = 0;
    }
 
    return GLKVector3Normalize(P);
}

This implements the same math we discussed above. Note we also normalize the vector at the end, because when calculating rotations it’s the direction that matters more than the length.

Now let’s start moving it. Make the following changes to HelloGLKitViewController.m:

// Add to the private interface
GLKVector3 _anchor_position;
GLKVector3 _current_position;
 
// Add to bottom of touchesBegan
UITouch * touch = [touches anyObject];
CGPoint location = [touch locationInView:self.view];
 
_anchor_position = GLKVector3Make(location.x, location.y, 0);
_anchor_position = [self projectOntoSurface:_anchor_position];
 
_current_position = _anchor_position;
 
// Add to bottom of touchesMoved
_current_position = GLKVector3Make(location.x, location.y, 0);
_current_position = [self projectOntoSurface:_current_position];

So far so good! We now have normalized vectors pointing to the start and end points on the sphere corresponding to the user’s mouse movements. Now let’s use them to calculate an axis and angle of rotation!

2) Calculate current rotation.

To calculate the axis and angle of rotation, we can use our friends the dot product and the cross product.

If you are a little rusty as to what these do, I highly recommend reading the article Linear Algebra for Game Developers by David Rosen. It does a great job of introducing these concepts in a simple and easy to understand way.

But if you can’t be bothered, I’ll give a brief summary here:

  • The cross product allows you to give two vectors, and it will give you the vector perpendicular (90 degrees) to both vectors. This will be the axis of rotation, now we just need to figure out the amount to rotate from one vector to the other along this axis.
  • Long story short, you can use the dot product to help determine the angle between two vectors. The angle is acos(dot(A, B)) if A and B are unit vectors. And ours are unit vectors – remember how we normalized them at the end of projectOntoSurface.

Once we determine the axis and angle, we can create a quaternion to store them using the handy GLKit GLKQuaternionMakeWithAngleAndVector3Axis function.

Let’s try it out! Make the following changes to HelloGLKitViewController.m:

// Add new method above touchesBegan
- (void)computeIncremental {
 
    GLKVector3 axis = GLKVector3CrossProduct(_anchor_position, _current_position);
    float dot = GLKVector3DotProduct(_anchor_position, _current_position);    
    float angle = acosf(dot);
 
    GLKQuaternion Q_rot = GLKQuaternionMakeWithAngleAndVector3Axis(angle * 2, axis);
    Q_rot = GLKQuaternionNormalize(Q_rot);
 
    // TODO: Do something with Q_rot...
 
}
 
// Call it at end of touchesEnded
[self computeIncremental];

OK awesome, we now have a quaternion representing how we can rotate the object from the start touch point to the current touch point (mapped to the sphere), now what?

3) Update overall rotation.

For the final step, we need to apply the rotation to the object. To do this we need to track two rotations: the original rotation of the object, and the current (temporary) rotation as the user drags their finger.

This is pretty simple so let’s just dive into code. Make the following changes to HelloGLKitViewController.m:

// Add new variables to private interface
GLKQuaternion _quatStart;
GLKQuaternion _quat;
 
// Initialize them at bottom of setupGL
_quat = GLKQuaternionMake(0, 0, 0, 1);
_quatStart = GLKQuaternionMake(0, 0, 0, 1);
 
// Set _quat at bottom of computeIncremental
_quat = GLKQuaternionMultiply(Q_rot, _quatStart);
 
// Set _quatStart at bottom of touchesBegan
_quatStart = _quat;
 
// In update, replace GLKMatrix4Multiply(modelViewMatrix, rotation) line with this:
GLKMatrix4 rotation = GLKMatrix4MakeWithQuaternion(_quat);
modelViewMatrix = GLKMatrix4Multiply(modelViewMatrix, rotation);

Here _quatStart represents the original rotation of the object (before the user starts dragging), and _quat is the current (temporary) rotation.

At the end of computeIncremental, we multiply the original rotation with the current rotation (i.e. the amount to rotate from the start to the current touch point).

In update, we convert the quaternion into a rotation matrix, and apply it to the model view matrix as usual.

Compile and run, and now you can rotate the cube with arcball rotation!

Bonus: Alternative projectOntoSurface

Note how with the current implementation, if you drag outside the sphere it maps to the closest point on the sphere rather than continuing the rotation. To make the rotation continue instead, replace the else clause in projectOntoSurface with this:

P.z = radius2 / (2.0 * sqrt(length2));
float length = sqrt(length2 + P.z * P.z);
P = GLKVector3DivideScalar(P, length);

Bonus: More Fun with Quaternions

You might wonder what else you can do with quaternions. One cool thing they can do is provide an easy way to interpolate between two rotations over time.

To see what I mean, let’s try it out and add some code to make our cube animate back to its original orientation if you double tap. Make the following changes to HelloGLKitViewController.m:

// Add new private instance variables
BOOL _slerping;
float _slerpCur;
float _slerpMax;
GLKQuaternion _slerpStart;
GLKQuaternion _slerpEnd;
 
// Add to bottom of setupGL
UITapGestureRecognizer * dtRec = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(doubleTap:)];
    dtRec.numberOfTapsRequired = 2;
    [self.view addGestureRecognizer:dtRec];
 
// Add new method
- (void)doubleTap:(UITapGestureRecognizer *)tap {
 
    _slerping = YES;
    _slerpCur = 0;
    _slerpMax = 1.0;
    _slerpStart = _quat;
    _slerpEnd = GLKQuaternionMake(0, 0, 0, 1);
 
}
 
// Add inside update method, right before declaration of modelViewMatrix
if (_slerping) {
 
    _slerpCur += self.timeSinceLastUpdate;
    float slerpAmt = _slerpCur / _slerpMax;
    if (slerpAmt > 1.0) {
        slerpAmt = 1.0;
        _slerping = NO;
    }
 
    _quat = GLKQuaternionSlerp(_slerpStart, _slerpEnd, slerpAmt);
}

When the user double taps, we set the start rotation to the current orientation (_quat), and the end rotation to the “identity” quaternion (no rotation at all).

Then inside update, if we are “sleeping” we figure out how far along the animation we are so far. Then we use the built-in GLKQuaternionSlerp method to come up with the appropriate rotation in-between slerpStart and slerpEnd, based on the current time.

Compile and run, rotate the object, and double tap to make it animate back to its original position. This technique is commonly used for keyframe animations on 3D objects and the like.

Where To Go From Here?

Here is an example project with all of the code from the above tutorial.

Hopefully this tutorial was helpful to others who wanted to learn a little more about rotating 3D objects based on touches and brush up on some 3D math concepts.

If anyone has any questions, corrections, or better or easier ways to explain things, please join the forum discussion below!


This is a blog post by site administrator Ray Wenderlich, an independent software developer and gamer.

Ray Wenderlich

Ray is an indie software developer currently focusing on iPhone and iPad development, and the administrator of this site. He’s the founder of a small iPhone development studio called Razeware, and is passionate both about making apps and teaching others the techniques to make them.

When Ray’s not programming, he’s probably playing video games, role playing games, or board games.

User Comments

13 Comments

  • Hi Ray,

    I have a few suggestions regarding this post.

    1. You might want to point out that the major reason for using quaternion-based arcball rotations instead of Euler angles is that they don't suffer from "gimbal lock." Mathematically, gimbal lock is a result of the fact that at some points of rotation, the number of "degrees of freedom" drops below 3 (less than 3D). Visually, as you rotate the model (or the camera), you'll see discontinuities in the rotation -- it appears to "jump around" (a non-mathematical description, no doubt). I haven't studied your algorithm very carefully, but when I compiled and ran the project, it seems to suffer from gimbal lock -- at certain angles the model jumps around.

    2. A goal of most arcball implementations is to have the point on the model that you select (with your finger, in a multitouch interface) follow your finger (or cursor) as you move it around. In the following line of code, you (arbitrarily?) multiply the angle by 2 when you make the quaternion:

    GLKQuaternion Q_rot = GLKQuaternionMakeWithAngleAndVector3Axis(angle * 2.0, axis);

    Visually, this results in the rotations "getting way ahead" of the finger. In other words, if you drag your finger from one end of the screen to the other, the model makes multiple full rotations, rather having the selected point move from one side of the screen to the other. If you were multiply the angle by 10.0, you can really see this effect. I tried leaving the angle as is (i.e. multiply by 1.0) and the result is that it "follows the finger."

    3. I have been doing quaternion rotations using something like SGI's original "trackball" code and my own matrix library. I came here looking for examples of how to use GLKit's quaternions. I also found the following page on the same subject:

    http://thestrangeagency.com/arcball-rot ... ith-glkit/

    It's a much simpler implementation (you don't need to be computing cross products and projecting onto surfaces, etc. -- GLKit's quaternions will do all of that work for you). In a couple of minutes, I plugged it into your HelloGLKit project and, sure enough, it works as I would expect -- no gimbal lock, and the model follows your finger.

    You (and your readers) may want to have a look at it. I'm thinking about using this approach (GLKit's quaternions rather than my home-grown stuff) in a couple of upcoming iPad products.

    This stuff is complicated mathematically and hopefully this note will help someone who is building the next great 3D app.

    Thanks for all of the great work that you make available to the developer community.

    Dave Doherty
    Developer of The Atomic Dashboard, available on the Mac App Store (in which there is lots of 3D stuff spinning around!)
    ddoherty
  • Good suggestions, thx ddoherty and Ray for the tutorial.
    fhng
  • Code: Select all
    // Call it at end of touchesEnded
    [self computeIncremental];


    The comment should say "Call it at the end of touchesMoved"

    This is good stuff. Linear algebra always makes me have to sit down and focus. I appreciate every chance to play with it.
    SSteve
  • The start of the tutorial says that this picks up where the glkit intro part 2 left off. The project at the end of glkit intro part 2 was a square rotating in 3d space and not a cube right?

    Am I missing a bridging tutorial or is that part of the book you guys put out?

    Great tutorial by the way.

    thanks,
    Nikhil.
    wraithmonk
  • im learning how to caculate touchpoint to rotate camera, but i don't know how to do it. Any one can help me. Thanks a lot
    tuanln1990
  • Great tutorial.

    I am new to openGL and have been trying to rotate the cube after touches end using the captured touches on the cube.

    I cannot seem to get it to work, the cube remains still when I apply the quats using the captured locations and diffs.

    Could you please suggest how to apply rotation to the cube after touches ended, based on the velocity and direction of the touches?

    Thanks
    jarrydios
  • Hi, this is a tutorial I was looking for great thanks! I have it working with the sample cube, but cannot get it to work with my own model, is there something specific this code requires in order to work, like the starting point as 0,0,0?

    My model is rotated and translated a bit on startup and so this may be the problem, but if I remove this and presuming it is now at 0,0,0 I still do not get any rotation?

    Thanks
    elpuerco63
  • Hi guys!

    How would you let the cube keep rotating when the user impress a velocity dragging over the object?

    Any hint is really appreciated!

    Thanks,
    DAN
    D'Antona
  • To fix rotation speed matching finger and fix the gimbal lock issues there are only two changes that need to be made. They are the following:

    As Dave mentioned, change:
    GLKQuaternion Q_rot = GLKQuaternionMakeWithAngleAndVector3Axis(angle * 2.0, axis);
    to
    GLKQuaternion Q_rot = GLKQuaternionMakeWithAngleAndVector3Axis(angle, axis);
    in projectOntoSurface.

    Now, this changes the speed of rotation but also introduces gimbal lock. To fix this we need to normalize the axis in computeIncremental.
    Add:
    axis = GLKVector3Normalize(axis);
    after the first line in this method.
    kschins
  • The Ken Shoemake ARCBALL Paper link appears to be down, I found a copy of it hosted here for the moment - http://tommyhinks.files.wordpress.com/2 ... rcball.pdf OR https://github.com/kingofthebongo2008/v ... rcball.pdf
    emmasteimann
  • Typo on the sentence 'Then inside update, if we are sleeping we...' it should say "slerping".
    emmasteimann
  • Hello everyone,

    Im quite new to OpenGL and at the moment im trying to get some little project running. (at the moment Im failing... )

    This tutorial really helped me understand some of the maths behind what I was trying to do the past week.

    The problem I have now is... im coding in c++ and therefore I dont use GLKit. I figured out most of the methodes you used in that tutorial and tried to find them in my own library (which was in most cases quite possible). Only one thing...

    can you explain to me what exactly this method does?

    _rotMatrix = GLKMatrix4Rotate(_rotMatrix, rotY, yAxis.x, yAxis.y, yAxis.z);

    I found a Rotate method in my library but it only takes a float for degrees and no axis at all. What do the parameter stand for and how does GLKit compute the Matrix with them?

    Greeting
    Holly
    HollyMood
  • Hi,

    I have one query.How can we add multiple images in this sample.I mean i have to show the object from all directions example i want to show the shoes from all sides(front,top, bottom and side),

    Please help to solve this situation.

    Thanks in advance.
    Payal Sharma

Other Items of Interest

Ray's Monthly Newsletter

Sign up to receive a monthly newsletter with my favorite dev links, and receive a free epic-length tutorial as a bonus!

Advertise with Us!

Hang Out With Us!

Every month, we have a free live Tech Talk - come hang out with us!


Coming up in July: Facebook Pop Tech Talk!

Sign Up - July

RWDevCon Conference?

We are considering having an official raywenderlich.com conference called RWDevCon in DC in early 2015.

The conference would be focused on high quality Swift/iOS 8 technical content, and connecting as a community.

Would this be something you'd be interested in?

    Loading ... Loading ...

Our Books

Our Team

Tutorial Team

  • Kyle Richter

... 50 total!

Update Team

Editorial Team

... 23 total!

Code Team

  • Orta Therox

... 1 total!

Translation Team

  • Jesus Guerra
  • David Xie
  • Przemysław Rembelski

... 33 total!

Subject Matter Experts

  • Richard Casey

... 4 total!