Как я поворачиваю CALayer вокруг диагональной строки?

Я пытаюсь реализовать зеркально отраженную анимацию, которая будет использоваться в настольной игре как приложение для iPhone. Анимация, как предполагается, похожа на игровую часть, которая вращается и изменяется на цвет ее спины (отчасти как часть Reversi). Мне удалось создать анимацию, которая зеркально отражает часть вокруг ее ортогональной оси, но когда я пытаюсь зеркально отразить ее вокруг диагональной оси путем изменения вращения вокруг оси z, действительный образ также повернут (не удивительно). Вместо этого я хотел бы повернуть изображение, "как" вокруг диагональной оси.

Я попытался измениться layer.sublayerTransform но без успеха.

Вот моя текущая реализация. Это работает путем выполнения приема для решения вопроса о получении зеркального изображения в конце анимации. Решение не состоит в том, чтобы на самом деле повернуть слой 180 градусов, вместо этого это поворачивает его 90 градусов, изменения отображают, и затем поворачивает его назад.

Окончательная версия: На основе предложения Lorenzos, чтобы создать дискретную включенную анимацию и вычислить матрицу преобразования для каждого кадра. Эта версия вместо этого пытается оценить количество "руководства" кадров, необходимых на основе размера слоя, и затем использует линейную включенную анимацию. Эта версия вращается с произвольным углом, таким образом, для вращения вокруг диагональной строки используют угол на 45 градусов.

Использование в качестве примера:

[someclass flipLayer:layer image:image angle:M_PI/4]

Реализация:

- (void)animationDidStop:(CAAnimationGroup *)animation
                finished:(BOOL)finished {
  CALayer *layer = [animation valueForKey:@"layer"];

  if([[animation valueForKey:@"name"] isEqual:@"fadeAnimation"]) {
    /* code for another animation */
  } else if([[animation valueForKey:@"name"] isEqual:@"flipAnimation"]) {
    layer.contents = [animation valueForKey:@"image"];
  }

  [layer removeAllAnimations];
}

- (void)flipLayer:(CALayer *)layer
            image:(CGImageRef)image
            angle:(float)angle {
  const float duration = 0.5f;

  CAKeyframeAnimation *rotate = [CAKeyframeAnimation
                                 animationWithKeyPath:@"transform"];
  NSMutableArray *values = [[[NSMutableArray alloc] init] autorelease];
  NSMutableArray *times = [[[NSMutableArray alloc] init] autorelease];
  /* bigger layers need more "guiding" values */
  int frames = MAX(layer.bounds.size.width, layer.bounds.size.height) / 2;
  int i;
  for (i = 0; i < frames; i++) {
    /* create a scale value going from 1.0 to 0.1 to 1.0 */
    float scale = MAX(fabs((float)(frames-i*2)/(frames - 1)), 0.1);

    CGAffineTransform t1, t2, t3;
    t1 = CGAffineTransformMakeRotation(angle);
    t2 = CGAffineTransformScale(t1, scale, 1.0f);
    t3 = CGAffineTransformRotate(t2, -angle);
    CATransform3D trans = CATransform3DMakeAffineTransform(t3);

    [values addObject:[NSValue valueWithCATransform3D:trans]];
    [times addObject:[NSNumber numberWithFloat:(float)i/(frames - 1)]];
  }
  rotate.values = values;
  rotate.keyTimes = times;
  rotate.duration = duration;
  rotate.calculationMode = kCAAnimationLinear;

  CAKeyframeAnimation *replace = [CAKeyframeAnimation
                                  animationWithKeyPath:@"contents"];
  replace.duration = duration / 2;
  replace.beginTime = duration / 2;
  replace.values = [NSArray arrayWithObjects:(id)image, nil];
  replace.keyTimes = [NSArray arrayWithObjects:
                      [NSNumber numberWithDouble:0.0f], nil];
  replace.calculationMode = kCAAnimationDiscrete;

  CAAnimationGroup *group = [CAAnimationGroup animation];
  group.duration = duration;
  group.timingFunction = [CAMediaTimingFunction
                          functionWithName:kCAMediaTimingFunctionLinear];
  group.animations = [NSArray arrayWithObjects:rotate, replace, nil];
  group.delegate = self;
  group.removedOnCompletion = NO;
  group.fillMode = kCAFillModeForwards;
  [group setValue:@"flipAnimation" forKey:@"name"];
  [group setValue:layer forKey:@"layer"];
  [group setValue:(id)image forKey:@"image"];

  [layer addAnimation:group forKey:nil];
}

