web-dev-qa-db-fra.com

SceneKit - Faire pivoter et animer un SCNNode

J'essaie d'afficher une pyramide qui pointe le long de l'axe z, puis tourne autour de lui-même autour de z . Comme ma caméra est sur l'axe z, j'attends de voir la pyramide d'en haut. J'ai réussi à faire pivoter la pyramide pour la voir ainsi, mais lorsque j'ajoute l'animation, elle semble pivoter sur plusieurs axes.

Voici mon code:

// The following create the pyramid and place it how I want
let pyramid = SCNPyramid(width: 1.0, height: 1.0, length: 1.0)
let pyramidNode = SCNNode(geometry: pyramid)
pyramidNode.position = SCNVector3(x: 0, y: 0, z: 0)
pyramidNode.rotation = SCNVector4(x: 1, y: 0, z: 0, w: Float(M_PI / 2))
scene.rootNode.addChildNode(pyramidNode)

// But the animation seems to rotate aroun 2 axis and not just z
var spin = CABasicAnimation(keyPath: "rotation")
spin.byValue = NSValue(SCNVector4: SCNVector4(x: 0, y: 0, z: 1, w: 2*Float(M_PI)))
spin.duration = 3
spin.repeatCount = HUGE
pyramidNode.addAnimation(spin, forKey: "spin around")
15
Newalp

Essayer de définir et d’animer manuellement la même propriété peut poser des problèmes. L'utilisation d'une animation byValue aggrave le problème: elle est concaténée avec la transformation en cours. Il est donc plus difficile de savoir si la transformation en cours correspond à ce que l'animation attend.

Au lieu de cela, séparez l'orientation fixe de la pyramide (son sommet est dans la direction -z) de l'animation (elle tourne autour de l'axe sur lequel elle pointe). Il y a deux bonnes façons de faire ceci:

  1. Faites de pyramidNode l'enfant d'un autre nœud qui obtient la rotation ponctuelle (π/2 autour de l'axe des x) et appliquez l'animation de rotation directement à pyramidNode. (Dans ce cas, le sommet de la pyramide pointera toujours dans la direction + y de son espace local, vous voudrez donc tourner autour de cet axe plutôt que de l'axe z.)

  2. Utilisez la propriété pivot pour transformer l'espace local du contenu de pyramidNode et animer pyramidNode par rapport à son espace contenant.

Voici un code pour montrer la deuxième approche:

let pyramid = SCNPyramid(width: 1.0, height: 1.0, length: 1.0)
let pyramidNode = SCNNode(geometry: pyramid)
pyramidNode.position = SCNVector3(x: 0, y: 0, z: 0)
// Point the pyramid in the -z direction
pyramidNode.pivot = SCNMatrix4MakeRotation(CGFloat(M_PI_2), 1, 0, 0)
scene.rootNode.addChildNode(pyramidNode)

let spin = CABasicAnimation(keyPath: "rotation")
// Use from-to to explicitly make a full rotation around z
spin.fromValue = NSValue(SCNVector4: SCNVector4(x: 0, y: 0, z: 1, w: 0))
spin.toValue = NSValue(SCNVector4: SCNVector4(x: 0, y: 0, z: 1, w: CGFloat(2 * M_PI)))
spin.duration = 3
spin.repeatCount = .infinity
pyramidNode.addAnimation(spin, forKey: "spin around")

Quelques modifications indépendantes pour améliorer la qualité du code:

  • Utilisez CGFloat quand une conversion explicite est requise pour initialiser un composant SCNVector; Si vous utilisez Float ou Double, une architecture 32 ou 64 bits sera utilisée.
  • Utilisez .infinity au lieu de la constante mathématique BSD héritée HUGE. Ce type infère le type de spin.repeatCount et utilise une valeur constante définie pour tous les types à virgule flottante.
  • Utilisez M_PI_2 pour π/2 pour faire preuve de précision.
  • Utilisez let au lieu de var pour l'animation, car nous n'attribuons jamais une valeur différente à spin.

Pour en savoir plus sur les erreurs CGFloat: Dans Swift, les littéraux numériques n'ont pas de type tant que l'expression dans laquelle ils se trouvent n'en a pas besoin. C'est pourquoi vous pouvez faire des choses comme spin.duration = 3 - même si duration est une valeur à virgule flottante, Swift vous permet de passer un "littéral entier". Mais si vous faites let d = 3; spin.duration = d, vous obtenez une erreur. Pourquoi? Parce que les variables/constantes ont des types explicites et que Swift ne fait pas de conversion de type implicite. Le 3 est sans type, mais lorsqu'il est affecté à d, l'inférence de type par défaut choisit Int car vous n'avez rien spécifié d'autre. 

Si vous constatez des erreurs de conversion de type, vous avez probablement du code qui mélange les littéraux, les constantes et/ou les valeurs renvoyées par les fonctions. Vous pouvez probablement éliminer les erreurs en convertissant tout le contenu de l'expression en CGFloat (ou quel que soit le type auquel vous transmettez cette expression). Bien entendu, cela rendra votre code illisible et laid. Ainsi, une fois que vous le maîtriserez, vous pourrez supprimer les conversions une par une jusqu'à ce que vous trouviez celui qui fait le travail.

14
rickster

SceneKit inclut des aides à l'animation qui sont beaucoup plus simples et plus rapides à utiliser que CAAnimations. Ceci est ObjC mais passe à travers le point:

[pyramidNode runAction:
    [SCNAction repeatActionForever:
        [SCNAction rotateByX:0 y:0 z:2*M_PI duration:3]]];
14
Graham Perks

J'ai changé byValue à toValue et cela a fonctionné pour moi. Alors changez de ligne ...

spin.byValue = NSValue(SCNVector4: SCNVector4(...

Changez-le en ...

spin.toValue = NSValue(SCNVector4: SCNVector4(x: 0, y:0, z:1, w: 2*float(M_PI))
0
FlippinFun