views:

396

answers:

2

I have a series of UIImages with which I need to simulate depth. I can't use scaling because I need to be able to rotate the parent view, and the images should look like they're stacked visibly in front of each other, not on the same plane.

I made a new ViewController-based project and put this in the viewDidLoad (as well as attached three 120x120 pixel images named 1.png, 2.png, and 3.png):

- (void)viewDidLoad {

    // display image 3
    UIImageView *three = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"3.png"]];
    three.center = CGPointMake(160 + 60, 240 - 60);
    [self.view addSubview:three];

    // rotate image 3 around the z axis
    // THIS IS INCORRECT
    CATransform3D theTransform = three.layer.transform;
    theTransform.m34 = 1.0 / -1000;
    three.layer.transform = theTransform;

    // display image 2
    UIImageView *two = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"2.png"]];
    two.center = CGPointMake(160, 240);
    [self.view addSubview:two];

    // display image 1
    UIImageView *one = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"1.png"]];
    one.center = CGPointMake(160 - 60, 240 + 60);
    [self.view addSubview:one];

    //  rotate image 3 around the z axis
    // THIS IS INCORRECT
    theTransform = one.layer.transform;
    theTransform.m34 = 1.0 / 1000;
    one.layer.transform = theTransform;

    // release the images
    [one release];
    [two release];
    [three release];

    // rotate the parent view around the y axis
    theTransform = self.view.layer.transform;
    theTransform.m14 = 1.0 / -500;
    self.view.layer.transform = theTransform;

    [super viewDidLoad];
}

I have very specific reasons why I'm not using an EAGLView and why I'm not loading the images as CALayers (i.e. why I'm using UIImageViews for each one). This is just a quick demo that I can use to work out exactly what I need in my parent application.

Is there some matrix way to translate these 2d images along the z-axis so they will look like what I'm trying to represent? I've gone through the other StackOverflow articles as well as the Wikipedia references, and have not found what I'm looking for -- although I might not necessarily be using the right terms for what I'm trying to do.

+1  A: 

In order to stack your views, and have them have perspective applied, you will need to first place them at different Z positions, then apply a perspective effect to the containing layer.

I believe the following code will do this, but I've not tested it (I've done something similar with pure CALayers, so the general principles are correct):

- (void)viewDidLoad {

    UIImageView *three = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"3.png"]];
    three.center = CGPointMake(160 + 60, 240 - 60);
    [self.view addSubview:three];

    three.layer.zPosition = -100;

    UIImageView *two = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"2.png"]];
    two.center = CGPointMake(160, 240);
    [self.view addSubview:two];

    two.layer.zPosition = 0;

    UIImageView *one = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"1.png"]];
    one.center = CGPointMake(160 - 60, 240 + 60);
    [self.view addSubview:one];

    one.layer.zPosition = 100;

    // release the images
    [one release];
    [two release];
    [three release];

    CATransform3D theTransform = self.view.layer.sublayerTransform;
    theTransform.m34 = 1.0 / -500;
    self.view.layer.sublayerTransform = theTransform;

    [super viewDidLoad];
}

In your code, you are only setting the perpective portion of the CATransform3D on each view's layer (that's what the m34 component of the CATransform3D matrix does). You are not actually displacing them in the Z plane. You could use a transform for this, but I find that the zPosition property of your CALayers provides a cleaner way to locate the layers in 3-D.

Once you've placed the layers relative to one another (with negative values going away from the viewpoint of the user), you need to apply a perspective effect to all layers hosted within your main view. For this, I find that setting the sublayerTransform of the hosting view's layer is necessary in order for the perspective to be applied correctly to all hosted layers.

If you wish to rotate the series of images, use CATransform3DRotate() on the sublayerTransform of the main hosting view's layer.

For an example application that does 3-D manipulation of CALayers, with perspective applied to them, check out the project I link to in the second update of my article here.

Brad Larson
The sublayerTransform definitely helped, but the zPosition didn't do anything. It seems like zPosition is like the CSS z-index property -- it changes which layers are on top of which other layers, but doesn't physically move them on the z-axis.
Jeffrey Berthiaume
@Jeffrey Berthiaume - It does indeed alter the location of CALayers in continuous 3-D space. For example, see this example: http://www.sunsetlakesoftware.com/sites/default/files/LayerExample2.zip and look at the placement of the red, green, and blue layers. Alter the Z positions and you can see them stacked at different spacings. Note that perspective is applied relative to the center of the layer, so the layers need to be slightly offset from center for you to see the stacking (or you could apply a rotation to the sublayer transform).
Brad Larson
Jeffrey Berthiaume
@Jeffrey Berthiaume - All sublayers are composited together within a layer, leaving behind a single flattened layer. If you want to rotate the layers in 3-D, you'll need to apply the rotation transform to the sublayerTransform, not the main transform, like I do in this example: http://www.sunsetlakesoftware.com/2008/10/22/3-d-rotation-without-trackball . You can also host your layers within a CATransformLayer. If you just transform a normal layer, all it does is rotate the flattened result of compositing the sublayers.
Brad Larson
A: 

I finally solved this by using CATransform3DMakeTranslation, which seems to be the only way to move a layer up or down the z-axis:

UIImageView *three = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"3.png"]];
three.layer.transform = CATransform3DMakeTranslation(0.0f, 0.0f, 20.0f);
three.center = CGPointMake(160 + 60, 240 - 60);
[self.view addSubview:three];

(I also moved UIImageView one -20.0f pixels on the z-axis. That finally gave the effect I was looking for.)

Jeffrey Berthiaume