added energy overlay

main
Benjamin Kraft 1 year ago
parent a99effbeb6
commit 8d1c374b0f
  1. 0
      shaders/fragment_pendula.glsl
  2. 11
      shaders/fragment_quad.glsl
  3. 6
      shaders/shaders.qrc
  4. 9
      shaders/vertex_pendula.glsl
  5. 11
      shaders/vertex_quad.glsl
  6. 83
      src/GLWidget.cpp
  7. 12
      src/GLWidget.h
  8. 2
      src/MainWindow.cpp
  9. 177
      src/Overlay.cpp
  10. 29
      src/Overlay.h
  11. 23
      src/Pendulum.cpp
  12. 2
      src/Pendulum.h
  13. 43
      src/Simulation.cpp
  14. 15
      src/Simulation.h
  15. 4
      src/Vector.cpp
  16. 1
      src/Vector.h
  17. 1
      src/main.cpp

@ -0,0 +1,11 @@
#version 330 core
out vec4 FragColor;
in vec2 texCoord;
uniform sampler2D quadTexture;
void main() {
FragColor = texture(quadTexture, texCoord);
}

@ -1,6 +1,8 @@
<RCC>
<qresource prefix="/shaders/">
<file>vertex.glsl</file>
<file>fragment.glsl</file>
<file>vertex_pendula.glsl</file>
<file>fragment_pendula.glsl</file>
<file>vertex_quad.glsl</file>
<file>fragment_quad.glsl</file>
</qresource>
</RCC>

@ -7,8 +7,8 @@ layout (location = 2) in float vMassRadius;
uniform mat3 VP;
uniform bool drawPoints;
uniform float screenSizePixels;
uniform float screenSizeMeters;
uniform int simulationSizePixels;
uniform float simulationSizeMeters;
uniform float depthOffset;
out vec3 color;
@ -16,8 +16,11 @@ out vec3 color;
void main() {
gl_Position = vec4(VP * vPos, 1.0);
if (drawPoints){
// Points always on top of neighbouring lines
gl_Position.z -= depthOffset * 0.5;
gl_PointSize = vMassRadius / screenSizeMeters * screenSizePixels * 2;
// PointSize is diameter in Pixels
gl_PointSize = vMassRadius / simulationSizeMeters * simulationSizePixels * 2;
} else {
color = vColor;
}

@ -0,0 +1,11 @@
#version 330 core
layout (location = 0) in vec2 vPos;
layout (location = 1) in vec2 vTex;
out vec2 texCoord;
void main() {
gl_Position = vec4(vPos, 0, 1);
texCoord = vTex;
}

@ -6,24 +6,25 @@
#include "Simulation.h"
#include <QApplication>
#include <QDialog>
#include "FPS.h"
#include "Overlay.h"
#include <QOpenGLShaderProgram>
#include "FPS.h"
GLWidget::GLWidget(Simulation * simulation) : simulation(simulation) {
startTimer(1000 / 144);
fps = new FPS;
fps->setUpdateInterval(500);
startTimer(1000 / 60);
overlay = new Overlay(simulation);
connect(simulation, &Simulation::layoutChanged, this, &GLWidget::uploadStaticDataToGPU);
connect(simulation, &Simulation::layoutChanged, overlay, &Overlay::fetchEnergyLimit);
connect(simulation, &Simulation::positionChanged, this, &GLWidget::changePosition);
}
void GLWidget::initializeGL() {
initializeOpenGLFunctions();
program = new QOpenGLShaderProgram;
program->addShaderFromSourceFile(QOpenGLShader::Vertex, ":/shaders/vertex.glsl");
program->addShaderFromSourceFile(QOpenGLShader::Fragment, ":/shaders/fragment.glsl");
program->link();
pendulaProgram = new QOpenGLShaderProgram;
pendulaProgram->addShaderFromSourceFile(QOpenGLShader::Vertex, ":/shaders/vertex_pendula.glsl");
pendulaProgram->addShaderFromSourceFile(QOpenGLShader::Fragment, ":/shaders/fragment_pendula.glsl");
pendulaProgram->link();
glGenVertexArrays(1, &VAO);
glGenBuffers(1, &positionVBO);
@ -35,57 +36,53 @@ void GLWidget::initializeGL() {
glEnable(GL_PROGRAM_POINT_SIZE);
glEnable(GL_PRIMITIVE_RESTART);
glEnable(GL_PRIMITIVE_RESTART_FIXED_INDEX);
glEnable(GL_DEPTH_TEST);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glDepthFunc(GL_LESS);
glClearDepth(1);
glClearColor(.15, .15, .15, 1);
std::vector<Pendulum*> empty;
uploadStaticDataToGPU(&empty);
uploadStaticDataToGPU();
overlay->init();
}
void GLWidget::paintGL() {
// Native OpenGL
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
program->bind();
program->setUniformValue("VP", VP);
// Content
{
glViewport(0, 0, width(), height());
glEnable(GL_DEPTH_TEST);
glDisable(GL_BLEND);
pendulaProgram->bind();
pendulaProgram->setUniformValue("VP", VP);
glBindVertexArray(VAO);
// Lines
{
program->setUniformValue("drawPoints", false);
pendulaProgram->setUniformValue("drawPoints", false);
glDrawElements(GL_LINE_STRIP, indexCount, GL_UNSIGNED_INT, nullptr);
}
// Mass Circles
if (showMasses) {
program->setUniformValue("drawPoints", true);
program->setUniformValue("depthOffset", depthOffset);
program->setUniformValue("screenSizePixels", screenSizePixels * 0.9f);
program->setUniformValue("screenSizeMeters", float(simulation->sizeMeters));
pendulaProgram->setUniformValue("drawPoints", true);
pendulaProgram->setUniformValue("depthOffset", depthOffset);
pendulaProgram->setUniformValue("simulationSizePixels", simulationSizePixels);
pendulaProgram->setUniformValue("simulationSizeMeters", float(simulation->sizeMeters));
glDrawElements(GL_POINTS, indexCount, GL_UNSIGNED_INT, nullptr);
}
glBindVertexArray(0);
}
QPixmap img(size());
auto p = new QPainter(&img);
// FPS
// Overlay
{
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);
glDisable(GL_DEPTH_TEST);
glEnable(GL_BLEND);
overlay->draw();
}
p->end();
}
void GLWidget::timerEvent(QTimerEvent *e) {
@ -101,7 +98,8 @@ bool GLWidget::AnyDialogOpen() {
return false;
}
void GLWidget::uploadStaticDataToGPU(const std::vector<Pendulum *> *pendula) {
void GLWidget::uploadStaticDataToGPU() {
auto pendula = &simulation->pendula;
int pointCount = std::transform_reduce(pendula->begin(), pendula->end(), 0, [](int prev, int curr){
return prev + curr + 1;
@ -185,11 +183,11 @@ void GLWidget::uploadStaticDataToGPU(const std::vector<Pendulum *> *pendula) {
simulation->pendulaMutex.unlock();
}
void GLWidget::changePosition(const std::vector<Pendulum *> *pendula) {
void GLWidget::changePosition() {
glBindBuffer(GL_ARRAY_BUFFER, positionVBO);
auto positions = (GLfloat*) glMapBufferRange(GL_ARRAY_BUFFER, 0, GLsizeiptr(positionCount * sizeof(float)), GL_MAP_WRITE_BIT);
size_t index = 0;
for (const auto p : *pendula){
for (const auto p : simulation->pendula){
index += 3;
for (auto x : p->X){
positions[index++] = float(x.x);
@ -202,16 +200,15 @@ void GLWidget::changePosition(const std::vector<Pendulum *> *pendula) {
}
void GLWidget::resizeGL(int w, int h) {
screenSizePixels = std::min(float(w), float(h));
float scale = screenSizePixels / float(simulation->sizeMeters) * 2 * 0.9f;
simulationSizePixels = std::min(w, h) - 100;
float scale = float(simulationSizePixels) / float(simulation->sizeMeters) * 2;
VP.setToIdentity();
VP(0, 0) = 1 / float(w);
VP(1, 1) = -1 / float(h);
VP *= scale;
VP(2, 2) = 1;
VP(0, 0) = scale / float(w);
VP(1, 1) = scale / float(h);
glViewport(0, 0, w, h);
int s = simulationSizePixels;
overlay->resize(QSize(w, h), QRect((w - s) / 2, (h - s) / 2, s, s));
}
void GLWidget::showMassesChanged(int state) {

@ -6,8 +6,8 @@
class Pendulum;
class Simulation;
class FPS;
class QOpenGLShaderProgram;
class Overlay;
class GLWidget : public QOpenGLWidget, protected QOpenGLExtraFunctions {
public:
@ -20,10 +20,10 @@ protected:
public slots:
void showMassesChanged(int state);
private slots:
void uploadStaticDataToGPU(const std::vector<Pendulum *> *pendula);
void changePosition(const std::vector<Pendulum *> *pendula);
void uploadStaticDataToGPU();
void changePosition();
private:
QOpenGLShaderProgram * program;
QOpenGLShaderProgram * pendulaProgram;
GLuint VAO;
GLuint positionVBO;
GLuint colorVBO;
@ -32,11 +32,11 @@ private:
GLsizei indexCount = 0;
GLsizei positionCount = 0;
QMatrix3x3 VP;
float screenSizePixels;
int simulationSizePixels;
float depthOffset;
bool showMasses;
Simulation * simulation;
FPS * fps;
Overlay * overlay;
static bool AnyDialogOpen();
};

@ -30,6 +30,8 @@ MainWindow::MainWindow() {
resetLengths();
buildUI();
normalizeLengths();
}
void MainWindow::buildUI() {

@ -0,0 +1,177 @@
#include "Overlay.h"
#include "FPS.h"
#include <QPainter>
#include <QOpenGLShaderProgram>
#include "Simulation.h"
#include <iostream>
#include <QOpenGLTexture>
#include <sstream>
#include <iomanip>
Overlay::Overlay(Simulation * simulation) : simulation(simulation) {
fps = new FPS;
fps->setUpdateInterval(500);
}
void Overlay::init() {
initializeOpenGLFunctions();
program = new QOpenGLShaderProgram;
program->addShaderFromSourceFile(QOpenGLShader::Vertex, ":/shaders/vertex_quad.glsl");
program->addShaderFromSourceFile(QOpenGLShader::Fragment, ":/shaders/fragment_quad.glsl");
program->link();
float quadVertices[] = {
-1, 1, 0, 1, // top left
-1, -1, 0, 0, // bottom left
1, -1, 1, 0, // bottom right
1, 1, 1, 1 // top right
};
GLuint indices[] = {
0, 1, 2, 2, 3, 0
};
glGenVertexArrays(1, &VAO);
glBindVertexArray(VAO);
GLuint VBO;
glGenBuffers(1, &VBO);
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(quadVertices), quadVertices, GL_STATIC_DRAW);
GLuint EBO;
glGenBuffers(1, &EBO);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float), nullptr);
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float), (GLvoid*) (2 * sizeof(float)));
glEnableVertexAttribArray(0);
glEnableVertexAttribArray(1);
glBindVertexArray(0);
}
void Overlay::draw() {
int w = viewportSize.width();
int h = (viewportSize.height() - simulationRect.height()) / 2;
// Top HUD with Energy Plot
{
glViewport(0, viewportSize.height() - h, w, h);
QImage hud(w, h, QImage::Format_RGBA8888);
hud.fill(Qt::transparent);
QPainter p(&hud);
p.setRenderHints(QPainter::Antialiasing);
p.setPen(Qt::white);
auto font = p.font();
font.setPixelSize(15);
p.setFont(font);
auto m = p.fontMetrics();
double pot = simulation->potentialEnergy;
double kin = simulation->kineticEnergy;
double total = pot + kin;
QColor empty(50, 50, 50);
QColor full(80, 80, 80);
// Total energy bar
{
double lossPercentage = (1 - total / energyLimit) * 100;
std::stringstream text;
text << std::fixed << std::setprecision(0) << "Total Energy: " << total << " J";
text << " --- ";
text << "Loss: " << std::setprecision(1) << lossPercentage << " %";
QString totalText = QString::fromStdString(text.str());
int x = int(total / energyLimit * w);
p.fillRect(0, 0, w, h / 2, empty);
p.fillRect(0, 0, x, h / 2, full);
p.drawLine(x, 0, x, h / 2);
p.drawText(0, 0, w, h / 2, Qt::AlignCenter | Qt::AlignVCenter, totalText);
}
// Split energy bar
{
int potX = total == 0 ? int(0.5 * w) : int(pot / total * w);
p.fillRect(0, h / 2, w, h / 2, empty);
p.fillRect(0, h / 2, potX, h / 2, full);
std::stringstream text;
text << std::fixed << std::setprecision(0) << "Potential: " << pot << " J";
QString potText = QString::fromStdString(text.str());
text = std::stringstream();
text << std::fixed << std::setprecision(0) << "Kinetic: " << kin << " J";
QString kinText = QString::fromStdString(text.str());
int textWidth1 = m.horizontalAdvance(potText);
int textWidth2 = m.horizontalAdvance(kinText);
int x1 = (w - textWidth1) / 4;
int x2 = (w - textWidth2) / 4 * 3;
p.drawLine(potX, h / 2, potX, h);
p.drawText(x1, h / 2, textWidth1, h / 2, Qt::AlignCenter | Qt::AlignVCenter, potText);
p.drawText(x2, h / 2, textWidth2, h / 2, Qt::AlignCenter | Qt::AlignVCenter, kinText);
}
p.drawLine(0, 0, w, 0);
p.drawLine(0, h / 2, w, h / 2);
p.drawLine(0, h, w, h);
drawTexture(&hud);
}
// Bottom HUD with FPS and UPS
{
glViewport(0, 0, w, h);
QImage hud(w, h, QImage::Format_RGBA8888);
// Background
hud.fill(Qt::transparent);
QPainter p(&hud);
p.setRenderHints(QPainter::Antialiasing);
// FPS
{
p.setPen(Qt::white);
auto font = p.font();
font.setPixelSize(15);
p.setFont(font);
fps->newFrame();
std::stringstream text;
text << "FPS: " << fps->current << "\n";
text << "UPS: " << simulation->ups->current;
p.drawText(0, 0, 100, h, Qt::AlignBottom | Qt::AlignLeft, QString::fromStdString(text.str()));
}
drawTexture(&hud);
}
}
void Overlay::resize(QSize newViewportSize, QRect newSimulationRect) {
viewportSize = newViewportSize;
simulationRect = newSimulationRect;
}
void Overlay::drawTexture(QImage *image) {
glBindVertexArray(VAO);
program->bind();
QOpenGLTexture texture(image->mirrored());
texture.bind();
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, nullptr);
}
void Overlay::fetchEnergyLimit() {
energyLimit = simulation->kineticEnergy + simulation->potentialEnergy;
}

@ -0,0 +1,29 @@
#include <QRect>
#include <QOpenGLExtraFunctions>
class QOpenGLWidget;
class FPS;
class QOpenGLShaderProgram;
class QOpenGLTexture;
class Simulation;
class Overlay : public QObject, protected QOpenGLExtraFunctions {
Q_OBJECT
FPS * fps;
QSize viewportSize;
QRect simulationRect;
QOpenGLShaderProgram * program;
GLuint VAO;
void drawTexture(QImage *image);
Simulation * simulation;
double energyLimit = 0;
public slots:
void fetchEnergyLimit();
public:
explicit Overlay(Simulation *);
void init();
void draw();
void resize(QSize newViewportSize, QRect newSimulationRect);
};

@ -7,9 +7,9 @@ Pendulum::Pendulum(const std::vector<double> &M,
const std::vector<double> &L,
QColor color, double startAngle) : M(M), L(L), color(color){
startAngle *= 3.141 / 180;
startAngle *= M_PI / 180;
Vector direction(-sin(startAngle), cos(startAngle));
Vector direction(-sin(startAngle), -cos(startAngle));
Vector currentPosition(0, 0);
for (int i = 0; i < M.size(); i++){
@ -31,7 +31,7 @@ void Pendulum::update(double h, double g) {
for (int i = 0; i < X.size(); i++){
// explicit integration with gravity as external force
V[i] = V[i] + Vector(0, g * h);
V[i] = V[i] + Vector(0, -g * h);
Vector p2 = X[i] + V[i] * h;
// solve distance constraint
@ -64,3 +64,20 @@ void Pendulum::update(double h, double g) {
}
double Pendulum::potentialEnergy(double gravity) const {
double total = 0;
double zeroLevel = 0;
for (int i = 0; i < X.size(); i++){
zeroLevel -= L[i];
total += M[i] * gravity * (X[i].y - zeroLevel);
}
return total;
}
double Pendulum::kineticEnergy() const {
double total = 0;
for (int i = 0; i < V.size(); i++)
total += M[i] * V[i].squaredLength();
return total / 2;
}

@ -12,6 +12,8 @@ public:
const std::vector<double> &L,
QColor color, double startAngle);
void update(double, double);
double potentialEnergy(double gravity) const;
double kineticEnergy() const;
private:
friend class GLWidget;
std::vector<Vector> X, V;

@ -4,36 +4,47 @@
#include <QThread>
#include <omp.h>
#include <iostream>
#include "FPS.h"
Simulation::Simulation() {
ups = new FPS;
ups->setUpdateInterval(100);
timer = new QTimer(this);
QTimer::connect(timer, &QTimer::timeout, this, &Simulation::update);
timer->setInterval(updateInterval);
timer->start();
lastUpdate = system_clock::now();
};
void Simulation::update() {
auto thisUpdate = system_clock::now();
auto ms = (int)duration_cast<milliseconds>(thisUpdate - lastUpdate).count();
lastUpdate = thisUpdate;
if (!isPlaying)
return;
double h = (timescale * updateInterval) / 1000;
ups->newFrame();
h /= substeps;
double h = (timescale * updateInterval) / 1000 / substeps;
pendulaMutex.lock();
double newPotentialEnergy = 0;
double newKineticEnergy = 0;
#pragma omp parallel for
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);
double localPotentialEnergy = pendula[i]->potentialEnergy(gravity);
double localKineticEnergy = pendula[i]->kineticEnergy();
#pragma omp atomic
newPotentialEnergy += localPotentialEnergy;
#pragma omp atomic
newKineticEnergy += localKineticEnergy;
}
emit positionChanged(&pendula);
potentialEnergy = newPotentialEnergy;
kineticEnergy = newKineticEnergy;
emit positionChanged();
}
void Simulation::clearPendula() {
@ -47,7 +58,9 @@ void Simulation::clearPendula() {
pendula.clear();
pendula.shrink_to_fit();
emit layoutChanged(&pendula);
updateEnergy();
emit layoutChanged();
}
void Simulation::addPendula(const std::vector<Pendulum *> &add) {
@ -56,6 +69,16 @@ void Simulation::addPendula(const std::vector<Pendulum *> &add) {
for (auto p : add)
pendula.push_back(p);
emit layoutChanged(&pendula);
updateEnergy();
emit layoutChanged();
}
void Simulation::updateEnergy() {
potentialEnergy = kineticEnergy = 0;
for (auto p : pendula){
potentialEnergy += p->potentialEnergy(gravity);
kineticEnergy += p->kineticEnergy();
}
}

@ -8,6 +8,7 @@
using namespace std::chrono;
class QTimer;
class FPS;
class Simulation : public QObject {
Q_OBJECT
@ -20,20 +21,24 @@ public:
double timescale {};
int substeps {};
double potentialEnergy = 0, kineticEnergy = 0;
bool isPlaying = false;
std::vector<Pendulum *> pendula;
std::mutex pendulaMutex;
FPS * ups;
signals:
void layoutChanged(const std::vector<Pendulum *> *newPendula);
void positionChanged(const std::vector<Pendulum *> *changedPendula);
void layoutChanged();
void positionChanged();
public slots:
void clearPendula();
void addPendula(const std::vector<Pendulum *> &add);
private slots:
void update();
private:
void updateEnergy();
QTimer * timer;
int updateInterval = 17;
std::vector<Pendulum *> pendula;
time_point<system_clock> lastUpdate;
int updateInterval = 16;
};

@ -29,3 +29,7 @@ Vector operator/(Vector v, double divisor){
return {v.x / divisor, v.y / divisor};
}
double Vector::squaredLength() const {
return x * x + y * y;
}

@ -3,6 +3,7 @@ struct Vector {
Vector(double x, double y);
double length() const;
double squaredLength() const;
void normalize();
friend Vector operator +(Vector lhs, Vector rhs);

@ -11,6 +11,7 @@ int main(int argc, char* argv[]) {
QSurfaceFormat fmt;
fmt.setSamples(4);
fmt.setDepthBufferSize(32);
fmt.setSwapInterval(1);
fmt.setVersion(3, 3);
fmt.setProfile(QSurfaceFormat::CoreProfile);
QSurfaceFormat::setDefaultFormat(fmt);

Loading…
Cancel
Save