Comment utiliser un geste panoramique pour faire pivoter une camera dans SceneKit en utilisant des quaternions

Je construis une visionneuse de videos 360 en utilisant le framework iOS SceneKit .

Je voudrais utiliser un UIPanGestureRecognizer pour contrôler l'orientation de la camera.

Les SCNNode ont plusieurs propriétés que nous pouvons utiliser pour spécifier leur rotation: rotation (une masortingce de rotation), orientation (un quaternion), eulerAngles (angles par axe).

Tout ce que j'ai lu dit pour éviter d'utiliser des angles d'euler afin d'éviter le blocage de cardan .

Je voudrais utiliser des quaternions pour quelques raisons que je n'entrerai pas ici.

J'ai du mal à faire fonctionner ça correctement. Le contrôle de la camera est presque là où je voudrais que ce soit, mais il y a quelque chose qui ne va pas. Il semble que la camera tourne autour de l'axe Z malgré mes tentatives d'influencer uniquement les axes X et Y.

Je crois que le problème a quelque chose à voir avec ma logique de multiplication du quaternion. Je n'ai rien fait de quaternion depuis des années 🙁 Mon gestionnaire de gestes est ici:

 func didPan(recognizer: UIPanGestureRecognizer) { switch recognizer.state { case .Began: self.previousPanTranslation = .zero case .Changed: guard let previous = self.previousPanTranslation else { assertionFailure("Attempt to unwrap previous pan translation failed.") return } // Calculate how much translation occurred between this step and the previous step let translation = recognizer.translationInView(recognizer.view) let translationDelta = CGPoint(x: translation.x - previous.x, y: translation.y - previous.y) // Use the pan translation along the x axis to adjust the camera's rotation about the y axis. let yScalar = Float(translationDelta.x / self.view.bounds.size.width) let yRadians = yScalar * self.dynamicType.MaxPanGestureRotation // Use the pan translation along the y axis to adjust the camera's rotation about the x axis. let xScalar = Float(translationDelta.y / self.view.bounds.size.height) let xRadians = xScalar * self.dynamicType.MaxPanGestureRotation // Use the radian values to construct quaternions let x = GLKQuaternionMakeWithAngleAndAxis(xRadians, 1, 0, 0) let y = GLKQuaternionMakeWithAngleAndAxis(yRadians, 0, 1, 0) let z = GLKQuaternionMakeWithAngleAndAxis(0, 0, 0, 1) let combination = GLKQuaternionMultiply(z, GLKQuaternionMultiply(y, x)) // Multiply the quaternions to obtain an updated orientation let scnOrientation = self.cameraNode.orientation let glkOrientation = GLKQuaternionMake(scnOrientation.x, scnOrientation.y, scnOrientation.z, scnOrientation.w) let q = GLKQuaternionMultiply(combination, glkOrientation) // And finally set the current orientation to the updated orientation self.cameraNode.orientation = SCNQuaternion(x: qx, y: qy, z: qz, w: qw) self.previousPanTranslation = translation case .Ended, .Cancelled, .Failed: self.previousPanTranslation = nil case .Possible: break } } 

Mon code est open source ici: https://github.com/alfiehanssen/360Player/

Découvrez en particulier la twig pan-gesture : https://github.com/alfiehanssen/360Player/tree/pan-gesture

Si vous retirez le code, je crois que vous devrez l'exécuter sur un périphérique plutôt que sur le simulateur.

J'ai posté une video ici qui démontre le bug (s'il vous plaît excuser la faible résistance du file video): https://vimeo.com/174346191

Merci d'avance pour votre aide!

J'ai été capable de faire fonctionner cela en utilisant des quaternions. Le code complet est ici: ThreeSixtyPlayer . Un échantillon est ici:

  let orientation = cameraNode.orientation // Use the pan translation along the x axis to adjust the camera's rotation about the y axis (side to side navigation). let yScalar = Float(translationDelta.x / translationBounds.size.width) let yRadians = yScalar * maxRotation // Use the pan translation along the y axis to adjust the camera's rotation about the x axis (up and down navigation). let xScalar = Float(translationDelta.y / translationBounds.size.height) let xRadians = xScalar * maxRotation // Represent the orientation as a GLKQuaternion var glQuaternion = GLKQuaternionMake(orientation.x, orientation.y, orientation.z, orientation.w) // Perform up and down rotations around *CAMERA* X axis (note the order of multiplication) let xMultiplier = GLKQuaternionMakeWithAngleAndAxis(xRadians, 1, 0, 0) glQuaternion = GLKQuaternionMultiply(glQuaternion, xMultiplier) // Perform side to side rotations around *WORLD* Y axis (note the order of multiplication, different from above) let yMultiplier = GLKQuaternionMakeWithAngleAndAxis(yRadians, 0, 1, 0) glQuaternion = GLKQuaternionMultiply(yMultiplier, glQuaternion) cameraNode.orientation = SCNQuaternion(x: glQuaternion.x, y: glQuaternion.y, z: glQuaternion.z, w: glQuaternion.w) 

Désolé, cela utilise SCNVector4 au lieu de quaternions mais fonctionne bien pour mon utilisation. Je l'applique à mon conteneur de nœuds de geometry racine ("rotContainer") au lieu de l'appareil photo, mais un bref test semble indiquer qu'il fonctionnera également pour l'utilisation d'une camera.

 func panGesture(sender: UIPanGestureRecognizer) { let translation = sender.translationInView(sender.view!) let pan_x = Float(translation.x) let pan_y = Float(-translation.y) let anglePan = sqrt(pow(pan_x,2)+pow(pan_y,2))*(Float)(M_PI)/180.0 var rotationVector = SCNVector4() rotationVector.x = -pan_y rotationVector.y = pan_x rotationVector.z = 0 rotationVector.w = anglePan rotContainer.rotation = rotationVector if(sender.state == UIGestureRecognizerState.Ended) { let currentPivot = rotContainer.pivot let changePivot = SCNMasortingx4Invert(rotContainer.transform) rotContainer.pivot = SCNMasortingx4Mult(changePivot, currentPivot) rotContainer.transform = SCNMasortingx4Identity } }