Chapter 3
Introducing 3-D
Chapter 2 introduced drawing a two-dimensional figure and projecting it onto the screen. 
However, OpenGL is rather underutilized in 2-D. OpenGL shines in three-dimensional 
operations. This chapter explores the third (z) dimension and shows a way to draw a cube in 
three-dimensional space. The cube has six square faces with different colors for each. This 
chapter shows how to rotate, move, and stretch the cube, as well as how to cut it open to 
show what is inside.
The Rect program from Chapter 2 makes a good starting point for the next program. Create a 
new directory as before and name it Chapter.3. Under that directory create a directory called 
Cube. In Delphi load the Rect.Dpr project and do a File|Save Project As in the new 
directory, naming it Cube.Dpr. Do File|Save As for the Rect1.Pas unit. Name it Cube1.Pas in 
the new directory.
Depth
The introduction of a third dimension calls for an understanding of the direction in which 
that dimension runs. Construct a new FormPaint method (using the following declaration, 
GL_DEPTH_BUFFER_BIT = $100;) as shown in Listing 3-1. 

procedure TForm1.FormPaint(Sender: TObject);

begin
  if not openGLReady then
    exit;

  {green background}
  glClearColor(0.0,0.4,0.0,0.0);
  glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT);

  glBegin(GL_POLYGON); {near face}
    glColor3f(0.5,0.0,0.0);


PAGE 33
     glVertex3f(-0.4,-0.4,-0.4);
    glVertex3f(+0.4,-0.4,-0.4);
    glVertex3f(+0.4,+0.4,-0.4);
    glVertex3f(-0.4,+0.4,-0.4);
  glEnd;

  glBegin(GL_POLYGON); {back face}
    glColor3f(0.0,0.0,0.5); 
    glVertex3f(-0.2,-0.2,+0.4);
    glVertex3f(+0.6,-0.2,+0.4);
    glVertex3f(+0.6,+0.6,+0.4);
    glVertex3f(-0.2,+0.6,+0.4);
  glEnd;

  glFlush;

  errorCode := glGetError;
  if errorCode<>GL_NO_ERROR then
    raise Exception.Create('Error in Paint'#13+
      gluErrorString(errorCode));
end; {FormPaint}
Listing 3-1. FormPaint method with depth.
The z value of the original (red) rectangle is –0.4. The second rectangle is 0.8 units farther 
along the positive z direction with a z value of +0.4. The second (blue) rectangle also lies 0.2 
units farther in the positive x direction and 0.2 units farther in the positive y direction, so 
that one rectangle does not completely obscure the other. 
Without a depth test an object drawn later may appear on top of an object drawn earlier, no 
matter how near or far the respective objects actually were. The explanation for the depth 
test appears in a few paragraphs, but for now, add some lines to the bottom of the 
FormCreate method:

  glOrtho(-1.0,1.0,-1.0,1.0,-1.0,1.0);	
  glEnable(GL_DEPTH_TEST);	
  glMatrixMode(GL_MODELVIEW);	
  glLoadIdentity;	
end; {FormCreate}	
Save, compile, and run to see the result. As Figure 3-1 shows, the red rectangle is in front 
and the blue rectangle is in back. The conclusion is that with an orthographic projection, the 
farther an object lies in the positive z direction (or at least less negative), the farther the object 
is from the viewer. 

PAGE 34
 Cube
Now you are ready for another illustration. For the sake of this illustration, comment out the 
glEnable(GL_DEPTH_TEST) statement you just put in.
Square Viewport
The new project name certainly suggests the next shape under discussion. A cube provides a 
fairly simple figure for introducing 3-D. In geometry a cube has identical height, width, and 
depth. Unfortunately the projection of such a figure onto the computer screen may not look 
like a cube unless the viewport is square. Since the current viewport matches the form’s 
client height and client width, those properties should be set equal to each other. Select the 
form and go to the Object Inspector. Set ClientHeight to 340, and set ClientWidth to 340.
Distinguish Faces
A cube has six square faces. In FormPaint draw six colored squares, using different colors for 
each face for easy distinction. If all the faces of the cube were the same color, they would 
blend into each other and obscure the shape. The application of lighting effects solves such 
problems, but that subject comes later. Modify the FormPaint method as in Listing 3-2.

procedure TForm1.FormPaint(Sender: TObject);
begin
  if not openGLReady then


PAGE 35
     exit;
    
  {green background}
  glClearColor(0.0,0.4,0.0,0.0);
  glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT);

  glBegin(GL_POLYGON); {near face}
    glColor3f(0.5,0.0,0.0);
    glVertex3f(-0.4,-0.4,-0.4);
    glVertex3f(+0.4,-0.4,-0.4);
    glVertex3f(+0.4,+0.4,-0.4);
    glVertex3f(-0.4,+0.4,-0.4);
  glEnd;

  glBegin(GL_POLYGON); {right face}
    glColor3f(0.3,0.3,0.8);
    glVertex3f(+0.4,-0.4,-0.4);
    glVertex3f(+0.4,-0.4,+0.4);
    glVertex3f(+0.4,+0.4,+0.4);
    glVertex3f(+0.4,+0.4,-0.4);
  glEnd;

  glBegin(GL_POLYGON); {left face}
    glColor3f(0.5,0.0,0.5);
    glVertex3f(-0.4,-0.4,+0.4);
    glVertex3f(-0.4,-0.4,-0.4);
    glVertex3f(-0.4,+0.4,-0.4);
    glVertex3f(-0.4,+0.4,+0.4);
  glEnd;

  glBegin(GL_POLYGON); {back face}
    glColor3f(0.0,0.0,0.5);
    glVertex3f(+0.4,-0.4,+0.4);
    glVertex3f(-0.4,-0.4,+0.4);
    glVertex3f(-0.4,+0.4,+0.4);
    glVertex3f(+0.4,+0.4,+0.4);
  glEnd;

  glBegin(GL_POLYGON); {bottom face}
    glColor3f(0.5,0.5,0.0);
    glVertex3f(-0.4,-0.4,-0.4);
    glVertex3f(-0.4,-0.4,+0.4);
    glVertex3f(+0.4,-0.4,+0.4);
    glVertex3f(+0.4,-0.4,-0.4);
  glEnd;

  glBegin(GL_POLYGON); {top face}
    glColor3f(0.3,0.0,0.1);
    glVertex3f(-0.4,+0.4,-0.4);


PAGE 36
     glVertex3f(+0.4,+0.4,-0.4);
    glVertex3f(+0.4,+0.4,+0.4);
    glVertex3f(-0.4,+0.4,+0.4);
  glEnd;

  glFlush;

  errorCode := glGetError;
  if errorCode<>GL_NO_ERROR then
    raise Exception.Create('Error in Paint'#13+gluErrorString(errorCode));
end; {FormPaint}
Listing 3-2. Six colored faces.
Depth Test
Save, compile, and run, and admire the beautiful cube! Well, at least tolerate the colored 
rectangle. Why is there no evidence of a third dimension? Actually the straight-on view 
guarantees that only the front face shows. Or does it? Notice the color. The front face is red. 
The back face is blue. The blue back face shows on the screen! Why? OpenGL drew all six 
faces. The four edge-on faces have no effect, but the back face was drawn after the front face, 
so it is the one that shows. Fortunately there is a way to tell OpenGL to test depth and not 
draw pixels that should be hidden. This ability, and others, becomes effective by means of 
the glEnable command:

procedure glEnable(cap:GLenum); stdcall;	
Here is its opposite:

procedure glDisable(cap:GLenum); stdcall;	
Turn on each capability by passing the appropriate constant. Here is the declaration for the 
depth-testing constant:

  GL_DEPTH_TEST = $0B71; 	
Enabling depth testing sets up a depth buffer that contains depth information on each pixel 
within the clipping volume. Of the pixels that map to the same spot on the screen, OpenGL 
draws only the one nearest the viewer. This process is OpenGL’s method of hidden surface 
removal. The bottom of the FormCreate method with depth testing added follows:

  openGLReady := true;	
  oldw := ClientWidth;	
  oldh := ClientHeight;	
  glOrtho(-1.0,1.0,-1.0,1.0,-1.0,1.0);	
  glEnable(GL_DEPTH_TEST);	
end; {FormCreate}	

PAGE 37
 Matrices Revisited
Save, compile, and run. Observe that no change is evident. This means that more must be 
done to make the depth test work. Therefore, it is time to give more consideration to 
matrices. OpenGL is full of them. Matrices describe the very scene itself, and there are 
matrices for texture, for projection, etc. There are commands to select among those matrices. 
There are even matrix stacks. Some commands, such as glLoadIdentity, directly manipulate 
the active matrix, and other commands use their parameters to construct a matrix, then 
multiply the appropriate matrix by the constructed matrix. 
The ModelView Matrix
Consider a 4x4 matrix that represents the coordinate system of the scene. Call it the Model 
matrix. Apply this matrix to every vertex in the scene as part of the process of illuminating 
the appropriate dots on the screen. Now consider a 4x4 matrix that represents the 
coordinates of the viewer. Call it the View matrix. Finally consider Einstein’s Theory of 
Relativity. What?! Well, anyway, think of two automobiles side by side. The observer is in 
the first car and sees the second car sliding backwards. Or is the first car with the observer 
actually moving forward? Or does it matter? In a universe that contains only those two cars, 
either description is equally valid. Similarly, the Model matrix and the View matrix are 
equally useful. In fact, they are the same matrix. Its true name is the ModelView matrix.
Here is the OpenGL command that identifies which matrix other commands will affect:

procedure glMatrixMode(mode:GLenum); stdcall;	
These are the constants to pass for the mode parameter:

  GL_MODELVIEW  = $1700;	
  GL_PROJECTION = $1701;	
  GL_TEXTURE    = $1702;	
Here is the declaration of the command that sets the current matrix to be the identity matrix, 
which is a matrix that, when multiplied with another matrix, leaves a product the same as 
the other matrix.

procedure glLoadIdentity; stdcall;
For the three-dimensional rendering program to work, make the bottom of FormCreate look 
like this:

  glEnable(GL_DEPTH_TEST);	
  glMatrixMode(GL_MODELVIEW);	


PAGE 38
   glLoadIdentity;              	
end; {FormCreate}	
So save, compile, and run. Ah! The correct face shows. But it still does not look like a cube.
Rotation
Three Faces
Looking at a cube straight on just does not reveal its third dimension. The cube must have an 
orientation that shows more than one face. Indeed, a cube showing two faces is not much 
better, because it still just looks like two rectangles side by side. The obvious answer is to 
draw the cube skewed at some angle. Not only that, but the rotation must not be about a 
single coordinate axis or about any line parallel to a single axis, otherwise no more than two 
faces can show.
glRotate
Drawing the cube rotated calls for some ugly math to apply to each vertex. Actually the 
math involved looks fairly simple when expressed as matrices, but it is ugly because the 
programmer has to carry out the details. There is a better way! Draw the cube simply, as 
before, then let OpenGL rotate it. Here is the needed command in two versions:

procedure glRotated(angle,x,y,z:GLdouble); stdcall;	
procedure glRotatef(angle,x,y,z:GLfloat); stdcall;	
Purpose
This command defines an axis of rotation and an amount of rotation. It then causes the scene 
to rotate about the axis. Rather, it causes the coordinate system of the scene, and therefore 
the viewing angle of the observer, to rotate about the axis. Take your choice; it is relative! 
What really happens is that OpenGL creates a rotation matrix from the parameters and 
multiplies it with the active matrix, in this case, the ModelView matrix.
Parameters
This simple program certainly does not need double precision to accomplish its purpose, so 
the float version is the proper choice for the command.
Either version uses four parameters. The angle parameter defines the amount of rotation. 
Interestingly it takes its value in degrees rather than radians, which is fortunate for the 
trigonometry-challenged. The x, y, and z 

PAGE 39
 parameters define a point in three-dimensional space with respect to the coordinates of the 
clipping (projection) volume.
The given point helps define the vector that serves as the axis of rotation. Two points can 
define a line; the other point is (0.0,0.0,0.0), the origin for the projection coordinate system. 
Think of a vector as a line with direction, from the origin toward the other point, and then 
continuing on forever.
Direction
To understand the direction of rotation, imagine an observer located at the origin looking 
toward the point defined by the x, y, and z parameters. The object turns counterclockwise 
with respect to that vantage point.
Action
In the Menu Designer, add a fourth main menu item. Give it a caption of &Angle and exit 
from the Menu Designer. Click the menu item to produce the event handler and fill it in as 
follows:

procedure TForm1.Angle1Click(Sender: TObject);	
var	
  astring: string[7];	
begin	
  astring := InputBox('Angle of Rotation','float:','0.0');	
  angle := StrToFloat(astring);	
  glMatrixMode(GL_MODELVIEW);	
  glLoadIdentity;	
	
  glRotatef(angle,xvalue,yvalue,zvalue);	
end;	
Be sure to declare angle in the private section of the form type declaration:

angle,
xvalue,
yvalue,
zvalue: GLfloat;
The other three menu choices, left over from the Rect program, set the coordinates for the 
point (other than the origin) that defines the axis of rotation. The new menu choice defines 
the angle of rotation and performs the rotation. Notice that glMatrixMode makes the 
ModelView matrix active every time, since that is the matrix on which to perform the action. 
This action is one of several transformations. OpenGL enables the programmer to perform 
three basic ModelView transformations or combinations thereof on the ModelView matrix: 
rotation, translation (movement in a straight line through space), and scale (changing the size 
of an object in each of the 

PAGE 40
 three dimensions independently). The call to glLoadIdentity is present to ensure that the 
rotation transformation acts from a known and understood condition. Some situations may 
naturally call for allowing the transformations to accumulate, but not this simple interactive 
angle entry.
Save, compile, and run. Enter coordinates to define the axis (vector) of rotation. Enter 
various angles and see the results. Notice that any multiple of 360.0 returns the cube to its 
original orientation and that negative angles rotate the cube clockwise. All the different 
colors for the faces help identify which faces come into view.
Two-Faced Polygons
Open the Cube
There is no way to avoid it: Polygons are two-faced. That is not a character assassination but 
a geometric statement. When a polygon facing the viewer rotates 180 degrees, it does not 
disappear; it has a back face that the viewer can see. To illustrate, expose the inside of the 
cube by commenting out the right side as in Listing 3-3.

procedure TForm1.FormPaint(Sender: TObject);
begin
     if not openGLReady then
    exit;
    
  {green background}
  glClearColor(0.0,0.4,0.0,0.0);
  glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT);

  glBegin(GL_POLYGON); {near face}
    glColor3f(0.5,0.0,0.0);
    glVertex3f(-0.4,-0.4,-0.4);
    glVertex3f(+0.4,-0.4,-0.4);
    glVertex3f(+0.4,+0.4,-0.4);
    glVertex3f(-0.4,+0.4,-0.4);
  glEnd;
