diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 3bbe2aa..cdf3cd9 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -7,7 +7,7 @@ cmake_minimum_required (VERSION 3.8) add_executable (GraviSim "main.cpp" "MainWindow.cpp" - "Screen.hpp" "Screen.cpp" "Planet.hpp" "Planet.cpp" "PlanetConfig.cpp" ) + "Screen.hpp" "Screen.cpp" "Planet.hpp" "Planet.cpp" "PlanetConfig.cpp" "PlanetManager.hpp" "PlanetManager.cpp") target_link_libraries(GraviSim Qt5::Widgets diff --git a/src/MainWindow.cpp b/src/MainWindow.cpp index 0730a45..234c1ff 100644 --- a/src/MainWindow.cpp +++ b/src/MainWindow.cpp @@ -33,6 +33,7 @@ void MainWindow::OpenPlanetDialog(Planet* planet) ui.config->Enable(); ui.config->SetTitle(planet->name); ui.config->SetRadius(planet->radius); + ui.config->SetMass(planet->mass); ui.config->SetColor(planet->GetColor()); ui.config->SetX(planet->position.rx()); ui.config->SetY(planet->position.ry()); @@ -48,10 +49,17 @@ void MainWindow::ClosePlanetDialog() ui.config->Disable(); ui.config->SetColor(QColor(0, 0, 0, 0)); ui.config->SetRadius(0.f); + ui.config->SetMass(0.f); ui.config->SetX(0.f); ui.config->SetY(0.f); } +void MainWindow::UpdatePlanetPositionInDialog(const QPointF& position) +{ + ui.config->SetX(position.x()); + ui.config->SetY(position.y()); +} + void MainWindow::OnNameChanged(const QString& name) { if (activePlanet != nullptr) @@ -64,6 +72,12 @@ void MainWindow::OnRadiusChanged(double radius) activePlanet->radius = radius; } +void MainWindow::OnMassChanged(double mass) +{ + if (activePlanet != nullptr) + activePlanet->mass = mass; +} + void MainWindow::OnColourChanged(const QColor& color) { if (activePlanet != nullptr) @@ -92,4 +106,5 @@ void MainWindow::OnToggle() { isSimulating = !isSimulating; ui.config->SetButtonLabel((isSimulating) ? "Stop Simulation" : "Start Simulation"); + ui.screen->simulate = isSimulating; } diff --git a/src/MainWindow.hpp b/src/MainWindow.hpp index f705e5a..40639c2 100644 --- a/src/MainWindow.hpp +++ b/src/MainWindow.hpp @@ -17,9 +17,12 @@ public: void OpenPlanetDialog(Planet* planet); void ClosePlanetDialog(); + void UpdatePlanetPositionInDialog(const QPointF& position); + public slots: void OnNameChanged(const QString& name); void OnRadiusChanged(double radius); + void OnMassChanged(double mass); void OnColourChanged(const QColor& color); void OnXChanged(double x); void OnYChanged(double y); diff --git a/src/Planet.cpp b/src/Planet.cpp index 069bb8b..9d31f0e 100644 --- a/src/Planet.cpp +++ b/src/Planet.cpp @@ -1,13 +1,18 @@ #include "Planet.hpp" -Planet::Planet(float xPos, float yPos, float radius, QColor color) : - position{ xPos, yPos }, radius(radius) +#define LENGTH(v) (sqrtf(v.x()*v.x() + v.y()*v.y())) + +QPen Planet::arrowColor = QPen(Qt::white); +QLine Planet::arrow = QLine(); + +Planet::Planet(uint32_t id, float xPos, float yPos, float radius, QColor color) : + position{ xPos, yPos }, radius(radius), id(id) { Initialize(color); } -Planet::Planet(QPointF pos, float radius, QColor color) : - position(pos), radius(radius) +Planet::Planet(uint32_t id, QPointF pos, float radius, QColor color) : + position(pos), radius(radius), id(id) { Initialize(color); } @@ -31,11 +36,33 @@ void Planet::SetColor(const QColor& color) selectionColor = QColor::fromHsv((h + 180) % 360, s, v, a); } -void Planet::Draw(QPainter* painter) +void Planet::Update() { - painter->setBrush(circle); - painter->setPen(outline); - painter->drawEllipse(position, radius, radius); + velocity += acceleration * (1.f / 60.f); + position += velocity * (1.f / 60.f); +} + +void Planet::Draw(QPainter & painter) +{ + painter.setBrush(circle); + painter.setPen(outline); + painter.drawEllipse(position, radius, radius); + + // Draw velocity arrow + if (!velocity.isNull()) + { + painter.save(); + qreal angle = QPointF::dotProduct(velocity, QPointF(1.0f, 0.0f)) / LENGTH(velocity); + painter.rotate(angle); + + arrow.setP1(position.toPoint()); + arrow.setP2((position + velocity).toPoint()); + + painter.setPen(arrowColor); + painter.drawLine(arrow); + + painter.restore(); + } } bool Planet::IsInside(QPointF point) const @@ -48,4 +75,6 @@ void Planet::Initialize(QColor color) { name = "Unnamed"; SetColor(color); + arrowColor.setWidthF(2.0f); + acceleration = QPointF(0.f, 0.f); } diff --git a/src/Planet.hpp b/src/Planet.hpp index 2b8d8a9..dec541b 100644 --- a/src/Planet.hpp +++ b/src/Planet.hpp @@ -7,8 +7,8 @@ class Planet { public: - Planet(float xPos, float yPos, float radius, QColor color); - Planet(QPointF pos, float radius, QColor color); + Planet(uint32_t id, float xPos, float yPos, float radius, QColor color); + Planet(uint32_t id, QPointF pos, float radius, QColor color); void Resize(float newRadius) { radius = newRadius; } void Select(bool select); @@ -16,7 +16,8 @@ public: void SetColor(const QColor& color); const QColor& GetColor() { return circle.color(); } - void Draw(QPainter* painter); + void Update(); + void Draw(QPainter & painter); bool IsInside(QPointF point) const; @@ -24,9 +25,14 @@ private: void Initialize(QColor color); public: + const uint32_t id; + QPointF position; + QPointF velocity; + QPointF acceleration; QString name; float radius; + float mass; private: @@ -34,4 +40,7 @@ private: QBrush circle; QPen outline; + + static QPen arrowColor; + static QLine arrow; }; \ No newline at end of file diff --git a/src/PlanetConfig.cpp b/src/PlanetConfig.cpp index fde0551..0ccb681 100644 --- a/src/PlanetConfig.cpp +++ b/src/PlanetConfig.cpp @@ -18,6 +18,9 @@ PlanetConfig::PlanetConfig(QWidget* parent) : connect(ui.radius, QOverload::of(&QDoubleSpinBox::valueChanged), instance, [instance](double d) { instance->OnRadiusChanged(d); }); + connect(ui.mass, QOverload::of(&QDoubleSpinBox::valueChanged), + instance, [instance](double d) { instance->OnMassChanged(d); }); + connect(ui.colour, SIGNAL(returnPressed()), this, SLOT(ColourTBSlot())); @@ -38,6 +41,7 @@ PlanetConfig::PlanetConfig(QWidget* parent) : void PlanetConfig::Disable() { ui.radius->setDisabled(true); + ui.mass->setDisabled(true); ui.colour->setDisabled(true); ui.xPos->setDisabled(true); ui.yPos->setDisabled(true); @@ -47,6 +51,7 @@ void PlanetConfig::Disable() void PlanetConfig::Enable() { ui.radius->setDisabled(false); + ui.mass->setDisabled(false); ui.colour->setDisabled(false); ui.xPos->setDisabled(false); ui.yPos->setDisabled(false); @@ -78,6 +83,11 @@ void PlanetConfig::SetY(double y) ui.yPos->setValue(y); } +void PlanetConfig::SetMass(double mass) +{ + ui.mass->setValue(mass); +} + void PlanetConfig::SetButtonLabel(const QString& label) { ui.toggle->setText(label); diff --git a/src/PlanetConfig.hpp b/src/PlanetConfig.hpp index 4f21ae2..d1f594d 100644 --- a/src/PlanetConfig.hpp +++ b/src/PlanetConfig.hpp @@ -19,6 +19,7 @@ public: void SetColor(const QColor& color); void SetX(double x); void SetY(double y); + void SetMass(double mass); void SetButtonLabel(const QString& label); diff --git a/src/PlanetManager.cpp b/src/PlanetManager.cpp new file mode 100644 index 0000000..97ea9be --- /dev/null +++ b/src/PlanetManager.cpp @@ -0,0 +1,99 @@ +#include "PlanetManager.hpp" + +std::map PlanetManager::planets; +uint32_t PlanetManager::planetCounter = 0; + +Planet* PlanetManager::AddPlanet(const QPointF& position, float radius, const QColor& color, const QString& name) +{ + Planet* planet = new Planet(planetCounter, position, radius, color); + planet->mass = rand() % 100; + + planets.insert( + std::make_pair( + planetCounter++, + planet + ) + ); + + return planet; +} + +void PlanetManager::RemovePlanet(uint32_t id) +{ + auto it = planets.find(id); + if (it == planets.end()) + return; + + delete it->second; + planets.erase(it); +} + +Planet* PlanetManager::GetPlanet(uint32_t id) +{ + auto it = planets.find(id); + if (it == planets.end()) + return nullptr; + return it->second; +} + +Planet* PlanetManager::GetLastPlanet() +{ + return planets.rbegin()->second; +} + +void PlanetManager::RemoveLastPlanet() +{ + auto it = planets.cbegin(); + delete it->second; + + planets.erase(it); +} + +Planet* PlanetManager::IsInPlanet(const QPointF& position) +{ + for (auto it : planets) + if (it.second->IsInside(position)) + return it.second; + + return nullptr; +} + +void PlanetManager::Update() +{ + for (auto planet : planets) + planet.second->Update(); + + // Collision detection && Gravitation + for (auto planet : planets) + { + planet.second->acceleration = QPointF(0.f, 0.f); + for (auto otherPlanet : planets) + { + if (planet == otherPlanet) + continue; + + QPointF distance = planet.second->position - otherPlanet.second->position; + float radiusSum = (planet.second->radius + otherPlanet.second->radius); + float dist = distance.x() * distance.x() + distance.y() * distance.y(); + if (dist < radiusSum * radiusSum) + { + // Collision + Planet* lighter = ((planet.second->mass > otherPlanet.second->mass) ? otherPlanet.second : planet.second); + lighter->position += (radiusSum - sqrt(dist) + 1) * distance / sqrt(dist); + } + else + { + // Gravitation + planet.second->acceleration -= 1000000 * (planet.second->mass * otherPlanet.second->mass) / pow(dist, 3.f / 2.f) * distance / sqrt(dist); + } + } + + planet.second->acceleration /= planet.second->mass; + } +} + +void PlanetManager::Render(QPainter& painter) +{ + for (auto it : planets) + it.second->Draw(painter); +} diff --git a/src/PlanetManager.hpp b/src/PlanetManager.hpp new file mode 100644 index 0000000..05698a2 --- /dev/null +++ b/src/PlanetManager.hpp @@ -0,0 +1,30 @@ +#pragma once + +#include + +#include + +#include "Planet.hpp" + +class PlanetManager +{ +public: + PlanetManager() = delete; + + static Planet* AddPlanet(const QPointF& position, float radius, const QColor& color, + const QString& name = "Unnamed"); + static void RemovePlanet(uint32_t id); + + static Planet* GetPlanet(uint32_t id); + static Planet* GetLastPlanet(); + static void RemoveLastPlanet(); + + static Planet* IsInPlanet(const QPointF& position); + + static void Update(); + static void Render(QPainter& painter); + +private: + static std::map planets; + static uint32_t planetCounter; +}; \ No newline at end of file diff --git a/src/Screen.cpp b/src/Screen.cpp index 0a56524..61d2953 100644 --- a/src/Screen.cpp +++ b/src/Screen.cpp @@ -9,6 +9,7 @@ #include #include "MainWindow.hpp" +#include "PlanetManager.hpp" #define BIND_LMB(f) (lmbAction = std::bind(&Screen::f, this, std::placeholders::_1)) @@ -32,32 +33,30 @@ Screen::Screen(QWidget* parent) : void Screen::mouseMoveEvent(QMouseEvent* event) { - if (mouseDown && hovered == nullptr) + if (mouseDown && selected == nullptr && hovered == nullptr) { - Planet* planet = planets.back(); + Planet* planet = PlanetManager::GetLastPlanet(); QPointF distance = event->localPos() - mouseClickPos; planet->Resize(qSqrt(distance.x()*distance.x() + distance.y() *distance.y())); } + else if (mouseDown && selected != nullptr) + { + selected->velocity = event->localPos() - selected->position; + } else { - hovered = nullptr; - for (unsigned i = planets.size(); i-- > 0; ) - { - if (planets[i]->IsInside(event->localPos())) - { - setCursor(Qt::PointingHandCursor); - BIND_LMB(lmb_SelectPlanet); - hovered = planets[i]; - break; - } - } - + hovered = PlanetManager::IsInPlanet(event->localPos()); if (hovered == nullptr) { setCursor(Qt::ArrowCursor); if(selected == nullptr) BIND_LMB(lmb_MakePlanet); } + else + { + BIND_LMB(lmb_SelectPlanet); + setCursor(Qt::PointingHandCursor); + } } } @@ -72,7 +71,7 @@ void Screen::mousePressEvent(QMouseEvent* event) { if (mouseDown) { - planets.pop_back(); + PlanetManager::RemoveLastPlanet(); mouseDown = false; } } @@ -86,17 +85,9 @@ void Screen::mouseReleaseEvent(QMouseEvent* event) void Screen::DeletePlanet(Planet* planet) { - for (std::vector::iterator it = planets.begin(); it != planets.end(); it++) - { - if ((*it) == planet) - { - planets.erase(it); - delete planet; - hovered = nullptr; - selected = nullptr; - break; - } - } + PlanetManager::RemovePlanet(planet->id); + hovered = nullptr; + selected = nullptr; } void Screen::Render() @@ -106,9 +97,12 @@ void Screen::Render() void Screen::lmb_MakePlanet(QMouseEvent* event) { - planets.push_back(new Planet(event->localPos(), 0, - QColor(rand() % 256, rand() % 256, rand() % 256) - )); + Planet* newPlanet = PlanetManager::AddPlanet(event->localPos(), 0, + QColor(rand() % 200, rand() % 200, rand() % 200) + ); + + newPlanet->velocity = QPointF(rand() % 400 - 200, rand() % 400 - 200); + mouseClickPos = event->localPos(); mouseDown = true; } @@ -117,12 +111,19 @@ void Screen::lmb_SelectPlanet(QMouseEvent* event) { if (hovered != nullptr) { - if (selected != nullptr) - selected->Select(false); + if (selected != hovered) + { + if (selected != nullptr) + selected->Select(false); - hovered->Select(true); - selected = hovered; - MainWindow::Instance()->OpenPlanetDialog(selected); + hovered->Select(true); + selected = hovered; + MainWindow::Instance()->OpenPlanetDialog(selected); + } + else + { + mouseDown = true; + } } else { @@ -136,12 +137,20 @@ void Screen::lmb_SelectPlanet(QMouseEvent* event) void Screen::paintEvent(QPaintEvent* event) { + if (simulate) + { + PlanetManager::Update(); + if (selected != nullptr) + { + MainWindow::Instance()->UpdatePlanetPositionInDialog(selected->position); + } + } + QPainter painter; painter.begin(this); painter.fillRect(rect(), background); - for (Planet* planet : planets) - planet->Draw(&painter); + PlanetManager::Render(painter); painter.end(); } \ No newline at end of file diff --git a/src/Screen.hpp b/src/Screen.hpp index 50b5dad..d93be25 100644 --- a/src/Screen.hpp +++ b/src/Screen.hpp @@ -5,8 +5,8 @@ #include -#include "Planet.hpp" +class Planet; class MainWindow; class Screen : public QOpenGLWidget @@ -30,6 +30,9 @@ private: protected: void paintEvent(QPaintEvent* event) override; +public: + bool simulate = false; + private: bool mouseDown = false; @@ -40,7 +43,6 @@ private: QBrush background; QTimer* renderTimer; - std::vector planets; Planet* selected = nullptr; Planet* hovered = nullptr; }; \ No newline at end of file diff --git a/ui/PlanetConfig.ui b/ui/PlanetConfig.ui index eedd0ac..9d22ae1 100644 --- a/ui/PlanetConfig.ui +++ b/ui/PlanetConfig.ui @@ -7,7 +7,7 @@ 0 0 308 - 380 + 454 @@ -82,6 +82,42 @@ + + + + + + + Courier New + 12 + + + + Mass + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + background-color: rgb(255, 255, 255); + + + 1000.000000000000000 + + + 1.000000000000000 + + + QAbstractSpinBox::AdaptiveDecimalStepType + + + + +