Как повернуть плоский объект вокруг его центра в перспективе?

Кажется, что то, что я пытаюсь сделать, должно быть достаточно простым: я создал двухмерный вид сверху вниз на старой грампластинке. Я хочу повернуть его (положить обратно) по оси X, а затем повернуть вокруг оси Z.

Я прочитал здесь все вопросы, содержащие CATransform3D в теле, я прочитал " Матрицы могут быть вашими друзьями », а также книгу Билла Дадни« Базовая анимация для Mac OS X и iPhone », я думаю, " Трехмерное вращение без трекбола " имеет правильный код, но поскольку он позволяет пользователю регулировать все три оси, Мне трудно сжать его код до того, что я воспринимаю как одно измерение (повернутая ось z).

Вот изображение, с которым я тестирую, но не то, чтобы детали важны для проблемы:

Золотая запись

Я вывожу это на экран обычным способом: (в подклассе 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? Я видел вопрос / ответ, в котором говорилось об объединении преобразований, но это было преобразование масштаба, за которым следовало преобразование поворота. Я напортачил с преобразованием преобразования, но не делаю того, что, по моему мнению, должен получить (т.е. передача результата одного преобразования в аргумент преобразования следующего, казалось, просто выполняла одно полностью, а затем оживляла другое).


person tobinjim    schedule 24.02.2012    source источник
comment
Я просто понимаю, что мое базовое понимание CABasicAnimation было немного ошибочным, хотя я не думаю, что это влияет на результат, которого я пытаюсь достичь. Преобразование, созданное вызовом CATransform3DRotate, является конечным результатом в конце CABasicAnimation. Я думал об этом как о функции, в которую передаются временные отрезки и которые используются для вычисления вращения временных отрезков. Фактически, CABasicAnimation интерполирует промежуточные шаги, необходимые для достижения конечного результата. Это означает, что я не смогу сделать это за одну трансформацию / анимацию, как я боюсь.   -  person tobinjim    schedule 24.02.2012


Ответы (2)


Это проще всего, если вы используете CATransformLayer в качестве родителя слоя представления изображения. Таким образом, вам понадобится собственный подкласс представления, который использует CATransformLayer в качестве своего слоя. Это тривиально:

@interface TransformView : UIView

@end

@implementation TransformView

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

@end

Поместите TransformView в перо и добавьте UIImageView как часть TransformView. Подключите эти представления к выходам в вашем контроллере представления, которые называются transformView и discView.

В вашем контроллере представления установите преобразование 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;
}

Добавьте анимацию для ключевого пути от transform.rotation.z к discView в viewWillAppear: и удалите его в 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:

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)

Скопируйте изображение из вопроса в папку ресурсов игровой площадки и назовите его 3DgoldRecord.png. Результат:

детская площадка

person rob mayoff    schedule 24.02.2012
comment
Роб, спасибо тебе большое. Я лег спать, но, как это часто бывает, мозг не отключался. Я вернулся к своему компьютеру, чтобы попробовать вариант предложения Томмили, но увидел вашу крутящуюся запись .gif! Ваш ответ очень ясен. Я раньше не видел описания того, как сделать слой представления CATransformLayer; возможно, из-за поиска CATransform3D. Тем не менее, в моей книге вы заслужили похвалу за то, что хотя бы мне показали, как ее правильно использовать! - person tobinjim; 24.02.2012
comment
Пощекотал! :-) Мне пришлось установить zPosition, как упоминалось в другом месте, чтобы диск не исчезал за моим фоном, когда он вращался в пространство -z, и увеличивал делитель перспективы, но теперь он выглядит великолепно. Спасибо!!! - person tobinjim; 24.02.2012
comment
@Rob вы можете преобразовать это в OpenGL? На самом деле мне нужно сделать снимок экрана слоя CATranform3D. Но он не может работать без opengl. пожалуйста, предложите. - person Muzammil; 19.04.2012
comment
Меня не интересует переписывание этого с помощью OpenGL. - person rob mayoff; 19.04.2012
comment
ссылка на тестовый проект исчезнет. не могли бы вы прислать мне демо? Спасибо большое - person ShineWang; 07.01.2016
comment
@ShineWang Я обновил свой ответ, добавив рабочую площадку Swift. - person rob mayoff; 07.01.2016
comment
скажи @robmayoff. Мне было интересно, как на самом деле можно перемещать ось вращения в CATransform3DRotate ... stackoverflow.com/questions/37364831. Возможно, вы знаете! - person Fattie; 21.05.2016

Вы можете обернуть recordLayer суперуровнем. Примените вращение по оси X к суперслою и добавьте анимацию вращения по оси Z в recordLayer.

person tommyli    schedule 24.02.2012
comment
Спасибо, tommyli, и если бы то, что я реализовал, сработало так, как я думаю, было бы близко. Я заменил вашу рекомендацию оси Z на ось Y, так как суперслой не был преобразован. Я ожидал эффекта вращающегося никеля, когда приподнятая спина будет вращаться вокруг и быть впереди (а затем продолжаться вокруг спины). Но все, что я действительно вижу, это вращение вокруг оси z. - person tobinjim; 24.02.2012