← index
libmx2/libmx/gl_curve/curve.cpp
Source: libmx2/libmx/gl_curve/curve.cpp
// This Demo was made with the help of AI
#include"mx.hpp"
#include"argz.hpp"

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

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

#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

struct Point {
    float x, y;
    Point(float _x = 0.0f, float _y = 0.0f) : x(_x), y(_y) {}
};

class Game : public gl::GLObject {
public:
    GLuint highlightVAO = 0, highlightVBO = 0;
    
    Game() = default;
    virtual ~Game() override {
        glDeleteBuffers(1, &curveVBO);
        glDeleteVertexArrays(1, &curveVAO);
        glDeleteBuffers(1, &controlPointsVBO);
        glDeleteVertexArrays(1, &controlPointsVAO);
        glDeleteBuffers(1, &highlightVBO);       
        glDeleteVertexArrays(1, &highlightVAO);  
    }

    void load(gl::GLWindow *win) override {
        font.loadFont(win->util.getFilePath("data/font.ttf"), 20);
        controlPoints = {
            {100.0f, 400.0f},  
            {400.0f, 140.0f},  
            {700.0f, 400.0f}   
        };

        if (!shaderProgram.loadProgram(win->util.getFilePath("data/curve.vert"), 
                                      win->util.getFilePath("data/curve.frag"))) {
            throw mx::Exception("Failed to load shader program");
        }

        glGenVertexArrays(1, &curveVAO);
        glGenBuffers(1, &curveVBO);
        glGenVertexArrays(1, &controlPointsVAO);
        glGenBuffers(1, &controlPointsVBO);
        glGenVertexArrays(1, &highlightVAO);
        glGenBuffers(1, &highlightVBO);
        calculateCurvePoints();
        projectionMatrix = glm::ortho(0.0f, static_cast<float>(win->w), 
                                      static_cast<float>(win->h), 0.0f, -1.0f, 1.0f);
    }

    void draw(gl::GLWindow *win) override {
        glDisable(GL_DEPTH_TEST);
        Uint32 currentTime = SDL_GetTicks();
        float deltaTime = (currentTime - lastUpdateTime) / 1000.0f; 
        lastUpdateTime = currentTime;
        
        glClearColor(0.1f, 0.1f, 0.1f, 1.0f);
        glClear(GL_COLOR_BUFFER_BIT);
        
        shaderProgram.useProgram();
        glUniformMatrix4fv(glGetUniformLocation(shaderProgram.id(), "projection"), 
                         1, GL_FALSE, glm::value_ptr(projectionMatrix));
        
        glUniform4f(glGetUniformLocation(shaderProgram.id(), "color"), 0.0f, 1.0f, 0.0f, 1.0f);
        glBindVertexArray(curveVAO);
        glLineWidth(3.0f);
        glDrawArrays(GL_LINE_STRIP, 0, curvePoints.size());
        glBindVertexArray(controlPointsVAO);
        glUniform4f(glGetUniformLocation(shaderProgram.id(), "color"), 0.7f, 0.7f, 0.7f, 1.0f);
        glLineWidth(1.0f);
        glDrawArrays(GL_LINE_STRIP, 0, 3);
        glUniform4f(glGetUniformLocation(shaderProgram.id(), "color"), 1.0f, 0.0f, 0.0f, 1.0f);
        glPointSize(10.0f);
        glDrawArrays(GL_POINTS, 0, 3);
        glBindVertexArray(0);
        win->text.setColor({255, 255, 255, 255});
        win->text.printText_Solid(font, 20.0f, 20.0f, "Quadratic Bezier Curve Demo");
        win->text.printText_Solid(font, 20.0f, 50.0f, "Formula: B(t) = (1-t)^2*P0 + 2*(1-t)*t*P1 + t^2*P2");
        win->text.printText_Solid(font, 20.0f, 80.0f, "Click and drag the red control points to modify the curve");
        
        for (int i = 0; i < 3; i++) {
            std::string pointLabel = "P" + std::to_string(i) + " (" + 
                                   std::to_string(int(controlPoints[i].x)) + ", " + 
                                   std::to_string(int(controlPoints[i].y)) + ")";
            win->text.printText_Solid(font, controlPoints[i].x + 10, controlPoints[i].y - 10, pointLabel);
        }
        
        if (isAnimating) {
            tParam += deltaTime * 0.5f; 
            if (tParam > 1.0f) {
                tParam = 0.0f;
            }
            highlightPoint = calculateBezierPoint(tParam);
            std::string tValue = "t = " + std::to_string(tParam).substr(0, 4);
            win->text.printText_Solid(font, 20.0f, 110.0f, tValue);
            shaderProgram.useProgram();
            glUniform4f(glGetUniformLocation(shaderProgram.id(), "color"), 1.0f, 1.0f, 0.0f, 1.0f);
            glBindVertexArray(highlightVAO);
            glBindBuffer(GL_ARRAY_BUFFER, highlightVBO);
            glBufferData(GL_ARRAY_BUFFER, sizeof(Point), &highlightPoint, GL_DYNAMIC_DRAW);
            glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, sizeof(Point), (void*)0);
            glEnableVertexAttribArray(0);    
            glPointSize(8.0f);
            glDrawArrays(GL_POINTS, 0, 1);
            glBindVertexArray(0);
        }
    }
    
    void event(gl::GLWindow *win, SDL_Event &e) override {
        if (e.type == SDL_MOUSEBUTTONDOWN && e.button.button == SDL_BUTTON_LEFT) {
            int mouseX = e.button.x;
            int mouseY = e.button.y;
            
            for (int i = 0; i < static_cast<int>(controlPoints.size()); i++) {
                float dx = mouseX - controlPoints[i].x;
                float dy = mouseY - controlPoints[i].y;
                float distSquared = dx*dx + dy*dy;
                
                if (distSquared < 100.0f) {  
                    selectedPointIndex = i;
                    isDragging = true;
                    break;
                }
            }
        }
        else if (e.type == SDL_MOUSEBUTTONUP && e.button.button == SDL_BUTTON_LEFT) {
            isDragging = false;
            selectedPointIndex = -1;
        }
        else if (e.type == SDL_MOUSEMOTION && isDragging && selectedPointIndex >= 0) {
            controlPoints[selectedPointIndex].x = e.motion.x;
            controlPoints[selectedPointIndex].y = e.motion.y;
            calculateCurvePoints();
        }
        else if (e.type == SDL_KEYDOWN) {
            if (e.key.keysym.sym == SDLK_SPACE) {
                isAnimating = !isAnimating;
                tParam = 0.0f;
            }
        }
    }

