diff --git a/particle-track-and-trace/src/layers/LColLayer.cpp b/particle-track-and-trace/src/layers/LColLayer.cpp index e69de29..5dbd57b 100644 --- a/particle-track-and-trace/src/layers/LColLayer.cpp +++ b/particle-track-and-trace/src/layers/LColLayer.cpp @@ -0,0 +1,179 @@ +#include "LGlyphLayer.h" +#include "../commands/SpawnPointCallback.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../CartographicTransformation.h" + +vtkSmartPointer LGlyphLayer::createSpawnPointCallback() { + auto newPointCallBack = vtkSmartPointer::New(); + newPointCallBack->setData(this->data); + newPointCallBack->setPoints(this->points); + newPointCallBack->setRen(this->ren); + newPointCallBack->setUVGrid(this->uvGrid); + newPointCallBack->setBeached(this->particlesBeached); + newPointCallBack->setAge(this->particlesAge); + 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. + +/** + * 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 [1,1,1,0.25] in n steps. + */ +vtkSmartPointer buildLut(int n) { + vtkNew lut; + lut->SetNumberOfColors(n); + lut->SetTableRange(0, n); + lut->SetScaleToLinear(); + lut->Build(); + for (int i=0; i < n; i++) { + lut->SetTableValue(i, 1, 1, 1, 1-(0.75*i/(n-1))); + } + lut->UseAboveRangeColorOn(); + lut->SetAboveRangeColor(1,1,1,0.20); + + // We cheat a little here: any particle with an age of -1 is out of bounds, and thus set invisible. + lut->UseBelowRangeColorOn(); + lut->SetBelowRangeColor(1,1,1,0); + + return lut; +} + +LGlyphLayer::LGlyphLayer(std::shared_ptr uvGrid, std::unique_ptr advectionKernel) { + this->ren = vtkSmartPointer::New(); + this->ren->SetLayer(2); + + this->points = vtkSmartPointer::New(); + vtkNew data; + data->SetPoints(this->points); + + this->particlesBeached = vtkSmartPointer::New(); + this->particlesBeached->SetName("particlesBeached"); + this->particlesBeached->SetNumberOfComponents(0); + + this->particlesAge = vtkSmartPointer::New(); + this->particlesAge->SetName("particlesAge"); + this->particlesAge->SetNumberOfComponents(0); + + data->GetPointData()->AddArray(this->particlesBeached); + data->GetPointData()->AddArray(this->particlesAge); + data->GetPointData()->SetActiveScalars("particlesAge"); + + advector = std::move(advectionKernel); + this->uvGrid = uvGrid; + + vtkSmartPointer transformFilter = createCartographicTransformFilter(uvGrid); + transformFilter->SetInputData(data); + + vtkNew circleSource; + circleSource->SetGlyphTypeToCircle(); + circleSource->SetScale(0.02); + circleSource->Update(); + + vtkNew glyph2D; + glyph2D->SetSourceConnection(circleSource->GetOutputPort()); + glyph2D->SetInputConnection(transformFilter->GetOutputPort()); + glyph2D->SetScaleModeToDataScalingOff(); + glyph2D->Update(); + + vtkNew mapper; + mapper->SetInputConnection(glyph2D->GetOutputPort()); + mapper->SetColorModeToMapScalars(); + mapper->SetLookupTable(buildLut(512)); + mapper->UseLookupTableScalarRangeOn(); + mapper->Update(); + + vtkNew actor; + actor->SetMapper(mapper); + + this->ren->AddActor(actor); +} + +void LGlyphLayer::spoofPoints() { + 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->particlesBeached->InsertNextValue(0); + this->particlesAge->InsertNextValue(0); + } + } + this->points->Modified(); +} + +void LGlyphLayer::updateData(int t) { + const int SUPERSAMPLINGRATE = 4; + double point[3], oldX, oldY; + bool modifiedData = false; + + // iterate over every point. + for (vtkIdType n=0; n < this->points->GetNumberOfPoints(); n++) { + // first check: only update points within our grid's boundary. + this->points->GetPoint(n, point); + 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+1); + this->particlesAge->SetValue(n, -1); + continue; + } + + // update particle age. + int age = this->particlesAge->GetValue(n); + if (age >= 0) + this->particlesAge->SetValue(n, age+1); + + // second check: only update non-beached particles. + int beachedFor = this->particlesBeached->GetValue(n); + if (beachedFor < this->beachedAtNumberOfTimes-1) { + + 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], this->dt/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; + } + } + } + if (modifiedData) { + this->particlesAge->Modified(); + this->points->Modified(); + } +} + +void LGlyphLayer::addObservers(vtkSmartPointer interactor) { + auto newPointCallBack = createSpawnPointCallback(); + interactor->AddObserver(vtkCommand::LeftButtonPressEvent, newPointCallBack); + interactor->AddObserver(vtkCommand::LeftButtonReleaseEvent, newPointCallBack); + interactor->AddObserver(vtkCommand::MouseMoveEvent, newPointCallBack); +} + + +void LGlyphLayer::setDt(int dt) { + this->dt = dt; +} diff --git a/particle-track-and-trace/src/layers/LColLayer.h b/particle-track-and-trace/src/layers/LColLayer.h index e69de29..79ede1f 100644 --- a/particle-track-and-trace/src/layers/LColLayer.h +++ b/particle-track-and-trace/src/layers/LColLayer.h @@ -0,0 +1,51 @@ +#ifndef LCOLLAYER_H +#define LCOLLAYER_H + +#include "Layer.h" +#include "../advection/kernel/AdvectionKernel.h" +#include "../commands/SpawnPointCallback.h" +#include +#include + +/** 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 LColLayer : public Layer { +private: + vtkSmartPointer points; + vtkSmartPointer data; + vtkSmartPointer particlesBeached; + vtkSmartPointer particlesAge; + std::unique_ptr advector; + std::shared_ptr uvGrid; + int dt = 3600; + int beachedAtNumberOfTimes = 20; + +public: + /** Constructor. + */ + LColLayer(std::shared_ptr uvGrid, std::unique_ptr advectionKernel); + + /** + * 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 createSpawnPointCallback(); + + void addObservers(vtkSmartPointer interactor) override; + + + /** + * Sets a custom DT value, needed for advect calls to the simulation logic. + */ + void setDt(int dt); +}; + +#endif