Sign in to follow this  

Impulses causing both objects to drift in normal direction when in contact.

This topic is 2855 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

I'm currently working on getting my impulse behaviour correct for two rigid bodies that are colliding: Colliding Bodies Body a has 0 mass and is not affected by an impulse Body b will be pushed in the direction of the -ve collision normal (0, 1) using an impulse. Both the colliding shapes will be repositioned based on the following formula: a.Shape.Centre += contacts[i].normal * contacts[i].penetration; b.Shape.Centre += -contacts[i].normal * contacts[i].penetration; The problem is that when both shapes/bodies are in contact, they both gradually drift in the direction of the collision normal (0, -1). Here is how I'm currently applying the impulses/resolving the collisions:
            // Collect all contacts from collision detection then solve them one by one
            List<Contact2D> contacts = detector.contact_List;

            // Iterate backwards through contacts so they can be removed from the list in the same loop
            for (int i = contacts.Count - 1; i >= 0; i--)
            {
                Contact2D contact = contacts[i];

                RigidBody2D a = contact.body[0];                // Solid object
                RigidBody2D b = contact.body[1];                // Man

                // Correct collision shape positions
                a.Shape.Centre += contacts[i].normal * contacts[i].penetration;
                b.Shape.Centre += -contacts[i].normal * contacts[i].penetration;
                
                // Coefficient of restitution
                float e = 0.9f; // BOING!

                // Calculate relative velocity
                Vector2 velocity_Relative = a.Velocity - b.Velocity;

                // Calculate scalar impulse
                float j = -(1f + e) * Vector2.Dot(velocity_Relative, contact.normal);

                // The following is not needed if normal is normalised (unit length):
                //j /= Vector2.Dot(contact.normal, contact.normal * (a.Mass_Inverse + b.Mass_Inverse));

                // Calculate vector impulse
                Vector2 impulse = j * contact.normal;

                // Add impulse force at contact point for each body
                a.ApplyImpulse(impulse, contact.point);

                // Negate the force for the opposite body (Newton's Third Law)
                b.ApplyImpulse(-impulse, contact.point);

                // Remove this contact now that it has been resolved
                contacts.RemoveAt(i);
            }

Applying the impulse for the body is as follows:
        public void ApplyImpulse(Vector2 impulse, Vector2 point)
        {
            // v2 = v1 + j/m * n
            velocity_Linear += mass_Inverse * impulse;
        }

The collision shapes positions/centres are also being updated when the body is integrated, if body does not have 0 mass:
        public void Integrate(float duration)
        {
            // We don't integrate things with zero mass
            if (!HasFiniteMass)
            {
                return;
            }

            // Update acceleration from accumulated forces
            // F = M * A
            // A = F / M
            //Vector2 acceleration_Resulting = acceleration;
            //acceleration_Resulting += force_Accumulated;
            Vector2 acceleration = force_Accumulated / Mass;

            // Update linear velocity from the acceleration
            // V = V + A * dT
            velocity_Linear += acceleration * duration;

            // Update linear position
            // P = P + V * dT
            position += velocity_Linear * duration;

            // aA = T / I
            float acceleration_Angular = torque_Accumulated / (Mass * shape.Inertia);
            
            // aV = aV + aA * dT
            velocity_Angular += acceleration_Angular * duration;

            // R = R + aV * dT 
            rotation += velocity_Angular * duration;

            // Clear the accumulated forces
            ClearAccumulatedForce();

            // Does the world transform need recreating this frame?
            //isAwake = velocity != Vector2.Zero ? true : false;

            // Update the shape 
            shape.Centre = position;
            shape.Rotation = rotation;
        }

Can anyone please explain why the drifting might be occuring? Thank you.

Share this post


Link to post
Share on other sites
Quote:
Original post by Buckeye
In your Integrate function, you set the shape's centre according to force_Accumulated. Where/how is force_Accumulated set?


Are you talking about these lines in the Integrate() method?


// Update the shape
shape.Position = position;
shape.Rotation = rotation;


The force_Accumulated is only set inside the rigid body class via the following methods:


public void AddForce(Vector2 force)
{
// Accumulation stage must take place before the particle is integrated [Integrate()]
force_Accumulated += force;
}

public void AddForce(Vector2 force, Vector2 point)
{
isAwake = true;

// Accumulation stage must take place before the particle is integrated [Integrate()]
force_Accumulated += force;

point -= position;

// Position is always at centre of mass
//torque_Accumulated += force.Cross(point);
}



Currently only gravity and drag are affecting the rigid body:


class Particle2D_Gravity : IRigidBody2D_ForceGenerator
{
Vector2 gravity; // Holds the acceleration due to gravity

// Create the generator with the given acceleration
public Particle2D_Gravity(Vector2 gravity)
{
this.gravity = gravity;
}

// Applies the gravitational force to the given particle
public override void UpdateForce(RigidBody2D body, float duration)
{
if (body.IgnoreGravity || !body.HasFiniteMass)
{
return;
}

// Apply the mass-scaled force to the particle
body.AddForce(gravity * body.Mass);
}
}

class Particle2D_Drag : IRigidBody2D_ForceGenerator
{
float k1; // Holds the velocity drag coefficient
float k2; // Holds the velocity squared drag coefficient

public Particle2D_Drag(float k1, float k2)
{
this.k1 = k1;
this.k2 = k2;
}

// Applies the drag force to the given particle
public override void UpdateForce(RigidBody2D body, float duration)
{
if (!body.HasFiniteMass)
{
return;
}

Vector2 force = body.Velocity;

// Calculate the total drag coefficient
float drag_Coefficient = force.Length();
drag_Coefficient = k1 * drag_Coefficient + k2 * drag_Coefficient * drag_Coefficient;

// Calculate the final force and apply it
if (drag_Coefficient != 0)
{
// Force must be normalised for drag equation to be correct
force.Normalize();
}

force *= -drag_Coefficient;

body.AddForce(force);
}
}



The acceleration of the body is as follows:


// Update acceleration from accumulated forces
// F = M * A
// A = F / M
Vector2 acceleration = force_Accumulated * mass_Inverse;


Thanks for coming to my hair's rescue Buckeye. I've been tearing it out for most of the day trying to figure this one out!

Share this post


Link to post
Share on other sites
Quote:
Are you talking about these lines in the Integrate() method?


// Update the shape
shape.Position = position;
shape.Rotation = rotation;


Nope. These lines:
Quote:
// Update the shape
shape.Centre = position;
shape.Rotation = rotation;

Share this post


Link to post
Share on other sites
Apologies, they are the same two lines now I've updated my code since the first post:


public void Integrate(float duration)
{
// We don't integrate things with zero mass
if (!HasFiniteMass)
{
return;
}

// ==========================================================================================================
// --[Linear]------------------------------------------------------------------------------------------------
// ==========================================================================================================

// Update acceleration from accumulated forces
// F = M * A
// A = F / M
Vector2 acceleration = force_Accumulated * mass_Inverse;
//Vector2 acceleration_Resulting = acceleration;
//acceleration_Resulting += force_Accumulated;

// Update linear velocity from the acceleration
// V = V + A * dT
velocity_Linear += acceleration * duration;

// Update linear position
// P = P + V * dT
position += velocity_Linear * duration;

// ==========================================================================================================
// --[Angular]-----------------------------------------------------------------------------------------------
// ==========================================================================================================

// aA = T / I
float acceleration_Angular = torque_Accumulated / (Mass * shape.Inertia);

// aV = aV + aA * dT
velocity_Angular += acceleration_Angular * duration;

// R = R + aV * dT
rotation += velocity_Angular * duration;

// ==========================================================================================================

// Clear the accumulated forces
ClearAccumulatedForce();

// Does the world transform need recreating this frame?
//isAwake = velocity != Vector2.Zero ? true : false;

// Update the shape
shape.Position = position;
shape.Rotation = rotation;
}



Share this post


Link to post
Share on other sites
What's the relationship between shape.Centre and shape.Position? It's not clear what you're using to render the shape.

Also, what do you use for mass_Inverse (assuming mass_Inverse = 1/mass) for mass=0?
Quote:
// We don't integrate things with zero mass
if (!HasFiniteMass)
{
return;
}


Also, should that be if(!HasZeroMass)??

Share this post


Link to post
Share on other sites
Here is my shape class:


public enum Shape_Type : int
{
CIRCLE = 0,
OBB = 1,
POLYGON = 2,
AABB = 3,
Count = 4 // Total number of shape types
}

public abstract class Shape
{
protected Vector2 centre; // Shape's centroid (centre of mass)
protected float mass; // Shape's mass
protected float inertia; // Moment of inertia
protected float inertia_Inverse; // The inverse of the shape's inertia
protected Vector2 position; // Position in world space coordinates
protected float radius; // Radius of minimum circle that encompasses all shape's points
protected bool recreateWorld = true; // Recreate the world transform this frame?
protected float rotation; // Rotation angle
protected Vector2 scale = Vector2.One;
protected Shape_Type type; // Shape type
protected Vector2 velocity; // Shape's velocity
protected Matrix world_Transform;

public Shape() { }

#if DEBUG
public abstract void Draw(); // All shapes must be able to be drawn
#endif

public Vector2 Position
{
get
{
return position;
}
set
{
recreateWorld = true;
position = value;
}
}

public float Inertia
{
get
{
return inertia;
}
set
{
inertia = value;
}
}

public float Radius
{
get
{
return radius;
}
set
{
recreateWorld = true;
radius = value;
}
}

public float Rotation
{
get
{
return rotation;
}
set
{
recreateWorld = true;
rotation = value;
}
}

public Shape_Type Type
{
get
{
return type;
}
}

public Vector2 Velocity
{
get
{
return velocity;
}
set
{
velocity = value;
}
}
}



