Reputation: 1816
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:
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
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