Search code examples
iosrotationquaternionsscenekit

issue with jumping rotation (or gimbal lock) at certain angle


I'm capturing the device's rotation as resting point where no rotation takes place. after the user tilts ipad/iphone and i check the new angle rotate to the left/right with 5 degree incrementally. at some point i see a 'jump' or gimbal lock in rotation. before jump happens it gets slower than usual the jump happens. maybe I'm not normalising the vector or quaternion properly. how to achieve smooth animation without having gimbal lock?

-(void) awakeFromNib
{
  tmp = GLKQuaternionIdentity;
  self.motionManager = [[CMMotionManager alloc] init];
  self.motionManager.deviceMotionUpdateInterval = 1.0/60.0;

  [self.motionManager startDeviceMotionUpdatesToQueue:[NSOperationQueue currentQueue] withHandler:^(CMDeviceMotion *motion, NSError *error) {
      if (error == nil && onceSampled == NO)
      {
             onceSampled = YES;
             roll = GLKMathRadiansToDegrees(atan2(2*(startingQuaternion.y*startingQuaternion.w - startingQuaternion.x*startingQuaternion.z), 1 - 2*startingQuaternion.y*startingQuaternion.y - 2*startingQuaternion.z*startingQuaternion.z)) ;
             pitch = GLKMathRadiansToDegrees(atan2(2*(startingQuaternion.x*startingQuaternion.w + startingQuaternion.y*startingQuaternion.z), 1 - 2*startingQuaternion.x*startingQuaternion.x - 2*startingQuaternion.z*startingQuaternion.z));
             yaw = GLKMathRadiansToDegrees(asin(2*startingQuaternion.x*startingQuaternion.y + 2*startingQuaternion.w*startingQuaternion.z));
      }
   }];
}

- (void)renderer:(id<SCNSceneRenderer>)aRenderer didSimulatePhysicsAtTime:(NSTimeInterval)time
{
   tmp.x = self.motionManager.deviceMotion.attitude.quaternion.x ;
   tmp.y = self.motionManager.deviceMotion.attitude.quaternion.y ;
   tmp.z = self.motionManager.deviceMotion.attitude.quaternion.z ;
   tmp.w = self.motionManager.deviceMotion.attitude.quaternion.w;

   double myRoll = GLKMathRadiansToDegrees(atan2(2*(tmp.y*tmp.w - tmp.x*tmp.z), 1 - 2*tmp.y*tmp.y - 2*tmp.z*tmp.z)) ;
   double myPitch = GLKMathRadiansToDegrees(atan2(2*(tmp.x*tmp.w + tmp.y*tmp.z), 1 - 2*tmp.x*tmp.x - 2*tmp.z*tmp.z));
   double myYaw = GLKMathRadiansToDegrees(asin(2*tmp.x*tmp.y + 2*tmp.w*tmp.z));

   SCNQuaternion oldRotScnQuat = _cameraNode.presentationNode.rotation;
   GLKQuaternion glQuatOldRot = GLKQuaternionMakeWithAngleAndAxis(oldRotScnQuat.w, oldRotScnQuat.x, oldRotScnQuat.y, oldRotScnQuat.z);

   GLKQuaternion netRotY = GLKQuaternionIdentity;

   if(myPitch >= pitch + 1 || myPitch <= pitch - 1)
    {
        int dir = myPitch > pitch ? 1: -1;
        GLKVector3 vec = GLKVector3Normalize(GLKVector3Make(0, dir, 0));
        netRotY = GLKQuaternionMakeWithAngleAndAxis(GLKMathDegreesToRadians(5), vec.x, vec.y , vec.z);
    }
    glQuatOldRot = GLKQuaternionMultiply(glQuatOldRot, netRotY);
    axis = GLKQuaternionAxis(glQuatOldRot);
    angle = GLKQuaternionAngle(glQuatOldRot);

    [_cameraNode runAction:[SCNAction rotateToAxisAngle:SCNVector4Make(axis.x, axis.y, axis.z, angle) duration:1.2]];
}

edit: actually i want to rotate on X and Y at the same time if device tilted left/ right or up/down from resting point. if someone can explain that it will be awesome.

edit: I've been trying hard to get this working. As I read more about quaternion, i found out that I'm making a mistake creating quaternion. the correct way, i suppose, should be:

if(myPitch >= pitch + 1 || myPitch <= pitch - 1)
 {
   int angle = myPitch > pitch ? 5: -5; // left or right tilt
   GLKVector3 vec = GLKVector3Normalize(GLKVector3Make(0, 1, 0));//rotate on y-axis (-1) is wrong
   double result = sinf(GLKMathDegreesToRadians(angle)/2);
   netRotY = GLKQuaternionMakeWithAngleAndAxis(cosf(GLKMathDegreesToRadians(angle)/2), vec.x *result, vec.y * result, vec.z * result);
   netRotY = GLKQuaternionNormalize(netRotY);
  }

the rest of the code remains as above, but still i see a glitch in rotation.


Solution

  • after a long testing session and observation I could find out that there is no gimbal lock. and my edited calculation is correct:

    if(myPitch >= pitch + 1 || myPitch <= pitch - 1)
     {
       int angle = myPitch > pitch ? 5: -5; // left or right tilt
       GLKVector3 vec = GLKVector3Normalize(GLKVector3Make(0, 1, 0));//rotate on y-axis (-1) is wrong
       double result = sinf(GLKMathDegreesToRadians(angle)/2);
       netRotY = GLKQuaternionMakeWithAngleAndAxis(cosf(GLKMathDegreesToRadians(angle)/2), vec.x *result, vec.y * result, vec.z * result);
       netRotY = GLKQuaternionNormalize(netRotY);
      }
    

    the issue is animation chaining and duration, which I have to ask in new question.