fix: stylistic changes + addObservers

This commit is contained in:
robin
2024-05-05 18:59:30 +02:00
parent e0f92e4d9e
commit 3f7de8fdce
18 changed files with 67 additions and 65 deletions

View File

@@ -0,0 +1,62 @@
#include "BackgroundImage.h"
#include <vtkCamera.h>
#include <vtkImageActor.h>
#include <vtkImageData.h>
#include <vtkImageReader2.h>
using std::string;
BackgroundImage::BackgroundImage(string imagePath) : imagePath(imagePath) {
this->ren = vtkSmartPointer<vtkRenderer>::New();
this->ren->SetLayer(0);
this->ren->InteractiveOff();
updateImage();
}
void BackgroundImage::updateImage() {
vtkSmartPointer<vtkImageData> imageData;
vtkSmartPointer<vtkImageReader2> imageReader;
imageReader.TakeReference(this->readerFactory->CreateImageReader2(this->imagePath.c_str()));
imageReader->SetFileName(this->imagePath.c_str());
imageReader->Update();
imageData = imageReader->GetOutput();
vtkNew<vtkImageActor> imageActor;
imageActor->SetInputData(imageData);
this->ren->AddActor(imageActor);
// camera stuff
// essentially sets the camera to the middle of the background, and points it at the background
// TODO: extract this to its own function, separate from the background class.
double origin[3], spacing[3];
int extent[6];
imageData->GetOrigin(origin);
imageData->GetSpacing(spacing);
imageData->GetExtent(extent);
vtkCamera *camera = this->ren->GetActiveCamera();
camera->ParallelProjectionOn();
double xc = origin[0] + 0.5 * (extent[0] + extent[1]) * spacing[0];
double yc = origin[1] + 0.5 * (extent[2] + extent[3]) * spacing[1];
double yd = (extent[3] - extent[2] + 1) * spacing[1];
double d = camera->GetDistance();
camera->SetParallelScale(0.5 * yd);
camera->SetFocalPoint(xc, yc, 0.0);
camera->SetPosition(xc, yc, d);
}
string BackgroundImage::getImagePath() {
return this->imagePath;
}
void BackgroundImage::setImagePath(string imagePath) {
this->imagePath = imagePath;
updateImage();
}

View File

@@ -0,0 +1,37 @@
#ifndef BACKGROUND_H
#define BACKGROUND_H
#include "Layer.h"
#include <vtkImageReader2Factory.h>
/** Implements the Layer class for the case of a background image.
* Specifically, reads a backgroundImage given by the imagePath attribute and puts it on layer 0.
*/
class BackgroundImage : public Layer {
private:
std::string imagePath;
vtkSmartPointer<vtkImageReader2Factory> readerFactory;
/** This private function updates the background image using the imagePath attribute.
*/
void updateImage();
public:
/** Constructor.
* @param imagePath : String to the path of the image to use as background.
*/
BackgroundImage(std::string imagePath);
/** Getter.
* @return the imagePath attribute.
*/
std::string getImagePath();
/** Setter. Can be used to change the background image
* @param imagePath : String to the path of the new image to use.
*/
void setImagePath(std::string imagePath);
};
#endif

View File

