main
Benjamin Kraft 1 year ago
parent bdaf7752e9
commit 1aa7bf1713
  1. 384
      src/MainWindow.cpp
  2. 11
      src/MainWindow.h
  3. 9
      src/Simulation.h

@ -11,8 +11,8 @@
#include <QSizePolicy> #include <QSizePolicy>
#include <iostream> #include <iostream>
#include <QDial> #include <QDial>
#include "Slider.h"
#include <iostream> #include <iostream>
#include <QColorDialog>
MainWindow::MainWindow() { MainWindow::MainWindow() {
simulation = new Simulation; simulation = new Simulation;
@ -62,123 +62,227 @@ QWidget * MainWindow::buildAddUI() {
auto lyt = new QVBoxLayout(w); auto lyt = new QVBoxLayout(w);
lyt->setAlignment(Qt::AlignTop); lyt->setAlignment(Qt::AlignTop);
// Segments
auto segLabel = new QLabel(); auto segLabel = new QLabel();
auto segmentSlider = new Slider<>(segLabel, "Segments: %d", &segments); auto segmentSlider = new Slider<>(segLabel, "Segments: %d", &segments);
segmentGrid = new QGridLayout(); {
segmentGrid = new QGridLayout();
segmentGrid->addWidget(new QLabel("Mass"), 0, 1);
segmentGrid->addWidget(new QLabel("Length"), 0, 3);
lyt->addWidget(segLabel);
lyt->addWidget(segmentSlider);
lyt->addLayout(segmentGrid);
connect(segmentSlider, &QSlider::valueChanged, [this, segmentSlider](int newV){
int oldSegments = segmentSlider->targetOld;
if (segments < oldSegments){
for (int row = segments; row < oldSegments; row++){
for (int i = 0; i < 5; i++){
auto item = segmentGrid->itemAtPosition(row + 1, i);
segmentGrid->removeItem(item);
delete item->widget();
}
}
}
if (segments > oldSegments){
for (int row = oldSegments; row < segments; row++){
char buff[100];
snprintf(buff, sizeof(buff), "(%02d)", row);
segmentGrid->addWidget(new QLabel(buff), row + 1, 0);
auto massLabel = new QLabel;
auto massSlider = new Slider<double>(massLabel, "%03.1fkg", &masses[row], [](int v){
return double(v) / 10;
}, [](double v) {
return int(v * 10);
});
massSlider->setFromTarget();
massSlider->setMinimum(1);
auto lengthLabel = new QLabel;
auto lengthSlider = new Slider<double>(lengthLabel, "% 4.1fm", &lengths[row], [](int v){
return double(v) / 10;
}, [](double v) {
return int(v * 10);
});
lengthSlider->setMaximum(250);
lengthSlider->setFromTarget();
lengthSlider->setMinimum(1);
segmentGrid->addWidget(massLabel, row + 1, 1);
segmentGrid->addWidget(massSlider, row + 1, 2);
segmentGrid->addWidget(lengthLabel, row + 1, 3);
segmentGrid->addWidget(lengthSlider, row + 1, 4);
}
}
});
segmentSlider->setValue(2);
segmentSlider->setMinimum(1);
segmentSlider->setMaximum(MaxSegments);
}
segmentGrid->addWidget(new QLabel("Mass"), 0, 1); // Buttons for Segments
segmentGrid->addWidget(new QLabel("Length"), 0, 3); {
auto segmentButtonGrid = new QHBoxLayout;
lyt->addLayout(segmentButtonGrid);
lyt->addWidget(segLabel); auto resetMassesBtn = new QPushButton("Reset");
lyt->addWidget(segmentSlider); auto normalizeLengthsBtn = new QPushButton("Normalize");
lyt->addLayout(segmentGrid); auto resetLengthsBtn = new QPushButton("Reset");
connect(segmentSlider, &QSlider::valueChanged, [this, segmentSlider](int newV){ connect(resetMassesBtn, &QPushButton::clicked, this, &MainWindow::resetMasses);
int oldSegments = segmentSlider->targetOld; connect(normalizeLengthsBtn, &QPushButton::clicked, this, &MainWindow::normalizeLengths);
connect(resetLengthsBtn, &QPushButton::clicked, this, &MainWindow::resetLengths);
if (segments < oldSegments){ segmentButtonGrid->addWidget(resetMassesBtn);
for (int row = segments; row < oldSegments; row++){ segmentButtonGrid->addWidget(normalizeLengthsBtn);
for (int i = 0; i < 5; i++){ segmentButtonGrid->addWidget(resetLengthsBtn);
auto item = segmentGrid->itemAtPosition(row + 1, i); }
segmentGrid->removeItem(item);
delete item->widget(); // Starting Angle
} {
} auto angleLabel = new QLabel;
auto dial = new QDial;
dial->setWrapping(true);
dial->setMinimum(0);
dial->setMaximum(360);
dial->setMinimumSize(100, 100);
connect(dial, &QDial::valueChanged, [this, angleLabel](int value){
angleLabel->setText("Starting Angle: " + QString::fromStdString(std::to_string(value)) + "°");
startingAngle = value;
});
dial->setValue(startingAngle);
lyt->addWidget(angleLabel);
lyt->addWidget(dial);
}
// Color
{
auto colorLyt = new QHBoxLayout;
lyt->addLayout(colorLyt);
auto colorBtn = new QPushButton("Select color");
auto colorLabel = new QLabel;
colorLabel->setStyleSheet("background-color: white");
auto setLabelColor = [colorLabel](QColor c){
QString hex = c.name();
colorLabel->setStyleSheet("QLabel {background-color: " + hex + "; border: 2px solid black; border-radius: 5px;}");
};
connect(colorBtn, &QPushButton::clicked, [this, setLabelColor](){
color = QColorDialog::getColor(color);
setLabelColor(color);
});
setLabelColor(color);
colorLyt->addWidget(colorBtn);
colorLyt->addWidget(colorLabel);
}
// Multiple
{
auto multipleWidget = new QWidget;
auto multipleCheckbox = new QCheckBox("Add multiple");
multipleCheckbox->setCheckState(Qt::Unchecked);
connect(multipleCheckbox, &QCheckBox::stateChanged, [this, multipleWidget](int state){
multiple = state == Qt::Checked;
multipleWidget->setVisible(multiple);
});
lyt->addWidget(multipleCheckbox);
lyt->addWidget(multipleWidget);
auto multipleLyt = new QVBoxLayout;
multipleWidget->setLayout(multipleLyt);
multipleWidget->setVisible(false);
// Rainbow coloring
{
auto rainbowCheckbox = new QCheckBox("Use rainbow coloring instead");
connect(rainbowCheckbox, &QCheckBox::stateChanged, [this](int state){
rainbow = state == Qt::Checked;
});
multipleLyt->addWidget(rainbowCheckbox);
} }
if (segments > oldSegments){
for (int row = oldSegments; row < segments; row++){ // Count
char buff[100]; {
snprintf(buff, sizeof(buff), "(%02d)", row); auto countLabel = new QLabel;
segmentGrid->addWidget(new QLabel(buff), row + 1, 0); auto countSlider = new Slider<>(countLabel, "Count: %d", &count);
countSlider->setMaximum(1000);
auto massLabel = new QLabel; countSlider->setValue(count);
auto massSlider = new Slider<double>(massLabel, "%03.1fkg", &masses[row], [](int v){ countSlider->setMinimum(5);
return double(v) / 10;
}, [](double v) { multipleLyt->addWidget(countLabel);
return int(v * 10); multipleLyt->addWidget(countSlider);
});
massSlider->setFromTarget();
massSlider->setMinimum(1);
auto lengthLabel = new QLabel;
auto lengthSlider = new Slider<double>(lengthLabel, "% 4.1fm", &lengths[row], [](int v){
return double(v) / 10;
}, [](double v) {
return int(v * 10);
});
lengthSlider->setMaximum(250);
lengthSlider->setFromTarget();
lengthSlider->setMinimum(1);
segmentGrid->addWidget(massLabel, row + 1, 1);
segmentGrid->addWidget(massSlider, row + 1, 2);
segmentGrid->addWidget(lengthLabel, row + 1, 3);
segmentGrid->addWidget(lengthSlider, row + 1, 4);
}
} }
});
segmentSlider->setValue(2); // Change
segmentSlider->setMinimum(1); {
segmentSlider->setMaximum(MaxSegments); multipleLyt->addWidget(new QLabel("Property to change slightly:"));
auto group = new QButtonGroup;
auto segmentButtonGrid = new QHBoxLayout; auto b1 = new QRadioButton("Starting Angle");
lyt->addLayout(segmentButtonGrid); auto b2 = new QRadioButton("Specific Mass");
auto b3 = new QRadioButton("Specific Length");
auto resetMassesBtn = new QPushButton("Reset"); group->addButton(b1, 0);
auto normalizeLengthsBtn = new QPushButton("Normalize"); group->addButton(b2, 1);
auto resetLengthsBtn = new QPushButton("Reset"); group->addButton(b3, 2);
multipleLyt->addWidget(b1);
connect(resetMassesBtn, &QPushButton::clicked, this, &MainWindow::resetMasses); multipleLyt->addWidget(b2);
connect(normalizeLengthsBtn, &QPushButton::clicked, this, &MainWindow::normalizeLengths); multipleLyt->addWidget(b3);
connect(resetLengthsBtn, &QPushButton::clicked, this, &MainWindow::resetLengths); b1->setChecked(true);
segmentButtonGrid->addWidget(resetMassesBtn); auto indexLabel = new QLabel;
segmentButtonGrid->addWidget(normalizeLengthsBtn); indexLabel->setVisible(false);
segmentButtonGrid->addWidget(resetLengthsBtn); auto indexSlider = new Slider<>(indexLabel, "Index: %d", &changeIndex);
indexSlider->setVisible(false);
lyt->addWidget(new QLabel("Starting Angle:")); indexSlider->setValue(1);
auto dial = new QDial; indexSlider->setValue(0);
dial->setMinimum(0); indexSlider->setMaximum(segments - 1);
dial->setMaximum(360);
dial->setMinimumSize(100, 100); connect(segmentSlider, &QSlider::valueChanged, [this, indexSlider](){
lyt->addWidget(dial); indexSlider->setMaximum(segments - 1);
});
auto colorLyt = new QHBoxLayout;
lyt->addLayout(colorLyt); connect(group, &QButtonGroup::idClicked, [this, indexLabel, indexSlider](int id){
changeProperty = static_cast<Property>(id);
colorLyt->addWidget(new QPushButton("Select color")); bool indexVisible = changeProperty == Mass || changeProperty == Length;
colorLyt->addWidget(new QLabel("Color this label")); indexLabel->setVisible(indexVisible);
indexSlider->setVisible(indexVisible);
lyt->addWidget(new QCheckBox("Add multiple")); });
lyt->addWidget(new QCheckBox("Use rainbow coloring"));
multipleLyt->addWidget(indexLabel);
lyt->addWidget(new QLabel("Count:")); multipleLyt->addWidget(indexSlider);
// lyt->addWidget(new Slider(nullptr, [](int v) {return "";}));
auto amountLabel = new QLabel;
lyt->addWidget(new QLabel("Property to change slightly:")); auto amountSlider = new Slider<double>(amountLabel, "Change Amount: %5.3f%%", &changeAmount, [](int v){
auto group = new QButtonGroup; return double(v) / 1000;
auto b1 = new QRadioButton("Starting Angle"); }, [](double v){
auto b2 = new QRadioButton("Starting Mass"); return int(v * 1000);
auto b3 = new QRadioButton("Starting Length"); });
group->addButton(b1); amountSlider->setMinimum(1);
group->addButton(b2); amountSlider->setMaximum(1000);
group->addButton(b3); multipleLyt->addWidget(amountLabel);
lyt->addWidget(b1); multipleLyt->addWidget(amountSlider);
lyt->addWidget(b2); }
lyt->addWidget(b3); }
b1->setChecked(true);
lyt->addWidget(new QLabel("Index:"));
// lyt->addWidget(new Slider(nullptr, [](int v) {return "";}));
lyt->addWidget(new QLabel("Change Amount:"));
// lyt->addWidget(new Slider(nullptr, [](int v) {return "";}));
auto btnLyt = new QHBoxLayout; auto btnLyt = new QHBoxLayout;
lyt->addLayout(btnLyt); lyt->addLayout(btnLyt);
btnLyt->addWidget(new QPushButton("Add")); auto addBtn = new QPushButton("Add");
btnLyt->addWidget(new QPushButton("Remove")); auto removeBtn = new QPushButton("Remove");
connect(addBtn, &QPushButton::clicked, this, &MainWindow::add);
connect(removeBtn, &QPushButton::clicked, this, &MainWindow::remove);
btnLyt->addWidget(addBtn);
btnLyt->addWidget(removeBtn);
return w; return w;
} }
@ -187,18 +291,52 @@ QWidget * MainWindow::buildSimulationUI() {
auto w = new QWidget; auto w = new QWidget;
auto lyt = new QVBoxLayout(w); auto lyt = new QVBoxLayout(w);
lyt->addWidget(new QLabel("Gravity:"));
// lyt->addWidget(new Slider(nullptr, [](int v) {return "";})); auto gravityLabel = new QLabel;
lyt->addWidget(new QLabel("Timescale:")); auto timescaleLabel = new QLabel;
// lyt->addWidget(new Slider(nullptr, [](int v) {return "";})); auto substepsLabel = new QLabel;
lyt->addWidget(new QLabel("PBD Substeps:"));
// lyt->addWidget(new Slider(nullptr, [](int v) {return "";})); gravitySlider = new Slider<double>(gravityLabel, "Gravity: %.2f N/kg", &simulation->gravity, [](int v){
return double(v) / 100;
}, [](double v){
return int(v * 100);
});
timescaleSlider = new Slider<double>(timescaleLabel, "Timescale: x%4.2f", &simulation->timescale, [](int v){
return double(v) / 100;
}, [](double v){
return int(v * 100);
});
substepsSlider = new Slider<>(substepsLabel, "Substeps: %d", &simulation->substeps);
gravitySlider->setMaximum(3000);
timescaleSlider->setMinimum(1);
timescaleSlider->setMaximum(500);
substepsSlider->setMinimum(1);
substepsSlider->setMaximum(1000);
resetSimulationControl();
lyt->addWidget(gravityLabel);
lyt->addWidget(gravitySlider);
lyt->addWidget(timescaleLabel);
lyt->addWidget(timescaleSlider);
lyt->addWidget(substepsLabel);
lyt->addWidget(substepsSlider);
auto btnLyt = new QHBoxLayout; auto btnLyt = new QHBoxLayout;
lyt->addLayout(btnLyt); lyt->addLayout(btnLyt);
btnLyt->addWidget(new QPushButton("Reset")); auto reset = new QPushButton("Reset");
btnLyt->addWidget(new QPushButton("Resume/Pause")); auto togglePlay = new QPushButton("Resume");
connect(reset, &QPushButton::clicked, this, &MainWindow::resetSimulationControl);
connect(togglePlay, &QPushButton::clicked, this, &MainWindow::toggleSimulation);
connect(togglePlay, &QPushButton::clicked, [this, togglePlay](){
togglePlay->setText(simulation->isPlaying ? "Pause" : "Resume");
});
btnLyt->addWidget(reset);
btnLyt->addWidget(togglePlay);
return w; return w;
} }
@ -229,3 +367,21 @@ void MainWindow::normalizeLengths() {
dynamic_cast<Slider<double> *>(segmentGrid->itemAtPosition(i + 1, 4)->widget())->setFromTarget(); dynamic_cast<Slider<double> *>(segmentGrid->itemAtPosition(i + 1, 4)->widget())->setFromTarget();
} }
} }
void MainWindow::add() {
}
void MainWindow::remove() {
}
void MainWindow::resetSimulationControl() {
gravitySlider->setValue(981);
timescaleSlider->setValue(100);
substepsSlider->setValue(30);
}
void MainWindow::toggleSimulation() {
simulation->isPlaying = !simulation->isPlaying;
}

