parent
cf0d2ece41
commit
bdaf7752e9
11 changed files with 361 additions and 3 deletions
@ -0,0 +1,10 @@ |
|||||||
|
#include "GLWidget.h" |
||||||
|
#include <QPainter> |
||||||
|
#include <QPaintEvent> |
||||||
|
#include <iostream> |
||||||
|
|
||||||
|
void GLWidget::paintEvent(QPaintEvent *e) { |
||||||
|
auto p = new QPainter(this); |
||||||
|
p->fillRect(e->rect(), Qt::black); |
||||||
|
p->end(); |
||||||
|
} |
@ -0,0 +1,7 @@ |
|||||||
|
#include <QOpenGLWidget> |
||||||
|
#include <QOpenGLFunctions> |
||||||
|
|
||||||
|
class GLWidget : public QOpenGLWidget, protected QOpenGLFunctions { |
||||||
|
protected: |
||||||
|
void paintEvent(QPaintEvent* e) override; |
||||||
|
}; |
@ -0,0 +1,231 @@ |
|||||||
|
#include "MainWindow.h" |
||||||
|
#include <QHBoxLayout> |
||||||
|
#include "GLWidget.h" |
||||||
|
#include "Simulation.h" |
||||||
|
#include <QPushButton> |
||||||
|
#include <QLabel> |
||||||
|
#include <QScrollArea> |
||||||
|
#include <QCheckBox> |
||||||
|
#include <QRadioButton> |
||||||
|
#include <QButtonGroup> |
||||||
|
#include <QSizePolicy> |
||||||
|
#include <iostream> |
||||||
|
#include <QDial> |
||||||
|
#include "Slider.h" |
||||||
|
#include <iostream> |
||||||
|
|
||||||
|
MainWindow::MainWindow() { |
||||||
|
simulation = new Simulation; |
||||||
|
|
||||||
|
masses = std::vector<double>(MaxSegments); |
||||||
|
lengths = std::vector<double>(MaxSegments); |
||||||
|
|
||||||
|
resetMasses(); |
||||||
|
resetLengths(); |
||||||
|
|
||||||
|
buildUI(); |
||||||
|
} |
||||||
|
|
||||||
|
void MainWindow::buildUI() { |
||||||
|
setMinimumSize(800, 500); |
||||||
|
|
||||||
|
auto uiLyt = new QVBoxLayout; |
||||||
|
glWidget = new GLWidget; |
||||||
|
|
||||||
|
auto mainLyt = new QHBoxLayout(this); |
||||||
|
mainLyt->addLayout(uiLyt); |
||||||
|
mainLyt->addWidget(glWidget, 1); |
||||||
|
|
||||||
|
auto scrollArea = new QScrollArea; |
||||||
|
scrollArea->setWidget(buildAddUI()); |
||||||
|
scrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); |
||||||
|
scrollArea->setWidgetResizable(true); |
||||||
|
|
||||||
|
auto l1 = new QLabel("Add Pendula"); |
||||||
|
auto l2 = new QLabel("Simulation"); |
||||||
|
auto f = l1->font(); |
||||||
|
f.setBold(true); |
||||||
|
f.setPixelSize(15); |
||||||
|
l1->setFont(f); |
||||||
|
l2->setFont(f); |
||||||
|
|
||||||
|
uiLyt->addWidget(l1); |
||||||
|
uiLyt->addWidget(scrollArea); |
||||||
|
uiLyt->addSpacing(30); |
||||||
|
uiLyt->addWidget(l2); |
||||||
|
uiLyt->addWidget(buildSimulationUI()); |
||||||
|
} |
||||||
|
|
||||||
|
QWidget * MainWindow::buildAddUI() { |
||||||
|
auto w = new QWidget; |
||||||
|
|
||||||
|
auto lyt = new QVBoxLayout(w); |
||||||
|
lyt->setAlignment(Qt::AlignTop); |
||||||
|
|
||||||
|
auto segLabel = new QLabel(); |
||||||
|
auto segmentSlider = new Slider<>(segLabel, "Segments: %d", &segments); |
||||||
|
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); |
||||||
|
|
||||||
|
auto segmentButtonGrid = new QHBoxLayout; |
||||||
|
lyt->addLayout(segmentButtonGrid); |
||||||
|
|
||||||
|
auto resetMassesBtn = new QPushButton("Reset"); |
||||||
|
auto normalizeLengthsBtn = new QPushButton("Normalize"); |
||||||
|
auto resetLengthsBtn = new QPushButton("Reset"); |
||||||
|
|
||||||
|
connect(resetMassesBtn, &QPushButton::clicked, this, &MainWindow::resetMasses); |
||||||
|
connect(normalizeLengthsBtn, &QPushButton::clicked, this, &MainWindow::normalizeLengths); |
||||||
|
connect(resetLengthsBtn, &QPushButton::clicked, this, &MainWindow::resetLengths); |
||||||
|
|
||||||
|
segmentButtonGrid->addWidget(resetMassesBtn); |
||||||
|
segmentButtonGrid->addWidget(normalizeLengthsBtn); |
||||||
|
segmentButtonGrid->addWidget(resetLengthsBtn); |
||||||
|
|
||||||
|
lyt->addWidget(new QLabel("Starting Angle:")); |
||||||
|
auto dial = new QDial; |
||||||
|
dial->setMinimum(0); |
||||||
|
dial->setMaximum(360); |
||||||
|
dial->setMinimumSize(100, 100); |
||||||
|
lyt->addWidget(dial); |
||||||
|
|
||||||
|
auto colorLyt = new QHBoxLayout; |
||||||
|
lyt->addLayout(colorLyt); |
||||||
|
|
||||||
|
colorLyt->addWidget(new QPushButton("Select color")); |
||||||
|
colorLyt->addWidget(new QLabel("Color this label")); |
||||||
|
|
||||||
|
lyt->addWidget(new QCheckBox("Add multiple")); |
||||||
|
lyt->addWidget(new QCheckBox("Use rainbow coloring")); |
||||||
|
|
||||||
|
lyt->addWidget(new QLabel("Count:")); |
||||||
|
// lyt->addWidget(new Slider(nullptr, [](int v) {return "";}));
|
||||||
|
|
||||||
|
lyt->addWidget(new QLabel("Property to change slightly:")); |
||||||
|
auto group = new QButtonGroup; |
||||||
|
auto b1 = new QRadioButton("Starting Angle"); |
||||||
|
auto b2 = new QRadioButton("Starting Mass"); |
||||||
|
auto b3 = new QRadioButton("Starting Length"); |
||||||
|
group->addButton(b1); |
||||||
|
group->addButton(b2); |
||||||
|
group->addButton(b3); |
||||||
|
lyt->addWidget(b1); |
||||||
|
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; |
||||||
|
lyt->addLayout(btnLyt); |
||||||
|
|
||||||
|
btnLyt->addWidget(new QPushButton("Add")); |
||||||
|
btnLyt->addWidget(new QPushButton("Remove")); |
||||||
|
|
||||||
|
return w; |
||||||
|
} |
||||||
|
|
||||||
|
QWidget * MainWindow::buildSimulationUI() { |
||||||
|
auto w = new QWidget; |
||||||
|
|
||||||
|
auto lyt = new QVBoxLayout(w); |
||||||
|
lyt->addWidget(new QLabel("Gravity:")); |
||||||
|
// lyt->addWidget(new Slider(nullptr, [](int v) {return "";}));
|
||||||
|
lyt->addWidget(new QLabel("Timescale:")); |
||||||
|
// lyt->addWidget(new Slider(nullptr, [](int v) {return "";}));
|
||||||
|
lyt->addWidget(new QLabel("PBD Substeps:")); |
||||||
|
// lyt->addWidget(new Slider(nullptr, [](int v) {return "";}));
|
||||||
|
|
||||||
|
auto btnLyt = new QHBoxLayout; |
||||||
|
lyt->addLayout(btnLyt); |
||||||
|
|
||||||
|
btnLyt->addWidget(new QPushButton("Reset")); |
||||||
|
btnLyt->addWidget(new QPushButton("Resume/Pause")); |
||||||
|
|
||||||
|
return w; |
||||||
|
} |
||||||
|
|
||||||
|
void MainWindow::resetMasses() { |
||||||
|
for (int i = 0; i < MaxSegments; i++) |
||||||
|
masses[i] = 1; |
||||||
|
for (int i = 0; i < segments; i++){ |
||||||
|
dynamic_cast<Slider<double> *>(segmentGrid->itemAtPosition(i + 1, 2)->widget())->setFromTarget(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
void MainWindow::resetLengths() { |
||||||
|
for (int i = 0; i < MaxSegments; i++) |
||||||
|
lengths[i] = 1; |
||||||
|
for (int i = 0; i < segments; i++){ |
||||||
|
dynamic_cast<Slider<double> *>(segmentGrid->itemAtPosition(i + 1, 4)->widget())->setFromTarget(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
void MainWindow::normalizeLengths() { |
||||||
|
double sum = std::reduce(lengths.begin(), lengths.begin() + segments); |
||||||
|
double factor = simulation->size / 2 / sum; |
||||||
|
for (int i = 0; i < segments; i++){ |
||||||
|
lengths[i] *= factor; |
||||||
|
} |
||||||
|
for (int i = 0; i < segments; i++){ |
||||||
|
dynamic_cast<Slider<double> *>(segmentGrid->itemAtPosition(i + 1, 4)->widget())->setFromTarget(); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,40 @@ |
|||||||
|
#include <QWidget> |
||||||
|
#include <vector> |
||||||
|
|
||||||
|
class Simulation; |
||||||
|
class QGridLayout; |
||||||
|
class GLWidget; |
||||||
|
|
||||||
|
enum Property {Angle, Mass, Length}; |
||||||
|
|
||||||
|
class MainWindow : public QWidget { |
||||||
|
public: |
||||||
|
explicit MainWindow(); |
||||||
|
private: |
||||||
|
void buildUI(); |
||||||
|
QWidget * buildAddUI(); |
||||||
|
QWidget * buildSimulationUI(); |
||||||
|
Simulation * simulation; |
||||||
|
|
||||||
|
GLWidget * glWidget; |
||||||
|
|
||||||
|
static const int MaxSegments = 50; |
||||||
|
|
||||||
|
std::vector<double> masses, lengths; |
||||||
|
int segments = 0; |
||||||
|
double startingAngle = 90; |
||||||
|
QColor color = Qt::white; |
||||||
|
bool multiple = false; |
||||||
|
bool rainbow = false; |
||||||
|
int count = 100; |
||||||
|
Property changeProperty = Angle; |
||||||
|
int changeIndex = 0; |
||||||
|
double changeAmount = 0.001; |
||||||
|
|
||||||
|
QGridLayout * segmentGrid; |
||||||
|
|
||||||
|
public slots: |
||||||
|
void resetMasses(); |
||||||
|
void resetLengths(); |
||||||
|
void normalizeLengths(); |
||||||
|
}; |
@ -0,0 +1 @@ |
|||||||
|
#include "Pendulum.h" |
@ -0,0 +1,3 @@ |
|||||||
|
class Pendulum { |
||||||
|
|
||||||
|
}; |
@ -0,0 +1,3 @@ |
|||||||
|
#include "Simulation.h" |
||||||
|
|
||||||
|
Simulation::Simulation() = default; |
@ -0,0 +1,18 @@ |
|||||||
|
#include <vector> |
||||||
|
#include <cstdint> |
||||||
|
|
||||||
|
class Pendulum; |
||||||
|
|
||||||
|
class Simulation { |
||||||
|
public: |
||||||
|
explicit Simulation(); |
||||||
|
|
||||||
|
double size = 50; |
||||||
|
|
||||||
|
private: |
||||||
|
std::vector<Pendulum *> pendula; |
||||||
|
|
||||||
|
double gravity; |
||||||
|
double timescale; |
||||||
|
uint64_t substeps; |
||||||
|
}; |
@ -0,0 +1,2 @@ |
|||||||
|
#include "Slider.h" |
||||||
|
#include <QLabel> |
@ -0,0 +1,37 @@ |
|||||||
|
#include <QSlider> |
||||||
|
#include <functional> |
||||||
|
#include <QLabel> |
||||||
|
|
||||||
|
template<typename T = int> |
||||||
|
class Slider : public QSlider { |
||||||
|
public: |
||||||
|
explicit Slider(QLabel* label, const char *format, T * target, |
||||||
|
const std::function<T(int)> &convertToT = [](int v){ return T(v); }, |
||||||
|
const std::function<int(T)> &convertFromT = [](T v){ return int(v); }) : QSlider(Qt::Horizontal) { |
||||||
|
this->convertToT = convertToT; |
||||||
|
this->convertFromT = convertFromT; |
||||||
|
this->target = target; |
||||||
|
|
||||||
|
connect(this, &QSlider::valueChanged, this, [=, this](int newValue){ |
||||||
|
T convertedValue = convertToT(newValue); |
||||||
|
|
||||||
|
char buff[100]; |
||||||
|
snprintf(buff, sizeof(buff), format, convertedValue); |
||||||
|
label->setText(buff); |
||||||
|
|
||||||
|
targetOld = *target; |
||||||
|
*target = convertedValue; |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
void setFromTarget(){ |
||||||
|
setValue(convertFromT(*target)); |
||||||
|
} |
||||||
|
|
||||||
|
T targetOld; |
||||||
|
|
||||||
|
private: |
||||||
|
T * target; |
||||||
|
std::function<T(int)> convertToT; |
||||||
|
std::function<int(T)> convertFromT; |
||||||
|
}; |
@ -1,10 +1,16 @@ |
|||||||
#include <iostream> |
|
||||||
#include <Eigen/Core> |
|
||||||
#include <QApplication> |
#include <QApplication> |
||||||
#include <QWidget> |
#include <QSurfaceFormat> |
||||||
|
#include "MainWindow.h" |
||||||
|
|
||||||
int main(int argc, char* argv[]) { |
int main(int argc, char* argv[]) { |
||||||
QApplication app(argc, argv); |
QApplication app(argc, argv); |
||||||
|
|
||||||
|
QSurfaceFormat fmt; |
||||||
|
fmt.setSamples(4); |
||||||
|
QSurfaceFormat::setDefaultFormat(fmt); |
||||||
|
|
||||||
|
MainWindow w; |
||||||
|
w.show(); |
||||||
|
|
||||||
return QApplication::exec(); |
return QApplication::exec(); |
||||||
} |
} |
||||||
|
Loading…
Reference in new issue