(*
  glBegin(GL_POLYGON); {right face}
    glColor3f(0.3,0.3,0.8);
    glVertex3f(+0.4,-0.4,-0.4);
    glVertex3f(+0.4,-0.4,+0.4);
    glVertex3f(+0.4,+0.4,+0.4);
    glVertex3f(+0.4,+0.4,-0.4);
  glEnd;
*)
  glBegin(GL_POLYGON); {left face}


PAGE 41
     glColor3f(0.5,0.0,0.5);
    glVertex3f(-0.4,-0.4,+0.4);
    glVertex3f(-0.4,-0.4,-0.4);
    glVertex3f(-0.4,+0.4,-0.4);
    glVertex3f(-0.4,+0.4,+0.4);
  glEnd;

  glBegin(GL_POLYGON); {back face}
    glColor3f(0.0,0.0,0.5);
    glVertex3f(+0.4,-0.4,+0.4);
    glVertex3f(-0.4,-0.4,+0.4);
    glVertex3f(-0.4,+0.4,+0.4);
    glVertex3f(+0.4,+0.4,+0.4);
  glEnd;

  glBegin(GL_POLYGON); {bottom face}
    glColor3f(0.5,0.5,0.0);
    glVertex3f(-0.4,-0.4,-0.4);
    glVertex3f(-0.4,-0.4,+0.4);
    glVertex3f(+0.4,-0.4,+0.4);
    glVertex3f(+0.4,-0.4,-0.4);
  glEnd;

  glBegin(GL_POLYGON); {top face}
    glColor3f(0.3,0.0,0.1);
    glVertex3f(-0.4,+0.4,-0.4);
    glVertex3f(+0.4,+0.4,-0.4);
    glVertex3f(+0.4,+0.4,+0.4);
    glVertex3f(-0.4,+0.4,+0.4);
  glEnd;

  glFlush;

  errorCode := glGetError;
  if errorCode<>GL_NO_ERROR then
    raise Exception.Create('Error in Paint'#13+gluErrorString(errorCode));
end; {FormPaint}
Listing 3-3. Right side missing.
Save, compile, and run. With the menus, make xvalue and yvalue each equal to 1.0, but leave 
zvalue alone. For the angle, enter 30.0 (degrees). Note the inside face for the back square and 
the top square. Experiment with various combinations of positive and negative values for all 
four parameters until all interior faces have presented themselves.

PAGE 42
 Culling
There it is! Those polygons have a back side! What if the cube was not open? The shocking 
truth is that even if there is no possible way to see the back side of the polygons, OpenGL 
draws them! Fortunately, depth testing (hidden surface removal) keeps them from bleeding 
through. Better yet, an OpenGL command can tell it not to even draw them.

procedure glCullFace(mode:GLenum); stdcall;	
The possible values for mode (with obvious meanings) are:

  GL_FRONT         = $0404;	
  GL_BACK          = $0405;	
  GL_FRONT_AND_BACK= $0408; 	
To cull faces means to remove them from the list of things to draw. Like many other OpenGL 
options, the programmer must first enable this option before using it. The value to give 
glEnable is:

  GL_CULL_FACE= $0B44;	
Add some code so the bottom of FormCreate looks like this:

  glEnable(GL_DEPTH_TEST);	
  glMatrixMode(GL_MODELVIEW);	
  glLoadIdentity;	
                 	
  glEnable(GL_CULL_FACE);	
  glCullFace(GL_BACK);	
end; {FormCreate}	
Save, compile, and run yet again. As before, experiment with various combinations of 
positive and negative values for all four parameters and see what the inside of the cube 
looks like when the inner faces simply are not drawn.
Vertex Direction
How does OpenGL know which face of a polygon the programmer intended to be front and 
which to be back? The answer lies in the order of the vertices. Consider the front square of 
this chapter’s cube:

  glBegin(GL_POLYGON); {near face}	
    glColor3f(0.5,0.0,0.0);	
    glVertex3f(-0.4,-0.4,-0.4);	
    glVertex3f(+0.4,-0.4,-0.4);	
    glVertex3f(+0.4,+0.4,-0.4);	


PAGE 43
     glVertex3f(-0.4,+0.4,-0.4);	
  glEnd; 	
Mentally number the vertices from one to four beginning in the lower left corner. Imagine 
movement from one to two to three to four and back to one. The direction, or winding, is 
counterclockwise, which is the default direction for OpenGL to identify the front face of a 
polygon. Those same vertices, if viewed from the other side, would present a clockwise 
winding. Figure 3-2 shows the counterclockwise winding of the front view.
Consider the back square of the cube. Its vertex order was designed to present a clockwise 
winding from the viewer’s position, making the face inside the cube the back face and the 
face outside the cube the front face. That is the usual arrangement. If the cube were rotated 
180 degrees around the y-axis, the front face of that square would be toward the viewer. 
Here is the original code for the back square:

  glBegin(GL_POLYGON); {back face}	
    glColor3f(0.0,0.0,0.5);	
    glVertex3f(+0.4,-0.4,+0.4);	
    glVertex3f(-0.4,-0.4,+0.4);	
    glVertex3f(-0.4,+0.4,+0.4);	
    glVertex3f(+0.4,+0.4,+0.4);	
  glEnd;	
Rearrange the vertices to reverse the winding: 

  glBegin(GL_POLYGON); {back face}	
    glColor3f(0.0,0.0,0.5); 	
    glVertex3f(+0.4,+0.4,+0.4);	
    glVertex3f(-0.4,+0.4,+0.4);	
    glVertex3f(-0.4,-0.4,+0.4);	
    glVertex3f(+0.4,-0.4,+0.4);	
  glEnd; 	
Save, compile, and run. Give the xvalue 0.3, the yvalue 1.0, and the angle 50.0. While the 
other interior faces have been culled, the interior face of the back square has been drawn. 
With a large enough angle, the back square is toward the viewer, but since its exterior face is 
a “back” face, it does not appear. Indeed, only the bottom of the cube shows at all. 

PAGE 44
 Translation
Restore the original winding of the back square and uncomment the right square. While 
rotation may be the most interesting transformation, it is certainly not the only one of 
importance. Translation may be the most common transformation.
Menu
Use the Menu Designer to add &Translate as another main menu item. Click on it and fill in 
the event handler thusly:

procedure TForm1.Translate1Click(Sender: TObject);	
begin	
  glMatrixMode(GL_MODELVIEW);	
  glTranslatef(xvalue,yvalue,zvalue);	
  Invalidate;	
end;	
The glTranslate command takes three parameters that specify the relative amount of 
movement in each direction. The translation command has two forms:

procedure glTranslated(x,y,z:GLdouble); stdcall;	
procedure glTranslatef x,y,z:GLfloat); stdcall;	
Accumulation
Notice the absence of glLoadIdentity. Transformations will accumulate and demonstrate the 
effects of combining different kinds. Comment out the glLoadIdentity in the Angle1Click 
method as well. Notice also the presence of the Invalidate Windows API call. This causes 
Windows to send a WM_PAINT message, which triggers the OnPaint event. This is certainly 
superior to the (temporary) reliance on covering and uncovering the window by another 
window.
More Clipping
As usual, save, compile, run, and play with the various menu choices and values. The 
transformations accumulate, and may allow the cube to disappear altogether. Certainly the 
translation transformation provides more opportunity to experiment with clipping. Enough 
translation in the positive z direction provides some interesting effects. The far side of the 
cube disappears as if cut off by an invisible wall.

PAGE 45
 Did you see that flicker each time you selected Translate? Ugh! Do not despair; the chapter 
on animation shows how to redraw images without flicker.
Even more interesting is the effect of translation in the negative z direction. With just the 
right z values, a face or a corner nearest the viewer disappears, allowing a peek inside the 
cube without having to neglect drawing a square. What does the viewer see inside? He sees 
a mysterious green nothing because of the culling, as in Figure 3-3, where clipping in both 
the negative y direction and the negative z direction is evident.
Scale
Menu
With the Menu Designer, add still another main menu item, &Scale. Produce the following 
event handler in the same manner as before:

procedure TForm1.Scale1Click(Sender: TObject);	
begin	
  glMatrixMode(GL_MODELVIEW);	
  glScalef(xvalue,yvalue,zvalue);	
  Invalidate;	
end;	
The only new command has the following declarations:

procedure glScaled(x,y,z:GLdouble); stdcall;	
procedure glScalef(x,y,z:GLfloat); stdcall;	
Description
The scale command multiplies the size of the objects in the scene. The value given for the x 
parameter multiplies the x dimension, the y value passed to the command multiplies the y 
dimension, and the z parameter’s value multiplies the z dimension. A call to 
glScale(2.0,2.0,2.0) makes the cube twice as wide, twice as tall, and twice as deep as the 
original 

PAGE 46
 specifications. So why are there three parameters? Each dimension’s multiplier has a 
separate specification so each dimension can be scaled differently. A call to 
glScale(0.5,3.0,0.8) makes the cube (or whatever drawing follows) only half as wide, three 
times as tall, and 80 percent of the original depth. The programmer can draw something and 
then distort it freely. Of course, a cube so mutilated is no longer a cube. Save, compile, run, 
and experiment. Figure 3-4 shows a distorted (former) cube.

Order of Transformations
A New Interface
The admittedly clumsy interface used thus far has exhausted its usefulness. It was kept 
simple in order to minimize distraction from the OpenGL coding, but further development 
calls for a better user interface. The new interface is nothing elegant but is a great 
improvement.
Create a new directory under Chapter.3 named Multi. Do a File|Save Project As of 
Cube.Dpr into the new directory, calling it Mult.Dpr. Save Cube1.Pas as Multi.Pas. Empty all 
the menu item event handlers by deleting the lines of code within and save again so Delphi 
can remove the newly empty event handlers. In the Menu Designer, delete the existing menu 
items and put in &Parameters, &Transformation, &Identity, and &Cull Disable. Create and 
fill in their event handlers as in Listing 3-4.

procedure TForm1.Parameters1Click(Sender: TObject);
begin
  FormTransform.ShowModal;
end;

procedure TForm1.Transformation1Click(Sender: TObject);
var
  list: TStringList;
  lno: integer;


PAGE 47
 begin
  list := TStringList.Create;
  list.sorted := true;
  with list,transData do
  begin
    if translate then
      Add(OrT+'T');
    if scale then
      Add(OrS+'S');
    if rotate then
      Add(OrR+'R');
    if count=0 then
      exit;

    glMatrixMode(GL_MODELVIEW);
    for lno := 0 to count-1 do
    begin
      case strings[lno][2] of
        'R': begin
          glRotatef(ra,rx,ry,rz);
        end;
        'S': begin
          glScalef(sx,sy,sz);
        end;
        'T': begin
          glTranslatef(tx,ty,tz);
        end;
      end; {case}
    end; {for}
  end; {with}
  List.Free;
  Invalidate;
end;

procedure TForm1.Identity1Click(Sender: TObject);
begin
  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity;
  Invalidate;
end;

procedure TForm1.Cull1Click(Sender: TObject);
begin
  glDisable(GL_CULL_FACE);
  Invalidate;
end;
Listing 3-4. Menu item event handlers.

PAGE 48
 Create a new form and name it FormTransform, then save the unit as Trans1. To the form 
add three check boxes, four labels, ten edit controls, three radio groups, and two bit buttons 
to look like Figure 3-5. 
An efficient way to describe the component names and properties is to show the form as text 
as in Listing 3-5.

object FormTransform: TFormTransform
  Left = 200
  Top = 100
  Width = 360
  Height = 185
  Caption = 'Transformation Parameters'
  Font.Charset = DEFAULT_CHARSET
  Font.Color = clWindowText
  Font.Height = -11
  Font.Name = 'MS Sans Serif'
  Font.Style = []
  PixelsPerInch = 96
  TextHeight = 13
  object Label1: TLabel
    Left = 76
    Top = 4
    Width = 7
    Height = 13
    Caption = 'X'
  end
  object Label2: TLabel
    Left = 120
    Top = 4
    Width = 7
    Height = 13
    Caption = 'Y'
  end
    object Label3: TLabel
    Left = 164
    Top = 4
    Width = 7
    Height = 13
    Caption = 'Z'
  end

PAGE 49
   object Label4: TLabel
    Left = 208
    Top = 4
    Width = 27
    Height = 13
    Caption = 'Angle'
  end
    object CheckBoxTranslate: TCheckBox
    Left = 4
    Top = 20
    Width = 64
    Height = 24
    Caption = 'Translate'
    TabOrder = 0
    OnClick = CheckBoxTranslateClick
  end
    object CheckBoxScale: TCheckBox
    Left = 4
    Top = 56
    Width = 64
    Height = 24
    Caption = 'Scale'
    TabOrder = 5
    OnClick = CheckBoxScaleClick
  end
    object CheckBoxRotate: TCheckBox
    Left = 4
    Top = 92
    Width = 64
    Height = 24
    Caption = 'Rotate'
    TabOrder = 10
    OnClick = CheckBoxRotateClick
  end
    object EditTX: TEdit
    Left = 72
    Top = 20
    Width = 40
    Height = 21
    Enabled = False
    TabOrder = 1
  end
    object EditTY: TEdit
    Left = 116
    Top = 20
    Width = 40
    Height = 21
    Enabled = False
    TabOrder = 2


PAGE 50
   end
    object EditTZ: TEdit
    Left = 160
    Top = 20
    Width = 40
    Height = 21
    Enabled = False
    TabOrder = 3
  end
    object EditRA: TEdit
    Left = 204
    Top = 92
    Width = 40
    Height = 21
    Enabled = False
    TabOrder = 14
  end
    object EditSX: TEdit
    Left = 72
    Top = 56
    Width = 40
    Height = 21
    Enabled = False
    TabOrder = 6
  end
    object EditSY: TEdit
    Left = 116
    Top = 56
    Width = 40
    Height = 21
    Enabled = False
    TabOrder = 7
  end
    object EditSZ: TEdit
    Left = 160
    Top = 56
    Width = 40
    Height = 21
    Enabled = False
    TabOrder = 8
  end
    object EditRX: TEdit
    Left = 72
    Top = 92
    Width = 40
    Height = 21
    Enabled = False
    TabOrder = 11
  end

PAGE 51
   object EditRY: TEdit
    Left = 116
    Top = 92
    Width = 40
    Height = 21
    Enabled = False
    TabOrder = 12
  end
    object EditRZ: TEdit
    Left = 160
    Top = 92
    Width = 40
    Height = 21
    Enabled = False
    TabOrder = 13
  end
    object BitBtnOk: TBitBtn
    Left = 60
    Top = 124
    Width = 90
    Height = 25
    Enabled = False
    TabOrder = 16
    OnClick = BitBtnOkClick
    Kind = bkOK
  end
    object BitBtnCancel: TBitBtn
    Left = 220
    Top = 124
    Width = 80
    Height = 25
    TabOrder = 17
    Kind = bkCancel
  end
    object RadioGroupT: TRadioGroup
    Left = 252
    Top = 14
    Width = 97
    Height = 32
    Columns = 3
    Enabled = False
    ItemIndex = 0
    Items.Strings = (
      '1'
      '2'
      '3')
    TabOrder = 4
    OnClick = RadioGroupClick
  end

PAGE 52
   object RadioGroupS: TRadioGroup
    Left = 252
    Top = 50
    Width = 97
    Height = 32
    Columns = 3
    Enabled = False
    ItemIndex = 1
    Items.Strings = (
      '1'
      '2'
      '3')
    TabOrder = 9
    OnClick = RadioGroupClick
  end
    object RadioGroupR: TRadioGroup
    Left = 252
    Top = 86
    Width = 97
    Height = 32
    Columns = 3
    Enabled = False
    ItemIndex = 2
    Items.Strings = (
      '1'
      '2'
      '3')
    TabOrder = 15
    OnClick = RadioGroupClick
  end
end
Listing 3-5. FormTransform as text.
The text version of the form identified the names of the event handlers, and the code for unit 
Trans1 in Listing 3-6 reveals the contents of the event handlers:

unit Trans1;

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, Buttons, OpenGL, ExtCtrls;

type
  TFormTransform = class(TForm)
    Label1: TLabel;
    Label2: TLabel;


PAGE 53
     Label3: TLabel;
    Label4: TLabel;
    CheckBoxTranslate: TCheckBox;
    CheckBoxScale: TCheckBox;
    CheckBoxRotate: TCheckBox;
    EditTX: TEdit;
    EditTY: TEdit;
    EditTZ: TEdit;
    EditRA: TEdit;
    EditSX: TEdit;
    EditSY: TEdit;
    EditSZ: TEdit;
    EditRX: TEdit;
    EditRY: TEdit;
    EditRZ: TEdit;
    BitBtnOk: TBitBtn;
    BitBtnCancel: TBitBtn;
    RadioGroupT: TRadioGroup;
    RadioGroupS: TRadioGroup;
    RadioGroupR: TRadioGroup;
    procedure BitBtnOkClick(Sender: TObject);
    procedure CheckBoxTranslateClick(Sender: TObject);
    procedure CheckBoxScaleClick(Sender: TObject);
    procedure CheckBoxRotateClick(Sender: TObject);
    procedure RadioGroupClick(Sender: TObject);
  private
    { Private declarations }
    public
        { Public declarations }
    end;

var
  FormTransform: TFormTransform;
  transData: record
    translate,
    scale,
    rotate: boolean;
    orT,
    orS,
    orR: char;
    tx,ty,tz,
    sx,sy,sz,
    rx,ry,rz,ra: GLfloat;
  end;

implementation

{$R *.DFM}



PAGE 54
 procedure TFormTransform.BitBtnOkClick(Sender: TObject);
begin
  ModalResult := mrNone;

  with transData do
  begin
    translate := checkBoxTranslate.Checked;
    scale := checkBoxScale.Checked;
    rotate := checkBoxRotate.Checked;

    if translate then
    begin
      tx := StrToFloat(EditTX.Text);
      ty := StrToFloat(EditTY.Text);
      tz := StrToFloat(EditTZ.Text);
    end;

    if scale then
    begin
      sx := StrToFloat(EditSX.Text);
      sy := StrToFloat(EditSY.Text);
      sz := StrToFloat(EditSZ.Text);
    end;

    if rotate then
    begin
      rx := StrToFloat(EditRX.Text);
      ry := StrToFloat(EditRY.Text);
      rz := StrToFloat(EditRZ.Text);
      ra := StrToFloat(EditRA.Text);
    end;

    orT := char(48+RadioGroupT.ItemIndex);
    orS := char(48+RadioGroupS.ItemIndex);
    orR := char(48+RadioGroupR.ItemIndex);
  end; {with}

  ModalResult := mrOk;
end;

procedure TFormTransform.CheckBoxTranslateClick(Sender: TObject);
var
  chck: boolean;
 begin
  with Sender as TCheckBox do
    chck := checked;
  EditTX.Enabled := chck;
  EditTY.Enabled := chck;
  EditTZ.Enabled := chck;


PAGE 55
   RadioGroupT.Enabled := chck;
  BitBtnOk.Enabled := chck
  or EditSX.Enabled
  or EditRX.Enabled;
end;

procedure TFormTransform.CheckBoxScaleClick(Sender: TObject);
var
  chck: boolean;
begin
  with Sender as TCheckBox do
    chck := checked;
  EditSX.Enabled := chck;
  EditSY.Enabled := chck;
  EditSZ.Enabled := chck;
  RadioGroupS.Enabled := chck;
  BitBtnOk.Enabled := chck
  or EditTX.Enabled
  or EditRX.Enabled;
end;

procedure TFormTransform.CheckBoxRotateClick(Sender: TObject);
var
  chck: boolean;
begin
  with Sender as TCheckBox do
    chck := checked;
  EditRX.Enabled := chck;
  EditRY.Enabled := chck;
  EditRZ.Enabled := chck;
  EditRA.Enabled := chck;
  RadioGroupR.Enabled := chck;
  BitBtnOk.Enabled := chck
  or EditSX.Enabled
  or EditTX.Enabled;
end;

procedure TFormTransform.RadioGroupClick(Sender:TObject);
type
  TRange=0..2;
  SRange=Set of TRange;
var
  ch: char;
  Xii,
  Uii,
  Tii,
  Sii,
  Rii: integer;
  Used: SRange;
  ar: array['R'..'T'] of TRadioGroup;


PAGE 56
   notif: TNotifyEvent;
begin
  {build set of used values to find what’s left}
  Tii := RadioGroupT.ItemIndex;
  Sii := RadioGroupS.ItemIndex;
  Rii := RadioGroupR.ItemIndex;
  Used := [Tii]+[Sii]+[Rii];
  Xii := TRadioGroup(Sender).ItemIndex;

  {build array of rg for convenient searching}
  ar['T'] := RadioGroupT;
  ar['S'] := RadioGroupS;
  ar['R'] := RadioGroupR;

  {find rg not the sender but with same ItemIndex}
  for ch := 'R' to 'T' do
  with ar[ch] do
  if (ar[ch]<>Sender) and (ItemIndex=Xii) then
  begin
    for Uii := 0 to 2 do
    if not (Uii in Used) then
    begin
      notif := OnClick;
      OnClick := nil; {prevent second triggering}

      {set to unused ItemIndex and leave}
      ar[ch].ItemIndex := Uii;
      OnClick := notif;
      exit;
    end; {for if}
  end; {for with if}

end;

end.
Listing 3-6. The Trans1 unit.
The scope of this book is to teach OpenGL with Delphi, with the assumption that the reader 
is at least somewhat experienced in programming with Delphi. Therefore, the ordinary 
Delphi code is presented with little comment.


PAGE 57
 Simultaneous Commands
This program still draws the same old cube, but provides for specifying two or three 
transformations (with independent parameters) to apply to the cube at the same time. The 
check boxes allow the user to specify which transformations to apply, and the radio groups 
allow the user to specify the order in which to apply them. The edit controls allow the user 
to specify the floating-point values for the various parameters.
The event handler for the &Transformation menu item performs the transformations 
specified on the secondary form. TForm1.Transformation1Click creates a sorted string list 
that identifies the order in which to perform the transformation. The handler sets the matrix 
mode to GL_MODELVIEW and performs the transformations in the appropriate order, 
using the arguments stored in the transData record. The call to Invalidate ensures that 
FormPaint fires after the transformation calls.
Save, compile, run, and experiment, not only with the values to give to each transformation, 
but also with the order of the transformation. Admittedly, the difference is subtle for just one 
“object” in the scene, but many combinations will show a difference according to the 
sequence of the transformations. Order does matter; try it and see.
Identifier Reference
Identifier			Definition
glCullFace
An OpenGL command that specifies culling of front or back faces or both.
glDisable
An OpenGL command that disallows certain features.
glEnable
An OpenGL command that allows certain features.
glLoadIdentity
An OpenGL command that sets the specified matrix equal to the identity matrix.
glMatrixMode
An OpenGL command that makes the specified matrix the currently active matrix.
glRotate
An OpenGL command that performs the rotation transformation.
glScale
An OpenGL command that performs the scale transformation.

PAGE 58
 
Identifier			Definition
glTranslate
An OpenGL command that performs the translation transformation.
GL_BACK
An OpenGL constant for glCullFace.
GL_CULL_FACE
An OpenGL constant for glEnable.
GL_DEPTH_BUFFER_
BIT
An OpenGL constant for glClear.
GL_DEPTH_TEST
An OpenGL constant for glEnable.
GL_FRONT
An OpenGL constant for glCullFace.
GL_FRONT_AND_
BACK
An OpenGL constant for glCullFace.
GL_MODELVIEW
An OpenGL constant for glMatrixMode.
GL_PROJECTION
An OpenGL constant for glMatrixMode.
GL_TEXTURE
An OpenGL constant for glMatrixMode.
Invalidate
A Windows function that causes Windows to send WM_PAINT and returns true on success, 
false on failure.
Summary
Chapter 3 introduced three-dimensional rendering with these concepts:
? Left to itself, OpenGL draws all surfaces whether they should show or not.
? Turn on depth testing with glEnable to allow hidden surface removal.
? The ModelView matrix represents the coordinate system of the scene or the coordinates 
of the viewer, whichever is convenient.
? The ModelView transformations are rotation, transformation, and scale.
? Use glRotate to rotate the scene counterclockwise by a certain angle about a given axis.
? Polygons have two faces. Cull back or front faces with glCullFace. Pass it GL_FRONT, 
GL_BACK, or GL_FRONT_AND_BACK.
? Determine clockwise or counterclockwise winding of a polygon by the order of 
specifying the vertices. By default, the front face of a polygon has counterclockwise winding.

PAGE 59
 
? Successive transformations before painting accumulate, and changing the order of the 
transformations can produce different results.
? Use the Invalidate Windows API call to ensure that a window redraws.

PAGE 60

