【iOS】 变换:CGAffineTransform 和 CATransform3D

矩阵乘法

矩阵A 和 矩阵B,只有A的列数与B的行数相同时,A*B的结果才有效:
矩阵A*B

A(j 行 m列) * B(m行k列)= C(m 行 m 列)

矩阵相乘的顺序不同,所得到的结果也不同
矩阵B*A

CGAffineTransform 仿射变换

概念和定义:

当对图层应用变换矩阵,图层矩形内的每一个点都被相应地做变换,从而形成一个新的四边形的形状。
CGAffineTransform中的“仿射”的意思是无论变换矩阵用什么值,图层中平行的两条线在变换之后仍然保持平行,
CGAffineTransform可以做出任意符合上述标注 的变换。

一些仿射变换和非仿射变换。

UIView可以通过设置transform属性做变换。
CALayer同样也有一个transform属性,但是它的类型是CATransform3D。
CALayer对应于UIView的CGAffineTransform的属性叫做affineTransform。

在程序中以结构体形式定义:

public struct CGAffineTransform {

    public var a: CGFloat

    public var b: CGFloat

    public var c: CGFloat

    public var d: CGFloat

    public var tx: CGFloat

    public var ty: CGFloat

    public init()

    public init(a: CGFloat, b: CGFloat, c: CGFloat, d: CGFloat, tx: CGFloat, ty: CGFloat)
}
矩阵说明

用CGPoint的每一列和CGAffineTransform矩阵的每一行对应元素相. 乘再求和,就形成了一个新的CGPoint类型的结果。
两个矩阵的第三例的值,为了能让矩阵做乘法,左边矩阵的列数一定要和右边矩阵的行数个数相同,
所以要给矩阵填充一些标志值,使得既可以让矩阵做乘法,又不改变运算结果,并且没必要存储这些添加的值,
因为它们的值不会发生变化,但是要用来作运算。

参数的含义:

  • 平移或缩放时:
    由于只有a是在直接改变x的倍数,而tx是在改变x的坐标,所以可以得出下面的结论:
    a : x轴方向的缩放
    d : y轴方向的缩放
    tx : x轴方向的平移
    ty : y轴方向的平移
  • 旋转时 (假设旋转角为A):
    那么旋转的变换矩阵运算结果如下:

  • 初始化一个默认的affineTransform:

let affineTransform = CGAffineTransform.identity

矩阵如下:

此时 a = d = 1 ,说明缩放倍数为1, tx = ty = 0说明缩放倍数为1, 平移距离为0

常用函数:

  • 常用函数:
/*
返回以[1 0 0 , 0 1 0 , 0 0 0] 为基准进行平移(tx, ty)变换之后的 transform 对象。
结果矩阵:
[1 0 0 , 0 1 0 , tx ty 1]
*/
public init(translationX tx: CGFloat, y ty: CGFloat)


/*
返回以[1 0 0 , 0 1 0 , 0 0 0] 为基准进行缩放(sx, sy)变换之后的 transform 对象。
结果矩阵:
[sx 0 0 , 0 sy 0 , 0 0 1]
*/
public init(scaleX sx: CGFloat, y sy: CGFloat)


/*
返回以[1 0 0 , 0 1 0 , 0 0 0] 为基准进行角度angle旋转变换之后的 transform 对象。
结果矩阵:
[cos(angle) sin(angle) 0 , -sin(angle) cos(angle) 0 , 0 0 1]
*/
public  init(rotationAngle angle: CGFloat)
  • 以当前变换为基准
/*
以当前变换 t 为基准进行平移(tx, ty)变换之后的 transform 对象。
结果矩阵: 
t' = [1 0 0 , 0 1 0 , tx ty 1] * t
*/
public func translatedBy(x tx: CGFloat, y ty: CGFloat) -> CGAffineTransform


/*
以当前变换 t 为基准进行缩放(sx, sy) 变换之后的 transform 对象。
结果矩阵: 
t' =  [sx 0 0 , 0 sy 0 , 0 0 1] * t
*/
public func scaledBy(x sx: CGFloat, y sy: CGFloat) -> CGAffineTransform


/*
以当前变换 t 为基准进行角度angle旋转变换之后的 transform 对象。
结果矩阵: 
t' = [cos(angle) sin(angle) 0 , -sin(angle) cos(angle) 0 , 0 0 1] * t
*/
public func rotated(by angle: CGFloat) -> CGAffineTransform

