Search Unity

  1. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  2. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

Terminal Velocity

Discussion in 'Scripting' started by Factoid, Nov 16, 2009.

  1. Factoid

    Factoid

    Joined:
    Mar 30, 2008
    Posts:
    69
    Just playing around with the physics engine, trying to work out the relationships between the various components, and I manged to work out a terminal velocity solution.

    If you want to have your rigidbody to have a top speed of x, and it's maximum acceleration is y, then the ideal drag is simply y / x.

    However, it gets a little weirder when you factor in large ratios of y/x, and the fixedDeltaTime of your simulation.

    For example, when I tried a top speed of 10, with an acceleration of 1000 at a time step of 0.01, the drag required was actually 50, as time steps got smaller I got closer to 100 as the drag, but never exactly.

    After getting a large set of data to work with, I worked out this:

    Code (csharp):
    1. idealDrag = maxAcceleration / terminalVelocity;
    2. rigidbody.drag = idealDrag / ( idealDrag * Time.fixedDeltaTime + 1 );
    Probably common knowledge for anyone who really uses PhysX, but maybe this will be useful for someone.

    Edit: Code block didn't match the description paragraph.
     
  2. ReallyJonatanCrafoord

    ReallyJonatanCrafoord

    Joined:
    Jun 30, 2011
    Posts:
    5
    I love you right now. I've been struggling with this problem all day, tearing my hair over the PhysX calculation for drag that did not seem to apply in Unity.

    From this I solved an equation for calculating top velocity based on an added constant force and a drag value:

    Code (csharp):
    1. Vector3 addedForce; // The force added each FixedUpdate
    2. float topVelocity = ((addedForce.magnitude / rigidbody.drag) - Time.fixedDeltaTime * addedForce.magnitude) / rigidbody.mass;
    Thanks!!
     
    tudta likes this.
  3. gevarre

    gevarre

    Joined:
    Jan 19, 2009
    Posts:
    132
    Unfortunately, this is only a partial solution. The big problem is that drag can't be applied to a particular direction, so if you have, for instance, a vehicle being pushed along the ground by force, if it leaves the ground and the drag multiplier is applied, it just hangs in the air instead of falling back to the ground.

    The biggest problem here is the PhysX engine itself, which is what Unity physics are based on. It's just not a very robust or accurate solution. It's good for generally keeping a slow-moving character on the ground and outside of walls, but that's about it. I've had the displeasure of having to compensate for it's shortcomings on several projects for various companies I've worked for, and I can tell you it's always been a nightmare getting it to work. I wish the devs would have taken the time to code their own solution instead of just going with PhysX, but of course that would have been a lot more work, so I can see why they did it.
     
  4. magiisto

    magiisto

    Joined:
    Feb 18, 2013
    Posts:
    1
    Well, there is a problem with this equation.
    If you, lets say, make
    drag = 1
    and
    fixedDeltaTime = 1

    your topVelocity will be ZERO no matter the force, what is obviously wrong.

    After spending half a day looking for proper solution it seems that the right equation for terminal velocity in unity is:
    Vmax = F / (mass * drag)

    You wont get 100% accuracy with it but its close.

    if you want to know the details behind this equation, check out "Mathematics for 3D Game Programming and Computer Graphics, 2nd edition".

    Hope it'll save someone some time and hair.

    peace
     
    angrypenguin likes this.
  5. root8888

    root8888

    Joined:
    Jul 25, 2013
    Posts:
    26
    I love you.
     
    mansoor090 likes this.
  6. Cameron_SM

    Cameron_SM

    Joined:
    Jun 1, 2009
    Posts:
    915
    Just thought I'd necro this thread to clarify some things because I hit a similar snag and ended up here via google (with 4,000+ views, seems I'm not the only one).

    I tried Factoid's formula and it appeared to work (Yay!), but that didn't really make sense to me because the math is wrong (WTF be happy, it works!). I don't like it when things don't make sense so I did some digging. The reason why Factoid's formula works is because in Unity forces are applied to the rigidbody then drag is calculated and applied afterwards, which I think might actually be a bug.

    I eventually discovered that the PhysX rigidbody's drag property seems to be used to apply a basic Stokes drag which in Unity you'd perhaps integrate simply as:

    Code (csharp):
    1. velocity += (forces - drag*velocity) * dt;
    However, with Unity, forces appear to be applied before this drag caculation (not sure why?) so it actually ends up being this:

    Code (csharp):
    1. velocity += forces * dt;
    2. velocity -= drag*velocity * dt;
    Which still feels fine in game but the drag is being calculated with the wrong velocity.

    This also explains why Factoid's method seems to work. It's still not correct, but it's close enough with constant acceleration that it really doesn't matter. However, the best way to do this is to set rigidbody.velocity directly, do your own drag calculation and always set a rigid body's drag to zero. This way you can use the more correct math and still get the desired result:

    Code (CSharp):
    1.  
    2. // a = acceleration, forward = transform.forward, dt = fixedDeltaTime
    3. var drag = a / maxSpeed;
    4. rigidbody.velocity += ((forward * a) - (rigidbody.velocity * drag)) * dt;
    As proof, here's two monobehaviours you can easily setup and test, one uses PhysX (will need rigid body), the other just does manual Euler integration in fixed update - both move identically.

    PhysX:
    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections;
    3.  
    4. public class PhysXTest : MonoBehaviour {
    5.  
    6.     public float topSpeed = 10f;
    7.     public float drag = 0f;
    8.     public float acceleration = 10f;
    9.     public float currentSpeed;
    10.  
    11.     void FixedUpdate()
    12.     {
    13.         drag = (acceleration / topSpeed);
    14.         rigidbody.velocity += (Vector3.right * acceleration - (drag * rigidbody.velocity)) * Time.fixedDeltaTime;
    15.         currentSpeed = rigidbody.velocity.magnitude;
    16.     }
    17.  
    18. }
    19.  
    Simple Euler:
    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections;
    3.  
    4. public class PhysicsTest : MonoBehaviour
    5. {
    6.     public float topSpeed = 10f;
    7.     public float drag = 0f;
    8.     public float acceleration = 10f;
    9.     private Vector3 velocity;
    10.     public float currentSpeed;
    11.  
    12.     void FixedUpdate()
    13.     {
    14.         drag = (acceleration / topSpeed);
    15.         velocity += (Vector3.right * acceleration - (drag * velocity)) * Time.fixedDeltaTime;
    16.         transform.position += velocity * Time.fixedDeltaTime;
    17.         currentSpeed = velocity.magnitude;
    18.     }
    19.  
    20. }
    21.  
    I also tested this by integrating the drag incorrectly in the Euler version of the above and setting the drag property on the rigidbody like so:

    Code (CSharp):
    1. // Manual
    2. var drag = (acceleration / topSpeed);
    3. velocity += Vector3.right * acceleration * Time.fixedDeltaTime;
    4. velocity -= drag * velocity * Time.fixedDeltaTime;
    5.  
    6. //PhysX
    7. rigidbody.drag = (acceleration / topSpeed);
    8. rigidbody.AddForce(Vector3.right * acceleration);
    9.  
    Even after letting it run for several minutes Unity's PhysX was identical Step for Step so this is probably a bug in the PhysX implementation of Unity.

    Note: This all assumes you're using objects with a mass of 1. If not you'll need to integrate that into the equations too.
     
    Last edited: Dec 2, 2014
  7. jknight-nc

    jknight-nc

    Joined:
    Jun 10, 2014
    Posts:
    52
    NICE

    But shouldnt it be

    var drag = (currentSpeed / topSpeed);


    ??

    In your example drag is always equal to 1 since accleration = 10 and topspeed = 10, thus the vehicle will go nowhere.
     
    Last edited: Dec 5, 2016