From 28fb65103755146b09c87a375cf19b87458a03ee Mon Sep 17 00:00:00 2001 From: djairoh Date: Wed, 29 May 2024 20:33:36 +0200 Subject: [PATCH] fix: LCol colour maps tweaked --- .../src/layers/EColLayer.cpp | 4 +- .../src/layers/EGlyphLayer.cpp | 2 +- .../src/layers/LColLayer.cpp | 143 ++++++++++-------- .../src/layers/LColLayer.h | 15 +- .../src/layers/LGlyphLayer.cpp | 8 +- particle-track-and-trace/src/layers/luts.cpp | 68 ++++++++- particle-track-and-trace/src/main.cpp | 9 ++ 7 files changed, 175 insertions(+), 74 deletions(-) diff --git a/particle-track-and-trace/src/layers/EColLayer.cpp b/particle-track-and-trace/src/layers/EColLayer.cpp index f003d6b..aa10f6f 100644 --- a/particle-track-and-trace/src/layers/EColLayer.cpp +++ b/particle-track-and-trace/src/layers/EColLayer.cpp @@ -117,7 +117,7 @@ void EColLayer::readCoordinates() { } u /= 4; v /= 4; - this->lutIdx->SetTuple1(cellId++, calcIndex(u,v, this->maxStrength)); + this->lutIdx->SetValue(cellId++, calcIndex(u,v, this->maxStrength)); } latIndex++; } @@ -156,7 +156,7 @@ void EColLayer::updateData(int t) { } u /= 4; v /= 4; - this->lutIdx->SetTuple1(i++, calcIndex(u,v, this->maxStrength)); + this->lutIdx->SetValue(i++, calcIndex(u,v, this->maxStrength)); } } this->lutIdx->Modified(); diff --git a/particle-track-and-trace/src/layers/EGlyphLayer.cpp b/particle-track-and-trace/src/layers/EGlyphLayer.cpp index 85a9bde..715bf81 100644 --- a/particle-track-and-trace/src/layers/EGlyphLayer.cpp +++ b/particle-track-and-trace/src/layers/EGlyphLayer.cpp @@ -69,7 +69,7 @@ void EGlyphLayer::readCoordinates() { vtkNew arrowSource; arrowSource->SetGlyphTypeToArrow(); - arrowSource->SetScale(0.2); //TODO: set this properly + arrowSource->SetScale(0.2); arrowSource->Update(); vtkNew glyph2D; diff --git a/particle-track-and-trace/src/layers/LColLayer.cpp b/particle-track-and-trace/src/layers/LColLayer.cpp index 3ca23ac..9d4afff 100644 --- a/particle-track-and-trace/src/layers/LColLayer.cpp +++ b/particle-track-and-trace/src/layers/LColLayer.cpp @@ -20,6 +20,9 @@ #include #include "../CartographicTransformation.h" +#include "luts.h" + +using namespace std; // TODO: spawning one particle per event is nice and all, but for a colour map doesnt really look great // potential solution: spawn a number of particles randomly around the selected point instead. @@ -36,33 +39,12 @@ vtkSmartPointer LColLayer::createSpawnPointCallback() { } -/** - */ -// TODO: edit this function; probably extract all lut builders into a builder pattern or something. -vtkSmartPointer buildLutDens() { - int n = 5; - vtkNew lut; - lut->SetNumberOfColors(n); - lut->SetTableRange(1, n+1); - lut->SetScaleToLinear(); - lut->Build(); - for (int i=n-1; i >= 0; i--) { - lut->SetTableValue(i, 0, 1-(0.75*i/(n-1)), 1, 1); - } - lut->UseAboveRangeColorOn(); - lut->SetAboveRangeColor(1,0,0,1); - - lut->UseBelowRangeColorOn(); - lut->SetBelowRangeColor(1,1,1,0); - - return lut; - -} - // There's two separate pipelines going on here: the one where we build a vtkPoints array for the spawnpointcallback, // and the one where we build a vtkPolyData with Cells for the colour map. // TODO: it would make sense to separate these pipelines out to their own functions. -LColLayer::LColLayer(std::shared_ptr uvGrid, std::unique_ptr advectionKernel) { +LColLayer::LColLayer(shared_ptr uvGrid, unique_ptr advectionKernel) { + buildLuts(); + // general management; probably should be the actual constructor. this->ren = vtkSmartPointer::New(); this->ren->SetLayer(2); @@ -78,15 +60,14 @@ LColLayer::LColLayer(std::shared_ptr uvGrid, std::unique_ptrparticlesBeached = vtkSmartPointer::New(); this->particlesBeached->SetName("particlesBeached"); - this->particlesBeached->SetNumberOfComponents(1); this->particlesAge = vtkSmartPointer::New(); this->particlesAge->SetName("particlesAge"); - this->particlesAge->SetNumberOfComponents(1); this->lutIdx = vtkSmartPointer::New(); this->lutIdx->SetName("lutIdx"); - this->lutIdx->SetNumberOfComponents(1); + this->lutIdx->SetNumberOfTuples((numLats-1)*(numLons-1)); + this->lutIdx->Fill(-1); // pipeline 2 @@ -97,12 +78,15 @@ LColLayer::LColLayer(std::shared_ptr uvGrid, std::unique_ptrAllocate((numLats-1)*(numLons-1)); this->cellParticleDensity = vtkSmartPointer::New(); - this->cellParticleDensity->SetName("cellParticleDensity"); - this->cellParticleDensity->SetNumberOfComponents(1); this->cellParticleDensity->SetNumberOfTuples((numLats-1)*(numLons-1)); + this->cellParticleDensity->Fill(0); - this->data->GetCellData()->AddArray(this->cellParticleDensity); - this->data->GetCellData()->SetActiveScalars("cellParticleDensity"); + this->cellParticleAge = vtkSmartPointer::New(); + this->cellParticleAge->SetNumberOfTuples((numLats-1)*(numLons-1)); + this->cellParticleDensity->Fill(0); + + this->data->GetCellData()->AddArray(this->lutIdx); + this->data->GetCellData()->SetActiveScalars("lutIdx"); vtkSmartPointer transformFilter = createCartographicTransformFilter(uvGrid); auto transform = transformFilter->GetTransform(); @@ -127,34 +111,39 @@ LColLayer::LColLayer(std::shared_ptr uvGrid, std::unique_ptrSetId(3, idx); this->data->InsertNextCell(VTK_QUAD, l); - this->cellParticleDensity->SetTuple1(cellId++, 0); } latIndex++; } lonIndex++; } - vtkNew(mapper); - mapper->SetInputData(data); - mapper->SetLookupTable(buildLutDens()); - mapper->UseLookupTableScalarRangeOn(); - mapper->Update(); + this->mapper = vtkSmartPointer::New(); + this->mapper->SetInputData(data); + setColourMode(COMPLEMENTARY); + this->mapper->UseLookupTableScalarRangeOn(); + this->mapper->Update(); vtkNew actor; - actor->SetMapper(mapper); - actor->GetProperty()->SetColor(0, 1, 0); - actor->GetProperty()->SetOpacity(0.5); - - // vtkNew act2; - // act2->SetMapper(mapper); - // act2->GetProperty()->SetRepresentationToWireframe(); - // this->ren->AddActor(act2); + actor->SetMapper(this->mapper); + // actor->GetProperty()->SetOpacity(0.5); + actor->GetProperty()->SetOpacity(1); this->ren->AddActor(actor); this->callback = createSpawnPointCallback(); } + +void LColLayer::buildLuts() { + this->tables.push_back(buildDensityComplementary()); + this->tables.push_back(buildDensityContrasting()); + this->tables.push_back(buildDensityMonochromatic()); + this->tables.push_back(buildDensityDesaturated()); + + this->activeColourMode = COMPLEMENTARY; + this->activeSaturationMode = SATURATED; +} + void LColLayer::spoofPoints() { for (int i=0; i < 330; i+=5) { for (int j=0; j < 330; j+=5) { @@ -166,15 +155,7 @@ void LColLayer::spoofPoints() { this->points->Modified(); } -// calculates the cellIndex for a given lat,lon pair. -int LColLayer::calcIndex(double x, double y) { - int lonIndex = std::floor((x - uvGrid->lonMin()) / uvGrid->lonStep()); - int latIndex = std::floor((y - uvGrid->latMin()) / uvGrid->latStep()); - - return latIndex+lonIndex*(uvGrid->latSize-1); -} - - +// FIXME: delete this once done testing void printArray(vtkSmartPointer data, int numLons, int numLats, int latsize) { for (int i=0; i < numLons-1; i++) { for (int j=0; j < numLats-1; j++) { @@ -189,8 +170,8 @@ void printArray(vtkSmartPointer data, int numLons, int numLats, int void LColLayer::updateData(int t) { const int SUPERSAMPLINGRATE = 4; double point[3], oldX, oldY; - bool modifiedData = false; this->cellParticleDensity->Fill(0); + this->cellParticleAge->Fill(0); // iterate over every point. for (vtkIdType n=0; n < this->points->GetNumberOfPoints(); n++) { @@ -216,28 +197,27 @@ void LColLayer::updateData(int t) { // supersampling for (int i=0; i < SUPERSAMPLINGRATE; i++) { - std::tie(point[1], point[0]) = advector->advect(t, point[1], point[0], this->dt/SUPERSAMPLINGRATE); + 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->particlesBeached->SetValue(n, max(beachedFor-1, 0)); this->points->SetPoint(n, point); - modifiedData = true; } } // add point to cellparticleDensity - int index = calcIndex(point[0], point[1]); + int index = calcCellIndex(point[0], point[1], uvGrid); this->cellParticleDensity->SetValue(index, cellParticleDensity->GetValue(index)+1); + this->cellParticleAge->SetValue(index, cellParticleAge->GetValue(index)+age+1); } - if (modifiedData) { - this->particlesAge->Modified(); - this->points->Modified(); - this->cellParticleDensity->Modified(); + + for (int idx=0; idx < (numLons-1)*(numLats-1); idx++) { + this->lutIdx->SetValue(idx, calcIndex(this->cellParticleAge->GetValue(idx), this->cellParticleDensity->GetValue(idx))); } - // printArray(this->cellParticleDensity, numLons, numLats, uvGrid->latSize-1); + this->lutIdx->Modified(); } void LColLayer::addObservers(vtkSmartPointer interactor) { @@ -256,3 +236,38 @@ void LColLayer::removeObservers(vtkSmartPointer inter void LColLayer::setDt(int dt) { this->dt = dt; } + + +void LColLayer::setColourMode(ColourMode mode) { + this->activeColourMode = mode; + if (this->activeSaturationMode == DESATURATED) return; + + this->mapper->SetLookupTable(this->tables[mode]); +} + +void LColLayer::setSaturationMode(SaturationMode mode) { + this->activeSaturationMode = mode; + + if (mode == DESATURATED) { + this->mapper->SetLookupTable(this->tables[mode]); + } else { + this->mapper->SetLookupTable(this->tables[this->activeColourMode]); + } +} + +// TODO: this function can do with some improvement as well; it's completely heuristic right now. +int calcIndex(const int age, const int density) { + if (not density) return -1; + int calcAge = (double)age/density/60; + return min(9, calcAge)+min(9, density)*10; +} + + +int calcCellIndex(const double u, const double v, const shared_ptr uvGrid) { + int lonIndex = floor((u - uvGrid->lonMin()) / uvGrid->lonStep()); + int latIndex = floor((v - uvGrid->latMin()) / uvGrid->latStep()); + + return latIndex+lonIndex*(uvGrid->latSize-1); +} + + diff --git a/particle-track-and-trace/src/layers/LColLayer.h b/particle-track-and-trace/src/layers/LColLayer.h index 8c45113..dad5ff3 100644 --- a/particle-track-and-trace/src/layers/LColLayer.h +++ b/particle-track-and-trace/src/layers/LColLayer.h @@ -14,11 +14,16 @@ class LColLayer : public Layer { private: vtkSmartPointer points; vtkSmartPointer data; + vtkSmartPointer mapper; vtkSmartPointer particlesBeached; vtkSmartPointer particlesAge; vtkSmartPointer lutIdx; vtkSmartPointer cellParticleDensity; + vtkSmartPointer cellParticleAge; vtkSmartPointer callback; + std::vector> tables; + ColourMode activeColourMode; + SaturationMode activeSaturationMode; std::unique_ptr advector; std::shared_ptr uvGrid; int dt = 3600; @@ -26,8 +31,7 @@ private: int numLats; int numLons; - - int calcIndex(double x, double y); + void buildLuts(); public: /** Constructor. @@ -51,10 +55,17 @@ public: void removeObservers(vtkSmartPointer interactor) override; + void setColourMode(ColourMode mode) override; + void setSaturationMode(SaturationMode mode) override; + /** * Sets a custom DT value, needed for advect calls to the simulation logic. */ void setDt(int dt); }; +// TODO: comments +int calcIndex(const int age, const int density); +int calcCellIndex(const double u, const double v, const std::shared_ptr uvGrid); + #endif diff --git a/particle-track-and-trace/src/layers/LGlyphLayer.cpp b/particle-track-and-trace/src/layers/LGlyphLayer.cpp index d458553..215efc4 100644 --- a/particle-track-and-trace/src/layers/LGlyphLayer.cpp +++ b/particle-track-and-trace/src/layers/LGlyphLayer.cpp @@ -120,7 +120,7 @@ void LGlyphLayer::updateData(int t) { 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->particlesAge->SetValue(n, -1); - this->lutIdx->SetTuple1(n, -1); + this->lutIdx->SetValue(n, -1); continue; } @@ -143,15 +143,15 @@ void LGlyphLayer::updateData(int t) { // 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); - this->lutIdx->SetTuple1(n, beachedFor == this->beachedAtNumberOfTimes-2 ? calcIndex(age+1, true) : calcIndex(age+1, false)); + this->lutIdx->SetValue(n, beachedFor == this->beachedAtNumberOfTimes-2 ? calcIndex(age+1, true) : calcIndex(age+1, false)); } else { this->particlesBeached->SetValue(n, std::max(beachedFor-1, 0)); this->points->SetPoint(n, point); - this->lutIdx->SetTuple1(n, calcIndex(age+1, false)); + this->lutIdx->SetValue(n, calcIndex(age+1, false)); modifiedData = true; } } else { - this->lutIdx->SetTuple1(n, calcIndex(age+1, true)); + this->lutIdx->SetValue(n, calcIndex(age+1, true)); } } if (modifiedData) { diff --git a/particle-track-and-trace/src/layers/luts.cpp b/particle-track-and-trace/src/layers/luts.cpp index 18eeff4..eac57e1 100644 --- a/particle-track-and-trace/src/layers/luts.cpp +++ b/particle-track-and-trace/src/layers/luts.cpp @@ -74,7 +74,7 @@ vtkSmartPointer buildCyclicComplementary() { lut->Build(); int idx=0; - for (double opacity=0.1; opacity <= 1.0; opacity+=0.1) { + for (double opacity=0.5; opacity <= 1.0; opacity+=0.05) { lut->SetTableValue(idx++, 0.247059, 0.243137, 0.227451, opacity); lut->SetTableValue(idx++, 0.243137, 0.266667, 0.364706, opacity); lut->SetTableValue(idx++, 0.329412, 0.431373, 0.580392, opacity); @@ -178,22 +178,88 @@ vtkSmartPointer buildCyclicDesaturated() { // LCol tables vtkSmartPointer buildDensityComplementary() { + // uses a reverse matplotlib YlGn colour map. vtkNew lut; + lut->SetNumberOfColors(100); + lut->SetTableRange(0, 99); + lut->SetScaleToLinear(); + lut->Build(); + int idx=0; + for (double opacity=0.5; opacity <= 1.0; opacity+=0.05) { + lut->SetTableValue(idx++, 0.0, 0.27058823529411763, 0.1607843137254902, opacity); + lut->SetTableValue(idx++, 0.0, 0.3911572472126105, 0.20901191849288736, opacity); + lut->SetTableValue(idx++, 0.10388312187620147, 0.4909496347558632, 0.2513033448673587, opacity); + lut->SetTableValue(idx++, 0.21568627450980393, 0.6196078431372549, 0.330718954248366, opacity); + lut->SetTableValue(idx++, 0.3724721261053441, 0.7283044982698962, 0.424559784698193, opacity); + lut->SetTableValue(idx++, 0.5651364859669358, 0.8175009611687812, 0.5119723183391004, opacity); + lut->SetTableValue(idx++, 0.7359477124183007, 0.8915032679738563, 0.5843137254901961, opacity); + lut->SetTableValue(idx++, 0.8795847750865051, 0.9526182237600923, 0.6601922337562476, opacity); + lut->SetTableValue(idx++, 0.9724413687043445, 0.9896655132641292, 0.7464667435601692, opacity); + lut->SetTableValue(idx++, 1.0, 1.0, 0.8980392156862745, opacity); + } + + lut->UseBelowRangeColorOn(); + lut->SetBelowRangeColor(0,0,0,0); + lut->SetNanColor(0.0,0,0,0); return lut; } vtkSmartPointer buildDensityContrasting() { + // uses the matplotlib viridis colour map. vtkNew lut; + lut->SetNumberOfColors(100); + lut->SetTableRange(0, 99); + lut->SetScaleToLinear(); + lut->Build(); + int idx=0; + for (double opacity=0.5; opacity <= 1.0; opacity+=0.05) { + lut->SetTableValue(idx++, 0.267004, 0.004874, 0.329415, opacity); + lut->SetTableValue(idx++, 0.281412, 0.155834, 0.469201, opacity); + lut->SetTableValue(idx++, 0.244972, 0.287675, 0.53726, opacity); + lut->SetTableValue(idx++, 0.190631, 0.407061, 0.556089, opacity); + lut->SetTableValue(idx++, 0.147607, 0.511733, 0.557049, opacity); + lut->SetTableValue(idx++, 0.119699, 0.61849, 0.536347, opacity); + lut->SetTableValue(idx++, 0.20803, 0.718701, 0.472873, opacity); + lut->SetTableValue(idx++, 0.430983, 0.808473, 0.346476, opacity); + lut->SetTableValue(idx++, 0.709898, 0.868751, 0.169257, opacity); + lut->SetTableValue(idx++, 0.993248, 0.906157, 0.143936, opacity); + } + + lut->UseBelowRangeColorOn(); + lut->SetBelowRangeColor(0,0,0,0); + lut->SetNanColor(0.0,0,0,0); return lut; } vtkSmartPointer buildDensityMonochromatic() { + // uses a reverse matplotlib Greens colour map. vtkNew lut; + lut->SetNumberOfColors(100); + lut->SetTableRange(0, 99); + lut->SetScaleToLinear(); + lut->Build(); + int idx=0; + for (double opacity=0.5; opacity <= 1.0; opacity+=0.05) { + lut->SetTableValue(idx++, 0.0, 0.26666666666666666, 0.10588235294117647, opacity); + lut->SetTableValue(idx++, 0.0, 0.4079046520569012, 0.16444444444444445, opacity); + lut->SetTableValue(idx++, 0.10388312187620147, 0.5164936562860438, 0.2467512495194156, opacity); + lut->SetTableValue(idx++, 0.21568627450980393, 0.6287581699346405, 0.3333333333333333, opacity); + lut->SetTableValue(idx++, 0.36392156862745095, 0.7240292195309497, 0.4181468665897732, opacity); + lut->SetTableValue(idx++, 0.5351787773933102, 0.8060899653979239, 0.5287504805843906, opacity); + lut->SetTableValue(idx++, 0.681045751633987, 0.8718954248366013, 0.6562091503267974, opacity); + lut->SetTableValue(idx++, 0.8089965397923875, 0.9251672433679354, 0.7834525182622069, opacity); + lut->SetTableValue(idx++, 0.9066205305651672, 0.9641214917339485, 0.8884429065743944, opacity); + lut->SetTableValue(idx++, 0.9686274509803922, 0.9882352941176471, 0.9607843137254902, opacity); + } + + lut->UseBelowRangeColorOn(); + lut->SetBelowRangeColor(0,0,0,0); + lut->SetNanColor(0.0,0,0,0); return lut; } diff --git a/particle-track-and-trace/src/main.cpp b/particle-track-and-trace/src/main.cpp index 1232dbe..449ab0e 100644 --- a/particle-track-and-trace/src/main.cpp +++ b/particle-track-and-trace/src/main.cpp @@ -2,6 +2,15 @@ #include #include "QT/MainWindow.h" +// TODO: add a widget to act as legend. This is fourfold: particle-age, LCol-density, Lcol-age, ECol-direction ECol-strength? +// TODO: add text widget showing simulation time (in days/weeks/months from 0). +// TODO: make Lagrangian Layers share one vtkPoints for seemless technique switching +// TODO: make LColLayer use a modified spawnpointCallback to spawn multiple particles per interaction +// TODO: add text gui explaining the above? +// TODO: yoink Robin's isNearestNeighbourZero function to improve beaching +// TODO: add a button to reset the simulation - set time=0 and reset points array of particles. +// FIXME: go over each function and add const where appropriate. + int main(int argc, char* argv[]) { QSurfaceFormat::setDefaultFormat(QVTKOpenGLNativeWidget::defaultFormat());