web-dev-qa-db-fra.com

Comment faire pivoter un objet plat autour de son centre en vue perspective?

Ce que j'essaie de faire semble être assez simple: j'ai créé une vue 2D de haut en bas d'un ancien enregistrement de phonographe. Je veux le faire pivoter (le remettre) dans son axe X, puis le faire tourner autour de son axe Z.

J'ai lu ici toutes les questions qui ont CATransform3D dans le corps, j'ai lu "Les matrices peuvent être vos amis" article ainsi que le livre de Bill Dudney "Core Animation pour Mac OS X et l'iPhone "Je pense que " La rotation 3D sans trackball de Brad Larson " possède le bon code, mais puisqu'il permet à l'utilisateur d'ajuster les trois axes, j'ai du mal à réduire son code en quoi Je perçois être juste une dimension (un axe z pivoté).

Voici l'image que je teste avec, pas que les détails sont importants pour le problème:

Gold Record

J'apporte cela à l'écran de la manière habituelle: (dans une sous-classe d'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];
    }
}

C'est le premier test, il suffit de l'avoir sur l'écran ;-)

Ensuite, pour "le remettre en place", j'applique une transformation à faire pivoter autour de l'axe X du calque, en insérant ce code après avoir défini le contenu du calque dans l'image et avant l'ajout du sous-calque:

    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;        

Cela a fonctionné comme prévu: le disque est bien en retrait.

Et pour la prochaine étape, provoquant la rotation de l'enregistrement, j'ai lié cette animation à l'événement touchEnded, bien qu'une fois la phase de test/apprentissage terminée, cette rotation ne soit plus sous le contrôle de l'utilisateur:

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"];

Je suis donc presque sûr que ce qui me bloque, c'est le vecteur de l'appel CATransform3DRotate (croyez-moi: j'ai essayé de simples changements dans ce vecteur pour observer le changement ... ce qui est indiqué ici est tout simplement le dernier changement. J'ai essayé). Si je comprends bien, les valeurs de x, y et z dans la transformation sont, pour l’essentiel, le pourcentage de la valeur transmise au cours de l’animation allant de Valeur à Valeur.

Si je suis sur la bonne voie pour comprendre cela, est-il possible de l'exprimer en une seule transformation? Ou dois-je, pour chaque image d'animation effective, faire pivoter légèrement l'image verticale d'origine autour de l'axe z, puis déposer le résultat avec une rotation de l'axe des x? J'ai vu une question/réponse qui parlait de combiner des transformations, mais c'était une transformation d'échelle suivie d'une transformation de rotation. Je me suis amusé à transformer la transformation, mais je ne fais pas ce que je pense que je devrais obtenir (c.-à-d. Transmettre le résultat d'une transformation à l'argument de transformation de la suivante semblait simplement exécuter l'une puis animer l'autre).

29
tobinjim

Cela est plus facile si vous utilisez une CATransformLayer comme parent du calque de la vue d'image. Vous aurez donc besoin d'une sous-classe de vue personnalisée utilisant CATransformLayer comme couche. C'est trivial:

@interface TransformView : UIView

@end

@implementation TransformView

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

@end

Mettez une TransformView dans votre nib, et ajoutez une UIImageView en tant que sous-vue de la TransformView. Connectez ces vues aux prises de votre contrôleur de vue appelées transformView et discView.

Dans votre contrôleur de vue, définissez la transformation de transformView pour appliquer la perspective (en définissant m34) et l'inclinaison de l'axe 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;
}

Ajoutez une animation pour le chemin de clé transform.rotation.z à discView dans viewWillAppear: et supprimez-la dans 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];
}

Résultat:

spinning disc

METTRE À JOUR

Voici une démonstration de terrain de jeu rapide:

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)

Copiez l'image de la question dans le dossier Ressources du terrain de jeu et nommez-la 3DgoldRecord.png. Résultat:

 playground result

63
rob mayoff

Vous pouvez envisager d'encapsuler le recordLayer avec un superlayer. Appliquez la rotation de l'axe des x à la supercouche et ajoutez l'animation de rotation de l'axe des z à recordLayer.

0
tommyli