visualizing isolines in a matrix

I have a n x n matrix with float values and I would like to visualize the isolines and shaded contours (like this) using jogl.

I’m unable to find an external library that can calculate the path(s) of an isolines for value x in a matrix, so I was wondering if there might be any functions in jogl/opengl which I can utilize. After being able to draw isolines, the next step would be to draw shaded contours. Any pointers/ideas or references to libraries I can use in my program would be great :slight_smile:

Do you need the paths or do you only want to shade a scene with isolines and colors? If you want the latter, it would possibly easiest to use a glsl shader and lookup a color from an 1dim-texture with “nearest” interpolation based on the gl_FragCoord.z value.

Thanks for the quick response, I think I want the latter.
I’ve never worked with glsl shaders, but from what I’ve just read I can create a small filter program that is iterated over every pixel of an opengl texture.

  1. I will first need to translate my float[][] array (does not contain pixel precise data but is about 15 times less detailed) to something in jogl which I can use a glsl shader on.
  2. Ideally, the jogl representation of my array scales while interpolating the data. Set it to the desired display size.
  3. Now I need to run the glsl shader filter and perform a filter on the data (eg. if (0.1>value<0.15) pixel=black).

Did I understand correctly so far? And what should I map my float[][] to in jogl?

OK, so your n x n matrix represents a heightmap of terrain data? You would need to create an array of vertices (x,y,z values) where every group of three represents a triangle. You need to duplicate vertices used for adjacent triangles. Look up some basic tutorials for opengl (nehe.gamedev.net for example) and then make yourself familar with “opengl vertex arrays”.

That would give you a stair effect. This won’t give you (1px wide) lines.

Without the paths, your map wouldn’t look much like the provided example.

Ofcourse, paths are a bit harder to calculate, but as your terrain is composed of triangles, you can slice through them with a plane. All resulting edges are your path segements.

Maybe you can fake it with cylab’s suggestion (stair effect), and then
applying a shader over the resulting image, finding the contrast, and
making those pixels a tad darker, this would yield a thin line.

I can’t actually post code for this, but I’ve worked on something similar and found an edge detection filter used to provide an alpha value which is then used to draw black over the top produces a nice effect.

what should be the “height difference” between isoline (how many isoline) ?

If you go the plane slicing route, you might find this helpful. It wouldn’t work out-of-the-box as your heightmap is not a volume, but the basic idea of traversing the surface graph and lerping edge values is the same. It probably wouldn’t take much alteration to get that code to handle the cases where isolines disappear off the side of the map.

edit: Actually, you could use that code as-is if you make the terrain surface two-sided. You’d end up with twice as many line segments in the isolines as is necessary, but it may not be a problem. Hmmm, I smell a heightmap IntersectionVolume coming on…

Alternatively, the accepted method of calculating isolines is the marching squares algorithm.

Should be variable, it depends on user settings.

I’ve quickly implemented a MS algorithm last week but a downside is the lack of smooth lines and no way to implement a shaded contour since you only get the lines but not the shapes. It’s a pain to construct closed shapes afterwards and accurately determine the value of it’s area when most lines are not closed.

Tomorrow I’ll try to find out how to correctly convert my float[][] into a terrain composed of triangles and let you guys know how things work out.

[quote]Should be variable, it depends on user settings.
[/quote]
ok, I was just thinking that converting your “height value in float” to “color” you can play with contrast to directly render isoline.

like rendering the final picture with 4 colors will give you 4 isoline area and change from one to another its edge that you draw bolder.

like
float[][] => int[][]

for each int[][]
(int[][]/n)*n
//other computation (bilinear filtering, make edge bold, etc…)
draw int[][]

Wouldn’t this only work, if the height values represents pixels, but not if representing supporting points in a heightfield?

So I just figured I could perhaps do the following:

  1. draw the matrix as a series of quads with each vertex’s color value set to it’s normalized data value
  2. use a GLSL shader to iterate of the pixels and perform some filter

Being very unpro at opengl, my approach was as follows:
-take a standard demo which uses shaders, found here
-simply draw my matrix over the demo’s stuff
-change the shader code to filter some random data as a test

