CATransform3D 设置透视效果
在一般設定下CATransform3D的Rotation都是平面的,例如
CATransform3DRotate(origin, DegreesToRadians(180), 0.0f, 1.0f, 0.0f);
如果需要透视效果(就是近大远小),现在只需要一个简单的改动即可实现此效果,在origin上修改其m34变量,如下所示:
CATransform3D origin = CATransform3DIdentity;
CATransform3D origin = CATransform3DIdentity;
- CGFloat zDistance = -1.0 / 1500;
- origin.m34 = zDistance;
- CATransform3DRotate(origin, DegreesToRadians(180), 0.0f, 1.0f, 0.0f);
-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. 執行結果










沒有留言:
張貼留言