问题
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?
回答1:
"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:
来源:https://stackoverflow.com/questions/54540095/xamarin-orientation-sensor-quaternion