Search code examples
ioscore-animationperspectiverotational-matrices

How to flip a CALayer around its Y-axis?


I use this code inside a subclass of CALayer which instances are sublayers of the root CALayer.

-(void)setSelected:(bool)s {
    selected=s;
    if (s)
    {
        CATransform3D  rot = CATransform3DMakeRotation(M_PI, 0, 1, 0);
        rot.m34=-1.0 / 200;
        [self setTransform:rot];

    }
    else
    {
            CATransform3D  rot = CATransform3DMakeRotation(0, 0, 1, 0);
        rot.m34=-1.0 / 200;
        [self setTransform:rot];
    }
}

When the selected property to set to TRUE, here is what happens: the rotation is done until the angle is equal to M_PI/2, then the layer disappears because it is orthogonal. The end of the animation is wrong: the edge that appeared to grow in the first part of the animation (for example on the left side), ends the animation on the left side instead of the right side. However the content is flipped.

I think this has something to do with the interpolation between the two rotation matrices but I can't understand exactly what is happening.

Details: The animation looks like it is doing this:

  1. increment rotation around Y axis by +Pi/2
  2. content flipping
  3. increment rotation around Y axis by -Pi/2, as if it bounced of the (yz)plane

The flipped content is what I am trying to achieve.

Here are the frames of the animation I get. As you can see, the small side of the trapezoid is always of the left; it should be on the right on at the end of the animation (top right frame). A layer rotating around the Y-axis


Solution

  • The math of your transform is not correct. You should apply the perspective transform after the rotation transform, which means concatenating both transforms. Changing the m34 coefficient of your rotation transform is not equivalent.

    You should replace your code by the following :

    -(void)setSelected:(bool)s {
        selected=s;
        CATransform3D perpectiveTransform = CATransform3DIdentity;
        perpectiveTransform.m34 =-1.0 / 200;
    
        if (s)
        {
    
            CATransform3D  rot = CATransform3DMakeRotation(M_PI, 0, 1, 0);
            [self setTransform:CATransform3DConcat(rot, perpectiveTransform)];
    
        }
        else
        {
            CATransform3D  rot = CATransform3DMakeRotation(0, 0, 1, 0);
            [self setTransform:CATransform3DConcat(rot, perpectiveTransform)];
        }
    }
    

    By the way, an often more convenient method to apply perspective transforms is to use the superlayer's sublayerTransform property, thus applying it to every sublayer (if this is needed).

    This would yield something like :

    self.superlayer.sublayerTransform = perspectiveTransform;  //do this in your setup
    ....
    self.transform = rot;
    

    for an identical looking result.