diff --git a/shaders/fragment.glsl b/shaders/fragment.glsl index 6daa09f..ddeb8b2 100644 --- a/shaders/fragment.glsl +++ b/shaders/fragment.glsl @@ -4,6 +4,19 @@ out vec4 FragColor; in vec3 color; +uniform bool drawPoints; + void main() { - FragColor = vec4(color, 1); + if (drawPoints){ + vec2 coord = gl_PointCoord - vec2(0.5); + if (length(coord) > 0.5) + discard; + if (length(coord) > 0.4) + FragColor = vec4(1, 1, 1, 1); + else + FragColor = vec4(0, 0, 0, 1); + } else { + FragColor = vec4(color, 1); + } + } diff --git a/shaders/vertex.glsl b/shaders/vertex.glsl index 5d3df08..4b938ec 100644 --- a/shaders/vertex.glsl +++ b/shaders/vertex.glsl @@ -1,13 +1,25 @@ #version 330 core -layout (location = 0) in vec2 vPos; +layout (location = 0) in vec3 vPos; layout (location = 1) in vec3 vColor; +layout (location = 2) in float vMassRadius; -uniform mat2 VP; +uniform mat3 VP; + +uniform bool drawPoints; +uniform float screenSizePixels; +uniform float screenSizeMeters; +uniform float depthOffset; out vec3 color; void main() { - gl_Position = vec4(VP * vPos, 0.0, 1.0); - color = vColor; + if (drawPoints){ + gl_Position = vec4(VP * vPos, 1.0); + gl_Position.z -= depthOffset * 0.25; + gl_PointSize = vMassRadius / screenSizeMeters * screenSizePixels * 2; + } else { + gl_Position = vec4(VP * vPos, 1.0); + color = vColor; + } } diff --git a/src/GLWidget.cpp b/src/GLWidget.cpp index d8ccf55..423ba24 100644 --- a/src/GLWidget.cpp +++ b/src/GLWidget.cpp @@ -28,42 +28,69 @@ void GLWidget::initializeGL() { glGenVertexArrays(1, &VAO); glGenBuffers(1, &positionVBO); glGenBuffers(1, &colorVBO); + glGenBuffers(1, &massRadiiVBO); glGenBuffers(1, &EBO); + glEnable(GL_POINT_SPRITE); + glEnable(GL_PROGRAM_POINT_SIZE); + glEnable(GL_PRIMITIVE_RESTART); + glEnable(GL_PRIMITIVE_RESTART_FIXED_INDEX); + glEnable(GL_DEPTH_TEST); + glDepthFunc(GL_LESS); + glDepthMask(GL_TRUE); + glClearColor(.15, .15, .15, 1); + std::vector empty; initGPUMemory(&empty); } void GLWidget::paintGL() { - auto p = new QPainter(this); - p->setRenderHint(QPainter::Antialiasing); - p->beginNativePainting(); + //auto p = new QPainter(this); + //p->setRenderHint(QPainter::Antialiasing); + //p->beginNativePainting(); - glEnable(GL_PRIMITIVE_RESTART); - glEnable(GL_PRIMITIVE_RESTART_FIXED_INDEX); - glClearColor(.15, .15, .15, 1); + // Native OpenGL + { + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); - glClear(GL_COLOR_BUFFER_BIT); - glViewport(0, 0, QWidget::width(), QWidget::height()); + program->bind(); + program->setUniformValue("VP", VP); - program->bind(); - program->setUniformValue("VP", VP); - glBindVertexArray(VAO); - glDrawElements(GL_LINE_STRIP, indexCount, GL_UNSIGNED_INT, nullptr); - glBindVertexArray(0); + glBindVertexArray(VAO); - p->endNativePainting(); + // Lines + { + program->setUniformValue("drawPoints", false); + glDrawElements(GL_LINE_STRIP, indexCount, GL_UNSIGNED_INT, nullptr); + } - p->setPen(Qt::white); - auto font = p->font(); - font.setPixelSize(20); - p->setFont(font); + // Mass Circles + if (showMasses) { + program->setUniformValue("drawPoints", true); + program->setUniformValue("depthOffset", depthOffset); + program->setUniformValue("screenSizePixels", screenSizePixels * 0.9f); + program->setUniformValue("screenSizeMeters", float(simulation->sizeMeters)); + glDrawElements(GL_POINTS, indexCount, GL_UNSIGNED_INT, nullptr); + } - fps->newFrame(); - QString fpsString = "FPS: " + QString::fromStdString(std::to_string(fps->current)); + glBindVertexArray(0); + } + //p->endNativePainting(); + + /* + // FPS + { + p->setPen(Qt::white); + auto font = p->font(); + font.setPixelSize(20); + p->setFont(font); + fps->newFrame(); + QString fpsString = "FPS: " + QString::fromStdString(std::to_string(fps->current)); + p->drawText(0, 0, 100, 100, Qt::AlignTop | Qt::AlignLeft, fpsString); + } + */ - p->drawText(0, 0, 100, 100, Qt::AlignTop | Qt::AlignLeft, fpsString); - p->end(); + //p->end(); } void GLWidget::timerEvent(QTimerEvent *e) { @@ -81,43 +108,59 @@ bool GLWidget::AnyDialogOpen() { void GLWidget::initGPUMemory(const std::vector *pendula) { + int segmentCount = std::transform_reduce(pendula->begin(), pendula->end(), 0, [](int prev, int curr){ + return prev + curr + 1; + }, [](const Pendulum * p){ + return p->X.size(); + }); + std::vector positions; std::vector colors; std::vector massRadii; std::vector indices; - GLuint index = 0; - for (const auto p : *pendula){ + if (!pendula->empty()){ - // Origin point - positions.push_back(0); - positions.push_back(0); + float depth = 1.f - 1.f / float(segmentCount); + depthOffset = 1.f * 2 / float(segmentCount); - 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++); + GLuint index = 0; + for (const auto p : *pendula){ - // 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)); + // Origin point + positions.push_back(0); + positions.push_back(0); + positions.push_back(depth); + depth -= depthOffset; colors.resize(colors.size() + 3); - red = &colors.back() - 2; + float * red = &colors.back() - 2; p->color.getRgbF(red, red + 1, red + 2); - massRadii.push_back(float(p->M[segment])); + massRadii.push_back(0); indices.push_back(index++); - } - // Primitive Restart - indices.push_back(0xFFFFFFFF); + // 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)); + positions.push_back(depth); + depth -= depthOffset; + + colors.resize(colors.size() + 3); + red = &colors.back() - 2; + p->color.getRgbF(red, red + 1, red + 2); + + massRadii.push_back(float(sqrt(p->M[segment]) / 5)); + + indices.push_back(index++); + } + + // Primitive Restart + indices.push_back(0xFFFFFFFF); + } } indexCount = GLsizei(indices.size()); @@ -127,7 +170,7 @@ void GLWidget::initGPUMemory(const std::vector *pendula) { 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); + glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), nullptr); glEnableVertexAttribArray(0); glBindBuffer(GL_ARRAY_BUFFER, colorVBO); @@ -135,6 +178,11 @@ void GLWidget::initGPUMemory(const std::vector *pendula) { glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), nullptr); glEnableVertexAttribArray(1); + glBindBuffer(GL_ARRAY_BUFFER, massRadiiVBO); + glBufferData(GL_ARRAY_BUFFER, GLsizeiptr(massRadii.size() * sizeof(float)), massRadii.data(), GL_STATIC_DRAW); + glVertexAttribPointer(2, 1, GL_FLOAT, GL_FALSE, 1 * sizeof(float), nullptr); + glEnableVertexAttribArray(2); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO); glBufferData(GL_ELEMENT_ARRAY_BUFFER, GLsizeiptr(indices.size() * sizeof(GLuint)), indices.data(), GL_STATIC_DRAW); @@ -152,10 +200,11 @@ void GLWidget::changePosition(const std::vector *pendula) { if (positions){ size_t index = 0; for (const auto p : *pendula){ - index += 2; + index += 3; for (auto x : p->X){ positions[index++] = float(x.x); positions[index++] = float(x.y); + index++; } } } @@ -165,13 +214,20 @@ void GLWidget::changePosition(const std::vector *pendula) { } void GLWidget::resizeGL(int w, int h) { - float m = std::min(float(w), float(h)); - float scale = 1 / (float(simulation->size) / 2) * 0.9f; + screenSizePixels = std::min(float(w), float(h)); + float scale = screenSizePixels / float(simulation->sizeMeters) * 2 * 0.9f; VP.setToIdentity(); VP(0, 0) = 1 / float(w); VP(1, 1) = -1 / float(h); - VP *= m * scale; + VP *= scale; + VP(2, 2) = 1; + + glViewport(0, 0, w, h); +} + +void GLWidget::showMassesChanged(int state) { + showMasses = state == Qt::Checked; } diff --git a/src/GLWidget.h b/src/GLWidget.h index f0f142d..7629a57 100644 --- a/src/GLWidget.h +++ b/src/GLWidget.h @@ -2,7 +2,7 @@ #include #include -#include +#include class Pendulum; class Simulation; @@ -17,6 +17,8 @@ protected: void initializeGL() override; void paintGL() override; void resizeGL(int w, int h) override; +public slots: + void showMassesChanged(int state); private slots: void initGPUMemory(const std::vector *pendula); void changePosition(const std::vector *pendula); @@ -25,10 +27,14 @@ private: GLuint VAO; GLuint positionVBO; GLuint colorVBO; + GLuint massRadiiVBO; GLuint EBO; GLsizei indexCount = 0; GLsizei positionCount = 0; - QMatrix2x2 VP; + QMatrix3x3 VP; + float screenSizePixels; + float depthOffset; + bool showMasses; Simulation * simulation; FPS * fps; diff --git a/src/MainWindow.cpp b/src/MainWindow.cpp index 9c3b5d3..30480fc 100644 --- a/src/MainWindow.cpp +++ b/src/MainWindow.cpp @@ -347,7 +347,7 @@ QWidget * MainWindow::buildSimulationUI() { auto reset = new Button("Reset", ":/icons/refresh.svg"); auto togglePlay = new Button("Resume", ":/icons/play.svg"); - reset->setToolTip("Reset Simulation controls"); + reset->setToolTip("Reset Simulation Controls"); togglePlay->setToolTip("Pause/Resume Simulation"); togglePlay->setStyleSheet("background-color: #aaaaff"); @@ -362,6 +362,15 @@ QWidget * MainWindow::buildSimulationUI() { btnLyt->addWidget(reset); btnLyt->addWidget(togglePlay); + // Show Masses + { + auto showMasses = new QCheckBox("Show Masses"); + connect(showMasses, &QCheckBox::stateChanged, glWidget, &GLWidget::showMassesChanged); + showMasses->setChecked(true); + + lyt->addWidget(showMasses); + } + return w; } @@ -383,7 +392,7 @@ void MainWindow::resetLengths() { void MainWindow::normalizeLengths() { double sum = std::reduce(lengths.begin(), lengths.begin() + segments); - double factor = simulation->size / 2 / sum; + double factor = simulation->sizeMeters / 2 / sum; for (int i = 0; i < segments; i++){ lengths[i] *= factor; } diff --git a/src/Pendulum.cpp b/src/Pendulum.cpp index 3b6fd85..352b5e5 100644 --- a/src/Pendulum.cpp +++ b/src/Pendulum.cpp @@ -19,29 +19,10 @@ Pendulum::Pendulum(const std::vector &M, } } -void Pendulum::draw(QPainter *p, double scale) const { - p->setPen(color); - p->setBrush(Qt::NoBrush); - QPainterPath path; - path.moveTo(0, 0); - for (auto x : X){ - x = x * scale; - path.lineTo(x.x, x.y); - } - p->drawPath(path); - - p->setPen(Qt::NoPen); - p->setBrush(Qt::white); - for (int i = 0; i < M.size(); i++){ - double r = sqrt(M[i]) * scale / 30; - p->drawEllipse(QPointF(X[i].x, X[i].y) * scale, r * 2, r * 2); - } -} - void Pendulum::update(double h, double g) { // Classic PBD needs multiple loops - // Here, I can put all operations safely into one single loop, + // Here, I can safely put all operations into one single loop, // because the positions and velocities in X, V are sorted // from the pendulum's origin to it's end which means // that only direct neighbours affect each other diff --git a/src/Pendulum.h b/src/Pendulum.h index 3fa2901..fa4e657 100644 --- a/src/Pendulum.h +++ b/src/Pendulum.h @@ -6,15 +6,11 @@ #include "Vector.h" #include "GLWidget.h" -class QPainter; - class Pendulum { public: explicit Pendulum(const std::vector &M, const std::vector &L, QColor color, double startAngle); - - void draw(QPainter*, double) const; void update(double, double); private: friend class GLWidget; diff --git a/src/Simulation.h b/src/Simulation.h index ae35439..2814cac 100644 --- a/src/Simulation.h +++ b/src/Simulation.h @@ -7,7 +7,6 @@ using namespace std::chrono; -class QPainter; class QTimer; class Simulation : public QObject { @@ -15,7 +14,7 @@ class Simulation : public QObject { public: explicit Simulation(); - double size = 50; + double sizeMeters = 50; double gravity {}; double timescale {}; diff --git a/src/main.cpp b/src/main.cpp index c224a4f..dba4834 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -12,6 +12,9 @@ int main(int argc, char* argv[]) { QSurfaceFormat fmt; fmt.setSamples(4); + fmt.setDepthBufferSize(24); + //fmt.setVersion(3, 3); + //fmt.setProfile(QSurfaceFormat::CoreProfile); QSurfaceFormat::setDefaultFormat(fmt); MainWindow w;