0
点赞
收藏
分享

微信扫一扫

iOS 高级核心动画 day04 - 仿射变换、CGAffineTransform、CATransform3D、立体筛子

微笑沉默 2021-09-25 阅读 75

一、仿射变换

1. 几何学的英文是什么?几何学主要研究哪方面的知识?

  • Geometry简称几何,是数学的一部分。
  • 主要研究:形状、大小、图形的相对位置等空间区域关系以及空间形式的度量。

2. 向量的英文名字是什么?向量的定义是什么?

  • 向量(Vector,矢量,欧几里得向量) 是数学、物理学和工程科学等多个自然科学中的基本概念。
  • 向量的定义: 指一个同时具有大小方向,且满足平行四边形法则的几何对象。

3. 什么是线性变换(线性变换从几何直观有三个要点)?

  • 变换前是直线的,变换后依然是直线。
  • 直线比例保持不变。
  • 变换前是原点的,变换后依然是原点。

4. AffineTransform 译为 仿射变换,请说一下什么意思?

  • 简单来说,仿射变换 = 线性变换 + 平移
  • 对比线性变换,从几何直观角度,少了 原点保持不变 这一条

5. 矩阵、矩阵乘法最初的目的是为了什么?

  • 解线性方程组。

二、CGAffineTransform

1. 理解:CGAffineTransform中的“仿射”?下图哪些符合放射变换

  • CGAffineTransform中的“仿射”的意思是无论变换矩阵用什么值,图层中平行的两条线在变换之后任然保持平行


2. CGAffineTransform 是做什么的?

  • UIViewtransform 属性是一个 CGAffineTransform 类型。
  • CALayeraffineTransform 属性是一个 CGAffineTransform 类型。
  • CGAffineTransform:用于在二维空间做旋转、缩放和平移。

3. CGAffineTransform 效果可以叠加吗?

  • 可以单独也可以叠加,关键代码如下:

//rotate the layer 45 degrees
CGAffineTransform transform = CGAffineTransformMakeRotation(M_PI_4);
self.layerView.layer.affineTransform = transform;

//create a new transform
CGAffineTransform transform = CGAffineTransformIdentity; //scale by 50%
transform = CGAffineTransformScale(transform, 0.5, 0.5); //rotate by 30 degrees
transform = CGAffineTransformRotate(transform, M_PI / 180.0 * 30.0); //translate by 200 points
transform = CGAffineTransformTranslate(transform, 200, 0);
//apply transform to layer
self.layerView.layer.affineTransform = transform;

三、3D 变换(CATransform3D)

1. 理解 iOS 中的三维坐标体系

  • 理解:由上图所见,绕Z轴的旋转等同于之前二维空间的仿射旋转,但是绕X轴和Y轴的旋转就突破了屏幕的二维空间,并且在用户视角看来发生了倾斜。

2. CATransform3D 和 CGAffineTransform 的区别?

  • CGAffineTransform:属于 Core Graphics 框架,Core Graphics 是一个严格意义上的 2D 绘图 API,并且 CGAffineTransform 仅仅对 2D 变换有效。
  • CATransform3D:属于 Core Animation框架,可以让图层在 3D 空间内移动或旋转。

3. 借助 CATransform3D 实现视图的图层绕 Y 轴做一个 45 度角的旋转?

//rotate the layer 45 degrees along the Y axis
CATransform3D transform = CATransform3DMakeRotation(M_PI_4, 0, 1, 0);
self.imageView.layer.transform = transform;

  • 看起来图层并没有旋转,而是仅仅在水平方向上的一个压缩,是哪里出了问题呢?
  • 其实完全没错,视图看起来更窄实际上是因为我们在用一个斜向的视角看它,而不是透视。

4. 为了解决上述问题,做到近大远小的视角效果,要怎么办?

  • 使用 CATransform3Dm34 元素,来做透视效果
  • m34 的默认值是 0,我们可以通过设置 m34 为 -1.0 / d 来应用透视效果,d 代表了想象中相机和屏幕之间的距离,以像素为单位,通常 500~1000 即可。
    //create a new transform
    CATransform3D transform = CATransform3DIdentity;
    //apply perspective
    transform.m34 = - 1.0 / 500.0;
    //rotate by 45 degrees along the Y axis
    transform = CATransform3DRotate(transform, M_PI_4, 0, 1, 0);
    //apply to layer
    self.layerView.layer.transform = transform;

5. 什么是灭点?如果有多个视图要做透视变化(近大远小),如果保证灭点一致?

  • Core Animation定义了这个灭点位于变换图层的 anchorPoint。
  • 使用一个容器包裹所有需要透视的视图,然后用 sublayerTransform 在父视图上做透视变换,就能保证所有子视图的灭点一致。
@interface ViewController ()

@property (nonatomic, weak) IBOutlet UIView *containerView;
@property (nonatomic, weak) IBOutlet UIView *layerView1;
@property (nonatomic, weak) IBOutlet UIView *layerView2;

@end

@implementation ViewController

- (void)viewDidLoad
{
    [super viewDidLoad];
    //apply perspective transform to container
    CATransform3D perspective = CATransform3DIdentity;
    perspective.m34 = - 1.0 / 500.0;
    self.containerView.layer.sublayerTransform = perspective;
    //rotate layerView1 by 45 degrees along the Y axis
    CATransform3D transform1 = CATransform3DMakeRotation(M_PI_4, 0, 1, 0);
    self.layerView1.layer.transform = transform1;
    //rotate layerView2 by 45 degrees along the Y axis
    CATransform3D transform2 = CATransform3DMakeRotation(-M_PI_4, 0, 1, 0);
    self.layerView2.layer.transform = transform2;
}

6. 我们既然可以在 3D 场景下旋转图层,那么也可以从背面去观察它,请问默认情况下视图的背面是什么?

  • 所有图层都是双面绘制的,反面显示的是正面的一个镜像图片。
  • 这并不是一个很好的特性,因为我们基本上永远都看不见这些图层的背面,那为什么要浪费 GPU 来绘制它们呢?
  • CALayer 有一个叫做 doubleSided 的属性来控制图层的背面是否需要绘制。当设置为 NO,那么当图层正面从相机视角消失的时候,它将不会被绘制。

7. 请问如下代码,能将 innerView 恢复正常状态吗?

@interface ViewController ()

@property (nonatomic, weak) IBOutlet UIView *outerView;
@property (nonatomic, weak) IBOutlet UIView *innerView;

@end

@implementation ViewController

- (void)viewDidLoad
{
 [super viewDidLoad];
 //rotate the outer layer 45 degrees
 CATransform3D outer = CATransform3DMakeRotation(M_PI_4, 0, 0, 1);
 self.outerView.layer.transform = outer;
 //rotate the inner layer -45 degrees
 CATransform3D inner = CATransform3DMakeRotation(-M_PI_4, 0, 0, 1);
 self.innerView.layer.transform = inner;
}

@end
  • innerView 可以恢复正常状态

8. 如果将上题的绕 Z 轴旋转,换成绕 Y 轴旋转,innerView 能恢复正常状态吗?

- (void)viewDidLoad
{
 [super viewDidLoad];
 //rotate the outer layer 45 degrees
 CATransform3D outer = CATransform3DIdentity;
 outer.m34 = -1.0 / 500.0;
 outer = CATransform3DRotate(outer, M_PI_4, 0, 1, 0);
 self.outerView.layer.transform = outer;
 //rotate the inner layer -45 degrees
 CATransform3D inner = CATransform3DIdentity;
 inner.m34 = -1.0 / 500.0;
 inner = CATransform3DRotate(inner, -M_PI_4, 0, 1, 0);
 self.innerView.layer.transform = inner;
}
  • innerView 无法恢复正常状态

  • 如上图所示内部的图层仍然向左侧旋转,并且发送了扭曲,但按道理说它应该保持正面超时,并且显示正常的方块。
  • 对于这个结果的成因,我暂时没看懂,后续再回来看吧。参考链接:https://www.kancloud.cn/manual/ios/97786

四、固体对象

  • 像如下创建出筛子 6 个面,然后再创建出一个容器 view


#import "SevenViewController.h"
#import <QuartzCore/QuartzCore.h>
#import <GLKit/GLKit.h>

#define LIGHT_DIRECTION 0, 1, -0.5
#define AMBIENT_LIGHT 0.5



@interface SevenViewController ()
@property (weak, nonatomic) IBOutlet UIView *containerView;
@property (strong, nonatomic) IBOutletCollection(UIView) NSArray *faces;

@end

@implementation SevenViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view from its nib.
    
    self.containerView.backgroundColor = [UIColor clearColor];
    
    for (UIView *view in self.faces) {
        view.backgroundColor = [UIColor whiteColor];
    }
    
    [self createDice];
}


