diff --git a/CMakeLists.txt b/CMakeLists.txt index f393b6a..c387957 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -10,6 +10,6 @@ find_package(Qt6 COMPONENTS OpenGLWidgets REQUIRED) find_package(OpenMP REQUIRED) file(GLOB_RECURSE SOURCE_FILES src/**.cpp) -add_executable(Pendulum WIN32 ${SOURCE_FILES} icons/icons.qrc) +add_executable(Pendulum WIN32 ${SOURCE_FILES} icons/icons.qrc shaders/shaders.qrc) target_link_libraries(Pendulum Qt6::OpenGLWidgets OpenMP::OpenMP_CXX) diff --git a/shaders/fragment.glsl b/shaders/fragment.glsl new file mode 100644 index 0000000..6daa09f --- /dev/null +++ b/shaders/fragment.glsl @@ -0,0 +1,9 @@ +#version 330 core + +out vec4 FragColor; + +in vec3 color; + +void main() { + FragColor = vec4(color, 1); +} diff --git a/shaders/shaders.qrc b/shaders/shaders.qrc new file mode 100644 index 0000000..65aede6 --- /dev/null +++ b/shaders/shaders.qrc @@ -0,0 +1,6 @@ + + + vertex.glsl + fragment.glsl + + \ No newline at end of file diff --git a/shaders/vertex.glsl b/shaders/vertex.glsl new file mode 100644 index 0000000..5d3df08 --- /dev/null +++ b/shaders/vertex.glsl @@ -0,0 +1,13 @@ +#version 330 core + +layout (location = 0) in vec2 vPos; +layout (location = 1) in vec3 vColor; + +uniform mat2 VP; + +out vec3 color; + +void main() { + gl_Position = vec4(VP * vPos, 0.0, 1.0); + color = vColor; +} diff --git a/src/GLWidget.cpp b/src/GLWidget.cpp index e83081e..f0c0ddd 100644 --- a/src/GLWidget.cpp +++ b/src/GLWidget.cpp @@ -7,29 +7,59 @@ #include #include #include "FPS.h" +#include GLWidget::GLWidget(Simulation * simulation) : simulation(simulation) { startTimer(1000 / 144); fps = new FPS; fps->setUpdateInterval(500); + connect(simulation, &Simulation::layoutChanged, this, &GLWidget::initGPUMemory); + connect(simulation, &Simulation::positionChanged, this, &GLWidget::changePosition); } -void GLWidget::paintEvent(QPaintEvent *e) { - fps->newFrame(); - QString fpsString = "FPS: " + QString::fromStdString(std::to_string(fps->current)); +void GLWidget::initializeGL() { + initializeOpenGLFunctions(); + + program = new QOpenGLShaderProgram; + program->addShaderFromSourceFile(QOpenGLShader::Vertex, ":/shaders/vertex.glsl"); + program->addShaderFromSourceFile(QOpenGLShader::Fragment, ":/shaders/fragment.glsl"); + program->link(); + + glGenVertexArrays(1, &VAO); + glGenBuffers(1, &positionVBO); + glGenBuffers(1, &colorVBO); + glGenBuffers(1, &EBO); +} +void GLWidget::paintGL() { auto p = new QPainter(this); p->setRenderHint(QPainter::Antialiasing); - p->fillRect(e->rect(), QColor(30, 30, 30)); + p->beginNativePainting(); + + glEnable(GL_PRIMITIVE_RESTART); + glEnable(GL_PRIMITIVE_RESTART_FIXED_INDEX); + glClearColor(.15, .15, .15, 1); + + glClear(GL_COLOR_BUFFER_BIT); + glViewport(0, 0, QWidget::width(), QWidget::height()); + + program->bind(); + program->setUniformValue("VP", VP); + glBindVertexArray(VAO); + glDrawElements(GL_LINE_STRIP, indexCount, GL_UNSIGNED_INT, nullptr); + glBindVertexArray(0); + + p->endNativePainting(); + p->setPen(Qt::white); auto font = p->font(); font.setPixelSize(20); p->setFont(font); - p->drawText(0, 0, 400, 400, Qt::AlignTop | Qt::AlignLeft, fpsString); - p->translate(e->rect().center()); - simulation->draw(p, std::min(width(), height())); + fps->newFrame(); + QString fpsString = "FPS: " + QString::fromStdString(std::to_string(fps->current)); + p->drawText(0, 0, 100, 100, Qt::AlignTop | Qt::AlignLeft, fpsString); p->end(); } @@ -46,4 +76,100 @@ bool GLWidget::AnyDialogOpen() { return false; } +void GLWidget::initGPUMemory(const std::vector *pendula) { + + std::vector positions; + std::vector colors; + std::vector massRadii; + std::vector indices; + + GLuint index = 0; + for (const auto p : *pendula){ + + // Origin point + positions.push_back(0); + positions.push_back(0); + + colors.resize(colors.size() + 3); + float * red = &colors.back() - 2; + p->color.getRgbF(red, red + 1, red + 2); + + massRadii.push_back(0); + + indices.push_back(index++); + + // All other points + for (int segment = 0; segment < p->M.size(); segment++){ + Vector pos = p->X[segment]; + positions.push_back(float(pos.x)); + positions.push_back(float(pos.y)); + + colors.resize(colors.size() + 3); + red = &colors.back() - 2; + p->color.getRgbF(red, red + 1, red + 2); + + massRadii.push_back(float(p->M[segment])); + + indices.push_back(index++); + } + + // Primitive Restart + indices.push_back(0xFFFFFFFF); + } + + indexCount = GLsizei(indices.size()); + positionCount = GLsizei(positions.size()); + + glBindVertexArray(VAO); + + glBindBuffer(GL_ARRAY_BUFFER, positionVBO); + glBufferData(GL_ARRAY_BUFFER, GLsizeiptr(positions.size() * sizeof(float)), positions.data(), GL_DYNAMIC_DRAW); + glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(float), nullptr); + glEnableVertexAttribArray(0); + + glBindBuffer(GL_ARRAY_BUFFER, colorVBO); + glBufferData(GL_ARRAY_BUFFER, GLsizeiptr(colors.size() * sizeof(float)), colors.data(), GL_STATIC_DRAW); + glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), nullptr); + glEnableVertexAttribArray(1); + + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO); + glBufferData(GL_ELEMENT_ARRAY_BUFFER, GLsizeiptr(indices.size() * sizeof(GLuint)), indices.data(), GL_STATIC_DRAW); + + glBindVertexArray(0); + + glBindBuffer(GL_ARRAY_BUFFER, 0); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); + + simulation->pendulaMutex.unlock(); +} + +void GLWidget::changePosition(const std::vector *pendula) { + glBindBuffer(GL_ARRAY_BUFFER, positionVBO); + auto positions = (GLfloat*) glMapBufferRange(GL_ARRAY_BUFFER, 0, GLsizeiptr(positionCount * sizeof(float)), GL_MAP_WRITE_BIT); + if (positions){ + size_t index = 0; + for (const auto p : *pendula){ + index += 2; + for (auto x : p->X){ + positions[index++] = float(x.x); + positions[index++] = float(x.y); + } + } + } + simulation->pendulaMutex.unlock(); + glUnmapBuffer(GL_ARRAY_BUFFER); + glBindBuffer(GL_ARRAY_BUFFER, 0); +} + +void GLWidget::resizeGL(int w, int h) { + float m = std::min(float(w), float(h)); + float scale = 1 / (float(simulation->size) / 2) * 0.9f; + + VP.setToIdentity(); + VP(0, 0) = 1 / float(w); + VP(1, 1) = -1 / float(h); + VP *= m * scale; +} + + diff --git a/src/GLWidget.h b/src/GLWidget.h index c740c18..f0f142d 100644 --- a/src/GLWidget.h +++ b/src/GLWidget.h @@ -1,18 +1,35 @@ -#include -#include - +#pragma once +#include +#include +#include +class Pendulum; class Simulation; class FPS; +class QOpenGLShaderProgram; -class GLWidget : public QOpenGLWidget, protected QOpenGLFunctions { +class GLWidget : public QOpenGLWidget, protected QOpenGLExtraFunctions { public: explicit GLWidget(Simulation *); protected: - void paintEvent(QPaintEvent* e) override; void timerEvent(QTimerEvent* e) override; + void initializeGL() override; + void paintGL() override; + void resizeGL(int w, int h) override; +private slots: + void initGPUMemory(const std::vector *pendula); + void changePosition(const std::vector *pendula); private: + QOpenGLShaderProgram * program; + GLuint VAO; + GLuint positionVBO; + GLuint colorVBO; + GLuint EBO; + GLsizei indexCount = 0; + GLsizei positionCount = 0; + QMatrix2x2 VP; + Simulation * simulation; FPS * fps; static bool AnyDialogOpen(); diff --git a/src/MainWindow.cpp b/src/MainWindow.cpp index 3f6caf6..db34ab6 100644 --- a/src/MainWindow.cpp +++ b/src/MainWindow.cpp @@ -30,6 +30,9 @@ MainWindow::MainWindow() { resetLengths(); buildUI(); + + normalizeLengths(); + add(); } void MainWindow::buildUI() { @@ -330,7 +333,7 @@ QWidget * MainWindow::buildSimulationUI() { timescaleSlider->setMinimum(1); timescaleSlider->setMaximum(500); substepsSlider->setMinimum(1); - substepsSlider->setMaximum(1000); + substepsSlider->setMaximum(100); resetSimulationControl(); @@ -430,7 +433,7 @@ void MainWindow::add() { void MainWindow::resetSimulationControl() { gravitySlider->setValue(981); timescaleSlider->setValue(100); - substepsSlider->setValue(30); + substepsSlider->setValue(5); } void MainWindow::toggleSimulation() { diff --git a/src/Pendulum.h b/src/Pendulum.h index f748371..3fa2901 100644 --- a/src/Pendulum.h +++ b/src/Pendulum.h @@ -1,7 +1,10 @@ +#pragma once + #include #include #include #include "Vector.h" +#include "GLWidget.h" class QPainter; @@ -13,8 +16,8 @@ public: void draw(QPainter*, double) const; void update(double, double); - private: + friend class GLWidget; std::vector X, V; std::vector M, L; QColor color; diff --git a/src/Simulation.cpp b/src/Simulation.cpp index f2f080e..48ae116 100644 --- a/src/Simulation.cpp +++ b/src/Simulation.cpp @@ -1,5 +1,4 @@ #include "Simulation.h" -#include "Pendulum.h" #include #include #include @@ -14,20 +13,10 @@ Simulation::Simulation() { lastUpdate = system_clock::now(); }; -void Simulation::draw(QPainter *p, int screenSize) { - pendulaMutex.lock(); - - double scale = screenSize * 0.95 / size; - - for (const auto pendulum : pendula) - pendulum->draw(p, scale); - - pendulaMutex.unlock(); -} - void Simulation::update() { auto thisUpdate = system_clock::now(); auto ms = (int)duration_cast(thisUpdate - lastUpdate).count(); + std::cout << ms << std::endl; lastUpdate = thisUpdate; if (!isPlaying) @@ -37,25 +26,37 @@ void Simulation::update() { h /= substeps; + pendulaMutex.lock(); + #pragma omp parallel for - for (int i = 0; i < pendula.size(); i++) // NOLINT(*-loop-convert) // no ranged based for msvc + for (int i = 0; i < pendula.size(); i++){ // NOLINT(*-loop-convert) // not ranged based for msvc for (int k = 0; k < substeps; k++) pendula[i]->update(h, gravity); + } + + emit positionChanged(&pendula); } void Simulation::clearPendula() { pendulaMutex.lock(); - for (auto p : pendula) + for (Pendulum* &p : pendula){ delete p; + p = nullptr; + } + pendula.clear(); pendula.shrink_to_fit(); - pendulaMutex.unlock(); + emit layoutChanged(&pendula); } void Simulation::addPendula(const std::vector &add) { + pendulaMutex.lock(); + for (auto p : add) pendula.push_back(p); + + emit layoutChanged(&pendula); } diff --git a/src/Simulation.h b/src/Simulation.h index 2f80c4e..ae35439 100644 --- a/src/Simulation.h +++ b/src/Simulation.h @@ -3,14 +3,15 @@ #include #include #include +#include "Pendulum.h" using namespace std::chrono; -class Pendulum; class QPainter; class QTimer; class Simulation : public QObject { + Q_OBJECT public: explicit Simulation(); @@ -22,14 +23,16 @@ public: bool isPlaying = false; - void draw(QPainter*, int); + std::mutex pendulaMutex; +signals: + void layoutChanged(const std::vector *newPendula); + void positionChanged(const std::vector *changedPendula); public slots: void clearPendula(); void addPendula(const std::vector &add); private slots: void update(); private: - std::mutex pendulaMutex; QTimer * timer; int updateInterval = 17; std::vector pendula;