The entire class exceeds the 10.000 char limit, so here’s the relevant stuff


    static double[][] data = {{0.5, 1.1, 1.5, 1, 2.0, 3, 3, 2, 1, 0.1},
        {1.0, 1.5, 3.0, 5, 6.0, 2, 1, 1.2, 1, 4},
        {0.9, 2.0, 2.1, 3, 6.0, 7, 3, 2, 1, 1.4},
        {1.0, 1.5, 3.0, 4, 6.0, 5, 2, 1.5, 1, 2},
        {0.8, 2.0, 3.0, 3, 4.0, 4, 3, 2.4, 2, 3},
        {0.6, 1.1, 1.5, 1, 4.0, 3.5, 3, 2, 3, 4},
        {1.0, 1.5, 3.0, 5, 6.0, 2, 1, 1.2, 2.7, 4},
        {0.8, 2.0, 3.0, 3, 5.5, 6, 3, 2, 1, 1.4},
        {1.0, 1.5, 3.0, 4, 6.0, 5, 2, 1, 0.5, 0.2}
    };
    int arrayHT = 0;
    int arrayWD = 0;
    float[] values = null;

    private static final String[] edgeFragSource = {
        "uniform sampler2D texUnit;",
        "void main(void)",
        "{",
        " vec2 texCoord = gl_TexCoord[0].xy; ",
        " vec4 c  = texture2D(texUnit, texCoord); ",
        " if(c.x>0.4 && c.x<0.5) ",
        " gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0); ",
        " else ",
        " gl_FragColor = vec4(0.0, 0.0, 1.0, 1.0); ",
        "}"
    };

//in the constructor
        //convert mock data array into a float[] of the kind we normally use
        arrayWD = data[0].length;
        arrayHT = data.length;
        values = new float[arrayHT * arrayWD];
        for (int i = 0; i < arrayHT; i++) {
            for (int j = 0; j < arrayWD; j++) {
                values[i * arrayWD + j] = (float) data[i][j];
            }
        }


    public void drawFill(GL gl, int current_spacing) {
        gl.glBegin(GL.GL_QUADS);

        float wd = Width / 2f;
        float ht = Height / 2f;

        for (int row = 0; row < arrayHT - 1; row++) {
            for (int col = 0; col < arrayWD - 1; col++) {
                int i1 = (row + 1) * arrayWD + col;
                int i2 = row * arrayWD + col;
                int i3 = row * arrayWD + col + 1;
                int i4 = (row + 1) * arrayWD + col + 1;
                //check for valid index and value on quad bounds
                if (i2 < values.length && i4 < values.length) {
                    gl.glColor3f(values[i1] / 8.0f, 0, 0);
                    gl.glVertex3f((col * current_spacing) / wd - 1, (row * current_spacing + current_spacing) / ht - 1, 0);
                    gl.glColor3f(values[i2] / 8.0f, 0, 0);
                    gl.glVertex3f((col * current_spacing) / wd - 1, (row * current_spacing) / ht - 1, 0);
                    gl.glColor3f(values[i3] / 8.0f, 0, 0);
                    gl.glVertex3f((col * current_spacing + current_spacing) / wd - 1, (row * current_spacing) / ht - 1, 0);
                    gl.glColor3f(values[i4] / 8.0f, 0, 0);
                    gl.glVertex3f((col * current_spacing + current_spacing) / wd - 1, (row * current_spacing + current_spacing) / ht - 1, 0);
                }
            }
        }

        gl.glEnd();
    }


//in the display function, after drawing the teapot
        drawFill(drawable.getGL(), 100);



However, the result is quite odd.
if I run the code and end the display function after drawing the teapot and my own drawFill(),
it will draw the teapot, a ring and my matrix. All good so far.
But if I run the entire display function with the drawing of my matrix inbetween the shader should get to work, but the result is a very, very faint red area that is being drawn and the rest is black. Wrong behaviour according to the filter.

If one would run the program without the painting of my matrix (comment out the drawFill call), the filter seems to work fine. What am I doing wrong?

I’ve added a GridSurface class to the volume intersection code I mentioned earlier. You can just fling your data at it, query on whatever z-value you’re after, and be given back something like this

http://homepages.inf.ed.ac.uk/rmcnally/intersect/terrain.png

Bear in mind what I’ve mentioned in the javadoc though - you’ll get back double the paths you require. You can either remove the extraneous segments manually or just live with the overdraw.

Have a play with with the webstart. Hit 5 to see the heightmap, q and w to alter the number of queries, a s and d to control the scan speed, left-button drag to rotate the view and right-button drag to rotate the query planes.