"Everything is relative"
So what you need to do is save a quaternion and use that as an origin (it is also call centre
) and then you can localize any new quaternions to determine what orientation changes have occurred.
Calibration
A calibration can be performed by telling the user to hold the phone steady and then sampling and debouncing a stream of quaternion and averaging them over a period of time. But for this example. just place the device on a table, screen up, before starting the app, and grab the first sample (not great, but for a quickie it works).
Note: A System.Reactive observable works great for sampling and debouncing
Note: Store this quaternion as its inverse (Quaternion.Inverse
) as that is one less calculation you have to perform on each sample.
Calc the difference on each sample:
You want to multiply the current sampled quaternion by the origin/centre (in inverse form).
Note: Remember multiplication is non-commutative with quaternions so order matters(!)
var currentQ = e.Reading.Orientation;
var q = Quaternion.Multiply(originQ, currentQ);
Convert your localized quaternion
So now you have a localized quaternion that you can convert to a Vector3 (transform it by a base vector (up, forward, down, ...) or obtain some Euler angles or ...
Example:
So using the Xamarin Essentials sample, this is how I would change the OrientationSensor_ReadingChanged
event as a very quick example.
Note: The sampling event is called A LOT depending upon the device and SensorSpeed
is really useless on controlling the output rate. If you are directly trying to update the screen with these samples (on a 1-to-1 basis), you could have serious problems (the Mono garbage collector can barely keep up with GC'ing the strings that are created when updating the UI (watch the application output, GC cycles are occurring constantly, even with SensorSpeed.UI
set). I use Reactive Observables to smooth the samples and throttle the sensor output to reasonable update cycles (16ms or more) before updating the UI.
void OrientationSensor_ReadingChanged(object sender, OrientationSensorChangedEventArgs e)
{
if (originQ == Quaternion.Identity) // auto-origin on first sample or when requested
{
originQ = Quaternion.Inverse(e.Reading.Orientation);
}
var q = Quaternion.Multiply(originQ, e.Reading.Orientation);
GetEulerAngles(q, out yaw, out pitch, out roll); // assuming "right-hand" orientation
SmoothAndThrottle(yaw, pitch, roll, () =>
{
Device.BeginInvokeOnMainThread(() =>
{
pitchLabel.Text = pitch.ToString();
rollLabel.Text = roll.ToString();
yawLabel.Text = yaw.ToString();
// This will appear to keep the image aligned to the origin/centre.
direction.RotateTo(90 * yaw, 1);
direction.RotationX = 90 * pitch;
direction.RotationY = -90 * roll;
});
});
}
Note: Just sub in your favorite quaternion to Euler angles routine (and write a smoothing and throttling routine if desired).
Output:
