- Home /
How to obtain roll / tilt angle from gyroscope?
Imagine that the user is holding the device with both hands in landscape mode. I need to determine the roll / tilt angle. The picture below show three different angles.
This is the only kind of rotation I am interested in. So I tried using Input.gyro.attitude.eulerAngles.z
. That works if the forward vector of the gyroscope is pointing at the horizon like the top image below. But if the forward vector is pointing downwards (3rd image) it fails.
So I was wondering if anyone has an idea of how to determine the angles (z rotation) shown in the first image regadles of x rotation. I know that this a typical case of gimbal lock, but there must be a work-around. How do racing games tackle this problem when using the gyro for steering??
Note: I do not want to use Input.gyro.rotationRate
and apply it as a delta to some kind of starting orientation, because that will become more and more inaccurate over time. Input.gyro.attitude
(which is a quaternion) is the way to go. I just don't know how to get roll / tilt if the device is facing down (or up).
Answer by codingChris · Apr 16, 2013 at 06:03 PM
As it turns out, the solution is pretty easy. After having spent way too much time with manual calculation I came up with this few lines instead:
Quaternion referenceRotation = Quaternion.identity;
Quaternion deviceRotation = DeviceRotation.Get();
Quaternion eliminationOfXY = Quaternion.Inverse(
Quaternion.FromToRotation(referenceRotation * Vector3.forward,
deviceRotation * Vector3.forward)
);
Quaternion rotationZ = eliminationOfXY * deviceRotation;
float roll = rotationZ.eulerAngles.z;
Rotating the quaternion so that its forward vector points to referenceRotation * Vector3.forward
eliminates x and y rotation while maintaining z.
Still, the resulting roll angle will get a little bit unstable if you turn yourself 180° around real worlds y axis while playing (e.g. if you're sitting in a moving vehicle). That's what referenceRotation
is for, which could either be reset manually by the user or even better via compass. That's what's next on my to do list :)
PS:
DeviceRotation.Get()
is a wrapper for gyro input which adjusts the forward axis and takes different device orientations into account. Read more about it here.
Where can I find information about the DeviceRotation.Get()? The content on the website you have posted the link to seems to have changed. Thanks!
DeviceRotation.Get() is a self written wrapper for Input.gyro.attitude. The article of that website explains what rotations have to be applied to Input.gyro.attitude and why. Here is the relevant code of my DeviceRotation class.
public static class DeviceRotation {
private static bool gyroInitialized = false;
public static bool HasGyroscope {
get {
return SystemInfo.supportsGyroscope;
}
}
public static Quaternion Get() {
if (!gyroInitialized) {
InitGyro();
}
return HasGyroscope
? ReadGyroscopeRotation()
: Quaternion.identity;
}
private static void InitGyro() {
if (HasGyroscope) {
Input.gyro.enabled = true; // enable the gyroscope
Input.gyro.updateInterval = 0.0167f; // set the update interval to it's highest value (60 Hz)
}
gyroInitialized = true;
}
private static Quaternion ReadGyroscopeRotation() {
return new Quaternion(0.5f, 0.5f, -0.5f, 0.5f) * Input.gyro.attitude * new Quaternion(0, 0, 1, 0);
}
}
Oh thanks for that! Do you know how I could modify this to get only the values of the z axis?
That's what the code in the original answer is for :)
How can I calculate pitch ins$$anonymous$$d? Sorry, but I dont understand quaternions at all
Look at my comment below. Using that code you can get the pitch by calling GetAngleByDeviceAxis(Vector3.right)
.
Answer by Big Bang Studio · Jun 26, 2016 at 08:14 AM
@codingChris we are dealing with a similar situation right now where we need the X and Y axis. You describe this working for the Z axis, do you mean the "heading" rotation as used in regular aeronautics?
much appreciated, and sorry for the gravereply!
You can pretty much use the same approach as above. I generalized the code, so that it looks like this:
/// <summary>
/// Returns the rotation angle of given device axis. Use Vector3.right to obtain pitch, Vector3.up for yaw and Vector3.forward for roll.
/// This is for landscape mode. Up vector is the wide side of the phone and forward vector is where the back camera points to.
/// </summary>
/// <returns>A scalar value, representing the rotation amount around specified axis.</returns>
/// <param name="axis">Should be either Vector3.right, Vector3.up or Vector3.forward. Won't work for anything else.</param>
float GetAngleByDeviceAxis(Vector3 axis) {
Quaternion deviceRotation = DeviceRotation.GetRotation();
Quaternion eli$$anonymous$$ationOfOthers = Quaternion.Inverse(
Quaternion.FromToRotation(axis, deviceRotation * axis)
);
Vector3 filteredEuler = (eli$$anonymous$$ationOfOthers * deviceRotation).eulerAngles;
float result = filteredEuler.z;
if (axis == Vector3.up) {
result = filteredEuler.y;
}
if (axis == Vector3.right) {
// incorporate different euler representations.
result = (filteredEuler.y > 90 && filteredEuler.y < 270) ? 180 - filteredEuler.x : filteredEuler.x;
}
return result;
}
So you'd call it with float xAngle = GetAngleByDeviceAxis(Vector3.right)
and float yAngle = GetAngleByDeviceAxis(Vector3.up)
.
@codingChris is GetRotation() the same as Get() ?
Unfortunately I do not think it is the same. He said that Get() adjusts for the forward-axis. When I try to use Get() for the y-axis (up), the readings go crazy. What's worse is that the link he posted is dead!
hi @codingChris,
I'm using your solution but i'm seeing some strange results trying to get the xAngle
(using Vector3.right
)
At certain orientations, the returned angle spikes wildly (which is why i think you 're doing the "incorporate different euler representations" line in your code). I find that just subtracting 180 degrees from filteredEuler.x
doesn't work. Say, i hold the phone so that i get about 70 degrees of pitch. However, as i turn the phone around the Y axis, it eventually spikes up to 360 degrees and then down to zero, and back to 70. Is this some kind of issue with the quaternion math? could you point me to somewhere i can read about this?
thanks a lot for your code anyway!
Answer by Cato11 · Apr 24 at 01:09 PM
When I use Chris' code for the z-axis (the most upvoted post), I am able to change the referenceRotation to my own value (using DeviceRotation.Get), and measure the difference from that. It works perfectly, as I set my referenceRotation to the player's resting hand position on startup. And I use that as my reference point.
But as soon as I try to do the same with the y-axis (using Vector3.up), it does not work and the reading goes completely crazy. This axis only seems to work with a referenceRotation of Quaternion.identity, nothing else. Can anyone please advise why this might be? I really need to do the same thing as the z-axis and I do not understand why it won't work.
@codingChris any ideas?
EDIT: I suspect it has something to do with when he says "DeviceRotation.Get() is a wrapper for gyro input which adjusts the forward axis and takes different device orientations into account". I suspect I need to do the same for the up axis, but unfortunately the tutorial he posted no longer works!! Does anyone know how to do this?
The linked article is available via the WayBack Machine here: https://web.archive.org/web/20180904151819/http://blog.heyworks.com/how-to-write-gyroscope-controller-with-unity3d/