基本使用:

  • 平移:
//平移1
var affineTrans_translation_02 = CGAffineTransform.identity
affineTrans_translation_02.tx = 100
affineTrans_translation_02.ty = 100
self.imageView02.transform = affineTrans_translation_02

//平移2
let affineTrans_translation_01 = CGAffineTransform(translationX: 100, y: 100)
self.imageView01.transform = affineTrans_translation_01

//平移3
self.imageView03.transform = self.imageView03.transform.translatedBy(x: 100, y: 100)
  • 缩放:
//缩放1
let affineTrans_scale_01 = CGAffineTransform(scaleX: 0.5, y: 0.5)
self.imageView01.transform = affineTrans_scale_01

//缩放2
var affineTrans_scale_02 = CGAffineTransform.identity
affineTrans_scale_02.a = 0.5
affineTrans_scale_02.d = 0.5
self.imageView02.transform = affineTrans_scale_02

//缩放3
self.imageView03.transform = self.imageView03.transform.scaledBy(x: 0.5, y: 0.5)
  • 旋转:
//旋转1
let affineTrans_rotate_01 = CGAffineTransform(rotationAngle: CGFloat.pi / 4)
self.imageView01.transform = affineTrans_rotate_01

//旋转2
var affineTrans_rotate_02 = CGAffineTransform.identity
affineTrans_rotate_02.a = cos(CGFloat.pi / 4)
affineTrans_rotate_02.b = sin(CGFloat.pi / 4)
affineTrans_rotate_02.c = -sin(CGFloat.pi / 4)
affineTrans_rotate_02.d = cos(CGFloat.pi / 4)
self.imageView02.transform = affineTrans_rotate_02

//旋转3
self.imageView03.transform = self.imageView03.transform.rotated(by: CGFloat.pi/4)
  • 混合变换:
//混合变换 1 平移(x:100, y : 100) + 缩放(0.5)
var trans1 = CGAffineTransform.identity
trans1.a = 0.5
trans1.d = 0.5
trans1.tx = 100
trans1.ty = 100
self.imageView01.transform = trans1

//混合变换 2 缩放(0.5) + 旋转(60°)
var trans2 = CGAffineTransform.identity
//因为决定旋转的是a、b、c、d,而决定缩放的是a的d,两个会有冲突。所以使用设置参数的方式则不太好实现
//trans2.a = 0.5//cos(CGFloat.pi / 2)
//trans2.d = 0.5//cos(CGFloat.pi / 2)
//trans2.b = sin(CGFloat.pi/2)
//trans2.c = -sin(CGFloat.pi/2)
//trans2.tx = 100
//trans2.ty = 100
//非参数方式
let tempTrans =  trans2.rotated(by: CGFloat.pi/3)
let tempTrans1 = tempTrans.scaledBy(x: 0.5, y: 0.5)
self.imageView02.transform = tempTrans1

//混合变换 3  旋转(60°) + 缩放(0.5) + 平移(x:100, y : 100)   1
let trans_rotate_01 = CGAffineTransform(rotationAngle: CGFloat.pi / 3)
let trans_scale_01 = CGAffineTransform(scaleX: 0.5, y: 0.5)
let trans_translation_01 = CGAffineTransform(translationX: 100, y: 100)
//以concat函数叠加的形式进行混合变换时,应该最后再叠加平移变换,
//否则会因为矩阵相乘的顺序问题导致平移的方向和距离不正确
//而旋转和缩放貌似没有先后顺序的区分
let trans_final_01 = (trans_rotate_01.concatenating(trans_scale_01)).concatenating(trans_translation_01)
self.imageView01.transform = trans_final_01

//混合变换 4  旋转(60°) + 缩放(0.5) + 平移(x:100, y : 100)   2
self.imageView02.transform = self.imageView02.transform.translatedBy(x: 100, y: 100)
self.imageView02.transform = self.imageView02.transform.rotated(by: CGFloat.pi/3)
self.imageView02.transform = self.imageView02.transform.scaledBy(x: 0.5, y: 0.5)
self.imageView03.transform = self.imageView03.transform.translatedBy(x: 100, y: 100) //参照用

