From 23359f9f54b07b47d760289a1d7c6a785bc90361 Mon Sep 17 00:00:00 2001 From: djairoh Date: Tue, 28 May 2024 17:59:54 +0200 Subject: [PATCH] feat: swappable colour schemes --- particle-track-and-trace/src/CMakeLists.txt | 2 + particle-track-and-trace/src/Program.cpp | 30 ++- particle-track-and-trace/src/Program.h | 8 +- .../src/QT/MainWindow.cpp | 22 +- .../src/advection/UVGrid.cpp | 6 + .../src/advection/UVGrid.h | 8 + .../src/commands/SpawnPointCallback.cpp | 6 + .../src/commands/SpawnPointCallback.h | 2 + .../src/layers/EColLayer.cpp | 142 ++++++------ .../src/layers/EColLayer.h | 46 +++- .../src/layers/LColLayer.cpp | 8 + .../src/layers/LColLayer.h | 1 + .../src/layers/LGlyphLayer.cpp | 124 +++++------ .../src/layers/LGlyphLayer.h | 15 +- particle-track-and-trace/src/layers/Layer.cpp | 3 +- particle-track-and-trace/src/layers/Layer.h | 3 +- .../src/layers/Technique.cpp | 4 +- .../src/layers/Technique.h | 3 +- particle-track-and-trace/src/layers/enums.h | 3 +- particle-track-and-trace/src/layers/luts.cpp | 205 ++++++++++++++++++ particle-track-and-trace/src/layers/luts.h | 20 ++ 21 files changed, 491 insertions(+), 170 deletions(-) create mode 100644 particle-track-and-trace/src/layers/luts.cpp create mode 100644 particle-track-and-trace/src/layers/luts.h diff --git a/particle-track-and-trace/src/CMakeLists.txt b/particle-track-and-trace/src/CMakeLists.txt index d024cdc..fcd6180 100644 --- a/particle-track-and-trace/src/CMakeLists.txt +++ b/particle-track-and-trace/src/CMakeLists.txt @@ -59,6 +59,8 @@ add_executable(ParticleTrackTrace MACOSX_BUNDLE main.cpp layers/Technique.cpp layers/Technique.h layers/enums.h + layers/luts.cpp + layers/luts.h Program.cpp Program.h commands/TimerCallbackCommand.h diff --git a/particle-track-and-trace/src/Program.cpp b/particle-track-and-trace/src/Program.cpp index 2802d43..a0c3232 100644 --- a/particle-track-and-trace/src/Program.cpp +++ b/particle-track-and-trace/src/Program.cpp @@ -57,7 +57,7 @@ Program::Program(QWidget *parent): QVTKOpenGLNativeWidget(parent) { setRenderWindow(this->win); this->interact = win->GetInteractor(); this->cam = createNormalisedCamera(); - this->activeIdx = -1; + this->activeTech = NOTECH; this->win->SetNumberOfLayers(0); setWinProperties(); @@ -75,38 +75,44 @@ void Program::removeTechnique(Technique *technique) { auto it = std::find(this->techniques.begin(), this->techniques.end(), technique); if (it != this->techniques.end()) { int idx = it - this->techniques.begin(); - if (idx == this->activeIdx) { + if (idx == this->activeTech) { throw std::out_of_range("Can't remove active technique."); } this->techniques.erase(it); - this->activeIdx = -1; - setActiveTechnique(0); + this->activeTech = NOTECH; + setActiveTechnique(COLGLYPH); } } +void Program::requestRender() { + this->win->Render(); +} + void Program::updateData(int t) { // FIXME: think on how to update techniques; do we update all? just active? unsure. win->Render(); - this->techniques[this->activeIdx]->updateData(t); + for (Technique *tech : this->techniques) { + tech->updateData(t); + } } -void Program::setActiveTechnique(int idx) { +void Program::setActiveTechnique(ActiveTechnique tech) { // Only change things if a different technique has been selected. - if (idx == this->activeIdx) { + if (tech == this->activeTech) { return; } // check the given idx is valid. - if (idx >= this->techniques.size()) { + if (tech >= this->techniques.size()) { throw std::out_of_range("Index out of range!"); } - if (this->activeIdx >= 0 and this->activeIdx < this->techniques.size()) - this->techniques[this->activeIdx]->unbind(this->win, this->interact); + if (this->activeTech >= 0 and this->activeTech < this->techniques.size()) + this->techniques[this->activeTech]->unbind(this->win, this->interact); - this->techniques[idx]->bind(this->win, this->interact); + this->techniques[tech]->bind(this->win, this->interact); - this->activeIdx = idx; + this->activeTech = tech; this->win->Render(); } diff --git a/particle-track-and-trace/src/Program.h b/particle-track-and-trace/src/Program.h index 193b76a..fb9932a 100644 --- a/particle-track-and-trace/src/Program.h +++ b/particle-track-and-trace/src/Program.h @@ -19,7 +19,7 @@ private: /** This attribute models a variable number of vtkRenderers, managed through the abstract Technique class. */ std::vector techniques; - int activeIdx; + ActiveTechnique activeTech; /** The window this program's layers render to. @@ -72,10 +72,10 @@ public: */ void updateData(int t); + // TODO: commenting + void requestRender(); - // TODO: using an idx to indicate which technique to use is not ideal; use an enum instead? But then the question is where to put it... - void setActiveTechnique(int idx); - + void setActiveTechnique(ActiveTechnique tech); vtkSmartPointer getCamera(); diff --git a/particle-track-and-trace/src/QT/MainWindow.cpp b/particle-track-and-trace/src/QT/MainWindow.cpp index 39a0d6a..7595eee 100644 --- a/particle-track-and-trace/src/QT/MainWindow.cpp +++ b/particle-track-and-trace/src/QT/MainWindow.cpp @@ -69,7 +69,7 @@ void MainWindow::setupTechniques() { program->addTechnique(technique1); program->addTechnique(technique2); - program->setActiveTechnique(0); + program->setActiveTechnique(COLGLYPH); // TODO: implement feature to call this function on widget // l->spoofPoints(); @@ -85,6 +85,7 @@ void MainWindow::setupTechniques() { void MainWindow::on_FirstButton_clicked(bool checked) { if (checked) { ui->program->setActiveTechnique(COLGLYPH); + ui->program->requestRender(); } } @@ -92,6 +93,7 @@ void MainWindow::on_FirstButton_clicked(bool checked) { void MainWindow::on_SecondButton_clicked(bool checked) { if (checked) { ui->program->setActiveTechnique(GLYPHCOL); + ui->program->requestRender(); } } @@ -99,8 +101,9 @@ void MainWindow::on_SecondButton_clicked(bool checked) { void MainWindow::on_ComplementaryButton_clicked(bool checked) { if (checked) { for (Technique *t : ui->program->getTechniques()) { - t->setColorMode(COMPLEMENTARY); + t->setColourMode(COMPLEMENTARY); } + ui->program->requestRender(); } } @@ -108,8 +111,9 @@ void MainWindow::on_ComplementaryButton_clicked(bool checked) { void MainWindow::on_ContrastingButton_clicked(bool checked) { if (checked) { for (Technique *t : ui->program->getTechniques()) { - t->setColorMode(CONTRASTING); + t->setColourMode(CONTRASTING); } + ui->program->requestRender(); } } @@ -117,8 +121,9 @@ void MainWindow::on_ContrastingButton_clicked(bool checked) { void MainWindow::on_MonochromaticButton_clicked(bool checked) { if (checked) { for (Technique *t : ui->program->getTechniques()) { - t->setColorMode(MONOCHROMATIC); + t->setColourMode(MONOCHROMATIC); } + ui->program->requestRender(); } } @@ -128,6 +133,7 @@ void MainWindow::on_SaturateButton_clicked(bool checked) { for (Technique *t : ui->program->getTechniques()) { t->setSaturationMode(SATURATED); } + ui->program->requestRender(); } } @@ -137,6 +143,7 @@ void MainWindow::on_DesaturateButton_clicked(bool checked) { for (Technique *t : ui->program->getTechniques()) { t->setSaturationMode(DESATURATED); } + ui->program->requestRender(); } } @@ -146,6 +153,7 @@ void MainWindow::on_CircleButton_clicked(bool checked) { for (Technique *t : ui->program->getTechniques()) { t->setGlyphStyle(CIRCLE); } + ui->program->requestRender(); } } @@ -155,6 +163,7 @@ void MainWindow::on_TriangleButton_clicked(bool checked) { for (Technique *t : ui->program->getTechniques()) { t->setGlyphStyle(TRIANGLE); } + ui->program->requestRender(); } } @@ -164,6 +173,7 @@ void MainWindow::on_SquareButton_clicked(bool checked) { for (Technique *t : ui->program->getTechniques()) { t->setGlyphStyle(SQUARE); } + ui->program->requestRender(); } } @@ -173,6 +183,7 @@ void MainWindow::on_HexagonButton_clicked(bool checked) { for (Technique *t : ui->program->getTechniques()) { t->setGlyphStyle(HEXAGON); } + ui->program->requestRender(); } } @@ -182,6 +193,7 @@ void MainWindow::on_FullySampledButton_clicked(bool checked) { for (Technique *t : ui->program->getTechniques()) { t->setSamplingMode(FULLYSAMPLED); } + ui->program->requestRender(); } } @@ -191,6 +203,7 @@ void MainWindow::on_RegularlySubsampledButton_clicked(bool checked) { for (Technique *t : ui->program->getTechniques()) { t->setSamplingMode(REGULARLYSUBSAMPLED); } + ui->program->requestRender(); } } @@ -200,6 +213,7 @@ void MainWindow::on_IregularlySubsampledButton_clicked(bool checked) { for (Technique *t : ui->program->getTechniques()) { t->setSamplingMode(IRREGULARLYSUBSAMPLED); } + ui->program->requestRender(); } } diff --git a/particle-track-and-trace/src/advection/UVGrid.cpp b/particle-track-and-trace/src/advection/UVGrid.cpp index 84e9449..f3c2def 100644 --- a/particle-track-and-trace/src/advection/UVGrid.cpp +++ b/particle-track-and-trace/src/advection/UVGrid.cpp @@ -31,6 +31,12 @@ UVGrid::UVGrid(string path) { for (auto vel: views::zip(us, vs)) { uvData.push_back(Vel(vel)); } + + // FIXME: should really read these from file instead of hard-coding them. + this->uMin = -0.381482899188995; + this->uMax = 0.566494882106781; + this->vMin = -0.381482899188995; + this->vMax = 0.470820993185043; } const Vel &UVGrid::operator[](size_t timeIndex, size_t latIndex, size_t lonIndex) const { diff --git a/particle-track-and-trace/src/advection/UVGrid.h b/particle-track-and-trace/src/advection/UVGrid.h index 2954d96..6912f94 100644 --- a/particle-track-and-trace/src/advection/UVGrid.h +++ b/particle-track-and-trace/src/advection/UVGrid.h @@ -25,6 +25,14 @@ public: size_t latSize; size_t lonSize; + /** + * Minimum and Maximum values of the u and v variables. + */ + double uMin; + double uMax; + double vMin; + double vMax; + /** * Assuming grid is a regular grid, gives the longitudinal spacing of grid. * @return longitudinal spacing diff --git a/particle-track-and-trace/src/commands/SpawnPointCallback.cpp b/particle-track-and-trace/src/commands/SpawnPointCallback.cpp index f29cb82..aa85257 100644 --- a/particle-track-and-trace/src/commands/SpawnPointCallback.cpp +++ b/particle-track-and-trace/src/commands/SpawnPointCallback.cpp @@ -43,6 +43,7 @@ void SpawnPointCallback::Execute(vtkObject *caller, unsigned long evId, void *ca points->InsertNextPoint(worldPos[0], worldPos[1], 0); this->particlesBeached->InsertNextValue(0); this->particlesAge->InsertNextValue(0); + this->lutIdx->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 @@ -79,3 +80,8 @@ void SpawnPointCallback::setBeached(const vtkSmartPointer &ints) { void SpawnPointCallback::setAge(const vtkSmartPointer &ints) { this->particlesAge = ints; } + + +void SpawnPointCallback::setIdx(const vtkSmartPointer &idx) { + this->lutIdx = idx; +} diff --git a/particle-track-and-trace/src/commands/SpawnPointCallback.h b/particle-track-and-trace/src/commands/SpawnPointCallback.h index 083c82d..d33d41e 100644 --- a/particle-track-and-trace/src/commands/SpawnPointCallback.h +++ b/particle-track-and-trace/src/commands/SpawnPointCallback.h @@ -24,6 +24,7 @@ public: void setBeached(const vtkSmartPointer &parts); void setAge(const vtkSmartPointer &parts); + void setIdx(const vtkSmartPointer &idx); void setUVGrid(const std::shared_ptr &uvGrid); @@ -32,6 +33,7 @@ private: vtkSmartPointer ren; vtkSmartPointer particlesBeached; vtkSmartPointer particlesAge; + vtkSmartPointer lutIdx; std::shared_ptr uvGrid; vtkSmartPointer inverseCartographicProjection; diff --git a/particle-track-and-trace/src/layers/EColLayer.cpp b/particle-track-and-trace/src/layers/EColLayer.cpp index a7ac2b6..f003d6b 100644 --- a/particle-track-and-trace/src/layers/EColLayer.cpp +++ b/particle-track-and-trace/src/layers/EColLayer.cpp @@ -24,6 +24,7 @@ #include #include "../CartographicTransformation.h" +#include "luts.h" using std::numbers::pi; @@ -38,59 +39,41 @@ EColLayer::EColLayer(std::shared_ptr uvGrid) { this->numLats = uvGrid->latSize; this->numLons = uvGrid->lonSize; - this->strength = vtkSmartPointer::New(); - this->strength->SetName("strength"); - this->strength->SetNumberOfComponents(1); - this->strength->SetNumberOfTuples((numLats-1)*(numLons-1)); - - this->direction = vtkSmartPointer::New(); - this->direction->SetName("direction"); - this->direction->SetNumberOfComponents(1); - this->direction->SetNumberOfTuples((numLats-1)*(numLons-1)); + this->lutIdx = vtkSmartPointer::New(); + this->lutIdx->SetName("lutIdx"); + this->lutIdx->SetNumberOfComponents(1); + this->lutIdx->SetNumberOfTuples((numLats-1)*(numLons-1)); + calcMaxStrength(); + buildLuts(); readCoordinates(); } -/** - * Sets a given rgba colour to a range of values [start, end] in the lut. - * @param lut : lookuptable to operate on. - * @ param start : starting index of range to assign - * @ param end: ending index of range to assign - * @param r : red value [0,1] - * @param g : green value [0,1] - * @param n : blue value [0,1] - * @param a : alpha value [0,1] - */ -void setLutRange(vtkSmartPointer lut, int start, int end, double r, double g, double b, double a) { - for (int i=start; i <= end; i++) { - lut->SetTableValue(i, r, g, b, a); - } +void EColLayer::buildLuts() { + this->tables.push_back(buildCyclicComplementary()); + this->tables.push_back(buildCyclicContrasting()); + this->tables.push_back(buildCyclicMonochromatic()); + this->tables.push_back(buildCyclicDesaturated()); + + this->activeColourMode = COMPLEMENTARY; + this->activeSaturationMode = SATURATED; } -// builds a 4-way lookuptable, used to encode the directional component -vtkSmartPointer buildLutDirs() { - vtkNew lut; - lut->SetNumberOfColors(360); - lut->SetTableRange(0, 359); - lut->SetScaleToLinear(); - lut->Build(); - //currently builds a corkO cyclic colour map, divided into 8 colours (see https://www.fabiocrameri.ch/cycliccolourmaps/) - setLutRange(lut, 000, 020, 0.247, 0.243, 0.227, 1); - setLutRange(lut, 021, 060, 0.243, 0.267, 0.365, 1); - setLutRange(lut, 061, 100, 0.318, 0.416, 0.557, 1); - setLutRange(lut, 101, 140, 0.518, 0.620, 0.729, 1); - setLutRange(lut, 141, 180, 0.667, 0.757, 0.773, 1); - setLutRange(lut, 181, 220, 0.631, 0.769, 0.651, 1); - setLutRange(lut, 221, 260, 0.451, 0.639, 0.435, 1); - setLutRange(lut, 261, 300, 0.298, 0.431, 0.224, 1); - setLutRange(lut, 301, 340, 0.263, 0.310, 0.173, 1); - setLutRange(lut, 341, 359, 0.247, 0.243, 0.227, 1); +void EColLayer::calcMaxStrength() { + double u1 = uvGrid->uMin, + u2 = uvGrid->uMax, + v1 = uvGrid->vMin, + v2 = uvGrid->vMax; - lut->SetNanColor(0,0,0,0); + double a = sqrt(u1*u1 + v1*v1); + double b = sqrt(u1*u1 + v2*v2); + double c = sqrt(u2*u2 + v1*v1); + double d = sqrt(u2*u2 + v2*v2); - return lut; + this->maxStrength = max(a, max(b, max(c, d))); } + // TODO: Bit of a superfunction here; can do with some refactoring. void EColLayer::readCoordinates() { vtkNew points; @@ -134,35 +117,27 @@ void EColLayer::readCoordinates() { } u /= 4; v /= 4; - this->strength->SetTuple1(cellId, std::sqrt(u*u + v*v)); - this->direction->SetTuple1(cellId++, atan(u/v)*180/pi); + this->lutIdx->SetTuple1(cellId++, calcIndex(u,v, this->maxStrength)); } latIndex++; } lonIndex++; } - data->GetCellData()->AddArray(this->strength); - data->GetCellData()->AddArray(this->direction); - // data->GetCellData()->SetActiveScalars("strength"); + data->GetCellData()->AddArray(this->lutIdx); + data->GetCellData()->SetActiveScalars("lutIdx"); - vtkNew(mapper); - mapper->SetInputData(data); - mapper->SetLookupTable(buildLutDirs()); - mapper->UseLookupTableScalarRangeOn(); - mapper->Update(); - data->GetCellData()->SetActiveScalars("direction"); + this->mapper = vtkSmartPointer::New(); + this->mapper->SetInputData(data); + setColourMode(COMPLEMENTARY); + this->mapper->UseLookupTableScalarRangeOn(); + this->mapper->Update(); vtkNew actor; - actor->SetMapper(mapper); + actor->SetMapper(this->mapper); actor->GetProperty()->SetColor(0, 1, 0); actor->GetProperty()->SetOpacity(0.5); - // vtkNew act2; - // act2->SetMapper(mapper); - // act2->GetProperty()->SetRepresentationToWireframe(); - // ren->AddActor(act2); - this->ren->AddActor(actor); } @@ -181,9 +156,50 @@ void EColLayer::updateData(int t) { } u /= 4; v /= 4; - this->strength->SetTuple1(i, std::sqrt(u*u + v*v)); - this->direction->SetTuple1(i++, atan(u/v)*180/pi); + this->lutIdx->SetTuple1(i++, calcIndex(u,v, this->maxStrength)); } } - this->strength->Modified(); + this->lutIdx->Modified(); +} + + +void EColLayer::setColourMode(ColourMode mode) { + this->activeColourMode = mode; + if (this->activeSaturationMode == DESATURATED) return; + + this->mapper->SetLookupTable(this->tables[mode]); +} + +void EColLayer::setSaturationMode(SaturationMode mode) { + this->activeSaturationMode = mode; + + if (mode == DESATURATED) { + this->mapper->SetLookupTable(this->tables[mode]); + } else { + this->mapper->SetLookupTable(this->tables[this->activeColourMode]); + } +} + + +int calcIndex(double u, double v, double maxStren) { + int angleIdx = calcAngleIndex(u,v); + int strIdx = calcStrengthIndex(u,v, maxStren); + + return angleIdx+strIdx*10; +} + +int calcAngleIndex(double u, double v) { + double angle = (atan2(v,u)+pi) * 180 / pi; + return (int)std::floor(angle/360*10) % 10; +} + +// TODO: there's room for improvement in this function. +// Currently maps all strengths to [0,10) where 10 is assigned when strength >= maxStrength/2 +// But this is completely heuristic - a more accurate calculation could take into account mean/std of velocity strengths, or do something more fancy with the maxStrength value. +int calcStrengthIndex(double u, double v, double maxStren) { + double strength = sqrt(u*u + v*v); + int idx = strength/(maxStren/2)*10; + + if (idx > 9) idx = 9; + return idx; } diff --git a/particle-track-and-trace/src/layers/EColLayer.h b/particle-track-and-trace/src/layers/EColLayer.h index 1097d21..ded1b48 100644 --- a/particle-track-and-trace/src/layers/EColLayer.h +++ b/particle-track-and-trace/src/layers/EColLayer.h @@ -14,9 +14,13 @@ */ class EColLayer : public Layer { private: - vtkSmartPointer strength; - vtkSmartPointer direction; + vtkSmartPointer lutIdx; + vtkSmartPointer mapper; + std::vector> tables; + ColourMode activeColourMode; + SaturationMode activeSaturationMode; std::shared_ptr uvGrid; + double maxStrength; int numLats; int numLons; @@ -25,11 +29,13 @@ private: */ void readCoordinates(); + /** This function calculates the maximum strength values for the associated uvGrid + */ + void calcMaxStrength(); - /** This private function builds up the lookup table VTK uses when determning what colour each cell ought to be. + /** This function builds the used lookuptables and adds them to the tables attribute. */ - // TODO: implement this function. - void buildLut(); + void buildLuts(); public: /** Constructor. @@ -39,9 +45,37 @@ public: /** updates the map to reflect the given timestamp in the dataset. * @param t : the time at which to fetch the data. */ - void updateData(int t); + void updateData(int t) override; + + + void setColourMode(ColourMode mode) override; + void setSaturationMode(SaturationMode mode) override; }; +/** Calculates the discrete index of a given velocity strength, taking into account both strength and direction. + * Maps to a 10*10 lookuptable, in which the columns are discrete colours (mapped to direction), and rows are discrete opacities (mapped to strength). + * @param u : longitudinal velocity + * @param v : latitudinal velocity + * @param maxStren : maximum strength value + * @return index in a 10*10 lookuptable. + */ +int calcIndex(double u, double v, double maxStren); + +/** Calculates the discrete index of the angle of a velocity, mapping it to the range [0,10). + * @param u : longitudinal velocity + * @param v : latitudinal velocity + * @return index in the range [0,10) + */ +int calcAngleIndex(double u, double v); + +/** Calculates the discrete index of the strength of a velocity, mapping it to the range [0,10). + * @param u : longitudinal velocity + * @param v : latitudinal velocity + * @param maxStren : maximum strength value + * @return index in the range [0,10) + */ +int calcStrengthIndex(double u, double v, double maxStren); + #endif diff --git a/particle-track-and-trace/src/layers/LColLayer.cpp b/particle-track-and-trace/src/layers/LColLayer.cpp index 7aa7f3f..486a590 100644 --- a/particle-track-and-trace/src/layers/LColLayer.cpp +++ b/particle-track-and-trace/src/layers/LColLayer.cpp @@ -21,6 +21,9 @@ #include "../CartographicTransformation.h" +// 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. +// Would involve a custom callback function probably. vtkSmartPointer LColLayer::createSpawnPointCallback() { vtkNew newPointCallBack; newPointCallBack->setPoints(this->points); @@ -28,6 +31,7 @@ vtkSmartPointer LColLayer::createSpawnPointCallback() { newPointCallBack->setUVGrid(this->uvGrid); newPointCallBack->setBeached(this->particlesBeached); newPointCallBack->setAge(this->particlesAge); + newPointCallBack->setIdx(this->lutIdx); return newPointCallBack; } @@ -80,6 +84,10 @@ LColLayer::LColLayer(std::shared_ptr uvGrid, std::unique_ptrparticlesAge->SetName("particlesAge"); this->particlesAge->SetNumberOfComponents(1); + this->lutIdx = vtkSmartPointer::New(); + this->lutIdx->SetName("lutIdx"); + this->lutIdx->SetNumberOfComponents(1); + // pipeline 2 this->data = vtkSmartPointer::New(); diff --git a/particle-track-and-trace/src/layers/LColLayer.h b/particle-track-and-trace/src/layers/LColLayer.h index 408d91d..8c45113 100644 --- a/particle-track-and-trace/src/layers/LColLayer.h +++ b/particle-track-and-trace/src/layers/LColLayer.h @@ -16,6 +16,7 @@ private: vtkSmartPointer data; vtkSmartPointer particlesBeached; vtkSmartPointer particlesAge; + vtkSmartPointer lutIdx; vtkSmartPointer cellParticleDensity; vtkSmartPointer callback; std::unique_ptr advector; diff --git a/particle-track-and-trace/src/layers/LGlyphLayer.cpp b/particle-track-and-trace/src/layers/LGlyphLayer.cpp index bcc8e89..13a2169 100644 --- a/particle-track-and-trace/src/layers/LGlyphLayer.cpp +++ b/particle-track-and-trace/src/layers/LGlyphLayer.cpp @@ -19,6 +19,7 @@ #include #include "../CartographicTransformation.h" +#include "luts.h" vtkSmartPointer LGlyphLayer::createSpawnPointCallback() { vtkNew newPointCallBack; @@ -27,62 +28,13 @@ vtkSmartPointer LGlyphLayer::createSpawnPointCallback() { newPointCallBack->setUVGrid(this->uvGrid); newPointCallBack->setBeached(this->particlesBeached); newPointCallBack->setAge(this->particlesAge); + newPointCallBack->setIdx(this->lutIdx); 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 [0.25, 0.25, 0.25, 1] in n steps. - */ -vtkSmartPointer buildLutBrightness(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-(0.75*i/(n-1)), 1-(0.75*i/(n-1)), 1-(0.75*i/(n-1)), 1); - } - lut->UseAboveRangeColorOn(); - lut->SetAboveRangeColor(0.2, 0.2, 0.2, 1); - - // 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; -} - -/** - * 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 buildLutOpacity(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, 0, 1, 1-(0.75*i/(n-1))); - } - lut->UseAboveRangeColorOn(); - lut->SetAboveRangeColor(1,0,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->luts.push(buildLutOpacity(512)); - this->luts.push(buildLutBrightness(512)); + buildLuts(); this->ren = vtkSmartPointer::New(); this->ren->SetLayer(2); @@ -92,16 +44,14 @@ LGlyphLayer::LGlyphLayer(std::shared_ptr uvGrid, std::unique_ptrSetPoints(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"); + this->lutIdx = vtkSmartPointer::New(); + this->lutIdx->SetName("lutIdx"); + this->lutIdx->SetNumberOfComponents(0); + + data->GetPointData()->AddArray(this->lutIdx); + data->GetPointData()->SetActiveScalars("lutIdx"); advector = std::move(advectionKernel); this->uvGrid = uvGrid; @@ -124,11 +74,7 @@ LGlyphLayer::LGlyphLayer(std::shared_ptr uvGrid, std::unique_ptrmapper->SetInputConnection(glyph2D->GetOutputPort()); this->mapper->SetColorModeToMapScalars(); - auto lut = this->luts.front(); - mapper->SetLookupTable(lut); - this->luts.pop(); - this->luts.push(lut); - + setColourMode(COMPLEMENTARY); this->mapper->UseLookupTableScalarRangeOn(); this->mapper->Update(); @@ -140,6 +86,17 @@ LGlyphLayer::LGlyphLayer(std::shared_ptr uvGrid, std::unique_ptrcallback = createSpawnPointCallback(); } + +void LGlyphLayer::buildLuts() { + this->tables.push_back(buildComplementary()); + this->tables.push_back(buildComplementary()); + this->tables.push_back(buildComplementary()); + this->tables.push_back(buildDesaturated()); + + this->activeColourMode = COMPLEMENTARY; + this->activeSaturationMode = SATURATED; +} + void LGlyphLayer::spoofPoints() { for (int i=0; i < 330; i+=5) { for (int j=0; j < 330; j+=5) { @@ -162,8 +119,8 @@ void LGlyphLayer::updateData(int t) { 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); + this->lutIdx->SetTuple1(n, -1); continue; } @@ -186,11 +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)); } else { this->particlesBeached->SetValue(n, std::max(beachedFor-1, 0)); this->points->SetPoint(n, point); + this->lutIdx->SetTuple1(n, calcIndex(age+1, false)); modifiedData = true; } + } else { + this->lutIdx->SetTuple1(n, calcIndex(age+1, true)); } } if (modifiedData) { @@ -199,6 +160,17 @@ void LGlyphLayer::updateData(int t) { } } +int LGlyphLayer::calcIndex(int age, bool beached) { + // particle out of bounds. + if (age == -1) return -1; + + int mod = beached ? 50 : 0; + int ageCalc = age/60; + if (ageCalc >= 50) ageCalc = 49; + + return ageCalc + mod; +} + void LGlyphLayer::addObservers(vtkSmartPointer interactor) { interactor->AddObserver(vtkCommand::LeftButtonPressEvent, this->callback); interactor->AddObserver(vtkCommand::LeftButtonReleaseEvent, this->callback); @@ -212,13 +184,25 @@ void LGlyphLayer::removeObservers(vtkSmartPointer int } -void LGlyphLayer::cycleGlyphStyle() { - auto lut = this->luts.front(); - this->mapper->SetLookupTable(lut); - this->luts.pop(); - this->luts.push(lut); +void LGlyphLayer::setColourMode(ColourMode mode) { + + this->activeColourMode = mode; + if (this->activeSaturationMode == DESATURATED) return; + + this->mapper->SetLookupTable(this->tables[mode]); } +void LGlyphLayer::setSaturationMode(SaturationMode mode) { + this->activeSaturationMode = mode; + + if (mode == DESATURATED) { + this->mapper->SetLookupTable(this->tables[mode]); + } else { + this->mapper->SetLookupTable(this->tables[this->activeColourMode]); + } +} + + void LGlyphLayer::setDt(int dt) { this->dt = dt; } diff --git a/particle-track-and-trace/src/layers/LGlyphLayer.h b/particle-track-and-trace/src/layers/LGlyphLayer.h index 2c1dc4d..0a1411c 100644 --- a/particle-track-and-trace/src/layers/LGlyphLayer.h +++ b/particle-track-and-trace/src/layers/LGlyphLayer.h @@ -4,7 +4,6 @@ #include "Layer.h" #include "../advection/kernel/AdvectionKernel.h" #include "../commands/SpawnPointCallback.h" -#include #include #include @@ -17,14 +16,21 @@ private: vtkSmartPointer data; vtkSmartPointer particlesBeached; vtkSmartPointer particlesAge; + vtkSmartPointer lutIdx; vtkSmartPointer mapper; std::unique_ptr advector; std::shared_ptr uvGrid; int dt = 3600; int beachedAtNumberOfTimes = 20; - std::queue> luts; + std::vector> tables; + ColourMode activeColourMode; + SaturationMode activeSaturationMode; vtkSmartPointer callback; + + void buildLuts(); + int calcIndex(int age, bool beached); + public: /** Constructor. */ @@ -46,9 +52,8 @@ public: void addObservers(vtkSmartPointer interactor) override; void removeObservers(vtkSmartPointer interactor) override; - /** This function cycles which lut is used for the layer, according to the lookuptables in the luts attribute. - */ - void cycleGlyphStyle(); + void setColourMode(ColourMode mode) override; + void setSaturationMode(SaturationMode mode) override; /** * Sets a custom DT value, needed for advect calls to the simulation logic. diff --git a/particle-track-and-trace/src/layers/Layer.cpp b/particle-track-and-trace/src/layers/Layer.cpp index 500a5a2..d9ef36c 100644 --- a/particle-track-and-trace/src/layers/Layer.cpp +++ b/particle-track-and-trace/src/layers/Layer.cpp @@ -14,10 +14,11 @@ void Layer::setCamera(vtkSmartPointer cam) { } +// do nothing by default for these functions. void Layer::updateData(int t) {} void Layer::addObservers(vtkSmartPointer interactor) {} void Layer::removeObservers(vtkSmartPointer interactor) {} -void Layer::setColorMode(ColourMode mode) {} +void Layer::setColourMode(ColourMode mode) {} void Layer::setSaturationMode(SaturationMode mode) {} void Layer::setGlyphStyle(GlyphStyle style) {} void Layer::setSamplingMode(SamplingMode mode) {} diff --git a/particle-track-and-trace/src/layers/Layer.h b/particle-track-and-trace/src/layers/Layer.h index 3eaf1b0..2b9f96d 100644 --- a/particle-track-and-trace/src/layers/Layer.h +++ b/particle-track-and-trace/src/layers/Layer.h @@ -41,7 +41,8 @@ public: virtual void setCamera(vtkSmartPointer cam); - virtual void setColorMode(ColourMode mode); + // TODO: Comments + virtual void setColourMode(ColourMode mode); virtual void setSaturationMode(SaturationMode mode); virtual void setGlyphStyle(GlyphStyle style); virtual void setSamplingMode(SamplingMode mode); diff --git a/particle-track-and-trace/src/layers/Technique.cpp b/particle-track-and-trace/src/layers/Technique.cpp index c4db355..a877ae9 100644 --- a/particle-track-and-trace/src/layers/Technique.cpp +++ b/particle-track-and-trace/src/layers/Technique.cpp @@ -50,9 +50,9 @@ void Technique::unbind(vtkSmartPointer win, vtkSmartPointerlayers) { - l->setColorMode(mode); + l->setColourMode(mode); } } diff --git a/particle-track-and-trace/src/layers/Technique.h b/particle-track-and-trace/src/layers/Technique.h index e594221..af8a183 100644 --- a/particle-track-and-trace/src/layers/Technique.h +++ b/particle-track-and-trace/src/layers/Technique.h @@ -7,6 +7,7 @@ #include "enums.h" class Technique { + //TODO: comments private: std::vector layers; vtkSmartPointer cam; @@ -20,7 +21,7 @@ public: void bind(vtkSmartPointer win, vtkSmartPointer intr); void unbind(vtkSmartPointer win, vtkSmartPointer intr); - void setColorMode(ColourMode mode); + void setColourMode(ColourMode mode); void setSaturationMode(SaturationMode mode); void setGlyphStyle(GlyphStyle style); void setSamplingMode(SamplingMode mode); diff --git a/particle-track-and-trace/src/layers/enums.h b/particle-track-and-trace/src/layers/enums.h index 537dc58..2639e07 100644 --- a/particle-track-and-trace/src/layers/enums.h +++ b/particle-track-and-trace/src/layers/enums.h @@ -2,6 +2,7 @@ #define ENUMS_H enum ActiveTechnique { + NOTECH = -1, COLGLYPH = 0, GLYPHCOL = 1, }; @@ -16,7 +17,7 @@ enum ColourMode { enum SaturationMode { SATURATED = 0, - DESATURATED = 1, + DESATURATED = 3, }; diff --git a/particle-track-and-trace/src/layers/luts.cpp b/particle-track-and-trace/src/layers/luts.cpp new file mode 100644 index 0000000..d7e0e8b --- /dev/null +++ b/particle-track-and-trace/src/layers/luts.cpp @@ -0,0 +1,205 @@ +#include +#include +#include "luts.h" + +// LGlyph tables +vtkSmartPointer buildComplementary() { + // uses a modified navia colour map as base. + // https://www.fabiocrameri.ch/colourmaps/ + vtkNew lut; + lut->SetNumberOfColors(100); + lut->SetTableRange(0, 99); + lut->SetScaleToLinear(); + lut->Build(); + + vtkNew colorTransferFunction; + colorTransferFunction->AddRGBPoint(0, 0.317647, 0.52549, 0.329412); + colorTransferFunction->AddRGBPoint(49, 0.584, 0.584, 0.584); + + double c[3]; + int idx=0; + for (double notBeached=1; notBeached >= 0.5; notBeached-=0.5) { + for (int i=0; i < 50; i++) { + colorTransferFunction->GetColor(i, c); + lut->SetTableValue(idx++, c[0], c[1], c[2], notBeached); + } + } + + lut->UseBelowRangeColorOn(); + lut->SetBelowRangeColor(1,1,1,0); + + lut->SetNanColor(0.0,0,0,0); + return lut; +} + + +vtkSmartPointer buildDesaturated() { + // uses the grayC colour map. + // https://www.fabiocrameri.ch/colourmaps/ + vtkNew lut; + lut->SetNumberOfColors(100); + lut->SetTableRange(0, 99); + lut->SetScaleToLinear(); + lut->Build(); + + vtkNew colorTransferFunction; + colorTransferFunction->AddRGBPoint(0, 0.313725, 0.313725, 0.313725); + colorTransferFunction->AddRGBPoint(49, 0.909804, 0.909804, 0.909804); + + double c[3]; + int idx=0; + for (double notBeached=1; notBeached >= 0.5; notBeached-=0.5) { + for (int i=0; i < 50; i++) { + colorTransferFunction->GetColor(i, c); + lut->SetTableValue(idx++, c[0], c[1], c[2], notBeached); + } + } + + lut->UseBelowRangeColorOn(); + lut->SetBelowRangeColor(1,1,1,0); + + lut->SetNanColor(0.0,0,0,0); + return lut; +} + + +// ECol tables +vtkSmartPointer buildCyclicComplementary() { + // uses the corkO cyclic colour map + // https://www.fabiocrameri.ch/colourmaps/ + vtkNew lut; + lut->SetNumberOfColors(100); + lut->SetTableRange(0, 99); + lut->SetScaleToLinear(); + lut->Build(); + + int idx=0; + for (double opacity=0.1; opacity <= 1.0; opacity+=0.1) { + lut->SetTableValue(idx++, 0, 1, 1, opacity); + lut->SetTableValue(idx++, 0, 0.9, 0.9, opacity); + lut->SetTableValue(idx++, 0, 0.8, 0.8, opacity); + lut->SetTableValue(idx++, 0, 0.7, 0.7, opacity); + lut->SetTableValue(idx++, 0, 0.6, 0.6, opacity); + lut->SetTableValue(idx++, 0, 0.5, 0.5, opacity); + lut->SetTableValue(idx++, 0, 0.4, 0.4, opacity); + lut->SetTableValue(idx++, 0, 0.3, 0.3, opacity); + lut->SetTableValue(idx++, 0, 0.2, 0.2, opacity); + lut->SetTableValue(idx++, 0, 0.1, 0.1, opacity); + } + + lut->SetNanColor(0.0,0,0,0); + return lut; +} + + +vtkSmartPointer buildCyclicContrasting() { + // uses the romaO cyclic colour map. + // https://www.fabiocrameri.ch/colourmaps/ + vtkNew lut; + lut->SetNumberOfColors(100.0); + lut->SetTableRange(0.0, 99); + lut->SetScaleToLinear(); + lut->Build(); + + int idx=0.0; + for (double opacity=0.1; opacity <= 1.0; opacity+=0.1) { + lut->SetTableValue(idx++, 0.45098, 0.223529, 0.341176, opacity); + lut->SetTableValue(idx++, 0.529412, 0.25098, 0.215686, opacity); + lut->SetTableValue(idx++, 0.639216, 0.403922, 0.172549, opacity); + lut->SetTableValue(idx++, 0.764706, 0.639216, 0.294118, opacity); + lut->SetTableValue(idx++, 0.839216, 0.847059, 0.576471, opacity); + lut->SetTableValue(idx++, 0.705882, 0.870588, 0.776471, opacity); + lut->SetTableValue(idx++, 0.454902, 0.733333, 0.803922, opacity); + lut->SetTableValue(idx++, 0.309804, 0.533333, 0.72549, opacity); + lut->SetTableValue(idx++, 0.360784, 0.32549, 0.545098, opacity); + lut->SetTableValue(idx++, 0.447059, 0.223529, 0.34902, opacity); + } + + lut->SetNanColor(0.0,0,0,0); + return lut; +} + + +vtkSmartPointer buildCyclicMonochromatic() { + // uses a slightly modified 9-class blues + // https://colorbrewer2.org/#type=sequential&scheme=Blues&n=9 + vtkNew lut; + lut->SetNumberOfColors(100.0); + lut->SetTableRange(0.0, 99); + lut->SetScaleToLinear(); + lut->Build(); + + int idx=0.0; + for (double opacity=0.1; opacity <= 1.0; opacity+=0.1) { + lut->SetTableValue(idx++, 0.258824, 0.572549, 0.776471, opacity); + lut->SetTableValue(idx++, 0.129412, 0.443137, 0.709804, opacity); + lut->SetTableValue(idx++, 0.031373, 0.317647, 0.611765, opacity); + lut->SetTableValue(idx++, 0.031373, 0.188235, 0.419608, opacity); + lut->SetTableValue(idx++, 0.968627, 0.984314, 1.0, opacity); + lut->SetTableValue(idx++, 0.870588, 0.921569, 0.968627, opacity); + lut->SetTableValue(idx++, 0.776471, 0.858824, 0.937255, opacity); + lut->SetTableValue(idx++, 0.619608, 0.792157, 0.882353, opacity); + lut->SetTableValue(idx++, 0.419608, 0.682353, 0.839216, opacity); + lut->SetTableValue(idx++, 0.180392, 0.494118, 0.698039, opacity); + } + + lut->SetNanColor(0.0,0,0,0); + return lut; +} + + +vtkSmartPointer buildCyclicDesaturated() { + // uses a modified brocO colour map + // https://www.fabiocrameri.ch/colourmaps/ + vtkNew lut; + lut->SetNumberOfColors(100.0); + lut->SetTableRange(0.0, 99); + lut->SetScaleToLinear(); + lut->Build(); + + int idx=0.0; + for (double opacity=0.1; opacity <= 1.0; opacity+=0.1) { + lut->SetTableValue(idx++, 44, 44, 44, opacity); + lut->SetTableValue(idx++, 56, 56, 56, opacity); + lut->SetTableValue(idx++, 85, 85, 85, opacity); + lut->SetTableValue(idx++, 114, 114, 114, opacity); + lut->SetTableValue(idx++, 149, 149, 149, opacity); + lut->SetTableValue(idx++, 146, 146, 146, opacity); + lut->SetTableValue(idx++, 110, 110, 110, opacity); + lut->SetTableValue(idx++, 78, 78, 78, opacity); + lut->SetTableValue(idx++, 52, 52, 52, opacity); + lut->SetTableValue(idx++, 45, 45, 45, opacity); + } + + lut->SetNanColor(0.0,0,0,0); + return lut; +} + + +// LCol tables +vtkSmartPointer buildDensityComplementary() { + vtkNew lut; + + return lut; +} + + +vtkSmartPointer buildDensityContrasting() { + vtkNew lut; + + return lut; +} + + +vtkSmartPointer buildDensityMonochromatic() { + vtkNew lut; + + return lut; +} + + +vtkSmartPointer buildDensityDesaturated() { + vtkNew lut; + + return lut; +} diff --git a/particle-track-and-trace/src/layers/luts.h b/particle-track-and-trace/src/layers/luts.h new file mode 100644 index 0000000..c256737 --- /dev/null +++ b/particle-track-and-trace/src/layers/luts.h @@ -0,0 +1,20 @@ +#ifndef LUTS_H +#define LUTS_H +#include + +// LGlyph tables +vtkSmartPointer buildComplementary(); +vtkSmartPointer buildDesaturated(); + +// ECol tables +vtkSmartPointer buildCyclicComplementary(); +vtkSmartPointer buildCyclicContrasting(); +vtkSmartPointer buildCyclicMonochromatic(); +vtkSmartPointer buildCyclicDesaturated(); + +// LCol tables +vtkSmartPointer buildDensityComplementary(); +vtkSmartPointer buildDensityContrasting(); +vtkSmartPointer buildDensityMonochromatic(); +vtkSmartPointer buildDensityDesaturated(); +#endif