rigidbody constraints (local space) unexpectedly block angularVelocity setter in world space

When setting rigidbody.angularVelocity, it is applied in world space. That is, regardless of the object’s orientation, if I set rigidbody.angularVelocity to 0,0,1 it rotates on the global z axis. This is expected behavior.

The documentation says that rigidbody rotation constraints are in local space, i.e. relative to that object’s transform. This seems to be true most of the time, but surprisingly appears to switch coordinate systems and block angularVelocity setting in world space.

Here are some tests done under 5.6.0p4

Example A:

  • Place a rigidbody cube at default rotation of 0,0,0 (local transform matches world coordinates)

  • Set the angular velocity to 0,0,1 and observe it spinning on world z. This is also on the rigidbody’s local Z.

  • Constrain Z rotation on rigidbody. Observe it stop spinning. This is expected behavior.

  • Unconstrain Z rotation.

Example B

  • Place a rigidbody rotated to 0,90,0 (local forward points to world right)

  • Set the angular velocity to 0,0,1 and observe it spinning on world z, just as before. This is a rotation on the rigidbody’s local X.

  • Constrain X rotation on the rigidbody. Observe it stop spinning. This is expected behavior.

  • Unconstrain X rotation.

Example C

  • Place a rigidbody rotated to 0,90,0 (local forward points to world right)
  • Set the angular velocity to 0,0,1 again. Cube spins.
  • Constrain Z rotation on rigidbody. Observe it continue to spin on world z because the local z doesn’t need to rotate to achieve this.
  • With the Z rotation constrained, set the angular velocity again to 0,0,1. This is expected to be a redundant assignment with no effect, but it stops the rotation. Why?

What is going on in example C? Hard to imagine that something this fundamental could have been around so long if it’s not intended behavior, but it definitely is unexpected and weird from my POV. Am I missing something?

If you’d like more context: I set angularVelocity directly (every frame) to manage rotation of a rigidbody character controller with a PID. The game is psuedo 2d, so I’m hoping to rely on rigidbody constraints to some degree. AddTorque correctly navigates the constraints, but it’s harder to manage overshoots / oscillations.

Here’s a script to help test the examples. Note that it sets the angular velocity every frame so you’ll want to set applyAngularVelocity back to false before step C3 (or else it’ll trigger step C4 prematurely).

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

[RequireComponent(typeof(Rigidbody))]
public class PhysicsTest : MonoBehaviour {

    public bool applyAngularVelocity = false;
    public Vector3 angularVelocity = Vector3.zero;
    public bool reset;

    Rigidbody _rigidbody;

    private void Start()
    {
        _rigidbody = GetComponent<Rigidbody>();
    }

    private void Update()
    {
        if (applyAngularVelocity)
            _rigidbody.angularVelocity = angularVelocity;

        if (reset)
        {
            angularVelocity = Vector3.zero;
            transform.localRotation = Quaternion.identity;
            _rigidbody.angularVelocity = angularVelocity;
            reset = false;
        }
    }
}
1 Like

Having the same issue here, Unity 2018.2, and I believe it is a pretty serious one.

AddTorque seems to add the torque at the end of the physics step, instead of immediately updating angular velocity. This makes it impossible to implement any iterative constraint using it, so we have to calculate the angular velocity increment resulting from the torque ourselves and add it to the angular velocity. This does have the effect of immediately updating the angular velocity.

However setting angular velocity does not work at all when using freeze rotation constraints, except if the torque axis coincides with the freeze axis in world space.

Both limitations result in: —> it’s impossible to implement custom joints.

My guess is that the angularVelocity setter is internally considering the freeze rotation constraints in world space, instead of local space (which would be the correct thing to do)