Search
Recommended for You

3D Transformations on iPhone with Core Animation


Apart from getting all my existing CA examples onto the iPhone I've also been toying with what the best way to build out the 'photo city' demo from WWDC 2008 would be (my next Core Animation screen cast series). The basic idea of the demo was that you had a set of perhaps 30 or 40 images, the images were combined into cubes and the cubes were used to make a 'city'. After getting a basic cube working I got distracted by some of the stuff I did to make the demo. Namely I finally got around to porting the OpenGL trackball example code to Core Animation.

For those that are not familiar with the trackball example; the idea is that you have a transparent sphere around your scene, you can move the scene around by moving the trackball. As you move your finger to the right it pushed this imaginary sphere around its center to the right (exposing the left side of the scene).

I'm not 100% sure this is the right API to have for such an object but I was able to use it in a couple of examples for a course I'm working on. I also will be using in one of the demos for my talk at iPhone Live. So while it might not be perfect I figure its good enough to post now. Please feel free to comment with what you think would be better.

Now on to document the TrackBall class. The idea is that you have a 2D viewport into a 3D scene, this view port has a width and height (i.e. the CGRect that defines the layers bounds). In this 3D world you construct an imaginary sphere with a radius of the minimum of height or width of your view port centered on the center of your scene. When the event begins (with a touchesBegan:withEvent:) you initialize the trackball with the touches location as the starting point. A vector is constructed from the center of the sphere to the touch (the depth dimension is calculated based on the radius of the sphere). As the user moves her finger around on the screen another vector is constructed from the center of the sphere to the current touch location (as received in touchesMoved:withEvent:). The cross product of these two vectors is the vector of rotation and the angle between them is the magnitude of the rotation.

Practically what all this means is that in the touchesBegan:withEvent: method you call the setStartPointFromLocation: method with the location of the touch (if you don't have multi touch turned on for the view there will be only one touch in the touches set, so you can use the anyObject method to get the touch, code to follow shortly). That initializes the trackball so it knows the first vector (from the center of the sphere to the starting point). As the user drags his finger around on the screen you call rotationTransformForLocation: to get a CATransform3D. This transform encapsulates the rotation vector and angle so you don't really have to grok it to use it (although it helps:). Next you set your layer's sublayersTransform property to this transform.

The scene contained in your layer will now rotate as if it existed in a sphere and you were moving that sphere around. Its a cool effect if you've never seen it before. If you want the trackball to remember where it is when the user picks up her finger you simply call finalizeTrackBallForLocation: with the touch location from the touchesEnded:withEvent: method. Now onto some code. Here is the code to initialize the trackball;

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
  CGPoint location = [[touches anyObject] locationInView:self];
  if(nil == self.trackBall) {
    self.trackBall = [TrackBall trackBallWithLocation:location inRect:self.bounds];
  } else {
    [self.trackBall setStartPointFromLocation:location];
  }
}

In this example I'm keeping the trackball and finalizing it in the touchesEnded: method (we will see that shortly). Next up I get the transformation from the trackball in the touchesMoved:withEvent: method.

- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
  CGPoint location = [[touches anyObject] locationInView:self];
  CATransform3D transform = [trackBall rotationTransformForLocation:location];
  transformed.sublayerTransform = transform;
}

Then in the touchesEnded:withEvent: method I finalize the trackball so it knows where it left off on the next event cycle.

- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
  CGPoint location = [[touches anyObject] locationInView:self];
  [self.trackBall finalizeTrackBallForLocation:location];
}

And finally here is the code. Happy hacking!

AddThis Social Bookmark Button
Comments (3)

3 Comments

M Schumann said:

October 18 2008 - 12.33p MDT

Bill,

The sphere is OK, but after using these in various three-dimensional cad programs, I find that having thumb wheels, or sliders, allowing just left-right and up-dowm movements are much easier to use and relate to. Another option would be to have the capability to restrict sphere motion to left-right and up-down. It is important, in either sphere or thumb wheel motion controllers to allow, at least, motion greater than just a half revolution. Continuous is probably the nicest.

John said:

Bill,

I am getting the error that "transformed" is undeclared....any idea where I can go ahead and fix this? What exactly is tranfromed? Its in the second code block in the post....

thanks!

John

greay said:

replace transformed with the name of the view you're transforming.

Leave a comment


Type the characters you see in the picture above.