Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. Dismiss Notice

Resolved Rolling ball bouncing issues preventing upgrade past 2018.2.

Discussion in 'Physics' started by Cramonky, Feb 8, 2021.

  1. Cramonky

    Cramonky

    Joined:
    Apr 1, 2013
    Posts:
    181
    Hi all,

    For a while I've really wished I could upgrade my project past 2018.2, however issues starting with the PhysX updates introduced way back in 2018.3 have prevented me from doing so. I've done all kinds of testing over the years to see if there was any way to fix these issues, but I have not come up with much of anything. So, before I give up for good I want to throw this out here to see if anyone has any ideas.

    My game is involves rolling balls. The balls are kind of small, with a radius of 0.1375. One very important thing to note is that my game has a level editor available for players, so I cannot guarantee that these scenarios will never happen. There are 3 scenarios that cause problems.

    Scenario 1:
    Mesh colliders with misaligned triangles will cause the ball to bounce off as if it hit an invisible wall. In 2018.2 I was able to get around this by setting the Default Contact Offset very low (0.0001). This no longer works in 2018.3.


    Scenario 2:
    Instances where a vertical quad its below a surface will cause the ball to bounce off it. This is actually an issue in 2018.2 as well, however I could work around it by lowering the top of the quad by a small value (0.01). In 2018.3 this no longer works. Even lowering it as much as 0.5 still causes issues, which seems silly because at that point the ball and the edge of the vertical quad are not even close to each other.

    This scenario might be improved in more recent versions of Unity. 2019.3 is the only other version I know of which had major PhysX updates. I wasn't able to get my project working quickly in 2019.3, but I ran a quick test setup in 2021.1 and the issue there at least was not as bad.

    Scenario 3:

    Rolling over adjacent box colliders will cause the same invisible wall bounce effect.


    If I can't get these issues resolved, I will be forever stuck in 2018.2.
     
    Last edited: Feb 8, 2021
    dan_ginovker likes this.
  2. Cramonky

    Cramonky

    Joined:
    Apr 1, 2013
    Posts:
    181
  3. MelvMay

    MelvMay

    Unity Technologies

    Joined:
    May 24, 2013
    Posts:
    10,507
    All physics engines calculate contacts between faces/edges. PhysX (3D) does this and so does Box2D (2D) so it's not "use a different engine". Separate colliders placeds next to each other do not form a continuous surface (you're imaging a continuous surface to move across) and it isn't a bug in Unity or something we don't care about. Whilst it won't help you here, in 2D I created the CompositeCollider2D which merges separate colliders into a continuous surface which won't have this issue. There's no equivalent of that for 3D but a single Mesh should not have this problem but putting different meshes side-by-side would.

    A good explanation of this behaviour (albeit in 2D) can be found here. Basically the physics engine (PhysX) is seeing the box-edge/opposing face so you get a normal facing back along the motion or upwards. To reduce this effect devs use capsules and/or use queries to validate motion.
     
  4. Cramonky

    Cramonky

    Joined:
    Apr 1, 2013
    Posts:
    181
    Thanks for the reply.

    The main use case where this is a very big problem is when colliders must be separate, like this quick example I threw together with moving platforms (made in 2021.1.0b5):


    Of course a level designer could get around this by, for example, making each platform slightly lower than the last. But if you're making a level editor available to everyone you don't have the luxury of relying on workarounds like that.

    It just is very odd how apparently the physics in 2018.2 (and I am assuming in earlier versions up to some point) is somehow able to deal with these situations without issue, with some minor tweaking.
     
    dan_ginovker likes this.
  5. Cramonky

    Cramonky

    Joined:
    Apr 1, 2013
    Posts:
    181
    Well since I'm apparently gonna be hung out to dry on this the only solution I have if I want to be able to use beyond 2018.2 is to try and implement some sort of predictive physics that can detect and rectify these problems.

    I wrote a very simple proof-of concept using Physics.Simulate with auto simulation turned off. It only bases the detection on velocity, and of course does not account for friction, drag, and actual valid changes in velocity from collisions or force added through code.
    Code (CSharp):
    1. physicsTimer += Time.deltaTime;
    2.  
    3. if (physicsTimer >= Time.fixedDeltaTime)
    4. {
    5.     while (physicsTimer >= Time.fixedDeltaTime)
    6.     {
    7.         Vector3 predictedPosition = transform.position + r.velocity * Time.fixedDeltaTime;
    8.         Vector3 vel = r.velocity;
    9.         Vector3 angularVel = r.angularVelocity;
    10.  
    11.         physicsTimer -= Time.fixedDeltaTime;
    12.         Physics.Simulate(Time.fixedDeltaTime);
    13.  
    14.         float dist = Vector3.Distance(predictedPosition, transform.position);
    15.         if (dist > 0.1f)
    16.         {
    17.             transform.position = predictedPosition;
    18.             r.velocity = vel;
    19.             r.angularVelocity = angularVel;
    20.         }
    21.     }
    22. }
    Here's a video of it. The red balls indicate when an unexpected change happens and show the path that would have been taken:


    I was also curious about the story the contact point information tells so I debugged that. As I expected whenever this problem happens the contact point normal is slightly wonky.


    I understand that physics is not easy and there are complexities that I am ignorant of, but it is extremely frustrating to be almost completely roadblocked from ever upgrading my Unity version because of this singular issue. I'm also concerned for those who may want to make a game where situations like this arise, only to run into this problem and be roadblocked as well.
     
  6. MelvMay

    MelvMay

    Unity Technologies

    Joined:
    May 24, 2013
    Posts:
    10,507
    You may not be experiencing it in 2018.2 but I can assure you the issue is there. It didn't appear after that version. This has been known in both PhysX and Box2D and other physics/game engines (Havok/Unreal) for a very very long time.

    I sense by posting again you "just want it fixed" and whilst I can understand that to some degree, it's not a Unity fix; it's how these physics engines work.
     
    hippocoder likes this.
  7. Cramonky

    Cramonky

    Joined:
    Apr 1, 2013
    Posts:
    181
    I guess the thing that bugs me so much is that these scenarios don't seem like that far-fetched of a use case. And yes, the issue is there in 2018.2, but the workaround for it is very simple: make the Contact Offset very low and adjust colliders by a very small amount.

    Anyways, I won't bump this thread any more. I realize I'm coming off as agitated here and if these things are out of your control I won't continue to be a bother. I'll continue to investigate whether or not making some kind of predictive physics to combat the issue is worth it.
     
    Last edited: Feb 17, 2021
    dan_ginovker and MelvMay like this.
  8. MelvMay

    MelvMay

    Unity Technologies

    Joined:
    May 24, 2013
    Posts:
    10,507
    I agree and I understand what a PITA this issue is and how frustrating it can be.

    Yeah, the trouble with that though is that the contact offset is what it is for a good reason. Changing it to reduce the above issue is quite a severe thing to do but I do understand that there's no other great workarounds.

    Not at all, I DO understand and I hope Im not coming off as discounting your frustration with this issue.

    I'm a 2D physics dev and I did all I could to address this issue years ago in 2D when I created the CompositeCollider2D which allows you to merge the geometry of different 2D colliders to produce continuous edge chains which don't have this issue. Something like that for 3D would be nice but it's much more complex there, essentially taking the volume geometry of colliders and producing a single mesh which has continuous faces.
     
  9. Cramonky

    Cramonky

    Joined:
    Apr 1, 2013
    Posts:
    181
    So I think I have come up with something that can rectify the erroneous bouncing without seriously interfering with valid physics interactions (that second part was the hard part).

    Here is the code with comments that hopefully should explain everything. It's still not accounting for drag or friction but so far that doesn't seem necessary. There is also likely some more tweaking that needs to be done to improve accuracy and mitigate false positives.
    Code (CSharp):
    1. private struct PredictedPhysics
    2. {
    3.     public Vector3 Position;
    4.     public Vector3 Velocity;
    5.     public bool Collision;
    6.     public RaycastHit Hit;
    7. }
    8.  
    9. [SerializeField]
    10. //The radius of the sphere used for prediction. Should be the same as the sphere collider raidus.
    11. private float ballRadius = 0.1375f;
    12. [SerializeField]
    13. //Layer mask used for detecting intersections. For example you could exclude the layer the ball is on so
    14. private LayerMask layerMask;
    15. [SerializeField]
    16. //Threshold for detecting unexpected changes in velocity. This is the maximum angle between the expected velocity and actual velocity after simulation.
    17. private float velocityVectorDeviationThreshold = 10;
    18. [SerializeField]
    19. //Threshold for detecting incorrect contact point normals. This is the maximum angle between the contact point's normal and raycast's normal.
    20. private float contactNormalDeviationThreshold = 0.01f;
    21. [SerializeField]
    22. //Threshold used to ignore sphere cast intersecting at shallow angles.
    23. private float sphereCastVelocityNormalAngleThreshold = 5f;
    24.  
    25. private Rigidbody r;
    26. private float physicsTimer = 0;
    27.  
    28. //addForceFlag is used when applying force in code
    29. private bool addForceFlag = false;
    30. private bool collisionEnterFlag = false;
    31.  
    32. void Start()
    33. {
    34.     r = GetComponent<Rigidbody>();
    35.     r.maxAngularVelocity = 10000;
    36. }
    37.  
    38. void Update()
    39. {
    40.     physicsTimer += Time.deltaTime;
    41.  
    42.     if (physicsTimer >= Time.fixedDeltaTime)
    43.     {
    44.         while (physicsTimer >= Time.fixedDeltaTime)
    45.         {
    46.             physicsTimer -= Time.fixedDeltaTime;
    47.  
    48.             //If there was a collision in the previous simulation or an added force is expected, simulate the physics without any corrections.
    49.             if (addForceFlag || collisionEnterFlag)
    50.             {
    51.                 Physics.Simulate(Time.fixedDeltaTime);
    52.  
    53.                 addForceFlag = false;
    54.                 collisionEnterFlag = false;
    55.             }
    56.             else
    57.             {
    58.                 //Get the predicted physics based on the current position and velocity
    59.                 PredictedPhysics predictedPhysics = getPrediction(transform.position, r.velocity);
    60.  
    61.                 //Save the current velocities for use later
    62.                 Vector3 vel = r.velocity;
    63.                 Vector3 angularVel = r.angularVelocity;
    64.  
    65.                 Physics.Simulate(Time.fixedDeltaTime);
    66.  
    67.                 //Ignore when a collision is predicted
    68.                 if (!predictedPhysics.Collision)
    69.                 {
    70.                     //Find the angle between the current velocity and the predicted velocity
    71.                     float velocityDeviation = Vector3.Angle(r.velocity, predictedPhysics.Velocity);
    72.  
    73.                     //If the angle is greater than a threshold, something went wrong and we need to correct.
    74.                     if (velocityDeviation > velocityVectorDeviationThreshold)
    75.                     {
    76.                         //Use the predicted position and just reset the velocity values to what they were before the simulation.
    77.                         transform.position = predictedPhysics.Position;
    78.                         r.velocity = vel;
    79.                         r.angularVelocity = angularVel;
    80.                     }
    81.                 }
    82.             }
    83.         }
    84.     }
    85. }
    86.  
    87. private PredictedPhysics getPrediction(Vector3 currentPosition, Vector3 velocity)
    88. {
    89.     RaycastHit hit;
    90.     PredictedPhysics predictedPhysics = new PredictedPhysics();
    91.  
    92.     //Get velocity with gravity applied
    93.     Vector3 velocityWithGravity = velocity + Physics.gravity * Time.fixedDeltaTime;
    94.     float distance = velocityWithGravity.magnitude * Time.fixedDeltaTime;
    95.     Ray ray = new Ray(currentPosition, velocityWithGravity);
    96.  
    97.     //Account for cases where ball may be slightly intersecting with a collider.
    98.     //In this case we don't want to do any corrections.
    99.     //(Also mind the GC allocation here)
    100.     Collider[] intersecting = Physics.OverlapSphere(currentPosition, ballRadius, layerMask);
    101.     //Using a layer mask to ignore the ball itself. Without a layer mask you would check that the length is greater than 1.
    102.     if (intersecting.Length > 0)
    103.     {
    104.         predictedPhysics.Collision = true;
    105.         return predictedPhysics;
    106.     }
    107.  
    108.     //Sphere cast across predicted path for this frame
    109.     if (Physics.SphereCast(ray, ballRadius, out hit, distance))
    110.     {
    111.         //Since we are always applying gravity to the velocity, many times the spherecast will hit the ground we are rolling over.
    112.         //Deal with this by ignoring very shallow angles.
    113.         float velocityNormalAngle = Mathf.Abs(90f - Vector3.Angle(hit.normal, velocityWithGravity));
    114.  
    115.         if (velocityNormalAngle > sphereCastVelocityNormalAngleThreshold)
    116.         {
    117.             //Don't do any corrections if collision is expected
    118.             predictedPhysics.Collision = true;
    119.             predictedPhysics.Hit = hit;
    120.             return predictedPhysics;
    121.         }
    122.     }
    123.  
    124.     //Getting to here means no collision is expected.
    125.     predictedPhysics.Position = currentPosition + velocityWithGravity * Time.fixedDeltaTime;
    126.     predictedPhysics.Velocity = velocityWithGravity;
    127.  
    128.     return predictedPhysics;
    129. }
    130.  
    131. //Account for collisions that occur that cannot be predicted.
    132. //i.e. other moving objects that hit us.
    133. private void OnCollisionEnter(Collision collision)
    134. {
    135.     //Automatically set the collision flag
    136.     collisionEnterFlag = true;
    137.  
    138.     //Validate each of the collision contact points.
    139.     for (int i = 0; i < collision.contactCount; i++)
    140.     {
    141.         ContactPoint cp = collision.GetContact(i);
    142.  
    143.         //In the cases where the ball bounces while rolling over an edge, the contact point normal is usually different than expected
    144.         //Raycasting on the other hand typically returns a normal in-line with what we expect, so compare the raycast normal to the contact point normal
    145.         RaycastHit hit;
    146.         Ray ray = new Ray(cp.point + cp.normal * 0.1f, -cp.normal);
    147.         if (Physics.Raycast(ray, out hit, 0.2f))
    148.         {
    149.             float normalDeviation = Vector3.Angle(cp.normal, hit.normal);
    150.  
    151.             //If the normals deviate too much then something has gone wrong.
    152.             //Set the collision flag to false so prediction and correction can be done.
    153.             if (normalDeviation > contactNormalDeviationThreshold)
    154.             {
    155.                 collisionEnterFlag = false;
    156.                 return;
    157.             }
    158.         }
    159.     }
    160. }
    And here is an example, same as my earlier post where the red balls show when a correction is made.


    There are some cases when corrections are made that aren't really necessary, mainly when other objects hit the ball, but they don't seem to negatively impact the physics in a significant way.

    Now I just have to implement this in my actual project and see what happens.
     
    dan_ginovker, SugoiDev and Haneferd like this.
  10. Cramonky

    Cramonky

    Joined:
    Apr 1, 2013
    Posts:
    181
    One final bump to share my findings.

    I have tested my project in versions 2018.4, 2019.4, and 2021.1 (I don't have a 2020 install handy). In 2018.4 and 2019.4 the above fix did not work well at all. However in 2021.1 things improved drastically.

    Here is a complete script for anyone to use in their project. Still may require tweaking but its a good starting point:
    Code (CSharp):
    1. using UnityEngine;
    2.  
    3. public class BallPhysicsCorrector : MonoBehaviour
    4. {
    5.     private struct PredictedPhysics
    6.     {
    7.         public Vector3 Position;
    8.         public Vector3 Velocity;
    9.         public bool Collision;
    10.         public RaycastHit Hit;
    11.     }
    12.  
    13.     [SerializeField]
    14.     //The Rigidbody being targeted.
    15.     private Rigidbody myRigidbody;
    16.     [SerializeField]
    17.     //The radius of the ball. This should typically be the same as the Sphere Collider radius, or very slightly less.
    18.     private float ballRadius = 0.1375f;
    19.     [SerializeField]
    20.     //LayerMask for the OverlapSphere check so you can avoid always getting the ball itself as a result.
    21.     private LayerMask layerMask;
    22.     [SerializeField]
    23.     //Threshold for detecting unexpected changes in velocity. This is the maximum angle between the expected velocity and actual velocity after simulation.
    24.     private float velocityVectorDeviationThreshold = 10;
    25.     [SerializeField]
    26.     //Threshold for detecting incorrect contact point normals. This is the maximum angle between the contact point's normal and raycast's normal.
    27.     private float contactNormalDeviationThreshold = 0.01f;
    28.     [SerializeField]
    29.     //Threshold used to ignore sphere cast intersecting at shallow angles.
    30.     private float sphereCastVelocityNormalAngleThreshold = 5f;
    31.     [SerializeField]
    32.     //Flag allowing you to disable correction for testing purposes.
    33.     private bool enableCorrection = true;
    34.  
    35.     private bool addForceFlag = false;
    36.     private bool collisionEnterFlag = false;
    37.     private bool hasInvalidCollisionEnter = false;
    38.  
    39.     private PredictedPhysics predictedPhysics;
    40.     private Vector3 currentVelocity;
    41.     private Vector3 currentAngularVelocity;
    42.  
    43.     private Collider[] overlapShereColliders = new Collider[2];
    44.  
    45.     //Use this whenever you add force or torque in code so that it will be accounted for on the next simulation.
    46.     public void SetAddForceFlag()
    47.     {
    48.         addForceFlag = true;
    49.     }
    50.  
    51.     //This should be called before Physics.Simulate.
    52.     public void PreSimulate()
    53.     {
    54.         if (addForceFlag)
    55.             return;
    56.  
    57.         predictedPhysics = getPrediction(transform.position, myRigidbody.velocity);
    58.         currentVelocity = myRigidbody.velocity;
    59.         currentAngularVelocity = myRigidbody.angularVelocity;
    60.     }
    61.  
    62.     //This should be called after Physics.Simulate.
    63.     public void PostSimulate()
    64.     {
    65.         //Various situations where we don't want to do any correction.
    66.         if (!addForceFlag && (!collisionEnterFlag || hasInvalidCollisionEnter) && enableCorrection && !predictedPhysics.Collision && !myRigidbody.isKinematic)
    67.         {
    68.             float velocityDeviation = Vector3.Angle(myRigidbody.velocity, predictedPhysics.Velocity);
    69.  
    70.             //If the expected velocity doesn't match the actual velocity, reset to the expected values.
    71.             if (velocityDeviation > velocityVectorDeviationThreshold)
    72.             {
    73.                 transform.position = predictedPhysics.Position;
    74.                 myRigidbody.velocity = currentVelocity;
    75.                 myRigidbody.angularVelocity = currentAngularVelocity;
    76.             }
    77.         }
    78.  
    79.         addForceFlag = false;
    80.         collisionEnterFlag = false;
    81.         hasInvalidCollisionEnter = false;
    82.     }
    83.  
    84.     private PredictedPhysics getPrediction(Vector3 currentPosition, Vector3 velocity)
    85.     {
    86.         RaycastHit hit;
    87.         PredictedPhysics predictedPhysics = new PredictedPhysics();
    88.  
    89.         //Apply
    90.         Vector3 velocityWithGravity = velocity + Physics.gravity * Time.fixedDeltaTime;
    91.         float distance = velocityWithGravity.magnitude * Time.fixedDeltaTime;
    92.         Ray ray = new Ray(currentPosition, velocityWithGravity);
    93.  
    94.         //Account for cases where ball may be slightly intersecting with a collider, as this will cause the SphereCast to not hit anything.
    95.         int count = Physics.OverlapSphereNonAlloc(currentPosition, ballRadius, overlapShereColliders, layerMask);
    96.         if (count > 0)
    97.         {
    98.             predictedPhysics.Collision = true;
    99.             return predictedPhysics;
    100.         }
    101.  
    102.         //Do a sphere cast across the predicted motion of the ball
    103.         if (Physics.SphereCast(ray, ballRadius, out hit, distance))
    104.         {
    105.             float velocityNormalAngle = Mathf.Abs(90f - Vector3.Angle(hit.normal, velocityWithGravity));
    106.  
    107.             //Since we are always applying gravity to the velocity, many times the spherecast will hit the ground we are rolling over.
    108.             //Deal with this by ignoring very shallow angles.
    109.             if (velocityNormalAngle > sphereCastVelocityNormalAngleThreshold)
    110.             {
    111.                 //Don't do any corrections if collision is expected
    112.                 predictedPhysics.Collision = true;
    113.                 predictedPhysics.Hit = hit;
    114.                 return predictedPhysics;
    115.             }
    116.         }
    117.  
    118.         //Getting to here means no collision is expected.
    119.         //Note there is no accounting for drag or friction.
    120.         predictedPhysics.Position = currentPosition + velocityWithGravity * Time.fixedDeltaTime;
    121.         predictedPhysics.Velocity = velocityWithGravity;
    122.  
    123.         return predictedPhysics;
    124.     }
    125.  
    126.     //Account for collisions that occur that cannot be predicted.
    127.     //i.e. other moving objects that hit us.
    128.     private void OnCollisionEnter(Collision collision)
    129.     {
    130.         //Set the collision flag
    131.         collisionEnterFlag = true;
    132.  
    133.         ContactPoint[] contactPoints = collision.contacts;
    134.                    
    135.         //OnCollisionEnter will be called when rolling over the edge between different objects.
    136.         //Since this is exactly the scenario we are trying to fix, we can't just ignore it so we need to do some validation.
    137.         for (int i = 0; i < collision.contactCount; i++)
    138.         {
    139.             ContactPoint cp = collision.GetContact(i);
    140.  
    141.             //In the cases where the ball bounces while rolling over an edge, the contact point normal is usually different than expected
    142.             //Raycasting on the other hand typically returns a normal in-line with what we expect, so compare the raycast normal to the contact point normal
    143.             RaycastHit hit;
    144.             Ray ray = new Ray(cp.point + cp.normal * 0.1f, -cp.normal);
    145.             if (Physics.Raycast(ray, out hit, 0.2f))
    146.             {
    147.                 float normalDeviation = Vector3.Angle(cp.normal, hit.normal);
    148.  
    149.                 //If the normals deviate too much then something has gone wrong.
    150.                 //Make sure we do correction event though collisionEnterFlag is true.
    151.                 if (normalDeviation > contactNormalDeviationThreshold)
    152.                 {
    153.                     hasInvalidCollisionEnter = true;
    154.                     return;
    155.                 }
    156.             }
    157.         }
    158.     }
    159. }
    I'm still terrified of coming across some situation where this all falls apart but my initial testing is promising.
     
    dan_ginovker, SugoiDev and Haneferd like this.
  11. Baste

    Baste

    Joined:
    Jan 24, 2013
    Posts:
    6,194
    t-junctions are the worst.

    We've spent a lot of time working around this in physics based-games. With static geometry, the solution is simply to make sure that the joints doesn't exist anywhere the player moves.

    With moving geometry like yours, you probably need this kind of workaround. I guess an alternative would be to combine the adjacent colliders into a meshCollider, and update the vertices of that on the fly. I'm not sure what the cost of that would be.

    The nice thing here is that it's not very hard to make that mesh collider. As @MelvMay is pointing out, the general case solution for combining 3D meshes is really hard. But you're not solving a general case issue, you're solving adjacent box colliders that you know are supposed to have the same height. That will allow you to cut a lot of corners.
     
  12. Crayz

    Crayz

    Joined:
    Mar 17, 2014
    Posts:
    192
    Just want to share my pain in dealing with this issue. I have a box collider that needs to slide smoothly along connecting edges at high velocity without getting caught up, the colliders look like this:
    https://i.imgur.com/DzKlNpB.png

    My gameplay mechanics derive from Source Engine where the problem also exists xD

    edit: It's been a while since I've actually toyed around with this and I could be wrong, but I believe PhysX's "dirty" physics has not helped much in solving the problem. PhysX allows slight penetration of colliders, and if a cast begins inside another collider it will return a useless result. I look forward to experimenting with Havok and/or Unity's physics engine. If I'm not mistaken Havok's collision detection has no penetration.
     
    Last edited: Mar 2, 2021
  13. MelvMay

    MelvMay

    Unity Technologies

    Joined:
    May 24, 2013
    Posts:
    10,507
    There's no magic in the DOTS physics, it's the same thing. What you'll be able to do in DOTS physics though is get access to the contacts before they go to the solver to tweak them. In classic 2D/3D physics you see contacts after the solver has used them. DOTS physics still uses collider/collider collisions; it doesn't do collider vs "a bunch of colliders lined up together".
     
    hippocoder and Crayz like this.
  14. Vengant

    Vengant

    Joined:
    Nov 19, 2018
    Posts:
    1
  15. Perturbator858

    Perturbator858

    Joined:
    Jan 21, 2021
    Posts:
    7
    I've set rigidbody, made new layer for ball and adjusted radius but the script is not correcting the bounce. What did I forget ?
     
  16. rz_0lento

    rz_0lento

    Joined:
    Oct 8, 2013
    Posts:
    2,361
    dan_ginovker likes this.