2012年12月13日 星期四

Core Animation的基本使用(十四)

CATransform3D 设置透视效果

在一般設定下CATransform3D的Rotation都是平面的,例如
CATransform3DRotate(origin, DegreesToRadians(180), 0.0f, 1.0f, 0.0f);  

如果需要透视效果(就是近大远小),现在只需要一个简单的改动即可实现此效果,在origin上修改其m34变量,如下所示:
CATransform3D origin = CATransform3DIdentity;
  1. CGFloat zDistance = -1.0 / 1500;  
  2. origin.m34 = zDistance;  
  3. CATransform3DRotate(origin, DegreesToRadians(180), 0.0f, 1.0f, 0.0f);  
 
其中,zDistance代表透视点离当前平面的距离,取值由-1~1各个,其中:
-1 表示透视点在负无穷,1 表示透视点在正无穷,0 表示透视点在当前平面。
通常情况下会取zDistance的值为负,负貌似表示透视点在当前平面的后面。
而且zDistance的取值不是线性分布的,取1及0.1的效果相差无几。

m34负责z轴方向的translation(移动),m34= -1/D,  默认值是0,也就是说D(=1500)相當於无穷大,这意味layer in projection plane(投射面)和layer in world coordinate重合了。

D越小透视效果越明显。

所谓的D,是eye(观察者)到投射面的距离。

matrix的定義需參考Core Animation的基本使用(十二)

實作中需要用到CATransform3DConcat

CATransform3DConcat
Concatenate 'b' to 'a' and return the result: t' = a * b.


實作如下


1. 沿用前一篇的專案,在storyboard上加上兩個Slider,兩個Label,一個Switch。然後刪除一個TextFiled,此處用Slider來取代,輸入值較方便。
 
 2. 在mainViewController.h加入相關的元件及所需要的變數
#import <UIKit/UIKit.h>
#import <QuartzCore/QuartzCore.h>

@interface mainViewController : UIViewController
{
    CALayer *transform3DLayer;
    CALayer *mirrorLayer;
}

@property (weak, nonatomic) IBOutlet UIButton *animationButton;
@property (weak, nonatomic) IBOutlet UISegmentedControl *animationModeSwitch;
@property (weak, nonatomic) IBOutlet UISegmentedControl *animationDirection;
//@property (weak, nonatomic) IBOutlet UITextField *valueInputField;
@property (weak, nonatomic) IBOutlet UISwitch *animationSaveSwitch;

@property (nonatomic) CATransform3D perspective;
@property (weak, nonatomic) IBOutlet UISwitch *perspectiveSwitch;
@property (weak, nonatomic) IBOutlet UISlider *zDistanceValue;
@property (weak, nonatomic) IBOutlet UILabel *zDistanceLabel;
@property (weak, nonatomic) IBOutlet UISlider *animationValueSlider;
@property (weak, nonatomic) IBOutlet UILabel *animationValueLabel;


@end

3. 在mainViewController.m加入透視的程式碼。
#import "mainViewController.h"

@interface mainViewController ()

@end

#define DEGREES_TO_RADIANS(d) (d * M_PI / 180)

@implementation mainViewController

@synthesize animationButton;
@synthesize animationDirection;
@synthesize animationModeSwitch;
//@synthesize valueInputField;
@synthesize animationSaveSwitch;

@synthesize perspective;

@synthesize perspectiveSwitch;
@synthesize zDistanceValue;
@synthesize zDistanceLabel;

@synthesize animationValueSlider;
@synthesize animationValueLabel;


//float inputvalue;

- (void)viewDidLoad
{
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
   
    transform3DLayer = [CALayer layer];
    transform3DLayer.bounds = CGRectMake(0, 0, 250, 250);
    transform3DLayer.position = CGPointMake(350, 350);
    transform3DLayer.contents = (id)[UIImage imageNamed:@"TaiwanMap.jpg"].CGImage;
   
    perspective = CATransform3DIdentity;
   
    transform3DLayer.anchorPoint = CGPointMake(0.0, 1.0);
   
    [self.view.layer addSublayer:transform3DLayer];
      
    animationValueSlider.maximumValue =200;
    animationValueSlider.minimumValue =-200;
    animationValueSlider.value = 100;
    animationValueLabel.text = [NSString stringWithFormat:@"移動%2.2f點",animationValueSlider.value];
   
    zDistanceValue.value = 200;
   
    zDistanceLabel.text = [NSString stringWithFormat:@"Z-Dis=%2.2f",zDistanceValue.value];
   

}
- (IBAction)mirrorEffection:(id)sender {
   
    mirrorLayer = [CALayer layer];
    mirrorLayer.bounds = transform3DLayer.bounds;
    mirrorLayer.position = transform3DLayer.position;
    mirrorLayer.contents = transform3DLayer.contents;
   
    mirrorLayer.anchorPoint = CGPointMake(0.0, 1.0);
   
    [self.view.layer addSublayer:mirrorLayer];
   
    //mirrorLayer.duration = 3.0; // 不可加,會讓transform沒有動作。
   
    mirrorLayer.transform = CATransform3DRotate(perspective, DEGREES_TO_RADIANS(-180),1, 0,0);
   
    mirrorLayer.opacity = 0.3;
   
}

- (void)didReceiveMemoryWarning
{
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}
- (IBAction)startAnimation:(id)sender {
   
  //  inputvalue = [valueInputField.text floatValue];
   
    switch (animationModeSwitch.selectedSegmentIndex) {
        case 0:
            [self  setTranslateDirection];
            break;
           
        case 1:
            [self  setRotateDirection];
            break;
           
        case 2:
            [self  setScaleDirection];
            break;
           
           
        default:
            [self  setTranslateDirection];
            break;
    }
   
}
- (IBAction)animationModeChanged:(id)sender {
   
    switch ([sender selectedSegmentIndex]) {
        case 0:
            //valueInputField.text = @"100";
            animationValueSlider.maximumValue =200;
            animationValueSlider.minimumValue =-200;
            animationValueSlider.value = 100;
            animationValueLabel.text = [NSString stringWithFormat:@"移動%2.2f點",animationValueSlider.value];
           
            break;
           
        case 1:
            //valueInputField.text = @"180";
           
            animationValueSlider.maximumValue =360;
            animationValueSlider.minimumValue =-360;
            animationValueSlider.value = 30;
            animationValueLabel.text = [NSString stringWithFormat:@"旋轉%2.2f度",animationValueSlider.value];
           
            break;
           
        case 2:
            //valueInputField.text = @"1.0";
           
            animationValueSlider.maximumValue =200;
            animationValueSlider.minimumValue =1;
            animationValueSlider.value = 100;
            animationValueLabel.text = [NSString stringWithFormat:@"縮放百分之%2.2f",animationValueSlider.value];
           

            break;
           
        default:
            break;
       
    }
   
}
- (IBAction)animationDirectionChanged:(id)sender {
}

