# Bezier Patches / Fullscreen Fix

Bezier Patches

Written by: David Nikdel ( ogapo@ithink.net )

This tutorial is intended to introduce you to Bezier Surfaces in the hopes that someone more artistic than myself will do something really cool with them and show all of us. This is not intended as a complete Bezier patch library, but more as proof of concept code to get you familiar with how these curved surfaces actually work. Also, as this is a very informal piece, I may have occasional lapses in correct terminology in favor of comprehensability; I hope this sits well with everyone. Finally, to those of you already familiar with Beziers who are just reading this to see if I screw up, shame on you ;-), but if you find anything wrong by all means let me or NeHe know, after all no one's perfect, eh? Oh, and one more thing, none of this code is optimized beyond my normal programming technique, this is by design. I want everyone to be able to see exactly what is going on. Well, I guess that's enough of an intro. On with the show!

The Math - ::evil music:: (warning, kinda long section)

Ok, it will be very hard to understand Beziers without at least a basic understanding of the math behind it, however, if you just don't feel like reading this section or already know the math, you can skip it. First I will start out by describing the Bezier curve itself then move on to how to create a Bezier Patch.

Odds are, if you've ever used a graphics program you are already familiar with Bezier curves, perhaps not by that name though. They are the primary method of drawing curved lines and are commonly represented as a series of points each with 2 points representing the tangent at that point from the left and right. Here's what one looks like:

This is the most basic Bezier curve possible (longer ones are made by attaching many of these together (many times without the user realizing it)). This curve is actually defined by only 4 points, those would be the 2 ending control points and the 2 middle control points. To the computer, all the points are the same, but to aid in design we often connect the first and the last two, respectively, because those lines will always be tangent to the endpoint. The curve is a parametric curve and is drawn by finding any number of points evenly spaced along the curve and connecting them with straight lines. In this way you can control the resolution of the patch (and the amount of computation). The most common way to use this is to tesselate it less at a farther distance and more at a closer distance so that, to the viewer, it always appears to be a perfectly curved surface with the lowest possible speed hit.

Bezier curves are based on a basis function from which more complicated versions are derived. Here's the function:

t + (1 - t) = 1

Sounds simple enough huh? Well it really is, this is the Bezier most basic Bezier curve, a 1st degree curve. As you may have guessed from the terminology, the Bezier curves are polynomials, and as we remember from algebra, a 1st degree polynomial is just a straight line; not very interesting. Well, since the basis function is true for all numbers t, we can square, cube, whatever, each side and it will still be true right? Well, lets try cubing it.

(t + (1-t))^3 = 1^3

t^3 + 3*t^2*(1-t) + 3*t*(1-t)^2 + (1-t)^3 = 1

This is the equation we use to calculate the most common Bezier, the 3rd degree Bezier curve. This is most common for two reasons, a) it's the lowest degree polynomial that need not necesarily lie in a plane (there are 4 control points) and b) the tangent lines on the sides are not dependant on one another (with a 2nd degree there would be only 3 control points). So do you see the Bezier curve yet? Hehe, me neither, that's because I still need to add one thing.

Ok, since the entire left side is equal to 1, it's safe to assume that if you add all the components they should still equal one. Does this sound like it could be used to decide how much of each control point to use in calculating a point on the curve? (hint: just say yes ;-) ) Well you're right! When we want to calculate the value of a point some percent along the curve we simply multiply each part by a control point (as a vector) and find the sum. Generally, we'll work with 0

P1*t^3 + P2*3*t^2*(1-t) + P3*3*t*(1-t)^2 + P4*(1-t)^3 = Pnew

Because polynomials are always continuous, this makes for a good way to morp between the 4 points. The only points it actually reaches though are P1 and P4, when t = 1 and 0 respectively.

Now, that's all well and good, but how can I use these in 3D you ask? Well it's actually quite simple, in order to form a Bezier patch, you need 16 control points (4*4), and 2 variables t and v. What you do from there is calculate a point at v along 4 of the parallel curves then use those 4 points to make a new curve and calculate t along that curve. By calculating enough of these points, we can draw triangle strips to connect them, thus drawing the Bezier patch.

Well, I suppose that's enough math for now, on to the code!

