4
\$\begingroup\$

I have been trying to have a sphere to plane eventually lead to the sphere coming to a resting contact, but my sphere will end up always bouncing forever. It will bounce correctly for the first few times, but eventually it will just be in a constant state of a very small bounce. I have tried to exhaust every article I can find but I don't see where I am going wrong here. Does anyone see the issue? I have attempted to put as much information into this post so I apologize if it is a bit long.

I first attempt to see if there is resting contact, collision, no collision or if the sphere and plane will collide

Vector3 forces = (sphereTotalForces + sphereVelocity);
float denom = Vec3Dot(&plane->GetNormal(), &forces);

if(sphereVelocity.GetMagnitude() > 0 && dist <= sphereRadius + .0013f) {
    crCollisionResult.enCollisionState = RESTING_CONTACT;
    crCollisionResult.vCollisionNormal = plane->GetNormal();
    return crCollisionResult;
}
else if(fabs(dist) + 0.001f <= sphere->GetRadius()) {
    crCollisionResult.enCollisionState = COLLISION_HAS;
    return crCollisionResult;
}
else if(denom * dist > 0.0f) {
    crCollisionResult.enCollisionState = COLLISION_NO;
    return crCollisionResult;
}

where

sphereRadius  = 1
denom = -9.98206139
dist = 2.34482479

Since none of those are true I will then attempt to calculate when the collision will happen

    F32 fIntersectionTime = (sphere->GetRadius() - dist) / denom;
    F32 r;
    if(dist > 0.0f)
        r = sphere->GetRadius();
    else
        r = -sphere->GetRadius();


    Vector3 collision = spherePosition + fIntersectionTime * rigidBodySphere->GetVelocity() - r * plane->GetNormal();
    crCollisionResult.fCollisionTime = fIntersectionTime;
    crCollisionResult.vCollisionNormal = plane->GetNormal();
    crCollisionResult.vAdjustedPosition = collision;

The intersection time will be calculated to be 0.00334050134 , which is less than the fElapsedTime of 0.00703109941. As a result, I will update my sphere using the intersection time as the delta (so fElapsedTime here will be 0.00334050134)

    mAcceleration = mTotalForces / mMass;
    mVelocity += mAcceleration * fElapsedTime;
    Vector3 translation = (mVelocity * fElapsedTime);

    mTransform->Translate(translation.x, translation.y, translation.z);

    mTotalForces = Vector3(0.0f, 0.0f, 0.0f);

where

mTotalForces = (0, -10, 0)
mMass = 22

After I update there sphere with its new position, I will then calculate the new velocity of it being reflected

        Vector3 vSurfaceNormalized;
        Vec3Normalize(&vSurfaceNormalized, &crCollisionResult.vCollisionNormal);

        Vector3 vIncomingVelocity = pSphere->GetVelocity();
        F32 fIDotN = Vec3Dot(&vIncomingVelocity, &vSurfaceNormalized);

        Vector3 vReflectedVelocity = vIncomingVelocity - ((1.0f+pSphere->GetCoefficientOfRestitution()) * vSurfaceNormalized * fIDotN);
        pSphere->SetVelocity(vReflectedVelocity);

where

pSphere->mCoefficientOfRestitution = .85
vIncomingVelocity = (0, -5.30, 0)
mNormal = (0, 1, 0)
fIDotN = -5.30350208
vReflectedVelocity = (0, 4.50797653)
\$\endgroup\$
3
  • \$\begingroup\$ Just something that isn't clarified: What's fabs()? \$\endgroup\$ – JPtheK9 May 10 '15 at 0:37
  • \$\begingroup\$ @JPtheK9 floating point abs(absolute value) in C probably: stackoverflow.com/a/3118188/796832 \$\endgroup\$ – MLM May 10 '15 at 5:27
  • \$\begingroup\$ Oh, I never used C hehe. But yeah, figured it was abs. \$\endgroup\$ – JPtheK9 May 10 '15 at 6:36
1
\$\begingroup\$

Like both answers said, there are 2 "secrets", one is you need to stop applying gravity when bounces are determined to be over (which is a bit of an extension over Leftium answers, he just said "resting" which is not enough because you rest as soon as you touch by this definition).
Secondly, check for speed, obviously. This later condition is in relation to energy. Speed is your simple measure of energy (cinetic), which is what really is congruated to the notion of rest. What is rest if simply a stable energy level.

