diff --git a/vtk/.gitignore b/vtk/.gitignore index 95abd3d..ea37cb8 100644 --- a/vtk/.gitignore +++ b/vtk/.gitignore @@ -3,4 +3,5 @@ src/.DS_Store src/.cache src/build .idea -src/cmake-build-debug \ No newline at end of file +src/cmake-build-debug +compile_commands.json \ No newline at end of file diff --git a/vtk/src/CMakeLists.txt b/vtk/src/CMakeLists.txt index 8a20136..cb39085 100644 --- a/vtk/src/CMakeLists.txt +++ b/vtk/src/CMakeLists.txt @@ -46,6 +46,7 @@ add_executable(VtkBase MACOSX_BUNDLE main.cpp commands/TimerCallbackCommand.cpp helperClasses/SpawnPointCallback.h helperClasses/SpawnPointCallback.cpp + helperClasses/CartographicTransformation.cpp ) execute_process( diff --git a/vtk/src/compile_commands.json b/vtk/src/compile_commands.json deleted file mode 120000 index 25eb4b2..0000000 --- a/vtk/src/compile_commands.json +++ /dev/null @@ -1 +0,0 @@ -build/compile_commands.json \ No newline at end of file diff --git a/vtk/src/helperClasses/CartographicTransformation.cpp b/vtk/src/helperClasses/CartographicTransformation.cpp new file mode 100644 index 0000000..54c528e --- /dev/null +++ b/vtk/src/helperClasses/CartographicTransformation.cpp @@ -0,0 +1,46 @@ +#include "CartographicTransformation.h" +#include +#include +#include + +vtkSmartPointer createNormalisedCamera() { + vtkSmartPointer camera = vtkSmartPointer::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 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::New(); + matrix->DeepCopy(eyeTransform); + return matrix; +} + +// Assumes Normalised camera is used +vtkSmartPointer createCartographicTransformFilter() { + vtkNew transform; + + transform->SetMatrix(getCartographicTransformMatrix()); + + vtkSmartPointer transformFilter = vtkSmartPointer::New(); + transformFilter->SetTransform(transform); + + return transformFilter; +} diff --git a/vtk/src/helperClasses/CartographicTransformation.h b/vtk/src/helperClasses/CartographicTransformation.h new file mode 100644 index 0000000..56ffbeb --- /dev/null +++ b/vtk/src/helperClasses/CartographicTransformation.h @@ -0,0 +1,30 @@ +#include +#include + +#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 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 getCartographicTransformMatrix(); + +/** + * Convenience function that converts the 4x4 projection matrix into a vtkTransformFilter + * @return pointer to transform filter + */ +vtkSmartPointer createCartographicTransformFilter(); \ No newline at end of file diff --git a/vtk/src/helperClasses/EGlyphLayer.cpp b/vtk/src/helperClasses/EGlyphLayer.cpp index 4e4b9c0..0722a34 100644 --- a/vtk/src/helperClasses/EGlyphLayer.cpp +++ b/vtk/src/helperClasses/EGlyphLayer.cpp @@ -13,6 +13,7 @@ #include #include #include +#include "CartographicTransformation.h" using namespace netCDF; using namespace std; @@ -64,11 +65,15 @@ void EGlyphLayer::readCoordinates() { 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++, (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 } } @@ -76,29 +81,32 @@ void EGlyphLayer::readCoordinates() { this->data->GetPointData()->AddArray(this->direction); this->data->GetPointData()->SetActiveVectors("direction"); + vtkSmartPointer transformFilter = createCartographicTransformFilter(); + transformFilter->SetInputData(data); + vtkNew arrowSource; arrowSource->SetGlyphTypeToArrow(); - arrowSource->SetScale(8); //TODO: set this properly + arrowSource->SetScale(0.2); //TODO: set this properly arrowSource->Update(); vtkNew glyph2D; glyph2D->SetSourceConnection(arrowSource->GetOutputPort()); - glyph2D->SetInputData(this->data); + glyph2D->SetInputConnection(transformFilter->GetOutputPort()); glyph2D->OrientOn(); glyph2D->ClampingOn(); glyph2D->SetScaleModeToScaleByVector(); glyph2D->SetVectorModeToUseVector(); glyph2D->Update(); - vtkNew coordinate; - coordinate->SetCoordinateSystemToWorld(); +// vtkNew coordinate; +// coordinate->SetCoordinateSystemToWorld(); - vtkNew(mapper); + vtkNew(mapper); // mapper->SetTransformCoordinate(coordinate); mapper->SetInputConnection(glyph2D->GetOutputPort()); mapper->Update(); - vtkNew actor; + vtkNew actor; actor->SetMapper(mapper); actor->GetProperty()->SetColor(0,0,0); diff --git a/vtk/src/helperClasses/LGlyphLayer.cpp b/vtk/src/helperClasses/LGlyphLayer.cpp index fb9f5bc..6fe6caf 100644 --- a/vtk/src/helperClasses/LGlyphLayer.cpp +++ b/vtk/src/helperClasses/LGlyphLayer.cpp @@ -9,16 +9,23 @@ #include #include #include +#include +#include +#include +#include +#include + +#include "CartographicTransformation.h" vtkSmartPointer LGlyphLayer::createSpawnPointCallback() { auto newPointCallBack = vtkSmartPointer::New(); newPointCallBack->setData(data); newPointCallBack->setPoints(points); + newPointCallBack->setRen(ren); 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. // 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. @@ -26,74 +33,64 @@ vtkSmartPointer 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. LGlyphLayer::LGlyphLayer() { - this->ren = vtkSmartPointer::New(); - this->ren->SetLayer(2); + this->ren = vtkSmartPointer::New(); + this->ren->SetLayer(2); - this->points = vtkSmartPointer::New(); - this->data = vtkSmartPointer::New(); - this->data->SetPoints(this->points); - - vtkNew circleSource; - circleSource->SetGlyphTypeToCircle(); - circleSource->SetScale(15); - circleSource->Update(); + this->points = vtkSmartPointer::New(); + this->data = vtkSmartPointer::New(); + this->data->SetPoints(this->points); - vtkNew glyph2D; - glyph2D->SetSourceConnection(circleSource->GetOutputPort()); - glyph2D->SetInputData(this->data); - glyph2D->SetColorModeToColorByScalar(); - glyph2D->Update(); + auto camera = createNormalisedCamera(); + ren->SetActiveCamera(camera); - vtkNew mapper; - mapper->SetInputConnection(glyph2D->GetOutputPort()); - mapper->Update(); + auto transform = createCartographicTransformFilter(); - vtkNew actor; - actor->SetMapper(mapper); - actor->GetProperty()->SetColor(1,1,1); + vtkSmartPointer transformFilter = createCartographicTransformFilter(); + transformFilter->SetInputData(data); - this->ren->AddActor(actor); + vtkNew circleSource; + circleSource->SetGlyphTypeToCircle(); + circleSource->SetScale(0.05); + circleSource->Update(); + + vtkNew glyph2D; + glyph2D->SetSourceConnection(circleSource->GetOutputPort()); + glyph2D->SetInputConnection(transformFilter->GetOutputPort()); + glyph2D->SetColorModeToColorByScalar(); + glyph2D->Update(); + + vtkNew mapper; + mapper->SetInputConnection(glyph2D->GetOutputPort()); + mapper->Update(); + + vtkNew 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(53, 2, 0); - this->points->InsertNextPoint(48.2, 111.01, 0); - this->points->InsertNextPoint(331, 331, 0); + 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(); + this->points->Modified(); } - // returns new coords for a point; used to test the updateData function std::pair advect(int time, double lat, double lon) { - return {lat+0.1, lon+0.1} ; + return {lat + 0.01, lon + 0.01}; } - -// converts a x,y pair from pixel coordinates to real world latitude and longitude. -// TODO: make this more modular by having it interact with the backgroundImage layer (and possibly the camera panning/zooming logic when that is implemented). -std::pair pixelToReal(double x, double y) { - //assumes a 661x661 window with a range of [46.125, 62.625] lat and [-15.875, 12.875] lon. - return {(x*25+46125)/1000, (y*43.5-15875)/1000}; -} - -// converts a lat,lon pair from real world values to pixel coordinates. -// TODO: see above. -std::pair 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(); +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(); } diff --git a/vtk/src/helperClasses/SpawnPointCallback.cpp b/vtk/src/helperClasses/SpawnPointCallback.cpp index 14dbc49..e52b88f 100644 --- a/vtk/src/helperClasses/SpawnPointCallback.cpp +++ b/vtk/src/helperClasses/SpawnPointCallback.cpp @@ -7,6 +7,8 @@ #include #include +#include "CartographicTransformation.h" + void convertDisplayToWorld(vtkRenderer* renderer, int x, int y, double *worldPos) { double displayPos[3] = {static_cast(x), static_cast(y), 0.0}; renderer->SetDisplayPoint(displayPos); @@ -31,7 +33,15 @@ void SpawnPointCallback::Execute(vtkObject *caller, unsigned long evId, void *ca int 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(x), static_cast(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); vtkSmartPointer vertex = vtkSmartPointer::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() { return new SpawnPointCallback; @@ -57,4 +70,8 @@ void SpawnPointCallback::setData(const vtkSmartPointer &data) { void SpawnPointCallback::setPoints(const vtkSmartPointer &points) { this->points = points; -} \ No newline at end of file +} + +void SpawnPointCallback::setRen(const vtkSmartPointer &ren) { + this->ren = ren; +} diff --git a/vtk/src/helperClasses/SpawnPointCallback.h b/vtk/src/helperClasses/SpawnPointCallback.h index 99ab160..bef6ca4 100644 --- a/vtk/src/helperClasses/SpawnPointCallback.h +++ b/vtk/src/helperClasses/SpawnPointCallback.h @@ -6,6 +6,7 @@ #include #include #include +#include class SpawnPointCallback : public vtkCallbackCommand { @@ -17,12 +18,13 @@ public: void setData(const vtkSmartPointer &data); + void setRen(const vtkSmartPointer &ren); private: vtkSmartPointer data; vtkSmartPointer points; -public: + vtkSmartPointer ren; + vtkSmartPointer inverseCartographicProjection; -private: void Execute(vtkObject *caller, unsigned long evId, void *callData) override; bool dragging = false; };