- Home /
Restricting angularVelocity on one particular direction
EDIT: I created a video that describes this question and shows the issue. Please watch it here:
https://www.youtube.com/watch?v=sCErZYzg-yU
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.
This problem is very hard to visualize for me. I kinda get it, but not well enough to offer a fix. It sounds to me like you would want to fix the angular velocity to some multiple of the velocity and the axis of rotation to be the cross product of the Vector3.up and the velocity vector.
$$anonymous$$aybe you could put your app on a web page, or take a video and put it on YouTube so we can visualize the problem.
robertbu, I'll see what I can do. $$anonymous$$aybe I'll make a video. It would probably be the easiest way to show what I'm trying to achieve. Thanks.
robertbu, I have created a video. Please check it out here: https://www.youtube.com/watch?v=sCErZYzg-yU. Thank you!
Answer by Owen-Reynolds · Aug 01, 2013 at 12:36 PM
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.
Oh, that's very interesting. So, after the calculations at the top, it's as if the ball is always sitting on a flat surface for the rest of the calculations, right?
One issue I would have is figuring out the slope at every update. Is there a command, or commands, in Unity that could tell me the slope of wherever the ball is currently at assu$$anonymous$$g it's in contact with the 'ground'? Consider a hilly environment. The slope would be constantly changing.
I realize a single point doesn't really have a slope, so to calculate that, would it have to take in to consideration the current vector of the object in the world?
EDIT: Now that I'm thinking this over, the ball's current vector (relative to the world) sort of is the slope, right? Could I get the 'slopeDir' from that at each update?
If you use the "slope arrow" as the local forward, yes it's like the ball is on a flat surface facing North. I used the direction only (not the slope's +/-Y) to get just "like it's facing north."
People often use the current velocity (flattened with y=0 or not) as a ball's current forward. But, in your case, it seems your game has to "know" which way is forward along the path you've made. $$anonymous$$aybe by marking with empty (faced "path forward") transforms, or some some of spline.
For slopes, you can raycast down and grab the normal. Setting y=0 gives the direction of the downward slope.