Help Needed: Preventing Roll Rotation in Unity Using AddTorque/AddRelativeTorque

Objective

I want to achieve the following rotations in Unity:

Pitch (local coordinate system)**: Follow controller input.

  • Yaw (global coordinate system): Follow controller input.
  • Roll (local coordinate system): Prevent this.
  • I want to achieve these rotations using only AddTorque or AddRelativeTorque (I don’t want to use Slerp due to potential issues with passing through walls).

Problems and Uncertainties

The following issue is occurring:

  • When rotating pitch and yaw simultaneously, there is an unnatural behavior (it seems to jitter?). If it weren’t for this, the behavior would be ideal.

Relevant Source Code

void ApplyTorque()
{
    rigidBody.AddTorque(0, torqueInput.y * Mathf.PI * coefficient, 0);
    rigidBody.AddRelativeTorque(new Vector3(torqueInput.x * Mathf.PI * coefficient, 0, 0));
    rigidBody.AddTorque(-rigidBody.angularVelocity * Mathf.PI * coefficient);

    ApplyTorqueToZeroZRotation();
}

[SerializeField] private float ratio = 20f;
void ApplyTorqueToZeroZRotation()
{
    Quaternion q = rigidBody.rotation;
    
    float curPitch = Mathf.Atan2(2 * q.x * q.y + 2 * q.w * q.z, q.w * q.w + q.x * q.x - q.y * q.y - q.z * q.z);
    float curYaw = Mathf.Asin(-2 * q.x * q.z + 2 * q.w * q.y);
    float curRoll = Mathf.Atan2(2 * q.y * q.z + 2 * q.w * q.x, q.w * q.w - q.x * q.x - q.y * q.y + q.z * q.z);
    
    Quaternion targetRotation = SetQuaternionForZeroRoll(q);

    Quaternion rotationDifference = targetRotation * Quaternion.Inverse(q);

    if (rotationDifference.w < 0)
    {
        rotationDifference = new Quaternion(-rotationDifference.x, -rotationDifference.y, -rotationDifference.z, -rotationDifference.w);
    }

    Vector3 rot = new Vector3(rotationDifference.x, rotationDifference.y, rotationDifference.z);
    rigidBody.AddTorque(rot * ratio);
}

Quaternion SetQuaternionForZeroRoll(Quaternion q)
{
    // Calculate q.z
    q.z = -q.x * q.y / q.w;

    // Normalize the quaternion
    float magnitude = Mathf.Sqrt(q.w * q.w + q.x * q.x + q.y * q.y + q.z * q.z);
    q.w /= magnitude;
    q.x /= magnitude;
    q.y /= magnitude;
    q.z /= magnitude;

    return q;
}

Attempts and Research

  • Searched through Google.
  • Modified the source code on my own.
  • Asked acquaintances.
  • Tried other methods.

Results of the above:

I tried applying Slerp before assigning to targetRotation, but if the Slerp speed is too slow, the effect of suppressing roll rotation diminishes, and if the speed is too fast, this issue occurs, so I gave up as it doesn’t seem to be a fundamental solution.

Also, I tried the following code, but when the pitch angle reaches 90 degrees or -90 degrees, attempting to rotate beyond that causes endless roll rotation (in the local coordinate system) instead of continuing the pitch rotation.

Quaternion currentRotation = rigidBody.rotation;
Quaternion targetRotation = Quaternion.Euler(new Vector3(currentRotation.eulerAngles.x, currentRotation.eulerAngles.y, 0));

Quaternion rotationDifference = targetRotation * Quaternion.Inverse(currentRotation);

if(rotationDifference.w < 0){
  rotationDifference = new Quaternion(-rotationDifference.x, -rotationDifference.y, -rotationDifference.z, rotationDifference.w);
}

var rot = new Vector3(rotationDifference.x, rotationDifference.y, rotationDifference.z) * 20f;
rigidBody.AddTorque(rot);

thank you.

At first, I was using the ToAngleAxis() function, but it didn’t work well.

Even after rewriting the code to use quaternion functions, the following issue persists:

Issue

  • When attempting to change the pitch angle beyond 90 degrees or -90 degrees, the pitch angle does not change, and the roll rotation continues instead.

Code

    void ApplyTorqueToZeroZRotation()
    {
        Quaternion q = droneRigidbody.rotation;
        
        Quaternion targetRotation = SetQuaternionForZeroRoll(q);

        Quaternion rotationDifference = targetRotation * Quaternion.Inverse(q);

        if (rotationDifference.w < 0)
        {
            rotationDifference = new Quaternion(-rotationDifference.x, -rotationDifference.y, -rotationDifference.z, -rotationDifference.w);
        }

        Vector3 rot = new Vector3(rotationDifference.x, rotationDifference.y, rotationDifference.z);
        droneRigidbody.AddTorque(rot * ratio);
    }

    Quaternion SetQuaternionForZeroRoll(Quaternion q)
    {
        Vector3 euler = q.eulerAngles;
        
        euler.z = 0;

        return Quaternion.Euler(euler);
    }