Make a moving rigidbody stop exactly at a given distance

Discussion in 'Physics' started by 40detectives, Aug 20, 2019.

I have an object that only moves along Z-axis, its rigidbody has a mass of 1 and a drag of 0.2.

That object accelerates till MaxVelocity and then moves at that constant velocity in the Z-axis.

When the user pushes the brakes I want to add a force on that rigidbody to make it stop completely at a given distance (lets say 10 meters from where it pushed the button).

Using:  Solve for t and replace in the second equation and you get: Where v is final velocity and in my case I want it to be zero.

-----------

My attempt so far

I call this method every FixedUpdate since the user pushed brakes:

Code (CSharp):
1.
2. private float CalculateBrakeAccel(float targetPos)
3. {
4.     // targetPos has the position in the z axis the object should stop
5.     // i.e.: velocity is 0.0f
6.     float remainingDistance = (targetPos - rigidbody.position.z);
7.     // formula: a = 0.5 * v0^2 / (e0 - e)
8.     float brake = 0.5f * rigidbody.velocity.sqrMagnitude / remainingDistance;
9.     return brake;
10. }
11.
That alone always results in a braking distance less than the expected (e.g.: passing 10m as braking distance makes the object stop after ~8.4m). I thought it could be drag. So I tried to compensate that braking accel with the drag (for the drag calculation I used the formula found here, there are similar answers in others threads and Q&A):

Code (CSharp):
1.
2. public float Accel = 8f;
3. private float maxVelocity = 19.44f; // in m/s, approx. 70km/h
4. [...]
5. void FixedUpdate ()
6. {
7.    Vector3 forwardForce;
8.    if (playerController.PowerInput < 0.0f)  // if user braking, PowerInput = -1
9.    {
10.        // BrakeTarget is the transform.position.z where the user pushed the brakes + 10 meters
11.        float brakeAccel = CalculateBrakeAccel(playerController.BrakeTarget);
12.        forwardForce = new Vector3(0.0f, 0.0f, brakeAccel * playerController.PowerInput);
13.        Vector3 drag = rigidbody.velocity * Mathf.Clamp01(1f - rigidbody.drag * Time.fixedDeltaTime);
14.        if (drag.z > 0.0f)
15.            forwardForce += drag;
16.        else
17.        {
18.            forwardForce -= drag;
19.        }
21.    }
22.    if (!CapAtMaxSpeed())    // if max. vel not reached, accel force can be applied:
23.    {
24.        forwardForce = new Vector3(0.0f, 0.0f, playerController.PowerInput * Accel);
26.    }
27. }
28.
29. bool CapAtMaxSpeed()
30. {
31.    // velocityMagnitude == rigidbody.velocity.magnitude but without sqrt() operation.
32.    velocityMagnitude = Vector3.Dot(rigidbody.velocity, myTransform.forward);
33.    if (velocityMagnitude > maxVelocity)
34.    {
35.        rigidbody.velocity = rigidbody.velocity.normalized * maxVelocity;
36.        return true;
37.    }
38.    return false;
39. }
40.
That code gave the best results till now for any given distance (but with that specific Accel and maxVelocity). For example, it makes it stop completely something in between 9.93-9.97 meters for a distance of 10 meters (which I would accept as valid enough and a precision issue with floating operations / and the fixed timestep value).

The problems:
1. If I encrease the Accel or the maxVelocity, the distance the object reaches full stop is progressively less and less than the one I'm passing to the script.

2. If I push the brakes while not at maxVelocity and still accelerating (i.e., applying a positive force along Z-axis), the braking distance is way shorter. Common sense suggests it should be larger in any case. And CalculateBrakeAccel() is using the current velocity at any given moment so the correct accel should be a calculated.

nitrofurano likes this.
2. But you want always to stop after "10 m" or only at max speed.
So at lower speed you will stop faster ?

3. Always after 5, 10, or X meters... It's a parameter.

It should do it at max speed or while still accelerating in z-axis. Right now it does not do neither if you change the accel force or the max speed.

Last edited: Aug 22, 2019
4. First of all during breaks disable drag (set to 0) this will simplify equations.

Known:

BodyV => velocity of the body the moment breaks were turned on.
Dist => distance you want your body to stop.

Unknown:
Time => time to stop.
Accel => your deacceleration.

Equations:
Time = Dist / (0.5 * BodyV ) // use average body speed to calculate time of deacceleration
Accel = (0 - BodyV) / Time // use this time to calculate deacceleration

Use Accel every FixedUpdate on your rigidbody, but not as you do it in your source.
You have to use parameter to AddForce to specify that it is acceleration and not a force.

5. Ok I'm gonna try that, one question though, if mass is 1, is does not end up with the same result ForceMode.Force than ForceMode.Acceleration?

6. If you use ForceMode.Acceleration you don't need mass in your equation, any mass will do.
And yes with my equations you should use ForceMode.Acceleration.

ForceMode.Force
will give you different results for different mass.

40detectives likes this.
7. Schazzwozzer

Joined:
Jul 27, 2012
Posts:
18
I've bashed my head against a similar problem before, and when I found myself reading up on integral calculus, I decided it was time to let the computer figure it out for me. If you're not running this code multiple times a frame, it may work for you as well.

Basically, you input your known variables (velocity, desired distance, you could incorporate rigidbody.drag), and start with a guess at your deceleration rate. Then the heuristic repeatedly runs little simulations, revising your guess until it arrives at the desired result.

