views:

488

answers:

2

I'm trying to create a simple iPhone app that displays a picture with a reflection beneath, and I want to rotate the picture around the X axis, using Core Animation.

I've started by creating a new iPhone app using the "View-based Application" template. I added a "Picture.jpg" image file, and then I added this to the view controller:

- (void)awakeFromNib {
    CGFloat zDistance = 1500.0f;

    // Create perspective transformation
    CATransform3D transform = CATransform3DIdentity;
    transform.m34 = 1.0f / -zDistance;

    // Create perspective transform for reflected layer
    CATransform3D reflectedTransform = CATransform3DMakeRotation(M_PI, 1.0f, 0.0f, 0.0f);
    reflectedTransform.m34 = 1.0f / -zDistance;

    // Create spinning picture
    CALayer *spinningLayer = [CALayer layer];
    spinningLayer.frame = CGRectMake(60.0f, 60.0f, 200.0f, 300.0f);
    spinningLayer.contents = (id)[UIImage imageNamed:@"Picture.jpg"].CGImage;
    spinningLayer.transform = transform;    

    // Create reflection of spinning picture
    CALayer *reflectionLayer = [CALayer layer];
    reflectionLayer.frame = CGRectMake(60.0f, 360.0f, 200.0f, 300.0f);
    reflectionLayer.contents = (id)[UIImage imageNamed:@"Picture.jpg"].CGImage;
    reflectionLayer.opacity = 0.4f;
    reflectionLayer.transform = reflectedTransform;

    // Add layers to the root layer
    [self.view.layer addSublayer:spinningLayer];
    [self.view.layer insertSublayer:reflectionLayer below:spinningLayer];

    // Spin the layers
    CABasicAnimation *anim = [CABasicAnimation animationWithKeyPath:@"transform.rotation.y"];
    anim.fromValue = [NSNumber numberWithFloat:0.0f];
    anim.toValue = [NSNumber numberWithFloat:(2 * M_PI)];
    anim.duration = 3.0f;
    anim.repeatCount = 1e100f;
    [spinningLayer addAnimation:anim forKey:@"anim"];
    [reflectionLayer addAnimation:anim forKey:@"anim"];
}

This almost works. The problem is that the spinning of the main picture and of the reflection are not perfectly synchronized. It's very close to perfect, but the edges of the bottom of the picture and of top of the reflection are a few pixels apart. They appear to differ by a few degrees.

On the left side of the screen, the reflection seems to be "ahead" of the picture, and on the right side, the reflection is behind the picture. In other words, it looks like the bottom corners of the top image are pushed toward the screen, while the top corners of the reflection are pulled toward the viewer. This is true both when looking at the front and at the back of the images as they spin.

If I increase zDistance, the effect is less noticeable. If I eliminate the perspective transformations altogether by leaving transform.m34 equal to zero for both layers, then the picture and reflection look to be perfectly synchronized. So I don't think the problem is related to time-synchronization issues.

I suspect my perspective transformations are missing something, but I'm not sure how to determine what's wrong. I think I'm basically doing the same transformation described in this related Stack Overflow question.

Can someone help?

A: 
e.James
I don't think it is a time synchronization issue. The animations appear to be running concurrently, but the seam between the images is "warped".
Kristopher Johnson
Hmm. Could it just be that one of the animations is running slightly ahead of the other? If the reflection was ahead by one frame, you would see a slight difference in the seams. This stuff isn't my area of expertise, though, so I could be wrong.
e.James
The reflection looks like it's ahead on one side, but behind on the other side.
Kristopher Johnson
The code you've provided spins the view's root layer, not the individual sublayers. So, the perspective transforms don't do anything, because the layers don't move in relation to the root layer.
Kristopher Johnson
I changed the key paths to point to the individual sublayers, but now that I think of it, I don't think they will be accessible by those names. Hmmm.
e.James
+1  A: 

I think I may have found an answer to my question. In my original code, I created the reflection by placing it underneath the picture, and applying the flip and perspective. The right way to do it seems to be to place the reflection at the same position as the picture, apply translation and rotation transforms to get it to the right place, and then apply the perspective transformation.

When animating the spin, it is necessary to spin the reflection in the opposite direction of the picture's animation.

So, here's code that works:

- (void)awakeFromNib {
    CGFloat zDistance = 1500.0f;

    // Create perspective transformation
    CATransform3D transform = CATransform3DIdentity;
    transform.m34 = 1.0f / -zDistance;

    // Create perspective transform for reflected layer
    CATransform3D reflectedTransform = CATransform3DMakeTranslation(0.0f, 300.0f, 0.0f);
    reflectedTransform = CATransform3DRotate(reflectedTransform, M_PI, 1.0f, 0.0f, 0.0f);
    reflectedTransform.m34 = 1.0f / -zDistance;

    // Create spinning picture
    CALayer *spinningLayer = [CALayer layer];
    spinningLayer.frame = CGRectMake(60.0f, 60.0f, 200.0f, 300.0f);
    spinningLayer.contents = (id)[UIImage imageNamed:@"Picture.jpg"].CGImage;
    spinningLayer.transform = transform;    

    // Create reflection of spinning picture
    CALayer *reflectionLayer = [CALayer layer];
    reflectionLayer.frame = CGRectMake(60.0f, 60.0f, 200.0f, 300.0f);
    reflectionLayer.contents = (id)[UIImage imageNamed:@"Picture.jpg"].CGImage;
    reflectionLayer.opacity = 0.4f;
    reflectionLayer.transform = reflectedTransform;

    // Add layers to the root layer
    [self.view.layer addSublayer:spinningLayer];
    [self.view.layer insertSublayer:reflectionLayer below:spinningLayer];

    // Spin the layers
    CABasicAnimation *anim = [CABasicAnimation animationWithKeyPath:@"transform.rotation.y"];
    anim.fromValue = [NSNumber numberWithFloat:0.0f];
    anim.toValue = [NSNumber numberWithFloat:(2 * M_PI)];
    anim.duration = 3.0f;
    anim.repeatCount = 1e100f;
    [spinningLayer addAnimation:anim forKey:@"anim"];

    CABasicAnimation *reflectAnim = [CABasicAnimation animationWithKeyPath:@"transform.rotation.y"];
    reflectAnim.fromValue = [NSNumber numberWithFloat:0.0f];
    reflectAnim.toValue = [NSNumber numberWithFloat:(-2 * M_PI)];
    reflectAnim.duration = 3.0f;
    reflectAnim.repeatCount = 1e100f;
    [reflectionLayer addAnimation:reflectAnim forKey:@"reflectAnim"];
}

I'm not sure exactly why this works. It makes intuitive sense to me that placing the reflection at the same position as the original and then transforming it somehow puts the two images into the same "transformation space", but I'd appreciate it if someone could explain the mathematics behind this.

Kristopher Johnson