Sunday, March 16, 2014

Physics Force Registry

This steps through how to set up the basic elements of a force registry system. This kind of system creates and manages forces and makes it easy to implement different kinds of physics behaviors such as acceleration due to gravity, planetary orbits, or spring motion simply by creating a variety of simple force-generating behaviors and applying them to basic units. To create this system I used OpenGL (freeglut) and my own Vector3 class with basic functionality. This system does not handle collision, but would not be difficult to integrate with a collision system.

The most basic element of this system is the ForceGenerator. This is a virtual base class that should be extended for each specific type of force needed. It is registered with a unit and is used to calculate the correct force and apply that force to the unit. The base class is very simple, but different members may be needed for more complex forces.

ForceGenerator.h

class ForceGenerator
{
    float Duration;
    virtual void updateForce(Unit* unit)=0;
}


updateForce(Unit* unit) is where the force is calculated and applied to unit and Duration indicates an amount of time this force should be applied. If you want a force to be applied forever setting duration to FLT_MAX and not decrementing the time if it is set to this would allow the ForceGenerator to never be removed from the registry.

One use for a ForceGenerator would be to create simple spring motion. With a simple ForceGenerator you can get fairly complex and convincing behavior with very little code:

Spring.h

class Spring:public ForceGenerator
{
    float RestLength;
    float SpringConstant;
    Unit* BaseUnit;

    Spring(Unit* base,float springConstant=1,float restLength=0);
    void updateForce(Unit* unit);
}


Spring.cpp

Spring::Spring(Unit* base,float springConstant,float restLength)
{
    BaseUnit=base;
    SpringConstant=springConstant;
    RestLength=restLenght;

    Duration=FLT_MAX; //this force will stay forever
}

void Spring::updateForce(Unit* unit)
{
    Vector3 dir = BaseUnit->Position - unit->Position;
     //get the direction of the force to be applied

    float dist = dir.Magnitude();
     //get the distance between unit and BaseUnit

    float offset = RestLength - dist;
     //get the offset from the rest length for determining the magnitude of the force

    dir.Normalize();
    Vector3 force = dir * (-SpringConstant * offset);
     //calculate the spring force

    unit->addForce(force);
}


By registering this Spring with a Unit you will get anchored spring motion with the anchor being the BaseUnit. If you would like the BaseUnit to be affected by the spring as well, you would register another Spring force with the BaseUnit and the BaseUnit of the Spring set to the Unit that the last Spring was registered with.

You can view my code for basic gravity here ( Gravity.h  Gravity.cpp )
or for planetary gravity here ( PlanetGravity.h  PlanetGravity.cpp ).

This ForceGenerator is queued for processing with a Unit pointer with a ForceRegistration which is a simple struct:

ForceRegistration.h

struct ForceRegistration
{
    Unit* unit;
    ForceGenerator* force;
}


The unit in the ForceRegistration is the one that will be passed to force->updateForce(). The Unit class at its most basic needs to contain the following:

Unit.h

class Unit
{
    Vector3 Position;
    Vector3 Forces; //Force accumulator
    Vector3 Velocity;

    float Mass;
    float InverseMass;
    float Radius;

    void update();
    void draw();
    void addForce(Vector3 force);
}


This Unit class is specifically for spherical units for simplicity, but Radius could be replaced with some kind of dimensions variable for other shapes. Also, I am storing both the mass and the inverse mass so that they do not have to be calculated each frame.

Now that we have each of the basic components involved in the force registry system we need to create the actual ForceRegistry. The ForceRegistry is entirely just a ForceRegistration holder and updater.

ForceRegistry.h

class ForceRegistry
{
    vector<forceregistration> ActiveForces;

    void add(ForceRegistration* force);
    void clear();
    void remove(int index);
    void updateForces();
}


ForceRegistry.cpp

void ForceRegistry::add(ForceRegistration* force)
{
    ActiveForces.push_back(force);
}

void ForceRegistry::clear()
{
    for(unsigned int i=0;i<ActiveForces.size();i++)
    {
        delete ActiveForces[i];
    }
    ActiveForces.clear();
}

void ForceRegistry::remove(int index)
{
    delete ActiveForces[index];
    ActiveForces.erase(ActiveForces.begin()+index);
}

void ForceRegistry::updateForces()
{
    for(unsigned int i=0;i<ActiveForces.size();i++)
    {
         //remove ForceRegistrations that have run their course
        if(ActiveForces[i]->Unit->Duration <= 0)
        {
            remove(i);
            i--;
            continue;
        }

         //update forces
        ActiveForces[i]->Force->updateForce(ActiveForces[i]->Unit);

         //decrement force duration
        if(ActiveForces[i]->Unit->Duration &lt FLT_MAX)
            ActiveForces[i]->Unit->Duration -= DeltaTime;
    }
}


The last step is to make sure that the units process and apply their accumulated forces correctly when updated. To do this you apply the velocity to the position, calculate acceleration from the accumulated forces, apply the acceleration to the velocity and then clear out the accumulated forces.

Unit.cpp

void Unit::update();
{
     //apply velocity
    Position += Velocity * DeltaTime;

     //calculate acceleration
    Vector3 acc = Forces * InverseMass;

     //apply acceleration
    Velocity += acc * DeltaTime;

     //clear force accumulator
    Forces = Vector3(0,0,0);
}



Now all that's left to do is register a ForceGenerator with a Unit.


//create the anchor and unit
Unit* anchor=new Unit();; anchor->Position=Vector3(0,0,0); anchor->Radius=1;
Unit* unit=new Unit();; unit->Position=Vector3(3,6,9); anchor->Radius=1;

//create the spring force and a force registration
Spring* spring=new Spring(anchor,1.2f,5.0f);
ForceRegistration* reg=new ForceRegistration(unit,spring);

//add the ForceRegistration to a previously made ForceRegistry
forceRegistry.add(reg);


This will result in an anchored spring object.
You can view my whole system code here.