feat: modules moved and engine moved to submodule

This commit is contained in:
Jan van der Weide 2025-04-12 18:40:44 +02:00
parent dfb5e645cd
commit c33d2130cc
5136 changed files with 225275 additions and 64485 deletions

View file

@ -34,7 +34,7 @@ Character::Character(const CharacterSettings *inSettings, RVec3Arg inPosition, Q
{
// Construct rigid body
BodyCreationSettings settings(mShape, inPosition, inRotation, EMotionType::Dynamic, mLayer);
settings.mAllowedDOFs = EAllowedDOFs::TranslationX | EAllowedDOFs::TranslationY | EAllowedDOFs::TranslationZ;
settings.mAllowedDOFs = inSettings->mAllowedDOFs;
settings.mEnhancedInternalEdgeRemoval = inSettings->mEnhancedInternalEdgeRemoval;
settings.mOverrideMassProperties = EOverrideMassProperties::MassAndInertiaProvided;
settings.mMassPropertiesOverride.mMass = inSettings->mMass;
@ -75,8 +75,18 @@ void Character::CheckCollision(RMat44Arg inCenterOfMassTransform, Vec3Arg inMove
// Create query object layer filter
DefaultObjectLayerFilter object_layer_filter = mSystem->GetDefaultLayerFilter(mLayer);
// Ignore my own body
IgnoreSingleBodyFilter body_filter(mBodyID);
// Ignore sensors and my own body
class CharacterBodyFilter : public IgnoreSingleBodyFilter
{
public:
using IgnoreSingleBodyFilter::IgnoreSingleBodyFilter;
virtual bool ShouldCollideLocked(const Body &inBody) const override
{
return !inBody.IsSensor();
}
};
CharacterBodyFilter body_filter(mBodyID);
// Settings for collide shape
CollideShapeSettings settings;
@ -85,7 +95,7 @@ void Character::CheckCollision(RMat44Arg inCenterOfMassTransform, Vec3Arg inMove
settings.mActiveEdgeMovementDirection = inMovementDirection;
settings.mBackFaceMode = EBackFaceMode::IgnoreBackFaces;
sGetNarrowPhaseQuery(mSystem, inLockBodies).CollideShape(inShape, Vec3::sReplicate(1.0f), inCenterOfMassTransform, settings, inBaseOffset, ioCollector, broadphase_layer_filter, object_layer_filter, body_filter);
sGetNarrowPhaseQuery(mSystem, inLockBodies).CollideShape(inShape, Vec3::sOne(), inCenterOfMassTransform, settings, inBaseOffset, ioCollector, broadphase_layer_filter, object_layer_filter, body_filter);
}
void Character::CheckCollision(RVec3Arg inPosition, QuatArg inRotation, Vec3Arg inMovementDirection, float inMaxSeparationDistance, const Shape *inShape, RVec3Arg inBaseOffset, CollideShapeCollector &ioCollector, bool inLockBodies) const

View file

@ -8,6 +8,7 @@
#include <Jolt/Physics/Collision/ObjectLayer.h>
#include <Jolt/Physics/Collision/TransformedShape.h>
#include <Jolt/Physics/EActivation.h>
#include <Jolt/Physics/Body/AllowedDOFs.h>
JPH_NAMESPACE_BEGIN
@ -28,6 +29,9 @@ public:
/// Value to multiply gravity with for this character
float mGravityFactor = 1.0f;
/// Allowed degrees of freedom for this character
EAllowedDOFs mAllowedDOFs = EAllowedDOFs::TranslationX | EAllowedDOFs::TranslationY | EAllowedDOFs::TranslationZ;
};
/// Runtime character object.

View file

@ -0,0 +1,98 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2025 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#pragma once
#include <Jolt/Core/HashCombine.h>
JPH_NAMESPACE_BEGIN
/// ID of a character. Used primarily to identify deleted characters and to sort deterministically.
class JPH_EXPORT CharacterID
{
public:
JPH_OVERRIDE_NEW_DELETE
static constexpr uint32 cInvalidCharacterID = 0xffffffff; ///< The value for an invalid character ID
/// Construct invalid character ID
CharacterID() :
mID(cInvalidCharacterID)
{
}
/// Construct with specific value, make sure you don't use the same value twice!
explicit CharacterID(uint32 inID) :
mID(inID)
{
}
/// Get the numeric value of the ID
inline uint32 GetValue() const
{
return mID;
}
/// Check if the ID is valid
inline bool IsInvalid() const
{
return mID == cInvalidCharacterID;
}
/// Equals check
inline bool operator == (const CharacterID &inRHS) const
{
return mID == inRHS.mID;
}
/// Not equals check
inline bool operator != (const CharacterID &inRHS) const
{
return mID != inRHS.mID;
}
/// Smaller than operator, can be used for sorting characters
inline bool operator < (const CharacterID &inRHS) const
{
return mID < inRHS.mID;
}
/// Greater than operator, can be used for sorting characters
inline bool operator > (const CharacterID &inRHS) const
{
return mID > inRHS.mID;
}
/// Get the hash for this character ID
inline uint64 GetHash() const
{
return Hash<uint32>{} (mID);
}
/// Generate the next available character ID
static CharacterID sNextCharacterID()
{
for (;;)
{
uint32 next = sNextID.fetch_add(1, std::memory_order_relaxed);
if (next != cInvalidCharacterID)
return CharacterID(next);
}
}
/// Set the next available character ID, can be used after destroying all character to prepare for a second deterministic run
static void sSetNextCharacterID(uint32 inNextValue = 1)
{
sNextID.store(inNextValue, std::memory_order_relaxed);
}
private:
/// Next character ID to be assigned
inline static atomic<uint32> sNextID = 1;
/// ID value
uint32 mID;
};
JPH_NAMESPACE_END

View file

@ -14,8 +14,10 @@
#include <Jolt/Physics/Collision/Shape/ScaledShape.h>
#include <Jolt/Physics/Collision/CollisionDispatch.h>
#include <Jolt/Core/QuickSort.h>
#include <Jolt/Core/ScopeExit.h>
#include <Jolt/Geometry/ConvexSupport.h>
#include <Jolt/Geometry/GJKClosestPoint.h>
#include <Jolt/Geometry/RayAABox.h>
#ifdef JPH_DEBUG_RENDERER
#include <Jolt/Renderer/DebugRenderer.h>
#endif // JPH_DEBUG_RENDERER
@ -34,25 +36,35 @@ void CharacterVsCharacterCollisionSimple::CollideCharacter(const CharacterVirtua
// Make shape 1 relative to inBaseOffset
Mat44 transform1 = inCenterOfMassTransform.PostTranslated(-inBaseOffset).ToMat44();
const Shape *shape = inCharacter->GetShape();
const Shape *shape1 = inCharacter->GetShape();
CollideShapeSettings settings = inCollideShapeSettings;
// Get bounds for character
AABox bounds1 = shape1->GetWorldSpaceBounds(transform1, Vec3::sOne());
// Iterate over all characters
for (const CharacterVirtual *c : mCharacters)
if (c != inCharacter
&& !ioCollector.ShouldEarlyOut())
{
// Collector needs to know which character we're colliding with
ioCollector.SetUserData(reinterpret_cast<uint64>(c));
// Make shape 2 relative to inBaseOffset
Mat44 transform2 = c->GetCenterOfMassTransform().PostTranslated(-inBaseOffset).ToMat44();
// We need to add the padding of character 2 so that we will detect collision with its outer shell
settings.mMaxSeparationDistance = inCollideShapeSettings.mMaxSeparationDistance + c->GetCharacterPadding();
// Check if the bounding boxes of the characters overlap
const Shape *shape2 = c->GetShape();
AABox bounds2 = shape2->GetWorldSpaceBounds(transform2, Vec3::sOne());
bounds2.ExpandBy(Vec3::sReplicate(settings.mMaxSeparationDistance));
if (!bounds1.Overlaps(bounds2))
continue;
// Collector needs to know which character we're colliding with
ioCollector.SetUserData(reinterpret_cast<uint64>(c));
// Note that this collides against the character's shape without padding, this will be corrected for in CharacterVirtual::GetContactsAtPosition
CollisionDispatch::sCollideShapeVsShape(shape, c->GetShape(), Vec3::sReplicate(1.0f), Vec3::sReplicate(1.0f), transform1, transform2, SubShapeIDCreator(), SubShapeIDCreator(), settings, ioCollector);
CollisionDispatch::sCollideShapeVsShape(shape1, shape2, Vec3::sOne(), Vec3::sOne(), transform1, transform2, SubShapeIDCreator(), SubShapeIDCreator(), settings, ioCollector);
}
// Reset the user data
@ -63,21 +75,32 @@ void CharacterVsCharacterCollisionSimple::CastCharacter(const CharacterVirtual *
{
// Convert shape cast relative to inBaseOffset
Mat44 transform1 = inCenterOfMassTransform.PostTranslated(-inBaseOffset).ToMat44();
ShapeCast shape_cast(inCharacter->GetShape(), Vec3::sReplicate(1.0f), transform1, inDirection);
ShapeCast shape_cast(inCharacter->GetShape(), Vec3::sOne(), transform1, inDirection);
// Get world space bounds of the character in the form of center and extent
Vec3 origin = shape_cast.mShapeWorldBounds.GetCenter();
Vec3 extents = shape_cast.mShapeWorldBounds.GetExtent();
// Iterate over all characters
for (const CharacterVirtual *c : mCharacters)
if (c != inCharacter
&& !ioCollector.ShouldEarlyOut())
{
// Collector needs to know which character we're colliding with
ioCollector.SetUserData(reinterpret_cast<uint64>(c));
// Make shape 2 relative to inBaseOffset
Mat44 transform2 = c->GetCenterOfMassTransform().PostTranslated(-inBaseOffset).ToMat44();
// Sweep bounding box of the character against the bounding box of the other character to see if they can collide
const Shape *shape2 = c->GetShape();
AABox bounds2 = shape2->GetWorldSpaceBounds(transform2, Vec3::sOne());
bounds2.ExpandBy(extents);
if (!RayAABoxHits(origin, inDirection, bounds2.mMin, bounds2.mMax))
continue;
// Collector needs to know which character we're colliding with
ioCollector.SetUserData(reinterpret_cast<uint64>(c));
// Note that this collides against the character's shape without padding, this will be corrected for in CharacterVirtual::GetFirstContactForSweep
CollisionDispatch::sCastShapeVsShapeWorldSpace(shape_cast, inShapeCastSettings, c->GetShape(), Vec3::sReplicate(1.0f), { }, transform2, SubShapeIDCreator(), SubShapeIDCreator(), ioCollector);
CollisionDispatch::sCastShapeVsShapeWorldSpace(shape_cast, inShapeCastSettings, shape2, Vec3::sOne(), { }, transform2, SubShapeIDCreator(), SubShapeIDCreator(), ioCollector);
}
// Reset the user data
@ -86,6 +109,7 @@ void CharacterVsCharacterCollisionSimple::CastCharacter(const CharacterVirtual *
CharacterVirtual::CharacterVirtual(const CharacterVirtualSettings *inSettings, RVec3Arg inPosition, QuatArg inRotation, uint64 inUserData, PhysicsSystem *inSystem) :
CharacterBase(inSettings, inSystem),
mID(inSettings->mID),
mBackFaceMode(inSettings->mBackFaceMode),
mPredictiveContactDistance(inSettings->mPredictiveContactDistance),
mMaxCollisionIterations(inSettings->mMaxCollisionIterations),
@ -102,6 +126,8 @@ CharacterVirtual::CharacterVirtual(const CharacterVirtualSettings *inSettings, R
mRotation(inRotation),
mUserData(inUserData)
{
JPH_ASSERT(!mID.IsInvalid());
// Copy settings
SetMaxStrength(inSettings->mMaxStrength);
SetMass(inSettings->mMass);
@ -112,7 +138,18 @@ CharacterVirtual::CharacterVirtual(const CharacterVirtualSettings *inSettings, R
BodyCreationSettings settings(inSettings->mInnerBodyShape, GetInnerBodyPosition(), mRotation, EMotionType::Kinematic, inSettings->mInnerBodyLayer);
settings.mAllowSleeping = false; // Disable sleeping so that we will receive sensor callbacks
settings.mUserData = inUserData;
mInnerBodyID = inSystem->GetBodyInterface().CreateAndAddBody(settings, EActivation::Activate);
const Body *inner_body;
BodyInterface &bi = inSystem->GetBodyInterface();
if (inSettings->mInnerBodyIDOverride.IsInvalid())
inner_body = bi.CreateBody(settings);
else
inner_body = bi.CreateBodyWithID(inSettings->mInnerBodyIDOverride, settings);
if (inner_body != nullptr)
{
mInnerBodyID = inner_body->GetID();
bi.AddBody(mInnerBodyID, EActivation::Activate);
}
}
}
@ -192,12 +229,13 @@ void CharacterVirtual::sFillContactProperties(const CharacterVirtual *inCharacte
outContact.mMaterial = inCollector.GetContext()->GetMaterial(inResult.mSubShapeID2);
}
void CharacterVirtual::sFillCharacterContactProperties(Contact &outContact, CharacterVirtual *inOtherCharacter, RVec3Arg inBaseOffset, const CollideShapeResult &inResult)
void CharacterVirtual::sFillCharacterContactProperties(Contact &outContact, const CharacterVirtual *inOtherCharacter, RVec3Arg inBaseOffset, const CollideShapeResult &inResult)
{
outContact.mPosition = inBaseOffset + inResult.mContactPointOn2;
outContact.mLinearVelocity = inOtherCharacter->GetLinearVelocity();
outContact.mSurfaceNormal = outContact.mContactNormal = -inResult.mPenetrationAxis.NormalizedOr(Vec3::sZero());
outContact.mDistance = -inResult.mPenetrationDepth;
outContact.mCharacterIDB = inOtherCharacter->GetID();
outContact.mCharacterB = inOtherCharacter;
outContact.mSubShapeIDB = inResult.mSubShapeID2;
outContact.mMotionTypeB = EMotionType::Kinematic; // Other character is kinematic, we can't directly move it
@ -294,8 +332,8 @@ void CharacterVirtual::ContactCastCollector::AddHit(const ShapeCastResult &inRes
&& inResult.mPenetrationAxis.Dot(mDisplacement) > 0.0f) // Ignore penetrations that we're moving away from
{
// Test if this contact should be ignored
for (const IgnoredContact &c : mIgnoredContacts)
if (c.mBodyID == inResult.mBodyID2 && c.mSubShapeID == inResult.mSubShapeID2)
for (const ContactKey &c : mIgnoredContacts)
if (c.mBodyB == inResult.mBodyID2 && c.mSubShapeIDB == inResult.mSubShapeID2)
return;
Contact contact;
@ -355,7 +393,7 @@ void CharacterVirtual::CheckCollision(RVec3Arg inPosition, QuatArg inRotation, V
auto collide_shape_function = mEnhancedInternalEdgeRemoval? &NarrowPhaseQuery::CollideShapeWithInternalEdgeRemoval : &NarrowPhaseQuery::CollideShape;
// Collide shape
(mSystem->GetNarrowPhaseQuery().*collide_shape_function)(inShape, Vec3::sReplicate(1.0f), transform, settings, inBaseOffset, ioCollector, inBroadPhaseLayerFilter, inObjectLayerFilter, body_filter, inShapeFilter);
(mSystem->GetNarrowPhaseQuery().*collide_shape_function)(inShape, Vec3::sOne(), transform, settings, inBaseOffset, ioCollector, inBroadPhaseLayerFilter, inObjectLayerFilter, body_filter, inShapeFilter);
// Also collide with other characters
if (mCharacterVsCharacterCollision != nullptr)
@ -417,14 +455,14 @@ void CharacterVirtual::RemoveConflictingContacts(TempContactList &ioContacts, Ig
if (contact1.mDistance < contact2.mDistance)
{
// Discard the 2nd contact
outIgnoredContacts.emplace_back(contact2.mBodyB, contact2.mSubShapeIDB);
outIgnoredContacts.emplace_back(contact2);
ioContacts.erase(ioContacts.begin() + c2);
c2--;
}
else
{
// Discard the first contact
outIgnoredContacts.emplace_back(contact1.mBodyB, contact1.mSubShapeIDB);
outIgnoredContacts.emplace_back(contact1);
ioContacts.erase(ioContacts.begin() + c1);
c1--;
break;
@ -445,14 +483,38 @@ bool CharacterVirtual::ValidateContact(const Contact &inContact) const
return mListener->OnContactValidate(this, inContact.mBodyB, inContact.mSubShapeIDB);
}
void CharacterVirtual::ContactAdded(const Contact &inContact, CharacterContactSettings &ioSettings) const
void CharacterVirtual::ContactAdded(const Contact &inContact, CharacterContactSettings &ioSettings)
{
if (mListener != nullptr)
{
if (inContact.mCharacterB != nullptr)
mListener->OnCharacterContactAdded(this, inContact.mCharacterB, inContact.mSubShapeIDB, inContact.mPosition, -inContact.mContactNormal, ioSettings);
// Check if we already know this contact
ListenerContacts::iterator it = mListenerContacts.find(inContact);
if (it != mListenerContacts.end())
{
// Max 1 contact persisted callback
if (++it->second.mCount == 1)
{
if (inContact.mCharacterB != nullptr)
mListener->OnCharacterContactPersisted(this, inContact.mCharacterB, inContact.mSubShapeIDB, inContact.mPosition, -inContact.mContactNormal, ioSettings);
else
mListener->OnContactPersisted(this, inContact.mBodyB, inContact.mSubShapeIDB, inContact.mPosition, -inContact.mContactNormal, ioSettings);
it->second.mSettings = ioSettings;
}
else
{
// Reuse the settings from the last call
ioSettings = it->second.mSettings;
}
}
else
mListener->OnContactAdded(this, inContact.mBodyB, inContact.mSubShapeIDB, inContact.mPosition, -inContact.mContactNormal, ioSettings);
{
// New contact
if (inContact.mCharacterB != nullptr)
mListener->OnCharacterContactAdded(this, inContact.mCharacterB, inContact.mSubShapeIDB, inContact.mPosition, -inContact.mContactNormal, ioSettings);
else
mListener->OnContactAdded(this, inContact.mBodyB, inContact.mSubShapeIDB, inContact.mPosition, -inContact.mContactNormal, ioSettings);
mListenerContacts.insert(ListenerContacts::value_type(inContact, ioSettings));
}
}
}
@ -517,7 +579,7 @@ bool CharacterVirtual::GetFirstContactForSweep(RVec3Arg inPosition, Vec3Arg inDi
RVec3 base_offset = start.GetTranslation();
ContactCastCollector collector(mSystem, this, inDisplacement, mUp, inIgnoredContacts, base_offset, contact);
collector.ResetEarlyOutFraction(contact.mFraction);
RShapeCast shape_cast(mShape, Vec3::sReplicate(1.0f), start, inDisplacement);
RShapeCast shape_cast(mShape, Vec3::sOne(), start, inDisplacement);
mSystem->GetNarrowPhaseQuery().CastShape(shape_cast, settings, base_offset, collector, inBroadPhaseLayerFilter, inObjectLayerFilter, body_filter, inShapeFilter);
// Also collide with other characters
@ -527,7 +589,7 @@ bool CharacterVirtual::GetFirstContactForSweep(RVec3Arg inPosition, Vec3Arg inDi
mCharacterVsCharacterCollision->CastCharacter(this, start, inDisplacement, settings, base_offset, collector);
}
if (contact.mBodyB.IsInvalid() && contact.mCharacterB == nullptr)
if (contact.mBodyB.IsInvalid() && contact.mCharacterIDB.IsInvalid())
return false;
// Store contact
@ -562,7 +624,7 @@ bool CharacterVirtual::GetFirstContactForSweep(RVec3Arg inPosition, Vec3Arg inDi
AddConvexRadius add_cvx(polygon, character_padding);
// Correct fraction to hit this inflated face instead of the inner shape
corrected = sCorrectFractionForCharacterPadding(mShape, start.GetRotation(), inDisplacement, Vec3::sReplicate(1.0f), add_cvx, outContact.mFraction);
corrected = sCorrectFractionForCharacterPadding(mShape, start.GetRotation(), inDisplacement, Vec3::sOne(), add_cvx, outContact.mFraction);
}
if (!corrected)
{
@ -619,7 +681,7 @@ void CharacterVirtual::DetermineConstraints(TempContactList &inContacts, float i
}
}
bool CharacterVirtual::HandleContact(Vec3Arg inVelocity, Constraint &ioConstraint, float inDeltaTime) const
bool CharacterVirtual::HandleContact(Vec3Arg inVelocity, Constraint &ioConstraint, float inDeltaTime)
{
Contact &contact = *ioConstraint.mContact;
@ -627,6 +689,9 @@ bool CharacterVirtual::HandleContact(Vec3Arg inVelocity, Constraint &ioConstrain
if (!ValidateContact(contact))
return false;
// We collided
contact.mHadCollision = true;
// Send contact added event
CharacterContactSettings settings;
ContactAdded(contact, settings);
@ -691,7 +756,7 @@ void CharacterVirtual::SolveConstraints(Vec3Arg inVelocity, float inDeltaTime, f
#ifdef JPH_DEBUG_RENDERER
, bool inDrawConstraints
#endif // JPH_DEBUG_RENDERER
) const
)
{
// If there are no constraints we can immediately move to our target
if (ioConstraints.empty())
@ -784,21 +849,16 @@ void CharacterVirtual::SolveConstraints(Vec3Arg inVelocity, float inDeltaTime, f
if (c->mContact->mWasDiscarded)
continue;
// Check if we made contact with this before
if (!c->mContact->mHadCollision)
// Handle the contact
if (!c->mContact->mHadCollision
&& !HandleContact(velocity, *c, inDeltaTime))
{
// Handle the contact
if (!HandleContact(velocity, *c, inDeltaTime))
{
// Constraint should be ignored, remove it from the list
c->mContact->mWasDiscarded = true;
// Constraint should be ignored, remove it from the list
c->mContact->mWasDiscarded = true;
// Mark it as ignored for GetFirstContactForSweep
ioIgnoredContacts.emplace_back(c->mContact->mBodyB, c->mContact->mSubShapeIDB);
continue;
}
c->mContact->mHadCollision = true;
// Mark it as ignored for GetFirstContactForSweep
ioIgnoredContacts.emplace_back(*c->mContact);
continue;
}
// Cancel velocity of constraint if it cannot push the character
@ -959,8 +1019,12 @@ void CharacterVirtual::UpdateSupportingContact(bool inSkipContactVelocityCheck,
&& c.mDistance < mCollisionTolerance
&& (inSkipContactVelocityCheck || c.mSurfaceNormal.Dot(mLinearVelocity - c.mLinearVelocity) <= 1.0e-4f))
{
if (ValidateContact(c) && !c.mIsSensorB)
if (ValidateContact(c))
{
CharacterContactSettings dummy;
ContactAdded(c, dummy);
c.mHadCollision = true;
}
else
c.mWasDiscarded = true;
}
@ -979,7 +1043,7 @@ void CharacterVirtual::UpdateSupportingContact(bool inSkipContactVelocityCheck,
const Contact *deepest_contact = nullptr;
float smallest_distance = FLT_MAX;
for (const Contact &c : mActiveContacts)
if (c.mHadCollision)
if (c.mHadCollision && !c.mWasDiscarded)
{
// Calculate the angle between the plane normal and the up direction
float cos_angle = c.mSurfaceNormal.Dot(mUp);
@ -1127,16 +1191,20 @@ void CharacterVirtual::UpdateSupportingContact(bool inSkipContactVelocityCheck,
void CharacterVirtual::StoreActiveContacts(const TempContactList &inContacts, TempAllocator &inAllocator)
{
StartTrackingContactChanges();
mActiveContacts.assign(inContacts.begin(), inContacts.end());
UpdateSupportingContact(true, inAllocator);
FinishTrackingContactChanges();
}
void CharacterVirtual::MoveShape(RVec3 &ioPosition, Vec3Arg inVelocity, float inDeltaTime, ContactList *outActiveContacts, const BroadPhaseLayerFilter &inBroadPhaseLayerFilter, const ObjectLayerFilter &inObjectLayerFilter, const BodyFilter &inBodyFilter, const ShapeFilter &inShapeFilter, TempAllocator &inAllocator
#ifdef JPH_DEBUG_RENDERER
, bool inDrawConstraints
#endif // JPH_DEBUG_RENDERER
) const
)
{
JPH_DET_LOG("CharacterVirtual::MoveShape: pos: " << ioPosition << " vel: " << inVelocity << " dt: " << inDeltaTime);
@ -1237,6 +1305,7 @@ Vec3 CharacterVirtual::CancelVelocityTowardsSteepSlopes(Vec3Arg inDesiredVelocit
Vec3 desired_velocity = inDesiredVelocity;
for (const Contact &c : mActiveContacts)
if (c.mHadCollision
&& !c.mWasDiscarded
&& IsSlopeTooSteep(c.mSurfaceNormal))
{
// Note that we use the contact normal to allow for better sliding as the surface normal may be in the opposite direction of movement.
@ -1253,12 +1322,72 @@ Vec3 CharacterVirtual::CancelVelocityTowardsSteepSlopes(Vec3Arg inDesiredVelocit
return desired_velocity;
}
void CharacterVirtual::StartTrackingContactChanges()
{
// Check if we're starting for the first time
if (++mTrackingContactChanges > 1)
return;
// No need to track anything if we don't have a listener
JPH_ASSERT(mListenerContacts.empty());
if (mListener == nullptr)
return;
// Mark all current contacts as not seen
mListenerContacts.reserve(ListenerContacts::size_type(mActiveContacts.size()));
for (const Contact &c : mActiveContacts)
if (c.mHadCollision)
mListenerContacts.insert(ListenerContacts::value_type(c, ListenerContactValue()));
}
void CharacterVirtual::FinishTrackingContactChanges()
{
// Check if we have to do anything
int count = --mTrackingContactChanges;
JPH_ASSERT(count >= 0, "Called FinishTrackingContactChanges more times than StartTrackingContactChanges");
if (count > 0)
return;
// No need to track anything if we don't have a listener
if (mListener == nullptr)
return;
// Since we can do multiple operations (e.g. Update followed by WalkStairs)
// we can end up with contacts that were marked as active to the listener but that are
// no longer in the active contact list. We go over all contacts and mark them again
// to ensure that these lists are in sync.
for (ListenerContacts::value_type &c : mListenerContacts)
c.second.mCount = 0;
for (const Contact &c : mActiveContacts)
if (c.mHadCollision)
{
ListenerContacts::iterator it = mListenerContacts.find(c);
JPH_ASSERT(it != mListenerContacts.end());
it->second.mCount = 1;
}
// Call contact removal callbacks
for (ListenerContacts::iterator it = mListenerContacts.begin(); it != mListenerContacts.end(); ++it)
if (it->second.mCount == 0)
{
const ContactKey &c = it->first;
if (!c.mCharacterIDB.IsInvalid())
mListener->OnCharacterContactRemoved(this, c.mCharacterIDB, c.mSubShapeIDB);
else
mListener->OnContactRemoved(this, c.mBodyB, c.mSubShapeIDB);
}
mListenerContacts.ClearAndKeepMemory();
}
void CharacterVirtual::Update(float inDeltaTime, Vec3Arg inGravity, const BroadPhaseLayerFilter &inBroadPhaseLayerFilter, const ObjectLayerFilter &inObjectLayerFilter, const BodyFilter &inBodyFilter, const ShapeFilter &inShapeFilter, TempAllocator &inAllocator)
{
// If there's no delta time, we don't need to do anything
if (inDeltaTime <= 0.0f)
return;
StartTrackingContactChanges();
JPH_SCOPE_EXIT([this]() { FinishTrackingContactChanges(); });
// Remember delta time for checking if we're supported by the ground
mLastDeltaTime = inDeltaTime;
@ -1405,6 +1534,7 @@ bool CharacterVirtual::CanWalkStairs(Vec3Arg inLinearVelocity) const
// Check contacts for steep slopes
for (const Contact &c : mActiveContacts)
if (c.mHadCollision
&& !c.mWasDiscarded
&& c.mSurfaceNormal.Dot(horizontal_velocity - c.mLinearVelocity) < 0.0f // Pushing into the contact
&& IsSlopeTooSteep(c.mSurfaceNormal)) // Slope too steep
return true;
@ -1414,6 +1544,9 @@ bool CharacterVirtual::CanWalkStairs(Vec3Arg inLinearVelocity) const
bool CharacterVirtual::WalkStairs(float inDeltaTime, Vec3Arg inStepUp, Vec3Arg inStepForward, Vec3Arg inStepForwardTest, Vec3Arg inStepDownExtra, const BroadPhaseLayerFilter &inBroadPhaseLayerFilter, const ObjectLayerFilter &inObjectLayerFilter, const BodyFilter &inBodyFilter, const ShapeFilter &inShapeFilter, TempAllocator &inAllocator)
{
StartTrackingContactChanges();
JPH_SCOPE_EXIT([this]() { FinishTrackingContactChanges(); });
// Move up
Vec3 up = inStepUp;
Contact contact;
@ -1442,6 +1575,7 @@ bool CharacterVirtual::WalkStairs(float inDeltaTime, Vec3Arg inStepUp, Vec3Arg i
steep_slope_normals.reserve(mActiveContacts.size());
for (const Contact &c : mActiveContacts)
if (c.mHadCollision
&& !c.mWasDiscarded
&& c.mSurfaceNormal.Dot(horizontal_velocity - c.mLinearVelocity) < 0.0f // Pushing into the contact
&& IsSlopeTooSteep(c.mSurfaceNormal)) // Slope too steep
steep_slope_normals.push_back(c.mSurfaceNormal);
@ -1490,7 +1624,7 @@ bool CharacterVirtual::WalkStairs(float inDeltaTime, Vec3Arg inStepUp, Vec3Arg i
RVec3 debug_pos = new_position + contact.mFraction * down;
DebugRenderer::sInstance->DrawArrow(new_position, debug_pos, Color::sWhite, 0.01f);
DebugRenderer::sInstance->DrawArrow(contact.mPosition, contact.mPosition + contact.mSurfaceNormal, Color::sWhite, 0.01f);
mShape->Draw(DebugRenderer::sInstance, GetCenterOfMassTransform(debug_pos, mRotation, mShape), Vec3::sReplicate(1.0f), Color::sWhite, false, true);
mShape->Draw(DebugRenderer::sInstance, GetCenterOfMassTransform(debug_pos, mRotation, mShape), Vec3::sOne(), Color::sWhite, false, true);
}
#endif // JPH_DEBUG_RENDERER
@ -1528,7 +1662,7 @@ bool CharacterVirtual::WalkStairs(float inDeltaTime, Vec3Arg inStepUp, Vec3Arg i
RVec3 debug_pos = test_position + test_contact.mFraction * down;
DebugRenderer::sInstance->DrawArrow(test_position, debug_pos, Color::sCyan, 0.01f);
DebugRenderer::sInstance->DrawArrow(test_contact.mPosition, test_contact.mPosition + test_contact.mSurfaceNormal, Color::sCyan, 0.01f);
mShape->Draw(DebugRenderer::sInstance, GetCenterOfMassTransform(debug_pos, mRotation, mShape), Vec3::sReplicate(1.0f), Color::sCyan, false, true);
mShape->Draw(DebugRenderer::sInstance, GetCenterOfMassTransform(debug_pos, mRotation, mShape), Vec3::sOne(), Color::sCyan, false, true);
}
#endif // JPH_DEBUG_RENDERER
@ -1551,6 +1685,9 @@ bool CharacterVirtual::WalkStairs(float inDeltaTime, Vec3Arg inStepUp, Vec3Arg i
bool CharacterVirtual::StickToFloor(Vec3Arg inStepDown, const BroadPhaseLayerFilter &inBroadPhaseLayerFilter, const ObjectLayerFilter &inObjectLayerFilter, const BodyFilter &inBodyFilter, const ShapeFilter &inShapeFilter, TempAllocator &inAllocator)
{
StartTrackingContactChanges();
JPH_SCOPE_EXIT([this]() { FinishTrackingContactChanges(); });
// Try to find the floor
Contact contact;
IgnoredContactList dummy_ignored_contacts(inAllocator);
@ -1565,7 +1702,7 @@ bool CharacterVirtual::StickToFloor(Vec3Arg inStepDown, const BroadPhaseLayerFil
if (sDrawStickToFloor)
{
DebugRenderer::sInstance->DrawArrow(mPosition, new_position, Color::sOrange, 0.01f);
mShape->Draw(DebugRenderer::sInstance, GetCenterOfMassTransform(new_position, mRotation, mShape), Vec3::sReplicate(1.0f), Color::sOrange, false, true);
mShape->Draw(DebugRenderer::sInstance, GetCenterOfMassTransform(new_position, mRotation, mShape), Vec3::sOne(), Color::sOrange, false, true);
}
#endif // JPH_DEBUG_RENDERER
@ -1576,6 +1713,9 @@ bool CharacterVirtual::StickToFloor(Vec3Arg inStepDown, const BroadPhaseLayerFil
void CharacterVirtual::ExtendedUpdate(float inDeltaTime, Vec3Arg inGravity, const ExtendedUpdateSettings &inSettings, const BroadPhaseLayerFilter &inBroadPhaseLayerFilter, const ObjectLayerFilter &inObjectLayerFilter, const BodyFilter &inBodyFilter, const ShapeFilter &inShapeFilter, TempAllocator &inAllocator)
{
StartTrackingContactChanges();
JPH_SCOPE_EXIT([this]() { FinishTrackingContactChanges(); });
// Update the velocity
Vec3 desired_velocity = mLinearVelocity;
mLinearVelocity = CancelVelocityTowardsSteepSlopes(desired_velocity);
@ -1652,37 +1792,54 @@ void CharacterVirtual::ExtendedUpdate(float inDeltaTime, Vec3Arg inGravity, cons
}
}
void CharacterVirtual::ContactKey::SaveState(StateRecorder &inStream) const
{
inStream.Write(mBodyB);
inStream.Write(mCharacterIDB);
inStream.Write(mSubShapeIDB);
}
void CharacterVirtual::ContactKey::RestoreState(StateRecorder &inStream)
{
inStream.Read(mBodyB);
inStream.Read(mCharacterIDB);
inStream.Read(mSubShapeIDB);
}
void CharacterVirtual::Contact::SaveState(StateRecorder &inStream) const
{
ContactKey::SaveState(inStream);
inStream.Write(mPosition);
inStream.Write(mLinearVelocity);
inStream.Write(mContactNormal);
inStream.Write(mSurfaceNormal);
inStream.Write(mDistance);
inStream.Write(mFraction);
inStream.Write(mBodyB);
inStream.Write(mSubShapeIDB);
inStream.Write(mMotionTypeB);
inStream.Write(mIsSensorB);
inStream.Write(mHadCollision);
inStream.Write(mWasDiscarded);
inStream.Write(mCanPushCharacter);
// Cannot store user data (may be a pointer) and material
// Cannot store pointers to character B, user data and material
}
void CharacterVirtual::Contact::RestoreState(StateRecorder &inStream)
{
ContactKey::RestoreState(inStream);
inStream.Read(mPosition);
inStream.Read(mLinearVelocity);
inStream.Read(mContactNormal);
inStream.Read(mSurfaceNormal);
inStream.Read(mDistance);
inStream.Read(mFraction);
inStream.Read(mBodyB);
inStream.Read(mSubShapeIDB);
inStream.Read(mMotionTypeB);
inStream.Read(mIsSensorB);
inStream.Read(mHadCollision);
inStream.Read(mWasDiscarded);
inStream.Read(mCanPushCharacter);
mCharacterB = nullptr; // Cannot restore character B
mUserData = 0; // Cannot restore user data
mMaterial = PhysicsMaterial::sDefault; // Cannot restore material
}

View file

@ -5,6 +5,7 @@
#pragma once
#include <Jolt/Physics/Character/CharacterBase.h>
#include <Jolt/Physics/Character/CharacterID.h>
#include <Jolt/Physics/Body/MotionType.h>
#include <Jolt/Physics/Body/BodyFilter.h>
#include <Jolt/Physics/Collision/BroadPhase/BroadPhaseLayer.h>
@ -23,6 +24,9 @@ class JPH_EXPORT CharacterVirtualSettings : public CharacterBaseSettings
public:
JPH_OVERRIDE_NEW_DELETE
/// ID to give to this character. This is used for deterministically sorting and as an identifier to represent the character in the contact removal callback.
CharacterID mID = CharacterID::sNextCharacterID();
/// Character mass (kg). Used to push down objects with gravity when the character is standing on top.
float mMass = 70.0f;
@ -50,6 +54,10 @@ public:
/// - Fast moving objects of motion quality LinearCast will not be able to pass through the CharacterVirtual in 1 time step
RefConst<Shape> mInnerBodyShape;
/// For a deterministic simulation, it is important to have a deterministic body ID. When set and when mInnerBodyShape is specified,
/// the inner body will be created with this specified ID instead of a generated ID.
BodyID mInnerBodyIDOverride;
/// Layer that the inner rigid body will be added to
ObjectLayer mInnerBodyLayer = 0;
};
@ -84,7 +92,7 @@ public:
/// Same as OnContactValidate but when colliding with a CharacterVirtual
virtual bool OnCharacterContactValidate(const CharacterVirtual *inCharacter, const CharacterVirtual *inOtherCharacter, const SubShapeID &inSubShapeID2) { return true; }
/// Called whenever the character collides with a body.
/// Called whenever the character collides with a body for the first time.
/// @param inCharacter Character that is being solved
/// @param inBodyID2 Body ID of body that is being hit
/// @param inSubShapeID2 Sub shape ID of shape that is being hit
@ -93,9 +101,32 @@ public:
/// @param ioSettings Settings returned by the contact callback to indicate how the character should behave
virtual void OnContactAdded(const CharacterVirtual *inCharacter, const BodyID &inBodyID2, const SubShapeID &inSubShapeID2, RVec3Arg inContactPosition, Vec3Arg inContactNormal, CharacterContactSettings &ioSettings) { /* Default do nothing */ }
/// Called whenever the character persists colliding with a body.
/// @param inCharacter Character that is being solved
/// @param inBodyID2 Body ID of body that is being hit
/// @param inSubShapeID2 Sub shape ID of shape that is being hit
/// @param inContactPosition World space contact position
/// @param inContactNormal World space contact normal
/// @param ioSettings Settings returned by the contact callback to indicate how the character should behave
virtual void OnContactPersisted(const CharacterVirtual *inCharacter, const BodyID &inBodyID2, const SubShapeID &inSubShapeID2, RVec3Arg inContactPosition, Vec3Arg inContactNormal, CharacterContactSettings &ioSettings) { /* Default do nothing */ }
/// Called whenever the character loses contact with a body.
/// Note that there is no guarantee that the body or its sub shape still exists at this point. The body may have been deleted since the last update.
/// @param inCharacter Character that is being solved
/// @param inBodyID2 Body ID of body that is being hit
/// @param inSubShapeID2 Sub shape ID of shape that is being hit
virtual void OnContactRemoved(const CharacterVirtual *inCharacter, const BodyID &inBodyID2, const SubShapeID &inSubShapeID2) { /* Default do nothing */ }
/// Same as OnContactAdded but when colliding with a CharacterVirtual
virtual void OnCharacterContactAdded(const CharacterVirtual *inCharacter, const CharacterVirtual *inOtherCharacter, const SubShapeID &inSubShapeID2, RVec3Arg inContactPosition, Vec3Arg inContactNormal, CharacterContactSettings &ioSettings) { /* Default do nothing */ }
/// Same as OnContactPersisted but when colliding with a CharacterVirtual
virtual void OnCharacterContactPersisted(const CharacterVirtual *inCharacter, const CharacterVirtual *inOtherCharacter, const SubShapeID &inSubShapeID2, RVec3Arg inContactPosition, Vec3Arg inContactNormal, CharacterContactSettings &ioSettings) { /* Default do nothing */ }
/// Same as OnContactRemoved but when colliding with a CharacterVirtual
/// Note that inOtherCharacterID can be the ID of a character that has been deleted. This happens if the character was in contact with this character during the last update, but has been deleted since.
virtual void OnCharacterContactRemoved(const CharacterVirtual *inCharacter, const CharacterID &inOtherCharacterID, const SubShapeID &inSubShapeID2) { /* Default do nothing */ }
/// Called whenever a contact is being used by the solver. Allows the listener to override the resulting character velocity (e.g. by preventing sliding along certain surfaces).
/// @param inCharacter Character that is being solved
/// @param inBodyID2 Body ID of body that is being hit
@ -139,6 +170,8 @@ public:
};
/// Simple collision checker that loops over all registered characters.
/// This is a brute force checking algorithm. If you have a lot of characters you may want to store your characters
/// in a hierarchical structure to make this more efficient.
/// Note that this is not thread safe, so make sure that only one CharacterVirtual is checking collision at a time.
class JPH_EXPORT CharacterVsCharacterCollisionSimple : public CharacterVsCharacterCollision
{
@ -159,8 +192,9 @@ public:
/// Runtime character object.
/// This object usually represents the player. Contrary to the Character class it doesn't use a rigid body but moves doing collision checks only (hence the name virtual).
/// The advantage of this is that you can determine when the character moves in the frame (usually this has to happen at a very particular point in the frame)
/// but the downside is that other objects don't see this virtual character. In order to make this work it is recommended to pair a CharacterVirtual with a Character that
/// moves along. This Character should be keyframed (or at least have no gravity) and move along with the CharacterVirtual so that other rigid bodies can collide with it.
/// but the downside is that other objects don't see this virtual character. To make a CharacterVirtual visible to the simulation, you can optionally create an inner
/// rigid body through CharacterVirtualSettings::mInnerBodyShape. A CharacterVirtual is not tracked by the PhysicsSystem so you need to update it yourself. This also means
/// that a call to PhysicsSystem::SaveState will not save its state, you need to call CharacterVirtual::SaveState yourself.
class JPH_EXPORT CharacterVirtual : public CharacterBase
{
public:
@ -180,6 +214,9 @@ public:
/// Destructor
virtual ~CharacterVirtual() override;
/// The ID of this character
inline const CharacterID & GetID() const { return mID; }
/// Set the contact listener
void SetListener(CharacterContactListener *inListener) { mListener = inListener; }
@ -266,6 +303,15 @@ public:
/// @return A new velocity vector that won't make the character move up steep slopes
Vec3 CancelVelocityTowardsSteepSlopes(Vec3Arg inDesiredVelocity) const;
/// This function is internally called by Update, WalkStairs, StickToFloor and ExtendedUpdate and is responsible for tracking if contacts are added, persisted or removed.
/// If you want to do multiple operations on a character (e.g. first Update then WalkStairs), you can surround the code with a StartTrackingContactChanges and FinishTrackingContactChanges pair
/// to only receive a single callback per contact on the CharacterContactListener. If you don't do this then you could for example receive a contact added callback during the Update and a
/// contact persisted callback during WalkStairs.
void StartTrackingContactChanges();
/// This call triggers contact removal callbacks and is used in conjunction with StartTrackingContactChanges.
void FinishTrackingContactChanges();
/// This is the main update function. It moves the character according to its current velocity (the character is similar to a kinematic body in the sense
/// that you set the velocity and the character will follow unless collision is blocking the way). Note it's your own responsibility to apply gravity to the character velocity!
/// Different surface materials (like ice) can be emulated by getting the ground material and adjusting the velocity and/or the max slope angle accordingly every frame.
@ -383,15 +429,53 @@ public:
static inline bool sDrawStickToFloor = false; ///< Draw the state of the stick to floor algorithm
#endif
// Encapsulates a collision contact
struct Contact
/// Uniquely identifies a contact between a character and another body or character
class ContactKey
{
public:
/// Constructor
ContactKey() = default;
ContactKey(const ContactKey &inContact) = default;
ContactKey(const BodyID &inBodyB, const SubShapeID &inSubShapeID) : mBodyB(inBodyB), mSubShapeIDB(inSubShapeID) { }
ContactKey(const CharacterID &inCharacterIDB, const SubShapeID &inSubShapeID) : mCharacterIDB(inCharacterIDB), mSubShapeIDB(inSubShapeID) { }
ContactKey & operator = (const ContactKey &inContact) = default;
/// Checks if two contacts refer to the same body (or virtual character)
inline bool IsSameBody(const ContactKey &inOther) const { return mBodyB == inOther.mBodyB && mCharacterIDB == inOther.mCharacterIDB; }
/// Equality operator
bool operator == (const ContactKey &inRHS) const
{
return mBodyB == inRHS.mBodyB && mCharacterIDB == inRHS.mCharacterIDB && mSubShapeIDB == inRHS.mSubShapeIDB;
}
bool operator != (const ContactKey &inRHS) const
{
return !(*this == inRHS);
}
/// Hash of this structure
uint64 GetHash() const
{
static_assert(sizeof(BodyID) + sizeof(CharacterID) + sizeof(SubShapeID) == sizeof(ContactKey), "No padding expected");
return HashBytes(this, sizeof(ContactKey));
}
// Saving / restoring state for replay
void SaveState(StateRecorder &inStream) const;
void RestoreState(StateRecorder &inStream);
// Checks if two contacts refer to the same body (or virtual character)
inline bool IsSameBody(const Contact &inOther) const { return mBodyB == inOther.mBodyB && mCharacterB == inOther.mCharacterB; }
BodyID mBodyB; ///< ID of body we're colliding with (if not invalid)
CharacterID mCharacterIDB; ///< Character we're colliding with (if not invalid)
SubShapeID mSubShapeIDB; ///< Sub shape ID of body or character we're colliding with
};
/// Encapsulates a collision contact
struct Contact : public ContactKey
{
// Saving / restoring state for replay
void SaveState(StateRecorder &inStream) const;
void RestoreState(StateRecorder &inStream);
RVec3 mPosition; ///< Position where the character makes contact
Vec3 mLinearVelocity; ///< Velocity of the contact point
@ -399,15 +483,13 @@ public:
Vec3 mSurfaceNormal; ///< Surface normal of the contact
float mDistance; ///< Distance to the contact <= 0 means that it is an actual contact, > 0 means predictive
float mFraction; ///< Fraction along the path where this contact takes place
BodyID mBodyB; ///< ID of body we're colliding with (if not invalid)
CharacterVirtual * mCharacterB = nullptr; ///< Character we're colliding with (if not null)
SubShapeID mSubShapeIDB; ///< Sub shape ID of body we're colliding with
EMotionType mMotionTypeB; ///< Motion type of B, used to determine the priority of the contact
bool mIsSensorB; ///< If B is a sensor
const CharacterVirtual * mCharacterB = nullptr; ///< Character we're colliding with (if not nullptr). Note that this may be a dangling pointer when accessed through GetActiveContacts(), use mCharacterIDB instead.
uint64 mUserData; ///< User data of B
const PhysicsMaterial * mMaterial; ///< Material of B
bool mHadCollision = false; ///< If the character actually collided with the contact (can be false if a predictive contact never becomes a real one)
bool mWasDiscarded = false; ///< If the contact validate callback chose to discard this contact
bool mWasDiscarded = false; ///< If the contact validate callback chose to discard this contact or when the body is a sensor
bool mCanPushCharacter = true; ///< When true, the velocity of the contact point can push the character
};
@ -415,9 +497,10 @@ public:
using ContactList = Array<Contact>;
/// Access to the internal list of contacts that the character has found.
/// Note that only contacts that have their mHadCollision flag set are actual contacts.
const ContactList & GetActiveContacts() const { return mActiveContacts; }
/// Check if the character is currently in contact with or has collided with another body in the last time step
/// Check if the character is currently in contact with or has collided with another body in the last operation (e.g. Update or WalkStairs)
bool HasCollidedWith(const BodyID &inBody) const
{
for (const CharacterVirtual::Contact &c : mActiveContacts)
@ -426,15 +509,21 @@ public:
return false;
}
/// Check if the character is currently in contact with or has collided with another character in the last time step
bool HasCollidedWith(const CharacterVirtual *inCharacter) const
/// Check if the character is currently in contact with or has collided with another character in the last time step (e.g. Update or WalkStairs)
bool HasCollidedWith(const CharacterID &inCharacterID) const
{
for (const CharacterVirtual::Contact &c : mActiveContacts)
if (c.mHadCollision && c.mCharacterB == inCharacter)
if (c.mHadCollision && c.mCharacterIDB == inCharacterID)
return true;
return false;
}
/// Check if the character is currently in contact with or has collided with another character in the last time step (e.g. Update or WalkStairs)
bool HasCollidedWith(const CharacterVirtual *inCharacter) const
{
return HasCollidedWith(inCharacter->GetID());
}
private:
// Sorting predicate for making contact order deterministic
struct ContactOrderingPredicate
@ -444,21 +533,14 @@ private:
if (inLHS.mBodyB != inRHS.mBodyB)
return inLHS.mBodyB < inRHS.mBodyB;
if (inLHS.mCharacterIDB != inRHS.mCharacterIDB)
return inLHS.mCharacterIDB < inRHS.mCharacterIDB;
return inLHS.mSubShapeIDB.GetValue() < inRHS.mSubShapeIDB.GetValue();
}
};
// A contact that needs to be ignored
struct IgnoredContact
{
IgnoredContact() = default;
IgnoredContact(const BodyID &inBodyID, const SubShapeID &inSubShapeID) : mBodyID(inBodyID), mSubShapeID(inSubShapeID) { }
BodyID mBodyID; ///< ID of body we're colliding with
SubShapeID mSubShapeID; ///< Sub shape of body we're colliding with
};
using IgnoredContactList = Array<IgnoredContact, STLTempAllocator<IgnoredContact>>;
using IgnoredContactList = Array<ContactKey, STLTempAllocator<ContactKey>>;
// A constraint that limits the movement of the character
struct Constraint
@ -517,20 +599,20 @@ private:
// Helper function to convert a Jolt collision result into a contact
template <class taCollector>
inline static void sFillContactProperties(const CharacterVirtual *inCharacter, Contact &outContact, const Body &inBody, Vec3Arg inUp, RVec3Arg inBaseOffset, const taCollector &inCollector, const CollideShapeResult &inResult);
inline static void sFillCharacterContactProperties(Contact &outContact, CharacterVirtual *inOtherCharacter, RVec3Arg inBaseOffset, const CollideShapeResult &inResult);
inline static void sFillCharacterContactProperties(Contact &outContact, const CharacterVirtual *inOtherCharacter, RVec3Arg inBaseOffset, const CollideShapeResult &inResult);
// Move the shape from ioPosition and try to displace it by inVelocity * inDeltaTime, this will try to slide the shape along the world geometry
void MoveShape(RVec3 &ioPosition, Vec3Arg inVelocity, float inDeltaTime, ContactList *outActiveContacts, const BroadPhaseLayerFilter &inBroadPhaseLayerFilter, const ObjectLayerFilter &inObjectLayerFilter, const BodyFilter &inBodyFilter, const ShapeFilter &inShapeFilter, TempAllocator &inAllocator
#ifdef JPH_DEBUG_RENDERER
, bool inDrawConstraints = false
#endif // JPH_DEBUG_RENDERER
) const;
);
// Ask the callback if inContact is a valid contact point
bool ValidateContact(const Contact &inContact) const;
// Trigger the contact callback for inContact and get the contact settings
void ContactAdded(const Contact &inContact, CharacterContactSettings &ioSettings) const;
void ContactAdded(const Contact &inContact, CharacterContactSettings &ioSettings);
// Tests the shape for collision around inPosition
void GetContactsAtPosition(RVec3Arg inPosition, Vec3Arg inMovementDirection, const Shape *inShape, TempContactList &outContacts, const BroadPhaseLayerFilter &inBroadPhaseLayerFilter, const ObjectLayerFilter &inObjectLayerFilter, const BodyFilter &inBodyFilter, const ShapeFilter &inShapeFilter) const;
@ -546,7 +628,7 @@ private:
#ifdef JPH_DEBUG_RENDERER
, bool inDrawConstraints = false
#endif // JPH_DEBUG_RENDERER
) const;
);
// Get the velocity of a body adjusted by the contact listener
void GetAdjustedBodyVelocity(const Body& inBody, Vec3 &outLinearVelocity, Vec3 &outAngularVelocity) const;
@ -557,7 +639,7 @@ private:
Vec3 CalculateCharacterGroundVelocity(RVec3Arg inCenterOfMass, Vec3Arg inLinearVelocity, Vec3Arg inAngularVelocity, float inDeltaTime) const;
// Handle contact with physics object that we're colliding against
bool HandleContact(Vec3Arg inVelocity, Constraint &ioConstraint, float inDeltaTime) const;
bool HandleContact(Vec3Arg inVelocity, Constraint &ioConstraint, float inDeltaTime);
// Does a swept test of the shape from inPosition with displacement inDisplacement, returns true if there was a collision
bool GetFirstContactForSweep(RVec3Arg inPosition, Vec3Arg inDisplacement, Contact &outContact, const IgnoredContactList &inIgnoredContacts, const BroadPhaseLayerFilter &inBroadPhaseLayerFilter, const ObjectLayerFilter &inObjectLayerFilter, const BodyFilter &inBodyFilter, const ShapeFilter &inShapeFilter) const;
@ -586,6 +668,9 @@ private:
// Move the inner rigid body to the current position
void UpdateInnerBodyTransform();
// ID
CharacterID mID;
// Our main listener for contacts
CharacterContactListener * mListener = nullptr;
@ -626,6 +711,22 @@ private:
// List of contacts that were active in the last frame
ContactList mActiveContacts;
// Remembers how often we called StartTrackingContactChanges
int mTrackingContactChanges = 0;
// View from a contact listener perspective on which contacts have been added/removed
struct ListenerContactValue
{
ListenerContactValue() = default;
explicit ListenerContactValue(const CharacterContactSettings &inSettings) : mSettings(inSettings) { }
CharacterContactSettings mSettings;
int mCount = 0;
};
using ListenerContacts = UnorderedMap<ContactKey, ListenerContactValue>;
ListenerContacts mListenerContacts;
// Remembers the delta time of the last update
float mLastDeltaTime = 1.0f / 60.0f;