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!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#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).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 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.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 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 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.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
// 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. :-)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// 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?).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
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).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
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).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
// 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.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
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)

* DOWNLOAD Visual C++ Code For This Lesson.

* DOWNLOAD Borland C++ Builder 6 Code For This Lesson. ( Conversion by Christian Kindahl )
* DOWNLOAD Code Warrior 5.3 Code For This Lesson. ( Conversion by Scott Lupton )
* DOWNLOAD Delphi Code For This Lesson. ( Conversion by Michal Tucek )
* DOWNLOAD Dev C++ Code For This Lesson. ( Conversion by Dan )
* DOWNLOAD Euphoria Code For This Lesson. ( Conversion by Evan Marshall )
* DOWNLOAD LCC Win32 Code For This Lesson. ( Conversion by Robert Wishlaw )
* DOWNLOAD Linux/GLX Code For This Lesson. ( Conversion by Rodolphe Suescun )
* DOWNLOAD LWJGL Code For This Lesson. ( Conversion by Mark Bernard )
* DOWNLOAD Mac OS X/Cocoa Code For This Lesson. ( Conversion by Bryan Blackburn )
* DOWNLOAD Visual Studio .NET Code For This Lesson. ( Conversion by Grant James )

 

< Lesson 27Lesson 29 >