21 October 2011

Using the motion API to check what direction the camera is looking

Recently I had my very first real try with the Windows Phone 7 Motion API, using the Simple Motion Sample and the Augmented Reality Motion Sample which both can be found here. Especially the motion API gave results that I found initially very puzzling. My idea was to find out where direction in which the camera is pointing – regardless of how the user holds his phone. Life then becomes pretty interesting. As long a you hold your phone in the standard upright portrait direction, things are pretty simple – Yaw direction is more or less the negative of compass direction – as far as the direction in which the camera is looking is concerned. But if the user holds his phone upside down or in landscape mode, you have to account for the fact that although the camera is pointing in the same direction, the top of the phone is pointing in another direction!

I found the following line of reasoning to be fairly correct:

  • If Roll is around –90 degrees, the user is holding his phone in landscape mode with the phone top to the left. This is similar to PageOrientation.LandscapeLeft. Compass direction = 360 - yaw + 90 degrees.
  • If Roll is around 90 degrees the user is holding his phone in landscape mode with the phone top the right. This is similar to PageOrientation.LandscapeRight. Compass direction = 360 – yaw - 90.
  • If Pitch is about 90 degrees and Roll around 0, the user is holding the phone in portrait mode and upright. This is similar to PageOrientation.PortraitUp. Compass direction =  -yaw.
  • If Pitch is about -90 degrees and Roll about 0, then the user is holding the phone in portrait mode and upside down. This is similar to PageOrientation.PortraitDown.
    Compass direction = 360 - yaw + 180.

In code I translated this as follows:

public int GetCameraViewDirection(SensorReadingEventArgs e)
{
  var yaw = MathHelper.ToDegrees(e.SensorReading.Attitude.Yaw);
  var roll = MathHelper.ToDegrees(e.SensorReading.Attitude.Roll);
  var pitch = MathHelper.ToDegrees(e.SensorReading.Attitude.Pitch);

  if (roll < -20 && roll > -160)
  {
    return Normalize(360 - yaw + 90);
  }
  else if (roll > 20 && roll < 160)
  {
    return Normalize(360 - yaw - 90);
  }
  else if (pitch > 20 && pitch < 160)
  {
    return Normalize(-yaw );
  }
  else if (pitch < -20 && pitch > -160)
  {
    return Normalize(360 - yaw + 180);
  }
  
  // No sensible data
  return -1; 
}

private int Normalize( int compassDirection )
{      
  if (compassDirection > 360)
  {
    compassDirection -= 360;
  }
  if (compassDirection < 0)
  {
    compassDirection += 360;
  }
  return compassDirection;
}

The MathHelper, by the way, comes from Microsoft.Xna.Framework. If you don’t like my ‘easy way out’ way of returning –1, throw an exception if you like. The Normalize function always makes sure the result is always between 0 and 360 degrees.

There are some interesting considerations. For example, if you can trap the “OnPageOrientationChanged” event and check it’s value, why then even check pitch and roll? Well, one important reason is that PageOrientation.PortraitDown for some reason is not supported. Try it – use any App supporting Landscape and Portrait. Hold your phone in portrait mode upside (i.e. with the phone top pointing to the floor… nothing happens).

I also noticed that in the first two cases any value coming from Pitch can best be ignored, because it varies wildly on my phone.

No comments: