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
Now 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:
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.