```#include <windows.h>							// Header File For Windows
#include <math.h>							// Header File For Math Library Routines
#include <stdio.h>							// Header File For Standard I/O Routines
#include <stdlib.h>							// Header File For Standard Library
#include <gl\gl.h>							// Header File For The OpenGL32 Library
#include <gl\glu.h>							// Header File For The GLu32 Library
#include <gl\glaux.h>							// Header File For The Glaux Library

typedef struct point_3d {						// Structure For A 3-Dimensional Point ( NEW )
double x, y, z;
} POINT_3D;

typedef struct bpatch {							// Structure For A 3rd Degree Bezier Patch ( NEW )
POINT_3D	anchors[4][4];					// 4x4 Grid Of Anchor Points
GLuint		dlBPatch;					// Display List For Bezier Patch
GLuint		texture;					// Texture For The Patch
} BEZIER_PATCH;

HDC			hDC=NULL;					// Private GDI Device Context
HGLRC			hRC=NULL;					// Permanent Rendering Context
HWND			hWnd=NULL;					// Holds Our Window Handle
HINSTANCE		hInstance;					// Holds The Instance Of The Application

DEVMODE			DMsaved;					// Saves The Previous Screen Settings ( NEW )

bool			keys[256];					// Array Used For The Keyboard Routine
bool			active=TRUE;					// Window Active Flag Set To TRUE By Default
bool			fullscreen=TRUE;				// Fullscreen Flag Set To Fullscreen Mode By Default

GLfloat			rotz = 0.0f;					// Rotation About The Z Axis
BEZIER_PATCH		mybezier;					// The Bezier Patch We're Going To Use ( NEW )
BOOL			showCPoints=TRUE;				// Toggles Displaying The Control Point Grid ( NEW )
int			divs = 7;					// Number Of Intrapolations (Controls Poly Resolution) ( NEW )

LRESULT	CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);			// Declaration For WndProc
```

The following are just a few quick functions for some simple vector math. If you're a fan of C++ you might consider using a point class (just make sure it's 3d).

```// Adds 2 Points. Don't Just Use '+' ;)
POINT_3D pointAdd(POINT_3D p, POINT_3D q) {
p.x += q.x;		p.y += q.y;		p.z += q.z;
return p;
}

// Multiplies A Point And A Constant. Don't Just Use '*'
POINT_3D pointTimes(double c, POINT_3D p) {
p.x *= c;	p.y *= c;	p.z *= c;
return p;
}

// Function For Quick Point Creation
POINT_3D makePoint(double a, double b, double c) {
POINT_3D p;
p.x = a;	p.y = b;	p.z = c;
return p;
}
```

This is basically just the 3rd degree basis function written in C, it takes a variable u and an array of 4 points and computes a point on the curve. By stepping u in equal increments between 0 and 1, we'll get a nice approximation of the curve.

```// Calculates 3rd Degree Polynomial Based On Array Of 4 Points
// And A Single Variable (u) Which Is Generally Between 0 And 1
POINT_3D Bernstein(float u, POINT_3D *p) {
POINT_3D	a, b, c, d, r;

a = pointTimes(pow(u,3), p[0]);
b = pointTimes(3*pow(u,2)*(1-u), p[1]);
c = pointTimes(3*u*pow((1-u),2), p[2]);
d = pointTimes(pow((1-u),3), p[3]);

return r;
}
```

This function does the lion's share of the work by generating all the triangle strips and storing them in a display list. We do this so that we don't have to recalculate the patch each frame, only when it changes. By the way, a cool effect you might want to try might be to use the morphing tutorial to morph the patch's control points. This would yield a very cool smooth, organic, morphing effect for relatively little overhead (you only morph 16 points, but you have to recalculate). The "last" array is used to keep the previous line of points (since a triangle strip needs both rows). Also, texture coordinates are calculated by using the u and v values as the percentages (planar mapping).

One thing we don't do is calculate the normals for lighting. When it comes to this, you basically have two options. The first is to find the center of each triangle, then use a bit of calculus and calculate the tangent on both the x and y axes, then do the cross product to get a vector perpendicular to both, THEN normalize the vector and use that as the normal. OR (yes, there is a faster way) you can cheat and just use the normal of the triangle (calculated your favorite way) to get a pretty good approximation. I prefer the latter; the speed hit, in my opinion, isn't worth the extra little bit of realism.

