Chapter 12
Bitmaps
When you consider bitmaps in an OpenGL context, you are dealing with something quite 
different from your usual Windows bitmap. Normally in Windows, a bitmap is an image of 
numerous colors. An OpenGL bitmap is just what the name implies: a map of bits.
Structure
An OpenGL bitmap consists of bits, represented as zeros and ones or offs and ons, so it can 
only have two colors. Those colors are a foreground and a transparent background.
Project
Create Chapter.12 as a directory under the OpenGL directory. Create BitMem as a directory 
under the Chapter.12 directory. Save a new Delphi project in the BitMem directory, naming 
it BitMem0.Dpr and its unit BitMem1.Pas. Drop a TOpenGL component on the form.
Properties
This project is fairly simple. Listing 12-1 shows its properties:

object Form1: TForm1	
  Left = 188	
  Top = 107	
  Width = 328	
  Height = 347	
  Caption = 'Form1'	
  Color = clBtnFace	
  Font.Charset = DEFAULT_CHARSET	
  Font.Color = clWindowText	
  Font.Height = -11	
  Font.Name = 'MS Sans Serif'	


PAGE 285
   Font.Style = []	
  OldCreateOrder = False	
  OnCreate = FormCreate	
  OnPaint = FormPaint	
  PixelsPerInch = 96	
  TextHeight = 13	
  object OpenGL1: TOpenGL	
    pfdFlags = [Draw_To_Window, Support_OpenGL]	
    PixelType = RGBA	
    Projection = Perspective	
    Enable = [depthTest, cullFace]	
    ClearRed = 0.6	
    ClearGreen = 0.8	
    ClearBlue = 0.8	
    prxLeft = -1	
    prxRight = 1	
    prxBottom = -1	
    prxTop = 1	
    prxNear = 2	
    prxFar = 9	
    ColorBits = 24	
    DepthBits = 32	
    StencilBits = 0	
    Left = 8	
    Top = 8	
  end	
end
Listing 12-1. BitMem0 project property settings.
Layout
As with anything else displayable in OpenGL, vertical values progress from bottom to top. 
In the case of OpenGL bitmaps, rows of bits start at the bottom and proceed up. Of course, in 
PCs bits do not exist in isolation; they exist in groups of eight bits at a time to form bytes. So 
an array of a certain number of bytes serves as a row of eight times as many bits; a two-
dimensional array of bytes can contain an entire bitmap. For a 64x64 bitmap, make the array 
64 rows of 8 bytes each:

implementation	
	
{$R *.DFM}	
	
type	
  TBitRow=array[0..7] of byte;	
  TCheckerBoard=array[0..63] of TBitRow;	


PAGE 286
 	
var	
  CheckerBoard: TCheckerBoard;	
As you can ascertain from the names, the bitmap will represent a checkerboard.
Fill
Of course just having a variable to contain a bitmap is fine, but it’s useless unless you fill it 
with appropriate data. For this task use the MakeCheckerBoard procedure. Some bytes it fills 
with 0-bits, and some with 1-bits. The bitwise not operator flips all the bits as needed.

procedure MakeCheckerBoard;	
var	
  col,	
  row: integer;	
  square,	
  start: byte;	
begin	
  fillchar(CheckerBoard,SizeOf(CheckerBoard),0);	
  start := 0; {black}	
  for row := 0 to 63 do	
  begin	
    if row mod 8 = 0 then	
      start := not start; {alternate $00 $FF}	
    square := start;	
    for col := 0 to 7 do	
    begin	
      square := not square;	
      CheckerBoard[row,col] := square;	
    end; {for}	
  end; {for}	
end;	
New Commands
You need a couple of OpenGL API calls to handle the bitmap:

procedure glRasterPos3f(x,y,z:GLfloat); stdcall;	
This is one of many variations on the glRasterPos command that have different numbers and 
kinds of parameters, much like the glVertex command. This version requires three 
parameters for three-dimensional coordinates. This command sets a value called the raster 
position, and, like the glVertex command, it is subject to transformation.

procedure glBitmap(width,height:GLsizei;xorig,yorig:GLfloat;	
  xmove,ymove:GLfloat;bitmap:pointer); stdcall;	

PAGE 287
 Each visual element in the bitmap is a single bit, so the width and height parameters are the 
number of bits, not the number of bytes. While the checkerboard is eight bytes wide, its 
width measurement in this command is really 64. The xorig and yorig specify the origin as 
an offset (in bits) from the lower left corner of the bitmap. When you use the glRasterPos 
command prior to glBitmap, you position the origin of the bitmap. The xmove and ymove 
parameters adjust the raster position after placing the bitmap, possibly eliminating the need 
for another glRasterPos command, depending on the application of course. The last, but 
certainly not least, parameter is a pointer to the actual bitmap.
Event Handlers
The FormCreate method initializes the checkerboard and specifies the alignment 
requirements for the start of each pixel row in memory (byte alignment). For OpenGL 
bitmaps on some versions of Windows you may need to use 4 for double-word alignment.

procedure TForm1.FormCreate(Sender: TObject);	
begin	
  glPixelStorei(GL_UNPACK_ALIGNMENT,1);	
  MakeCheckerBoard;	
end;	
The FormPaint method draws the 64x64 bitmap. By using 32 for xorig and yorig, which is 
half the height and width, you will be placing the center of the bitmap at the raster position. 
Use 0 for the xmove and ymove values since this application has no need to move the raster 
position after drawing the bitmap. The glRasterPos command with 0.0 for  the x and y 
coordinates places the bitmap in the center of the window. The z value of –2.0 places the 
bitmap right at the zNear clipping plane, just far enough into the screen to become visible.

procedure TForm1.FormPaint(Sender: TObject);	
begin	
  glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT);	
  glRasterPos3f(+0.0,+0.0,-2.0);	
  glBitmap(64,64,32,32,0,0,@CheckerBoard);	
  glFlush;	
  OpenGL1.ErrorCheck('FormPaint');	
end;	
That’s all there is! This very simple program has just enough features to illustrate bitmap 
essentials.

PAGE 288
 Results
Save, compile, and run. Figure 12-1 shows the resulting pattern. The empty squares made of 
0 bits allow the background (the clear color) to show through. The foreground color is . . . 
Well, actually you did not specify one in this program.
Change the FormCreate method to give it a color. A bitmap picks up the last specified color 
or, if you enable lighting, the last specified material color. This time give it a variation on 
purple:

procedure TForm1.FormCreate(Sender: TObject);	
begin	
  glPixelStorei(GL_UNPACK_ALIGNMENT,1);	
  MakeCheckerBoard;	
  glColor3f(0.6,0.1,0.3);	
end;	
While you are at it, make a change to FormPaint so that the effect of the origin values will be 
more evident. Use 0.0 for both origin coordinates so the lower left corner of the bitmap, 
rather than the center of the bitmap, appears at the raster position (window center).

procedure TForm1.FormPaint(Sender: TObject);	
begin	
  glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT);	
  glRasterPos3f(+0.0,+0.0,-2.0);	
  glBitmap(64,64,0,0,0,0,@CheckerBoard);	
  glFlush;	
  OpenGL1.ErrorCheck('FormPaint');	
end;	
Save, compile, and run again.
From Windows Bitmap
The main reason a checkerboard pattern was the first bitmap example was its simplicity in 
programmatic construction. Most bitmaps will not have every byte consist of all 1-bits or all 
0-bits. In order for OpenGL bitmaps to be useful, you need another way to obtain them.
Monochrome Bitmaps
Fortunately there is a Windows bitmap format to the rescue. The (probably rarely used) 
Monochrome format is identical in structure to the OpenGL bitmap, except for the header.

PAGE 289
 Project
Under the Chapter.12 directory, create a new directory named Monochrm. Save a new 
project into it named Monochr0.Dpr with a unit named Monchr1.Pas. On the main form, 
place a TOpenGL component, two TTransform components, a TLight component, and a 
TMaterial component. Be sure to add the Shapes unit to the project (and the unit). You will 
also need to use the MMSystem unit.
Properties
Although this application will not be doing any animation, you will still need to set the 
DoubleBuffer property. Listing 12-2 shows the properties. Notice that the components have 
more meaningful names. Soon you will have fuller scenes with a multitude of components, 
so better names for them should become a habit before that time.

object Form1: TForm1	
  Left = 188	
  Top = 107	
  Width = 328	
  Height = 347	
  Caption = 'Form1'	
  Color = clBtnFace	
  Font.Charset = DEFAULT_CHARSET	
  Font.Color = clWindowText	
  Font.Height = -11	
  Font.Name = 'MS Sans Serif'	
  Font.Style = []	


PAGE 290
   OldCreateOrder = False	
  OnCreate = FormCreate	
  OnKeyPress = FormKeyPress	
  OnPaint = FormPaint	
  OnResize = FormResize	
  PixelsPerInch = 96	
  TextHeight = 13	
  object OpenGL1: TOpenGL	
    pfdFlags = [DoubleBuffer, Draw_To_Window, Support_OpenGL]	
    PixelType = RGBA	
    Projection = Perspective	
    Enable = [depthTest, Lighting]	
    ClearRed = 0.6	
    ClearGreen = 0.8	
    ClearBlue = 0.8	
    prxLeft = -1	
    prxRight = 1	
    prxBottom = -1	
    prxTop = 1	
    prxNear = 2	
    prxFar = 9	
    ColorBits = 24	
    DepthBits = 32	
    StencilBits = 0	
    Left = 8	
    Top = 4	
  end	
  object TransformLight: TTransform	
    TranZ = -1	
    AxisX = 0.2	
    AxisY = 1	
    Angle = 15	
    ScaleX = 1	
    ScaleY = 1	
    ScaleZ = 1	
    OnPaint = TransformLightPaint	
    Left = 8	
    Top = 40	
  end	
  object Light1: TLight	
    Which = 1	
    Cutoff = 65	
    AmbientRed = 0.2	
    AmbientGreen = 0.2	
    AmbientBlue = 0.2	
    DiffuseRed = 1	
    DiffuseGreen = 1	
    DiffuseBlue = 1	
    SpecularRed = 1	


PAGE 291
     SpecularGreen = 1	
    SpecularBlue = 1	
    Direction = True	
    Position = True	
    Spotlight = True	
    Enabled = True	
    Left = 44	
    Top = 40	
  end	
  object TransformCyl: TTransform	
    TranZ = -3	
    AxisX = 1	
    AxisY = 2	
    AxisZ = 3	
    Angle = 30	
    ScaleX = 1	
    ScaleY = 1	
    ScaleZ = 1	
    OnPaint = TransformCylPaint	
    Left = 8	
    Top = 76	
  end	
  object MaterialCyl: TMaterial	
    ambRed = 0.8	
    ambGreen = 0.4	
    ambBlue = 0.2	
    ambAlpha = 1	
    difRed = 0.8	
    difGreen = 0.4	
    difBlue = 0.2	
    difAlpha = 1	
    aFace = faFront	
    dFace = faFront	
    sFace = faFront	
    mShine = 128	
    Left = 44	
    Top = 76	
  end	
end
Listing 12-2. Properties for the monochrome bitmap project.
Loading
You need a function to load the monochrome bitmap and make it useable by OpenGL. The 
ReadMonochrome function is similar to the ReadBitmap function in the previous chapter. 
Note that the width and height values set in this function are pixels for the monochrome 
image. Since each pixel is a 

PAGE 292
 single bit, this gives the right values for an OpenGL bitmap. Note also that this bmp 
variation has an additional header block compared to a true-color (24-bit) bitmap, which this 
function must skip by using the seek procedure. The ReadMonochrome function returns a 
structureless, untyped pointer to the location of the bitmap. OpenGL does not mind, since all 
it wants is a pointer and the dimensions. You should not care either, since you do not have to 
fill it in yourself.

function ReadMonochrome(const FilePath:string;var bmWidth,bmHeight: 	
  GLsizei): pointer;	
const	
  szh=SizeOf(TBitmapFileHeader);	
  szi=SizeOf(TBitmapInfoHeader);	
var	
  bmpfile: file;	
  bfh: TBitmapFileHeader;	
  bmi: TBitmapInfoHeader;	
  siz,	
  size: integer;	
begin	
  assignFile(bmpfile,FilePath);	
  reset(bmpfile,1);	
  size := FileSize(bmpfile)-szh-szi;	
  blockread(bmpfile,bfh,szh);	
  if Bfh.bfType<>$4D42 then	
    raise EInvalidGraphic.Create('Invalid Bitmap');	
  blockread(bmpfile,bmi,szi);	
  with bmi do	
  begin	
    bmWidth := biWidth;	
    bmHeight := biHeight;	
    siz := biSizeImage;	
  end;	
  if size>siz then {some more offset in this particular format}	
    seek(bmpfile,szh+szi+size-siz);	
  getmem(result,siz);	
  blockread(bmpfile,result^,siz);	
end;	
Private
This project needs a few private fields and methods. You have used the Idle method before, 
and the ShowBitmap method will display the bitmap when requested. The start, elapsed, 
and frames fields are used to calculate frame rates. The LastKey field communicates user 
commands. Times points to the bitmap once it is in memory, and the bmWidth and 
bmHeight fields remember the dimensions of the bitmap when you read it in.

PAGE 293
 
  private	
    start,	
    elapsed,	
    frames: integer;	
    LastKey: char;	
    Times: pointer;	
    bmWidth,	
    bmHeight:GLsizei;	
    procedure Idle(Sender:TObject;var Done:boolean);	
    procedure ShowBitmap;	
  public	
Event Handlers and Other Methods
The FormCreate method will, as usual, perform some initialization. In this project you need 
two-sided lighting, so FormCreate sets it up. It also sets up the Idle routine to go with the 
double buffering. Your program will draw a cylinder, so it calls CylinderBase. Finally, it puts 
the system time (in milliseconds since Windows started) into the start field. You will need to 
copy the Times.bmp file from the CD into your project directory. Be sure to put the complete 
path to the file in the code below instead of the simple filename, so it will still work even if 
you launch the executable file from some other directory.

procedure TForm1.FormCreate(Sender: TObject);	
begin	
  glPixelStorei(GL_UNPACK_ALIGNMENT,1);	
  Times := ReadMonochrome('Times.bmp',bmWidth,bmHeight);	
  glLightModeli(GL_LIGHT_MODEL_TWO_SIDE,GL_TRUE);	
  CylinderBase;	
  Application.OnIdle := Idle;	
  start := TimeGetTime;	
end;	
Of course, any dynamic memory you allocate, you must eventually give back. In Delphi 3 
and later, a call to the freemem procedure will deallocate the same memory allocated in the 
corresponding call to the getmem procedure. In Delphi 2, however, you will need to have an 
integer variable global to the implementation section of the unit or a private integer field in 
the form to remember how much memory was allocated, and pass it as a second parameter 
to freemem. You can handle those details if needed.

procedure TForm1.FormDestroy(Sender: TObject);	
begin	
  freemem(Times);	
end;	

PAGE 294
 The last program did not use the FormResize method in order to keep to the bare essentials. 
For this more robust application, you should include it in the usual manner:

procedure TForm1.FormResize(Sender: TObject);	
begin	
  if OpenGL1.Smaller then	
    FormPaint(Sender);	
end;	
Naturally, the FormPaint method is “where the action is.” It does the usual things, but notice 
the if statement. Rather than display the bitmap all the time, it will only show it when a user 
command has put a letter “B” in the LastKey field.

procedure TForm1.FormPaint(Sender: TObject);	
begin	
  glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT);	
  TransformLight.Apply;	
  TransformCyl.Apply;	
  if LastKey='B' then	
    ShowBitmap;	
  OpenGL1.Swap;	
  OpenGL1.ErrorCheck('FormPaint');	
end;	
The TTransform component event handlers are just what you expect:

procedure TForm1.TransformLightPaint(Sender: TObject);	
begin	
  Light1.Apply;	
end;	
	
procedure TForm1.TransformCylPaint(Sender: TObject);	
begin	
  MaterialCyl.Apply;	
  Cylinder;	
end;	
Since the program will not perform full motion animation, the Idle routine has a limited roll. 
It will calculate the frame rate, for a reason that will be apparent shortly. Idle performs the 
calculation every thousand frames. On some machines this will happen every couple of 
seconds. If your machine turns out to be significantly faster or slower, you may want to 
adjust the rollover constant. The mspersec constant just stores into a symbolic name the fact 
that the times are in milliseconds rather than seconds.

procedure TForm1.Idle(Sender:TObject;var Done:boolean);	
const	
  rollover=1000;	


PAGE 295
   mspersec=1000.0;	
begin	
  Done := false;	
  inc(frames);	
  if frames>= rollover then	
  begin	
    elapsed := TimeGetTime;	
    Caption := Format('fps=%0.1f',[rollover*mspersec/(elapsed-start)]);	
    start := elapsed;	
    frames := 0;	
  end else	
    FormPaint(nil);	
end;	
The FormKeyPress method receives the user commands, of which there are three. If the user 
presses the Escape key, the program will close down (because the main form closes).
The only other commands are the letter “B” and any character that is not the letter “B.” 
Whatever the user presses winds up (in uppercase form) in the LastKey field. If it is a “B,” 
the bitmap will appear. Any other letter causes the bitmap to disappear. Note that the form 
has no controls on it that can eat the keystrokes; otherwise you would have to set the form’s 
KeyPreview property to true.

procedure TForm1.FormKeyPress(Sender: TObject; var Key: Char);	
begin	
  LastKey := UpCase(Key);	
  if Key=#27 then	
    Close;	
end;	
And now, the star of this show, the ShowBitmap method, makes its entrance on stage. It sets 
the raster position so the bitmap will appear near the top of the window, just far enough to 
the left so that the end of the bitmap will not hit the right edge of the window.

procedure TForm1.ShowBitmap;	
begin	
  glRasterPos3f(-0.4,+0.9,-2.0);	
  glBitmap(bmWidth,bmHeight,00,00,0,0,Times);	
end;	
Results
Save, compile, and run. Let it run long enough so that the program will update the frame 
rate (in the form caption) a couple of times. Now press the letter “B.” Behold the bitmap 
(shown in Figure 12-2). Let it run long 

PAGE 296
 enough to update the frame rate a couple more times. You may need to take a break while 
this is happening. Make some coffee, feed the cat, walk the dog, read the rest of this 
book...Actually the delay will not be that bad, but you will probably see a substantial 
performance hit when the bitmap is up. Press some other letter to put away the bitmap and 
let the frame rate display update a couple more times. Your mileage will vary, of course, but 
on one computer the frame rate dropped from about 542 frames per second to about 185 fps 
when the bitmap appeared.
Notice that with lighting enabled, the bitmap picks up the last used material color. Now try a 
variation. Change the TranZ coordinate in TransformLight from –1.0 to –2.2. Save, compile, 
and run again. Press “B.” Now the bitmap is black because the light is farther into the screen 
than the bitmap is, and the light is a spotlight pointing in. Since the bitmap is not in the path 
of the light beam, it has no color. On the other hand, the new position of the light brings out 
a nice specular highlight on the cylinder.
Make It Like the Others
You may have noticed that the way the program calls the bitmap deviates from the 
componentized model this book has adopted. You can rectify that easily. Set the TranZ 
property back to –1.0. Drop another TTransform component on the form and name it 
TransformBitmap. Drop on a TMaterial component and name it MaterialBitmap. Listing 12-3 
shows the applicable properties:

  object TransformBitmap: TTransform	
    TranX = -0.4	
    TranY = 0.9	
    TranZ = -2	
    ScaleX = 1	
    ScaleY = 1	
    ScaleZ = 1	
    OnPaint = TransformBitmapPaint	
    Left = 8	


PAGE 297
     Top = 112	
  end	
  object MaterialBitmap: TMaterial	
    ambBlue = 1	
    ambAlpha = 1	
    difBlue = 1	
    difAlpha = 1	
    aFace = faFront	
    dFace = faFront	
    sFace = faFront	
    Left = 44	
    Top = 112	
  end
Listing 12-3. Properties of the bitmap helpers.
Modify the FormPaint method so that it calls the new component like the others. Replace the 
if statement with

  TransformBitmap.Apply;	
Put a conditional statement into the new event handler instead. With the TMaterial 
component in place just for the benefit of the bitmap, the bitmap will get to have its own 
special color (blue in this case).

procedure TForm1.TransformBitmapPaint(Sender: TObject);	
begin	
  if LastKey<>'B' then	
    exit;	
  Material1.Apply;	
  ShowBitmap;	
end;	
Fonts
The particular bitmap file used for this example is a piece of text on purpose. OpenGL 
bitmaps are one of the main ways to get text into an OpenGL scene. Conversely, displaying 
text is one of the main uses of OpenGL bitmaps. You may find that having all your text 
stored in bitmap files prior to use is not always convenient. What you really need is a way to 
transform the fonts used by your operating system into bitmaps. In Windows you can do 
just that.
Project
Under the Chapter.12 directory, create a new directory named Font. Save a new project into 
it named Font0.Dpr, with a unit named Font1.Pas. Drop on 

PAGE 298
 a TOpenGL component, three TTransform components, a TLight component, and two 
TMaterial components.
New Commands
You will use a Windows API function called CreateFont that is standard but not very 
common in Delphi programs. For your convenience, here is the declaration found in your 
Windows unit in Delphi:

function CreateFont(nHeight, nWidth, nEscapement, nOrientation, fnWeight: Integer; 
fdwItalic, fdwUnderline, fdwStrikeOut, fdwCharSet, fdwOutputPrecision, fdwClipPrecision, 
fdwQuality, fdwPitchAndFamily: DWORD; lpszFace: PChar): HFONT; stdcall;	
The main parameters of interest are nHeight and lpszFace. In the former, you say how tall 
you want your font to be, and in the latter you say which font you want, by name. The 
nWidth parameter can allow you to do some interesting things, but for the purposes of this 
exercise you will give it a value of zero and let Windows figure out the most appropriate 
width for your characters.
The next two parameters enable you to produce terrific effects, such as text that does not run 
horizontally, but they are not germane to the current project; give them zeros. For the 
fnWeight parameter, use FW_NORMAL. You could use values that make the text anywhere 
from very light to an extremely heavy bold, but those would just distract from the current -
purpose. Give the next three (attribute) parameters zeros also. Use ANSI_CHARSET along 
with default values for precision, quality, and pitch parameters. Pass the name of the font to 
use as a quoted string, which the compiler will construct in dynamic memory and pass as a 
pointer. See Delphi and Windows online help for more information on the fancy things you 
can do with the CreateFont call.
Another standard Windows API call you will need is SelectObject, to “select” the font you 
want to process. Again, its declaration is already provided in the Windows unit, but it 
appears here for convenience:

function SelectObject(DC: HDC; p2: HGDIOBJ): HGDIOBJ; stdcall;	
The first parameter is the device context handle created by the TOpenGL component; the 
second parameter is the handle returned from the CreateFont function.
The next call to consider is an extension to the Windows API; it is central to making a 
Windows font available as OpenGL bitmaps. Although it is an extension, it too is declared in 
the Windows unit:

function wglUseFontBitmaps(DC: HDC; p2, p3, p4: DWORD): BOOL; stdcall;	

PAGE 299
 The first parameter is that same device context handle, the second is the starting ASCII 
value you want (32 for the space), the third is how many characters you want, and the fourth 
is a reference to the first of a series of display lists.

The bitmaps in this exercise will be available in a slightly different way. OpenGL has a 
facility for compiling a group of commands into a single object. These display lists, as they 
are called, are identified by name; OpenGL “names” are really just numbers.
Where, you may ask, does this first of a series of display list names come from? Since you 
asked, here is the pertinent OpenGL command:

function glGenLists(range:GLsizei): GLuint; stdcall;	
The glGenLists function generates a series of contiguous empty display lists. For the range 
parameter, you pass it the number of display lists you want; the function returns the name of 
the first one in the series. Each subsequent display list in the series has the next name in 
sequence. When you pass that first name to wglUseFontBitmaps, that API call fills in each 
display list with a glBitmap command, one for each character of the font you want. The 
number you gave to the glGenLists command is the same number you should give the 
wglUseFontBitmaps command for the third parameter. If you tell the OpenGL command 
you want 94 display lists, you should then tell the Windows command that you have 94 
display lists available to fill in.

