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

Inconsistent gravity/random forces/solver problem?

Discussion in 'Physics' started by DeaD4MaN, Apr 28, 2016.

  1. DeaD4MaN

    DeaD4MaN

    Joined:
    Jun 25, 2014
    Posts:
    7
    Maybe I'm not considering some fundamental forces, or even making stupid errors and assumption in the simplest of equations, or this is indeed an floating point precession error/physics solver error. But here are the steps to reproduce the problem (using Unity 5.3.4f1):

    1. Make a default 2D project with not additional packages.
    2. Make a scene with a BoxCollider2D for a floor and two identical objects with BoxCollider2D and RigidBody2D. Leave the first rigidbody as is, and set gravity scale to 0 on the second rigidbody.
    3. Add this script on any object:
    Code (CSharp):
    1. using UnityEngine;
    2.  
    3. public class RBTest2 : MonoBehaviour
    4. {
    5.     public Rigidbody2D rb1;
    6.     public Rigidbody2D rb2;
    7.  
    8.     public float scale = 1;
    9.     public float h1;
    10.     public float h2;
    11.  
    12.     public bool test = false;
    13.  
    14.     void FixedUpdate ()
    15.     {
    16.         Vector2 trueForce = Vector2.up * 3.0F * Mathf.Abs(Physics2D.gravity.y);
    17.  
    18.         if (test)
    19.         {
    20.             test = false;
    21.             rb1.AddForce(trueForce, ForceMode2D.Impulse);
    22.             rb2.AddForce(trueForce * scale, ForceMode2D.Impulse);
    23.         }
    24.         rb2.AddForce(Physics2D.gravity * scale, ForceMode2D.Force);
    25.  
    26.         h1 = Mathf.Max(h1, rb1.position.y);
    27.         h2 = Mathf.Max(h2, rb2.position.y);
    28.     }
    29. }
    4. Populate the rb1 and rb2 fields with the two created rigidbodies.
    5. Play the scene and test the results by setting 'test' to true when BOTH of the objects are lying STILL on the grounds. Do it a few times. Look how both of the objects get higher every time you set 'test' to true. In ideal physics they should reach the same height every time, or at least be within some boundaries, not higher EVERY time. This is minor gripe, I can live with it, since the difference is so small.
    6. Now set the 'scale' value to 0.5, for example, and do the same test as above. Correct me if I'm wrong, but since the applied force and the gravity is scaled by 0.5 for the second rigidbody it should have exactly half the acceleration, hence half traveled speed, because the force was and impulse and the time is not squared. But if you compare maximum heights reached (scaling the second one up by the factor given) they don't add up. Even worse - the smaller the scale, the bigger the gap.

    What am I missing here?
     
  2. gorbit99

    gorbit99

    Joined:
    Jul 14, 2015
    Posts:
    1,350
    Let's just say we have two objects with m=1kg and a g=10m/s^2
    Both will have a gravitational force of 1kg * 10m/s^2 = 10N
    Let's apply 40N of upwards force on the first one and 20N on the second
    You have F1-Fg=40-10= 30N
    Then F2-Fg=20-10 = 10N
    It doesn't seem like a twice as big force to me

    I think the other problem is really a floting point inaccuracy problem
     
  3. DeaD4MaN

    DeaD4MaN

    Joined:
    Jun 25, 2014
    Posts:
    7
    The only thing is, artificial gravity of the second object isn't 10, it's 10/2 = 5. So F2-Fg=20-5 = 15N and that's exactly half of 30N.
     
  4. MelvMay

    MelvMay

    Unity Technologies

    Joined:
    May 24, 2013
    Posts:
    11,321
    I don't follow what you're trying to achieve here. You'll probably find that both objects are not at the same Y position at the time you're calling 'rest'. In-fact, you're applying a force to one continually so it'll never sleep and it's highly likely that it's Y position is different than the other one that is probably sleeping. When they both initially touch the ground, that'll probably be at the same Y position and the one with stock gravity will sleep. The other one you're applying force to is in a conflict between the gravity force you're applying and the impulse force to keep it from overlapping the ground.

    In a quick test of my own, I see one as '-3.285287' whereas the other is '-3.285002'. You can reduce this difference by using a Continous collision detection mode on both bodies if they absolutely have to have the same contact point. Also, you might find, due to precision, that's a very tiny rotation on the body you're continually applying force to. The impulse forces to stop it from overlapping the ground are applied to both contact points on the box so tiny differences can cause imperceptable rotations. Setting both bodies so they can't rotate Z-constraint will stop this. Alternately, you can prove this by changing the BoxCollider2D to CircleCollider2D which will produce only a single contact-poitn thus there'll won't be any rotation (no matter how small). You should then see that both rest at the same Y position.

    So I set both bodies to use Continuous and used a CircleCollider2D (single contact point) although using a Z-Rotation constraint worked the same. When I did this, both came to rest at the same point and jumped to the same height.

    I think the following video might help explain the non linearity you're getting on the height:


    BTW: If you want to see the exact source of this then here's the Box2D source here: https://github.com/erincatto/Box2D/blob/master/Box2D/Box2D/Dynamics/b2Island.cpp (Line#206).
     
  5. DeaD4MaN

    DeaD4MaN

    Joined:
    Jun 25, 2014
    Posts:
    7
    This is a prototype for a crude way to scale forces and position for a fast moving object, but trying to use real life equations. For example, an airplane that has real world mass and wing area, and behaves based on thrust, lifting force, gravity and drag. But all of the forces are scaled down, so that the traveled distance is also scaled down, but somewhat maintaining results of real forces.

    The problem is not the resting position. I perfectly understand that by applying constant force and and not letting the rigid body rest I'm creating additional calculations and errors. That's perfectly fine, the difference of 0.0003 is really negligible in this case. And locking Z rotation actually fixed always increasing height - thanks for that. :)

    Everything comes down to incorrect maximum height. Let me brake down my train of though (using simple numbers as g1 = 10, g2 = 5, V01 = 50, V02 = 25):
    Since F=ma=mg, and my initial force is applied as an impulse, not a constant, we can assume that initial velocity equals to acceleration, which is a = F/m and default mass is 1. Now, using maximum height of projectile equations: https://en.wikipedia.org/wiki/Projectile_motion#Maximum_height_of_projectile we get something along the lines of: max height of fist object is 50^2 * Sin^2(90) / 20 = 125; max height of second object is 25^2 * Sin^2(90) / 10 = 62.5. And 62.5 * 2 is indeed equals to 125. I don't get such a nice result in Unity, far from it.
     
  6. MelvMay

    MelvMay

    Unity Technologies

    Joined:
    May 24, 2013
    Posts:
    11,321
    But Unity (Box2D) isn't using your equations and isn't calculating some continuous function either as you explain above, it's using the ones I posted previous so there should be zero expectation that they match. This isn't some pure physics simulation, it's an approximation of rigid-body physics for games where emphasis is on speed. The integration is done in time-slices (default of 50Hz) so your continuous functions won't necessarily scale.

    Here's a decent overview of projectile motion; maybe this will help.

    http://www.iforce2d.net/b2dtut/projected-trajectory
     
    DeaD4MaN likes this.
  7. DeaD4MaN

    DeaD4MaN

    Joined:
    Jun 25, 2014
    Posts:
    7
    Wow. That link is eye opening, thank you very much! So I gather the problem somewhat related to float precision and it accumulating over time?
     
  8. MelvMay

    MelvMay

    Unity Technologies

    Joined:
    May 24, 2013
    Posts:
    11,321
    I think it explains the issues well in the article.
     
  9. DeaD4MaN

    DeaD4MaN

    Joined:
    Jun 25, 2014
    Posts:
    7
    Hm... In the case of rounded numbers it takes the same amount of time steps to reach maximum heights. But I still have quite big of a difference.
    Setting 2D gravity to Vector2 (0, -10) in project settings and using update code from above (and scale = 0.5):
    Code (CSharp):
    1. using UnityEngine;
    2.  
    3. public class RBTest2 : MonoBehaviour
    4. {
    5.     public Rigidbody2D rb1;
    6.     public Rigidbody2D rb2;
    7.  
    8.     public float scale = 0.5F;
    9.     public float h1;
    10.     public float h2;
    11.  
    12.     public bool test = false;
    13.  
    14.     void FixedUpdate ()
    15.     {
    16.         Vector2 trueForce = Vector2.up * 3F * Mathf.Abs(Physics2D.gravity.y);
    17.  
    18.         if (test)
    19.         {
    20.             test = false;
    21.             rb1.AddForce(trueForce, ForceMode2D.Impulse);
    22.             rb2.AddForce(trueForce * scale, ForceMode2D.Impulse);
    23.             //rb2.velocity += trueForce * scale;
    24.  
    25.             float[] vals = getMaxHeight(rb1.position, trueForce, Physics2D.gravity);
    26.             this.ClearEditorConsole();
    27.             print("Default Min pos: " + vals[0].ToString() + " with " + vals[2].ToString() + " steps. OR Max pos: " +
    28.                                         vals[1].ToString() + " with " + vals[3].ToString() + " steps.");
    29.  
    30.             vals = getMaxHeight(rb2.position, trueForce * scale, Physics2D.gravity * scale);
    31.             print("Scaled Min pos: " + vals[0].ToString() + " with " + vals[2].ToString() + " steps. OR Max pos: " +
    32.                                         vals[1].ToString() + " with " + vals[3].ToString() + " steps.");
    33.         }
    34.         rb2.AddForce(Physics2D.gravity * scale * rb2.mass, ForceMode2D.Force);
    35.         //rb2.velocity += Physics2D.gravity * scale * Time.fixedDeltaTime;
    36.  
    37.         h1 = Mathf.Max(h1, rb1.position.y);
    38.         h2 = Mathf.Max(h2, rb2.position.y);
    39.     }
    40.  
    41.     float[] getMaxHeight(Vector2 startingPosition, Vector2 startingVelocity, Vector2 gravity)
    42.     {
    43.         if ( startingVelocity.y < 0 ) return new float[4] {startingPosition.y, 0, startingPosition.y, 0};
    44.  
    45.         float t = Time.fixedDeltaTime;
    46.         Vector2 stepVelocity = t * startingVelocity;
    47.         Vector2 stepGravity = t * t * gravity;
    48.  
    49.         float nMin = Mathf.Floor(-stepVelocity.y / stepGravity.y - 1);
    50.         float nMax = Mathf.Ceil(-stepVelocity.y / stepGravity.y - 1);
    51.  
    52.         return new float[4]
    53.         {
    54.             startingPosition.y + nMin * stepVelocity.y + 0.5f * (nMin*nMin+nMin) * stepGravity.y,
    55.             startingPosition.y + nMax * stepVelocity.y + 0.5f * (nMax*nMax+nMax) * stepGravity.y,
    56.             nMin,
    57.             nMax
    58.         };
    59.     }
    60. }
    I get an output of:
    I even made two tables similar to one in the beginning, with both using time steps = 1/5 sec; with first gravity -20m/s/s for one second; and -10m/s/s for one second. Resulting the first one reaching position of -60 and the second one -30, so it's exactly twice as big.
     
    Last edited: May 3, 2016