Position is the world position of the shape. Centre no longer exists as a property as it has been replace by Position. The protected variable centre is the shape's centroid.

All shapes are drawn using a world transform, as follows:


world_Transform =
// Translate centroid to origin first (only required if centroid is not (0,0,0))
Matrix.CreateTranslation(new Vector3(-centre, 0.0f)) *
Matrix.CreateScale(scale.X, scale.Y, 0f) *
Matrix.CreateRotationZ(rotation) *
Matrix.CreateTranslation(position.X, position.Y, 0f);



Quote:

Also, what do you use for mass_Inverse (assuming mass_Inverse = 1/mass) for mass=0?


Mass and mass_Inverse are set as follows:


public bool HasFiniteMass
{
get { return mass_Inverse > 0f; }
}

public float Mass
{
get
{
if (mass_Inverse == 0)
{
return float.MaxValue;
}
else
{
return (1.0f / mass_Inverse);
}
}
set
{
mass_Inverse = value;
}
}

public float Mass_Inverse
{
get { return mass_Inverse; }
set { mass_Inverse = value; }
}



Are my properties correct?

Share this post


Link to post
Share on other sites

public float Mass
{
get
{
/* .. */
}
set
{
mass_Inverse = value; /* <- shouldn't that be mass_Inverse = 1 / value ? */
}
}



Share this post


Link to post
Share on other sites
What Wan said, with maybe:

set
{
if(value==0) mass_Inverse = FLT_MAX;
else mass_Inverse = 1/value;
}

With your code, set(2); get() = 1/2;

Also, HasInfiniteMass is always true.

Maybe HasInfiniteMass = (mass==FLT_MAX)?

Share this post


Link to post
Share on other sites
Argh! Sorry folks and thanks for pointing that out.

Here are the corrected properties:


public bool HasInfiniteMass
{
get { return mass_Inverse == float.MaxValue || mass_Inverse == 0; }
}

public bool IgnoreGravity
{
get { return ignoreGravity; }
set { ignoreGravity = value; }
}

public float Mass
{
get
{
return mass_Inverse == 0 ? float.MaxValue : 1f / mass_Inverse;
}
set
{
mass_Inverse = value == 0 ? float.MaxValue : 1f / value;
}
}

public float Mass_Inverse
{
get { return mass_Inverse; }
set { mass_Inverse = value; }
}




The "mass_Inverse == 0" is needed because the mass might not be set by default (remains at 0) and therefore infinite mass is assumed.

The drifting problem still remains though when the two bodies are in contact.

Share this post


Link to post
Share on other sites
I may have messed you up by using HasInfiniteMass where you had HasFinateMass (I misread), but it probably doesn't affect the results you're seeing.

In any case, you apparently changed HasFiniteMass to HasInfiniteMass. Did you reverse the logic for "if( !HasFinateMass )" to "if( HasInfinateMass )"?
Quote:
Position is the world position of the shape. Centre no longer exists as a property as it has been replace by Position.

Just to see the changes you've made (it's getting a little difficult to follow at this point), could you post the "collision resolving" code as you now have it?

Share this post


Link to post
Share on other sites
The collision response code is a follows:


// Collect all contacts from collision detection then solve them one by one
List<Contact2D> contacts = detector.contact_List;

// Iterate backwards through contacts so they can be removed from the list in the same loop
for (int i = contacts.Count - 1; i >= 0; i--)
{
Contact2D contact = contacts[i];

RigidBody2D a = contact.body[0];
RigidBody2D b = contact.body[1];

// Correct collision shape positions
a.Shape.Position += contacts[i].normal * contacts[i].penetration;
b.Shape.Position += -contacts[i].normal * contacts[i].penetration;

// Coefficient of restitution
float e = 0.9f; // BOING!

// Calculate relative velocity
Vector2 velocity_Relative = a.Velocity - b.Velocity;

// Calculate scalar impulse
float j = -(1f + e) * Vector2.Dot(velocity_Relative, contact.normal);

// The following is not needed if normal is normalised (unit length):
//j /= Vector2.Dot(contact.normal, contact.normal * (a.Mass_Inverse + b.Mass_Inverse));

// Calculate vector impulse
Vector2 impulse = j * contact.normal;

// Add impulse force at contact point for each body
a.ApplyImpulse(impulse, contact.point);

// Negate the force for the opposite body (Newton's Third Law)
b.ApplyImpulse(-impulse, contact.point);

// Remove this contact now that it has been resolved
contacts.RemoveAt(i);
}