Исходный код:

+ (void)flipLayer:(CALayer *)layer
          toImage:(CGImageRef)image
        withAngle:(double)angle {
  const float duration = 0.5f;

  CAKeyframeAnimation *diag = [CAKeyframeAnimation
                               animationWithKeyPath:@"transform.rotation.z"];
  diag.duration = duration;
  diag.values = [NSArray arrayWithObjects:
                 [NSNumber numberWithDouble:angle],
                 [NSNumber numberWithDouble:0.0f],
                 nil];
  diag.keyTimes = [NSArray arrayWithObjects:
                   [NSNumber numberWithDouble:0.0f],
                   [NSNumber numberWithDouble:1.0f],
                   nil];
  diag.calculationMode = kCAAnimationDiscrete;

  CAKeyframeAnimation *flip = [CAKeyframeAnimation
                               animationWithKeyPath:@"transform.rotation.y"];
  flip.duration = duration;
  flip.values = [NSArray arrayWithObjects:
                 [NSNumber numberWithDouble:0.0f],
                 [NSNumber numberWithDouble:M_PI / 2],
                 [NSNumber numberWithDouble:0.0f],
                 nil];
  flip.keyTimes = [NSArray arrayWithObjects:
                   [NSNumber numberWithDouble:0.0f],
                   [NSNumber numberWithDouble:0.5f],
                   [NSNumber numberWithDouble:1.0f],
                   nil];
  flip.calculationMode = kCAAnimationLinear;

  CAKeyframeAnimation *replace = [CAKeyframeAnimation
                                  animationWithKeyPath:@"contents"];
  replace.duration = duration / 2;
  replace.beginTime = duration / 2;
  replace.values = [NSArray arrayWithObjects:(id)image, nil];
  replace.keyTimes = [NSArray arrayWithObjects:
                      [NSNumber numberWithDouble:0.0f], nil];
  replace.calculationMode = kCAAnimationDiscrete;

  CAAnimationGroup *group = [CAAnimationGroup animation];
  group.removedOnCompletion = NO;
  group.duration = duration;
  group.timingFunction = [CAMediaTimingFunction
                          functionWithName:kCAMediaTimingFunctionLinear];
  group.animations = [NSArray arrayWithObjects:diag, flip, replace, nil];
  group.fillMode = kCAFillModeForwards;

  [layer addAnimation:group forKey:nil];
}

13
задан Mattias Wadman 21 March 2010 в 18:03
поделиться

2 ответа

вы можете подделать это следующим образом: создайте аффинное преобразование, которое сворачивает слой по его диагонали:

A-----B           B
|     |          /
|     |   ->   A&D
|     |        /
C-----D       C

измените изображение и снова преобразуйте CALayer в другая анимация. Это создаст иллюзию вращения слоя вокруг своей диагонали.

матрица для этого должна быть, если я правильно помню математику:

0.5 0.5 0
0.5 0.5 0
0   0   1

Обновление: хорошо, CA не очень любит использовать вырожденные преобразования, но вы можете аппроксимировать это следующим образом:

CGAffineTransform t1 = CGAffineTransformMakeRotation(M_PI/4.0f);
CGAffineTransform t2 = CGAffineTransformScale(t1, 0.001f, 1.0f);
CGAffineTransform t3 = CGAffineTransformRotate(t2,-M_PI/4.0f);  

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

после дополнительных исследований выяснилось, что нужно анимировать собственный переход с помощью ключевых кадров, чтобы получить максимальный контроль над самим переходом. скажем, вы должны отображать эту анимацию за секунду, вы должны сделать десять матриц, которые будут отображаться на каждой десятой доли секунды без интерполяции, используя kCAAnimationDiscrete; эта матрица может быть сгенерирована с помощью кода ниже:

CGAffineTransform t1 = CGAffineTransformMakeRotation(M_PI/4.0f);
CGAffineTransform t2 = CGAffineTransformScale(t1, animationStepValue, 1.0f);
CGAffineTransform t3 = CGAffineTransformRotate(t2,-M_PI/4.0f);  

где animationStepValue для каждого ключевого кадра берется из этой последовательности:

{1 0.7 0.5 0.3 0.1 0.3 0.5 0.7 1}

то есть: вы генерируете десять различных матриц преобразования (фактически 9), вставляя их в качестве ключевых кадров в отображаться каждую десятую секунды, а затем использовать параметр «не интерполировать». вы можете настроить номер анимации для баланса плавности и производительности *

* извините за возможные ошибки, эта последняя часть была написана без проверки орфографии.

6
ответ дан 2 December 2019 в 01:10
поделиться

Я решил. У вас, вероятно, уже есть решение, но вот что я нашел. На самом деле это довольно просто ...

Вы можете использовать CABasicAnimation для поворота по диагонали, но это должно быть объединение двух матриц, а именно существующей матрицы слоя плюс CATransform3DRotate. «Уловка» в том, что в 3DRotate вам нужно указать координаты для вращения.

Код выглядит примерно так:


CATransform3DConcat(theLayer.transform, CATransform3DRotate(CATransform3DIdentity, M_PI/2, -1, 1, 0));

Это сделает поворот, который выглядит так, как если бы верхний левый угол квадрата вращался вокруг оси Y = X и перемещался в нижний правый угол.

Код для анимации выглядит так:


CABasicAnimation *ani1 = [CABasicAnimation animationWithKeyPath:@"transform"];

// set self as the delegate so you can implement (void)animationDidStop:finished: to handle anything you might want to do upon completion
[ani1 setDelegate:self];

// set the duration of the animation - a float
[ani1 setDuration:dur];

// set the animation's "toValue" which MUST be wrapped in an NSValue instance (except special cases such as colors)
ani1.toValue = [NSValue valueWithCATransform3D:CATransform3DConcat(theLayer.transform, CATransform3DRotate(CATransform3DIdentity, M_PI/2, -1, 1, 0))];

// give the animation a name so you can check it in the animationDidStop:finished: method
[ani1 setValue:@"shrink" forKey:@"name"];

// finally, apply the animation
[theLayer addAnimation:ani1 forKey@"arbitraryKey"];

Вот и все! Этот код будет вращать квадрат (theLayer) до невидимости, когда он перемещается на 90 градусов и представляет себя перпендикулярно экрану. Затем вы можете изменить цвет и сделать ту же самую анимацию, чтобы вернуть его. Та же анимация работает, потому что мы объединяем матрицы, поэтому каждый раз, когда вы хотите повернуть, просто делайте это дважды или измените M_PI / 2 на M_PI .

Наконец, следует отметить, и это меня бесило, что по завершении слой вернется в исходное состояние, если вы явно не установите его в состояние окончания анимации. Это означает, что непосредственно перед строкой [theLayer addAnimation: ani1 forKey @ "randomKey"]; вы захотите добавить


theLayer.transform = CATransform3DConcat(v.theSquare.transform, CATransform3DRotate(CATransform3DIdentity, M_PI/2, -1, 1, 0));

, чтобы установить его значение после завершения анимации. Это предотвратит возврат к исходному состоянию.

Надеюсь, это поможет.Если не вы, то, возможно, кто-то другой, который бился головой о стену, как и мы! :)

Ура,

Крис

4
ответ дан 2 December 2019 в 01:10
поделиться
Другие вопросы по тегам:

Похожие вопросы: