iOS开发 图形变换-做一个正方体

    现在你懂得了在3D空间的一些图层布局的基础,我们来试着创建一个固态的3D对象(实际上是一个技术上所谓的空洞对象,但它以固态呈现)。我们用六个独立的视图来构建一个立方体的各个面。

在这个例子中,我们用Interface Builder来构建立方体的面,我们当然可以用代码来写,但是用Interface Builder的好处是可以方便的在每一个面上添加子视图。记住这些面仅仅是包含视图和控件的普通的用户界面元素,它们完全是我们界面交互的部分,并且当把它折成一个立方体之后也不会改变这个性质。

171348_ujzY_2430867.png

这些面视图并没有放置在主视图当中,而是松散地排列在根nib文件里面。我们并不关心在这个容器中如何摆放它们的位置,因为后续将会用图层的transform对它们进行重新布局,并且用Interface Builder在容器视图之外摆放他们可以让我们容易看清楚它们的内容,如果把它们一个叠着一个都塞进主视图,将会变得很难看。

我们把一个有颜色的UILabel放置在视图内部,是为了清楚的辨别它们之间的关系,并且UILabel被放置在第视图里面,后面会做简单的解释。

//
//  Transform3DViewController.m
//  iOSanimation
//
//  Created by biyabi on 15/10/25.
//  Copyright © 2015年 caijunrong. All rights reserved.
//

#import "Transform3DViewController.h"

#import <GLKit/GLKit.h>

@interface Transform3DViewController ()

@property (nonatomic, weak) IBOutlet UIView *contianVIew;

@property (nonatomic, strong) IBOutletCollection(UIView) NSArray *faces;

@end

@implementation Transform3DViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    //    [self.containerView setFrame:CGRectMake(0, 200, [UIScreen mainScreen].bounds.size.width, [UIScreen mainScreen].bounds.size.width)];
    //    [self.containerView setBackgroundColor:[UIColor groupTableViewBackgroundColor]];
    //
    //    [self.view addSubview:self.containerView];
    
    NSLog(@"faces:%@",self.faces);
    
    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.view.layer.sublayerTransform = perspective;
    
    //加入第一个
    CATransform3D transform = CATransform3DMakeTranslation(0, 0, 100);
    [self addFace:0 withTransform:transform];
    
    //加入第二个
    CATransform3D transform2 = CATransform3DMakeTranslation(100, 0, 0);
    transform2 = CATransform3DRotate(transform2, M_PI_2, 0, 1, 0);
    [self addFace:1 withTransform:transform2];
    
    //加入第三个
    CATransform3D transform3 = CATransform3DMakeTranslation(0, -100, 0);
    transform3 = CATransform3DRotate(transform3, M_PI_2, 1, 0, 0);
    [self addFace:2 withTransform:transform3];
    
    //加入第四个
    CATransform3D transform4 = CATransform3DMakeTranslation(0, 100, 0);
    transform4 = CATransform3DRotate(transform4, -M_PI_2, 1, 0, 0);
    [self addFace:3 withTransform:transform4];
    
    //加入第五个
    CATransform3D transform5 = CATransform3DMakeTranslation(-100, 0, 0);
    transform5 = CATransform3DRotate(transform5, -M_PI_2, 0, 1, 0);
    [self addFace:4 withTransform:transform5];
    
    //加入第六个
    CATransform3D transform6 = CATransform3DMakeTranslation(0, 0, -100);
    transform6 = CATransform3DRotate(transform6, -M_PI_2, 0, 0, 1);
    [self addFace:5 withTransform:transform6];
}

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

//光源的位置
#define LIGHT_DIRECTION 0, 1, -0.5

//光源的强弱 越小越弱,看起来越黑
#define AMBIENT_LIGHT 0.5

//增加光影效果
- (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的转换
    CATransform3D transform = face.transform;
    GLKMatrix4 matrix4 = *(GLKMatrix4 *)&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;
}

- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}

@end

    

    173152_QWUf_2430867.png

    光亮和阴影

    现在它看起来更像是一个立方体没错了,但是对每个面之间的连接还是很难分辨。Core Animation可以用3D显示图层,但是它对光线并没有概念。如果想让立方体看起来更加真实,需要自己做一个阴影效果。你可以通过改变每个面的背景颜色或者直接用带光亮效果的图片来调整。

    如果需要动态地创建光线效果,你可以根据每个视图的方向应用不同的alpha值做出半透明的阴影图层,但为了计算阴影图层的不透明度,你需要得到每个面的正太向量(垂直于表面的向量),然后根据一个想象的光源计算出两个向量叉乘结果。叉乘代表了光源和图层之间的角度,从而决定了它有多大程度上的光亮。

    实现了这样一个结果,我们用GLKit框架来做向量的计算(你需要引入GLKit库来运行代码),每个面的CATransform3D都被转换成GLKMatrix4,然后通过GLKMatrix4GetMatrix3函数得出一个3×3的旋转矩阵。这个旋转矩阵指定了图层的方向,然后可以用它来得到正太向量的值。

结果如图5.22所示,试着调整LIGHT_DIRECTION和AMBIENT_LIGHT的值来切换光线效果


    另外说明:即使你在第三个C面放上一个button,点击它是没有反应的,为什么呢?

    

    这并不是因为iOS在3D场景下正确地处理响应事件,实际上是可以做到的。问题在于视图顺序。在第三章中我们简要提到过,点击事件的处理由视图在父视图中的顺序决定的,并不是3D空间中的Z轴顺序。当给立方体添加视图的时候,我们实际上是按照一个顺序添加,所以按照视图/图层顺序来说,4,5,6在3的前面。

    即使我们看不见4,5,6的表面(因为被1,2,3遮住了),iOS在事件响应上仍然保持之前的顺序。当试图点击表面3上的按钮,表面4,5,6截断了点击事件(取决于点击的位置),这就和普通的2D布局在按钮上覆盖物体一样。

    你也许认为把doubleSided设置成NO可以解决这个问题,因为它不再渲染视图后面的内容,但实际上并不起作用。因为背对相机而隐藏的视图仍然会响应点击事件(这和通过设置hidden属性或者设置alpha为0而隐藏的视图不同,那两种方式将不会响应事件)。所以即使禁止了双面渲染仍然不能解决这个问题(虽然由于性能问题,还是需要把它设置成NO)。

    

    这里有几种正确的方案:把除了表面3的其他视图userInteractionEnabled属性都设置成NO来禁止事件传递。或者简单通过代码把视图3覆盖在视图6上。无论怎样都可以点击按钮了。(解决方案)


转载于:https://my.oschina.net/caijunrong/blog/535509

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值