feat: modules moved and engine moved to submodule
This commit is contained in:
parent
dfb5e645cd
commit
c33d2130cc
5136 changed files with 225275 additions and 64485 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
98
engine/thirdparty/jolt_physics/Jolt/Physics/Character/CharacterID.h
vendored
Normal file
98
engine/thirdparty/jolt_physics/Jolt/Physics/Character/CharacterID.h
vendored
Normal 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
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue