如何在透视视图中围绕平面物体的中心旋转?

31

我想做的事情似乎很简单:我创建了一个老唱片的二维俯视图。我想绕其X轴旋转(使其倾斜)然后再绕其Z轴旋转。

我读过这里所有带有CATransform3D的问题,以及Steve Baker的"Matrices can be your friends"文章和Bill Dudney的书《Core Animation for Mac OS X and the iPhone》。我认为Brad Larson的"3-D Rotation without a trackball"代码是正确的,但由于他允许用户调整所有三个轴,所以我很难将他的代码缩小到我认为只有一个维度(旋转的z轴)。

这是我正在测试的图像,不过细节对问题并不重要:

Gold Record

我按照通常的方式将其显示在屏幕上:(在UIView的子类中)

- (void)awakeFromNib
{
    UIImage *recordImage = [UIImage imageNamed:@"3DgoldRecord"];
    if (recordImage) {
        recordLayer = [CALayer layer];
        [recordLayer setFrame:CGRectMake(0.0, 0.0, 1024, 1024)];
        [recordLayer setContents:(id)[[UIImage imageNamed:@"3DgoldRecord"] CGImage]];
        [self.layer addSublayer:recordLayer];
    }
}

这是第一个测试,只是将其显示在屏幕上 ;-)

然后,为了“放回”它,我应用了一个变换来绕图层的X轴旋转,将此代码插入在设置图层内容之后、添加子层之前:

    CATransform3D myRotationTransform = 
                    CATransform3DRotate(recordLayer.transform,
                                        (M_PI_2 * 0.85), //experiment with flatness
                                        1.0, // rotate only across the x-axis
                                        0.0, // no y-axis transform
                                        0.0); //  no z-axis transform
    recordLayer.transform = myRotationTransform;        

这正如预期的那样有效:记录很好地放回去了。

至于下一步,让记录旋转,我将此动画绑定到touchesEnded事件上,尽管在测试/学习阶段之后,此旋转将不受用户控制:

CATransform3D currentTransform = recordLayer.transform; // to come back to at the end
CATransform3D myRotationTransform = 
                CATransform3DRotate(currentTransform,
                                    1.0, // go all the way around once
                                    (M_PI_2 * 0.85),    // x-axis change
                                    1.00,    // y-axis change ?
                                    0.0);   // z-axis change ?
recordLayer.transform = myRotationTransform;        
CABasicAnimation *myAnimation = [CABasicAnimation animationWithKeyPath:@"transform.rotation"];
myAnimation.duration = 5.0;
myAnimation.fromValue = [NSNumber numberWithFloat:0.0];
myAnimation.toValue = [NSNumber numberWithFloat:M_PI * 2.0];
myAnimation.delegate = self;
[recordLayer addAnimation:myAnimation forKey:@"transform.rotation"];

我相信卡在CATransform3DRotate调用中的是向量(相信我:我一直试图对该向量进行简单更改以观察变化...现在列出的内容只是我尝试的最后一个变化)。据我所知,转换中的x、y和z值本质上是动画期间从fromValue到toValue传递的值的百分比。

如果我正确理解了这一点,是否可以在一个转换中表达这个概念?或者必须为每个有效的动画帧轻微围绕z轴旋转原始的正立图像,然后以x轴旋转的方式展示结果?我看到过一个关于组合变换的问题/答案,但那是一个比例变换后面跟着一个旋转变换。我已经尝试过变换变换,但似乎不是我认为应该得到的结果(即将一个变换的结果传递给下一个变换的参数似乎只是执行了一个完全的变换,然后动画另一个)。


我刚意识到我对CABasicAnimation的基本理解有些错误,尽管我不认为这会影响我想要实现的结果。CATransform3DRotate调用创建的变换是CABasicAnimation结束时的最终结果。我一直认为它是一种函数,可以传递时间片并用于计算时间片旋转。事实上,CABasicAnimation正在插值需要实现最终结果的中间步骤。这意味着我恐怕不能在一个变换/动画中完成这个操作。 - tobinjim
2个回答

63

如果您使用CATransformLayer作为图像视图的图层父级,则最简单的方法是使用它。因此,您需要一个自定义视图子类,该子类使用CATransformLayer作为其图层。这很简单:

@interface TransformView : UIView

@end

@implementation TransformView

+ (Class)layerClass {
    return [CATransformLayer class];
}

