Search code examples
ios3dcore-animationcalayerperspective

Core Animation: moving a layer along a plane


I am working on a card game where cards are shown lying on a table with perspective. The table and cards both have the same x rotation (45°).

As the cards get farther away from the player (move "up" on the table) I would like them to get smaller. I thought that simply modifying their z translation (or z scale) I could accomplish the desired effect but it does not.

Do I have to move and scale it at the same time or is there a way to change the z depth and have Core Animation handle everything for me?

Thanks!


Solution

  • This is something that's not documented directly, except in an example under “Modifying the Transform Data Structure” in the Core Animation Programming Guide:

    The example in Listing 2 illustrates how to configure a CATransform3D as a perspective transform.

    Listing 2 Modifying the CATransform3D data structure directly

     CATransform3D aTransform = CATransform3DIdentity;
    // the value of zDistance affects the sharpness of the transform.
    zDistance = 850;
    aTransform.m34 = 1.0 / -zDistance;
    

    What's going on here? A 3D transform matrix is a 4x4 matrix (not a 3x3 matrix as you might expect). The fourth column of the third row (counting from one, not zero) controls the perspective transform along the Z axis. Set it to the negative reciprocal of the distance from the “camera” to the Z=0 plane (which you can think of as the screen).

    By default, m34 is zero, which implies that the distance from the camera to the screen is infinity. At infinity, every (finite) point is equidistant from the camera, so there's no perspective. This is called an “orthogonal projection”.

    As you set the distance to smaller values (which make m34 larger), you get increasingly strong perspective projections. You will want to play with the value to find one you like.

    Most layers flatten their sublayers into the Z=0 plane. This means that the perspective projection you apply to a layer won't apply to its sublayers, and you'll have to set the transform of every sublayer.

    If you don't need the user to be able to tap on specific cards, you should add the card layers as sublayers of a CATransformLayer. A CATransformLayer does not flatten its sublayers into the Z=0 plane, so you can apply the perspective projection transform to just the CATransformLayer, and you don't have to worry about applying it to the individual card layers. However, a CATransformLayer doesn't support hitTest:, so you can't easily determine which cards are touched by a touch event.

    CATransformLayer Class Reference