3-D rotation without a trackball

Trackball rotation sample

UPDATE 2: Perspective rendering has been added: see below for the details.
UPDATE: 3-D scaling using pinch gestures has been added: see below for the details.

Recently, Bill Dudney posted a sample iPhone application that used Core Animation and a concept called a trackball to rotate an object in 3-D space using a finger as input. I actually use a different method of rotation within Molecules, so I thought I'd modify his sample to use that rotation.

That modified example iPhone application can be downloaded here.

I recommend reading Bill's original post about the concept of a trackball for 3-D rotation and its use with Core Animation. Basically, this technique places a virtual sphere around your 3-D object, and as you move your finger around it the sphere is rotated as if you were pushing a trackball around. It's a common technique, but I chose not to use it for the rotation effects in Molecules because it required a bit of code and didn't seem to produce the effect that I wanted.

Instead, I chose to represent the movement of a finger as rotation about two axes, one parallel to the X axis of the display, and the other parallel to the Y axis. For example, if you move your finger up on the display, it rotates the object counterclockwise about the X axis. This rotation method requires fewer lines of code, but you have to do a little matrix math to generate the proper rotation call. Fortunately, Core Animation uses transformation matrices that are identical in form to OpenGL matrices, so I was able to lift some code from Molecules to do the rotation:

CATransform3D currentTransform = transformed.sublayerTransform;
CGFloat displacementInX = location.x - previousLocation.x;
CGFloat displacementInY = previousLocation.y - location.y;
 
CGFloat totalRotation = sqrt(displacementInX * displacementInX + displacementInY * displacementInY);
 
CATransform3D rotationalTransform = CATransform3DRotate(currentTransform, totalRotation * M_PI / 180.0,
	((displacementInX/totalRotation) * currentTransform.m12 + (displacementInY/totalRotation) * currentTransform.m11), 
	((displacementInX/totalRotation) * currentTransform.m22 + (displacementInY/totalRotation) * currentTransform.m21), 
	((displacementInX/totalRotation) * currentTransform.m32 + (displacementInY/totalRotation) * currentTransform.m31));

The rotation is done incrementally, with the existing sublayer transform being modified by the amount of rotation caused between the last touch location and the current touch location.

A few people have asked about how I did the rotation in Molecules, so I hope this simpler example provides another perspective on the code.

Also, don't interpret this as a put-down on the trackball method of rotation. The trackball method produces more appealing results for many types of 3-D rotation. It's more a matter of personal preference, and I happened to choose the axis-based means of rotation for my application. Again, thanks go out to Bill for posting this code as an educational example for iPhone and Mac programmers getting into Core Animation.

UPDATE: For the fun of it, I also added pinch-based scaling of the 3-D views. Again, this code is based on what I used for Molecules. Scaling is a simpler operation and should be straightforward to follow in the code. This newer version of the program can be downloaded here.

UPDATE 2: I implemented a simple means of applying perspective to the CALayers as they're rendered. This version of the program can be downloaded here.

Comments

I see a problem with this technique (Manipulation of a temporary copy of the modelview matrix and overwriting it with the original one @ rendering): Ever used translate and rotate function both with it?
Say ... lets do a translate 180 degrees around the y axis and later on the user wants to move the model to the right. Its moving to the left instead (and vice versa)

Yes, it does, but that's the behavior you'd expect. For Molecules, I use this same technique, and let people pan across the 3-D structure. If they go to pan it left, they'll want the model to be displaced left from their current perspective on the object, not the object's original orientation.

can you post the code snipped of the translation?
i had a look at a molecules version (maybe older one?) that did not use the CATransform3D matrix for translation.

Here is the version of Molecules that I've been porting to iPhone OS 3.0. It includes the CATransform3D logic in its rotation, scaling, and translation code. I'm sorry that I got a little sidetracked with other things and haven't updated the application yet.

the point is that usually translation and rotation is allways done from the original perspective as a starting point...(if I am not mistaken-> still a beginner in OpenGL)
now, when you replace the modelview matrix (by glLoadMatrixx(currentModelViewMatrix);) at each frame and do a incremental translate/rotate this implies that you lose your original orientation and hence do not know where to go with a translate once one rotation has been applied

would be interesting how you solved that problem...the molecules code of dec08 does not use the CATransform3D mechanism as far as i can see.

Hi

I was playing with the pinch example and noticed a bug. If you pinch and bring them toward the center and then continue the motion the image will disappear. The problem is the calculated distance becomes negative. I made this change to line 129 or MyView.m to fix it. Basically I take the absolute value of the calculation and feed that to sqrt.

	return (sqrt(abs((point1.x - point2.x) * (point1.x - point2.x) + (point1.y - point2.y) * (point1.y - point2.y))));

Great example! Keep up the good work.

Hi , if i wanna make the sides clickable and on tapping of the user i open a new view relative to the side he tapped on . what should i do ?

One problem that after I few days I haven't been able to come up with a solution.

When using this technique, I can find literally no way to properly capture the screen as a UIImage, the CATransform3D is not applied by renderInContext, so it's all flat.

Any ideas? There must be a way besides rewriting my app in OpenGl, especially because using UIGetScreenImage() to take an image of CATransform3D works like a charm.

Unfortunately, there is no good way that anyone has found for capturing a 3-D transformation in Core Animation without using the private UIGetScreenImage(). It's a frequent question on Stack Overflow, and I've not seen a solution for it.

File an enhancement request for this functionality and maybe Apple will add it in the next SDK version.

Syndicate content