Неудачная попытка использовать преобразования перспективы в основной анимации

Я пытаюсь создать простое приложение для iPhone, которое отображает изображение с отражением под ним, и я хочу повернуть изображение вокруг оси X, используя Core Animation.

Я начал с создания нового приложения для iPhone, используя шаблон «Приложение на основе просмотра». Я добавил файл изображения «Picture.jpg», а затем добавил его в контроллер представления:

- (void)awakeFromNib {
    CGFloat zDistance = 1500.0f;

    // Create perspective transformation
    CATransform3D transform = CATransform3DIdentity;
    transform.m34 = 1.0f / -zDistance;

    // Create perspective transform for reflected layer
    CATransform3D reflectedTransform = CATransform3DMakeRotation(M_PI, 1.0f, 0.0f, 0.0f);
    reflectedTransform.m34 = 1.0f / -zDistance;

    // Create spinning picture
    CALayer *spinningLayer = [CALayer layer];
    spinningLayer.frame = CGRectMake(60.0f, 60.0f, 200.0f, 300.0f);
    spinningLayer.contents = (id)[UIImage imageNamed:@"Picture.jpg"].CGImage;
    spinningLayer.transform = transform;    

    // Create reflection of spinning picture
    CALayer *reflectionLayer = [CALayer layer];
    reflectionLayer.frame = CGRectMake(60.0f, 360.0f, 200.0f, 300.0f);
    reflectionLayer.contents = (id)[UIImage imageNamed:@"Picture.jpg"].CGImage;
    reflectionLayer.opacity = 0.4f;
    reflectionLayer.transform = reflectedTransform;

    // Add layers to the root layer
    [self.view.layer addSublayer:spinningLayer];
    [self.view.layer insertSublayer:reflectionLayer below:spinningLayer];

    // Spin the layers
    CABasicAnimation *anim = [CABasicAnimation animationWithKeyPath:@"transform.rotation.y"];
    anim.fromValue = [NSNumber numberWithFloat:0.0f];
    anim.toValue = [NSNumber numberWithFloat:(2 * M_PI)];
    anim.duration = 3.0f;
    anim.repeatCount = 1e100f;
    [spinningLayer addAnimation:anim forKey:@"anim"];
    [reflectionLayer addAnimation:anim forKey:@"anim"];
}

Это почти работает. Проблема в том, что вращение основного изображения и отражения не идеально синхронизировано. Это очень близко к идеальному, но края нижней части изображения и верхней части отражения находятся на расстоянии нескольких пикселей. Кажется, что они различаются на несколько градусов.

В левой части экрана отражение кажется «впереди» изображения, а в правой части - отражение позади изображения. Другими словами, похоже, что нижние углы верхнего изображения подталкиваются к экрану, а верхние углы отражения притягиваются к зрителю. Это верно как при взгляде на переднюю, так и на заднюю часть вращающихся изображений.

Если я увеличу zDistance, эффект будет менее заметным. Если я полностью устраню перспективные преобразования, оставив transform.m34 равным нулю для обоих слоев, тогда изображение и отражение будут выглядеть идеально синхронизированными. Поэтому я не думаю, что проблема связана с проблемами синхронизации времени.

Я подозреваю, что в моих перспективных трансформациях чего-то не хватает, но я не уверен, как определить, что не так. Я думаю, что в основном выполняю то же преобразование, которое описано в соответствующем вопросе о переполнении стека.

Может кто поможет?


person Kristopher Johnson    schedule 14.02.2010    source источник


Ответы (2)


Думаю, я нашел ответ на свой вопрос. В моем исходном коде я создал отражение, поместив его под изображением и применив флип и перспективу. Кажется, что правильный способ сделать это - разместить отражение в том же месте, что и изображение, применить преобразования сдвига и поворота, чтобы переместить его в нужное место, а затем применить преобразование перспективы.

При анимации вращения необходимо вращать отражение в направлении, противоположном анимации изображения.

Итак, вот код, который работает:

