← index
libmx2/libmx/gl_ps3/ps.cpp
Source: libmx2/libmx/gl_ps3/ps.cpp
#include"mx.hpp"
#include"argz.hpp"

#ifdef __EMSCRIPTEN__
#include <emscripten/emscripten.h>
#include <GLES3/gl3.h>
#endif

#include"gl.hpp"
#include"loadpng.hpp"
#include<random>

#define CHECK_GL_ERROR() \
{ GLenum err = glGetError(); \
if (err != GL_NO_ERROR) \
printf("OpenGL Error: %d at %s:%d\n", err, __FILE__, __LINE__); }

#ifndef M_PI
#define M_PI 3.14159265358979323846
#endif

#ifndef __EMSCRIPTEN__
const char* vertSource = R"(#version 330 core

layout (location = 0) in vec2 inPosition; 
layout (location = 1) in float inSize;    
layout (location = 2) in vec4 inColor;    

out vec4 fragColor; 

void main() {
    gl_Position = vec4(inPosition, 0.0, 1.0); 
    gl_PointSize = inSize;                   
    fragColor = inColor;                     
}
)";

const char* fragSource = R"(#version 330 core
in vec4 fragColor;           
in vec2 texCoord;            
out vec4 FragColor;          
uniform sampler2D spriteTexture; 
void main() {
    float dist = length(gl_PointCoord - vec2(0.5)); 
    if (dist > 0.5) {
        discard; 
    }
    vec4 texColor = texture(spriteTexture, gl_PointCoord); 
    FragColor = texColor * fragColor;
}
)";
#else
const char* vertSource = R"(#version 300 es
precision highp float;
layout (location = 0) in vec2 inPosition; 
layout (location = 1) in float inSize;    
layout (location = 2) in vec4 inColor;    

out vec4 fragColor; 

void main() {
    gl_Position = vec4(inPosition, 0.0, 1.0); 
    gl_PointSize = inSize;                   
    fragColor = inColor;                     
}
)";

const char* fragSource = R"(#version 300 es
precision highp float;
in vec4 fragColor;           
in vec2 texCoord;            
out vec4 FragColor;          
uniform sampler2D spriteTexture; 
void main() {
    float dist = length(gl_PointCoord - vec2(0.5)); 
    if (dist > 0.5) {
        discard; 
    }
    vec4 texColor = texture(spriteTexture, gl_PointCoord); 
    FragColor = texColor * fragColor;
}
)";
#endif

float generateRandomFloat(float min, float max) {
    std::random_device rd; 
    std::default_random_engine eng(rd()); 
    std::uniform_real_distribution<float> dist(min, max);
    return dist(eng);
}



class Game : public gl::GLObject {
public:

    struct Particle {
        float x, y, vx, vy, life, angle;
    };

    static constexpr int NUM_PARTICLES = 1000;
    gl::ShaderProgram program;
    std::vector<Particle> particles;
    GLuint VAO, VBO[3];
    GLuint texture;
    Game() : particles(NUM_PARTICLES) {}

    ~Game() override {
         glDeleteVertexArrays(1, &VAO);
         glDeleteBuffers(3, VBO);
         glDeleteTextures(1, &texture);
    }

    void load(gl::GLWindow *win) override {
        font.loadFont(win->util.getFilePath("data/font.ttf"), 36);
        if(!program.loadProgramFromText(vertSource, fragSource)) {
            throw mx::Exception("Error loading shader");
        }
        for (auto& p : particles) {
            p.x = (rand() % 200 - 100) / 100.0f; 
            p.y = (rand() % 200 - 100) / 100.0f;
            p.vx = 0.0;
            p.vy = generateRandomFloat(-0.01f, -0.2f);
            p.life = 1.0f;
            p.angle = generateRandomFloat(0.0f, 1.0f);
        }

        glGenVertexArrays(1, &VAO);
        glGenBuffers(3, VBO);
        glBindVertexArray(VAO);
        glBindBuffer(GL_ARRAY_BUFFER, VBO[0]);
        glBufferData(GL_ARRAY_BUFFER, NUM_PARTICLES * 2 * sizeof(float), nullptr, GL_DYNAMIC_DRAW);
        glEnableVertexAttribArray(0);
        glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, (void*)0);
        glBindBuffer(GL_ARRAY_BUFFER, VBO[1]);
        glBufferData(GL_ARRAY_BUFFER, NUM_PARTICLES * sizeof(float), nullptr, GL_DYNAMIC_DRAW);
        glEnableVertexAttribArray(1);
        glVertexAttribPointer(1, 1, GL_FLOAT, GL_FALSE, 0, (void*)0);
        glBindBuffer(GL_ARRAY_BUFFER, VBO[2]);
        glBufferData(GL_ARRAY_BUFFER, NUM_PARTICLES * 4 * sizeof(float), nullptr, GL_DYNAMIC_DRAW);
        glEnableVertexAttribArray(2);
        glVertexAttribPointer(2, 4, GL_FLOAT, GL_FALSE, 0, (void*)0);
        texture = gl::loadTexture(win->util.getFilePath("data/snowball.png"));
    }

    void draw(gl::GLWindow *win) override { 
#ifndef __EMSCRIPTEN__ 
        glEnable(GL_PROGRAM_POINT_SIZE);
#endif
        glDisable(GL_DEPTH_TEST);
        Uint32 currentTime = SDL_GetTicks();
        float deltaTime = (currentTime - lastUpdateTime) / 1000.0f; // Convert to seconds
        lastUpdateTime = currentTime;
        update(deltaTime);
        CHECK_GL_ERROR();
        program.useProgram();
        program.setUniform("spriteTexture", 0);
        glActiveTexture(GL_TEXTURE0);
        glBindTexture(GL_TEXTURE_2D, texture);
        glBindVertexArray(VAO);
        glDrawArrays(GL_POINTS, 0, NUM_PARTICLES);
        CHECK_GL_ERROR();
        win->text.setColor({255, 0, 0, 255});
        win->text.printText_Solid(font, 25.0f, 25.0f, "Paritcle Count: " + std::to_string(NUM_PARTICLES)); 
      
    }   
    
    void event(gl::GLWindow *win, SDL_Event &e) override {}
    
    void update(float deltaTime) {
        if(deltaTime > 0.1f)
            deltaTime = 0.1f;
        std::vector<float> positions, sizes, colors;

        float circularSpeed = 0.1f; 
        float descentSpeed = -0.5f; 
        float radius = 0.1f;        

        for (auto& p : particles) {
            p.angle += circularSpeed * deltaTime;
            p.x += radius * cos(p.angle);
            p.y += descentSpeed * deltaTime;

            p.life -= 0.05f;
            if(p.life <= 0) {
                p.life = 1.0f;
            }
            if(p.x < -1.0f || p.x > -1.0f) {
                p.x  = generateRandomFloat(-1.0f, 1.0f);
                p.angle = generateRandomFloat(0.0f, 1.0f);
            }
            if (p.y < -1.0f) {
                p.y = 1.0f; 
                p.x = generateRandomFloat(-1.0f, 1.0f);  
            }
            positions.push_back(p.x);
            positions.push_back(p.y);

            float size = 50.0f * p.life;
            sizes.push_back(size);

            float particleAlpha = p.life;
            colors.push_back(generateRandomFloat(0.0f, 1.0f));
            colors.push_back(generateRandomFloat(0.0f, 1.0f));
            colors.push_back(generateRandomFloat(0.0f, 1.0f));
            colors.push_back(particleAlpha);
        }


        glBindBuffer(GL_ARRAY_BUFFER, VBO[0]);
        glBufferSubData(GL_ARRAY_BUFFER, 0, positions.size() * sizeof(float), positions.data());

        glBindBuffer(GL_ARRAY_BUFFER, VBO[1]);
        glBufferSubData(GL_ARRAY_BUFFER, 0, sizes.size() * sizeof(float), sizes.data());

        glBindBuffer(GL_ARRAY_BUFFER, VBO[2]);
        glBufferSubData(GL_ARRAY_BUFFER, 0, colors.size() * sizeof(float), colors.data());
        CHECK_GL_ERROR();
    }

private:
    mx::Font font;
    Uint32 lastUpdateTime = SDL_GetTicks();
};

class MainWindow : public gl::GLWindow {
public:
    MainWindow(std::string path, int tw, int th) : gl::GLWindow("-[Particle Effect]-", tw, th) {
        setPath(path);
        setObject(new Game());
        object->load(this);
    }
    
    ~MainWindow() override {}
    virtual void event(SDL_Event &e) override {}
    
    virtual void draw() override {
        glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
        glClear(GL_COLOR_BUFFER_BIT);
        glViewport(0, 0, w, h);
        object->draw(this);
        swap();
        delay();
    }
};

MainWindow *main_w = nullptr;

void eventProc() {
    main_w->proc();
}

int main(int argc, char **argv) {
#ifdef __EMSCRIPTEN__
    MainWindow main_window("/", 1280, 720);
    main_w =&main_window;
    emscripten_set_main_loop(eventProc, 0, 1);
#else
    Argz<std::string> parser(argc, argv);
    parser.addOptionSingle('h', "Display help message")
        .addOptionSingleValue('p', "assets path")
        .addOptionDoubleValue('P', "path", "assets path")
        .addOptionSingleValue('r',"Resolution WidthxHeight")
        .addOptionDoubleValue('R',"resolution", "Resolution WidthxHeight");
    Argument<std::string> arg;
    std::string path;
    int value = 0;
    int tw = 1280, th = 720;
    try {
        while((value = parser.proc(arg)) != -1) {
            switch(value) {
                case 'h':
                case 'v':
                    parser.help(std::cout);
                    exit(EXIT_SUCCESS);
                    break;
                case 'p':
                case 'P':
                    path = arg.arg_value;
                    break;
                case 'r':
                case 'R': {
                    auto pos = arg.arg_value.find("x");
                    if(pos == std::string::npos)  {
                        mx::system_err << "Error invalid resolution use WidthxHeight\n";
                        mx::system_err.flush();
                        exit(EXIT_FAILURE);
                    }
                    std::string left, right;
                    left = arg.arg_value.substr(0, pos);
                    right = arg.arg_value.substr(pos+1);
                    tw = atoi(left.c_str());
                    th = atoi(right.c_str());
                }
                    break;
            }
        }
    } catch (const ArgException<std::string>& e) {
        mx::system_err << e.text() << "\n";
    }
    if(path.empty()) {
        mx::system_out << "mx: No path provided trying default current directory.\n";
        path = ".";
    }
    try {
        MainWindow main_window(path, tw, th);
        main_window.loop();
    } catch(const mx::Exception &e) {
        mx::system_err << "mx: Exception: " << e.text() << "\n";
        mx::system_err.flush();
        exit(EXIT_FAILURE);
    }
#endif
    return 0;
}