To extend and finish the last blurry part in the phrase "bounces are determined to be over", this is a criterion that you can measure by looking at the bounce velocity (projection of speed on the normal of the contact plane). If it is small then you can consider that bouncing again is un-necessary, because your object is not infinitely hard, if will have elasticity and stop to bounce in reality. Thus a threshold makes sense.

Now please consider this project:
http://sourceforge.net/projects/carnage-engine/

and this piece of code is of interest to you:
demo/demo.cpp

method Simulate line 332.

ce::Vector2f force(0);
ce::Vector2f accel = force / mass;
if (inAir)
    accel.y = 380;  // gravity acceleration
// newton integration
speed += accel * dt;
if (sqrt(speed.LengthSq()) > 800.f)  // speed limit; cross it, you'll have a ticket.
    speed = Normalize(speed) * 800.f;
if (!inAir)
{
    // regular slowdown when on the ground.
    speed -= speed * 0.9 * dt;
}

pos += speed * dt;  // integration
ce::Vector2i prevpos = spm.GetRect(ball).pos;  // remember the free position before moving
spm.Move(ball, pos);
// check for collision with world objects: (static objects, i.e. the level)
if (collmgr->ShapeIntersectWorld(ce::MkCircleShape(spm.GetRect(ball))))
{
    // we must find the true collision position
    ce::Vector2i freePos = collmgr->FindFreePosition(prevpos, ce::MkCircleShape(spm.GetRect(ball)));
    spm.Move(ball, freePos);
    // the free directions is a stupid but efficient technique to know
    // where you can go now. It tests your shape with 1 pixel offset on 4 directions.
    ce::CollisionManager::Directions freedirs =
        collmgr->DiscoverFreeDirections(ce::MkCircleShape(spm.GetRect(ball)));
    if ( (!freedirs.left && speed.x < 0)
         || (!freedirs.right && speed.x > 0)  )
    {
        speed.x = -speed.x*0.2;  // can't travel on X because i'm blocked. so bounce back
    }
    if ( (!freedirs.up && speed.y < 0)
         ||  (!freedirs.down && speed.y > 0) )
    {
        if (fabs(speed.y) > 100)
            speed.y = -speed.y * 0.5;  // bounce ! :)
        else
            speed.y = 0;  // freeze pos to avoind bouncing infinitesimal jumps.
    }
    if (inAir && !freedirs.down)  // was in the air, but our bottom collided.
    {
        inAir = false;  // we are touching ground.
    }
    pos = spm.GetRect(ball).pos; // don't let the internal float pos get crazy (it doesn't know yet that we collided, so we must reset it to the actual pos (an integer, because we work in pixels))
}
else  // we are free for this movement. (not sure at 100% actually, because if framerate is SUPER low, we could pass through thin objects)
{
    if (!inAir)
    {
        ce::CollisionManager::Directions freedirs =
            collmgr->DiscoverFreeDirections(ce::MkCircleShape(spm.GetRect(ball)));
        if (freedirs.down)
        {
            inAir = true;  // nothing under us, thus we are flying.
        }
    }
}
// at this position, we should always be free:
BOOST_ASSERT(!collmgr->ShapeIntersectWorld(ce::MkCircleShape(spm.GetRect(ball))));

As you can see, there are all your elements in this code, notably the contact epsilon, which for me is 1 pixel, for you 0.0013. either way it should work.
Then there is the speed check to stop bouncing, which is what JPthek9 was proposing, in my case its 100, I got it experimentally.
I also have energy dumpers as you can see, a fluid one, which you can consider as being air drag.
And the bounce losses, laterally 80% is lost on each vertical walls. Vertically 50%, this is more fun this way. You can make something more physically realist in your case.

And the Leftium's point, is the "inAir" boolean flag, this code gives you a concrete way to determine it.
After reading you will notice that "inAir" is not equivalent to "contact". Because in my case, you can be inAir while bouncing against a vertical wall. Which doesn't allow vertical bouncing, just horizontal bouncing. In your case, the criterion could be a projection of the normal with the vertical normal. (just take the .y and compare with 0.98 or smth) Remember that a sphere cannot rest on slopes. (that is, if you want to simulate that. my simulator doesn't.)

\$\endgroup\$
1
\$\begingroup\$

Instead of checking the distance for a resting contact, check for velocity. If 2 objects are moving slowly and hit each other, they should be put to rest. If not, they should do what they would do if they weren't resting.

So...

