Przeglądaj źródła

Added enhanced edge removal for characters (#1095)

Fixes #1085
Jorrit Rouwe 1 rok temu
rodzic
commit
ec88660cb8

+ 1 - 0
Jolt/Physics/Character/Character.cpp

@@ -35,6 +35,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.mEnhancedInternalEdgeRemoval = inSettings->mEnhancedInternalEdgeRemoval;
 	settings.mOverrideMassProperties = EOverrideMassProperties::MassAndInertiaProvided;
 	settings.mMassPropertiesOverride.mMass = inSettings->mMass;
 	settings.mFriction = inSettings->mFriction;

+ 3 - 0
Jolt/Physics/Character/CharacterBase.h

@@ -41,6 +41,9 @@ public:
 	/// Maximum angle of slope that character can still walk on (radians).
 	float								mMaxSlopeAngle = DegreesToRadians(50.0f);
 
+	/// Set to indicate that extra effort should be made to try to remove ghost contacts (collisions with internal edges of a mesh). This is more expensive but makes bodies move smoother over a mesh with convex edges.
+	bool								mEnhancedInternalEdgeRemoval = false;
+
 	/// Initial shape that represents the character's volume.
 	/// Usually this is a capsule, make sure the shape is made so that the bottom of the shape is at (0, 0, 0).
 	RefConst<Shape>						mShape;

+ 69 - 1
Jolt/Physics/Character/CharacterVirtual.cpp

@@ -10,6 +10,7 @@
 #include <Jolt/Physics/Collision/ShapeCast.h>
 #include <Jolt/Physics/Collision/CollideShape.h>
 #include <Jolt/Physics/Collision/Shape/RotatedTranslatedShape.h>
+#include <Jolt/Physics/Collision/InternalEdgeRemovingCollector.h>
 #include <Jolt/Core/QuickSort.h>
 #include <Jolt/Geometry/ConvexSupport.h>
 #include <Jolt/Geometry/GJKClosestPoint.h>
@@ -31,6 +32,7 @@ CharacterVirtual::CharacterVirtual(const CharacterVirtualSettings *inSettings, R
 	mMaxNumHits(inSettings->mMaxNumHits),
 	mHitReductionCosMaxAngle(inSettings->mHitReductionCosMaxAngle),
 	mPenetrationRecoverySpeed(inSettings->mPenetrationRecoverySpeed),
+	mEnhancedInternalEdgeRemoval(inSettings->mEnhancedInternalEdgeRemoval),
 	mShapeOffset(inSettings->mShapeOffset),
 	mPosition(inPosition),
 	mRotation(inRotation),
@@ -223,7 +225,73 @@ void CharacterVirtual::CheckCollision(RVec3Arg inPosition, QuatArg inRotation, V
 	settings.mMaxSeparationDistance = mCharacterPadding + inMaxSeparationDistance;
 
 	// Collide shape
-	mSystem->GetNarrowPhaseQuery().CollideShape(inShape, Vec3::sReplicate(1.0f), transform, settings, inBaseOffset, ioCollector, inBroadPhaseLayerFilter, inObjectLayerFilter, inBodyFilter, inShapeFilter);
+	if (mEnhancedInternalEdgeRemoval)
+	{
+		// Version that does additional work to remove internal edges
+		settings.mCollectFacesMode = ECollectFacesMode::CollectFaces;
+
+		// This is a copy of NarrowPhaseQuery::CollideShape with additional logic to wrap the collector in an InternalEdgeRemovingCollector and flushing that collector after every body
+		class MyCollector : public CollideShapeBodyCollector
+		{
+		public:
+								MyCollector(const Shape *inShape, RMat44Arg inCenterOfMassTransform, const CollideShapeSettings &inCollideShapeSettings, RVec3Arg inBaseOffset, CollideShapeCollector &ioCollector, const BodyLockInterface &inBodyLockInterface, const BodyFilter &inBodyFilter, const ShapeFilter &inShapeFilter) :
+				CollideShapeBodyCollector(ioCollector),
+				mShape(inShape),
+				mCenterOfMassTransform(inCenterOfMassTransform),
+				mBaseOffset(inBaseOffset),
+				mCollideShapeSettings(inCollideShapeSettings),
+				mBodyLockInterface(inBodyLockInterface),
+				mBodyFilter(inBodyFilter),
+				mShapeFilter(inShapeFilter),
+				mCollector(ioCollector)
+			{
+			}
+
+			virtual void		AddHit(const ResultType &inResult) override
+			{
+				// See NarrowPhaseQuery::CollideShape
+				if (mBodyFilter.ShouldCollide(inResult))
+				{
+					BodyLockRead lock(mBodyLockInterface, inResult);
+					if (lock.SucceededAndIsInBroadPhase())
+					{
+						const Body &body = lock.GetBody();
+						if (mBodyFilter.ShouldCollideLocked(body))
+						{
+							TransformedShape ts = body.GetTransformedShape();
+							mCollector.OnBody(body);
+							lock.ReleaseLock();
+							ts.CollideShape(mShape, Vec3::sReplicate(1.0f), mCenterOfMassTransform, mCollideShapeSettings, mBaseOffset, mCollector, mShapeFilter);
+
+							// After each body, we need to flush the InternalEdgeRemovingCollector because it uses 'ts' as context and it will go out of scope at the end of this block
+							mCollector.Flush();
+
+							UpdateEarlyOutFraction(mCollector.GetEarlyOutFraction());
+						}
+					}
+				}
+			}
+
+			const Shape *					mShape;
+			RMat44							mCenterOfMassTransform;
+			RVec3							mBaseOffset;
+			const CollideShapeSettings &	mCollideShapeSettings;
+			const BodyLockInterface &		mBodyLockInterface;
+			const BodyFilter &				mBodyFilter;
+			const ShapeFilter &				mShapeFilter;
+			InternalEdgeRemovingCollector	mCollector;
+		};
+
+		// Calculate bounds for shape and expand by max separation distance
+		AABox bounds = inShape->GetWorldSpaceBounds(transform, Vec3::sReplicate(1.0f));
+		bounds.ExpandBy(Vec3::sReplicate(settings.mMaxSeparationDistance));
+
+		// Do broadphase test
+		MyCollector collector(inShape, transform, settings, inBaseOffset, ioCollector, mSystem->GetBodyLockInterface(), inBodyFilter, inShapeFilter);
+		mSystem->GetBroadPhaseQuery().CollideAABox(bounds, collector, inBroadPhaseLayerFilter, inObjectLayerFilter);
+	}
+	else
+		mSystem->GetNarrowPhaseQuery().CollideShape(inShape, Vec3::sReplicate(1.0f), transform, settings, inBaseOffset, ioCollector, inBroadPhaseLayerFilter, inObjectLayerFilter, inBodyFilter, inShapeFilter);
 }
 
 void CharacterVirtual::GetContactsAtPosition(RVec3Arg inPosition, Vec3Arg inMovementDirection, const Shape *inShape, TempContactList &outContacts, const BroadPhaseLayerFilter &inBroadPhaseLayerFilter, const ObjectLayerFilter &inObjectLayerFilter, const BodyFilter &inBodyFilter, const ShapeFilter &inShapeFilter) const

+ 5 - 0
Jolt/Physics/Character/CharacterVirtual.h

@@ -150,6 +150,10 @@ public:
 	float								GetPenetrationRecoverySpeed() const						{ return mPenetrationRecoverySpeed; }
 	void								SetPenetrationRecoverySpeed(float inSpeed)				{ mPenetrationRecoverySpeed = inSpeed; }
 
+	/// Set to indicate that extra effort should be made to try to remove ghost contacts (collisions with internal edges of a mesh). This is more expensive but makes bodies move smoother over a mesh with convex edges.
+	bool								GetEnhancedInternalEdgeRemoval() const					{ return mEnhancedInternalEdgeRemoval; }
+	void								SetEnhancedInternalEdgeRemoval(bool inApply)			{ mEnhancedInternalEdgeRemoval = inApply; }
+
 	/// Character padding
 	float								GetCharacterPadding() const								{ return mCharacterPadding; }
 
@@ -467,6 +471,7 @@ private:
 	uint								mMaxNumHits;											// Max num hits to collect in order to avoid excess of contact points collection
 	float								mHitReductionCosMaxAngle;								// Cos(angle) where angle is the maximum angle between two hits contact normals that are allowed to be merged during hit reduction. Default is around 2.5 degrees. Set to -1 to turn off.
 	float								mPenetrationRecoverySpeed;								// This value governs how fast a penetration will be resolved, 0 = nothing is resolved, 1 = everything in one update
+	bool								mEnhancedInternalEdgeRemoval;							// Set to indicate that extra effort should be made to try to remove ghost contacts (collisions with internal edges of a mesh). This is more expensive but makes bodies move smoother over a mesh with convex edges.
 
 	// Character mass (kg)
 	float								mMass;

+ 4 - 0
Jolt/Physics/Collision/InternalEdgeRemovingCollector.h

@@ -212,6 +212,10 @@ public:
 			// Void the features of this face
 			VoidFeatures(r);
 		}
+
+		// All delayed results have been processed
+		mVoidedFeatures.clear();
+		mDelayedResults.clear();
 	}
 
 	/// Version of CollisionDispatch::sCollideShapeVsShape that removes internal edges

