Click here to Skip to main content
Click here to Skip to main content
Technical Blog

Flexible particle system - The Container 2

, 16 May 2014 CPOL
Rate this:
Please Sign up or sign in to vote.
Description of the implementation of my particle container
code and implementation
Last time I've written about problems that we can face when designing a particle container. This post will basically show my current (basic - without any optimizations) implementation. I will also write about possible improvements.

The Series

Introduction

Basic design:
  • ParticleData class which represents the container
    • Allocates and manages memory for a given max number of particles
    • Can kill and activate a particle
    • Active particles are in the front of the buffer, stored continuously
    • Each parameter is stored in a separate array. Most of them are 4d vectors
    • No use of std::vectors. The reason: they are very slow in debug mode. Another thing is that I know the max size of elements so managing memory is quite simple. And also I have more control over it.
  • So far GLM library is used, but it might change in the future
  • ParticleSystem holds one ParticleData
  • Generators and Updaters (stored also in ParticleSystem) operate on ParticleData

The declaration

The gist is located here: gist.github.com/fenbf/BasicParticles
ParticleData class
class ParticleData
{
public:
    std::unique_ptr<glm::vec4[]> m_pos;
    std::unique_ptr<glm::vec4[]> m_col;
    std::unique_ptr<glm::vec4[]> m_startCol;
    std::unique_ptr<glm::vec4[]> m_endCol;
    std::unique_ptr<glm::vec4[]> m_vel;
    std::unique_ptr<glm::vec4[]> m_acc;
    std::unique_ptr<glm::vec4[]> m_time;
    std::unique_ptr<bool[]>  m_alive;

    size_t m_count{ 0 };
    size_t m_countAlive{ 0 };
public:
    explicit ParticleData(size_t maxCount) { generate(maxCount); }
    ~ParticleData() { }

    ParticleData(const ParticleData &) = delete;
    ParticleData &operator=(const ParticleData &) = delete;

    void generate(size_t maxSize);
    void kill(size_t id);
    void wake(size_t id);
    void swapData(size_t a, size_t b);
};
Notes:
  • So far std::unique_ptr are used to hold raw arrays. But this will change, because we will need in the future to allocate aligned memory.

Implementation

Generation:
void ParticleData::generate(size_t maxSize)
{
    m_count = maxSize;
    m_countAlive = 0;

    m_pos.reset(new glm::vec4[maxSize]);
    m_col.reset(new glm::vec4[maxSize]);
    m_startCol.reset(new glm::vec4[maxSize]);
    m_endCol.reset(new glm::vec4[maxSize]);
    m_vel.reset(new glm::vec4[maxSize]);
    m_acc.reset(new glm::vec4[maxSize]);
    m_time.reset(new glm::vec4[maxSize]);
    m_alive.reset(new bool[maxSize]);
}
Kill:
void ParticleData::kill(size_t id)
{
    if (m_countAlive > 0)
    {
        m_alive[id] = false;
        swapData(id, m_countAlive - 1);
        m_countAlive--;
    }
}
Wake:
void ParticleData::wake(size_t id)
{
    if (m_countAlive < m_count)
    {
        m_alive[id] = true;
        swapData(id, m_countAlive);
        m_countAlive++;
    }
}   
Swap:
void ParticleData::swapData(size_t a, size_t b)
{
    std::swap(m_pos[a], m_pos[b]);
    std::swap(m_col[a], m_col[b]);
    std::swap(m_startCol[a], m_startCol[b]);
    std::swap(m_endCol[a], m_endCol[b]);
    std::swap(m_vel[a], m_vel[b]);
    std::swap(m_acc[a], m_acc[b]);
    std::swap(m_time[a], m_time[b]);
    std::swap(m_alive[a], m_alive[b]);
}
Hints for optimizations:
  • maybe full swap is not needed?
  • maybe those if's in wake and kill could be removed?

Improvements

Configurable attributes

SoA style object gives use a nice possibility to create various ParticleData configurations. I do not have implemented it in current class, but I've used it before in some other system.
The simplest idea is to hold a mask of configured params:
ParticleData::mask = Params::Pos | Params::Vel | Params::Acc | Params::Color...
In the constructor memory for only selected param will be allocated.
generate() {
    // ..
    if (mask & Params::Vel)
        allocate ParticleData::vel array
    // ...
The change is also needed in updaters and generators: briefly we will be able to update only active parameters. A lot of if statements would be needed there. But it is doable.
update() {
    // ..
    if (mask & Params::Vel)
        update ParticleData::vel array
    // ...
Please not that the problem arise when one param depends on the other.
Limitations: there is a defined set of parameters, we only can choose a subset.
The second idea (not tested) would be to allow full dynamic configuration. Instead of having named set of available parameters we could store a map of <name, array>. Both name and type of param (vector, scalar, int) would be configurable. This would mean a lot of work, but for some kind of an particle editor this could be a real benefit.

What's Next

In the next article I will touch particle generation and update modules.
Again: the gist is located here: gist.github.com/fenbf/BasicParticles

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Bartlomiej Filipek
Software Developer
Poland Poland
Software developer interested in creating great code and passionate about teaching.
 
Technologies I use(d): C++, C#, JavaScript, OpenGL, GLSL, DirectX, OpenCL, CUDA, Windows Api, MFC, Visual Studio and even HTML and CSS.
 
See my programming blog: www.bfilipek.com
Follow on   Twitter   Google+

Comments and Discussions

 
-- There are no messages in this forum --
| Advertise | Privacy | Terms of Use | Mobile
Web01 | 2.8.141220.1 | Last Updated 16 May 2014
Article Copyright 2014 by Bartlomiej Filipek
Everything else Copyright © CodeProject, 1999-2014
Layout: fixed | fluid