private:
    mx::Font font;
    Uint32 lastUpdateTime = SDL_GetTicks();
    gl::ShaderProgram shaderProgram;
    
    GLuint curveVAO = 0, curveVBO = 0;
    GLuint controlPointsVAO = 0, controlPointsVBO = 0;
    
    std::vector<Point> controlPoints;
    std::vector<Point> curvePoints;
    glm::mat4 projectionMatrix;
    
    bool isDragging = false;
    int selectedPointIndex = -1;
    
    bool isAnimating = false;
    float tParam = 0.0f;
    Point highlightPoint;
    Point calculateBezierPoint(float t) {
        float u = 1.0f - t;
        float tt = t * t;
        float uu = u * u;
        Point p;
        p.x = uu * controlPoints[0].x + 2.0f * u * t * controlPoints[1].x + tt * controlPoints[2].x;
        p.y = uu * controlPoints[0].y + 2.0f * u * t * controlPoints[1].y + tt * controlPoints[2].y;
        return p;
    }
    
    void calculateCurvePoints() {
        const int numPoints = 100;  
        curvePoints.clear();
        curvePoints.reserve(numPoints);
        
        for (int i = 0; i < numPoints; i++) {
            float t = static_cast<float>(i) / (numPoints - 1);
            curvePoints.push_back(calculateBezierPoint(t));
        }
        
        glBindVertexArray(curveVAO);
        glBindBuffer(GL_ARRAY_BUFFER, curveVBO);
        glBufferData(GL_ARRAY_BUFFER, curvePoints.size() * sizeof(Point), curvePoints.data(), GL_DYNAMIC_DRAW);
        glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, sizeof(Point), (void*)0);
        glEnableVertexAttribArray(0);
        
        glBindVertexArray(controlPointsVAO);
        glBindBuffer(GL_ARRAY_BUFFER, controlPointsVBO);
        glBufferData(GL_ARRAY_BUFFER, controlPoints.size() * sizeof(Point), controlPoints.data(), GL_DYNAMIC_DRAW);
        glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, sizeof(Point), (void*)0);
        glEnableVertexAttribArray(0);
        
        glBindVertexArray(0);
    }
};

class MainWindow : public gl::GLWindow {
public:
    MainWindow(std::string path, int tw, int th) : gl::GLWindow("Quadratic Bezier Curve", tw, th, false) {
        setPath(path);
        setObject(new Game());
        object->load(this);
    }
    
    ~MainWindow() override {}
    
    virtual void event(SDL_Event &e) override {
        if (object) {
            object->event(this, e);
        }
    }
    
    virtual void draw() override {
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_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("/", 800, 600);
    main_w = &main_window;
    emscripten_set_main_loop(eventProc, 0, 1);
#else
    Arguments args = proc_args(argc, argv);
    try {
        MainWindow main_window(args.path, args.width, args.height);
        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;
}