Remember, you will have one display list, containing one glBitmap command, for each letter 
and symbol you want from the chosen font. For this project you will create 94 display lists 
for the ASCII characters, starting with the space (32) and ending with the tilde (126).
The next command you need just sets an offset for convenience in working with a string of 
characters:

procedure glListBase(base: GLuint); stdcall;	
For base you will pass the name of the first display list, minus the ASCII value of the first 
character represented. That way, when you process a string of characters, which OpenGL 
will see as a string of byte-sized display list names, OpenGL will add the offset generated to 
each of the ASCII values in the string and use that as the name by which to find the 
appropriate display list.

Suppose, for example, you generated a series of 26 display lists, and the first available name 
was 15. The series of 26 display lists would be named 15 through 40. Now suppose you told 
the Windows command that you wanted the letters “A” through “Z,” 26 letters with ASCII 
values 65 through 

PAGE 300
 90. The value you would pass to glListBase would be 15-65. OpenGL would then remember 
to use an offset of  –50. If you then tell OpenGL you want the bitmap for “P,” OpenGL will 
see it as an 80, apply the offset of –50, and conclude that you want the display list named 30, 
which is the 16th display list in the series starting with 15. OpenGL would execute the 
selected display list and draw the bitmap for the letter “P.”
It gets even better. Do you remember that the glBitmap command had an xmove and a 
ymove parameter to adjust the raster position after drawing a bitmap? The example 
programs so far did not make use of that feature, but it really comes in handy now. Each 
glBitmap command that wglUseFontBitmaps compiles into a display list (one for each 
character) has the appropriate xmove and ymove values so that the raster position will wind 
up just right for starting the next character.
The final new command for this project is the one that will execute a series of display lists. 
The first parameter is the number of display lists to execute. The second parameter tells how 
to interpret the array of names. In this case you want to tell OpenGL that each byte is a 
name. The third parameter is a pointer to an array of display lists.

procedure glCallLists(n:GLsizei;cltype:GLenum;lists:pointer); stdcall;	
Properties
This project is quite similar to the previous one in its final state. Listing 12-4 shows the 
properties:

object Form1: TForm1	
  Left = 188	
  Top = 107	
  Width = 328	
  Height = 347	
  Caption = 'Form1'	
  Color = clBtnFace	
  Font.Charset = DEFAULT_CHARSET	
  Font.Color = clWindowText	
  Font.Height = -11	
  Font.Name = 'MS Sans Serif'	
  Font.Style = []	
  OldCreateOrder = False	
  OnCreate = FormCreate	
  OnKeyPress = FormKeyPress	
  OnPaint = FormPaint	
  OnResize = FormResize	
  PixelsPerInch = 96	
  TextHeight = 13	
  object OpenGL1: TOpenGL	


PAGE 301
     pfdFlags = [DoubleBuffer, Draw_To_Window, Support_OpenGL]	
    PixelType = RGBA	
    Projection = Perspective	
    Enable = [depthTest, Lighting]	
    ClearRed = 0.6	
    ClearGreen = 0.8	
    ClearBlue = 0.8	
    prxLeft = -1	
    prxRight = 1	
    prxBottom = -1	
    prxTop = 1	
    prxNear = 2	
    prxFar = 9	
    ColorBits = 24	
    DepthBits = 32	
    StencilBits = 0	
    Left = 8	
    Top = 4	
  end	
  object TransformLight: TTransform	
    TranZ = -1	
    AxisX = 0.2	
    AxisY = 1	
    Angle = 15	
    ScaleX = 1	
    ScaleY = 1	
    ScaleZ = 1	
    OnPaint = TransformLightPaint	
    Left = 8	
    Top = 40	
  end	
  object Light1: TLight	
    Which = 1	
    Cutoff = 65	
    AmbientRed = 0.2	
    AmbientGreen = 0.2	
    AmbientBlue = 0.2	
    DiffuseRed = 1	
    DiffuseGreen = 1	
    DiffuseBlue = 1	
    SpecularRed = 1	
    SpecularGreen = 1	
    SpecularBlue = 1	
    Direction = True	
    Position = True	
    Spotlight = True	
    Enabled = True	
    Left = 44	
    Top = 40	


PAGE 302
   end	
  object TransformCyl: TTransform	
    TranZ = -3	
    AxisX = 1	
    AxisY = 2	
    AxisZ = 3	
    Angle = 30	
    ScaleX = 1	
    ScaleY = 1	
    ScaleZ = 1	
    OnPaint = TransformCylPaint	
    Left = 8	
    Top = 76	
  end	
  object MaterialCyl: TMaterial	
    ambRed = 0.8	
    ambGreen = 0.4	
    ambBlue = 0.2	
    ambAlpha = 1	
    difRed = 0.8	
    difGreen = 0.4	
    difBlue = 0.2	
    difAlpha = 1	
    aFace = faFront	
    dFace = faFront	
    sFace = faFront	
    mShine = 128	
    Left = 44	
    Top = 76	
  end	
  object TransformBitmap: TTransform	
    TranX = -0.4	
    TranY = 0.9	
    TranZ = -2	
    ScaleX = 1	
    ScaleY = 1	
    ScaleZ = 1	
    OnPaint = TransformBitmapPaint	
    Left = 8	
    Top = 112	
  end	
  object MaterialBitmap: TMaterial	
    ambBlue = 1	
    ambAlpha = 1	
    difBlue = 1	
    difAlpha = 1	
    aFace = faFront	
    dFace = faFront	
    sFace = faFront	


PAGE 303
     Left = 44	
    Top = 112	
  end	
end
Listing 12-4. Properties for the Font0.Dpr project.
Mostly the Same
Much of the code in this project is the same as the previous project. The form declaration is 
the same, including the private fields and methods. The FormResize method, the 
TransformLightPaint method, the TransformCylPaint method, the Idle method, the 
FormKeyPress method, and the TransformBitmapPaint method are all unchanged from the 
final state of the previous project. The FormPaint method is also the same as the final state, 
but since it is different from the first version of the last project, it bears repeating to avoid 
confusion:

procedure TForm1.FormPaint(Sender: TObject);	
begin	
  glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT);	
  TransformLight.Apply;	
  TransformCyl.Apply;	
  TransformBitmap.Apply;	
  OpenGL1.Swap;	
  OpenGL1.ErrorCheck('FormPaint');	
end;	
Since the TransformBitmapPaint method was introduced as a variation, here it is again just 
to be certain:

procedure TForm1.TransformBitmapPaint(Sender: TObject);	
begin	
  if LastKey<>'B' then	
    exit;	
  MaterialBitmap.Apply;	
  ShowBitmap;	
end;	
The Differences
You need some new variables for display list related matters. Declare them in the 
implementation section:

implementation	
	
{$R *.DFM}	
	
var	


PAGE 304
   tnrFont: HFont;	
  tnrBase: GLuint;	
  tnrHello: string;	
The FormCreate method is changed to use the new techniques. The tnrFont variable receives 
the font handle returned by the CreateFont function. Notice that the requested font is Times 
New Roman, simply passed as a string literal. The tnrBase variable receives the first display 
list name (in a series of 94) from the glGenLists command. The wglUseFontBitmaps 
command creates 94 bitmaps, starting with ASCII character 32. It compiles them into the 
series of  display lists starting with the one whose name is in tnrBase. The tnrHello variable, 
a Delphi huge string, contains the message for display. The glListBase command sets an 
offset equal to the difference between the lowest display list name generated and the lowest 
ASCII value used.

procedure TForm1.FormCreate(Sender: TObject);	
begin	
  glPixelStorei(GL_UNPACK_ALIGNMENT,1);	
  tnrFont := CreateFont(20,0,0,0,FW_NORMAL,0,0,0,ANSI_CHARSET, 	
    OUT_TT_PRECIS,CLIP_DEFAULT_PRECIS,DRAFT_QUALITY,DEFAULT_PITCH,	
    'Times New Roman');	
  SelectObject(OpenGL1.glDC,tnrFont);	
  tnrBase := glGenLists(94);	
  wglUseFontBitmaps(OpenGL1.glDC,32,94,tnrBase);	
  tnrHello := 'Hello, Times New Roman!';	
  glListBase(tnrBase-32);	
  glLightModeli(GL_LIGHT_MODEL_TWO_SIDE,GL_TRUE);	
  CylinderBase;	
  Application.OnIdle := Idle;	
  start := TimeGetTime;	