- (void)awakeFromNib {
    CGFloat zDistance = 1500.0f;

    // Create perspective transformation
    CATransform3D transform = CATransform3DIdentity;
    transform.m34 = 1.0f / -zDistance;

    // Create perspective transform for reflected layer
    CATransform3D reflectedTransform = CATransform3DMakeTranslation(0.0f, 300.0f, 0.0f);
    reflectedTransform = CATransform3DRotate(reflectedTransform, M_PI, 1.0f, 0.0f, 0.0f);
    reflectedTransform.m34 = 1.0f / -zDistance;

    // Create spinning picture
    CALayer *spinningLayer = [CALayer layer];
    spinningLayer.frame = CGRectMake(60.0f, 60.0f, 200.0f, 300.0f);
    spinningLayer.contents = (id)[UIImage imageNamed:@"Picture.jpg"].CGImage;
    spinningLayer.transform = transform;    

    // Create reflection of spinning picture
    CALayer *reflectionLayer = [CALayer layer];
    reflectionLayer.frame = CGRectMake(60.0f, 60.0f, 200.0f, 300.0f);
    reflectionLayer.contents = (id)[UIImage imageNamed:@"Picture.jpg"].CGImage;
    reflectionLayer.opacity = 0.4f;
    reflectionLayer.transform = reflectedTransform;

    // Add layers to the root layer
    [self.view.layer addSublayer:spinningLayer];
    [self.view.layer insertSublayer:reflectionLayer below:spinningLayer];

    // Spin the layers
    CABasicAnimation *anim = [CABasicAnimation animationWithKeyPath:@"transform.rotation.y"];
    anim.fromValue = [NSNumber numberWithFloat:0.0f];
    anim.toValue = [NSNumber numberWithFloat:(2 * M_PI)];
    anim.duration = 3.0f;
    anim.repeatCount = 1e100f;
    [spinningLayer addAnimation:anim forKey:@"anim"];

    CABasicAnimation *reflectAnim = [CABasicAnimation animationWithKeyPath:@"transform.rotation.y"];
    reflectAnim.fromValue = [NSNumber numberWithFloat:0.0f];
    reflectAnim.toValue = [NSNumber numberWithFloat:(-2 * M_PI)];
    reflectAnim.duration = 3.0f;
    reflectAnim.repeatCount = 1e100f;
    [reflectionLayer addAnimation:reflectAnim forKey:@"reflectAnim"];
}

Я не совсем понимаю, почему это работает. Мне кажется интуитивно понятным, что размещение отражения в том же месте, что и оригинал, а затем его преобразование каким-то образом помещает два изображения в одно и то же «пространство преобразования», но я был бы признателен, если бы кто-нибудь мог объяснить математику, лежащую в основе этого.

Полный проект доступен на GitHub: https://github.com/kristopherjohnson/perspectivetest.

person Kristopher Johnson    schedule 14.02.2010

Вы можете использовать _ 1_ для запуска двух анимаций в одном временном пространстве. Это должно решить любые проблемы с синхронизацией.

РЕДАКТИРОВАТЬ: удален глупый код.

person e.James    schedule 14.02.2010
comment
Я не думаю, что это проблема синхронизации времени. Кажется, что анимации выполняются одновременно, но стык между изображениями искажен. - person Kristopher Johnson; 15.02.2010
comment
Хм. Может быть, одна из анимаций немного опережает другую? Если бы отражение было впереди на один кадр, вы бы заметили небольшую разницу в швах. Однако это не моя область знаний, поэтому я могу ошибаться. - person e.James; 15.02.2010
comment
Отражение выглядит так, как будто оно впереди с одной стороны, но позади с другой стороны. - person Kristopher Johnson; 15.02.2010
comment
Предоставленный вами код вращает корневой слой представления, а не отдельные подслои. Итак, перспективные преобразования ничего не делают, потому что слои не перемещаются относительно корневого слоя. - person Kristopher Johnson; 15.02.2010
comment
Я изменил ключевые пути, чтобы они указывали на отдельные подслои, но теперь, когда я думаю об этом, я не думаю, что они будут доступны по этим именам. Хм. - person e.James; 15.02.2010