+ 2 - 0
Samples/Tests/Character/CharacterVirtualTest.cpp

@@ -30,6 +30,7 @@ void CharacterVirtualTest::Initialize()
 	settings->mPenetrationRecoverySpeed = sPenetrationRecoverySpeed;
 	settings->mPredictiveContactDistance = sPredictiveContactDistance;
 	settings->mSupportingVolume = Plane(Vec3::sAxisY(), -cCharacterRadiusStanding); // Accept contacts that touch the lower sphere of the capsule
+	settings->mEnhancedInternalEdgeRemoval = sEnhancedInternalEdgeRemoval;
 	mCharacter = new CharacterVirtual(settings, RVec3::sZero(), Quat::sIdentity(), 0, mPhysicsSystem);
 	mCharacter->SetListener(this);
 }
@@ -176,6 +177,7 @@ void CharacterVirtualTest::AddConfigurationSettings(DebugUI *inUI, UIElement *in
 	inUI->CreateSlider(inSubMenu, "Predictive Contact Distance", sPredictiveContactDistance, 0.01f, 1.0f, 0.01f, [](float inValue) { sPredictiveContactDistance = inValue; });
 	inUI->CreateCheckBox(inSubMenu, "Enable Walk Stairs", sEnableWalkStairs, [](UICheckBox::EState inState) { sEnableWalkStairs = inState == UICheckBox::STATE_CHECKED; });
 	inUI->CreateCheckBox(inSubMenu, "Enable Stick To Floor", sEnableStickToFloor, [](UICheckBox::EState inState) { sEnableStickToFloor = inState == UICheckBox::STATE_CHECKED; });
+	inUI->CreateCheckBox(inSubMenu, "Enhanced Internal Edge Removal", sEnhancedInternalEdgeRemoval, [](UICheckBox::EState inState) { sEnhancedInternalEdgeRemoval = inState == UICheckBox::STATE_CHECKED; });
 }
 
 void CharacterVirtualTest::SaveState(StateRecorder &inStream) const

+ 1 - 0
Samples/Tests/Character/CharacterVirtualTest.h

@@ -60,6 +60,7 @@ private:
 	static inline float		sPredictiveContactDistance = 0.1f;
 	static inline bool		sEnableWalkStairs = true;
 	static inline bool		sEnableStickToFloor = true;
+	static inline bool		sEnhancedInternalEdgeRemoval = false;
 
 	// The 'player' character
 	Ref<CharacterVirtual>	mCharacter;