end;	

Another difference is the absence of a FormDestroy method. Since you do not allocate 
memory in loading a bitmap, you do not need a place to give it back. No doubt OpenGL 
allocates memory, but since it is transparent to the programmer, OpenGL retains the 
responsibility to return whatever memory it obtains.

The final difference is in the ShowBitmap method. Here the display lists execute rather than 
a single glBitmap command. Notice that the number of display lists to execute is the length 
of the string. The size of each name is a single byte, and the array of display list names is the 
string itself. Each ASCII value in the string, combined with the offset, becomes the actual 
name of the appropriate display list.

Just as objects in Delphi are implicit pointers, so is a Delphi huge string. The variable 
actually contains the address of the first character of the 

PAGE 305
 string, just like a PChar. So you can properly typecast the string as a pointer, which is 
exactly what glCallLists needs for its last parameter:

procedure TForm1.ShowBitmap;	
begin	
  glRasterPos3f(0.0,0.0,0.0);	
  glCallLists(length(tnrHello),GL_UNSIGNED_BYTE,pointer(tnrHello));	
end;	
Results
Save, compile, and run as with the previous project. Outwardly the result is very similar, as 
Figure 12-3 shows. Actually, you have much greater flexibility because you can change the 
message that displays just by changing the contents of the string.
Interactive
The flexibility introduced with capturing Windows fonts in display lists of bitmaps opens up 
the possibility of textual interaction with the user without ever leaving the graphics 
environment. This project will illustrate.
Project
Create a User directory under the Chapter.12 directory and save a new project in it named 
User0.Dpr with a unit named User1.Pas. Drop on a TOpenGL component, three TTransform 
components, a TLight component, and two TMaterial components.

PAGE 306
 Properties
Add the following properties:

object Form1: TForm1	
  Left = 188	
  Top = 107	
  Width = 328	
  Height = 347	
  Caption = 'Form1'	
  Color = clBtnFace	
  Font.Charset = DEFAULT_CHARSET	
  Font.Color = clWindowText	
  Font.Height = -11	
  Font.Name = 'MS Sans Serif'	
  Font.Style = []	
  KeyPreview = True	
  OldCreateOrder = False	
  OnCreate = FormCreate	
  OnKeyPress = FormKeyPress	
  OnPaint = FormPaint	
  OnResize = FormResize	
  PixelsPerInch = 96	
  TextHeight = 13	
  object OpenGL1: TOpenGL	
    pfdFlags = [DoubleBuffer, Draw_To_Window, Support_OpenGL]	
    PixelType = RGBA	
    Projection = Perspective	
    Enable = [depthTest, Lighting]	
    ClearRed = 0.6	
    ClearGreen = 0.8	
    ClearBlue = 0.8	
    prxLeft = -1	
    prxRight = 1	
    prxBottom = -1	
    prxTop = 1	
    prxNear = 2	
    prxFar = 9	
    ColorBits = 24	
    DepthBits = 32	
    StencilBits = 0	
    Left = 4	
    Top = 4	
  end	
  object TransformLight: TTransform	
    TranZ = -1	
    AxisX = 0.2	
    AxisY = 1	
    Angle = -10	


PAGE 307
     ScaleX = 1	
    ScaleY = 1	
    ScaleZ = 1	
    OnPaint = TransformLightPaint	
    Left = 4	
    Top = 40	
  end	
  object Light1: TLight	
    Which = 1	
    Cutoff = 65	
    AmbientRed = 0.2	
    AmbientGreen = 0.2	
    AmbientBlue = 0.2	
    DiffuseRed = 1	
    DiffuseGreen = 1	
    DiffuseBlue = 1	
    SpecularRed = 1	
    SpecularGreen = 1	
    SpecularBlue = 1	
    Direction = True	
    Position = True	
    Spotlight = True	
    Enabled = True	
    Left = 40	
    Top = 40	
  end	
  object TransformCyl: TTransform	
    TranZ = -3	
    AxisX = 1	
    AxisY = 0.2	
    Angle = 60	
    ScaleX = 1	
    ScaleY = 1	
    ScaleZ = 1	
    OnPaint = TransformCylPaint	
    Left = 4	
    Top = 72	
  end	
  object MaterialCyl: TMaterial	
    ambRed = 1	
    ambGreen = 1	
    ambAlpha = 1	
    difRed = 1	
    difGreen = 1	
    difAlpha = 1	
    aFace = faFront	
    dFace = faFront	
    sFace = faFront	
    mShine = 128	


PAGE 308
     Left = 40	
    Top = 72	
  end	
  object MaterialBitmap: TMaterial	
    aFace = faFront	
    dFace = faFront	
    sFace = faFront	
    Left = 40	
    Top = 104	
  end	
  object TransformBitmap: TTransform	
    TranX = -1	
    TranY = 1	
    TranZ = -2	
    ScaleX = 1	
    ScaleY = 1	
    ScaleZ = 1	
    OnPaint = TransformBitmapPaint	
    Left = 4	
    Top = 104	
  end	
end
Listing 12-5. Properties for the user interaction project.
Private
Here are the private fields and methods. You have already seen the integers in another 
project; the GLfloats help in the animation, as do the Idle method and the Adjust method. 
The other methods implement the user interaction.

  private	
    frames,	
    start,	
    elapsed: integer;	
    deltaAngle: GLfloat;	
    deltaX: GLfloat;	
    deltaY: GLfloat;	
    deltaZ: GLfloat;	
    signX: GLfloat;	
    signY: GLfloat;	
    signZ: GLfloat;	
    procedure Idle(Sender:TObject;var Done:boolean);	
    procedure ShowMenu;	
    procedure SetXSpeed;	
    procedure SetYSpeed;	
    procedure SetZSpeed;	
    procedure SetXAxis;	


PAGE 309
     procedure SetYAxis;	
    procedure SetZAxis;	
    procedure SetRotationRate;	
    procedure Adjust;	
    procedure AddDigit(Key:char);	
  public	
Variables
You need a few variables in the implementation section:

implementation	
	
{$R *.DFM}	
	
var	
  tnrFont: HFont;	
  tnrBase: GLuint;	
  msg: string;	
Event Handlers
The FormCreate method does familiar things, then it initializes the sign fields so that 
animation will be able to proceed:

procedure TForm1.FormCreate(Sender: TObject);	
begin	
  glPixelStorei(GL_UNPACK_ALIGNMENT,1);	
  Application.OnIdle := Idle;	
  CylinderBase;	
  tnrFont := CreateFont(20,0,0,0,FW_NORMAL,0,0,0,ANSI_CHARSET,	
    OUT_TT_PRECIS,CLIP_DEFAULT_PRECIS,DRAFT_QUALITY,DEFAULT_PITCH,	
    'Times New Roman');	
  SelectObject(OpenGL1.glDC,tnrFont);	
  tnrBase := glGenLists(94);	
  wglUseFontBitmaps(OpenGL1.glDC,32,94,tnrBase);	
  glListBase(tnrBase-32);	
  signX := 1.0;	
  signY := 1.0;	
  signZ := 1.0;	
end;	
The FormResize and FormPaint methods have the expected contents:

procedure TForm1.FormResize(Sender: TObject);	
begin	
  if OpenGL1.Smaller then	
    FormPaint(Sender);	
end;	


PAGE 310
 	
procedure TForm1.FormPaint(Sender: TObject);	
begin	
  glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT);	
  TransformLight.Apply;	
  TransformCyl.Apply;	
  TransformBitmap.Apply;	
  OpenGL1.Swap;	
  OpenGL1.ErrorCheck('FormPaint');	
end;	
The TransformBitmapPaint method exits if there is no message to display; otherwise it sets 
the color for the bitmaps from the MaterialBitmap component. Then it breaks the message 
string at the carriage return characters and displays each piece on a separate line.

procedure TForm1.TransformBitmapPaint(Sender: TObject);	
var	
  ymsg: GLfloat;	
  cr: integer;	
  tmsg,	
  temp: string;	
