|
@@ -27,6 +27,7 @@ CharacterVirtual::CharacterVirtual(const CharacterVirtualSettings *inSettings, R
|
|
|
mCollisionTolerance(inSettings->mCollisionTolerance),
|
|
|
mCharacterPadding(inSettings->mCharacterPadding),
|
|
|
mMaxNumHits(inSettings->mMaxNumHits),
|
|
|
+ mHitReductionCosMaxAngle(inSettings->mHitReductionCosMaxAngle),
|
|
|
mPenetrationRecoverySpeed(inSettings->mPenetrationRecoverySpeed),
|
|
|
mShapeOffset(inSettings->mShapeOffset),
|
|
|
mPosition(inPosition),
|
|
@@ -58,6 +59,60 @@ void CharacterVirtual::sFillContactProperties(Contact &outContact, const Body &i
|
|
|
|
|
|
void CharacterVirtual::ContactCollector::AddHit(const CollideShapeResult &inResult)
|
|
|
{
|
|
|
+ // If we exceed our contact limit, try to clean up near-duplicate contacts
|
|
|
+ if (mContacts.size() == mMaxHits)
|
|
|
+ {
|
|
|
+ // Flag that we hit this code path
|
|
|
+ mMaxHitsExceeded = true;
|
|
|
+
|
|
|
+ // Check if we can do reduction
|
|
|
+ if (mHitReductionCosMaxAngle > -1.0f)
|
|
|
+ {
|
|
|
+ // Loop all contacts and find similar contacts
|
|
|
+ for (int i = (int)mContacts.size() - 1; i >= 0; --i)
|
|
|
+ {
|
|
|
+ Contact &contact_i = mContacts[i];
|
|
|
+ for (int j = i - 1; j >= 0; --j)
|
|
|
+ {
|
|
|
+ Contact &contact_j = mContacts[j];
|
|
|
+ if (contact_i.mBodyB == contact_j.mBodyB // Same body
|
|
|
+ && contact_i.mContactNormal.Dot(contact_j.mContactNormal) > mHitReductionCosMaxAngle) // Very similar contact normals
|
|
|
+ {
|
|
|
+ // Remove the contact with the biggest distance
|
|
|
+ bool i_is_last = i == (int)mContacts.size() - 1;
|
|
|
+ if (contact_i.mDistance > contact_j.mDistance)
|
|
|
+ {
|
|
|
+ // Remove i
|
|
|
+ if (!i_is_last)
|
|
|
+ contact_i = mContacts.back();
|
|
|
+ mContacts.pop_back();
|
|
|
+
|
|
|
+ // Break out of the loop, i is now an element that we already processed
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ // Remove j
|
|
|
+ contact_j = mContacts.back();
|
|
|
+ mContacts.pop_back();
|
|
|
+
|
|
|
+ // If i was the last element, we just moved it into position j. Break out of the loop, we'll see it again later.
|
|
|
+ if (i_is_last)
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (mContacts.size() == mMaxHits)
|
|
|
+ {
|
|
|
+ // There are still too many hits, give up!
|
|
|
+ ForceEarlyOut();
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
BodyLockRead lock(mSystem->GetBodyLockInterface(), inResult.mBodyID2);
|
|
|
if (lock.SucceededAndIsInBroadPhase())
|
|
|
{
|
|
@@ -67,15 +122,14 @@ void CharacterVirtual::ContactCollector::AddHit(const CollideShapeResult &inResu
|
|
|
Contact &contact = mContacts.back();
|
|
|
sFillContactProperties(contact, body, mUp, mBaseOffset, *this, inResult);
|
|
|
contact.mFraction = 0.0f;
|
|
|
-
|
|
|
- // Protection from excess of contact points
|
|
|
- if (mContacts.size() == mMaxHits)
|
|
|
- ForceEarlyOut();
|
|
|
}
|
|
|
}
|
|
|
|
|
|
void CharacterVirtual::ContactCastCollector::AddHit(const ShapeCastResult &inResult)
|
|
|
{
|
|
|
+ // Should not have gotten here without a lower fraction
|
|
|
+ JPH_ASSERT(inResult.mFraction < mContact.mFraction);
|
|
|
+
|
|
|
if (inResult.mFraction > 0.0f // Ignore collisions at fraction = 0
|
|
|
&& inResult.mPenetrationAxis.Dot(mDisplacement) > 0.0f) // Ignore penetrations that we're moving away from
|
|
|
{
|
|
@@ -89,14 +143,18 @@ void CharacterVirtual::ContactCastCollector::AddHit(const ShapeCastResult &inRes
|
|
|
{
|
|
|
const Body &body = lock.GetBody();
|
|
|
|
|
|
- mContacts.emplace_back();
|
|
|
- Contact &contact = mContacts.back();
|
|
|
+ // Convert the hit result into a contact
|
|
|
+ Contact contact;
|
|
|
sFillContactProperties(contact, body, mUp, mBaseOffset, *this, inResult);
|
|
|
contact.mFraction = inResult.mFraction;
|
|
|
-
|
|
|
- // Protection from excess of contact points
|
|
|
- if (mContacts.size() == mMaxHits)
|
|
|
- ForceEarlyOut();
|
|
|
+
|
|
|
+ // Check if the contact that will make us penetrate more than the allowed tolerance
|
|
|
+ if (contact.mDistance + contact.mContactNormal.Dot(mDisplacement) < -mCharacter->mCollisionTolerance
|
|
|
+ && mCharacter->ValidateContact(contact))
|
|
|
+ {
|
|
|
+ mContact = contact;
|
|
|
+ UpdateEarlyOutFraction(contact.mFraction);
|
|
|
+ }
|
|
|
}
|
|
|
}
|
|
|
}
|
|
@@ -123,9 +181,12 @@ void CharacterVirtual::GetContactsAtPosition(RVec3Arg inPosition, Vec3Arg inMove
|
|
|
outContacts.clear();
|
|
|
|
|
|
// Collide shape
|
|
|
- ContactCollector collector(mSystem, mMaxNumHits, mUp, mPosition, outContacts);
|
|
|
+ ContactCollector collector(mSystem, mMaxNumHits, mHitReductionCosMaxAngle, mUp, mPosition, outContacts);
|
|
|
CheckCollision(inPosition, mRotation, inMovementDirection, mPredictiveContactDistance, inShape, mPosition, collector, inBroadPhaseLayerFilter, inObjectLayerFilter, inBodyFilter, inShapeFilter);
|
|
|
|
|
|
+ // Flag if we exceeded the max number of hits
|
|
|
+ mMaxHitsExceeded = collector.mMaxHitsExceeded;
|
|
|
+
|
|
|
// Reduce distance to contact by padding to ensure we stay away from the object by a little margin
|
|
|
// (this will make collision detection cheaper - especially for sweep tests as they won't hit the surface if we're properly sliding)
|
|
|
for (Contact &c : outContacts)
|
|
@@ -205,7 +266,7 @@ inline static bool sCorrectFractionForCharacterPadding(const Shape *inShape, Mat
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-bool CharacterVirtual::GetFirstContactForSweep(RVec3Arg inPosition, Vec3Arg inDisplacement, Contact &outContact, const IgnoredContactList &inIgnoredContacts, const BroadPhaseLayerFilter &inBroadPhaseLayerFilter, const ObjectLayerFilter &inObjectLayerFilter, const BodyFilter &inBodyFilter, const ShapeFilter &inShapeFilter, TempAllocator &inAllocator) const
|
|
|
+bool CharacterVirtual::GetFirstContactForSweep(RVec3Arg inPosition, Vec3Arg inDisplacement, Contact &outContact, const IgnoredContactList &inIgnoredContacts, const BroadPhaseLayerFilter &inBroadPhaseLayerFilter, const ObjectLayerFilter &inObjectLayerFilter, const BodyFilter &inBodyFilter, const ShapeFilter &inShapeFilter) const
|
|
|
{
|
|
|
// Too small distance -> skip checking
|
|
|
float displacement_len_sq = inDisplacement.LengthSq();
|
|
@@ -224,29 +285,16 @@ bool CharacterVirtual::GetFirstContactForSweep(RVec3Arg inPosition, Vec3Arg inDi
|
|
|
settings.mReturnDeepestPoint = false;
|
|
|
|
|
|
// Cast shape
|
|
|
- TempContactList contacts(inAllocator);
|
|
|
- contacts.reserve(mMaxNumHits);
|
|
|
- ContactCastCollector collector(mSystem, inDisplacement, mMaxNumHits, mUp, inIgnoredContacts, start.GetTranslation(), contacts);
|
|
|
+ Contact contact;
|
|
|
+ contact.mFraction = 1.0f + FLT_EPSILON;
|
|
|
+ ContactCastCollector collector(mSystem, this, inDisplacement, mUp, inIgnoredContacts, start.GetTranslation(), contact);
|
|
|
RShapeCast shape_cast(mShape, Vec3::sReplicate(1.0f), start, inDisplacement);
|
|
|
mSystem->GetNarrowPhaseQuery().CastShape(shape_cast, settings, start.GetTranslation(), collector, inBroadPhaseLayerFilter, inObjectLayerFilter, inBodyFilter, inShapeFilter);
|
|
|
- if (contacts.empty())
|
|
|
+ if (contact.mBodyB.IsInvalid())
|
|
|
return false;
|
|
|
|
|
|
- // Sort the contacts on fraction
|
|
|
- QuickSort(contacts.begin(), contacts.end(), [](const Contact &inLHS, const Contact &inRHS) { return inLHS.mFraction < inRHS.mFraction; });
|
|
|
-
|
|
|
- // Check the first contact that will make us penetrate more than the allowed tolerance
|
|
|
- bool valid_contact = false;
|
|
|
- for (const Contact &c : contacts)
|
|
|
- if (c.mDistance + c.mContactNormal.Dot(inDisplacement) < -mCollisionTolerance
|
|
|
- && ValidateContact(c))
|
|
|
- {
|
|
|
- outContact = c;
|
|
|
- valid_contact = true;
|
|
|
- break;
|
|
|
- }
|
|
|
- if (!valid_contact)
|
|
|
- return false;
|
|
|
+ // Store contact
|
|
|
+ outContact = contact;
|
|
|
|
|
|
// Fetch the face we're colliding with
|
|
|
TransformedShape ts = mSystem->GetBodyInterface().GetTransformedShape(outContact.mBodyB);
|
|
@@ -878,7 +926,7 @@ void CharacterVirtual::MoveShape(RVec3 &ioPosition, Vec3Arg inVelocity, float in
|
|
|
|
|
|
// Do a sweep to test if the path is really unobstructed
|
|
|
Contact cast_contact;
|
|
|
- if (GetFirstContactForSweep(ioPosition, displacement, cast_contact, ignored_contacts, inBroadPhaseLayerFilter, inObjectLayerFilter, inBodyFilter, inShapeFilter, inAllocator))
|
|
|
+ if (GetFirstContactForSweep(ioPosition, displacement, cast_contact, ignored_contacts, inBroadPhaseLayerFilter, inObjectLayerFilter, inBodyFilter, inShapeFilter))
|
|
|
{
|
|
|
displacement *= cast_contact.mFraction;
|
|
|
time_simulated *= cast_contact.mFraction;
|
|
@@ -1053,7 +1101,7 @@ bool CharacterVirtual::WalkStairs(float inDeltaTime, Vec3Arg inStepUp, Vec3Arg i
|
|
|
Vec3 up = inStepUp;
|
|
|
Contact contact;
|
|
|
IgnoredContactList dummy_ignored_contacts(inAllocator);
|
|
|
- if (GetFirstContactForSweep(mPosition, up, contact, dummy_ignored_contacts, inBroadPhaseLayerFilter, inObjectLayerFilter, inBodyFilter, inShapeFilter, inAllocator))
|
|
|
+ if (GetFirstContactForSweep(mPosition, up, contact, dummy_ignored_contacts, inBroadPhaseLayerFilter, inObjectLayerFilter, inBodyFilter, inShapeFilter))
|
|
|
{
|
|
|
if (contact.mFraction < 1.0e-6f)
|
|
|
return false; // No movement, cancel
|
|
@@ -1086,7 +1134,7 @@ bool CharacterVirtual::WalkStairs(float inDeltaTime, Vec3Arg inStepUp, Vec3Arg i
|
|
|
// Note that we travel the same amount down as we travelled up with the character padding and the specified extra
|
|
|
// If we don't add the character padding, we may miss the floor (note that GetFirstContactForSweep will subtract the padding when it finds a hit)
|
|
|
Vec3 down = -up - mCharacterPadding * mUp + inStepDownExtra;
|
|
|
- if (!GetFirstContactForSweep(new_position, down, contact, dummy_ignored_contacts, inBroadPhaseLayerFilter, inObjectLayerFilter, inBodyFilter, inShapeFilter, inAllocator))
|
|
|
+ if (!GetFirstContactForSweep(new_position, down, contact, dummy_ignored_contacts, inBroadPhaseLayerFilter, inObjectLayerFilter, inBodyFilter, inShapeFilter))
|
|
|
return false; // No floor found, we're in mid air, cancel stair walk
|
|
|
|
|
|
#ifdef JPH_DEBUG_RENDERER
|
|
@@ -1124,7 +1172,7 @@ bool CharacterVirtual::WalkStairs(float inDeltaTime, Vec3Arg inStepUp, Vec3Arg i
|
|
|
|
|
|
// Then sweep down
|
|
|
Contact test_contact;
|
|
|
- if (!GetFirstContactForSweep(test_position, down, test_contact, dummy_ignored_contacts, inBroadPhaseLayerFilter, inObjectLayerFilter, inBodyFilter, inShapeFilter, inAllocator))
|
|
|
+ if (!GetFirstContactForSweep(test_position, down, test_contact, dummy_ignored_contacts, inBroadPhaseLayerFilter, inObjectLayerFilter, inBodyFilter, inShapeFilter))
|
|
|
return false;
|
|
|
|
|
|
#ifdef JPH_DEBUG_RENDERER
|
|
@@ -1160,7 +1208,7 @@ bool CharacterVirtual::StickToFloor(Vec3Arg inStepDown, const BroadPhaseLayerFil
|
|
|
// Try to find the floor
|
|
|
Contact contact;
|
|
|
IgnoredContactList dummy_ignored_contacts(inAllocator);
|
|
|
- if (!GetFirstContactForSweep(mPosition, inStepDown, contact, dummy_ignored_contacts, inBroadPhaseLayerFilter, inObjectLayerFilter, inBodyFilter, inShapeFilter, inAllocator))
|
|
|
+ if (!GetFirstContactForSweep(mPosition, inStepDown, contact, dummy_ignored_contacts, inBroadPhaseLayerFilter, inObjectLayerFilter, inBodyFilter, inShapeFilter))
|
|
|
return false; // If no floor found, don't update our position
|
|
|
|
|
|
// Calculate new position
|