Animated surface ripple with OpenGL

In the previous article I wrote an example program that drew a 3D surface. Starting with a wireframe, and then progressing to a solid surface. This was a demonstration of my simple graphics library in C. I implemented a very basic drawing library using GDI for Windows and OpenGL for OSX and Linux. I then built the 3D surface using those primitives. In the course of writing the library I got to learn a little about OpenGL. My reason for choosing it in the first place was that it was available on OSX and has a C interface. Having seen some of the OpenGL interface I was keen to do a little bit more with it. In particular I wanted to animate the surface wave I had made, and wanted to render it with lighting effects. OpenGL is perfect for this task.

Windows also has OpenGL, it does not have GLUT though which comes with OSX. So I wrote a small wrapper library that provides a very basic windows creating API that works cross platform. On Windows it creates the Window and then attaches OpenGL to it and provides a message loop. For OSX and Linux it uses GLUT.

The library has one function called OpenGLRun

//////////////////////////////////////////////////////////////////////////////////
//  OpenGLRun
//
//  Starts the OpenGL module. This will create a window of the specified size 
//  which will be used for OpenGL operations. The DrawFunction will be called when//  it is time for the window to be drawn as well as each tick of the timer
//////////////////////////////////////////////////////////////////////////////////
bool
    OpenGLRun
    (
        uint16_t                ResolutionX,            // [in]
        uint16_t                ResolutionY,            // [in]
        char*                   Title,                  // [in]
        LibOpenGlFunction       DrawFunction,           // [in]
        int                     TimerElapseMilliseconds // [in]
    );

The prototype for LibOpenGlFunction is a functiont hat takes no parameters and returns no return code. This function is called every TimerElapseMilliseconds (and also other times it needs to redraw). This function can then use OpenGL drawing commands to draw the frame. When the function returns the library will call “SwapBuffers” to make the instant transition from the previous frame.

In my DrawFunction I drew the surface from the plot function (that made the ripple) out of triangles. OpenGL likes triangles in a 3D space.

//////////////////////////////////////////////////////////////////////////////////
//  DrawMesh
//
//  Draws the mesh using OpenGL functions
//////////////////////////////////////////////////////////////////////////////////
void
    DrawMesh
    (
        MeshPoints*     Mesh
    )
{
    int ix;
    int iy;
    double normalX;
    double normalY;
    double normalZ;

    // Draw two triangles between sets of 4 points
    for( iy=gNumMeshPoints; iy>0; iy-- )
    {
        for( ix=1; ix<=gNumMeshPoints; ix++ )
        {
            CalcSurfaceNormal(
        Mesh->meshX[ix-1][iy-1], Mesh->meshY[ix-1][iy-1], Mesh->meshZ[ix-1][iy-1],
        Mesh->meshX[ix-1][iy], Mesh->meshY[ix-1][iy], Mesh->meshZ[ix-1][iy],
        Mesh->meshX[ix][iy-1], Mesh->meshY[ix][iy-1], Mesh->meshZ[ix][iy-1],
        &normalX, &normalY, &normalZ );

            glBegin( GL_TRIANGLES );

            glNormal3d( normalX, normalY, normalZ );
            glVertex3d(
                Mesh->meshX[ix-1][iy-1], 
                Mesh->meshY[ix-1][iy-1],
                Mesh->meshZ[ix-1][iy-1] );
            glVertex3d(
                Mesh->meshX[ix-1][iy],
                Mesh->meshY[ix-1][iy],
                Mesh->meshZ[ix-1][iy] );
            glVertex3d(
                Mesh->meshX[ix][iy-1], 
                Mesh->meshY[ix][iy-1], 
                Mesh->meshZ[ix][iy-1] );

            glNormal3d( normalX, normalY, normalZ );
            glVertex3d(
                Mesh->meshX[ix][iy-1],
                Mesh->meshY[ix][iy-1],
                Mesh->meshZ[ix][iy-1] );
            glVertex3d(
                Mesh->meshX[ix-1][iy], 
                Mesh->meshY[ix-1][iy], 
                Mesh->meshZ[ix-1][iy] );
            glVertex3d( 
                Mesh->meshX[ix][iy],
                Mesh->meshY[ix][iy],
                Mesh->meshZ[ix][iy] );

            glEnd( );
        }
    }
}

I wanted to put some lighting effects in. I used the following

glClearColor( 0.0f, 0.0f, 0.0f, 0.0f );
glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
glEnable(GL_DEPTH_TEST); 
glEnable(GL_POLYGON_SMOOTH);
glEnable( GL_LIGHTING );
glEnable( GL_LIGHT0 );
glShadeModel( GL_SMOOTH );