begin	
  if msg='' then	
    exit;	
  MaterialBitmap.Apply;	
  tmsg := msg;	
  ymsg := 0.0;	
  repeat	
    cr := pos(#13,tmsg);	
    temp := copy(tmsg,1,cr-1);	
    ymsg := ymsg-0.1;	
    glRasterPos3f(0.0,ymsg,0.0);	
    glCallLists(length(temp),GL_UNSIGNED_BYTE,pointer(temp));	
    system.delete(tmsg,1,cr);	
  until tmsg='';	
end;	
The Idle method performs the previous duty of tracking the frame rate and calls Adjust 
when there is no message to display. The Adjust method handles the details of the animation 
for the Idle method. When there is a message to display, animation freezes.

procedure TForm1.Idle(Sender:TObject;var Done:boolean);	
const	
  rollover=1000;	
  mspersec=1000.0;	
begin	
  Done := false;	
  if msg='' then	


PAGE 311
     Adjust;	
  inc(frames);	
  if frames>=1000 then	
  begin	
    elapsed := TimeGetTime;	
    Caption := Format('fps=%0.1f',[rollover*mspersec/(elapsed-start)]);	
    start := elapsed;	
    frames := 0;	
  end else	
    FormPaint(nil);	
end;	
The TransformLightPaint and TransformCylPaint methods do their normal actions:

procedure TForm1.TransformLightPaint(Sender: TObject);	
begin	
  Light1.Apply;	
end;	
	
procedure TForm1.TransformCylPaint(Sender: TObject);	
begin	
  MaterialCyl.Apply;	
  Cylinder;	
end;	
The FormKeyPress method receives user commands and data entry. The Escape key will exit 
the program, and the letter “M” will display a list of available user commands. The other 
letters in the case statement display a prompt and prepare to receive data entry. The 
characters in the last line of the case statement are the data entry, processed through the 
AddDigit method. The UpCase function ensures that the user will not have to worry about 
pressing the Shift or Caps Lock keys.

procedure TForm1.FormKeyPress(Sender: TObject; var Key: Char);	
begin	
  case UpCase(Key) of	
    #27: Close;	
    'M': ShowMenu;	
    'A': SetXSpeed;	
    'B': SetYSpeed;	
    'C': SetZSpeed;	
    'R': SetRotationRate;	
    'X': SetXAxis;	
    'Y': SetYAxis;	
    'Z': SetZAxis;	
    #13,'-','+','.','0'..'9': AddDigit(Key);	
  end; {case}	
end;	

PAGE 312
 Other Methods
The Adjust method, called from the Idle method, is the heart of the animation. The various 
delta values add to the fields of the TransformCyl component, constantly adjusting the 
position and orientation of the cylinder. The ratio variable helps adjust the travel volume of 
the cylinder according to the depth into the screen. The sign fields flip in sign when the 
cylinder reaches an edge of its travel volume, so the cylinder bounces back rather than 
completely leaving the viewable area.

procedure TForm1.Adjust;	
var	
  ratio: GLfloat;	
begin	
  with OpenGL1,TransformCyl do	
  begin	
    Angle := Angle+deltaAngle;	
    if (Angle<0.0) and (deltaAngle<0.0) then	
      Angle := 360.0	
    else if (Angle>360.0) and (deltaAngle>0.0) then	
      Angle := 0.0;	
 	
    ratio := abs(tranZ/prxNear);	
	
    if (-TranZ<prxNear) then	
      signZ := -1.0	
    else if (-TranZ>prxFar) then	
      signZ := 1.0;	
    TranZ := TranZ+deltaZ*signZ;	
   	
    if (TranY<prxBottom*ratio) then	
      signY := 1.0	
    else if (TranY>prxTop*ratio) then	
      signY := -1.0;	
    TranY := TranY+deltaY*signY;	
	
    if (TranX<prxLeft*ratio) then	
      signX := 1.0	
    else if (TranX>prxRight*ratio) then	
      signX := -1.0;	
    TranX := TranX+deltaX*signX;	
	
  end;	
end;	
The ShowMenu method merely prepares the message string (the msg variable) for display in 
the TransformBitmapPaint method, giving it the entire list of user commands, separated by 
carriage returns:

PAGE 313
 
procedure TForm1.ShowMenu;	
begin	
  msg := 'Escape = Exit'#13	
        +'M = Show Menu'#13	
        +'A = Set X Speed'#13	
        +'B = Set Y Speed'#13	
        +'C = Set Z Speed'#13	
        +'R = Set Rotation Rate'#13	
        +'X = Set X Axis'#13	
        +'Y = Set Y Axis'#13	
        +'Z = Set Z Axis'#13	
        ;	
end;	
The various Set methods put a prompt into the msg variable. Notice that all messages end in 
a carriage return so the display method does not have to have any special case code. Every 
one of the prompts has an underscore to signal that user data entry is expected. The 
underscore also serves as a pseudo cursor to indicate where the next typed digit will appear. 
The colon in each prompt serves as a divider between prompt and user data.

procedure TForm1.SetXSpeed;	
begin	
  msg := 'A = Set X Speed:_'#13;	
end;	
	
procedure TForm1.SetYSpeed;	
begin	
  msg := 'B = Set Y Speed:_'#13;	
end;	
	
procedure TForm1.SetZSpeed;	
begin	
  msg := 'C = Set Z Speed:_'#13;	
end;	
	
procedure TForm1.SetRotationRate;	
begin	
  msg := 'R = Set Rotation Rate:_'#13;	
end;	
	
procedure TForm1.SetXAxis;	
begin	
  msg := 'X = Set X Axis:_'#13;	
end;	
	
procedure TForm1.SetYAxis;	
begin	
  msg := 'Y = Set Y Axis:_'#13;	


PAGE 314
 end;	
	
procedure TForm1.SetZAxis;	
begin	
  msg := 'Z = Set Z Axis:_'#13;	
end;	
The AddDigit method processes the user data. If the message string is empty, then the 
method exits, since if there is no prompt, there is no entered data. The method also exits if 
the string has no underscore, since all the prompts for user data entry contain an underscore. 
The start local variable remembers the first character of the prompt (in the message string) to 
identify the type of data being received. If the Key passed to this method is a carriage return, 
then the user has finished entering this particular piece of data; otherwise the method inserts 
the Key into the string just before the underscore. The modified string will display, showing 
the user input this far. After stripping off the prompt part of a temporary copy of the string 
(up to the colon), the method makes the user data entry part available for further processing. 
If the user has signaled data completion by pressing the Enter key (carriage return), then the 
remainder of the string yields a numeric value. The case statement decides which field gets 
the new value according to which command had been entered. By this means the user sets 
up the motion parameters for the cylinder.

procedure TForm1.AddDigit(Key:char);	
var	
  temp: string;	
  und,	
  err,	
  colon: integer;	
  start: char;	
  number: GLfloat;	
begin	
  if msg='' then	
    exit;	
  start := msg[1];	
  und := pos('_',msg);	
  if und=0 then	
    exit;	
  if Key<>#13 then	
    system.Insert(Key,msg,und)	
  else	
    dec(und);	
  colon := pos(':',msg);	
  temp := copy(msg,1,und);	
  temp := copy(temp,colon+1,und);	
  if Key=#13 then	
  begin	


PAGE 315
     msg := '';	
    val(temp,number,err);	
    if err=0 then	
    with TransformCyl do	
    case start of	
      'A': deltaX := abs(number);	
      'B': deltaY := abs(number);	
      'C': deltaZ := abs(number);	
      'R': deltaAngle := number;	
      'X': AxisX := number;	
      'Y': AxisY := number;	
      'Z': AxisZ := number;	
    end; {case}	
  end;	
end;	
Result
Save, compile, and run. Press “M” to see the menu of available commands as in Figure 12-4.
Press the various letters listed, type in appropriate floating-point numbers, positive or 
negative, and press Enter after each complete number. The values you type determine the 
motion of the cylinder. Type carefully; the purpose of this application is to illustrate some 
OpenGL commands and techniques, not to produce a full-featured editor that would 
obscure the key concepts. You cannot backspace to correct mistakes. You can type periods, 
minus signs, and plus signs in the wrong place. No harm comes from invalid numbers; their 
entries are ignored. Numbers too large cause no problems either, but you may find having 
the cylinder bounce around at half the speed of light a little disconcerting, so start with small 
fractions like 0.01.

PAGE 316
 With Texture
You now have the ability to support user data entry with OpenGL programs. You must 
admit, though, that it is rather plain. There are many graphics programs that receive user 
input with fancy textured backgrounds and edit boxes. This example program will 
demonstrate a technique you can use in OpenGL to accomplish a similar effect. As usual, it 
will not be anything elaborate so that the most important concepts will not disappear behind 
a myriad of elaborate details.
Project
Create a Texture directory under the Chaper.12 directory and save in it a project named 
Tex0.Dpr and a unit named Tex1.Pas. Drop on a TOpenGL component, five TTransform 
components, a TLight component, and three TMaterial components.
Properties
Add the following properties:

object Form1: TForm1	
  Left = 188	
  Top = 107	
  Width = 328	
  Height = 347	
  Caption = 'Form1'	
  Color = clBtnFace	
  Font.Charset = DEFAULT_CHARSET	
  Font.Color = clWindowText	
  Font.Height = -11	
  Font.Name = 'MS Sans Serif'	
  Font.Style = []	
  KeyPreview = True	
  OldCreateOrder = False	
  OnCreate = FormCreate	
  OnKeyPress = FormKeyPress	
  OnPaint = FormPaint	
  OnResize = FormResize	
  PixelsPerInch = 96	
  TextHeight = 13	
  object OpenGL1: TOpenGL	
    pfdFlags = [DoubleBuffer, Draw_To_Window, Support_OpenGL]	
    PixelType = RGBA	
    Projection = Perspective	
    Enable = [depthTest, Lighting]	
    ClearRed = 0.6	


PAGE 317
     ClearGreen = 0.8	
    ClearBlue = 0.8	
    prxLeft = -1	
    prxRight = 1	
    prxBottom = -1	
    prxTop = 1	
    prxNear = 2	
    prxFar = 9	
    ColorBits = 24	
    DepthBits = 32	
    StencilBits = 0	
    Left = 4	
    Top = 4	
  end	
  object TransformLight: TTransform	
    TranZ = -2.2	
    AxisX = 0.2	
    AxisY = 1	
    Angle = -15	
    ScaleX = 1	
    ScaleY = 1	
    ScaleZ = 1	
    OnPaint = TransformLightPaint	
    Left = 4	
    Top = 40	
  end	
  object Light1: TLight	
    Which = 1	
    Cutoff = 65	
    AmbientRed = 0.2	
    AmbientGreen = 0.2	
    AmbientBlue = 0.2	
    DiffuseRed = 1	
    DiffuseGreen = 1	
    DiffuseBlue = 1	
    SpecularRed = 1	
    SpecularGreen = 1	
    SpecularBlue = 1	
    Direction = True	
    Position = True	
    Spotlight = True	
    Enabled = True	
    Left = 40	
    Top = 40	
  end	
  object TransformCyl: TTransform	
    TranZ = -3	
    AxisX = 1	
    AxisY = 0.2	


PAGE 318
     Angle = 60	
    ScaleX = 1	
    ScaleY = 1	
    ScaleZ = 1	
    OnPaint = TransformCylPaint	
    Left = 4	
    Top = 72	
  end	
  object MaterialCyl: TMaterial	
    ambGreen = 1	
    ambBlue = 1	
    ambAlpha = 1	
    difGreen = 1	
    difBlue = 1	
    difAlpha = 1	
    aFace = faFront	
    dFace = faFront	
    sFace = faFront	
    mShine = 128	
    Left = 40	
    Top = 72	
  end	
  object MaterialBitmap: TMaterial	
    ambRed = 1	
    ambGreen = 1	
    ambBlue = 1	
    ambAlpha = 1	
    difRed = 1	
    difGreen = 1	
    difBlue = 1	
    difAlpha = 1	
    aFace = faFront	
    dFace = faFront	
    sFace = faFront	
    Left = 40	
    Top = 140	
  end	
  object TransformInput: TTransform	
    TranZ = -2.01	
    ScaleX = 1	
    ScaleY = 1	
    ScaleZ = 1	
    OnPaint = TransformInputPaint	
    Left = 4	
    Top = 104	
  end	
  object TransformTex: TTransform	
    TranZ = 1	
    ScaleX = 1	


PAGE 319
     ScaleY = 1	
    ScaleZ = 1	
    OnPaint = TransformTexPaint	
    Left = 80	
    Top = 40	
  end	
  object MaterialInput: TMaterial	
    ambRed = 1	
    ambAlpha = 1	
    difRed = 1	
    difAlpha = 1	
    aFace = faFront	
    dFace = faFront	
    sFace = faFront	
    Left = 40	
    Top = 104	
  end	
  object TransformBitmap: TTransform	
    TranX = 0.16	
    TranZ = -2	
    ScaleX = 1	
    ScaleY = 1	
    ScaleZ = 1	
    OnPaint = TransformBitmapPaint	
    Left = 4	
    Top = 140	
  end	
end
Listing 12-6. Properties for the texture interaction project.
Private
Here are the private fields and methods. The inStr field receives the user-typed digits. 
FieldNo keeps track of which kind of data the user enters.

  private	
    frames,	
    start,	
    elapsed: integer;	
    deltaAngle: GLfloat;	
    deltaX: GLfloat;	
    deltaY: GLfloat;	
    deltaZ: GLfloat;	
    signX: GLfloat;	
    signY: GLfloat;	
    signZ: GLfloat;	
    inStr: string;	


PAGE 320
     FieldNo: integer;	
    procedure Idle(Sender:TObject;var Done:boolean);	
    procedure Adjust;	
    procedure AddDigit(Key:char);	
  public	
Variables
You need just a few constants, types, and variables in the implementation section:

implementation	
	
{$R *.DFM}	
	
const	
  Level=0;	
  Border=0;	
  ColorComps=3;	
type	
  TRGB=packed record	
    r,g,b: byte;	
  end;	
  TWrap=array[0..0] of TRGB;	
var	
  tnrFont: HFont;	
  tnrBase: GLuint;	
ReadBitmap
This project uses the ReadBitmap procedure given in the previous chapter to load 24-bit 
Windows bitmaps and convert them to textures. Copy Input.bmp from the companion CD to 
your Texture directory.

function ReadBitmap(const FilePath:string;var sWidth,tHeight:GLsizei): pointer;	
const	
  szh=SizeOf(TBitmapFileHeader);	
  szi=SizeOf(TBitmapInfoHeader);	
var	
  bmpfile: file;	
  bfh: TBitmapFileHeader;	
  bmi: TBitmapInfoHeader;	
  t: byte;	
  x,	
  fpos,	
  size: integer;	
begin	


PAGE 321
   assignFile(bmpfile,FilePath);	
  reset(bmpfile,1);	
  size := FileSize(bmpfile)-szh-szi;	
  blockread(bmpfile,bfh,szh);	
  if Bfh.bfType<>$4D42 then	
    raise EInvalidGraphic.Create('Invalid Bitmap');	
  blockread(bmpfile,bmi,szi);	
  with bmi do	
  begin	
    sWidth := biWidth;	
    tHeight := biHeight;	
  end;	
  getmem(result,size);	
  blockread(bmpfile,result^,size);	
  for x := 0 to sWidth*tHeight-1 do	
  with TWrap(result^)[x] do	
  begin	
    t := r;	
    r := b;	
    b := t;	
  end;	
end;	
New Command
The glIsEnabled function returns the enabled condition for the various constants you can 
pass to glEnable and glDisable:

function glIsEnabled(cap:GLenum): GLBoolean; stdcall;	
Event Handlers
The FormCreate method initializes both texturing and bitmapping. Notice the chosen font is 
Courier New. Since it is not a proportional font, it is more suitable for numbers. Notice the 
use of GL_MODULATE. GL_DECAL would seem to be more suitable for providing an 
always-visible background for user input, but it does not work well with bitmaps.

procedure TForm1.FormCreate(Sender: TObject);	
var	
  sWidth,	
  tHeight: GLsizei;	
  Wrapper: pointer;	
begin	
  glPixelStorei(GL_UNPACK_ALIGNMENT,1);	
  Application.OnIdle := Idle;	
  CylinderBase;	
  glPixelStorei(GL_UNPACK_ALIGNMENT,1);	


PAGE 322
 	
  glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_S,GL_CLAMP);	
  glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_T,GL_CLAMP);	
  glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_NEAREST);	
  glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_NEAREST);	
  Wrapper := ReadBitmap('input.bmp',sWidth,tHeight);	
  glTexImage2D(GL_TEXTURE_2D,Level,ColorComps,sWidth,tHeight,Border,	
    GL_RGB,GL_UNSIGNED_BYTE,Wrapper);	
  freemem(Wrapper);	
  glTexEnvf(GL_TEXTURE_ENV,GL_TEXTURE_ENV_MODE,GL_MODULATE);	
	
  tnrFont := CreateFont(30,0,0,0,FW_NORMAL,0,0,0,ANSI_CHARSET,	
    OUT_TT_PRECIS,CLIP_DEFAULT_PRECIS,DRAFT_QUALITY,DEFAULT_PITCH,	
    'Courier New');	
  SelectObject(OpenGL1.glDC,tnrFont);	
  tnrBase := glGenLists(94);	
  wglUseFontBitmaps(OpenGL1.glDC,32,94,tnrBase);	
  glListBase(tnrBase-32);	
  signX := 1.0;	
  signY := 1.0;	
  signZ := 1.0;	
end;	

The FormResize method is the same as always. The FormPaint method is fairly standard, but 
the if statement bears comment. If texturing is active, the method makes three special calls. 
The first repositions and applies the light so that the texture and bitmap will show properly. 
The second positions and displays the polygon with its texture, applying the appropriate 
material color, and the third call positions and displays the bitmaps showing the user-typed 
digits.

procedure TForm1.FormResize(Sender: TObject);	
begin	
  if OpenGL1.Smaller then	
    FormPaint(Sender);	
end;	
	
procedure TForm1.FormPaint(Sender: TObject);	
begin	
  glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT);	
  TransformLight.Apply;	
  TransformCyl.Apply;	
  if glIsEnabled(GL_TEXTURE_2D) then	
  begin	
    TransformTex.Apply;	
    TransformInput.Apply;	
    TransformBitmap.Apply;	
  end;	
  OpenGL1.Swap;	


PAGE 323
   OpenGL1.ErrorCheck('FormPaint');	
end;	
	
procedure TForm1.TransformTexPaint(Sender: TObject);	
begin	
  Light1.Apply;	
end;	
	
procedure TForm1.TransformInputPaint(Sender: TObject);	
begin	
  MaterialInput.Apply;	
  glBegin(GL_POLYGON);	
    glTexCoord2D(-0.0,+1.0); glVertex3f(-1.0,+1.0,0.0);	
    glTexCoord2D(-0.0,-0.0); glVertex3f(-1.0,-1.0,0.0);	
    glTexCoord2D(+1.0,-0.0); glVertex3f(+1.0,-1.0,0.0);	
    glTexCoord2D(+1.0,+1.0); glVertex3f(+1.0,+1.0,0.0);	
  glEnd;	