- (void) setTranslateDirection
{
    switch (animationDirection.selectedSegmentIndex) {
        case 0: // X
            if (animationSaveSwitch.on == 1) // ON
            {
                transform3DLayer.transform = CATransform3DTranslate(perspective, animationValueSlider.value, 0,0);
                perspective = CATransform3DTranslate(perspective, animationValueSlider.value, 0,0);
            }
            else
                transform3DLayer.transform = CATransform3DMakeTranslation(animationValueSlider.value, 0,0);
            break;
           
        case 1: // Y
           
            if (animationSaveSwitch.on == 1) // ON
            {
                transform3DLayer.transform = CATransform3DTranslate(perspective, 0, animationValueSlider.value ,0);
                perspective = CATransform3DTranslate(perspective, 0, animationValueSlider.value,0);
            }
            else
                transform3DLayer.transform = CATransform3DMakeTranslation( 0, animationValueSlider.value,0);
            break;
           
        case 2: // Z
            if (animationSaveSwitch.on == 1) // ON
            {
                transform3DLayer.transform = CATransform3DTranslate(perspective, 0, 0,animationValueSlider.value);
                perspective = CATransform3DTranslate(perspective, 0, 0,animationValueSlider.value);
            }
            else
                transform3DLayer.transform = CATransform3DMakeTranslation( 0, 0,animationValueSlider.value);
            break;
           
           
        default:
            if (animationSaveSwitch.on == 1) // ON
            {
                transform3DLayer.transform = CATransform3DTranslate(perspective, animationValueSlider.value, 0,0);
                perspective = CATransform3DTranslate(perspective, animationValueSlider.value, 0,0);
            }
            else
                transform3DLayer.transform = CATransform3DMakeTranslation(animationValueSlider.value, 0,0);
           
            break;
    }
}

- (void) setRotateDirection
{
    switch (animationDirection.selectedSegmentIndex) {
        case 0: // X
           
            if (perspectiveSwitch.on == 1)
            {
                perspective = CATransform3DMakePerspective(CGPointMake(0, 0), zDistanceValue.value);
               
                transform3DLayer.transform = CATransform3DRotate(perspective, DEGREES_TO_RADIANS(animationValueSlider.value),1, 0,0);
               
                break;
            }
           
            if (animationSaveSwitch.on == 1) // ON
            {
                transform3DLayer.transform = CATransform3DRotate(perspective, DEGREES_TO_RADIANS(animationValueSlider.value),1, 0,0);
                perspective = CATransform3DRotate(perspective, DEGREES_TO_RADIANS(animationValueSlider.value),1, 0,0);
            }
            else
                transform3DLayer.transform = CATransform3DMakeRotation(DEGREES_TO_RADIANS(animationValueSlider.value),1, 0,0);
            break;
           
        case 1: // Y
           
            if (perspectiveSwitch.on == 1)
            {
                perspective = CATransform3DMakePerspective(CGPointMake(0, 0), zDistanceValue.value);
               
                transform3DLayer.transform = CATransform3DRotate(perspective, DEGREES_TO_RADIANS(animationValueSlider.value), 0,1,0);
               
                break;
            }

           
           
            if (animationSaveSwitch.on == 1) // ON
            {
                transform3DLayer.transform = CATransform3DRotate(perspective, DEGREES_TO_RADIANS(animationValueSlider.value), 0,1,0);
                perspective = CATransform3DRotate(perspective, DEGREES_TO_RADIANS(animationValueSlider.value), 0,1,0);
            }
            else
                transform3DLayer.transform = CATransform3DMakeRotation(DEGREES_TO_RADIANS(animationValueSlider.value), 0,1,0);
            break;
           
        case 2: // Z
           
            if (perspectiveSwitch.on == 1)
            {
                perspective = CATransform3DMakePerspective(CGPointMake(0, 0), zDistanceValue.value);
               
                transform3DLayer.transform = CATransform3DRotate(perspective, DEGREES_TO_RADIANS(animationValueSlider.value), 0,0, 1);
               
                break;
            }
           
            if (animationSaveSwitch.on == 1) // ON
            {
                transform3DLayer.transform = CATransform3DRotate(perspective, DEGREES_TO_RADIANS(animationValueSlider.value), 0,0,1);
                perspective = CATransform3DRotate(perspective, DEGREES_TO_RADIANS(animationValueSlider.value), 0,0,1);
            }
            else
                transform3DLayer.transform = CATransform3DMakeRotation(DEGREES_TO_RADIANS(animationValueSlider.value), 0,0,1);
            break;
           
           
        default:
            if (animationSaveSwitch.on == 1) // ON
            {
                transform3DLayer.transform = CATransform3DRotate(perspective, DEGREES_TO_RADIANS(animationValueSlider.value),1, 0,0);
                perspective = CATransform3DRotate(perspective, DEGREES_TO_RADIANS(animationValueSlider.value),1, 0,0);
            }
            else
                transform3DLayer.transform = CATransform3DMakeRotation(DEGREES_TO_RADIANS(animationValueSlider.value),1, 0,0);
           
            break;
    }
}

- (void) setScaleDirection
{
    switch (animationDirection.selectedSegmentIndex) {
        case 0: // X
            if (animationSaveSwitch.on == 1) // ON
            {
                transform3DLayer.transform = CATransform3DScale(perspective, animationValueSlider.value/100, 1.0, 1.0);
                perspective = CATransform3DScale(perspective, animationValueSlider.value/100, 1.0, 1.0);
            }
            else
                transform3DLayer.transform = CATransform3DMakeScale(animationValueSlider.value/100, 1.0, 1.0);
            break;
           
        case 1: // Y
            if (animationSaveSwitch.on == 1) // ON
            {
                transform3DLayer.transform = CATransform3DScale(perspective, 1.0, animationValueSlider.value/100, 1.0);
                perspective = CATransform3DScale(perspective, 1.0,animationValueSlider.value/100, 1.0);
            }
            else
                transform3DLayer.transform = CATransform3DMakeScale(1.0, animationValueSlider.value/100, 1.0);
            break;
           
        case 2: // Z
            if (animationSaveSwitch.on == 1) // ON
            {
                transform3DLayer.transform = CATransform3DScale(perspective, 1.0, 1.0,animationValueSlider.value/100);
                perspective = CATransform3DScale(perspective, 1.0, 1.0, animationValueSlider.value/100);
            }
            else
                transform3DLayer.transform = CATransform3DMakeScale(1.0, 1.0,animationValueSlider.value/100);
            break;
           
           
        default:
            if (animationSaveSwitch.on == 1) // ON
            {
                transform3DLayer.transform = CATransform3DScale(perspective, animationValueSlider.value/100, 1.0, 1.0);
                perspective = CATransform3DScale(perspective, animationValueSlider.value/100, 1.0, 1.0);
            }
            else
                transform3DLayer.transform = CATransform3DMakeScale(animationValueSlider.value/100, 1.0, 1.0);
           
            break;
    }
}


CATransform3D CATransform3DMakePerspective(CGPoint center, float disZ)
{
    CATransform3D transToCenter = CATransform3DMakeTranslation(-center.x, -center.y, 0);
    CATransform3D transBack = CATransform3DMakeTranslation(center.x, center.y, 0);
    CATransform3D scale = CATransform3DIdentity;
    scale.m34 = -1.0f/disZ;
    return CATransform3DConcat(CATransform3DConcat(transToCenter, scale), transBack);
}

CATransform3D CATransform3DPerspect(CATransform3D t, CGPoint center, float disZ)
{
    return CATransform3DConcat(t, CATransform3DMakePerspective(center, disZ));
}

- (IBAction)zDisChange:(id)sender {
    zDistanceLabel.text = [NSString stringWithFormat:@"Z-Dis=%2.2f",zDistanceValue.value];
}
- (IBAction)animationValueChange:(id)sender {
   
    switch ([animationModeSwitch selectedSegmentIndex]) {
        case 0:

            animationValueLabel.text = [NSString stringWithFormat:@"移動%2.2f點",animationValueSlider.value];
           
            break;
           
        case 1:

            animationValueLabel.text = [NSString stringWithFormat:@"旋轉%2.2f度",animationValueSlider.value];
           
            break;
           
        case 2:
            animationValueLabel.text = [NSString stringWithFormat:@"縮放百分之%2.2f",animationValueSlider.value];
           
           
            break;
           
        default:
            break;
           
    }

}


@end

4. 執行結果









沒有留言: