3
\$\begingroup\$

I have a basic physics body system (incomplete/very basic; as collision resolution and detection are not implemented, just mass, position, velocity, acceleration, force and impulse).

I implemented position, velocity, acceleration, force and impulse as described in Game Coding Complete 3rd. Ed. by Mike "Mr. Mike" McShaffrty et al. specifically Chapter 15: Collision and Simple Physics by Jeff Lake which is: (paraphrasing from text and code, US copyright law and all that.)

Gravity is a Force.

Application of a Force vector adds it to a std::list of forces.

Application of an Impulse vector adds it to a std::list of impulses.

Upon calling Update on the physics body:

  1. Add all forces and all impulses together (F).
  2. Clear the impulses list.
  3. Set acceleration to (F / mass)
  4. Set velocity to (velocity + (acceleration * deltaTime))
  5. Set position to (position + (velocity * deltaTime))

(See code below)

The problem I'm having is when gravity is applied as a force the list grows linearly...forever. Eventually it overcomes any attempt to directly set the position of an object to the point that A) The object is always affected by gravity no matter if or how fast the position is changed (clamping to the screen is a good example) B) The list grows so large that the system implodes and chugs. C) If, before the system implodes, the object is stopped at a specific position, the acceleration due to gravity continually increases making it ever harder to change the acceleration/velocity/position of the object (specifically applying an impulse or force upwards).

If I change the noted line in GravityHandler to elem->ApplyImpulse(deltaG); it works satisfactorily but is this correct or am I missing something?

My implementation according to his example:

RigidBody and its state:

void State::ApplyImpulse(const Vector2D& impulse) {
    _impulse.push_back(impulse);
}

void State::ApplyForce(const Vector2D& force) {
    _forces.push_back(force);
}

//RigidBody is a `friend` of State.
void RigidBody::ApplyForce(const Vector2D& force) {
    _curState.ApplyForce(force);
}
void RigidBody::ApplyImpulse(const Vector2D& impulse) {
    _curState.ApplyImpulse(impulse);
}

GravityHandler:

void GravityHandler::Update(double deltaTime) {

    class UpdateObject {
    public:
        UpdateObject(double deltaTime) : _deltaTime(deltaTime) { };
        void operator() (RigidBody* elem) {
            double mass = elem->GetMass();
            if(Math::IsEqual(mass, 0.0)) return;

            double gxm = elem->GetXGravityModifier();
            double gym = elem->GetYGravityModifier();

            Vector2D deltaG(_GRAVITY_VALUE.GetX() * gxm * mass * _deltaTime, _GRAVITY_VALUE.GetY() * gym * mass * _deltaTime);
            elem->ApplyForce(deltaG); //The problem seems to be here.
        };
    private:
        double _deltaTime;
    };

    _accumulator += deltaTime;
    if(_accumulator < (1.0 / 60.0)) return;
    _accumulator -= deltaTime;
    if(!_isActive) return;
    std::for_each(_subscribers->begin(), _subscribers->end(), UpdateObject(deltaTime));
}

RigidBody:

void RigidBody::Update(double deltaTime) {

    double mass = GetMass();
    //If static body, do nothing.
    if(Math::IsEqual(mass, 0.0)) return;

    Vector2D F;
    F += std::accumulate(_curState._forces.begin(), _curState._forces.end(), Vector2D());
    F += std::accumulate(_curState._impulses.begin(), _curState._impulses.end(), Vector2D());
    _curState._impulses.clear();
    SetAcceleration(F / mass);
    SetVelocity(GetVelocity() + GetAcceleration() * deltaTime);
    SetPosition(GetPosition() + GetVelocity() * deltaTime);

}
\$\endgroup\$
3
  • \$\begingroup\$ Sounds like you need to maintain your list of Forces. You keep adding the Gravity force, but you never remove it, resulting in new force being added every frame. Try clearing the _forces list as well. \$\endgroup\$
    – William
    Aug 13, 2012 at 19:20
  • \$\begingroup\$ @William'MindWorX'Mariager But the the "force" becomes an impulse and the calculations will be equivalent to just adding the impulses twice instead of the object gradually increase acceleration. A force continually exerts itself upon a body, whereas an impulse only exerts itself once. \$\endgroup\$
    – Casey
    Aug 13, 2012 at 19:25
  • \$\begingroup\$ Yeah, but it sounds like you continue to add to the _forces list. You should only add to it once and remove once outside the force. Add gravity when it enters the world, remove it when it leaves. \$\endgroup\$
    – William
    Aug 13, 2012 at 19:31

2 Answers 2

3
\$\begingroup\$

In the line you noted, elem->ApplyForce(deltaG); //The problem seems to be here. you are continuously adding a Force, every time the object updates. Lets use a static value to see what I mean

  1. You add a force of 9.8. The list contains 1 Force for a total force of 9.8.
  2. You add a force of 9.8. The list contains 2 Forces for a total force of 19.6.
  3. You add a force of 9.8. The list contains 3 Forces for a total force of 29.4
  4. Etcetera, etcetera, etcetera.

You can see how your total force increases each update, by update 120, only two seconds into the simulation, you have a downward force of 120 * gravity. To fix this, you only add the Force once, like when the object enters the world and is subjected to gravity, and remove it once it leaves the world.

\$\endgroup\$
6
  • \$\begingroup\$ Okay, now I get it. Now the hard part: Figuring out which force applied was due to the gravity handler so it can be removed...The naive solution I can think of right now is apply a force negative to gravity but that won't remove it, just negate it. \$\endgroup\$
    – Casey
    Aug 13, 2012 at 20:22
  • 1
    \$\begingroup\$ @Casey, You could always have it be a hashed list of forces, and then have add/remove methods. This way, you could have a "World" object, which has a "Gravity" member of the type Force. When you add an object to the world, the Gravity is added to the list of forces, when you remove an object from the world, the Gravity is removed from the object. This way you don't have to create a new force for each object, as they'll all share one Force, and if the gravity in the world needs changing, you just change it, and all object know right away. \$\endgroup\$
    – William
    Aug 13, 2012 at 20:43
  • \$\begingroup\$ I fixed the issue by separating gravity from the rest of the forces. Now it can be independently set and removed (set to zero) \$\endgroup\$
    – Casey
    Aug 17, 2012 at 7:26
  • \$\begingroup\$ @Casey, That sounds like a bad decision. This way, you wont be able to add other forces. What if you want a constant wind force? It's better to design flexibly(within reason) if you can. You never know what you might need in the future. \$\endgroup\$
    – William
    Aug 17, 2012 at 13:14
  • \$\begingroup\$ Sure I can: the total forces is now gravity + all other forces + all impulses: Vector2D F; _total_forces = [accumulation of _forces iterators as before]; _total_impulses = [accumulation of _impulses iterators as before]; F = _gravity + _total_forces + _total_impulses; \$\endgroup\$
    – Casey
    Aug 17, 2012 at 17:02
1
\$\begingroup\$

A simpler solution is to just add all force vectors together every frame. As all acting forces will have one vector as the force resultant it makes sense to add them all together every frame (don't forget to reset this vector once you have handled it in your integration step).

If your force generators all will stay the same you only have to do this once but chances are you will have a lot of force generators changing every frame such as springs changing lengths, player controller rocket thrusters turning on and off etc.

So have one vector called _forceResultant and adjust your applyForce method as follows:

    void State::ApplyForce(const Vector2D& force) {
    _forceResultant += force; 
    }
\$\endgroup\$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .