Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I stack images to simulate depth using Core Animation?

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.

like image 303
Jeffrey Berthiaume Avatar asked May 14 '10 14:05

Jeffrey Berthiaume


2 Answers

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.

like image 181
Brad Larson Avatar answered Nov 16 '22 23:11

Brad Larson


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.)

like image 32
Jeffrey Berthiaume Avatar answered Nov 16 '22 22:11

Jeffrey Berthiaume