```// Generates A Display List Based On The Data In The Patch
// And The Number Of Divisions
GLuint genBezier(BEZIER_PATCH patch, int divs) {
int		u = 0, v;
float		py, px, pyold;
GLuint		drawlist = glGenLists(1);			// Make The Display List
POINT_3D	temp[4];
POINT_3D	*last = (POINT_3D*)malloc(sizeof(POINT_3D)*(divs+1));
// Array Of Points To Mark The First Line Of Polys

if (patch.dlBPatch != NULL)					// Get Rid Of Any Old Display Lists
glDeleteLists(patch.dlBPatch, 1);

temp[0] = patch.anchors[0][3];					// The First Derived Curve (Along X-Axis)
temp[1] = patch.anchors[1][3];
temp[2] = patch.anchors[2][3];
temp[3] = patch.anchors[3][3];

for (v=0;v<=divs;v++) {						// Create The First Line Of Points
px = ((float)v)/((float)divs);				// Percent Along Y-Axis
// Use The 4 Points From The Derived Curve To Calculate The Points Along That Curve
last[v] = Bernstein(px, temp);
}

glNewList(drawlist, GL_COMPILE);				// Start A New Display List
glBindTexture(GL_TEXTURE_2D, patch.texture);			// Bind The Texture

for (u=1;u<=divs;u++) {
py    = ((float)u)/((float)divs);			// Percent Along Y-Axis
pyold = ((float)u-1.0f)/((float)divs);			// Percent Along Old Y Axis

temp[0] = Bernstein(py, patch.anchors[0]);		// Calculate New Bezier Points
temp[1] = Bernstein(py, patch.anchors[1]);
temp[2] = Bernstein(py, patch.anchors[2]);
temp[3] = Bernstein(py, patch.anchors[3]);

glBegin(GL_TRIANGLE_STRIP);				// Begin A New Triangle Strip

for (v=0;v<=divs;v++) {
px = ((float)v)/((float)divs);			// Percent Along The X-Axis

glTexCoord2f(pyold, px);			// Apply The Old Texture Coords
glVertex3d(last[v].x, last[v].y, last[v].z);	// Old Point

last[v] = Bernstein(px, temp);			// Generate New Point
glTexCoord2f(py, px);				// Apply The New Texture Coords
glVertex3d(last[v].x, last[v].y, last[v].z);	// New Point
}

glEnd();						// END The Triangle Strip
}

glEndList();							// END The List

free(last);							// Free The Old Vertices Array
return drawlist;						// Return The Display List
}
```

Here we're just loading the matrix with some values I've picked that I think look cool. Feel free to screw around with these and see what it looks like. :-)

```void initBezier(void) {
mybezier.anchors[0][0] = makePoint(-0.75,	-0.75,	-0.50);	// Set The Bezier Vertices
mybezier.anchors[0][1] = makePoint(-0.25,	-0.75,	 0.00);
mybezier.anchors[0][2] = makePoint( 0.25,	-0.75,	 0.00);
mybezier.anchors[0][3] = makePoint( 0.75,	-0.75,	-0.50);
mybezier.anchors[1][0] = makePoint(-0.75,	-0.25,	-0.75);
mybezier.anchors[1][1] = makePoint(-0.25,	-0.25,	 0.50);
mybezier.anchors[1][2] = makePoint( 0.25,	-0.25,	 0.50);
mybezier.anchors[1][3] = makePoint( 0.75,	-0.25,	-0.75);
mybezier.anchors[2][0] = makePoint(-0.75,	 0.25,	 0.00);
mybezier.anchors[2][1] = makePoint(-0.25,	 0.25,	-0.50);
mybezier.anchors[2][2] = makePoint( 0.25,	 0.25,	-0.50);
mybezier.anchors[2][3] = makePoint( 0.75,	 0.25,	 0.00);
mybezier.anchors[3][0] = makePoint(-0.75,	 0.75,	-0.50);
mybezier.anchors[3][1] = makePoint(-0.25,	 0.75,	-1.00);
mybezier.anchors[3][2] = makePoint( 0.25,	 0.75,	-1.00);
mybezier.anchors[3][3] = makePoint( 0.75,	 0.75,	-0.50);
mybezier.dlBPatch = NULL;					// Go Ahead And Initialize This To NULL
}
```

This is basically just an optimized routine to load a single bitmap. It can easily be used to load an array of em just by putting it in a simple loop.