This is mostly pseudocode, and not necessarily adapted to your exact needs.

Code (CSharp):
1.
2. // Your known variables.
3. float initialVelocity = 8;
4. float desiredDistance = 10;
5.
6. // Start with a guess at the required deceleration.
7. // Try to start this out 'in the ballpark', if you can.
8. float deceleration = 10;
9. // The amount by which deceleration is revised after every iteration.
10. float delta = deceleration * 0.5;
11. // Track whether delta was previously revised upward or downward.
12. bool decreased;
13.
14. // Avoid infinite loops or big performance hits.
15. int i = 0;
16. int maxIterations = 100;
17.
18. while( i < maxIterations )
19. {
20.     // The working values for this iteration.
21.     float distance = 0;
22.     float velocity = initialVelocity;
23.
24.     // A simple physics simulation. Decelerate an
25.     // imaginary object and see how far it moved.
26.     while( velocity > 0 )
27.     {
28.         distance += velocity * deltaTime;
29.         velocity -= deceleration * deltaTime;
30.     }
31.
32.     // 0.01 here is the minimum acceptable inaccuracy for the heuristic.
33.     if( Mathf.Abs( distance - desiredDistance ) < 0.01 )
34.     {
35.         // Satisfactory solution found.
36.         return deceleration;
37.     }
38.     else
39.     {
40.         // Here is where the estimate is revised.
41.         // If the goal is exceeded, delta is halved and the heuristic reverses direction.
42.         if( distance > desiredDistance )
43.         {
44.             if( !decreased )
45.             {
46.                 delta /= 2f;
47.                 decreased = true;
48.             }
49.             deceleration -= delta;
50.         }
51.         else
52.         {
53.             if( decreased )
54.             {
55.                 delta /= 2f;
56.                 decreased = false;
57.             }
58.             deceleration +=delta;
59.         }
60.     }
61.
62.     ++i;
63. }
64. // If the code reaches this point, the heuristic has exceeded its maximum iterations. Throw an error or return some default value.

8. I've been trying your suggestion (with 0 drag and all) but more or less got the same results. It stopped faster because of the suggestion of using the 'peak' velocity at where the brakes were turned on during all the iterations of the FixedUpdate.

9. Thanks for your post,
I think I understand the general idea behind your solution (haven't tested it yet), but just to be sure I'm getting it:

1 - You said "If you're not running this code multiple times a frame, it may work for you as well." That means you're doing this outside of a FixedUpdate. Right? So you're returning just one deceleration value.
2 - That one deceleration value is the one that, applied "constantly" (I mean in each following FixedUpdate) can reach the full stop at the desiredDistance.
3 - Then, I don't need to calculate the heuristic more than once (just after brakes are pushed). Because that one deceleration value that is returned resolves the problem with a tolerance of 0.01

10. Schazzwozzer

Joined:
Jul 27, 2012
Posts:
18
Yes, it sounds like you have the right idea. Assuming your only concern is stopping the object at a specific distance, and it's not, like, re-accelerating or easing off the brakes, you should be able to calculate the deceleration just once, after the brakes activate.

I only said "multiple times a frame" in case you need hundreds or thousands of objects to be doing this. In which case it might become an issue... though, really, the heuristic isn't that complex.

Oh, and just to be clear, my tolerance value of 0.01 is arbitrary, and can be set to whatever you need. Finer values will of course result in more iterations though (my maxIterations value was arbitrary as well).

11. Tried quite a few deceleration and delta values (I kept deceleration near to the first value calculated by my code in the first post). It reaches maxIterations or crashes Unity Editor. Even with a tolerance of 0.1f. So far not a single success. I wonder why.

I'm not sure why the editor crashes, before throwing any erros, but it looks like the heuristic is taking up much more time than the expected? It crashes when the velocity the brakes are turn on is high enough (~19m/s), which means more iterations in the 'simple physics simulation loop'.

Last edited: Aug 25, 2019
12. Schazzwozzer

Joined:
Jul 27, 2012
Posts:
18
Sorry about that. Here's a .NET Fiddle that I can confirm to be working: https://dotnetfiddle.net/0rLE0U

Main changes:
• The "distance > desiredDistance" check before revising delta should have been a less than.
• Skip simulations when deceleration is 0 or less.
• Previously, the simulation loop would complete when velocity had dropped to 0. It now also completes when the desired distance has been exceeded.
Obviously, this isn't running in Unity, so I can't be sure it's spitting out correct values. If you decide to implement it, let me know how it works out.

Oh, and I should probably point out that it's still timing out for velocities above 500. Not sure why, but it may be related to my initial guess values for deceleration and delta. As I said, the better guesses you can provide the heuristic, the better it works.

13. Hi @Schazzwozzer thanks a lot for revising it, I've made some quick tests with it and didn't get it to brake at the correct distance. But maybe this time I've made a typo or something implementing it in Unity.

- - -

For those interested in solving this kind of problem, I got more than acceptable results with this Proportional Derivative Controller over here (Unity Answers) from user aldonaletto. It kind of starts being less accurate with bigger initial accel thrush, and a bigger 'peak' velocity; but it is related to the control variables you feed it (toVel, maxForce, gain), so I can tweak it if need it.

This way I also solve the issue I had reaching destination at low speeds, because it keep the velocity at which player turns on brakes constant if it is too far away from target, and slows down when is getting near.