Browse Source

Configurable maximum substeps per frame in PhysicsWorld. Zero (default) = unlimited, positive = limited, negative = adaptive timestep, always 1 per rendering frame. Adaptive step is not recommended when consistent physics behavior is desired.

Lasse Öörni 11 years ago
parent
commit
b65bb59092

+ 1 - 1
Docs/Reference.dox

@@ -1529,7 +1529,7 @@ Urho3D implements rigid body physics simulation using the Bullet library.
 
 To use, a PhysicsWorld component must first be created to the Scene.
 
-The physics simulation has its own fixed update rate, which by default is 60Hz. When the rendering framerate is higher than the physics update rate, physics motion is interpolated so that it always appears smooth. The update rate can be changed with \ref PhysicsWorld::SetFps "SetFps()" function. The physics update rate also determines the frequency of fixed timestep scene logic updates.
+The physics simulation has its own fixed update rate, which by default is 60Hz. When the rendering framerate is higher than the physics update rate, physics motion is interpolated so that it always appears smooth. The update rate can be changed with \ref PhysicsWorld::SetFps "SetFps()" function. The physics update rate also determines the frequency of fixed timestep scene logic updates. Hard limit for physics steps per frame or adaptive timestep can be configured with \ref PhysicsWorld::SetMaxSubSteps "SetMaxSubSteps()" function. These can help to prevent a "spiral of death" due to the CPU being unable to handle the physics load. However, note that using either can lead to time slowing down (when steps are limited) or inconsistent physics behavior (when using adaptive step.)
 
 The other physics components are:
 

+ 3 - 0
Source/Engine/LuaScript/pkgs/Physics/PhysicsWorld.pkg

@@ -17,6 +17,7 @@ class PhysicsWorld : public Component
     void UpdateCollisions();
     void SetFps(int fps);
     void SetGravity(Vector3 gravity);
+    void SetMaxSubSteps(int num);
     void SetNumIterations(int num);
     void SetInterpolation(bool enable);
     void SetInternalEdge(bool enable);
@@ -43,6 +44,7 @@ class PhysicsWorld : public Component
     void RemoveCachedGeometry(Model* model);
 
     Vector3 GetGravity() const;
+    int GetMaxSubSteps() const;
     int GetNumIterations() const;
     bool GetInterpolation() const;
     bool GetInternalEdge() const;
@@ -51,6 +53,7 @@ class PhysicsWorld : public Component
     float GetMaxNetworkAngularVelocity() const;
 
     tolua_property__get_set Vector3 gravity;
+    tolua_property__get_set int maxSubSteps;
     tolua_property__get_set int numIterations;
     tolua_property__get_set bool interpolation;
     tolua_property__get_set bool internalEdge;

+ 19 - 4
Source/Engine/Physics/PhysicsWorld.cpp

@@ -119,6 +119,7 @@ PhysicsWorld::PhysicsWorld(Context* context) :
     solver_(0),
     world_(0),
     fps_(DEFAULT_FPS),
+    maxSubSteps_(0),
     timeAcc_(0.0f),
     maxNetworkAngularVelocity_(DEFAULT_MAX_NETWORK_ANGULAR_VELOCITY),
     interpolation_(true),
@@ -180,6 +181,7 @@ void PhysicsWorld::RegisterObject(Context* context)
 
     ACCESSOR_ATTRIBUTE(PhysicsWorld, VAR_VECTOR3, "Gravity", GetGravity, SetGravity, Vector3, DEFAULT_GRAVITY, AM_DEFAULT);
     ATTRIBUTE(PhysicsWorld, VAR_INT, "Physics FPS", fps_, DEFAULT_FPS, AM_DEFAULT);
+    ATTRIBUTE(PhysicsWorld, VAR_INT, "Max Substeps", maxSubSteps_, 0, AM_DEFAULT);
     ACCESSOR_ATTRIBUTE(PhysicsWorld, VAR_INT, "Solver Iterations", GetNumIterations, SetNumIterations, int, 10, AM_DEFAULT);
     ATTRIBUTE(PhysicsWorld, VAR_FLOAT, "Net Max Angular Vel.", maxNetworkAngularVelocity_, DEFAULT_MAX_NETWORK_ANGULAR_VELOCITY, AM_DEFAULT);
     ATTRIBUTE(PhysicsWorld, VAR_BOOL, "Interpolation", interpolation_, true, AM_FILE);
@@ -232,20 +234,27 @@ void PhysicsWorld::Update(float timeStep)
     PROFILE(UpdatePhysics);
 
     float internalTimeStep = 1.0f / fps_;
