diff --git a/shaders/fragment.glsl b/shaders/fragment_pendula.glsl
similarity index 100%
rename from shaders/fragment.glsl
rename to shaders/fragment_pendula.glsl
diff --git a/shaders/fragment_quad.glsl b/shaders/fragment_quad.glsl
new file mode 100644
index 0000000..3d32963
--- /dev/null
+++ b/shaders/fragment_quad.glsl
@@ -0,0 +1,11 @@
+#version 330 core
+
+out vec4 FragColor;
+
+in vec2 texCoord;
+
+uniform sampler2D quadTexture;
+
+void main() {
+ FragColor = texture(quadTexture, texCoord);
+}
diff --git a/shaders/shaders.qrc b/shaders/shaders.qrc
index 65aede6..eb51953 100644
--- a/shaders/shaders.qrc
+++ b/shaders/shaders.qrc
@@ -1,6 +1,8 @@
- vertex.glsl
- fragment.glsl
+ vertex_pendula.glsl
+ fragment_pendula.glsl
+ vertex_quad.glsl
+ fragment_quad.glsl
\ No newline at end of file
diff --git a/shaders/vertex.glsl b/shaders/vertex_pendula.glsl
similarity index 60%
rename from shaders/vertex.glsl
rename to shaders/vertex_pendula.glsl
index 544fdaa..ab4b822 100644
--- a/shaders/vertex.glsl
+++ b/shaders/vertex_pendula.glsl
@@ -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;
}
diff --git a/shaders/vertex_quad.glsl b/shaders/vertex_quad.glsl
new file mode 100644
index 0000000..76d0754
--- /dev/null
+++ b/shaders/vertex_quad.glsl
@@ -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;
+}
diff --git a/src/GLWidget.cpp b/src/GLWidget.cpp
index dca7c64..e5adb9d 100644
--- a/src/GLWidget.cpp
+++ b/src/GLWidget.cpp
@@ -6,24 +6,25 @@
#include "Simulation.h"
#include
#include
-#include "FPS.h"
+#include "Overlay.h"
#include
+#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 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 *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 *pendula) {
simulation->pendulaMutex.unlock();
}
-void GLWidget::changePosition(const std::vector *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 *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) {
diff --git a/src/GLWidget.h b/src/GLWidget.h
index a440bb0..7b5da54 100644
--- a/src/GLWidget.h
+++ b/src/GLWidget.h
@@ -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 *pendula);
- void changePosition(const std::vector *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();
};
\ No newline at end of file
diff --git a/src/MainWindow.cpp b/src/MainWindow.cpp
index 30480fc..5db725b 100644
--- a/src/MainWindow.cpp
+++ b/src/MainWindow.cpp
@@ -30,6 +30,8 @@ MainWindow::MainWindow() {
resetLengths();
buildUI();
+
+ normalizeLengths();
}
void MainWindow::buildUI() {
diff --git a/src/Overlay.cpp b/src/Overlay.cpp
new file mode 100644
index 0000000..5db2bb6
--- /dev/null
+++ b/src/Overlay.cpp
@@ -0,0 +1,177 @@
+#include "Overlay.h"
+#include "FPS.h"
+#include
+#include
+#include "Simulation.h"
+#include
+#include
+#include
+#include
+
+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;
+}
diff --git a/src/Overlay.h b/src/Overlay.h
new file mode 100644
index 0000000..0e4af91
--- /dev/null
+++ b/src/Overlay.h
@@ -0,0 +1,29 @@
+#include
+
+#include
+
+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);
+};
\ No newline at end of file
diff --git a/src/Pendulum.cpp b/src/Pendulum.cpp
index 352b5e5..791b12f 100644
--- a/src/Pendulum.cpp
+++ b/src/Pendulum.cpp
@@ -7,9 +7,9 @@ Pendulum::Pendulum(const std::vector &M,
const std::vector &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;
+}
diff --git a/src/Pendulum.h b/src/Pendulum.h
index fa4e657..faf0cd7 100644
--- a/src/Pendulum.h
+++ b/src/Pendulum.h
@@ -12,6 +12,8 @@ public:
const std::vector &L,
QColor color, double startAngle);
void update(double, double);
+ double potentialEnergy(double gravity) const;
+ double kineticEnergy() const;
private:
friend class GLWidget;
std::vector X, V;
diff --git a/src/Simulation.cpp b/src/Simulation.cpp
index 608190d..89c1619 100644
--- a/src/Simulation.cpp
+++ b/src/Simulation.cpp
@@ -4,36 +4,47 @@
#include
#include
#include
+#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(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 &add) {
@@ -56,6 +69,16 @@ void Simulation::addPendula(const std::vector &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();
+ }
}
diff --git a/src/Simulation.h b/src/Simulation.h
index 2814cac..bea0365 100644
--- a/src/Simulation.h
+++ b/src/Simulation.h
@@ -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 pendula;
+
std::mutex pendulaMutex;
+ FPS * ups;
signals:
- void layoutChanged(const std::vector *newPendula);
- void positionChanged(const std::vector *changedPendula);
+ void layoutChanged();
+ void positionChanged();
public slots:
void clearPendula();
void addPendula(const std::vector &add);
private slots:
void update();
private:
+ void updateEnergy();
QTimer * timer;
- int updateInterval = 17;
- std::vector pendula;
- time_point lastUpdate;
+ int updateInterval = 16;
};
\ No newline at end of file
diff --git a/src/Vector.cpp b/src/Vector.cpp
index 9378f92..247ba06 100644
--- a/src/Vector.cpp
+++ b/src/Vector.cpp
@@ -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;
+}
+
diff --git a/src/Vector.h b/src/Vector.h
index 334f913..ecaeef4 100644
--- a/src/Vector.h
+++ b/src/Vector.h
@@ -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);
diff --git a/src/main.cpp b/src/main.cpp
index 501f7aa..b2a5eda 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -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);