18

I am having a hard time mapping device motion (sensor fusion) to SceneKit node rotation. The premise of the problem is as follows,

I have sphere, and the camera is positioned to be inside the the sphere such that the geometric center of sphere and camera node's center coincide. What i want to achieve is when i rotate physically around a point, the motion needs to be mapped accurately on the camera as well.

The implementaion details are as follows:

I have a node, with a sphere as geomertry and is a child to root node. I am using sensor fusion to get attitude quternions and then converting them to euler angles. The code for that is:

- (SCNVector3)eulerAnglesFromCMQuaternion:(CMQuaternion) {
    GLKQuaternion gq1 =  GLKQuaternionMakeWithAngleAndAxis(GLKMathDegreesToRadians(90), 1, 0, 0);
    GLKQuaternion gq2 =  GLKQuaternionMake(q.x, q.y, q.z, q.w);
    GLKQuaternion qp  =  GLKQuaternionMultiply(gq1, gq2);
    CMQuaternion rq =   {.x = qp.x, .y = qp.y, .z = qp.z, .w = qp.w};
    CGFloat roll = atan2(-rq.x*rq.z - rq.w*rq.y, .5 - rq.y*rq.y - rq.z*rq.z);
    CGFloat pitch = asin(-2*(rq.y*rq.z + rq.w*rq.x));
    CGFloat yaw = atan2(rq.x*rq.y - rq.w*rq.z, .5 - rq.x*rq.x - rq.z*rq.z);
    return SCNVector3Make(roll, pitch, yaw);
}

The conversion equation is from 3D Math Primer for Graphics and Game Development. I have used to book as a reference, haven't read it. But definitely on my reading list.

Now to simulate the physical rotation in SceneKit, am rotating my node containing SCNCamera. This is a child to rootNode. And am using .rotation property to do the same. One thing to note here is that Now my device motion update thread looks something like this

[self.motionManager startDeviceMotionUpdatesUsingReferenceFrame:CMAttitudeReferenceFrameXArbitraryCorrectedZVertical toQueue:mq withHandler:^(CMDeviceMotion *motion, NSError *error) {
    CMAttitude *currentAttitude = motion.attitude;
    [SCNTransaction begin];
    [SCNTransaction setDisableActions:YES];
    SCNVector3 v = [self eulerAnglesFromCMQuaternion:currentAttitude.quaternion];
    self.cameraNode.eulerAngles = SCNVector3Make( v.y, -v.x, 0); //SCNVector3Make(roll, yaw, pitch)
    [SCNTransaction commit];
}];

Something of caution here is the euler angles are different for device and for SceneKit node. The links explain them.

SceneKit Euler Angles

CMAttitude Euler Angles

And in my understanding the mapping between two goes something like this.

[Image]

Now the problem I am facing is, the camera's 360 degree rotation is over before my physical rotation about a point is. Which am trying to dpecit via the following image.

[Image2]

I doubt the quaternion -> euler angle conversion is causing the error, and quaternion math is way over my head as of now.

I hope i have provided all the information, if anything more is required am only glad to add.

Andy Fedoroff
  • 26,838
  • 8
  • 85
  • 144
egghese
  • 2,088
  • 15
  • 26
  • 1
    This answer solves for all device orientations: http://stackoverflow.com/questions/31823012/cameranode-rotate-as-ios-device-moving – Alfie Hanssen Jul 10 '16 at 12:40

2 Answers2

11

Thanks for your description of your problem and the beginning of a solution! I am quite sure that this can not be done with the Euler angles, because of the gimbal lock (loose of 1 degree of freedom).

The solution that worked for is to get the attitude and use the quaternion to set the orientation of the camera:

[self.motionManager startDeviceMotionUpdatesUsingReferenceFrame:CMAttitudeReferenceFrameXArbitraryCorrectedZVertical toQueue:myQueue withHandler:^(CMDeviceMotion *motion, NSError *error) {
    CMAttitude *currentAttitude = motion.attitude;
    [SCNTransaction begin];
    [SCNTransaction setDisableActions:YES];

    SCNQuaternion quaternion = [self orientationFromCMQuaternion:currentAttitude.quaternion];
    _cameraNode.orientation = quaternion;

    [SCNTransaction commit];
}];
- (SCNQuaternion)orientationFromCMQuaternion:(CMQuaternion)q
{
    GLKQuaternion gq1 =  GLKQuaternionMakeWithAngleAndAxis(GLKMathDegreesToRadians(-90), 1, 0, 0); // add a rotation of the pitch 90 degrees
    GLKQuaternion gq2 =  GLKQuaternionMake(q.x, q.y, q.z, q.w); // the current orientation
    GLKQuaternion qp  =  GLKQuaternionMultiply(gq1, gq2); // get the "new" orientation
    CMQuaternion rq =   {.x = qp.x, .y = qp.y, .z = qp.z, .w = qp.w}; 

    return SCNVector4Make(rq.x, rq.y, rq.z, rq.w);
}
Ruud Visser
  • 3,066
  • 2
  • 18
  • 18
3

If CoreMotion and SceneKit don't use the same convention for euler angles then you can probably use intermediate nodes to simplify the conversion. For example by having a hierarchy like:

YawNode
  |
  |___PitchNode
      |
      |___RollNode
          |
          |___CameraNode

YawNode, PitchNode, RollNode would rotate only around one single axis. Then by playing on the hierarchy order you can reproduce CoreMotion's convention.

That being said, I'm not sure the conversion quaternion -> euler is a great idea. I suspect that you won't get a continuous/smooth transition at angles like 0 / PI / 2PI... I would try quaternion->matrix4 instead and update the "transform" of your node.

Toyos
  • 3,866
  • 11
  • 15
  • If i do a quaternion to rotation matrix (it'll be a 3x3 matrix or so the book says) how do i make it a 4x4 (padding?). Or how do i do a quaternion -> matrix4 conversion? – egghese Aug 13 '14 at 13:58
  • What about passing the CM quaternion into SceneKit as a quaternion? You can do that with the `SCNNode.orientation` property. (And if the coordinate spaces don't match, use an enclosing node or `pivot` to transform the space containing the node.) – rickster Aug 13 '14 at 18:23
  • @rickster: tried that, but still the difference between start and end exist. I had a check at the sensor value, looks like there is a drift of around 20 degrees. Sensors are fine since other apps works perfectly fine. What could possibly be wrong ? – egghese Aug 14 '14 at 10:45
  • To add to this, surprisingly the difference happens only when i move /rotate rightwards. And the rotation is perfect when i move leftwards. – egghese Aug 14 '14 at 14:30
  • @Toyos it'd be great to augment this answer with code snippets. Esp because this is a solution that works. Otherwise it's tough to translate the diagram. – Alfie Hanssen Jul 10 '16 at 12:39