diff --git a/particle-track-and-trace/src/CMakeLists.txt b/particle-track-and-trace/src/CMakeLists.txt index cf6696a..ff62643 100644 --- a/particle-track-and-trace/src/CMakeLists.txt +++ b/particle-track-and-trace/src/CMakeLists.txt @@ -5,8 +5,14 @@ project(ParticleTrackTrace) set(CMAKE_CXX_STANDARD 23) set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_AUTOMOC ON) set(CMAKE_EXPORT_COMPILE_COMMANDS ON) +find_package(Qt5Core REQUIRED) +find_package(Qt5Gui REQUIRED) +find_package(Qt5Widgets REQUIRED) +find_package(Qt5OpenGL REQUIRED) + find_package(VTK COMPONENTS GeovisCore CommonColor @@ -48,8 +54,6 @@ add_executable(ParticleTrackTrace MACOSX_BUNDLE main.cpp layers/Layer.h layers/LGlyphLayer.cpp layers/LGlyphLayer.h - layers/LColLayer.cpp - layers/LColLayer.h Program.cpp Program.h commands/TimerCallbackCommand.h @@ -74,6 +78,10 @@ add_executable(ParticleTrackTrace MACOSX_BUNDLE main.cpp advection/Vel.h advection/kernel/SnapBoundaryConditionKernel.h advection/kernel/SnapBoundaryConditionKernel.cpp + QT/MainWindow.h + QT/MainWindow.cpp + QT/MainWindow.ui + QT/ui_mainwindow.h ) execute_process( @@ -94,7 +102,7 @@ find_library(NETCDF_LIB NAMES netcdf-cxx4 netcdf_c++4 PATHS ${NETCDFCXX_LIB_DIR} # Prevent a "command line is too long" failure in Windows. set(CMAKE_NINJA_FORCE_RESPONSE_FILE "ON" CACHE BOOL "Force Ninja to use response files.") -target_link_libraries(ParticleTrackTrace ${NETCDF_LIB} ${VTK_LIBRARIES}) +target_link_libraries(ParticleTrackTrace ${NETCDF_LIB} ${VTK_LIBRARIES} Qt5::Widgets Qt5::OpenGL VTK::GUISupportQt VTK::IOLegacy) # vtk_module_autoinit is needed vtk_module_autoinit( diff --git a/particle-track-and-trace/src/Program.cpp b/particle-track-and-trace/src/Program.cpp index bae7b2c..c75b93e 100644 --- a/particle-track-and-trace/src/Program.cpp +++ b/particle-track-and-trace/src/Program.cpp @@ -14,6 +14,7 @@ #include #include #include +#include #include #include @@ -27,11 +28,11 @@ void Program::setWinProperties() { this->win->SetSize(661, 661); this->win->SetDesiredUpdateRate(60); - this->interact->SetRenderWindow(this->win); + this->interact->SetRenderWindow(this->win.Get()); this->interact->Initialize(); vtkNew style; - interact->SetInteractorStyle(style); + this->interact->SetInteractorStyle(style); } void Program::setupTimer(int dt) { @@ -50,15 +51,17 @@ void Program::setupCameraCallback() { this->interact->AddObserver(vtkCommand::KeyPressEvent, callback); } - -Program::Program(int timerDT) { - this->win = vtkSmartPointer::New(); - this->interact = vtkSmartPointer::New(); +Program::Program(QWidget *parent): QVTKOpenGLNativeWidget(parent) { + this->win = vtkSmartPointer::New(); + // this->interact = vtkSmartPointer::New(); + // this->interact = vtkSmartPointer::New(); + setRenderWindow(this->win); + this->interact = win->GetInteractor(); this->cam = createNormalisedCamera(); this->win->SetNumberOfLayers(0); setWinProperties(); - setupTimer(timerDT); + setupTimer(60 * 60); // FIXME: manually tracking this variable is a little stupid. setupCameraCallback(); } @@ -91,7 +94,7 @@ void Program::updateData(int t) { void Program::setupInteractions() { for (Layer *l: layers) { - l->addObservers(interact); + l->addObservers(this->interact); } } @@ -100,3 +103,7 @@ void Program::render() { win->Render(); interact->Start(); } + +Program::~Program() { + cout << "deleting program" << endl; +} diff --git a/particle-track-and-trace/src/Program.h b/particle-track-and-trace/src/Program.h index 5009957..60fc6e3 100644 --- a/particle-track-and-trace/src/Program.h +++ b/particle-track-and-trace/src/Program.h @@ -1,6 +1,7 @@ #ifndef PROGRAM_H #define PROGRAM_H +#include #include #include #include @@ -10,7 +11,9 @@ /** This class manages the upper levels of the vtk pipeline; it has attributes for the vtkrenderWindow and a vector of Layers to represent a variable number of vtkRenderers. * It can also set up a vtkTimer by connecting an instance of TimerCallbackCommand with its contained vtkRenderWindowInteractor. */ -class Program { +class Program : public QVTKOpenGLNativeWidget { + Q_OBJECT + private: /** This attribute models a variable number of vtkRenderers, managed through the abstract Layer class. */ @@ -18,11 +21,13 @@ private: /** The window this program's layers render to. */ - vtkSmartPointer win; + // vtkSmartPointer win; + vtkSmartPointer win; /** The interactor through which the layers can interact with the window. */ vtkSmartPointer interact; + // vtkSmartPointer interact; /** The camera used by all layers for this program. @@ -37,9 +42,6 @@ private: */ void setupTimer(int dt); - /** This function adds all interactors of each layer to the interactor/window - */ - void setupInteractions(); /** This function sets up the camera's associated movement callbacks.. */ @@ -48,7 +50,8 @@ private: public: /** Constructor. */ - Program(int timerDT); + Program(QWidget *parent = nullptr); + ~Program() override; /** This function adds a new layer (and thus vtkRenderer) to the program. * The layer is expected to set its own position in the vtkRenderWindow layer system. @@ -68,11 +71,16 @@ public: */ void updateData(int t); + /** This function adds all interactors of each layer to the interactor/window + */ + void setupInteractions(); + /** * This function renders the vtkRenderWindow for the first time. * Only call this function once! */ void render(); + }; #endif diff --git a/particle-track-and-trace/src/QT/MainWindow.cpp b/particle-track-and-trace/src/QT/MainWindow.cpp new file mode 100644 index 0000000..3a5afee --- /dev/null +++ b/particle-track-and-trace/src/QT/MainWindow.cpp @@ -0,0 +1,156 @@ +#include "MainWindow.h" +#include "ui_mainwindow.h" + +#include +#include +#include + +#include + +#include "../layers/BackgroundImage.h" +#include "../layers/EColLayer.h" +#include "../layers/EGlyphLayer.h" +#include "../layers/LGlyphLayer.h" +#include "../Program.h" +#include "../advection/UVGrid.h" +#include "../advection/kernel/RK4AdvectionKernel.h" +#include "../advection/kernel/SnapBoundaryConditionKernel.h" +using namespace std; + +MainWindow::MainWindow(QWidget* parent) + : QMainWindow(parent) + , ui(new Ui::MainWindow) +{ + ui->setupUi(this); + + cout << "Reading data..." << endl; + string dataPath = "../../../../data"; + shared_ptr uvGrid = make_shared(dataPath); + auto kernelRK4 = make_unique(uvGrid); + auto kernelRK4BoundaryChecked = make_unique(std::move(kernelRK4), uvGrid); + cout << "Starting vtk..." << endl; + + auto l = new LGlyphLayer(uvGrid, std::move(kernelRK4BoundaryChecked)); + // l->spoofPoints(); + l->setDt(3600); + // TODO: implement feature to call this function on widget + // l->cycleGlyphStyle(); + + Program *program = this->ui->program; + program->addLayer(new BackgroundImage(dataPath + "/map_qgis_1035.png")); + // TODO: implement feature to cycle between layers thru QT + program->addLayer(new EGlyphLayer(uvGrid)); + program->addLayer(new EColLayer(uvGrid)); + program->addLayer(l); + + program->setupInteractions(); +} + +MainWindow::~MainWindow() { + delete ui; +} + + +/* -------------------------------------------------------------------- + * + QTWidget callbacks + + * --------------------------------------------------------------------*/ + + +void MainWindow::on_FirstButton_clicked(bool checked) { + if (checked) { + cout << "clicked button 1!" << endl; + } +} + + +void MainWindow::on_SecondButton_clicked(bool checked) { + if (checked) { + cout << "clicked button 2!" << endl; + + } +} + + +void MainWindow::on_ComplementaryButton_clicked(bool checked) { + if (checked) { + + } +} + + +void MainWindow::on_ContrastingButton_clicked(bool checked) { + if (checked) { + + } +} + + +void MainWindow::on_MonochromaticButton_clicked(bool checked) { + if (checked) { + + } +} + + +void MainWindow::on_SaturateButton_clicked(bool checked) { + if (checked) { + + } +} + + +void MainWindow::on_DesaturateButton_clicked(bool checked) { + if (checked) { + + } +} + + +void MainWindow::on_CircleButton_clicked(bool checked) { + if (checked) { + + } +} + + +void MainWindow::on_TriangleButton_clicked(bool checked) { + if (checked) { + + } +} + + +void MainWindow::on_SquareButton_clicked(bool checked) { + if (checked) { + + } +} + + +void MainWindow::on_HexagonButton_clicked(bool checked) { + if (checked) { + + } +} + + +void MainWindow::on_FullySampledButton_clicked(bool checked) { + if (checked) { + + } +} + + +void MainWindow::on_RegularlySubsampledButton_clicked(bool checked) { + if (checked) { + + } +} + + +void MainWindow::on_IregularlySubsampledButton_clicked(bool checked) { + if (checked) { + + }} + + diff --git a/particle-track-and-trace/src/QT/MainWindow.h b/particle-track-and-trace/src/QT/MainWindow.h new file mode 100644 index 0000000..ef9a5cf --- /dev/null +++ b/particle-track-and-trace/src/QT/MainWindow.h @@ -0,0 +1,37 @@ +#ifndef MAINWINDOW_H +#define MAINWINDOW_H + +#include + +namespace Ui { +class MainWindow; +} + +class MainWindow : public QMainWindow { + Q_OBJECT + +public: + explicit MainWindow(QWidget* parent = 0); + ~MainWindow(); + +private slots: + void on_FirstButton_clicked(bool checked); + void on_SecondButton_clicked(bool checked); + void on_ComplementaryButton_clicked(bool checked); + void on_ContrastingButton_clicked(bool checked); + void on_MonochromaticButton_clicked(bool checked); + void on_SaturateButton_clicked(bool checked); + void on_DesaturateButton_clicked(bool checked); + void on_CircleButton_clicked(bool checked); + void on_TriangleButton_clicked(bool checked); + void on_SquareButton_clicked(bool checked); + void on_HexagonButton_clicked(bool checked); + void on_FullySampledButton_clicked(bool checked); + void on_RegularlySubsampledButton_clicked(bool checked); + void on_IregularlySubsampledButton_clicked(bool checked); + +private: + Ui::MainWindow* ui; +}; + +#endif diff --git a/particle-track-and-trace/src/QT/MainWindow.ui b/particle-track-and-trace/src/QT/MainWindow.ui new file mode 100644 index 0000000..95eb54a --- /dev/null +++ b/particle-track-and-trace/src/QT/MainWindow.ui @@ -0,0 +1,355 @@ + + + MainWindow + + + + 0 + 0 + 1048 + 772 + + + + Simulation + + + + + + + + 0 + 0 + + + + + 220 + 0 + + + + + 280 + 16777215 + + + + Settings + + + + + + + 0 + 0 + + + + + 0 + 0 + + + + + 500 + 300 + + + + Technique + + + + + + ECol + LGlyph + + + true + + + + + + + EGlyph + LCol + + + false + + + + + + + + + + + 0 + 550 + + + + Channel Options + + + + + 0 + 30 + 300 + 120 + + + + + 0 + 0 + + + + + 250 + 0 + + + + + 500 + 120 + + + + Color + + + Qt::AlignmentFlag::AlignLeading|Qt::AlignmentFlag::AlignLeft|Qt::AlignmentFlag::AlignVCenter + + + + + + Complementary + + + + + + + + 0 + 0 + + + + Contrasting + + + + + + + Monochromatic + + + + + + + + + 0 + 160 + 269 + 100 + + + + + 0 + 0 + + + + + 260 + 0 + + + + + 500 + 100 + + + + Saturation + + + Qt::AlignmentFlag::AlignLeading|Qt::AlignmentFlag::AlignLeft|Qt::AlignmentFlag::AlignVCenter + + + + + + Fully saturated + + + + + + + Desaturated + + + + + + + + + 0 + 270 + 260 + 150 + + + + + 0 + 0 + + + + + 260 + 0 + + + + + 500 + 150 + + + + Glyph Shape + + + Qt::AlignmentFlag::AlignLeading|Qt::AlignmentFlag::AlignLeft|Qt::AlignmentFlag::AlignVCenter + + + + + + Circle + + + + + + + Triangle + + + + + + + Square + + + + + + + Hexagon + + + + + + + + + 0 + 430 + 260 + 120 + + + + + 0 + 0 + + + + + 260 + 0 + + + + + 500 + 120 + + + + Glyph count + + + Qt::AlignmentFlag::AlignLeading|Qt::AlignmentFlag::AlignLeft|Qt::AlignmentFlag::AlignVCenter + + + + + + Fully sampled + + + + + + + Regularly subsampled + + + + + + + Irregularly sampled + + + + + + + + + + + + + + + + + + + + MainView + QVTKOpenGLNativeWidget +
Program.h
+
+
+ + +
diff --git a/particle-track-and-trace/src/QT/ui_mainwindow.h b/particle-track-and-trace/src/QT/ui_mainwindow.h new file mode 100644 index 0000000..dd3ff49 --- /dev/null +++ b/particle-track-and-trace/src/QT/ui_mainwindow.h @@ -0,0 +1,278 @@ +/******************************************************************************** +** Form generated from reading UI file 'MainWindow.ui' +** +** Created by: Qt User Interface Compiler version 5.15.13 +** +** WARNING! All changes made in this file will be lost when recompiling UI file! +********************************************************************************/ + +#ifndef UI_MAINWINDOW_H +#define UI_MAINWINDOW_H + +#include "../Program.h" +#include +#include +#include +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class Ui_MainWindow +{ +public: + QWidget *centralWidget; + QHBoxLayout *horizontalLayout; + QGroupBox *settingsBox; + QVBoxLayout *verticalLayout_3; + QGroupBox *TechniqueBox; + QVBoxLayout *verticalLayout_2; + QRadioButton *FirstButton; + QRadioButton *SecondButton; + QGroupBox *ChannelBox; + QGroupBox *ColourBox; + QVBoxLayout *verticalLayout_12; + QRadioButton *ComplementaryButton; + QRadioButton *ContrastingButton; + QRadioButton *MonochromaticButton; + QGroupBox *SaturationBox; + QVBoxLayout *verticalLayout_13; + QRadioButton *SaturateButton; + QRadioButton *DesaturateButton; + QGroupBox *GlyphBox; + QVBoxLayout *verticalLayout_14; + QRadioButton *CircleButton; + QRadioButton *TriangleButton; + QRadioButton *SquareButton; + QRadioButton *HexagonButton; + QGroupBox *GlyphBox_2; + QVBoxLayout *verticalLayout_16; + QRadioButton *FullySampledButton; + QRadioButton *RegularlySubsampledButton; + QRadioButton *IregularlySubsampledButton; + Program *program; + + void setupUi(QMainWindow *MainWindow) + { + if (MainWindow->objectName().isEmpty()) + MainWindow->setObjectName(QString::fromUtf8("MainWindow")); + MainWindow->resize(1048, 772); + centralWidget = new QWidget(MainWindow); + centralWidget->setObjectName(QString::fromUtf8("centralWidget")); + horizontalLayout = new QHBoxLayout(centralWidget); + horizontalLayout->setSpacing(6); + horizontalLayout->setContentsMargins(11, 11, 11, 11); + horizontalLayout->setObjectName(QString::fromUtf8("horizontalLayout")); + settingsBox = new QGroupBox(centralWidget); + settingsBox->setObjectName(QString::fromUtf8("settingsBox")); + QSizePolicy sizePolicy(QSizePolicy::Fixed, QSizePolicy::Preferred); + sizePolicy.setHorizontalStretch(0); + sizePolicy.setVerticalStretch(0); + sizePolicy.setHeightForWidth(settingsBox->sizePolicy().hasHeightForWidth()); + settingsBox->setSizePolicy(sizePolicy); + settingsBox->setMinimumSize(QSize(220, 0)); + settingsBox->setMaximumSize(QSize(280, 16777215)); + verticalLayout_3 = new QVBoxLayout(settingsBox); + verticalLayout_3->setSpacing(6); + verticalLayout_3->setContentsMargins(11, 11, 11, 11); + verticalLayout_3->setObjectName(QString::fromUtf8("verticalLayout_3")); + TechniqueBox = new QGroupBox(settingsBox); + TechniqueBox->setObjectName(QString::fromUtf8("TechniqueBox")); + sizePolicy.setHeightForWidth(TechniqueBox->sizePolicy().hasHeightForWidth()); + TechniqueBox->setSizePolicy(sizePolicy); + TechniqueBox->setMinimumSize(QSize(0, 0)); + TechniqueBox->setMaximumSize(QSize(500, 100)); + verticalLayout_2 = new QVBoxLayout(TechniqueBox); + verticalLayout_2->setSpacing(6); + verticalLayout_2->setContentsMargins(11, 11, 11, 11); + verticalLayout_2->setObjectName(QString::fromUtf8("verticalLayout_2")); + FirstButton = new QRadioButton(TechniqueBox); + FirstButton->setObjectName(QString::fromUtf8("FirstButton")); + FirstButton->setChecked(true); + + verticalLayout_2->addWidget(FirstButton); + + SecondButton = new QRadioButton(TechniqueBox); + SecondButton->setObjectName(QString::fromUtf8("SecondButton")); + SecondButton->setChecked(false); + + verticalLayout_2->addWidget(SecondButton); + + + verticalLayout_3->addWidget(TechniqueBox); + + ChannelBox = new QGroupBox(settingsBox); + ChannelBox->setObjectName(QString::fromUtf8("ChannelBox")); + ChannelBox->setMinimumSize(QSize(0, 550)); + ColourBox = new QGroupBox(ChannelBox); + ColourBox->setObjectName(QString::fromUtf8("ColourBox")); + ColourBox->setGeometry(QRect(0, 30, 300, 120)); + sizePolicy.setHeightForWidth(ColourBox->sizePolicy().hasHeightForWidth()); + ColourBox->setSizePolicy(sizePolicy); + ColourBox->setMinimumSize(QSize(250, 0)); + ColourBox->setMaximumSize(QSize(500, 120)); + ColourBox->setAlignment(Qt::AlignmentFlag::AlignLeading|Qt::AlignmentFlag::AlignLeft|Qt::AlignmentFlag::AlignVCenter); + verticalLayout_12 = new QVBoxLayout(ColourBox); + verticalLayout_12->setSpacing(6); + verticalLayout_12->setContentsMargins(11, 11, 11, 11); + verticalLayout_12->setObjectName(QString::fromUtf8("verticalLayout_12")); + ComplementaryButton = new QRadioButton(ColourBox); + ComplementaryButton->setObjectName(QString::fromUtf8("ComplementaryButton")); + + verticalLayout_12->addWidget(ComplementaryButton); + + ContrastingButton = new QRadioButton(ColourBox); + ContrastingButton->setObjectName(QString::fromUtf8("ContrastingButton")); + QSizePolicy sizePolicy1(QSizePolicy::Maximum, QSizePolicy::Fixed); + sizePolicy1.setHorizontalStretch(0); + sizePolicy1.setVerticalStretch(0); + sizePolicy1.setHeightForWidth(ContrastingButton->sizePolicy().hasHeightForWidth()); + ContrastingButton->setSizePolicy(sizePolicy1); + + verticalLayout_12->addWidget(ContrastingButton); + + MonochromaticButton = new QRadioButton(ColourBox); + MonochromaticButton->setObjectName(QString::fromUtf8("MonochromaticButton")); + + verticalLayout_12->addWidget(MonochromaticButton); + + SaturationBox = new QGroupBox(ChannelBox); + SaturationBox->setObjectName(QString::fromUtf8("SaturationBox")); + SaturationBox->setGeometry(QRect(0, 160, 269, 100)); + sizePolicy.setHeightForWidth(SaturationBox->sizePolicy().hasHeightForWidth()); + SaturationBox->setSizePolicy(sizePolicy); + SaturationBox->setMinimumSize(QSize(260, 0)); + SaturationBox->setMaximumSize(QSize(500, 100)); + SaturationBox->setAlignment(Qt::AlignmentFlag::AlignLeading|Qt::AlignmentFlag::AlignLeft|Qt::AlignmentFlag::AlignVCenter); + verticalLayout_13 = new QVBoxLayout(SaturationBox); + verticalLayout_13->setSpacing(6); + verticalLayout_13->setContentsMargins(11, 11, 11, 11); + verticalLayout_13->setObjectName(QString::fromUtf8("verticalLayout_13")); + SaturateButton = new QRadioButton(SaturationBox); + SaturateButton->setObjectName(QString::fromUtf8("SaturateButton")); + + verticalLayout_13->addWidget(SaturateButton); + + DesaturateButton = new QRadioButton(SaturationBox); + DesaturateButton->setObjectName(QString::fromUtf8("DesaturateButton")); + + verticalLayout_13->addWidget(DesaturateButton); + + GlyphBox = new QGroupBox(ChannelBox); + GlyphBox->setObjectName(QString::fromUtf8("GlyphBox")); + GlyphBox->setGeometry(QRect(0, 270, 260, 150)); + sizePolicy.setHeightForWidth(GlyphBox->sizePolicy().hasHeightForWidth()); + GlyphBox->setSizePolicy(sizePolicy); + GlyphBox->setMinimumSize(QSize(260, 0)); + GlyphBox->setMaximumSize(QSize(500, 150)); + GlyphBox->setAlignment(Qt::AlignmentFlag::AlignLeading|Qt::AlignmentFlag::AlignLeft|Qt::AlignmentFlag::AlignVCenter); + verticalLayout_14 = new QVBoxLayout(GlyphBox); + verticalLayout_14->setSpacing(6); + verticalLayout_14->setContentsMargins(11, 11, 11, 11); + verticalLayout_14->setObjectName(QString::fromUtf8("verticalLayout_14")); + CircleButton = new QRadioButton(GlyphBox); + CircleButton->setObjectName(QString::fromUtf8("CircleButton")); + + verticalLayout_14->addWidget(CircleButton); + + TriangleButton = new QRadioButton(GlyphBox); + TriangleButton->setObjectName(QString::fromUtf8("TriangleButton")); + + verticalLayout_14->addWidget(TriangleButton); + + SquareButton = new QRadioButton(GlyphBox); + SquareButton->setObjectName(QString::fromUtf8("SquareButton")); + + verticalLayout_14->addWidget(SquareButton); + + HexagonButton = new QRadioButton(GlyphBox); + HexagonButton->setObjectName(QString::fromUtf8("HexagonButton")); + + verticalLayout_14->addWidget(HexagonButton); + + GlyphBox_2 = new QGroupBox(ChannelBox); + GlyphBox_2->setObjectName(QString::fromUtf8("GlyphBox_2")); + GlyphBox_2->setGeometry(QRect(0, 430, 260, 120)); + QSizePolicy sizePolicy2(QSizePolicy::Preferred, QSizePolicy::Preferred); + sizePolicy2.setHorizontalStretch(0); + sizePolicy2.setVerticalStretch(0); + sizePolicy2.setHeightForWidth(GlyphBox_2->sizePolicy().hasHeightForWidth()); + GlyphBox_2->setSizePolicy(sizePolicy2); + GlyphBox_2->setMinimumSize(QSize(260, 0)); + GlyphBox_2->setMaximumSize(QSize(500, 120)); + GlyphBox_2->setAlignment(Qt::AlignmentFlag::AlignLeading|Qt::AlignmentFlag::AlignLeft|Qt::AlignmentFlag::AlignVCenter); + verticalLayout_16 = new QVBoxLayout(GlyphBox_2); + verticalLayout_16->setSpacing(6); + verticalLayout_16->setContentsMargins(11, 11, 11, 11); + verticalLayout_16->setObjectName(QString::fromUtf8("verticalLayout_16")); + FullySampledButton = new QRadioButton(GlyphBox_2); + FullySampledButton->setObjectName(QString::fromUtf8("FullySampledButton")); + + verticalLayout_16->addWidget(FullySampledButton); + + RegularlySubsampledButton = new QRadioButton(GlyphBox_2); + RegularlySubsampledButton->setObjectName(QString::fromUtf8("RegularlySubsampledButton")); + + verticalLayout_16->addWidget(RegularlySubsampledButton); + + IregularlySubsampledButton = new QRadioButton(GlyphBox_2); + IregularlySubsampledButton->setObjectName(QString::fromUtf8("IregularlySubsampledButton")); + + verticalLayout_16->addWidget(IregularlySubsampledButton); + + + verticalLayout_3->addWidget(ChannelBox); + + + horizontalLayout->addWidget(settingsBox); + + program = new Program(centralWidget); + program->setObjectName(QString::fromUtf8("program")); + + horizontalLayout->addWidget(program); + + MainWindow->setCentralWidget(centralWidget); + + retranslateUi(MainWindow); + + QMetaObject::connectSlotsByName(MainWindow); + } // setupUi + + void retranslateUi(QMainWindow *MainWindow) + { + MainWindow->setWindowTitle(QCoreApplication::translate("MainWindow", "Simulation", nullptr)); + settingsBox->setTitle(QCoreApplication::translate("MainWindow", "Settings", nullptr)); + TechniqueBox->setTitle(QCoreApplication::translate("MainWindow", "Technique", nullptr)); + FirstButton->setText(QCoreApplication::translate("MainWindow", "ECol + LGlyph", nullptr)); + SecondButton->setText(QCoreApplication::translate("MainWindow", "EGlyph + LCol", nullptr)); + ChannelBox->setTitle(QCoreApplication::translate("MainWindow", "Channel Options", nullptr)); + ColourBox->setTitle(QCoreApplication::translate("MainWindow", "Color", nullptr)); + ComplementaryButton->setText(QCoreApplication::translate("MainWindow", "Complementary ", nullptr)); + ContrastingButton->setText(QCoreApplication::translate("MainWindow", "Contrasting", nullptr)); + MonochromaticButton->setText(QCoreApplication::translate("MainWindow", "Monochromatic", nullptr)); + SaturationBox->setTitle(QCoreApplication::translate("MainWindow", "Saturation", nullptr)); + SaturateButton->setText(QCoreApplication::translate("MainWindow", "Fully saturated", nullptr)); + DesaturateButton->setText(QCoreApplication::translate("MainWindow", "Desaturated", nullptr)); + GlyphBox->setTitle(QCoreApplication::translate("MainWindow", "Glyph Shape", nullptr)); + CircleButton->setText(QCoreApplication::translate("MainWindow", "Circle", nullptr)); + TriangleButton->setText(QCoreApplication::translate("MainWindow", "Triangle", nullptr)); + SquareButton->setText(QCoreApplication::translate("MainWindow", "Square", nullptr)); + HexagonButton->setText(QCoreApplication::translate("MainWindow", "Hexagon", nullptr)); + GlyphBox_2->setTitle(QCoreApplication::translate("MainWindow", "Glyph count", nullptr)); + FullySampledButton->setText(QCoreApplication::translate("MainWindow", "Fully sampled", nullptr)); + RegularlySubsampledButton->setText(QCoreApplication::translate("MainWindow", "Regularly subsampled", nullptr)); + IregularlySubsampledButton->setText(QCoreApplication::translate("MainWindow", "Irregularly sampled", nullptr)); + } // retranslateUi + +}; + +namespace Ui { + class MainWindow: public Ui_MainWindow {}; +} // namespace Ui + +QT_END_NAMESPACE + +#endif // UI_MAINWINDOW_H diff --git a/particle-track-and-trace/src/layers/EColLayer.cpp b/particle-track-and-trace/src/layers/EColLayer.cpp index e6c5325..ebd59bf 100644 --- a/particle-track-and-trace/src/layers/EColLayer.cpp +++ b/particle-track-and-trace/src/layers/EColLayer.cpp @@ -2,6 +2,7 @@ #include #include #include +#include #include #include #include @@ -24,8 +25,9 @@ #include "../CartographicTransformation.h" -using namespace std; +using std::numbers::pi; +using namespace std; EColLayer::EColLayer(std::shared_ptr uvGrid) { this->ren = vtkSmartPointer::New(); @@ -41,9 +43,53 @@ EColLayer::EColLayer(std::shared_ptr uvGrid) { this->strength->SetNumberOfComponents(1); this->strength->SetNumberOfTuples((numLats-1)*(numLons-1)); + this->direction = vtkSmartPointer::New(); + this->direction->SetName("direction"); + this->direction->SetNumberOfComponents(1); + this->direction->SetNumberOfTuples((numLats-1)*(numLons-1)); + readCoordinates(); } +/** + * Sets a given rgba colour to a range of values [start, end] in the lut. + * @param lut : lookuptable to operate on. + * @ param start : starting index of range to assign + * @ param end: ending index of range to assign + * @param r : red value [0,1] + * @param g : green value [0,1] + * @param n : blue value [0,1] + * @param a : alpha value [0,1] + */ +void setLutRange(vtkSmartPointer lut, int start, int end, double r, double g, double b, double a) { + for (int i=start; i <= end; i++) { + lut->SetTableValue(i, r, g, b, a); + } +} + +// builds a 4-way lookuptable, used to encode the directional component +vtkSmartPointer buildLutDirs() { + vtkNew lut; + lut->SetNumberOfColors(360); + lut->SetTableRange(0, 359); + lut->SetScaleToLinear(); + lut->Build(); + //currently builds a corkO cyclic colour map, divided into 8 colours (see https://www.fabiocrameri.ch/cycliccolourmaps/) + setLutRange(lut, 000, 020, 0.247, 0.243, 0.227, 1); + setLutRange(lut, 021, 060, 0.243, 0.267, 0.365, 1); + setLutRange(lut, 061, 100, 0.318, 0.416, 0.557, 1); + setLutRange(lut, 101, 140, 0.518, 0.620, 0.729, 1); + setLutRange(lut, 141, 180, 0.667, 0.757, 0.773, 1); + setLutRange(lut, 181, 220, 0.631, 0.769, 0.651, 1); + setLutRange(lut, 221, 260, 0.451, 0.639, 0.435, 1); + setLutRange(lut, 261, 300, 0.298, 0.431, 0.224, 1); + setLutRange(lut, 301, 340, 0.263, 0.310, 0.173, 1); + setLutRange(lut, 341, 359, 0.247, 0.243, 0.227, 1); + + lut->SetNanColor(0,0,0,0); + + return lut; +} // TODO: Bit of a superfunction here; can do with some refactoring. void EColLayer::readCoordinates() { @@ -64,27 +110,26 @@ void EColLayer::readCoordinates() { for (auto lat : uvGrid->lats) { double out[3] = {lon, lat, 0}; transform->TransformPoint(out, out); - // cout << "inserting point (" << pointId << "): " << out[0] << " " << out[1] << endl; points->InsertPoint(pointId++, out[0], out[1], 0); + // logic for adding cells if (latIndex > 0 and lonIndex > 0 ) { int idx = latIndex+lonIndex*numLats; - // cout << idx << " at " << lonIndex << " " << latIndex << endl; vtkNew l; l->SetNumberOfIds(4); l->SetId(0, idx-1); l->SetId(1, idx-numLats-1); l->SetId(2, idx-numLats); l->SetId(3, idx); - // cout << "inserting cell: " << idx-1 << " " << idx-numLats-1 << " " << idx- numLats<< " " << idx << endl; + double coords[12]; points->GetPoint(idx-1, coords); points->GetPoint(idx-numLats-1, coords+3); points->GetPoint(idx-numLats, coords+6); points->GetPoint(idx, coords+9); - // cout << "Inserting cell with points at: (" << coords[0] << " " << coords[1] << "), (" << coords[3] << " " << coords[4] << "), (" << coords[6] << " " << coords[7] << "), (" << coords[9] << " " << coords[10] << ")" << endl; data->InsertNextCell(VTK_QUAD, l); + // ltake the average of the four surrounding points as the cell's velocity. double u=0, v=0; for (int j=0; j < 2; j++ ) { for (int k=0; k < 2; k++ ) { @@ -95,7 +140,8 @@ void EColLayer::readCoordinates() { } u /= 4; v /= 4; - this->strength->SetTuple1(cellId++, std::sqrt(u*u + v*v)); + this->strength->SetTuple1(cellId, std::sqrt(u*u + v*v)); + this->direction->SetTuple1(cellId++, atan(u/v)*180/pi); } latIndex++; } @@ -103,16 +149,20 @@ void EColLayer::readCoordinates() { } data->GetCellData()->AddArray(this->strength); - data->GetCellData()->SetActiveScalars("strength"); + data->GetCellData()->AddArray(this->direction); + // data->GetCellData()->SetActiveScalars("strength"); vtkNew(mapper); mapper->SetInputData(data); + mapper->SetLookupTable(buildLutDirs()); + mapper->UseLookupTableScalarRangeOn(); mapper->Update(); + data->GetCellData()->SetActiveScalars("direction"); vtkNew actor; actor->SetMapper(mapper); actor->GetProperty()->SetColor(0, 1, 0); - actor->GetProperty()->SetOpacity(0.2); + actor->GetProperty()->SetOpacity(0.5); // vtkNew act2; // act2->SetMapper(mapper); @@ -137,7 +187,8 @@ void EColLayer::updateData(int t) { } u /= 4; v /= 4; - this->strength->SetTuple1(i++, std::sqrt(u*u + v*v)); + this->strength->SetTuple1(i, std::sqrt(u*u + v*v)); + this->direction->SetTuple1(i++, atan(u/v)*180/pi); } } this->strength->Modified(); diff --git a/particle-track-and-trace/src/layers/EColLayer.h b/particle-track-and-trace/src/layers/EColLayer.h index 4934728..1097d21 100644 --- a/particle-track-and-trace/src/layers/EColLayer.h +++ b/particle-track-and-trace/src/layers/EColLayer.h @@ -15,6 +15,7 @@ class EColLayer : public Layer { private: vtkSmartPointer strength; + vtkSmartPointer direction; std::shared_ptr uvGrid; int numLats; int numLons; diff --git a/particle-track-and-trace/src/layers/LGlyphLayer.cpp b/particle-track-and-trace/src/layers/LGlyphLayer.cpp index 5dbd57b..311a2f7 100644 --- a/particle-track-and-trace/src/layers/LGlyphLayer.cpp +++ b/particle-track-and-trace/src/layers/LGlyphLayer.cpp @@ -33,12 +33,36 @@ vtkSmartPointer LGlyphLayer::createSpawnPointCallback() { // Further notes; current thinking is to allow tracking a particle's age by using a scalar array in the VtkPolyData. This would be incremented for every tick/updateData function call. +/** + * Build and returns a vtkLookupTable for the given number of colours in grayscale. + * @param n : number of colours to add to the SetTableRange + * @return : a vtkLookupTable with grayscale colours from [1,1,1,1] to [0.25, 0.25, 0.25, 1] in n steps. + */ +vtkSmartPointer buildLutBrightness(int n) { + vtkNew lut; + lut->SetNumberOfColors(n); + lut->SetTableRange(0, n); + lut->SetScaleToLinear(); + lut->Build(); + for (int i=0; i < n; i++) { + lut->SetTableValue(i, 1-(0.75*i/(n-1)), 1-(0.75*i/(n-1)), 1-(0.75*i/(n-1)), 1); + } + lut->UseAboveRangeColorOn(); + lut->SetAboveRangeColor(0.2, 0.2, 0.2, 1); + + // We cheat a little here: any particle with an age of -1 is out of bounds, and thus set invisible. + lut->UseBelowRangeColorOn(); + lut->SetBelowRangeColor(1,1,1,0); + + return lut; +} + /** * Build and returns a vtkLookupTable for the given number of colours in grayscale. * @param n : number of colours to add to the SetTableRange * @return : a vtkLookupTable with grayscale colours from [1,1,1,1] to [1,1,1,0.25] in n steps. */ -vtkSmartPointer buildLut(int n) { +vtkSmartPointer buildLutOpacity(int n) { vtkNew lut; lut->SetNumberOfColors(n); lut->SetTableRange(0, n); @@ -58,6 +82,9 @@ vtkSmartPointer buildLut(int n) { } LGlyphLayer::LGlyphLayer(std::shared_ptr uvGrid, std::unique_ptr advectionKernel) { + this->luts.push(buildLutOpacity(512)); + this->luts.push(buildLutBrightness(512)); + this->ren = vtkSmartPointer::New(); this->ren->SetLayer(2); @@ -94,15 +121,20 @@ LGlyphLayer::LGlyphLayer(std::shared_ptr uvGrid, std::unique_ptrSetScaleModeToDataScalingOff(); glyph2D->Update(); - vtkNew mapper; - mapper->SetInputConnection(glyph2D->GetOutputPort()); - mapper->SetColorModeToMapScalars(); - mapper->SetLookupTable(buildLut(512)); - mapper->UseLookupTableScalarRangeOn(); - mapper->Update(); + this->mapper = vtkSmartPointer::New(); + this->mapper->SetInputConnection(glyph2D->GetOutputPort()); + this->mapper->SetColorModeToMapScalars(); + + auto lut = this->luts.front(); + mapper->SetLookupTable(lut); + this->luts.pop(); + this->luts.push(lut); + + this->mapper->UseLookupTableScalarRangeOn(); + this->mapper->Update(); vtkNew actor; - actor->SetMapper(mapper); + actor->SetMapper(this->mapper); this->ren->AddActor(actor); } @@ -174,6 +206,13 @@ void LGlyphLayer::addObservers(vtkSmartPointer intera } +void LGlyphLayer::cycleGlyphStyle() { + auto lut = this->luts.front(); + this->mapper->SetLookupTable(lut); + this->luts.pop(); + this->luts.push(lut); +} + void LGlyphLayer::setDt(int dt) { this->dt = dt; } diff --git a/particle-track-and-trace/src/layers/LGlyphLayer.h b/particle-track-and-trace/src/layers/LGlyphLayer.h index 8cec151..e15d0f1 100644 --- a/particle-track-and-trace/src/layers/LGlyphLayer.h +++ b/particle-track-and-trace/src/layers/LGlyphLayer.h @@ -4,6 +4,7 @@ #include "Layer.h" #include "../advection/kernel/AdvectionKernel.h" #include "../commands/SpawnPointCallback.h" +#include #include #include @@ -16,10 +17,12 @@ private: vtkSmartPointer data; vtkSmartPointer particlesBeached; vtkSmartPointer particlesAge; + vtkSmartPointer mapper; std::unique_ptr advector; std::shared_ptr uvGrid; int dt = 3600; int beachedAtNumberOfTimes = 20; + std::queue> luts; public: /** Constructor. @@ -41,6 +44,9 @@ public: void addObservers(vtkSmartPointer interactor) override; + /** This function cycles which lut is used for the layer, according to the lookuptables in the luts attribute. + */ + void cycleGlyphStyle(); /** * Sets a custom DT value, needed for advect calls to the simulation logic. diff --git a/particle-track-and-trace/src/main.cpp b/particle-track-and-trace/src/main.cpp index 6997c65..0f7d692 100644 --- a/particle-track-and-trace/src/main.cpp +++ b/particle-track-and-trace/src/main.cpp @@ -18,11 +18,60 @@ #include "advection/kernel/RK4AdvectionKernel.h" #include "advection/kernel/SnapBoundaryConditionKernel.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "QT/MainWindow.h" + using namespace std; #define DT 60 * 60 // 60 sec/min * 60 mins -int main() { +int main(int argc, char* argv[]) { + QSurfaceFormat::setDefaultFormat(QVTKOpenGLNativeWidget::defaultFormat()); + + QApplication app(argc, argv); + + // Main window. + MainWindow mainWindow; + mainWindow.resize(1200, 900); + + mainWindow.show(); + return app.exec(); + + // Control area. + // QDockWidget controlDock; + // mainWindow.addDockWidget(Qt::LeftDockWidgetArea, &controlDock); + // + // QLabel controlDockTitle("Control Dock"); + // controlDockTitle.setMargin(20); + // controlDock.setTitleBarWidget(&controlDockTitle); + // + // QPointer dockLayout = new QVBoxLayout(); + // QWidget layoutContainer; + // layoutContainer.setLayout(dockLayout); + // controlDock.setWidget(&layoutContainer); + // + // QPushButton randomizeButton; + // randomizeButton.setText("Randomize"); + // dockLayout->addWidget(&randomizeButton); + + // Render area. + // QPointer vtkRenderWidget = + // new QVTKOpenGLNativeWidget(); + // mainWindow.setCentralWidget(vtkRenderWidget); + + // VTK part. + // vtkNew window; + // vtkRenderWidget->setRenderWindow(window.Get()); + cout << "Reading data..." << endl; string dataPath = "../../../../data"; shared_ptr uvGrid = make_shared(dataPath); @@ -31,17 +80,31 @@ int main() { cout << "Starting vtk..." << endl; auto l = new LGlyphLayer(uvGrid, std::move(kernelRK4BoundaryChecked)); - l->spoofPoints(); + // l->spoofPoints(); l->setDt(DT); + // TODO: implement feature to call this function on widget + // l->cycleGlyphStyle(); - unique_ptr program = make_unique(DT); - program->addLayer(new BackgroundImage(dataPath + "/map_661-661.png")); - // program->addLayer(new EGlyphLayer(uvGrid)); + unique_ptr program = make_unique(); + program->addLayer(new BackgroundImage(dataPath + "/map_qgis_1035.png")); + // TODO: implement feature to cycle between layers thru QT + program->addLayer(new EGlyphLayer(uvGrid)); program->addLayer(new EColLayer(uvGrid)); program->addLayer(l); - program->render(); + program->setupInteractions(); - return EXIT_SUCCESS; + // program->render(); + + // Setup initial status. + // std::mt19937 randEng(0); + // ::Randomize(sphere, mapper, window, randEng); + + // connect the buttons + // QObject::connect(&randomizeButton, &QPushButton::released, + // [&]() { ::Randomize(sphere, mapper, window, randEng); }); + + mainWindow.show(); + return app.exec(); } diff --git a/particle-track-and-trace/src/technique.cpp b/particle-track-and-trace/src/technique.cpp new file mode 100644 index 0000000..e69de29 diff --git a/particle-track-and-trace/src/technique.h b/particle-track-and-trace/src/technique.h new file mode 100644 index 0000000..e43888a --- /dev/null +++ b/particle-track-and-trace/src/technique.h @@ -0,0 +1,25 @@ +#ifndef TECHNIQUE_H +#define TECHNIQUE_H + +#include "layers/Layer.h" +#include +class Technique { +private: + std::vector layers; + vtkSmartPointer points; + vtkSmartPointer data; + + void setupInteractions(); + +public: + Technique(); + void addLayer(Layer *l); + void removeLayer(Layer *l); + void updateData(int t); + int numberOfLayers(); + +}; + + + +#endif