+    int maxSubSteps = (int)(timeStep * fps_) + 1;
+    if (maxSubSteps_ < 0)
+    {
+        internalTimeStep = timeStep;
+        maxSubSteps = 1;
+    }
+    else if (maxSubSteps_ > 0)
+        maxSubSteps = Min(maxSubSteps, maxSubSteps_);
+    
     delayedWorldTransforms_.Clear();
 
     if (interpolation_)
-    {
-        int maxSubSteps = (int)(timeStep * fps_) + 1;
         world_->stepSimulation(timeStep, maxSubSteps, internalTimeStep);
-    }
     else
     {
         timeAcc_ += timeStep;
-        while (timeAcc_ >= internalTimeStep)
+        while (timeAcc_ >= internalTimeStep && maxSubSteps > 0)
         {
             world_->stepSimulation(internalTimeStep, 0, internalTimeStep);
             timeAcc_ -= internalTimeStep;
+            --maxSubSteps;
         }
     }
 
@@ -286,6 +295,12 @@ void PhysicsWorld::SetGravity(Vector3 gravity)
     MarkNetworkUpdate();
 }
 
+void PhysicsWorld::SetMaxSubSteps(int num)
+{
+    maxSubSteps_ = num;
+    MarkNetworkUpdate();
+}
+
 void PhysicsWorld::SetNumIterations(int num)
 {
     num = Clamp(num, 1, MAX_SOLVER_ITERATIONS);

+ 8 - 2
Source/Engine/Physics/PhysicsWorld.h

@@ -130,10 +130,12 @@ public:
     void Update(float timeStep);
     /// Refresh collisions only without updating dynamics.
     void UpdateCollisions();
-    /// Set simulation steps per second.
+    /// Set simulation substeps per second.
     void SetFps(int fps);
     /// Set gravity.
     void SetGravity(Vector3 gravity);
+    /// Set maximum number of physics substeps per frame. 0 (default) is unlimited. Positive values cap the amount. Use a negative value to enable an adaptive timestep. This may cause inconsistent physics behavior.
+    void SetMaxSubSteps(int num);
     /// Set number of constraint solver iterations.
     void SetNumIterations(int num);
     /// Set whether to interpolate between simulation steps.
@@ -165,6 +167,8 @@ public:
     
     /// Return gravity.
     Vector3 GetGravity() const;
+    /// Return maximum number of physics substeps per frame.
+    int GetMaxSubSteps() const { return maxSubSteps_; }
     /// Return number of constraint solver iterations.
     int GetNumIterations() const;
     /// Return whether interpolation between simulation steps is enabled.
@@ -262,8 +266,10 @@ private:
     VariantMap nodeCollisionData_;
     /// Preallocated buffer for physics collision contact data.
     VectorBuffer contacts_;
-    /// Simulation steps per second.
+    /// Simulation substeps per second.
     unsigned fps_;
+    /// Maximum number of simulation substeps per frame. 0 (default) unlimited, or negative values for adaptive timestep.
+    int maxSubSteps_;
     /// Time accumulator for non-interpolated mode.
     float timeAcc_;
     /// Maximum angular velocity for network replication.

+ 2 - 0
Source/Engine/Script/PhysicsAPI.cpp

@@ -305,6 +305,8 @@ static void RegisterPhysicsWorld(asIScriptEngine* engine)
     engine->RegisterObjectMethod("PhysicsWorld", "void RemoveCachedGeometry(Model@+)", asMETHOD(PhysicsWorld, RemoveCachedGeometry), asCALL_THISCALL);
     engine->RegisterObjectMethod("PhysicsWorld", "void set_gravity(Vector3)", asMETHOD(PhysicsWorld, SetGravity), asCALL_THISCALL);
     engine->RegisterObjectMethod("PhysicsWorld", "Vector3 get_gravity() const", asMETHOD(PhysicsWorld, GetGravity), asCALL_THISCALL);
+    engine->RegisterObjectMethod("PhysicsWorld", "void set_maxSubSteps(int)", asMETHOD(PhysicsWorld, SetMaxSubSteps), asCALL_THISCALL);
+    engine->RegisterObjectMethod("PhysicsWorld", "int get_maxSubSteps() const", asMETHOD(PhysicsWorld, GetMaxSubSteps), asCALL_THISCALL);
     engine->RegisterObjectMethod("PhysicsWorld", "void set_numIterations(int)", asMETHOD(PhysicsWorld, SetNumIterations), asCALL_THISCALL);
     engine->RegisterObjectMethod("PhysicsWorld", "int get_numIterations() const", asMETHOD(PhysicsWorld, GetNumIterations), asCALL_THISCALL);
     engine->RegisterObjectMethod("PhysicsWorld", "void set_fps(int)", asMETHOD(PhysicsWorld, SetFps), asCALL_THISCALL);