From 5d9228e549d14082673644f1d22d54e8125f41e8 Mon Sep 17 00:00:00 2001 From: djairoh Date: Tue, 7 May 2024 13:13:08 +0200 Subject: [PATCH 1/5] added comment + ref to github issue --- .../src/commands/SpawnPointCallback.cpp | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/particle-track-and-trace/src/commands/SpawnPointCallback.cpp b/particle-track-and-trace/src/commands/SpawnPointCallback.cpp index 3253e23..b14b6e2 100644 --- a/particle-track-and-trace/src/commands/SpawnPointCallback.cpp +++ b/particle-track-and-trace/src/commands/SpawnPointCallback.cpp @@ -17,6 +17,7 @@ void convertDisplayToWorld(vtkRenderer *renderer, int x, int y, double *worldPos } void SpawnPointCallback::Execute(vtkObject *caller, unsigned long evId, void *callData) { + // Note the use of reinterpret_cast to cast the caller to the expected type. auto interactor = reinterpret_cast(caller); @@ -39,17 +40,12 @@ void SpawnPointCallback::Execute(vtkObject *caller, unsigned long evId, void *ca 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); + points->InsertNextPoint(worldPos[0], worldPos[1], 0); - vtkSmartPointer vertex = vtkSmartPointer::New(); - vertex->GetPointIds()->SetId(0, id); - - vtkSmartPointer vertices = vtkSmartPointer::New(); - vertices->InsertNextCell(vertex); - data->SetVerts(vertices); + // FIXME: The below lines cause some weird interaction with our vtkTimer. + // see github issue https://github.com/MakeNEnjoy/interactive-track-and-trace/issues/28 + this->points->Modified(); ren->GetRenderWindow()->Render(); } @@ -60,7 +56,7 @@ SpawnPointCallback::SpawnPointCallback() : data(nullptr), uvGrid(nullptr) { } SpawnPointCallback *SpawnPointCallback::New() { - return new SpawnPointCallback; + return new SpawnPointCallback; } void SpawnPointCallback::setData(const vtkSmartPointer &data) { From e674dde67befe43a1ebeaf79a48c787aae565681 Mon Sep 17 00:00:00 2001 From: djairoh Date: Tue, 7 May 2024 15:46:29 +0200 Subject: [PATCH 2/5] added funcs to get min/max of lon/lat values --- .../src/advection/UVGrid.cpp | 18 +++++++++++++- .../src/advection/UVGrid.h | 24 +++++++++++++++++++ 2 files changed, 41 insertions(+), 1 deletion(-) diff --git a/particle-track-and-trace/src/advection/UVGrid.cpp b/particle-track-and-trace/src/advection/UVGrid.cpp index f35aa72..393b507 100644 --- a/particle-track-and-trace/src/advection/UVGrid.cpp +++ b/particle-track-and-trace/src/advection/UVGrid.cpp @@ -55,6 +55,22 @@ int UVGrid::timeStep() const { return times[1] - times[0]; } +double UVGrid::lonMin() const { + return this->lons.front(); +} + +double UVGrid::lonMax() const { + return this->lons.back(); +} + +double UVGrid::latMin() const { + return this->lats.front(); +} + +double UVGrid::latMax() const { + return this->lats.back(); +} + void UVGrid::streamSlice(ostream &os, size_t t) { for (int x = 0; x < latSize; x++) { for (int y = 0; y < lonSize; y++) { @@ -63,4 +79,4 @@ void UVGrid::streamSlice(ostream &os, size_t t) { } os << endl; } -} \ No newline at end of file +} diff --git a/particle-track-and-trace/src/advection/UVGrid.h b/particle-track-and-trace/src/advection/UVGrid.h index 221363f..4d3a99a 100644 --- a/particle-track-and-trace/src/advection/UVGrid.h +++ b/particle-track-and-trace/src/advection/UVGrid.h @@ -38,6 +38,30 @@ public: */ int timeStep() const; + /** + * Returns the lowest longitudinal value of grid. + * @return minimum longitude + */ + double lonMin() const; + + /** + * Returns the highest longitudinal value of grid. + * @return maximum longitude + */ + double lonMax() const; + + /** + * Returns the lowest latitudinal value of grid. + * @return minimum latitude + */ + double latMin() const; + + /** + * Returns the higehst latitudinal value of grid. + * @return maximum latitude + */ + double latMax() const; + /** * times, lats, lons are vector of length timeSize, latSize, lonSize respectively. * The maintain the following invariant: From 0bde78ee7dd64b3d68cd0a206c171612b678e0e3 Mon Sep 17 00:00:00 2001 From: djairoh Date: Tue, 7 May 2024 15:46:54 +0200 Subject: [PATCH 3/5] feat: beaching of particles --- .../src/commands/SpawnPointCallback.cpp | 6 + .../src/commands/SpawnPointCallback.h | 3 + .../src/layers/LGlyphLayer.cpp | 103 ++++++++++++++---- .../src/layers/LGlyphLayer.h | 2 + 4 files changed, 92 insertions(+), 22 deletions(-) diff --git a/particle-track-and-trace/src/commands/SpawnPointCallback.cpp b/particle-track-and-trace/src/commands/SpawnPointCallback.cpp index b14b6e2..e0c793e 100644 --- a/particle-track-and-trace/src/commands/SpawnPointCallback.cpp +++ b/particle-track-and-trace/src/commands/SpawnPointCallback.cpp @@ -42,9 +42,11 @@ void SpawnPointCallback::Execute(vtkObject *caller, unsigned long evId, void *ca inverseCartographicProjection->MultiplyPoint(worldPos, worldPos); points->InsertNextPoint(worldPos[0], worldPos[1], 0); + this->particlesBeached->InsertNextValue(0); // FIXME: The below lines cause some weird interaction with our vtkTimer. // see github issue https://github.com/MakeNEnjoy/interactive-track-and-trace/issues/28 + this->particlesBeached->Modified(); this->points->Modified(); ren->GetRenderWindow()->Render(); } @@ -76,3 +78,7 @@ void SpawnPointCallback::setUVGrid(const std::shared_ptr &uvGrid) { inverseCartographicProjection = getCartographicTransformMatrix(uvGrid); inverseCartographicProjection->Invert(); } + +void SpawnPointCallback::setBeached(const vtkSmartPointer &ints) { + this->particlesBeached = ints; +} diff --git a/particle-track-and-trace/src/commands/SpawnPointCallback.h b/particle-track-and-trace/src/commands/SpawnPointCallback.h index b7f7682..da527ae 100644 --- a/particle-track-and-trace/src/commands/SpawnPointCallback.h +++ b/particle-track-and-trace/src/commands/SpawnPointCallback.h @@ -23,12 +23,15 @@ public: void setRen(const vtkSmartPointer &ren); + void setBeached(const vtkSmartPointer &parts); + void setUVGrid(const std::shared_ptr &uvGrid); private: vtkSmartPointer data; vtkSmartPointer points; vtkSmartPointer ren; + vtkSmartPointer particlesBeached; std::shared_ptr uvGrid; vtkSmartPointer inverseCartographicProjection; diff --git a/particle-track-and-trace/src/layers/LGlyphLayer.cpp b/particle-track-and-trace/src/layers/LGlyphLayer.cpp index 3a4077a..8d112a6 100644 --- a/particle-track-and-trace/src/layers/LGlyphLayer.cpp +++ b/particle-track-and-trace/src/layers/LGlyphLayer.cpp @@ -2,8 +2,10 @@ #include "../commands/SpawnPointCallback.h" #include #include +#include #include #include +#include #include #include #include @@ -20,16 +22,34 @@ vtkSmartPointer LGlyphLayer::createSpawnPointCallback() { auto newPointCallBack = vtkSmartPointer::New(); - newPointCallBack->setData(data); - newPointCallBack->setPoints(points); - newPointCallBack->setRen(ren); - newPointCallBack->setUVGrid(uvGrid); + newPointCallBack->setData(this->data); + newPointCallBack->setPoints(this->points); + newPointCallBack->setRen(this->ren); + newPointCallBack->setUVGrid(this->uvGrid); + newPointCallBack->setBeached(this->particlesBeached); 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. + +/** + * 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.5,0.5,0.5,0.5] in n steps. + */ +vtkSmartPointer buildLut(int n) { + vtkNew lut; + lut->SetNumberOfColors(n); + lut->SetTableRange(0, n-1); + lut->SetScaleToLinear(); + lut->Build(); + for (int i=0; i < n; i++) { + lut->SetTableValue(i, 1-(0.5*i/(n-1)), 1-(0.5*i/(n-1)), 1-(0.5*i/(n-1)), 1-(0.5*i/(n-1))); + } + // set the last value to separate fully beached particles from those that have simply not moved in a bit. + lut->SetTableValue(n-1, 0, 0, 0, 0.25); + return lut; +} LGlyphLayer::LGlyphLayer(std::shared_ptr uvGrid, std::unique_ptr advectionKernel) { this->ren = vtkSmartPointer::New(); @@ -39,6 +59,13 @@ LGlyphLayer::LGlyphLayer(std::shared_ptr uvGrid, std::unique_ptrdata = vtkSmartPointer::New(); this->data->SetPoints(this->points); + this->particlesBeached = vtkSmartPointer::New(); + this->particlesBeached->SetName("particlesBeached"); + this->particlesBeached->SetNumberOfComponents(0); + + this->data->GetPointData()->AddArray(this->particlesBeached); + this->data->GetPointData()->SetActiveScalars("particlesBeached"); + advector = std::move(advectionKernel); this->uvGrid = uvGrid; @@ -47,17 +74,19 @@ LGlyphLayer::LGlyphLayer(std::shared_ptr uvGrid, std::unique_ptr circleSource; circleSource->SetGlyphTypeToCircle(); - circleSource->SetScale(0.05); + circleSource->SetScale(0.02); circleSource->Update(); vtkNew glyph2D; glyph2D->SetSourceConnection(circleSource->GetOutputPort()); glyph2D->SetInputConnection(transformFilter->GetOutputPort()); - glyph2D->SetColorModeToColorByScalar(); + glyph2D->SetScaleModeToDataScalingOff(); glyph2D->Update(); vtkNew mapper; mapper->SetInputConnection(glyph2D->GetOutputPort()); + mapper->SetLookupTable(buildLut(this->beachedAtNumberOfTimes)); + mapper->SetScalarRange(0, this->beachedAtNumberOfTimes); mapper->Update(); vtkNew actor; @@ -66,17 +95,22 @@ LGlyphLayer::LGlyphLayer(std::shared_ptr uvGrid, std::unique_ptrren->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 + // auto id =this->points->InsertNextPoint(6.532949683882039, 53.24308582564463, 0); // Coordinates of Zernike + // this->particlesBeached->SetValue(id, 0); + // id = this->points->InsertNextPoint(5.315307819255385, 60.40001057122271, 0); // Coordinates of Bergen + // this->particlesBeached->SetValue(id, 0); + // id = this->points->InsertNextPoint( 6.646210231365825, 46.52346296009023, 0); // Coordinates of Lausanne + // this->particlesBeached->SetValue(id, 0); + // id = this->points->InsertNextPoint(-6.553894313570932, 62.39522131195857, 0); // Coordinates of the top of the Faroe islands + // this->particlesBeached->SetValue(id, 0); for (int i=0; i < 330; i+=5) { for (int j=0; j < 330; j+=5) { - this->points->InsertNextPoint(-15.875+(12.875+15.875)/330*j, 46.125+(62.625-46.125)/330*i, 0); + this->points->InsertNextPoint(-15.875+(12.875+15.875)/330*j, 46.125+(62.625-46.125)/330*i, 0); + this->particlesBeached->InsertNextValue(0); } } @@ -85,16 +119,41 @@ void LGlyphLayer::spoofPoints() { void LGlyphLayer::updateData(int t) { const int SUPERSAMPLINGRATE = 4; - double point[3]; - for (vtkIdType n = 0; n < this->points->GetNumberOfPoints(); n++) { - this->points->GetPoint(n, point); - for (int i = 0; i < SUPERSAMPLINGRATE; i++) { - std::tie(point[1], point[0]) = advector->advect(t, point[1], point[0], (t-lastT)/SUPERSAMPLINGRATE); + double point[3], oldX, oldY; + bool modifiedData = false; + + // iterate over every point. + for (vtkIdType n=0; n < this->points->GetNumberOfPoints(); n++) { + int beachedFor = this->particlesBeached->GetValue(n); + // first check: only update non-beached particles. + if (beachedFor < this->beachedAtNumberOfTimes) { + this->points->GetPoint(n, point); + // second check: only update points within our grid's boundary. + if (point[0] < this->uvGrid->lonMin() or point[0] > this->uvGrid->lonMax() or point[1] < this->uvGrid->latMin() or point[1] > this->uvGrid->latMax()) { + // sets any particle out of bounds to be beached - so it gets assigned the right colour in the lookup table. + this->particlesBeached->SetValue(n, this->beachedAtNumberOfTimes); + continue; + } + + oldX = point[0]; oldY = point[1]; + + // supersampling + for (int i=0; i < SUPERSAMPLINGRATE; i++) { + std::tie(point[1], point[0]) = advector->advect(t, point[1], point[0], (t-this->lastT)/SUPERSAMPLINGRATE); + } + + // if the particle's location remains unchanged, increase beachedFor number. Else, decrease it and update point position. + if (oldX == point[0] and oldY == point[1]) { + this->particlesBeached->SetValue(n, beachedFor+1); + } else { + this->particlesBeached->SetValue(n, std::max(beachedFor-1, 0)); + this->points->SetPoint(n, point); + modifiedData = true; + } } - this->points->SetPoint(n, point[0], point[1], 0); } - lastT = t; - this->points->Modified(); + if (modifiedData) this->points->Modified(); + this->lastT = t; } void LGlyphLayer::addObservers(vtkSmartPointer interactor) { diff --git a/particle-track-and-trace/src/layers/LGlyphLayer.h b/particle-track-and-trace/src/layers/LGlyphLayer.h index 125282f..91bc42a 100644 --- a/particle-track-and-trace/src/layers/LGlyphLayer.h +++ b/particle-track-and-trace/src/layers/LGlyphLayer.h @@ -14,9 +14,11 @@ class LGlyphLayer : public Layer { private: vtkSmartPointer points; vtkSmartPointer data; + vtkSmartPointer particlesBeached; std::unique_ptr advector; std::shared_ptr uvGrid; int lastT = 1000; + int beachedAtNumberOfTimes = 20; public: /** Constructor. From 0c081763869ab743bfcc60426f134b6bdad11de2 Mon Sep 17 00:00:00 2001 From: djairoh Date: Tue, 7 May 2024 16:06:30 +0200 Subject: [PATCH 4/5] made cartograph use new uvgrid funcs --- .../src/CartographicTransformation.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/particle-track-and-trace/src/CartographicTransformation.cpp b/particle-track-and-trace/src/CartographicTransformation.cpp index 5518d61..5932c9c 100644 --- a/particle-track-and-trace/src/CartographicTransformation.cpp +++ b/particle-track-and-trace/src/CartographicTransformation.cpp @@ -16,10 +16,10 @@ vtkSmartPointer createNormalisedCamera() { } vtkSmartPointer getCartographicTransformMatrix(const std::shared_ptr uvGrid) { - const double XMin = uvGrid->lons.front(); - const double XMax = uvGrid->lons.back(); - const double YMin = uvGrid->lats.front(); - const double YMax = uvGrid->lats.back(); + const double XMin = uvGrid->lonMin(); + const double XMax = uvGrid->lonMax(); + const double YMin = uvGrid->latMin(); + const double YMax = uvGrid->latMax(); double eyeTransform[] = { 2/(XMax-XMin), 0, 0, -(XMax+XMin)/(XMax-XMin), From f57562a14d942f4904a99c1e11c1a052b21fb552 Mon Sep 17 00:00:00 2001 From: djairoh Date: Tue, 7 May 2024 17:09:09 +0200 Subject: [PATCH 5/5] fix off-by-one in boundary check --- particle-track-and-trace/src/layers/LGlyphLayer.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/particle-track-and-trace/src/layers/LGlyphLayer.cpp b/particle-track-and-trace/src/layers/LGlyphLayer.cpp index 8d112a6..243cd29 100644 --- a/particle-track-and-trace/src/layers/LGlyphLayer.cpp +++ b/particle-track-and-trace/src/layers/LGlyphLayer.cpp @@ -129,7 +129,7 @@ void LGlyphLayer::updateData(int t) { if (beachedFor < this->beachedAtNumberOfTimes) { this->points->GetPoint(n, point); // second check: only update points within our grid's boundary. - if (point[0] < this->uvGrid->lonMin() or point[0] > this->uvGrid->lonMax() or point[1] < this->uvGrid->latMin() or point[1] > this->uvGrid->latMax()) { + if (point[0] <= this->uvGrid->lonMin() or point[0] >= this->uvGrid->lonMax() or point[1] <= this->uvGrid->latMin() or point[1] >= this->uvGrid->latMax()) { // sets any particle out of bounds to be beached - so it gets assigned the right colour in the lookup table. this->particlesBeached->SetValue(n, this->beachedAtNumberOfTimes); continue;