|
|
@ -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,8 +62,10 @@ 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("Mass"), 0, 1); |
|
|
@ -120,7 +122,10 @@ QWidget * MainWindow::buildAddUI() { |
|
|
|
segmentSlider->setValue(2); |
|
|
|
segmentSlider->setValue(2); |
|
|
|
segmentSlider->setMinimum(1); |
|
|
|
segmentSlider->setMinimum(1); |
|
|
|
segmentSlider->setMaximum(MaxSegments); |
|
|
|
segmentSlider->setMaximum(MaxSegments); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Buttons for Segments
|
|
|
|
|
|
|
|
{ |
|
|
|
auto segmentButtonGrid = new QHBoxLayout; |
|
|
|
auto segmentButtonGrid = new QHBoxLayout; |
|
|
|
lyt->addLayout(segmentButtonGrid); |
|
|
|
lyt->addLayout(segmentButtonGrid); |
|
|
|
|
|
|
|
|
|
|
@ -135,50 +140,149 @@ QWidget * MainWindow::buildAddUI() { |
|
|
|
segmentButtonGrid->addWidget(resetMassesBtn); |
|
|
|
segmentButtonGrid->addWidget(resetMassesBtn); |
|
|
|
segmentButtonGrid->addWidget(normalizeLengthsBtn); |
|
|
|
segmentButtonGrid->addWidget(normalizeLengthsBtn); |
|
|
|
segmentButtonGrid->addWidget(resetLengthsBtn); |
|
|
|
segmentButtonGrid->addWidget(resetLengthsBtn); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
lyt->addWidget(new QLabel("Starting Angle:")); |
|
|
|
// Starting Angle
|
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
auto angleLabel = new QLabel; |
|
|
|
auto dial = new QDial; |
|
|
|
auto dial = new QDial; |
|
|
|
|
|
|
|
dial->setWrapping(true); |
|
|
|
dial->setMinimum(0); |
|
|
|
dial->setMinimum(0); |
|
|
|
dial->setMaximum(360); |
|
|
|
dial->setMaximum(360); |
|
|
|
dial->setMinimumSize(100, 100); |
|
|
|
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); |
|
|
|
lyt->addWidget(dial); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Color
|
|
|
|
|
|
|
|
{ |
|
|
|
auto colorLyt = new QHBoxLayout; |
|
|
|
auto colorLyt = new QHBoxLayout; |
|
|
|
lyt->addLayout(colorLyt); |
|
|
|
lyt->addLayout(colorLyt); |
|
|
|
|
|
|
|
|
|
|
|
colorLyt->addWidget(new QPushButton("Select color")); |
|
|
|
auto colorBtn = new QPushButton("Select color"); |
|
|
|
colorLyt->addWidget(new QLabel("Color this label")); |
|
|
|
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); |
|
|
|
|
|
|
|
|
|
|
|
lyt->addWidget(new QCheckBox("Add multiple")); |
|
|
|
auto multipleLyt = new QVBoxLayout; |
|
|
|
lyt->addWidget(new QCheckBox("Use rainbow coloring")); |
|
|
|
multipleWidget->setLayout(multipleLyt); |
|
|
|
|
|
|
|
multipleWidget->setVisible(false); |
|
|
|
|
|
|
|
|
|
|
|
lyt->addWidget(new QLabel("Count:")); |
|
|
|
// Rainbow coloring
|
|
|
|
// lyt->addWidget(new Slider(nullptr, [](int v) {return "";}));
|
|
|
|
{ |
|
|
|
|
|
|
|
auto rainbowCheckbox = new QCheckBox("Use rainbow coloring instead"); |
|
|
|
|
|
|
|
connect(rainbowCheckbox, &QCheckBox::stateChanged, [this](int state){ |
|
|
|
|
|
|
|
rainbow = state == Qt::Checked; |
|
|
|
|
|
|
|
}); |
|
|
|
|
|
|
|
multipleLyt->addWidget(rainbowCheckbox); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Count
|
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
auto countLabel = new QLabel; |
|
|
|
|
|
|
|
auto countSlider = new Slider<>(countLabel, "Count: %d", &count); |
|
|
|
|
|
|
|
countSlider->setMaximum(1000); |
|
|
|
|
|
|
|
countSlider->setValue(count); |
|
|
|
|
|
|
|
countSlider->setMinimum(5); |
|
|
|
|
|
|
|
|
|
|
|
lyt->addWidget(new QLabel("Property to change slightly:")); |
|
|
|
multipleLyt->addWidget(countLabel); |
|
|
|
|
|
|
|
multipleLyt->addWidget(countSlider); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Change
|
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
multipleLyt->addWidget(new QLabel("Property to change slightly:")); |
|
|
|
auto group = new QButtonGroup; |
|
|
|
auto group = new QButtonGroup; |
|
|
|
auto b1 = new QRadioButton("Starting Angle"); |
|
|
|
auto b1 = new QRadioButton("Starting Angle"); |
|
|
|
auto b2 = new QRadioButton("Starting Mass"); |
|
|
|
auto b2 = new QRadioButton("Specific Mass"); |
|
|
|
auto b3 = new QRadioButton("Starting Length"); |
|
|
|
auto b3 = new QRadioButton("Specific Length"); |
|
|
|
group->addButton(b1); |
|
|
|
group->addButton(b1, 0); |
|
|
|
group->addButton(b2); |
|
|
|
group->addButton(b2, 1); |
|
|
|
group->addButton(b3); |
|
|
|
group->addButton(b3, 2); |
|
|
|
lyt->addWidget(b1); |
|
|
|
multipleLyt->addWidget(b1); |
|
|
|
lyt->addWidget(b2); |
|
|
|
multipleLyt->addWidget(b2); |
|
|
|
lyt->addWidget(b3); |
|
|
|
multipleLyt->addWidget(b3); |
|
|
|
b1->setChecked(true); |
|
|
|
b1->setChecked(true); |
|
|
|
|
|
|
|
|
|
|
|
lyt->addWidget(new QLabel("Index:")); |
|
|
|
auto indexLabel = new QLabel; |
|
|
|
// lyt->addWidget(new Slider(nullptr, [](int v) {return "";}));
|
|
|
|
indexLabel->setVisible(false); |
|
|
|
|
|
|
|
auto indexSlider = new Slider<>(indexLabel, "Index: %d", &changeIndex); |
|
|
|
|
|
|
|
indexSlider->setVisible(false); |
|
|
|
|
|
|
|
indexSlider->setValue(1); |
|
|
|
|
|
|
|
indexSlider->setValue(0); |
|
|
|
|
|
|
|
indexSlider->setMaximum(segments - 1); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
connect(segmentSlider, &QSlider::valueChanged, [this, indexSlider](){ |
|
|
|
|
|
|
|
indexSlider->setMaximum(segments - 1); |
|
|
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
connect(group, &QButtonGroup::idClicked, [this, indexLabel, indexSlider](int id){ |
|
|
|
|
|
|
|
changeProperty = static_cast<Property>(id); |
|
|
|
|
|
|
|
bool indexVisible = changeProperty == Mass || changeProperty == Length; |
|
|
|
|
|
|
|
indexLabel->setVisible(indexVisible); |
|
|
|
|
|
|
|
indexSlider->setVisible(indexVisible); |
|
|
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
lyt->addWidget(new QLabel("Change Amount:")); |
|
|
|
multipleLyt->addWidget(indexLabel); |
|
|
|
// lyt->addWidget(new Slider(nullptr, [](int v) {return "";}));
|
|
|
|
multipleLyt->addWidget(indexSlider); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
auto amountLabel = new QLabel; |
|
|
|
|
|
|
|
auto amountSlider = new Slider<double>(amountLabel, "Change Amount: %5.3f%%", &changeAmount, [](int v){ |
|
|
|
|
|
|
|
return double(v) / 1000; |
|
|
|
|
|
|
|
}, [](double v){ |
|
|
|
|
|
|
|
return int(v * 1000); |
|
|
|
|
|
|
|
}); |
|
|
|
|
|
|
|
amountSlider->setMinimum(1); |
|
|
|
|
|
|
|
amountSlider->setMaximum(1000); |
|
|
|
|
|
|
|
multipleLyt->addWidget(amountLabel); |
|
|
|
|
|
|
|
multipleLyt->addWidget(amountSlider); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
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; |
|
|
|
|
|
|
|
} |
|
|
|