注意点:

  • transform变换之后frame和bounds的宽高会有所不同:
    对于视图或者图层来说,frame并不是一个非常清晰的属性。
    它其实是一个虚拟属性,是根据bounds,position和transform计算而来,
    所以当其中任何一个值发生变化,frame都会改变。相反, 改变frame的值同样会影响到他们当中的值。当对图层做变换的时候,比如旋转或者缩放,frame实际上代表了覆盖在图层旋转之后的整个轴对齐的矩形区域,也就是说frame的宽度可能和bounds的宽度不一致了。
    如图所示:

    在旋转完成后一个视图或者图层的frame属性
print("即将开始动画 frame : \(self.imageView03.frame)")
UIView.animate(withDuration: 0.5, animations: {
    print("动画中 - 变换前, imageView frame : \(self.imageView03.frame)")
    let value_Rotation_03 = NSNumber(value: Float(CGFloat.pi/4))
    self.imageView03.layer.setValue(value_Rotation_03, forKeyPath: "transform.rotation.z")
    print("动画中 - 变换后, imageView frame : \(self.imageView03.frame)")
            
}) { (finished) in
    print("动画完成, imageView frame : \(self.imageView03.frame)")
    self.isInitializedStatus = !self.isInitializedStatus
}

输出结果:

即将开始动画 frame : (10.0, 440.0, 150.0, 150.0)
动画中 - 变换前, imageView frame : (10.0, 440.0, 150.0, 150.0)
动画中 - 变换后, imageView frame : (-21.0660, 408.9339, 212.1320, 212.1320)
动画完成, imageView frame : (-21.0660, 408.9339, 212.1320, 212.1320)
  • 叠加变换时各个变换的叠加顺序不同,会导致最终的结果不同:
  1. 如最开始说的,矩阵之间相乘的顺序不同所得出的结果也不同。
    当多个变换中有平移变换时,尽量让平移变换最后被叠加,否则会导致最终平移的方向或者距离错误。
  2. 旋转变换是a,b,c,d四个参数来决定旋转效果的,而缩放是由a,d两个参数来决定的,两者有交集,如下:
var trans2 = CGAffineTransform.identity
trans2.a = 0.5//cos(CGFloat.pi / 2)
trans2.d = 0.5//cos(CGFloat.pi / 2)
trans2.b = sin(CGFloat.pi/2)
trans2.c = -sin(CGFloat.pi/2)
trans2.tx = 100
trans2.ty = 100

所以不应该使用设置参数的方式来实现旋转和缩放的叠加,而应该分别创建两个 affineTransform 对象分别进行旋转和缩放,
然后通过concat函数来达到最终效果。

CATransform3D

CATransform3D 用于进行三维图形的几何变换,属于CoreAnimation。它定义了一个4*4的变化矩阵矩阵:

struct CATransform3D
{
  CGFloat m11, m12, m13, m14;
  CGFloat m21, m22, m23, m24;
  CGFloat m31, m32, m33, m34;
  CGFloat m41, m42, m43, m44;
};

通过对矩阵参数的设置,我们可以改变layer的一些属性,这个属性的改变,可以产生动画的效果。
一个三维空间中的坐标点(x,y,z)构成的简单矩阵经过与上述的变换矩阵相乘之后就可以得到变化之后的坐标点


屏幕快照 2018-11-27 下午3.42.19.png

参数的含义:

  • 平移或缩放:
    由于只有m11是在直接改变x的倍数,而m41是在改变x的坐标,所以可以得出下面的结论:
    m11 : x轴方向的缩放
    m41 : x轴方向的平移
    同理也可以得到y轴和z轴上的平移和缩放关系:
    m22 : y轴方向的缩放
    m42 : y轴方向的平移
    m33 : y轴方向的缩放
    m43 : z轴方向的平移
  • 旋转:
    具体可以分为绕固定轴(x、y、z)的二维旋转和绕任意轴的三维旋转
  1. 绕固定轴的二维旋转(旋转角度为A)

    m22、m23、m32、m33 同时决定了绕X轴的旋转
    绕X轴旋转

m11、m13、m31、m33 同时决定了绕X轴的旋转
绕Y轴旋转

m11、m12、m21、m22 同时决定了绕Z轴的旋转


