Particle System

mail@pastecode.io avatar
unknown
c_cpp
3 years ago
5.2 kB
77
Indexable
Never
#include "Emitter.h"

#include <iostream>
#include "ResourceManager.h"
using namespace glm;

/* From the Header */
// -----------------

// Represents a single particle and its state
struct Particle {
    vec2 Position, Velocity;
    vec4 Color;
    float     Life;
    float Rotation;
    short int type;

    Particle() : Position(0.0f), Velocity(0.0f), Color(1.0f), Rotation(0.0f), type(0), Life(0.0f) { }
};

// -----------------

std::vector<Particle> Emitter::particles;
unsigned int Emitter::amount;
Shader Emitter::shader;
unsigned int Emitter::SpriteSheetID;
unsigned int Emitter::VAO;


void Emitter::Init( unsigned int a ) {
    shader = ResourceManager::GetShader("particle");
    SpriteSheetID = SpriteSheet::LoadSpriteSheet("particles", "particles.png", 16, 16);
    amount = a;

    init();
}

void Emitter::Update(float dt)
{
    // update all particles
    for (unsigned int i = 0; i < amount; ++i)
    {
        Particle& p = particles[i];
        p.Life -= dt; // reduce life
        if (p.Life > 0.0f)
        {	// particle is alive, thus update
            p.Position -= p.Velocity * dt;
            p.Color.a -= dt * 2.5f;
        }
    }
}

void Emitter::AddParticles(short int type, vec2 position, vec2 velocity, float rotation, unsigned int newParticles, vec2 offset) {
    // add new particles 
    for (unsigned int i = 0; i < newParticles; ++i)
    {
        int unusedParticle = firstUnusedParticle();
        respawnParticle(particles[unusedParticle], type, position, velocity, rotation, offset);
    }
}

// render all particles
void Emitter::Draw()
{
    // use additive blending to give it a 'glow' effect
    glBlendFunc(GL_SRC_ALPHA, GL_ONE);
    shader.Use();

    glActiveTexture(GL_TEXTURE0);
    glBindTexture(GL_TEXTURE_2D_ARRAY, SpriteSheetID);
    
    for (Particle particle : particles)
    {
        if (particle.Life > 0.0f)
        {
            // Prepare transformations
            // Create a model matrix
            mat4 model = mat4(1.0f);
            // Move the sprite to the desired position
            model = translate(model, vec3(abs(particle.Position.x) - 15, abs(particle.Position.y) - 15, 0.0f));

            model = translate(model, vec3(0.5 * 30, 0.5f * 30, 0.0f)); // Move the sprite so the origin is in the center
            model = rotate(model, radians(particle.Rotation), vec3(0.0f, 0.0f, 1.0f)); // Rotate around the center
            model = translate(model, vec3(-0.5 * 30, -0.5f * 30, 0.0f)); // Reverse previous translation

            model = scale(model, vec3(30, 30, 1.0f)); // Finally, scale if needed

            shader.SetMatrix4("model", model); // Set the model matrix
            shader.SetVector4f("color", particle.Color);
            shader.SetFloat("type", particle.type); // Set the model matrix
            glBindVertexArray(VAO);
            glDrawArrays(GL_TRIANGLES, 0, 6);
            glBindVertexArray(0);
        }
    }
    // don't forget to reset to default blending mode
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
}

void Emitter::init()
{
    // set up mesh and attribute properties
    unsigned int VBO;
    float particle_quad[] = {
        0.0f, 1.0f, 0.0f, 1.0f,
        1.0f, 0.0f, 1.0f, 0.0f,
        0.0f, 0.0f, 0.0f, 0.0f,

        0.0f, 1.0f, 0.0f, 1.0f,
        1.0f, 1.0f, 1.0f, 1.0f,
        1.0f, 0.0f, 1.0f, 0.0f
    };
    glGenVertexArrays(1, &VAO);
    glGenBuffers(1, &VBO);
    glBindVertexArray(VAO);
    // fill mesh buffer
    glBindBuffer(GL_ARRAY_BUFFER, VBO);
    glBufferData(GL_ARRAY_BUFFER, sizeof(particle_quad), particle_quad, GL_STATIC_DRAW);
    // set mesh attributes
    glEnableVertexAttribArray(0);
    glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, 4 * sizeof(float), (void*)0);
    glBindVertexArray(0);

    // create amount default particle instances
    for (unsigned int i = 0; i < amount; ++i)
        particles.push_back(Particle());
}

// stores the index of the last particle used (for quick access to next dead particle)
unsigned int lastUsedParticle = 0;
unsigned int Emitter::firstUnusedParticle()
{
    // first search from last used particle, this will usually return almost instantly
    for (unsigned int i = lastUsedParticle; i < amount; ++i) {
        if (particles[i].Life <= 0.0f) {
            lastUsedParticle = i;
            return i;
        }
    }
    // otherwise, do a linear search
    for (unsigned int i = 0; i < lastUsedParticle; ++i) {
        if (particles[i].Life <= 0.0f) {
            lastUsedParticle = i;
            return i;
        }
    }
    // all particles are taken, override the first one (note that if it repeatedly hits this case, more particles should be reserved)
    lastUsedParticle = 0;
    return 0;
}

void Emitter::respawnParticle(Particle& particle, short int type, vec2 position, vec2 velocity, float rotation, vec2 offset)
{
    particle.Position = position + offset;
    particle.Color = vec4(1, 1, 1, 1.0f);
    particle.Life = 1.0f;
    particle.type = type;
    particle.Rotation = rotation;
    particle.Velocity = velocity * 0.1f;
}