feat: swappable colour schemes

This commit is contained in:
Djairo Hougee 2024-05-28 17:59:54 +02:00
parent 305a812dfa
commit 23359f9f54
21 changed files with 491 additions and 170 deletions

View File

@ -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

View File

@ -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();
}

View File

@ -19,7 +19,7 @@ private:
/** This attribute models a variable number of vtkRenderers, managed through the abstract Technique class.
*/
std::vector<Technique *> 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<vtkCamera> getCamera();

View File

@ -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();
}
}

View File

@ -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 {

View File

@ -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

View File

@ -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<vtkIntArray> &ints) {
void SpawnPointCallback::setAge(const vtkSmartPointer<vtkIntArray> &ints) {
this->particlesAge = ints;
}
void SpawnPointCallback::setIdx(const vtkSmartPointer<vtkIntArray> &idx) {
this->lutIdx = idx;
}

View File

@ -24,6 +24,7 @@ public:
void setBeached(const vtkSmartPointer<vtkIntArray> &parts);
void setAge(const vtkSmartPointer<vtkIntArray> &parts);
void setIdx(const vtkSmartPointer<vtkIntArray> &idx);
void setUVGrid(const std::shared_ptr<UVGrid> &uvGrid);
@ -32,6 +33,7 @@ private:
vtkSmartPointer<vtkRenderer> ren;
vtkSmartPointer<vtkIntArray> particlesBeached;
vtkSmartPointer<vtkIntArray> particlesAge;
vtkSmartPointer<vtkIntArray> lutIdx;
std::shared_ptr<UVGrid> uvGrid;
vtkSmartPointer<vtkAbstractTransform> inverseCartographicProjection;

View File

@ -24,6 +24,7 @@
#include <vtkArrowSource.h>
#include "../CartographicTransformation.h"
#include "luts.h"
using std::numbers::pi;
@ -38,59 +39,41 @@ EColLayer::EColLayer(std::shared_ptr<UVGrid> uvGrid) {
this->numLats = uvGrid->latSize;
this->numLons = uvGrid->lonSize;
this->strength = vtkSmartPointer<vtkDoubleArray>::New();
this->strength->SetName("strength");
this->strength->SetNumberOfComponents(1);
this->strength->SetNumberOfTuples((numLats-1)*(numLons-1));
this->direction = vtkSmartPointer<vtkDoubleArray>::New();
this->direction->SetName("direction");
this->direction->SetNumberOfComponents(1);
this->direction->SetNumberOfTuples((numLats-1)*(numLons-1));
this->lutIdx = vtkSmartPointer<vtkIntArray>::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<vtkLookupTable> 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<vtkLookupTable> buildLutDirs() {
vtkNew<vtkLookupTable> 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<vtkPoints> 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<vtkPolyDataMapper>(mapper);
mapper->SetInputData(data);
mapper->SetLookupTable(buildLutDirs());
mapper->UseLookupTableScalarRangeOn();
mapper->Update();
data->GetCellData()->SetActiveScalars("direction");
this->mapper = vtkSmartPointer<vtkPolyDataMapper>::New();
this->mapper->SetInputData(data);
setColourMode(COMPLEMENTARY);
this->mapper->UseLookupTableScalarRangeOn();
this->mapper->Update();
vtkNew<vtkActor> actor;
actor->SetMapper(mapper);
actor->SetMapper(this->mapper);
actor->GetProperty()->SetColor(0, 1, 0);
actor->GetProperty()->SetOpacity(0.5);
// vtkNew<vtkActor> 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;
}

View File

@ -14,9 +14,13 @@
*/
class EColLayer : public Layer {
private:
vtkSmartPointer<vtkDoubleArray> strength;
vtkSmartPointer<vtkDoubleArray> direction;
vtkSmartPointer<vtkIntArray> lutIdx;
vtkSmartPointer<vtkPolyDataMapper> mapper;
std::vector<vtkSmartPointer<vtkLookupTable>> tables;
ColourMode activeColourMode;
SaturationMode activeSaturationMode;
std::shared_ptr<UVGrid> uvGrid;
double maxStrength;
int numLats;
int numLons;
@ -25,11 +29,13 @@ private:
*/
void readCoordinates();
/** This private function builds up the lookup table VTK uses when determning what colour each cell ought to be.
/** This function calculates the maximum strength values for the associated uvGrid
*/
// TODO: implement this function.
void buildLut();
void calcMaxStrength();
/** This function builds the used lookuptables and adds them to the tables attribute.
*/
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

View File

@ -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<SpawnPointCallback> LColLayer::createSpawnPointCallback() {
vtkNew<SpawnPointCallback> newPointCallBack;
newPointCallBack->setPoints(this->points);
@ -28,6 +31,7 @@ vtkSmartPointer<SpawnPointCallback> 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> uvGrid, std::unique_ptr<AdvectionKe
this->particlesAge->SetName("particlesAge");
this->particlesAge->SetNumberOfComponents(1);
this->lutIdx = vtkSmartPointer<vtkIntArray>::New();
this->lutIdx->SetName("lutIdx");
this->lutIdx->SetNumberOfComponents(1);
// pipeline 2
this->data = vtkSmartPointer<vtkPolyData>::New();

View File

@ -16,6 +16,7 @@ private:
vtkSmartPointer<vtkPolyData> data;
vtkSmartPointer<vtkIntArray> particlesBeached;
vtkSmartPointer<vtkIntArray> particlesAge;
vtkSmartPointer<vtkIntArray> lutIdx;
vtkSmartPointer<vtkIntArray> cellParticleDensity;
vtkSmartPointer<SpawnPointCallback> callback;
std::unique_ptr<AdvectionKernel> advector;

View File

@ -19,6 +19,7 @@
#include <vtkCamera.h>
#include "../CartographicTransformation.h"
#include "luts.h"
vtkSmartPointer<SpawnPointCallback> LGlyphLayer::createSpawnPointCallback() {
vtkNew<SpawnPointCallback> newPointCallBack;
@ -27,62 +28,13 @@ vtkSmartPointer<SpawnPointCallback> 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<vtkLookupTable> buildLutBrightness(int n) {
vtkNew<vtkLookupTable> 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<vtkLookupTable> buildLutOpacity(int n) {
vtkNew<vtkLookupTable> 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> uvGrid, std::unique_ptr<AdvectionKernel> advectionKernel) {
this->luts.push(buildLutOpacity(512));
this->luts.push(buildLutBrightness(512));
buildLuts();
this->ren = vtkSmartPointer<vtkRenderer>::New();
this->ren->SetLayer(2);
@ -92,16 +44,14 @@ LGlyphLayer::LGlyphLayer(std::shared_ptr<UVGrid> uvGrid, std::unique_ptr<Advecti
data->SetPoints(this->points);
this->particlesBeached = vtkSmartPointer<vtkIntArray>::New();
this->particlesBeached->SetName("particlesBeached");
this->particlesBeached->SetNumberOfComponents(0);
this->particlesAge = vtkSmartPointer<vtkIntArray>::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<vtkIntArray>::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> uvGrid, std::unique_ptr<Advecti
this->mapper->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> uvGrid, std::unique_ptr<Advecti
this->callback = 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<vtkRenderWindowInteractor> interactor) {
interactor->AddObserver(vtkCommand::LeftButtonPressEvent, this->callback);
interactor->AddObserver(vtkCommand::LeftButtonReleaseEvent, this->callback);
@ -212,13 +184,25 @@ void LGlyphLayer::removeObservers(vtkSmartPointer<vtkRenderWindowInteractor> 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;
}

View File

@ -4,7 +4,6 @@
#include "Layer.h"
#include "../advection/kernel/AdvectionKernel.h"
#include "../commands/SpawnPointCallback.h"
#include <queue>
#include <vtkPolyData.h>
#include <vtkInteractorStyle.h>
@ -17,14 +16,21 @@ private:
vtkSmartPointer<vtkPolyData> data;
vtkSmartPointer<vtkIntArray> particlesBeached;
vtkSmartPointer<vtkIntArray> particlesAge;
vtkSmartPointer<vtkIntArray> lutIdx;
vtkSmartPointer<vtkPolyDataMapper> mapper;
std::unique_ptr<AdvectionKernel> advector;
std::shared_ptr<UVGrid> uvGrid;
int dt = 3600;
int beachedAtNumberOfTimes = 20;
std::queue<vtkSmartPointer<vtkLookupTable>> luts;
std::vector<vtkSmartPointer<vtkLookupTable>> tables;
ColourMode activeColourMode;
SaturationMode activeSaturationMode;
vtkSmartPointer<SpawnPointCallback> callback;
void buildLuts();
int calcIndex(int age, bool beached);
public:
/** Constructor.
*/
@ -46,9 +52,8 @@ public:
void addObservers(vtkSmartPointer<vtkRenderWindowInteractor> interactor) override;
void removeObservers(vtkSmartPointer<vtkRenderWindowInteractor> 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.

View File

@ -14,10 +14,11 @@ void Layer::setCamera(vtkSmartPointer<vtkCamera> cam) {
}
// do nothing by default for these functions.
void Layer::updateData(int t) {}
void Layer::addObservers(vtkSmartPointer<vtkRenderWindowInteractor> interactor) {}
void Layer::removeObservers(vtkSmartPointer<vtkRenderWindowInteractor> 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) {}

View File

@ -41,7 +41,8 @@ public:
virtual void setCamera(vtkSmartPointer<vtkCamera> 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);

View File

@ -50,9 +50,9 @@ void Technique::unbind(vtkSmartPointer<vtkRenderWindow> win, vtkSmartPointer<vtk
}
void Technique::setColorMode(ColourMode mode) {
void Technique::setColourMode(ColourMode mode) {
for (Layer *l : this->layers) {
l->setColorMode(mode);
l->setColourMode(mode);
}
}

View File

@ -7,6 +7,7 @@
#include "enums.h"
class Technique {
//TODO: comments
private:
std::vector<Layer *> layers;
vtkSmartPointer<vtkCamera> cam;
@ -20,7 +21,7 @@ public:
void bind(vtkSmartPointer<vtkRenderWindow> win, vtkSmartPointer<vtkRenderWindowInteractor> intr);
void unbind(vtkSmartPointer<vtkRenderWindow> win, vtkSmartPointer<vtkRenderWindowInteractor> intr);
void setColorMode(ColourMode mode);
void setColourMode(ColourMode mode);
void setSaturationMode(SaturationMode mode);
void setGlyphStyle(GlyphStyle style);
void setSamplingMode(SamplingMode mode);

View File

@ -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,
};

View File

@ -0,0 +1,205 @@
#include <vtkLookupTable.h>
#include <vtkColorTransferFunction.h>
#include "luts.h"
// LGlyph tables
vtkSmartPointer<vtkLookupTable> buildComplementary() {
// uses a modified navia colour map as base.
// https://www.fabiocrameri.ch/colourmaps/
vtkNew<vtkLookupTable> lut;
lut->SetNumberOfColors(100);
lut->SetTableRange(0, 99);
lut->SetScaleToLinear();
lut->Build();
vtkNew<vtkColorTransferFunction> 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<vtkLookupTable> buildDesaturated() {
// uses the grayC colour map.
// https://www.fabiocrameri.ch/colourmaps/
vtkNew<vtkLookupTable> lut;
lut->SetNumberOfColors(100);
lut->SetTableRange(0, 99);
lut->SetScaleToLinear();
lut->Build();
vtkNew<vtkColorTransferFunction> 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<vtkLookupTable> buildCyclicComplementary() {
// uses the corkO cyclic colour map
// https://www.fabiocrameri.ch/colourmaps/
vtkNew<vtkLookupTable> 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<vtkLookupTable> buildCyclicContrasting() {
// uses the romaO cyclic colour map.
// https://www.fabiocrameri.ch/colourmaps/
vtkNew<vtkLookupTable> 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<vtkLookupTable> buildCyclicMonochromatic() {
// uses a slightly modified 9-class blues
// https://colorbrewer2.org/#type=sequential&scheme=Blues&n=9
vtkNew<vtkLookupTable> 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<vtkLookupTable> buildCyclicDesaturated() {
// uses a modified brocO colour map
// https://www.fabiocrameri.ch/colourmaps/
vtkNew<vtkLookupTable> 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<vtkLookupTable> buildDensityComplementary() {
vtkNew<vtkLookupTable> lut;
return lut;
}
vtkSmartPointer<vtkLookupTable> buildDensityContrasting() {
vtkNew<vtkLookupTable> lut;
return lut;
}
vtkSmartPointer<vtkLookupTable> buildDensityMonochromatic() {
vtkNew<vtkLookupTable> lut;
return lut;
}
vtkSmartPointer<vtkLookupTable> buildDensityDesaturated() {
vtkNew<vtkLookupTable> lut;
return lut;
}

View File

@ -0,0 +1,20 @@
#ifndef LUTS_H
#define LUTS_H
#include <vtkLookupTable.h>
// LGlyph tables
vtkSmartPointer<vtkLookupTable> buildComplementary();
vtkSmartPointer<vtkLookupTable> buildDesaturated();
// ECol tables
vtkSmartPointer<vtkLookupTable> buildCyclicComplementary();
vtkSmartPointer<vtkLookupTable> buildCyclicContrasting();
vtkSmartPointer<vtkLookupTable> buildCyclicMonochromatic();
vtkSmartPointer<vtkLookupTable> buildCyclicDesaturated();
// LCol tables
vtkSmartPointer<vtkLookupTable> buildDensityComplementary();
vtkSmartPointer<vtkLookupTable> buildDensityContrasting();
vtkSmartPointer<vtkLookupTable> buildDensityMonochromatic();
vtkSmartPointer<vtkLookupTable> buildDensityDesaturated();
#endif