绕Z轴旋转
  1. 绕任意轴的三维旋转
    假设是以向量 K(x, y, z)为轴,旋转角度A,得向量K’(x', y', z')

    如果A’ = A * R(右乘), 则R为:

如果A’ = R * A(左乘), 则R为:

可见,m11 ~ m13、 m21 ~ m23、 m31 ~ m33 都会影响绕轴旋转的效果。

常用函数:

//初始化一个CATransform3D的实例,默认的值是[1 0 0 0; 0 1 0 0; 0 0 1 0; 0 0 0 1]
public let CATransform3DIdentity: CATransform3D


//判断一个CATransform3D的实例是否是初始化值
public func CATransform3DIsIdentity(_ t: CATransform3D) -> Bool


//判断两个CATransform3D的实例的值是否相等。
public func CATransform3DEqualToTransform(_ a: CATransform3D, _ b: CATransform3D) -> Bool
/* 以默认值为基准,返回一个平移'(tx, ty, tz)'后的CATransform3D实例t':
* t' = [1 0 0 0; 0 1 0 0; 0 0 1 0; tx ty tz 1]
* tx, ty, tz分别代表在x方向、y方向、z方向的位移量 */
public func CATransform3DMakeTranslation(_ tx: CGFloat, _ ty: CGFloat, _ tz: CGFloat) -> CATransform3D


/* 以默认值为基准,返回一个缩放'(sx, sy, sz)'后的CATransform3D实例t':
* t' = [sx 0 0 0; 0 sy 0 0; 0 0 sz 0; 0 0 0 1]
* sx, sy, sz分别代表在x方向、y方向、z方向的缩放比例,缩放是以layer的中心对称变化
* 当sx < 0时,layer会在缩放的基础上沿穿过其中心的竖直线翻转
* 当sy < 0时,layer会在缩放的基础上沿穿过其中心的水平线翻转 */
public func CATransform3DMakeScale(_ sx: CGFloat, _ sy: CGFloat, _ sz: CGFloat) -> CATransform3D


/* 以默认值为基准,返回一个沿矢量'(x, y, z)'轴线,逆时针旋转'angle'弧度后的CATransform3D实例
* 弧度 = π / 180 × 角度,'M_PI'代表180角度
* x,y,z决定了旋转围绕的轴线,取值为[-1, 1]。例如(1,0,0)是绕x轴旋转,(0.5,0.5,0)是绕x轴与y轴夹角45°为轴线旋转 */
public func CATransform3DMakeRotation(_ angle: CGFloat, _ x: CGFloat, _ y: CGFloat, _ z: CGFloat) -> CATransform3D
/* 以't'为基准,返回一个平移'(tx, ty, tz)'后的CATransform3D实例t':
* t' = translate(tx, ty, tz) * t.
* '(tx, ty, tz)'同'CATransform3DMakeTranslation' */
public func CATransform3DTranslate(_ t: CATransform3D, _ tx: CGFloat, _ ty: CGFloat, _ tz: CGFloat) -> CATransform3D


/* 以't'为基准,返回一个缩放'(sx, sy, sz)'后的CATransform3D实例t':
* t' = scale(sx, sy, sz) * t.
* '(sx, sy, sz)'同'CATransform3DMakeScale' */
public func CATransform3DScale(_ t: CATransform3D, _ sx: CGFloat, _ sy: CGFloat, _ sz: CGFloat) -> CATransform3D


/* 以't'为基准,返回一个沿矢量'(x, y, z)'轴线,逆时针旋转'angle'弧度后的CATransform3D实例t':
* t' = rotation(angle, x, y, z) * t.
* '(angle, x, y, z)'同'CATransform3DMakeRotation' */
public func CATransform3DRotate(_ t: CATransform3D, _ angle: CGFloat, _ x: CGFloat, _ y: CGFloat, _ z: CGFloat) -> CATransform3D
/* 叠加两个CATransform3D实例的值并返回得到的CATransform3D实例t':
* t' = a * b. */
public func CATransform3DConcat(_ a: CATransform3D, _ b: CATransform3D) -> CATransform3D


/* 反转一个CATransform3D实例并返回结果
* 如果没有翻转则返回原始矩阵 */
public func CATransform3DInvert(_ t: CATransform3D) -> CATransform3D


/* 将一个CGAffineTransform实例转换得到一个同样效果的CATransform3D实例 */
public func CATransform3DMakeAffineTransform(_ m: CGAffineTransform) -> CATransform3D


/* 判断一个CATransform3D实例能否被成功的转换成一个CGAffineTransform实例 */
public func CATransform3DIsAffine(_ t: CATransform3D) -> Bool


/* 将一个CATransform3D实例转换得到一个同样效果的CGAffineTransform实例
* 如果不能成功转换,则返回空值 */
public func CATransform3DGetAffineTransform(_ t: CATransform3D) -> CGAffineTransform

基本使用:

  • 平移:
//函数方式
var transform3D_Translate_01 = CATransform3DIdentity
transform3D_Translate_01 = CATransform3DTranslate(transform3D_Translate_01, 50, 100, 0)
self.imageView01.layer.transform = transform3D_Translate_01

//参数方式
var transform3D_Translate_02 = CATransform3DIdentity
transform3D_Translate_02.m41 = 50
transform3D_Translate_02.m42 = 100
self.imageView02.layer.transform = transform3D_Translate_02

//KVC方式
let value_Translate_03_x = NSNumber(value: 50)
let value_Translate_03_y = NSNumber(value: 100)
self.imageView03.layer.setValue(value_Translate_03_x, forKeyPath: "transform.translation.x")
self.imageView03.layer.setValue(value_Translate_03_y, forKeyPath: "transform.translation.y")
  • 缩放:
//函数方式
var transform3D_Scale_01 = CATransform3DIdentity
transform3D_Scale_01 = CATransform3DScale(transform3D_Scale_01, 0.5, 0.5, 1)
self.imageView01.layer.transform = transform3D_Scale_01

//参数方式
var transform3D_Scale_02 = CATransform3DIdentity
transform3D_Scale_02.m11 = 0.5
transform3D_Scale_02.m22 = 0.5
transform3D_Scale_02.m33 = 1
self.imageView02.layer.transform = transform3D_Scale_02

//KVC方式
let value_Scale_03_x = NSNumber(value: 0.5)
let value_Scale_03_y = NSNumber(value: 0.5)
let value_Scale_03_z = NSNumber(value: 1)
self.imageView03.layer.setValue(value_Scale_03_x, forKeyPath: "transform.scale.x")
self.imageView03.layer.setValue(value_Scale_03_y, forKeyPath: "transform.scale.y")
self.imageView03.layer.setValue(value_Scale_03_z, forKeyPath: "transform.scale.z")
  • 旋转:
//1 函数方式
var transform3D_Rotation_01 = CATransform3DIdentity
transform3D_Rotation_01 = CATransform3DRotate(transform3D_Rotation_01, CGFloat.pi / 4, 0, 0, 1)
self.imageView01.layer.transform = transform3D_Rotation_01

//2 参数方式
var transform3D_Rotation_02 = CATransform3DIdentity
transform3D_Rotation_02.m22 = cos(CGFloat.pi / 4)
transform3D_Rotation_02.m12 = sin(CGFloat.pi / 4)
transform3D_Rotation_02.m11 = cos(CGFloat.pi / 4)
transform3D_Rotation_02.m21 = -sin(CGFloat.pi / 4)
self.imageView02.layer.transform = transform3D_Rotation_02

//3 KVC方式
let value_Rotation_03 = NSNumber(value: Float(CGFloat.pi/4))
self.imageView03.layer.setValue(value_Rotation_03, forKeyPath: "transform.rotation.z”)
  • 混合变换Concat (x,y轴平移100,旋转90°, x,y轴缩放0.5):
//混合变换1 平移 、 缩放 、 旋转
let tempTrans_translate = CATransform3DMakeTranslation(100, 100, 0)
let tempTrans_scale = CATransform3DMakeScale(0.5, 0.5, 1)
let tempTrans_rotation = CATransform3DMakeRotation(CGFloat.pi / 2, 0, 0, 1)

let tempTrans = CATransform3DConcat(tempTrans_rotation,tempTrans_scale)
let finalTrans = CATransform3DConcat(tempTrans, tempTrans_translate)
self.imageView02.layer.transform = tempTrans_translate
self.imageView03.layer.transform = finalTrans


//混合变换2 平移 、 缩放 、 旋转
//这种方式需要先平移然后再做旋转或者缩放
self.imageView03.layer.transform = CATransform3DTranslate(self.imageView03.layer.transform, 100, 100, 0)
self.imageView03.layer.transform = CATransform3DRotate(self.imageView03.layer.transform, CGFloat.pi / 2, 0, 0, 1)
self.imageView03.layer.transform = CATransform3DScale(self.imageView03.layer.transform, 0.5, 0.5, 1)
self.imageView02.layer.transform = CATransform3DTranslate(self.imageView02.layer.transform, 100, 100, 0)


//混合变换3 平移 、 缩放 、 旋转
//KVC的方式不需要注意平移与缩放旋转的顺序
self.imageView03.layer.setValue(NSNumber(value: 100), forKeyPath: "transform.translation.x")
self.imageView03.layer.setValue(NSNumber(value: 100), forKeyPath: "transform.translation.y")
self.imageView03.layer.setValue(NSNumber(value: 0.5), forKeyPath: "transform.scale.x")
self.imageView03.layer.setValue(NSNumber(value: 0.5), forKeyPath: "transform.scale.y")
self.imageView03.layer.setValue(NSNumber(value: Float.pi / 2), forKeyPath: "transform.rotation.z")

self.imageView02.layer.setValue(NSNumber(value: 100), forKeyPath: "transform.translation.x")
self.imageView02.layer.setValue(NSNumber(value: 100), forKeyPath: "transform.translation.y")
  • 反转:
//CATransform3DInvert 反转
//let trans = CATransform3DMakeTranslation(-100, -100, 0)
let trans = CATransform3DMakeScale(2, 2, 1)
let invertedTrans = CATransform3DInvert(trans)
self.imageView02.layer.transform = trans
self.imageView03.layer.transform = invertedTrans
  • 与CGAffineTransform之间的转换:
//CATransform3DIsAffine :如果是2d效果的变换,如x,y轴的平移,x,y轴的缩放 和 z轴的旋转时,则CATransform3DIsAffine返回true
let trans1 = CATransform3DMakeTranslation(100, 100, 0)
let trans2 = CATransform3DMakeRotation(CGFloat.pi/2, 1, 0, 0)
let trans3 = CATransform3DMakeScale(1.1, 1.1, 1.1)
print("trans1 is affine transform : \(CATransform3DIsAffine(trans1))")
print("trans2 is affine transform : \(CATransform3DIsAffine(trans2))")
print("trans3 is affine transform : \(CATransform3DIsAffine(trans3))")

//对x,y轴的缩放是2d效果,所以可以成功转换成2d的放射变换
//let trans3d = CATransform3DMakeScale(0.5, 0.5, 1)
//绕X轴旋转是3d效果的变换无法转换成2d的仿射变换,所以此时返回空
let trans3d = CATransform3DMakeRotation(CGFloat.pi / 2, 1, 0, 0)
let affineTrans = CATransform3DGetAffineTransform(trans3d)
self.imageView02.transform = affineTrans
self.imageView03.layer.transform = trans3d

注意点

  • 3D效果旋转时要注意透视效果:
    使用CATransform3DMakeRotation或CATransform3DRotate,绕平行于x轴或y轴的某一条线旋转后,感觉内容被压扁了,看起来并没有旋转。其实是旋转了,只不过没有透视效果(简单来说就是近大远小的视觉效果),看起来就像是”压扁了“。
    如果想要内容看起来有旋转的立体效果,可以修改实例m34属性的值,它决定透视效果,m34 = -1 / D,D越小,透视效果越明显。
    D具体的取值范围需要开发者根据实际显示效果调试,建议取400左右。通常500 - 1000
//绕X轴旋转
var transform3D_Rotation_01 = CATransform3DIdentity
transform3D_Rotation_01.m34 = -1 / 400
transform3D_Rotation_01 = CATransform3DRotate(transform3D_Rotation_01, CGFloat.pi / 4, 1, 0, 0)
self.imageView01.layer.transform = transform3D_Rotation_01
透视
  • Make系列函数实现变换叠加时要使用concat:
    当使用Make系列函数进行混合变换时需要使用CATransform3DConcat将生成的变换叠加起来才会有效果(混合变换1)
    因为CATransform3DMake...’系列函数是以默认值为基准来生成变换对象的。
    如使用CATransform3DMakeTranslation平移后,值是[1 0 0 0; 0 1 0 0; 0 0 1 0; tx ty tz 1]。
    接下来使用CATransform3DMakeScale时,修改的值以初始化的默认值[1 0 0 0; 0 1 0 0; 0 0 1 0; 0 0 0 1]为基准,
    得到的值为[sx 0 0 0; 0 sy 0 0; 0 0 sz 0; 0 0 0 1],上一步平移的修改不再生效,以此类推。
    这样的使用结果只有最后的修改或赋值才会生效。
    也可以使用不带'Make'的'CATransform3D...'系列函数,但它需要传入一个CATransform3D实例,并以此为基准进行形变,
    可以实现和CATransform3DConcat同样的效果。(混合变换2)

  • 灭点:
    当在透视角度绘图的时候,远离相机视角的物体将会越远越小,当远离到一个极限距离时它们可能就缩成了一个点,
    于是所有的物体最后都汇聚消失在同一点上。
    在现实中,这个点通常是视图的中心。于是为了在应用中创建拟真效果的透视,这个点应该聚在屏幕中点。
    或者至少是包含所有3D对象视图中点。

在代码中,一个图层在3D旋转之后的灭点取决于它的锚点(anchorPoint),当改变这个视图的锚点之后,这个图层的位置同时会改变,但是它的灭点却还在锚点改变之前的位置。如下代码:

var trans1 = CATransform3DIdentity
trans1.m34 = 1 / 200
trans1 = CATransform3DRotate(trans1, CGFloat.pi/3, 0, 1, 0)
self.imageView01.layer.transform = trans1

var trans2 = CATransform3DIdentity
trans2.m34 = 1 / 200
trans2 = CATransform3DRotate(trans2, CGFloat.pi/3, 0, 1, 0)
self.imageView02.layer.anchorPoint = CGPoint(x: 1, y: 1) //改变锚点
self.imageView02.layer.transform = trans2

var trans3 = CATransform3DIdentity
trans3.m34 = 1 / 200
trans3 = CATransform3DRotate(trans3, CGFloat.pi/3, 0, 1, 0)
self.imageView03.layer.anchorPoint = CGPoint(x: 0, y: 0) //改变锚点
self.imageView03.layer.transform = trans3
可以看出,第二、第三个imageView的灭点还是在变换锚点之前的位置

sublayerTransform属性:
CALayer有一个属性叫做sublayerTransform,它也是CATransform3D类型,但和对一个图层的变换不同,
它影响到所有的子图层。这意味着可以一次性对包含这些图层的容器做变换,于是所有的子图层都自动继承了这个变换方法
相较而言,通过在一个地方设置透视变换会更方便,同时它会带来一个更显著的优势:
灭点被设置在容器图层的中点,从而不需要再对子视图分别设置了。
这意味着你可以随意使用position和frame来放置子图层了,而不需要把他们放置到屏幕中点,
然后为了保证这个统一的灭点而做平移变换。

将上面的代码修改一下并应用sublayerTransform属性:

//对公共的父视图应用sublayerTransform属性
var subTransform = CATransform3DIdentity
subTransform.m34 = 1 / 200
self.view.layer.sublayerTransform = subTransform
        
var trans1 = CATransform3DIdentity
//trans1.m34 = 1 / 200  //应用了subTransform属性后,就不需要设置子视图的m34了
trans1 = CATransform3DRotate(trans1, CGFloat.pi/3, 0, 1, 0)
self.imageView01.layer.transform = trans1

var trans2 = CATransform3DIdentity
//trans2.m34 = 1 / 200 //应用了subTransform属性后,就不需要设置子视图的m34了
trans2 = CATransform3DRotate(trans2, CGFloat.pi/3, 0, 1, 0)
self.imageView02.layer.anchorPoint = CGPoint(x: 1, y: 1) //改变锚点
self.imageView02.layer.transform = trans2

var trans3 = CATransform3DIdentity
//trans3.m34 = 1 / 200 //应用了subTransform属性后,就不需要设置子视图的m34了
trans3 = CATransform3DRotate(trans3, CGFloat.pi/3, 0, 1, 0)
self.imageView03.layer.anchorPoint = CGPoint(x: 0, y: 0) //改变锚点
self.imageView03.layer.transform = trans3

参考:
三维图形旋转矩阵基本概念及推导:
https://www.cnblogs.com/TianFang/p/3920734.html
https://blog.csdn.net/csxiaoshui/article/details/65446125

矩阵的基本运算:
https://blog.csdn.net/darkrabbit/article/details/80025935

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,293评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,604评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,958评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,729评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,719评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,630评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,000评论 3 397
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,665评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,909评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,646评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,726评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,400评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,986评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,959评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,197评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,996评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,481评论 2 342