end;	

The TransformBitmapPaint method, if called, applies the material color desired for the 
bitmaps. Then it sets the raster position relative to the transformation, accepting the x and z 
values and adjusting the y value for the current input field. Finally, the method executes the 
display lists determined by the contents of inStr:

procedure TForm1.TransformBitmapPaint(Sender: TObject);	
begin	
  MaterialBitmap.Apply;	
  glRasterPos3f(0.0,0.69-FieldNo*0.25,0.0);	
  glCallLists(length(inStr),GL_UNSIGNED_BYTE,pchar(inStr));	
end;	

The Idle method tracks the frame rate and handles animation if there is no texturing for user 
input:

procedure TForm1.Idle(Sender:TObject;var Done:boolean);	
const	
  rollover=1000;	
  mspersec=1000.0;	
begin	
  Done := false;	
  if not glIsEnabled(GL_TEXTURE_2D) then	
    Adjust;	
  inc(frames);	
  if frames>=rollover then	
  begin	
    elapsed := TimeGetTime;	
    Caption := Format('fps=%0.1f',[rollover*mspersec/(elapsed-start)]);	
    start := elapsed;	
    frames := 0;	


PAGE 324
   end else	
    FormPaint(nil);	
end;	
The TransformLightPaint and TransformCylPaint methods do their normal actions:

procedure TForm1.TransformLightPaint(Sender: TObject);	
begin	
  Light1.Apply;	
end;	
	
procedure TForm1.TransformCylPaint(Sender: TObject);	
begin	
  MaterialCyl.Apply;	
  Cylinder;	
end;	
The FormKeyPress method receives user commands and data entry. The Escape key will exit 
the program, and the letter “M” (either case) will cause the textured user input area to 
appear. The tab character advances to the next field. The space character also performs this 
job since the Tab key of some keyboards does not produce the ASCII standard tab character. 
The Enter key closes the input area. Digits, decimals, and signs process as the user input 
data.

procedure TForm1.FormKeyPress(Sender: TObject; var Key: Char);	
begin	
  case Key of	
    #27: Close;	
    'M','m': begin	
      glEnable(GL_TEXTURE_2D);	
      InStr := '_';	
      FieldNo := 0;	
    end;	
    #09,' ': begin	
      if FieldNo<6 then	
        inc(FieldNo)	
      else	
        FieldNo := 0;	
      inStr := '_';	
    end;	
    #13: begin	
      glDisable(GL_TEXTURE_2D);	
      inStr := '';	
    end;	
    '-','+','.','0'..'9': AddDigit(Key);	
  end; {case}	
end;

PAGE 325
 Other Methods
The Adjust method performs the animation duties for the Idle method as in the previous 
project:

procedure TForm1.Adjust;	
var	
  ratio: GLfloat;	
begin	
  with OpenGL1,TransformCyl do	
  begin	
    Angle := Angle+deltaAngle;	
    if (Angle<0.0) and (deltaAngle<0.0) then	
      Angle := 360.0	
    else if (Angle>360.0) and (deltaAngle>0.0) then	
      Angle := 0.0;	
               	
    ratio := abs(tranZ/prxNear);	
	
    if (-TranZ<prxNear) then	
      signZ := -1.0	
    else if (-TranZ>prxFar) then	
      signZ := 1.0;	
    TranZ := TranZ+deltaZ*signZ;	
   	
    if (TranY<prxBottom*ratio) then	
      signY := 1.0	
    else if (TranY>prxTop*ratio) then	
      signY := -1.0;	
    TranY := TranY+deltaY*signY;	
	
    if (TranX<prxLeft*ratio) then	
      signX := 1.0	
    else if (TranX>prxRight*ratio) then	
      signX := -1.0;	
    TranX := TranX+deltaX*signX;	
	
  end;	
end;	

The AddDigit method processes the user data similarly to the previous project, except that it 
does not need to strip off a prompt, and the destination of the data depends on the value of 
FieldNo:

procedure TForm1.AddDigit(Key:char);	
var	
  und,	
  err,	
  colon: integer;	
  number: GLfloat;	


PAGE 326
 begin	
  und := pos('_',inStr);	
  if und=0 then	
    exit;	
  system.insert(Key,inStr,und);	
  val(copy(inStr,1,und),number,err);	
  if err=0 then	
  with TransformCyl do	
  case FieldNo of	
    0: deltaX := abs(number);	
    1: deltaY := abs(number);	
    2: deltaZ := abs(number);	
    3: deltaAngle := number;	
    4: AxisX := number;	
    5: AxisY := number;	
    6: AxisZ := number;     	
  end; {case}	
end;	
Result

Save, compile, and run. Press “M” to see the user input area shown in Figure 12-5. Type 
numbers for the appropriate values. Press Tab or space to move from field to field and Enter 
to close the input area.

PAGE 327
 
Identifier Reference
Identifier			Description
glBitmap
An OpenGL command that draws a bitmap. 
glCallLists
An OpenGL command that executes a sequence of display lists.
glGenLists
An OpenGL command that creates a consecutive sequence of display lists.
glIsEnabled
An OpenGL function that returns the state for constants that can be passed to glEnable and 
glDisable.
glListBase
An OpenGL command that defines an offset to be used by glCallLists.
glRasterPos
An OpenGL command that sets the raster position.
Summary
In this chapter you learned how to create and use OpenGL bitmaps, especially for text in a 
scene. You have learned techniques for using bitmaps for user interaction, including some 
nice displays in conjunction with textures. While the examples in this chapter were, as usual, 
quite simple, they laid the foundation for some very sophisticated uses.

PAGE 328