@@ -0,0 +1,124 @@
#include "EGlyphLayer.h"
#include <vtkPointData.h>
#include <vtkDoubleArray.h>
#include <vtkGlyphSource2D.h>
#include <vtkRegularPolygonSource.h>
#include <vtkGlyph2D.h>
#include <vtkActor2D.h>
#include <vtkNamedColors.h>
#include <vtkPolyDataMapper2D.h>
#include <vtkPolyDataMapper.h>
#include <vtkProperty.h>
#include <vtkProperty2D.h>
#include <vtkVertexGlyphFilter.h>
#include <netcdf>
#include <vtkArrowSource.h>
#include "../CartographicTransformation.h"
using namespace netCDF;
using namespace std;
template <typename T>
vector<T> getVarVector(const NcVar &var) {
int length = 1;
for (NcDim dim : var.getDims()) {
length *= dim.getSize();
}
vector<T> vec(length);
var.getVar(vec.data());
return vec;
}
tuple<vector<int>, vector<double>, vector<double>> readGrid() {
netCDF::NcFile data("../../../../data/grid.h5", netCDF::NcFile::read);
multimap< string, NcVar > vars = data.getVars();
vector<int> time = getVarVector<int>(vars.find("times")->second);
vector<double> longitude = getVarVector<double>(vars.find("longitude")->second);
vector<double> latitude = getVarVector<double>(vars.find("latitude")->second);
return {time, latitude, longitude};
}
EGlyphLayer::EGlyphLayer() {
this->ren = vtkSmartPointer<vtkRenderer>::New();
this->ren->SetLayer(1);
this->ren->InteractiveOff();
this->data = vtkSmartPointer<vtkPolyData>::New();
this->direction = vtkSmartPointer<vtkDoubleArray>::New();
this->direction->SetName("direction");
readCoordinates();
}
void EGlyphLayer::readCoordinates() {
vtkNew<vtkPoints> points;
auto [times, lats, lons] = readGrid(); // FIXME: import Robin's readData function and use it
this->numLats = lats.size();
this->numLons = lons.size();
this->direction->SetNumberOfComponents(3);
this->direction->SetNumberOfTuples(numLats*numLons);
points->Allocate(numLats*numLons);
auto camera = createNormalisedCamera();
ren->SetActiveCamera(camera);
int i = 0;
for (double lat : lats) {
for (double lon : lons) {
cout << "lon: " << lon << " lat: " << lat << endl;
direction->SetTuple3(i, 0.45, 0.90, 0); //FIXME: read this info from file
points->InsertPoint(i++, lon, lat, 0);
// see also https://vtk.org/doc/nightly/html/classvtkPolyDataMapper2D.html
}
}
this->data->SetPoints(points);
this->data->GetPointData()->AddArray(this->direction);
this->data->GetPointData()->SetActiveVectors("direction");
vtkSmartPointer<vtkTransformFilter> transformFilter = createCartographicTransformFilter();
transformFilter->SetInputData(data);
vtkNew<vtkGlyphSource2D> arrowSource;
arrowSource->SetGlyphTypeToArrow();
arrowSource->SetScale(0.2); //TODO: set this properly
arrowSource->Update();
vtkNew<vtkGlyph2D> glyph2D;
glyph2D->SetSourceConnection(arrowSource->GetOutputPort());
glyph2D->SetInputConnection(transformFilter->GetOutputPort());
glyph2D->OrientOn();
glyph2D->ClampingOn();
glyph2D->SetScaleModeToScaleByVector();
glyph2D->SetVectorModeToUseVector();
glyph2D->Update();
// vtkNew<vtkCoordinate> coordinate;
// coordinate->SetCoordinateSystemToWorld();
vtkNew<vtkPolyDataMapper>(mapper);
// mapper->SetTransformCoordinate(coordinate);
mapper->SetInputConnection(glyph2D->GetOutputPort());
mapper->Update();
vtkNew<vtkActor> actor;
actor->SetMapper(mapper);
actor->GetProperty()->SetColor(0,0,0);
actor->GetProperty()->SetOpacity(0.2);
this->ren->AddActor(actor) ;
}
void EGlyphLayer::updateData(int t) {
for (int i=0; i < numLats*numLons; i++) {
this->direction->SetTuple3(i, std::cos(t), std::sin(t), 0); // FIXME: fetch data from file.
}
this->direction->Modified();
}

View File

