[SOLVED] How Can I Predict The Angle That A Wheel Will Stop Spinning At?

Discussion in 'Physics' started by sean244, Apr 12, 2019.

1. In my 2d game, I have a wheel that spins using the rigidbody function _rb.AddTorque(_torque) that I call in FixedUpdate. Also in FixedUpdate, I cap the wheel's angular velocity so that it doesn't exceed a certain amount. When the wheel has finally reached its max angular velocity, I set the torque to zero so that it slowly comes to a stop

Code (CSharp):
1. private void FixedUpdate()
2. {
4.
5.     if (_rb.angularVelocity >= _maxAngularVelocity)
6.     {
7.         _rb.angularVelocity = _maxAngularVelocity;
8.         _torque = 0;
9.     }
10. }
The wheel spins exactly how I want it to, but is there any way I can predict the exact angle that the wheel will land on before it finishes spinning? I don't know much about physics, but if anyone can give me the general formula, I can try to implement it in code myself. Last edited: Apr 12, 2019
2. I think most game actually generate a random angle and spin it over time via lerping to land at the wanted position.

3. Perhaps. But I'd still like to learn how to predict the angle, just as an excuse to learn some physics.

4. Apply the same angular drag as the phyics engine does (id look for it in Nvidia website, or who ever is incharge of Box2D or how tge 2D system called), see how many iterations it would take to get to basically zero angular velocity, multiply the number of iterations with the time delta for each tuck. (Never done it, just assuming it works like that)

But why would you wanna do it like that?
Use as much smoke & mirrors as you can if its indistinguishable to the user

Unity Technologies

Joined:
May 24, 2013
Posts:
1,972
I agree with the "smoke & mirrors" thing but as an exercise it's pretty simple.

Given an angular velocity of n-degrees/sec you can obviously easily calculate the angle over time however drag (damping) changes this. You can find the damping code here (line 217).

Code (CSharp):
1. w *= 1.0f / (1.0f + h * b->m_angularDamping);
h is the time-step so in a typical case this is the "Time.fixedDeltaTime" or whatever you are using for the simulation step. w is the angular velocity.

6. Hi MelvMay,
When I input the algorithm that you posted into my FixedUpdate function, it does accurately calculate the angular velocity of the wheel as it's being dampened, but I was wondering if it would be possible to calculate the angle that the wheel will land on as soon as torque is applied to it? So right when the wheel starts spinning, we instantly know what the angle will be. Could you provide me with a little more detail on I can go about this? Thanks in advance.

Unity Technologies

Joined:
May 24, 2013
Posts:
1,972
Not sure I follow. When you add torque, it doesn't change the angle there and then. Torque is just an angular force and it's summed up until the simulation is run i.e. if you add torque several times, it'll just all be used when the simulation runs. That's the "b->m_torque" on line 207, where the angular velocity is changed with the torque you apply on the body. The inertia for that formula can be found with Rigidbody2D.inertia (actually 1/inertia). The angle is integrated at line 299.

Note you can always read the rotational angle with Rigidbody2D.rotation.

8. Ok, so that makes sense. I do add torque to the wheel on every frame in FixedUpdate, and that torque accumulates and affects the angular velocity. So my question then is: If I already know how much torque will be applied at each frame, can I write a function in the Start method that will predict how long the wheel will spin for and what its resultant angle will be? So as soon as you run the game, a text will print to the console displaying those two pieces of information.

9. You can't calculate for arbitrary torque numbers because it's relative to the object mass, like velocity, it's a force being applied, not the actual property of the object. (sorry if there are better terms for this, I didn't study physics in english)

I believe what he wants is a function that looks like this
Code (CSharp):
1. float GetFinalAngle(float angularVelocity, float angularDrag/*, etc.*/){
2. //do calculations
3. return angle;
4. }
where you input the angular velocity of the target with all the needed information and it returns you the angle that the object will come to rest at.
I also assume he wants the angle as the euler angle of what ever axis spin is in 2D (if Y is for 3D i assume Z is for 2D?)

I'd fill up more of the function but I think I've already contributed as much as I can to this.

10. That's pretty much it; I just don't know how to do the calculations.

Last edited: Apr 15, 2019

Unity Technologies

Joined:
May 24, 2013
Posts:
1,972
Here is an example of one way to do it. This example is based upon the most accurate way of doing it which is performing time-steps and waiting until the body has gone to sleep. You can wait until the body angular velocity has gone below the angular sleep tolerance but it is allowed to do that for the sleep period meaning it moves slightly further depending on the time-to-sleep.

Just be careful not to try this method with no damping otherwise it'll just stop. There's a more complex way to do this that would avoid that situation or alternately you could just add in an interation limit that throws an error.

This example should provide you with enough accuracy though.

Last edited: Apr 16, 2019
12. When I try to open that project, I get the following error If I click 'Retry', I get the same error, and if I click 'Continue', I get errors in the console I'm using Unity 2018.3.9f1

Last edited: Apr 16, 2019

Unity Technologies

Joined:
May 24, 2013
Posts:
1,972
Try this link, the same project created in 2018.3.12f1. Note that resetting the packages works too.

14. Wow, thank you so much for taking the time to write that out for me. I really appreciate it. Just one thing though: I notice that you spin the wheel by setting it's angular velocity in the Start function, whereas I do it by constantly adding torque in FixedUpdate, and once the velocity reaches a certain amount, I cap it at that amount and shut off torque.
Code (CSharp):
1. private void FixedUpdate()
2. {
4.     if (_rb.angularVelocity >= _maxAngularVelocity)
5.     {
6.         _rb.angularVelocity = _maxAngularVelocity;
7.         _torque = 0;
8.     }
9. }
Is there any way to still predict the angle by spinning the wheel the way that I do it? I prefer adding torque because it looks a little more natural to just immediately setting the velocity to a certain amount. Thanks again for the code. I know you didn't have to spend as much time as you did on it, so I really appreciate it.

15. I don't know if melvs code works with that, but you can gradually assing an increasing velocity from 0 to max with lerp over x time.

Unity Technologies

Joined:
May 24, 2013
Posts:
1,972
Yes, but you confirmed you wanted a function like this which specified the velocity:
Code (CSharp):
1. float GetFinalAngle(float angularVelocity, float angularDrag/*, etc.*/)
Either way, how long it takes to slow down is always based upon its velocity at that point, it has nothing whatsoever to do with forces being added. Just run the function after they've been added.

In your fixed-update, you add torque as a force which won't get added to the angular velocity until the simulation runs. Just change that to be an impulse using ForceMode2D.Impulse which changes the angular velocity immediately (you'll need to scale it by the "Time.fixedDeltaTime" to get the same force). TBH though, this is exactly the same as adding a value to "Rigidbody2D.angularVelocity" directly so you might as well do that. Then just run the function after you've done it.

Unity Technologies

Joined:
May 24, 2013
Posts:
1,972
Try downloading this one that accelerates the object, continuously predicting the rotation at rest. Same function though.

https://gyazo.com/45f3c451408661892f525959970d6632

