Jump to content
  • Advertisement
Sign in to follow this  
Spa8nky

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

This topic is 3067 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.normal * contacts.penetration; b.Shape.Centre += -contacts.normal * contacts.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;

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

                // Correct collision shape positions
                a.Shape.Centre += contacts.normal * contacts.penetration;
                b.Shape.Centre += -contacts.normal * contacts.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
Advertisement
In your Integrate function, you set the shape's centre according to force_Accumulated. Where/how is force_Accumulated set?

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
Sign in to follow this  

  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

We are the game development community.

Whether you are an indie, hobbyist, AAA developer, or just trying to learn, GameDev.net is the place for you to learn, share, and connect with the games industry. Learn more About Us or sign up!

Sign me up!