Search code examples
iosswiftscenekitquaternionscore-motion

Pointing to right direction in SceneKit


Currently I meet a problem on iOS platform. Basically, I load an arrow which points to east in 2D environment (upside is north, leftside is west, rightside is east and downside is south). I expect it can point to real east in 3D environment so that it will automatically rotates to the right direction. I draw a picture to describe my situation precisely. (The dotted arrow is my loaded arrow and the solid arrow is what I want if I use core motion data)

enter image description here

Now I did

let motionManager = CMMotionManager()
    motionManager.deviceMotionUpdateInterval = 1.0 / 60.0
    if motionManager.isDeviceMotionAvailable {
        motionManager.startDeviceMotionUpdates(to: OperationQueue.main, withHandler: { (devMotion, error) -> Void in
                parent_arrows[0].orientation = SCNQuaternion(-CGFloat((motionManager.deviceMotion?.attitude.quaternion.x)!), -CGFloat((motionManager.deviceMotion?.attitude.quaternion.y)!), -CGFloat((motionManager.deviceMotion?.attitude.quaternion.z)!), CGFloat((motionManager.deviceMotion?.attitude.quaternion.w)!))

        })}

The snippet can't automatically make the arrow rotate to the right direction. My idea is to get the angle between the device and the north and then apply this angle to the arrow's orientation. But how can I add the angle to a quaternion? And is there any other idea to achieve this?


Solution

  • This thread enlightens me.

     let motionManager = CMMotionManager()
        motionManager.deviceMotionUpdateInterval = 1.0 / 60.0
        if motionManager.isDeviceMotionAvailable {
            motionManager.startDeviceMotionUpdates(to: OperationQueue.main, withHandler: { (devMotion, error) -> Void in
                parent_arrows[0].orientation = self.orient(q: (motionManager.deviceMotion?.attitude.quaternion)!)
    
            })}
    }
    
    func orient(q:CMQuaternion) -> SCNQuaternion{
        let gq1: GLKQuaternion = GLKQuaternionMakeWithAngleAndAxis(GLKMathDegreesToRadians(-heading), 0, 0, 1)
        // add a rotation of the yaw and the heading relative to true north
        let gq2: GLKQuaternion = GLKQuaternionMake(Float(q.x), Float(q.y), Float(q.z), Float(q.w))
        // the current orientation
        let qp: GLKQuaternion = GLKQuaternionMultiply(gq1, gq2)
        // get the "new" orientation
        var rq = CMQuaternion()
        rq.x = Double(qp.x)
        rq.y = Double(qp.y)
        rq.z = Double(qp.z)
        rq.w = Double(qp.w)
        return SCNVector4Make(-Float(rq.x), -Float(rq.y), -Float(rq.z), Float(rq.w))
    }
    

    After doing this, the arrow will point to the right direction at the beginning. However, it brings another problem I asked in another thread. The final answer is in this