How to use the pan gesture to rotate the camera in SceneKit using quaternions

I am creating a 360 video viewer using the iOS SceneKit framework .

I would like to use UIPanGestureRecognizer to control camera orientation.

SCNNode have several properties that we can use to indicate their rotation: rotation ( rotation matrix), orientation (a quaternion), eulerAngles (at each angle of the axis).

All I read says to avoid using Euler angles to avoid gimbal lock .

I would like to use quaternions for several reasons that I will not do here.

I am having trouble getting this to work correctly. Camera control is almost where I would like to, but something is wrong. It seems that the camera rotates around the Z axis, despite my attempts to influence only the X and Y axes.

I believe the problem is related to my quaternion multiplication logic. I have not done anything related to quaternion in years :( My migration handler is here:

 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 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 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 } } 

My open source code is here: https://github.com/alfiehanssen/360Player/

Check out the pan-gesture : https://github.com/alfiehanssen/360Player/tree/pan-gesture

If you pull the code down, I believe that you will have to run it on the device, not on the simulator.

I posted a video here demonstrating the error (please, excuse me for the low quality of the video file): https://vimeo.com/174346191

Thanks in advance for your help!

+3
source share
3 answers

I was able to get this job using quaternions. The full code is here: ThreeSixtyPlayer . Sample is here:

  let orientation = cameraNode.orientation // Use the pan translation along the x axis to adjust the camera 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 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) 
+5
source

Sorry, this uses SCNVector4 instead of quaternions, but works well for my use. I apply it to my container of nodes of the root geometry itself ("rotContainer") instead of the camera, but a brief test seems to indicate that it will work to use the camera as well.

 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 = SCNMatrix4Invert(rotContainer.transform) rotContainer.pivot = SCNMatrix4Mult(changePivot, currentPivot) rotContainer.transform = SCNMatrix4Identity } } 
+1
source

The bbedit solution goes well with Rotating the camera in orbit . Set up the camera as suggested in the linked answer, then rotate the β€œorbit” node using the ideas of bbedit. I changed the code for the Swift 4 version of its code that really worked:

 @IBAction func handlePan(_ sender: UIPanGestureRecognizer) { print("Called the handlePan method") let scnView = self.view as! SCNView let cameraOrbit = scnView.scene?.rootNode.childNode(withName: "cameraOrbit", recursively: true) let translation = sender.translation(in: 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)(Double.pi)/180.0 var rotationVector = SCNVector4() rotationVector.x = -pan_y rotationVector.y = pan_x rotationVector.z = 0 rotationVector.w = anglePan cameraOrbit!.rotation = rotationVector if(sender.state == UIGestureRecognizerState.ended) { let currentPivot = cameraOrbit!.pivot let changePivot = SCNMatrix4Invert(cameraOrbit!.transform) cameraOrbit!.pivot = SCNMatrix4Mult(changePivot, currentPivot) cameraOrbit!.transform = SCNMatrix4Identity } } 
0
source

Source: https://habr.com/ru/post/1261569/


All Articles