- (void)applyLightingToFace:(CALayer *)face
{
    //add lighting layer
    CALayer *layer = [CALayer layer];
    layer.frame = face.bounds;
    [face addSublayer:layer];
    //convert the face transform to matrix
    //(GLKMatrix4 has the same structure as CATransform3D)
    //译者注:GLKMatrix4和CATransform3D内存结构一致,但坐标类型有长度区别,所以理论上应该做一次float到CGFloat的转换,感谢[@zihuyishi](https://github.com/zihuyishi)同学~
    CATransform3D transform = face.transform;
    GLKMatrix4 matrix4 = [self matrixFrom3DTransformation:transform];
    GLKMatrix3 matrix3 = GLKMatrix4GetMatrix3(matrix4);
    //get face normal
    GLKVector3 normal = GLKVector3Make(0, 0, 1);
    normal = GLKMatrix3MultiplyVector3(matrix3, normal);
    normal = GLKVector3Normalize(normal);
    //get dot product with light direction
    GLKVector3 light = GLKVector3Normalize(GLKVector3Make(LIGHT_DIRECTION));
    float dotProduct = GLKVector3DotProduct(light, normal);
    //set lighting layer opacity
    CGFloat shadow = 1 + dotProduct - AMBIENT_LIGHT;
    UIColor *color = [UIColor colorWithWhite:0 alpha:shadow];
    layer.backgroundColor = color.CGColor;
}

- (GLKMatrix4)matrixFrom3DTransformation:(CATransform3D)transform {
    GLKMatrix4 matrix = GLKMatrix4Make(transform.m11, transform.m12, transform.m13, transform.m14,
                                       transform.m21, transform.m22, transform.m23, transform.m24,
                                       transform.m31, transform.m32, transform.m33, transform.m34,
                                       transform.m41, transform.m42, transform.m43, transform.m44);
    
    return matrix;
}

- (void)addFace:(NSInteger)index withTransform:(CATransform3D)transform {
    // get the face view and add it to the container
    UIView *face = self.faces[index];
    [self.containerView addSubview:face];
    //center the face view within the container
    CGSize containerSize = self.containerView.bounds.size;
    face.center = CGPointMake(containerSize.width / 2.0, containerSize.height / 2.0);
    //apply the transform
    face.layer.transform = transform;
    //apply lighting
    [self applyLightingToFace:face.layer];
}

- (void)createDice {
    
    CGFloat containerHalfHeight = self.containerView.bounds.size.height / 2.0;
    
    //add cube face 1
    CATransform3D transform = CATransform3DMakeTranslation(0, 0, containerHalfHeight);
    [self addFace:0 withTransform:transform];
    //add cube face 2
    transform = CATransform3DMakeTranslation(containerHalfHeight, 0, 0);
    transform = CATransform3DRotate(transform, M_PI_2, 0, 1, 0);
    [self addFace:1 withTransform:transform];
    //add cube face 3
    transform = CATransform3DMakeTranslation(0, -containerHalfHeight, 0);
    transform = CATransform3DRotate(transform, M_PI_2, 1, 0, 0);
    [self addFace:2 withTransform:transform];
    //add cube face 4
    transform = CATransform3DMakeTranslation(0, containerHalfHeight, 0);
    transform = CATransform3DRotate(transform, -M_PI_2, 1, 0, 0);
    [self addFace:3 withTransform:transform];
    //add cube face 5
    transform = CATransform3DMakeTranslation(-containerHalfHeight, 0, 0);
    transform = CATransform3DRotate(transform, -M_PI_2, 0, 1, 0);
    [self addFace:4 withTransform:transform];
    //add cube face 6
    transform = CATransform3DMakeTranslation(0, 0, -containerHalfHeight);
    transform = CATransform3DRotate(transform, M_PI, 0, 1, 0);
    [self addFace:5 withTransform:transform];
    
    
    CATransform3D perspective = CATransform3DIdentity;
    perspective.m34 = -1.0 / 500.0;
    perspective = CATransform3DRotate(perspective, -M_PI_4, 1, 0, 0);
    perspective = CATransform3DRotate(perspective, -M_PI_4, 0, 1, 0);
    self.containerView.layer.sublayerTransform = perspective;
    
    // 把视图1、2、3提到最前面,响应点击事件
    [self.containerView bringSubviewToFront:self.faces[0]];
    [self.containerView bringSubviewToFront:self.faces[1]];
    [self.containerView bringSubviewToFront:self.faces[2]];
}

@end

举报

相关推荐

0 条评论