Merge branch 'robin-worldspace-transformation-normalised-camera' into djairo-vtk-test

This commit is contained in:
Djairo Hougee 2024-05-05 18:11:13 +02:00
commit e0f92e4d9e
9 changed files with 170 additions and 69 deletions

1
vtk/.gitignore vendored
View File

@ -4,3 +4,4 @@ src/.cache
src/build src/build
.idea .idea
src/cmake-build-debug src/cmake-build-debug
compile_commands.json

View File

@ -46,6 +46,7 @@ add_executable(VtkBase MACOSX_BUNDLE main.cpp
commands/TimerCallbackCommand.cpp commands/TimerCallbackCommand.cpp
helperClasses/SpawnPointCallback.h helperClasses/SpawnPointCallback.h
helperClasses/SpawnPointCallback.cpp helperClasses/SpawnPointCallback.cpp
helperClasses/CartographicTransformation.cpp
) )
execute_process( execute_process(

View File

@ -1 +0,0 @@
build/compile_commands.json

View File

@ -0,0 +1,46 @@
#include "CartographicTransformation.h"
#include <vtkMatrix4x4.h>
#include <vtkTransform.h>
#include <vtkTransformFilter.h>
vtkSmartPointer<vtkCamera> createNormalisedCamera() {
vtkSmartPointer<vtkCamera> camera = vtkSmartPointer<vtkCamera>::New();
camera->ParallelProjectionOn(); // Enable parallel projection
camera->SetPosition(0, 0, 1000); // Place the camera above the center
camera->SetFocalPoint(0, 0, 0); // Look at the center
camera->SetViewUp(0, 1, 0); // Set the up vector to be along the Y-axis
camera->SetParallelScale(1); // x,y in [-1, 1]
return camera;
}
vtkSmartPointer<vtkMatrix4x4> getCartographicTransformMatrix() {
const double XMin = -15.875;
const double XMax = 12.875;
const double YMin = 46.125;
const double YMax = 62.625;
double eyeTransform[] = {
2/(XMax-XMin), 0, 0, -(XMax+XMin)/(XMax-XMin),
0, 2/(YMax-YMin), 0, -(YMax+YMin)/(YMax-YMin),
0, 0, 1, 0,
0, 0, 0, 1
};
auto matrix = vtkSmartPointer<vtkMatrix4x4>::New();
matrix->DeepCopy(eyeTransform);
return matrix;
}
// Assumes Normalised camera is used
vtkSmartPointer<vtkTransformFilter> createCartographicTransformFilter() {
vtkNew<vtkTransform> transform;
transform->SetMatrix(getCartographicTransformMatrix());
vtkSmartPointer<vtkTransformFilter> transformFilter = vtkSmartPointer<vtkTransformFilter>::New();
transformFilter->SetTransform(transform);
return transformFilter;
}

View File

@ -0,0 +1,30 @@
#include <vtkCamera.h>
#include <vtkTransformFilter.h>
#ifndef VTKBASE_NORMALISEDCARTOGRAPHICCAMERA_H
#define VTKBASE_NORMALISEDCARTOGRAPHICCAMERA_H
#endif //VTKBASE_NORMALISEDCARTOGRAPHICCAMERA_H
/**
* Constructs a orthographically projected camera that looks at the square x,y in [-1, 1] with z = 0 and w = 1.
* The space [-1,1] x [-1,1] x {0} will be referred to as the normalised space.
* @return pointer to camera
*/
vtkSmartPointer<vtkCamera> createNormalisedCamera();
/**
* Constructs a 4x4 projection matrix that maps homogenious (longitude, latitude, 0, 1) points
* to the normalised space.
* TODO: This will soon require UVGrid as a parameter after the advection code is merged properly.
* TODO: This transformation has room for improvement see:
* https://github.com/MakeNEnjoy/interactive-track-and-trace/issues/12
* @return pointer to 4x4 matrix
*/
vtkSmartPointer<vtkMatrix4x4> getCartographicTransformMatrix();
/**
* Convenience function that converts the 4x4 projection matrix into a vtkTransformFilter
* @return pointer to transform filter
*/
vtkSmartPointer<vtkTransformFilter> createCartographicTransformFilter();

View File

@ -13,6 +13,7 @@
#include <vtkVertexGlyphFilter.h> #include <vtkVertexGlyphFilter.h>
#include <netcdf> #include <netcdf>
#include <vtkArrowSource.h> #include <vtkArrowSource.h>
#include "CartographicTransformation.h"
using namespace netCDF; using namespace netCDF;
using namespace std; using namespace std;
@ -64,11 +65,15 @@ void EGlyphLayer::readCoordinates() {
this->direction->SetNumberOfTuples(numLats*numLons); this->direction->SetNumberOfTuples(numLats*numLons);
points->Allocate(numLats*numLons); points->Allocate(numLats*numLons);
auto camera = createNormalisedCamera();
ren->SetActiveCamera(camera);
int i = 0; int i = 0;
for (double lat : lats) { for (double lat : lats) {
for (double lon : lons) { for (double lon : lons) {
cout << "lon: " << lon << " lat: " << lat << endl;
direction->SetTuple3(i, 0.45, 0.90, 0); //FIXME: read this info from file direction->SetTuple3(i, 0.45, 0.90, 0); //FIXME: read this info from file
points->InsertPoint(i++, (lat*1000-46125)/25, (lon*1000+15875)/43.5, 0); // FIXME: counts on fixed window geometry to map properly; refactor to make use of active window geometry. points->InsertPoint(i++, lon, lat, 0);
// see also https://vtk.org/doc/nightly/html/classvtkPolyDataMapper2D.html // see also https://vtk.org/doc/nightly/html/classvtkPolyDataMapper2D.html
} }
} }
@ -76,29 +81,32 @@ void EGlyphLayer::readCoordinates() {
this->data->GetPointData()->AddArray(this->direction); this->data->GetPointData()->AddArray(this->direction);
this->data->GetPointData()->SetActiveVectors("direction"); this->data->GetPointData()->SetActiveVectors("direction");
vtkSmartPointer<vtkTransformFilter> transformFilter = createCartographicTransformFilter();
transformFilter->SetInputData(data);
vtkNew<vtkGlyphSource2D> arrowSource; vtkNew<vtkGlyphSource2D> arrowSource;
arrowSource->SetGlyphTypeToArrow(); arrowSource->SetGlyphTypeToArrow();
arrowSource->SetScale(8); //TODO: set this properly arrowSource->SetScale(0.2); //TODO: set this properly
arrowSource->Update(); arrowSource->Update();
vtkNew<vtkGlyph2D> glyph2D; vtkNew<vtkGlyph2D> glyph2D;
glyph2D->SetSourceConnection(arrowSource->GetOutputPort()); glyph2D->SetSourceConnection(arrowSource->GetOutputPort());
glyph2D->SetInputData(this->data); glyph2D->SetInputConnection(transformFilter->GetOutputPort());
glyph2D->OrientOn(); glyph2D->OrientOn();
glyph2D->ClampingOn(); glyph2D->ClampingOn();
glyph2D->SetScaleModeToScaleByVector(); glyph2D->SetScaleModeToScaleByVector();
glyph2D->SetVectorModeToUseVector(); glyph2D->SetVectorModeToUseVector();
glyph2D->Update(); glyph2D->Update();
vtkNew<vtkCoordinate> coordinate; // vtkNew<vtkCoordinate> coordinate;
coordinate->SetCoordinateSystemToWorld(); // coordinate->SetCoordinateSystemToWorld();
vtkNew<vtkPolyDataMapper2D>(mapper); vtkNew<vtkPolyDataMapper>(mapper);
// mapper->SetTransformCoordinate(coordinate); // mapper->SetTransformCoordinate(coordinate);
mapper->SetInputConnection(glyph2D->GetOutputPort()); mapper->SetInputConnection(glyph2D->GetOutputPort());
mapper->Update(); mapper->Update();
vtkNew<vtkActor2D> actor; vtkNew<vtkActor> actor;
actor->SetMapper(mapper); actor->SetMapper(mapper);
actor->GetProperty()->SetColor(0,0,0); actor->GetProperty()->SetColor(0,0,0);

View File

@ -9,16 +9,23 @@
#include <vtkVertexGlyphFilter.h> #include <vtkVertexGlyphFilter.h>
#include <vtkInteractorStyle.h> #include <vtkInteractorStyle.h>
#include <vtkInteractorStyleUser.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() { vtkSmartPointer<SpawnPointCallback> LGlyphLayer::createSpawnPointCallback() {
auto newPointCallBack = vtkSmartPointer<SpawnPointCallback>::New(); auto newPointCallBack = vtkSmartPointer<SpawnPointCallback>::New();
newPointCallBack->setData(data); newPointCallBack->setData(data);
newPointCallBack->setPoints(points); newPointCallBack->setPoints(points);
newPointCallBack->setRen(ren);
return newPointCallBack; return newPointCallBack;
} }
// TODO: how do we handle mapping between pixelspace and lat/lon (needed for advection)? Current idea: store the vtkPoints in lat/lon system, then apply a transformfilter to map them to the current window geometry. This should allow for a changing viewport as well - we can query the new camera position and map accordingly. // TODO: how do we handle mapping between pixelspace and lat/lon (needed for advection)? Current idea: store the vtkPoints in lat/lon system, then apply a transformfilter to map them to the current window geometry. This should allow for a changing viewport as well - we can query the new camera position and map accordingly.
// 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. // 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. // 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.
@ -26,74 +33,64 @@ vtkSmartPointer<SpawnPointCallback> LGlyphLayer::createSpawnPointCallback() {
// 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. // 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() { LGlyphLayer::LGlyphLayer() {
this->ren = vtkSmartPointer<vtkRenderer>::New(); this->ren = vtkSmartPointer<vtkRenderer>::New();
this->ren->SetLayer(2); this->ren->SetLayer(2);
this->points = vtkSmartPointer<vtkPoints>::New(); this->points = vtkSmartPointer<vtkPoints>::New();
this->data = vtkSmartPointer<vtkPolyData>::New(); this->data = vtkSmartPointer<vtkPolyData>::New();
this->data->SetPoints(this->points); this->data->SetPoints(this->points);
vtkNew<vtkGlyphSource2D> circleSource; auto camera = createNormalisedCamera();
circleSource->SetGlyphTypeToCircle(); ren->SetActiveCamera(camera);
circleSource->SetScale(15);
circleSource->Update();
vtkNew<vtkGlyph2D> glyph2D; auto transform = createCartographicTransformFilter();
glyph2D->SetSourceConnection(circleSource->GetOutputPort());
glyph2D->SetInputData(this->data);
glyph2D->SetColorModeToColorByScalar();
glyph2D->Update();
vtkNew<vtkPolyDataMapper2D> mapper; vtkSmartPointer<vtkTransformFilter> transformFilter = createCartographicTransformFilter();
mapper->SetInputConnection(glyph2D->GetOutputPort()); transformFilter->SetInputData(data);
mapper->Update();
vtkNew<vtkActor2D> actor; vtkNew<vtkGlyphSource2D> circleSource;
actor->SetMapper(mapper); circleSource->SetGlyphTypeToCircle();
actor->GetProperty()->SetColor(1,1,1); circleSource->SetScale(0.05);
circleSource->Update();
this->ren->AddActor(actor); 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 // creates a few points so we can test the updateData function
void LGlyphLayer::spoofPoints() { void LGlyphLayer::spoofPoints() {
this->points->InsertNextPoint(53, 2, 0); this->points->InsertNextPoint(-4.125, 61.375 , 0);
this->points->InsertNextPoint(48.2, 111.01, 0); this->points->InsertNextPoint(6.532949683882039, 53.24308582564463, 0); // Coordinates of Zernike
this->points->InsertNextPoint(331, 331, 0); 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(); this->points->Modified();
} }
// returns new coords for a point; used to test the updateData function // returns new coords for a point; used to test the updateData function
std::pair<double, double> advect(int time, double lat, double lon) { std::pair<double, double> advect(int time, double lat, double lon) {
return {lat+0.1, lon+0.1} ; return {lat + 0.01, lon + 0.01};
} }
void LGlyphLayer::updateData(int t) {
// converts a x,y pair from pixel coordinates to real world latitude and longitude. double point[3];
// TODO: make this more modular by having it interact with the backgroundImage layer (and possibly the camera panning/zooming logic when that is implemented). for (vtkIdType n = 0; n < this->points->GetNumberOfPoints(); n++) {
std::pair<double, double> pixelToReal(double x, double y) { this->points->GetPoint(n, point);
//assumes a 661x661 window with a range of [46.125, 62.625] lat and [-15.875, 12.875] lon. auto [xNew, yNew] = advect(n, point[0], point[1]);
return {(x*25+46125)/1000, (y*43.5-15875)/1000}; this->points->SetPoint(n, xNew, yNew, 0);
} }
this->points->Modified();
// converts a lat,lon pair from real world values to pixel coordinates.
// TODO: see above.
std::pair<double, double> realToPixel(double lat, double lon) {
//assumes a 661x661 window with a range of [46.125, 62.625] lat and [-15.875, 12.875] lon.
return {(lat*1000-46125)/25, (lon*1000+15875)/43.5};
}
// FIXME: actually the above functions are a bit of a naive way of modelling these. Much better would be to have the points at the real-world latitude and longitude, and apply a filter in the pipeline to convert them to the appropriate window geometry.
void LGlyphLayer::updateData(int t) {
double point[3];
for (vtkIdType n=0; n < this->points->GetNumberOfPoints(); n++) {
this->points->GetPoint(n, point);
auto grads = pixelToReal(point[0], point[1]);
auto newGrads = advect(n, grads.first, grads.second);
auto newPixs = realToPixel(newGrads.first, newGrads.second);
this->points->SetPoint(n, newPixs.first, newPixs.second, 0);
}
this->points->Modified();
} }

View File

@ -7,6 +7,8 @@
#include <vtkCommand.h> #include <vtkCommand.h>
#include <vtkRenderWindow.h> #include <vtkRenderWindow.h>
#include "CartographicTransformation.h"
void convertDisplayToWorld(vtkRenderer* renderer, int x, int y, double *worldPos) { void convertDisplayToWorld(vtkRenderer* renderer, int x, int y, double *worldPos) {
double displayPos[3] = {static_cast<double>(x), static_cast<double>(y), 0.0}; double displayPos[3] = {static_cast<double>(x), static_cast<double>(y), 0.0};
renderer->SetDisplayPoint(displayPos); renderer->SetDisplayPoint(displayPos);
@ -31,7 +33,15 @@ void SpawnPointCallback::Execute(vtkObject *caller, unsigned long evId, void *ca
int x, y; int x, y;
interactor->GetEventPosition(x, y); interactor->GetEventPosition(x, y);
vtkIdType id = points->InsertNextPoint(x, y, 0); double worldPos[4] = {2, 0 ,0, 0};
double displayPos[3] = {static_cast<double>(x), static_cast<double>(y), 0.0};
ren->SetDisplayPoint(displayPos);
ren->DisplayToWorld();
ren->GetWorldPoint(worldPos);
inverseCartographicProjection->MultiplyPoint(worldPos, worldPos);
cout << "clicked on lon = " << worldPos[0] << " and lat = " << worldPos[1] << endl;
vtkIdType id = points->InsertNextPoint(worldPos[0], worldPos[1], 0);
data->SetPoints(points); data->SetPoints(points);
vtkSmartPointer<vtkVertex> vertex = vtkSmartPointer<vtkVertex>::New(); vtkSmartPointer<vtkVertex> vertex = vtkSmartPointer<vtkVertex>::New();
@ -45,7 +55,10 @@ void SpawnPointCallback::Execute(vtkObject *caller, unsigned long evId, void *ca
} }
SpawnPointCallback::SpawnPointCallback() : data(nullptr), points(nullptr) {} SpawnPointCallback::SpawnPointCallback() : data(nullptr), points(nullptr), inverseCartographicProjection(nullptr) {
inverseCartographicProjection = getCartographicTransformMatrix();
inverseCartographicProjection->Invert();
}
SpawnPointCallback *SpawnPointCallback::New() { SpawnPointCallback *SpawnPointCallback::New() {
return new SpawnPointCallback; return new SpawnPointCallback;
@ -58,3 +71,7 @@ void SpawnPointCallback::setData(const vtkSmartPointer<vtkPolyData> &data) {
void SpawnPointCallback::setPoints(const vtkSmartPointer<vtkPoints> &points) { void SpawnPointCallback::setPoints(const vtkSmartPointer<vtkPoints> &points) {
this->points = points; this->points = points;
} }
void SpawnPointCallback::setRen(const vtkSmartPointer<vtkRenderer> &ren) {
this->ren = ren;
}

View File

@ -6,6 +6,7 @@
#include <vtkRenderWindowInteractor.h> #include <vtkRenderWindowInteractor.h>
#include <vtkPoints.h> #include <vtkPoints.h>
#include <vtkPolyData.h> #include <vtkPolyData.h>
#include <vtkMatrix4x4.h>
class SpawnPointCallback : public vtkCallbackCommand { class SpawnPointCallback : public vtkCallbackCommand {
@ -17,12 +18,13 @@ public:
void setData(const vtkSmartPointer<vtkPolyData> &data); void setData(const vtkSmartPointer<vtkPolyData> &data);
void setRen(const vtkSmartPointer<vtkRenderer> &ren);
private: private:
vtkSmartPointer<vtkPolyData> data; vtkSmartPointer<vtkPolyData> data;
vtkSmartPointer<vtkPoints> points; vtkSmartPointer<vtkPoints> points;
public: vtkSmartPointer<vtkRenderer> ren;
vtkSmartPointer<vtkMatrix4x4> inverseCartographicProjection;
private:
void Execute(vtkObject *caller, unsigned long evId, void *callData) override; void Execute(vtkObject *caller, unsigned long evId, void *callData) override;
bool dragging = false; bool dragging = false;
}; };