Skip to content

Commit

Permalink
Added 'is first step' and 'is last step' to the PhysicsStepListener (#…
Browse files Browse the repository at this point in the history
…1284)

PhysicsStepListener::OnStep now receives a single PhysicsStepListenerContext parameter, making it easier to extend parameters later without breaking the API.
  • Loading branch information
jrouwe authored Sep 27, 2024
1 parent b6e22f7 commit 8153cd8
Show file tree
Hide file tree
Showing 7 changed files with 58 additions and 25 deletions.
12 changes: 11 additions & 1 deletion Jolt/Physics/PhysicsStepListener.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,16 @@ JPH_NAMESPACE_BEGIN

class PhysicsSystem;

/// Context information for the step listener
class JPH_EXPORT PhysicsStepListenerContext
{
public:
float mDeltaTime; ///< Delta time of the current step
bool mIsFirstStep; ///< True if this is the first step
bool mIsLastStep; ///< True if this is the last step
PhysicsSystem * mPhysicsSystem; ///< The physics system that is being stepped
};

/// A listener class that receives a callback before every physics simulation step
class JPH_EXPORT PhysicsStepListener
{
Expand All @@ -21,7 +31,7 @@ class JPH_EXPORT PhysicsStepListener
/// The best way to do this is to have each step listener operate on a subset of the bodies and constraints
/// and making sure that these bodies and constraints are not touched by any other step listener.
/// Note that this function is not called if there aren't any active bodies or when the physics system is updated with 0 delta time.
virtual void OnStep(float inDeltaTime, PhysicsSystem &inPhysicsSystem) = 0;
virtual void OnStep(const PhysicsStepListenerContext &inContext) = 0;
};

JPH_NAMESPACE_END
9 changes: 7 additions & 2 deletions Jolt/Physics/PhysicsSystem.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -626,7 +626,12 @@ void PhysicsSystem::JobStepListeners(PhysicsUpdateContext::Step *ioStep)
BodyManager::GrantActiveBodiesAccess grant_active(true, false);
#endif

float step_time = ioStep->mContext->mStepDeltaTime;
PhysicsStepListenerContext context;
context.mDeltaTime = ioStep->mContext->mStepDeltaTime;
context.mIsFirstStep = ioStep->mIsFirst;
context.mIsLastStep = ioStep->mIsLast;
context.mPhysicsSystem = this;

uint32 batch_size = mPhysicsSettings.mStepListenersBatchSize;
for (;;)
{
Expand All @@ -637,7 +642,7 @@ void PhysicsSystem::JobStepListeners(PhysicsUpdateContext::Step *ioStep)

// Call the listeners
for (uint32 i = batch, i_end = min((uint32)mStepListeners.size(), batch + batch_size); i < i_end; ++i)
mStepListeners[i]->OnStep(step_time, *this);
mStepListeners[i]->OnStep(context);
}
}

Expand Down
22 changes: 11 additions & 11 deletions Jolt/Physics/Vehicle/VehicleConstraint.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -157,13 +157,13 @@ RMat44 VehicleConstraint::GetWheelWorldTransform(uint inWheelIndex, Vec3Arg inWh
return mBody->GetWorldTransform() * GetWheelLocalTransform(inWheelIndex, inWheelRight, inWheelUp);
}

