0

So I'm working on a project that involves taking pre-existing skeleton code of an application that simulates "fluid flow and visualization" and applying different visualization techniques on it.

The first step of the project is to apply different color-mapping techniques on three different data-sets which are as follows: fluid density (rho), fluid velocity magnitude (||v||) and the force field magnitude ||f||.

The skeleton code provided already has an example that I can study to be able to determine how best to design and implement different color-maps such as red-to-white or blue-to-yellow or what have you.

The snippet of code I'm trying to understand is the following:

//rainbow: Implements a color palette, mapping the scalar 'value' to a rainbow color RGB
    void rainbow(float value,float* R,float* G,float* B)
    {                          
       const float dx=0.8; 
       if (value<0) value=0; if (value>1) value=1;
       value = (6-2*dx)*value+dx;
       *R = max(0.0,(3-fabs(value-4)-fabs(value-5))/2);
       *G = max(0.0,(4-fabs(value-2)-fabs(value-4))/2);
       *B = max(0.0,(3-fabs(value-1)-fabs(value-2))/2);
    }

The float valuebeing passed by the first parameter is, as far as I can tell, the fluid density. I've determined this by studying these two snippets.

//set_colormap: Sets three different types of colormaps
void set_colormap(float vy)
{
   float R,G,B; 

   if (scalar_col==COLOR_BLACKWHITE)
       R = G = B = vy;
   else if (scalar_col==COLOR_RAINBOW)
       rainbow(vy,&R,&G,&B); 
   else if (scalar_col==COLOR_BANDS)
       {  
          const int NLEVELS = 7;
          vy *= NLEVELS; vy = (int)(vy); vy/= NLEVELS; 
          rainbow(vy,&R,&G,&B);   
       }

   glColor3f(R,G,B);
}

and

set_colormap(rho[idx0]);
glVertex2f(px0, py0);
set_colormap(rho[idx1]);
glVertex2f(px1, py1);
set_colormap(rho[idx2]);
glVertex2f(px2, py2);


set_colormap(rho[idx0]);
glVertex2f(px0, py0);
set_colormap(rho[idx2]);
glVertex2f(px2, py2);
set_colormap(rho[idx3]);
glVertex2f(px3, py3);

With all of this said, could somebody please explain to me how the first method works?

Here's the output when the method is invoked by the user and matter is injected into the window by means of using the cursor:

-

Whereas otherwise it would look like this (gray-scale):

-

genpfault
  • 51,148
  • 11
  • 85
  • 139
K. Saudi
  • 47
  • 9

2 Answers2

1

I suspect that this is a variation of HSV to RGB.

The idea is that you can map your fluid density (on a linear scale) to the Hue parameter of a color in HSV format. Saturation and Value can just maintain constant value of 1. Normally Hue starts and ends at red, so you also want to shift your Hue values into the [red, blue] range. This will give you a "heatmap" of colors in HSV format depending on the fluid density, which you then have to map to RGB in the shader.

Because some of your values can be kept constant and because you don't care about any of the intermediate results, the algorithm that transforms fluid density to RGB can be simplified to the snippet above.

0x5453
  • 12,753
  • 1
  • 32
  • 61
1

I'm not sure which part of the function you don't understand, so let me explain this line by line:

void rainbow(float value,float* R,float* G,float* B){

}

This part is probably clear to you - the function takes in a single density/color value and outputs a rainbow color in rgb space.

const float dx=0.8;

Next, the constant dx is initialised. I'm not sure what the name "dx" stands for, but looks like it's later used to determine which part of the color spectrum is used.

if (value<0) value=0; if (value>1) value=1;

This clamps the input to a value between 0 and 1.

   value = (6-2*dx)*value+dx;

This maps the input to a value between dx and 6-dx.

   *R = max(0.0,(3-fabs(value-4)-fabs(value-5))/2);

This is probably the most complicated part. If value is smaller than 4, this simplifies to max(0.0,(2*value-6)/2) or max(0.0,value-3). This means that if value is less than 3, the red output will be 0, and if it is between 3 and 4, it will be value-3.

If value is between 4 and 5, this line instead simplifies to max(0.0,(3-(value-4)-(5-value))/2) which is equal to 1. So if value is between 4 and 5, the red output will be 1.

Lastly, if value is greater than 5, this line simplifies to max(0.0,(12-2*value)/2) or just 6-value.

So the output R is 1 when value is between 4 and 5, 0 when value is smaller than 3, and something in between otherwise. The calculations for the green and blue output or pretty much the same, just with tweaked value; green is brightest for values between 2 and 4, and blue is brightest for values between 1 and 2. This way the output forms a smooth rainbow color spectrum.

peabrainiac
  • 998
  • 9
  • 16
  • I'm not sure if I completely follow your explanation of this line in particular: `*R = max(0.0,(3-fabs(value-4)-fabs(value-5))/2);` If you could break it down a tad more that'd be awesome because as of right now I'm not sure why the output is what it is. Sorry for the trouble! – K. Saudi Feb 27 '19 at 19:10
  • `fabs(value-4)` equals `value-4` if `value` is bigger than 4, and `4-value` otherwise. Applying this logic to `fabs(value-5)` too, you get a total of 3 different outcomes; one for `value<4`, one for `4 – peabrainiac Feb 27 '19 at 19:26
  • Just a second, in your example if you're taking `value<4` then wouldn't it be `(3-(value-4)-(value-5))/2)` ? Also how'd you go (mathematically) from `(3-(4-value)-(5-value))/2` to `(3-9+2*value)/2 or just value-3` – K. Saudi Feb 27 '19 at 19:45
  • When `value` is smaller than four, `value-4` would be negative - so `fabs(value-4)` actually results in `4-value` in this case. And regarding your second question, `3-(4-value)-(5-value)` can be written as `3-((4-value)+(5-value) )` and that is equal to ``3-(9-2*value)` or `3-9+2*value`. – peabrainiac Feb 27 '19 at 19:53