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;