Неудачная попытка использовать перспективные преобразования в базовой анимации
-
20-09-2019 - |
Вопрос
Я пытаюсь создать простое приложение для 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
равно нулю для обоих слоев, то изображение и отражение выглядят идеально синхронизированными.Поэтому я не думаю, что проблема связана с проблемами синхронизации времени.
Я подозреваю, что в моих перспективных преобразованиях чего-то не хватает, но я не знаю, как определить, что не так.Я думаю, что я, по сути, делаю то же преобразование, которое описано в этот связанный вопрос о переполнении стека.
Может ли кто-нибудь помочь?
Решение
Думаю, возможно, я нашел ответ на свой вопрос.В исходном коде я создал отражение, поместив его под изображение и применив переворот и перспективу.Кажется, правильный способ сделать это — поместить отражение в то же положение, что и изображение, применить преобразования перемещения и вращения, чтобы переместить его в нужное место, а затем применить преобразование перспективы.
При анимации вращения необходимо вращать отражение в направлении, противоположном анимации картинки.
Итак, вот код, который работает:
- (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
Другие советы
Вы можете использовать CAAnimationGroup
объект для запуска двух анимаций в одном и том же временном пространстве.Это должно решить любые проблемы с синхронизацией.
РЕДАКТИРОВАТЬ: удалил глупый код.