52926.fb2 NeHes OpenGL Tutorials - читать онлайн бесплатно полную версию книги . Страница 28

NeHes OpenGL Tutorials - читать онлайн бесплатно полную версию книги . Страница 28

Lesson 28

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 optimised 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 descide 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 <= t <= 1, but it's not technically necesary. Confused yet? Here's the function:

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]);

 r = pointAdd(pointAdd(a, b), pointAdd(c, d));

 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 yeild 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 optimised 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 LoadGLTexture(GLuint *texPntr, char* name) {

 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

  TextureImage = auxDIBImageLoad(name); // And Load The Texture

 }

 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

 glShadeModel(GL_SMOOTH); // Enable Smooth Shading

 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 )

 LoadGLTexture(&(mybezier.texture), "./Data/NeHe.bmp"); // Load The Texture ( 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.hIcon = LoadIcon(NULL, IDI_WINLOGO); // Load The Default Icon

 wc.hCursor = LoadCursor(NULL, IDC_ARROW); // Load The Arrow Pointer

 wc.hbrBackground = NULL; // No Background Required For GL

 wc.lpszMenuName = NULL; // We Don't Want A Menu

 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 NikdelJeff Molofee (NeHe)

* DOWNLOAD Visual C++ Code For This Lesson.

* DOWNLOAD Linux/GLX Code For This Lesson. (Conversion by Rodolphe Suescun)