Particle System – Shapes

This update adds the ability to create shapes using a particle collector. The physics for each particle is calculated independently for individual destinations; they do not need to have the same destination. This allows me to specify any number of destinations, which can be moved independently. Because the physics system takes care of tweening between positions, the collector destinations can jump from place to place, and the physics will result in a fluid animation.

There was a big feature implemented in this update as well. The last videos I captured of the particle collector had a particularly chaotic nature, because the framerate was tied to the simulation timestep (update and render once per frame), and because the framerate drops during video capture, the numerical integrator in my update phase became far less accurate. There is an excellent article by Glenn Fiedler at his site, gafferongames. The code below demonstrates how Glenn’s example fixed my timestep issues, but all credit should really go to him; it’s simply included here to provide another example.

// global declarations
ULONG               timeNow;        // the timestamp of the current frame
ULONG               timePrev;       // the timestamp of the previous frame
ULONG               timeFrame;      // the time delta for the current frame
ULONG               timeSum;        // accumulation of time
const ULONG         timeDelta = 8;  // the constant update time delta
const ULONG         timeMax = 16;   // the constant maximum time delta

// function prototypes
void InitScene();               // initialize the scene
void Update(float dt);          // updates the simulation
void RenderFrame();             // renders a single frame
void DestroyScene();            // free memory allocated by the scene


// the entry point for any Windows program
int WINAPI WinMain(HINSTANCE hInstance,
                   HINSTANCE hPrevInstance,
                   LPSTR lpCmdLine,
                   int nCmdShow)
{
    timePrev = timeGetTime();
    timeNow  = 0;
    timeSum = 0;

    InitScene();

    MSG msg;

    while(TRUE)
    {

        while(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
        {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }

        if(msg.message == WM_QUIT)
        {
            break;
        }
   
        timeNow = timeGetTime();
        timeFrame = timeNow - timePrev;
        timePrev = timeNow;

        // practical limitation, don't let updates overcome framerate
        if (timeFrame > timeMax)
        {
            timeFrame = timeMax;
        }

        timeSum += timeFrame;

        // update at a rate of timeDiff, regardless of framerate
        while (timeSum >= timeDelta)
        {
            Update(timeDelta * .001f);
            timeSum -= timeDelta;
        }

        // render once per frame
        RenderFrame();
    }

    DestroyScene();
    return msg.wParam;
}

There were also a number of crucial bugfixes and optimizations I made. My last post has been updated to reflect the bug fixes. A particular performance enhancement was the calculation of the orbital decay force. I wasn’t happy with the previous code, which looked something like this:

D3DXVECTOR3 Collector::GetOrbitalDecayForce(const D3DXVECTOR3& magVector,
                                            const float& magLength,
                                            const D3DXVECTOR3& curVelocity,
                                            const D3DXVECTOR3& curPos,
                                            const D3DXVECTOR3& collectorPos,
                                            const float& dt)
{
    D3DXVECTOR3 force = GetIdealOrbitalForce(magVector, magLength);
    D3DXVECTOR3 newVelocity = curVelocity + force * dt;

    if (dt > 0)
    {
        D3DXVECTOR3 newPos = curPos + newVelocity * dt;
        D3DXVECTOR3 magVector2 = collectorPos - newPos;
        float magLength2 = D3DXVec3Length(&magVector2);
        float damping = 4.0f;

        D3DXVECTOR3 decayVelocity = ((magVector2 / magLength2) - (magVector / magLength)) / dt * damping;
        if(magLength2 > 1)
        {
            decayVelocity /= (magLength2 * magLength2);
        }
        force += decayVelocity/dt;
    }
    return force;
}

The problem with the above code is that it computes the position from the force and velocity of the particle, calculates a vector from the previous position and the new position, generates a new velocity vector, and divides by the timestep to get back to a force. Well, I coded it this way just to validate my initial thought process. Now that I know it can achieve the sort of effect I’m looking for, I whittled it down to something like this:

inline D3DXVECTOR3 Collector::GetOrbitalDecayForce(const D3DXVECTOR3& magVector, const float& magLength,
                                                   const D3DXVECTOR3& velVector, const float& velLength)
{
    D3DXVECTOR3 force = GetIdealOrbitalForce(magVector, magLength);
    D3DXVECTOR3 magVector2 = (magVector) - (velVector);
    float damping = 100.0f;
    force += magVector2 * velLength * damping / (magLength * magLength);

    return force;
}

Note that the vectors are now passed in normalized, and their length is included. I made this change to each of my force computations, since each force function would eventually calculate the vector length and normalize. The magVector always points at the collector, and the current velocity vector indicates the direction the particle is moving. We wish to apply some force acting against the particle as it tries to move laterally throughout its orbit of the collector. The simple vector subtraction yields a vector that will act against the lateral movement of the particle, as well as contribute to the force aimed directly at the collector. While this may not be entirely physically accurate, it does yield the same effect, with far less work. It’s good to remind yourself, sometimes you don’t need perfect physical accuracy, an approximation may be good enough.

Leave a Comment

Tags: , ,

February 8, 2011 Graphics, Physics