cancelling out angularVelocity with forces
I'm creating a space ship game with Newtonian forces. Rather than rotating and translating the ship directly, when the user presses a direction the code fires thrusters in the opposite direction to move or rotate the ship in the right direction - this all works fine.
I'm now trying to add an "auto stop" button so that when the user gets into a spin they can hold down the button and the code will fire the appropriate thrusters to reverse the spin and bring them to a stop.
This works fine for translation and it works fine for rotations in a single axis, but when it's spinning in more than one axis it gets into a situation where it only makes the problem worse - the numbers seem to get flipped so it fires the wrong thrusters.
This sounds like gimbal lock to me, but I've spent all day googling and trying suggestions without success.
Usually the solution to gimbal lock is either separate the axes so each gameobject is only rotated in one axis (which is not possible because a ship floating in space is free to rotate in all axes) or to use quaterions instead of Euler angles (which is not possible because all I have to work with is Rigidbody.angularVelocity which is a Vector3).
What I'm doing is:
get Rigidbody.angularVelocity
if it's positive in the X axis then fire the pitch down thrusters, if negative pitch up etc.
That's basically it. When it's doing this if I get it to log the angularVelocity and it shows positive X, it'll correctly fire the pitch down thrusters. But this makes it worse and I find that if I manually fire the pitch up thrusters it fixes the spin. So the problem was that it's showing positive X, which must be wrong.
Answer by Edy · Apr 19, 2021 at 04:17 PM
Note that in Unity / PhysX freely rotating objects rotate around a single static axis defined by the center of mass and the angular velocity. Contrarily to reality, this axis of rotation and the angular velocity never change unless there's some force or torque applied on the object.
A possible solution might be considering the angular velocity as an axis of rotation instead of a set of 3 angular velocities. Then you could figure out the positions of the thrusters with respect to this axis of rotation, and fire them at the appropriate times in order to align this axis of rotation with one of the local axes of the ship (i.e. the one that is closer).
When the axis of rotation is aligned with a local ship axis then the local angular velocity vector should have a single non-zero value. You could then cancel out that velocity using the proper pair of trusters.
I wouldn't try implementing realistic algorithms here because most probably they will require considering the momentum of inertia of the ship. This aspect is severely simplified in PhysX, so such algoritms won't likely work.
Thanks for the advice, I'm not really sure how to write code to make it happen though.
I can use Vector3.Dot to compare the angularVelocity to each local axis to find the one it's closest to.
Then I use Vector3.Cross to get an axis I can rotate in to move toward that axis?
I don't understand the math, I'm just failing...
Answer by SpaceManDan · Apr 20, 2021 at 04:01 AM
Here you go, this has everything you need in it to stabilize torque. You have a little work to do to tailor it to your project but this is how you do it.
public class TorqueStabilizer : MonoBehaviour {
public float stability = 0.3f;
public float speed = 2.0f;
// Update is called once per frame
void FixedUpdate () {
Vector3 predictedUp = Quaternion.AngleAxis(
rigidbody.angularVelocity.magnitude * Mathf.Rad2Deg * stability / speed,
rigidbody.angularVelocity
) * transform.up;
Vector3 torqueVector = Vector3.Cross(predictedUp, Vector3.up);
// Uncomment the next line to stabilize on only 1 axis.
//torqueVector = Vector3.Project(torqueVector, transform.forward);
rigidbody.AddTorque(torqueVector * speed);
}
}
Thanks! I don't suppose you could explain how it works? I'd rather understand so I can improve
Sure, lets go through it. I added some stuff so you can play with it. Ok step by step now, here we set up our variables
public class TorqueStabilizer : MonoBehaviour
{
public Rigidbody rb;
public float stability = 0.3f;
public float speed = 2.0f;
public Vector3 wantedUp = new Vector3(0f, 1f, 0f);
private void Start()
{
rb = GetComponent<Rigidbody>();
}
private void FixedUpdate()
{
if (Input.GetKey(KeyCode.Z))
{
Here we get a vector of rotation and a torque power to apply based on how fast it's going. The faster it's rotating, the higher the torque will be. We make sure to convert it from a radian so that we can use a Vector 3.
Vector3 predictedUp = Quaternion.AngleAxis(
rb.angularVelocity.magnitude * Mathf.Rad2Deg * stability / speed,
rb.angularVelocity
) * transform.up;
Here we get our torqueVector cross product of our prediction verses the EulerAngle we want to orient too "wantedUp". Change wantedUp to the EulerAngle you want to stabilize too. Say for example an input vector from a controller.
Vector3 torqueVector = Vector3.Cross(predictedUp, wantedUp);
If you don't want to stabilize all the rotation but only the rotation on 1 axis you'd uncomment the next line. Say for example you want to keep y rotation but want to cancel out z rotation. Play with it on and off to see how it behaves.
// Uncomment the next line to stabilize on only 1 axis.
//torqueVector = Vector3.Project(torqueVector, transform.forward);
Apply our math to a torque force...
rb.AddTorque(torqueVector * speed);
}
I added this so you can get some random spin to try to stabilize.
if (Input.GetKey(KeyCode.L))
{
rb.AddTorque(new Vector3(30f, 30f, 30f));
}
}
}
So put a cube in your scene with no parent. Add a rigidbody, set angular velocity to 1 (so it doesn't spinn too much) and turn off gravity. Add this script. press play and then hold L. It will start to spin. Hold Z to activate the stabilizer. Change wantedUp in the inspector to see how it behaves.
As far as your game goes, all you need now is a EulerAngle to call up and assign it to wantedUp.
Have fun!
Sorry forgot to say,
I know you wanted to do this with ship thrusters so what you do is read the final torqueVector.
Figure out which thruster is firing on the x axis and feed torqueVector.x to it, y thruster torquevector.y, z thruster torqueVector.z.
Obviously you'll need to write some code to make this work but thats the logic and you should be able to follow it on your own. And it will be good practice since your getting into Newtonian stuff, this is like 101. You can do it though!
So Quaternion.AngleAxis gives me a rotation in the direction of the angular velocity, multiplying it by transform.up gives me a vector rotation in local space?
And then Vector3.Cross gives me the right angle to that rotation in world space?
I don't think I understand what Vector3.Project does at all.
I tried feeding torqueVector directly to the appropriate thrusters:
It works for roll and pitch, except that it overcorrects and goes back and forth past stationary - I can fix that by adjusting it to give less thrust as it approaches zero.
If the ship's up is pointing at world up and I give it a yaw, then torqueVector is zero and it never fires the thrusters. If ship's up is not pointing at world up then when it tries to fix the yaw it ends up rotating in all axes, and never corrects it.
Your answer

Follow this Question
Related Questions
Control accurate velocity and angular velocity 0 Answers
how to calculate rotation 0 Answers
i wanna set max velocity, max angular velocity to joint 0 Answers
Angular Velocity not correctly rotating objects 1 Answer
Ball slows down too fast.. why? 1 Answer