Quaternion.Lerp X rotation not working properly, yet Y and Z work great.

Hello,

I have been making a script to rotate an object with a joystick (not for a game, just for personal understanding of how Quaternion.Lerp works). For the curious, here’s the whole script:

Full script (76 lines)

using UnityEngine;
using System.Collections;

public class PlaneController : MonoBehaviour
{

    //================JOYSTICK================//
    //using default joystick
    public UnityStandardAssets.CrossPlatformInput.Joystick joystick;
    //joystick positions
    public float Joyx = 0, Joyy = 0;
    //original position of joystick
    public Vector3 OrigPos;
    //difference--how much the joystick has moved compared to original position
    public float DiffX;
    public float DiffY;
    //ratio of current difference and max possible difference
    //used to calculate how fast to move the plane based on how much the joystick is moved
    public float DiffXRatio;
    public float DiffYRatio;
    //how much the joystick moves from the center
    public float MovementRange;

    //================PLANE================//
    public Quaternion PlaneRotation;
    public float MaxYPitch;
    public float MaxXYaw;
    public float MaxXTilt;
    public float RotationLerpSpeed;
    //How much to tilt when turning
    public float BankFactor;

    void Start()
    {
        OrigPos = joystick.transform.position;
    }

    void Update()
    {
        CalculateJoystick();
        CalculatePlaneAngle();
        SlowChanges();
    }

    void CalculateJoystick()
    {
        Joyx = joystick.transform.position.x;
        Joyy = joystick.transform.position.y;
        //find the diffirence of joystick position
        DiffX = Joyx - OrigPos.x;
        DiffY = Joyy - OrigPos.y;
        //now find the ratio, which determines how much to move the plane
        DiffXRatio = DiffX / joystick.MovementRange;
        DiffYRatio = DiffY / joystick.MovementRange;
    }

    void CalculatePlaneAngle()
    {
        //Change X for pitch up-down
        float NewXRotation = DiffYRatio * MaxYPitch;
        //Change Y for yaw left-right
        float NewYRotation = DiffXRatio * MaxXYaw;
        //Change Z for tilt left-right
        //don't forget to inverse so it tilts in the logical direction
        float NewZRotation = DiffXRatio * MaxXTilt * -1;
        //
        PlaneRotation = Quaternion.Euler(transform.localEulerAngles.x + NewXRotation, transform.localEulerAngles.y + NewYRotation,
            transform.localEulerAngles.z + NewZRotation);
    }

    void SlowChanges()
    {
        //let's calculate how to move
        this.transform.rotation = Quaternion.Lerp(this.transform.rotation, PlaneRotation, Time.deltaTime * RotationLerpSpeed);
    }
}

My issue is that when I use the joystick, the Y and Z axises rotate normally. As long as I drag the joystick, they keep moving forever. However, when I try with the X axis (named MaxYPitch in script), it will stop rotating.

Also, if I enable Yaw and Tilt together, the object rotates but in a few seconds get stuck in a vertical position.

I have no idea why the Y and Z work perfectly, but I’m having such issues with the X axis or using 2 axises at the same time.

Any ideas on why 1 axis doesn’t work like the 2 others? Any help appreciated.

What you’re experiencing is probably related to gimbal lock, where rotations become super wonky. Secondly, you’re not using lerp correctly at all, which is only confounding the issue.

The solution to the gimbal lock problem is to store your rotations as absolutes and rebuild the rotation every frame.

Create six three variables at the top of your script (you can use 2 Vectors if you’d prefer but I’ll just illustrate the point):

private float currentPitch;
private float currentYaw;
private float currentRoll;

private float targetPitch;
private float targetYaw;
private float targetRoll;

Set them to match the starting value of the object:

void Start()
{
   currentPitch = targetPitch = transform.localEulerAngles.x;
  currentYaw = targetYaw = transform.localEulerAngles.y;
  currentRoll = targetRoll = transform.localEulerAngles.z;

  // your other code
}

Then adjust your calculations at such:

void CalculatePlaneAngles()
{
    //Change X for pitch up-down
  float NewXRotation = DiffYRatio * MaxYPitch;
  //Change Y for yaw left-right
  float NewYRotation = DiffXRatio * MaxXYaw;
  //Change Z for tilt left-right
  //don't forget to inverse so it tilts in the logical direction
  float NewZRotation = DiffXRatio * MaxXTilt * -1;

  targetPitch += NewXRotation;
  targetYaw += NewYRotation;
  targetRoll += NewZRotation;
}

And finally, rebuilding the absolute rotation:

void SlowChanges()
{
  currentPitch = Mathf.MoveTowards(currentPitch, targetPitch, 10f * Time.deltaTime); // change the 10f to whatever you want
  currentYaw = Mathf.MoveTowards(currentYaw, targetYaw, 10f * Time.deltaTime);
  currentRoll = Mathf.MoveTowards(currentRoll, targetRoll, 10f * Time.deltaTime);

  transform.rotation = Quaterion.Euler(currentPitch, currentYaw, currentRoll);
}

That should do it!