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 <QWidget> |
||||
#include <QSurfaceFormat> |
||||
#include "MainWindow.h" |
||||
|
||||
int main(int argc, char* argv[]) { |
||||
QApplication app(argc, argv); |
||||
|
||||
QSurfaceFormat fmt; |
||||
fmt.setSamples(4); |
||||
QSurfaceFormat::setDefaultFormat(fmt); |
||||
|
||||
MainWindow w; |
||||
w.show(); |
||||
|
||||
return QApplication::exec(); |
||||
} |
||||
|
Loading…
Reference in new issue