as the title already says, i'm currently working on a small ball balancing game using the gyroscope rotation to rotate a floating platform with a ball on it. The problem i always encounter is: i can't lock the y axis. I used euler angles and it worked perfectly, but then i have to deal with gimbal lock. Because i don't want to deal with this issue of euler, i tried using quaternions instead and it does work in some kind of way. I can set the rotation and can define a calibration offset for the gyroscope. If i wouldn't calculate this offset, the rotation would be absolute and not relative to the ingame world space. But i always have some sort of y rotation going on, even if i set it to 0 for the quaternion.
For example: i start the game, calibrate the gyro to my rotation but after that rotate around the y axis in real world space. The platform keeps facing the camera, but so doesn't the rotation axes.
The normal behaviour and how it always should be (no rotation on y axis in real world space): https://www.nani-games.net/share/umSc6XH1vE.gif
The actual behaviour and how it shouldn't be (rotation on y axis in real world space): https://www.nani-games.net/share/AVicmV4fWe.gif
A list of what i already tried:
How my scene is build up: https://www.nani-games.net/share/Unity_fAQKW7cfPK.png
I have two scripts which are used to get the gyroscope rotation, make it useful in unity and use it to rotate other objects.
A static script called DeviceRotation. It activates the gyro and gets the rotation from it:
using UnityEngine;
static class DeviceRotation
{
public static Quaternion offsetRotation;
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;
Input.gyro.updateInterval = 0.0167f; // 60Hz
}
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);
}
public static void calibrateCoords()
{
Quaternion deviceRotation = Get();
offsetRotation = deviceRotation;
}
}
The second script which is appended to the platform object. It gets a calibration quaternion, calculates a new quaternion out of the calibration quaternion and a constantly updating quaternion (both taken from the gyroscopes rotation) and then sets the platforms transform to this new rotation:
using UnityEngine;
using UnityEngine.UI;
public class Gyroscope : MonoBehaviour
{
public Button resetSphere;
public Button calibrate;
public GameObject sphere;
public Text platformCoords;
public Text gyroOffsetCoords;
void Start()
{
DeviceRotation.calibrateCoords();
calibrate.onClick.AddListener(DeviceRotation.calibrateCoords);
setGyroRotation();
resetSphere.onClick.AddListener(ResetSphere);
}
void Update()
{
setGyroRotation();
Vector3 offsetRotationEuler = DeviceRotation.offsetRotation.eulerAngles;
gyroOffsetCoords.text = "GyroOffset:\n" +
"X: " + offsetRotationEuler.x + "\n" +
"Y: " + offsetRotationEuler.y + "\n" +
"Z: " + offsetRotationEuler.z;
}
private void ResetSphere()
{
GameObject temp_sphere = Instantiate(sphere);
temp_sphere.transform.position = new Vector3(0, 3, 0);
}
private void setGyroRotation()
{
Quaternion deviceRotation = DeviceRotation.Get();
Quaternion newRotation = Quaternion.Inverse(DeviceRotation.offsetRotation) * deviceRotation;
transform.localRotation = newRotation;
Vector3 platformAngles = transform.localRotation.eulerAngles;
platformCoords.text = "Platform:" + "\n" +
"X: " + platformAngles.x + "\n" +
"Y: " + platformAngles.y + "\n" +
"Z: " + platformAngles.z;
}
}
So, to conclude everything: my problem is that i cannot lock one axis (the y axis) of a quaternion. I also tried a combination of using euler to make the desired rotation i want and then converting it to a quaternion (that's why those two functions QuaternionToEuler and EulerToQuaternion exist in the script OffsetGyro), but unfortunately this caused gimbal lock (what a surprise i guess).
I really need help at this quaternion problem, i'm stuck with it since several days.
So, i played with everything for a bit and tried to randomly create a possible solution. This is my current function setGyroRotation:
private void setGyroRotation()
{
Quaternion deviceRotation = DeviceRotation.Get();
Quaternion newRotation = Quaternion.Inverse(DeviceRotation.offsetRotation) * deviceRotation; // Calculating "initial" rotation
axisObject.transform.rotation = newRotation; // Setting the axis' rotation to our "initial" rotation
Vector3 unfixedUp = newRotation * Vector3.up;
float angle = Vector3.Angle(Vector3.up, unfixedUp);
Vector3 axis = Vector3.Cross(Vector3.up, unfixedUp);
if (axis == Vector3.zero)
{
transform.rotation = Quaternion.AngleAxis(unfixedUp.y >= 0f ? 0f : 180f, Vector3.right);
}
else
{
transform.rotation = Quaternion.AngleAxis(angle, axis);
}
Vector3 platformAngles = transform.localRotation.eulerAngles;
platformCoords.text = "Platform:" + "\n" +
"X: " + platformAngles.x + "\n" +
"Y: " + platformAngles.y + "\n" +
"Z: " + platformAngles.z;
}
It almost does work as expected: no gimbal lock (atleast not one which occurs easily. Only if you rotate your phone about 180 degrees on x or z) and y axis is freezed, but the initial / calibration rotation axes still are a big problem. Here's a gif which hopefully displays the problem a bit better than the other ones: https://www.nani-games.net/share/93eHK0Fz1t.gif
The axis above the platform displays the rotation of the gyro with an offset applied.