```// Load Bitmaps And Convert To Textures

{
BOOL success = FALSE;
AUX_RGBImageRec *TextureImage = NULL;

glGenTextures(1, texPntr);					// Generate 1 Texture

FILE* test=NULL;
TextureImage = NULL;

test = fopen(name, "r");					// Test To See If The File Exists
if (test != NULL) {						// If It Does
fclose(test);						// Close The File
}

if (TextureImage != NULL) {					// If It Loaded
success = TRUE;

// Typical Texture Generation Using Data From The Bitmap
glBindTexture(GL_TEXTURE_2D, *texPntr);
glTexImage2D(GL_TEXTURE_2D, 0, 3, TextureImage->sizeX, TextureImage->sizeY, 0, GL_RGB, GL_UNSIGNED_BYTE, TextureImage->data);
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
}

if (TextureImage->data)
free(TextureImage->data);

return success;
}
```

Just adding the patch initialization here. You would do this whenever you create a patch. Again, this might be a cool place to use C++ (bezier class?).

```int InitGL(GLvoid)							// All Setup For OpenGL Goes Here
{
glEnable(GL_TEXTURE_2D);					// Enable Texture Mapping
glClearColor(0.05f, 0.05f, 0.05f, 0.5f);			// Black Background
glClearDepth(1.0f);						// Depth Buffer Setup
glEnable(GL_DEPTH_TEST);					// Enables Depth Testing
glDepthFunc(GL_LEQUAL);						// The Type Of Depth Testing To Do
glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);		// Really Nice Perspective Calculations

initBezier();							// Initialize the Bezier's Control Grid ( NEW )
mybezier.dlBPatch = genBezier(mybezier, divs);			// Generate The Patch ( NEW )

return TRUE;							// Initialization Went OK
}
```

First call the bezier's display list. Then (if the outlines are on) draw the lines connecting the control points. You can toggle these by pressing SPACE.

```int DrawGLScene(GLvoid)	{						// Here's Where We Do All The Drawing
int i, j;
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);		// Clear Screen And Depth Buffer
glLoadIdentity();						// Reset The Current Modelview Matrix
glTranslatef(0.0f,0.0f,-4.0f);					// Move Left 1.5 Units And Into The Screen 6.0
glRotatef(-75.0f,1.0f,0.0f,0.0f);
glRotatef(rotz,0.0f,0.0f,1.0f);					// Rotate The Triangle On The Z-Axis

glCallList(mybezier.dlBPatch);					// Call The Bezier's Display List
// This Need Only Be Updated When The Patch Changes

if (showCPoints) {						// If Drawing The Grid Is Toggled On
glDisable(GL_TEXTURE_2D);
glColor3f(1.0f,0.0f,0.0f);
for(i=0;i<4;i++) {					// Draw The Horizontal Lines
glBegin(GL_LINE_STRIP);
for(j=0;j<4;j++)
glVertex3d(mybezier.anchors[i][j].x, mybezier.anchors[i][j].y, mybezier.anchors[i][j].z);
glEnd();
}
for(i=0;i<4;i++) {					// Draw The Vertical Lines
glBegin(GL_LINE_STRIP);
for(j=0;j<4;j++)
glVertex3d(mybezier.anchors[j][i].x, mybezier.anchors[j][i].y, mybezier.anchors[j][i].z);
glEnd();
}
glColor3f(1.0f,1.0f,1.0f);
glEnable(GL_TEXTURE_2D);
}

return TRUE;							// Keep Going
}
```