Code (CSharp):
1. using UnityEngine;
2. using UnityEngine.Assertions;
3.
4. [RequireComponent(typeof(Rigidbody2D))]
5. public class CalculateAngle : MonoBehaviour
6. {
7.     public float Torque = 500.0f;
8.     public float MaxAngularVelocity = 1000.0f;
9.     public float AngularDrag = 0.7f;
10.
11.     private float PredictedRotation;
12.     private Rigidbody2D Body;
13.
14.     void Start()
15.     {
16.         Assert.raiseExceptions = true;
17.
18.         Body = GetComponent<Rigidbody2D>();
19.         Body.angularVelocity = 0f;
20.         Body.angularDrag = 0f;
21.     }
22.
23.     private void FixedUpdate()
24.     {
25.         if (Torque == 0f)
26.             return;
27.
28.         var timeStep = Time.fixedDeltaTime;
29.
30.         var angularVelocity = Body.angularVelocity + Torque * timeStep;
31.         if (angularVelocity >= MaxAngularVelocity)
32.         {
33.             angularVelocity = MaxAngularVelocity;
34.             Body.angularDrag = AngularDrag;
35.             Torque = 0f;
36.         }
37.
38.         Body.angularVelocity = angularVelocity;
39.         PredictedRotation = PredictRotation(Body.rotation, angularVelocity, AngularDrag, timeStep);
40.     }
41.
42.     void OnGUI()
43.     {
44.         GUILayout.Label(\$"Predicted Rotation: {PredictedRotation} ({Mathf.Repeat(PredictedRotation, 360-Mathf.Epsilon)})");
45.         GUILayout.Label(\$"Current Rotation: {Body.rotation} ({Mathf.Repeat(Body.rotation, 360-Mathf.Epsilon)})");
46.         GUILayout.Label(\$"Current Velocity: {Body.angularVelocity}");
47.     }
48.
49.     float PredictRotation(float rotation, float angularVelocity, float angularDrag, float timeStep)
50.     {
51.         // If drag is zero then this integration method becomes an infinite loop.
52.         Assert.IsTrue(angularDrag > 0f);
53.
54.         var sleepTime = 0f;
55.         var sleepToleranceSqr = Physics2D.angularSleepTolerance * Physics2D.angularSleepTolerance;
56.
57.         // Iterate until the body would be asleep.
58.         while(sleepTime < Physics2D.timeToSleep)
59.         {
60.             angularVelocity *= 1f / (1f + timeStep * angularDrag);
61.             rotation += timeStep * angularVelocity;
62.
63.             if ((angularVelocity * angularVelocity) <= sleepToleranceSqr)
64.                 sleepTime += timeStep;
65.         };
66.
67.         return rotation;
68.     }
69. }
70.

Last edited: May 1, 2019
SparrowsNest likes this.
18. Dude, thank you so much! I don't know how to repay you! That's exactly what I wanted. It works PERFECTLY. I'm going to study this code and try to learn as much as I can from it. Thank you again. You're AWESOME.

MelvMay likes this.

Unity Technologies

Joined:
May 24, 2013
Posts:
1,972
You're most welcome, good luck with your project.

Unity Technologies

Joined:
May 24, 2013
Posts:
1,972
Before spinning the wheel it's stationary so you already know the angle (Rigidbody2D.rotation) so perhaps I don't follow the question.

21. Melv, if I step on a train and throw a ball out the window at some point on the journey, can I predict where the ball will land when I buy my ticket? Unity Technologies

Joined:
May 24, 2013
Posts:
1,972
Here's the algorithm for that. https://gph.is/1pScDZs

SparrowsNest, sean244 and halley like this.
23. Hi MelvMay,

I was just wondering why you take the square of the sleep tolerance, rather than just sleep tolerance by itself?

Unity Technologies

Joined:
May 24, 2013
Posts:
1,972
EDIT: The reason is that the angular velocity can be negative. By using the square, it's always positive.

25. Ok, since it's a float, can I just do this instead
Code (CSharp):
1. if (Mathf.Abs(angularVelocity) <= Physics2D.angularSleepTolerance)

Last edited: Apr 30, 2019

Unity Technologies

Joined:
May 24, 2013
Posts:
1,972
comparing squares is often done because it's quicker. Use whatever you want though, it's not critical here.

27. Thanks. Just one other thing: When I wanted to test the Assert function, I set the angular drag to zero to see if it throws an error. Unfortunately the program crashes before it can print anything to the console. Should I still keep that line of code? I'm not really familiar with assertions, as I've never had to use them before.

Unity Technologies

Joined:
May 24, 2013
Posts:
1,972
Seems raising exceptions for asserts is off by default so needs turning on. If you look at the code I posted above, I changed it to turn that option on in the "Start" method.

You can equally check for zero then write-out a message and return.

29. Can the assertion print a custom error? Right now it prints a very cryptic message saying:
AssertionException: Assertion failure. Value was False
Expected: True

Unity Technologies

Joined:
May 24, 2013
Posts:
1,972
It's in the docs here.

31. Thanks. It still prints a cryptic error, but now when I click on that error, it shows my custom message inside the description. I think that's good enough. Thank you for all your help. I really appreciate it.