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

Started by
20 comments, last by Spa8nky 14 years, 1 month ago
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.
Advertisement
In your Integrate function, you set the shape's centre according to force_Accumulated. Where/how is force_Accumulated set?

Please don't PM me with questions. Post them in the forums for everyone's benefit, and I can embarrass myself publicly.

You don't forget how to play when you grow old; you grow old when you forget how to play.

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!
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;

Please don't PM me with questions. Post them in the forums for everyone's benefit, and I can embarrass myself publicly.

You don't forget how to play when you grow old; you grow old when you forget how to play.

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;        }
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)??

Please don't PM me with questions. Post them in the forums for everyone's benefit, and I can embarrass myself publicly.

You don't forget how to play when you grow old; you grow old when you forget how to play.

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?
public float Mass{  get  {    /* .. */  }  set  {    mass_Inverse = value; /* <- shouldn't that be mass_Inverse = 1 / value ? */  }}


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)?

Please don't PM me with questions. Post them in the forums for everyone's benefit, and I can embarrass myself publicly.

You don't forget how to play when you grow old; you grow old when you forget how to play.

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.

This topic is closed to new replies.

Advertisement