The impulse is applied inside the body class as follows:


public void ApplyImpulse(Vector2 impulse, Vector2 point)
{
// v2 = v1 + j/m * n
velocity_Linear += mass_Inverse * impulse;
}



The only other aspect of collision response is at the end of the body's Integrate() method:


// Update the shape
shape.Position = position;
shape.Rotation = rotation;


Thanks again.

Share this post


Link to post
Share on other sites
I'm thinking at this point (your code's just a bit too convoluted for me to keep straight just from inspection), you should start checking each step (or small sequence of steps) to make sure it's doing what you expect - set breakpoints or output debug statements for the important steps.

It seems the most obvious place to start is to check shape positions in your collision routine to ensure they move away from the collision position as you want them to do, before you apply any other forces.

If that's as expected, check the positions after 1 or 2 forces have been applied.

Share this post


Link to post
Share on other sites
If I add a small epsilon value to the impulse and remove the correction of the shapes from the collision resolution stage then that stops the drifting but causes object b to stutter a very small amount on the surface of a.


for (int i = contacts.Count - 1; i >= 0; i--)
{
Contact2D contact = contacts[i];

RigidBody2D a = contact.body[0]; // Solid object
RigidBody2D b = contact.body[1]; // Man

// Correct collision shape positions
//a.Shape.Position += contacts[i].normal * contacts[i].penetration;
//b.Shape.Position += -contacts[i].normal * contacts[i].penetration;

// Coefficient of restitution
float e = 0.9f; // BOING!

// Calculate relative velocity
Vector2 velocity_Relative = a.Velocity - b.Velocity;

// Calculate scalar impulse
float j = -(1f + e) * Vector2.Dot(velocity_Relative, contact.normal);
j += 0.01f; // SMALL EPSILON VALUE

// The following is not needed if normal is normalised (unit length):
//j /= Vector2.Dot(contact.normal, contact.normal * (a.Mass_Inverse + b.Mass_Inverse));

// Calculate vector impulse
Vector2 impulse = j * contact.normal;

// Add impulse force at contact point for each body
a.ApplyImpulse(impulse, contact.point);

// Negate the force for the opposite body (Newton's Third Law)
b.ApplyImpulse(-impulse, contact.point);

// Remove this contact now that it has been resolved
contacts.RemoveAt(i);
}




Should I perhaps test with a projected shape position instead of the shape's current position?

Any thoughts welcome.

Share this post


Link to post
Share on other sites
Yes, I believe so. Gravity pulls b into a and the collision is resolved each frame which causes the bouncing only for gravity to pull b into a again.

Any ideas how I can minimize this effect?

Share this post


Link to post
Share on other sites
Actually, that's consistent with physical phenomena. Eventually it should dampen out. Maybe reduce the force due to gravity (only in the vertical direction, of course) when there's a collision, limiting that portion of the rebound, as if you're making the objects just a little bit "sticky" when they're in contact.

Share this post


Link to post
Share on other sites
It is applied when resolving collisions:


// Coefficient of restitution
float e = 0.9f; // BOING!

// Calculate relative velocity
Vector2 velocity_Relative = a.Velocity - b.Velocity;

// Calculate scalar impulse
float j = -(1f + e) * Vector2.Dot(velocity_Relative, contact.normal);
j += 0.01f; // SMALL EPSILON VALUE



Object b will now slowly sink into vertical walls also if I push in the direction of the wall! What could be the cause of this behaviour?

Share this post


Link to post
Share on other sites
Did you change the restitution? Looks like 0.9 both places. You add a fixed epsilon (reducing the restitution and perhaps changing the sign of j) which isn't the same as changing the restitution. Is there a reason for the "epsilon"?

Also, a previous post:
Quote:
you should start checking each step (or small sequence of steps) to make sure it's doing what you expect

Have you tried that to see what's happening?

Share this post


Link to post
Share on other sites
I've changed the restitution to 0 and removed gravity altogether, only allowing for movement via key input; the same problem still persists.

The collision resolution process now undergoing a complete rewrite as the problem is not obvious even when debugging. I'll report back when it's rewritten with some better news, hopefully.

Share this post


Link to post
Share on other sites

This topic is 2855 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this