Help fixing jittering in my character collision

Started by
17 comments, last by ruben67 1 year, 11 months ago

@dirk gregorius haha no worries

Advertisement

Another thing you can do to avoid the "stuck in solid" problem is to ignore contacts at t = 0 (since you are already accounting for them) and set the sweep ‘free’ so to say. I adapted my sweep functions to do this.

@dirk gregorius I'm actually already doing that!

Thank you for all the help man really appreciate it.

This is how I implemented it originally:

//--------------------------------------------------------------------------------------------------
static void rnSolveConstraints( RnVector3& Position, RnVector3& Velocity, const RnArray< RnPlane >& Planes, int MaxIterations = 8 )
	{
	for ( int Iteration = 0; Iteration < MaxIterations; ++Iteration )
		{
		for ( int Index = 0; Index < Planes.Size(); ++Index )
			{
			RnPlane Plane = Planes[ Index ];

			float Separation = rnDistance( Plane, Position );
			if ( Separation < 0.0f )
				{
				Position -= Plane.Normal * Separation;

				float Projection = rnDot( Velocity, Plane.Normal );
				if ( Projection < 0.0f )
					{
					Velocity -= Projection * Plane.Normal;
					}
				}
			}
		}
	}


//--------------------------------------------------------------------------------------------------
static RnWorldSweepResult rnSweepCapsule( const RnVector3& Position, const RnVector3& Target, const RnCapsule& Capsule, const RnWorld* World )
	{
	// Compute swept bounds
	RnBounds3 Bounds1 = Capsule.ComputeBounds( RnTransform( Position, RN_QUAT_IDENTITY ) );
	RnBounds3 Bounds2 = Capsule.ComputeBounds( RnTransform( Target, RN_QUAT_IDENTITY ) );
	RnBounds3 Bounds = Bounds1 + Bounds2;

	// Find all overlapping proxies 
	RnStackArray< RnShape*, 32 > Buffer;
	World->Query( Buffer, Bounds );

	// Cast against shapes
	RnWorldSweepResult WorldSweepResult;

	RnTransform Transform = RnTransform( Position, RN_QUAT_IDENTITY );
	RnVector3 Delta = Target - Position;
	RnTOIProxy Proxy( 2, &Capsule.Center1, Capsule.Radius );
	
	for ( RnShape* Shape : Buffer )
		{
		RnBody* Body = Shape->GetBody();

		if ( Shape->GetType() != RN_MESH_SHAPE )
			{
			RnTOIResult Result = rnTOI( Transform, Delta, Proxy, Body->GetTransform(), RN_VEC3_ZERO, Shape->GetTOIProxy() );
			if ( Result.Alpha == 0.0f )
				{
				// Ignore initial overlap
				continue;
				}
			if ( Result.Alpha < WorldSweepResult.Alpha )
				{
				RN_ASSERT( Result.State == RN_TOI_TOUCHING );

				WorldSweepResult.Body = Body;
				WorldSweepResult.Shape = Shape;
				WorldSweepResult.Alpha = Result.Alpha;
				}
			}
		else
			{
			// Get mesh shape
			const RnMeshShape* MeshShape = static_cast< const RnMeshShape* >( Shape );

			// Transform swept bounds into local space of mesh
			RnBounds3 LocalBounds = rnInvert( Body->GetTransform() ) * Bounds;

			// Find overlapping triangles
			RnArray< int > Triangles;
			MeshShape->Query( Triangles, LocalBounds );
			
			for ( int Index = 0; Index < Triangles.Size(); ++Index )
				{
				RnVector3 Triangle[ 3 ];
				MeshShape->GetTriangle( Triangle, Triangles[ Index ] );

				RnTOIResult Result = rnTOI( Transform, Delta, Proxy, Body->GetTransform(), RN_VEC3_ZERO, RnTOIProxy( 3, Triangle ) );
				if ( Result.Alpha == 0.0f )
					{
					// Ignore initial overlap
					continue;
					}
				if ( Result.Alpha < WorldSweepResult.Alpha )
					{
					RN_ASSERT( Result.State == RN_TOI_TOUCHING );

					WorldSweepResult.Body = Body;
					WorldSweepResult.Shape = Shape;
					WorldSweepResult.Alpha = Result.Alpha;
					WorldSweepResult.Triangle = Triangles[ Index ];
					}
				}
			}
		}

 	return WorldSweepResult;
	}


