Repository: bulletphysics/bullet3

Issue:
Ccd introduces too high impulses too easily in 2.89, leads to floating point errors. Patch draft included [needs proper rework]
I have played around with bullet 2.89 in a custom game engine, and hit a brick wall with Ccd:

Enabling Ccd/anti tunneling in some simple but non-trivial setups (like, only boxes in somewhat reasonable sizes with all sides significantly larger than the collision margin, and triangle meshes as static level geometry but the geometry not being super trivial) can occasionally introduce too high impulses too easily in bullet 2.89, leading to floating point errors. Occasionally sadly means it *will* happen at some point if there's just enough activity, and objects fall down from high enough often enough in a large enough pile.

When these floating point errors happen, it leads to a `NaN` injection which poisons all the positions and/or velocities, and propagates brokenness into all objects:

:arrow_right: [Video illustrating the issue](https://wobble.ninja/temp/test_windows_omgwat.mp4)

I found this to be almost unavoidable in any non-trivial scenario. With only using really large objects (>1.0 only) it is *somewhat* avoidable, but games often have smaller inventory items so I find not using anything at least down to size 0.3 x 0.3 x 0.3 extremely limiting in practice. Disabling Ccd worked also, but that obviously leads to lots of tunneling again, especially for these smaller objects. Often having a pile of them fall down onto a triangle mesh ground is enough for half of them to pass through! (Edit: I'm aware cranking up the timestep frequency helps with this too, but given I don't need the otherwise higher detail in calculations Ccd just seemed like the smarter option.)

This sadly makes Ccd way less useful, because in less trivial scenarios tunneling can be pretty bad but these scenarios also appear to like to cause `NaN` breakages with Ccd enabled. :cry: 

Therefore, I enabled floating point exceptions and devised this patch to more permanently address this:

```
diff --git a/src/BulletDynamics/Dynamics/btRigidBody.cpp b/src/BulletDynamics/Dynamics/btRigidBody.cpp
index 9e8705b00..684b570f3 100644
--- a/src/BulletDynamics/Dynamics/btRigidBody.cpp
+++ b/src/BulletDynamics/Dynamics/btRigidBody.cpp
@@ -294,6 +294,7 @@ btVector3 btRigidBody::computeGyroscopicImpulseImplicit_Body(btScalar step) cons
 {
 	btVector3 idl = getLocalInertia();
 	btVector3 omega1 = getAngularVelocity();
+	limitForceOrVel(omega1);
 	btQuaternion q = getWorldTransform().getRotation();
 
 	// Convert to body coordinates
diff --git a/src/BulletDynamics/Dynamics/btRigidBody.h b/src/BulletDynamics/Dynamics/btRigidBody.h
index 39d47cbbd..0d103af61 100644
--- a/src/BulletDynamics/Dynamics/btRigidBody.h
+++ b/src/BulletDynamics/Dynamics/btRigidBody.h
@@ -305,6 +305,7 @@ public:
 	void applyTorque(const btVector3& torque)
 	{
 		m_totalTorque += torque * m_angularFactor;
+		limitForceOrVel(m_totalTorque);
 	}
 
 	void applyForce(const btVector3& force, const btVector3& rel_pos)
@@ -316,11 +317,13 @@ public:
 	void applyCentralImpulse(const btVector3& impulse)
 	{
 		m_linearVelocity += impulse * m_linearFactor * m_inverseMass;
+		limitForceOrVel(m_linearVelocity);
 	}
 
 	void applyTorqueImpulse(const btVector3& torque)
 	{
 		m_angularVelocity += m_invInertiaTensorWorld * torque * m_angularFactor;
+		limitForceOrVel(m_angularVelocity);
 	}
 
 	void applyImpulse(const btVector3& impulse, const btVector3& rel_pos)
@@ -362,19 +365,34 @@ public:
         m_pushVelocity = v;
     }
     
+    void limitForceOrVel(btVector3& v) const {
+        v.setX(
+            fmax(-9999, fmin(9999, v.getX()))
+        );
+        v.setY(
+            fmax(-9999, fmin(9999, v.getY()))
+        );
+        v.setZ(
+            fmax(-9999, fmin(9999, v.getZ()))
+        );
+    }
+
     void setTurnVelocity(const btVector3& v)
     {
         m_turnVelocity = v;
+        limitForceOrVel(m_turnVelocity);
     }
     
     void applyCentralPushImpulse(const btVector3& impulse)
     {
         m_pushVelocity += impulse * m_linearFactor * m_inverseMass;
+        limitForceOrVel(m_pushVelocity);
     }
     
     void applyTorqueTurnImpulse(const btVector3& torque)
     {
         m_turnVelocity += m_invInertiaTensorWorld * torque * m_angularFactor;
+        limitForceOrVel(m_turnVelocity);
     }
 
 	void clearForces()
@@ -408,12 +426,14 @@ public:
 	{
 		m_updateRevision++;
 		m_linearVelocity = lin_vel;
+		limitForceOrVel(m_linearVelocity);
 	}
 
 	inline void setAngularVelocity(const btVector3& ang_vel)
 	{
 		m_updateRevision++;
 		m_angularVelocity = ang_vel;
+		limitForceOrVel(m_angularVelocity);
 	}
 
 	btVector3 getVelocityInLocalPoint(const btVector3& rel_pos) const
```
This works surprisingly well. It won't prevent instabilities / wildly exploding around items, but it prevents the floating point exceptions and permanent `NaN` breakage in my tests even when things briefly go wild.

I recognize this patch can't go in like this, because it has performance implications and changes the simulation outcome in ways some people might not like. But I think this as an option would help really greatly in making Ccd are more practically useful option, since right now I find it hard to find much use in it given these numeric issues.

Maybe this could be added to premake.lua with a sane option, like `--hard-clamp-all-velocity`? (by someone who knows how to add such options)


Discussion:

Patch:
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 68e0a93bc0..c0d4102dad 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -24,6 +24,7 @@ SET(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -D_DEBUG")
 #MESSAGE("CMAKE_CXX_FLAGS_DEBUG="+${CMAKE_CXX_FLAGS_DEBUG})
 
 OPTION(USE_DOUBLE_PRECISION "Use double precision"	OFF)
+SET(CLAMP_VELOCITIES "0" CACHE STRING "Clamp rigid bodies' velocity to this value, if larger than zero. Useful to prevent floating point errors or in general runaway velocities in complex scenarios")
 OPTION(USE_GRAPHICAL_BENCHMARK "Use Graphical Benchmark" ON)
 OPTION(BUILD_SHARED_LIBS "Use shared libraries" OFF)
 OPTION(USE_SOFT_BODY_MULTI_BODY_DYNAMICS_WORLD "Use btSoftMultiBodyDynamicsWorld" ON)
@@ -212,6 +213,10 @@ IF (INTERNAL_UPDATE_SERIALIZATION_STRUCTURES)
 ADD_DEFINITIONS( -DBT_INTERNAL_UPDATE_SERIALIZATION_STRUCTURES)
 ENDIF (INTERNAL_UPDATE_SERIALIZATION_STRUCTURES)
 
+IF (CLAMP_VELOCITIES)
+ADD_DEFINITIONS( -DBT_CLAMP_VELOCITY_TO=${CLAMP_VELOCITIES})
+ENDIF (CLAMP_VELOCITIES)
+
 IF (USE_DOUBLE_PRECISION)
 ADD_DEFINITIONS( -DBT_USE_DOUBLE_PRECISION)
 SET( BULLET_DOUBLE_DEF "-DBT_USE_DOUBLE_PRECISION")
diff --git a/build3/premake4.lua b/build3/premake4.lua
index e1167b0aae..8fce384233 100644
--- a/build3/premake4.lua
+++ b/build3/premake4.lua
@@ -337,6 +337,12 @@ end
 		trigger = "double",
 		description = "Double precision version of Bullet"
 	}
+
+	newoption
+	{
+		trigger = "clamp-velocities",
+		description = "Limit maximum velocities to reduce FP exception risk"
+	}
 	
 	newoption
 	{
@@ -360,6 +366,9 @@ end
 	if _OPTIONS["double"] then
 		defines {"BT_USE_DOUBLE_PRECISION"}
 	end
+	if _OPTIONS["clamp-velocities"] then
+		defines {"BT_CLAMP_VELOCITY_TO=9999"}
+	end
 
 	configurations {"Release", "Debug"}
 	configuration "Release"
diff --git a/src/BulletDynamics/Dynamics/btRigidBody.cpp b/src/BulletDynamics/Dynamics/btRigidBody.cpp
index 9e8705b001..154b2cc255 100644
--- a/src/BulletDynamics/Dynamics/btRigidBody.cpp
+++ b/src/BulletDynamics/Dynamics/btRigidBody.cpp
@@ -380,6 +380,9 @@ void btRigidBody::integrateVelocities(btScalar step)
 	{
 		m_angularVelocity *= (MAX_ANGVEL / step) / angvel;
 	}
+	#if defined(BT_CLAMP_VELOCITY_TO) && BT_CLAMP_VELOCITY_TO > 0
+	clampVelocity(m_angularVelocity);
+	#endif
 }
 
 btQuaternion btRigidBody::getOrientation() const
diff --git a/src/BulletDynamics/Dynamics/btRigidBody.h b/src/BulletDynamics/Dynamics/btRigidBody.h
index 39d47cbbda..943d724cce 100644
--- a/src/BulletDynamics/Dynamics/btRigidBody.h
+++ b/src/BulletDynamics/Dynamics/btRigidBody.h
@@ -305,6 +305,9 @@ class btRigidBody : public btCollisionObject
 	void applyTorque(const btVector3& torque)
 	{
 		m_totalTorque += torque * m_angularFactor;
+		#if defined(BT_CLAMP_VELOCITY_TO) && BT_CLAMP_VELOCITY_TO > 0
+		clampVelocity(m_totalTorque);
+		#endif
 	}
 
 	void applyForce(const btVector3& force, const btVector3& rel_pos)
@@ -316,11 +319,17 @@ class btRigidBody : public btCollisionObject
 	void applyCentralImpulse(const btVector3& impulse)
 	{
 		m_linearVelocity += impulse * m_linearFactor * m_inverseMass;
+		#if defined(BT_CLAMP_VELOCITY_TO) && BT_CLAMP_VELOCITY_TO > 0
+		clampVelocity(m_linearVelocity);
+		#endif
 	}
 
 	void applyTorqueImpulse(const btVector3& torque)
 	{
 		m_angularVelocity += m_invInertiaTensorWorld * torque * m_angularFactor;
+		#if defined(BT_CLAMP_VELOCITY_TO) && BT_CLAMP_VELOCITY_TO > 0
+		clampVelocity(m_angularVelocity);
+		#endif
 	}
 
 	void applyImpulse(const btVector3& impulse, const btVector3& rel_pos)
@@ -361,20 +370,46 @@ class btRigidBody : public btCollisionObject
     {
         m_pushVelocity = v;
     }
-    
+
+    #if defined(BT_CLAMP_VELOCITY_TO) && BT_CLAMP_VELOCITY_TO > 0
+    void clampVelocity(btVector3& v) const {
+        v.setX(
+            fmax(-BT_CLAMP_VELOCITY_TO,
+                 fmin(BT_CLAMP_VELOCITY_TO, v.getX()))
+        );
+        v.setY(
+            fmax(-BT_CLAMP_VELOCITY_TO,
+                 fmin(BT_CLAMP_VELOCITY_TO, v.getY()))
+        );
+        v.setZ(
+            fmax(-BT_CLAMP_VELOCITY_TO,
+                 fmin(BT_CLAMP_VELOCITY_TO, v.getZ()))
+        );
+    }
+    #endif
+
     void setTurnVelocity(const btVector3& v)
     {
         m_turnVelocity = v;
+        #if defined(BT_CLAMP_VELOCITY_TO) && BT_CLAMP_VELOCITY_TO > 0
+        clampVelocity(m_turnVelocity);
+        #endif
     }
     
     void applyCentralPushImpulse(const btVector3& impulse)
     {
         m_pushVelocity += impulse * m_linearFactor * m_inverseMass;
+        #if defined(BT_CLAMP_VELOCITY_TO) && BT_CLAMP_VELOCITY_TO > 0
+        clampVelocity(m_pushVelocity);
+        #endif
     }
     
     void applyTorqueTurnImpulse(const btVector3& torque)
     {
         m_turnVelocity += m_invInertiaTensorWorld * torque * m_angularFactor;
+        #if defined(BT_CLAMP_VELOCITY_TO) && BT_CLAMP_VELOCITY_TO > 0
+        clampVelocity(m_turnVelocity);
+        #endif
     }
 
 	void clearForces()
@@ -408,12 +443,18 @@ class btRigidBody : public btCollisionObject
 	{
 		m_updateRevision++;
 		m_linearVelocity = lin_vel;
+		#if defined(BT_CLAMP_VELOCITY_TO) && BT_CLAMP_VELOCITY_TO > 0
+		clampVelocity(m_linearVelocity);
+		#endif
 	}
 
 	inline void setAngularVelocity(const btVector3& ang_vel)
 	{
 		m_updateRevision++;
 		m_angularVelocity = ang_vel;
+		#if defined(BT_CLAMP_VELOCITY_TO) && BT_CLAMP_VELOCITY_TO > 0
+		clampVelocity(m_angularVelocity);
+		#endif
 	}
 
 	btVector3 getVelocityInLocalPoint(const btVector3& rel_pos) const