void VehicleConstraint::OnStep(float inDeltaTime, PhysicsSystem &inPhysicsSystem)
void VehicleConstraint::OnStep(const PhysicsStepListenerContext &inContext)
{
JPH_PROFILE_FUNCTION();

// Callback to higher-level systems. We do it before PreCollide, in case steering changes.
if (mPreStepCallback != nullptr)
mPreStepCallback(*this, inDeltaTime, inPhysicsSystem);
mPreStepCallback(*this, inContext);

if (mIsGravityOverridden)
{
Expand All @@ -181,11 +181,11 @@ void VehicleConstraint::OnStep(float inDeltaTime, PhysicsSystem &inPhysicsSystem
else
{
// Calculate new world up vector by inverting gravity
mWorldUp = (-inPhysicsSystem.GetGravity()).NormalizedOr(mWorldUp);
mWorldUp = (-inContext.mPhysicsSystem->GetGravity()).NormalizedOr(mWorldUp);
}

// Callback on our controller
mController->PreCollide(inDeltaTime, inPhysicsSystem);
mController->PreCollide(inContext.mDeltaTime, *inContext.mPhysicsSystem);

// Calculate if this constraint is active by checking if our main vehicle body is active or any of the bodies we touch are active
mIsActive = mBody->IsActive();
Expand Down Expand Up @@ -213,7 +213,7 @@ void VehicleConstraint::OnStep(float inDeltaTime, PhysicsSystem &inPhysicsSystem
if (!w->mContactBodyID.IsInvalid())
{
// Test if the body is still valid
w->mContactBody = inPhysicsSystem.GetBodyLockInterfaceNoLock().TryGetBody(w->mContactBodyID);
w->mContactBody = inContext.mPhysicsSystem->GetBodyLockInterfaceNoLock().TryGetBody(w->mContactBodyID);
if (w->mContactBody == nullptr)
{
// It's not, forget the contact
Expand All @@ -224,7 +224,7 @@ void VehicleConstraint::OnStep(float inDeltaTime, PhysicsSystem &inPhysicsSystem
else
{
// Extrapolate the wheel contact properties
mVehicleCollisionTester->PredictContactProperties(inPhysicsSystem, *this, wheel_index, ws_origin, ws_direction, mBody->GetID(), w->mContactBody, w->mContactSubShapeID, w->mContactPosition, w->mContactNormal, w->mSuspensionLength);
mVehicleCollisionTester->PredictContactProperties(*inContext.mPhysicsSystem, *this, wheel_index, ws_origin, ws_direction, mBody->GetID(), w->mContactBody, w->mContactSubShapeID, w->mContactPosition, w->mContactNormal, w->mSuspensionLength);
}
}
}
Expand All @@ -237,7 +237,7 @@ void VehicleConstraint::OnStep(float inDeltaTime, PhysicsSystem &inPhysicsSystem
w->mSuspensionLength = settings->mSuspensionMaxLength;

// Test collision to find the floor
if (mVehicleCollisionTester->Collide(inPhysicsSystem, *this, wheel_index, ws_origin, ws_direction, mBody->GetID(), w->mContactBody, w->mContactSubShapeID, w->mContactPosition, w->mContactNormal, w->mSuspensionLength))
if (mVehicleCollisionTester->Collide(*inContext.mPhysicsSystem, *this, wheel_index, ws_origin, ws_direction, mBody->GetID(), w->mContactBody, w->mContactSubShapeID, w->mContactPosition, w->mContactNormal, w->mSuspensionLength))
{
// Store ID (pointer is not valid outside of the simulation step)
w->mContactBodyID = w->mContactBody->GetID();
Expand Down Expand Up @@ -278,7 +278,7 @@ void VehicleConstraint::OnStep(float inDeltaTime, PhysicsSystem &inPhysicsSystem

// Callback to higher-level systems. We do it immediately after wheel collision.
if (mPostCollideCallback != nullptr)
mPostCollideCallback(*this, inDeltaTime, inPhysicsSystem);
mPostCollideCallback(*this, inContext);

// Calculate anti-rollbar impulses
for (const VehicleAntiRollBar &r : mAntiRollBars)
Expand All @@ -290,7 +290,7 @@ void VehicleConstraint::OnStep(float inDeltaTime, PhysicsSystem &inPhysicsSystem
{
// Calculate the impulse to apply based on the difference in suspension length
float difference = rw->mSuspensionLength - lw->mSuspensionLength;
float impulse = difference * r.mStiffness * inDeltaTime;
float impulse = difference * r.mStiffness * inContext.mDeltaTime;
lw->mAntiRollBarImpulse = -impulse;
rw->mAntiRollBarImpulse = impulse;
}
Expand All @@ -302,11 +302,11 @@ void VehicleConstraint::OnStep(float inDeltaTime, PhysicsSystem &inPhysicsSystem
}

// Callback on our controller
mController->PostCollide(inDeltaTime, inPhysicsSystem);
mController->PostCollide(inContext.mDeltaTime, *inContext.mPhysicsSystem);

// Callback to higher-level systems. We do it before the sleep section, in case velocities change.
if (mPostStepCallback != nullptr)
mPostStepCallback(*this, inDeltaTime, inPhysicsSystem);
mPostStepCallback(*this, inContext);

// If the wheels are rotating, we don't want to go to sleep yet
bool allow_sleep = mController->AllowSleep();
Expand Down
4 changes: 2 additions & 2 deletions Jolt/Physics/Vehicle/VehicleConstraint.h
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ class JPH_EXPORT VehicleConstraint : public Constraint, public PhysicsStepListen
const CombineFunction & GetCombineFriction() const { return mCombineFriction; }

/// Callback function to notify of current stage in PhysicsStepListener::OnStep.
using StepCallback = function<void(VehicleConstraint &inVehicle, float inDeltaTime, PhysicsSystem &inPhysicsSystem)>;
using StepCallback = function<void(VehicleConstraint &inVehicle, const PhysicsStepListenerContext &inContext)>;

/// Callback function to notify that PhysicsStepListener::OnStep has started for this vehicle. Default is to do nothing.
/// Can be used to allow higher-level code to e.g. control steering. This is the last moment that the position/orientation of the vehicle can be changed.
Expand Down Expand Up @@ -196,7 +196,7 @@ class JPH_EXPORT VehicleConstraint : public Constraint, public PhysicsStepListen

private:
// See: PhysicsStepListener
virtual void OnStep(float inDeltaTime, PhysicsSystem &inPhysicsSystem) override;
virtual void OnStep(const PhysicsStepListenerContext &inContext) override;

// Calculate the position where the suspension and traction forces should be applied in world space, relative to the center of mass of both bodies
void CalculateSuspensionForcePoint(const Wheel &inWheel, Vec3 &outR1PlusU, Vec3 &outR2) const;
Expand Down
8 changes: 4 additions & 4 deletions Samples/Tests/Character/CharacterPlanetTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -170,19 +170,19 @@ void CharacterPlanetTest::RestoreInputState(StateRecorder &inStream)
inStream.Read(mJump);
}

void CharacterPlanetTest::OnStep(float inDeltaTime, PhysicsSystem &inPhysicsSystem)
void CharacterPlanetTest::OnStep(const PhysicsStepListenerContext &inContext)
{
// Use the length of the global gravity vector
float gravity = inPhysicsSystem.GetGravity().Length();
float gravity = inContext.mPhysicsSystem->GetGravity().Length();

// We don't need to lock the bodies since they're already locked in the OnStep callback.
// Note that this means we're responsible for avoiding race conditions with other step listeners while accessing bodies.
// We know that this is safe because in this demo there's only one step listener.
const BodyLockInterface &body_interface = inPhysicsSystem.GetBodyLockInterfaceNoLock();
const BodyLockInterface &body_interface = inContext.mPhysicsSystem->GetBodyLockInterfaceNoLock();

// Loop over all active bodies
BodyIDVector body_ids;
inPhysicsSystem.GetActiveBodies(EBodyType::RigidBody, body_ids);
inContext.mPhysicsSystem->GetActiveBodies(EBodyType::RigidBody, body_ids);
for (const BodyID &id : body_ids)
{
BodyLockWrite lock(body_interface, id);
Expand Down
2 changes: 1 addition & 1 deletion Samples/Tests/Character/CharacterPlanetTest.h
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ class CharacterPlanetTest : public Test, public PhysicsStepListener, public Char
virtual void RestoreInputState(StateRecorder &inStream) override;

// See: PhysicsStepListener
virtual void OnStep(float inDeltaTime, PhysicsSystem &inPhysicsSystem) override;
virtual void OnStep(const PhysicsStepListenerContext &inContext) override;

// See: CharacterContactListener
virtual void OnContactAdded(const CharacterVirtual *inCharacter, const BodyID &inBodyID2, const SubShapeID &inSubShapeID2, RVec3Arg inContactPosition, Vec3Arg inContactNormal, CharacterContactSettings &ioSettings) override;
Expand Down
26 changes: 22 additions & 4 deletions UnitTests/Physics/PhysicsStepListenerTests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,16 @@ TEST_SUITE("StepListenerTest")
class TestStepListener : public PhysicsStepListener
{
public:
virtual void OnStep(float inDeltaTime, PhysicsSystem &inPhysicsSystem) override
virtual void OnStep(const PhysicsStepListenerContext &inContext) override
{
CHECK(inDeltaTime == mExpectedDeltaTime);

++mCount;
CHECK(inContext.mDeltaTime == mExpectedDeltaTime);
CHECK(inContext.mIsFirstStep == ((mCount % mExpectedSteps) == 0));
int new_count = mCount.fetch_add(1) + 1;
CHECK(inContext.mIsLastStep == ((new_count % mExpectedSteps) == 0));
}

atomic<int> mCount = 0;
int mExpectedSteps;
float mExpectedDeltaTime = 0.0f;
};

Expand All @@ -32,7 +34,10 @@ TEST_SUITE("StepListenerTest")
// Initialize and add listeners
TestStepListener listeners[10];
for (TestStepListener &l : listeners)
{
l.mExpectedDeltaTime = 1.0f / 60.0f / inCollisionSteps;
l.mExpectedSteps = inCollisionSteps;
}
for (TestStepListener &l : listeners)
c.GetSystem()->AddStepListener(&l);

Expand Down Expand Up @@ -61,6 +66,13 @@ TEST_SUITE("StepListenerTest")
// Unregister all listeners
for (TestStepListener &l : listeners)
c.GetSystem()->RemoveStepListener(&l);

// Step the simulation
c.SimulateSingleStep();

// Check that no further callbacks were triggered
for (TestStepListener &l : listeners)
CHECK(l.mCount == 2 * inCollisionSteps);
}

// Test the step listeners with a single collision step
Expand All @@ -74,4 +86,10 @@ TEST_SUITE("StepListenerTest")
{
DoTest(2);
}

// Test the step listeners with four collision steps
TEST_CASE("TestStepListener4")
{
DoTest(4);
}
}

0 comments on commit 8153cd8

Please sign in to comment.