1

I'm trying to determine a 3d vector Vector3 representing my phones orientation without an angle. What I'm looking for is the vector that figuratively comes out of the back of the phone like a ray, basically a normal vector of the phone.

I know the quaternion gives me a transformation, but to what?

Furthermore, I found Quaternion.ToAxisAngle(), which transforms a quaternion to an axis and its respective roll angle. I thought, great, that's what I need, I can just ignore the angle.

When the phone lies on the table, I get the following axis:

axis = [0,0,-1]

And the angle basically represents the angle of the compass. In that particular situation, that's what I expected. But when the phone has a different arbitrary spatial position, the axis doesn't seem to be the phone's normal vector any more.

How can I calculate a normal vector to the phone's plane?

mefiX
  • 1,702
  • 3
  • 24
  • 39
  • Have you looked at the example section : https://learn.microsoft.com/en-us/xamarin/essentials/orientation-sensor – SushiHangover Feb 05 '19 at 18:29
  • Yes, I actually did. The Quaternion describes the rotation of the device's coordinate system relative to the Earth's coordinate system.. So how can I get a local orientation vector that is not relative to the earth coordinate system? – mefiX Feb 05 '19 at 20:04

1 Answers1

5

"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:

enter image description here

SushiHangover
  • 73,120
  • 10
  • 106
  • 165
  • Wow, thank you very much for this detailed answer. I understand the calibration aspect and the inverse quaternion in order to come to a localized quaternion. However, I don't want Euler angles for each sample, but a 3d vector being normal/orthogonal to my phone's surface/plane. (...)So now you have a localized quaternion that you can convert to a Vector3 (transform it by a base vector(...) this is the part I don't understand. What do you mean by base vector (1,0,0)? And what do you mean by "transform it by base vector"? – mefiX Feb 06 '19 at 10:28
  • @mefiX A base vector might be a standard VectorUp|VectorForward|... (Unity|XNA|... and others pre-define these, but you can use any vector depending upon you needs.. A VectorUp would be defined as `new Vector3(0f, 1f, 0f);` in a right-hand coord system. From there you can transform that vector by your localized quaternion, System.Numerics.Vector3 has a static transform, so `Vector3.Transform(VectorUp, q);` will return a vector "rotated" from its base by the quaternion rotation. Make sense? – SushiHangover Feb 06 '19 at 13:00
  • The "localized quaternion" still being the multiplication of the inverse origin quaternion and the current sampled quaternion (in that order)? – mefiX Feb 06 '19 at 14:22
  • @mefiX Yes... converting to vectors too soon will land you in a world filled with issues like gimbal locks and such. You should always work with quaternions as they can express any rotation freely. In terms of "user presentation" of a quaternion, use the last one in the chain of rotations before applying any "real world" transformation on. – SushiHangover Feb 06 '19 at 14:34
  • Here's the thing. I think it's much simpler than I thought. If I transform a "forward" aka. "out of the back of the phone" vector `[0,0,-1]` just with the current read quaternion, I get exactly what I want. – mefiX Feb 07 '19 at 10:20
  • I'm sorry, I just realized that I might need to rephrase my question. What I need is a 3d orientation vector in `(lat,lon,altitude)` (WGS). I need to calculate the angle between my phone's orientation vector `o` and a distance vector `d` between the phone's location `p=(lat,lon,alt)` and another fix `f=(lat,lon,alt)` being `d = f - p`. So the actual question: How do I get from a single Quaternion to an orientation vector in WGS? – mefiX Feb 07 '19 at 15:26
  • @mefiX I do "something" similar in an aviation navigation app (given pointA and pointB, what is the initial bearing which if followed in a straight line along a great-circle arc leads from A to B). Post a new question with what you have done so far and what you are looking for. – SushiHangover Feb 07 '19 at 16:16
  • Huh, interesting, we might work in similar contexts. My problem is aviation related, too. I'll write a new question and post a link here. – mefiX Feb 08 '19 at 07:16
  • Here's the new question: https://stackoverflow.com/questions/54589349/xamarin-orientation-sensor-vector-angle thanks BTW for the hint to reactive! I really dig that concept! `.Throttle` doesn't work, tho. Don't know why. – mefiX Feb 08 '19 at 09:30
  • @SushiHangover - do you have an open-source 'GetEulerAngles()' that you'd recommend? – DefenestrationDay Oct 30 '19 at 09:03