//--------------------------------------------------------------------------------------------------
static void rnCollideShape( RnArray< RnPlane >& Planes, const RnTransform& Transform, const RnCapsule& Capsule, const RnWorld* World )
	{
	RnBounds3 Bounds = Capsule.ComputeBounds( Transform );

	RnStackArray< RnShape*, 32 > Buffer;
	World->Query( Buffer, Bounds );

	for ( RnShape* Shape : Buffer )
		{
		RnBody* Body = Shape->GetBody();
		switch ( Shape->GetType() )
			{
			case RN_SPHERE_SHAPE:
				{
				RN_ASSERT( 0 );
				}
				break;

			case RN_CAPSULE_SHAPE:
				{
				RN_ASSERT( 0 );
				}
				break;

			case RN_HULL_SHAPE:
				{
				const RnHullShape* HullShape = static_cast< const RnHullShape* >( Shape );

				RnGJKResult Result = rnGJK( Transform, RnGJKProxy( 2, &Capsule.Center1 ), Body->GetTransform(), HullShape->GetGJKProxy() );
				if ( 0.0f < Result.Distance && Result.Distance <= Capsule.Radius )
					{
					// Plane through closest point on hull with normal pointing towards capsule
					RnVector3 Normal = rnNormalize( Result.Point1 - Result.Point2 );
					RnVector3 Point = Result.Point2;

					// Shift to origin
					float Separation = Result.Distance - Capsule.Radius;
					Point = Transform.Translation - Separation * Normal;
						
					Planes.PushBack( RnPlane( Normal, Point ) );
					}
				}
				break;

			case RN_MESH_SHAPE:
				{
				const RnMeshShape* MeshShape = static_cast< const RnMeshShape* >( Shape );

				// Transform swept bounds into local space of mesh
				RnBounds3 LocalBounds = rnInvert( Body->GetTransform() ) * Bounds;

				// Find overlapping triangles
				RnArray< int > Triangles;
				MeshShape->Query( Triangles, LocalBounds );

				for ( int Index = 0; Index < Triangles.Size(); ++Index )
					{
					RnVector3 Triangle[ 3 ];
					MeshShape->GetTriangle( Triangle, Triangles[ Index ] );

					RnGJKResult Result = rnGJK( Transform, RnGJKProxy( 2, &Capsule.Center1 ), Body->GetTransform(), RnGJKProxy( 3, Triangle ) );
					if ( 0.0f < Result.Distance && Result.Distance <= Capsule.Radius )
						{
						// Plane through closest point on triangle with normal pointing towards capsule
						RnVector3 Normal = rnNormalize( Result.Point1 - Result.Point2 );
						RnVector3 Point = Result.Point2;

						// Shift to origin 
						float Separation = Result.Distance - Capsule.Radius;
						Point = Transform.Translation - Separation * Normal;

						Planes.PushBack( RnPlane( Normal, Point ) );
						}
					}
				}
				break;

			default:
				RN_ASSERT( 0 );
				break;
			}
		}
	}

//--------------------------------------------------------------------------------------------------
void RnCharacterProxy::ResolveCollision( float Timestep )
	{
	RnCapsule Capsule = GetCapsule();

	RnVector3 Position = mPosition;
	RnVector3 Velocity = mVelocity;
	RnVector3 Target = Position + Velocity * Timestep;

	RnArray< RnPlane > Planes;
 	rnCollideShape( Planes, RnTransform( Position, RN_QUAT_IDENTITY ), Capsule, mWorld );
	
	while ( true )
		{
		// Save the current tentative position 
 		RnVector3 LastPosition = Position;

		// Move player to target and resolve collision constraints (NGS)
 		Position = Target;
		rnSolveConstraints( Position, Velocity, Planes );

		// Break if two consecutive positions are close together 
		if ( rnDistanceSq( LastPosition, Position ) < RN_LINEAR_SLOP * RN_LINEAR_SLOP )
			{
			break;
			}

		// Sweep from current towards the new position
		RnWorldSweepResult Result = rnSweepCapsule( mPosition, Position, Capsule, mWorld );

		// Collect new contact planes at TOI
		RnVector3 Contact = rnLerp( mPosition, Position, Result.Alpha );
		rnCollideShape( Planes, RnTransform( Contact, RN_QUAT_IDENTITY ), Capsule, mWorld );
		}

	// Remember if we solved collisions
	mColliding = !Planes.Empty();

	// Save new velocity and position
	mVelocity = Velocity;
	mPosition = Position;
	}

One important realization is that you can transform the capsule contacts into a point to plane problem. E.g. by shifting to the contact planes on the surface of the capsule to the origin of the capsule since the capsule is invariant under rotation around the up axis. (e.g. rnCollideShape() → Shift to origin)

HTH,

-Dirk

Oh, and don't compute the velocity of the character proxy as the difference of the positions over time. This would add velocity to the character if another character bumps into him. Instead keep a separate velocity state and project the velocity as well ( e.g. see rnSolveConstraints() ).

Finally, you might notice that I remember whether I resolved collisions ( mCollision = !Planes.Empty() ). It is important NOT to apply acceleration if you are not on the ground (air acceleration) in this case. Otherwise you can get very weird results. E.g. you might be pushed up a cliff unnaturally.

@Dirk Gregorius I solved it now, thank you for all the help!

This topic is closed to new replies.

Advertisement