Restricting angularVelocity on one particular direction

EDIT: I created a video that describes this question and shows the issue. Please watch it here:

How could I prevent applying torque in a particular direction if angularVelocity in that direction exceeds a specific ‘speed limit’, while still applying torque in all other directions that have not exceeded the speed limit?

Problem and what I’m trying to achieve:

I have the player able to control a rigidbody ball through an obstacle course. That part works well. I use addTorque on the ball using a vector based on player input relative to the camera’s rotation around the y axis.

The problem is if I set the ball’s maximum angular velocity to an arbitrary speed limit, it does prevent a player from exceeding that speed, but the ball no longer rolls down slopes in a believable manner. It hits that max angular velocity and rolls slowly down the slope at that constant velocity.

What I’d like to achieve is, when the ball is on a downward slope, it can accelerate (because of gravity*) to virtually any speed as long as it continues down that slope. The challenge, though, is I need a way to limit how fast the player can cause the ball to spin via input. I haven’t figured out a reliable way to do that.

What I’ve tried so far:

I could just say if the ball’s angularVelocity.magnitude > speedLimit, then ignore player input for that update, but that would cause the ball to be completely uncontrollable once it reached a certain speed. I want players to still be able to steer the ball and resist gravity on a downward slope.

I tried using a dot product, comparing the input vector with the current angularVelocity’s vector. I scripted that if the two vectors were dissimilar enough, the player’s input would be processed. This method proved to be VERY unreliable. The speed would only sometimes be limited to the speedLimit value.

I tried checking the ball’s velocity, rather than angulerVelocity. I checked each direction (x, y, z) separately and, if velocity in that direction was higher than the speed limit (or lower than the speed limit * -1), I ignored input on that axis. This was the one that almost worked OK. It limited the speed pretty reliably, but for reasons I couldn’t figure out, the player input was ignored at times when it shouldn’t have been.

Summary of question:

How can I have a high maximum angular velocity, but prevent users from exceeding a different, lower maximum angular velocity when processing player input and, based on the same player input, apply torque to any directions where the angular velocity speed limit has not been exceeded even if some directions have exceeded this speed limit?

Thank you.

Most problems like this can be solved by using a local coordinate system. Find the current “forward”; get copies with everything spun by those numbers; check/adjust things using x=sideways, z=forward; then spin everything back to get real final values.

Suppose you know the current slope is facing slopeDir. Find the y-spins to covert to and from local coords:

// find the spin around y to convert to/from local coords:
Vector3 flatDir = slopeDir; flatDir.y=0;
Quaternion toLocal = Quaternion.FromToRotation(flatFwd, Vector.forward);
// NOTE: this will (almost) always be a y-rotation
Quaternion fromLocal = Quaternion.FromToRotation(V3.forward, flatFwd);

// get local everything:
Vector3 localVel = toLocal * rigidbody.velocity;
Vector3 localAngV = toLocal * rigidbody.angularVelocity;

Now it’s like the ball is always moving due north, so easier to work with the numbers. It happens that angularVelocity is in radians (angVel.x=6.28 will make 1 spin/second.)

if(localAngV.z>45*Mathf.Deg2Rad) // ball is spinning quickly right
if(localVel.x<1) // moving left on the slope ...
if(localAngV.x<0) // ball is spinning backwards!!

Once you’ve adjusted everything, convert back to the real numbers:

rigidbody.velocity = fromLocal * localVel;
rigidbody.angVel = fromLocal * localangV;

One problem is if the ball is facing exactly backwards, Quat.FTrot will sometimes decide not to give a y-rotation. Instead it will spin over the top (or under the bottom.) Can check for that specially.

transform.TransformDirection (and InverseTD) do the same thing as the first step – convert to and from local coords. But they would use the ball’s forward arrow (which is random for a spinning ball,) and include any up/down tilt.