This function contains some modified code to make your projects more compatable. It doesn't have anything to do with Bezier curves, but it does fix a problem with switching back the resolution after fullscreen mode with some video cards (including mine, a crappy old ATI Rage PRO, and a few others). I hope, you'll use this from now on so me and others with similar cards can view your cool examples GL code properly. To make these modifications make the changes in KillGLWindow(), make sure and define DMsaved, and make the one line change in CreateGLWindow() (it's marked).

```GLvoid KillGLWindow(GLvoid)						// Properly Kill The Window
{
if (fullscreen)							// Are We In Fullscreen Mode?
{
if (!ChangeDisplaySettings(NULL,CDS_TEST)) { 		// If The Shortcut Doesn't Work ( NEW )
ChangeDisplaySettings(NULL,CDS_RESET);		// Do It Anyway (To Get The Values Out Of The Registry) ( NEW )
ChangeDisplaySettings(&DMsaved,CDS_RESET);	// Change It To The Saved Settings ( NEW )
} else {
ChangeDisplaySettings(NULL,CDS_RESET);		// If It Works, Go Right Ahead ( NEW )
}

ShowCursor(TRUE);					// Show Mouse Pointer
}

if (hRC)							// Do We Have A Rendering Context?
{
if (!wglMakeCurrent(NULL,NULL))				// Are We Able To Release The DC And RC Contexts?
{
MessageBox(NULL,"Release Of DC And RC Failed.","SHUTDOWN ERROR",MB_OK | MB_ICONINFORMATION);
}

if (!wglDeleteContext(hRC))				// Are We Able To Delete The RC?
{
MessageBox(NULL,"Release Rendering Context Failed.","SHUTDOWN ERROR",MB_OK | MB_ICONINFORMATION);
}
hRC=NULL;						// Set RC To NULL
}

if (hDC && !ReleaseDC(hWnd,hDC))				// Are We Able To Release The DC
{
MessageBox(NULL,"Release Device Context Failed.","SHUTDOWN ERROR",MB_OK | MB_ICONINFORMATION);
hDC=NULL;						// Set DC To NULL
}

if (hWnd && !DestroyWindow(hWnd))				// Are We Able To Destroy The Window?
{
MessageBox(NULL,"Could Not Release hWnd.","SHUTDOWN ERROR",MB_OK | MB_ICONINFORMATION);
hWnd=NULL;						// Set hWnd To NULL
}

if (!UnregisterClass("OpenGL",hInstance))			// Are We Able To Unregister Class
{
MessageBox(NULL,"Could Not Unregister Class.","SHUTDOWN ERROR",MB_OK | MB_ICONINFORMATION);
hInstance=NULL;						// Set hInstance To NULL
}
}
```

Just added the EnumDisplaySettings() command here to save the old display settings. (part of the old graphics card fix).

```// This Code Creates Our OpenGL Window.  Parameters Are:			*
// title		- Title To Appear At The Top Of The Window		*
// width		- Width Of The GL Window Or Fullscreen Mode		*
// height		- Height Of The GL Window Or Fullscreen Mode		*
// bits			- Number Of Bits To Use For Color (8/16/24/32)		*
// fullscreenflag	- Use Fullscreen Mode (TRUE) Or Windowed Mode (FALSE)	*/

BOOL CreateGLWindow(char* title, int width, int height, int bits, bool fullscreenflag)
{
GLuint		PixelFormat;					// Holds The Results After Searching For A Match
WNDCLASS	wc;						// Windows Class Structure
DWORD		dwExStyle;					// Window Extended Style
DWORD		dwStyle;					// Window Style
RECT		WindowRect;					// Grabs Rectangle Upper Left / Lower Right Values
WindowRect.left=(long)0;					// Set Left Value To 0
WindowRect.right=(long)width;					// Set Right Value To Requested Width
WindowRect.top=(long)0;						// Set Top Value To 0
WindowRect.bottom=(long)height;					// Set Bottom Value To Requested Height

fullscreen=fullscreenflag;					// Set The Global Fullscreen Flag

hInstance		= GetModuleHandle(NULL);		// Grab An Instance For Our Window
wc.style		= CS_HREDRAW | CS_VREDRAW | CS_OWNDC;	// Redraw On Size, And Own DC For Window
wc.lpfnWndProc		= (WNDPROC) WndProc;			// WndProc Handles Messages
wc.cbClsExtra		= 0;					// No Extra Window Data
wc.cbWndExtra		= 0;					// No Extra Window Data
wc.hInstance		= hInstance;				// Set The Instance
wc.hbrBackground	= NULL;					// No Background Required For GL
wc.lpszClassName	= "OpenGL";				// Set The Class Name

EnumDisplaySettings(NULL, ENUM_CURRENT_SETTINGS, &DMsaved);	// Save The Current Display State ( NEW )

if (fullscreen)							// Attempt Fullscreen Mode?
{
DEVMODE dmScreenSettings;				// Device Mode
memset(&dmScreenSettings,0,sizeof(dmScreenSettings));	// Makes Sure Memory's Cleared
dmScreenSettings.dmSize=sizeof(dmScreenSettings);	// Size Of The Devmode Structure
dmScreenSettings.dmPelsWidth	= width;		// Selected Screen Width
dmScreenSettings.dmPelsHeight	= height;		// Selected Screen Height
dmScreenSettings.dmBitsPerPel	= bits;			// Selected Bits Per Pixel
dmScreenSettings.dmFields=DM_BITSPERPEL|DM_PELSWIDTH|DM_PELSHEIGHT;

... Code Cut To Save Space (No Further Changes To This Function) ...

return TRUE;							// Success
}
```

All I did here was add commands to rotate the patch, raise/lower the resolution, and toggle the control lines.

```int WINAPI WinMain(	HINSTANCE	hInstance,			// Instance
HINSTANCE	hPrevInstance,  // Previous Instance
LPSTR		lpCmdLine,	// Command Line Parameters
int		nCmdShow)	// Window Show State
{
MSG		msg;						// Windows Message Structure
BOOL	done=FALSE;						// Bool Variable To Exit Loop

// Ask The User Which Screen Mode They Prefer
if (MessageBox(NULL,"Would You Like To Run In Fullscreen Mode?", "Start FullScreen?",MB_YESNO|MB_ICONQUESTION)==IDNO)
{
fullscreen=FALSE;					// Windowed Mode
}

// Create Our OpenGL Window
if (!CreateGLWindow("NeHe's Solid Object Tutorial",640,480,16,fullscreen))
{
return 0;						// Quit If Window Was Not Created
}

while(!done)							// Loop That Runs While done=FALSE
{
if (PeekMessage(&msg,NULL,0,0,PM_REMOVE))		// Is There A Message Waiting?
{
if (msg.message==WM_QUIT)			// Have We Received A Quit Message?
{
done=TRUE;				// If So done=TRUE
}
else						// If Not, Deal With Window Messages
{
TranslateMessage(&msg);			// Translate The Message
DispatchMessage(&msg);			// Dispatch The Message
}
}
else							// If There Are No Messages
{
// Draw The Scene.  Watch For ESC Key And Quit Messages From DrawGLScene()
if ((active && !DrawGLScene()) || keys[VK_ESCAPE])	// Active?  Was There A Quit Received?
{
done=TRUE;				// ESC or DrawGLScene Signalled A Quit
}
else						// Not Time To Quit, Update Screen
{
SwapBuffers(hDC);			// Swap Buffers (Double Buffering)
}

if (keys[VK_LEFT])	rotz -= 0.8f;		// Rotate Left ( NEW )
if (keys[VK_RIGHT])	rotz += 0.8f;		// Rotate Right ( NEW )
if (keys[VK_UP]) {				// Resolution Up ( NEW )
divs++;
mybezier.dlBPatch = genBezier(mybezier, divs);	// Update The Patch
keys[VK_UP] = FALSE;
}
if (keys[VK_DOWN] && divs > 1) {		// Resolution Down ( NEW )
divs--;
mybezier.dlBPatch = genBezier(mybezier, divs);	// Update The Patch
keys[VK_DOWN] = FALSE;
}
if (keys[VK_SPACE]) {				// SPACE Toggles showCPoints ( NEW )
showCPoints = !showCPoints;
keys[VK_SPACE] = FALSE;
}

if (keys[VK_F1])				// Is F1 Being Pressed?
{
keys[VK_F1]=FALSE;			// If So Make Key FALSE
KillGLWindow();				// Kill Our Current Window
fullscreen=!fullscreen;			// Toggle Fullscreen / Windowed Mode
// Recreate Our OpenGL Window
if (!CreateGLWindow("NeHe's Solid Object Tutorial",640,480,16,fullscreen))
{
return 0;			// Quit If Window Was Not Created
}
}
}
}

// Shutdown
KillGLWindow();							// Kill The Window
return (msg.wParam);						// Exit The Program
}
```

Well, I hope this tutorial has been enlightening and you all now love Bezier curves as much as I do ;-). If you like this tutorial I may write another one on NURBS curves if anyone's interested. Please e-mail me and let me know what you thought of this tutorial.

About The Author: David Nikdel is currently 18 and a senior at Bartow Senior High School. His current projects include a research paper on curved surfaces in 3D graphics, an OpenGL based game called Blazing Sands and being lazy. His hobbies include programming, football, and paintballing. He will (hopefully) be a freshman at Georgia Tech next year.

David Nikdel

Jeff Molofee (NeHe)