@end

在你的nib文件中添加一个TransformView视图,将一个UIImageView作为TransformView的子视图。在你的视图控制器中将这些视图连接到名为transformViewdiscView的outlet。

在你的视图控制器中,设置transformView的变换以应用透视效果(通过设置m34),并在X轴上倾斜:

- (void)viewDidLoad
{
    [super viewDidLoad];
    CATransform3D transform = CATransform3DIdentity;
    transform.m34 = -1 / 500.0;
    transform = CATransform3DRotate(transform, .85 * M_PI_2, 1, 0, 0);
    self.transformView.layer.transform = transform;
}
viewWillAppear:中为关键路径transform.rotation.zdiscView添加动画,在viewDidDisappear:中删除它。
- (void)viewWillAppear:(BOOL)animated {
    [super viewWillAppear:animated];

    CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"transform.rotation.z"];
    animation.fromValue = [NSNumber numberWithFloat:0];
    animation.toValue = [NSNumber numberWithFloat:2 * M_PI];
    animation.duration = 1.0;
    animation.repeatCount = HUGE_VALF;
    animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
    [self.discView.layer addAnimation:animation forKey:@"transform.rotation.z"];
}

- (void)viewDidDisappear:(BOOL)animated {
    [super viewDidDisappear:animated];
    [self.discView.layer removeAllAnimations];
}

结果:

旋转的圆盘

更新

这里有一个Swift playground演示:

import UIKit
import XCPlayground

class TransformView: UIView {
    override class func layerClass() -> AnyClass {
        return CATransformLayer.self
    }
}

let view = UIView(frame: CGRectMake(0, 0, 300, 150))
view.backgroundColor = UIColor.groupTableViewBackgroundColor()
XCPlaygroundPage.currentPage.liveView = view

let transformView = TransformView(frame: view.bounds)
view.addSubview(transformView)

var transform = CATransform3DIdentity
transform.m34 = CGFloat(-1) / transformView.bounds.width
transform = CATransform3DRotate(transform, 0.85 * CGFloat(M_PI_2), 1, 0, 0)
transformView.layer.transform = transform

let image = UIImage(named: "3DgoldRecord")!
let imageView = UIImageView(image: image)
imageView.bounds = CGRectMake(0, 0, 200, 200)
imageView.center = CGPointMake(transformView.bounds.midX, transformView.bounds.midY)
transformView.addSubview(imageView)

let animation = CABasicAnimation(keyPath: "transform.rotation.z")
animation.fromValue = 0
animation.toValue = 2 * M_PI
animation.duration = 1
animation.repeatCount = Float.infinity
animation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionLinear)
imageView.layer.addAnimation(animation, forKey: animation.keyPath)

将问题中的图像复制到playground资源文件夹中,并将其命名为3DgoldRecord.png。结果:

playground result


Rob,非常感谢。我已经上床睡觉了,但大脑总是无法停止。我回到电脑上尝试了Tommyli建议的变化,但看到了你的旋转唱片.gif!你的答案非常清晰。我之前没有看到如何将视图的层设置为CATransformLayer的描述;可能是因为搜索了CATransform3D。尽管如此,在我的书中,你至少展示了如何正确使用它的功劳! - tobinjim
感到非常高兴! :-) 我必须像其他地方提到的那样设置zPosition,这样当它旋转到-z空间时,圆盘就不会消失在我的背景后面,并增加了透视除数,但现在看起来非常棒。谢谢!!! - tobinjim
@Rob,你能把这个转换成OpenGL吗?实际上我需要截取CATranform3D层的屏幕截图。但是如果没有OpenGL它就无法工作。请给予建议。 - Muzammil
23
我不感兴趣用OpenGL重新编写这个。 - rob mayoff
测试项目链接已经消失。你能否发送一个演示给我?非常感谢。 - ShineWang
显示剩余2条评论

0
您可以考虑使用superlayer包装recordLayer。将x轴旋转应用于superlayer,并将z轴旋转动画添加到recordLayer中。

谢谢,tommyli。如果我实现的方式按照我的意图进行,它会很接近。由于超层尚未转换,我用y轴代替了您建议的z轴。我期望看到“旋转镍币”效果,即“凸起”的背面将绕着前面旋转(然后继续绕到背面)。但是我真正看到的只是绕z轴旋转。 - tobinjim

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接