Search code examples
iosscenekitquaternionscore-motion

Unexpected roll and pitch extracted from GLKQuaternion


I use quaternion from CMAttitude to rotate SceneKit camera. Also I need to use Y rotation angle extracted from the quaternion. I expected that it would be roll, but after extraction Y rotation angle corresponds to the pitch which has -90:90 range. How can I convert this range to 0:180 or 0:360?

- (SCNQuaternion)SCNQuaternionFromCMQuaternion:(CMQuaternion)q {

    GLKQuaternion Q = GLKQuaternionMake(q.x, q.y, q.z, q.w);
    GLKQuaternion xRotQ = GLKQuaternionMakeWithAngleAndAxis(-M_PI_2, 1, 0, 0);
    Q = GLKQuaternionMultiply(xRotQ, Q);

    double roll = atan2(2.0 * (Q.y * Q.z - Q.w * Q.x), 1.0 - 2.0 * (Q.x * Q.x + Q.y * Q.y)); // 0:180 but around X

    double pitch = RADIANS_TO_DEGREES(asin(-2.0f * (Q.x * Q.z + Q.w * Q.y))); // 0:90 around Y

    NSLog(@"%f", pitch);
    // ...

    CMQuaternion rq = {.x = Q.x, .y = Q.y, .z = Q.z, .w = Q.w};

    return SCNVector4Make(rq.x, rq.y, rq.z, rq.w);
}

Solution

  • I found this way:

    - (SCNQuaternion)SCNQuaternionFromCMQuaternion:(CMQuaternion)q {
    
        GLKQuaternion Q = GLKQuaternionMake(q.x, q.y, q.z, q.w);
        GLKQuaternion xRotQ = GLKQuaternionMakeWithAngleAndAxis(-M_PI_2, 1, 0, 0);
        Q = GLKQuaternionMultiply(xRotQ, Q);
    
        double gx = 2.0 * (Q.y * Q.w - Q.x * Q.z);
        //double gy = 2.0 * (Q.x * Q.y + Q.z * Q.w);
        double gz = Q.x * Q.x - Q.y * Q.y - Q.z * Q.z + Q.w * Q.w;
    
        double pitch = RADIANS_TO_DEGREES(-asin( -2.0 * (Q.y * Q.w - Q.x * Q.z))); 
    
        if (gx >= 0 && gz < 0)
            pitch = 180 - pitch;
        else if (gx < 0 && gz < 0)
            pitch = 180 - pitch;
        else if (gx < 0 && gz >= 0)
            pitch = 360 + pitch;
    
        NSLog(@"%f", pitch); // now it has 0-360 range
    
        CMQuaternion rq = {.x = Q.x, .y = Q.y, .z = Q.z, .w = Q.w};
    
        return SCNVector4Make(rq.x, rq.y, rq.z, rq.w);
    }