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:
private float CalculateBrakeAccel(float targetPos)
{
// targetPos has the position in the z axis the object should stop
// i.e.: velocity is 0.0f
float remainingDistance = (targetPos - rigidbody.position.z);
// formula: a = 0.5 * v0^2 / (e0 - e)
float brake = 0.5f * rigidbody.velocity.sqrMagnitude / remainingDistance;
return brake;
}
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):
public float Accel = 8f;
private float maxVelocity = 19.44f; // in m/s, approx. 70km/h
[...]
void FixedUpdate ()
{
Vector3 forwardForce;
if (playerController.PowerInput < 0.0f) // if user braking, PowerInput = -1
{
// BrakeTarget is the transform.position.z where the user pushed the brakes + 10 meters
float brakeAccel = CalculateBrakeAccel(playerController.BrakeTarget);
forwardForce = new Vector3(0.0f, 0.0f, brakeAccel * playerController.PowerInput);
Vector3 drag = rigidbody.velocity * Mathf.Clamp01(1f - rigidbody.drag * Time.fixedDeltaTime);
if (drag.z > 0.0f)
forwardForce += drag;
else
{
forwardForce -= drag;
}
rigidbody.AddForce(myTransform.TransformDirection(forwardForce));
}
if (!CapAtMaxSpeed()) // if max. vel not reached, accel force can be applied:
{
forwardForce = new Vector3(0.0f, 0.0f, playerController.PowerInput * Accel);
rigidbody.AddForce(myTransform.TransformDirection(forwardForce));
}
}
bool CapAtMaxSpeed()
{
// velocityMagnitude == rigidbody.velocity.magnitude but without sqrt() operation.
velocityMagnitude = Vector3.Dot(rigidbody.velocity, myTransform.forward);
if (velocityMagnitude > maxVelocity)
{
rigidbody.velocity = rigidbody.velocity.normalized * maxVelocity;
return true;
}
return false;
}
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:
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.
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.
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.
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.
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.
// Your known variables.
float initialVelocity = 8;
float desiredDistance = 10;
// Start with a guess at the required deceleration.
// Try to start this out 'in the ballpark', if you can.
float deceleration = 10;
// The amount by which deceleration is revised after every iteration.
float delta = deceleration * 0.5;
// Track whether delta was previously revised upward or downward.
bool decreased;
// Avoid infinite loops or big performance hits.
int i = 0;
int maxIterations = 100;
while( i < maxIterations )
{
// The working values for this iteration.
float distance = 0;
float velocity = initialVelocity;
// A simple physics simulation. Decelerate an
// imaginary object and see how far it moved.
while( velocity > 0 )
{
distance += velocity * deltaTime;
velocity -= deceleration * deltaTime;
}
// 0.01 here is the minimum acceptable inaccuracy for the heuristic.
if( Mathf.Abs( distance - desiredDistance ) < 0.01 )
{
// Satisfactory solution found.
return deceleration;
}
else
{
// Here is where the estimate is revised.
// If the goal is exceeded, delta is halved and the heuristic reverses direction.
if( distance > desiredDistance )
{
if( !decreased )
{
delta /= 2f;
decreased = true;
}
deceleration -= delta;
}
else
{
if( decreased )
{
delta /= 2f;
decreased = false;
}
deceleration +=delta;
}
}
++i;
}
// If the code reaches this point, the heuristic has exceeded its maximum iterations. Throw an error or return some default value.
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.
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
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).
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ā.
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.
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.