@ -1,5 +1,6 @@
#include <QWidget> #include <QWidget>
#include <vector> #include <vector>
#include "Slider.h"
class Simulation; class Simulation;
class QGridLayout; class QGridLayout;
@ -22,7 +23,7 @@ private:
std::vector<double> masses, lengths; std::vector<double> masses, lengths;
int segments = 0; int segments = 0;
double startingAngle = 90; int startingAngle = 90;
QColor color = Qt::white; QColor color = Qt::white;
bool multiple = false; bool multiple = false;
bool rainbow = false; bool rainbow = false;
@ -32,9 +33,17 @@ private:
double changeAmount = 0.001; double changeAmount = 0.001;
QGridLayout * segmentGrid; QGridLayout * segmentGrid;
Slider<double> * gravitySlider, * timescaleSlider;
Slider<> * substepsSlider;
public slots: public slots:
void resetMasses(); void resetMasses();
void resetLengths(); void resetLengths();
void normalizeLengths(); void normalizeLengths();
void add();
void remove();
void resetSimulationControl();
void toggleSimulation();
}; };

@ -9,10 +9,11 @@ public:
double size = 50; double size = 50;
private:
std::vector<Pendulum *> pendula;
double gravity; double gravity;
double timescale; double timescale;
uint64_t substeps; int substeps;
bool isPlaying = false;
std::vector<Pendulum *> pendula;
}; };
Loading…
Cancel
Save