Hashmat Khalil
Hashmat Khalil

Reputation: 1816

keep rotating scene node until the device gets back to starting position

while experimenting with scene kit I'm using self.motionManager.deviceMotion.attitude.quaternion to rotate a scene node (camera) in 3D scene. lets say a user starts the app and device is somehow tilted, so this starting orientation should be counted as the resting point and no rotation should happen while in this position. when the user tilts the device (left <--> right and/or up <--> down) then it should rotate incrementally until the user puts the device back to starting/resting orientation where no rotation will happen. According to this answer I know how to filter the noise and random jiggling

these following points are important:

  1. have a resting orientation where no rotation happens
  2. when the device is tilted then rotate incrementally
  3. by achieving the above points the user doesn't have to flip the device around for getting complete rotation :-)

I have no clue how to achieve this. any help is greatly appreciated.

edit: adding some code trying to implement rickster's answer

I have to get roll, yaw, pitch values in startDeviceMotionUpdatesToQueue:withHandler: otherwise its all zero

-(void) awakeFromNib
{
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)
    {
        if (firstValue == NO)
        {
           firstValue = YES;
           yaw = self.motionManager.deviceMotion.attitude.yaw;
           roll = self.motionManager.deviceMotion.attitude.roll;
           pitch = self.motionManager.deviceMotion.attitude.pitch;
        }
    }
}];
}

I'm trying to just first rotate on x-axis by 5 degree for simplicity reason:

- (void)renderer:(id<SCNSceneRenderer>)aRenderer didSimulatePhysicsAtTime:(NSTimeInterval)time
{
    currentYaw = self.motionManager.deviceMotion.attitude.yaw;
    currentRoll = self.motionManager.deviceMotion.attitude.roll;
    currentPitch = self.motionManager.deviceMotion.attitude.pitch;
    SCNVector4 q =_cameraNode.presentationNode.rotation;
  float phi = atan((2*(q.x*q.y + q.z*q.w))/(1-2*(pow(q.y,2)*pow(q.z,2))));
  if (currentRoll > 0)
  {
    [_cameraNode runAction:[SCNAction rotateToAxisAngle:SCNVector4Make(1, 0, 0, ( phi + DEGREES_TO_RADIANS(5))) duration:1]];
  }
  else if(currentRoll < 0 )
  {
    [_cameraNode runAction:[SCNAction rotateToAxisAngle:SCNVector4Make(1, 0, 0, (phi - DEGREES_TO_RADIANS(5))) duration:1]];
  }

}

debug output for variables are:

debug: q = (x = 0.999998092, y = 0, z = 0, w = -0.0872662216)

phi = 0

currentRoll = -1.1897298305515105

it actually doesn't rotate at all. phi value doesn't look good. What am I doing wrong?

side note: how to do rotation for all axis at once? multiply quaternion oldRotation with x then y then z? is it the correct order?

Upvotes: 0

Views: 449

Answers (1)

rickster
rickster

Reputation: 126167

To track a resting orientation, you'll need to keep track of the first CMAttitude you sample. Then you can use multiplyByInverseOfAttitude: to get the difference between that attitude and the current one each time you sample. (If you're already working with quaternions due to filtering, you can perform the equivalent operation with GLKQuaternionInvert and GLKQuaternionMultiply.)

To do the incremental rotation you're talking about, it might be easiest to use Euler angles --relative to the difference in attitude you just found, so that a roll of zero means no change from the neutral orientation the user started in (regardless of what that orientation is is absolute terms). Then, when the roll angle is positive during a given time step (e.g. in your SCNSceneRendererDelegate update method) you can increment the roll of your camera node, and vice versa.

In fact, if you're doing incremental rotation this way, you might not need the filtering step (from my other answer that you linked to). Instead, you can probably get away with the raw attitude input and a threshold. For example, increment the node's roll only when the attitude's roll is greater than 5 degrees. In this case you can just get the Euler angles from the CMAttitude (the difference attitude from earlier in this answer), and not have to worry about extracting Euler angles from a quaternion yourself (not that it's all that hard).

Upvotes: 2

Related Questions