if (CollisionDetection())
{
    if (sphereVelocity.GetMagnitude() <= .1f)
    {
        //NoGravity();
        //NoForces();
        //NoVelocity();
        return;
    }
    //CollisionStuff ();
}

This will allow the ball to naturally bounce and keep losing velocity from the incomplete restitution, until it eventually loses enough velocity to rest upon contact. Make sure you have incomplete restitution so your ball loses some velocity with every bounce.

Here's the collision detection between circle and line:

//This collision detection uses SAT
bool CollisionDetection()
{
    ///Get the axis to project on
    Vector2 Axis = Line.PointB - Line.PointA;
    //Make it perpendicular for the separating axis
    Axis = new Vector2 (-Axis.y, Axis.x);
    Axis.Normalize ();

    //CP as in Circle Projection
    float CP = Vector2.Dot(Circle.Position, Axis);
    //Circles are oriented the same way on all axes so just +- the radius
    float CP_Min = CP - Circle.Radius;
    float CP_Max = CP + Circle.Radius;

    //LP as in Line Projection
    float LP_A = Vector2.Dot (Line.PointA, Axis);
    float LP_B = Vector2.Dot (Line.PointB, Axis);
    float LP_Min;
    float LP_Max;
    //Get the minimum projection
    if (LP_A < LP_B)
    { 
        LP_Min = LP_A;
        LP_Max = LP_B;
    }
    else {
        LP_Min = LP_B;
        LP_Max = LP_A;
    }

    //Checking for collision, now that our entities are axis-aligned
    //We can do a simple bounds check
    if (CP_Min <= LP_Max)
        if (CP_Max >= LP_Min)
            return true;
    return false;
}

Basically, you get the projection of the bounds of each entity on the line's axis (the min and max) then use these axis-aligned points to determine whether or not there is a collision. I recommend reading this sample for a better understanding of this collision detection method called SAT.

\$\endgroup\$
9
  • \$\begingroup\$ I just tried to multiply by the elapsed time to have it be "dist <= sphereRadius + (0.0013f * fElapsedTime)" but that still never reached resting contact. For this dist was "1.04232144" and sphereRadius + (0.0013f * fElapsedTime) was "1.00000954". I seem to think part of the problem is the way the dist or planeToSphere is calculated as planeToSphere.y here is 2.04 \$\endgroup\$ – judeclarke May 9 '15 at 23:59
  • \$\begingroup\$ Have you tried increasing .0013 to maybe .1? Maybe that distance just isn't enough to detect resting contacts. If you increase this, you might also need to have a maximum velocity the object needs to be under for a resting contact. Actually, there's a better way which I've added to the answer. \$\endgroup\$ – JPtheK9 May 10 '15 at 0:28
  • \$\begingroup\$ Do you mean it would be "if(sphereVelocity.GetMagnitude() > 0 && dist <= sphereRadius + 0.1f)"? If so then that would be true upon it first falling before it ever bounced \$\endgroup\$ – judeclarke May 10 '15 at 6:43
  • \$\begingroup\$ An object rests on another object if its velocity is very low and it comes into contact with the other object. A bunch of static friction starts going on. All you need to do is first detect a collision, then check the velocities of the objects. If the velocity is low, you will have a resting contact. \$\endgroup\$ – JPtheK9 May 10 '15 at 9:04
  • \$\begingroup\$ Just in case it's not clear, resting contacts aren't decided by distance. Distance can only detect collisions. Once a collision is detected, it can be a resting contact is reached if there is no difference in velocity between the two objects - they're staying still relative to each other. This makes them "rest", creating static friction and increasing the impulse needed to push them out of their resting contact. \$\endgroup\$ – JPtheK9 May 11 '15 at 6:04
0
\$\begingroup\$

Stop applying (the gravity) force after reaching RESTING_CONTACT.

My guess is your sphere is reaching resting contact, but gravity (or other forces) causes it to continue moving as soon as the next update happens.

Explanation:

Generally, once an object reaches resting contact, it should not respond to forces until there is a force large enough to "push" it out of the resting state. This is an approximation because in real life, objects in resting contact do not really stop moving: they are constantly vibrating and colliding with each other at a microscopic level. So play with the the magnitude of this threshold force until it looks realistic. This threshold force is the "stickiness" of the objects at resting contact with each other.


Also, JPtheK9 is correct. You should only enter the resting contact state if the object velocities are low. Imagine if the sphere were moving at the maximum speed possible and it happened to be sphereRadius units away when you calculate the updates. Should it go to into the resting contact state?

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.