feat: technique overhaul
This commit is contained in:
parent
211a219431
commit
86ec78f831
|
|
@ -54,6 +54,8 @@ add_executable(ParticleTrackTrace MACOSX_BUNDLE main.cpp
|
|||
layers/Layer.h
|
||||
layers/LGlyphLayer.cpp
|
||||
layers/LGlyphLayer.h
|
||||
layers/Technique.cpp
|
||||
layers/Technique.h
|
||||
Program.cpp
|
||||
Program.h
|
||||
commands/TimerCallbackCommand.h
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
#include <stdexcept>
|
||||
#include <vtkRenderWindow.h>
|
||||
#include <vtkPointData.h>
|
||||
#include <vtkDoubleArray.h>
|
||||
|
|
@ -53,11 +54,10 @@ void Program::setupCameraCallback() {
|
|||
|
||||
Program::Program(QWidget *parent): QVTKOpenGLNativeWidget(parent) {
|
||||
this->win = vtkSmartPointer<vtkGenericOpenGLRenderWindow>::New();
|
||||
// this->interact = vtkSmartPointer<vtkRenderWindowInteractor>::New();
|
||||
// this->interact = vtkSmartPointer<QVTKInteractor>::New();
|
||||
setRenderWindow(this->win);
|
||||
this->interact = win->GetInteractor();
|
||||
this->cam = createNormalisedCamera();
|
||||
this->activeIdx = -1;
|
||||
|
||||
this->win->SetNumberOfLayers(0);
|
||||
setWinProperties();
|
||||
|
|
@ -66,44 +66,55 @@ Program::Program(QWidget *parent): QVTKOpenGLNativeWidget(parent) {
|
|||
}
|
||||
|
||||
|
||||
void Program::addLayer(Layer *layer) {
|
||||
layer->setCamera(this->cam);
|
||||
void Program::addTechnique(Technique *technique) {
|
||||
this->techniques.push_back(technique);
|
||||
|
||||
this->layers.push_back(layer);
|
||||
this->win->AddRenderer(layer->getLayer());
|
||||
this->win->SetNumberOfLayers(this->win->GetNumberOfLayers() + 1);
|
||||
}
|
||||
|
||||
void Program::removeLayer(Layer *layer) {
|
||||
this->win->RemoveRenderer(layer->getLayer());
|
||||
|
||||
auto it = std::find(this->layers.begin(), this->layers.end(), layer);
|
||||
if (it != this->layers.end()) {
|
||||
this->layers.erase(it);
|
||||
this->win->SetNumberOfLayers(this->win->GetNumberOfLayers() - 1);
|
||||
void Program::removeTechnique(Technique *technique) {
|
||||
auto it = std::find(this->techniques.begin(), this->techniques.end(), technique);
|
||||
if (it != this->techniques.end()) {
|
||||
int idx = it - this->techniques.begin();
|
||||
if (idx == this->activeIdx) {
|
||||
throw std::out_of_range("Can't remove active technique.");
|
||||
}
|
||||
this->techniques.erase(it);
|
||||
this->activeIdx = -1;
|
||||
setActiveTechnique(0);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void Program::updateData(int t) {
|
||||
// FIXME: think on how to update techniques; do we update all? just active? unsure.
|
||||
win->Render();
|
||||
for (Layer *l: layers) {
|
||||
l->updateData(t);
|
||||
}
|
||||
this->techniques[this->activeIdx]->updateData(t);
|
||||
}
|
||||
|
||||
void Program::setupInteractions() {
|
||||
for (Layer *l: layers) {
|
||||
l->addObservers(this->interact);
|
||||
}
|
||||
void Program::setActiveTechnique(int idx) {
|
||||
// Only change things if a different technique has been selected.
|
||||
if (idx == this->activeIdx) {
|
||||
return;
|
||||
}
|
||||
|
||||
void Program::render() {
|
||||
setupInteractions();
|
||||
win->Render();
|
||||
interact->Start();
|
||||
// check the given idx is valid.
|
||||
if (idx >= this->techniques.size()) {
|
||||
throw std::out_of_range("Index out of range!");
|
||||
}
|
||||
|
||||
if (this->activeIdx >= 0 and this->activeIdx < this->techniques.size())
|
||||
this->techniques[this->activeIdx]->unbind(this->win, this->interact);
|
||||
|
||||
this->techniques[idx]->bind(this->win, this->interact);
|
||||
|
||||
this->activeIdx = idx;
|
||||
}
|
||||
|
||||
|
||||
Program::~Program() {
|
||||
cout << "deleting program" << endl;
|
||||
}
|
||||
|
||||
|
||||
vtkSmartPointer<vtkCamera> Program::getCamera() {
|
||||
return this->cam;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
#include <vtkRenderer.h>
|
||||
|
||||
#include "layers/Layer.h"
|
||||
#include "layers/Technique.h"
|
||||
|
||||
/** 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.
|
||||
|
|
@ -15,19 +16,19 @@ class Program : public QVTKOpenGLNativeWidget {
|
|||
Q_OBJECT
|
||||
|
||||
private:
|
||||
/** This attribute models a variable number of vtkRenderers, managed through the abstract Layer class.
|
||||
/** This attribute models a variable number of vtkRenderers, managed through the abstract Technique class.
|
||||
*/
|
||||
std::vector<Layer *> layers;
|
||||
std::vector<Technique *> techniques;
|
||||
int activeIdx;
|
||||
|
||||
|
||||
/** The window this program's layers render to.
|
||||
*/
|
||||
// vtkSmartPointer<vtkRenderWindow> win;
|
||||
vtkSmartPointer<vtkGenericOpenGLRenderWindow> win;
|
||||
|
||||
/** The interactor through which the layers can interact with the window.
|
||||
*/
|
||||
vtkSmartPointer<vtkRenderWindowInteractor> interact;
|
||||
// vtkSmartPointer<QVTKInteractor> interact;
|
||||
|
||||
|
||||
/** The camera used by all layers for this program.
|
||||
|
|
@ -53,33 +54,30 @@ public:
|
|||
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.
|
||||
* @param layer : pointer to the layer to add.
|
||||
/** This function adds a new technique to the program.
|
||||
* The technique is expected to manage the layers itself in the vtkRenderWindow layer system.
|
||||
* @param technique : pointer to the technique to add.
|
||||
*/
|
||||
void addLayer(Layer *layer);
|
||||
void addTechnique(Technique *technique);
|
||||
|
||||
/** This function removes a given layer from the vtkRenderWindow and layers vector.
|
||||
* If the given layer is not actually in the program, nothing happens.
|
||||
* @param layer : the layer to removeLayer
|
||||
* @param layer : the layer to removeTechnique
|
||||
*/
|
||||
void removeLayer(Layer *layer);
|
||||
void removeTechnique(Technique *layer);
|
||||
|
||||
/** This function updates the data for the associated layers to the given timestamp.
|
||||
/** This function updates the data for the associated techniques to the given timestamp.
|
||||
* Also updates the renderWindow.
|
||||
* @param t : the timestamp to update the data to.
|
||||
*/
|
||||
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();
|
||||
// TODO: using an idx to indicate which technique to use is not ideal; use an enum instead? But then the question is where to put it...
|
||||
void setActiveTechnique(int idx);
|
||||
|
||||
|
||||
vtkSmartPointer<vtkCamera> getCamera();
|
||||
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -22,28 +22,7 @@ MainWindow::MainWindow(QWidget* parent)
|
|||
, ui(new Ui::MainWindow)
|
||||
{
|
||||
ui->setupUi(this);
|
||||
|
||||
cout << "Reading data..." << endl;
|
||||
string dataPath = "../../../../data";
|
||||
shared_ptr<UVGrid> uvGrid = make_shared<UVGrid>(dataPath);
|
||||
auto kernelRK4 = make_unique<RK4AdvectionKernel>(uvGrid);
|
||||
auto kernelRK4BoundaryChecked = make_unique<SnapBoundaryConditionKernel>(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();
|
||||
this->setupTechniques();
|
||||
}
|
||||
|
||||
MainWindow::~MainWindow() {
|
||||
|
|
@ -51,6 +30,50 @@ MainWindow::~MainWindow() {
|
|||
}
|
||||
|
||||
|
||||
void MainWindow::setupTechniques() {
|
||||
cout << "Reading data..." << endl;
|
||||
string dataPath = "../../../../data";
|
||||
shared_ptr<UVGrid> uvGrid = make_shared<UVGrid>(dataPath);
|
||||
|
||||
// initialize techniques
|
||||
Program *program = this->ui->program;
|
||||
auto technique1 = new Technique(program->getCamera());
|
||||
auto technique2 = new Technique(program->getCamera());
|
||||
|
||||
// add bg layer
|
||||
auto bg = new BackgroundImage(dataPath + "/map_qgis_1035.png");
|
||||
technique1->addLayer(bg);
|
||||
technique2->addLayer(bg);
|
||||
|
||||
// add Euler layers
|
||||
technique1->addLayer(new EColLayer(uvGrid));
|
||||
technique2->addLayer(new EGlyphLayer(uvGrid));
|
||||
|
||||
// setup LGlyphLayer
|
||||
auto kernelRK4 = make_unique<RK4AdvectionKernel>(uvGrid);
|
||||
auto kernelRK4BoundaryChecked = make_unique<SnapBoundaryConditionKernel>(std::move(kernelRK4), uvGrid);
|
||||
auto lGlyph = new LGlyphLayer(uvGrid, std::move(kernelRK4BoundaryChecked));
|
||||
lGlyph->setDt(3600);
|
||||
|
||||
technique1->addLayer(lGlyph);
|
||||
// technique2->addLayer(new LColLayer(uvGrid)); // TODO: add LColLayer
|
||||
technique2->addLayer(lGlyph);
|
||||
|
||||
cout << technique1->numberOfLayers() << endl;
|
||||
|
||||
program->addTechnique(technique1);
|
||||
program->addTechnique(technique2);
|
||||
|
||||
program->setActiveTechnique(0);
|
||||
|
||||
// TODO: implement feature to call this function on widget
|
||||
// l->spoofPoints();
|
||||
// l->cycleGlyphStyle();
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
/* --------------------------------------------------------------------
|
||||
* + QTWidget callbacks +
|
||||
* --------------------------------------------------------------------*/
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
#define MAINWINDOW_H
|
||||
|
||||
#include <QMainWindow>
|
||||
#include "../advection/UVGrid.h"
|
||||
|
||||
namespace Ui {
|
||||
class MainWindow;
|
||||
|
|
@ -32,6 +33,8 @@ private slots:
|
|||
|
||||
private:
|
||||
Ui::MainWindow* ui;
|
||||
|
||||
void setupTechniques();
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -173,6 +173,10 @@ void LGlyphLayer::addObservers(vtkSmartPointer<vtkRenderWindowInteractor> intera
|
|||
interactor->AddObserver(vtkCommand::MouseMoveEvent, newPointCallBack);
|
||||
}
|
||||
|
||||
void LGlyphLayer::removeObservers(vtkSmartPointer<vtkRenderWindowInteractor> interactor) {
|
||||
// todo: logic for these
|
||||
}
|
||||
|
||||
|
||||
void LGlyphLayer::setDt(int dt) {
|
||||
this->dt = dt;
|
||||
|
|
|
|||
|
|
@ -40,6 +40,7 @@ public:
|
|||
vtkSmartPointer<SpawnPointCallback> createSpawnPointCallback();
|
||||
|
||||
void addObservers(vtkSmartPointer<vtkRenderWindowInteractor> interactor) override;
|
||||
void removeObservers(vtkSmartPointer<vtkRenderWindowInteractor> interactor) override;
|
||||
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@
|
|||
#include "../CartographicTransformation.h"
|
||||
|
||||
vtkSmartPointer<SpawnPointCallback> LGlyphLayer::createSpawnPointCallback() {
|
||||
auto newPointCallBack = vtkSmartPointer<SpawnPointCallback>::New();
|
||||
vtkNew<SpawnPointCallback> newPointCallBack;
|
||||
newPointCallBack->setData(this->data);
|
||||
newPointCallBack->setPoints(this->points);
|
||||
newPointCallBack->setRen(this->ren);
|
||||
|
|
@ -137,6 +137,8 @@ LGlyphLayer::LGlyphLayer(std::shared_ptr<UVGrid> uvGrid, std::unique_ptr<Advecti
|
|||
actor->SetMapper(this->mapper);
|
||||
|
||||
this->ren->AddActor(actor);
|
||||
|
||||
this->callback = createSpawnPointCallback();
|
||||
}
|
||||
|
||||
void LGlyphLayer::spoofPoints() {
|
||||
|
|
@ -199,10 +201,15 @@ void LGlyphLayer::updateData(int t) {
|
|||
}
|
||||
|
||||
void LGlyphLayer::addObservers(vtkSmartPointer<vtkRenderWindowInteractor> interactor) {
|
||||
auto newPointCallBack = createSpawnPointCallback();
|
||||
interactor->AddObserver(vtkCommand::LeftButtonPressEvent, newPointCallBack);
|
||||
interactor->AddObserver(vtkCommand::LeftButtonReleaseEvent, newPointCallBack);
|
||||
interactor->AddObserver(vtkCommand::MouseMoveEvent, newPointCallBack);
|
||||
interactor->AddObserver(vtkCommand::LeftButtonPressEvent, this->callback);
|
||||
interactor->AddObserver(vtkCommand::LeftButtonReleaseEvent, this->callback);
|
||||
interactor->AddObserver(vtkCommand::MouseMoveEvent, this->callback);
|
||||
}
|
||||
|
||||
void LGlyphLayer::removeObservers(vtkSmartPointer<vtkRenderWindowInteractor> interactor) {
|
||||
interactor->RemoveObserver(this->callback);
|
||||
interactor->RemoveObserver(this->callback);
|
||||
interactor->RemoveObserver(this->callback);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ private:
|
|||
int dt = 3600;
|
||||
int beachedAtNumberOfTimes = 20;
|
||||
std::queue<vtkSmartPointer<vtkLookupTable>> luts;
|
||||
vtkSmartPointer<SpawnPointCallback> callback;
|
||||
|
||||
public:
|
||||
/** Constructor.
|
||||
|
|
@ -43,6 +44,7 @@ public:
|
|||
vtkSmartPointer<SpawnPointCallback> createSpawnPointCallback();
|
||||
|
||||
void addObservers(vtkSmartPointer<vtkRenderWindowInteractor> interactor) override;
|
||||
void removeObservers(vtkSmartPointer<vtkRenderWindowInteractor> interactor) override;
|
||||
|
||||
/** This function cycles which lut is used for the layer, according to the lookuptables in the luts attribute.
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
#include "Layer.h"
|
||||
#include <vtkRenderWindow.h>
|
||||
#include <vtkCamera.h>
|
||||
#include <vtkRenderWindowInteractor.h>
|
||||
|
||||
using std::string;
|
||||
|
|
@ -16,7 +17,10 @@ void Layer::addObservers(vtkSmartPointer<vtkRenderWindowInteractor> interactor)
|
|||
// By default, do nothing
|
||||
}
|
||||
|
||||
|
||||
void Layer::setCamera(vtkCamera *camera) {
|
||||
this->getLayer()->SetActiveCamera(camera);
|
||||
void Layer::removeObservers(vtkSmartPointer<vtkRenderWindowInteractor> interactor) {
|
||||
// By default, do nothing
|
||||
}
|
||||
|
||||
void Layer::setCamera(vtkSmartPointer<vtkCamera> cam) {
|
||||
this->getLayer()->SetActiveCamera(cam.GetPointer());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,10 +28,16 @@ public:
|
|||
*/
|
||||
virtual void addObservers(vtkSmartPointer<vtkRenderWindowInteractor> interactor);
|
||||
|
||||
|
||||
/** Removes observers to the renderWindowinteractor within which this layer is active.
|
||||
* @param interactor : pointer to the interactor that observers can be removed from.
|
||||
*/
|
||||
virtual void removeObservers(vtkSmartPointer<vtkRenderWindowInteractor> interactor);
|
||||
|
||||
/** Sets the active camera for the vtkRenderer associated with this layer.
|
||||
* Used to share one camera between multiple layers.
|
||||
*/
|
||||
virtual void setCamera(vtkCamera *camera);
|
||||
virtual void setCamera(vtkSmartPointer<vtkCamera> cam);
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -0,0 +1,50 @@
|
|||
#include <vtkRenderWindow.h>
|
||||
|
||||
#include "Technique.h"
|
||||
|
||||
Technique::Technique(vtkSmartPointer<vtkCamera> cam) : cam(cam) {}
|
||||
|
||||
|
||||
void Technique::addLayer(Layer *l) {
|
||||
l->setCamera(this->cam);
|
||||
this->layers.push_back(l);
|
||||
}
|
||||
|
||||
|
||||
void Technique::removeLayer(Layer *l) {
|
||||
auto it = std::find(this->layers.begin(), this->layers.end(), l);
|
||||
if (it != this->layers.end()) {
|
||||
this->layers.erase(it);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void Technique::updateData(int t) {
|
||||
for (Layer *l : this->layers) {
|
||||
l->updateData(t);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
int Technique::numberOfLayers() {
|
||||
return this->layers.size();
|
||||
}
|
||||
|
||||
|
||||
void Technique::bind(vtkSmartPointer<vtkRenderWindow> win, vtkSmartPointer<vtkRenderWindowInteractor> intr) {
|
||||
for (Layer *l : this->layers) {
|
||||
l->addObservers(intr);
|
||||
win->AddRenderer(l->getLayer());
|
||||
}
|
||||
win->SetNumberOfLayers(this->layers.size());
|
||||
// win->Render();
|
||||
}
|
||||
|
||||
|
||||
void Technique::unbind(vtkSmartPointer<vtkRenderWindow> win, vtkSmartPointer<vtkRenderWindowInteractor> intr) {
|
||||
for (Layer *l : this->layers) {
|
||||
l->removeObservers(intr);
|
||||
win->RemoveRenderer(l->getLayer());
|
||||
}
|
||||
win->SetNumberOfLayers(0);
|
||||
}
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
#ifndef TECHNIQUE_H
|
||||
#define TECHNIQUE_H
|
||||
|
||||
#include "Layer.h"
|
||||
#include <vtkGenericOpenGLRenderWindow.h>
|
||||
#include <vtkPolyData.h>
|
||||
class Technique {
|
||||
private:
|
||||
std::vector<Layer *> layers;
|
||||
vtkSmartPointer<vtkCamera> cam;
|
||||
|
||||
public:
|
||||
Technique(vtkSmartPointer<vtkCamera> cam);
|
||||
void addLayer(Layer *l);
|
||||
void removeLayer(Layer *l);
|
||||
void updateData(int t);
|
||||
int numberOfLayers();
|
||||
void bind(vtkSmartPointer<vtkRenderWindow> win, vtkSmartPointer<vtkRenderWindowInteractor> intr);
|
||||
void unbind(vtkSmartPointer<vtkRenderWindow> win, vtkSmartPointer<vtkRenderWindowInteractor> intr);
|
||||
|
||||
};
|
||||
|
||||
|
||||
|
||||
#endif
|
||||
|
|
@ -1,49 +1,16 @@
|
|||
#include <netcdf>
|
||||
#include <vtkActor2D.h>
|
||||
#include <vtkNamedColors.h>
|
||||
#include <vtkPoints.h>
|
||||
#include <vtkPolyData.h>
|
||||
#include <vtkPolyDataMapper2D.h>
|
||||
#include <vtkProperty2D.h>
|
||||
#include <vtkRenderer.h>
|
||||
#include <vtkVertexGlyphFilter.h>
|
||||
#include <memory>
|
||||
|
||||
#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"
|
||||
|
||||
#include <QApplication>
|
||||
#include <QVTKOpenGLNativeWidget.h>
|
||||
#include <QDockWidget>
|
||||
#include <QGridLayout>
|
||||
#include <vtkGenericOpenGLRenderWindow.h>
|
||||
#include <QLabel>
|
||||
#include <QMainWindow>
|
||||
#include <QPointer>
|
||||
#include <QPushButton>
|
||||
#include <QVBoxLayout>
|
||||
#include "QT/MainWindow.h"
|
||||
|
||||
using namespace std;
|
||||
|
||||
#define DT 60 * 60 // 60 sec/min * 60 mins
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
QSurfaceFormat::setDefaultFormat(QVTKOpenGLNativeWidget::defaultFormat());
|
||||
|
||||
QApplication app(argc, argv);
|
||||
|
||||
// Main window.
|
||||
MainWindow mainWindow;
|
||||
mainWindow.resize(1200, 900);
|
||||
MainWindow w;
|
||||
w.resize(1200, 900);
|
||||
|
||||
mainWindow.show();
|
||||
w.show();
|
||||
return app.exec();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,25 +0,0 @@
|
|||
#ifndef TECHNIQUE_H
|
||||
#define TECHNIQUE_H
|
||||
|
||||
#include "layers/Layer.h"
|
||||
#include <vtkPolyData.h>
|
||||
class Technique {
|
||||
private:
|
||||
std::vector<Layer *> layers;
|
||||
vtkSmartPointer<vtkPoints> points;
|
||||
vtkSmartPointer<vtkPolyData> data;
|
||||
|
||||
void setupInteractions();
|
||||
|
||||
public:
|
||||
Technique();
|
||||
void addLayer(Layer *l);
|
||||
void removeLayer(Layer *l);
|
||||
void updateData(int t);
|
||||
int numberOfLayers();
|
||||
|
||||
};
|
||||
|
||||
|
||||
|
||||
#endif
|
||||
Loading…
Reference in New Issue