I also rotated the view point using

glRotated( angle, 1.0, 0.5, 0.0 );
glRotated( angle/2, 0.0, 0.0, 0.1 );

Where angle is 240 degrees.

One thing I found disappointing in OpenGL is that although you are providing it triangles which are therefore flat planes, it does not calculate the reflection angle of the plane itself. This probably makes sense for efficiency, but it seemed annoying that I had to provide the normals for each triangle where in my opinion it already had all the information it needed.

The following function calculates a normal vector for a triangle in 3D space

//////////////////////////////////////////////////////////////////////////////////
//  CalcSurfaceNormal
//
//  Calculates the surface normal of the triangle
//////////////////////////////////////////////////////////////////////////////////
void
    CalcSurfaceNormal
    (
        double      x1,
        double      y1,
        double      z1,
        double      x2,
        double      y2,
        double      z2,
        double      x3,
        double      y3,
        double      z3,
        double*     pNormalX,
        double*     pNormalY,
        double*     pNormalZ
    )
{
    double  d1x;
    double  d1y;
    double  d1z;
    double  d2x;
    double  d2y;
    double  d2z;
    double  crossx;
    double  crossy;
    double  crossz;
    double  dist;

    d1x = x2 - x1;
    d1y = y2 - y1;
    d1z = z2 - z1;

    d2x = x3 - x2;
    d2y = y3 - y2;
    d2z = z3 - z3;

    crossx = d1y*d2z - d1z*d2y;
    crossy = d1y*d2x - d1x*d2z;
    crossz = d1x*d2y - d1y*d2x;

    dist = sqrt( crossx*crossx + crossy*crossy + crossz*crossz );

    *pNormalX = crossx / dist;
    *pNormalY = crossy / dist;
    *pNormalZ = crossz / dist;
}

The end result looked like this
OpenGlExample_Frame_04Now I wanted to animate it. I did this by changing the plot function to name take a 3rd parameter which is time (in seconds). So it now takes an X and Y coordinate and a time value and returns a Z (height) value.

//////////////////////////////////////////////////////////////////////////////////
//  RippleFunction
//
//  This function is a 3d plot function. It provides a Z value for given X and Y.
//  All values are between 0 and 1
//////////////////////////////////////////////////////////////////////////////////
double
    RippleFunction
    (
        double  x,
        double  y,
        double  seconds
    )
{
    double pi = 3.14159265358979;
    double z;
    double dist;
    double wave;
    double posX;
    double posY;
    double factor;

    // Get distance of point from centre (0.5, 0.5)
    posX = 0.5;
    posY = 0.5;
    dist = sqrt( (posX-x)*(posX-x) + (posY-y)*(posY-y) );

    wave = sin( 4*(2*pi) * dist - (seconds/1.0) ) * 0.4;
    z = (wave / 2.0) + 0.5;
    z *= (1.0-dist*1.2);

    // Now calculate a second wave with a moving centre and a low amplitude and
    // low frequency wave.
    // Get Distance of point from moving centre
    posX = 0.8 * (sin( (2*pi)*seconds))*0.01;
    posY = 0.8 * (sin( (4*pi)*seconds/2))*0.01;
    dist = sqrt( (posX-x)*(posX-x) + (posY-y)*(posY-y) );
    wave = sin( 2*(2*pi) * dist - (seconds/2.0) ) * 0.05;

    // Apply the second wave to first as a factor that varies over a sine wave
    factor = 1.0 - (cos( ((2*pi) * seconds/40) ) + 1.0) / 2.0;
    z += (wave * factor);

    return z;
}

This has two ripples with different frequencies, and one which moves the centre of its origin.  Additionally the second ripple is applied to the first one using a scale factor that is attached to a sine wave based on time. When the factor is 0 only the first wave is seen, which is a nice uniform ripple. As time progresses it becomes more distorted with the second wave and then eventually that fades off again.

An example of it with the second wave distorting it:

OpenGlExample_Frame_21

The end result as a QuickTime .mov file is available here

I had quite a lot of fun playing with this. Small changes in the plot function can create interesting results. I have put the program that generates the above wave online for download. It contains the LibOpenGL library which is the small wrapper library I wrote. It should be noted that this is just quick and simple experimental code. My expertise is not in OpenGL or graphics programming. I did this just for fun, and if other people find it handy that is good. However it is most unlikely that this is the “proper” way of doing this sort of graphics.

OpenGLExample.zip – C Source code using OpenGL to draw an animated ripple on a surface. Includes binaries for Windows, OSX, and Linux. This is free and unencumbered software released into the public domain.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s