13

It's 360 degree video player project.

I add a cameranode(SCNNode) to a rootnode, the cameranode was put at the center(0,0,0) of the SCNSphere, It can play video by now.

Now I have to use devicemotion. I need to rotate the camera when device moves. Not just rotate a certain angle. (Not just device move, When I'm holding a device, my moving regard as the device moving. because I found if I use deviceMotion.attitude.roll, the cameranode only moves when device moves by itself, not when I circle around with device)

When device is located in (x1,y1,z1) position, the cameranode would rotate as the device moving, and when the device located in (x1,y1,z1) position again, the view should be as same as the last we left.

Here's what I've done:

if (self.motionManager.gyroAvailable) {
        self.motionManager.gyroUpdateInterval = 1.0/60.0;
        [self.motionManager startGyroUpdatesToQueue:self.queue withHandler:^(CMGyroData *gyroData, NSError *error){
            if (error) {
                [self.motionManager stopGyroUpdates];
                NSLog(@"Gyroscope encountered error:%@",error);
            }else {
                CGFloat tempX = 0;
                CGFloat tempY = 0;
                CGFloat tempZ = 0;
                if ((fabs(gyroData.rotationRate.y)/60) > 0.002) {
                    tempY = gyroData.rotationRate.y/60;
                }
                tempX = gyroData.rotationRate.x/60;
                tempZ = gyroData.rotationRate.z/60;
                [self.cameraNode runAction:[SCNAction rotateByX:-tempY y:tempX z:tempZ duration:0]];
            }
        }];
    }else {
        NSLog(@"This device has no gyroscope");
    }

The math could be wrong, I don't know why would I divide 60, but it seems the nearest value I need when I use [self.cameraNode runAction:[SCNAction rotateByX:-tempY y:tempX z:tempZ duration:0]];

Here's the problem?

  1. which data should I use?

CMGyroData or CMDeviceMotion. If use CMDeviceMotion, which specific value should I use? deviceMotion.attitude, deviceMotion.attitude.quaternion, deviceMotion.gravity

  1. which is the right method to rotate a cameranode? There are many methods, I'm not sure.

+ (SCNAction *)rotateByX:(CGFloat)xAngle y:(CGFloat)yAngle z:(CGFloat)zAngle duration:(NSTimeInterval)duration;

+ (SCNAction *)rotateToX:(CGFloat)xAngle y:(CGFloat)yAngle z:(CGFloat)zAngle duration:(NSTimeInterval)duration;

ronan
  • 1,531
  • 11
  • 20

2 Answers2

29

Device motion is best, as it ties in multiple sensors and fuses the data together in a meaningful way. It's also best to avoid using euler angles, as they suffer from what's known as gimbal lock. Instead, use quaternions and set your camera orientation with them.

I've created a Swift class extension for CMDeviceMotion that'll let you determine what the device is looking at (for any orientation of the screen).

From there, it's straightforward to rotate your camera. First, setup your device motion (in view did appear, or some other appropriate place):

if motionManager.deviceMotionAvailable {

    motionManager.deviceMotionUpdateInterval = 0.017
    motionManager.startDeviceMotionUpdatesToQueue(NSOperationQueue(), withHandler: deviceDidMove)
}

Then handle those updates in your deviceDidMove implementation, using the previously mentioned CMDeviceMotion extension:

func deviceDidMove(motion: CMDeviceMotion?, error: NSError?) {

    if let motion = motion {

        yourCameraNode.orientation = motion.gaze(atOrientation: UIApplication.sharedApplication().statusBarOrientation)
    }
}

And now your camera should follow your devices orientation in space.

Travis
  • 3,207
  • 18
  • 19
  • This should be marked as the correct answer. For completeness' sake, might be nice to (1) add comments to your class extension to explain what it does specifically, and (2) add the framework import statements to the top of the file. – Alfie Hanssen Jul 10 '16 at 12:36
  • in your solution, when you construct the final SCNQuaternion can you explain why you need to swap the signs and values of x and y at times? I understand this is necessary to work in all cases. Could we use another multiplication vector in lieu of this? – Alfie Hanssen Jul 10 '16 at 18:29
  • @AlfieHanssen The signs and swapping are correcting for the attitude of the device. If there's a more clever way to calculate them, I don't know of it. But I do agree, it'd be nice. – Travis Jul 11 '16 at 13:22
  • @Travis thanks for your very useful extension. Do you have any idea how to block the roll component in my camera? http://stackoverflow.com/q/39026329/235297 – Ortwin Gentz Aug 18 '16 at 19:43
2

Swift 5.0 / iOS 13 Update

using the brilliant extension from the initial answer, you can simply do something like this:

override func viewDidAppear(_ animated: Bool) {
    // let motionManager = CMMotionManager() // definition made globally
    
    // CoreMotion
    motionManager.startDeviceMotionUpdates()
    motionManager.deviceMotionUpdateInterval = 1.0 / 60.0
    
    let interfaceOrientation = UIApplication.shared.windows.first(where: { $0.isKeyWindow })?.windowScene?.interfaceOrientation // for iOS13 and later
    
    // .xTrueNorthZVertical // always aligns to the real north
    // .xArbitraryZVertical // will use this one for initial direction
    motionManager.startDeviceMotionUpdates(using: .xArbitraryZVertical, to: OperationQueue.main, withHandler: { (motion: CMDeviceMotion?, err: Error?) in
        guard let m = motion else { return }
        // self.cameraNode.orientation = m.gaze(atOrientation: UIApplication.shared.statusBarOrientation) // before iOS 13
        self.cameraNode.orientation = m.gaze(atOrientation: interfaceOrientation!) // for iOS13 and later
    })
}
ZAY
  • 558
  • 5
  • 16