These tutorials focus mainly on OpenGL, Win32 programming and the ODE physics engine. OpenGL has moved on to great heights and I don't cover the newest features but cover all of the basic concepts you will need with working example programs.

 

Working with the Win32 API is a great way to get to the heart of Windows and is just as relevant today as ever before. Whereas ODE has been marginalized as hardware accelerated physics becomes more common.

 

Games and graphics utilities can be made quickly and easily using game engines like Unity so this and Linux development in general will be the focus of my next tutorials.    

  

 


typedef struct

{

    int Id;

    int numAlive;

    bool Visibility;

    int numParticles;

    ParticleType Type;

    int BlendMode;

    unsigned int TexID;

    GLfloat Color[4];

    VECTOR Pos;

    VECTOR Normal;

    VECTOR InitialVelocity;

} SystemInfo;



typedef struct

{

    bool Alive;

    int Leaf;

    VECTOR Pos;

    VECTOR OldPos;

    VECTOR OrigPos;

    VECTOR Velocity;

    VECTOR Vertex[4];

    GLfloat Color[4];

    GLfloat Energy;

    GLfloat SizeX;

    GLfloat SizeY;

} ParticleInfo;

Next we have the particle class which includes the ParticleInfo struct. The Compare and other methods are for use by the linked list that holds the particles.


class PARTICLE

{

    public:

        PARTICLE(){};

        ~PARTICLE(){};



        int Compare(const PARTICLE& Particle);

        int GetMyPosition() const {return linkPosition;}

        void SetMyPosition(int newPosition) {linkPosition = newPosition;}

        int linkPosition;



        ParticleInfo PartInfo;

};



int PARTICLE::Compare(const PARTICLE& Particle)

{

    if (linkPosition < Particle.linkPosition)

        return smaller;

    if (linkPosition > Particle.linkPosition)

        return bigger;

    else

        return same;

}

The following ParticleSystem class includes the SystemInfo struct and a linked list of particles called ParticleList. There are also a few methods to initialize, add and remove particles from the system. The virtual methods will provide default behavior to any particle systems that derive from this base class, but note that I haven't written these methods yet.


class ParticleSystem

{

    public:

          ParticleSystem(){};

          ~ParticleSystem(){};



        int Compare(const ParticleSystem& ParticleSys);

        int GetMyPosition() const {return linkPosition;}

        void SetMyPosition(int newPosition) {linkPosition = newPosition;}

        int linkPosition;



        LinkedList ParticleList;

        SystemInfo SysInfo;



        int GetNumAlive();

        void SetupParticles();

        PARTICLE* Add();

        void Remove();



        virtual void SetDefaults(PARTICLE* Particle);

        virtual void SetShape(PARTICLE* Particle);

        virtual void Update();

        virtual void Render();

};



int ParticleSystem::Compare(const ParticleSystem& ParticleSys)

{

  if (linkPosition < ParticleSys.linkPosition)

    return smaller;

  if (linkPosition > ParticleSys.linkPosition)

    return bigger;

  else

    return same;

}



void ParticleSystem::SetDefaults(PARTICLE* Particle)

{

   // Add a default method

}



void ParticleSystem::SetShape(PARTICLE* Particle)

{

   // Add a default method

}



void ParticleSystem::Update()

{

   // Add a default method

}



void ParticleSystem::Render(int nodeid)

{

   // Add a default method

}



int ParticleSystem::GetNumAlive()

{

    int numParticles = 0;

    PARTICLE* tempParticle;

    for (int loop = 1; loop <= SysInfo.numParticles; loop++)

    {

        tempParticle = ParticleList.Get(loop);

        if (tempParticle->PartInfo.Alive)

            numParticles++;

    }

    return numParticles;

}



// Add initial particles to the empty list

void ParticleSystem::SetupParticles()

{

    for (int loop = 1; loop <= SysInfo.numParticles; loop++)

    {

        PARTICLE* newParticle = new PARTICLE;

        SetDefaults(newParticle);

        SetShape(newParticle);

        newParticle->linkPosition = loop;

        ParticleList.Insert(newParticle);

    }

}



PARTICLE* ParticleSystem::Add()

{

    PARTICLE* newParticle = new PARTICLE;

    SetDefaults(newParticle);

    SetShape(newParticle);

    newParticle->linkPosition = ++SysInfo.numParticles;

    ParticleList.Insert(newParticle);

    return newParticle;

}



void ParticleSystem::Remove()

{

    if (SysInfo.numParticles > 0)

    {

        ParticleList.Delete(1);

        --SysInfo.numParticles;

    }

}

This then brings us to the particle manager class itself. It includes a linked list of particle systems called SystemList and a counter that keeps track of the number of systems in the list. Again, most of the methods here are for future use and are untested. The main methods here are the Update and Render methods.


class ParticleManager

{

    public:

          ParticleManager(){numSystems = 0;}

          ~ParticleManager(){};



        int numSystems;

        LinkedList SystemList;



        void SetVisibility(int Id, bool State);

        void ToggleVisibility(int Id);

        void SetType(int Id, ParticleType Type);

        void SetBlendMode(int Id, int BlendMode);

        void SetTextureId(int Id, unsigned int TexID);

        void SetId(int Id, int newId);

        void Remove(int Id);

        void RemoveType(ParticleType Type);

        void Update();

        void Render();

        ParticleSystem* Add(ParticleSystem* PartSys);

};



void ParticleManager::Update()

{

    int loop, innerloop;

    ParticleSystem* PartSys;

    PARTICLE* tempParticle;

    for (loop = 1; loop <= numSystems; loop++)

    {

        PartSys = SystemList.Get(loop);



        if (!PartSys->GetNumAlive())

        {

            SystemList.Delete(PartSys->linkPosition);

            numSystems--;

        }

        else

            PartSys->Update();

    }

}



void ParticleManager::Render()

{

    for (int loop = 1; loop <= numSystems; loop++)

    {

        ParticleSystem* PartSys = SystemList.Get(loop);

        PartSys->Render();

    }

}



void ParticleManager::SetId(int Id, int newId)

{

    for (int loop = 1; loop <= numSystems; loop++)

    {

        ParticleSystem* PartSys = SystemList.Get(loop);

        if (Id == PartSys->SysInfo.Id)

            PartSys->SysInfo.Id = newId;

    }

}



void ParticleManager::SetTextureId(int Id, unsigned int TexID)

{

    for (int loop = 1; loop <= numSystems; loop++)

    {

        ParticleSystem* PartSys = SystemList.Get(loop);

        if (Id == PartSys->SysInfo.Id)

            PartSys->SysInfo.TexID = TexID;

    }

}



void ParticleManager::SetBlendMode(int Id, int BlendMode)

{

    for (int loop = 1; loop <= numSystems; loop++)

    {

        ParticleSystem* PartSys = SystemList.Get(loop);

        if (Id == PartSys->SysInfo.Id)

            PartSys->SysInfo.BlendMode = BlendMode;

    }

}



void ParticleManager::SetType(int Id, ParticleType Type)

{

    for (int loop = 1; loop <= numSystems; loop++)

    {

        ParticleSystem* PartSys = SystemList.Get(loop);

        if (Id == PartSys->SysInfo.Id)

            PartSys->SysInfo.Type = Type;

    }

}



void ParticleManager::SetVisibility(int Id, bool State)

{

    for (int loop = 1; loop <= numSystems; loop++)

    {

        ParticleSystem* PartSys = SystemList.Get(loop);

        if (Id == PartSys->SysInfo.Id)

            PartSys->SysInfo.Visibility = State;

    }

}



void ParticleManager::ToggleVisibility(int Id)

{

    for (int loop = 1; loop <= numSystems; loop++)

    {

        ParticleSystem* PartSys = SystemList.Get(loop);

        if (Id == PartSys->SysInfo.Id)

            PartSys->SysInfo.Visibility = !PartSys->SysInfo.Visibility;

    }

}



ParticleSystem* ParticleManager::Add(ParticleSystem* PartSys)

{

    PartSys->SetupParticles();

    PartSys->linkPosition = ++numSystems;

    SystemList.Insert(PartSys);

    return PartSys;

}



void ParticleManager::Remove(int Id)

{

    for (int loop = 1; loop <= numSystems; loop++)

    {

        ParticleSystem* PartSys = SystemList.Get(loop);

        if (Id == PartSys->SysInfo.Id)

        {

            SystemList.Delete(PartSys->linkPosition);

            --numSystems;

        }

    }

}



void ParticleManager::RemoveType(ParticleType Type)

{

    for (int loop = 1; loop <= numSystems; loop++)

    {

        ParticleSystem* PartSys = SystemList.Get(loop);

        if (Type == PartSys->SysInfo.Type)

        {

            SystemList.Delete(PartSys->linkPosition);

            --numSystems;

        }

    }

}

Now that the main classes are defined we can create new types of particle systems that derive from the base class. The example class here is called Spark.


enum ParticleType{spark};   // Particle types

// spark type

class Spark : public ParticleSystem

{

    public:

        Spark(){};

        ~Spark(){};



        // Override base methods

        void Update();

        void Render();

        void SetShape(PARTICLE* Particle);

        void SetDefaults(PARTICLE* Particle);

};



void Spark::Update()

{

    bool CollisionFlag;

    VECTOR VelocityVector, normal, pos, oldpos, temppos;

    PARTICLE* tempParticle;

    // Loop through all the particles of this system

    for (int loop = 1; loop <= SysInfo.numParticles; loop++)

    {

        // Get the particle from the list

        tempParticle = ParticleList.Get(loop);

        // Set the old position

        tempParticle->PartInfo.OldPos = tempParticle->PartInfo.Pos;

        // Apply gravity

        tempParticle->PartInfo.Velocity.x += 0.0;

        tempParticle->PartInfo.Velocity.y += -0.002;

        tempParticle->PartInfo.Velocity.z += 0.0;



        if (tempParticle->PartInfo.Energy > 0.0)

        {

            // Set the alpha channel to the energy value

            tempParticle->PartInfo.Color[3] = tempParticle->PartInfo.Energy;

            // Decrease the energy

            tempParticle->PartInfo.Energy -= 0.002;

            // Update the position

            oldpos = tempParticle->PartInfo.Pos;

            pos = tempParticle->PartInfo.Pos + tempParticle->PartInfo.Velocity;

            temppos = pos;

            // Check for a collision and get the normal of the collision polygon

            CollisionFlag = CheckForParticleCollision(tempParticle, oldpos,

                              &temppos, &normal);

            // If there was a collision then reflect the velocity vector

            if (CollisionFlag)

            {

                VECTOR vectn = normal * (normal.dot(tempParticle->PartInfo.Velocity));

                VECTOR vectt = tempParticle->PartInfo.Velocity - vectn;

                VECTOR vel = (vectt - vectn);

                tempParticle->PartInfo.Pos.x += vel.x;

                tempParticle->PartInfo.Pos.y += vel.y;

                tempParticle->PartInfo.Pos.z += vel.z;

                // Decrease the velocity

                tempParticle->PartInfo.Velocity.x = vel.x / 3.0;

                tempParticle->PartInfo.Velocity.y = vel.y / 3.0;

                tempParticle->PartInfo.Velocity.z = vel.z / 3.0;

                // Reduce the particles energy due to the collision

                tempParticle->PartInfo.Energy -= 0.2;

            }

            else // Update the position as normal

            {

                tempParticle->PartInfo.Pos.x += tempParticle->PartInfo.Velocity.x;

                tempParticle->PartInfo.Pos.y += tempParticle->PartInfo.Velocity.y;

                tempParticle->PartInfo.Pos.z += tempParticle->PartInfo.Velocity.z;

            }

        }

        else // The particles energy has reduced to zero

        {

            tempParticle->PartInfo.Alive = false;

        }

    }

}



void Spark::Render()

{

    MATRIX mat;

    VECTOR up;

    VECTOR right;

    PARTICLE* tempParticle;

    // Get the current modelview matrix

    glGetFloatv(GL_MODELVIEW_MATRIX, mat.Element);

    // Set texture states

    glEnable(GL_BLEND);

    glDepthMask(0);

    glBlendFunc(GL_SRC_ALPHA, GL_ONE);

    glDisable(GL_LIGHTING);

    glBindTexture(GL_TEXTURE_2D, SysInfo.TexID);

    // Loop through all the particles in this system

    for (int loop = 1; loop <= SysInfo.numParticles; loop++)

    {

        // Get the particle from the list

        tempParticle = ParticleList.Get(loop);

        // If the particle is still alive

        if (tempParticle->PartInfo.Alive)

        {

            // Set the shape, should really be performed in Spark::SetShape()

            right.x = mat.Element[0];

            right.y = mat.Element[4];

            right.z = mat.Element[8];

            right.Normalize();

            right.x *= tempParticle->PartInfo.SizeX / 2;

            right.y *= tempParticle->PartInfo.SizeX / 2;

            right.z *= tempParticle->PartInfo.SizeX / 2;



            up.x = mat.Element[1];

            up.y = mat.Element[5];

            up.z = mat.Element[9];

            up.Normalize();

            up.x *= tempParticle->PartInfo.SizeY / 2;

            up.y *= tempParticle->PartInfo.SizeY / 2;

            up.z *= tempParticle->PartInfo.SizeY / 2;

            // Set the color to the default values

            glColor4f(tempParticle->PartInfo.Color[0],

                        tempParticle->PartInfo.Color[1],

                        tempParticle->PartInfo.Color[2],

                        tempParticle->PartInfo.Color[3]);

            // Render the billboarded particle

            glBegin(GL_QUADS);

                glTexCoord2f(0.0f, 0.0f);

                glVertex3f(tempParticle->PartInfo.Pos.x + (-right.x - up.x),

                            tempParticle->PartInfo.Pos.y + (-right.y - up.y),

                            tempParticle->PartInfo.Pos.z + (-right.z - up.z));

                glTexCoord2f(1.0f, 0.0f);

                glVertex3f(tempParticle->PartInfo.Pos.x + (right.x - up.x),

                            tempParticle->PartInfo.Pos.y + (right.y - up.y),

                            tempParticle->PartInfo.Pos.z + (right.z - up.z));

                glTexCoord2f(1.0f, 1.0f);

                glVertex3f(tempParticle->PartInfo.Pos.x + (right.x + up.x),

                            tempParticle->PartInfo.Pos.y + (right.y + up.y),

                            tempParticle->PartInfo.Pos.z + (right.z + up.z));

                glTexCoord2f(0.0f, 1.0f);

                glVertex3f(tempParticle->PartInfo.Pos.x + (up.x - right.x),

                            tempParticle->PartInfo.Pos.y + (up.y - right.y),

                            tempParticle->PartInfo.Pos.z + (up.z - right.z));

            glEnd();

        }

    }

    // Reset texture states

    glEnable(GL_LIGHTING);

    glDepthMask(1);

    glDisable(GL_BLEND);

}