@@ -0,0 +1,35 @@
#ifndef EGLYPHLAYER_H
#define EGLYPHLAYER_H
#include "Layer.h"
#include <vtkPolyData.h>
/** Implements the Layer class for the case of a Eulerian visualization.
* Specifically, this class models the eulerian flow-fields of the simulation using the 'glyph' mark and 'direction' and 'form' channels to denote direction and strength of velocities.
*/
class EGlyphLayer : public Layer {
private:
vtkSmartPointer<vtkPolyData> data;
vtkSmartPointer<vtkDoubleArray> direction;
int numLats;
int numLons;
/** This private function sets up the initial coordinates for the glyphs in the dataset.
* It also reads some initial data to actually display.
*/
void readCoordinates();
public:
/** Constructor.
*/
EGlyphLayer();
/** updates the glyphs to reflect the given timestamp in the dataset.
* @param t : the time at which to fetch the data.
*/
void updateData(int t);
};
#endif

View File

@@ -0,0 +1,105 @@
#include "LGlyphLayer.h"
#include "../commands/SpawnPointCallback.h"
#include <vtkActor2D.h>
#include <vtkGlyph2D.h>
#include <vtkGlyphSource2D.h>
#include <vtkNamedColors.h>
#include <vtkPolyDataMapper2D.h>
#include <vtkProperty2D.h>
#include <vtkVertexGlyphFilter.h>
#include <vtkInteractorStyle.h>
#include <vtkInteractorStyleUser.h>
#include <vtkTransform.h>
#include <vtkTransformFilter.h>
#include <vtkPolyDataMapper.h>
#include <vtkRenderWindow.h>
#include <vtkCamera.h>
#include "../CartographicTransformation.h"
vtkSmartPointer<SpawnPointCallback> LGlyphLayer::createSpawnPointCallback() {
auto newPointCallBack = vtkSmartPointer<SpawnPointCallback>::New();
newPointCallBack->setData(data);
newPointCallBack->setPoints(points);
newPointCallBack->setRen(ren);
return newPointCallBack;
}
// 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.
// Another challenge is the concept of beaching; dead particles must not be included in the advect function call (wasted computations), but they should not be outright deleted from the vtkPoints either (we still want to display them). Working Solution: have another array of ints in the vtkPolyData, which tracks for how many calls of UpdateData a given particle has not had its position changed. If this int reaches some treshold (5? 10? 3? needs some testing), exclude the particle from the advect call.
//
// TODO: modelling all this in vtkClasses is workable, but ideally i would want to work with a native C++ class. See if this is doable and feasible.
LGlyphLayer::LGlyphLayer() {
this->ren = vtkSmartPointer<vtkRenderer>::New();
this->ren->SetLayer(2);
this->points = vtkSmartPointer<vtkPoints>::New();
this->data = vtkSmartPointer<vtkPolyData>::New();
this->data->SetPoints(this->points);
auto camera = createNormalisedCamera();
ren->SetActiveCamera(camera);
auto transform = createCartographicTransformFilter();
vtkSmartPointer<vtkTransformFilter> transformFilter = createCartographicTransformFilter();
transformFilter->SetInputData(data);
vtkNew<vtkGlyphSource2D> circleSource;
circleSource->SetGlyphTypeToCircle();
circleSource->SetScale(0.05);
circleSource->Update();
vtkNew<vtkGlyph2D> glyph2D;
glyph2D->SetSourceConnection(circleSource->GetOutputPort());
glyph2D->SetInputConnection(transformFilter->GetOutputPort());
glyph2D->SetColorModeToColorByScalar();
glyph2D->Update();
vtkNew<vtkPolyDataMapper> mapper;
mapper->SetInputConnection(glyph2D->GetOutputPort());
mapper->Update();
vtkNew<vtkActor> actor;
actor->SetMapper(mapper);
this->ren->AddActor(actor);
}
// creates a few points so we can test the updateData function
void LGlyphLayer::spoofPoints() {
this->points->InsertNextPoint(-4.125, 61.375 , 0);
this->points->InsertNextPoint(6.532949683882039, 53.24308582564463, 0); // Coordinates of Zernike
this->points->InsertNextPoint(5.315307819255385, 60.40001057122271, 0); // Coordinates of Bergen
this->points->InsertNextPoint( 6.646210231365825, 46.52346296009023, 0); // Coordinates of Lausanne
this->points->InsertNextPoint(-6.553894313570932, 62.39522131195857, 0); // Coordinates of the top of the Faroe islands
this->points->Modified();
}
// returns new coords for a point; used to test the updateData function
std::pair<double, double> advect(int time, double lat, double lon) {
return {lat + 0.01, lon + 0.01};
}
void LGlyphLayer::updateData(int t) {
double point[3];
for (vtkIdType n = 0; n < this->points->GetNumberOfPoints(); n++) {
this->points->GetPoint(n, point);
auto [xNew, yNew] = advect(n, point[0], point[1]);
this->points->SetPoint(n, xNew, yNew, 0);
}
this->points->Modified();
}
void LGlyphLayer::addObservers(vtkSmartPointer<vtkRenderWindowInteractor> interactor) {
auto newPointCallBack = vtkSmartPointer<SpawnPointCallback>::New();
newPointCallBack->setData(data);
newPointCallBack->setPoints(points);
newPointCallBack->setRen(ren);
interactor->AddObserver(vtkCommand::LeftButtonPressEvent, newPointCallBack);
interactor->AddObserver(vtkCommand::LeftButtonReleaseEvent, newPointCallBack);
interactor->AddObserver(vtkCommand::MouseMoveEvent, newPointCallBack);
}

View File

@@ -0,0 +1,38 @@
#ifndef LGLYPHLAYER_H
#define LGLYPHLAYER_H
#include "Layer.h"
#include "../commands/SpawnPointCallback.h"
#include <vtkPolyData.h>
#include <vtkInteractorStyle.h>
/** Implements the Layer class for the case of a Lagrangian visualization.
* Specifically, this class models the Lagrangian particles in the simulation using the 'glyph' mark and 'transparency' channel to denote age.
*/
class LGlyphLayer : public Layer {
private:
vtkSmartPointer<vtkPoints> points;
vtkSmartPointer<vtkPolyData> data;
public:
/** Constructor.
*/
LGlyphLayer();
/** This function spoofs a few points in the dataset. Mostly used for testing.
*/
void spoofPoints();
/** updates the glyphs to reflect the given timestamp in the dataset.
* @param t : the time at which to fetch the data.
*/
void updateData(int t) override;
vtkSmartPointer<SpawnPointCallback> createSpawnPointCallback();
void addObservers(vtkSmartPointer<vtkRenderWindowInteractor> interactor) override;
};
#endif

16
vtk/src/layers/Layer.cpp Normal file
View File

@@ -0,0 +1,16 @@
#include "Layer.h"
#include <vtkRenderWindow.h>
#include <vtkRenderWindowInteractor.h>
using std::string;
vtkSmartPointer<vtkRenderer> Layer::getLayer() {
return this->ren;
}
void Layer::updateData(int t) {
}
void Layer::addObservers(vtkSmartPointer<vtkRenderWindowInteractor> interactor) {
// By default, do nothing.
}

32
vtk/src/layers/Layer.h Normal file
View File

@@ -0,0 +1,32 @@
#ifndef LAYER_H
#define LAYER_H
#include <vtkInteractorStyle.h>
#include <vtkRenderer.h>
/** This class represents one abstract layer to be rendered to VTK.
* It exists to manage multiple different layers under the Program class.
*/
class Layer {
protected:
vtkSmartPointer<vtkRenderer> ren;
public:
/** gets the vtkRenderer to assign it to the vtkRenderWindow of the program class.
* @return pointer to the vtkRenderer of this class.
*/
virtual vtkSmartPointer<vtkRenderer> getLayer();
/** updates the data in the layer to reflect the given timestamp.
* @param t : the timestamp which the data should reflect.
*/
virtual void updateData(int t);
/** Adds an observer to the renderWindowinteractor within which this layer is active.
* @param interactor : pointer to the interactor that observers can be added to.
*/
virtual void addObservers(vtkSmartPointer<vtkRenderWindowInteractor> interactor);
};
#endif