Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

Open Source

Programming with OpenGL


JUL95: Programming with OpenGL

Programming with OpenGL

3-D graphics for Windows NT

Ron Fosner

Ron is a principal software developer at Lotus Development, where he researches and develops graphical and interactive techniques for data analysis and exploration. Ron can be contacted at [email protected].


If you've worked with the Windows GDI, you're painfully aware of its limitations, particularly when trying to create anything other than a flat, 2-D, static scene. And whether you program games or business graphics, you know that in Windows, attempting to create any effects beyond a simple gradient fill usually means some complicated programming. Recognizing these shortcomings, Microsoft has added to Windows NT 3.5 (and promised for all Microsoft 32-bit operating systems) a graphics library called "OpenGL," which provides the advanced 3-D rendering and animation that is difficult to do with GDI.

OpenGL is a computer-industry standard based upon Silicon Graphics' internal graphics library. OpenGL was designed and is maintained by an industry-wide review board composed of SGI, Microsoft, IBM, Intel, and DEC. Until recently, OpenGL was usually found only on UNIX workstations. However, with the availability of a standardized (and well-known) interface for 3-D graphics, along with advances in dedicated 3-D rendering hardware, it's possible to create some amazingly complicated and realistic scenes in Windows and render them quickly. In this article, I'll provide an overview of OpenGL and illustrate how you can start writing your own OpenGL programs.

OpenGL Primitives

OpenGL provides primitives for points, lines, and polygons. Everything you create is based on these three primitives. The library also provides support routines that draw curves, surfaces, or text; you can also create filled polygons (thereby creating a surface). Once you've created a scene out of primitives, you can specify lighting effects, specialized effects (fog or transparency), and viewing angle. OpenGL takes care of the rest: shading, hidden-surface removal, and perspective rendering. If you don't like the viewpoint, simply change it and OpenGL will recalculate the scene for you. In fact, once the objects are created, you can dynamically alter their location and rotation, your viewpoint, the lighting effects, shading, and so on; these, too, will be recalculated for you. The hard part is locating and describing the objects themselves.

OpenGL is designed to run efficiently as a state machine in a client/server model. For instance, in a typical environment, you might have one powerful computer generating the drawing commands (the server), while a networked client workstation receives these commands and does the actual rendering on its screen. While there's nothing in NT 3.5 preventing you from creating such a program using remote-procedure calls (RPCs), OpenGL works just as well if the same computer is both client and server. Again, the tricky part is learning how to create an OpenGL scene and trying to interface between OpenGL and Windows, since OpenGL (as a hardware-independent library) knows nothing about Windows, device contexts, pens, or brushes.

OpenGL Libraries

Three libraries are provided with the NT version of OpenGL, the main one being opengl32.lib. By convention, functions in this library (such as glDrawPixels()) use the prefix "gl". Next is the OpenGL utility library, glu32.lib, containing functions such as gluBeginPolygon(), which use the prefix "glu". These are helper routines for OpenGL that provide services such as creating a sphere, performing matrix manipulations, and tessellation. If you think of opengl32.lib as the workhorse of OpenGL, then the utility library provides higher-level functionality. The final library is the auxiliary library written for the OpenGL Programming Guide. Routines in this library contain an "aux" prefix, as in auxInitWindow(). These functions are not strictly part of OpenGL, and needn't be included for most OpenGL programs. However, you will likely find them in most OpenGL implementations, including NT's. I'll use the auxiliary library, since it allows you to ignore the Windows-specific portions of a program and concentrate on the OpenGL parts.

Finally, six new, implementation-specific interface routines allow OpenGL to work on a Windows platform. Interface routines like wglGetCurrentContext() use a "wgl" prefix and are referred to as "wiggle" routines. These routines provide the interface between straight OpenGL and Windows and are analogous to the "glx" interface functions (X Windows' interface for OpenGL) in an X Window System implementation.

In addition, four Win32 functions allow access to the pixel formats. These are important since you have to try to match your program's needs with your system's hardware. Finally, there's one Win32 function that deals with swapping the buffers in a double-buffered window.

Watch the Bouncing Ball

Listing One (page 106) is an OpenGL program that displays a bouncing ball on a checkerboard surface; see Figure 1. I'm using the auxiliary library, which lets me ignore Windows and concentrate on OpenGL. It also lets me write more-traditional C code rather than Windows-style code. The first three aux functions in the localInitialize() procedure of Listing One initialize the display mode, set the window's size and position, and open the window. Clearly, using the aux library hides a lot of the Windows code.

After initialization, the next few routines in main() take function names as arguments and show how the auxiliary library operates. auxReshapeFunc is called when the window needs to be reshaped; auxKeyFunc, when a specified key is pressed; auxIdleFunc, when you have idle time; and auxMainLoop is the main loop of the program. In the Windows implementation, these functions all hide the Windows messaging system, making OpenGL programming straightforward. Of course, when you write an OpenGL program for Windows, you have to worry about all the other nastiness that accompanies writing for the Windows API, plus the additional worries of an OpenGL Windows app.

Creating and Viewing a 3-D Object

The biggest change that comes from rendering a 3-D scene is learning how to specify both object and a viewing volume. In the 2-D world, you could just specify a line to be drawn from, say, 100,100 to 200,300, and there it would be, on your screen. Things aren't that simple in 3-D, because 3-D objects are described by their vertices using x-, y-, and z-coordinates. The difficulty is compounded by the fact that you must specify the coordinates of both an object and the viewpoint.

When a vertex is rendered to the screen, it goes through a couple of transformation matrices. Figure 2 shows the steps that a single point goes through. An object is initially specified in what's usually called "object" coordinates, which are considered local for each object. The object is then usually translated, rotated, and scaled into "world" coordinates. Objects in world coordinates are positioned with respect to all other objects in the world. When everything is set, all of the viewing and projection calculations are performed to render your object to a collection of pixels on the screen.

When an object is created, its default origin is 0,0,0. Any initial transformations to an object are called "modeling transformations." For example, if you create a rendering of a car, you'd probably have one routine to draw a wheel in object coordinates, and then just call the routine four times with four different transformations that would place the wheels in the correct location and orientation about the car body in world coordinates. In this way, you can create complex objects out of simply rendered objects. When all of the objects are correctly positioned in world coordinates, we can specify the viewing transformation. This will determine the viewpoint from which we "see" the objects.

As a performance improvement, OpenGL combines the modeling transformations with the viewing transformation since the viewing and modeling matrices can be combined at this point. What this means for the programmer is that the viewing transformation is applied first and the modeling transformations follow. This is one of the trickier issues about 3-D graphics, particularly OpenGL's implementation.

Next, the projection matrix is applied to take the specified viewing volume and clip out everything outside it, along with parts of any object obscured by another object. The perspective division adjusts the results from the projection matrix (3-D coordinates) and gives you 2-D device coordinates. Finally, these 2-D coordinates are mapped to the physical screen by the viewport transformation. Fortunately, the only complex part of this whole procedure is the specification of the modelview matrix and the projection matrix. For now, I'll just use a simple set for both. The localReshape function in Listing One selects, initializes, then sets up the projection so that the result is a simple perspective projection. This is done each time the window is resized to maintain the correct aspect ratio. The localIdle function controls the modelview matrix, which is selected and initialized, and then translates our viewpoint along the z-axis. Next, rotations are applied along all three axes. In Listing One, all these values are controlled by the user, so that you can manipulate the view.

Bouncing Ball Revisited

The real substance of the Listing One program is contained in two areas. The first is the visible part--the program functions that render the ball and the surface. The functions localDrawSurface and localDrawSphere are straightforward. The localDrawSphere function simply draws a white (glColor3f) solid sphere (auxSolidSphere) along the y-axis (glTranslatef). Since OpenGL is a state machine, you must first modify the state (in this case, the color and position). Hence, you set the color and position and then draw a sphere. Note that I've taken advantage of the aux library function to draw a sphere, rather than the more-complicated gluSphere.

Drawing the surface is similar, except that you have to explicitly create the surface out of polygons, and the polygons out of vertices. Inside the two nested for loops that divide up the surface into squares, OpenGL primitives are created between calls to glBegin and glEnd (auxSolidSphere handled this in localDrawSphere). This is similar to a WM_PAINT message, where you call BeginPaint, do some painting, then call EndPaint. In the case of OpenGL primitives, you signal OpenGL that you are going to create an object out of some vertices, construct the object, then signal you're done.

For the localDrawSurface function, the glBegin(GL_QUADS) call tells OpenGL that we are going to construct a four-sided polygon (quad). You set the color of a vertex with glColor3fv, then the position of the vertex with glVertex3fv. After the fourth vertex, glEnd signals that you are done. You do this for each square in the checkerboard surface.

Note that a more powerful form of glBegin could have been used for a simple checkerboard, making up the top side of a surface. However, I decided to add an interesting feature to the program: For the underside of the surface, I only draw alternating squares. If you rotate the view around the x- or z-axis, you can flip the scene over so that you are looking at it from underneath. From this viewpoint you can see the bouncing ball through the checkerboard! Try to imagine how many calculations are required to perform this feat and you'll quickly appreciate what OpenGL can do.

Animating the Scene

In my original plan, I was just going to render a scene of a ball bouncing on a red and blue checkered surface. With just a few additional lines of code, I added the capability to spin the scene around the y-axis, then the x- and z-axes. At this point, the bottom of the surface was visible, so I added code to change its color and place holes in it. The ease with which I added the code created a classic example of feature creep; I had to stop or I'd never finish. Here's how the scene is animated.

The main() procedure of Listing One makes a call to auxMainLoop(localDisplay). localDisplay is normally your display routine for nonanimated scenes. However, if you're creating an animated scene, the localIdle function is where the rendering is done. In the localIdle function the call to glClear clears out the display buffer, then adjusts the animation parameters in the call to localAdjustParameters. This adds spin to each axis and moves the ball along its trajectory. Next, set up the modelview matrix by first initializing it, then accounting for the rotations and translations. Then call the routines to draw the surface and sphere, and finally, since this is a double buffered window (as specified in the call to auxInitDisplayMode), swap the front and rear buffers, which sends the rendering to the screen.

With the program running in its startup state, you'll just see the ball hovering above the checkered surface. If you press the "a" key, you'll start the ball bouncing. The "a" key toggles the animation on and off. Eventually the bounces will diminish in magnitude, reaching a cutoff point at which the ball is reset to above the surface. The "x," "y," and "z" keys increase the rotation of their respective axes for each time through the animation loop. The more times you press a key, the larger each change will be through the loop. Press the uppercase letter to subtract from the rotation. The up and down arrow keys will move you closer and further away (add to or subtract from the initial z-axis translation). Actually, the center of the animation moves relative to the specified viewpoint. You'll see this when moving the surface away from you till it's beyond the far clipping plane of the viewing volume. If you just start the y-axis spinning from the initial position, you can see the far corner disappear when it rotates into the clipping plane. The "f" key will freeze the display, and the "r" key will reset it to the initial viewing parameters.

One thing you'll immediately notice about the animation is its constant speed. Since all of the matrix calculations are done each time through the loop, the values of the matrices are not important. In other words, the scene renders at the same rate even when the animation is running and spinning about the axes. The only thing that will affect the scene-rendering rate (aside from different hardware or a different scene) is the size of the window. If you make the scene full screen, the render rate drops off. If you make the window smaller, the speed picks up.

Conclusion

OpenGL is not a high-level toolkit, but it provides excellent capabilities that are pretty hard to program yourself. The low level of functionality provides opportunities for both video-hardware manufacturers, to provide dedicated OpenGL hardware, and third-party developers to provide high-level wrappers for 3-D graphics applications. We may, in fact, see an explosion of OpenGL applications including games, virtual reality, analytical graphics, and architectural applications.

References

Crain, Dennis. Windows NT OpenGL: Getting Started. Microsoft Developer Network CD-ROM, Disk #8, July 1994.

OpenGL Architecture Review Board. OpenGL Reference Manual. Reading, MA: Addison-Wesley, 1992.

OpenGL Architecture Review Board. OpenGL Programming Guide. Reading, MA: Addison-Wesley, 1992.

Prosie, Jeff. "Advanced 3-D Graphics for Windows NT 3.5: Introducing the OpenGL Interface, Part 1." Microsoft Systems Journal (October 1994).

Figure 1: An OpenGL program displaying the underside of a checkerboard surface.

Figure 2: The path from 3-D coordinate space to screen pixel.

Listing One

// MS supplied file to turn off compiler warnings
#include "glos.h"

// OpenGL, utility, and aux header files
#include <windows.h>
#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glaux.h>

// local functions
static void localInitialize(int argc, char** argv);
static void localDrawSurface( void );
static void localDrawSphere( void );
static void localAdjustParameters( void );
static void localAdjustRotationalParameters( float * , float * );

// These functions are called by the AUX library
// These are CALLBACK, which just means treat them as cdecl functions
static void CALLBACK localDisplay(void);
static void CALLBACK localReshape(GLsizei w, GLsizei h);
static void CALLBACK localIdle(void);

static void CALLBACK Key_a(void);
static void CALLBACK Key_Z(void);
static void CALLBACK Key_z(void);
static void CALLBACK Key_X(void);
static void CALLBACK Key_x(void);
static void CALLBACK Key_Y(void);
static void CALLBACK Key_y(void);
static void CALLBACK Key_r(void);
static void CALLBACK Key_f(void);
static void CALLBACK Key_up(void);
static void CALLBACK Key_down(void);

#define INITIAL_HEIGHT  (7.0)
#define INITIAL_ACCEL   (0.1)
#define INITIAL_ANGLE   (15.0)
#define SPHERE_RADIUS   (0.5)
#define ANGULAR_CHANGE  (1.0)
#define INITIAL_TRANSLATION (-15.0)

#define MAX_DIMENSION           (5.0)
#define SUBDIVISION     (10.0)

// State variables
int animate = 0;
int freeze = 0;
float   sphere_height = INITIAL_HEIGHT;
float   sphere_drop_speed = 0;
float   sphere_drop_accel = INITIAL_ACCEL;
float   z_axis_rotation = 0;
float   z_axis_rotational_speed = 0;
float   y_axis_rotation = 0;
float   y_axis_rotational_speed = 0;
float   x_axis_rotation = INITIAL_ANGLE;
float   x_axis_rotational_speed = 0;
float   z_axis_translation = INITIAL_TRANSLATION;

// main -- just like any other main you've seen before
void main(int argc, char** argv)
{
    // Initialize our program and OpenGL
    localInitialize(argc, argv);
    // if the window is resized, call this function
    auxReshapeFunc(localReshape);
    // Assign some keys to some functions
    auxKeyFunc(AUX_a, Key_a);
    auxKeyFunc(AUX_z, Key_z);
    auxKeyFunc(AUX_Z, Key_Z);
    auxKeyFunc(AUX_x, Key_x);
    auxKeyFunc(AUX_X, Key_X);
    auxKeyFunc(AUX_y, Key_y);
    auxKeyFunc(AUX_Y, Key_Y);
    auxKeyFunc(AUX_r, Key_r);
    auxKeyFunc(AUX_f, Key_f);
    auxKeyFunc(AUX_UP, Key_up);
    auxKeyFunc(AUX_DOWN, Key_down);
    // what to do with our idle time
    auxIdleFunc(localIdle);
    // which function to call when the window needs to be repainted
    auxMainLoop(localDisplay);
}
// localReshape -- called whenever the window is resized, moved, or
// uncovered. The two arguments are the new windows width & height.
static void CALLBACK localReshape(GLsizei w, GLsizei h)
{
    GLfloat adjust_height, adjust_width;
    // Resize the viewport to the new window's size
    glViewport(0, 0, w, h);
    // scale the width/height by the size of the window so that
    // aspect ratio is retained, i.e., a sphere remains a sphere
    if ( w <= h )
      {
      adjust_height = 1.0;
      adjust_width      = (GLfloat)h/(GLfloat)w;
      }
     else
      {
      adjust_height = (GLfloat)w/(GLfloat)h;
      adjust_width      = 1.0;
      }
    // Set up a projection matrix
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    gluPerspective(     60.0,          // field of view in degrees
             (GLfloat) w/(GLfloat) h,  // aspect ratio
                                1.0,
                                20.0);
}
// Adjust a rotational value, keeping it between 0 and 360 degrees
static void  localAdjustRotationalParameters(float*rot_value,float*rot_rate )
{
    *rot_value += *rot_rate;
    *rot_value =  *rot_value < 0 ?
        (*rot_value + 360) :
        (*rot_value > 360 ? (*rot_value - 360) : *rot_value );
}
static void localAdjustParameters( void )
{
    if ( freeze )
        return;
    localAdjustRotationalParameters(&x_axis_rotation,&x_axis_rotational_speed);
    localAdjustRotationalParameters(&y_axis_rotation,&y_axis_rotational_speed);
    localAdjustRotationalParameters(&z_axis_rotation,&z_axis_rotational_speed);
    if ( animate )
      {
      sphere_drop_speed += sphere_drop_accel;// effect of gravity
      sphere_drop_speed *= 0.95;         // effect of resistance
      sphere_height -= sphere_drop_speed;
      // Detect when we've hit the floor
      if ( sphere_height < 0 )
        {
        sphere_height = -sphere_height*.95;
         sphere_drop_speed *= -0.95;
         sphere_drop_accel *= 0.95; // battle roundoff errors
        }
      // Detect when we've stopped bouncing
      if ( sphere_height <= 0.001 && abs(sphere_drop_speed) <= 0.001 )
        {
         sphere_height = INITIAL_HEIGHT; // Start it over again
         sphere_drop_speed = 0.0;
         sphere_drop_accel = INITIAL_ACCEL;
        }
      }
}
// localDrawSphere -- Draw a sphere above (+Y) the surface
static void localDrawSphere( void )
{
    if ( sphere_height <= 0 )
        return;
    // Now create a sphere along the +Y axis
    glColor3f (.9, .9, 0.9);
    glTranslatef (0.0, SPHERE_RADIUS+sphere_height, 0.0);
    auxSolidSphere(SPHERE_RADIUS); 
}
// localDrawSurface -- Draw a checkerboard surface, explicitly creating each 
// square make one side (the "front") two color, and the other side (the
// "back") alternating squares and blanks. The surface is centered about 
// 0,0,0 and is perpendiculr to the Y axis
static void localDrawSurface( void )
{
    int x,y;
    GLfloat vertices[4][3];
    GLfloat  red_color[3] = {0.8, 0.0, 0.0};
    GLfloat blue_color[3] = {0.0, 0.0, 0.8};
    GLfloat *color1, *color2, *color3, *color4;

    GLfloat mesh_delta = 2.0*MAX_DIMENSION/SUBDIVISION;

    for ( x=1 ; x <= SUBDIVISION ; x++ )
        {
        for ( y=1 ; y <= SUBDIVISION ; y++ )
            {
            // Orient them counter clockwise
            // vertice 1
            vertices[0][0] = -MAX_DIMENSION+mesh_delta*(x-1); // x
            vertices[0][1] = 0.0;                 // y
            vertices[0][2] = -MAX_DIMENSION+mesh_delta*(y-1); // z
            // vertice 2
            vertices[3][0] = -MAX_DIMENSION+mesh_delta*(x-0); // x
            vertices[3][1] = 0.0;                 // y
            vertices[3][2] = -MAX_DIMENSION+mesh_delta*(y-1); // z
            // vertice 3
            vertices[2][0] = -MAX_DIMENSION+mesh_delta*(x-0); // x
            vertices[2][1] = 0.0;                 // y
            vertices[2][2] = -MAX_DIMENSION+mesh_delta*(y-0); // z
            // vertice 4
            vertices[1][0] = -MAX_DIMENSION+mesh_delta*(x-1); // x
            vertices[1][1] = 0.0;                 // y
            vertices[1][2] = -MAX_DIMENSION+mesh_delta*(y-0); // z

            // Color squares such that four squares form a pattern
            if ( x%2 == 1 && y%2 == 1 ) // quadrant ul
              {
              color1 = blue_color;  // ul
              color2 = blue_color;  // ll
              color3 = red_color;   // lr
              color4 = blue_color;  //  ur
              }
            else if ( x%2 == 1 && y%2 == 0 )    // quadrant ll
              {
              color1 = blue_color;
              color2 = blue_color;
              color3 = blue_color;
              color4 = red_color; 
              }
            else if ( x%2 == 0 && y%2 == 1 ) // quadrant ur
              {
              color1 = blue_color;
              color2 = red_color;
              color3 = blue_color;
              color4 = blue_color;
              }
            else    // quadrant lr
              {
              color1 = red_color;
              color2 = blue_color;
              color3 = blue_color;
              color4 = blue_color;
              }
            glBegin(GL_QUADS);
                glColor3fv ( color1 );
                glVertex3fv(vertices[0]);
                glColor3fv ( color2 );
                glVertex3fv(vertices[1]);
                glColor3fv ( color3 );
                glVertex3fv(vertices[2]);
                glColor3fv ( color4 );
                glVertex3fv(vertices[3]);
            glEnd();
            // now, draw alternating back faces in different colors
            if ( (x+y)%2 )
                {
                glBegin(GL_QUADS);
                // note that these "face" a different direction
                glColor3f (0.4, 0.4, 0.6);  // blue-grey
                glVertex3fv(vertices[3]);
                glColor3f (0.0, 1.0, 0.0);  // green
                glVertex3fv(vertices[2]);
                glColor3f (0.4, 0.4, 0.6);  // blue-grey
                glVertex3fv(vertices[1]);
                glColor3f (1.0, 1.0, 0.);   // yellow
                glVertex3fv(vertices[0]);
                glEnd();
               }

            }
        }
}
// localIdle -- Called whenever there is idle time. Use it
// for rendering frames when using double buffering
static void CALLBACK localIdle(void)
{
     // clear the viewport buffers, in this case the color & depth buffers
     // (there are other buffers we could include)
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    // Select the modelview matrix
    glMatrixMode(GL_MODELVIEW);
    // Initialize it
    glLoadIdentity();

    // adjuset all of the animation and rotation parameters
    localAdjustParameters();

    // apply Z axis translation. i.e. move viewpoint along the Z axis
    // (by default we're facing -Z with Y+ as the up vector)
    glTranslatef (0.0, 0.0, z_axis_translation);
    // apply the rotations
    glRotatef(x_axis_rotation, 1.0, 0.0, 0.0);
    glRotatef(y_axis_rotation, 0.0, 1.0, 0.0);
    glRotatef(z_axis_rotation, 0.0, 0.0, 1.0);

    // draw the objects
    localDrawSurface();
    localDrawSphere();

    // New Win32 function to swap buffers in a double-buffered window
   auxSwapBuffers();
}
// localDisplay -- Called whenever we need to redisplay the scene
static void CALLBACK localDisplay(void)
{
    ;  // do nothing for now, the Idle function takes care of it
}
// localInitialize -- Initializes program and sets up the initial OpenGL state
static void localInitialize(int argc, char** argv)
{
    // double buffering, RGBA mode, 16 bit depth buffer
    auxInitDisplayMode (AUX_DOUBLE | AUX_RGBA | AUX_DEPTH16 );

    // create a default window centered at 0,0, that's 400 pixels wide
    // (note that this is shifted to Windows coordinate system)
    auxInitPosition (0, 0, 400, 400);

    // Open the window, specify the title
    auxInitWindow ("OPENGL DEMO");

    // turn on depth testing
   glEnable( GL_DEPTH_TEST );
    // turn on back face removal
   glEnable( GL_CULL_FACE );
}
// These are the misc key functions
static void CALLBACK Key_z(void)
{
    z_axis_rotational_speed += ANGULAR_CHANGE;
}
static void CALLBACK Key_Z(void)
{
    z_axis_rotational_speed -= ANGULAR_CHANGE;
}
static void CALLBACK Key_y(void)
{
    y_axis_rotational_speed += ANGULAR_CHANGE;
}
static void CALLBACK Key_Y(void)
{
    y_axis_rotational_speed -= ANGULAR_CHANGE;
}
static void CALLBACK Key_x(void)
{
    x_axis_rotational_speed += ANGULAR_CHANGE;
}
static void CALLBACK Key_X(void)
{
    x_axis_rotational_speed -= ANGULAR_CHANGE;
}
static void CALLBACK Key_up(void)
{
    z_axis_translation += 1;
}
static void CALLBACK Key_down(void)
{
    z_axis_translation -= 1;
}
static void CALLBACK Key_f(void)
{
    // toggle all movement
    freeze = !freeze;
}
static void CALLBACK Key_a(void)
{
    // toggle animation
    animate = !animate;
}
static void CALLBACK Key_r(void)
{
    x_axis_rotation = INITIAL_ANGLE;
    z_axis_translation = INITIAL_TRANSLATION;
    z_axis_rotation = y_axis_rotation = 0;
    z_axis_rotational_speed = y_axis_rotational_speed = 
                                                 x_axis_rotational_speed = 0;
}
DDJ

Copyright © 1995, Dr. Dobb's Journal


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.