void Spark::SetDefaults(PARTICLE* Particle)

{

    // Set this particle systems properties

    Particle->PartInfo.Alive = true;

    Particle->PartInfo.Pos = SysInfo.Pos;

    Particle->PartInfo.OldPos = SysInfo.Pos;

    Particle->PartInfo.OrigPos = SysInfo.Pos;

    // Reflect the initial velocity

    VECTOR Velocity = SysInfo.InitialVelocity;

    VECTOR vectn = SysInfo.Normal * (SysInfo.Normal.dot(Velocity));

    VECTOR vectt = Velocity - vectn;

    VECTOR vel = (vectt - vectn);

    Velocity.normalize();

    // Create a random spread for each particle

    GLfloat x;

    x = (float)rand()/(float)RAND_MAX;

    Particle->PartInfo.Velocity.x = Velocity.x + (x - 0.5);

    Particle->PartInfo.Velocity.x /= 2.0;

    x = (float)rand()/(float)RAND_MAX;

    Particle->PartInfo.Velocity.y = Velocity.y + (x - 0.5);

    Particle->PartInfo.Velocity.y /= 2.0;

    x = (float)rand()/(float)RAND_MAX;

    Particle->PartInfo.Velocity.z = Velocity.z + (x - 0.5);

    Particle->PartInfo.Velocity.z /= 2.0;

    // Set the color

    Particle->PartInfo.Color[0] = 1.0;

    Particle->PartInfo.Color[1] = 1.0;

    Particle->PartInfo.Color[2] = 1.0;

    Particle->PartInfo.Color[3] = 1.0;

    // Create a random energy value between 0.5 and 1.0

    x = (float)rand()/(float)RAND_MAX;

    Particle->PartInfo.Energy = x + 0.5;

    if (Particle->PartInfo.Energy > 1.0)

        Particle->PartInfo.Energy = 1.0;

    // Set the size of the particle

    Particle->PartInfo.SizeX = 0.8;

    Particle->PartInfo.SizeY = 0.8;

}



void Spark::SetShape(PARTICLE* Particle)

{

    // Unused for now

}

To create a new particle system you make a new Spark object and assign it to a ParticleSystem pointer. Using this pointer, you fill in the SystemInfo struct with the properties you want and then add the object to the particle manager using the Add method. The following function demonstrates one way to do this.


// global instance of particle manager

ParticleManager PManager;



void CreateSparks(VECTOR OldPos, VECTOR Pos, VECTOR Normal)

{



    SystemInfo SI;

    ZeroMemory(&SI, sizeof(SI));

    SI.Visibility = true;

    SI.numParticles = rand()%6 + 2;

    SI.Type = spark;

    SI.TexID = texture[8].TexID;

    SI.Id = 1;

    SI.Pos = Pos;

    SI.Normal = Normal;

    SI.InitialVelocity = Pos - OldPos;



    ParticleSystem* sparky = new Spark;

    sparky->SysInfo = SI;

    PManager.Add(sparky);

}