Delphi™ Developer’s 
Guide to OpenGL




Jon Q. Jacobs


Wordware Publishing, Inc.

PAGE i
 Library of Congress Cataloging-in-Publication Data

Jacobs, Jon (Jon Q.)
Delphi developer’s guide to OpenGL / by Jon Jacobs.
  p. cm.
Includes index.
ISBN 1-55622-657-8 (pbk)
1. Computer graphics. 2. OpenGL. 3. Delphi (Computer file).
I. Title.
T385.J334  1998                 99-27203
006.6'6--dc21                     CIP



Copyright © 1999, Wordware Publishing, Inc.
All Rights Reserved
2320 Los Rios Boulevard
Plano, Texas 75074


No part of this book may be reproduced in any form or by
any means without permission in writing from
Wordware Publishing, Inc.

Printed in the United States of America



ISBN 1-55622-657-8
10 9 8 7 6 5 4 3 2 1
9906

Delphi is a trademark of Inprise Corporation.
Windows and Microsoft are registered trademarks of Microsoft Corporation.
Other product names mentioned are used for identification purposes only and may be trademarks of their respective 
companies.


All inquiries for volume purchases of this book should be addressed to Wordware Publishing, Inc., at the 
above address. Telephone inquiries may be made by calling:
(972) 423-0090

PAGE ii
 Contents


Introduction	xv
Chapter 1
The First Steps	1
Directory Structure	1
Preliminary Code	1
Getting Ready to Start to Begin	1
Device Context	2
Descriptor Fields	3
Starting to Begin	4
Rendering Context	4
Cleanup	4
Begin	5
Uses Clause	5
Paint	5
Error Checking	6
More Device Context	7
More Error Handling	8
Identifier Reference	10
Summary	12
Chapter 2
Basics	13
Viewport	13
Event Handler	13
Declaration	14
Analogy	15
Orthographic Projection	16
Meaning	16
Flexibility	17
Drawing	18
Command Placement	18
State Variables	18
Drawing Mode	20
Additional Considerations	23
Naming Convention	23
Error Handling in Depth	23
Order of Events	24

PAGE iii
 Matrices and Command Placement	26
Viewport Manipulation	26
Mixing Windows Components	27
Pushing the (X,Y,Z) Limits	28
Identifier Reference	31
Summary	32
Chapter 3
Introducing 3-D	33
Depth	33
Cube	35
Square Viewport	35
Distinguish Faces	35
Depth Test	37
Matrices Revisited	38
The ModelView Matrix	38
Rotation	39
Three Faces	39
glRotate	39
Purpose	39
Parameters	39
Direction	40
Action	40
Two-Faced Polygons	41
Open the Cube	41
Culling	43
Vertex Direction	43
Translation	45
Menu	45
Accumulation	45
More Clipping	45
Scale	46
Menu	46
Description	46
Order of Transformations	47
A New Interface	47
Simultaneous Commands	58
Identifier Reference	58
Summary	59
Chapter 4
Perspective	61
Illustration	61
Command	62
Clipping Volume	62
Parameters	63
Depth	63

PAGE iv
 Previous Cube	63
Test	64
Better Cube	65
Movement	67
Rotation	67
Translation	67
Multiple Objects	68
Matrix Stack	68
Reuse an Object	69
Make a Scene	70
Independent Movement	71
Translate	72
Rotate	73
Transformation Order Revisited	75
Another View	76
Identifier Reference	76
Summary	77
Chapter 5
Lighting	79
Enable Lighting	79
Ambient Light	80
Definition	80
Commands and Constants	80
The Easy Way	81
The Hard Way	82
Diffuse Light	85
Definition	85
Positional Lamps	86
Different Material Colors	89
Spotlights	91
Directional Spotlights	94
Emission	96
Definition	96
Implementation	96
Specular Light	98
Definition	98
Implementation	98
How Much Light?	100
Identifier Reference	101
Summary	102
Chapter 6
Components	105
Setup	105
OpenGL Component	105
Preliminary Types	106

PAGE v
 The Component Fields and Other Private Matters	107
The Public Section	107
The Published Section	108
Implementation	109
Start	109
Running	111
Finish	113
Registration	115
Transformation Component	116
Units	116
The Class Declaration	116
The Private Section	116
The Protected Section	117
The Public Section	117
The Published Section	117
Implementation	118
Material Component	119
Units	119
Preliminary Types	120
The Class Declaration	120
The Private Section	121
The Public Section	121
The Published Section	122
Variables	122
Implementation	123
Light Component	126
Units	126
The Class Declaration	127
The Private Section	127
The Public Section	128
The Published Section	128
Implementation	129
Path	134
Icons	134
Installing the Components	135
Delphi 2	136
Delphi 3, 4, Etc.	136
Summary	136
Chapter 7
Picking	137
Filling Out the Form	137
TOpenGL	137
TTransform	138
FormPaint	138
FormResize	139

PAGE vi
 TMaterial	140
Cube	140
More	142
Names	144
Numbers	144
Persistent State	144
Command	145
Stack	145
Coding for Names	145
Levels	146
Naming the Objects	146
Picking	147
The Challenge	147
Selection	148
Matrices	149
Get Mousey	151
Which Event	151
Collect the Data	152
Displaying the Data	155
Hierarchy	157
Identifier Reference	158
Summary	159
Chapter 8
Triangles	161
The Plane Problem	161
Building Shapes From Triangles	166
Triangle Fans	168
Order	168
Flat Fans	168
Winding and Faces	169
3-D Fans	173
Triangle Strips	178
Winding	178
Order	178
A Cube of Triangles	179
Shading	183
Curves	184
Cylinder	184
Cone	189
Dome	190
Highlights	191
Identifier Reference	195
Summary	196

PAGE vii
 Chapter 9
Animation	197
Mouse Movement	197
Properties	197
Picking	200
Fields and Methods	202
Action	207
Flicker	207
Double Buffer	208
Automatic Motion	208
Private	211
Idle	211
Methods	213
Frame Rates	214
Private	214
Initialize	214
Idle	215
Action	215
Independent Speed	215
Initialize	216
Idle	216
Action	217
Identifier Reference	217
Summary	217
Chapter 10
Effects	219
Transparency	219
The Project	219
Units	219
Components	220
Properties	220
Methods	223
Action	225
Enable	225
Glass	226
Fight the Jaggies	227
Antialiasing	227
Draw the Line	227
Smooth the Jagged Line	229
How Did it Do That?	230
Antialiasing Polygons	230
Points Also	231
Scaling	231
Homogeneous Coordinates	231
Opportunity	231
Blow up a Pyramid	231

PAGE viii
 Fog	236
Declarations	236
Project	237
Identifier Reference	243
Summary	244
Chapter 11
Textures	245
Complicated Surfaces	245
The Problem	245
The Solution	245
Texture Structure	246
Layout, Limits, and a Command	246
Make a Brick	248
Simple Texture, Simple Surface	250
Set Up Texturing	251
More Methods	253
Applying the Texture	253
Run	254
Stretch	255
Mapping Manner	255
Coordinates	256
Run	256
Replication	257
Two for One	257
Parameters	258
Coordinates	258
Run	258
A Brick Wall	260
Multiple Polygons	261
Triangle Strip Square	261
Still More Polygons	262
Windows Bitmaps as Textures	266
A Close One	266
The Project	266
The Differences	267
Run	269
Close, But No Cigar	269
Mipmapping	270
Lighting	275
Add Components	275
Alpha	278
Modulate	279
Identifier Reference	280
Summary	283

PAGE ix
 Chapter 12
Bitmaps	285
Structure	285
Project	285
Properties	285
Layout	286
Fill	287
New Commands	287
Event Handlers	288
Results	289
From Windows Bitmap	289
Monochrome Bitmaps	289
Project	290
Properties	290
Loading	292
Private	293
Event Handlers and Other Methods	294
Results	296
Make It Like the Others	297
Fonts	298
Project	298
New Commands	299
Properties	301
Mostly the Same	304
The Differences	304
Results	306
Interactive	306
Project	306
Properties	307
Private	309
Variables	310
Event Handlers	310
Other Methods	313
Result	316
With Texture	317
Project	317
Properties	317
Private	320
Variables	321
ReadBitmap	321
New Command	322
Event Handlers	322
Other Methods	326
Result	327
Identifier Reference	328
Summary	328

PAGE x
 Chapter 13
Drive-Through	329
Simple Scene	329
Project	329
Properties	330
Private	332
Variables	333
Event Handlers	334
Other Methods	337
Results	338
Opening	339
Project	339
Properties	339
Private	342
Event Handlers	343
Other Methods	344
Results	345
Textured Opening	346
Project	346
Other Differences	346
Copy Texture	346
Prepare to Read Texture	346
Initialize Texture	348
Apply the Texture	348
Result	349
Collision Detection	350
Project	350
Distance	350
Results	354
Textured Objects	355
Project	355
Properties	355
Methods and Other Routines	359
A Light Pole	359
An Array of Vertices	359
A Wall	360
Paint	363
Adjust the Adjust Method	364
Run	365
More	366
Globe	370
Skeleton	372
Stand Up	372
Skin	373
Earth	373
Go for a Spin	375

PAGE xi
 Who Turned Out the Lights?	377
Terrain	378
Project	378
Properties	378
Private	381
Constants, Types, Variables, and Functions	381
Setup and Shutdown	382
The Usual	383
FormPaint	383
TransformPaint	383
Idle	384
Adjust	384
Terrain Implementation	385
Run	387
A Display List	387
Prefiltered Data	388
A Bumpy Ride	389
Identifier Reference	391
Summary	393
Chapter 14
Feedback	395
Commands and Constants	395
Capture the Triangle	397
Project	397
Properties	398
Private	399
Initialize	400
Standard Stuff	400
Rendering	401
Keyboard	401
Feedback	402
Result	405
Go for Three	405
Get the Points	407
Project	407
Properties and Declarations	407
Rendering Code	408
Other Changes in the Methods	409
Run	409
The Formula	410
Bitmap	412
Project	412
Variables	412
Add a Function	412
Rendering Code	413

PAGE xii
 Other Changes in the Methods	414
Run	414
Lines	415
Project	415
Support More Tokens	415
Draw the Lines	416
Run	416
Break Up	417
Project	417
Properties	417
Rendering	417
Run	418
Identification	418
Project	418
New Component	418
Properties	419
New Token	419
Rendering	420
Run	420
Real Coordinates	421
Project	421
Calculate	421
Run	422
Color	423
Texture	423
Project	423
Properties	424
Load a Texture	425
Initializing and Finishing	426
Paint	427
Keyboard	427
Rendering	427
Floor	428
Light	428
Vertex Values	428
Run	429
Shadows	429
Project	429
Properties	430
Implementation	433
Initialize	434
Paint	434
New Keys	435
Rendering	437
Idle	438

PAGE xiii
 Run	438
Capture Data	440
Draw Shadow	442
Run Again	443
Identifier Reference	443
Summary	445
Chapter 15
The End—the Beginning	447
More Buffers	447
More Curves	447
More Effects	447
More Math	448
Matrix Math	448
Algebra	448
Esoterica	448
More Information	448
Kinds of Data	448
Geometry Data	449
Pixel Data	449
More Efficiency	449
More Resources	450
Appendix A
Definitions	451

Index	457


PAGE xiv
 Introduction
About OpenGL
What It Is and Is Not
OpenGL is an Open Graphics Library. Although Silicon Graphics originally developed it for 
one of its own graphics workstations, it is not a proprietary system for a single platform. 
OpenGL is now an industry standardized library of graphics commands that enables 
programs to produce anything from simple lines to 3-D animation. The OpenGL 
Architecture Review Board works to ensure that each implementation complies with the 
library’s defined standard.
OpenGL is strictly about graphics. Unlike DirectX (for Windows), it has nothing to do with 
sound, joysticks, etc. It does one thing—graphics—and it does that one thing very well. It is 
not a resource hog; its two Windows libraries consume less than one megabyte of disk space. 
Although OpenGL gives the graphics programmer a rich and powerful set of commands, it 
provides a clean and simple application programming interface (API). Just the little bit of 
programming work introduced in Chapter 1 is sufficient to produce some graphics effects. 
Most of that work is simply getting Windows to work with OpenGL. Very little effort with 
OpenGL commands themselves is required to produce some basic graphics.
The Windows Implementation
In Windows, OpenGL is implemented in opengl32.dll for the main graphics commands and 
glu32.dll for the utility library. As those filenames imply, OpenGL is only available for 32-bit 
versions of Windows, such as NT and 95 or later. Windows NT, Windows 98, and OEM 
versions of Windows 95 (pre-installed by the computer manufacturer) include the OpenGL 
libraries, but a separately purchased copy of Windows 95 may require downloading the two 
DLLs from the Internet. Alternatively, you may obtain modules from the appropriate 
vendors that take advantage of hardware support in newer video cards. Such hardware-
supporting libraries may have names slightly different from the ones provided by Microsoft.

PAGE xv
 The two dynamic-link libraries (DLLs) contain a powerful set of routines by which one can 
produce truly impressive three-dimensional graphics scenes and animations, especially with 
16-bit graphics or better. The Windows implementation can work with 8-bit palettes, but that 
requires extra coding and has severe functionality limitations. For now, set the system to 
work with high color (16-bit) or true color (24-bit or 32-bit).
About the Reader
The intended audience for this book is the Delphi developer interested in graphics 
programming. While the reader does not require expert-level knowledge to benefit from this 
book, he or she should be familiar with Delphi’s event-driven model, with Object Pascal’s 
Unit structure, and with object-oriented programming. The reader should also be 
comfortable with Windows programming in general.
Not only should the reader be experienced in using the above programming concepts, but he 
or she should also be familiar enough with Delphi’s IDE (integrated development 
environment) to implement those concepts. The book, without detailed explanation, asks the 
reader to perform such tasks as generating specific event handlers or saving a project to a 
new location with a different name.
The reader also needs to know enough about the user interface for a suitable 32-bit version 
of Windows to be able to set the color depth to 16 or 24 bits. Of course, the reader must have 
direct access to Windows NT or 95 or later, as well as to a 32-bit version of Delphi.
Finally, the reader needs to have an interest in graphics programming. That’s it! The reader 
does not need to start out with much graphics knowledge, just an interest.
If you have read this far without cringing, this book must be for you. No longer are you (the 
reader) treated as if you were off in another room being discussed behind your back. 
Congratulations! Welcome to the wonderful world of graphics programming with Delphi 
and OpenGL.
About Delphi
OpenGL produces various graphic images in a window, which in Delphi is a form. The place 
to perform the OpenGL commands is Delphi event handlers. Since the Windows version of 
OpenGL is a 32-bit DLL, use at least Delphi 2. Finally, Delphi 3 and 4 bring a required 
feature: They come with an interface unit for OpenGL and its utility library. Using OpenGL 
with 

PAGE xvi
 Delphi requires a 32-bit version of Delphi, an OpenGL interface unit, and a working 
knowledge of developing Delphi applications.
The Interface Unit
If your version of Delphi does not have an OpenGL interface unit, you can download one 
from the Internet. Some links are available on the companion CD. Wherever you obtain the 
interface unit, simply reference it in the uses clause of any unit that invokes OpenGL 
commands. Be sure to place the .DCU (Delphi Compiled Unit) file in Delphi’s library path; 
otherwise give a full path to it in the .DPR (Delphi PRoject) file. For example, in Delphi 3, the 
interface file is in the LIB directory under the DELPHI 3 directory. To see or modify the 
library path, go to the Library tab of the Environment Options window.
Roll Your Own Interface
Actually, this book provides enough information about the OpenGL commands to construct 
an interface unit. A sample format of an interface unit follows. Notice the call in the 
initialization section that disables floating-point exceptions. Keep in mind that Silicon 
Graphics, Inc., holds the copyright to the OpenGL libraries, and the required unit is an 
interface to its libraries.

unit OpenGL;

interface

uses
  Windows;

const
  GL_COLOR_BUFFER_BIT = $4000;
Type
  HGLRC = THandle;
  GLenum = cardinal;
  GLbitfield = cardinal;
  GLfloat = single;
  GLclampf = single;

{regular commands}
procedure glClear(mask:GLbitfield); stdcall;
procedure glClearColor(red,green,blue,alpha:GLclampf); stdcall;
function  glGetError: GLenum; stdcall;

{utility commands}



PAGE xvii
 implementation

{regular commands}
procedure glClear; external opengl32;
procedure glClearColor; external opengl32;
function glGetError; external opengl32;

{utility commands}

initialization 
  Set8087CW($133F);
end.
About This Book
Style Convention
The typestyle used in this book is simple and straightforward. Bold type, larger font sizes, 
and capitalization indicate levels of organization of the topics in a fairly obvious way. Italics 
introduce new terms that are defined in Appendix A. Code examples and listings use a 
monospaced font, with certain key words, such as procedure, in boldface in a manner 
similar to the syntax highlighting used in the Delphi editor. The interface example above 
illustrates these techniques.
Content
This is a hands-on, learn-by-doing book. For greatest benefit from the book, place it near the 
computer, turn on the computer, and launch Delphi. Create a separate directory or folder in 
a suitable location for placing various OpenGL projects. Naming the directory OpenGL is a 
good choice.
The companion CD-ROM contains the source code for all the example programs, along with 
the text of the book and Internet links. Please see the last page of this book for more 
information.
Some people may prefer to develop a thorough background before they write their first line 
of code. I am not one of those people. While I do like to have a good understanding of the 
tools I am using and the tasks I am (hopefully) accomplishing, I also like to dive in and start 
doing something! I wrote this book the way someone like me would like to see books 
written.
Code writing begins as soon as possible, and explanation and theory appear as needed, 
building the concepts a little at a time. Each step has just enough explanation to use the 
program code under development, without a dumping of the whole load at once. Each place 
in the book that uses 

PAGE xviii
 a new OpenGL command or other identifier gives its precise declaration as well as an 
explanation of the way to use it in the given context. In addition, a reference section at the 
end of each chapter lists all newly introduced OpenGL identifiers alphabetically with a brief 
description.
This book generally takes each concept from the simple to the complex, making the basic 
steps obvious before obscuring them with a lot of detail. Do not be alarmed when some 
portions of code do not work very well. Sometimes the best way to show the reason for 
doing something a certain way is to take the direct approach first and, by the results, 
demonstrate why it must be a little different.
In short, this is a learning adventure, so fasten your seat belts, launch Delphi, and start!

PAGE xix
 





To my wife, Jill, who suffered through widowhood 
and my son, Nathan, who bravely endured being 
an orphan while this book was in creation.

PAGE xx


Chapter 1
The First Steps

In an ideal world, only a few lines of code would produce graphics right now. Alas, in the 
real world, some preliminary housekeeping is necessary. In this chapter you will learn how 
to make the necessary connection between Windows and OpenGL. The plan is to produce 
some graphics as soon as possible, so be patient with the necessary setup. In the spirit of 
getting to the interesting code as soon as possible, this chapter is fairly short and ends with 
actual graphics output. Although it is just a simple colored background, it shows that the 
code setting it up successfully did its job. Though trivial, this simple scene is not just a token 
reward for enduring a necessary evil. The code for producing it forms the basis for more 
advanced output.
Directory Structure
To use this book effectively you will be writing code along the way and will create and save 
a number of projects, so pick a drive for keeping these OpenGL projects. Create a directory 
and name it OpenGL. Under that directory, create subdirectories named Chapter.1, 
Chapter.2, etc. Some chapters may produce several projects, and these each have their own 
subdirectories under the chapter directory. You can, of course, organize in some other way, 
but the book proceeds as if you use the recommended directory structure and names for 
your projects, so you must translate as you go.
Preliminary Code
Getting Ready to Start to Begin
OpenGL is intended to be fairly platform-independent, rather than just for Windows. 
Therefore, OpenGL needs a link to Windows, using some special 

PAGE 1
 structures and API extensions in Windows to provide this link. In Delphi a good place for 
the connection is within a form’s OnCreate event handler.
Create a new project and save it in a new directory under the OpenGL directory created 
earlier. Name the new directory Chapter.1, name the project First.Dpr, and name the main 
unit First1.Pas. Double-click the main form to set up the form’s OnCreate event handler. 
Define a variable of type TPixelFormatDescriptor and fill it in. Defining the pixel format with 
this structure permits describing some properties that the Windows GDI (graphical device 
interface) needs in order to work with OpenGL.

procedure TForm1.FormCreate(Sender: TObject);	
var	
  pfd: TPixelFormatDescriptor;	
  FormatIndex: integer;	
begin	
  fillchar(pfd,SizeOf(pfd),0);	
  with pfd do	
  begin	
    nSize      := SizeOf(pfd);	
    nVersion   := 1; {The current version of the descriptor is 1}	
    dwFlags    := PFD_DRAW_TO_WINDOW or PFD_SUPPORT_OPENGL;	
    iPixelType := PFD_TYPE_RGBA;	
    cColorBits := 24; {support 24-bit color}	
    cDepthBits := 32; {depth of z-buffer}	
    iLayerType := PFD_MAIN_PLANE;	
  end; {with}	
  FormatIndex := ChoosePixelFormat(Canvas.Handle,@pfd);	
end; {FormCreate}		
Inform the system of the desired properties by calling ChoosePixelFormat, passing it the 
form’s device context and a pointer to the descriptor. Depending on what the device context 
can support, the contents of the descriptor may be altered to reflect the best approximation 
of the request. Windows uses device contexts, and OpenGL uses rendering contexts. This code 
attempts to map them to each other.
Device Context
The use of a device context is not obvious in this code. In Delphi, the Canvas property is a 
wrapper for a window’s device context, and Canvas.Handle is the handle to the device 
context. This code uses native Delphi where possible, leaving out error checking and other 
details for clarity. Be patient for a while; the code improves later.

PAGE 2
 Descriptor Fields
This descriptor has a number of fields that can remain zero or the equivalent, so explicit 
assignment statements were unnecessary. Here is a quick look at the rest of the contents of 
the descriptor to meet the current needs:
? nSize. Windows structures often require the size of the structure as part of the structure 
itself. This field follows that tradition.
? nVersion. The version number of the descriptor structure is 1, so a 1 must be stored here.
? dwFlags. The bits are set by OR-ing together some predefined constants. 
PFD_DRAW_TO_WINDOW has an obvious meaning; you could be drawing to a bitmap in 
memory instead. PFD_SUPPORT_OPENGL is certainly a desired feature. Keep in mind that 
this code does not yet do anything with OpenGL; it just gets Windows ready for OpenGL. 
These are Windows structures and Windows API calls. Windows does not assume the code 
will work with OpenGL unless the code tells it to. ChoosePixelFormat attempts to find a 
pixel format with the same flags set as those passed to it in the descriptor.
? iPixelType. Use RGBA (red, green, blue, alpha) pixels. Explanation of Alpha comes later.
? cColorBits. Support 24-bit color.
? cDepthBits. Set the depth of the z-buffer to 32. Explanation of depth comes later.
? iLayerType. The current version only supports the main plane.
ChoosePixelFormat is a function that returns an integer, stored in FormatIndex. It returns 
zero to indicate an error or a positive number as an index to the appropriate pixel format. 
SetPixelFormat sets the pixel format of the device context, using that index. Now the bottom 
of the OnCreate event handler looks like this:

  FormatIndex := ChoosePixelFormat(Canvas.Handle,@pfd);	
  SetPixelFormat(Canvas.Handle,FormatIndex,@pfd);	
end; {FormCreate}	
The functions receive the handle to the window’s device context and change the pixel format 
of the window. So far none of the code produces visible results other than a standard blank 
form, but keep coding. OpenGL will shine forth soon.

PAGE 3
 Starting to Begin
Rendering Context
Now that the code takes care of the pixel format, proceed to the rendering context. Add 
GLContext to the private section of the form:

type	
  TForm1 = class(TForm)	
    procedure FormCreate(Sender: TObject);	
  private	
    GLContext  : HGLRC;	
  public	
    { Public declarations }	
  end;	
The HGLRC type is a handle to an OpenGL rendering context type. Here is its declaration 
for the interface unit:

type	
  HGLRC = THandle;	
The new variable receives the result of wglCreateContext. This is one of several wgl 
(Windows-GL) functions for managing rendering contexts. Place this call at the bottom of 
the event handler:

  FormatIndex := ChoosePixelFormat(Canvas.Handle,@pfd);	
  SetPixelFormat(Canvas.Handle,FormatIndex,@pfd);	
  GLContext := wglCreateContext(Canvas.Handle);	
end; {FormCreate}	
As the name implies, this function creates the OpenGL rendering context needed by the 
window. Now make it current:

  FormatIndex := ChoosePixelFormat(Canvas.Handle,@pfd);	
  SetPixelFormat(Canvas.Handle,FormatIndex,@pfd);	
  GLContext := wglCreateContext(Canvas.Handle);	
  wglMakeCurrent(Canvas.Handle,GLContext); 	
end; {FormCreate}	
Cleanup
You should always put away your tools when finished with them, and you should always 
put away Windows resources when finished with them. Go to the events page of the Object 
Inspector for the form. Double-click the OnDestroy event. Fill in the event handler as 
follows:

procedure TForm1.FormDestroy(Sender: TObject);	
begin	
  wglMakeCurrent(Canvas.Handle,0);	


PAGE 4
   wglDeleteContext(GLContext);	
end;	
Passing a zero to wglMakeCurrent makes the previously current context no longer current. 
Now that GLContext is no longer current, delete it with wglDeleteContext.
Begin
Uses Clause
So far the code is nothing but Windows and Delphi code. The time has arrived for some 
OpenGL code. Add OpenGL to a uses clause. Put it in the interface section in order to use an 
OpenGL type in the interface.

uses	
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms,	
  Dialogs, OpenGL, Menus;	
Paint
Again go to the events page of the form’s Object Inspector. This time double-click the 
OnPaint event. This event fires every time the window (form) needs repainting, such as 
when the form has been partially (or fully) covered and then exposed again. It also fires 
when the form first shows. Make this event handler like the following:

procedure TForm1.FormPaint(Sender: TObject);	
begin	
  {background}	
  glClearColor(0.0,0.4,0.0,0.0);	
  glClear(GL_COLOR_BUFFER_BIT);	
	
  {error checking}	
  errorCode := glGetError;	
  if errorCode<>GL_NO_ERROR then	
    raise Exception.Create('Error in Paint'#13+	
      gluErrorString(errorCode));	
end;	
The given OpenGL commands have the following declaration:

procedure glClear(mask:GLbitfield); stdcall;	
procedure glClearColor(red,green,blue,alpha:GLclampf); stdcall;	
Their implementations are simply references to the DLL that contains the code and look like 
this:


PAGE 5
 procedure glClear; external opengl32;	
procedure glClearColor; external opengl32;	
Error Checking
Notice this code introduces a little bit of error checking. This method is small enough (so far) 
that including the error checking code at this point does not obscure the main activity.
Use glClearColor to set the background color. Each color component ranges in floating-point 
value from 0 to 1. In this case the color has no red, 0.4 units of green, and no blue. Set alpha 
to 0 for now. The background color is a dark green (a little less than half the maximum 
intensity of green, which would be 1.0).
This is a good time to introduce an OpenGL type. Much of this graphics library, including 
color specifications, uses floating-point numbers. To keep itself portable, OpenGL defines a 
type called GLfloat. Under Windows on Intel processors and compatibles, GLfloat is the 
same as a Delphi single, which is an IEEE (Institute for Electrical and Electronics Engineers) 
32-bit floating-point value. In the interface unit is a type declaration like this:

  GLfloat = single;	
Next call glClear and pass it a bit mask that tells it what to clear. This clears the buffers 
enabled for writing colors. For error checking this segment of code introduces the function 
glGetError, which appears often. It returns a number of type GLenum, which is an alias for 
cardinal. The following type declaration appears in the interface unit:

  GLenum = Cardinal;	
Store the result in errorCode, which you should add to the private section of the form 
declaration.

errorCode: GLenum;	
Compare the result to GL_NO_ERROR, a predefined constant of obvious meaning. If there is 
an error, raise an exception. The application handles this exception simply by displaying the 
message. The message includes another new function, gluErrorString, from the OpenGL 
utility library. It returns a pointer to some text (PChar). The text represents the meaning of 
the error number passed to the function. Delphi’s string concatenation knows how to handle 
that PChar.
The constant has this declaration:

  GL_NO_ERROR = 0;	

PAGE 6
 Here is the declaration for the function:

function gluErrorString(errCode:GLenum):PChar; stdcall;	
Since this function name begins with “glu,” its code is found in the other DLL:

function gluErrorString; external glu32;	
Now, save the program (you have been warned!) and compile it. Do not run it from the 
Delphi IDE (integrated development environment). OpenGL programs sometimes crash if 
launched from the IDE even though they work fine when run independently. That is why it 
was so important to save your work before starting, just in case you got stubborn and ran it 
from the IDE anyway. You might be lucky, but you might not. Use the Windows Run 
command, giving it the appropriate path. Behold! A dark green form!
More Device Context
Some of the code just written needs improving. The time has arrived to demonstrate why. 
Drag the form by its title bar left and right and up and down, so that parts are obscured and 
re-exposed repeatedly. Partially cover it with some other window, then uncover it. Do this a 
number of times until something strange happens. Eventually some part of the form fails to 
show green after exposure.
While Canvas.Handle is the handle to the device context, it is not reliable for these purposes, 
because it is generated on the fly when it is referenced and then released. So the device 
context matched up with the rendering context at one point may not even exist at another 
point. The rendering context would not then have a valid association. The application needs 
a handle it can control. In the private section of the form, define glDC:

type	
  TForm1 = class(TForm)	
    procedure FormCreate(Sender: TObject);	
    procedure FormDestroy(Sender: TObject);	
    procedure FormPaint(Sender: TObject);	
  private	
    GLContext: HGLRC;	
    glDC: HDC;	
    errorCode: GLenum;	
  public	
    { Public declarations }	
  end;	
In the FormCreate method (OnCreate handler), call getDC and replace all references to 
Canvas.Handle with glDC:


PAGE 7
   glDC := getDC(handle);	
  FormatIndex := ChoosePixelFormat(glDC,@pfd);	
  SetPixelFormat(glDC,FormatIndex,@pfd);	
  GLContext := wglCreateContext(glDC);	
  wglMakeCurrent(glDC,GLContext);	
end; {FormCreate}	
Also in the FormDestroy method (OnDestroy handler) replace Canvas.Handle with glDC:

procedure TForm1.FormDestroy (Sender: TObject);	
begin	
  wglMakeCurrent(glDC,0);	
  wglDeleteContext(GLContext);	
end;	
Save the code again, compile it, then run it (independently). Now it works much more 
reliably.
More Error Handling
In the FormCreate method is the basic setup required to use OpenGL under Windows. Now 
that the code has appeared in its simplicity, it is time to complicate it with error checking. It 
is not exciting, but all good software requires some kind of error handling or reporting. With 
these learning programs, if something fails, at least some kind of clue should appear. Add a 
Boolean to the form’s private declaration. As a field of a TObject descendant, it is initialized 
to false. If you are not familiar with this initialization, see Delphi’s online help for the 
InitInstance method under TObject.

  private	
    GLContext: HGLRC;	
    glDC: HDC;	
    errorCode: GLenum;	
    openGLReady: boolean;	
In the FormPaint method make a test of the value of OpenGLReady and exit if false. There is 
no point in calling OpenGL commands if they were not even set up.

procedure TForm1.FormPaint(Sender: TObject);	
begin	
  if not OpenGLReady then	
    exit;	
  {background}	
  glClearColor(0.0,0.4,0.0,0.0);	
  glClear(GL_COLOR_BUFFER_BIT);	
	
  {error checking}	


PAGE 8
   error := glGetError;	
  if error<>GL_NO_ERROR then	
    raise Exception.Create('Error in Paint'#13+	
      gluErrorString(errorCode));	
end;	
FormCreate also needs to test some function results. On failure, call GetLastError; this 
returns a Windows error code. If execution makes it all the way to the end of the method, the 
reward is setting OpenGLReady to true.

  glDC := getDC(handle);	
  FormatIndex := ChoosePixelFormat(glDC,@pfd);	
  if FormatIndex=0 then	
    raise Exception.Create('ChoosePixelFormat failed '+	
      IntToStr(GetLastError));	
	
  if not SetPixelFormat(glDC,FormatIndex,@pfd) then	
    raise Exception.Create('SetPixelFormat failed '+	
      IntToStr(GetLastError));	
	
  GLContext := wglCreateContext(glDC);	
  if GLContext=0 then	
    raise Exception.Create('wglCreateContext failed '+	
      IntToStr(GetLastError));	
	
  if not wglMakeCurrent(glDC,GLContext) then	
    raise Exception.Create('wglMakeCurrent failed '+	
      IntToStr(GetLastError));	
 	
  OpenGLReady := true;	
end; {FormCreate}	
In the FormDestroy method you need to shut down OpenGLReady so that any last-moment 
event messages do not cause code that is no longer valid to execute.

procedure TForm1.FormDestroy(Sender: TObject);	
begin	
  OpenGLReady := false;	
  wglMakeCurrent(glDC,0);	
  wglDeleteContext(GLContext);	
end;	
Be sure to save your work. The next chapter starts with this code as the foundation.


PAGE 9
 Identifier Reference
Identifier			Description
ChoosePixelFormat
A Windows function that receives a handle to a device context and a pointer to a pixel 
format descriptor. It returns an integer index to Windows’ best attempt at a pixel format 
matching the specifications in the descriptor.
getDC
A Windows function that receives a handle to a window and returns a handle to that 
window’s device context.
GetLastError
A Windows function that returns an integer representing the most recent error.
glClear
An OpenGL command that erases a buffer, filling it with the most recently specified 
background color. If the buffer is the color buffer, then the window is filled with the 
background color.
glClearColor
An OpenGL command that receives red, green, blue, and alpha floating-point values and 
sets the background color to be used by glClear.
GLenum
An OpenGL numeric type that maps to Delphi cardinal.
GLfloat
An OpenGL numeric type that maps to Delphi single.
glGetError
An OpenGL function that returns an integer value representing an error flag set by an 
OpenGL command, and clears that flag. If no error flags are set when called, the function 
returns the value of GL_NO_ERROR.
gluErrorString
An OpenGL utility function that receives an error number (such as returned by glGetError) 
and returns a pointer to a human-readable string (null-terminated) describing the associated 
OpenGL error flag.
GL_NO_ERROR
An OpenGL constant to represent a successful command.
HDC
A Windows type. Handle to a device context.

PAGE 10
 Identifier			Description
HGLRC
A Windows/OpenGL type. Handle to a rendering context.
SetPixelFormat
A Windows function that specifies the pixel format to use with the given device context. 
While ChoosePixelFormat merely obtains an index to a pixel format, this function sets that 
pixel format as the one to use. The function receives a handle to the device context, an index 
to the desired pixel format (such as the one returned by ChoosePixelFormat), and a pointer 
to the pixel format descriptor (as modified by ChoosePixelFormat). It returns true on success 
or false on failure. Call GetLastError to learn more about the failure.
TPixelFormatDescriptor
A Windows type. A structure of this type contains the fields needed to select and set a pixel 
format.
wglCreateContext
A Windows function that creates a rendering context for the specified device context. It 
receives a handle to the device context and returns a handle to the rendering context.
wglDeleteContext
A Windows function that deletes the specified rendering context. It receives the handle to 
the rendering context and returns true on success and false on failure.
wglMakeCurrent
A Windows function that makes the specified rendering context current (the one available 
for use) with the specified device context. It receives the handle to the device context and the 
handle to the rendering context and returns true on success and false on failure.


PAGE 11
 Summary
Chapter 1 showed how to:
? Link the Windows graphics device context with the OpenGL rendering context.
? Use actual OpenGL commands to fill a window with a color.
? Perform some basic error checking with OpenGL commands.
 
PAGE 12

Chapter 2
Basics
OpenGL majors in three-dimensional graphics, but computer monitors operate with 
essentially flat, two-dimensional screens. Representing a three-dimensional space on a two-
dimensional surface requires projection. Each point in the space projects mathematically to an 
appropriate point on the surface. This chapter introduces OpenGL commands to identify the 
part of the window to receive the projection, to set up the projection, and to draw an object. 
Included with the projection is a clipping volume, outside of which no drawing happens. 
These foundational concepts and commands permit drawing something besides a mere 
background. This chapter also brings another Delphi event handler into use.
Use the first chapter’s final code as the starting point in this chapter. If you have not already 
done so, create a new directory under the OpenGL directory and name it Chapter.2. In 
Delphi load the First.Dpr project and do a File|Save Project As in the new directory, naming 
it Rect.Dpr. Do File|Save As for the First1.Pas unit. Name it Rect1.Pas in the new directory.
Viewport
Event Handler
Drawing a green background is a wonderful accomplishment, but by itself, a green 
background is not very exciting. Drawing a shape on the background is much more 
interesting. For now, drawing a rectangle is the next great advance in OpenGL knowledge. 
The place for drawing is the form’s OnPaint event handler, but setting up the drawing area 
also needs the OnResize handler. Go to the Events page of the form’s Object Inspector and 
double-click OnResize. In the event handler place a call to glViewport, a call to glOrtho, and 
a modicum of error checking:


PAGE 13
 procedure TForm1.FormResize(Sender: TObject);	
begin	
  if not openGLReady then	
    exit;	
  glViewport(0,0,ClientWidth,ClientHeight);	
  glOrtho(-1.0,1.0,-1.0,1.0,-1.0,1.0);	
	
  errorCode := glGetError;	
  if errorCode<>GL_NO_ERROR then	
    raise Exception.Create('FormResize:'+gluErrorString(errorCode));	
end;	
Declaration
Remember that OpenGL is fairly platform-independent, so it does not inherently know 
anything about the coordinate system of the operating environment. Use glViewport to 
make a connection between the coordinates used by the windowing system (Microsoft 
Windows) and the coordinates used by OpenGL. Its declaration looks like this:

procedure glViewport(x,y:GLint; width,height:GLsizei); stdcall;	
GLint and GLsizei both map to integer. The meaning of width and height are as expected, 
but x and y need more attention. Windows measures from the upper left corner of a region, 
but OpenGL measures from the lower left, as if it were relating to the first quadrant of a set 
of Cartesian coordinates. The main thing to note is that the y direction runs opposite to the 
familiar Windows coordinates, but fits more naturally with coordinates commonly used in 
mathematics.
If you are building your own interface unit, Listing 2-1 encompasses the declarations used 
thus far, plus a few more that are just waiting in the wings. Add other declarations when 
they appear.

unit OpenGL;

interface

uses
  Windows;

const
  GL_COLOR_BUFFER_BIT = $4000;
  GL_NO_ERROR = 0;
Type
  HGLRC = THandle;
  GLenum = cardinal;
  GLbitfield = cardinal;


PAGE 14
   GLfloat = single;
  GLclampf = single;
  GLint = integer;
  GLsizei = integer;

{regular commands}
procedure glClear(mask:GLbitfield); stdcall;
procedure glClearColor(red,green,blue,alpha:GLclampf); stdcall;
function  glGetError: GLenum; stdcall;
procedure glViewport(x,y:GLint; width,height: GLsizei); stdcall;

{utility commands}
function gluErrorString(errCode:GLenum):PChar; stdcall;

implementation

{regular commands}
procedure glClear; external opengl32;
procedure glClearColor; external opengl32;
function glGetError; external opengl32;
procedure glViewport; external opengl32;

{utility commands}
function gluErrorString; external glu32;

initialization
  Set8087CW($133F);
end.
Listing 2-1. Beginning of an interface unit.
Analogy
Think of the form as a wall. Use glViewport to cut a hole in the wall through which to view a 
scene. In this case, cut a hole the size of the entire client area of the form, so all that is left is 
the frame. A smaller “opening” is possible:

  glViewport(50,30,80,70);	
The above example starts at 50 pixels from the left side of the form and 30 pixels up from the 
bottom of the form. It is 80 pixels wide and 70 pixels tall (going up from the bottom). It may 
not be a good idea to specify such absolute coordinates without specifying a non-resizable 
border or limiting the minimum window size.

PAGE 15
 
? Note:
glViewport limits the portion of the window in which the scene is rendered, but does not 
restrict the area in which the background is cleared. In the wall analogy, think of the 
background color as something sprayed onto the stage for the scene. Not only does the spray 
go through the hole in the wall, but the spray also covers the entire wall!
In simplest terms, rendering is drawing with OpenGL. To render a scene is to use OpenGL 
constructs and commands to cause pixels to illuminate in a pattern that represents the scene 
envisioned by the programmer.
Orthographic Projection
Meaning
The call to glOrtho defines a clipping volume, which is a region in space in which objects can 
be seen. Any part of a scene that lies outside of the region is not visible (clipped). Of course, 
though the rendered scenes may be conceptually three-dimensional, they must be projected 
onto a two-dimensional screen. The available projections are orthographic projection and 
perspective projection. (You get one guess as to which kind glOrtho uses.) In orthographic 
projection, also known as parallel projection, an object appears the same size whether it is 
near or far from the viewer. Showing a simple, flat rectangle certainly does not need 
anything more complicated. Here is the declaration:

procedure glOrtho(left,right,bottom,top,zNear,zFar: GLdouble); stdcall;	
GLdouble is the same thing as a Delphi double, an IEEE 64-bit floating-point value.

  GLdouble = double;	
Left, right, bottom, and top describe the same area as the glViewport, but with programmer-
defined values for the edges. The rendering commands use values relative to these. In the 
above FormResize method listed above in the Event Handler section, –1.0 is the new value 
given to the left side of the viewport, and 1.0 is the value for the right side. The bottom of the 
viewport is now –1.0, and the top is 1.0. The z values represent directions perpendicular to 
the screen. The zNear value represents the z-axis coordinate of the clipping region face 
nearest the viewer, while zFar designates the face farthest from the viewer, deepest into the 
screen.  Those values represent in three-dimensional space whatever the programmer wants 
them to mean.

PAGE 16
 Flexibility
Here is another example of glOrtho with glViewport:

  glViewport(0,0,ClientWidth,ClientHeight);	
  glOrtho(3.204,17.2,-11.0,1.45,5.025,41.0);	
The call to glViewport remains the same to isolate the effect, in this example, of changing the 
values given to glOrtho. For example, the clipping region now has a value of 17.2 for its right 
side. It is still located at the same place as before, which is the right side of the viewport, at 
ClientWidth. The value of 17.2 was just convenient. Delphi’s Object Pascal language has a 
similar concept for arrays:

var	
  first: array[0..9] of integer;	
  second: array[1..10] of integer;	
  third: array[100..109] of integer;	
All three of the above variables are equivalent arrays of ten integers, but with different 
starting points. Why not just use zero-based arrays? After all, that is all that is available in 
some programming languages. The answer is that they are declared in a manner convenient 
for the programmer. An alternate form may allow less typing in the coding of an algorithm, 
or may more obviously represent some real-world concept.

PAGE 17
 Similarly the programmer has the freedom to assign arbitrary values to the orthographic 
clipping region. He may choose values for computational convenience or for better 
conceptual representation of the scene. There is a little bit of order imposed on the glOrtho 
universe, however. Negative z values are in front of the viewport in the window, and 
positive z-values are behind the viewport. Placing the clipping region entirely in front of the 
viewport strains the hole-in-the-wall analogy, but the comparison has already served its 
purpose.
Drawing
Command Placement
At last the time has arrived to draw something: a rectangle. As expected, the place to draw 
the rectangle is in the OnPaint event handler. The drawing commands need to appear 
between glBegin and glEnd; the latter requires no parameters while, the former takes a 
parameter to set the drawing mode:

procedure glBegin(mode:GLenum); stdcall;	
procedure glEnd; stdcall;	
Between glBegin and glEnd goes a list of vertices, and possibly a few commands related to 
the vertices. Each vertex is simply a point in space specified by the glVertex command. This 
command takes many forms, but only a few of them are appropriate here:

procedure glVertex2f (x,y: GLfloat); stdcall;	
procedure glVertex3f (x,y,z: GLfloat); stdcall;	
procedure glVertex4f (x,y,z,w: GLfloat); stdcall;	
State Variables
In the FormPaint method (see Listing 2-2, below), not only does glClearColor set the clear 
color (background), but glColor sets the foreground color for drawing. These calls set state 
variables which remain the same until similar calls explicitly change them. If the command 
says to draw in blue, all drawing is done in blue until another call changes the state. 
OpenGL’s state variables are in DLLs, not directly accessible to the programmer. You read 
and write OpenGL’s state variables via function and procedure calls. The glColor command 
is before glBegin in the current event handler method, but it is among the limited set of 
commands that can appear between glBegin and glEnd, since color changes are often 
necessary in the midst of a drawing sequence.


PAGE 18
 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);

  {red rectangle}
  glColor3f(0.5,0.0,0.0);
  glBegin(GL_POLYGON);
    glVertex2f(0.25,0.25);
    glVertex2f(0.75,0.25);
    glVertex2f(0.75,0.75);
    glVertex2f(0.25,0.75);
  glEnd;
  glFlush;

  errorCode := glGetError;
  if errorCode<>GL_NO_ERROR then
    raise Exception.Create(‘Error in Paint’#13+
      gluErrorString(errorCode));
end;
Listing 2-2. Setting state variables in FormPaint.
Here is the declaration of the glColor command used in the FormPaint method:

procedure glColor3f(red,green,blue:GLfloat); stdcall;	
The new constant has this declaration:

  GL_POLYGON = 9;	
Since glColor3f has arguments of 0.5, 0.0, and 0.0, OpenGL is drawing in half-intensity red. 
Alpha is not specified, so it gets 1.0 as a default. In the calls to glVertex2f, no z value is 
passed, so all of the rectangle lies in the z = 0.0 plane. That means the rectangle lies in the 
same plane as the viewport, neither in front nor behind. Recall that glOrtho received 
arguments of –1.0 for left and 1.0 for right. Since the x values in glVertex are all positive, the 
rectangle is entirely on the right side of the viewing area. The same is true of the y values, so 
the rectangle lies entirely in the upper half of the viewing area. Remember that all the 
drawing coordinates are relative to the values defining the clipping region, so changing the 
arguments to glOrtho could affect the size, shape, and position of the drawing.

PAGE 19
 Drawing Mode
Note the argument passed to glBegin. GL_POLYGON informs OpenGL that the subsequent 
vertex list establishes the corners of a polygon. The number of vertices between glEnd and 
glBegin is the number of vertices in the polygon, which in this case is a quadrilateral. The first 
side of the polygon is a line from the first vertex to the second. The second side of the 
polygon is a line from the second vertex to the third, and so on until the polygon closes with 
a line from the last vertex to the first. OpenGL knows to do all that simply by being given 
GL_POLYGON. It further knows to fill in the interior of the polygon with the color 
previously specified in glColor. What if a complete scene needs more than one type of 
figure? Make multiple glBegin/glEnd pairs, passing the appropriate mode arguments.
Another new command introduced here is glFlush. For the sake of efficiency, the graphics 
engine does not necessarily start drawing as soon as it receives its first command. It may 
continue to accumulate information in a buffer until given a little kick. The call to glFlush 
forces it to start drawing.

  procedure glFlush; stdcall;	
To illustrate another drawing mode, GL_LINES, Listing 2-3 is the code for the OnPaint event 
handler that produced Figure 2-1, including a couple of new commands needed for that 
purpose.

  {white background}
  glClearColor(1.0,1.0,1.0,0.0);
  glClear(GL_COLOR_BUFFER_BIT);

  {top of the form}
  glColor3f(0.0,0.0,1.0);
  glRectf(-300.0,300.0,300.0,345.0);

  glLineWidth(2.0);
  glColor3f(0.0,0.0,0.0);
  glBegin(GL_LINES);
    {left side}
    glVertex2f(-300.0,-300.0);
    glVertex2f(-300.0,300.0);

    {bottom}
    glVertex2f(-300.0,-300.0);
    glVertex2f(300.0,-300.0);

    {right side}
    glVertex2f(300.0,-300.0);
    glVertex2f(300.0,300.0);
  glEnd;


PAGE 20
             
  glLineWidth(1.0);
  glBegin(GL_LINES);
    {view port measure left to right}
    glVertex2f(-300.0,-340.0); {line}
    glVertex2f(300.0,-340.0);

    glVertex2f(-300.0,-320.0); {end}
    glVertex2f(-300.0,-360.0);

    glVertex2f(-300.0,-340.0); {end}
    glVertex2f(-290.0,-330.0);

    glVertex2f(-300.0,-340.0); {arrow}
    glVertex2f(-290.0,-350.0);

    glVertex2f(300.0,-320.0); {arrow}
    glVertex2f(300.0,-360.0);

    glVertex2f(300.0,-340.0); {arrow}
    glVertex2f(290.0,-330.0);

    glVertex2f(300.0,-340.0); {arrow}
    glVertex2f(290.0,-350.0);

    {viewport measure bottom to top}
    glVertex2f(-325.0,-300.0); {line}
    glVertex2f(-325.0,300.0);

    glVertex2f(-312.0,-300.0); {end}
    glVertex2f(-337.0,-300.0);

    glVertex2f(-312.0,300.0); {end}
    glVertex2f(-337.0,300.0);

    glVertex2f(-325.0,-300.0); {arrow}
    glVertex2f(-332.0,-285.0);

    glVertex2f(-325.0,-300.0); {arrow}
    glVertex2f(-318.0,-285.0);

    glVertex2f(-325.0,300.0); {arrow}
    glVertex2f(-332.0,285.0);

    glVertex2f(-325.0,300.0); {arrow}
    glVertex2f(-318.0,285.0);

    {ortho measure left to right}
    glVertex2f(-300.0,-260.0); {line}


PAGE 21
     glVertex2f(300.0,-260.0);

    glVertex2f(-300.0,-260.0); {arrow}
    glVertex2f(-288.0,-272.0);

    glVertex2f(-300.0,-260.0); {arrow}
    glVertex2f(-290.0,-250.0);

    glVertex2f(300.0,-260.0); {arrow}
    glVertex2f(290.0,-250.0);

    glVertex2f(300.0,-260.0); {arrow}
    glVertex2f(288.0,-272.0);

    {ortho measure bottom to top}
    glVertex2f(-275.0,-300.0); {line}
    glVertex2f(-275.0,300.0);

    glVertex2f(-275.0,-300.0); {arrow}
    glVertex2f(-282.0,-285.0);

    glVertex2f(-275.0,-300.0); {arrow}
    glVertex2f(-268.0,-285.0);

    glVertex2f(-275.0,300.0); {arrow}
    glVertex2f(-282.0,285.0);

    glVertex2f(-275.0,300.0); {arrow}
    glVertex2f(-268.0,285.0);
  glEnd;
Listing 2-3. Using GL_LINES.
When the drawing mode is GL_LINES, each pair of calls to glVertex defines a line segment. 
The declaration for the new mode is:

GL_LINES = 1;	
Rectangles are so common that OpenGL has a special command just for drawing them. The 
first two parameters define one vertex, and the next two define the opposite vertex. The 
rectangle assumes z = 0. The float version just introduced is one of many variations. Also 
introduced is a command for setting the width of the lines in pixels. The default width is 1.0.

procedure glRectf(x1,y1,x2,y2:GLfloat); stdcall;	
procedure glLineWidth(width:GLfloat); stdcall;	

PAGE 22
 Additional Considerations
Naming Convention
You may have noticed by now that all the OpenGL command names begin with “gl.” The 
graphics library has a well-defined naming convention, which this book mentions from time 
to time. So far the “gl” prefix has dominated, which does apply to all OpenGL commands. A 
“glu” prefix has also made a cameo appearance. It is the standard for all commands of the 
utility library. Chapter 1 presented the “wgl” prefix for the extensions to Microsoft Windows 
that support OpenGL rendering. Similarly the X Window System (for UNIX) extensions use 
a “glx” prefix, and OS/2 Presentation Manager extensions use “pgl.”
The naming convention does not stop with prefixes. The glVertex commands, for example, 
have a number suffix, such as 2, 3, or 4, indicating the number of parameters the command 
requires. A later chapter explains the fourth (w) parameter, but for now, if it receives no 
explicit value, it receives a value of 1.0. In the two-parameter version, even the z value goes 
without assignment, so it gets a 0.0 value by default. The “f” suffix indicates these versions 
of glVertex take GLfloat parameters. Other parameter types are possible, so as many as 24 
different versions of the glVertex command are available.
The FormPaint method used the glColor3f command. The “3” indicates that this version of 
the command takes three parameters, but there is another version of the command that takes 
four parameters. The “f” indicates that the parameters are of type GLfloat.
Error Handling in Depth
Chapter 1 introduced glGetError, but gave it only superficial treatment. Checking it every 
time a command could possibly produce an error would have a performance penalty. Notice 
that FormPaint performs a single call to glGetError at the end of the method. Fortunately the 
graphics library is capable of storing multiple error codes. If several error codes have -
accumulated, multiple calls to glGetError are required to clear them all. Accordingly, this 
application replaces its default exception handler. Place this at the end of the private section 
of the form’s type declaration:

procedure ExceptionGL(Sender:TObject;E:Exception);	
The application’s OnException event handler activates whenever an exception climbs all the 
way up the stack without being handled along the way. The default handler simply displays 
the corresponding error message. To 

PAGE 23
 replace the default one with one more suitable for OpenGL, place this at the beginning of 
the FormCreate method:

  Application.OnException := ExceptionGL;	
Finally, here is the body of the new exception handler. You are supposed to call 
ShowException, but in this context it discards the intended error message and shows the 
name of the form as the error message. Instead, just call ShowMessage with the message in 
the exception object, then in the loop call glGetError and show all the pending OpenGL 
errors until they have all been cleared:

procedure TForm1.ExceptionGL(Sender:TObject;E:Exception);	
begin	
  ShowMessage(E.Message);	
  repeat	
    errorCode := glGetError;	
    if errorCode<>GL_NO_ERROR then	
      showMessage(gluErrorString(errorCode));	
  until errorCode=GL_NO_ERROR;	
end;	
Order of Events
Now, compile the program and launch it with the Windows Run command. Resize it several 
ways. Watch the shape of the rectangle as you do the resizing. Careful observation reveals 
that if the form only shrinks, the shape of the rectangle does not change. If the form enlarges 
either horizontally or vertically or both, the shape of the rectangle changes appropriately. An 
expected event is not firing. This calls for further investigation.
In the variable declaration section add:

    EventFile: TextFile;	
So that an “Event.Log” text file opens for writing at program start and closes at program 
end, make the end of the unit look like this:

initialization	
  assignFile(EventFile,'Event.Log');	
  rewrite(EventFile);	
finalization	
  closeFile(EventFile);	
end.	
To record each firing of the OnPaint event, add the following at the beginning of FormPaint:

  writeln(EventFile,'Paint  '+IntToStr(TimeGetTime));	

PAGE 24
 TimeGetTime is a Windows API call that returns the number of milliseconds since Windows 
last loaded. To access it the unit needs MMSystem in the uses clause. Now, to log each firing 
of the OnResize event, at the beginning of FormResize add this line:

  writeln(EventFile,'Resize '+IntToStr(TimeGetTime));	
Now compile and run as before. Wait about one second. Shrink the form from the right side. 
Wait another second. Shrink the form from the bottom. Wait another second. Enlarge the 
form a little at the bottom. Now close the form. Examine the contents of the Event.Log file in 
the current (project) directory. At the top of the file are “Resize” and “Paint” with nearly 
identical time stamps, only 10 or 20 milliseconds apart. They fired at program start. About 
1,000 milliseconds later (depending on the accuracy of the “one-second” wait) is another 
“Resize” line. That fired when the form shrank from the right side. Another 1,000 
milliseconds later is another “Resize” entry, which fired when the form shrank from the 
bottom. Still 1,000 millisconds later is another “Resize” followed shortly by a “Paint.”
After careful consideration, this makes sense. When the form shrinks, Windows only sends a 
Resize message. Why should Windows send a Paint message just for the covering of some 
already painted territory? Only form enlargement in one or both directions exposes new area 
that needs painting. Windows does not know about the special need to adjust the interior of 
the form. Since the Windows Resize and Paint messages are what trigger the Delphi 
OnResize and OnPaint events, a form shrinkage does not trigger the necessary adjustment of 
the rectangle.
The way to remedy this little problem is to call FormPaint on those occasions when it would 
otherwise be neglected. FormResize is called for every OnResize event, so that is a good 
place to check whether either dimension enlarged. If not, then no OnPaint event happens, so 
you must call FormPaint directly. After cleaning out the text file code, add these fields to the 
private section of the form type declaration:

    oldw,	
    oldh: integer;	
The fields preserve the previous width and height for comparison with new values. Initialize 
them at the end of the FormCreate method:

  oldw := ClientWidth;	
  oldh := ClientHeight;	
Finally, this code at the end of FormResize ensures the FormPaint method always fires when 
needed:


PAGE 25
   if (ClientWidth<=oldw) and (ClientHeight<=oldh) then	
    FormPaint(Sender);	
  oldh := ClientHeight;	
  oldw := ClientWidth;	
Save, compile, and run (from Windows). Resize to your heart’s content.
Matrices and Command Placement
At this point the program is ready for a little experimentation with glOrtho. Change left 
from –1.0 to 0.0, then save, compile, and run as before.

  glOrtho(0.0,1.0,-1.0,1.0,-1.0,1.0);	
Remember that the values given to the vertices of the rectangle are relative to the clipping 
volume. Since the range of horizontal numbers in the call to glOrtho is half the former range, 
the rectangle should be twice as wide as before. Program execution shows that to be the case. 
Now, resize the form exactly one time. Something is wrong! The rectangle jumps to the left 
side. Resize the form again. The rectangle is gone, never to be seen again!
This experience exposes another important concept. OpenGL relies heavily on matrices. A 
matrix is a two-dimensional array of numbers for which a special set of mathematical 
operations applies. For every call to glOrtho, OpenGL creates a matrix from the parameters 
and multiplies it by the projection matrix. Each successive call changes the appropriate 
matrix. The symmetrical numbers passed to glOrtho just masked the potential strange 
behavior, but the unbalanced numbers for this version exposed the problem. One solution is 
to move the call to glOrtho to the last line of the FormCreate method. Save, compile, and run. 
Resize the form a few times. Now the expected behavior manifests itself.
Go ahead and experiment some more with passing different values to glOrtho, leaving 
everything else the same. These variations should give a sense of the relationship between 
the values in the projection matrix and the values of the vertices.
Viewport Manipulation
The viewport is the next experimental subject. The orthographic projection can change some 
of its boundary numbers but still have the same boundary (the viewport). Just the arbitrary 
meaning of the numbers change. The viewport boundary numbers have a real meaning, 
which is window coordinates. Changing these numbers changes the actual boundary of the 
viewport.

PAGE 26
 Restore the call to glOrtho to its symmetrical arrangement, but leave it at the bottom of 
FormCreate.

  glOrtho(-1.0,1.0,-1.0,1.0,-1.0,1.0);	
end; {FormCreate}	
In FormResize, change the vertical extent in the call to glViewport.

  glViewport(0,0,ClientWidth,ClientHeight div 2);	
Compile and run as before. From this point on, using the Windows Run command rather 
than running from the IDE is taken for granted and needs no further mention. Now the 
viewport only covers the lower half of the window. Remember that OpenGL coordinates 
normally start from the bottom of the window, even when using “window coordinates.” The 
red rectangle is still in the upper right quadrant of the viewport, but since the viewport itself 
is confined to the lower half of the window, the rectangle is now in the lower half of the 
window. Since the viewport is only half as tall, the rectangle is half as tall as it was.
Now, try it the other way.

  glViewport(0,0,ClientWidth div 2,ClientHeight);	
Compile and run. This time everything lies on the left side of the screen. The rectangle is still 
in the right side of the viewport, but it lies in the left side of the window. Notice that with 
these restrictions on the size of the viewport, the background color still covers the whole 
window. This experiment should be enough to give a good feel for glViewport, so take out 
the div operators to restore the original appearance.
Mixing Windows Components
One of the really neat features of OpenGL is that it does not prevent using regular Windows 
components. This opens many possibilities for user interaction with your graphics 
constructs. The coming chapters will explore a number of different ways users can give 
information to your applications. Since these are illustrations and exercises, they are not the 
ultimate in slick, state-of-the-art front ends; they are just enough to demonstrate a technique. 
For the first such demonstration, drop a MainMenu component on the form. Right-click the 
component and select the Menu Designer. Give it one menu item with a caption of &Z-
Value. Close the Menu Designer, click the menu item, and edit the event handler as follows:

procedure TForm1.Zvalue1Click(Sender: TObject);	
var	
  zString: string[7];	
begin	


PAGE 27
   zString := InputBox('Z-Value','float:','0.0');	
end;	
The first argument gives the title of the input box. The second is the prompt. “float:” serves 
as a reminder to enter a floating-point value. The third argument provides a default value to 
return.
Position the form near the center of the screen or set the form’s Position property to 
poScreenCenter. Save, compile, and run. Click the menu item, and close the input box. Both 
the menu and the input box showed properly. Since the form shows at or near the center of 
the screen, and the input box shows near the center of the screen, the input box has to 
obscure part of the form. Since there was no hole left in the form when the input box closed, 
then the OnPaint event must have fired. The only adverse effect is the fact that the viewport 
is a little shorter, since the presence of the menu reduced the ClientHeight.
Pushing the (X,Y,Z) Limits
Add zvalue as a new field to the private section of the form declaration:

  private	
    GLContext: HGLRC;	
    glDC: HDC;	
    errorCode: GLenum;	
    openGLReady: boolean;	
    oldw,	
    oldh: integer;	
    zvalue: GLfloat;	
    procedure ExceptionGL(Sender:TObject;E:Exception);	
Edit the Zvalue1Click event handler:

procedure TForm1.Zvalue1Click(Sender: TObject);	
var	
  zString: string[7];	
begin	
  zString := InputBox('Z-Value','float:','0.0');	
  zvalue := StrToFloat(zString);	
end;	
Change the calls to glVertex from the two-parameter version to the three-parameter version 
inside FormPaint, using the freshly minted zvalue as the third argument:

  glBegin(GL_POLYGON);	
    glVertex3f(0.25,0.25,zvalue);	
    glVertex3f(0.75,0.25,zvalue);	
    glVertex3f(0.75,0.75,zvalue);	


PAGE 28
     glVertex3f(0.25,0.75,zvalue);	
  glEnd;	
Save, compile, and run. Select the menu item and enter 0.5 in the input box (and press the 
OK button, of course). Do it again, but enter 1.0 as the value. Again, but use 1.01. Something 
different happened! The rectangle disappeared! Every time the input box covers part of the 
form and closes, the form must repaint. The results of each new zvalue appear as soon as the 
box closes. As long as zvalue is between the z-axis limits of the clipping volume (the glOrtho 
projection), the rectangle is visible. The side of the clipping volume toward the viewer is at z 
= 1.0. The side of the clipping volume away from the viewer, into the screen, is at z = –1.0. As 
long as the rectangle lies between –1.0 and 1.0 on the z-axis, it is visible. Values of 0.5, 1.0, –
0.5, and –1.0 kept the figure in sight, but 1.01 and –1.01 made it vanish.
Use the Menu Designer to add two more main menu items. The captions are &Y-Value and 
&X-Value. Add the following event handlers:

procedure TForm1.YValue1Click(Sender: TObject);	
var	
  yString: string[7];	
begin	
  yString := InputBox('Y-Value','float:','0.0');	
  yvalue := StrToFloat(yString);	
end;	
	
procedure TForm1.XValue1Click(Sender: TObject);	
var	
  xString: string[7];	
begin	
  xString := InputBox('X-Value','float:','0.0');	
  xvalue := StrToFloat(xString);	
end;	
Add xvalue and yvalue to the private section of the form declaration:

  private	
    GLContext: HGLRC;	
    glDC: HDC;	
    errorCode: GLenum;	
    openGLReady: boolean;	
    oldw,	
    oldh: integer;	
    xvalue,	
    yvalue,	
    zvalue: GLfloat;	
    procedure ExceptionGL(Sender:TObject;E:Exception);	

PAGE 29
 Use the new fields in the drawing:

  glBegin(GL_POLYGON);	
    glVertex3f(xvalue+0.25,yvalue+0.25,zvalue);	
    glVertex3f(xvalue+0.75,yvalue+0.25,zvalue);	
    glVertex3f(xvalue+0.75,yvalue+0.75,zvalue);	
    glVertex3f(xvalue+0.25,yvalue+0.75,zvalue);	
  glEnd;	
Save, compile, and run. Try 0.5 and –1.5 for yvalue, then go back to 0.0. Try the same 
numbers for xvalue. Now the meaning of a clipping volume should be quite clear. If any part 
of the figure goes outside the boundaries of the clipping volume, that part is clipped off, but 
the rest remains visible.
Naturally, good code uses a more reliable method of ensuring a repaint than just having an 
input box go away, but for this little clipping demonstration, it was sufficient.

PAGE 30
 
Identifier Reference
Identifier			Description
glBegin
An OpenGL command designating the start of a list of vertices. It receives a mode parameter 
that tells OpenGL what to do with those vertices.
GLbitfield
An OpenGL type that maps to Delphi cardinal.
GLclampf
An OpenGL type equivalent to Delphi single.
glColor
An OpenGL command that sets the foreground color.
GLdouble
An OpenGL type equivalent to Delphi double.
glEnd
An OpenGL command that terminates a list of vertices started with glBegin.
glFlush
An OpenGL command that forces drawing to the screen.
GLint
An OpenGL type that maps to Delphi integer.
glLineWidth
An OpenGL command that specifies the width, in pixels, of all subsequent lines drawn.
glOrtho
An OpenGL command that specifies an orthographic projection and clipping volume.
glRect
An OpenGL command for drawing a rectangle.
GLsizei
An OpenGL type that maps to Delphi integer.
glVertex
An OpenGL command for specifying the coordinates of a vertex.
glViewport
An OpenGL command that specifies the viewport in window coordinates.
GL_COLOR_BUFFER_BIT
An OpenGL constant that identifies the color buffer.
GL_LINES
An OpenGL constant to pass to glBegin. Each pair of vertices then specifies a line segment, 
separate from any of the other line segments.
GL_POLYGON
An OpenGL constant to pass to glBegin. All the vertices listed before glEnd are connected to 
form a polygon.

PAGE 31
 Identifier			Description
TimeGetTime
A Windows function that returns the number of milliseconds since Windows was started.
Summary
Chapter 2 presented the following concepts:
? Drawing is performed in the window’s OnPaint event handler.
? Drawing occurs within the bounds of the viewport.
? The viewport definition uses the operating system’s window coordinates, but counts in 
the y-direction from the bottom up.
? Use glColor to set the foreground drawing color, changing as needed. The color is one of 
many state variables that remain the same until the programmer directly changes them via 
function or procedure call.
? Presenting a 3-D scene on a 2-D screen requires projection.
? Orthographic projection does not change the apparent size of an object to correct for 
distance. It is also called parallel projection.
? The projection is a volume in space bounded by the viewport.
? The programmer may assign the numbers defining the projection volume for clarity or 
convenience.
? The projection region is a clipping volume. Any part of a scene that lies outside the 
volume simply does not appear.
? Adjust the viewport in the window’s OnResize event handler.
? Projection involves matrix multiplication. Repeated invocation is repeated multiplication 
which probably gives unintended results. The solution in this chapter was to invoke 
projection only once in the FormCreate method.
? OpenGL does not hinder the use of Windows features such as menus and dialog boxes.
? The glGetError function returns the error code number for recent operations. It returns a 
number equal to GL_NO_ERROR if no errors have accumulated. OpenGL stores 
accumulated errors, so the program must call glGetError repeatedly until GL_NO_ERROR 
comes back.

PAGE 32

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

Chapter 4
Perspective
Orthographic, or parallel, projection is fine for many purposes, but when the goal is realism 
in the graphics, perspective projection may be more appropriate. In the real world, the fact that 
objects appear smaller when more distant is universally familiar. Artists recognize that 
elements of a scene appear to come together to a vanishing point at some great distance. This 
chapter shows how to produce this realistic effect and to position vertices in such an 
environment.
Illustration
With OpenGL the programmer can construct a (you guessed it!) matrix to project a scene 
onto the computer screen in a manner that simulates perspective. This process is called 
(appropriately) perspective projection. Consider two parallel lines as in the following code:

  glBegin(GL_LINES);	
    glVertex3f(-0.5,0.1,-2.2);	
    glVertex3f(-0.5,15.0,-32.2);	
    glVertex3f(+0.5,0.1,-2.2);	
    glVertex3f(+0.5,15.0,-32.2);	
  glEnd;	
The two line segments run side by side off into the z distance, rising in the y direction. 
Figure 4-1 shows how they appear with perspective projection. Although reproducing this 
effect requires more code than given here, this is enough to show that the two lines are 
indeed parallel. The apparent convergence of the lines is due entirely to the perspective 
projection.


PAGE 61
 Command
To set up the perspective projection matrix, use this command:

procedure glFrustum(left,right,bottom,top,zNear,zFar:GLdouble);stdcall;	
Clipping Volume
What in the world is a frustum? Basically, a frustum is a pyramid with the top chopped off. 
Since the clipping volume looks like a frustum with a rectangular base, lying horizontally, 
the command name makes sense. Figure 4-2 shows the shape of the clipping volume.


PAGE 62
 Parameters
Notice that the parameters for glFrustum are the same as the parameters for glOrtho. In 
general these parameters have a similar usage, but there are enough differences between 
them to warrant caution. Just as in glOrtho, left, right, bottom, and top define a rectangle. In 
glOrtho, however, the front rectangle and the rear rectangle are identical except for their z 
value, so only one rectangle specification is sufficient. In glFrustum, the near and the far 
rectangles are very different sizes, but the command only takes the values for the near 
rectangle.
How in the world does this command define the far rectangle? The answer lies in the usage 
of the znear and zfar parameters. The znear value is the distance from the viewer to the near 
face of the frustum, which is the “cut” across the pyramid. The znear distance must be 
expressed as a positive number. The zfar value is the distance to the farther face of the 
frustum (base of the pyramid), which must be a positive number larger than znear. In effect 
the viewer is at the apex of the pyramid, so znear and zfar are enough to define the base of 
the frustum from its front face, as Figure 4-2 suggests.
Depth
Previous Cube
Create a directory (under the OpenGL directory, of course) and name it Chapter.4. Under 
that directory create a directory named Multi. Copy the files from OpenGL\Chapter.3\Multi 
to OpenGL\Chapter.4\Multi. Open this project in Delphi and edit the FormCreate method 
in Multi.Pas. Replace

  glOrtho(-1.0,1.0,-1.0,1.0,-1.0,1.0); 	
with

  glMatrixMode(GL_PROJECTION);	
  glFrustum(-1.0,1.0,-1.0,1.0,2.0,37.0); 	
This command change maps the same near face rectangle, but specifies the z range 
differently, as well as providing a different projection. The clipping volume starts at 2.0 units 
from the viewer and extends to 37.0 units from the viewer. In glOrtho, znear and zfar just 
provided a range of values whose meaning was entirely up to the programmer. With parallel 
projection, apparent sizes are not affected by distance from the viewer, so that distance is not 
a consideration. With perspective projection, distance from the viewer is a primary 
ingredient.

PAGE 63
 Compile and run. Nothing but the green background shows because under the new 
coordinate system the cube was constructed entirely in front of the front clipping face. Use 
transformations to bring the cube into view. Fill out the parameters as in Figure 4-3, click 
OK, and select Transformation from the menu.
Now the cube shows, but it looks a little strange. It is like the back is larger than the front. 
Test this theory with a rotation transformation. Set it up as in Figure 4-4.
Sure enough, the back of the cube appears larger than the front. Since this is the opposite of 
what perspective projection should produce, something must be wrong. This is a good time 
to test the coordinates.
Test
Like the depth test in Chapter 3 draw two rectangles in the FormPaint method, replacing the 
current contents. The reason for the choice of z coordinates will soon become evident. Save 
the project as Depth.Dpr in OpenGL\Chapter.4\Depth and save the main unit as 
Depth1.Pas in the Depth directory also. Remove the Trans1 unit from the project, remove the 
uses Trans1 statement from the main unit, remove the Main Menu control, and empty the 
contents of the menu item event handlers. Adjust the form’s ClientHeight back to 340. Here 
is what FormPaint should look like:


PAGE 64
 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,-2.4);	
    glVertex3f(+0.4,-0.4,-2.4);	
    glVertex3f(+0.4,+0.4,-2.4);	
    glVertex3f(-0.4,+0.4,-2.4);	
  glEnd;	
	
  glBegin(GL_POLYGON); {back face}	
    glColor3f(0.0,0.0,0.5);	
    glVertex3f(-0.2,-0.2,-2.8);	
    glVertex3f(+0.6,-0.2,-2.8);	
    glVertex3f(+0.6,+0.6,-2.8);	
    glVertex3f(-0.2,+0.6,-2.8);	
  glEnd;	
	
  glFlush;	
	
  errorCode := glGetError;	
  if errorCode<>GL_NO_ERROR then	
    raise Exception.Create(‘Error in Paint’#13+	
      gluErrorString(errorCode));	
end; {FormPaint}	
Save, compile, and run. Again the red rectangle is in front of the blue rectangle. But look 
again at the z coordinates of the two rectangles. The farther (blue) rectangle is more negative 
in the z direction than the near rectangle. In the orthographic projection clipping volume a 
more distant point is more positive (or less negative) than a nearer point, and the 
specifications of the cube vertices took that fact into account. So when the same vertices 
appeared in a perspective projection clipping volume whose z coordinates run in the 
opposite direction, naturally the cube was distorted.
Better Cube
In Delphi load the previous project under Chapter.4, Multi.Dpr. Adapt the coordinates of the 
cube vertices to the new z direction reality. Remember that moving away from the viewer is 
to travel in the negative z direction, 

PAGE 65
 even though the znear and zfar parameters are positive and more positive, respectively. 
Make the FormPaint method look like this:

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


PAGE 66
   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}	
For convenience, move the cube inside the clipping volume with a call to glTranslatef near 
the bottom of the FormCreate method. Place it there so it will only happen once.

  glLoadIdentity;	
  glTranslatef(0.0,0.0,-2.4);	
	
  glEnable(GL_CULL_FACE);	
  glCullFace(GL_BACK);	
end; {FormCreate}	
Movement
Rotation
Set up a transformation using the parameters of Figure 4-4, then perform the rotation. Notice 
that since the cube is so close to the front clipping face, the rotation sends a corner right 
outside the clipping volume. Now it is apparent that clipping works just like in the 
orthographic projection clipping volume. Notice too that back face culling also works as 
expected. The exposed interior of the cube reveals a green nothing. Select Cull Disable from 
the menu and behold the interior faces of the cube.
Translation
In the parameter screen uncheck Rotate and check Translate. Set z to –0.1 and the rest to 0.0. 
Click OK, and select Transformation from the menu repeatedly until the entire cube is inside 
the clipping volume. Now the 

PAGE 67
 image, shown in Figure 4-5, demonstrates clearly that the far side of the cube appears 
smaller than the near side.
Next press and hold Alt+T so that the keyboard starts repeating. Watch the cube retreat into 
the distance. Now that’s perspective!


        Figure 4-5. Near and far sides of a
        cube in perspective.

Multiple Objects
Matrix Stack
Most scenes built with OpenGL consist of more than just a single object on a plain 
background. Building various objects in the right places is an obvious way to build more 
complex scenes, but it can get tedious. Suppose the scene has a number of objects that are 
similar to each other and the programmer has to describe them over and over. Isn’t 
computer software supposed to relieve people of certain tiresome repetitive tasks? OpenGL 
provides a way!
Put a commonly used object in its own procedure or method. Build it around the origin 
(0.0,0.0,0.0) for ease of coding. Rotate it, translate it, even scale it, to put it in the right place, 
with the right orientation, and in the right shape. Then (this is important!) restore the 
ModelView matrix to its former condition and build and place another object.
What programming structure useful for saving and restoring something comes to mind? A 
stack would be nice. Indeed, as mentioned previously, OpenGL even provides a matrix 
stack. The commands for storing the current matrix on the stack, and retrieving it, are:

procedure glPopMatrix; stdcall;	
procedure glPushMatrix; stdcall;	

PAGE 68
 Reuse an Object
Save the Mult project as Obj.Dpr in a new directory named Objects under Chapter.4. 
Remove Trans1.Pas from the project and save Multi.Pas as Objct.Pas in the new directory. 
Delete the menu object from the form, empty all the menu item event handlers, and adjust 
the ClientHeight of the form back to 340.
The object of choice these days is a cube. To make it available for reuse, collect the code to 
draw the cube and put it in a separate method.

procedure TForm1.Cube;	
begin	
  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);	


PAGE 69
     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;	
end;	
Declare the method at the end of the private section of the form:

  private	
    GLContext: HGLRC;	
    glDC: HDC;	
    errorCode: GLenum;	
    openGLReady: boolean;	
    oldw,	
    oldh: integer;	
    procedure ExceptionGL(Sender:TObject;E:Exception);	
    procedure Cube;	
  public	
Make a Scene
This is enough information to build a scene with multiple copies of the cube. Make the 
FormPaint method look like this:

procedure TForm1.FormPaint(Sender: TObject);	
begin	
  if not openGLReady then	
    exit;	
	
  {background}	
  glClearColor(0.6,0.8,0.8,0.0);	
  glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT);	
	
  glPushMatrix;	
  glTranslatef(-0.7,0.7,-2.8);	
  Cube;	
  glPopMatrix;	
	
  glPushMatrix;	
  glTranslatef(1.9,-1.6,-5.4);	
  Cube;	
  glPopMatrix;	
	


PAGE 70
   glFlush;	
	
  errorCode := glGetError;	
  if errorCode<>GL_NO_ERROR then	
    raise Exception.Create('Error in Paint'#13+	
      gluErrorString(errorCode));	
end; {FormPaint}	
Notice the sequence: Push the ModelView matrix, translate to the desired location, call the 
routine that builds the object about the origin, and pop the matrix. Do this for each object in 
the scene; even if a given object is not repeated, it is usually simpler to construct at the 
origin, especially if it has symmetry. In this simple example only a translation appears before 
each cube, but any combination of the three transformations could take its place.
Save, compile, and run. Behold a 3-D, two-object scene! Notice the marvelous new 
background color. Perhaps its name should be “Clear Blue Sky with Light Smog.” Figure 4-6 
shows how it looks. The new color, though far from pretty, provides better contrast for the 
cubes.

Independent Movement
The techniques for creating multiple copies of an object and placing them in different places 
also lend themselves to moving the objects separately. To demonstrate, add some fields to 
the private section of the form type declaration:

    x1,y1,z1,x2,y2,z2,xr1,yr1,zr1,	
    xr2,yr2,zr2,angle1,angle2,sign: glFloat;	
Initialize sign at the very bottom of the FormCreate method:

  sign := 0.1;	

PAGE 71
 Translate
Remove the glTranslate command from the FormCreate method, then in the FormPaint 
method replace

  glTranslatef(-0.7,0.7,-2.8);	
with

  glTranslatef(x1,y1,z1);	
Also replace

  glTranslatef(1.9,-1.6,-5.4);	
with

  glTranslatef(x2,y2,z2);	
Now that the arguments in the glTranslatef calls are variables, code to manipulate the 
variables is appropriate. Using the Object Inspector, set the form’s KeyPreview property to 
true. On the Events page, double-click the OnKeyPress event and fill in the following event 
handler:

procedure TForm1.FormKeyPress(Sender: TObject; var Key: Char);	
var	
  ok: boolean;	
begin	
  ok := true;	
  case Key of	
    '+': sign := 0.1;	
    '-': sign := -0.1;	
    'X': x1 := x1+sign;	
    'x': x2 := x2+sign;	
    'Y': y1 := y1+sign;	
    'y': y2 := y2+sign;	
    'Z': z1 := z1+sign;	
    'z': z2 := z2+sign;	
    else	
      ok := false;	
  end; {case}	
  if ok then	
    Invalidate;	
end;	
Whenever the user executes a keystroke, the FormKeyPress method tests the character in the 
case statement and adjusts the variables accordingly. Pressing  “+” or “–” sets the sign 
variable accordingly. The letters “x,” “y,” and “z” set the corresponding variables, with the 
capital letters setting x1, etc., and the lowercase letters setting x2, etc. If a desired letter came 

PAGE 72
 through, then a call to Invalidate causes the FormPaint method to fire again.
The sign variable starts with a positive value of one-tenth. Pressing “–” changes it to 
negative one-tenth and pressing “+” changes it back. This value adds to the other variables 
in response to the appropriate letter. Save, compile, and run. Start by pressing the minus 
key, then pressing and holding a capital “Z” until a cube comes into view. Since the 
variables are fields of the form, they start as 0.0 unless explicitly changed. That means both 
cubes remain out of sight at the origin until the user gives them a lot of negative z. Capital 
letters affect one cube and lowercase letters affect the other cube. Experiment with pressing 
the desired keys and enjoy the power of moving the two cubes independently.
Rotate
Being able to move two cubes independently represents a definite advance in the available 
OpenGL power, but rotating the cubes separately is an even greater advance. Better yet is 
the power to combine rotation and translation for each cube (independently). The variables 
have already been declared. After each glTranslatef goes a glRotatef:

  glTranslatef(x1,y1,z1);	
  glRotatef(angle1,xr1,yr1,zr1);	
	
  glTranslatef(x2,y2,z2);        	
  glRotatef(angle2,xr2,yr2,zr2);	
The case statement in the FormKeyPress method needs an addition so that it can manipulate 
the rotation variables. The letters to check are upper- and lowercase “r,” “s,” and “t.”

    'a': angle2 := angle2+10.0*sign;	
    'r': xr2 := xr2+sign;	
    's': yr2 := yr2+sign;	
    't': zr2 := zr2+sign;	
    'A': angle1 := angle1+10.0*sign;	
    'R': xr1 := xr1+sign;	
    'S': yr1 := yr1+sign;	
    'T': zr1 := zr1+sign;	
The letters “r,” “s,” and “t” set up the axis of rotation for each cube. Each axis is a vector (line 
segment with direction) from (0.0,0.0,0.0) toward the vertex constructed from (xr1,yr1,yr1) or 
(xr2,yr2,zr2). The letter “a” adjusts the amount of rotation (angle1 or angle2) about the 
appropriate vector. It would be nice to have a little help visualizing the axes of rotation. 

PAGE 73
 Add the following method to draw a representation of the coordinate axes along with the 
two rotation vectors:

procedure TForm1.Axes;	
begin	
  glLineWidth(3.0);	
  glBegin(GL_LINES);	
    glColor3f(0.0,0.0,0.0);	
	
    glVertex3f(+0.0,+0.0,+0.4);	
    glVertex3f(+0.0,+0.0,-0.4);	
	
    glVertex3f(+0.4,+0.0,+0.0);	
    glVertex3f(-0.4,+0.0,+0.0);	
	
    glVertex3f(+0.0,+0.4,+0.0);	
    glVertex3f(+0.0,-0.4,+0.0);	
  glEnd;	
	
  glLineWidth(1.0);	
  glBegin(GL_LINES);	
    glColor3f(1.0,1.0,0.0);	
    glVertex3f(0.0,0.0,0.0);	
    glVertex3f(xr1,yr1,zr1);	
	
    glColor3f(0.0,1.0,1.0);	
    glVertex3f(0.0,0.0,0.0);	
    glVertex3f(xr2,yr2,zr2);	
  glEnd;	
end;	
Declare the new method at the bottom of the form’s private section:

    procedure ExceptionGL(Sender:TObject;E:Exception);	
    procedure Cube;	
    procedure Axes;	
  public	
    { Public declarations }	
  end;	
Call the Axes method inside the FormPaint method, just before the call to glFlush. Translate 
it along the z-axis to bring it inside the clipping volume. Translate it a little along the x and y 
axes so that the viewer will see it slightly from above and from the side, to make its three-
dimensional nature more evident.

  glPushMatrix;	
  glTranslatef(-0.6,-0.6,-2.8);	
  Axes;	


PAGE 74
   glPopMatrix;	
	
  glFlush;	
Save, compile, and run. In addition to all the previous abilities of the program, it can now 
use the three new letters to rotate the cubes. Whew!
Transformation Order Revisited
About the only thing left for this structure is to add scaling as well, but that would not add 
much to the OpenGL knowledge because the results are easy to predict. There is something 
else that can contribute significantly to the level of OpenGL understanding. The ability to 
switch back and forth between rotating before translation and rotating after translation 
reveals a dramatic difference between the two. Add the following to the case statement:

    'B': before := true;	
    'b': before := false;	
The form declaration needs to include the before field:
	
    errorCode: GLenum;	
    before,	
    openGLReady: boolean;	
    oldw,	
    oldh: integer;	
    x1,y1,z1,x2,y2,z2,xr1,yr1,zr1,	
    xr2,yr2,zr2,angle1,angle2,sign: glFloat;	
    procedure ExceptionGL(Sender:TObject;E:Exception);	
    procedure Cube;	
    procedure Axes;	
FormPaint needs an enhancement using the value of the before field to control whether the 
call to glRotatef happens before or after the call to glTranslatef:

  glPushMatrix;	
  if before then	
    glRotatef(angle1,xr1,yr1,zr1);	
  glTranslatef(x1,y1,z1);	
  if not before then	
    glRotatef(angle1,xr1,yr1,zr1);	
  Cube;	
  glPopMatrix;	
  glPushMatrix;	
  if before then	
    glRotatef(angle2,xr2,yr2,zr2);	
  glTranslatef(x2,y2,z2);       	


PAGE 75
   if not before then	
    glRotatef(angle2,xr2,yr2,zr2);	
  Cube;	
  glPopMatrix;	
Again save, compile, and run. Bring the cubes into view and position and orient them as you 
please, but be sure to include some rotation for each of them. Now press “B” and “b” to 
show the difference between rotation before translation and rotation after translation. The 
experiment with the order of transformations at the end of Chapter 3 gave small distinctions, 
but this time the difference is exceedingly obvious. For example, a small rotation about the y-
axis changes the direction of the cube’s own personal z-axis, so a subsequent translation 
along the z-axis sends it off in a different direction than if the translation happens first. Try it 
and see.
Another View
Frankly, transformations in OpenGL can be very confusing, so any aid to comprehension is 
welcome. For this purpose think of the translations as applying to the objects rather than the 
coordinate system. Then think of the transformations as happening in reverse order. When 
you push and pop the ModelView matrix several times, treat this reverse order concept 
separately between each push and pop.
Different people may need to consider OpenGL transformations in different ways to 
understand them. Possibly this alternate view will be the best one for many readers of this 
book.
Identifier Reference
Identifier			Definition
glFrustum
An OpenGL command that sets up perspective projection and the clipping volume.
glPopMatrix
An OpenGL command that saves the current OpenGL matrix on the matrix stack.
glPushMatrix
An OpenGL command that retrieves a matrix from the matrix stack and makes it the current 
OpenGL matrix.


PAGE 76
 Summary
Here are the high points of Chapter 4:
? The shape of the clipping region for perspective projection is a frustum, which is a 
pyramid with the top chopped off.
? The command is glFrustum; the parameters are the same as for glOrtho, but the znear 
and zfar parameters have different effects and must be positive.
? To move away from the viewer, deeper into the screen, is to move in the negative z 
direction.
? The effects of clipping and back face culling are the same as with glOrtho.
? When viewed in perspective, parallel lines appear to converge in the distance, and objects 
appear smaller as they retreat into the distance.
? Use glPushMatrix to save the ModelView matrix and glPopMatrix to restore it. Placing 
transformations between those calls allows for independent positioning and movement of 
objects in a scene.
? Rotation changes the orientation of the axes so that a subsequent translation may go off in 
a different direction.When planning a positioning or movement within a scene, be careful 
about the order of the transformations.
? OpenGL transformations seem to take effect in the reverse order of their specification in 
program code.

PAGE 77
 

PAGE 78

Chapter 5
Lighting
In the real world, things are visible because of light. All light comes from various sources, 
usually identifiable, but even when the source is unknown, the light was actually emitted 
from somewhere. Not only does the visibility of everything (except those objects that emit 
their own light) depend on light emitted from somewhere and reflected to the viewer, but 
the detailed appearance of each object depends on properties of the material composing the 
illuminated surface. A realistic graphics system should incorporate these facts. This chapter 
explores lighting and material properties.
Building on the work of Chapter 4, save Obj.Dpr as ALight.Dpr in 
OpenGL\Chapter.5\Ambient. Save the main unit as ALight1.Pas.
Enable Lighting
One of the great features of OpenGL is the ability to produce realistic effects through 
simulation of lighting. In the code in the first four chapters, OpenGL provided the equivalent 
of lighting so that the objects were visible. Now is the time to provide lighting specifications 
directly. Here is a necessary OpenGL constant:

  GL_LIGHTING = $0B50;	
At the bottom of the FormCreate method, enable lighting:

glEnable(GL_LIGHTING);	
Save, compile, and run. Maneuver one of the cubes into the viewing volume (clipping 
region). Ugh! The red square is black! Rotate the cube so that two more faces show at least a 
little. They are black, too! It is difficult to distinguish one face from another because they 
have exactly the same color. Now that the program is taking responsibility for making the 
objects in the scene visible, OpenGL is taking no responsibility at all. Obviously the program 
needs to do more than just enable lighting.

PAGE 79
 Basically three kinds of lighting are available through OpenGL: ambient, diffuse, and specular. 
This chapter will examine each of them in turn.
Ambient Light
Definition
Ambient light has no apparent source, but seems to come from all directions. In the real 
world, all light has a source, but some light may be reflected and scattered from several 
surfaces before finally reaching the viewer. The source of such light may be difficult or 
impossible to trace. This kind of light is called ambient light. OpenGL simulates this effect by 
providing illumination equally from all directions. It can be associated with a particular light 
or it can have no specific source, just magically appearing (at the programmer’s command).
Commands and Constants
The command for defining a light is a variation of glLight. Use the “f” suffix to use GLfloat 
parameters. Many commands, including ones already discussed, have a vector version, 
indicated by a “v” suffix. The vector version substitutes a pointer to an array in place of 
several scalar parameters. The glLight command only takes three parameters in any version, 
so only the vector version meets the current need. The glLightModel command takes three 
parameters and also has a vector version. Then there is the glColorMaterial command, which 
introduces the idea of material properties. The relevant declarations follow. First a necessary 
type:

  PGLfloat =  ^GLFloat;	
Here are some useful constants:

  GL_LIGHT_MODEL_AMBIENT = $0B53;	
  GL_COLOR_MATERIAL      = $0B57;	
  GL_AMBIENT             = $1200;	
  GL_LIGHT0              = $4000;	
  GL_LIGHT1              = $4001;	
  GL_LIGHT2              = $4002;	
  GL_LIGHT3              = $4003;	
  GL_LIGHT4              = $4004;	
  GL_LIGHT5              = $4005;	
  GL_LIGHT6              = $4006;	
  GL_LIGHT7              = $4007;	
Finally, the commands:

PAGE 80
 
procedure glColorMaterial(face,mode:GLenum); stdcall;	
procedure glLightModelfv(pname:GLenum;params:PGLfloat); stdcall;	
procedure glLightfv(light,pname:GLenum;params:PGLfloat); stdcall;
procedure glMaterialf(face,pname:GLenum;param:GLfloat); stdcall;
procedure glMaterialfv(face,pname:GLenum;params:PGLfloat); stdcall;
procedure glMateriali(face,pname:GLenum;param:GLint); stdcall;	
Consider the parameters in the commands. The face parameter can be GL_ FRONT, 
GL_BACK, or GL_FRONT_AND_BACK. For now, use GL_FRONT. The mode parameter 
specifies the kind of lighting to which the material property relates. The example under 
development uses GL_AMBIENT. The params pointer could be pointing to a single glFloat, 
or the first element of an array of them. While this is very C-like, Delphi can handle it nicely. 
The pname parameter identifies the purpose for two of the commands. The command for the 
simplest method of implementing ambient light is glLightModel. Pass it 
GL_LIGHT_MODEL_AMBIENT for the first parameter.
The Easy Way
Lighting introduces a new level of complexity to the specification of an object’s appearance. 
A simple color specification does not tell the whole story about how a surface responds to 
light. Material properties allow a more complete way to describe responses to light. The 
present example avoids some of the complexity by having the material color track the 
current color by means of the glColorMaterial command. This is one of the options that 
requires enabling before use.
At the bottom of the FormCreate method, after the enabling of lighting, initialize the array 
and place some of the new commands with the appropriate arguments.

AmbientDef[0] := 1.0; {red}	
  AmbientDef[1] := 1.0; {green}	
  AmbientDef[2] := 1.0; {blue}	
  AmbientDef[3] := 1.0; {alpha}	
  glEnable(GL_COLOR_MATERIAL);	
glColorMaterial(GL_FRONT,GL_AMBIENT);	
glLightModelfv(GL_LIGHT_MODEL_AMBIENT,@AmbientDef);	
In this version of the glLightModel command, using GL_LIGHT_MODEL_ AMBIENT 
means the array contains the color specification of the ambient light. Here, giving maximum 
intensity (1.0) to the red, green, and blue components specifies a white ambient light. A later 
chapter covers alpha; just set it to 1.0 for now. Declare the array in the form’s private section.

    AmbientDef: array[0..3] of glFloat;	

PAGE 81
 Save, compile, and run. Now the cubes look like they did before. So what did the new 
commands accomplish? One benefit is control. Experiment with decreasing the red, green, 
and blue components of AmbientDef (positions 0, 1, and 2 in the array). Reducing the overall 
intensity of the ambient light or giving it a color bias produces effects that may be desirable 
in some applications.
The Hard Way
A more flexible approach uses the glLight command. Make a new directory named 
OpenGL\Chapter.5\Ambient0. In that directory save the program as Amb0.Dpr and the 
main unit as Ambient0.Pas. Change the bottom of the FormCreate method to this:

  glEnable(GL_LIGHTING);	
  AmbientDef[0] := 1.0; {red}	
  AmbientDef[1] := 1.0; {green}	
  AmbientDef[2] := 1.0; {blue}	
  AmbientDef[3] := 1.0; {alpha}	
	
  glLightfv(GL_LIGHT0,GL_AMBIENT,@AmbientDef);	
  glEnable(GL_LIGHT0);	
This approach defines a particular light (LIGHT0) instead of just setting up a light model. At 
least eight lights are available; some implementations of OpenGL may allow more. Notice 
that you must turn on any light you want to use. This example switches on LIGHT0 with 
glEnable(GL_LIGHT0). To use a light, the simple color tracking of glColorMaterial is not 
adequate. So instead of glColor commands inside the Cube method, use glMaterial 
commands. A vector version is most convenient, so define an array at the top of Cube. For 
this example there is no attempt to produce the same colors as before. The Cube method’s 
listing follows:

procedure TForm1.Cube;	
var	
  ambient: array[0..3] of glFloat;	
begin	
  fillchar(ambient,sizeOf(ambient),0);	
  ambient[3] := 1.0; {alpha}	
  glBegin(GL_POLYGON); {near face}	
    ambient[0] := 0.5; {red}	
    glMaterialfv(GL_FRONT,GL_AMBIENT,@ambient);	
    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;	


PAGE 82
 	
  glBegin(GL_POLYGON); {right face}	
    ambient[1] := 0.5; {green} {total=yellow}	
    glMaterialfv(GL_FRONT,GL_AMBIENT,@ambient);	
    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}	
    ambient[0] := 0.0; {red} {total=green}	
    glMaterialfv(GL_FRONT,GL_AMBIENT,@ambient);	
    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}	
    ambient[2] := 0.5; {blue} {total=turquoise}	
    glMaterialfv(GL_FRONT,GL_AMBIENT,@ambient);	
    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}	
    ambient[1] := 0.0; {green} {total=blue}	
    glMaterialfv(GL_FRONT,GL_AMBIENT,@ambient);	
    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}	
    ambient[0] := 0.5; {red} {total=magenta/magenta}	
    glMaterialfv(GL_FRONT,GL_AMBIENT,@ambient);	
    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;	
end;	
Listing 5-1. The Cube method.

PAGE 83
 Save, compile, and run. Bring the cubes into view and move them around. Notice something 
rather strange. A side of the cube facing directly toward the viewer is brighter than the same 
side with a significantly different angle to the viewer.
That is not the behavior of ambient light. Perhaps it has been set too bright. Ambient light is 
often a minor part of the total light in a scene. In the FormCreate method cut it down as 
follows:

  AmbientDef[0] := 0.3; {red}	
  AmbientDef[1] := 0.3; {green}	
  AmbientDef[2] := 0.3; {blue}	
  AmbientDef[3] := 1.0; {alpha}	
Save, compile, and run. Manipulate the scene as before. Alas, there is little noticeable 
difference between ambient light at 30% and ambient light at 100%. Something is very 
wrong.
Actually, LIGHT0 has a special characteristic different from LIGHT1, LIGHT2, or any of the 
others. LIGHT0 by default has a white light diffuse component and a white light specular 
component. The default ambient component is black, but the present code replaced the 
default value. To see the effect of ambient light alone requires replacing the diffuse and 
specular parts (with black). Make these changes to the FormCreate method:

  glEnable(GL_LIGHTING);	
  fillchar(AmbientDef,SizeOf(AmbientDef),0);	
  glLightfv(GL_LIGHT0,GL_DIFFUSE,@AmbientDef);	
  glLightfv(GL_LIGHT0,GL_SPECULAR,@AmbientDef);	
	
  AmbientDef[0] := 0.3; {red}	
  AmbientDef[1] := 0.3; {green}	
  AmbientDef[2] := 0.3; {blue}	
  AmbientDef[3] := 1.0; {alpha}	
	
  glLightfv(GL_LIGHT0,GL_AMBIENT,@AmbientDef);	
  glEnable(GL_LIGHT0);	
The new constants are:

  GL_DIFFUSE  = $1201;	
  GL_SPECULAR = $1202;	
Now save, compile, and run. Bring the cubes into view. What a difference! The cubes are 
rather dim, which is natural for only 30% ambient illumination, but most importantly they 
are uniformly dim. This is how ambient light should appear!

PAGE 84
 Can OpenGL handle bright white ambient light, or did the earlier supposition that the 
ambient light was set out of natural range have merit? Find out by replacing all the 0.3 
values in AmbientDef with 1.0. Save, compile, run, and move cubes around. Now uniform 
illumination abounds. Though it is quite bright, the ambient light appears evenly distributed 
regardless of angle, which is the right behavior for ambient light.
Diffuse Light
Definition
Imagine a lamp lighting a large room. Much of the light bounces off many surfaces before 
reaching a viewer. While the light actually comes from the lamp, it seems simply to be 
everywhere. This is the ambient portion of the light. Obviously, however, some of the light 
proceeds directly from the source to the surface of some object in the room and reflects 
directly to the viewer. However, unless the surface is a mirror, the light tends to scatter 
rather than bounce off at a predictable angle. In the real world, nothing is perfectly smooth, 
but some things have been polished enough that the surface irregularities are too small to 
matter. Such surfaces are “shiny.” Most surfaces have irregularities large enough to scatter 
most light as in Figure 5-1. These surfaces are “flat.” Unless the surface is white, it will 
absorb some colors from the light and reflect the rest, thus defining its color in terms of the 
portion of the light it reflects. This scattering effect determines the diffuse portion of the 
light. Not only does OpenGL allow the definition of at least eight light sources, but it also 
allows independent specification of the ambient portion and diffuse portion of each light.


PAGE 85
 Positional Lamps
Save the project as Difu.Dpr in a new directory called OpenGL\Chapter.5\ Dif1 and save 
the main unit as Diffus.Pas. For the lamp under development, most light shines directly on 
an object from the lamp (the diffuse portion), and a lesser contribution comes from multiple 
reflections (the ambient portion). Prepare to change the AmbientDef assignments in the 
FormCreate method as follows, but do not type it in just yet:

  glEnable(GL_LIGHTING);	
  AmbientDef[0] := 0.3; {red}	
  AmbientDef[1] := 0.3; {green}	
  AmbientDef[2] := 0.3; {blue}	
  AmbientDef[3] := 1.0; {alpha}	
While bland, homogenized arrays like this provide a simple, fairly language-independent 
way of passing data to the OpenGL DLLs, Delphi allows more meaningful structures that 
use memory exactly the same way. The following type declaration of a record structure is 
more intuitive and does not require the programmer to memorize the order of the colors:

  TLightDesc=record	
    red,	
    green,	
    blue,	
    alpha: glFloat;	
  end;	
Wait! Don’t type that in either! Arrays use contiguous memory, but record structures may 
have “holes” in the data due to memory alignment by the compiler. This particular record 
structure is safe (for the moment) because each of the elements is four bytes in size, but to 
guarantee that the record memory layout is exactly the same as the array memory layout, 
use the packed reserved word and place the declaration just above the form’s class 
declaration:

type	
  TLightDesc=packed record	
    red,	
    green,	
    blue,	
    alpha: glFloat;	
  end;	
TForm1 = class(TForm)	
Of course, the type for the AmbientDef field within the form declaration should become:

    AmbientDef: TLightDesc;	

PAGE 86
 Now the new assignments to AmbientDef in the FormCreate method can proceed:

  glEnable(GL_LIGHTING);	
  with AmbientDef do	
  begin	
    red := 0.3;	
    green := 0.3;	
    blue := 0.3;	
    alpha := 1.0;	
  end;	
  LightPlace.w := 1.0;	
The key distinction between diffuse light and ambient light is that the source of diffuse light 
has a position. Therefore, a demonstration of diffuse light requires the ability to set the 
position of the lamp. Accordingly, add these lines to the case statement in the FormKeyPress 
method:

    'l': xl := xl+sign;	
    'm': yl := yl+sign;	
    'n': zl := zl+sign;	
In the form’s class declaration, add these “L” variable declarations to describe the lamp 
position:

    xl,yl,zl,	
    x1,y1,z1,x2,y2,z2,xr1,yr1,zr1,	
    xr2,yr2,zr2,angle1,angle2,sign: glFloat;	
    AmbientDef: TLightDesc;	
    LightPlace: TLightPlace;	
LightPlace is also new and needs a type declaration:

type	
  TLightDesc=packed record	
    red,	
    green,	
    blue,	
    alpha: glFloat;	
  end;	
  TLightPlace=packed record	
    x,	
    y,	
    z,	
    w: glFloat;	
  end;	
TForm1 = class(TForm)	
In the FormPaint method, place some code to manipulate the light position:

PAGE 87
 
  glPushMatrix;	
  if before then	
     glRotatef(angle2,xr2,yr2,zr2);	
  glTranslatef(x2,y2,z2);   	
  if not before then	
     glRotatef(angle2,xr2,yr2,zr2);	
  Cube;	
  glPopMatrix;	
	
  glPushMatrix;	
  glTranslatef(xl,yl,zl);	
  glLightfv(GL_LIGHT0,GL_POSITION,@LightPlace);	
  glPopMatrix;	
Here is the declaration of the new constant:

GL_POSITION = $1203
Save, compile, run, and bring a cube barely into view, then go two keystrokes more down 
the negative z-axis. Move the cube near the upper left corner of the form. The cube appears 
very bright, but that will change as the lamp moves.
Press the minus key, then press the lowercase “n” repeatedly. After about five such presses, 
a slight difference is noticeable. As you continue to advance the lamp down the negative z-
axis, watch the shading develop on the cube!
Now it should be possible to have a uniformly colored cube that still shows its three-
dimensional nature by its shading in a diffuse light source. In the Cube method, change the 
type of the ambient local variable:

procedure TForm1.Cube;	
var	
  ambient: TLightDesc;	
begin	
Remove all assignments to ambient inside the Cube method and replace them with one set 
of assignments as follows:

procedure TForm1.Cube;	
var	
  ambient: TLightDesc;	
begin	
  ambient.red := 0.2;	
  ambient.green := 0.5;	
  ambient.blue := 0.8;	
  ambient.alpha := 1.0;	
  glMaterialfv(GL_FRONT,GL_AMBIENT,@ambient);	
  glBegin(GL_POLYGON); {near face}	

PAGE 88
 Save, compile, run, and perform movements as before. Around the 22nd “n” press the 
shading of the cube is more than sufficient to distinguish the faces. Enjoy! This is 
tremendous progress from the first step of making a colored background.
Different Material Colors
Various material properties can be set independently, including colors for ambient 
reflectivity, specular reflectivity, and diffuse reflectivity. Allowing the surface to reflect 
ambient light differently from diffuse light provides for some interesting effects. Adjust the 
top of the Cube method as follows:

procedure TForm1.Cube;	
var	
  diffuse,	
  ambient: TLightDesc;	
begin	
  ambient.red := 0.2;	
  ambient.green := 0.5;	
  ambient.blue := 0.8;	
  ambient.alpha := 1.0;	
  diffuse.red := 0.8;	
  diffuse.green := 0.5;	
  diffuse.blue := 0.2;	
  diffuse.alpha := 1.0;	
  glMaterialfv(GL_FRONT,GL_DIFFUSE,@diffuse);	
  glMaterialfv(GL_FRONT,GL_AMBIENT,@ambient);	
  glBegin(GL_POLYGON); {near face}	
Save, compile, run, and perform movements as before. This version more clearly 
demonstrates the ambient contribution and the diffuse contribution from the light source. At 
first, the diffuse light reflection color clearly dominates. As the light advances down the 
negative z-axis until it is almost past the near face of the cube, ambient reflection clearly 
shows one color, and diffuse reflection another color. When the light moves a little more, the 
ambient reflection color will completely dominate. Why does the diffuse contribution wane 
before the light is past the cube? Another behavior of real light reflecting off real surfaces is 
the fact that as the angle from the light source to the surface flattens, less of the diffuse light 
reflects to the viewer. Figure 5-2 illustrates light shining on a surface from an acute angle 
(left) and from a more obtuse angle (right). The right side of the illustration shows less of the 
scattered (diffuse) light reaching the viewer. OpenGL reproduces this behavior nicely.


PAGE 89
 So far the exercises have depended on the fact that LIGHT0 has a white diffuse component 
by default. The next exercise illustrates colored light on surfaces of different color. 
Accordingly, at the bottom of the FormCreate method, make the light pure red and explicitly 
define the diffuse component:

  glEnable(GL_LIGHTING);	
  with AmbientDef do	
    begin	
    red := 0.6;	
    green := 0.0;	
    blue := 0.0;	
    alpha := 1.0;	
  end;	
  LightPlace.w := 1.0;	
	
  glLightfv(GL_LIGHT0,GL_AMBIENT,@AmbientDef);	
  glLightfv(GL_LIGHT0,GL_DIFFUSE,@AmbientDef);	
  glEnable(GL_LIGHT0);	
end; {FormCreate}	
Save...well, you know the drill. Now, since the only light is red, that is the only light 
reflected, so the cube looks pure red. What will happen if the material property has no red 
component? Find out by removing the red at the top of the Cube method. At the same time, 
return the cube to having only one color response to the light:

procedure TForm1.Cube;	
var	
  ambient: TLightDesc;	
begin	
  ambient.red := 0.0;	
  ambient.green := 0.5;	


PAGE 90
   ambient.blue := 0.8;	
  ambient.alpha := 1.0;	
  glMaterialfv(GL_FRONT,GL_DIFFUSE,@ambient);	
  glMaterialfv(GL_FRONT,GL_AMBIENT,@ambient);	
  glBegin(GL_POLYGON); {near face}	
Repeat the steps and see the result. The cube is black. Only red light is available. The cube 
reflects no red light. Therefore, the cube reflects virtually no light.
Spotlights
The light source discussed so far can be a spotlight. First, this naked light needs some clothes. 
For this stage of the learning adventure, dress it in a...cube. Save the project as Spot.Dpr in 
OpenGL\Chapter.5\Spot, and save the main unit as Spot1.Pas in that directory. Give the 
Cube method the ability to set its material color from a passed parameter. Adjust its 
declaration inside the form class declaration and add a few fields:

    CubeColor,	
    SpotColor,	
    DiffDef,	
    AmbientDef: TLightDesc;	
    LightPlace: TLightPlace;	
    procedure ExceptionGL(Sender:TObject;E:Exception);	
    procedure Cube(matColor:TLightDesc);	
    procedure Axes;	
Now change its implementation code to handle the parameter:

procedure TForm1.Cube(matColor:TLightDesc);	
begin	
  glMaterialfv(GL_FRONT,GL_AMBIENT,@matColor);	
  glMaterialfv(GL_FRONT,GL_DIFFUSE,@matColor);	
  glBegin(GL_POLYGON); {near face}	
Set the color records in the FormCreate method:

  glEnable(GL_LIGHTING);	
  with AmbientDef do	
  begin	
    red := 0.3;	
    green := 0.3;	
    blue := 0.3;	
    alpha := 1.0;	
  end;	
  with DiffDef do	
  begin	
    red := 1.0;	
    green := 1.0;	


PAGE 91
     blue := 1.0;	
    alpha := 1.0;	
  end;	
  with CubeColor do	
    begin	
    red := 0.5;	
    green := 0.0;	
    blue := 0.8;	
    alpha := 1.0;	
  end;	
  with SpotColor do	
  begin	
    red := 0.0;	
    green := 0.5;	
    blue := 0.5;	
    alpha := 1.0;	
  end;	
	
  LightPlace.w := 1.0;	
	
  glLightfv(GL_LIGHT0,GL_AMBIENT,@AmbientDef);	
  glLightfv(GL_LIGHT0,GL_DIFFUSE,@DiffDef);	
Make the two calls to Cube in the FormPaint method use the parameter:

  Cube(CubeColor);	
Also adjust the code that positions the light. Be sure to make this cube smaller with glScalef:

  glPushMatrix;	
  glScalef(0.33,0.33,0.33);	
  glTranslatef(xl,yl,zl);	
  glLightfv(GL_LIGHT0,GL_POSITION,@LightPlace);	
  Cube(SpotColor);	
  glPopMatrix;	
Now, go back to the bottom of the FormCreate method and initialize some positions so you 
will not have to do so much work to position the objects. Note that the variable names 
contain the letter “l” not a number “1.”

  z2 := -2.7;	
  x2 := -0.7;	
  y2 := 0.7;	
  zl := -6.7;	
  xl := -1.8;	
  yl := -2.1;	
end; {FormCreate}	
Since the glScale command affects translation (and rotation), as well as the object size, adjust 
the case statement in the FormKeyPress method:

PAGE 92
 
    ‘l’: xl := xl+3.0*sign;	
    ‘m’: yl := yl+3.0*sign;	
    ‘n’: zl := zl+3.0*sign;	
Save, compile, and run. (You are remembering not to run from the IDE, aren’t you?) The 
magenta (or purple) cube and the cyan (blue-green, turquoise, whatever) “spotlight” are 
visible from the start. Use the “l,” “m,” and “n” keys to move the spotlight around. The 
center of the small cyan cube tracks the position of the light source so that the cube appears 
to be the light source. Notice the effect different light positions have on illumination of the 
magenta cube.
A really interesting effect is the illumination of the small cube from the inside, so that the 
sides of the cube appear to be translucent. This brings up an important fact: OpenGL knows 
nothing about shadows. When you “shine” an OpenGL light source on an OpenGL object, 
you see a very good representation of real illumination. However, if you interpose another 
seemingly solid OpenGL object between the light source and the first object, the more distant 
object receives illumination just as if there were nothing in the way. The consequence of this 
fact is that if you want shadows, you will have to program them yourself. Do not despair; it 
can be done. More about that comes later.
Notice that only half of the cyan cube lights up. By default, an OpenGL diffuse light source 
has a 180 degree spread as shown in Figure 5-3, which includes the cyan cube that 
represented the spotlight. A real spotlight is more focused, and OpenGL permits this effect 
with an appropriate constant in a familiar command:

  glLightf(GL_LIGHT0,GL_SPOT_CUTOFF,cutoff);	
The new constant has this value:

  GL_SPOT_CUTOFF = $1206;            
Express the value of the cutoff variable in degrees. Think of the light spreading from a 
spotlight as a cone of light. The angle from the center of the cone to the edge of the cone is 
the cutoff angle, as shown in Figure 5-4.
         Figure 5-4. The spotlight cutoff angle.

PAGE 93
 Add the cutoff field to the “ell” line in the form’s class declaration:

    xl,yl,zl,cutoff,	
    x1,y1,z1,x2,y2,z2,xr1,yr1,zr1,	
    xr2,yr2,zr2,angle1,angle2,sign: glFloat;	
Use the cutoff field in the FormCreate method:

  glLightfv(GL_LIGHT0,GL_AMBIENT,@AmbientDef);	
  glLightfv(GL_LIGHT0,GL_DIFFUSE,@DiffDef);	
  cutoff := 55.0;	
  glLightf(GL_LIGHT0,GL_SPOT_CUTOFF,cutoff);	
  glEnable(GL_LIGHT0);	
Save, compile, and run. Move the spotlight around with “l,” “m,” and “n.” Notice that you 
must move the spotlight closer to the magenta cube in the x and y directions or farther in the 
positive z direction in order to illuminate the magenta cube.
Directional Spotlights
A feature still missing from the spotlight illustrated thus far in the chapter is the ability to 
aim it. What kind of spotlight is it that you can’t...spot? One thing needed for that purpose 
is to add some more “ell” fields in the form class declaration:

    xl,yl,zl,xrl,yrl,zrl,anglel,cutoff,	
    x1,y1,z1,x2,y2,z2,xr1,yr1,zr1,	
    xr2,yr2,zr2,angle1,angle2,sign: glFloat;	
The xrl, yrl, and zrl fields set up the axis of rotation for the cyan cube and the aiming 
direction of the spotlight. Similarly the anglel field is the number of degrees of rotation about 
the axis for both the cube and the aiming of the spotlight. The default aim of a spotlight is a 
vector from the origin (0.0,0.0,0.0) to (0.0,0.0,–1.0) down the negative z-axis, which points 
toward the far face of the cube at its starting position. Therefore, aiming the far face of the 
cube effectively aims the spotlight.
To manipulate those fields, add to the case statement in the FormKeyPress method:

    'k': anglel := anglel+30.0*sign;	
    'o': xrl := xrl+3.0*sign;	
    'p': yrl := yrl+3.0*sign;	
    'q': zrl := zrl+3.0*sign;	
Add a new type:

  TLightDesc=packed record	
    red,	


PAGE 94
     green,	
    blue,	
    alpha: glFloat;	
  end;	
  TLightPlace=packed record	
    x,	
    y,	
    z,	
    w: glFloat;	
  end;	
  TPosition=packed record	
    x,	
    y,	
    z: glFloat;	
  end;	
Add a new field of that type in the form’s class declaration:

    AmbientDef: TLightDesc;	
    LightPlace: TLightPlace;	
    SpotDirect: TPosition;	
Initialialize SpotDirect in the FormCreate method to match the default value of a spotlight 
direction. Except for the z field, the zeros that come automatically from creating an instance 
of the form are sufficient:

  with SpotColor do	
  begin	
    red := 0.0;	
    green := 0.5;	
    blue := 0.5;	
    alpha := 1.0;	
  end;	
  SpotDirect.z := -1.0;	
  LightPlace.w := 1.0;	
Finally, add the use of SpotDirect and the rotational “ell” fields:

  glPushMatrix;	
  glScalef(0.33,0.33,0.33);	
  if before then	
    glRotatef(anglel,xrl,yrl,zrl);	
  glTranslatef(xl,yl,zl);	
  if not before then	
    glRotatef(anglel,xrl,yrl,zrl);	
  glLightfv(GL_LIGHT0,GL_POSITION,@LightPlace);	
  glLightfv(GL_LIGHT0,GL_SPOT_DIRECTION,@SpotDirect);	
  Cube(SourceColor);	
Here is the declaration of the new constant:

PAGE 95
 
GL_SPOT_DIRECTION = $1204;
Save, compile, run, and press the plus key. Press the lowercase “o” to set the axis of rotation 
for the spotlight in the positive x direction. Press and hold the lowercase “k” to produce 
rotation of the spotlight about the x-axis. Watch the change in lighting on the magenta cube 
as the spotlight direction changes. That is not a bad effect for just a little code. There is 
certainly a lot of power in simple OpenGL commands! Notice the brilliance of the face of the 
cyan cube from which the light seems to emanate.
Emission
Definition
Here is a profound revelation: Light sources emit light. They glow. A surface that glows 
looks different from a surface that merely reflects light. OpenGL provides a way to produce 
this effect. Notice that the subject is the appearance of surfaces. Therefore the appropriate 
command is glMaterialfv rather than glLightfv. The new constant is:

  GL_EMISSION = $1600;	
The purpose of the use of GL_EMISSION is to intensify the surface color so that it seems to 
glow. It does not add to the light shed on other surfaces.
Implementation
Save the spot project as Emission.Dpr in OpenGL\Chapter.5\Emit. Save the main unit as 
Emit1.Pas in the same directory. Since the use of GL_EMISSION applies to the material 
properties of a face in the spotlight cube, some special purpose code is necessary for this 
illustration. In the Cube method, surround the back face part with material commands. The 
if statement tests for a red component so the magenta cubes will not be affected, but the cyan 
cube will be affected. Remember this is just special code for a brief illustration.

  if matColor.red=0.0 then	
    glMaterialfv(GL_FRONT,GL_EMISSION,@matColor);	
  glBegin(GL_POLYGON); {back face}	
    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;	
  glMaterialfv(GL_FRONT,GL_EMISSION,@black);	

PAGE 96
 The black emission statement confines the effect of the previous emission statement to the 
one face; otherwise everything would eventually end up with GL_EMISSION. Of course, the 
black field requires declaration within the form’s class declaration. Happily, it will 
automatically be initialized to all zeros.

    black: TLightDesc;	
    LightPlace: TLightPlace;	
    SpotDirect: TPosition;	
Frankly, since the back face of the cyan cube already almost seems to glow, using matColor 
with GL_EMISSION will have no noticeable effect. Replace it with the emit field:

if matColor.red=0.0 then	
    glMaterialfv(GL_FRONT,GL_EMISSION,@emit);	
Declare emit in the form’s class declaration:

    emit: TLightDesc;	
    black: TLightDesc;	
    LightPlace: TLightPlace;	
    SpotDirect: TPosition;	
Initialize emit in the FormCreate method:

  with emit do	
  begin	
    red := 0.6;	
    green := 1.0;	
    blue := 1.0;	
    alpha := 1.0;	
  end;	
end; {FormCreate}	
Save, compile, run, press the plus symbol, and press the lowercase “o.” Hold down 
lowercase “k” until the back side partially faces the viewer. Notice the intensity of the back 
face. This illustrates a feature that is seldom used, but you may find it comes in handy on 
special occasions.
To see why you needed glMaterialfv(GL_FRONT,GL_EMISSION,@black), comment it out, 
recompile, and rerun. GL_EMISSION affected everything. Ugh! The effect of a glMaterial 
command remains until explicitly changed.
Remember that emission is just a surface effect in OpenGL; it is not one of the three kinds of 
light that come from a light source. The next topic deals with such a type of light.

PAGE 97
 Specular Light
Definition
The third component of light from a light source is specular light. Ambient light has been 
scattered from many surfaces and seems to come from all directions. Diffuse light comes 
from a specific direction (the source, of course), but scatters from surfaces, giving a soft, 
“flat” appearance to surfaces. Ambient and diffuse components of a light source commonly 
share the same color. Specular light is also directional, but much “tighter” in reflection than 
diffuse light, often with a different color. Specular light more closely follows the classical 
rule of reflection, wherein the angle of reflection equals the angle of incidence, as in Figure 5-
5.
As you might expect, the result of reflection of specular light depends greatly on the material 
properties of the surface. You may have noticed that in the real world surfaces that are 
smooth and “shiny” often show a small bright spot in bright, direct light. The bright spot is 
known as the specular highlight. With OpenGL you can give a surface the appearance of being 
smooth and shiny by producing such a bright spot. While the color of the surface with 
respect to ambient and diffuse light may vary, the color of the specular highlight tends 
toward white.
Implementation
To demonstrate the specular component of a light source, load the Spot.Dpr project from 
OpenGL\Chapter.5\Spot. Create a new directory called OpenGL\Chapter.5\Spec and save 
the project as Specular.Dpr into the new directory. Save its main unit as Spec1.Pas in the 
same directory. At the bottom of the FormCreate method, add a new glLightfv command:

  glLightfv(GL_LIGHT0,GL_SPECULAR,@SpecDef);	
end; {FormCreate}	
Actually, LIGHT0 by default has a pure white specular component, but this illustration 
needs something a little less intense. Assign a medium gray to it:


PAGE 98
   with SpecDef do	
  begin	
    red := 0.5;	
    green := 0.5;	
    blue := 0.5;	
    alpha := 1.0;	
  end;	
  glLightfv(GL_LIGHT0,GL_SPECULAR,@SpecDef);	
end; {FormCreate}	
Oh yes! Be sure to declare SpecDef in the form’s class declaration:

    CubeColor,	
    SpotColor,	
    DiffDef,	
    SpecDef,	
    AmbientDef: TLightDesc;	
    LightPlace: TLightPlace;	
    SpotDirect: TPosition;	
For convenience, change the initial value of z2 from –2.7 to –3.7, moving the larger cube 
farther back, so that the spotlight cube will not bury itself in the larger cube in the coming 
illustration.

  z2 := -3.7;	
  x2 := -0.7;	
  y2 := 0.7;	
  zl := -6.7;	
  xl := -1.8;	
  yl := -2.1;	
To the specular light component, add the surface color response to that light:

  glLightfv(GL_LIGHT0,GL_SPECULAR,@SpecDef);	
  glMaterialfv(GL_FRONT,GL_SPECULAR,@SpecDef);	
end; {FormCreate}	
Notice that the new glMaterialfv command is in the FormCreate method. Remember that 
such a command remains in effect until another command changes it. By placing it here, 
with no contradictory command elsewhere, you give all surfaces the same response to 
specular light for all time. Well, it lasts for as long as the program runs, anyway.
The specular effect needs one more specification. Remember that specular highlights appear 
on shiny surfaces. Naturally OpenGL defines a constant for setting shininess in a command:

  GL_SHININESS = $1601;	

PAGE 99
 The shininess property is an integer that can range in value from 0 to 128. Therefore, the 
property needs an integer form of the glMaterial command. Give it maximum shininess for 
this illustration.

  glLightfv(GL_LIGHT0,GL_SPECULAR,@SpecDef);	
  glMaterialfv(GL_FRONT,GL_SPECULAR,@SpecDef);	
  glMateriali(GL_FRONT,GL_SHININESS,128);	
end; {FormCreate}	
Save, compile, run, and press plus. Press the lowercase “m” four times to raise the spotlight 
toward the magenta cube. Do you see the faint light streak running from the lower left 
corner to the upper right corner of the near face? It is not much, but that is as good as it gets 
with a face of a cube. To see a better specular highlight, you need a surface with some 
curvature. A later chapter introduces curved surfaces.
How Much Light?
So far you have seen three light source components: ambient, diffuse, and specular. Each of 
these has three color components: red, green, and blue. There is also an alpha component, 
which still must be put off until later. Never fear! You will see alpha when the time is right. 
You have also seen material properties that specify the response to light. Material properties 
also have ambient, diffuse, and specular parts. Each of these also has the three color 
components.
So what determines how much light of each color reaches the viewer? Would you believe a 
simple multiplication? Figure 5-6 illustrates.

PAGE 100
 
Identifier Reference
Identifier			Description
glColorMaterial
An OpenGL command that causes material properties to track the current color set by 
glColor. It takes two parameters. The first tells which face or faces will be affected. The 
second tells which properties will track the current color. You must enable color tracking for 
this to work.
glLight
An OpenGL command that defines characteristics of an OpenGL light source. You must 
enable lighting and enable the specific light.
glLightModel
An OpenGL command that provides a simplified method of setting up a lighting 
environment instead of defining specific lights.
glMaterial
An OpenGL command for defining material properties.
GL_AMBIENT
An OpenGL constant specifying the ambient portion of light or material properties in 
various OpenGL commands.
GL_BACK
An OpenGL constant specifying the back face of polygons to be affected in various OpenGL 
commands.
GL_COLOR_MATERIAL
The OpenGL constant to pass to enable or disable color tracking.
GL_DIFFUSE
An OpenGL constant specifying the diffuse portion of light or material properties in various 
OpenGL commands.
GL_EMISSION
An OpenGL constant to pass to the glMaterial command, so that the affected surfaces appear 
to be glowing.
GL_FRONT
An OpenGL constant specifying the front face of polygons to be affected in various OpenGL 
commands.
GL_FRONT_AND_
BACK
An OpenGL constant specifying both the front and back faces of polygons to be affected in 
various OpenGL commands.

PAGE 101
 
Identifier			Description
GL_LIGHT0 ..
GL_LIGHT7
OpenGL constants for specifying specific OpenGL light sources.
GL_LIGHTING
An OpenGL constant to pass to glEnable or glDisable to turn on or turn off lighting 
calculations.
GL_LIGHT_MODEL_
AMBIENT
An OpenGL constant to pass to glLightModel to specify global ambient light.
GL_POSITION
An OpenGL constant to pass to glLight to specify the position coordinate of the light.
GL_SHININESS
An OpenGL constant to pass to the glMaterial command for setting the shininess property.
GL_SPECULAR
An OpenGL constant specifying the specular portion of light or material properties in 
various OpenGL commands.
GL_SPOT_CUTOFF
An OpenGL constant to pass to glLight to specify the angle that defines the cone of 
illumination from a spotlight.
GL_SPOT_DIRECTION
An OpenGL constnt to pass to glLight to specify the direction vector of the light.
packed
An Object Pascal reserved word used to make a data structure take the minimum amount of 
memory. The elements of the structure fit together with no unused memory between them 
because the compiler does not attempt to align the data elements on word or double-word 
boundaries.
PGLfloat
A data type which is a pointer to a Glfloat.
Summary
Chapter 5 presented new levels of realism with lighting. Here are the high points:
? Use glEnable(GL_LIGHTING) to inform OpenGL that you will take responsibility for 
lighting specification.
? OpenGL allows at least eight separate light sources.

PAGE 102
 
? All light has a source. Each light source has three components whose color compositions 
can be separately identified:
? Ambient light seems to come from all directions.
? Diffuse light comes from a specific direction and scatters upon reflection, giving a soft, 
flat appearance to surfaces.
? Specular light reflects sharply and appears as a specular highlight on smooth surfaces.
? Specify the color response of a surface to each light component with the glMaterial 
commands.
? Emission is also a material property for OpenGL.
? In addition to the color response to specular light, you must also specify the smoothness 
or shininess of the surface with the GL_SHININESS constant in the glMaterial command.


PAGE 103
 

PAGE 104

Chapter 6
Components
Create a new project. Copy the FormCreate method from the previous project. Wait! This is 
getting tedious! Delphi programming is not usually this way. What are the building blocks 
for which Delphi is famous? They are components, of course. The time has come to use this 
Delphi strength with OpenGL programming.
Setup
As usual, create a new directory under the OpenGL directory, name it Chapter.6, then create 
a directory under that and name it Components. Create a new project and save it in the new 
directory. Name the form unit Dummy, since the form itself is not important for this project. 
Name the project Comp0.Dpr, then create units named cOpenGL.Pas, Transfrm.Pas, 
Material.Pas, and Light.Pas, and add them to the project.
OpenGL Component
The first component required is one that will change a Delphi form into an OpenGL 
window. Naturally it will need access to other units in order to perform this impressive 
function. In the cOpenGL unit, list the units it requires in the interface section:

unit cOpenGL;	
	
interface	
	
uses	
  Windows,	
  SysUtils,	
  Classes,	
  Controls,	
  Forms,	


PAGE 105
   Dialogs,	
  OpenGL;	
Preliminary Types
Below the uses clause, define some types. An important consideration in a type declaration 
for use by a component is the ability of the Object Inspector to display it. Start with the flags 
used by the pixel format descriptor. First declare an enumerated type, then make a set out of 
it. There are already property editors defined to handle these types.

type	
  TPFDFlag = (	
    DoubleBuffer,	
    Stereo,	
    Draw_To_Window,	
    Draw_To_Bitmap,	
    Support_GDI,	
    Support_OpenGL,	
    Generic_Format,	
    Need_Palette,	
    Need_System_Palette,	
    Swap_Exchange,	
    Swap_Copy,	
    Swap_Layer_Buffers,	
    Generic_Accelerated	
  ); {TPFDFlag}	
  TPFDFlags= set of TPFDFlag;	
The set type corresponds exactly to some PFD constants declared in the Windows unit to be 
successive powers of two. Elements of a set are represented by bits in a series of bytes, so 
when an integer overlays the set (using the absolute directive) the powers of two magically 
appear. To avoid a transcontinental Object Inspector, be sure to use mixed case for the 
identifiers and leave off the PFD_ prefix found in the constants in the Windows unit. Now 
declare some more enumerated and set types:

  TPFDPixelType=(	
    RGBA,	
    ColorIndex	
  );	
  TProjection=(	
    Orthographic,	
    Perspective	
  );	
  TEnable=(	
   depthTest,	
   cullFace,	


PAGE 106
    Lighting	
  );	
  TEnables=set of TEnable;	
The Component Fields and Other Private Matters
With those preliminaries out of the way, declare the component:

TOpenGL=class(TComponent)	
private	
public	
published	
end; {TOpenGL}	
Admittedly, this little type declaration does not provide any substance, but it does provide a 
framework on which to build. Start by putting in actual data fields in the private section. 
These should be familiar, since they are basically the same fields you previously declared in 
each new form. Now you can see where this is headed. Instead of declaring these things 
every time you create an OpenGL project, all you need to do is drop an OpenGL component 
on the form. Of course, this is only the beginning of such conveniences.

  private	
    GLContext: HGLRC;	
    FReady: boolean;	
    FFlags: TPFDFlags;	
    FPixelType: TPFDPixelType;	
    FColorBits,	
    FDepthBits,	
    FStencilBits,	
    oldWidth,	
    oldHeight: integer;	
    FProjection: TProjection;	
    FEnable: TEnables;	
    FClearRed,FClearGreen,FClearBlue: GLclampf;	
    FLeft,FRight,FBottom,FTop,FNear,FFar: GLdouble;	
Next, declare a private method. Again, this should be familiar, since it comes from the forms 
you created before:

    procedure ExceptionGL(Sender:TObject;E:Exception);	
The Public Section
In the public section, declare the constructor, destructor, a few other methods, and a read-
only property. Override the constructor to set some initial default values, and override the 
Loaded method to perform the setup you previously placed in the FormCreate method.


PAGE 107
   public	
    glDC: HDC;	
    constructor Create(AOwner:TComponent); override;	
    procedure Loaded; override;	
    procedure ErrorCheck(const s:string);	
    procedure Swap;	
    function Smaller: boolean;	
    property Ready: boolean read FReady;	
Why is Ready a property? Why is it not published? The answer to both questions is that it is 
information to be read, not manipulated. The underlying field is private so only the 
component can set it. The property is public but read-only, so the information is available 
but immune from accidental tampering.
The Published Section
One of the main points about making a component is to have properties you can manipulate 
appear in the Object Inspector. In order for a property to appear there it must:
1.
Be published (declared in the published section).
2.
Be changeable. Read-only properties will not appear.
3.
Be type-compatible with an available property editor.
The third requirement may not be clear unless you have written a few components already. 
For now just accept the fact that the Object Inspector uses entities called property editors to 
display and change values, and that each type declared in these components has a standard 
property editor already in place. You will not have to write one. Here are the published 
properties:

  published	
    property pfdFlags: TPFDFlags read FFlags write FFlags;	
    property PixelType: TPFDPixelType read FPixelType write FPixelType;	
    property Projection: TProjection read FProjection write FProjection;	
    property Enable: TEnables read FEnable write FEnable;	
    property ClearRed: GLclampf read FClearRed write FClearRed;	
    property ClearGreen: GLclampf read FClearGreen write FClearGreen;	
    property ClearBlue: GLclampf read FClearBlue write FClearBlue;	
    property prxLeft: GLdouble read FLeft write FLeft;	
    property prxRight: GLdouble read FRight write FRight;	
    property prxBottom: GLdouble read FBottom write FBottom;	
    property prxTop: GLdouble read FTop write FTop;	
    property prxNear: GLdouble read FNear write FNear;	
    property prxFar: GLdouble read FFar write FFar;	
    property ColorBits: integer read FColorBits write FColorBits;	
    property DepthBits: integer read FDepthBits write FDepthBits;	


PAGE 108
     property StencilBits: integer read FStencilBits write FStencilBits;	
  end; {TOpenGL}	
These properties provide values to pass as parameters, or to fill in a structure that you pass 
as a parameter. ColorBits and DepthBits are examples of the latter. The first four properties 
implement the newly defined types. The first two are also fields in a sizable structure for 
which the Windows API is so famous. ClearRed, ClearGreen, and ClearBlue allow you to set 
the parameters to pass to glClearColor. The next six properties, prxLeft through prxFar, 
provide the parameters for glOrtho or glFrustum. The prx prefix simply serves to group the 
parameters together in the alphabetic listing in the Object Inspector.
Implementation
Start
Only a few methods appear in this component, but they are the heart of this approach to 
using OpenGL. The constructor is a good place to start. The values set therein serve as 
reasonable default values until they are deliberately changed or loaded from a stream if the 
component had already been created and saved. Naturally, anything not explicitly set 
defaults to zero or the equivalent. Do not confuse this idea of default values with the default 
keyword, which simply identifies a specific value that does not require storage on the 
stream.

constructor TOpenGL.Create(AOwner:TComponent);	
begin	
  inherited Create(AOwner);	
  FProjection := Perspective;	
  FLeft := -1.0;	
  FRight := 1.0;	
  FBottom := -1.0;	
  FTop := 1.0;	
  FNear := 2.0;	
  FFar := 9.0;	
  FColorBits := 24;	
  FDepthBits := 32;	
  FEnable := [depthTest,cullFace];	
  FFlags := [DRAW_TO_WINDOW,SUPPORT_OPENGL];	
end;	
When you design a form and save it, you stream it into the .DFM file, which contains the 
form and all its components, including their property settings. When you load the project, 
each form you create streams its components and property values back from the .DFM file. 
Of course, when you compile the project, the contents of the .DFM files become part of the 

PAGE 109
 executable. At run time the forms, etc., stream from the executable file. When a particular 
component, such as a TOpenGL component, appears on the stream, the component is 
created, using the constructor. The stream continues to “pour” in, reading the stored 
property values. Once the entire component has streamed in, the Loaded method fires. 
Listing 6-1 shows the Loaded method for the TOpenGL component, which does the job of 
the FormCreate method of earlier projects. The various Windows and OpenGL API calls had 
to wait for the Loaded method, instead of the Create constructor, because the values of the 
properties remained unknown until the component had been “loaded” from the stream. 
Hence the name.

procedure TOpenGL.Loaded;	
var	
  pfd: TPixelFormatDescriptor;	
  FormatIndex: integer;	
  pflags: TPFDFlags;	
  flags: word absolute pFlags;	
begin	
  inherited Loaded;	
  if (Owner<>nil) then	
  with TWinControl(Owner) do	
    begin	
    pflags := pfdFlags;	
    fillchar(pfd,SizeOf(pfd),0);	
    with pfd do	
      begin	
      nSize      := SizeOf(pfd);	
      nVersion   := 1;	
      dwFlags    := flags;	
      iPixelType := byte(PixelType);	
      cColorBits := ColorBits;	
      cDepthBits := DepthBits;	
      cStencilBits := StencilBits;	
      iLayerType := PFD_MAIN_PLANE;	
    end; {with}	
    glDC := getDC(handle);	
    FormatIndex := ChoosePixelFormat(glDC,@pfd);	
    if FormatIndex=0 then	
      raise Exception.Create('ChoosePixelFormat failed '+	
        IntToStr(GetLastError));	
	
    if not SetPixelFormat(glDC,FormatIndex,@pfd) then	
      raise Exception.Create('SetPixelFormat failed '+	
        IntToStr(GetLastError));	
	
    GLContext := wglCreateContext(glDC);	
    if GLContext=0 then	
      raise Exception.Create('wglCreateContext failed '+	


PAGE 110
         IntToStr(GetLastError));	
	
    if not wglMakeCurrent(glDC,GLContext) then	
      raise Exception.Create('wglMakeCurrent failed '+	
        IntToStr(GetLastError));	
    if depthTest in Enable then	
      glEnable(GL_DEPTH_TEST);	
    if cullFace in Enable then	
      glEnable(GL_CULL_FACE);	
    if Lighting in Enable then	
      glEnable(GL_LIGHTING);	
	
    glMatrixMode(GL_PROJECTION);	
    if Projection=ORTHOGRAPHIC then	
      glOrtho(prxLeft,prxRight,prxBottom,prxTop,prxNear,prxFar)	
    else	
      glFrustum(prxLeft,prxRight,prxBottom,prxTop,prxNear,prxFar);	
	
    glClearColor(FClearRed,FClearGreen,FClearBlue,1.0);	
	
    ErrorCheck('Loaded');	
	
    FReady := true;	
	
  end; {if}	
  Application.OnException := ExceptionGL;	
end;	
Listing 6-1. The Loaded method.
Running
The last line of the Loaded method sets the exception handler, which has to be a method of 
the form. The method shows the exception just raised, clears it, then unwinds and shows any 
accumulated OpenGL errors:

procedure TOpenGL.ExceptionGL(Sender:TObject;E:Exception);	
var	
  errorCode: GLenum;	
begin	
  ShowMessage(E.Message);	
  repeat	
    errorCode := glGetError;	
    if errorCode<>GL_NO_ERROR then	
      showMessage(gluErrorString(errorCode));	
  until errorCode=GL_NO_ERROR;	
end;	

PAGE 111
 The Smaller function encapsulates most of the FormResize functionality and more:

function TOpenGL.Smaller:boolean;	
begin	
  result := (Owner<>nil) and Ready;	
  if not result then	
    exit;	
  with TWinControl(Owner) do	
  begin	
    glViewPort(0,0,ClientWidth,ClientHeight);	
    ErrorCheck(‘Smaller’);	
    result := (ClientWidth<=oldWidth) and (ClientHeight<=oldHeight);	
    oldWidth := ClientWidth;	
    oldHeight := ClientHeight;	
  end;	
end;	
Smaller does not eliminate the FormResize method, but does simplify it. Here is a sample 
FormResize method to demonstrate the use of Smaller. This Resize method is much shorter 
than the ones you have written before because of the TOpenGL component. Note that this 
code is not part of the unit you are currently building.

procedure TForm1.FormResize(Sender: TObject);	
begin	
  if OpenGL1.Smaller then	
    FormPaint(Sender);	
end;	
Both Loaded and Smaller eliminated tedious repetition by calling the ErrorCheck method. 
They pass a string that identifies where an error may have occurred, then ErrorCheck does 
the rest. First it determines if there was an error, then reports the error if present.

procedure TOpenGL.ErrorCheck(const s:string);	
var	
  errorCode: GLenum;	
 begin	
  errorCode := glGetError;	
  if errorCode<>GL_NO_ERROR then	
    raise Exception.Create(‘Error in ‘+s+#13+	
      gluErrorString(errorCode));	
end;	
The Swap procedure just calls the SwapBuffers Windows function, which needs the handle 
to the device context. Its use will become evident a few chapters later.


PAGE 112
 procedure TOpenGL.Swap;	
begin	
  SwapBuffers(glDC);	
end;	
Finish
The job is not finished until the cleanup. When you exit your application, you must put away 
the Windows resources you previously obtained. The rule of thumb is that the one who 
allocates should be the one who deallocates. Delphi’s VCL (Visual Component Library) 
simplifies the Windows programmer’s life dramatically by obtaining resources when needed 
and putting them away when finished. However, if your application code explicitly allocates 
memory, creates a handle, or otherwise obtains resources, generally your application code 
must explicitly clean up. Similarly, this component obtained a device context as part of the 
setup and must release it when done.
When the form is being destroyed, it arranges for the destruction of all the components it 
owns. The form owns any component placed on it. Although the programmer “placed” the 
component with a few mouse clicks, Delphi behind-the-scenes code actually did the creating 
and loading of the component. Appropriately, Delphi behind-the-scenes code disposes of the 
components at the end. Since the component is automatically destroyed, you must arrange 
for the component, as it is passing away, to clean up after itself automatically. You do that by 
overriding the destructor:

destructor TOpenGL.Destroy;	
begin	
  wglMakeCurrent(glDC,0);	
  wglDeleteContext(GLContext);	
  ReleaseDC(TWinControl(Owner).Handle,glDC);	
  FReady := false;	
  inherited Destroy;	
end;	
There is just one little problem: The destructor will bomb. The owner/form has its own 
destructor, of course, and as a good software citizen it performs cleanup in the same fashion. 
It releases its allocated resources, then calls its inherited destructor. Each preceding ancestor 
does the same. Therein lies the problem. In the ancestral chain the window handle is 
destroyed at the TWinControl level, while the owned components are destroyed at the 
TComponent level. As a result, by the time the TOpenGL destructor is ready to use its 
owner’s window handle, the window handle is already gone. Boom!

PAGE 113
 Fortunately the form’s OnDestroy event handler (typically called FormDestroy) fires before 
the form’s destructor. Let the TOpenGL component do a little sleight-of-hand with its 
owner’s OnDestroy event handler to solve the problem. Instead of using a destructor, put 
the cleanup code in a method that would qualify as an event handler:

procedure TOpenGL.ShutDown(Sender: TObject);	
begin	
  wglMakeCurrent(glDC,0);	
  wglDeleteContext(GLContext);	
  ReleaseDC(TWinControl(Owner).Handle,glDC);	
  FReady := false;	
  if Assigned(OldShutdown) then	
    OldShutDown(Sender);	
end;	
In the TOpenGL component’s type declaration, add the declaration of this method and the 
new field:

  private	
    GLContext: HGLRC;	
    FReady: boolean;	
    FFlags: TPFDFlags;	
    FPixelType: TPFDPixelType;	
    FColorBits,	
    FDepthBits,	
    FStencilBits,	
    oldWidth,	
    oldHeight: integer;	
    FProjection: TProjection;	
    FEnable: TEnables;	
    FClearRed,FClearGreen,FClearBlue: GLclampf;	
    FLeft,FRight,FBottom,FTop,FNear,FFar: GLdouble;	
    OldShutDown: TNotifyEvent;	
    procedure ShutDown(Sender:TObject);	
    procedure ExceptionGL(Sender:TObject;E:Exception);	
In the component constructor you perform the little feat of legerdemain:

constructor TOpenGL.Create(AOwner:TComponent);	
var	
  frm: Tform;	
begin	
  inherited Create(AOwner);	
  FProjection := Perspective;	
  FLeft := -1.0;	
  FRight := 1.0;	
  FBottom := -1.0;	
  FTop := 1.0;	
  FNear := 2.0;	


PAGE 114
   FFar := 9.0;	
  FColorBits := 24;	
  FDepthBits := 32;	
  FEnable := [depthTest,cullFace];	
  FFlags := [DRAW_TO_WINDOW,SUPPORT_OPENGL];	
  frm := TForm(AOwner);	
  OldShutDown := frm.OnDestroy;	
  frm.OnDestroy := ShutDown;	
end;	
Did you see it? Or is the hand (or word processor) quicker than the eye? It saves the owner’s 
OnDestroy event handler in the OldShutDown field. Then it substitutes its own method for 
the form’s original event handler. When the form is about to destroy itself, it checks for an 
OnDestroy event handler. If it has one it calls the handler, passing itself as the Sender 
parameter. The code (simplified) looks something like this:

destructor TCustomForm.Destroy;	
begin	
  if Assigned(FOnDestroy) then	
    FOnDestroy(Self);	
  inherited Destroy;	
end;	
The TOpenGL component ensures that there is an OnDestroy handler, its own ShutDown 
method. So the form obligingly calls the component’s cleanup code. That code, in turn, 
checks to see if there was an original OnDestroy method (stored in OldShutDown) and, if so, 
calls it. Presto! The form never knows the difference! Then the form calls its ancestor 
destructor, wherein the window handle and other resources are released, after ReleaseDC 
has done its job.
Registration
Any installable component must register itself with Delphi. The component installation 
process will detect a procedure named (appropriately) Register. It calls the 
RegisterComponents procedure, whose two parameters identify the page of the component 
palette (‘OpenGL’) and the list of component types being registered for that page:

procedure Register;	
begin	
  RegisterComponents('OpenGL',[TOpenGL]);	
end;	
	
end.	

PAGE 115
 Since this is a simple procedure, and not a method of the component, its declaration must be 
exposed in the interface section, typically as the last thing:

procedure Register;	
	
implementation	
Transformation Component
While the TOpenGL component sets up an environment, the TTransform component is 
“where the action is.”
Units
In the interface section of the Transfrm unit, list the units it uses:

unit Transfrm;	
	
interface	
	
uses	
  Classes,	
  OpenGL;	
The Class Declaration
TTransform is a direct descendent of TComponent.

type	
  TTransform=class(TComponent)	
  private	
  protected	
  public	
  published	
  end; {TTransform}	
The Private Section
The private section contains 10 floating-point fields that provide the parameters for the three 
kinds of transformation: translation, rotation, and scale. The remaining field underlies a 
property that will appear on the Events page of the Object Inspector.

  TTransform=class(TComponent)	
  private	
    FTranX,	


PAGE 116
     FTranY,	
    FTranZ,	
    FAxisX,	
    FAxisY,	
    FAxisZ,	
    FAngle,	
    FScaleX,	
    FScaleY,	
    FScaleZ: GLfloat;	
    FOnPaint: TNotifyEvent;	
The Protected Section
The protected section contains just one item, a virtual method. The method is virtual so that 
a possible descendant component can override it. The method is protected so that the 
component alone decides when to call it. Making it private would accomplish that, but 
would prevent overriding by a descendant.

  protected	
    procedure Paint; virtual;	
The Public Section
The public section has the constructor and a method. The public Apply method is the 
component user’s gateway to “do something.”

  public	
    constructor Create(AOwner:TComponent); override;	
    procedure Apply;	
The Published Section
The 11 published properties expose each of the private fields to the Object Inspector. That is 
their sole purpose in this component. The OnPaint property is noteworthy since it will 
appear on the Events page. The event handler you can create by means of this property is 
where you can place your rendering code.

  published	
    property TranX: GLfloat read FTranX write FTranX;	
    property TranY: GLfloat read FTranY write FTranY;	
    property TranZ: GLfloat read FTranZ write FTranZ;	
    property AxisX: GLfloat read FAxisX write FAxisX;	
    property AxisY: GLfloat read FAxisY write FAxisY;	
    property AxisZ: GLfloat read FAxisZ write FAxisZ;	
    property Angle: GLfloat read FAngle write FAngle;	
    property ScaleX: GLfloat read FScaleX write FScaleX;	
    property ScaleY: GLfloat read FScaleY write FScaleY;	


PAGE 117
     property ScaleZ: GLfloat read FScaleZ write FScaleZ;	
    property OnPaint: TNotifyEvent read FOnPaint write FOnPaint;	
  end; {TTransform}	
Implementation
The interface portion of the unit ends with the obligatory declaration of the Register 
procedure. Then the implementation portion provides the code for the three declared 
methods and the Register procedure.

procedure Register;	
	
implementation	
The constructor sets default values of 1.0 for the scale fields. If nothing changes them, all the 
scale fields will leave the scene unaffected by the scale transformation. The automatic value 
of zero for all the other fields is the appropriate default so the other two transformations will 
do nothing. If you want to make a transformation, you must do it on purpose. The 
component just lets you do it more easily.

constructor TTransform.Create(AOwner:TComponent);	
begin	
  inherited Create(AOwner);	
  FScaleX := 1.0;	
  FScaleY := 1.0;	
  FScaleZ := 1.0;	
end;	
The Apply method systematically allows you to create a scene, or portion thereof, save the 
state of the ModelView matrix, transform the scene, and restore the matrix. Your scene, or 
scene portion, or object, or whatever will be rendered by means of the Paint method. The 
glScalef command is the last one before the call to Paint so that the scale commands will only 
affect the dimensions of the drawing, and not the translation and rotation. Scaling them is 
perfectly legitimate, but would make them harder to predict. This component takes the 
simplistic approach that scaling will be used to reuse a common object with different 
dimensions.

procedure TTransform.Apply;	
begin	
  glMatrixMode(GL_MODELVIEW);	
  glPushMatrix;	
  glTranslatef(TranX,TranY,TranZ);	
  glRotatef(FAngle,FAxisX,FAxisY,FAxisZ);	
  glScalef(FScaleX,FScaleY,FScaleZ);	
  Paint;	


PAGE 118
   glPopMatrix;	
end;	
You call the Apply method of each TTransform component from the OnPaint event handler 
of the form. Note that the following sample code is not part of the unit under development.

procedure TForm1.FormPaint(Sender: TObject);	
begin	
  glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT);	
  Transform3.Apply;	
  Transform1.Apply;	
  Transform2.Apply;	
  glFlush;	
  OpenGL1.ErrorCheck('FormPaint');	
end;	
The Paint method of the TTransform component serves to fire the OnPaint event handler if it 
exists. The programmer (user of the component) creates the event handler to place the 
rendering code.

procedure TTransform.Paint;	
begin	
  if Assigned(FOnPaint) then	
    FOnPaint(Self);	
end;	
The Register procedure registers the TTransform component for the ‘OpenGL’ page.

procedure Register;	
begin	
  RegisterComponents('OpenGL',[TTransform]);	
end;	
	
end.	
Material Component
The glMaterial command is very important to the appearance of objects in a scene, and is 
encapsulated by the TMaterial component type. The code and explanations follow.
Units
In the interface part of the Material unit, list the units it needs:

unit Material;	


PAGE 119
 	
interface	
	
uses	
  SysUtils,	
  Classes,	
  OpenGL;	
Preliminary Types
The commands you are about to encapsulate in this unit and others include vector versions. 
In OpenGL this traditionally means passing an array as the last parameter. Fortunately, 
Object Pascal provides a way to declare more meaningful structures with the same memory 
layout. To OpenGL, the passed data will look just like the array it expected. Use a record 
layout to give names to each field in the data. Use packed records so that the compiler will 
not attempt to align the data on particular boundaries. While the alignment of data is 
efficient for the processor, it might cause the data to have “gaps” which would not be 
present in the array expected by an OpenGL vector command.
The types specified here will be used many times, so they are worthy of a little attention. A 
TGLColor record provides the three colors and the alpha value needed for most color 
specifications. A TGLPlace record provides the endpoint of a three-dimensional vector. A 
TGLPosition record specifies a location. Last (and maybe least, in the sense that it is not a 
record) is TFace, an enumerated type by which to define which face(s) a glMaterial 
command affects.

type	
  TGLColor=packed record	
    red,green,blue,alpha: GLclampf;	
  end;	
  TGLPlace=packed record	
    x,y,z: glFloat;	
  end;	
  TGLPosition=packed record	
    x,y,z,w: glFloat;	
  end;	
	
  TFace=(faFront,faBack,faFrontAndBack);	
The Class Declaration
The TMaterial component type has the usual sections.

  TMaterial=class(TComponent)	
  private	


PAGE 120
   public	
  published	
  end; {TMaterial}	
The Private Section
The TMaterial component has three TFace fields for use with specular, diffuse, and ambient 
response to light. These enumerated type fields are supported by a standard property editor, 
so the corresponding properties can appear in the Object Inspector. Of course, the GLclampf 
field (really a single) also enjoys this benefit. Unfortunately, the two TGLColor fields, being 
complex types, do not share this characteristic. As a result, 16 property access methods also 
appear in the private section, as shown in Listing 6-2:

  TMaterial=class(TComponent)	
  private	
    FsFace,	
    FdFace,	
    FmFace: TFace;	
    FAmbColor,	
    FDifColor: TGLColor;	
    FShine: GLclampf;	
    procedure SetRed(value:GLclampf);	
    function GetRed:GLclampf;	
    procedure SetGreen(value:GLclampf);	
    function GetGreen:GLclampf;	
    procedure SetBlue(value:GLclampf);	
    function GetBlue:GLclampf;	
    procedure SetAlpha(value:GLclampf);	
    function GetAlpha:GLclampf;	
    procedure SetAmbRed(value:GLclampf);	
    function GetAmbRed:GLclampf;	
    procedure SetAmbGreen(value:GLclampf);	
    function GetAmbGreen:GLclampf;	
    procedure SetAmbBlue(value:GLclampf);	
    function GetAmbBlue:GLclampf;	
    procedure SetAmbAlpha(value:GLclampf);	
    function GetAmbAlpha:GLclampf;	
Listing 6-2. The TMaterial private section.
The Public Section
Only one TMaterial item is public, the Apply method. This method is really intended for use 
by TTransfer components rather than directly by the programmer.

PAGE 121
 
  public	
    procedure Apply;	
The Published Section
There is no point in publishing complex properties, since there are no standard property 
editors for them. Not only is writing such property editors outside the scope of this book, 
but it also is recommended against by component writing authorities. These simple 
properties instead expose simple parts of the more complex fields by means of the property 
access methods. The three face properties allow setting which face or faces apply to ambient, 
diffuse, and specular glMaterial commands.

  published	
    property ambRed: GLclampf read GetAmbRed write SetAmbRed;	
    property ambGreen: GLclampf read GetAmbGreen write SetAmbGreen;	
    property ambBlue: GLclampf read GetAmbBlue write SetAmbBlue;	
    property ambAlpha: GLclampf read GetAmbAlpha write SetAmbAlpha;	
    property difRed: GLclampf read GetRed write SetRed;	
    property difGreen: GLclampf read GetGreen write SetGreen;	
    property difBlue: GLclampf read GetBlue write SetBlue;	
    property difAlpha: GLclampf read GetAlpha write SetAlpha;	
    property aFace: TFace read FmFace write FmFace;	
    property dFace: TFace read FdFace write FdFace;	
    property sFace: TFace read FsFace write FsFace;	
    property mShine: GLclampf read FShine write FShine;	
  end; {TMaterial}	
Variables
A couple of variables needed by the TMaterial component may be useful elsewhere, so they 
are global and exposed in the interface section:

var 	
  black,	
  white: TGLColor;	
  	
implementation	
These variables need specific values, so at the end of the unit set those values in the 
initialization section:

initialization	
   with white do	
  begin	
    red := 1.0;	
    green := 1.0;	
    blue := 1.0;	
    alpha := 1.0;	


PAGE 122
   end; {with}	
  fillchar(black,sizeOf(black),0);	
  black.alpha := 1.0;	
end.	
Implementation
You need a couple of variables to use for specular light:

end; {TMaterial}	
	
var	
  black,	
  white: TGLColor;	
Naturally, your code must expose a Register procedure to make the component installable. 
Then comes the implementation section:

procedure Register;	
	
implementation	
TMaterial has no special constructor; the TComponent constructor is sufficient. Nearly all 
the new code for this component resides in the property access methods. Perhaps the most 
important field is the diffuse part of material properties. Remember that the ambient and 
diffuse elements of material properties relate to the response to ambient and diffuse light. 
The diffuse part has four subfields, and each subfield has a get method for reading and a set 
method for writing. While the entire diffuse record cannot appear in the Object Inspector, 
the following eight methods allow the subfields to appear. The properties are difRed, 
difGreen, difBlue, and difAlpha.

procedure TMaterial.SetRed(value:GLclampf);	
begin	
  FDifColor.red := value;	
end;	
	
function TMaterial.GetRed:GLclampf;	
begin	
  result := FDifColor.red;	
end;	
	
procedure TMaterial.SetGreen(value:GLclampf);	
begin	
  FDifColor.green := value;	
end;	
	
function TMaterial.GetGreen:GLclampf;	


PAGE 123
 begin	
  result := FDifColor.green;	
end;	
	
procedure TMaterial.SetBlue(value:GLclampf);	
begin	
  FDifColor.blue := value;	
end;	
	
function TMaterial.GetBlue:GLclampf;	
begin	
  result := FDifColor.blue;	
end;	
	
procedure TMaterial.SetAlpha(value:GLclampf);	
begin	
  FDifColor.alpha := value;	
end;	
	
function TMaterial.GetAlpha:GLclampf;	
begin	
  result := FDifColor.alpha;	
end;	
Similarly, the ambient record cannot appear in the Object Inspector, but its subfields can 
because of the next eight methods. The properties are ambRed, ambGreen, ambBlue, and 
ambAlpha.

procedure TMaterial.SetAmbRed(value:GLclampf);	
begin	
  FAmbColor.red := value;	
end;	
	
function TMaterial.GetAmbRed:GLclampf;	
begin	
  result := FAmbColor.red;	
end;	
	
procedure TMaterial.SetAmbGreen(value:GLclampf);	
begin	
  FAmbColor.green := value;	
end;	
	
function TMaterial.GetAmbGreen:GLclampf;	
begin	
  result := FAmbColor.green;	
end;	
	
procedure TMaterial.SetAmbBlue(value:GLclampf);	


PAGE 124
 begin	
  FAmbColor.blue := value;	
end;	
	
function TMaterial.GetAmbBlue:GLclampf;	
begin	 
  result := FAmbColor.blue;	
end;	
	
procedure TMaterial.SetAmbAlpha(value:GLclampf);	
begin	
  FAmbColor.alpha := value;	
end;	
	
function TMaterial.GetAmbAlpha:GLclampf;	
begin	
  result := FAmbColor.alpha;	
end;	
The Apply method selects faces, passes color information, and executes glMaterial 
commands for ambient, diffuse, and specular material properties. Notice that no specular 
colors appear in this component. Since specular highlights normally match the color of the 
specular component of the light source, this code makes no attempt to set a specular material 
color. Instead it passes the shininess value, a number from 1 to 128. The higher numbers 
result in a brighter and smaller (tighter) highlight. Naturally, 0 means no highlight at all.

procedure TMaterial.Apply;	
var	
  aface: GLenum;	
  spec: pointer;	
begin	
  case FmFace of	
    faFront: aface:= GL_FRONT;	
    faBack: aface:= GL_BACK;	
    faFrontAndBack: aface:= GL_FRONT_AND_BACK;	
  end; {case}	
  glMaterialfv(aface,GL_AMBIENT,@FAmbColor);	
	
  case FdFace of	
    faFront: aface:= GL_FRONT;	
    faBack: aface:= GL_BACK;	
    faFrontAndBack: aface:= GL_FRONT_AND_BACK;	
  end; {case}	
  glMaterialfv(aface,GL_DIFFUSE,@FDifColor);	
	
  case FsFace of	
    faFront: aface:= GL_FRONT;	


PAGE 125
     faBack: aface:= GL_BACK;	
    faFrontAndBack: aface:= GL_FRONT_AND_BACK;	
  end; {case}	
  if FShine=0 then	
    spec := @black	
  else	
    spec := @white;	
  glMaterialfv(aface,GL_SPECULAR,spec);	
  glMaterialf(aface,GL_SHININESS,FShine);	
end;	
The Register procedure puts the TMaterial component on the OpenGL page:

procedure Register;	
begin	
  RegisterComponents(‘OpenGL’,[TMaterial]);	
end;	
	
initialization	
    with white do	
    begin	
    red := 1.0;	
    green := 1.0;	
    blue := 1.0;	
    alpha := 1.0;	
  end; {with}	
  fillchar(black,sizeOf(black),0);	
  black.alpha := 1.0;	
end.	
Light Component
The TLight component encapsulates some of the most interesting, but tricky, OpenGL 
features the book has covered so far. OpenGL lighting can provide impressive realism to 
your scenes.
Units
The unit declaration is like the others you have seen, but uses the Material unit as well:

unit Light;	
	
interface	
	
uses	
  SysUtils,	


PAGE 126
   Classes,	
  Material,	
  OpenGL;	
The Class Declaration
TLight has the usual component sections, but wait until you see how many fields, methods, 
and properties it has when filled in!

type	
  TLight=class(TComponent)	
  private	
  public	
  published	
  end; {TLight}	
The Private Section
The private field FWhich, the first one in Listing 6-3, identifies which light the component 
contains, in order to pass the correct value to some OpenGL commands. OpenGL declares at 
least eight separate lights: GL_LIGHT0 to GL_LIGHT7. The values are $4000 to $4007. Some 
implementations may provide for more lights, but 0 to 7 are guaranteed to be supported by 
any OpenGL version. This component makes no attempt to detect and support any 
additional lights that may be available, although you can make such a modification after you 
have finished this book.

  TLight=class(TComponent)	
  private	
    FWhich: GLenum;	
    FAmbient: TGLColor;	
    FDiffuse: TGLColor;	
    FSpecular: TGLColor;	
    FCutoff: GLfloat;	
    FSpotlight: boolean;	
    FDirection: boolean;	
    FPosition: boolean;	
    FEnabled: boolean;	
    FLightPlace: TGLPosition;	
    FSpotDirect: TGLPlace;	
    procedure SetAmbientRed(value: GLclampf);	
    function GetAmbientRed: GLclampf;	
    procedure SetAmbientGreen(value: GLclampf);	
    function GetAmbientGreen: GLclampf;	
    procedure SetAmbientBlue(value: GLclampf);	
    function GetAmbientBlue: GLclampf;	
    procedure SetDiffuseRed(value: GLclampf);	
    function GetDiffuseRed: GLclampf;	


PAGE 127
     procedure SetDiffuseGreen(value: GLclampf);	
    function GetDiffuseGreen: GLclampf;	
    procedure SetDiffuseBlue(value: GLclampf);	
    function GetDiffuseBlue: GLclampf;	
    procedure SetSpecularRed(value: GLclampf);	
    function GetSpecularRed: GLclampf;	
    procedure SetSpecularGreen(value: GLclampf);	
    function GetSpecularGreen: GLclampf;	
    procedure SetSpecularBlue(value: GLclampf);	
    function GetSpecularBlue: GLclampf;	
    procedure SetWhich(value: byte);	
    function GetWhich: byte;	
Listing 6-3. The TLight private section.
FEnabled is the light switch; the remaining fields provide the values for some more 
important OpenGL light qualities. An OpenGL light has three components: ambient, diffuse, 
and specular. Accordingly, Listing 6-3 has FAmbient, FDiffuse, and FSpecular fields. 
Another feature of OpenGL lights is the ability to simulate spotlights. The FCutoff, 
FSpotlight, FDirection, FPosition, FLightPlace, and FSpotDirect fields support that behavior.
The FSpotDirect field is a vector that describes the aim direction of the light. Its three 
coordinates define a point in space. In your mind draw an imaginary line segment from the 
origin (0,0,0) to that point. That imaginary (directed) line segment is typical of OpenGL’s 
way of describing a direction.
Since the three OpenGL light components (not to be confused with Delphi components) are 
complex types, access methods provide the link to simple properties. Multiply three 
components by three colors each and by two methods (Get and Set) per color to produce 18 
methods. The FWhich field also has two methods to mediate between the 0 to 7 values to 
place in the Object Inspector and the GL_LIGHT constants, bringing the total to 20.
The Public Section
The public section at least is small and simple, with just a constructor and the usual Apply 
method:

  public	
    constructor Create(AOwner:TComponent); override;	
    procedure Apply;	
The Published Section
The published section, on the other hand, is fairly hefty, as you would expect from the 
previous information on the private section. That 

PAGE 128
 information is sufficient for an understanding of most of the properties you see in Listing 6-
4, but a few interrelated properties will bear a little extra scrutiny.

  published	
    property Which: byte read GetWhich write SetWhich;	
    property Cutoff: GLfloat read FCutoff write FCutoff;	
    property AmbientRed: GLclampf read GetAmbientRed write SetAmbientRed;
    property AmbientGreen: GLclampf read GetAmbientGreen write SetAmbientGreen;
    property AmbientBlue: GLclampf read GetAmbientBlue write SetAmbientBlue;
    property DiffuseRed: GLclampf read GetDiffuseRed write SetDiffuseRed;
    property DiffuseGreen: GLclampf read GetDiffuseGreen write SetDiffuseGreen;
    property DiffuseBlue: GLclampf read GetDiffuseBlue write SetDiffuseBlue;
    property SpecularRed: GLclampf read GetSpecularRed write SetSpecularRed;
    property SpecularGreen: GLclampf read GetSpecularGreen write SetSpecularGreen;
    property SpecularBlue: GLclampf read GetSpecularBlue write SetSpecularBlue;
    property Direction: boolean read FDirection write FDirection;	
    property Position: boolean read FPosition write FPosition;	
    property Spotlight: boolean read FSpotlight write FSpotlight;	
    property Enabled: boolean read FEnabled write FEnabled;	
  end; {TLight}	
Listing 6-4. TLight published properties.
The Cutoff property describes a cone of light from the source, expressed in degrees of spread 
from the aim direction of the light. The Direction, Position, and Spotlight Boolean properties 
simply declare whether or not the light aims in a particular direction, whether or not the 
light source has a particular position (versus being an infinite distance away), and whether 
or not the light is a spotlight.
Implementation
You may be tired of being reminded of the Register procedure declaration just before the 
start of the implementation section, but it means the difference between having a component 
appear on the Component Palette and just having some interesting code hanging around in 
Never-Never Land.

  end; {TLight}	
	
procedure Register;	
	
implementation	
The constructor initializes two values. LightPlace has four coordinates: x, y, z, and w. As 
object fields they naturally start out filled with zeros, which is exactly what they should be. 
In this approach everything starts at the origin (0,0,0) and translates to its final location. 
However, w is not a 

PAGE 129
 coordinate but a divisor. Bad things happen when you divide by zero. Actually, OpenGL 
will substitute a one if it detects a value of zero for w, but explicitly setting it to 1.0 has the 
advantage of reminding you that it is a divisor. That information has received no notice yet, 
but this statement should whet your curiosity until the subject appears in a later chapter. The 
vector field FSpotDirect must not be all zeros; otherwise it would point nowhere. By setting 
the z value to –1.0 you aim the light deeper into the screen. Since the FLightPlace and 
FSpotDirect (private) fields have no corresponding property or method, the values set in the 
constructor will remain unchanged. These components suppose that you want all positions 
and orientations to be the result of appropriate transformations.

constructor TLight.Create(AOwner:TComponent);	
begin	
    inherited Create(AOwner);	
  FLightPlace.w := 1.0;	
  FSpotDirect.z := -1.0;	
end;	
The access methods for the ambient, diffuse, and specular color records are very 
straightforward, just setting or retrieving a simple floating-point field from a record type. 
The sole reason for these access methods is to provide simple property types from the more 
complex types needed for the OpenGL v (vector) commands. Remember that the data type 
GLclampf is just an alias for the single precision floating-point type.

procedure TLight.SetAmbientRed(value: GLclampf);	
begin	
  FAmbient.Red := value;	
end;	
	
function TLight.GetAmbientRed: GLclampf;	
begin	
  result := FAmbient.red;	
end;	
	
procedure TLight.SetAmbientGreen(value: GLclampf);	
begin	
  FAmbient.Green := value;	
end;	
	
function TLight.GetAmbientGreen: GLclampf;	
begin	
  result := FAmbient.green;	
end;	
	
procedure TLight.SetAmbientBlue(value: GLclampf);	
begin	


PAGE 130
   FAmbient.blue := value;	
end;	
	
function TLight.GetAmbientBlue: GLclampf;	
begin	
  result := FAmbient.blue;	
end;	
	
procedure TLight.SetDiffuseRed(value: GLclampf);	
begin	
  FDiffuse.red := value;	
end;	
	
function TLight.GetDiffuseRed: GLclampf;	
begin	
  result := FDiffuse.red;	
end;	
	
procedure TLight.SetDiffuseGreen(value: GLclampf);	
begin	
  FDiffuse.green := value;	
end;	
	
function TLight.GetDiffuseGreen: GLclampf;	
begin	
  result := FDiffuse.green;	
end;	
	
procedure TLight.SetDiffuseBlue(value: GLclampf);	
begin	
  FDiffuse.blue := value;	
end;	
	
function TLight.GetDiffuseBlue: GLclampf;	
begin	
  result := FDiffuse.blue;	
end;	
	
procedure TLight.SetSpecularRed(value: GLclampf);	
begin	
  FSpecular.red := value;	
end;	
	
function TLight.GetSpecularRed: GLclampf;	
begin	
  result := FSpecular.red;	
end;	
	
procedure TLight.SetSpecularGreen(value: GLclampf);	


PAGE 131
 begin	
  FSpecular.green := value;	
end;	
	
function TLight.GetSpecularGreen: GLclampf;	
begin	
  result := FSpecular.green;	
end;	
	
procedure TLight.SetSpecularBlue(value: GLclampf);	
begin	
  FSpecular.blue := value;	
end;	
	
function TLight.GetSpecularBlue: GLclampf;	
begin	
  result := FSpecular.blue;	
end;	
The property named Which is a simple integer intended to follow a sequence. The OpenGL 
GL_LIGHT constants may not all be sequential if you have an implementation that allows 
more than the original eight lights. Therefore the mapping between the property and the 
constants uses case statements that you can expand as needed. Actually you could just add 
and subtract $4000, but that would be like insider stock trading. You see the actual values for 
the purpose of being able to write an interface unit, but you should pretend you really do 
not have such intimate knowledge.

function TLight.GetWhich: byte;	
begin	
  case FWhich of	
    GL_LIGHT1: result := 1;	
    GL_LIGHT2: result := 2;	
    GL_LIGHT3: result := 3;	
    GL_LIGHT4: result := 4;	
    GL_LIGHT5: result := 5;	
    GL_LIGHT6: result := 6;	
    GL_LIGHT7: result := 7;	
    else       result := 0;	
  end; {case}	
end;	
	
procedure TLight.SetWhich(value: byte);	
begin	
  case value of	
    1: FWhich := GL_LIGHT1;	
    2: FWhich := GL_LIGHT2;	


PAGE 132
     3: FWhich := GL_LIGHT3;	
    4: FWhich := GL_LIGHT4;	
    5: FWhich := GL_LIGHT5;	
    6: FWhich := GL_LIGHT6;	
    7: FWhich := GL_LIGHT7;	
    else FWhich := GL_LIGHT0;	
  end; {case}	
end;	
The Apply method executes the appropriate OpenGL commands, glEnable or glDisable, and 
several invocations of glLightfv. The FEnabled field determines whether to enable or disable 
the light. The FPosition field determines whether to call glLightfv with the GL_POSITION 
parameter. The FDirection field determines whether to call glLightfv with the 
GL_SPOT_DIRECTION parameter. The FSpotlight field determines whether to call glLightfv 
with the GL_SPOT_CUTOFF parameter, and the FCutoff field in the same command defines 
the size of the cone of light. Apply unconditionally calls the same command to set the 
ambient and diffuse components of the light. You should call the TLight’s Apply method 
from a TTransform OnPaint event handler.

procedure TLight.Apply;	
begin	
  if FEnabled then	
    glEnable(FWhich)	
  else begin	
    glDisable(FWhich);	
    exit;	
  end;	
  if FPosition then	
    glLightfv(FWhich,GL_POSITION,@FLightPlace);	
  if FDirection then	
    glLightfv(FWhich,GL_SPOT_DIRECTION,@FSpotDirect);	
  if FSpotlight then	
    glLightf(FWhich,GL_SPOT_CUTOFF,FCutoff);	
  glLightfv(FWhich,GL_AMBIENT,@FAmbient);	
  glLightfv(FWhich,GL_DIFFUSE,@FDiffuse);	
  glLightfv(FWhich,GL_SPECULAR,@FSpecular);	
end;	
The Register procedure arranges to place the TLight component on the OpenGL page of the 
Component Palette:

procedure Register;	
begin	
  RegisterComponents(‘OpenGL’,[TLight]);	
end;	
	
end.	

PAGE 133
 Path
Give careful consideration to the placement of the components and related files. Delphi must 
be able to find the files. Delphi keeps track of a library path that you can edit. It consists of 
one or more directory specifications separated by semicolons. If you will add to the path, 
make the new directory entry as short as you can so you do not end up with a library path 
that is too big to be manageable. Simple is often best. The author uses the main library 
directory (BIN for Delphi 2 and LIB for later versions) and never has any trouble installing 
components.
Once you have decided on the proper location for the component files, create the directory if 
it does not already exist. Compile the project to make sure you did not make any 
typographical errors. Next, move or copy all the PAS and DCU files to the chosen location. 
Add the location to the library path if it is not already there. Be sure you have your interface 
unit (OpenGL.PAS) in a library path. If Delphi cannot find the files it needs, the component 
installation process will not completely succeed.
Icons
If you want components on the Component Palette, you should make unique icons to 
represent them. From the Delphi IDE, open your image editor. Create a new Component 
Resource File (dcr). As a Resource within it create a new 24x24 VGA (16-color) bitmap. 
Name it TOPENGL. A possible representation would be a little window like Figure 6-1, i.e., 
the form that becomes an OpenGL display. You can, of course, use your imagination to make 
any graphical representation you want, within the limitations of 24x24x16. When you finish 
your drawing, close that one, single window so you can add another bitmap. Why should 
you use only 16 colors? If you use this safe, minimal color choice, you will never have to 
worry about changed appearance under different screen settings.
Create another bitmap as a resource and name it TMATERIAL. Figure 6-2 shows a possible 
representation. Here, a surface covered with color 

PAGE 134
 stripes suggests a surface’s response to colored light, which is the essence of material 
properties.
Create a third bitmap and name it TTRANSFORM. In Figure 6-3, the green arrow suggests 
movement, the translation aspect of OpenGL transformations.
Name your fourth bitmap TLIGHT. While the other icons are rather abstract, and possibly 
obscure, pictures representing the components, the light bulb of Figure 6-4 is quite obvious.
Now, save the entire Component Resource as cOpenGL.DCR and store it in the location of 
the components.


Installing the Components
While you can create components that you only instantiate dynamically, such is not the 
intent of these components. The OpenGL components you just coded have a simple destiny. 
You should drop them on a form and configure them in the Object Inspector.
For this to happen you must install the components into the component library. There is a 
major difference between Delphi 2 and later versions.

PAGE 135
 Delphi 2
For Delphi 2, you must compile the component files into a monolithic library file. Always 
create a backup copy of Cmplib32.Dcl before you do anything to it. If anything goes wrong, 
you can restore the old library. Delphi 2 will not really work if its library is corrupt.
Delphi 3, 4, Etc.
For Delphi 3 and later, you install your components into packages. Create a new package, 
add OpenGL.Pas (the interface unit) to the Contains page, and add VCL?0 to the Requires 
page. The question mark is the Delphi version number. Use VCL30 for Delphi 3, VCL40 for 
Delphi 4, etc. Save the package as GL?0.Dpk and compile it. The interface unit gets its own 
package to be in the Requires page of other packages.
Create another new package. Add cOpenGL, Light, Material, and Transfrm to the Contains 
page. Add GL?0 and VCL?0 (as before) to the Requires page. Save as OpenGL?0.Dpk and 
compile. Again, the ? stands for the traditional Delphi version-specific nomenclature. Just as 
Delphi compiled units (dcu) are incompatible between Delphi versions, so are packages. By 
using versioned nomenclature you enable easy matching between package version and 
compiler version.
Summary
This chapter has been pure Delphi. In it we developed a few components to encapsulate 
some key features of the OpenGL API. Just as it is not a good idea for a person to become 
dependent on a calculator until he or she has become fairly competent in arithmetic, we 
needed to wait on these components until we had some practice in a few OpenGL 
fundamentals. While these components do not encompass a very large part of OpenGL, they 
accomplish what Delphi components do best: reduce tedium. The next chapter and those 
following will put them to good use.

PAGE 136

Chapter 7
Picking
Many graphics programs must have user interaction. Often this means the ability to select 
graphics objects with a mouse. OpenGL does provide a mechanism for selection or picking, 
but it requires a little work. OpenGL is not tied to a specific operating system, but mouse 
operation is very OS-specific. As a result, connecting the two is not very direct. This does 
not mean the situation is hopeless, or even difficult. Read on!
Go ahead and create a new directory named Chapter.7 in the usual manner. Under it create a 
directory called Pick. In Delphi create a new project and save it into the new directory. Name 
the main unit Pick1.Pas and the project Pick.Dpr. Set the form’s ClientHeight and 
ClientWidth to 340.
Filling Out the Form
The components developed in the last chapter now find their first employment. While the 
point of this section is to create an application that implements OpenGL picking, this is the 
first usage of the new Delphi components. Certain steps or relationships do require careful 
explanation. Be patient while such concepts appear several times in slightly different words. 
This description of the process of placing and configuring components on the form 
illustrates usage of the components and takes advantage of the defaults developed in the 
previous chapter.
TOpenGL
Place a TOpenGL component on the form. Functionally the location does not matter, but 
placing it near the upper left corner of the form will aid in organizing or grouping 
components. Enable lighting by making all the sub-properties of the Enabled property true. 
Make sure the Projection property has the setting of Perspective. Set prxNear to 2 and prxFar 
to 37. Change ClearBlue and ClearGreen to 0.8 and ClearRed to 0.6. Now that was much 
easier than writing a lot of FormCreate code! Note that 

PAGE 137
 floating-point values in the Object Inspector that just happen to have 0 after the decimal 
place will look like integers.
TTransform
Place a TTransform component on the form. For convenience in grouping, place it a little 
below the TOpenGL component. Set the TranX and TranY properties to –0.6 to place an 
object toward the lower left part of the window. Set the TranZ property to –3.2 to bring it 
into view. Remember that the near part of the perspective projection is 2, so you will need a 
z value at least 2 (in the negative direction) to bring an object into view. Actually you need a 
little more when you allow for the size of the object.
What object are you bringing into view? To understand that, select the Events page in the 
Object Inspector and create the only event handler for the component.

procedure TForm1.Transform1Paint(Sender: TObject);	
begin	
  Material1.Apply;	
  Cube;	
end;	
Aha! We will draw another cube. The code for it appears a little later. The component named 
Material1 also appears later. A key fact to note is that the OnPaint event handler for the 
TTransform component provides the link between the TTransform component itself and 
other things. Normally these “other things” will be a TMaterial component and some 
rendering code to be affected by the TMaterial component and the TTransform component.
FormPaint
Oh, well; even with components you still have to write some code! The FormPaint method is 
still the natural location for drawing on the screen. Create the event handler and fill it in. 
Notice how simple it is, though. Just clear and apply to accomplish the main job. Add the 
glFlush call to ensure the scene gets to the screen. Oh, don’t forget the error checking. 

procedure TForm1.FormPaint(Sender: TObject);	
begin	
  glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT);	
  Transform1.Apply;	
  glFlush;	
  OpenGL1.ErrorCheck('FormPaint');	
end;	

PAGE 138
 The call to the Apply method of the TTransform component from within the FormPaint 
method starts the rendering with the appropriate properties. Remember that the rendering 
code itself should be called from the TTransform OnPaint event handler. In essence, the 
following steps produce your scene:
1.
Windows sends a WM_PAINT message or a WM_RESIZE message.
2.
In response to the WM_PAINT message, the Delphi application executes the form’s OnPaint 
event handler (FormPaint). Alternatively, in response to a WM_RESIZE message, the Delphi 
application executes the form’s OnResize event handler (FormResize). Within FormResize, if 
the Smaller method of the TOpenGL component determines that the form did not grow in 
any direction (therefore a WM_PAINT message is lacking), FormResize calls FormPaint 
directly.
3.
FormPaint clears the color buffer and the depth buffer, then calls the TTransform 
component’s Apply method. Actually, a form could have a number of such components; 
FormPaint should call the Apply method of each one.
4.
The TTransform component’s Apply method saves the ModelView matrix; performs the 
scale, translation, and rotation transformations; and fires its OnPaint event handler.
5.
The event handler executes the code you place there: the TMaterial component’s Apply 
method and the rendering code.
6.
The Apply method of the TMaterial component sets the material properties that affect the 
subsequently called rendering code. 
FormResize
FormResize is still the place to set up your viewport. Create this event handler and put in a 
single statement. Now that is easy! Basically the Smaller method called here is the handler 
for ClientHeight and ClientWidth. Not only does the Smaller method perform the viewport 
chore (using ClientHeight and ClientWidth), but it also returns true if the form did not grow 
in either the ClientHeight or ClientWidth direction. Since that is a situation for which no 
WM_PAINT message arrives, the code calls FormPaint directly.

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

PAGE 139
 TMaterial
After all that reference to a TMaterial component, actually place a TMaterial component on 
the form. Again, just for convenient grouping, place it a little to the right of the TTransform 
component that calls its Apply method. Viewed as text, its settings are like the following, 
though you might see a slight round-off error in some floating-point values:

  object Material1: TMaterial	
    ambRed = 0.2	
    ambGreen = 0.2	
    ambBlue = 0.2	
    ambAlpha = 1	
    difRed = 0.8	
    difGreen = 0.3	
    difBlue = 0.5	
    difAlpha = 1	
    aFace = faFront	
    dFace = faFront	
    sFace = faFront	
    Left = 44	
    Top = 44	
  end	
Those property settings determine the appearance of an appropriate part of the scene, as 
specified in the event handler for the TTransform component.
Cube
Again we shall draw our faithful old cube. Very soon we shall have more interesting things 
to draw, but for now the familiar cube is very useful for illustrating new OpenGL 
capabilities without a lot of distraction.
Add a Cube procedure to the form type declaration:

type	
  TForm1 = class(TForm)	
    OpenGL1: TOpenGL;	
    Transform1: TTransform;	
    Material1: TMaterial;	
    procedure Transform1Paint(Sender: TObject);	
    procedure FormResize(Sender: TObject);	
    procedure FormPaint(Sender: TObject);	
  private	
    procedure Cube;	
  public	
    { Public declarations }	
  end;	

PAGE 140
 At the bottom of the unit, implement the cube code as in Listing 7-1.

procedure TForm1.Cube;	
begin	
  glBegin(GL_POLYGON); {near face}	
    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}	
    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}	
    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}	
    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}	
    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}	
    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;	
end;	
	
end.	
Listing 7-1. The Cube method.

PAGE 141
 More
In order to compile you need to add OpenGL to the uses clause. Go ahead and compile to 
ensure there are no typographical errors. You can even run the program, but the cube will be 
black because we told OpenGL we would handle lighting but have not yet supplied any.
Obviously, if you will do any meaningful picking or selecting, you need more than one 
object in the window. Drop another TTransform component on the form just a little below 
the first one. Drop a TMaterial component a little to the right of the second TTransform 
component. Make Transform2 properties just like Transform1 properties except that the 
TranX value should be positive. The second cube will appear near the lower right corner of 
the window. Make the properties of Material2 just like the properties of Material1, except 
that difBlue should be 0.8 and difRed should be 0.3.
At the Events page of the Object Inspector, create an event handler for Transform2:

procedure TForm1.Transform2Paint(Sender: TObject);	
begin	
  Material2.Apply;	
  Cube;	
end;	
Go back to the FormPaint method and add a reference to Transform2:

procedure TForm1.FormPaint(Sender: TObject);	
begin	
  glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT);	
  Transform1.Apply; 	
  Transform2.Apply;	
  glFlush;	
  OpenGL1.ErrorCheck(‘FormPaint’);	
end;	
Now if you compile and run you will see two black cubes.
Repeat the previous processes to place another TTransform component and another 
TMaterial component. Also add a TLight component to the right of the TMaterial 
component. 
If you view the form as text, the properties of the Transform3 object would appear as 
follows:

  object Transform3: TTransform	
    TranY = 0.6	
    TranZ = -2.2	
    ScaleX = 0.5	
    ScaleY = 0.5	


PAGE 142
     ScaleZ = 0.5	
    Left = 8	
    Top = 116	
  end	
Similarly, the properties of the Material3 object would look like this:

  object Material3: TMaterial	
    ambRed = 0.2	
    ambGreen = 0.2	
    ambBlue = 0.2	
    ambAlpha = 1	
    difGreen = 0.6	
    difBlue = 0.6	
    difAlpha = 1	
    aFace = faFront	
    dFace = faFront	
    sFace = faFront	
    Left = 44	
    Top = 116	
  end	
Now let us see the long-awaited Light1:

  object Light1: TLight	
    Which = 0	
    AmbientRed = 0.3	
    AmbientGreen = 0.3	
    AmbientBlue = 0.3	
    DiffuseRed = 1	
    DiffuseGreen = 1	
    DiffuseBlue = 1	
    Direction = True	
    Position = True	
    Spotlight = True	
    Enabled = True	
    Left = 80	
    Top = 116	
  end	
Of course you should update the FormPaint method. Call the Apply method of Transform3 
before the others so that the light is in place before the other objects appear:

procedure TForm1.FormPaint(Sender: TObject);	
begin	
  glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT); 	
  Transform3.Apply;	
  Transform1.Apply;	
  Transform2.Apply;	
  glFlush;	


PAGE 143
   OpenGL1.ErrorCheck('FormPaint');	
end;	
You also need an event handler for Transform3. It should be a little different, calling 
Light1.Apply. The TLight component SpotDirect private field was created pointing down 
the negative z direction and not exposed in a property so that the light could be positioned 
and aimed with the same transformation applied to some scene object. At this point in the 
OpenGL exploration the other object is a...cube. (Despair not. More interesting shapes are 
coming soon.)

procedure TForm1.Transform3Paint(Sender: TObject);	
begin	
  Light1.Apply;	
  Material3.Apply;	
  Cube;	
end;	
Now if you save, compile, and run, the two large cubes are no longer black. They are 
illuminated and shaded by the spotlight. Actually you could illuminate part of the small 
cube if you increased the cutoff angle to something greater than 45 degrees (but no more 
than 90 degrees, of course).
Names
So far, other than use components, we have done nothing new. Now that a basic scene with 
a few objects exists, you can start picking. How do you know what you have picked? Each 
object must have an identity, a name.
Numbers
Well, what OpenGL calls a name is actually just a number. Nevertheless, if you can give each 
object a unique number, you identify it as effectively as if you called it Mary, Alex, or Igor.
Persistent State
Like many OpenGL entities, a name is a state that remains in effect until changed. You can 
give several different objects the same name by one command if you like. In this context an 
“object” is a set of vertices between glBegin and glEnd since name commands cannot fit 
between glBegin and glEnd. In OpenGL we call these objects primitives.

PAGE 144
 Command
OpenGL has several commands for manipulating names. Here is the declaration for the one 
that actually applies a name to subsequent primitives. The integer value you pass as the 
name parameter becomes the name of all primitives created afterwards until another name 
command changes the situation.

procedure glLoadName(name:GLuint); stdcall;	
Stack
Although names persist like OpenGL state variables, names are quite different in another 
way. Names are maintained on a name stack. This provides for structured naming. You can 
create compound objects with compound names. Suppose you create several wagons with 
four wheels each and six spokes per wheel. You click on a spoke. With suitable name stack 
manipulation you would be able to identify the fourth spoke on the first wheel of the third 
wagon. Name stack manipulation commands follow.
The glInitNames command initializes the name stack, ensuring that it is empty. Start here:

procedure glInitNames; stdcall;
GLuint = Cardinal;
PGLuint = GLuint;	
The glPushName command pushes a name onto the stack. Even if you only work with one 
level of names, you must push a name onto the stack, so you do not have zero name levels. 
The command for setting the current name is glLoadName, which works by replacing the 
value on the top of the name stack. If there is no value on the stack, the command will fail.

procedure glPushName(name:GLuint); stdcall;	
If you do create a name hierarchy, you will need to push names and pop names at 
appropriate times. To pop a name off the stack, use glPopName:

procedure glPopName; stdcall;	
Coding for Names
Now your code can include the name commands. Create a private method called Selection 
where you will perform selection operations. You will also need a SelectionDone method to 
clean up afterwards. You will need to pass coordinates to Selection to identify the screen 
location the user chose. The reason the coordinates are of type GLdouble will become 
apparent shortly.


PAGE 145
 type	
  TForm1 = class(TForm)	
    OpenGL1: TOpenGL;	
    Transform1: TTransform;	
    Material1: TMaterial;	
    Transform2: TTransform;	
    Material2: TMaterial;	
    Material3: TMaterial;	
    Light1: TLight;	
    Transform3: TTransform;	
    procedure Transform1Paint(Sender: TObject);	
    procedure FormResize(Sender: TObject);	
    procedure FormPaint(Sender: TObject);	
    procedure Transform2Paint(Sender: TObject);	
    procedure Transform3Paint(Sender: TObject);	
    procedure FormCreate(Sender: TObject);	
  private	
    procedure Cube;	
    procedure Selection(px,py:GLdouble);	
    procedure SelectionDone;	
  public	
    { Public declarations }	
  end;	
In the Selection method you will initialize the name stack:

procedure TForm1.Selection(px,py:GLdouble);	
begin	
  glInitNames;	
end;	
Levels
This application only needs one level of names, so it has only one call to glPushName, the 
call that ensures the stack has a level glLoadName can use:

procedure TForm1.Selection(px,py:GLdouble);	
begin	
  glInitNames;	
  glPushName(0);	
end;	
Naming the Objects
Give the first cube an appropriate name. In the OnCreate event handler for the first 
TTransform component, give the first cube a unique name. Be creative! Be original! Name 
it...1:

PAGE 146
 procedure TForm1.Transform1Paint(Sender: TObject);	
begin	
  Material1.Apply; 	
  glLoadName(1);	
  Cube;	
end;	
Name the second cube 2:

procedure TForm1.Transform2Paint(Sender: TObject);	
begin	
  Material2.Apply; 	
  glLoadName(2);	
  Cube;	
end;	
Go ahead and name the third cube, also. This is the small cube that stands in for the invisible 
light source. Wherever you move the cube, you move the light source. Wherever you aim the 
back side of the cube, you set the direction of the spotlight. Name this cube 3:

procedure TForm1.Transform3Paint(Sender: TObject);	
begin	
  Light1.Apply;	
  Material3.Apply; 	
  glLoadName(3);	
  Cube;	
end;	
Picking
Now that you have a way of identifying your on-screen primitives to OpenGL, you have the 
basis for picking out objects on the screen.
The Challenge
OpenGL provides three-dimensional representation on a two-dimensional screen. This 3-D 
nature presents a challenge for picking and selecting. For any x-y pixel coordinate you select 
there may be several objects at various z depths. OpenGL must search for qualifying objects. 
Often the success by OpenGL to find a named object under a given coordinate is called a hit. 
As if that were not enough, OpenGL provides for structured, hierarchical naming. Because 
of these facts the selection mechanism must return several pieces of information:
? How many “hits” were there?
? What are the “names” of each hit?

PAGE 147
 
? What are the z depths of each hit?
? How deep was the name stack at that point?
Selection
Up to this point we have used “picking” and “selection” very loosely. Selection in OpenGL is 
actually a rendering mode in which OpenGL reports back about the contents of a particular 
region. Picking is having such a region specified by the user, as with a mouse.
You specify the rendering mode by passing certain constants to the appropriate command:

function glRenderMode(mode:GLenum): GLint; stdcall;	
Here are the rendering mode constants:

  GL_RENDER   = $1C00;	
  GL_FEEDBACK = $1C01;	
  GL_SELECT   = $1C02;	
GL_RENDER is the normal, default mode. The command function just returns zero when 
changing from this value.
GL_FEEDBACK fills a buffer with the coordinates of the vertices that would have been 
drawn for GL_RENDER, and is listed here merely for completeness. The command function 
returns the number of values in the buffer when changing from this value. 
GL_SELECT fills a buffer with hit records for the specified region. The command function 
returns the number of hits when changing from this value.
Since you are filling a buffer, you must create the buffer, and tell OpenGL where it is (the 
address) and how big it is. The command expects the buffer to be an array of unsigned 
integers (GLuint), and the size information to be the number of array elements. Here is the 
command:

procedure glSelectBuffer(size:GLsizei;buffer:PGLuint); stdcall;	
This command must be issued before you enter selection mode, so FormCreate is a good 
place for it. Give it 64 elements for more than enough room.

procedure TForm1.FormCreate(Sender: TObject);	
begin	
  glSelectBuffer(64,@Buffer);	
end;	

PAGE 148
 Of course you must actually allocate the buffer. Make it a private field of the form:

type	
  TForm1 = class(TForm)	
    OpenGL1: TOpenGL;	
    Transform1: TTransform;	
    Material1: TMaterial;	
    Transform2: TTransform;	
    Material2: TMaterial;	
    Material3: TMaterial;	
    Light1: TLight;	
    Transform3: TTransform;	
    procedure Transform1Paint(Sender: TObject);	
    procedure FormResize(Sender: TObject);	
    procedure FormPaint(Sender: TObject);	
    procedure Transform2Paint(Sender: TObject);	
    procedure Transform3Paint(Sender: TObject);	
    procedure FormCreate(Sender: TObject);	
  private	
    Buffer: array[1..64] of GLuint;	
    procedure Cube;	
    procedure Selection(px,py:GLdouble); 	
    procedure SelectionDone;	
  public	
    { Public declarations }	
  end;	
To perform selection you must use the command to change the rendering mode. Actually, 
none of the name commands have any effect until the new rendering mode takes over, so 
glRenderMode must appear first:

procedure TForm1.Selection(px,py:GLdouble);	
begin	
  glRenderMode(GL_SELECT);	
  glInitNames;	
  glPushName(0);	
end;	
Matrices
Like most of OpenGL, the actual operation is performed by matrix manipulation. An 
OpenGL utility, gluPickMatrix, operates on the projection matrix and restricts drawing to a 
small region:

procedure gluPickMatrix(x,y,width,height:GLdouble;viewport:PGLint); 	
stdcall;	

PAGE 149
 PGLint has the obvious definition. The viewport parameter is a C-style declaration that 
really points to an array:

  PGLInt = ^GLInt;	
First, set the proper matrix mode, save the existing projection matrix, then start with the 
identity matrix to prepare for the pick matrix:

procedure TForm1.Selection(px,py:GLdouble);	
begin	
  glRenderMode(GL_SELECT);	
  glInitNames;	
  glPushName(0); 	
  glMatrixMode(GL_PROJECTION);	
  glPushMatrix; 	
  glLoadIdentity;	
end;	
Notice that gluPickMatrix needs the viewport. You could just keep track of dimensions that 
make up the current viewport, but this is a good time to introduce a new OpenGL command. 
You can use glGetIntegerv to return the current value of a specified integer vector. Pass it a 
constant that names the value you want and a pointer to the array to receive the vector 
value.

procedure glGetIntegerv(pname:GLenum;params:PGLint); stdcall;	
OpenGL defines many constants to pass in this command, but only one is useful for the 
moment:

  GL_VIEWPORT = $0BA2;	
Use the new command to retrieve the current viewport to pass to gluPickMatrix. Both 
commands expect an array of GLint, but Delphi easily allows you to define a much friendlier 
structure. The reason for the friendly structure is to use the height field more easily. 
Remember that Windows defines the y direction from top to bottom, but OpenGL defines it 
from bottom to top. Selection receives coordinates as GLdoubles to pass to gluPickMatrix, 
but first the y coordinate must be adjusted for the difference in direction between Windows 
and OpenGL:

procedure TForm1.Selection(px,py:GLdouble);	
type	
  TViewPort=packed record	
    left,bottom,width,height: GLint;	
  end;	
var	
  viewport: TViewPort;	
begin	
  glRenderMode(GL_SELECT);	
  glInitNames;	


PAGE 150
   glPushName(0); 	
  glMatrixMode(GL_PROJECTION);	
  glPushMatrix; 	
  glLoadIdentity; 	
  glGetIntegerv(GL_VIEWPORT,@viewport); 	
  py := viewport.height-py;	
  gluPickMatrix(px,py,2.0,2.0,@viewport);	
end;	
The final step needed for the Selection method is to set up the same viewing volume that 
would otherwise have been in effect. The width and height values (2.0,2.0) passed to 
gluPickMatrix are the size of the selection area in pixels. Provide small values like this for 
very precise picking, or provide larger values to allow users who are not neurosurgeons to 
be able to pick out small targets they might otherwise miss.

  glGetIntegerv(GL_VIEWPORT,@viewport);	
  py := viewport.height-py;	
  gluPickMatrix(px,py,2.0,2.0,@viewport);	
  with OpenGL1 do	
    glFrustum(prxLeft,prxRight,prxBottom,prxTop,prxNear,prxFar);
end;	
Get Mousey
The usual selection device under Windows is the mouse. Delphi provides plenty of support 
for obtaining mouse information, especially in event handlers.
Which Event
A number of the form’s event handlers have potential use; the most obvious is the OnClick 
event handler, fired when the user clicks the mouse somewhere on the form. Unfortunately, 
no mouse position information reaches the event handler.

procedure TForm1.FormClick(Sender:Tobject);	
The OnMouseMove handler shows promise with its X and Y values for the current mouse 
position, but would serve better for dragging than for picking:

procedure TForm1.FormMouseMove(Sender:TObject;Shift:TShiftState;X,Y: Integer);
The OnMouseDown handler also has the X and Y mouse coordinates and fires at the 
beginning of a mouse click. It is just about right for the current 

PAGE 151
 purpose, but is also suitable for the beginning of a drag operation. The current illustration is 
only picking, not dragging, so look for another.

procedure TForm1.FormMouseDown(Sender:TObject;Button:TMouseButton; 
Shift:TShiftState;X,Y:Integer);	
The OnMouseUp handler receives the same information as the OnMouseDown handler, and 
fires at the end of a click operation. The quest is over.

procedure TForm1.FormMouseUp(Sender:TObject;Button:TMouseButton;Shift: 
TShiftState;X,Y:Integer);
So create the handler and fill it in with a call to the Selection method. Then call FormPaint so 
that drawing will occur under the new mode. Complete the process with a call to the 
SelectionDone method:

procedure TForm1.FormMouseUp(Sender:TObject;Button:TMouseButton;	
  Shift: TShiftState;X,Y:Integer);	
begin	
  Selection(x,y);	
  FormPaint(nil);	
  SelectionDone;	
end;	
Collect the Data
The SelectionDone method certainly has its work cut out for it. It must retrieve the results of 
drawing under selection mode, clean up, and return the system to the state it had before 
selection mode. First, make it restore the old projection matrix:

procedure TForm1.SelectionDone;	
begin	
  glMatrixMode(GL_PROJECTION);	
  glPopMatrix;	
end;	
Remember that glRenderMode returns values based on the mode it is coming from. It was in 
GL_SELECT mode, so it will give back the number of hits. Add a call to glRenderMode to 
restore the original, default rendering mode and return the number of hit records:

procedure TForm1.SelectionDone;	
var	
  hits: GLint;	
begin	
  glMatrixMode(GL_PROJECTION);	
  glPopMatrix; 	


PAGE 152
   hits := glRenderMode(GL_RENDER);	
end;	
Lying in the selection buffer (aptly named “Buffer”) is the selection data. Although the data 
is passed into a linear array, it has underlying structure. Use Delphi’s Object Pascal to define 
a record structure to match. The fundamental piece of data in the buffer is a hit record 
consisting of a count (of how many names), two depth numbers, and the series of “names.” 
Accordingly, declare a record type just after the form’s type declaration:

  public	
    { Public declarations }	
  end;	
	
  PHit=^THit;	
  THit=packed record	
    ncount,	
    dnear,	
    dfar: GLuint;	
    names: array[1..32] of GLuint;	
  end; {THit}	
The depth values (dnear and dfar) are integer representations of the z coordinates of the 
nearest and farthest vertices that share the same name. They have little use in the present 
illustration, but bear more scrutiny later. Since a record declaration describes a fixed amount 
of space, just specify a generous amount of name space, such as 32 names. Define a pointer 
type for the record as well. In actual use you will just point a variable at a particular array 
element in the buffer and just use as much of the hit record as you need.
Meanwhile, back at the SelectionDone method, add hit as a loop index and place as an index 
into the buffer. Also declare ahit as a pointer to a hit record. Loop through the number of hit 
records found. In each iteration of the loop, advance place by the size of the current hit 
record to indicate the start of the next hit record. Define some constants to make the 
calculation of the record size obvious. In the calculation you advance place by one array 
element for the ncount field, one for the near depth field, and one for the far depth field, as 
well as “ncount” elements for the names:

procedure TForm1.SelectionDone;	
const	
  countElement=1;	
  nearElement=1;	
  farElement=1;	
var	
  place,	
  hit,	


PAGE 153
   hits: GLint;	
  ahit: PHit;	
begin	
  glMatrixMode(GL_PROJECTION);	
  glPopMatrix; 	
  hits := glRenderMode(GL_RENDER);	
  place := 1;	
 for hit := 1 to hits do	
  begin	
    ahit := @Buffer[place];	
    with ahit^ do	
      place := place+countElement+nearElement+farElement+ncount; 	
  end; {for}	
	
end;	
Insert an inner loop to traverse each of the names within a given hit record. Declare nm as 
the loop index:

procedure TForm1.SelectionDone;	
const	
  countElement=1; 	
  nearElement=1;	
  farElement=1;	
var	
  place,	
  nm,	
  hit,	
  hits: GLint;	
  ahit: PHit;	
begin	
  glMatrixMode(GL_PROJECTION);	
  glPopMatrix; 	
  hits := glRenderMode(GL_RENDER);	
  place := 1;	
  for hit := 1 to hits do	
  begin	
    ahit := @Buffer[place]; 	
    with ahit^ do	
    begin	
      place := place+countElement+nearElement+farElement+ncount;	
      for nm := 1 to ncount do ;	
    end; {with}	
  end; {for}	
end;	

PAGE 154
 Displaying the Data
Now that you know how to access the information in the hit records, wouldn’t you like to do 
something with the information? To display hit record information, create an additional 
form for the project. Place a list box on the new form and save the unit as Pick2.Pas. Figure 7-
1 shows the form appearance, and Listing 7-2 shows the form as text.
       Figure 7-1. The hit record form.

object Form2: TForm2
  Left = 200
  Top = 108
  Width = 378
  Height = 347
  Caption = 'Form2'
  Font.Charset = ANSI_CHARSET
  Font.Color = clWindowText
  Font.Height = -11
  Font.Name = 'Courier New'
  Font.Style = []
  PixelsPerInch = 96
  TextHeight = 14
  object ListBox1: TListBox
    Left = 4
    Top = 4
    Width = 362
    Height = 312
    ItemHeight = 13
    TabOrder = 0
  end
end
Listing 7-2. The form as text.
Make a uses clause for the implementation section and put Pick2 in it:

implementation	


PAGE 155
 	
uses	
  Pick2;	
	
{$R *.DFM}	
Now that Form2 is available, format some lines to put into the list box. Show Form2 when 
the user picks an object. Be sure to clear any previous items from the list box for each 
selection attempt. Also add a string variable for the list of names.

procedure TForm1.SelectionDone;	
const	
  countElement=1; 	
  nearElement=1;	
  farElement=1;	
var	
  place,	
  nm,	
  hit,	
  hits: GLint;	
  ahit: PHit;	
  nameList: string;	
begin	
  glMatrixMode(GL_PROJECTION);	
  glPopMatrix; 	
  hits := glRenderMode(GL_RENDER);	
  place := 1; 	
  with Form2,ListBox1,Items do	
  begin	
    Clear;	
    Add(Format('Hits = %d',[hits]));	
    for hit := 1 to hits do	
    begin	
      ahit := @Buffer[place]; 	
      with ahit^ do	
      begin 	
        place := place+countElement+nearElement+farElement+ncount;	
        Add(Format('  hit#%d  count=%d  near=%d  far=%d', 	
          [hit,ncount,dnear,dfar])); 	
        nameList := '    names:';	
        for nm := 1 to ncount do	
          nameList := nameList+' '+IntToStr(names[nm]);	
        Add(nameList);	
      end; {with}	
    end; {for}	
  end; {with}	
  Form2.Show;	
end;	

PAGE 156
 The SelectionDone method is able to show relationships by using indentation in the strings 
added to the list box and hierarchy by listing related names from the name stack on a single 
line. When the user clicks on the main form, the second form will appear in front with a line 
in the list box showing the number of hits. If there are any hits, an indented line appears for 
each hit, giving its hit number, the count of how many name records in the hit, and the near 
and far depth values for the hit. Each name under a hit will appear in a doubly indented line.
Save, compile, and run. Click on various regions of the screen and admire the information 
OpenGL gives you.
Hierarchy
With just a little added code, you can demonstrate how the name stack works. Give cube 2 a 
little bud and name it 4 as shown below:

procedure TForm1.Transform2Paint(Sender: TObject);	
begin	
  Material2.Apply; 	
  glLoadName(2);	
  glPushMatrix;	
    glTranslatef(0.5,0.0,0.0);	
    glScalef(0.3,0.3,0.3);	
    glPushName(4);	
    Cube;	
    glPopName;	
  glPopMatrix;	
  Cube;	
end;	

PAGE 157
 Notice the glPushMatrix and glPopMatrix surrounding the new cube so that the ModelView 
matrix will be unchanged when the original cube is drawn. Inside that pair of commands 
glTranslatef moves the new cube a little to the right and glScalef shrinks it. Surrounding the 
call to Cube is a glPushName and a glPopName. When the small cube that will look like a 
bud off the larger cube is drawn, the current name on top of the stack is 4 and the stack is 
two names deep.
Save, compile, and run again. Pick out the bud with the mouse and see what the selection 
buffer looks like. Pick it out again close enough to the larger cube, so that (because of the 
angle) the larger cube also appears in the hit record. Figure 7-3 gives an example result.
Identifier Reference
Identifier			Description
glGetIntegerv
An OpenGL command to retrieve the current value of a particular integer vector (array). 
Pass the command an appropriate constant to identify which vector.
glInitNames
An OpenGL command to empty the name stack.
glLoadName
An OpenGL command to replace the name on the top of the name stack.
glPopName
An OpenGL command to remove the top of the name stack.
glPushName
An OpenGL command to push a new name onto the name stack.

PAGE 158
 
Identifier			Description
glRenderMode
An OpenGL command to change the rendering mode 
according to the constant passed to it. It returns an 
integer according to the former mode.
glSelectBuffer
An OpenGL command to specify the buffer to receive 
hit records.
GLuint
An OpenGL data type equivalent to Delphi cardinal.
gluPickMatrix
An OpenGL utility command by which to specify the 
selection region.
GL_FEEDBACK
An OpenGL constant to identify the feedback rendering 
mode, which fills a buffer with the coordinates of the 
vertices in the scene.
GL_RENDER
An OpenGL constant to identify the normal, default 
rendering mode.
GL_SELECT
An OpenGL constant to identify the selection rendering 
mode, which fills a buffer with hit records for a selection 
region.
GL_VIEWPORT
An OpenGL constant to pass to the glGetIntegerv 
command for retrieving the current viewport.
Summary
Chapter 7 provides a bridge between platform-independent OpenGL commands and OS-
specific user interaction. Delphi event handlers receive window coordinates for a mouse 
click and give those coordinates, adjusted for OpenGL use, to OpenGL commands. Those 
commands cause OpenGL to identify scene objects within the specified region and report 
back. In this chapter you learned several required techniques:
? How to identify scene objects by means of OpenGL names.
? How to create name hierarchies for complex objects.
? How to set up a memory region through which OpenGL can report its results.
? How to define a selection region.
? How to map window coordinates to a selection region.
? How to put OpenGL into selection mode.
? How to interpret the data OpenGL returns.

PAGE 159



PAGE 160
Chapter 8
Triangles
The Plane Problem
The time has come for building more interesting shapes than just a cube. The first step is to 
examine a problem that goes with the main primitive you have used so far. It is possible, 
with the OpenGL commands you have learned so far, to draw polygons that OpenGL cannot 
handle well. The following project will demonstrate. Note that in all projects you should add 
OpenGL to the interface uses clause.
Create a new directory under OpenGL and call it Chapter.8. Under Chapter.8 create a 
directory called NotPlane. Create a new Delphi project, and save the unit as Plane1.Pas in 
the NotPlane directory. Save the project as Plane.Dpr in the same directory. Place a 
TOpenGL component on the form. Below it place a TTransform component. To the right of 
that, place a TLight component. Place another TTransform component and two TMaterial 
components to the right of it. Figure 8-1 shows the design-mode form.


PAGE 161
 Create an OnPaint event handler for TTransform1 and an OnPaint event handler for 
TTransform2. Use two TMaterial components because both the front and back faces of the 
object need separate attention.

procedure TForm1.Transform1Paint(Sender: TObject);	
begin	
  Light1.Apply;	
end;	
	
procedure TForm1.Transform2Paint(Sender: TObject);	
begin	
  Material1.Apply;	
  Material2.Apply;	
  glBegin(GL_POLYGON);	
    glVertex3f(-0.4,-0.6,+0.0);	
    glVertex3f(+0.4,+0.2,+0.0);	
    glVertex3f(+0.4,-0.8,-0.0);	
    glVertex3f(-0.4,+0.4,-0.0);	
  glEnd;	
end;	
Here are a couple of declarations you need at this point. The i suffix indicates the procedure 
uses integer parameters. The param value is 0 or 1 for true or false, on or off.

GL_LIGHT_MODEL_TWO_SIDE = $B52;	
GL_TRUE                 = 1;	
GL_FALSE                = 0;	
	
procedure glLightModeli(pname:GLenum;param:GLint); stdcall;;	
Create an OnCreate, an OnPaint, and an OnResize event handler for the form. In the 
OnCreate handler, set the light model to allow two-sided lighting. See Listing 8-1.

procedure TForm1.FormCreate(Sender: TObject);
begin
  glLightModeli(GL_LIGHT_MODEL_TWO_SIDE,GL_TRUE);
end;

procedure TForm1.FormPaint(Sender: TObject);
begin
  glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT);
  Transform1.Apply;
  Transform2.Apply;
  glFlush;
  OpenGL1.ErrorCheck('FormPaint');
end;

procedure TForm1.FormResize(Sender: TObject);


PAGE 162
 begin
  if OpenGL1.Smaller then
    FormPaint(Sender);
end;
Listing 8-1. Event handlers for non-planar project.
Listing 8-2 shows the form as text to reveal the component property settings. Notice that 
cullFace is not in the Enable list. This is important; otherwise the back face would not be 
visible. Notice also the values for AxisX and Angle for the Transform2 component, which 
mean the object rotates 130 degrees about the x-axis. Those values, plus AxisY, for the 
Transform1 component mean the spotlight rotates 15 degrees, mostly about the y-axis.

object Form1: TForm1
  Left = 309
  Top = 100
  Width = 328
  Height = 347
  Caption = 'Form1'
  Font.Charset = DEFAULT_CHARSET
  Font.Color = clWindowText
  Font.Height = -11
  Font.Name = 'MS Sans Serif'
  Font.Style = []
  OnCreate = FormPaint
  OnPaint = FormPaint
  OnResize = FormResize
  PixelsPerInch = 96
  TextHeight = 13
  object OpenGL1: TOpenGL
    pfdFlags = [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
    Left = 4
    Top = 4


PAGE 163
   end
  object Transform1: TTransform
    TranX = 0.6
    TranY = -0.6
    TranZ = -2.2
    AxisX = 0.2
    AxisY = 1
    Angle = 15
    ScaleX = 1
    ScaleY = 1
    ScaleZ = 1
    OnPaint = Transform1Paint
    Left = 4
    Top = 68
  end
  object Light1: TLight
    Which = 1
    Cutoff = 85
    AmbientRed = 0.5
    AmbientGreen = 0.5
    AmbientBlue = 0.5
    DiffuseRed = 1
    DiffuseGreen = 1
    DiffuseBlue = 1
    Direction = True
    Position = True
    Spotlight = True
    Enabled = True
    Left = 36
    Top = 68
  end
  object Transform2: TTransform
    TranZ = -2.9
    AxisX = 1
    Angle = 130
    ScaleX = 1
    ScaleY = 1
    ScaleZ = 1
    OnPaint = Transform2Paint
    Left = 4
    Top = 36
  end
  object Material1: TMaterial
    ambRed = 1
    ambAlpha = 1
    difRed = 1
    difAlpha = 1
    aFace = faFront
    dFace = faFront


PAGE 164
     sFace = faFront
    Left = 36
    Top = 36
  end
  object Material2: TMaterial
    ambBlue = 1
    ambAlpha = 1
    difBlue = 1
    difAlpha = 1
    aFace = faBack
    dFace = faBack
    sFace = faBack
    Left = 68
    Top = 36
  end
end
Listing 8-2. Properties for the non-planar project.
Save, compile, and run. Hopefully you are remembering never to run an OpenGL 
application from the Delphi IDE. A useful trick you can do with Windows 95, Windows 98, 
and Windows NT 4 (and later) is to create a shortcut on the desktop and set its target 
property as the complete path to the .EXE file of whatever OpenGL project you are 
developing. You may find that more convenient than using the Run command from the Start 
button.
Figure 8-2 shows the resulting polygon. The front face is red and the back face is blue. You 
can readily see that the four vertices do not lie in a plane. As a result, the edges appear to 
cross each other when viewed from some directions. There is no guarantee as to how a 
particular implementation will render such a figure, if at all.

One polygon exists that will always lie in a plane. Furthermore, it will always be convex. A 
convex polygon has no indentations and no crossing edges like the polygons shown in Figure 
8-3 do. In order for a graphics library to handle all possible polygons, it would have to 
employ complex and slow algorithms. For the sake of efficiency, OpenGL only intends to 
draw convex 

PAGE 165
 polygons. Furthermore, OpenGL does not even check for a “bad” polygon. The programmer 
bears the sole responsibility for giving OpenGL convex polygons.
There is a polygon that will always lie in a plane and will always be convex. That perfect 
polygon is the triangle. Divide your squares and pentagons and other polygons into several 
triangles. You may think that a carefully designed polygon is more efficient than multiple 
triangles, but triangle rendering is so simple that it provides an offsetting efficiency. In fact, 
this simplest of polygons even has hardware support in many video cards and graphics 
accelerators. Many hardware manufacturers boast of their products’ prowess in terms of 
how many thousands or millions of triangles per second they render! Suppose you really 
want to draw a polygon that does not lie entirely in a plane, or has indentations, or has sides 
that cross each other. No problem! Any general polygon can be subdivided into triangles.
Building Shapes From Triangles
Let’s start with a simple example. Here is a square:

  glBegin(GL_POLYGON);	
    glVertex3f(-0.0.4,-0.0.4,+0.0););	
    glVertex3f(+0.4,-0.0.4,+0.0););	
    glVertex3f(+0.4,+0.4,-0.0.0););	
    glVertex3f(-0.0.4,+0.4,-0.0.0););	
  glEnd;	
Here is the same square divided into triangles:

  glBegin(GL_TRIANGLES);	
    glVertex3f(-0.4,-0.4,+0.0);	
    glVertex3f(+0.4,-0.4,+0.0);	
    glVertex3f(-0.4,+0.4,+0.0);	
	
    glVertex3f(+0.4,-0.4,+0.0);	
    glVertex3f(+0.4,+0.4,+0.0);	


PAGE 166
     glVertex3f(-0.4,+0.4,+0.0);	
  glEnd;	
Notice that with GL_TRIANGLES as the parameter passed to glBegin, you can specify more 
than one polygon between glBegin and glEnd. Since all triangles have exactly three vertices, 
OpenGL knows how to group the vertex list. This eliminates some extra calls to glBegin and 
glEnd. Here is the declaration of the new constant and its relatives:

  GL_TRIANGLES      = 4;	
  GL_TRIANGLE_STRIP = 5;	
  GL_TRIANGLE_FAN   = 6;	
A hexagon provides a little more challenge. You could divide a hexagon into four triangles, 
but using six triangles will provide a lead-in for something else. Listing 8-3 shows how to 
draw the hexagon:

  glBegin(GL_TRIANGLES);
    glVertex3f(+0.0,+0.4,+0.0);
    glVertex3f(-0.3,+0.2,+0.0);
    glVertex3f(+0.0,+0.0,+0.0);

    glVertex3f(-0.3,+0.2,+0.0);
    glVertex3f(-0.3,-0.2,+0.0);
    glVertex3f(+0.0,+0.0,+0.0);

    glVertex3f(-0.3,-0.2,+0.0);
    glVertex3f(+0.0,-0.4,+0.0);
    glVertex3f(+0.0,+0.0,+0.0);

    glVertex3f(+0.0,-0.4,+0.0);
    glVertex3f(+0.3,-0.2,+0.0);
    glVertex3f(+0.0,+0.0,+0.0);

    glVertex3f(+0.3,-0.2,+0.0);
    glVertex3f(+0.3,+0.2,+0.0);
    glVertex3f(+0.0,+0.0,+0.0);
 
    glVertex3f(+0.3,+0.2,+0.0);
    glVertex3f(+0.0,+0.4,+0.0);
    glVertex3f(+0.0,+0.0,+0.0);
  glEnd;
Listing 8-3. A hexagon from triangles.

PAGE 167
 Triangle Fans
You surely noticed that a lot of vertices were repeated when you saw code to produce 
adjacent triangles. OpenGL provides a shortcut that saves code and reduces the number of 
command calls, and is therefore more efficient. The triangle fan allows you to specify three 
vertices for the first triangle, then one more vertex to specify a second triangle that shares 
two vertices with the previous triangle. Specify another vertex for a third triangle that shares 
two vertices from the second triangle. You can specify as many triangles as you want, using 
only one vertex per triangle, plus two more for the first triangle. All the triangles must share 
one common vertex that serves as a pivot point around which the triangles form.
Order
Triangle fans start with the pivot point. The first vertex you specify is the one around which 
all the triangles fan. Give two more vertices, being careful to progress in a clockwise manner. 
After you complete the first triangle, give an additional vertex. The next triangle will be 
made from the pivot point, the last vertex of the previous triangle, and the new vertex.
Flat Fans
Create a new directory under Chapter.8 and call it Fans. Create a new project and save the 
unit in the new directory as Fan1.Pas and the project file as Fan.Dpr. Drop on a TOpenGL 
component, a TTransform component, and a TLight. Add another TTransform and two 
TMaterial components. Add still another TTransform and two TMaterial components. Create 
an OnCreate, an OnPaint, and an OnResize event handler for the form. See Listing 8-4:

procedure TForm1.FormCreate(Sender: TObject);
begin
  glLightModeli(GL_LIGHT_MODEL_TWO_SIDE,GL_TRUE);
  glFrontFace(GL_CW);
end;

procedure TForm1.FormPaint(Sender: TObject);
begin
  glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT);
  Transform1.Apply;
  Transform2.Apply;
  Transform3.Apply;
  glFlush;
  OpenGL1.ErrorCheck('FormPaint');
end;



PAGE 168
 procedure TForm1.FormResize(Sender: TObject);
begin
  if OpenGL1.Smaller then
    FormPaint(Sender);
end;
Listing 8-4. Event handlers for triangle fans.
Winding and Faces
Triangle fans start with the vertex that will be the pivot point. The vertices then proceed in a 
clockwise direction for the first triangle, then each successive triangle proceeds in a 
clockwise direction from the first triangle. Since the winding for triangle fans usually is 
clockwise, the front face definition must change. That is the purpose of the glFrontFace 
command in the FormCreate method. The default value is GL_CCW for counterclockwise 
winding, but this project needs GL_CW passed for clockwise winding. 

  GL_CW  = $900;	
  GL_CCW = $901;	
	
procedure glFrontFace(mode:GLenum); stdcall;	
Create event handlers for the TTransform components as in Listing 8-5. Figure 8-4 shows the 
square and hexagon made from triangle fans. Notice that a square created with 
GL_TRIANGLE_FAN uses no more vertices than one created with GL_POLYGON. The 
hexagon, with six triangles, requires only eight vertices. Listing 8-6 shows the properties of 
the form and its components.



PAGE 169
 procedure TForm1.Transform1Paint(Sender: TObject);
begin
  Light1.Apply;
end;

procedure TForm1.Transform2Paint(Sender: TObject);
begin
  Material1.Apply;
  Material2.Apply;
  glBegin(GL_TRIANGLE_FAN);
    glVertex3f(+0.4,-0.4,+0.0);
    glVertex3f(-0.4,-0.4,+0.0);
    glVertex3f(-0.4,+0.4,+0.0);
    glVertex3f(+0.4,+0.4,+0.0);
  glEnd;
end;

procedure TForm1.Transform3Paint(Sender: TObject);
begin
  Material3.Apply;
  Material4.Apply;
  glBegin(GL_TRIANGLE_FAN);
    glVertex3f(+0.0,+0.0,+0.0);
    glVertex3f(-0.3,+0.2,+0.0);
    glVertex3f(+0.0,+0.4,+0.0);
    glVertex3f(+0.3,+0.2,+0.0);
    glVertex3f(+0.3,-0.2,+0.0);
    glVertex3f(+0.0,-0.4,+0.0);
    glVertex3f(-0.3,-0.2,+0.0);
    glVertex3f(-0.3,+0.2,+0.0);
  glEnd;
end;
Listing 8-5. TTransform event handlers.
object Form1: TForm1
  Left = 309
  Top = 100
  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 = True
  OnCreate = FormCreate


PAGE 170
   OnPaint = FormPaint
  OnResize = FormResize
  PixelsPerInch = 96
  TextHeight = 13
  object OpenGL1: TOpenGL
    pfdFlags = [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 Transform1: TTransform
    TranX = 0.6
    TranY = -0.6
    TranZ = -2.2
    AxisX = 0.2
    AxisY = 1
    Angle = 15
    ScaleX = 1
    ScaleY = 1
    ScaleZ = 1
    OnPaint = Transform1Paint
    Left = 4
    Top = 36
  end
  object Light1: TLight
    Which = 1
    Cutoff = 85
    AmbientRed = 0.5
    AmbientGreen = 0.5
    AmbientBlue = 0.5
    DiffuseRed = 1
    DiffuseGreen = 1
    DiffuseBlue = 1
    Direction = True
    Position = True


PAGE 171
     Spotlight = True
    Enabled = True
    Left = 36
    Top = 36
  end
  object Transform2: TTransform
    TranX = -0.5
    TranY = 0.5
    TranZ = -2.9
    ScaleX = 1
    ScaleY = 1
    ScaleZ = 1
    OnPaint = Transform2Paint
    Left = 4
    Top = 76
  end
  object Material1: TMaterial
    ambRed = 1
    ambAlpha = 1
    difRed = 1
    difAlpha = 1
    aFace = faFront
    dFace = faFront
    sFace = faFront
    Left = 36
    Top = 76
  end
  object Material2: TMaterial
    ambBlue = 1
    ambAlpha = 1
    difBlue = 1
    difAlpha = 1
    aFace = faBack
    dFace = faBack
    sFace = faBack
    Left = 68
    Top = 76
  end
  object Transform3: TTransform
    TranX = 0.5
    TranY = -0.5
    TranZ = -2.9
    ScaleX = 1
    ScaleY = 1
    ScaleZ = 1
    OnPaint = Transform3Paint
    Left = 4
    Top = 112
  end


PAGE 172
   object Material3: TMaterial
    ambRed = 1
    ambAlpha = 1
    difRed = 1
    difAlpha = 1
    aFace = faFront
    dFace = faFront
    sFace = faFront
    Left = 36
    Top = 112
  end
  object Material4: TMaterial
    ambBlue = 1
    ambAlpha = 1
    difBlue = 1
    difAlpha = 1
    aFace = faBack
    dFace = faBack
    sFace = faBack
    Left = 68
    Top = 112
  end
end
Listing 8-6. Fan project properties.
Save, compile, and run. So why does this example use two-sided lighting and two different 
material colors for the two sides? If you see red, and nothing but red, you have the winding 
right, not only for the first triangle, but for all the additional ones as well. If you see any blue, 
then something is wrong. Of course, you could apply some rotation in the transformations to 
show the blue backsides on purpose.
3-D Fans
While each individual triangle in a triangle fan is, of course, planar, triangle fans as a whole 
need not stay in a single plane. You can build a simple triangular pyramid to illustrate. 
Under the Chapter.8 directory create a Pyramid directory. Create a new project and save the 
unit as Pyr1.Pas and the project as Pyr.Dpr into the new directory. Place components and set 
properties in the same manner that you did for Fan.Dpr. Listing 8-7 shows the form as text 
for the actual property values:

object Form1: TForm1
  Left = 308
  Top = 100
  Width = 328
  Height = 347


PAGE 173
   Caption = 'Form1'
  Font.Charset = DEFAULT_CHARSET
  Font.Color = clWindowText
  Font.Height = -11
  Font.Name = 'MS Sans Serif'
  Font.Style = []
  OnCreate = FormCreate
  OnPaint = FormPaint
  OnResize = FormResize
  PixelsPerInch = 96
  TextHeight = 13
  object OpenGL1: TOpenGL
    pfdFlags = [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
    Left = 4
    Top = 4
  end
  object Transform1: TTransform
    TranX = 0.6
    TranY = -0.6
    TranZ = -2.2
    AxisX = 0.2
    AxisY = 1
    Angle = 15
    ScaleX = 0.3
    ScaleY = 0.3
    ScaleZ = 0.3
    OnPaint = Transform1Paint
    Left = 4
    Top = 36
  end
  object Light1: TLight
    Which = 1
    Cutoff = 85
    AmbientRed = 0.3
    AmbientGreen = 0.3


PAGE 174
     AmbientBlue = 0.3
    DiffuseRed = 1
    DiffuseGreen = 1
    DiffuseBlue = 1
    Direction = True
    Position = True
    Spotlight = True
    Enabled = True
    Left = 40
    Top = 36
  end
  object Transform2: TTransform
    TranX = -1.2
    TranY = 1.2
    TranZ = -5
    AxisX = 1
    Angle = 120
    ScaleX = 1
    ScaleY = 1
    ScaleZ = 1
    OnPaint = Transform2Paint
    Left = 4
    Top = 68
  end
  object Material1: TMaterial
    ambRed = 1
    ambAlpha = 1
    difRed = 1
    difAlpha = 1
    aFace = faFront
    dFace = faFront
    sFace = faFront
    Left = 40
    Top = 68
  end
  object Material2: TMaterial
    ambBlue = 1
    ambAlpha = 1
    difBlue = 1
    difAlpha = 1
    aFace = faBack
    dFace = faBack
    sFace = faBack
    Left = 72
    Top = 68
  end
  object Transform3: TTransform
    TranX = 0.8
    TranY = -0.8


PAGE 175
     TranZ = -4
    AxisX = 1
    Angle = -120
    ScaleX = 1
    ScaleY = 1
    ScaleZ = 1
    OnPaint = Transform3Paint
    Left = 4
    Top = 100
  end
  object Material3: TMaterial
    ambRed = 1
    ambAlpha = 1
    difRed = 1
    difAlpha = 1
    aFace = faFront
    dFace = faFront
    sFace = faFront
    Left = 40
    Top = 100
  end
  object Material4: TMaterial
    ambBlue = 1
    ambAlpha = 1
    difBlue = 1
    difAlpha = 1
    aFace = faBack
    dFace = faBack
    sFace = faBack
    Left = 72
    Top = 100
  end
end
Listing 8-7. Pyr project properties.
Create a new directory under the OpenGL directory and name it Shared. Create a new unit 
named Shapes.Pas, save it in the Shared directory, and add it to the Pyr project. Listing 8-8 
shows the new unit in all its glory:

unit Shapes;

interface

uses
  OpenGL;

procedure Pyramid;

implementation


PAGE 176
 
procedure Pyramid;
begin
  glBegin(GL_TRIANGLE_FAN);
    glVertex3f(+0.0,+0.0,+0.0);
    glVertex3f(-0.3,+0.3,-0.4);
    glVertex3f(+0.3,+0.3,-0.4);
    glVertex3f(+0.0,-0.3,-0.4);
    glVertex3f(-0.3,+0.3,-0.4);
  glEnd;
end;

end.
Listing 8-8. Shapes.Pas unit.
Listing 8-9 shows the event handlers of the Pyr project. Be sure to list Shapes in the uses 
clause in the implementation section, so you have access to the Pyramid procedure.

procedure TForm1.Transform1Paint(Sender: TObject);
begin
  Light1.Apply;
end;

procedure TForm1.Transform2Paint(Sender: TObject);
begin
  Material1.Apply;
  Material2.Apply;
  Pyramid;
end;

procedure TForm1.Transform3Paint(Sender: TObject);
begin
  Material3.Apply;
  Material4.Apply;
  Pyramid;
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
  glLightModeli(GL_LIGHT_MODEL_TWO_SIDE,GL_TRUE);
  glFrontFace(GL_CW);
end;

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


PAGE 177
 
procedure TForm1.FormPaint(Sender: TObject);
begin
  glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT);
  Transform1.Apply;
  Transform2.Apply;
  Transform3.Apply;
  glFlush;
  OpenGL1.ErrorCheck(‘FormPaint’);
end;
Listing 8-9. Pyr project event handler methods.
Call Pyramid in both Transform2 and Transform3 OnPaint methods using different property 
settings in the two components to present different aspects of the pyramid in different 
locations. Both presentations rotate the pyramid enough so that the bottom appears. Since 
the fourth side was not drawn, looking at the bottom is to peer into the blue interior.
Triangle Strips
OpenGL provides another way to draw a series of triangles that share vertices. Triangle strips 
also start with three vertices for the first triangle and one vertex for each additional one. 
However, triangle strips do not pivot about a single vertex. To create triangle strips, use 
GL_TRIANGLE_STRIP as the parameter to pass to the glBegin command:

  glBegin(GL_TRIANGLE_STRIP);	
Winding
Another difference between triangle strips and triangle fans is the winding. Triangle strips 
use the default counterclockwise winding. You will not need to call glFrontFace before using 
triangle strips unless you have previously called the command to specify clockwise winding 
for front faces.
Order
Create a triangle in the normal manner, specifying the vertices in a counterclockwise 
manner. When you give an additional vertex, the last two vertices of the previous triangle 
belong to the new triangle, followed by the new vertex.

PAGE 178
 A Cube of Triangles
To illustrate using a triangle strip, make the old standby, the cube. Create a new directory 
named Cube under the Chapter.8 directory. Create a new project named Cubic.Dpr with the 
unit named Cube1.Pas and save them in the new directory. List Shapes in the 
implementation uses clause. Place a TOpenGL component, two TTransform components, a 
TLight component, and two TMaterial components. Use Listing 8-10 for the properties:

object Form1: TForm1
  Left = 308
  Top = 102
  Width = 328
  Height = 347
  Caption = 'Form1'
  Font.Charset = DEFAULT_CHARSET
  Font.Color = clWindowText
  Font.Height = -11
  Font.Name = 'MS Sans Serif'
  Font.Style = []
  OnCreate = FormCreate
  OnPaint = FormPaint
  OnResize = FormResize
  PixelsPerInch = 96
  TextHeight = 13
  object OpenGL1: TOpenGL
    pfdFlags = [Draw_To_Window, Support_OpenGL]
    PixelType = RGBA
    Projection = Perspective
    Enable = [depthTest, Lighting]
    ClearRed = 0.600000023841858
    ClearGreen = 0.800000011920929
    ClearBlue = 0.800000011920929
    prxLeft = -1
    prxRight = 1
    prxBottom = -1
    prxTop = 1
    prxNear = 2
    prxFar = 9
    ColorBits = 24
    DepthBits = 32
    Left = 4
    Top = 4
  end
  object Transform1: TTransform
    TranX = 0.600000023841858
    TranY = -0.600000023841858
    TranZ = -2.20000004768372
    AxisX = 0.200000002980232


PAGE 179
     AxisY = 1
    Angle = 15
    ScaleX = 1
    ScaleY = 1
    ScaleZ = 1
    OnPaint = Transform1Paint
    Left = 4
    Top = 36
  end
  object Light1: TLight
    Which = 1
    Cutoff = 65
    AmbientRed = 0.200000002980232
    AmbientGreen = 0.200000002980232
    AmbientBlue = 0.200000002980232
    DiffuseRed = 1
    DiffuseGreen = 1
    DiffuseBlue = 1
    Direction = True
    Position = True
    Spotlight = True
    Enabled = True
    Left = 40
    Top = 36
  end
  object Transform2: TTransform
    TranX = 0.699999988079071
    TranY = -0.699999988079071
    TranZ = -3
    ScaleX = 1
    ScaleY = 1
    ScaleZ = 1
    OnPaint = Transform2Paint
    Left = 4
    Top = 72
  end
  object Material1: TMaterial
    ambRed = 1
    ambAlpha = 1
    difRed = 1
    difAlpha = 1
    aFace = faFront
    dFace = faFront
    sFace = faFront
    Left = 36
    Top = 72
  end
  object Material2: TMaterial
    ambBlue = 1


PAGE 180
     ambAlpha = 1
    difBlue = 1
    difAlpha = 1
    aFace = faBack
    dFace = faBack
    sFace = faBack
    Left = 68
    Top = 72
  end
end
Listing 8-10. Cubic properties.
Listing 8-11 shows the event handler methods for the Cubic project. Listing 8-12 shows the 
Cube method you should add to the Shapes unit, which should also declare the Cube 
procedure in its interface section.

procedure TForm1.FormCreate(Sender: TObject);
begin
  glLightModeli(GL_LIGHT_MODEL_TWO_SIDE,GL_TRUE);
end;

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);
  Transform1.Apply;
  Transform2.Apply;
  glFlush;
  OpenGL1.ErrorCheck('FormPaint');
end;

procedure TForm1.Transform1Paint(Sender: TObject);
begin
  Light1.Apply;
end;

procedure TForm1.Transform2Paint(Sender: TObject);
begin
  Material1.Apply;
  Material2.Apply;
  Cube;
end;
Listing 8-11. Cubic project event handler methods.

PAGE 181
 procedure Cube;
begin
  glBegin(GL_TRIANGLE_STRIP);
    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);
    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);
    glVertex3f(-0.4,+0.4,+0.4);
    glVertex3f(-0.4,-0.4,+0.4); 
  glEnd;
end;
Listing 8-12. The Cube procedure in the Shapes unit.

Next, of course, is a picture of the result. Figure 8-5 shows a topless, bottomless, but 
otherwise perfect cube.


PAGE 182
 Shading
So far every example program has been using smooth shading; OpenGL uses the color of 
each vertex in the polygon, as computed from the material color, lighting, distance, and 
angle. The color of any point on the polygon is computed from the vertex colors, producing 
a gradual transition from one vertex to the next. However, smooth shading is not the only 
possibility under OpenGL, although it is the default. Naturally, OpenGL has a couple of 
constants appropriate for the subject:

  GL_FLAT   = $1D00;	
  GL_SMOOTH = $1D01;	
Of course, naming some OpenGL constants does no good without a command to receive 
them. Here is the command to set the shading model:

procedure glShadeModel(mode:GLenum); stdcall;	
If you pass GL_FLAT to the command (for flat shading), OpenGL selects one vertex of the 
polygon and uses its (computed) color for the entire polygon surface. Take the Cube project 
and make one change in the FormCreate method:

procedure TForm1.FormCreate(Sender: TObject);	
begin	
  glLightModeli(GL_LIGHT_MODEL_TWO_SIDE,GL_TRUE);	
  glShadeModel(GL_FLAT);	
end;	
When you compile and run this time, each side of the cube has a single (flat) color as in 
Figure 8-6:

PAGE 183
 Curves
You have seen OpenGL commands for points, lines, and polygons. You may have been 
waiting for curve commands with great anticipation. You can stop holding your breath. 
Here are the OpenGL curve commands...all zero of them. Do not panic. OpenGL allows 
you to represent curved surfaces quite nicely. Rather than using actual curve commands, the 
usual way a 3-D rendering system represents curved surfaces is by means of many small 
polygons. OpenGL is no exception to that approach. Even OpenGL commands that 
ostensibly deal with curves actually produce polygons. You should find the following 
project quite pleasing.
Cylinder
Create a new directory under Chapter.8 and name it Cylinder. Create a new project, saving 
the unit as Cyl1.Pas and the project as Cyl.Dpr. As you may guess, the object to appear in 
this project is a cylinder. A cylinder has a circular base, so the surface is curved. Add two 
procedures (to the ones already there) in the interface section of the Shapes unit:

procedure Pyramid;	
procedure Cube;	
procedure CylinderBase;	
procedure Cylinder;	
In the implementation section, declare a couple of circle variables and implement the two 
procedures:

implementation	
	
var	
  circleX,	
  circleY: array[0..35] of glFloat;	
The CylinderBase procedure does a lot of floating-point math. This would be very expensive 
for your rendering if you had to perform the math every time you drew the cylinder. 
Fortunately you do not need to do the math over and over. That is the purpose of the arrays. 
Perform the calculations once in the CylinderBase procedure and store the results in the 
array:

procedure CylinderBase;	
var	
  around: integer;	
  pi18: double;	
begin	
  pi18 := pi/18.0;	
  for around := 0 to 35 do	


PAGE 184
   begin	
    circleX[around] := 0.4*sin(around*pi18);	
    circleY[around] := 0.4*cos(around*pi18);	
  end;	
end;	
The Cylinder procedure draws the cylinder as a triangle strip guided by the circle arrays. 
Notice that you are free to use various non-OpenGL programming statements between 
glBegin and glEnd.

procedure Cylinder;	
var	
  around: integer;	
begin	
  glBegin(GL_TRIANGLE_STRIP);	
    for around := 0 to 35 do	
    begin	
      glVertex3f(circleX[around],circleY[around],0.0);	
      glVertex3f(circleX[around],circleY[around],1.0);	
    end; {for}	
    glVertex3f(circleX[0],circleY[0],0.0);	
    glVertex3f(circleX[0],circleY[0],1.0);	
  glEnd;	
end;	
For the Cyl project, place on the form a TOpenGL component, two TTransform components, 
a TLight component, and two TMaterial components. Listing 8-13 shows the properties for 
the form and its components.

object Form1: TForm1
  Left = 310
  Top = 98
  Width = 328
  Height = 347
  Caption = 'Form1'
  Font.Charset = DEFAULT_CHARSET
  Font.Color = clWindowText
  Font.Height = -11
  Font.Name = 'MS Sans Serif'
  Font.Style = []
  OnCreate = FormCreate
  OnPaint = FormPaint
  OnResize = FormResize
  PixelsPerInch = 96
  TextHeight = 13
  object OpenGL1: TOpenGL
    pfdFlags = [Draw_To_Window, Support_OpenGL]
    PixelType = RGBA
    Projection = Perspective
    Enable = [depthTest, Lighting]


PAGE 185
     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
    Left = 4
    Top = 4
  end
  object Transform1: TTransform
    TranX = 0.6
    TranY = -0.6
    TranZ = -2.2
    AxisX = 0.2
    AxisY = 1
    Angle = 15
    ScaleX = 1
    ScaleY = 1
    ScaleZ = 1
    OnPaint = Transform1Paint
    Left = 4
    Top = 36
  end
  object Light1: TLight
    Which = 1
    Cutoff = 65
    AmbientRed = 0.2
    AmbientGreen = 0.2
    AmbientBlue = 0.2
    DiffuseRed = 1
    DiffuseGreen = 1
    DiffuseBlue = 1
    Direction = True
    Position = True
    Spotlight = True
    Enabled = True
    Left = 40
    Top = 36
  end
  object Transform2: TTransform
    TranX = -0.8
    TranY = 0.8
    TranZ = -3
    ScaleX = 1


PAGE 186
     ScaleY = 1
    ScaleZ = 1
    OnPaint = Transform2Paint
    Left = 4
    Top = 68
  end
  object Material1: TMaterial
    ambRed = 1
    ambAlpha = 1
    difRed = 1
    difAlpha = 1
    aFace = faFront
    dFace = faFront
    sFace = faFront
    Left = 40
    Top = 68
  end
  object Material2: TMaterial
    ambBlue = 1
    ambAlpha = 1
    difBlue = 1
    difGreen = 1
    difRed = 0.6
    difAlpha = 1
    aFace = faBack
    dFace = faBack
    sFace = faBack
    Left = 72
    Top = 68
  end
end
Listing 8-13. Cyl project properties.
Listing 8-14 shows the event handlers for the form and its components. This time only one 
object appears. Since this and later projects include Shapes in the implementation uses 
clause, these listings are quite enough to describe the project.

procedure TForm1.Transform1Paint(Sender: TObject);
begin
  Light1.Apply;
end;

procedure TForm1.Transform2Paint(Sender: TObject);
begin
  Material1.Apply;
  Material2.Apply;
  Cylinder;
end;



PAGE 187
 procedure TForm1.FormCreate(Sender: TObject);
begin
  CylinderBase;
  glLightModeli(GL_LIGHT_MODEL_TWO_SIDE,GL_TRUE);
end;

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);
  Transform1.Apply;
  Transform2.Apply;
  glFlush;
  OpenGL1.ErrorCheck(‘FormPaint’);
end;
Listing 8-14. Cyl project event handler methods.
When you compile and run, the cylinder appears smooth and round in the upper left corner 
as in Figure 8-7:
Just for fun, make the shading flat:

procedure TForm1.FormCreate(Sender: TObject);	
begin	
  CylinderBase;	


PAGE 188
   glLightModeli(GL_LIGHT_MODEL_TWO_SIDE,GL_TRUE);	
  glShadeModel(GL_FLAT);	
end;	
When you compile and run, some of the triangles become much more evident under a flat 
shading model as in Figure 8-8:
Cone
A cone is another kind of object that can illustrate curved surfaces under OpenGL. As an 
added bonus you can reuse the arrays and the CylinderBase procedure from the earlier 
example. In the Shapes unit, declare a Cone procedure in the interface section and develop it 
in the implementation section:

procedure Cone;	
var	
  around: integer;	
begin	
  glBegin(GL_TRIANGLE_FAN);	
    glVertex3f(0.0,0.0,0.4);	
    for around := 35 downto 0 do	
      glVertex3f(circleX[around],circleY[around],0.0);	
    glVertex3f(circleX[35],circleY[35],0.0);	
  glEnd;	
end;	
Create a directory under Chapter.8 and name it Cone. Create a new project, saving the unit 
as Cone1.Pas and the project file as Cone0.Dpr. Place components, set properties, and add 
event handlers just like the Cyl project (without the flat shading call). Change 
Transform2Paint to call Cone 

PAGE 189
 instead of Cylinder. Change two properties of Transform2. Make Angle equal to 120 and 
AxisX equal to 1. Compile and run to behold a smoothly curved cone.
Dome
For even more curvature, try making a dome. Create a new directory under the Chapter.8 
directory and name it Dome. In it create a new project named Dome0.Dpr with a main unit 
named Dome1.Pas. Drop on components, set properties, and add event handlers just as you 
did for the Cone project. Change Transform2Paint to call Dome instead of Cone.
Start with a triangle fan for a cone, but smaller and shallower. Then make a few triangle 
strips to build out from the fan, each one somewhat like the cylinder. You will also need 
some more calculations to determine the base for each strip, and you will need an array in 
the Shapes unit to store the results.

var	
  circleX,	
  circleY,	
  circleZ: array[0..35] of glFloat;	
It will be substantially more complex than previous objects, but correspondingly more fun. 
Add a Dome procedure, shown in Listing 8-15, to the Shapes unit and reference it in the 
interface section:

procedure Dome;	
var	
  olddiam,	
  oldlayer,	
  diam,	
  layer: double;	
  place,	
  around: integer;	
begin	
  for place := 1 to 9 do	
  begin	
    layer := circleY[place];	
    diam := circleX[place]*2.5;	
    if place=1 then	
    begin	
      glFrontFace(GL_CCW);	
      glBegin(GL_TRIANGLE_FAN);	
        glVertex3f(0.0,0.0,0.4);	
        for around := 35 downto 0 do	
          glVertex3f(circleX[around]*diam,circleY[around]*diam,layer);	
        glVertex3f(circleX[35]*diam,circleY[35]*diam,layer);	


PAGE 190
     end else begin	
      glFrontFace(GL_CW);	
      glBegin(GL_TRIANGLE_STRIP);	
        for around := 0 to 35 do	
        begin	
          glVertex3f(circleX[around]*olddiam,circleY[around]*olddiam,	
            oldlayer);	
          glVertex3f(circleX[around]*diam,circleY[around]*diam,layer);	
        end; {for}	
        glVertex3f(circleX[0]*olddiam,circleY[0]*olddiam,oldlayer);	
        glVertex3f(circleX[0]*diam,circleY[0]*diam,layer);	
    end;	
    glEnd;	
    olddiam := diam;	
    oldlayer := layer;	
  end; {for}	
end;	
Listing 8-15. The Dome procedure.
There are better ways to create a sphere or portion thereof, but this example shows changing 
the front face definition from one part to the next. You may have noticed that the triangle fan 
requires counterclockwise winding, and the triangle strip requires clockwise winding. These 
are the opposite of what you will usually use for those primitives. The loops for these 
portions happen to specify the vertices in an order different from the usual for such 
structures. Pay little attention to that quirk. Instead focus on the combination of primitives to 
build a larger structure.
When you compile and run, you will see that you can make very round figures from flat 
little triangles as in Figure 8-9.
Highlights
The world is full of curves. In fact, straight lines are mostly a man-made feature rarely found 
in nature. The ability of OpenGL to simulate curved surfaces is a fascinating property that 
extends the potential realism of 

PAGE 191
 OpenGL scenes dramatically. Hopefully you found these curvature demonstrations quite 
exciting. Nevertheless, no matter how impressed you may have been by them, you have to 
admit that the curved surfaces shown so far are rather, well, dull.
OpenGL can represent shiny surfaces quite nicely by means of specular highlights on curved 
surfaces. You can use glMaterial commands to set the specular color (usually white) and to 
set the shininess of the surface. OpenGL allows shininess to range from 0 for no shininess to 
a maximum of 128. With the material components you can simply set the mShine property to 
a non-zero value to invoke glMaterial commands with the proper parameters.
For this illustration, a cylinder will serve well. Create a directory named High under the 
Chaper.8 directory. Create a new project, saving the unit as High1.Pas and the project as 
HighL.Dpr. Place OpenGL components and create event handlers as before. Listing 8-16 
shows the specific settings:

object Form1: TForm1
  Left = 310
  Top = 98
  Width = 328
  Height = 347
  Caption = 'Form1'
  Font.Charset = DEFAULT_CHARSET
  Font.Color = clWindowText
  Font.Height = -11
  Font.Name = 'MS Sans Serif'
  Font.Style = []
  OnCreate = FormCreate
  OnPaint = FormPaint
  OnResize = FormResize
  PixelsPerInch = 96
  TextHeight = 13
  object OpenGL1: TOpenGL
    pfdFlags = [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 = 37


PAGE 192
     ColorBits = 24
    DepthBits = 32
    Left = 4
    Top = 4
  end
  object Transform1: TTransform
    TranX = 0.9
    TranZ = -2.6
    AxisY = 1
    ScaleX = 1
    ScaleY = 1
    ScaleZ = 1
    OnPaint = Transform1Paint
    Left = 4
    Top = 36
  end
  object Light1: TLight
    Which = 1
    Cutoff = 30
    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 = 36
  end
  object Transform2: TTransform
    TranX = 1.4
    TranY = -0.13
    TranZ = -4
    AxisY = 1
    ScaleX = 1
    ScaleY = 1
    ScaleZ = 1
    OnPaint = Transform2Paint
    Left = 4
    Top = 68
  end
  object Material1: TMaterial
    ambRed = 1


PAGE 193
     ambAlpha = 1
    difRed = 1
    difAlpha = 1
    aFace = faFront
    dFace = faFront
    sFace = faFront
    mShine = 128
    Left = 40
    Top = 68
  end
  object Material2: TMaterial
    ambBlue = 1
    ambAlpha = 1
    difBlue = 1
    difAlpha = 1
    aFace = faBack
    dFace = faBack
    sFace = faBack
    mShine = 128
    Left = 72
    Top = 68
  end
end
Listing 8-16. HighL properties.
Notice that the transformation component moves the cylinder to the side near the light so 
that a highlight will appear. Listing 8-17 shows the event handler code that supports this 
situation:

procedure TForm1.Transform1Paint(Sender: TObject);
begin
  Light1.Apply;
end;

procedure TForm1.Transform2Paint(Sender: TObject);
begin
  Material1.Apply;
  Material2.Apply;
  Cylinder;
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
  CylinderBase;
  glLightModeli(GL_LIGHT_MODEL_TWO_SIDE,GL_TRUE);
end;

procedure TForm1.FormResize(Sender: TObject);
begin


PAGE 194
   if OpenGL1.Smaller then
    FormPaint(Sender);
end;

procedure TForm1.FormPaint(Sender: TObject);
begin
  glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT);
  Transform1.Apply;
  Transform2.Apply;
  glFlush;
  OpenGL1.ErrorCheck('FormPaint');
end;
Listing 8-17. HighL event handlers.

Compile and run as usual. Now the cylinder appears to have a very shiny surface that 
gleams in the presence of the spotlight. That is not bad for just illuminating some pixels on a 
flat monitor screen. Figure 8-10 gives an idea, but nothing can beat seeing it on your own 
computer in full color.




Identifier Reference
Identifier			Description
glFrontFace
An OpenGL command that specifies the winding for the front faces of polygons.
glLightModel
An OpenGL command to set the lighting model.
glShadeModel
An OpenGL command to set the shade model. It accepts either GL_FLAT or GL_SMOOTH.
GL_CCW
An OpenGL constant specifying counterclockwise winding for the front faces of polygons. 
This is the default.

PAGE 195
 
Identifier			Description
GL_CW
An OpenGL constant specifying clockwise winding for the front faces of polygons.
GL_FALSE
An OpenGL constant representing a setting of false.
GL_FLAT
An OpenGL constant specifying a flat shading model in which each polygon has a single 
shade selected from one of its vertices.
GL_LIGHT_MODEL_
TWO_SIDE 
An OpenGL constant to pass to glLightModel. It specifies lighting for both front and back 
faces of polygons.
GL_SMOOTH
An OpenGL constant specifying a smooth shading model in which the shade at a given point 
on a polygon is an intermediate value calculated from each of the vertices.
GL_TRIANGLES
An OpenGL constant to pass to glBegin. All the vertices listed before glEnd connect in 
groups of three to form triangles.
GL_TRIANGLE_FAN
An OpenGL constant to pass to glBegin. All the vertices before glEnd form a triangle fan.
GL_TRIANGLE_STRIP
An OpenGL constant to pass to glBegin. All the vertices before glEnd form a triangle strip. 
GL_TRUE
An OpenGL constant representing a setting of true.
Summary
This chapter developed several key concepts relating to triangles:
? General polygons may not lie in a plane and may not be convex. For efficiency, OpenGL 
assumes convex polygons.
? Triangles always lie in a plane and are always convex.
? Combinations of triangles can build any general polygon.
? OpenGL can render some combinations of triangles more efficiently as strips and fans.
? OpenGL can simulate curved surfaces with combinations of triangles.
? OpenGL can produce realistic specular highlights on simulated curved surfaces.


PAGE 196

Chapter 9
Animation
Other chapters hinted. This chapter fulfills. At long last you get to do some OpenGL 
movement and animation. Create a new directory under OpenGL and name it Chapter.9.
Mouse Movement
Start with moving graphics objects by means of the mouse. Create a directory under 
Chapter.9 and name it Mouse. Create a new project and save it into the new directory. Name 
the unit Mouse1.Pas and the project file Mouse0.Dpr. Perform an Add to Project and bring in 
the Shapes unit. Don’t forget to include Shapes as well as OpenGL in the Mouse1 interface 
uses clause.
Properties
Other than the movement, this project does not use anything really new. It has a cylinder, a 
cone, and a light. The project uses the picking introduced in Chapter 7, as well as the 
TOpenGL, TTransform, and TMaterial components. Listing 9-1 shows the components and 
their properties:

object Form1: TForm1
  Left = 310
  Top = 108
  Width = 328
  Height = 347
  Caption = 'Form1'
  Font.Charset = DEFAULT_CHARSET
  Font.Color = clWindowText
  Font.Height = -11
  Font.Name = 'MS Sans Serif'
  Font.Style = []
  OnCreate = FormCreate
  OnMouseDown = FormMouseDown


PAGE 197
   OnMouseMove = FormMouseMove
  OnMouseUp = FormMouseUp
  OnPaint = FormPaint
  OnResize = FormResize
  PixelsPerInch = 96
  TextHeight = 13
  object OpenGL1: TOpenGL
    pfdFlags = [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
    Left = 4
    Top = 4
  end
  object Transform1: TTransform
    TranX = 0.6
    TranY = -0.6
    TranZ = -2.2
    AxisX = 0.2
    AxisY = 1
    Angle = 15
    ScaleX = 1
    ScaleY = 1
    ScaleZ = 1
    OnPaint = Transform1Paint
    Left = 4
    Top = 36
  end
  object Light1: TLight
    Which = 1
    Cutoff = 65
    AmbientRed = 0.2
    AmbientGreen = 0.2
    AmbientBlue = 0.2
    DiffuseRed = 1
    DiffuseGreen = 1
    DiffuseBlue = 1
    Direction = True


PAGE 198
     Position = True
    Spotlight = True
    Enabled = True
    Left = 36
    Top = 36
  end
  object Transform2: TTransform
    TranX = -0.8
    TranY = 0.8
    TranZ = -3
    ScaleX = 1
    ScaleY = 1
    ScaleZ = 1
    OnPaint = Transform2Paint
    Left = 4
    Top = 68
  end
  object Material1: TMaterial
    ambRed = 1
    ambAlpha = 1
    difRed = 1
    difAlpha = 1
    aFace = faFront
    dFace = faFront
    sFace = faFront
    Left = 36
    Top = 68
  end
  object Material2: TMaterial
    ambBlue = 1
    ambAlpha = 1
    difBlue = 1
    difAlpha = 1
    aFace = faBack
    dFace = faBack
    sFace = faBack
    Left = 68
    Top = 68
  end
  object Transform3: TTransform
    TranX = 0.3
    TranY = -0.3
    TranZ = -2.6
    ScaleX = 1
    ScaleY = 1
    ScaleZ = 1
    OnPaint = Transform3Paint
    Left = 4
    Top = 100


PAGE 199
   end
  object Material3: TMaterial
    ambRed = 1
    ambAlpha = 1
    difRed = 1
    difAlpha = 1
    aFace = faFront
    dFace = faFront
    sFace = faFront
    Left = 36
    Top = 100
  end
  object Material4: TMaterial
    ambBlue = 1
    ambAlpha = 1
    difBlue = 1
    difAlpha = 1
    aFace = faBack
    dFace = faBack
    sFace = faBack
    Left = 68
    Top = 100
  end
end
Listing 9-1. Mouse0 project properties.
Picking
The Shapes unit needs some additional work. Not only does the unit need to build some 
shapes, but it also needs to enable picking those shapes by borrowing code from the Pick 
project.
Define a hit record type at the top of the Shapes unit interface section:

interface	
	
uses	
  sysutils,	
  OpenGL;	
	
type	
  PHit=^THit;	
  THit=packed record	
    ncount,	
    dnear,	
    dfar: GLuint;	
    names: array[1..32] of GLuint;	
  end; {THit}	

PAGE 200
 Add the declaration of a procedure and a function, still in the interface section:

procedure Pyramid;	
procedure Cube;	
procedure CylinderBase;	
procedure Cylinder;	
procedure Cone;	
procedure Dome;	
procedure Selection(px,py:GLdouble);	
function SelectionDone:GLint;	
At the bottom of the interface section, declare a buffer; be fairly generous so it will easily 
handle any situations in this book:

var	
  PickBuffer: array[1..256] of GLuint;	
Near the end of the implementation section, bring in the code for the Selection procedure, 
slightly modified so that it does not depend on a particular form or component:

procedure Selection(px,py:GLdouble);	
type	
  TViewPort=packed record	
    left,bottom,width,height: GLint;	
  end;	
var	
  viewport: TViewPort;	
begin	
  glRenderMode(GL_SELECT);	
  glInitNames;	
  glPushName(0);	
  glMatrixMode(GL_PROJECTION);	
  glPushMatrix;	
  glLoadIdentity;	
  glGetIntegerv(GL_VIEWPORT,@viewport);	
  py := viewport.height-py;	
  gluPickMatrix(px,py,2.0,2.0,@viewport);	
end;	
After the Selection procedure, implement SelectionDone. Make it a function so that it will 
return the number of hits, since it cannot determine on its own what to do with the hits like 
it did as a method in the Pick project.

function SelectionDone:GLint;	
begin	
  glMatrixMode(GL_PROJECTION);	
  glPopMatrix;	
  result := glRenderMode(GL_RENDER);	


PAGE 201
 end;	
	
end.	
Fields and Methods
Meanwhile, back at the main unit (Mouse1), add some private fields to the form declaration. 
Their mission is to “remember” how things were when the mouse button went down. startX, 
startY, and startShift store the mouse cursor position and the state of the shift keys at that 
instant. The private component (pointer) selTrans references the transformation component 
affecting the selected primitives, while trnX and trnY store the TransX and TransY property 
values at the time of selection. This chapter does not make any real use of the shift keys, but 
they are read anyway because many projects could use them.

  private	
    startX,	
    startY: integer;	
    startShift: TShiftState;	
    selTrans: TTransform;	
    trnX,	
    trnY: GLfloat;	
The FormCreate method initializes the circle required for the cylinder and the cone, sets up 
two-sided lighting, and identifies the selection buffer. It also initializes startX for use as a 
flag:

procedure TForm1.FormCreate(Sender: TObject);	
begin	
  CylinderBase;	
  glLightModeli(GL_LIGHT_MODEL_TWO_SIDE,GL_TRUE);	
  glSelectBuffer(128,@PickBuffer);	
  startX := -1;	
end;	
The FormResize, FormPaint, and Transform1Paint methods perform the usual:

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);	
  Transform1.Apply;	


PAGE 202
   Transform2.Apply;	
  Transform3.Apply;	
  glFlush;	
  OpenGL1.ErrorCheck(‘FormPaint’);	
end;	
	
procedure TForm1.Transform1Paint(Sender: TObject);	
begin	
  Light1.Apply;	
end;	
The Transform2Paint and Transform3Paint methods perform an extra service, loading the 
appropriate “name.” Remember that the glLoadName command has no effect unless the 
system is in selection mode.

procedure TForm1.Transform2Paint(Sender: TObject);	
begin	
  Material1.Apply;	
  Material2.Apply;	
  glLoadName(2);	
  Cylinder;	
end;	
	
procedure TForm1.Transform3Paint(Sender: TObject);	
begin	
  Material3.Apply;	
  Material4.Apply;	
  glLoadName(3);	
  Cone;	
end;	
Now comes the fun part. This new code is just a minor extension over what you have 
already done. Remember that in the Pick project you created an OnMouseUp event handler 
since OnClick did not have enough information, and OnMouseDown would be more 
suitable for a dragging operation. This project will use a kind of dragging, so create a 
handler for the OnMouseDown event.
The constants just clarify the process of advancing down the selection buffer, and place, of 
course, keeps track of the position in the buffer. The GLuint selno stores the “name” of the 
selected primitives, hits is the number of objects found (returned from SelectionDone), hit is 
a loop index for processing all the hits, and ahit points to the current hit record in that 
processing. Of the three doubles, mx just stores the value of maxint as a floating-point 
number, nearer stores the distance to the “near” part of the current hit, and nearest stores the 
closest hit examined so far.

procedure TForm1.FormMouseDown(Sender:TObject;Button:TMouseButton;	
  Shift:TShiftState;X,Y:Integer);	


PAGE 203
 const	
  countElement=1;	
  nearElement=1;	
  farElement=1;	
var	
  selno,	
  place: integer;	
  hit,	
  hits: GLint;	
  ahit: PHit;	
  mx,	
  nearer,	
  nearest: double;	
begin	
FormMouseDown initializes startX to –1 to signal other methods to do nothing if this 
method did not go far enough. After checking for the left mouse button as the only one of 
interest, the first thing this mouse method does is enter the selection mode. It calls the 
Selection procedure, passing the X and Y pixel coordinates of the mouse pointer. Next, the 
method sets up the viewing volume and invokes FormPaint to render the scene under 
selection mode. After the rendering, it collects the number of hits under the passed 
coordinates and restores normal rendering by calling SelectionDone. If there were no hits, 
the method bails out.

begin	
  startX := -1;	
  if Button<>mbLeft then	
    exit;	
  Selection(x,y);	
  with OpenGL1 do	
    glFrustum(prxLeft,prxRight,prxBottom,prxTop,prxNear,prxFar);	
  FormPaint(nil);	
  hits := SelectionDone;	
  if (hits<1) or (Button<>mbLeft) then	
    exit;	
If the processing gets past this point, startX, startY, and startShift keep track of the initial 
mouse coordinates and the shift key states at the time of pressing the left mouse button. 
Since startX no longer has a negative value, other methods can do their jobs. The initial value 
of place, the position in the selection buffer, is 1. The nearest variable gets a very large value 
so that the first hit processed will qualify as closer. Finally mx receives the value of the 
largest 32-bit integer, stored as a floating-point number to provide for calculating still larger 
values.

  startX := X;	
  startY := Y;	


PAGE 204
   startShift := Shift;	
  place := 1;	
  nearest := 9e33;	
  mx := maxint;	
The loop processes each hit. In the current project two hits are the most hits possible, but this 
code will work for a somewhat more general case. The processing consists of finding the hit 
closest to the viewer and storing the corresponding name. The method compares the dnear 
field to the lowest value found so far and stores the “name” of the closest in selno.
The buffer contains a series of unsigned 32-bit integers, but until version 4, Delphi did not 
have any 32-bit unsigned integers. Until then, cardinal only used 31 bits. The floating-point 
numbers serve to handle the resulting rollovers.

  for hit := 1 to hits do	
  begin	
    ahit := @PickBuffer[place];	
    with ahit^ do	
    begin	
      nearer := dnear;	
      if nearer<0 then	
      begin	
        nearer := mx+nearer; {how much did it roll over?}	
        nearer := mx+nearer; {add amount rolled over to maxint}	
      end;	
      if nearer<nearest then	
      begin	
        nearest := nearer;	
        selno := names[1];	
      end;	
      place := place+countElement+nearElement+farElement+ncount;	
    end; {with}	
  end; {for}	
Based on the name found to be closest, the method finishes by storing the associated 
TTransform component and the X and Y translation values.

  case selno of	
    2: selTrans := Transform2;	
    3: selTrans := Transform3;	
  end; {case}	
  with selTrans do	
  begin	
    trnX := TranX;	
    trnY := TranY;	
  end; {with}	
end;	

PAGE 205
 Create a handler for the OnMouseMove event, which moves the selected object around the 
form as long as the left mouse button remains down. This method only operates if the left 
mouse button were pressed and at least one hit returned; the method exits immediately if 
startX is less than 0. The two integer variables, deltaX and deltaY, get the difference between 
the original mouse position and the current mouse position passed as the X and Y 
parameters. The GLfloats, changeX and changeY, are the calculated difference between the 
original object X and Y coordinates in the viewing volume. Notice the different direction of 
subtraction for calculating deltaY compensates for the fact that Windows Y coordinates and 
OpenGL Y coordinates run in opposite directions.

procedure TForm1.FormMouseMove(Sender:TObject;Shift:TShiftState; X,Y:Integer);
var	
  deltaX,	
  deltaY: integer;	
  ratio,	
  changeX,	
  changeY: GLfloat;	
begin	
  if startX<0 then	
    exit;	
  deltaX := X-startX;	
  deltaY := startY-Y;	
Since the viewing volume is a frustum, more distant objects must move farther in the X and 
Y directions in order to track the mouse. The ratio variable provides the appropriate 
multiplier. If an object were right at the near face of the frustum, prxNear, then ratio would 
be 1.0. If the object were twice as far, then ratio would be 2.0. The floating-point change 
values are proportional to the delta values of the mouse cursor and the width or height of 
the front face of the frustum. The values are inversely proportional to the client width or 
height. The new location coordinates of the object are the sum of the original coordinates and 
the change multiplied by the ratio. Once the method has figured the new location, FormPaint 
redraws the scene.

  with selTrans,OpenGL1 do	
  begin	
    ratio := -tranZ/prxNear;	
    changeX := deltaX*(prxRight-prxLeft)/ClientWidth;	
    changeY := deltaY*(prxTop-prxBottom)/ClientHeight;	
    tranX := trnX+changeX*ratio;	
    tranY := trnY+changeY*ratio;	
  end;	
  FormPaint(nil);	
end;	

PAGE 206
 To complete the sequence, create a handler for the OnMouseUp event. All it does is set 
startX negative. Basically, releasing the mouse button shuts off the actions of the 
OnMouseMove event handler by setting that flag.

procedure TForm1.FormMouseUp(Sender:TObject;Button:TMouseButton;	
  Shift:TShiftState;X,Y:Integer);	
begin	
  startX := -1;	
end;	
Action
Save, compile, and run. Figure 9-1 shows the initial appearance of the form. Place the mouse 
cursor on the cylinder, hold down the left mouse button, and drag the cylinder around. Do 
the same for the cone. Congratulations! Making your OpenGL graphics objects move around 
with the mouse is a real accomplishment.
Flicker
As wonderful as this simple animation may be, it has a serious problem. The flicker is 
positively horrendous! Obviously there must be a way to improve the quality of the 
movement. Many animations run quite smoothly under Windows, including OpenGL 
animations. A nice solution would be the ability to draw the scene off-screen, then move it to 
the screen in one operation after finishing the drawing. Such a solution actually exists for 
OpenGL. The on-screen display is called the front buffer, the off-screen drawing area is 
called the back buffer, and the technique is called double buffering.


PAGE 207
 Double Buffer
By using the double buffering technique you will dramatically improve the OpenGL 
animation quality. Be warned, though: Implementing double buffering for OpenGL under 
Windows has a certain complexity, requiring a number of steps. Brace yourself; here they 
are.
First, set the DoubleBuffer bit to true in the pfdFlags property of OpenGL1. Second, replace 
the call to glFlush with a call to SwapBuffers. You could use the handle to the device context 
(OpenGL1.glDC) by calling that Windows API function, but call the OpenGL1.Swap method 
instead. That’s it! The number of steps is two! The level of complexity for this change is 
insignificant!

procedure TForm1.FormPaint(Sender: TObject);	
begin	
  glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT);	
  Transform1.Apply;	
  Transform2.Apply;	
  Transform3.Apply;	
  {glFlush} OpenGL1.Swap;	
  OpenGL1.ErrorCheck('FormPaint');	
end;	
Save, compile, and run. Now when you drag the objects around, the scene does not flicker. 
In fact, if you drag slowly, the motion is quite smooth. Yes, if you move the mouse more 
rapidly, the object motion may be a little jumpy, but without flicker.
Automatic Motion
Movement with the mouse is fine, but the next advance in OpenGL animation knowledge is 
how to make objects move on their own. Under the Chapter.9 directory, create a new 
directory named Auto. Create a new project and save the files into the new directory. Name 
the unit Auto1.Pas and the project file Auto0.Dpr.
Add the usual components to the form, which will display a cylinder translating in all three 
dimensions and spinning. Listing 9-2 shows the components and their properties:

object Form1: TForm1
  Left = 308
  Top = 100
  Width = 328
  Height = 347


PAGE 208
   Caption = 'Form1'
  Font.Charset = DEFAULT_CHARSET
  Font.Color = clWindowText
  Font.Height = -11
  Font.Name = 'MS Sans Serif'
  Font.Style = []
  OnCreate = FormCreate
  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
    Left = 8
    Top = 4
  end
  object Transform1: TTransform
    TranX = 0.6
    TranY = -0.6
    TranZ = -2.2
    AxisX = 0.2
    AxisY = 1
    Angle = 15
    ScaleX = 1
    ScaleY = 1
    ScaleZ = 1
    OnPaint = Transform1Paint
    Left = 8
    Top = 40
  end
  object Light1: TLight
    Which = 1
    Cutoff = 65
    AmbientRed = 0.2
    AmbientGreen = 0.2


PAGE 209
     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
  end
  object Transform2: TTransform
    TranZ = -3
    AxisX = 0.2
    AxisY = 1
    ScaleX = 1
    ScaleY = 1
    ScaleZ = 1
    OnPaint = Transform2Paint
    Left = 8
    Top = 76
  end
  object Material1: TMaterial
    ambRed = 1
    ambAlpha = 1
    difRed = 1
    difAlpha = 1
    aFace = faFront
    dFace = faFront
    sFace = faFront
    mShine = 128
    Left = 44
    Top = 76
  end
  object Material2: TMaterial
    ambBlue = 1
    ambAlpha = 1
    difBlue = 1
    difAlpha = 1
    aFace = faBack
    dFace = faBack
    sFace = faBack
    mShine = 128
    Left = 80
    Top = 76


PAGE 210
   end
end
Listing 9-2. Auto project properties.
Private
In the private section of the form type declaration, make three floating-point fields for the 
change in translation amount and three for the sign of the directions. Declare a procedure 
that will calculate the new positions:

  private	
    signX,	
    signY,	
    signZ,	
    deltaX,	
    deltaY,	
    deltaZ: GLfloat;	
    procedure Idle(Sender:TObject;var Done:boolean);	
Idle
The procedure named Idle is a special event handler for the TApplication object. The OnIdle 
event fires when the application has no user interaction to process. Unlike the form, shown 
in Figure 9-2 while the application runs, the TApplication object has no visual 
representation, no property page in the Object Inspector. Because of that fact, you must set 
the event handler in the FormCreate method, as well as initializing the amounts by which 
the cylinder will move.



PAGE 211
 procedure TForm1.FormCreate(Sender: TObject);	
begin	
  deltaX := 0.005;	
  deltaY := 0.006;	
  deltaZ := 0.007;	
  signX := 1.0;	
  signY := 1.0;	
  signZ := -1.0;	
  Application.OnIdle := Idle;	
  CylinderBase;	
  glLightModeli(GL_LIGHT_MODEL_TWO_SIDE,GL_TRUE);	
end;	
The Idle method itself is the heart of the animation. The Boolean var parameter arrives with 
a true value. If that value remains unchanged, the Idle method will only fire once until the 
next time user interaction occurs and ceases. To make sure the Idle method fires rapidly over 
and over again, always set Done to false, just before calling FormPaint to cause a redraw:

procedure TForm1.Idle(Sender:TObject;var Done:boolean);	
var	
  ratio: GLfloat;	
begin	
  with OpenGL1,Transform2 do	
  begin	
    if Angle<359.5 then	
      Angle := Angle+0.5	
    else	
      Angle := 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	


PAGE 212
         signX := -1.0;	
    TranX := TranX+deltaX*signX;	
     	
  end; {with}	
  Done := false;	
  FormPaint(nil);	
end;	
The local variable ratio helps keep track of the boundaries at various depths in the 
perspective frustum. The statements in the Idle method cause the cylinder to rotate an 
additional half degree each pass as well as translate by different amounts in each of the three 
dimensions. Sign assignment makes sure the cylinder “bounces” back into view when it 
penetrates the edges of the clipping region.
Methods
The rest of the methods are what you would expect. Listing 9-3 shows them:

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);	
  Transform1.Apply;	
  Transform2.Apply;	
  {glFlush}OpenGL1.Swap;	
  OpenGL1.ErrorCheck('FormPaint');	
end;	
	
procedure TForm1.Transform1Paint(Sender: TObject);	
begin	
  Light1.Apply;	
end;	
	
procedure TForm1.Transform2Paint(Sender: TObject);	
begin	
  Material1.Apply;	
  Material2.Apply;	
  Cylinder;	
end;	
Listing 9-3. Auto project methods.

PAGE 213
 Save, compile, and run. Relax and watch a cylinder slowly spin and bounce around the 
viewing volume. The smooth motion is a joy to behold.
Frame Rates
How fast is the animation? The number of images drawn per second is called the frame rate. 
There is a minimum threshold required to fool the eye into seeing smooth motion. 
Obviously, calculating the frame rate is easy, but how do you display it? OpenGL does not 
provide a simple way to display text; it requires you to render textual shapes just like any 
other graphical objects.
For now, just display another window instead. Just add a form to the project and make sure 
it does not overlap the first form. Of course, the left and top positions in the listings are just 
values that were convenient for the author. You can set them to any values you find useful. 
To keep the second form very simple the form’s caption will be the place to display the 
frame rate. Make the form 32 pixels high and 164 pixels wide. Save the second form’s unit as 
Count.Pas.
Private
To the uses clause of the first unit, add MMSystem and Count. Add some private fields to 
the form type declaration. Startms will contain the starting time, lCounter will keep the 
number of frames, and bCounter will serve as a reminder when to recalculate the frame rate:

  private	
    signX,	
    signY,	
    signZ,	
    deltaX,	
    deltaY,	
    deltaZ: GLfloat;	
    Startms,	
    ms256,	
    Counter256,	
    lCounter: integer;	
    bCounter: byte;	
    procedure Idle(Sender:TObject;var Done:boolean);	
Initialize
At the bottom of the FormCreate method, initialize the start time with the number of 
milliseconds since Windows started. This call is the reason for the inclusion of the 
MMSystem unit.


PAGE 214
   Startms := timeGetTime;	
end;	
Idle
Each time the Idle method fires, increment the counters at the top of the method. If the byte-
sized bCounter rolls over to 0, calculate the frame rate near the bottom of the method and 
display it in the caption of the second form. The bCounter variable will roll over and cause a 
calculation every 256 frames. Since difms is elapsed time in milliseconds, multiply lCounter 
by 1000 so the result will be frames per second.

var	
  difms: integer;	
  ratio: GLfloat;	
begin	
  inc(lCounter);	
  inc(bCounter);	
  difms := TimeGetTime-Startms;	
  with OpenGL1,Transform2 do	
  begin	
    if bCounter=0 then	
    begin	
      Form2.Caption := Format('%d %d',[lCounter*1000 div difms,	
        (lCounter-Counter256)*1000 div (difms-ms256)]);	
      Counter256 := lCounter;	
      ms256 := difms;	
      Form2.Show;	
    end;	
Action
Save, compile, and run. It will look just like the previous run, but after a few seconds you 
will see a second little form off to the side with a pair of numbers for a caption. The first 
number is the average frame rate for the entire run. The second number is the frame rate for 
the last 256 frames. The rate averaged about 86 frames per second on a 300 MHz AMD K6-2 
system running Windows 98, using only software rendering. That is a very nice rate indeed. 
Of course, it will be slower with larger windows or more complex scenes.
Independent Speed
Your mileage may vary. Different machines may produce different results, but you may 
want the objects in your animations to move at the same speed regardless of the frame rate. 
Let us say that you want the cylinder to traverse the z dimension in eight seconds, the y 
dimension in two and a 

PAGE 215
 half seconds, and the x dimension in three seconds, regardless of processor speed and other 
factors. One technique for achieving this is to adjust the amount of travel per frame by the 
frame rate. Travel less per frame if there are more frames per second to keep the rate of 
travel fairly constant. The most accurate results come from calculating the rates every pass, 
but this example takes a simpler approach, using the existing calculations.
Initialize
Set bCounter in FormCreate so that it will roll over very soon after start:

  Startms := timeGetTime;	
  bCounter := 240;	
end;	
Idle
In the Idle method, recalculate the size of the deltaZ, deltaY, and deltaX fields before 
calculating signZ, signY, signX. Multiply by the elapsed time and the distance to travel in a 
certain time. Divide by the specified time in milliseconds and the elapsed number of frames.

    deltaZ := difms*(prxFar-prxNear)/(lCounter*8000.0);	
    if (-TranZ<prxNear) then	
      signZ := -1.0	
    else	
      if (-TranZ>prxFar) then	
        signZ := 1.0;	
    TranZ := TranZ+deltaZ*signZ;	
	
    deltaY := difms*(prxTop-prxBottom)/(lCounter*2500.0);	
    if (TranY<prxBottom*ratio) then	
      signY := 1.0	
    else	
      if (TranY>prxTop*ratio) then	
        signY := -1.0;	
    TranY := TranY+deltaY*signY;	
	
    deltaX := difms*(prxRight-prxLeft)/(lCounter*3000);	
    if (TranX<prxLeft*ratio) then	
      signX := 1.0	
    else	
      if (TranX>prxRight*ratio) then	
        signX := -1.0;	
    TranX := TranX+deltaX*signX;	

PAGE 216
 Action
Save, compile, and run. This time will not seem much different from previous runs, but the 
speed should be about the same regardless of your machine’s characteristics. Of course, the x 
and y motions will seem slower as the cylinder moves farther down the negative z-axis 
because of perspective adjustment. You will probably see the short-term frame rate decrease 
when the cylinder is closer, because there is more to draw, and increase when the cylinder is 
far away and appears smaller.
Identifier Reference
Identifier			Description
SwapBuffers
A Windows API call that swaps the front and back rendering buffers. It takes a handle to a 
device context as its parameter.
Summary
This chapter demonstrated some important aspects of OpenGL animation:
? How to use double buffering to avoid flickering by setting the double buffer bit and 
calling SwapBuffers instead of glFlush.
? How to select and drag objects with a mouse.
? How to move objects automatically with an algorithm.
? How to set movement speeds independently of processor speed or other system 
characteristics.


PAGE 217
 

PAGE 218

Chapter 10
Effects
Well, have you been wondering about things mentioned in passing and put off until later, 
such as the alpha parameter of material properties and the w parameter of glVertex4f? Good! 
Later has arrived.
Transparency
The alpha parameter pertains to a concept called blending. Imagine two surfaces, one behind 
the other. Leave the material alpha properties as the usual 1.0 for the farther surface. Set the 
alpha properties for the nearer surface to some fraction, such as 0.3. Typically for such a 
value the alpha blending will result in a color made 30 percent from the source (front) 
surface and 70 percent from the destination (rear) surface. Naturally, an alpha value of 1.0 
(the usual) means the color comes 100 percent from the source.
The Project
Create a new directory under OpenGL and name it Chapter.10. Create another directory 
under Chapter.10 and name it Alpha. Create a new project and save it as Alpha0.Dpr in the 
Alpha directory, naming the unit Alpha1.Pas on the way.
Units
Add the Shapes unit to the project and be sure to add OpenGL and Shapes to the interface 
uses clause of the main unit. From now on you can take for granted that the OpenGL unit 
must be included in the uses clause of any unit that makes OpenGL API calls. Again, always 
be sure to include Shapes in the project and in the uses clause of any unit that calls its 
routines. This is the last mention of such conditions.

PAGE 219
 Within the Shapes unit you need to center the cylinder about the origin for easier placement. 
Inside the Cylinder procedure, replace the 0.0 z values with –0.4, and replace the 1.0 z values 
with +0.4.
Components
Drop on a TOpenGL component, three TTransform components, a TLight component, and 
four TMaterial components, as shown in Figure 10-1.
Properties
Listing 10-1, which is the form as text, shows the property settings for the form and its 
components:

object Form1: TForm1
  Left = 309
  Top = 104
  Width = 328
  Height = 347
  Caption = ‘Form1’
  Font.Charset = DEFAULT_CHARSET
  Font.Color = clWindowText
  Font.Height = -11
  Font.Name = ‘MS Sans Serif’
  Font.Style = []
  OnCreate = FormCreate
  OnPaint = FormPaint
  OnResize = FormResize
  PixelsPerInch = 96
  TextHeight = 13


PAGE 220
   object OpenGL1: TOpenGL
    pfdFlags = [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
    Left = 4
    Top = 4
  end
  object Transform1: TTransform
    TranY = 1
    TranZ = -3
    AxisX = 1
    Angle = -90
    ScaleX = 0.4
    ScaleY = 0.4
    ScaleZ = 0.4
    OnPaint = Transform1Paint
    Left = 4
    Top = 36
  end
  object Light1: TLight
    Which = 1
    Cutoff = 65
    AmbientRed = 0.2
    AmbientGreen = 0.2
    AmbientBlue = 0.2
    DiffuseRed = 1
    DiffuseGreen = 1
    DiffuseBlue = 1
    Direction = True
    Position = True
    Spotlight = True
    Enabled = True
    Left = 36
    Top = 36
  end
  object Material1: TMaterial
    ambRed = 0.5


PAGE 221
     ambGreen = 0.5
    ambBlue = 0.5
    ambAlpha = 1
    difRed = 0.5
    difGreen = 0.5
    difBlue = 0.5
    difAlpha = 1
    aFace = faFront
    dFace = faFront
    sFace = faFront
    Left = 68
    Top = 36
  end
  object Transform2: TTransform
    ScaleX = 1
    ScaleY = 1
    ScaleZ = 1
    OnPaint = Transform2Paint
    Left = 4
    Top = 68
  end
  object Material2: TMaterial
    ambRed = 0.8
    ambGreen = 0.6
    ambBlue = 0.4
    ambAlpha = 1
    difRed = 0.8
    difGreen = 0.6
    difBlue = 0.4
    difAlpha = 1
    aFace = faFront
    dFace = faFront
    sFace = faFront
    Left = 36
    Top = 68
  end
  object Transform3: TTransform
    TranX = 0.6
    TranY = -0.6
    TranZ = -3
    AxisX = 1
    Angle = 90
    ScaleX = 1
    ScaleY = 1
    ScaleZ = 1
    OnPaint = Transform3Paint
    Left = 4
    Top = 100
  end


PAGE 222
   object Material3: TMaterial
    ambRed = 1
    ambGreen = 1
    ambBlue = 1
    ambAlpha = 0.5
    difRed = 1
    difGreen = 1
    difBlue = 1
    difAlpha = 0.5
    aFace = faFront
    dFace = faFront
    sFace = faFront
    mShine = 128
    Left = 36
    Top = 100
  end
  object Material4: TMaterial
    ambRed = 1
    ambGreen = 1
    ambBlue = 1
    ambAlpha = 0.2
    difRed = 1
    difGreen = 1
    difBlue = 1
    difAlpha = 0.2
    aFace = faBack
    dFace = faBack
    sFace = faBack
    mShine = 128
    Left = 68
    Top = 100
  end
end
Listing 10-1. Alpha project properties.
Methods
This project needs FormCreate, FormResize, FormPaint, Transform1Paint, Transform2Paint, 
and Transform3Paint methods.
The FormCreate method prepares for drawing circular objects and sets up the two-sided 
light model:

procedure TForm1.FormCreate(Sender: TObject);	
begin	
  CylinderBase;	
  glLightModeli(GL_LIGHT_MODEL_TWO_SIDE,GL_TRUE);	
end;	

PAGE 223
 The FormResize method has the usual contents:

procedure TForm1.FormResize(Sender: TObject);	
begin	
  if OpenGL1.Smaller then	
    FormPaint(Sender);	
end;	
The FormPaint method is also typical: clearing, applying, flushing, and checking for errors:

procedure TForm1.FormPaint(Sender: TObject);	
begin	
  glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT);	
  Transform1.Apply;	
  Transform2.Apply;	
  Transform3.Apply;	
  glFlush;	
  OpenGL1.ErrorCheck(‘FormPaint’);	
end;	
The Transform1Paint method applies the lighting and material property, then calls the Cube 
method. Transform1 positions the light near the top of the viewing volume and points it 
down. The cube serves to show the light’s position and aim. The TMaterial component gives 
the cube a gray color.

procedure TForm1.Transform1Paint(Sender: TObject);	
begin	
  Light1.Apply;	
  Material1.Apply;	
  Cube;	
end;	
The Transform2Paint method draws a square floor for the scene. The Material2 component 
gives it a wood-like color.

procedure TForm1.Transform2Paint(Sender: TObject);	
begin	
  Material2.Apply;	
  glBegin(GL_POLYGON);	
    glVertex3f(-1.0,-1.0,-2.0);	
    glVertex3f(+1.0,-1.0,-2.0);	
    glVertex3f(+1.0,-1.0,-4.0);	
    glVertex3f(-1.0,-1.0,-4.0);	
  glEnd;	
end;	
The Transform3 component and its Transform3Paint method place a cylinder on the floor 
and color it white inside and out. The TMaterial 

PAGE 224
 components referenced here are the ones that do the magic with alpha blending.

procedure TForm1.Transform3Paint(Sender: TObject);	
begin	
  Material3.Apply;	
  Material4.Apply;	
  Cylinder;	
end;	
Action
Save, compile, and run. Figure 10-2 shows the result:
Enable
So, why is there no difference? If you guessed that something needs enabling, give yourself a 
gold star. Here are some OpenGL constant declarations:

  GL_BLEND               = $BE2;	
  GL_SRC_ALPHA           = $302;	
  GL_ONE_MINUS_SRC_ALPHA = $303;	
  GL_DST_ALPHA           = $304;	
  GL_ONE_MINUS_DST_ALPHA = $305;	
GL_BLEND is the constant to pass to glEnable. By this means you enable alpha blending. 
Are you surprised? You should pass the next two constants to a procedure that specifies how 
to perform the blending. GL_SRC_ALPHA as the first parameter instructs OpenGL to use 
the source alpha value as 

PAGE 225
 the multiplier to determine the source contribution to the result color. 
GL_ONE_MINUS_SRC_ALPHA as the second parameter tells OpenGL to use one minus the 
source alpha value as the multiplier to determine the destination contribution to the result 
color. The last two constants, GL_DST_ALPHA and GL_ONE_MINUS_DST_ ALPHA, are 
not supported by the Microsoft OpenGL implementations, but you will probably not have 
occasion to shed tears over that fact. Here is the OpenGL procedure declaration:

procedure glBlendFunc(sfactor,dfactor:GLenum); stdcall;	
Glass
Add code to the FormCreate method to enable alpha blending and specify how to do it:

procedure TForm1.FormCreate(Sender: TObject);	
begin	
  CylinderBase;	
  glLightModeli(GL_LIGHT_MODEL_TWO_SIDE,GL_TRUE);	
  glEnable(GL_BLEND);	
  glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA);	
end;	
Save, compile, and run. Figure 10-3 shows the very different result. It illustrates one of the 
primary uses of alpha blending: transparency. The cylinder looks like it is made of glass.


PAGE 226
 Fight the Jaggies
Something you may have noticed in some scenes is that some supposedly straight lines or 
straight sides look more like zigzags. The reason, of course, is that pixels on the screen have 
specific grid locations as in Figure 10-4. This is not a problem for horizontal and vertical 
lines, but lines (or edges) on a slant must affect pixels that come closest to approximating the 
mathematically calculated positions.
Antialiasing
When a line has a jagged appearance because of the necessity of approximating the pixel 
positions, the line is said to be aliased. Making such lines and edges appear smoother is 
certainly a desirable goal, and fortunately there are ways to fight the jaggies in OpenGL. 
Accomplishing this task is called antialiasing, and OpenGL utilizes alpha blending for it.
Draw the Line
To illustrate the technique, set your screen resolution to 640x480 if you are currently using a 
higher resolution. The coarser display serves to emphasize the alias problem and makes the 
correction more evident. Under Chapter.10 create a directory called Line. Create a new 
project in the new directory, saving its unit as Alias1.Pas and the project file as Anti.Dpr.
Drop on a TOpenGL component and a TTransform component. Create event handlers and 
set properties for the form and the components on it according to Listing 10-2:

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


PAGE 227
   Font.Style = []
  OnCreate = FormCreate
  OnPaint = FormPaint
  OnResize = FormResize
  PixelsPerInch = 96
  TextHeight = 13
  object OpenGL1: TOpenGL
    pfdFlags = [Draw_To_Window, Support_OpenGL]
    PixelType = RGBA
    Projection = Perspective
    Enable = []
    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
    Left = 4
    Top = 4
  end
  object Transform1: TTransform
    ScaleX = 1
    ScaleY = 1
    ScaleZ = 1
    OnPaint = Transform1Paint
    Left = 4
    Top = 40
  end
end
Listing 10-2. Aliased lines project properties.
The FormCreate method sets the line width and the drawing color. This very simple project 
does not have lighting or material properties. The remaining lines in the method set up 
antialiasing for lines, but are commented out in order to demonstrate the problem before 
demonstrating the solution.

procedure TForm1.FormCreate(Sender: TObject);	
begin	
  glLineWidth(2.0);	
  glColor3f(0.0,0.0,0.0);	
  {	
  glEnable(GL_LINE_SMOOTH);	
  glEnable(GL_BLEND);	


PAGE 228
   glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA);	
  }	
end;	
FormPaint is just what you would expect and FormResize is the same as always:

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);	
  Transform1.Apply;	
  glFlush;	
  OpenGL1.ErrorCheck('FormPaint');	
end;	
The Transform1Paint method draws a nearly vertical line:

procedure TForm1.Transform1Paint(Sender: TObject);	
begin	
  glBegin(GL_LINES);	
    glVertex3f(0.0,1.0,-2.2);	
    glVertex3f(0.2,0.0,-2.2);	
  glEnd;	
end;	
Save, compile, run, and behold a line with a bad case of the jaggies. You have probably seen 
many other such lines on computer screens. Perhaps you stopped noticing long ago, just 
accepting it as a necessary part of computer graphics.
Smooth the Jagged Line
Now, remove the curly braces from the FormCreate method so Delphi can compile the lines 
of code between them. Those lines enable line smoothing and alpha blending, which work 
together, and specify the appropriate method of blending. Here is the new constant you 
need and its relatives:

  GL_POINT_SMOOTH   = $B10;	
  GL_LINE_SMOOTH    = $B20;	
  GL_POLYGON_SMOOTH = $B41;	
Save, compile, and run again. You should see an amazing difference.

PAGE 229
 How Did it Do That?
OpenGL antialiasing fools the eye into seeing a smooth line by placing blended color pixels 
at the jagged places so that the eye sees a more gradual transition from one line segment to 
the next. Basically, the color of a pixel depends on how much of the pixel would be covered 
if the line (of the given width) could color a partial pixel. OpenGL accomplishes this by 
setting appropriate alpha values. Figure 10-5 shows a simplified magnification of the pixels.
Antialiasing Polygons
The edges of polygons have the same aliasing problem that lines possess and need a similar 
solution. To illustrate, create a directory named Edge under Chapter.10, then create a project 
in it named Poly.Dpr. Save its unit as Edge1.Pas. Add components, create methods, and set 
properties just like those in the Anti project. Make the contents of the FormCreate method 
slightly different:

procedure TForm1.FormCreate(Sender: TObject);	
begin	
  glColor3f(0.0,0.0,0.0);	
  {	
  glEnable(GL_POLYGON_SMOOTH);	
  glEnable(GL_BLEND);	
  glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA);	
  }	
end;	
In Transform1Paint, draw a triangle instead of a line:

procedure TForm1.Transform1Paint(Sender: TObject);	
begin	
  glBegin(GL_TRIANGLES);	
    glVertex3f(+0.0,1.0,-2.2);	
    glVertex3f(-0.2,0.0,-2.2);	
    glVertex3f(+0.2,0.0,-2.2);	
  glEnd;	
end;	
Save, compile, and run. You will see a skinny black triangle with noticeably aliased edges. 
Now remove the curly braces and do it again. This time you should see a significantly 
smoother triangle, although the difference may not be as dramatic as the difference in lines. 
Also note that some 

PAGE 230
 implementations of OpenGL for certain video cards may not show any improvement at all.
Points Also
As you can see from the “smooth” constants, you can also apply antialiasing to points. Of 
course that is only meaningful for “points” larger than one pixel. The technique for 
antialiasing points, if you happen to need it, should be evident from the examples for lines 
and polygons.
Scaling
You have been using glVertex3f to give three-dimensional coordinates. That should be 
enough, right? Well, there is such a thing as glVertex4f. You may remember from Chapter 2 
that the parameters for glVertex4f are x, y, z, and w. The fourth coordinate is not some 
mysterious fourth dimension, but a divider. If you do not specify it (as with glVertex3f), it 
defaults to one.
Homogeneous Coordinates
Even if you only specify two or three dimensions, OpenGL represents coordinates as four 
numbers, with defaults for the unspecified values. Coordinates become a 1x4 array to which 
OpenGL can apply a matrix. OpenGL uses homogeneous coordinates wherein (x,y,z,w) 
represent the three-dimensional point in space (x/w,y/w,z/w). If you multiply the 
coordinates by factor m, the resulting coordinates (mx,my,mz,mw) produce the same point 
in space. The point (mx/mw,my/mw,mz/mw) is exactly the same as the point 
(x/w,y/w,z/w). This ability to multiply homogeneous coordinates without changing the 
point they represent makes them useful for a matrix-based graphics system such as OpenGL.
Opportunity
OpenGL’s use of homogeneous coordinates provides an interesting opportunity for scaling. 
If you use glVertex4f and use a variable for the fourth parameter, you can change the size of 
an object just by changing the value of a single variable. This is certainly not the standard 
way to scale a part of a scene, but it does serve to illustrate what the w parameter does.
Blow up a Pyramid
Create a new directory under Chapter.10 and call it W. Create a new Delphi project and save 
its files into the W directory. Name the unit W1.Pas and 

PAGE 231
 the project file W0.Dpr. Drop some of the usual components on the form, as shown in Figure 
10-6. Listing 10-3 shows the property values.

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


PAGE 232
     prxRight = 1
    prxBottom = -1
    prxTop = 1
    prxNear = 2
    prxFar = 9
    ColorBits = 24
    DepthBits = 32
    Left = 4
    Top = 4
  end
  object Transform1: TTransform
    TranX = 0.6
    TranY = -0.6
    TranZ = -2.2
    AxisX = 0.2
    AxisY = 1
    Angle = 15
    ScaleX = 1
    ScaleY = 1
    ScaleZ = 1
    OnPaint = Transform1Paint
    Left = 4
    Top = 36
  end
  object Light1: TLight
    Which = 1
    Cutoff = 65
    AmbientRed = 0.2
    AmbientGreen = 0.2
    AmbientBlue = 0.2
    DiffuseRed = 1
    DiffuseGreen = 1
    DiffuseBlue = 1
    Direction = True
    Position = True
    Spotlight = True
    Enabled = True
    Left = 40
    Top = 36
  end
  object Transform2: TTransform
    TranZ = -3
    ScaleX = 1
    ScaleY = 1
    ScaleZ = 1
    OnPaint = Transform2Paint
    Left = 4
    Top = 72
  end


PAGE 233
   object Material1: TMaterial
    ambRed = 1
    ambAlpha = 1
    difRed = 1
    difAlpha = 1
    aFace = faFront
    dFace = faFront
    sFace = faFront
    Left = 40
    Top = 72
  end
end
Listing 10-3. Property settings for the W pyramid.
In the private section of the form type declaration, provide a private field to be the fourth 
parameter in the homogeneous coordinates:

  private	
    hom: GLfloat;	
  public	
In the FormCreate method, initialize hom and set the front face winding direction clockwise 
since this program draws a pyramid from a triangle fan:

procedure TForm1.FormCreate(Sender: TObject);	
begin	
  hom := 1.0;	
  glFrontFace(GL_CW);	
end;	
The FormResize, FormPaint, and Transform1Paint methods are what you would expect:

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);	
  Transform1.Apply;	
  Transform2.Apply;	
  glFlush;	
  OpenGL1.ErrorCheck('FormPaint');	
end;	
	
procedure TForm1.Transform1Paint(Sender: TObject);	


PAGE 234
 begin	
  Light1.Apply;	
end;	
In the Transform2Paint method, draw the pyramid, using hom as the fourth parameter in 
gVertex4f. Note that the properties of the TTransform2 and Material1 components will result 
in the display of a red triangle.

procedure TForm1.Transform2Paint(Sender: TObject);	
begin	
  Material1.Apply;	
  glBegin(GL_TRIANGLE_FAN);	
    glVertex4f(+0.0,+0.0,+0.0,hom);	
    glVertex4f(-0.3,+0.3,-0.4,hom);	
    glVertex4f(+0.3,+0.3,-0.4,hom);	
    glVertex4f(+0.0,-0.3,-0.4,hom);	
    glVertex4f(-0.3,+0.3,-0.4,hom);	
  glEnd;	
end;	
In the FormKeyPress method, use a case statement to assign values to the hom field. Since 
the KeyPreview property of the form is true, your key presses will pass through this event 
handler. Lowercase “a” through “n” will affect the size of the pyramid. Pressing “n” will 
reduce the pyramid size by 40 percent, and “a” will blow it up to ten times its normal size.

procedure TForm1.FormKeyPress(Sender: TObject; var Key: Char);	
begin	
  case Key of	
    'a': hom := 0.1;	
    'b': hom := 0.2;	
    'c': hom := 0.3;	
    'd': hom := 0.4;	
    'e': hom := 0.5;	
    'f': hom := 0.6;	
    'g': hom := 0.7;	
    'h': hom := 0.8;	
    'i': hom := 0.9;	
    'j': hom := 1.0;	
    'k': hom := 1.1;	
    'l': hom := 1.2;	
    'm': hom := 1.3;	
    'n': hom := 1.4;	
    else Key := #0;	
  end; {case}	
  if Key<>#0 then	
    Invalidate;	
end;	
Save, compile, and run. Have fun.

PAGE 235
 Fog
When you look at scenes on Earth, you may have noticed that not only do objects appear 
smaller in the distance (perspective), but they also become less distinct. The OpenGL scenes 
you have rendered up to this point in the book retain perfect clarity as they retreat into the 
distance, but that is not the natural appearance unless you are floating in outer space. 
OpenGL provides fog effects to bring more realism to its scenes. In OpenGL fog is a general 
term for atmospheric effects; OpenGL supports three kinds, embodied in three OpenGL fog 
mode constants.
Declarations
Even on a clear day, distant objects are less distinct than nearby objects when viewed 
through an atmosphere. We have all learned to use haziness as a clue to an object’s distance, 
in addition to the apparent size of an object. Here is the OpenGL fog mode constant used to 
command this effect:

  GL_LINEAR = $2601;	
For heavy fog or clouds, use:

  GL_EXP = $800;	
For smoke and weather haze, use:

  GL_EXP2 = $801;	
To tell OpenGL you are setting the fog mode, you need this constant:

  GL_FOG_MODE = $B65;	
As you probably guessed, you must enable fog effects; you need a constant to pass to 
glEnable:

  GL_FOG = $B60;	
Obviously, since there is more to fog than just turning it on and off, there must be a set of 
glFog commands:

procedure glFogf(pname: GLenum; param: GLfloat); stdcall;	
procedure glFogfv(pname: GLenum; params: PGLfloat); stdcall;	
procedure glFogi(pname: GLenum; param: GLint); stdcall;	
procedure glFogiv(pname: GLenum; params: PGLint); stdcall;	
Here are other fog declarations, which will be explained in an example program. The 
exception is GL_FOG_INDEX, which is used with color index mode (corresponding to a 
Windows 256-color palette mode), and is listed here only for completeness:

PAGE 236
 
  GL_FOG_INDEX = $B61;	
  GL_FOG_DENSITY = $B62;	
  GL_FOG_START = $B63;	
  GL_FOG_END = $B64;	
  GL_FOG_COLOR = $B66;	
Project
Create a new directory under Chapter.10 and name it Fog. In that directory create a project 
named Foggy.Dpr with a main unit named Fog1.Pas. Place a TOpenGL component, five 
TTransform components, a TLight component, and four TMaterial components on the form. 
Create event handlers and set the properties according to Listing 10-4. Note the 
DoubleBuffer flag and the KeyPreview property.

object Form1: TForm1
  Left = 204
  Top = 99
  Width = 328
  Height = 347
  Caption = 'Form1'
  Font.Charset = DEFAULT_CHARSET
  Font.Color = clWindowText
  Font.Height = -11
  Font.Name = 'MS Sans Serif'
  Font.Style = []
  KeyPreview = True
  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


PAGE 237
     DepthBits = 32
    Left = 4
    Top = 4
  end
  object Transform1: TTransform
    TranX = 0.6
    TranY = -0.6
    TranZ = -2.2
    AxisX = 0.2
    AxisY = 1
    Angle = 15
    ScaleX = 1
    ScaleY = 1
    ScaleZ = 1
    OnPaint = Transform1Paint
    Left = 4
    Top = 36
  end
  object Light1: TLight
    Which = 1
    Cutoff = 65
    AmbientRed = 0.4
    AmbientGreen = 0.4
    AmbientBlue = 0.4
    DiffuseRed = 1
    DiffuseGreen = 1
    DiffuseBlue = 1
    Direction = True
    Position = True
    Spotlight = True
    Enabled = True
    Left = 40
    Top = 36
  end
  object Transform2: TTransform
    TranX = 1
    TranZ = -3.0
    AxisX = 1
    Angle = 75
    ScaleX = 1
    ScaleY = 1
    ScaleZ = 1
    OnPaint = Transform2Paint
    Left = 4
    Top = 72
  end
  object Material1: TMaterial
    ambRed = 1
    ambAlpha = 1


PAGE 238
     difRed = 1
    difAlpha = 1
    aFace = faFrontAndBack
    dFace = faFrontAndBack
    sFace = faFrontAndBack
    Left = 40
    Top = 72
  end
  object Transform3: TTransform
    TranY = 0.6
    TranZ = -3.6
    ScaleX = 1
    ScaleY = 1
    ScaleZ = 1
    OnPaint = Transform3Paint
    Left = 4
    Top = 108
  end
  object Material2: TMaterial
    ambBlue = 1
    ambAlpha = 1
    difBlue = 1
    difAlpha = 1
    aFace = faFrontAndBack
    dFace = faFrontAndBack
    sFace = faFrontAndBack
    Left = 40
    Top = 108
  end
  object Transform4: TTransform
    TranX = -1.3
    TranZ = -4
    AxisX = 1
    AxisY = 1
    Angle = 65
    ScaleX = 1
    ScaleY = 1
    ScaleZ = 1
    OnPaint = Transform4Paint
    Left = 4
    Top = 144
  end
  object Material3: TMaterial
    ambGreen = 1
    ambAlpha = 1
    difGreen = 1
    difAlpha = 1
    aFace = faFrontAndBack
    dFace = faFrontAndBack


PAGE 239
     sFace = faFrontAndBack
    Left = 40
    Top = 144
  end
  object Transform5: TTransform
    ScaleX = 1
    ScaleY = 1
    ScaleZ = 1
    OnPaint = Transform5Paint
    Left = 4
    Top = 176
  end
  object Material4: TMaterial
    ambRed = 0.8
    ambGreen = 0.6
    ambBlue = 0.4
    ambAlpha = 1
    difRed = 0.8
    difGreen = 0.6
    difBlue = 0.4
    difAlpha = 1
    aFace = faFront
    dFace = faFront
    sFace = faFront
    Left = 40
    Top = 176
  end
end
Listing 10-4. Properties for the Foggy project.
In the form type declaration, set up a couple of private fields that the GL_LINEAR fog mode 
will need:

  private	
    fogStart,	
    fogEnd: GLfloat;	
  public	
In the FormCreate method, prepare for drawing round things and set up conditions for a 
fog. You need the FogColor variable to pass to a glFog command to set the (you guessed it) 
fog color. OpenGL fog works by mixing the fog color with the color of each part of a scene. 
Although this is still another kind of blending, fog effects do not require enabling 
GL_BLEND because fog has its own special constant. In this event handler, enable fog, set 
up one of the fog modes, establish the fog color, and specify the density of the fog.

procedure TForm1.FormCreate(Sender: TObject);	
var	


PAGE 240
   FogColor: packed record	
    red,	
    green,	
    blue,	
    alpha: GLfloat;	
  end;	
begin	
  CylinderBase;	
  glEnable(GL_FOG);	
  glFogi(GL_FOG_MODE,GL_EXP2);	
  with FogColor do	
  begin	
    red := 0.5;	
    green := 0.5;	
    blue := 0.5;	
    alpha := 1.0;	
  end;	
  glFogfv(GL_FOG_COLOR,@FogColor);	
  glFogf(GL_FOG_DENSITY,0.3);	
end;	
The FormResize and FormPaint methods are about what you would expect. Note the use of 
Swap instead of glFlush. This example needs non-flickering transitions from one fog state to 
another so you can detect some of the more subtle changes.

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);	
  Transform1.Apply;	
  Transform2.Apply;	
  Transform3.Apply;	
  Transform4.Apply;	
  Transform5.Apply;	
  OpenGL1.Swap;	
  OpenGL1.ErrorCheck('FormPaint');	
end;	
The TTransform OnPaint event handlers are also typical. In them you apply lighting and 
draw a red cylinder, a blue cube, a green cone, and a brown rectangular floor at varying 
distances.

procedure TForm1.Transform1Paint(Sender: TObject);	
begin	


PAGE 241
   Light1.Apply;	
end;	
	
procedure TForm1.Transform2Paint(Sender: TObject);	
begin	
  Material1.Apply;	
  Cylinder;	
end;	
	
procedure TForm1.Transform3Paint(Sender: TObject);	
begin	
  Material2.Apply;	
  Cube;	
end;	
	
procedure TForm1.Transform4Paint(Sender: TObject);	
begin	
  Material3.Apply;	
  Cone;	
end;	
	
procedure TForm1.Transform5Paint(Sender: TObject);	
begin	
  Material4.Apply;	
  glBegin(GL_POLYGON);	
    glVertex3f(-1.0,-1.0,-2.0);	
    glVertex3f(+1.0,-1.0,-2.0);	
    glVertex3f(+1.0,-1.0,-7.0);	
    glVertex3f(-1.0,-1.0,-7.0);	
  glEnd;	
end;	
To allow yourself to explore various fog settings by pressing keys, fill in a FormKeyPress 
event handler. A small “a” disables fog, and “b,” “c,” and “d” enable fog with various fog 
modes. The fog mode for “d” is GL_LINEAR, which needs start and end information. Use 
“0” through “9” to set the fog start value from 0.0 to 9.0. Use “e” through “z” to set the fog 
end value from 1.0 to 22.0 greater than the current start value. Note that the start and end 
values have no effect on the other two fog modes.
How thick is the fog? Is it clear, the faintest wisp, or as thick as pea soup? Of course OpenGL 
cannot use those descriptions, but it can take a floating-point number from 0.0 to 1.0. Use 
capital letters “A” through “Z” to set GL_FOG_DENSITY.

procedure TForm1.FormKeyPress(Sender: TObject; var Key: Char);	
var	
  density: GLfloat;	
begin	


PAGE 242
   case Key of	
    '0'..'9': begin	
      fogStart := ord(key)-48;	
      glFogf(GL_FOG_START,fogStart);	
    end;	
    'A'..'Z': begin	
      density := (ord(Key)-65)/25.0;	
      glFogf(GL_FOG_DENSITY,density);	
    end;	
    'a': glDisable(GL_FOG);	
    'b': begin	
      glEnable(GL_FOG);	
      glFogi(GL_FOG_MODE,GL_EXP);	
    end;	
    'c': begin	
      glEnable(GL_FOG);	
      glFogi(GL_FOG_MODE,GL_EXP2);	
    end;	
    'd': begin	
      glEnable(GL_FOG);	
      glFogi(GL_FOG_MODE,GL_LINEAR);	
    end;	
    'e'..'z': begin	
      fogEnd := ord(Key)-ord('d')+fogStart;	
      glFogf(GL_FOG_END,fogEnd);	
    end;	
  end; {case}	
  FormPaint(nil);	
end;	
Save, compile, run, and enjoy.
Identifier Reference
Identifier			Description
glBlendFunc
OpenGL command that specifies rules for blending.
GL_BLEND
OpenGL constant to pass to glEnable for enabling 
blending.
GL_DST_ALPHA
OpenGL constant for specifying rules for blending.
GL_EXP
OpenGL constant that tells OpenGL to use an 
exponential fog equation.
GL_EXP2
OpenGL constant that tells OpenGL to use a higher 
order exponential fog equation.

PAGE 243
 
Identifier			Description
glFog
An OpenGL command to set parameters for fog.
GL_FOG
An OpenGL constant to pass to glEnable or glDisable to enable or disable atmospheric 
effects.
GL_FOG_COLOR
OpenGL constant that specifies RGBA fog color.
GL_FOG_DENSITY
An OpenGL constant that tells OpenGL what density to use with the exponential equations.
GL_FOG_END
An OpenGL constant that tells OpenGL the far distance in the linear fog equation.
GL_FOG_INDEX
An OpenGL constant that tells OpenGL the palette entry to use for fog color in color index 
mode.
GL_FOG_MODE
An OpenGL constant that introduces a parameter telling OpenGL what fog equation to use.
GL_FOG_START
An OpenGL constant that tells OpenGL the near distance in the linear fog equation.
GL_LINE_SMOOTH
The OpenGL constant to pass to glEnable for antialiasing lines.
GL_LINEAR
OpenGL constant for use of linear fog equation.
GL_ONE_MINUS_
DST_ALPHA
An OpenGL constant for specifying rules for blending.
GL_ONE_MINUS_
SRC_ALPHA
An OpenGL constant for specifying rules for blending.
GL_POINT_SMOOTH
The OpenGL constant to pass to glEnable for antialiasing points.
GL_POLYGON_
SMOOTH
The OpenGL constant to pass to glEnable for antialiasing polygons.
GL_SRC_ALPHA
OpenGL constant for specifying rules for blending.
Summary
In this chapter you learned the meaning of the mysterious fourth coordinate in 
homogeneous coordinate systems and how to use it in a scaling effect. You also learned 
some helpful visual effects with blending. In particular, you learned how to smooth out lines 
and polygon edges, how to simulate transparency, and how to simulate atmospheric effects.

PAGE 244

Chapter 11
Textures
With the glMaterial commands embedded in the TMaterial component you have 
considerable power over the appearance of objects in your scenes. Not only do you control 
the basic color of your polygons, but combined with lighting, you can control shading and 
shininess.
Complicated Surfaces
However, many surfaces have more detail than you may want to tackle. For a simple 
example, consider an ordinary brick. A brick is not just a simple rectangular solid; it has a 
bumpy, irregular surface and numerous color variations. You could draw a polygon for each 
variation, with different material properties for each polygon.
The Problem
Thus, a simple brick that might be represented with a few primitives becomes a major task 
for the programmer and quite computationally intensive. Imagine the impossible task of 
creating a scene with bricks and rocks and grass and dirt and trees with leaves. Even if you 
could program it, the computer would probably take a very long time to render each frame. 
Even with massive simplification and approximation, fairly simple scenes would be 
prohibitive. There has to be a better way.
The Solution
Fortunately there is a standard way to represent complicated surfaces more efficiently. You 
can prepare or obtain flat graphics patterns and wrap them around simple graphics objects 
to simulate complicated surfaces. OpenGL knows how to perform transformations 
(translation, rotation, scaling) on the patterns along with the polygons to which they are 
attached. The name for these graphics patterns is textures. The technique of applying such 

PAGE 245
 patterns to your polygons is texture mapping. The subject of textures in OpenGL covers a 
huge territory; this book deals with a reasonable subset only.
Texture Structure
A texture is a rectangular graphic image, and as you might expect, OpenGL treats it as an 
array. 
Layout, Limits, and a Command
OpenGL commands will be quite satisfied receiving a simple linear array to represent a 
texture; in the command identifying a texture you must tell OpenGL the width and height of 
the graphic, since all the command sees is a pointer to a linear region of memory.

procedure glTexImage2D(target:GLenum; level,components:GLint; width,	
height:GLsizei; border:GLint; format,_type:GLenum; pixels:Pointer); stdcall;	
For the target parameter, only one value is permissible:

  GL_TEXTURE_2D = $DE1;	
The level parameter should be 0 for now. You would use a higher number for mipmapped 
textures, which involve multiple images.
The components parameter tells how many color components are in the texture. Use 1, 2, 3, 
or 4, depending on the format parameter.
The width and height parameters tell how many units wide and tall the texture is. You must 
explicitly tell OpenGL this information because it cannot determine the structure from the 
memory region. These values cannot be just anything; they are limited to powers of two. 
Your textures could be, among others, 512x512, 128x128, or 8x8. You are not limited to 
square textures; they could be 256x64, 128x32, 16x8, and many others.
The border parameter specifies the width of the texture’s border. Border issues can become 
quite complex and will not be considered here; use 0.
The format parameter describes the structure of each unit of texture information. These 
values are below:

  GL_COLOR_INDEX = $1900; 	
Each value in the texture is an index into a color palette. Use 1 as the number of color 
components.


PAGE 246
   GL_RED = $1903; 	
Each value in the texture is an intensity of red.

  GL_GREEN = $1904; 	
Each value in the texture is an intensity of green.

  GL_BLUE = $1905; 	
Each value in the texture is an intensity of blue.

  GL_ALPHA = $1906; 	
Each value in the texture is an alpha value.

  GL_RGB = $1907; 	
Each value in the texture is a structure with three color components—red green, and blue. 
Use 3 for the components parameter.

  GL_RGBA = $1908;	
Each value in the texture is a structure with four color components—red green, blue, and 
alpha. Use 4 for the components parameter.

  GL_LUMINANCE = $1909; 	
Each value in the texture is a grayscale intensity.

  GL_LUMINANCE_ALPHA = $190A; 	
Each value in the texture has a grayscale intensity and an alpha value.
The _type parameter specifies the underlying data type in the texture element structure. The 
symbolic constants have obvious meanings:

  GL_BYTE           = $1400;	
  GL_UNSIGNED_BYTE  = $1401;	
  GL_SHORT          = $1402;	
  GL_UNSIGNED_SHORT = $1403;	
  GL_INT            = $1404;	
  GL_UNSIGNED_INT   = $1405;	
  GL_FLOAT          = $1406;	
Here are some corresponding OpenGL data types not covered yet:

  GLbyte   = shortint;	
  GLshort  = smallInt;	
  GLubyte  = byte;	
  GLushort = word;	

PAGE 247
 Finally, the pixels parameter points to the region of memory containing the texture. The 
fundamental piece of information within the array is the texel, or texture element, which 
corresponds to the pixel, or picture element.
Make a Brick
The first example program will use a texture representing a brick. You could use a linear 
array, but the richness of Delphi certainly provides a more meaningful way to represent the 
layout. Define TTexel3 for GL_RGB and TTexel4 for GL_RGBA as follows:

type	
  TTexel3=packed record	
    red,	
    green,	
    blue: GLubyte;	
  end;	
  TTexel4=packed record	
    red,	
    green,	
    blue,	
    alpha: GLubyte;	
  end;	
As the names indicate, the types have three and four color components, respectively, one of 
which will be the appropriate value of the components parameter in the glTexImage2D 
command. In this example program, use 4 in the FormCreate method:

glTexImage2D(GL_TEXTURE_2D,Level,4,wBrick,hBrick,Border,GL_RGBA,	 
GL_UNSIGNED_BYTE,@Brick); 	
The width and height parameters are symbolic constants. Other symbolic constants also 
appear in this code to help identify the parameters in a rather bulky API call:

const	
  wBrick=16;	
  hBrick=8; 	
  Level=0;	
  Border=0;	
  ColorComps=4;	


PAGE 248
 The Brick variable is of type TBrick, which is a two-dimensional array of TTexel4:

type	
  TBrickLine=array[0..wBrick-1] of TTexel4;	
  TBrick=array[0..hBrick-1] of TBrickLine;	
	
var	
  Brick: TBrick;	
Use a MakeBrick procedure to fill in the Brick structure. Randomize initializes the random 
seed and Random adjusts the red value of each texel slightly to give the brick texture a 
mottled appearance. Notice the loop control variables. For dealing with textures the 
convention is to refer to S and T coordinates instead of X and Y coordinates.

procedure MakeBrick;	
var	
  sTex, {x}	
  tTex: {y} integer;	
begin	
  Randomize;	
  for tTex := 0 to hBrick-1 do	
    for sTex := 0 to wBrick-1 do	
    with Brick[tTex][sTex] do	
    begin	
      alpha := 255;	
      if (tTex=hBrick-1) or (sTex=wBrick-1) then	
      begin	
        red := 220;	
        green := 220;	
        blue := 220;	
      end else begin	
        red := 150+Random(60);	
        green := 60;	
        blue := 0;	
      end;	
    end; {for with}	
end;	
Remember that OpenGL coordinates proceed from bottom to top, so as the tTex coordinate 
advances, the procedure builds the brick from the bottom up. The special condition makes 
the top row and right column of texels an off-white to represent mortar. Figure 11-1 shows 
the randomly mottled brick texture.


PAGE 249
 Simple Texture, Simple Surface
Now, with several pages of explanation out of the way, you can start building an example 
program. Create a new directory under the OpenGL directory and name it Chapter.11. 
Under that directory create one called Simple. Create a new project and save its files into the 
Simple directory. Name the main unit Simple1.Pas and the project Simple0.Dpr. Listing 11-1 
shows the components and properties.

object Form1: TForm1
  Left = 311
  Top = 102
  Width = 328
  Height = 347
  Caption = 'Form1'
  Font.Charset = DEFAULT_CHARSET
  Font.Color = clWindowText
  Font.Height = -11
  Font.Name = 'MS Sans Serif'
  Font.Style = []
  OnCreate = FormCreate
  OnPaint = FormPaint
  OnResize = FormResize
  PixelsPerInch = 96
  TextHeight = 13
  object OpenGL1: TOpenGL
    pfdFlags = [Draw_To_Window, Support_OpenGL]
    PixelType = RGBA
    Projection = Perspective
    Enable = [depthTest]


PAGE 250
     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
    Left = 4
    Top = 4
  end
  object Transform1: TTransform
    TranZ = -2.2
    ScaleX = 1
    ScaleY = 1
    ScaleZ = 1
    OnPaint = Transform1Paint
    Left = 4
    Top = 36
  end
end
Listing 11-1. Simple texture project properties.
Set Up Texturing
You need some commands in the FormCreate method to set up texturing. FormCreate also 
calls MakeBrick to create a simple texture. Be sure to place the MakeBrick procedure 
described earlier just before FormCreate.

procedure TForm1.FormCreate(Sender: TObject);	
begin	
  glPixelStorei(GL_UNPACK_ALIGNMENT,1);	
  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); 	
  MakeBrick;	
  glTexImage2D(GL_TEXTURE_2D,Level,ColorComps,wBrick,hBrick,Border,	
    GL_RGBA,GL_UNSIGNED_BYTE,@Brick);	
  glEnable(GL_TEXTURE_2D);	
  glTexEnvf(GL_TEXTURE_ENV,GL_TEXTURE_ENV_MODE,GL_DECAL);	
end;	


PAGE 251
 Here are the pertinent constant declarations:

  GL_UNPACK_ALIGNMENT       = $0CF5;	
  GL_TEXTURE_2D             = $0DE1; 	
  GL_TEXTURE_ENV_MODE       = $2200; 	
  GL_TEXTURE_ENV            = $2300; 	
  GL_NEAREST                = $2600;	
  GL_NEAREST_MIPMAP_NEAREST = $2700; 	
  GL_TEXTURE_MAG_FILTER     = $2800;	
  GL_TEXTURE_MIN_FILTER     = $2801;	
  GL_TEXTURE_WRAP_S         = $2802; 	
  GL_TEXTURE_WRAP_T         = $2803;	
  GL_CLAMP                  = $2900;	
  GL_REPEAT                 = $2901;	
Here are the declarations of the new commands:

procedure glPixelStorei(pname:GLenum;param:GLint); stdcall;	
procedure glTexParameteri(target,pname:GLenum;param:GLint); stdcall;	
procedure glTexEnvf(target,pname:GLenum;param:GLfloat); stdcall;	
In FormCreate you first tell OpenGL about the pixel storage mode with the glPixelStorei 
command. It affects several different operations, including the unpacking of texture patterns. 
The first parameter, GL_UNPACK_ ALIGNMENT, tells OpenGL the alignment 
requirements for the start of each pixel row in memory. The second value is 1, which means 
byte alignment. Larger values would mean alignment on 2-, 4-, or 8-byte boundaries, but 
byte alignment is the current need.
A lot of different parameters control texture mapping; the glTexParameteri command allows 
you to specify a target (one- or two-dimensional textures), name a parameter, and assign a 
value to that parameter. The target parameter for each glTexParameteri command is 
GL_TEXTURE_2D, telling OpenGL that the parameters being set are for two-dimensional 
textures rather than for one-dimensional textures. One-dimensional textures have their uses, 
but the scope of this book does not include them. FormCreate calls the command several 
times, each with a different pname.
GL_TEXTURE_WRAP_S and GL_TEXTURE_WRAP_T tell OpenGL what the wrapping 
parameters will be, either GL_CLAMP or GL_REPEAT. GL_REPEAT enables multiple copies 
of the texture to appear in the S or T direction when dimensions warrant. You could use 
GL_REPEAT even when wrapping would not occur, but GL_CLAMP would prevent 
possible wrapping artifacts.
GL_TEXTURE_MAG_FILTER and GL_TEXTURE_MIN_FILTER tell OpenGL what to do 
when the pixel being textured is larger or smaller than the texel. The third parameter set to 
GL_NEAREST tells OpenGL to use the 

PAGE 252
 color of the texel nearest the center of the pixel being textured. Other possible values would 
require OpenGL to calculate a color after selecting from multiple images in mipmaps.
The glTexImage2D command uses Level 0 for the base image level and also uses 0 for the 
border width. The format is GL_RGBA (red, green, blue, and alpha), so the number of color 
components is four. The texture is 16 texels wide and 8 texels tall. The data type in the 
structure is GLubyte, which is the equivalent of a Delphi byte. The texture is contained in the 
Brick variable, to which the call to the command passes a pointer.
The glEnable command is self explanatory, but glTexEnvf needs a little more description. 
The first two parameters are inflexible, but for the third you have several choices; however, 
this book only uses two of them:

  GL_MODULATE = $2100; 	
  GL_DECAL    = $2101;	
The command tells OpenGL how to interpret texture values when texturing a fragment. For 
now, use GL_DECAL, which basically tells OpenGL to paste an opaque or possibly an alpha 
blended texture over the surface.
More Methods
The following methods illustrate how simple and straightforward this project is:

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);	
  Transform1.Apply;	
  glFlush;	
  OpenGL1.ErrorCheck('FormPaint');	
end;	
Applying the Texture
The final piece of this texture puzzle is tacking the texture to the surface. Where you specify 
the vertices, also specify texture coordinates with glTexCoord2f commands:


PAGE 253
 procedure TForm1.Transform1Paint(Sender: TObject);	
begin	
  glBegin(GL_POLYGON);	
    glTexCoord2f(0.0,0.0); glVertex3f(-1.0,-0.5,+0.0);	
    glTexCoord2f(1.0,0.0); glVertex3f(+1.0,-0.5,+0.0);	
    glTexCoord2f(1.0,1.0); glVertex3f(+1.0,+0.5,+0.0);	
    glTexCoord2f(0.0,1.0); glVertex3f(-1.0,+0.5,+0.0);	
  glEnd;	
end;	
Run
Save, compile, and run. Figure 11-3 shows the polygon is completely covered by the texture.
So what has this accomplished? How is this better than drawing a picture in Windows 
Paint? The answer is once a texture is mapped to an object, transformations on the object also 
transform the texture. Exit from the running Simple0.Exe, select the TTransform component 
on the form designer, and change a few properties in the Object Inspector. 

PAGE 254
 Set Angle to 65, AxisX to 0.3, and AxisY to 1.0.

Again save, compile, and run. Figure 11-4 shows the polygon and texture together rotated 
and clipped by the zNear clipping plane.




       Figure 11-4. A texture transformed 
       with its polygon.

Stretch
The texture fit very nicely on the polygon and moved around perfectly with the polygon. 
What more is there? Well, there is the fact that the texture and the polygon were custom 
designed to fit each other. What if the texture and the polygon do not have the same 
proportional dimensions?
Mapping Manner
The answer lies in these two statements:

  glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_S,GL_CLAMP);	
  glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_T,GL_CLAMP);	
The manner in which OpenGL performs its texture mapping proceeds from the various 
parameters set in the glTexParameteri commands. These particular command instances set 
the wrapping parameters to GL_CLAMP, so the texture image has a one-to-one exact fit to 
the polygon; it tacks to the appropriate vertices, shrinking or stretching as needed.
The best way to explain that effect is by means of an example in which the coordinates of the 
polygon are not proportional to the texture dimensions.


PAGE 255
 Coordinates
Create a new directory under the Chapter.11 directory and name it Stretch. Create a new 
project and save it into the new directory, naming the unit Stretch1.Pas and the project 
Stretch0.Dpr. Make this project identical to the previous one except for the vertex 
coordinates in the Transform1Paint method. Also use the original zero values for Angle, 
AxisX, and AxisY in the Object Inspector for the Transform1 component.

procedure TForm1.Transform1Paint(Sender: TObject);	
begin	
  glBegin(GL_POLYGON);	
    glTexCoord2f(0.0,0.0); glVertex3f(-1.0,-1.0,+0.0);	
    glTexCoord2f(1.0,0.0); glVertex3f(+1.0,-1.0,+0.0);	
    glTexCoord2f(1.0,1.0); glVertex3f(+1.0,+1.0,+0.0);	
    glTexCoord2f(0.0,1.0); glVertex3f(-1.0,+1.0,+0.0);	
  glEnd;	
end;	
Run
Save, compile, and run. Figure 11-5 shows the result. The texture image is stretched 
vertically by a factor of two, or compressed horizontally; take your preference. Figure 11-6 
diagrams the mapping process. The point is that texture mapping has much more flexibility 
than the first example program alone would indicate. The Stretch0 application shows that a 
texture image can deform to fit its surface.



PAGE 256
 Replication
These examples of texture mapping suggest great possibilities for effective rendering, but 
they are very far from exhausting the available varieties. Suppose you want to render a brick 
wall twenty bricks tall and a hundred bricks wide. Do you apply 2,000 separate textures? 
That is certainly a dreadful thought!
Two for One
Create another directory and project under Chapter.11; name the directory Double, the unit 
Double1.Pas, and the project Double0.Dpr. The names were chosen because this application 
will make two bricks out of one. Make this project just like the Stretch0 project except for a 
couple of changes.

PAGE 257
 Parameters
In the FormCreate method, replace the GL_CLAMP values with GL_REPEAT:

  glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_S,GL_REPEAT);	
  glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_T,GL_REPEAT);	
By setting the wrap parameters to GL_REPEAT you allow OpenGL to repeat the texture 
image over the surface. This value allows but does not require repetition. The final 
distribution of image copies depends on the S and T texture coordinates.
Coordinates
Change the texture coordinates in the Transform1Paint method. Rather than running the T 
values from 0.0 to 1.0, replace the higher value with 2.0. By making the T coordinates run 
from 0.0 to 2.0 you provide for two copies of the texture image in the vertical direction.

procedure TForm1.Transform1Paint(Sender: TObject);	
begin	
  glBegin(GL_POLYGON);	
    glTexCoord2f(0.0,0.0); glVertex3f(-1.0,-1.0,+0.0);	
    glTexCoord2f(1.0,0.0); glVertex3f(+1.0,-1.0,+0.0);	
    glTexCoord2f(1.0,2.0); glVertex3f(+1.0,+1.0,+0.0);	
    glTexCoord2f(0.0,2.0); glVertex3f(-1.0,+1.0,+0.0);	
  glEnd;	
end;	
Run
Save, compile, and run. Behold the magic of two bricks as in Figure 11-7, enlarged for your 
viewing pleasure. Notice how well the image copies fit together at the mortar seams.
Figure 11-8 diagrams the image copy layout.

PAGE 258
 
PAGE 259
 
A Brick Wall
Admittedly the two-brick example is a bit lame for most situations, but it does introduce a 
technique which you can easily expand. To illustrate, the next example program builds a 
brick wall.
Make another directory under Chapter.11 named Wall. Create and save a project in it named 
(surprise!) Wall0.Dpr with a unit named Wall1.Pas. Make this project just like the Double0 
project except for the texture coordinates:

procedure TForm1.Transform1Paint(Sender: TObject);	
begin	
  glBegin(GL_POLYGON);	
    glTexCoord2f(0.0,00.0); glVertex3f(-1.0,-1.0,+0.0);	
    glTexCoord2f(8.0,00.0); glVertex3f(+1.0,-1.0,+0.0);	
    glTexCoord2f(8.0,16.0); glVertex3f(+1.0,+1.0,+0.0);	
    glTexCoord2f(0.0,16.0); glVertex3f(-1.0,+1.0,+0.0);	
  glEnd;	
end;	
These texture coordinates result in eight bricks across and sixteen bricks up. (Remember that 
OpenGL y window coordinates increase from bottom to top.) Save, compile, and run. You 
will see a brick wall like that in Figure 11-9.

PAGE 260
 Multiple Polygons
So far you have mapped one or more copies of a texture image to a single polygon, which is 
great, but few OpenGL scenes depend solely on such simple surfaces. 
Triangle Strip Square
The first example of texturing multiple polygons will be just one step up from texturing a 
single polygon. In this exercise you will create a square of the same size as before out of a 
triangle strip. You advance from texturing one polygon to two.
Create a new directory under Chapter.11 and call it Strip. Save a new project in it named 
Strip0.Dpr with a unit named Strip1.Pas. Make it just like the Wall0 project except for your 
rendering code.
Here is the code to make a square out of a strip of two triangles:

  glBegin(GL_TRIANGLE_STRIP);	
    glVertex3f(-1.0,+1.0,+0.0);	
    glVertex3f(-1.0,-1.0,+0.0);	
    glVertex3f(+1.0,+1.0,+0.0);	
    glVertex3f(+1.0,-1.0,+0.0); 	
  glEnd;	
You have four vertices, just like before. They even have the same values. On the other hand 
they are not in the same order and are not used the same way. So how do you map your 
texture to these vertices? The basic idea is that you match parts of the texture image to 
specific vertices. Although the vertices are not specified in the same order, you still tack a 
particular texture coordinate to a particular vertex. Here is the rendering code:

procedure TForm1.Transform1Paint(Sender: TObject);	
begin	
  glBegin(GL_TRIANGLE_STRIP); 	
    glTexCoord2f(00.0,16.0); glVertex3f(-1.0,+1.0,+0.0);	
    glTexCoord2f(00.0,00.0); glVertex3f(-1.0,-1.0,+0.0);	
    glTexCoord2f(08.0,16.0); glVertex3f(+1.0,+1.0,+0.0);	
    glTexCoord2f(08.0,00.0); glVertex3f(+1.0,-1.0,+0.0);	
  glEnd;	
end;	
Save, compile, and run. It should look just like the Wall0 project, as shown in Figure 11-9.

PAGE 261
 Still More Polygons
With that experience under your belt, you can go all out, mapping a repeating texture image 
to a strip of 36 triangles. As if that were not daring enough, you will apply your flat texture 
map to a curved surface! By combining a cylinder with a brick texture you can build a brick 
silo. Unlike an ordinary silo, this one will go spinning through space.
Create another directory under Chapter.11 and name it Silo. Save a new project in it named 
Silo0.Dpr with a unit named Silo1.Pas. Although the Shapes unit does not yet contain a 
textured cylinder, you will still need it in the project because you will use the CylinderBase 
procedure.
On the form place a TOpenGL component and a TTransform component. Listing 11-2 shows 
the properties:

object Form1: TForm1
  Left = 314
  Top = 108
  Width = 328
  Height = 347
  Caption = 'Form1'
  Font.Charset = DEFAULT_CHARSET
  Font.Color = clWindowText
  Font.Height = -11
  Font.Name = 'MS Sans Serif'
  Font.Style = []
  Position = poScreenCenter
  OnCreate = FormCreate
  OnPaint = FormPaint
  OnResize = FormResize
  PixelsPerInch = 96
  TextHeight = 13
  object OpenGL1: TOpenGL
    pfdFlags = [DoubleBuffer, Draw_To_Window, Support_OpenGL]
    PixelType = RGBA
    Projection = Perspective
    Enable = [depthTest]
    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


PAGE 262
     Left = 8
    Top = 8
  end
  object Transform1: TTransform
    TranZ = -2.55
    AxisX = 0.2
    AxisY = 1
    Angle = -65
    ScaleX = 1
    ScaleY = 1
    ScaleZ = 1
    OnPaint = Transform1Paint
    Left = 8
    Top = 40
  end
end
Listing 11-2. Silo0 properties.
In the form type declaration you need some private fields for the cylinder translation and an 
Idle method:

  private	
    deltaX,	
    deltaY,	
    deltaZ: GLfloat;	
    procedure Idle(Sender:TObject;var Done:boolean);	
  public	
In the implementation section you need the constants, types, variables, and the MakeBrick 
procedure you have seen before in the last several projects. Also, in the Shapes unit, you will 
need to move the circleX, circleY, and circleZ variables from the implementation section to 
the interface section so the Silo1 unit can use them after they have been initialized by the 
CylinderBase procedure.
The FormCreate method is also the same as before, except for a little extra initialization at the 
end, just after the glTexEnvf command:

  glTexEnvf(GL_TEXTURE_ENV,GL_TEXTURE_ENV_MODE,GL_DECAL);	
  Application.OnIdle := Idle;	
  deltaX := 0.01;	
  deltaY := 0.01;	
  deltaZ := 0.01;	
  CylinderBase;	
end;	

Put the code for a texture-capable cylinder after the FormCreate method. Call it CylinderT, 
and give it parameters for the ranges of texture coordinates. The range of the S texture 
coordinates (the sTex parameter) will be 

PAGE 263
 tacked to 36 evenly spaced vertices, so divide the range by 36 and store it in the s36 variable.

procedure CylinderT(sTex,tTex:glFloat);	
var	
  around: integer;	
  s36: GLfloat;	
begin	
  S36 := sTex/36.0;	
  glBegin(GL_TRIANGLE_STRIP);	
    for around := 0 to 35 do	
    begin	
      glTexCoord2f(around*s36,tTex);	
      glVertex3f(circleX[around],circleY[around],-0.4);	
      glTexCoord2f(around*s36,0.0);	
      glVertex3f(circleX[around],circleY[around],+0.4);	
    end; {for}	
    glTexCoord2f(sTex,tTex);	
    glVertex3f(circleX[0],circleY[0],-0.4);	
    glTexCoord2f(sTex,0.0);	
    glVertex3f(circleX[0],circleY[0],+0.4);	
  glEnd;	
end;	
The FormResize method is the same as always, but the FormPaint method needs adjustment 
for double buffering:

procedure TForm1.FormPaint(Sender: TObject);	
begin	
  glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT);	
  Transform1.Apply;	
  OpenGL1.Swap;	
  OpenGL1.ErrorCheck('FormPaint');	
end;	
For rendering code, just call CylinderT in the Transform1Paint method:

procedure TForm1.Transform1Paint(Sender: TObject);	
begin	
  CylinderT(16.0,8.0);	
end;	
Finally, here is the Idle method that moves the cylinder around, keeping it within some 
boundaries by changing the sign of the delta variables. (So that this example stays focused 
on the current purpose, no extra code for machine-independent speed is present.) You may 
want to adjust the delta values in this code for the most appropriate speed for your 
computer:

procedure TForm1.Idle(Sender:TObject;var Done:boolean);	
begin	


PAGE 264
   Done := false;	
  with Transform1 do	
  begin	
    if Angle<360.0 then	
      Angle := Angle+0.1	
    else	
      Angle := 0.0;	
	
    if TranX<-0.7 then	
      deltaX := 0.001;	
    if TranX>0.7 then	
      deltaX := -0.001;	
    TranX := TranX+deltaX;	
	
    if TranY<-0.8 then	
      deltaY := 0.001;	
    if TranY>0.8 then	
      deltaY := -0.001;	
    TranY := TranY+deltaY;	
	
    if TranZ>-2.22 then	
      deltaZ := -0.001;	
    if TranZ<-9.00 then	
      deltaZ := 0.001;	
    TranZ := TranZ+deltaZ;	
  end; {with}	
  FormPaint(nil);	
end;	
Save, compile, and run to see a flying brick silo like in Figure 11-10.







         Figure 11-10. A flying brick silo.


PAGE 265
 Windows Bitmaps as Textures
Obviously many texture images are more complicated than the little memory brick used so 
far, and they require more sophisticated tools to draw. In the world of Microsoft Windows, 
the dominant image format is the Windows bitmap. The typical file extension is .BMP. (Since 
OpenGL has its own concept called “bitmap,” the term must be qualified with “Windows” 
or “OpenGL” for clarity.) Windows bitmap structures and OpenGL texture structures 
developed completely independently of each other, so they are not necessarily compatible.
A Close One
Fortunately, one Windows bitmap format is very close to one OpenGL texture image format, 
so that about the only conversion required is skipping the header. The selected Windows 
bitmap format is 24-bit true color, and the selected OpenGL texture format is GL_RGB. 
In both formats, each element takes three bytes, one for red, one for green, and one for blue. 
Not only that, but Windows bitmaps run their vertical coordinates from bottom to top, just 
like OpenGL.
Remember the power-of-two requirement for OpenGL texture dimensions. In order for a 
Windows bitmap to be useful as an OpenGL texture image, it must abide by the same 
restrictions. The forthcoming example program will use a 256x256 image as the label of a 
fanciful beverage called Zap, which appears in Figure 11-11. The file is available on the 
companion CD as Fig11-11.bmp, created by means of Windows Paint.
The Project
Create a new directory under Chapter.11 called Zap and save a new project in it called 
Zap0.Dpr, with a unit called Zap1.Pas. Copy Fig11-11.bmp from 

PAGE 266
 the CD into the new directory. Make the project just like the Silo0 project with a few 
exceptions, which follow.
The Differences
Near the top of the implementation section, remove the wBrick and hBrick constants and 
change ColorComps from 4 to 3 because the 24-bit Windows bmp file has no alpha 
component:

const	
  Level=0;	
  Border=0;	
  ColorComps=3;	
Just below the constants, replace the type and variable definitions completely:

type	
  TRGB=packed record	
    r,g,b: byte;	
  end;	
  TWrap=array[0..0] of TRGB;	
	
var	
  Wrapper: pointer;	
Remove the MakeBrick procedure and replace it with a ReadBitmap function that opens the 
designated file, gets the file size, and subtracts the header sizes to obtain the size of the 
actual bitmap. Then it reads the file header and checks for a valid bitmap signature. Next the 
function reads the info header and extracts the width and height in pixels, which it 
communicates to the caller via the var parameters. Finally, it allocates the required memory 
and reads the bitmap into that memory. The function returns a pointer to the memory 
containing the raw true-color bitmap.

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 267
   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);	
end;	
Add a couple of local variables to the FormCreate method, which will receive the width and 
height information from the ReadBitmap function:

procedure TForm1.FormCreate(Sender: TObject);	
var	
  sWidth,	
  tHeight: GLsizei;	
Replace the call to MakeBrick and the parameters to glTexImage2D. Free the Wrapper 
memory after passing it to the OpenGL command. Do not be alarmed by this; when you pass 
a structure to OpenGL, it makes its own copy, so you can do anything you want with the 
original any time there- after without affecting OpenGL’s copy. Actually, you should replace 
the filename with a fully qualified path to the file so that the program will work even if you 
have changed your current directory before starting.

  Wrapper := ReadBitmap(‘Fig11-11.bmp’,sWidth,tHeight);	
  glTexImage2D(GL_TEXTURE_2D,Level,ColorComps,sWidth,tHeight,Border,	
    GL_RGB,GL_UNSIGNED_BYTE,Wrapper);	
  Freemem(Wrapper);	
Change the parameters in the call to CylinderT because instead of eight bricks across by 
sixteen bricks tall, you only need two beverage labels wrapped around, and only one label 
tall:

procedure TForm1.Transform1Paint(Sender: TObject);	
begin	
  CylinderT(2.0,1.0);	
end;	
Finally, change some properties of the TTransform component. Change Angle from –65.0 to 
90.0, AxisX from 0.2 to 1.0, AxisY from 1.0 to 0.0, and AxisZ from 0.0 to 1.0.

PAGE 268
 Run
Save, compile, and run. Watch the deadly beverage can tumble around with a dazzling 
green label. Figure 11-12 captures one frame:
Close, But No Cigar
If you opened the bitmap with Windows Paint you may have noticed a color difference 
between it and the OpenGL rendering. The blue “ZAP” should have been a red “ZAP.” The 
cyan lightning bolts were originally yellow. The reason is that OpenGL is using RGB and 
Windows stored BGR, so red and blue are switched. Therefore, the program will have to do 
some conversion after all.
Go back to the ReadBitmap function and add a pixel-flipping loop at the end. The types and 
variables are already in place:

  blockread(bmpfile,result^,size);	
  for x := 0 to sWidth*tHeight-1 do	
  with TWrap(result^)[x] do	
  begin	
    t := r;	


PAGE 269
     r := b;	
    b := t;	
  end;	
end;	
Now save your changes, compile, and run again. Now you will have red lettering and 
yellow lightning bolts.
Mipmapping
Have you ever seen one of those first-person games where you wander around and shoot 
everything that moves? If you have, you have probably noticed that if you approach too 
closely to one of those nicely textured stone walls, the illusion is spoiled by an extremely 
blocky close-up. You may have wondered why the games did not have finer detail in those 
textures so that you would not be disappointed by the proximity. The issue, of course, was 
memory. A texture image can take up precious memory in the video card. A high-detail 
texture image would possibly have taken too much of a scarce resource, and most of the time 
the detail would have remained unappreciated, except by those folks who perversely decide 
to bump into walls.
OpenGL provides a mechanism by which you can avoid the worst of such artifacts. An 
earlier part of the chapter introduced the term “mipmap” involving multiple images for a 
texture. With mipmapping, basically you start with your largest and best texture image. 
Then you make various shrunken, lower-detail images from it. OpenGL picks which image 
is the most appropriate to use as the texture in a given frame. As an object becomes larger 
(closer), OpenGL replaces the texture with a larger one with more detail. As the object 
becomes smaller, OpenGL can use a texture image that requires less memory.
To illustrate the technique, first illustrate the problem. Create a new directory named Mip 
under the Chapter.11 directory. In it save a new project named Mip0.Dpr with a unit named 
Mip1.Pas. Make it just like the Zap0 project. Naturally, differences will follow. Copy from 
the CD a series of bitmap files named GW0.BMP, GW1.BMP...GW8.BMP to the new 
directory.
To the FormCreate method, add a string to hold a potentially variable filename:

procedure TForm1.FormCreate(Sender: TObject);	
var	
  sWidth,	
  tHeight: GLsizei;	
  fn: string;	
begin  	

PAGE 270
 Change the lines around the call to glTexImage2D. Instead of the assignment to fn shown 
below, use a fully qualified path to the file. Remove the call to CylinderBase and the 
assignments to deltaX and deltaY. While you are at it, remove their declarations as well:

  fn := 'GW3.BMP';	
  Wrapper := ReadBitmap(fn,sWidth,tHeight);	
  glTexImage2D(GL_TEXTURE_2D,0,ColorComps,sWidth,tHeight,Border,GL_RGB,	
    GL_UNSIGNED_BYTE,Wrapper);	
  freemem(Wrapper); 	
  glEnable(GL_TEXTURE_2D);	
  glTexEnvf(GL_TEXTURE_ENV,GL_TEXTURE_ENV_MODE,GL_DECAL);	
  Application.OnIdle := Idle;	
  deltaZ := 0.01;	
  OpenGL1.ErrorCheck(‘FormCreate’);	
end;	
This time draw a square rather than a cylinder. Since you are no longer using the CylinderT 
procdure, go ahead and remove it:

procedure TForm1.Transform1Paint(Sender: TObject);	
begin	
  glBegin(GL_POLYGON);	
    glTexCoord2f(0.0,0.0); glVertex3f(-1.0,-1.0,+0.0);	
    glTexCoord2f(1.0,0.0); glVertex3f(+1.0,-1.0,+0.0);	
    glTexCoord2f(1.0,1.0); glVertex3f(+1.0,+1.0,+0.0);	
    glTexCoord2f(0.0,1.0); glVertex3f(-1.0,+1.0,+0.0);	
  glEnd;	
end;	
For the TOpenGL component, change the pxFar property to 32.0 in the Object Inspector. 
Correspondingly change the TranZ property of the TTransform to –32.0; change its Angle 
and Axis properties to zero. Trim the Idle method to manipulate only deltaZ:

procedure TForm1.Idle(Sender:TObject;var Done:boolean);	
begin	
  Done := false;	
  with Transform1 do	
  begin	
    if TranZ>-2.01 then	
      deltaZ := -0.001;	
    if TranZ<-31.99 then	
      deltaZ := 0.001;	
    TranZ := TranZ+deltaZ;	
  end; {with}	
  FormPaint(nil);	
end; {Idle}	

PAGE 271
 Save, compile, and run. The program uses a single 32x32 texture image representing some 
gold foil. Notice what happens to the texture as the image gets close. Ugly! You can even see 
a sparkling effect as OpenGL keeps recalculating the size of the texture from perspective 
adjustment. See Figure 11-13 for the closest (and ugliest) view.
Now, change the FormCreate method to accommodate mipmapping, as in Listing 11-3. 
Notice the new parameter to go with GL_TEXTURE_MIN_FILTER. 
GL_NEAREST_MIPMAP_ NEAREST tells OpenGL you will be using multiple texture 
images and, for a given image, tells OpenGL to use the color of the texel nearest the center of 
the pixel being textured.
The base image is 256x256, so there are a total of nine images for sizes down to 1x1. Each 
successive image must be one-half the size of the previous one in each direction (so it will 
usually be one-fourth the “area” of the previous one). For example, the second image is 
128x128. Even if the base image were not square, the number of images is determined by the 
larger dimension. Once the smaller dimension has been cut down to one pixel, successive 
images continue to cut down the longer dimension until it too reaches one pixel. The nine 
bitmap files beginning with GW0.BMP are the nine images for the mipmap.

procedure TForm1.FormCreate(Sender: TObject);
var
  sWidth,
  tHeight: GLsizei;
  fn: string;
begin
  glPixelStorei(GL_UNPACK_ALIGNMENT,1);
  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_MIPMAP_NEAREST);

  fn := 'GW0.BMP';


PAGE 272
   Wrapper := ReadBitmap(fn,sWidth,tHeight);
  glTexImage2D(GL_TEXTURE_2D,0,ColorComps,sWidth,tHeight,Border,GL_RGB,
    GL_UNSIGNED_BYTE,Wrapper);
  freemem(Wrapper);

  inc(fn[3]);
  Wrapper := ReadBitmap(fn,sWidth,tHeight);
  glTexImage2D(GL_TEXTURE_2D,1,ColorComps,sWidth,tHeight,Border,GL_RGB,
    GL_UNSIGNED_BYTE,Wrapper);
  freemem(Wrapper);

  inc(fn[3]);
  Wrapper := ReadBitmap(fn,sWidth,tHeight);
  glTexImage2D(GL_TEXTURE_2D,2,ColorComps,sWidth,tHeight,Border,GL_RGB,
    GL_UNSIGNED_BYTE,Wrapper);
  freemem(Wrapper);

  inc(fn[3]);
  Wrapper := ReadBitmap(fn,sWidth,tHeight);
  glTexImage2D(GL_TEXTURE_2D,3,ColorComps,sWidth,tHeight,Border,GL_RGB,
    GL_UNSIGNED_BYTE,Wrapper);
  freemem(Wrapper);

  inc(fn[3]);
  Wrapper := ReadBitmap(fn,sWidth,tHeight);
  glTexImage2D(GL_TEXTURE_2D,4,ColorComps,sWidth,tHeight,Border,GL_RGB,
    GL_UNSIGNED_BYTE,Wrapper);
  freemem(Wrapper);

  inc(fn[3]);
  Wrapper := ReadBitmap(fn,sWidth,tHeight);
  glTexImage2D(GL_TEXTURE_2D,5,ColorComps,sWidth,tHeight,Border,GL_RGB,
    GL_UNSIGNED_BYTE,Wrapper);
  freemem(Wrapper);

  inc(fn[3]);
  Wrapper := ReadBitmap(fn,sWidth,tHeight);
  glTexImage2D(GL_TEXTURE_2D,6,ColorComps,sWidth,tHeight,Border,GL_RGB,
    GL_UNSIGNED_BYTE,Wrapper);
  freemem(Wrapper);

  inc(fn[3]);
  Wrapper := ReadBitmap(fn,sWidth,tHeight);
  glTexImage2D(GL_TEXTURE_2D,7,ColorComps,sWidth,tHeight,Border,GL_RGB,
    GL_UNSIGNED_BYTE,Wrapper);
  freemem(Wrapper);

  inc(fn[3]);
  Wrapper := ReadBitmap(fn,sWidth,tHeight);


PAGE 273
   glTexImage2D(GL_TEXTURE_2D,8,ColorComps,sWidth,tHeight,Border,GL_RGB,
    GL_UNSIGNED_BYTE,Wrapper);
  freemem(Wrapper);

  glEnable(GL_TEXTURE_2D);
  glTexEnvf(GL_TEXTURE_ENV,GL_TEXTURE_ENV_MODE,GL_DECAL);
  Application.OnIdle := Idle;
  deltaZ := 0.01;
  OpenGL1.ErrorCheck('FormCreate');
end;
Listing 11-3. FormCreate sets up mipmapping.

Again, you should replace the filename with a fully qualified path to the file so that the 
program will work even if you have changed your current directory before starting. Replace 
fn[3] with the proper resulting position in the fn string for the numeric digit part of the 
filename.
Save, compile, and run. Behold a mipmap in operation. Figure 11-14 shows the closest view 
of the gold foil. 
If you watch closely while the program is running, you may notice when OpenGL switches 
to the next size texture. The somewhat clumsy transitions and the coarseness of the smaller 
images are due to the fact that the tool (Windows Paint) uses a relatively crude method of 
image reduction from the base image. While this serves quite well to demonstrate the 
mipmap process, you will need to use better tools to create mipmaps for real applications.


PAGE 274
 Lighting
One really big thing that has been missing from these demonstrations is lighting. In the next 
illustrative program, you will add the lighting that makes OpenGL so powerful.
Add Components
Under Chapter.11 create a new directory named Light, a project named Light0.Dpr, and a 
unit named Light1.Pas. Naturally you should save the project and its unit in the new 
directory. Make the project just like the Wall0 project, but add another TTransfer component 
and a TLight component. Listing 11-4 shows the added components and their properties, 
along with a few changes in Transform1:

object Form1: TForm1
  Left = 308
  Top = 100
  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 = True
  OnCreate = FormCreate
  OnPaint = FormPaint
  OnResize = FormResize
  PixelsPerInch = 96
  TextHeight = 13
  object OpenGL1: TOpenGL
    pfdFlags = [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


PAGE 275
     ColorBits = 24
    DepthBits = 32
    StencilBits = 0
    Left = 8
    Top = 4
  end
  object Transform1: TTransform
    TranZ = -2.2
    ScaleX = 1
    ScaleY = 1
    ScaleZ = 1
    OnPaint = Transform1Paint
    Left = 8
    Top = 40
  end
  object Light2: TLight
    Which = 0
    Cutoff = 65
    AmbientRed = 0.2
    AmbientGreen = 0.2
    AmbientBlue = 0.2
    DiffuseRed = 1
    DiffuseGreen = 1
    DiffuseBlue = 1
    Direction = True
    Position = True
    Spotlight = True
    Enabled = True
    Left = 44
    Top = 76
  end
  object Transform2: TTransform
    TranX = -4
    TranY = 1
    ScaleX = 1
    ScaleY = 1
    ScaleZ = 1
    OnPaint = Transform2Paint
    Left = 8
    Top = 76
  end
  object Material1: TMaterial
    ambRed = 1
    ambGreen = 1
    ambBlue = 1
    ambAlpha = 1
    difRed = 1
    difGreen = 1
    difBlue = 1


PAGE 276
     difAlpha = 1
    aFace = faFront
    dFace = faFront
    sFace = faFront
    Left = 44
    Top = 40
  end
  object Material2: TMaterial
    ambRed = 1
    ambGreen = 1
    ambBlue = 1
    ambAlpha = 1
    difRed = 1
    difGreen = 1
    difBlue = 1
    difAlpha = 1
    aFace = faBack
    dFace = faBack
    sFace = faBack
    Left = 84
    Top = 40
  end
end
Listing 11-4. Light0 project properties.
The OnPaint event handler for Transform2 applies the lighting:

procedure TForm1.Transform2Paint(Sender: TObject);	
begin	
  Light2.Apply;	
end;	
The FormPaint method should call Transform2.Apply first since it carries the lighting:

procedure TForm1.FormPaint(Sender: TObject);	
begin	
  glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT);	
  Transform2.Apply;	
  Transform1.Apply;	
  glFlush;	
  OpenGL1.ErrorCheck(‘FormPaint’);	
end;	
Save, compile, and run. Because of the angles and positions of the brick wall and the light, 
the near part of the wall should be dark and the more distant part should be fairly well 
illuminated. Instead, as Figure 11-15 shows, the wall is uniformly bright.


PAGE 277
 Alpha
Well, that did not work, so try something else. Under Chapter.11, make an Alpha directory 
containing an Alpha0.Dpr project and an Alpha1.Pas unit, which you make just like the 
Light0 project, with a slight change, of course.
The brick has an alpha component, so put it to use. In the MakeBrick procedure, make your 
texture halfway transparent:

    with Brick[tTex][sTex] do	
    begin	
      alpha := 222;	
Now you need material color that can respond to the lighting and blend with the texture. 
Change the properties of the Material1 component according to its text representation in 
Listing 11-5:

  object Material1: TMaterial
    ambRed = 0.5
    ambGreen = 0.5
    ambBlue = 0.5
    ambAlpha = 1
    difRed = 0.5
    difGreen = 0.5
    difBlue = 0.5
    difAlpha = 1
    aFace = faFront
    dFace = faFront
    sFace = faFront


PAGE 278
     Left = 40
    Top = 40
  end
Listing 11-5. Material to blend with texture.
Save, compile, and run. Now the texture shows little, if any, shading. This may be an 
improvement, but not anything to brag about or even notice. The real use of alpha is to allow 
texture color to combine with material color, not shading. Maybe OpenGL has a suitable 
constant to pass as a parameter to facilitate light and shade.
Modulate
Indeed it does. Instead of GL_DECAL, pass GL_MODULATE. GL_DECAL tells OpenGL to 
apply the texture like a decal that covers a surface but does not partake of the properties of 
the surface, other than the shape and possibly some alpha blending. GL_MODULATE 
passed to the texture environment command allows the lit material color to attenuate the 
texture color. While it does facilitate lighting effects, it does not provide for shininess. For 
that you would have to render twice, once with the texture and once with only the shiny 
material properties. Such a technique is not covered here.
Create a new directory under the Chapter.11 directory and name it Modulate. Create a new 
project and save it in the new directory as Mod0.Dpr, with a unit named Mod1.Pas. Make 
the project like the Alpha0 project, but leave the alpha value for the brick at the original 255. 
In the FormCreate method, change the parameter passed to the texture environment 
command from GL_DECAL to GL_MODULATE:

  glTexEnvf(GL_TEXTURE_ENV,GL_TEXTURE_ENV_MODE,GL_MODULATE);	
Save, compile, run, and behold shading, as shown in Figure 11-16.

PAGE 279
 
Identifier Reference
Identifier			Description
GLbyte
An OpenGL data type equivalent to Delphi shortint.
glPixelStorei
An OpenGL command that specifies the pixel storage mode.
GLshort
An OpenGL data type equivalent to Delphi smallint.
glTexCoord2f
An OpenGL command that gives the texture coordinate to go with the next vertex 
coordinate.
glTexEnvf
An OpenGL command that tells OpenGL how to interpret texture values when texturing a 
fragment.
glTexImage2D
An OpenGL command that tells OpenGL important information about the two-dimensional 
texture image and points to the texture image.
glTexParameteri
An OpenGL command that allows you to set the parameters that control texture mapping.
GLubyte
An OpenGL data type equivalent to Delphi byte.
GLushort
An OpenGL data type equivalent to Delphi word.
GL_ALPHA
An OpenGL constant that informs OpenGL that each value in the texture is an alpha value 
so that it represents an RGBA value in which the red, green, and blue values are 0 and the 
alpha value is the specified amount.
GL_BLUE
An OpenGL constant that informs OpenGL that each value in the texture is an intensity of 
blue.
GL_BYTE
An OpenGL constant that informs OpenGL that an underlying data type is GLbyte.
GL_CLAMP
An OpenGL constant, used as a texture wrapping parameter, that tells OpenGL not to repeat 
the texture image in the given direction.

PAGE 280
 
Identifier			Description
GL_COLOR_INDEX
An OpenGL constant that informs OpenGL that each value in the texture is an index into a 
color palette. 
GL_DECAL
An OpenGL constant that tells OpenGL to paste the texture image over the surface using the 
shape as the only property of the surface, with some account of the material color if the 
texture alpha is less than the maximum.
GL_GREEN
An OpenGL constant that informs OpenGL that each value in the texture is an intensity of 
green.
GL_FLOAT
An OpenGL constant that informs OpenGL that an underlying data type is GLfloat.
GL_INT
An OpenGL constant that informs OpenGL that an underlying data type is GLint.
GL_LINEAR
An OpenGL constant which, when passed to glTexParameteri, tells OpenGL to use the color 
of the four texels nearest the center of the pixel being textured.
GL_LUMINANCE
An OpenGL constant that informs OpenGL that each value in the texture is a grayscale 
intensity.
GL_LUMINANCE_
ALPHA
An OpenGL constant that informs OpenGL that each value in the texture has a grayscale 
intensity and an alpha value.
GL_MODULATE
An OpenGL constant that allows modulation between the lit material color and the texture 
color.
GL_NEAREST
An OpenGL constant which, when passed to glTexParameteri, tells OpenGL to use the color 
of the texel nearest the center of the pixel being textured.
GL_NEAREST_MIPMAP_
NEAREST
An OpenGL constant which, when passed to the glTexParameteri command, tells OpenGL 
you will be using multiple texture images and, for a given image, tells OpenGL to use the 
color of the texel nearest the center of the pixel being textured.

PAGE 281
 
Identifier			Description
GL_RED
An OpenGL constant that informs OpenGL that each value in the texture is an intensity of 
red.
GL_REPEAT
An OpenGL constant that enables multiple copies of the texture to appear in the S or T 
direction when dimensions warrant.
GL_RGB
An OpenGL constant that informs OpenGL that each value in the texture is a structure with 
three color components—red, green, and blue.
GL_RGBA
An OpenGL constant that informs OpenGL that each value in the texture is a structure with 
four color components—red, green, blue, and alpha.
GL_SHORT
An OpenGL constant that informs OpenGL that an underlying data type is GLshort.
GL_TEXTURE_2D
An OpenGL constant that informs OpenGL that the OpenGL command applies to a two-
dimensional texture, rather than to a one-dimensional texture.
GL_TEXTURE_ENV
The obligatory OpenGL constant that serves as the target for the glTexEnvf command.
GL_TEXTURE_ENV_
MODE
An OpenGL constant to pass to the glTexEnvf command. When you pass this constant you 
can tell OpenGL with the next parameter how texture values should combine with color 
values from the fragment being textured.
GL_TEXTURE_MAG_
FILTER
An OpenGL constant to pass to glTexParameteri to tell OpenGL what to do when the pixel 
being textured is larger than the texel.
GL_TEXTURE_MIN_
FILTER
An OpenGL constant to pass to glTexParameteri to tell OpenGL what to do when the pixel 
being textured is smaller than the texel.
GL_TEXTURE_WRAP_S
An OpenGL constant to pass to glTexParameteri to tell OpenGL what the wrapping 
parameters will be in the S direction.
GL_TEXTURE_WRAP_T
An OpenGL constant to pass to glTexParameteri to tell OpenGL what the wrapping 
parameters will be in the T direction.

PAGE 282
 
Identifier			Description
GL_UNPACK_
ALIGNMENT
An OpenGL constant which, when passed to the GlPixelStorei command, tells OpenGL the 
alignment requirements for the start of each pixel row in memory. 
GL_UNSIGNED_BYTE
An OpenGL constant that informs OpenGL that an underlying data type is GLubyte.
GL_UNSIGNED_INT
An OpenGL constant that informs OpenGL that an underlying data type is GLuint.
GL_UNSIGNED_SHORT
An OpenGL constant that informs OpenGL that an underlying data type is GLushort.
Summary
OpenGL has a method to add realistic detail to scenes without a massive increase in the 
number of polygons. With texture mapping you can apply a flat image to a polygon or 
group of polygons. Various OpenGL commands and constants allow you to set up a 
multitude of possible texture mapping criteria. Use the GL_DECAL constant to paste a 
texture on a surface or GL_MODULATE to allow lighting effects in combination with 
material properties.
A key factor in successful texture mapping is properly matching up texture coordinates, 
using glTexCoord2f, with vertex coordinates, which you specify just after the texture 
coordinates. A very useful texture mapping feature allows you to multiply a small texture 
image many times over a surface. For example, you could represent an entire brick wall 
when you only draw one brick. The constant you use for this is GL_REPEAT in conjunction 
with texture coordinates greater than 1.0. Another, rather advanced, feature  comes in handy 
when different levels of detail are needed for a texture seen from a wide range of distances. 
With mipmapping, OpenGL can automatically select the most appropriate image from a 
group of images.
Texture mapping is one more example of the tremendous power OpenGL brings to the 
world of three-dimensional graphics. Indeed, although this chapter unlocks fascinating and 
versatile texturing techniques, it only touches the “surface” of OpenGL’s texturing 
capabilities.


PAGE 283
 

PAGE 284

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

Chapter 13
Drive-Through
In this chapter you will learn a few new techniques, including how to do a “drive-through” 
or “walk-through” of a scene with OpenGL. In addition, the drive-through will provide the 
medium in which to bring together various techniques and commands you have already 
learned.
Simple Scene
First, a simple scene serves to introduce the basic drive-through requirement. The user does 
not actually change position; to simulate the movement of the user, you will instead move 
the scene in the opposite direction. See Figure 13-1 for the initial appearance.
Project
Create a new directory under the OpenGL directory and name it Chapter.13. In that 
directory, create a directory named Simple. Save a new project therein, naming it 
Simple0.Dpr with a unit named Simple1.Pas. This application will have a light, a floor or 
ground, and an object on the floor. On the form, drop a TOpenGL component, a TLight 
component, three TTransform components, and three TMaterial components.

PAGE 329
 Properties
This project is simple indeed. In addition to lighting for the scene, the components provide a 
broad expanse of floor-like ground and a cube on the ground you can drive around or 
through. Listing 13-1 shows the properties:

object Form1: TForm1	
  Left = 192	
  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	
  OnKeyDown = FormKeyDown	
  OnKeyUp = FormKeyUp	
  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 = 42	
    ColorBits = 24	
    DepthBits = 32	
    StencilBits = 0	
    Left = 4	
    Top = 8	
  end	
  object TransformLight: TTransform	
    TranY = 5	


PAGE 330
     TranZ = -3	
    AxisX = 1	
    Angle = -20	
    ScaleX = 1	
    ScaleY = 1	
    ScaleZ = 1	
    OnPaint = TransformLightPaint	
    Left = 4	
    Top = 44	
  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 = 44	
  end	
  object MaterialLight: TMaterial	
    ambRed = 1	
    ambGreen = 1	
    ambBlue = 1	
    ambAlpha = 1	
    difRed = 1	
    difGreen = 1	
    difBlue = 1	
    difAlpha = 1	
    aFace = faFront	
    dFace = faFront	
    sFace = faFront	
    Left = 76	
    Top = 44	
  end	
 object TransformFloor: TTransform	
    TranY = -1	
    ScaleX = 1	
    ScaleY = 1	
    ScaleZ = 1	


PAGE 331
     OnPaint = TransformFloorPaint	
    Left = 4	
    Top = 80	
  end	
  object MaterialFloor: TMaterial	
    ambRed = 0.7	
    ambGreen = 0.5	
    ambBlue = 0.1	
    ambAlpha = 1	
    difRed = 0.7	
    difGreen = 0.7	
    difBlue = 0.1	
    difAlpha = 1	
    aFace = faFront	
    dFace = faFront	
    sFace = faFront	
    Left = 40	
    Top = 80	
  end	
  object TransformCube: TTransform	
    TranY = -0.6	
    TranZ = -10	
    ScaleX = 1	
    ScaleY = 1	
    ScaleZ = 1	
    OnPaint = TransformCubePaint	
    Left = 4	
    Top = 116	
  end	
  object MaterialCube: TMaterial	
    ambBlue = 1	
    ambAlpha = 1	
    difBlue = 1	
    difAlpha = 1	
    aFace = faFront	
    dFace = faFront	
    sFace = faFront	
    Left = 40	
    Top = 116	
  end	
end
Listing 13-1. Simple drive-through project properties.
Private
The private section of the form type declaration needs a few fields and method declarations. 
The viewer movement uses the GLfloat fields, and the frame rate calculations use the integer 
fields. The Idle method handles the 

PAGE 332
 frame rate calculations and the basic animation including the viewer movement. The angl, 
wx, and wz fields are the key positional fields; the others are helpers. In Figure 13-2 you see 
an example of some accumulation of values for the positional fields.

  private	
    angl,	
    rspeed,	
    speed,	
    tran,	
    rot,	
    wx,	
    wz: GLfloat;	
    frames,	
    elapsed,	
    start: integer;	
    procedure Idle(Sender:TObject;var Done:boolean);	
    procedure Adjust;	
  public	
Variables
In addition to the usual form variable declaration, you should declare pi180. It serves as a 
calculation saver. OpenGL uses degrees and Delphi trigonometric functions use radians. 
Rather than calling the pi function and dividing by 180 degrees (or multiplying by 
0.0055555555556) every frame, just use this pre-initialized variable:

PAGE 333
 
var	
  Form1: TForm1;	
  pi180: double=pi/180.0;	
In Delphi 2 you would use a typed constant rather than an initialized variable:

const	
  pi180: double=pi/180.0;	
Event Handlers
The FormCreate method sets up two-sided lighting and the Idle method:

procedure TForm1.FormCreate(Sender: TObject);	
begin	
  glLightModeli(GL_LIGHT_MODEL_TWO_SIDE,GL_TRUE);	
  Application.OnIdle := Idle;	
end;	
The FormPaint method contains the magic that allows you to simulate a moving viewpoint. 
Figure 13-3 shows the viewpoint moved to the back side of the floor area and turned around.

Each Apply method called pushes the ModelView matrix onto the matrix stack, performs the 
transformations, performs the renderings, and pops the matrix stack so that the next 
transformation will start from the same initial condition. The key to the magic is the initial 
condition of each frame. The glMatrixMode command ensures that the next commands will 
affect the ModelView matrix.

First, the glLoadIdentity command makes sure you adjust the ModelView matrix starting 
from the identity matrix each frame. The next two commands rotate and translate the matrix. 
The matrix basically represents the position and orientation of the entire scene, so the 
transformations before the Apply calls move the scene. The subsequent transformations 
place the objects starting from that condition.

PAGE 334
 The angl field changes with user input, and only the y-axis is non-zero, so the user can turn 
the scene about the y-axis, giving the appearance that the user, with a first-person viewpoint, 
turns in the opposite direction. The wx and wz fields also respond to user input, moving the 
scene and giving the appearance the user moves left and right and back and forth, but not up 
and down.

procedure TForm1.FormPaint(Sender: TObject);	
begin	
  glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT);	
	
  glMatrixMode(GL_MODELVIEW);	
  glLoadIdentity;	
  glRotatef(angl,0.0,1.0,0.0);	
  glTranslatef(wx,0.0,wz);	
	
  TransformLight.Apply;	
  TransformFloor.Apply;	
  TransformCube.Apply;	
	
  OpenGL1.Swap;	
  OpenGL1.ErrorCheck('FormPaint');	
end;	
The FormResize method does the usual. The TransformFloorPaint method draws a very 
large horizontal rectangle even with the bottom of the viewport. The TransformLightPaint 
method executes the lighting commands and draws a cube around the light. The 
TransformCubePaint method draws a cube sitting on the “floor.”

procedure TForm1.FormResize(Sender: TObject);	
begin	
  if OpenGL1.Smaller then	
    FormPaint(Sender);	
end;	
	
procedure TForm1.TransformFloorPaint(Sender: TObject);	
begin	
  MaterialFloor.Apply;	
  glBegin(GL_POLYGON);	
    glVertex3f(-29.0,0.0,-29.0);	
    glVertex3f(-29.0,0.0,+29.0);	
    glVertex3f(+29.0,0.0,+29.0);	
    glVertex3f(+29.0,0.0,-29.0);	
  glEnd;	
end;	
	
procedure TForm1.TransformLightPaint(Sender: TObject);	
begin	


PAGE 335
   Light1.Apply;	
  MaterialLight.Apply;	
  Cube;	
end;	
	
procedure TForm1.TransformCubePaint(Sender: TObject);	
begin	
  MaterialCube.Apply;	
  Cube;	
end;	

The FormKeyDown method receives the user input that guides the drive-through as the user 
holds down certain keys. The method does not directly set the wx, wz, and angl fields. 
Instead it sets the tran and rot fields. The tran field is either zero (not moving) or equal to the 
value of the speed field (moving at that rate). The rot field is either zero (not turning) or 
equal to the value of the rspeed field (turning at that rate). The Idle method in turn uses 
those values to adjust the wx, wz, and angl fields.

The Up Arrow key sets a positive speed and the Down Arrow key sets a negative speed for 
backing up (or actually moving the scene forward). The Left Arrow key sets a negative 
rotational speed to turn the scene to the right and make the user seem to turn to the left; the 
Right Arrow key sets a positive rotational speed for the opposite effect. The S key adjusts the 
value of the speed field, increasing it if the user is also holding down the Shift key, 
decreasing it otherwise. Similarly, the R key adjusts the size of the rspeed field.

The Escape key closes the main form, thereby exiting the application.

procedure TForm1.FormKeyDown(Sender: TObject; var Key: Word;	
  Shift: TShiftState);	
begin	
  case Key of	
    VK_UP: tran := +speed;	
    VK_DOWN: tran := -speed;	
    VK_LEFT: rot := -rspeed;	
    VK_RIGHT: rot := +rspeed;	
    VK_ESCAPE: Close;	
    ord('R'): begin	
      if ssShift in Shift then	
        rspeed := rspeed+0.01	
      else begin	
        if rspeed>0.01 then	
          rspeed := rspeed-0.01;	
      end;	
    end;	
    ord('S'): begin	
      if ssShift in Shift then	


PAGE 336
         speed := speed+0.001	
      else begin	
        if speed>0.001 then	
          speed := speed-0.001;	
      end;	
    end;	
  end; {case}	
end;	
The FormKeyUp method stops any motion when the user releases the keys by setting the 
tran and rot fields to zero:

procedure TForm1.FormKeyUp(Sender: TObject; var Key: Word;	
  Shift: TShiftState);	
begin	
  tran := 0.0;	
  rot := 0.0;	
end;	
The Idle method tracks frame rate just as in the previous chapter. It also calls the Adjust 
method to handle the animation:

procedure TForm1.Idle(Sender:TObject;var Done:boolean);	
const	
  rollover=1000;	
  mspersec=1000.0;	
begin	
  Done := false;	
  Adjust;	
  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;	
Other Methods
The Adjust method accumulates the rot and tran fields into the values of the wx, wz, and 
angl fields. When rot and tran are zero, the positional fields remain static, but when they are 
non-zero, the positional fields grow with each frame. Notice that the method uses a little 
standard trigonometry to coordinate the rotation and translation to give a realistic 
appearance of first-person movement.

PAGE 337
 
procedure TForm1.Adjust;	
var	
  theta: GLfloat;	
begin	
  angl := angl+rot;	
  theta := angl*pi180;	
  wx := wx-tran*sin(theta);	
  wz := wz+tran*cos(theta);	
end;	
Results
Save, compile, and run. Figure 13-1 shows the starting view. Hold down a Shift key and tap 
the S key a few times to establish a decent speed, then release. Notice that when you press 
the Up Arrow key, you seem to advance forward into the scene. Hold down the Up Arrow 
key until you are close to the blue cube, but not in it. Figure 13-2 shows this view.
Now hold down the Up Arrow key until you seem to pass through the cube and reach the 
edge of the floor. Hold down a Shift key and tap the R key a few times to set a reasonable 
rotational speed, then release. Turn right by holding down the Right Arrow key until the 
cube is again in the center of the form as in Figure 13-3. From this distant view of the back 
side you can see the elevated cube surrounding the light. The fact that the side toward you is 
white indicates that the light is pointing generally toward you, since the orientation of that 
cube matches the orientation of the light.
Turn left with the Left Arrow key until the blue cube just moves off the right side of the 
form. Go forward a little ways with the Up Arrow key. Try to estimate about a quarter of the 
way across the floor. Turn right until the blue cube fully appears again. Advance forward 
until you just open up a corner of the cube, as in Figure 13-4. Press the Escape key to exit.





    Figure 13-4. Approaching from another angle.

PAGE 338
 Opening
That was one basic technique down, several more to go. The next feature is an opening 
through which the user will view the scene. The challenge is to make the opening shift with 
the user rather than with the rest of the scene.
Project
Create a directory under the Chapter.13 directory and name it Open. Save a new project in it 
named Open0.Dpr with a unit named Open1.Pas. Make this project just like the Simple0 
project. Differences will become evident along the way.
Properties
In addition to the components like those of the previous project, drop on another 
TTransform component and another TMaterial component. Since a few properties on the 
other components are different from those of the previous project, Listing 13-2 includes the 
entire form rather than just the additional components:

object Form1: TForm1	
  Left = 192	
  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	
  OnDestroy = FormDestroy	
  OnKeyDown = FormKeyDown	
  OnKeyUp = FormKeyUp	
  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]	


PAGE 339
     ClearRed = 0.6	
    ClearGreen = 0.8	
    ClearBlue = 0.8	
    prxLeft = -1	
    prxRight = 1	
    prxBottom = -1	
    prxTop = 1	
    prxNear = 2	
    prxFar = 42	
    ColorBits = 24	
    DepthBits = 32	
    StencilBits = 0	
    Left = 4	
    Top = 8	
  end	
  object TransformLight: TTransform	
    TranY = 5	
    TranZ = -3	
    AxisX = 1	
    Angle = -20	
    ScaleX = 1	
    ScaleY = 1	
    ScaleZ = 1	
    OnPaint = TransformLightPaint	
    Left = 4	
    Top = 44	
  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 = 44	
  end	
  object MaterialLight: TMaterial	
    ambRed = 1	
    ambGreen = 1	


PAGE 340
     ambBlue = 1	
    ambAlpha = 1	
    difRed = 1	
    difGreen = 1	
    difBlue = 1	
    difAlpha = 1	
    aFace = faFront	
    dFace = faFront	
    sFace = faFront	
    Left = 76	
    Top = 44	
  end	
 object TransformFloor: TTransform	
    TranY = -1	
    ScaleX = 1	
    ScaleY = 1	
    ScaleZ = 1	
    OnPaint = TransformFloorPaint	
    Left = 4	
    Top = 80	
  end	
  object MaterialFloor: TMaterial	
    ambRed = 0.7	
    ambGreen = 0.5	
    ambBlue = 0.1	
    ambAlpha = 1	
    difRed = 0.7	
    difGreen = 0.7	
    difBlue = 0.1	
    difAlpha = 1	
    aFace = faFront	
    dFace = faFront	
    sFace = faFront	
    Left = 40	
    Top = 80	
  end	
  object TransformCube: TTransform	
    TranX = 3.5	
    TranY = -0.6	
    TranZ = -10	
    ScaleX = 1	
    ScaleY = 1	
    ScaleZ = 1	
    OnPaint = TransformCubePaint	
    Left = 4	
    Top = 116	
  end	
  object MaterialCube: TMaterial	
    ambBlue = 1	


PAGE 341
     ambAlpha = 1	
    difBlue = 1	
    difAlpha = 1	
    aFace = faFront	
    dFace = faFront	
    sFace = faFront	
    mShine = 128	
    Left = 40	
    Top = 116	
  end	
  object TransformCyl: TTransform	
    TranX = -3	
    TranY = -0.6	
    TranZ = -9	
    AxisX = 1	
    Angle = 90	
    ScaleX = 1	
    ScaleY = 1	
    ScaleZ = 1	
    OnPaint = TransformCylPaint	
    Left = 4	
    Top = 152	
  end	
  object MaterialCyl: TMaterial	
    ambGreen = 1	
    ambAlpha = 1	
    difGreen = 1	
    difAlpha = 1	
    aFace = faFront	
    dFace = faFront	
    sFace = faFront	
    mShine = 128	
    Left = 40	
    Top = 152	
  end	
end	
Listing 13-2. Open0 project properties.
Private
The private section of the form declaration has one additional element. The Opening method 
draws polygons to represent the border of the view onto the scene. Think of the polygons as 
part of a bulkhead in your armored exploration vehicle. Figure 13-5 shows how that will 
look.

    procedure Opening;						

PAGE 342
 Event Handlers
The FormCreate method has an additional call to prepare for drawing a cylinder:

procedure TForm1.FormCreate(Sender: TObject);	
begin	
  glLightModeli(GL_LIGHT_MODEL_TWO_SIDE,GL_TRUE);	
  Application.OnIdle := Idle;	
  CylinderBase;	
end;	
The FormPaint method now calls the Apply method of the added TTransform component to 
draw a green cylinder in addition to the blue cube. More importantly, the method calls 
Opening to draw the bulkhead. The precise placement of this call is critical. If the call to the 
Opening method appears after the calls to glRotatef and glTranslatef, the polygons will 
move with the rest of the scene. If Opening executes before the call to glLoadIdentity, the 
polygons that compose the bulkhead will be affected by the transformation calls in the 
previous frame. By calling the Opening method just after the call to glLoadIdentity, you 
ensure that the bulkhead remains fixed with respect to the client area of the form, which is 
the viewport.

procedure TForm1.FormPaint(Sender: TObject);	
begin	
  glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT);	
	
  glMatrixMode(GL_MODELVIEW);	
  glLoadIdentity;	


PAGE 343
 	
  Opening; 	
	
  glRotatef(angl,0.0,1.0,0.0);	
  glTranslatef(wx,0.0,wz);	
	
  TransformLight.Apply;	
  TransformFloor.Apply;	
  TransformCube.Apply;	
  TransformCyl.Apply;	
	
  OpenGL1.Swap;	
  OpenGL1.ErrorCheck('FormPaint');	
end;	
Except for one new event handler, the rest of the event handler methods remain the same as 
the ones in the Simple0 project. The new event handler draws the green cylinder:

procedure TForm1.TransformCylPaint(Sender: TObject);	
begin	
  MaterialCyl.Apply;	
  Cylinder;	
end;	
Other Methods
The Adjust method remains the same as before and needs no further elaboration. However, 
the new method does bear a little examination.
Now comes the unveiling of the often-mentioned Opening method. It just draws a few 
rectangular polygons that outline most of the viewport. It does not have its own TMaterial 
component call, so it will respond to the commands from the last used TMaterial component, 
which is MaterialCyl that makes the cylinder green. In a later project you will see why the 
choice of material properties for the bulkhead does not really matter.

procedure TForm1.Opening;	
begin	
  glBegin(GL_POLYGON);	
    glVertex3f(-1.0,+1.0,-2.0);	
    glVertex3f(-1.0,-1.0,-2.0);	
    glVertex3f(-0.7,-1.0,-2.0);	
    glVertex3f(-0.7,+1.0,-2.0);	
  glEnd;	
	
  glBegin(GL_POLYGON);	
    glVertex3f(-0.7,+1.0,-2.0);	
    glVertex3f(-0.7,+0.7,-2.0);	


PAGE 344
     glVertex3f(-0.4,+0.7,-2.0);	
    glVertex3f(-0.4,+1.0,-2.0);	
  glEnd;	
	
  glBegin(GL_POLYGON);	
    glVertex3f(+0.7,+1.0,-2.0);	
    glVertex3f(+0.7,-1.0,-2.0);	
    glVertex3f(+1.0,-1.0,-2.0);	
    glVertex3f(+1.0,+1.0,-2.0);	
  glEnd;	
	
  glBegin(GL_POLYGON);	
    glVertex3f(+0.4,+1.0,-2.0);	
    glVertex3f(+0.4,+0.7,-2.0);	
    glVertex3f(+0.7,+0.7,-2.0);	
    glVertex3f(+0.7,+1.0,-2.0);	
  glEnd;	
end;	
Results
Save, compile, and run. Figure 13-5 shows the initial appearance. Use the commands familiar 
from the last project to drive around the scene. Figure 13-6 shows the scene if you back up 
enough to show the location of the light source.

PAGE 345
 Textured Opening
Now you can add a little more detail to your armored vehicle. When you add texture with 
GL_DECAL to the bulkhead you will recognize that material properties affecting those 
polygons are, in the end, not important.
Project
Create a directory named Texture under the Chapter.13 directory. Save a new project in it 
named Texture0.Dpr, with a unit named Texture1.Pas. Drop on the same components you 
did in the Open0 project and set properties similarly. The only changes are to decrease by 0.2 
the TranY property of all the TTransform components except for TransformLight.
Other Differences
The floor has moved from TranY = –1.0 to TranY = –1.2 so that your viewport is not scraping 
along the surface. Naturally, the cube and the cylinder had to lower their TranY as well, to 
stay in touch with their “roots.”
Copy Texture
Copy Fig13-07.bmp from the companion CD into this project’s directory. Figure 13-7 at right 
shows the bulkhead texture.






         Figure 13-7. Bulkhead texture.
Prepare to Read Texture
Of course you need to read in and process the texture image. First add some constants, 
types, and variables to the top of the implementation section:

PAGE 346
 
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	
  sBulk,	
  tBulk: GLsizei;	
  bulk: pointer;	
Add the ReadBitmap procedure just after the variables:

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,	
  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	
    sWidth := biWidth;	
    tHeight := biHeight;	
  end;	
  getmem(result,size);	
  blockread(bmpfile,result^,size);	


PAGE 347
   for x := 0 to sWidth*tHeight-1 do	
  with TWrap(result^)[x] do	
  begin	
    t := r;	
    r := b;	
    b := t;	
  end;	
end;	
Initialize Texture
Add some texture setup code to the top of the FormCreate method. The bulk variable will 
point to a 64x64 texture image. The method uses GL_DECAL so that the bulkhead looks the 
same regardless of the lighting in the rest of the scene. You should replace the simple 
filename with the full path to the file on your computer. You also need to add a 
FormDestroy method to dispose of the bitmap memory. Remember the Delphi 2 adjustments 
if appropriate.

procedure TForm1.FormCreate(Sender: TObject);	
begin	
  glPixelStorei(GL_UNPACK_ALIGNMENT,1);	
  glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_S,GL_REPEAT);	
  glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_T,GL_REPEAT);	
  glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_NEAREST);	
  glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_NEAREST);	
  glTexEnvf(GL_TEXTURE_ENV,GL_TEXTURE_ENV_MODE,GL_DECAL);	
  bulk := ReadBitmap('fig13-07.bmp',sBulk,tBulk);	
	
  glLightModeli(GL_LIGHT_MODEL_TWO_SIDE,GL_TRUE);	
  Application.OnIdle := Idle;	
  CylinderBase;	
end;	
	
procedure TForm1.FormDestroy(Sender: TObject);	
begin	
  FreeMem(bulk);	
end;	
Apply the Texture
All that is left is to tack the textures onto the vertices of the polygons in the Opening method. 
Be sure to enable texturing (2D) at the top of the method, then disable it at the bottom. Use it 
only where needed, otherwise non-textured surfaces will not display properly. Notice that 
the larger polygons take advantage of GL_REPEAT by using texture coordinates larger than 
1.0.

PAGE 348
 
procedure TForm1.Opening;	
begin	
  glEnable(GL_TEXTURE_2D);	
	
  glTexImage2D(GL_TEXTURE_2D,Level,ColorComps,sBulk,tBulk,Border,	
    GL_RGB,GL_UNSIGNED_BYTE,bulk);	
	
  glBegin(GL_POLYGON);	
    glTexCoord2f(0.0,0.0); glVertex3f(-1.0,+1.0,-2.0);	
    glTexCoord2f(0.0,7.0); glVertex3f(-1.0,-1.0,-2.0);	
    glTexCoord2f(1.0,7.0); glVertex3f(-0.7,-1.0,-2.0);	
    glTexCoord2f(1.0,0.0); glVertex3f(-0.7,+1.0,-2.0);	
  glEnd;	
	
  glBegin(GL_POLYGON);	
    glTexCoord2f(0.0,0.0); glVertex3f(-0.7,+1.0,-2.0);	
    glTexCoord2f(0.0,1.0); glVertex3f(-0.7,+0.7,-2.0);	
    glTexCoord2f(1.0,1.0); glVertex3f(-0.4,+0.7,-2.0);	
    glTexCoord2f(1.0,0.0); glVertex3f(-0.4,+1.0,-2.0);	
  glEnd;	
	
  glBegin(GL_POLYGON);	
    glTexCoord2f(0.0,0.0); glVertex3f(+0.7,+1.0,-2.0);	
    glTexCoord2f(0.0,7.0); glVertex3f(+0.7,-1.0,-2.0);	
    glTexCoord2f(1.0,7.0); glVertex3f(+1.0,-1.0,-2.0);	
    glTexCoord2f(1.0,0.0); glVertex3f(+1.0,+1.0,-2.0);	
  glEnd;	
	
  glBegin(GL_POLYGON);	
    glTexCoord2f(0.0,0.0); glVertex3f(+0.4,+1.0,-2.0);	
    glTexCoord2f(0.0,1.0); glVertex3f(+0.4,+0.7,-2.0);	
    glTexCoord2f(1.0,1.0); glVertex3f(+0.7,+0.7,-2.0);	
    glTexCoord2f(1.0,0.0); glVertex3f(+0.7,+1.0,-2.0);	
  glEnd;	
	
  glDisable(GL_TEXTURE_2D);	
	
end;	
Result
Save, compile, and run. Now you have a fancier display. See Figure 13-8. As you can expect 
with more to render, the frame rate has dropped considerably, although it is still quite good. 
With the slower frame rate you will have to jack up the motion rates with the S and R keys 
quite a bit more in order to move around at a decent speed.

PAGE 349
 
Collision Detection
Perhaps you enjoyed opening up the cylinder and cube with the zNear clipping plane. While 
that can be a very interesting effect, it is not usually the desired effect. You probably have 
thought about the need for preventing your vehicle from traveling through the objects in the 
scene. This is one use of collision detection, in which you use a suitable algorithm to 
determine if two or more objects in your scene intersect. Exact solutions can be 
computationally expensive, so collision detection often involves approximations and 
simplifying assumptions.
Project
Create a new directory under the Chapter.13 directory named Detect. Save a new project in it 
named Detect0.Dpr, with a unit named Detect1.Pas. Make this project initially just like the 
Texture0 project.
Distance
The Adjust method modifies the values of wx and wz, which are the coordinates of the 
viewer, and therefore you might expect them to be the coordinates of the portal through the 
bulkhead of your vehicle. In reality, your portal lies on the zNear clipping plane, which most 
of the example programs, including this one, set at 2.0 units away from the viewer into the 
screen. Your distance calculations must take the extra space into account.

PAGE 350
 Before you change wx and wz, be sure to save them, in savex and savez, naturally. In a 
more elaborate collision detection scheme you would also need to save the value of the angl 
field, but the present approach makes some simplifying assumptions about the shape of the 
vehicle so you will not produce a collision merely by turning.

If the new values of wx and wz would result in a collision, simply restore the former values 
so your vehicle does not move in that frame. Adjust the contents of the Adjust method as 
described in the following paragraphs.

First you need some additional local variables. The viewerx and viewerz variables track the 
viewer’s position in the same coordinate system the objects in the scene use. The deltax and 
deltaz variables store intermediate values in the distance calculations and allow more 
readable code. The distance variable, as you would expect, stores the result of distance 
calculations. You already know about savex and savez.

procedure TForm1.Adjust;	
var	
  theta,	
  viewerx,	
  viewerz,	
  deltax,	
  deltaz,	
  distance,	
  savex,	
  savez: GLfloat;	

Next you need a little nested function to handle restoring old values to prevent movement. It 
is a Boolean function that always returns a result value of true for the sake of simpler if 
statements. As an added touch, it sets the form’s caption to “Bump!” when a collision occurs.

function restore: boolean;	
begin	
  result := true;	
  wx := savex;	
  wz := savez;	
  Caption := 'Bump!'	
end;	

The body of the Adjust method starts out almost the same as you have seen before, except 
that it saves the viewer position variables before changing them, and it converts the viewer 
coordinate system. The wx and wz fields have opposite signs since they are used for moving 
the entire scene in the opposite direction from the apparent movement of the viewer. For the 
sake of calculations of the distance from viewer to object, the viewerx and viewerz variables 
reverse the signs.

PAGE 351
 
begin	
  angl := angl+rot;	
  theta := angl*pi180;	
  savex := wx;	
  wx := wx-tran*sin(theta);	
  savez := wz;	
  wz := wz+tran*cos(theta);	
	
  viewerz := -wz;	
  viewerx := -wx;	

The rest of the Adjust method is strictly new code added since the last project. The following 
block of code calculates the distance from the viewer to the cylinder. The delta variables 
simply receive the difference between each of the viewer coordinates and the center of the 
cylinder. The distance is the square root of the sum of the squares, just like the calculation of 
the hypotenuse of a right triangle. If the distance resulting from the new position is less than 
a specified amount, restore the old positions, alert the user, and exit. Perform no further tests 
once a test fails.

The number 2.515 merits some discussion. Remember that the viewport is always 2.0 units 
“ahead” of the viewer. That explains the 2, and insider knowledge of the drawing of the 
cylinder reveals that the surface will always be 0.4 units (the radius of the base) closer than 
the center in any direction. That accounts for 2.4, but what about the remaining 0.115 units? 
You could call it a “fudge factor,” but if you are in a charitable mood you can consider it an 
allowance for an unspecified rounded bumper below the viewport. This extra distance 
ensures that the vehicle stops at a point where rotation will not be a factor.

You could specify the shape of the vehicle precisely and calculate distances accordingly. 
That would buy you extremely little added realism at the cost of a lot of added complexity of 
code that would merely distract from this introduction to collision detection.

  with TransformCyl do	
  begin	
    deltax := TranX-viewerx;	
    deltaz := TranZ-viewerz;	
    distance := sqrt(deltax*deltax+deltaz*deltaz);	
    if (distance<2.515) and restore then	
      exit;	
  end;	

Collision detection with the cube is similar but has its own unique characteristics. It starts 
out in a familiar way, calculating a distance to the center of the cube. The distance from the 
center to a face is 0.4 units, but the 

PAGE 352
 distance from the center to a corner is about 0.566 units. Add in 2.0 units for distance from 
viewer to viewport, plus a small bumper allowance to obtain the mysterious 2.67. Without 
the bumper allowance you could hit the cube just right without entering it, but a turn to the 
left or right could still tear the cube open.

Notice that approaching within the specified distance does not automatically call the restore 
function and exit. This distance is a worst-case value for when you drive straight toward the 
center of the cube with a corner of the cube exactly in your path. Since approaching from 
other directions could allow you to move closer without a collision, this is just an initial test 
to determine if the method should make further calculations.

  with TransformCube do	
  begin	
    deltax := TranX-viewerx;	
    deltaz := TranZ-viewerz;	
    distance := sqrt(deltax*deltax+deltaz*deltaz);	
    if (distance<2.67) then	
    begin	

If your proposed motion does indeed fail the initial distance test, the method then tests the 
distance from each corner. Again, the whole part of the number (2.0 units) allows for the 
distance from the viewer to the viewport. Allow 0.4 units since that is the distance in the x 
direction and the distance in the z direction from the center of the cube to a given vertex. The 
remainder of the 2.42 is the omnipresent bumper allowance.

For this particular corner, add +0.4 units to TranX and add +0.4 units to TranZ. If the 
proposed distance is less than the given amount, the test has failed; restore the old position 
values, alert the user, and exit. No further tests are useful.

     begin	
       if (distance<2.42) and restore then	
         exit;	
       deltax := TranX+0.4-viewerx;	
       deltaz := TranZ+0.4-viewerz;	
       distance := sqrt(deltax*deltax+deltaz*deltaz);	
       if (distance<2.42) and restore then	
         exit;	

The remaining three corners get the same test. The method specifies each corner by using all 
combinations of +0.4 and –0.4 with the center coordinates. In each case, if the motion fails the 
test for any corner, it does not need to check any remaining corners.

PAGE 353
 
       deltax := TranX+0.4-viewerx;	
       deltaz := TranZ-0.4-viewerx;	
       distance := sqrt(deltax*deltax+deltaz*deltaz);	
       if (distance<2.42) and restore then	
         exit;	
	
       deltax := TranX-0.4-viewerx;	
       deltaz := TranZ+0.4-viewerz;	
       distance := sqrt(deltax*deltax+deltaz*deltaz);	
       if (distance<2.42) and restore then	
         exit;	
When the method tests against the final corner, calling exit is not necessary, but duplicating 
the code for the other corners is convenient:

       deltax := TranX-0.4-viewerx;	
       deltaz := TranZ-0.4-viewerz;	
       distance := sqrt(deltax*deltax+deltaz*deltaz);	
       if (distance<2.42) and restore then	
         exit;	
    end;	
  end;	
end;	
Results	
Save, compile, and run. Drive around as usual. This time you will not be able to penetrate 
the objects in the scene, as Figure 13-9 suggests. Since the program effectively treats the 
vehicle as if it were just as long behind the viewer as in front of the viewer, you will not be 
able to back into any objects either. The shape of the vehicle has become a rounded rectangle.


PAGE 354
 Textured Objects
Are you a little tired of the cylinder and the cube? Take heart; this example will place more 
interesting objects in the scene.
Project
Create a directory under the Chapter.13 directory and name it Real. Save a new project in it 
named Real0.Dpr, with a unit named Real1.Pas. Drop on a TOpenGL component, a TLight 
component, five TTransform components, and four TMaterial components. Make the 
TOpenGL and TLight components just like their counterparts in the previous project, as well 
as the TTransform component for the TLight. Also appropriate TransformFloor and 
MaterialFloor from the previous project.
Properties
The components for the Real0 project set up a textured brick wall, a light pole supporting a 
box around a spotlight, and more. Listing 13-3 shows the properties for this project:

object Form1: TForm1	
  Left = 192	
  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	
  OnDestroy = FormDestroy	
  OnKeyDown = FormKeyDown	
  OnKeyUp = FormKeyUp	
  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]	


PAGE 355
     ClearRed = 0.6	
    ClearGreen = 0.8	
    ClearBlue = 0.8	
    prxLeft = -1	
    prxRight = 1	
    prxBottom = -1	
    prxTop = 1	
    prxNear = 2	
    prxFar = 42	
    ColorBits = 24	
    DepthBits = 32	
    StencilBits = 0	
    Left = 4	
    Top = 8	
  end	
  object TransformLight: TTransform	
    TranY = 5	
    TranZ = -3	
    AxisX = 1	
    Angle = -20	
    ScaleX = 1	
    ScaleY = 1	
    ScaleZ = 1	
    OnPaint = TransformLightPaint	
    Left = 4	
    Top = 44	
  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 = 44	
  end	
  object MaterialLight: TMaterial	
    ambRed = 1	
    ambGreen = 1	


PAGE 356
     ambBlue = 1	
    ambAlpha = 1	
    difRed = 1	
    difGreen = 1	
    difBlue = 1	
    difAlpha = 1	
    aFace = faFront	
    dFace = faFront	
    sFace = faFront	
    Left = 76	
    Top = 44	
  end	
  object TransformFloor: TTransform	
    TranY = -0.2	
    ScaleX = 1	
    ScaleY = 1	
    ScaleZ = 1	
    OnPaint = TransformFloorPaint	
    Left = 4	
    Top = 124	
  end	
  object MaterialFloor: TMaterial	
    ambRed = 0.7	
    ambGreen = 0.5	
    ambBlue = 0.1	
    ambAlpha = 1	
    difRed = 0.7	
    difGreen = 0.7	
    difBlue = 0.1	
    difAlpha = 1	
    aFace = faFront	
    dFace = faFront	
    sFace = faFront	
    Left = 40	
    Top = 124	
  end	
  object TransformWall: TTransform	
    TranY = -0.2	
    TranZ = -7	
    ScaleX = 1	
    ScaleY = 1	
    ScaleZ = 1	
    OnPaint = TransformWallPaint	
    Left = 4	
    Top = 160	
  end	
  object MaterialWall: TMaterial	
    ambRed = 1	
    ambGreen = 1	


PAGE 357
     ambBlue = 1	
    ambAlpha = 1	
    difRed = 1	
    difGreen = 1	
    difBlue = 1	
    difAlpha = 1	
    aFace = faFront	
    dFace = faFront	
    sFace = faFront	
    Left = 40	
    Top = 160	
  end	
  object TransformPole: TTransform	
    TranY = 1.675	
    TranZ = -3	
    AxisX = 1	
    Angle = 90	
    ScaleX = 0.1	
    ScaleY = 0.1	
    ScaleZ = 7.75	
    OnPaint = TransformPolePaint	
    Left = 4	
    Top = 76	
  end	
  object MaterialPole: TMaterial	
    ambAlpha = 1	
    difAlpha = 1	
    aFace = faFront	
    dFace = faFront	
    sFace = faFront	
    mShine = 128	
    Left = 40	
    Top = 76	
  end	
  object TransformGlobe: TTransform	
    TranX = -7	
    TranY = 0.8	
    TranZ = -9	
    AxisX = 1	
    ScaleX = 1	
    ScaleY = 1	
    ScaleZ = 1	
    OnPaint = TransformGlobePaint	
    Left = 4	
    Top = 196	
  end	
end
Listing 13-3. Real0 project properties.

PAGE 358
 Methods and Other Routines
From the Detect0 project, borrow the ReadBitmap function and the FormKeyDown, 
FormResize, FormKeyUp, TransformLightPaint, TransformFloorPaint, Opening, and Idle 
methods. Also use the Adjust method, although you will need to adapt its collision detection 
for the different objects in the scene.
A Light Pole
One tiny step toward more realism is to stop the light from being a vagabond with no visible 
means of support. The Real0 project borrows the TransformCyl and MaterialCyl components 
from the Detect0 project, renaming them TransformPole and MaterialPole, respectively. The 
Scale properties make the cylinder long and skinny, the material color properties are all 0.0 
to make a black pole, and the Tran properties make the pole line up with the cube around 
the light. The OnPaint event handler method is the same as the previous project’s 
TransformCylPaint method except the names have been changed:

procedure TForm1.TransformPolePaint(Sender: TObject);	
begin	
  MaterialPole.Apply;	
  Cylinder;	
end;	
An Array of Vertices
The next object has a sizable number of vertices; rather than writing a lot of individual 
glVertex3f calls, you can use an array of vertices. First declare a type you can use as a vertex 
ahead of the form type declaration:

type	
  TPoint=packed record	
    x,y,z: GLfloat;	
  end; {TPoint}	
  TForm1 = class(TForm)	
You will code a single call to glVertex3f in a loop, using the array. That means you must 
already have values in the array. In the private section of the form declaration, declare an 
array and a method to fill it:

    wall: array[1..28] of TPoint;	
    procedure BuildWall;	

PAGE 359
 A Wall
You can reuse the TransformCube component and call it TransformWall. Use MaterialCube, 
rename it MaterialWall, and make all the color properties 1.0. In the OnPaint event handler, 
do not call the Cube procedure; you will create a custom object. The wall field contains the 
vertices for drawing the object (a wall, of course). At the bottom of the FormCreate method 
add a call to the BuildWall method:

  CylinderBase;	
  BuildWall;	
end;	
Just above the FormCreate method, code the BuildWall method. The program will render the 
wall using a triangle strip. Since the wall will run parallel to the x-axis and y-axis, the 
algorithm is very simple. The z value retains the zero it had by virtue of being a field of the 
form. The x value stays the same for each two vertices, then increases by one for the next 
pair. The y value alternates between +1 and –1, making a height of two for the wall. Delphi’s 
odd function is very convenient for this pairing and alternating. The first pair of vertices for 
the wall will have an x value of –1.0.

procedure TForm1.BuildWall;	
var	
  q: GLfloat;	
  ix: integer;	
begin	
  q := -2.0;	
  for ix := 1 to 14 do	
  with wall[ix] do	
  if odd(ix) then	
  begin	
    q := q+1.0;	
    x := q;	
    y := +1.0;	
  end else begin	
    x := q;	
    y := -1.0;	
  end;	
The for loop created vertices for a triangle strip 14 units long, but that is not the end of the 
story. The strip must turn a corner, proceed a half unit down the negative z direction, turn 
another corner, and head back in the negative x direction to form a rectangular solid. Jog the 
q value before the next loop so that the x value will start where it left off in the previous 
loop:

PAGE 360
 
  q := q+1.0;	
  for ix := 15 to 28 do	
  with wall[ix] do	
  if odd(ix) then	
  begin	
    q := q-1.0;	
    x := q;	
    y := +1.0;	
    z := -0.5;	
  end else begin	
    x := q;	
    y := -1.0;	
    z := -0.5;	
  end;	
end;	

Once the array has its values, the appropriate OnPaint method can render it as a triangle 
strip. To appear as a brick wall, the object needs a texture, which you can see in Figure 13-10.



You need a pointer variable to point to the texture memory and a couple of variables to store 
the dimensions of the brick texture. Declare them in the implementation section:

var	
  sBulk,	
  tBulk,	
  sBrick,	
  tBrick: GLsizei;	
  globe,	
  bulk,	
  brick: pointer;	
In the FormCreate method, load the bitmap and convert it to a texture right after the texture 
for the bulkhead. Don’t forget to copy Fig13-10.bmp 

PAGE 361
 from the CD to the directory for this program. Also remember to use the full path instead of 
just the simple filename shown below.

  bulk := ReadBitmap('fig13-07.bmp',sBulk,tBulk);	
  brick := ReadBitmap('fig13-10.bmp',sBrick,tBrick);	
The FormDestroy method gives back the texture memory:

procedure TForm1.FormDestroy(Sender: TObject);	
begin	
  FreeMem(bulk);	
  FreeMem(brick);	
end;	

The TransformWallPaint method renders the strip. First enable two-dimensional texturing 
and point to the brick texture:

procedure TForm1.TransformWallPaint(Sender: TObject);	
var	
  ix: integer;	
  s: GLfloat;	
  t: array[false..true] of GLfloat;	
  tt: boolean;	
begin	
  glEnable(GL_TEXTURE_2D);	
  glTexImage2D(GL_TEXTURE_2D,Level,ColorComps,sBrick,tBrick,Border,	
    GL_RGB,GL_UNSIGNED_BYTE,Brick);	

Apply the texture and initialize some local variables. The local variables s and t provide the 
texture coordinates. The s texture coordinate starts with zero and increases by two every 
other vertex. The t texture coordinate alternates between two values. An array from false to 
true provides a convenient repository for the two alternating values. The tt Boolean variable 
provides the switch for selecting between the two t texture values.

  MaterialWall.Apply;	
  s := 0.0;	
  t[false] := 0.0;	
  t[true] := 4.0;	
  tt := false;	

Draw the triangle strip in a loop and tack on the repeating texture image:

  glBegin(GL_TRIANGLE_STRIP);	
    for ix := 1 to 28 do	
    with wall[ix] do	
    begin	
      glTexCoord2f(s,t[tt]);	
      glVertex3f(x,y,z);	
      if tt then	
        s := s+2.0;	


PAGE 362
       tt := not tt;	
    end;	
Close the gap by connecting the dots between the beginning and the end:

    for ix := 1 to 2 do	
    with wall[ix] do	
    begin	
      glTexCoord2f(s,t[tt]);	
      glVertex3f(x,y,z);	
      if tt then	
        s := s+2.0;	
      tt := not tt;	
    end;	
Finish the rendering step and turn off the texturing to protect the rest of the scene:

  glEnd;	
  glDisable(GL_TEXTURE_2D);	
end;	
Paint
The FormPaint method at this point should apply the transformations for the light, the pole, 
the wall, and the floor. Continue using the first-person movement and the buffer swapping.

procedure TForm1.FormPaint(Sender: TObject);	
begin	
  glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT);	
	
  glMatrixMode(GL_MODELVIEW);	
  glLoadIdentity;	
	
  Opening;	
	
  glRotatef(angl,0.0,1.0,0.0);	
  glTranslatef(wx,0.0,wz);	
	
  TransformLight.Apply;	
  TransformPole.Apply;	
  TransformWall.Apply;	
  TransformFloor.Apply;	
	
  OpenGL1.Swap;	
  OpenGL1.ErrorCheck('FormPaint');	
end;	

PAGE 363
 Adjust the Adjust Method
You should make the collision detection in the Adjust method fit the current objects. The 
nested restore function is the same as before. The variables are also the same, but an integer 
joins the list:

procedure TForm1.Adjust;	
var	
  theta,	
  viewerx,	
  viewerz,	
  deltax,	
  deltaz,	
  distance,	
  savex,	
  savez: GLfloat;	
  ix: integer;	
	
function restore: boolean;	
begin	
  result := true;	
  wx := savex;	
  wz := savez;	
  Caption := 'Bump!'	
end;	
The first-person position adjustment code remains the same as before:

begin	
  angl := angl+rot;	
  theta := angl*pi180;	
  savex := wx;	
  wx := wx-tran*sin(theta);	
  savez := wz;	
  wz := wz+tran*cos(theta);	
	
  viewerz := -wz;	
  viewerx := -wx;	
Detecting the pole is very similar to detecting the previous cylinder, except that the radius of 
the pole is smaller:

  with TransformPole do	
  begin	
    deltax := TranX-viewerx;	
    deltaz := TranZ-viewerz;	
    distance := sqrt(deltax*deltax+deltaz*deltaz);	
    if (distance<2.115) and restore then	
      exit;	
  end;	

PAGE 364
 Replace the code for the cube with code for the wall, this time using just the vertices alone. 
Fortunately, those vertices are all available to you in an array. Use the new integer variable 
as the index into the vertex array:

  with TransformWall do	
  begin	
    ix := 0;	
    repeat	
      inc(ix,2);	
      with wall[ix] do	
      begin	
        deltax := x-viewerx;	
        deltaz := TranZ+z-viewerz;	
      end; 	
      distance := sqrt(deltax*deltax+deltaz*deltaz);	
      if (distance<2.42) and restore then	
        exit;	
    until ix=28;	
  end;	
end;	
Run
Save, compile, and run. Figure 13-11 shows the initial view, including the light pole and the 
wall. Drive around a bit. Figure 13-12 shows the wall and the light pole from a different 
angle.


PAGE 365
 More
You are not done yet. That was just a brief rest. For the next object you will need some new 
commands and constants from the OpenGL utility library. Such commands have a glu 
prefix. In the utility library are some convenient objects called quadrics, so named because 
the mathematical description of the three-dimensional surfaces are quadratic equations, which 
take the general form of

a1x2 + a2y2 + a3z2 + a4xy + a5yz + a6xz + a7x + a8y + a9z +a10 = 0	
where each of the subscripted constants could be any number, including zero, which would 
eliminate that term from the equation.
The surface this example program uses is a sphere. Its most familiar equation is:

x2 + y2 + z2 = r2	

which can easily be recast into the general quadratic equation format.

Do not panic if your high school algebra is a bit rusty. The math has been done for you in the 
OpenGL utility library. Here are some OpenGL commands that take out most of the pain of 
drawing quadrics. Naturally the objects will be collections of polygons rather than true 
curved surfaces, but you have already seen that you can do very nice approximations that 
way.

To create a new quadric object, use the gluNewQuadric function. Have a pointer variable 
ready to receive the function result. You can declare your variable to be a GLUquadricObj, 
but a general pointer will do just as well. Delphi’s OpenGL interface unit just declares the 
type as a pointer to an 

PAGE 366
 empty record structure. The region of memory the pointer references contains various 
settings, which you can call a state. If the call fails, it will return nil; otherwise the state 
describes how to draw a quadric, but not what to draw. For that (and other purposes) you 
will pass your pointer variable to other quadric commands wherever they have a parameter 
of that type.

function  gluNewQuadric: GLUquadricObj; stdcall;	

When you want to draw more than one quadric surface, but not at the same time, you can 
simply reuse the pointer, but for multiple quadrics in the same scene at the same time, you 
need to call the function separately for each simultaneous object. When you are finished with 
a given quadric object, and you are not going to reuse it, you need to be a good resource 
citizen and delete it with the gluDeleteQuadric command:

procedure gluDeleteQuadric(state:GLUquadricObj); stdcall;	

You do not have to live with only the default state you get when you first create a quadric 
object. By default your objects will have their skin on; they will appear as collections of filled 
polygons. You do have other choices, however, which you can express with the 
gluQuadricDrawStyle command. You already know the pointer to pass as the quadObject 
parameter. For the drawStyle parameter, you can use one of several OpenGL constants to 
specify whether you want points, lines, or filled polygons.
To render quadrics with filled polygons that have counterclockwise winding, use:

  GLU_FILL = $186AC;	
To render a quadric as a set of points, use:

  GLU_POINT = $186AA;	
To render a quadric as a set of lines, use:

  GLU_LINE = $186AB;	
To render a quadric as a set of lines with certain edges removed, use:

  GLU_SILHOUETTE = $186AD;	
The difference from using GLU_LINE is that any group of lines that form adjacent polygons 
that lie in the same plane will have the shared edges removed to make a single polygon.

Now, here is the command for which you have waiting patiently, to which you pass one of 
those OpenGL constants:

PAGE 367
 
procedure gluQuadricDrawStyle(quadObject:GLUquadricObj; drawStyle: GLenum);	
 stdcall;	
The gluQuadricOrientation function specifies inside/outside orientation for quadrics. Guess 
what: To use it, you need a couple more OpenGL constants. To draw quadrics with normals 
pointing outward (the default), use:

GLU_OUTSIDE = $186B4;	
A normal is a vector used in lighting calculations. Usually it is perpendicular to the face of a 
polygon or to a pair of sides joining at a vertex. Sometimes it is the weighted average of the 
normals for several sides joining at a single vertex, taken two at a time.
To draw quadrics with normals pointing inward, use:

  GLU_INSIDE = $186B5;	
To set the desired orientation of normal vectors for subsequent quadrics, pass one of those 
two OpenGL constants to:

procedure gluQuadricOrientation(quadObject:GLUquadricObj; orientation:	
 GLenum); stdcall;	
To specify what kind of normals a quadric should have, use gluQuadricNormals and pass 
one of the following OpenGL constants to it.
To generate one normal for every vertex of the quadric, use:

  GLU_SMOOTH = $186A0;	
As the name implies, this kind of vertex provides for smooth shading in your lighting 
environment, which is the default.
To generate a normal for every face in a quadric and provide flat shading in your lighting 
environment, use:

  GLU_FLAT = $186A1;	
If you do not intend to use lighting and want no normals generated, use:

  GLU_NONE = $186A2;	
Here is the declaration of the command to which to pass one of those constants:

procedure gluQuadricNormals(quadObject:GLUquadricObj; normals:GLenum);	
  stdcall;	

A really helpful feature is the ability to generate texture coordinates along with each vertex 
in a quadric. If you want to wrap a texture image around a quadric, pass a value 
representing true to:

PAGE 368
 procedure gluQuadricTexture(quadObject:GLUquadricObj; textureCoords: 	
GLboolean );stdcall;	

where

  GLboolean = ByteBool;	

Of course, to turn off texture coordinate generation in quadrics, pass a false (zero) value to 
the same command.

Now that you have seen the commands that determine how to draw various quadrics, you 
probably would like to see some commands that tell OpenGL what objects to draw. To draw 
a cylinder, use:

procedure gluCylinder(quadObject:GLUquadricObj; baseRadius,topRadius,	
 height:GLdouble; slices,stacks:GLint); stdcall;	

You can specify the overall dimensions of the cylinder with the GLdouble parameters, and 
the granularity with the GLint parameters. Notice that you do not just specify the radius of 
the cylinder, but two radii, one for the top and one for the bottom. If they are equal, you will 
have an ordinary cylinder. If one is zero, you generate a cone. Any other pair of values will 
produce a conic frustum.

The height parameter, as you would expect, determines how tall the cylinder will be. 
Actually you might want to think of it as specifying how long the cylinder is, since it will lie 
on its side along the z-axis. If you want it to stand up or appear some place besides the 
origin, you must perform transformations. The cylinder will have no top or bottom; it will be 
like an open-ended pipe or tube.

The slices parameter corresponds to the number of slices with which you divide a pie and 
has the same use in all the quadrics. A larger number makes the object rounder; a smaller 
number makes it blockier. You could use six slices, along with GLU_FLAT for the normals, 
to draw a pencil. For a nice, round cylinder 18 or 20 slices should be enough, while more 
slices would detract needlessly from animation performance.

The stacks parameter tells how many divisions along the length of the cylinder you want. 
You could think of it as the number of short cylinders you stack on top of each other to make 
the longer cylinder.
If you want a circle or disk, use:

procedure gluDisk(quadObject:GLUquadricObj; innerRadius,outerRadius: GLdouble; 
slices,loops:GLint); stdcall;	

This one also has two radii. outerRadius sets the size of the disk, while innerRadius sets the 
size of the hole in the disk. Use zero for the innerRadius parameter if you do not want a hole. 
The loops parameter is 

PAGE 369
 another granularity parameter, determining how many concentric circles with which to 
divide the disk. Since this object is only two-dimensional, orientation needs special 
consideration. The “outside” for purposes of generating normals is the positive z side.
To render a disk that does not make a complete circle, use:

procedure gluPartialDisk(quadObject:GLUquadricObj; innerRadius,	
outerRadius:GLdouble; slices,loops:GLint; startAngle,sweepAngle:	
GLdouble); stdcall;	
The added angle parameters determine how much of a circle you draw. The angles are in 
degrees, as usual. Imagine a clock face on the disk. Noon is at 0 degrees. Three o’clock is at 
90 degrees, six o’clock is at 180 degrees, and nine o’clock is at 270 degrees. The startAngle 
parameter tells where to start drawing the disk; the sweepAngle parameter tells how much 
disk to draw. The disk runs from startAngle to startAngle + sweepAngle.
To draw a sphere or globe, use:

procedure gluSphere(quadObject:GLUquadricObj; radius:GLdouble; slices, loops:GLint); 
stdcall;	
Radius has the obvious meaning, slices has the same meaning as before, and the loops 
parameter corresponds to stacks for the cylinder, like the latitude lines around a globe of the 
earth.
? Caution!
Adding quadrics to your animations may reduce your scene’s overall performance (frame 
rate). Movie films have been acceptable at 18 frames per second, but anything less would 
definitely appear jerky. If your animation’s performance is already borderline, you may have 
to “roll your own” as earlier example programs did with the cylinder. On the other hand, 
static scenes usually do not have this issue. While 4 fps is totally ludicrous for an animation, 
a render time of a fourth of a second for a static scene is usually just fine.
Globe
Near the top of the implementation section, add a few variables:

var	
  sBulk,	
  tBulk,	
  sBrick,	
  tBrick,	
  sWorld,	
  tWorld: GLsizei;	


PAGE 370
   globe,	
  world,	
  bulk,	
  brick: pointer;	
At the bottom of the FormCreate method, create a quadric. Accept the default state except 
for the draw style.

  CylinderBase;	
  BuildWall;	
  globe := gluNewQuadric;	
  gluQuadricDrawStyle(globe,GLU_LINE);	
end;	
At the end give back what you took:

procedure TForm1.FormDestroy(Sender: TObject);	
begin	
  FreeMem(bulk);	
  FreeMem(brick);	
  gluDeleteQuadric(globe);	
end;	
Name the fifth TTransform component on the form TransformGlobe and create an event 
handler for it named TransformGlobePaint:

procedure TForm1.TransformGlobePaint(Sender: TObject);	
begin	
  gluSphere(globe,2.0,18,12);	
end;	
Be sure to call the component’s Apply method in FormPaint:

procedure TForm1.FormPaint(Sender: TObject);	
begin	
  glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT);	
	
  glMatrixMode(GL_MODELVIEW);	
  glLoadIdentity;	
	
  Opening;	
	
  glRotatef(angl,0.0,1.0,0.0);	
  glTranslatef(wx,0.0,wz);	
	
  TransformLight.Apply;	
  TransformPole.Apply;	
  TransformWall.Apply;	
  TransformGlobe.Apply;	
  TransformFloor.Apply;	


PAGE 371
 	
  OpenGL1.Swap;	
  OpenGL1.ErrorCheck('FormPaint');	
end;	
Skeleton
Save, compile, and run. Turn left until you see the sphere. Figure 13-13 shows the natural 
inclination of the sphere, made obvious by GLU_LINE. The gluSphere command builds the 
sphere around the origin and aligns the axis along the z-axis. The transformations put 
the sphere in its final location but did not change its orientation.



Stand Up
This program needs to have the sphere standing up on its axis, so rotate it about the x-axis. 
The Axis fields in the component already have the appropriate values, so just change the 
Angle field to –90.0 degrees.
Save, compile, and run. Turn to the left again to see the sphere standing up as in Figure 13-
14.


PAGE 372
 Skin
Add some skin to the sphere. You can change the GLU_LINE parameter to GLU_FILL in the 
gluQuadricDrawStyle command so that the polygons in the sphere will be filled polygons. 
However, since that is the default state, just delete the command.
Save, compile, run, and turn left as before to see the sphere as in Figure 13-15.


Earth
A skin is nice, but the plan for this sphere is more ambitious. You are going to wrap a map of 
the earth around it to make an earth globe. Not just any map will do. Have you ever seen 
earth maps with the areas near the poles stretched out of proportion? You know, the ones 
where the island of Greenland looks as big as the continent of Africa? That is called a 
Mercator projection. You need a map that is stretched more and more as you move from the 
equator to the poles, because as you wrap a rectangular texture image around a sphere, you 
compress the image more and more as you move from the equator to the poles. Figure 13-16 
shows the texture image. The colors are mostly arbitrary.

Copy Fig13-16.bmp from the CD to the directory for this example program. In the middle of 
the FormCreate method, add a ReadBitmap for this texture image. Again, use the full path 
rather than just a simple filename.

PAGE 373
 
  bulk := ReadBitmap('fig13-08.bmp',sBulk,tBulk);	
  brick := ReadBitmap('fig13-10.bmp',sBrick,tBrick);	
  world := ReadBitmap('fig13-16.bmp',sWorld,tWorld);
To the bottom of the FormCreate method, add a gluQuadricTexture command to turn on 
texture map coordinate generation for the quadric object:

 BuildWall;	
  globe := gluNewQuadric;	
  gluQuadricDrawStyle(globe,GLU_FILL);	
  gluQuadricTexture(globe,byteBool(GL_TRUE));	
end;	
Be sure to give back the texture and quadric resources:

procedure TForm1.FormDestroy(Sender: Tobject);	
begin	
  FreeMem(bulk);	
  FreeMem(brick);	
  FreeMem(world);	
  gluDeleteQuadric(globe);	
end;	
Add texturing ability to the TransformGlobePaint method:

procedure TForm1.TransformGlobePaint(Sender: TObject);	
begin	
  glEnable(GL_TEXTURE_2D);	
  glTexImage2D(GL_TEXTURE_2D,Level,ColorComps,sWorld,tWorld,Border,	
    GL_RGB,GL_UNSIGNED_BYTE,world);	
  gluSphere(globe,2.0,18,12);	
  glDisable(GL_TEXTURE_2D);	
end;	
Save, compile, run, and turn left. Figure 13-17 shows the earth globe in the scene.







       Figure 13-17. A globe of the Earth.

PAGE 374
 Go for a Spin
The finishing touch for this example program is to set the earth spinning and add collision 
detection for it. Add a GLfloat named ang as a new private field of the form to track the 
earth globe’s angular position:

  private	
    ang,	
    angl,	
    rspeed,	
Since your speed of movement within the scene is adjustable to your taste, note the program 
included no attempt to adjust speed to frame rate. Now, however, the globe rotation begs for 
a system-independent speed. You can modify the Idle method to advance the ang field so 
the globe rotates at two revolutions per minute. Since the frame rate has fallen so much with 
the addition of a texture mapped quadric, cut the rollover constant from 1,000 frames to 100 
frames. Add a constant named half to represent the number of milliseconds in half a minute 
(30,000), or one rotation of the globe. The constant uses the reciprocal for computational 
convenience. The fullrot constant represents the number of degrees in one rotation of the 
globe.

procedure TForm1.Idle(Sender:TObject;var Done:boolean);	
const	
  rollover=100;	
  mspersec=1000.0;	
  half=1/30000.0;	
  fullrot=360.0;	
You also need to add some GLfloat variables to participate in the globe rotation calculations:

var	
  deltaAngle,	
  averageTime,	
  timedif: GLfloat;	
Some lines of the body of the method are just rearrangements and some are new. The call to 
TimeGetTime now happens every frame, since the rotation speed should be sensitive to 
fluctuating frame rates. The timedif variable accumulates the time since start rather than just 
the time since the last frame. Start adjusts every rollover frames. Accumulating over a block 
of frames smooths out round-off errors that would happen just calculating the amount of 
angle change from one frame to the next. In essence, the change in angle value varies 
inversely with the frame rate. The higher the frame rate, the less the globe should turn per 
frame.

PAGE 375
 
begin	
  Done := false;	
  Adjust;	
  inc(frames);	
  elapsed := TimeGetTime;	
  timedif := (elapsed-start);	
  averageTime := timedif/frames;	
  deltaAngle := fullrot*averageTime*half;	
  ang := ang+deltaAngle;	
  if ang>=360.0 then	
    ang := ang-360.0;	
	
  if frames>=rollover then	
  begin	
    Caption := Format('fps=%0.1f',[frames*mspersec/timedif]);	
    start := elapsed;	
    frames := 0;	
  end else	
    FormPaint(nil);	
end;	
Now put the ang field to use in the TransformGlobePaint method. This requires some matrix 
stack manipulation as well as a glRotatef command:

procedure TForm1.TransformGlobePaint(Sender: TObject);	
begin	
  glPushMatrix;	
    glRotatef(ang,0.0,0.0,1.0);	
	
    glEnable(GL_TEXTURE_2D);	
    glTexImage2D(GL_TEXTURE_2D,Level,ColorComps,sWorld,tWorld,Border,	
      GL_RGB,GL_UNSIGNED_BYTE,World);	
    gluSphere(globe,2.0,18,12);	
    glDisable(GL_TEXTURE_2D);	
	
  glPopMatrix;	
end;	

At the bottom of the Adjust method, adding collision detection for the globe is very simple 
because it is round. The 4.14 accounts for the radius of the sphere, the viewer offset, and a 
small rounded bumper just above the viewport:

  with TransformGlobe do	
  begin	
    deltax := TranX-viewerx;	
    deltaz := TranZ-viewerz;	
    distance := sqrt(deltax*deltax+deltaz*deltaz);	
    if (distance<4.14) and restore then	
      exit;	


PAGE 376
   end;	
	
end;	
Save, compile, and run. Drive around a while and admire the spinning globe from various 
angles. Figure 13-18 shows collision detection in action.

Who Turned Out the Lights?

You may have noticed that after the addition of the sphere, the floor went uniformly dark, 
no longer demonstrating the shading effect of the light. The reason is the fact that the sphere 
generated normal vectors to give a smooth, round appearance. The last normal vector in 
effect before the drawing of the floor was wrong for the floor. All the sample programs up to 
this time have, by chance, been able to ignore the issue, but now you must explicitly deal 
with normals.

Most surfaces in the scene are covered by decal textures, so they do not show shading 
anyway. The light pole is black, so you can skip it also. Only the floor and one face of the 
cube around the light will show shading once the normals are right. For simplicity, ignore 
the cube and concentrate on the floor.

The glNormal commands are among the few that can fit between glBegin and glEnd, 
although they do not have to. Here is the declaration you need:

procedure glNormal3f(nx,ny,nz:GLFloat); stdcall;	

The floor lies parallel to the x-z plane, so its face normal is a vector that runs parallel to the y-
axis. Make sure the vector is one unit in length, and 

PAGE 377
 make it start at the origin. Only the direction and length matter, so the coordinates of a 
single point are sufficient to specify the vector, with the origin understood as the starting 
point. Add the command to the TransformFloorPaint method:

procedure TForm1.TransformFloorPaint(Sender: TObject);	
begin	
  MaterialFloor.Apply;	
  glBegin(GL_POLYGON);	
    glNormal3f(0.0,1.0,0.0);	
    glVertex3f(-29.0,0.0,-29.0);	
    glVertex3f(-29.0,0.0,+29.0);	
    glVertex3f(+29.0,0.0,+29.0);	
    glVertex3f(+29.0,0.0,-29.0);	
  glEnd;	
end;	
Save, compile, and run. Shading has returned.
Terrain
So far you have been driving around on a surface so flat and featureless you can consider it a 
floor. An outdoor scene would not have a large flat area, even in the Great Plains, unless you 
have an artificial construct such as a concrete tarmac. Normally, an outdoor expanse of 
ostensibly level ground really has its ups and downs. The example program in this section 
will produce an exaggerated rolling grassland.
Project
Create a new directory under the Chapter.13 directory and name it Terrain. In the Terrain 
directory save a new project named Terrain0.Dpr with a unit named Terrain1.Pas. On the 
form, drop a TOpenGL component, a TLight component, two TTransform components, and 
a TMaterial component.
Properties
The Terrain0 project returns to simplicity to illustrate a single concept. Gone are the textured 
objects in the scene and in the opening into the scene, as well as visible constructs for the 
light source. All you have here is a naked light source and a framework for drawing the 
terrain. Listing 13-4 shows the properties:

PAGE 378
 
object Form1: TForm1	
  Left = 192	
  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	
  OnKeyDown = FormKeyDown	
  OnKeyUp = FormKeyUp	
  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 = 42	
    ColorBits = 24	
    DepthBits = 32	
    StencilBits = 0	
    Left = 4	
    Top = 4	
  end	
  object TransformLight: TTransform	
    TranZ = 2	
    AxisX = 1	
    Angle = -10	
    ScaleX = 1	
    ScaleY = 1	
    ScaleZ = 1	
    OnPaint = TransformLightPaint	
    Left = 4	


PAGE 379
     Top = 40	
  end	
  object Light1: TLight	
    Which = 1	
    Cutoff = 55	
    AmbientRed = 0.2	
    AmbientGreen = 0.2	
    AmbientBlue = 0.2	
    DiffuseRed = 1	
    DiffuseGreen = 1	
    DiffuseBlue = 1	
    Direction = True	
    Position = True	
    Spotlight = True	
    Enabled = True	
    Left = 40	
    Top = 40	
  end	
  object TransformLand: TTransform	
    TranY = -0.2	
    ScaleX = 1	
    ScaleY = 1	
    ScaleZ = 1	
    OnPaint = TransformLandPaint	
    Left = 4	
    Top = 72	
  end	
  object MaterialLand: TMaterial	
    ambRed = 0.5	
    ambGreen = 0.5	
    ambBlue = 0.5	
    ambAlpha = 1	
    difRed = 0.7	
    difGreen = 0.7	
    difBlue = 0.7	
    difAlpha = 1	
    aFace = faFront	
    dFace = faFront	
    sFace = faFront	
    Left = 40	
    Top = 72	
  end	
end
Listing 13-4. Terrain0 project properties.

PAGE 380
 Private
In the private section of the form type declaration, you need some fields and methods, most 
of which are already familiar:

private	
    wx,	
    wy,	
    wz,	
    tran,	
    rot,	
    speed,	
    rspeed,	
    theta,	
    angl: GLfloat;	
    start,	
    elapsed,	
    frames: integer;	
    procedure DrawTerrain;	
    procedure BuildTerrain;	
    procedure Adjust;	
    procedure Idle(Sender:TObject;var Done:boolean);	
  public	
Constants, Types, Variables, and Functions
In the implementation section, you need a few identifiers. While you have seen some of them 
in other projects, some of them are unique to this project. The terrain types and the 
corresponding variable help construct triangle strips that will become grassland. The grassy 
variables relate to the texture image intended to suggest a grassy surface. The dl variable 
identifies a display list. Just after those identifiers you need the ReadBitmap function, for 
which you already have listings.

const	
  Level=0;	
  Border=0;	
  ColorComps=3;	
	
type	
  TRGB=packed record	
    r,g,b: byte;	
  end;	
  TWrap=array[0..0]of TRGB;	
  TPoint=packed record	
    x,y,z: GLfloat;	
  end;	
  TTerrainRow=array[1..66] of TPoint;	


PAGE 381
   TTerrain=array[-32..32] of TTerrainRow;	
	
var	
  grass: pointer;	
  sGrass,	
  tGrass: GLsizei;	
  ter: TTerrain;	
  dlTerrain: GLuint;	
  pi180: double=pi/180.0;	
Setup and Shutdown
As usual you will use the FormCreate method to initialize the project, particularly the 
texture and the timing. Copy Fig13-19.bmp from the CD to this project’s directory. Change 
the filename reference to its full path. That Windows bitmap file becomes the texture image 
assigned to the grass variable and shows in Figure 13-19.

procedure TForm1.FormCreate(Sender: TObject);	
begin	
  glPixelStorei(GL_UNPACK_ALIGNMENT,1);	
  glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_S,GL_REPEAT);	
  glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_T,GL_REPEAT);	
  glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_NEAREST);	
  glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_NEAREST);	
  glTexEnvf(GL_TEXTURE_ENV,GL_TEXTURE_ENV_MODE,GL_MODULATE);	
  grass := ReadBitmap('fig13-19.bmp',sGrass,tGrass);	
  glTexImage2D(GL_TEXTURE_2D,Level,ColorComps,sGrass,tGrass,Border,	
    GL_RGB,GL_UNSIGNED_BYTE,Grass);	
  glEnable(GL_TEXTURE_2D);	


PAGE 382
   Application.OnIdle := Idle;	
  BuildTerrain;	
  start := TimeGetTime;	
end;	
This project has a FormDestroy method to dispose of the grass:

procedure TForm1.FormDestroy(Sender: TObject);	
begin	
  FreeMem(grass);	
end;	
The Usual
The FormResize, FormKeyDown, and FormKeyUp methods are the same as in the last 
project.
FormPaint
The FormPaint method is what you would expect, but does need some attention. The Apply 
method calls position the light and the terrain. The transformation commands enable the 
drive-through. Notice the use of wy instead of a hard-coded zero. For now, the wy field will 
retain its initial value of zero, but keep it in mind for later.

procedure TForm1.FormPaint(Sender: TObject);	
begin	
  glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT);	
  glMatrixMode(GL_MODELVIEW);	
  glLoadIdentity;	
	
  glRotatef(angl,0.0,1.0,0.0);	
  glTranslatef(wx,wy,wz);	
	
  TransformLight.Apply;	
  TransformLand.Apply;	
	
  OpenGL1.Swap;	
  OpenGL1.ErrorCheck('FormPaint');	
end;	
TransformPaint
The OnPaint event handlers for the TTransform components are small. The light source is 
completely unadorned. The event handler defers the rendering code for the terrain to a 
separate method so that later the rendering code can be in a display list.

PAGE 383
 
procedure TForm1.TransformLightPaint(Sender: TObject);	
begin	
  Light1.Apply;	
end;	
	
procedure TForm1.TransformLandPaint(Sender: TObject);	
begin	
  MaterialLand.Apply;	
  DrawTerrain;	
end;	
Idle
The Idle method does not implement the machine-independent speed code of the Real0 
project, so it is as simple as in prior projects. It tracks frame rate and hands off the drive-
through code to the Adjust method:

procedure TForm1.Idle(Sender:TObject;var Done:boolean);	
const	
  rollover=100;	
  mspersec=1000.0;	
begin	
  Done := false;	
  Adjust;	
  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;	
Adjust
The Adjust method adjusts the wx and wz fields to perform the drive-through. The private 
form field theta replaces the local variable theta:

procedure TForm1.Adjust;	
begin	
  angl := angl+rot;	
  if angl>360.0 then	
    angl := angl-360.0;	
  if angl<0.0 then	
    angl := angl+360.0;	


PAGE 384
   theta := angl*pi180;	
  wx := wx-tran*sin(theta);	
  wz := wz+tran*cos(theta);	
end;	
Terrain Implementation
Finally comes the distinguishing feature and purpose for this project. The BuildTerrain 
method uses random numbers to modify the vertices in a series of triangle strips. The ter 
variable contains an array of 65 triangle strips that should lie side by side to form a 
continuous stretch of land. The last project used an array to hold a set of programmatically 
generated vertices to build a wall. This terrain generator is an extension of that technique. 
The new twist is to modify the y coordinate of each vertex randomly so the landscape will 
have height variations. The routine also randomizes the x and z coordinates so that each 
triangle has a somewhat different shape, but the variations in height are the most important 
feature.

procedure TForm1.BuildTerrain;	
var	
  row,	
  ix: integer;	
  q: GLfloat;	
begin	
  Randomize;	
  for row := -32 to +32 do	
  begin	
    q := -17.0;	
    for ix := 1 to 66 do	
    with ter[row,ix] do	
    begin	
      y := -1.5+0.001*Random(500);	
      if odd(ix) then	
      begin	
        q := q+1.0;	
        x := q+0.1-0.001*Random(201);	
        z := 0.001*Random(450)+row;	
      end else begin	
        x := q;	
        z := 1.0-0.001*Random(450)+row;	
      end;	
    end; {for}	
  end; {for}	
In order for the scene to look like continuous surface rather than disconnected hash, the 
vertices from the last edge of one strip must match the vertices of the first edge of the next 
strip. An additional pair of for loops takes care of that important detail:

PAGE 385
 
  for row := -32 to +31 do	
  for ix := 1 to 33 do	
    ter[row+1,2*ix-1] := ter[row,2*ix];	
end;	
The FormCreate method calls the BuildTerrain method just once to initialize the triangle 
strips. The FormCreate method indirectly calls the DrawTerrain method each frame. 
DrawTerrain loops though the vertices to pass them to OpenGL in a manner similar to the 
way that BuildTerrain looped through them to give them values. Notice that the 
glBegin/glEnd pair execute once for each separate triangle strip. The inner for loop just 
passes the vertices in the proper order within a strip.

procedure TForm1.DrawTerrain;	
var	
  ix,	
  row,	
  extra,
  start,	
  finish: integer;	
  s: GLfloat;	
  t: array[false..true] of GLfloat;	
  tt: boolean;	
begin	
  start := -32;	
  finish := +32;	
  for row := start to finish do	
  begin	
    s := 0.0;	
    t[false] := 0.0;	
    t[true] := 2.0;	
    tt := false;	
    glBegin(GL_TRIANGLE_STRIP);	
    for ix := 1 to 66 do	
    with ter[row,ix] do	
    begin	
      glTexCoord2f(s,t[tt]);	
      glVertex3f(x,y,z);	
      if tt then	
        s := s+1.0;	
      tt := not tt;	
    end; {for}	
    glEnd;	
  end; {for}	
end;	

PAGE 386
 Run
Save, compile, and run. Drive around a bit. While the unevenness of the terrain is detectable 
but somewhat washed out in the uniform lighting of the area that has full light, it shows 
quite well near the boundary between light and dark, as in Figure 13-20. If you have ever 
looked at the moon through a telescope you are already familiar with such an effect, 
although the reasons for the two cases are not exactly the same. You can see mountains and 
crater rims quite well near the terminator on the moon, but not in the really bright areas.
This terrain illustration is exaggerated for effect. Most grassland is not this lumpy. If you 
drove an M1 Abrams at full throttle over land like this, you would probably shake your 
teeth out.
A Display List
While this scene does not seem to have a lot in it—just rolling grassland—it does have a 
large performance impact. The same computer system that was able to send a 72-triangle 
cylinder tumbling through space at 540 fps dropped to 51 fps with the opening view of this 
scene. In fact, there are a lot of vertices in a large area that OpenGL must process, not to 
mention a great deal of texture. Performance improvements would be welcome. Here are 
some declarations you will need:

procedure glCallList(list:GLuint); stdcall;	
	
procedure glEndList; stdcall;	
	
procedure glNewList(ListIndex:GLuint;mode:GLenum); stdcall;	
OpenGL display lists, containing precompiled rendering code, are supposed to be more 
efficient than calling the rendering code over again each frame. So, instead of calling the 
DrawTerrain method every frame, call it just once at the bottom of the BuildTerrain method 
to put into a display list:

for row := -32 to +31 do	
  for ix := 1 to 33 do	


PAGE 387
     ter[row+1,2*ix-1] := ter[row,2*ix];	
	
  dlTerrain := glGenLists(1);	
  glNewList(dlTerrain,GL_COMPILE);	
    DrawTerrain;	
  glEndList;	
end;	
Now call the display list instead of the DrawTerrain method each frame:

procedure TForm1.TransformLandPaint(Sender: TObject);	
begin	
  MaterialLand.Apply;	
  glCallList(dlTerrain);	
end;	
Of course, since you use resources in creating a display list, you should eventually give them 
back:

procedure TForm1.FormDestroy(Sender: Tobject);	
begin	
  glDeleteLists(dlTerrain,1);	
  FreeMem(grass);
end;	
Save, compile, and run. Compare with the previous frame rates. On the 51 fps machine just 
mentioned, the display list increased the frame rate to 55 fps. While helpful, that is not a 
breath-taking improvement. Try again.
Prefiltered Data
This problem requires some thought. OpenGL receives a large number of vertices from the 
program, but only shows a small subset at any one time. While OpenGL does cull a lot of 
data before final processing, it still has to do a certain amount of work with the entire scene 
before it decides to throw part of it out. If the program could eliminate some of the data 
before even sending it to OpenGL, without much ado, performance should improve.

Simplicity is important. If your program has to perform a lot of complicated calculations to 
filter out just the right amount of data, it will end up doing just about as much extra work as 
it saved. This example program takes simplicity to the extreme, for the sake of clear 
illustration. At the top of the DrawTerrain method you can manipulate the values of start 
and finish so that you only send some of the triangle strips to OpenGL:

begin	
  extra := round(abs(10.0*cos(theta)));	
  start := round(-wz);	


PAGE 388
   finish := start;	
  start := start-10-extra;	
  if start<-32 then	
    start := -32;	
  finish := finish+10+extra;	
  if finish>32 then	
    finish := 32;	
	
  for row := start to finish do	

Basically this eliminates some of the triangle strips behind you, and the most distant ones 
ahead of you. This algorithm is a bit crude; as you drive forward you will see a hint of new 
strips materializing. However, you can treat that as more surface coming into view above the 
horizon as you travel over a round earth rather than a flat one.
The display list is no longer useful since it contains the entire scene as an indivisible unit. 
Take its creation out of the BuildTerrain method and its destruction out of the FormDestroy 
method, and restore the call to the DrawTerrain method in TransformLandPaint. Also 
remove dlTerrain from the variable declarations.

procedure TForm1.TransformLandPaint(Sender: TObject);	
begin	
  MaterialLand.Apply;	
  DrawTerrain;	
end;	
Save, compile, and run. Again compare frame rates. On the previously referenced 51 fps 
machine, this technique almost doubled the rate to 96 fps.
A Bumpy Ride
Have you noticed that you are getting a very smooth ride for such a hilly area? That will 
now change. Do you remember the wy field that was being left at zero? By manipulating the 
value of wy in

glTranslatef(wx,wy,wz);	

you can make your view rise and fall with the terrain you pass over. The Adjust method will 
now act as if the tracks of the vehicle were at the location of wx and wz and will find the y 
value of the nearest terrain vertex under that point.
The top of the method stays the same:

procedure TForm1.Adjust;	
var	
  minx,	


PAGE 389
   deltaX: GLfloat;	
  ix,	
  start,	
  finish,	
  strip: integer;	
begin	
  angl := angl+rot;	
  if angl>360.0 then	
    angl := angl-360.0;	
  if angl<0.0 then	
    angl := angl+360.0;	
  theta := angl*pi180;	
  wx := wx-tran*sin(theta);	
  wz := wz+tran*cos(theta);	

The value of strip is the array position of the triangle strip over which your vehicle is 
passing. The values of start and finish are the search range in a loop to find the vertex in the 
strip horizontally closest to wx. A search is necessary since the vertices randomly deviated 
from an even spacing down the strip. Initialize the value of minx so that the first deltaX will 
be less that this sliding minumum.

  strip := round(-wz);	
  start := round(-wx)+34;	
  finish := start+5;	
  dec(start,5);	
  minx := 99999.9;	

Check for boundary conditions and search from start to finish for the minimum deltaX. Each 
time you find a candidate, set a new trial value for wy. The offset allows for the average 
distance of the ground below the zero level. The sign reversal allows for the fact that when 
you seem to move your vehicle in one direction, you are actually moving the scene in the 
opposite direction.

  if (finish<1) or (start>66) or (strip<-32) or (strip>32) then	
    wy := 0	
  else	
  for ix := start to finish do	
  with ter[strip,ix] do	
  begin	
    deltaX := abs(x-wx);	
    if deltaX<minx then	
    begin	
      minx := deltaX;	
      wy := -1.45-y;	
    end;	
  end; {else for}	
end;	

PAGE 390
 Save, compile, run, and go for a spin. You might want to take some Dramamine along.
Identifier Reference
Identifier			Description
GLboolean
An OpenGL type equivalent to Delphi ByteBool.
glCallList
An OpenGL command to execute a display list.
glEndList
An OpenGL command that concludes a series of OpenGL commands that go into a display 
list.
glNewList
An OpenGL command that starts a series of OpenGL commands to go into a display list.
glNormal
An OpenGL command to create and make current an OpenGL vector called a normal, which 
is used for lighting calculation.
gluCylinder
An OpenGL Utility library command to draw a cylinder.
gluDisk
An OpenGL Utility library command to draw a disk or circle, possibly with a hole in it.
gluDeleteQuadric
An OpenGL Utility library command that deletes a specified quadric object.
gluNewQuadric
An OpenGL Utility library command that creates a new quadric object.
gluPartialDisk
An OpenGL Utility library command to draw an arc or slice of a disk.
gluQuadricDrawStyle
An OpenGL Utility library command to set the drawing style of the given quadric object, so 
that a subsequent drawing command will render points, lines, or filled polygons.
gluQuadricNormals
An OpenGL Utility library command to set the kind of generation of normals during the 
drawing of a quadric. The command determines whether drawing generates no normals, a 
normal for each face, or a normal for each vertex.

PAGE 391
 
Identifier			Description
GLUquadricObj
An OpenGL pointer type. Pointers of this type should point to the memory region of a 
quadric object.
gluQuadricOrientation
An OpenGL Utility library command to determine whether generated normals should point 
inside or outside the quadric to be drawn.
gluQuadricTexture
An OpenGL Utility library command to determine whether or not generation of vertices in 
drawing a quadric should also generate texture coordinates.
gluSphere
An OpenGL Utility library command to draw a sphere.
GLU_FILL
An OpenGL Utility library constant to specify filled polygons in the drawing of a quadric.
GLU_FLAT
An OpenGL Utility library constant to specify face normals in the drawing of a quadric.
GLU_INSIDE
An OpenGL Utility library constant to specify generation of inward pointing normals in the 
drawing of a quadric.
GLU_LINE
An OpenGL Utility library constant to specify lines (like a wire frame) in the drawing of a 
quadric.
GLU_NONE
An OpenGL Utility library constant to specify no generation of normals in the drawing of a 
quadric.
GLU_OUTSIDE
An OpenGL Utility library constant to specify generation of outward pointing normals in the 
drawing of a quadric.
GLU_POINT
An OpenGL Utility library constant to specify points in the drawing of a quadric.
GLU_SILHOUETTE
An OpenGL Utility library constant like GL_LINES except that adjacent co-planar polygons 
will be consolidated by the elimination of shared edges.
GLU_SMOOTH
An OpenGL Utility library constant to specify vertex normals in the drawing of a quadric.

PAGE 392
 Summary

In this chapter you learned a few new commands and some techniques for using the 
commands you have learned in new ways, with a drive-through as the means of this 
learning. These example programs are, of course, just simple starting points; where you take 
them is limited only by your imagination. As a further stimulus, Figure 13-21 shows one of 
the vehicle simulations with a front or hood added.

Naturally, such an extension forward adds complication to the collision detection 
calculations. That, as they say, is left as an exercise for the reader.


PAGE 393
 

PAGE 394

Chapter 14
Feedback

OpenGL has three rendering modes. You have been using the normal rendering mode most 
of the time, and you have also tried out the selection mode. You have also seen a reference to 
the third mode, feedback. To reiterate from Chapter 7, feedback mode fills a buffer with the 
coordinates of the vertices that would have been drawn for normal rendering. You must set 
aside a buffer for this purpose, and you must tell OpenGL about it with an OpenGL 
command. As with the other rendering modes, you put OpenGL into feedback mode with 
the glRenderMode command. You receive back the number of entries placed in the feedback 
buffer as the function result when you call glRenderMode again to leave feedback mode. 
Most entries contain information about each vertex.

Keep in mind that during feedback mode, no rasterization (drawing on the screen) will occur. 
Instead, information about what would have appeared on the screen goes into the buffer. No 
vertices that would have been clipped go into the buffer; however, new vertices that 
correspond to the points where a figure was partially clipped do arrive in the buffer.

Commands and Constants

To tell OpenGL about the necessary buffer, use glFeedbackBuffer; in reality, the command 
does more than just identify the buffer to OpenGL and indicate its size. It actually 
determines what kind of feedback will occur. Always issue the glFeedbackBuffer command 
before entering feedback mode. Always exit feedback mode before trying to access 
information in the feedback buffer.

procedure glFeedbackBuffer(size:GLsizei;buftype:GLenum; buffer: PGLFloat); stdcall;	

The size parameter tells OpenGL how much data the buffer can hold. The buffer parameter 
is a pointer to the data structure that receives the feedback information. Notice that the 
buffer contains GLfloats. Many of the 

PAGE 395
 return values could actually be integer types, but since some values, such as coordinates, 
must be GLfloats, then the whole buffer has to be GLfloat values.

The buftype parameter is a symbolic constant that tells OpenGL what type of information 
you want back. Here are the relevant declarations:

  GL_2D               = $0600;	
  GL_3D               = $0601;	
  GL_3D_COLOR         = $0602;	
  GL_3D_COLOR_TEXTURE = $0603;	
  GL_4D_COLOR_TEXTURE = $0604;	

Each symbol determines how much coordinate information, color information, and texture 
information you get back for each vertex. The size of the color information depends on the 
color mode. This chapter only considers true color, not palettized color. The kind of 
information you get back for each vertex with a given buftype follows:

Constant				Information Returned for Each Vertex
GL_2D				x y
GL_3D				x y z
GL_3D_COLOR			x y z r g b a
GL_3D_COLOR_TEXTURE	x y z r g b a s t u v
GL_4D_COLOR_TEXTURE	x y z w r g b a s t u v
Coordinate, color, and texture values are not the only data placed in the buffer. Tokens also 
appear in the buffer that describe how the vertex data is organized:

  GL_PASS_THROUGH_TOKEN = $0700;	
  GL_POINT_TOKEN        = $0701;	
  GL_LINE_TOKEN         = $0702;	
  GL_POLYGON_TOKEN      = $0703;	
  GL_BITMAP_TOKEN       = $0704;	
  GL_DRAW_PIXEL_TOKEN   = $0705;	
  GL_COPY_PIXEL_TOKEN   = $0706;	
  GL_LINE_RESET_TOKEN   = $0707;	

As you can see, the tokens that appear in the buffer could have been represented by an 
integer type, but the buffer has to contain only floating-point values. The scope of this book 
does not include every possibility. The kind of information that comes after each considered 
token follows. The amount and kind of information for each vertex depends on the buftype.

Token				Following Values
GL_PASS_THROUGH_TOKEN	programmer-defined value
GL_POINT_TOKEN		vertex
GL_LINE_TOKEN			vertex vertex


PAGE 396
 GL_POLYGON_TOKEN		how_many vertex .. vertex
GL_BITMAP_TOKEN		vertex
The GL_PASS_THROUGH_TOKEN bears some explanation. It provides you an opportunity 
to mark the beginning of an object or group of objects and thereby identify and distinguish 
one huge group of data from another. Just before you start the rendering code for a 
particular object you want to distinguish, call the following OpenGL command:

procedure glPassThrough(token:GLfloat); stdcall;	

You can safely sprinkle calls to glPassThrough throughout your rendering code. They have 
no effect when OpenGL is not in feedback mode. Of course, you must not place 
glPassThrough between glBegin and glEnd.
The GL_POLYGON_TOKEN explanation needs a little more help. The first number after the 
token is how many vertices the polygon has. Following that are that many vertices. Each 
vertex in the feedback buffer is a vertex information block consisting of coordinates, possibly 
colors, and possibly texture information. 
You may be in for a bit of a surprise relating to polygons. Many implementations of 
OpenGL, including Microsoft’s, break all polygons into triangles before doing any further 
work with them. Instead of the x-sided polygon you thought you drew, you will find in the 
feedback several triangles that add up to your polygon.

Capture the Triangle

By now you probably want to see some code to put this information to good use. You won’t 
be disappointed. The first attempt will just draw a triangle and get feedback from it.

Project

Create a new directory under the OpenGL directory and name it Chapter.14; in it create a 
directory named Triangle. Save a new project in it named Tri0.Dpr with a unit named 
Tri1.Pas. Declare an initialized variable (or typed constant for Delphi 2):

var	
  Form1: TForm1;	
  pi180: double=pi/180.0;	

PAGE 397
 Properties

You will need a TOpenGL component and a TTransform component. Listing 14-1 shows the 
properties:

object Form1: TForm1	
  Left = 192	
  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	
  OnKeyDown = FormKeyDown	
  OnKeyUp = FormKeyUp	
  OnPaint = FormPaint	
  OnResize = FormResize	
  PixelsPerInch = 96	
  TextHeight = 13	
  object OpenGL1: TOpenGL	
    pfdFlags = [DoubleBuffer, 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 = 4	
    Top = 4	
  end	
  object Transform1: TTransform	
    TranZ = -2	
    ScaleX = 1	
    ScaleY = 1	


PAGE 398
     ScaleZ = 1	
    OnPaint = Transform1Paint	
    Left = 4	
    Top = 36	
  end	
end	

Listing 14-1. Tri0 project properties.

Private

In addition to the fields and methods that enable the drive-through, you need a few more to 
support the feedback mode. The buffer field will hold the feedback data. The Feedback 
method enters feedback mode, gathers the data, returns to normal rendering mode, and 
processes the data. The DumpBuffer method processes the data for the Feedback method by 
saving it to a file in human-readable form. The ParseFeedback method handles the nasty 
details of processing the feedback data for the DumpBuffer method. The ValuesPerVertex 
method returns the number of values a vertex has, appropriate for each bufType.

  private	
    angl,	
    rspeed,	
    speed,	
    tran,	
    rot,	
    wx,	
    wz: GLfloat;	
    bufType: GLenum;	
    how_many: GLsizei;	
    frames,	
    elapsed,	
    start: integer;	
    buffer: array[1..256] of GLfloat;	
    procedure Idle(Sender:TObject;var Done:boolean);	
    procedure Adjust;	
    procedure Feedback;	
    procedure DumpBuffer;	
    function ParseFeedback(var lastToken,values,verts,kind:integer;	
      index:integer):string;	
    function ValuesPerVertex:integer;  	
    function ProcessVertexValues(var lastToken,values,kind:integer;	
      index:integer):string;	
  public	

PAGE 399
 Initialize

The FormCreate method is not nearly as large as the ones that set up textures. This one just 
needs to assign the OnIdle method and an initial value for the bufType field:

procedure TForm1.FormCreate(Sender: TObject);	
begin	
  Application.OnIdle := Idle;	
  bufType := GL_2D;	
end;	

Standard Stuff

The FormResize and FormPaint methods are just what you would expect. The FormPaint 
method is just like the ones in the last chapter, with the transformation commands for the 
first-person drive-through:

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);	
	
  glMatrixMode(GL_MODELVIEW);	
  glLoadIdentity;	
  glRotatef(angl,0.0,1.0,0.0);	
  glTranslatef(wx,0.0,wz);	
	
  Transform1.Apply;	
	
  OpenGL1.Swap;	
  OpenGL1.ErrorCheck('FormPaint');	
end;	

The Idle method is the same as in the last chapter:

procedure TForm1.Idle(Sender:TObject;var Done:boolean);	
const	
  rollover=1000;	
  mspersec=1000.0;	
begin	
  Done := false;	
  Adjust;	
  inc(frames);	


PAGE 400
   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 Adjust method, called by Idle to adjust the positional fields, is also just as before:

procedure TForm1.Adjust;	
var	
  theta: GLfloat;	
begin	
  angl := angl+rot;	
  theta := angl*pi180;	
  wx := wx-tran*sin(theta);	
  wz := wz+tran*cos(theta);	
end;	

Rendering

Transform1Paint draws a simple green triangle. The Tran properties of the component 
ensure that the triangle lies on the zNear clipping plane.

procedure TForm1.Transform1Paint(Sender: TObject);	
begin	
  glColor3f(0.0,1.0,0.0);	
  glBegin(GL_TRIANGLES);	
    glVertex3f(-0.5,+0.5,+0.0);	
    glVertex3f(-0.5,-0.5,+0.0);	
    glVertex3f(+0.5,+0.5,+0.0);	
  glEnd;	
end;	

Keyboard

The FormKeyDown and FormKeyUp methods are the same as you have used in the 
previous chapter. In support of feedback, add the following to the case statement in the 
FormKeyDown method. The F key calls the Feedback method; the 2, 3, and C keys set the 
bufType value.

    ord('F'): begin	
      Feedback;	
    end;	
    ord('2'): begin	


PAGE 401
       bufType := GL_2D;	
    end;	
    ord('3'): begin	
      bufType := GL_3D;	
    end;	
    ord('C'): begin	
      bufType := GL_3D_COLOR;	
    end;	
  end; {case}	
end; {FormKeyDown}	

Feedback

The Feedback method handles the feedback operation in broad strokes. First, it identifies the 
feedback buffer and passes the buffer type, using the bufType field set in the FormKeyDown 
method. Next, it changes the rendering mode to GL_FEEDBACK. Then it calls the FormPaint 
method to capture all the rendering code. Immediately thereafter the method changes the 
rendering mode back to normal and collects the count of values in the buffer. Finally, it calls 
the DumpBuffer method to handle processing the buffer contents.

procedure TForm1.Feedback;	
begin	
  glFeedbackBuffer(256,bufType,@buffer);	
  glRenderMode(GL_FEEDBACK);	
  FormPaint(nil);	
  how_many := glRenderMode(GL_RENDER);	
  DumpBuffer;	
end;	

The DumpBuffer method opens a text file, converts the buffer to human-readable strings, 
and writes them into the text file. Replace the filename with a complete path to the directory 
in which you created this project. It uses the presence of a space at the end of a string to 
determine whether to allow building onto the same line in the text file or to start a new line 
in the text file right after writing the string.

procedure TForm1.DumpBuffer;	
var	
  ix: integer;	
  tf: TextFile;	
  len,	
  kind,	
  values,	
  vertices,	
  lastToken: integer;	
  s: string;	


PAGE 402
 begin	
  assignFile(tf,'Feedback.dmp');	
  rewrite(tf);	
  writeln(tf,Format('Feedback values returned=%d',[how_many]));	
  writeln(tf);	
  lastToken := 0;	
  for ix := 1 to how_many do	
  begin	
    s := ParseFeedback(lastToken,values,vertices,kind,ix);	
    len := length(s);	
    write(tf,s);	
    if (s='') or (s[len]<>' ') then	
      writeln(tf);	
  end;	
  closeFile(tf);	
end;	

The ParseFeedback method handles the actual conversion to strings. It keeps track of the last 
identifying token and makes decisions in a large case statement accordingly. The first thing 
the method does is obtain a value from the buffer at the passed index. The first call to it 
passes a zero as the value of lastToken. Later calls will also pass a zero when a given token 
and all its data have been fully processed.

function TForm1.ParseFeedback(var lastToken,values,verts,kind:integer;	
  index:integer):string; 	
var	
  value: GLfloat;	
begin	
  value := buffer[index];	
  case lastToken of	
    0: begin	
      lastToken := round(value);	
      case lastToken of	
        GL_POLYGON_TOKEN: begin	
          result := 'GL_POLYGON_TOKEN: ';	
          values := -1;	
        end;	
      end;	
    end; {new token}	

The outer case statement relies on the value of lastToken, which remembers the token 
processed for the zero. For the case of GL_POLYGON_ TOKEN, the only one under 
consideration for this example, two behaviors are available. If the values number previously 
set is –1, verts gets the number of vertices and values gets the number of GLfloats in the 
buffer for those vertices. It also initializes kind, which tracks where the method 

PAGE 403
 currently is in the processing of the data for the vertices. The result string shows the number 
of vertices for the polygon:

    GL_POLYGON_TOKEN: begin	
      if values=-1 then	
      begin	
        verts := round(value);	
        result := Format('Vertices=%d',[verts]);	
        values := verts*ValuesPerVertex;	
        kind := 0;	
      end else	
        result := ProcessVertexValues(lastToken,values,kind,index);	
    end;	
  end; {case}	
end;	

When the values number is not –1, the method gets its result from ProcessVertexValues.
The ProcessVertexValues method works, in successive calls, through successive numbers in 
the buffer and returns coordinate or color information as appropriate. When kind reaches 
the number of ValuesPerVertex, it resets back to zero, preparing to process the next vertex. 
When values decreases to zero, it resets lastToken to zero, preparing to process the next 
token:

function TForm1.ProcessVertexValues(var lastToken,values,kind:integer;	
  index:integer):string; 	
var	
  value: GLfloat;	
begin	
  value := buffer[index];  	
  inc(kind);	
  dec(values);	
  case kind of	
    1: result := Format('x=%6.2f',[value]);	
    2: result := Format('y=%6.2f',[value]);	
    3: result := Format('z=%6.2f',[value]);	
    4: result := Format('r=%4.2f',[value]);	
    5: result := Format('g=%4.2f',[value]);	
    6: result := Format('b=%4.2f',[value]);	
    7: result := Format('a=%4.2f',[value]);	
  end; {case}	
  if kind<ValuesPerVertex then	
    result := result+' '	
  else	
    kind := 0; 	
  if values=0 then	
    lastToken := 0; 	
end;	

PAGE 404
 The ValuesPerVertex method simply looks at the bufType and returns the corresponding 
number of GLfloats each vertex should have:

function TForm1.ValuesPerVertex: integer;	
begin	
  case bufType of	
    GL_2D: result := 2;	
    GL_3D: result := 3;	
    GL_3D_COLOR: result := 7;	
  end; {case}	
end;	

Result

Save, compile, and run. Press the letter “F.” Exit. Look at the file. It shows that the buffer 
contained eight values. This values were a GL_POLYGON_ TOKEN, a number (of vertices) 
equal to three, and three pairs of numbers that serve as x and y coordinates of the vertices:

Feedback values returned=8	
	
GL_POLYGON_TOKEN: Vertices=3	
  x=240.00 y=240.00	
  x= 80.00 y=240.00	
  x= 80.00 y= 80.00	

Did you notice anything odd about the coordinate values? Aren’t they a little large? Weren’t 
the coordinates about 0.5 units in size? Actually the vertex values are given in window 
coordinates. So what good are they? Be patient; you will see useful values soon. Remember 
that the apparent size of an object is related to the distance from the viewer. So to determine 
true x and y coordinates from the window coordinates, you must know the z coordinate.

Go for Three

Run the program again, but press “3” before you press “F” and exit. This way the bufType 
value is GL_3D before the program enters feedback mode. Look at the file again. The 
interesting thing is that the z value right at the zNear clipping plane is 0.0.

Feedback values returned=11	
	
GL_POLYGON_TOKEN: Vertices=3	
  x=240.00 y=240.00 z=  0.00	
  x= 80.00 y=240.00 z=  0.00	
  x= 80.00 y= 80.00 z=  0.00	

PAGE 405
 
Try it again, but back up until the triangle disappears through the zFar clipping plane. Then 
move forward again until it just reappears. Press “3.” Press “F” and exit. Look at the file 
again:

Feedback values returned=11	
	
GL_POLYGON_TOKEN: Vertices=3	
  x=177.95 y=177.78 z=  1.00	
  x=142.05 y=177.78 z=  1.00	
  x=142.05 y=142.22 z=  1.00	

Now you have the “window” coordinates in the z direction. The z value of a vertex is 0.0 at 
the zNear clipping plane and 1.0 at the zFar clipping plane. Since the maximum value for z is 
1.0, you can adjust the formatting for writing out the z value in the ProcessVertexValues 
method:

    3: result := Format('z=%6.4f',[value]);	

Temporarily change the size parameter in glFeedbackBuffer in the Feedback method. Set it 
to 10 to see what OpenGL does with a feedback buffer lacking room for all the data:

  glFeedbackBuffer(10,bufType,@buffer);

Save, compile, run, press “3,” press “F,” exit, and look at the resulting file:

Feedback values returned=-1	

The negative value tells you the buffer was too small. Restore 256 as the size parameter and 
put in some temporary code to show the viewer the wz value in the caption. In the Idle 
method, replace the Caption assignment:

Caption := Format('fps=%0.1f wz=%0.2f',[rollover*mspersec/(elapsed-start),wz]);

Save, compile, and run again. Press “3” and move back just enough so that the triangle is 
about two more units down the viewing volume, or about –4.0. The wz value should be 
about –2.00. Let it sit long enough that the current wz value shows. Press “F” and exit. 
Figure 14-1 shows how it should look.


            Figure 14-1. About half.

PAGE 406
 Look at the file yet again:

Feedback values returned=11	
	
GL_POLYGON_TOKEN: Vertices=3	
  x=199.99 y=199.99 z=0.6430	
  x=120.01 y=199.99 z=0.6430	
  x=120.01 y=120.01 z=0.6430	

The goal is to calculate the true z coordinate of a vertex from the feedback value, since there 
is a direct mathematical relationship between them. Once you have the true distance down 
the viewing frustum, you can calculate the x and y coordinates from the window 
coordinates. Here is a summary of the values you can obtain at various points:

wz  RealZ WinZ 	
0.0 –2.0  0.0000	
0.5 –2.5  0.2567	
1.0 –3.0  0.4286	
2.0 –4.0  0.6429	
3.0 –5.0  0.7714	
4.0 –6.0  0.8571	
5.0 –7.0  0.9184	
6.0 –8.0  0.9643	
7.0 –9.0  1.0000	

Obviously there is not a simple linear relationship between the “window” z coordinate and 
the actual z coordinate. So more work is necessary to convert back to the true coordinates. A 
graph of the data would be nice.

Get the Points

Two birds take a hit from one programming stone. Graphing the data combines nicely with 
an example of using GL_POINT_TOKEN.

Project

Create a new directory under the Chapter.14 directory called Points. Save a new project in it 
named Points0.Dpr with a unit named Points1.Pas. Make it just like the previous project 
with exceptions as noted below.

Properties and Declarations

Change the properties of the TOpenGL component to fit the graph comfortably:

PAGE 407
 
  object OpenGL1: TOpenGL	
    pfdFlags = [DoubleBuffer, Draw_To_Window, Support_OpenGL]	
    PixelType = RGBA	
    Projection = Perspective	
    Enable = [depthTest, cullFace]	
    ClearRed = 0.6	
    ClearGreen = 0.8	
    ClearBlue = 0.8	
    prxLeft = -0.05	
    prxRight = 7.05	
    prxBottom = -0.015	
    prxTop = 1.015	
    prxNear = 2	
    prxFar = 9	
    ColorBits = 24	
    DepthBits = 32	
    StencilBits = 0	
    Left = 4	
    Top = 4	
  end	

Here are a couple of declarations you need for this project:

procedure glPointSize(size:GLfloat); stdcall;	
	
  GL_POINTS = $0;	

Rendering Code

Change the Transform1Paint method completely to plot points instead of draw a triangle:

procedure TForm1.Transform1Paint(Sender: TObject);	
begin	
  glColor3f(1.0,1.0,1.0);	
  glBegin(GL_POINTS);	
    glVertex3f(0.0,0.0000,0.0);	
    glVertex3f(0.5,0.2567,0.0);	
    glVertex3f(1.0,0.4286,0.0);	
    glVertex3f(2.0,0.6429,0.0);	
    glVertex3f(3.0,0.7714,0.0);	
    glVertex3f(4.0,0.8571,0.0);	
    glVertex3f(5.0,0.9184,0.0);	
    glVertex3f(6.0,0.9643,0.0);	
    glVertex3f(7.0,1.0000,0.0);	
  glEnd;	
end;	

PAGE 408
 Other Changes in the Methods

Add a statement to the FormCreate method to make the points larger, five pixels wide. The 
default value for the glPointSize command is 1.0, meaning one pixel.

procedure TForm1.FormCreate(Sender: TObject);	
begin	
  Application.OnIdle := Idle;	
  bufType := GL_2D;	
  glPointSize(5.0);	
end;	

In the DumpBuffer method, adjust the assignFile statement to use the path to the present 
project’s directory.

Now add some code to process points in the feedback buffer. In the ParseFeedback method 
is a main case statement. The cases handled so far are GL_POLYGON_TOKEN and 0. Add 
one for GL_POINT_TOKEN:

    GL_POINT_TOKEN: begin	
      result := ProcessVertexValues(lastToken,values,kind,index);	
    end;	
In the 0 case is an inner case statement. Add a case for GL_POINT_TOKEN there also:

        GL_POINT_TOKEN: begin	
          result := 'GL_POINT_TOKEN: vertices=1';	
          kind := 0;	
          verts := 1;	
          values := ValuesPerVertex;	
        end;	
Run

Save, compile, and run. This time GL_2D is sufficient so you can press the “2” or you can 
leave it alone, since GL_2D is the starting value. Press “F” and exit. Look at the 
Feedback.dmp file in the new directory:

Feedback values returned=27	
	
GL_POINT_TOKEN: vertices=1	
  x=  2.25 y=  4.66	
GL_POINT_TOKEN: vertices=1	
  x= 24.79 y= 84.41	
GL_POINT_TOKEN: vertices=1	
  x= 47.32 y=137.82	
GL_POINT_TOKEN: vertices=1	
  x= 92.39 y=204.40	


PAGE 409
 GL_POINT_TOKEN: vertices=1	
  x=137.46 y=244.32	
GL_POINT_TOKEN: vertices=1	
  x=182.54 y=270.94	
GL_POINT_TOKEN: vertices=1	
  x=227.61 y=289.99	
GL_POINT_TOKEN: vertices=1	
  x=272.68 y=304.25	
GL_POINT_TOKEN: vertices=1	
  x=317.75 y=315.34	

Figure 14-2 shows the graph with some embellishments.

The Formula

The graph shows an inverse relationship. The following equation describes the relationship 
between the window coordinate z and the actual z coordinate of a vertex:

winz = a + b/z	

The constants a and b must be chosen so that winz is zero at the zNear clipping plane and 
one at the zFar clipping plane. So

0 = a + b/zNear	
1 = a + b/zFar	

Solving for a and b in terms of the clipping planes gives you

a = zFar/(zFar - zNear)	
b = zNear*zFar/(zNear – zFar)	

PAGE 410
 
Now you can substitute and solve for z as a function of winz:

z = zNear*zFar/((zNear - zFar)*(winz + zFar/(zNear - zFar)))	

Keep in mind that winz is always positive while z, zNear, and zFar are always negative. If 
you use d as the depth of the viewing volume, equal to zNear – zFar, you can write the 
equations in a simpler form. In this example program, d = –2 – (–9) = 7:

a = zFar/-d	
b = zNear*zFar/d	
z = zNear*zFar/d*(winz + zFar/d)	

Once you have z you can determine x and y from the coordinates in the feedback buffer.
Here is the pattern:

zNear  HeightNear      z      winHeight zNear/z  winHeight/HeightNear	
-2.0     160         -2.0        160      1.0           1.0	
-2.0     160         -4.0        80       0.5           0.5 	
-2.0     160         -9.0        35.56    0.22          0.22 	

The windowed height is proportional to zNear/z. Obviously it must also be proportional to 
the actual height. Now, to deal with y coordinates instead of y differences (heights), you 
need an offset:

y = bottom + h*winy*z/zNear	

The x coordinate has a similar relationship:

x = left + w*winx*z/zNear	

Of course, the h and w factors need explanation. They are ratios of the zNear clipping plane 
dimensions (in programmer-defined units) to the viewport dimensions (in pixels):

h = (top – bottom)/ClientHeight	
w = (right – left)/ClientWidth	

Now, hold that thought for later use.


PAGE 411
 Bitmap

In this example program, you will add one of the enhancements of Figure 14-2. 
GL_BITMAP_TOKEN appears in a feedback list when you draw an OpenGL bitmap in your 
scene. In this case you will add two bitmaps to the graph.
Project
Create a new directory named Bitmap under the Chapter.14 directory. Save a new Delphi 
project in it called Bitmap0.Dpr with a unit named Bitmap1.Pas. Make it just like the Points0 
project, with some additions.
Variables
At the start of the implementation section, add some variables for the benefit of the bitmaps. 
Two pointer variables hold the addresses of the two bitmaps. The four integer type variables 
hold the dimensions of the bitmaps.

{$R *.DFM}	
	
var	
  bmWindowZ,	
  bmActualZ: pointer;	
  bmwzHeight,	
  bmwzWidth,	
  bmazHeight,	
  bmazWidth: GLsizei;	
Add a Function
Just after the variable declarations, you need the ReadMonochrome function to read in the 
bitmap files. You could use display lists to produce bitmap fonts, but that would produce a 
separate bitmap for each character and clutter the example.

function ReadMonochrome(const FilePath:string;var bmWidth,bmHeight:	
  GLsizei): pointer;	
const	
  szh=SizeOf(TBitmapFileHeader);	
  szi=SizeOf(TBitmapInfoHeader);	
var	
  bmpfile: file;	
  bfh: TBitmapFileHeader;	
  bmi: TBitmapInfoHeader;	


PAGE 412
   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;	

Rendering Code

Add bitmap placing to the Transform1Paint method. Put one at the top of the method and 
one at the bottom of the method to see how the arrangement of the calls will affect the 
feedback:

procedure TForm1.Transform1Paint(Sender: TObject);	
begin	
  glColor3f(1.0,1.0,1.0);	
	
  glRasterPos3f(0.4,0.4,0.0);	
  glBitmap(bmwzWidth,bmwzHeight,00,00,0,0,bmWindowZ);	
	
  glBegin(GL_POINTS);	
    glVertex3f(0.0,0.0000,0.0);	
    glVertex3f(0.5,0.2567,0.0);	
    glVertex3f(1.0,0.4286,0.0);	
    glVertex3f(2.0,0.6429,0.0);	
    glVertex3f(3.0,0.7714,0.0);	
    glVertex3f(4.0,0.8571,0.0);	
    glVertex3f(5.0,0.9184,0.0);	
    glVertex3f(6.0,0.9643,0.0);	
    glVertex3f(7.0,1.0000,0.0);	
  glEnd;	
	
  glRasterPos3f(2.7,0.04,0.0);	


PAGE 413
   glBitmap(bmazWidth,bmazHeight,00,00,0,0,bmActualZ);	
end;	

Other Changes in the Methods

Copy ActualZ.bmp and WindowZ.bmp from the CD to the current project’s directory. In the 
FormCreate method, add code at the bottom to read in the bitmaps. Notice that a 4 in 
glPixelStorei sets up double word boundaries. Some bitmaps will work with a 1, but under 
Windows, you cannot always get away with a 1:

  glPixelStorei(GL_UNPACK_ALIGNMENT,4);	
  bmActualz := ReadMonochrome('ActualZ.bmp',bmazWidth,bmazHeight);	
  bmWindowz := ReadMonochrome('WindowZ.bmp',bmwzWidth,bmwzHeight);	
Give back what you take. Add a FormDestroy method to return the memory. Remember 
what you have to do extra if you are using Delphi 2.

procedure TForm1.FormDestroy(Sender: TObject);	
begin	
  FreeMem(bmActualZ);	
  FreeMem(bmWindowZ);	
end;	
Now, to the ParseFeedback method add GL_BITMAP_TOKEN to the outer case statement, 
handled the same way as GL_POINT_TOKEN:

    GL_BITMAP_TOKEN,	
    GL_POINT_TOKEN: begin	
Also add GL_BITMAP_TOKEN to the inner case statement:

        GL_BITMAP_TOKEN: begin	
          result := 'GL_BITMAP_TOKEN: coordinates=1';	
          kind := 0;	
          verts := 1;	
          values := ValuesPerVertex;	
        end;	
Run
Save, compile, and run. Press “2,” press “F,” and exit. Notice that in the file the bitmaps are 
not grouped together. This demonstrates that the objects appear in the feedback in the same 
order they were called.

Feedback values returned=33	
	
GL_BITMAP_TOKEN: coordinates=1	
  x= 20.28 y=128.93	
GL_POINT_TOKEN: vertices=1	


PAGE 414
   x=  2.25 y=  4.66	
GL_POINT_TOKEN: vertices=1	
  x= 24.79 y= 84.41	
GL_POINT_TOKEN: vertices=1	
  x= 47.32 y=137.82	
GL_POINT_TOKEN: vertices=1	
  x= 92.39 y=204.40	
GL_POINT_TOKEN: vertices=1	
  x=137.46 y=244.32	
GL_POINT_TOKEN: vertices=1	
  x=182.54 y=270.94	
GL_POINT_TOKEN: vertices=1	
  x=227.61 y=289.99	
GL_POINT_TOKEN: vertices=1	
  x=272.68 y=304.25	
GL_POINT_TOKEN: vertices=1	
  x=317.75 y=315.34	
GL_BITMAP_TOKEN: coordinates=1	
  x=123.94 y= 17.09	
Lines

Sometimes a graph is more readable with some grid lines, and adding grid lines is a 
convenient way to examine another feedback token.

Project

Create a directory under Chapter.14 named Lines. Save a new Delphi project in it named 
Lines0.Dpr with a unit named Lines1.Pas. Use everything the Bitmap0 project had.

Support More Tokens

In the ParseFeedback method, add GL_LINE_TOKEN and GL_LINE_RESET_ TOKEN to the 
outer case statement:

    GL_LINE_RESET_TOKEN,	
    GL_LINE_TOKEN,	
    GL_BITMAP_TOKEN,	
    GL_POINT_TOKEN: begin	
Also add them to the inner case statement.

        GL_LINE_RESET_TOKEN,	
        GL_LINE_TOKEN: begin	
          result := 'GL_LINE_TOKEN: vertices=2';	
          kind := 0;	
          verts := 2;	


PAGE 415
           values := verts*ValuesPerVertex;	
        end;	

Draw the Lines

Add the following rendering code to the bottom of the Transform1Paint method:

  glBegin(GL_LINES);	
    {horizontal}	
        for ix := 0 to 10 do	
    begin	
      y := ix*0.1;	
      glVertex3f(0.0,y,0.0);	
      glVertex3f(7.0,y,0.0);	
    end;	
    {vertical}	
        for ix := 0 to 7 do	
    begin	
      glVertex3f(ix,0.0,0.0);	
      glVertex3f(ix,1.0,0.0);	
    end;	
  glEnd;	

Declare the new local variables at the top of the method:

procedure TForm1.Transform1Paint(Sender: TObject);	
var	
  y: GLfloat;	
  ix: integer;	

Run

Save, compile, run, press “2,” press “F,” and exit. The top of the file should be:

Feedback values returned=128
Added to the end of the file should be 19 entries similar to:

GL_LINE_TOKEN: vertices=2	
  x=  2.25 y=  4.66	
  x=317.75 y=  4.66	

If you care to make a distinction, GL_LINES will produce GL_LINE_RESET_ TOKEN, while 
GL_LINE_STRIP in your glBegin command will produce GL_LINE_TOKEN, except for the 
first one, which will also be a GL_LINE_ RESET_TOKEN.


PAGE 416
 Break Up

Many implementations of OpenGL break larger polygons into triangles. In the world of 
Windows, the implementation of OpenGL on any given PC very likely has that behavior. 
The current project will demonstrate that.

Project

Under the Chapter.14 directory, create a new directory named Break. In it save a new project 
named Break0.Dpr with a unit named Break1.Pas. Use everything the Lines0 project had, but 
empty the FormDestroy method (so it will go away) and leave out the ReadMonochrome 
function and the variables declared just above it. Also leave out the bitmap and points 
related code in the FormCreate method:

procedure TForm1.FormCreate(Sender: TObject);	
begin	
  Application.OnIdle := Idle;	
  bufType := GL_2D;	
  start := TimeGetTime;	
end;	

Properties

Change the frustum properties of the TOpenGL component back to their default values:

    prxLeft = -1	
    prxRight = 1	
    prxBottom = -1	
    prxTop = 1	
    prxNear = 2	
    prxFar = 9	

Rendering

Make Transform1Paint really simple:

procedure TForm1.Transform1Paint(Sender: TObject);	
begin	
  glColor3f(0.2,0.4,1.0);	
	
  glBegin(GL_POLYGON); 	
    glVertex3f(-0.4,-0.4,0.0);	
    glVertex3f(+0.4,-0.4,0.0);	
    glVertex3f(+0.4,+0.4,0.0);	
    glVertex3f(-0.4,+0.4,0.0);	


PAGE 417
   glEnd; 	
end;	

Run
Save, compile, run, press “2,” press “F,” and exit. If your implementation of OpenGL breaks 
larger polygons into triangles, the file will look like this:

Feedback values returned=16	
	
GL_POLYGON_TOKEN: Vertices=3	
  x= 96.00 y= 96.00	
  x=224.00 y= 96.00	
  x=224.00 y=224.00	
GL_POLYGON_TOKEN: Vertices=3	
  x= 96.00 y= 96.00	
  x=224.00 y=224.00	
  x= 96.00 y=224.00	

Identification

Suppose you have several different objects visible at the same time. At first you might think 
that you can distinguish them by the kinds of polygons they contain, but on many systems, 
all the polygons become triangles. Perhaps you can distinguish them by the general values of 
their coordinates. However, a pattern the human mind can discern while studying a 
feedback dump may take only a little effort, but if you need to make the distinctions 
programmatically, you may have to write a lot of code. Fortunately, OpenGL provides a 
very simple way: the GL_PASS_THROUGH_TOKEN generated by the glPassThrough 
command.

Project

Create a directory under the Chapter.14 directory and name it Id. Save a new project in it 
and name it Id0.Dpr, with a unit named Id1.Pas. Use the Break0 project as a starting point.

New Component

To accommodate two different objects to distinguish, place a second TTransform component 
on the form. Double-click it to create an OnPaint event handler. The FormPaint method 
needs to call the Apply method of the new component.

PAGE 418
 
procedure TForm1.FormPaint(Sender: TObject);	
begin	
  glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT);	
	
  glMatrixMode(GL_MODELVIEW);	
  glLoadIdentity;	
  glRotatef(angl,0.0,1.0,0.0);	
  glTranslatef(wx,0.0,wz);	
	
  Transform1.Apply;	
  Transform2.Apply;	
	
  OpenGL1.Swap;	
  OpenGL1.ErrorCheck('FormPaint');	
end;	

Properties

Here are the properties of the TTransform components:

  object Transform1: TTransform	
    TranX = -1	
    TranZ = -4	
    ScaleX = 1	
    ScaleY = 1	
    ScaleZ = 1	
    OnPaint = Transform1Paint	
    Left = 4	
    Top = 36	
  end	
  object Transform2: TTransform	
    TranX = 1	
    TranZ = -3	
    AxisX = 1	
    Angle = -90	
    ScaleX = 1	
    ScaleY = 1	
    ScaleZ = 1	
    OnPaint = Transform2Paint	
    Left = 4	
    Top = 68	
  end	
New Token

Add GL_PASS_THROUGH_TOKEN to the outer case statement of the ParseFeedback 
method:

PAGE 419
 
    GL_PASS_THROUGH_TOKEN:	
    begin	
      result := Format('id=%0.4f',[value]);	
      dec(values);	
      lastToken := 0;	
    end;	

Also add it to the inner case statement. The extra carriage return and line feed characters 
ensure that a blank line separates the newly identified object from the lines above.

        GL_PASS_THROUGH_TOKEN: begin	
          result := #13#10'GL_PASS_THROUGH_TOKEN: ';	
          values := 1;	
        end;	

Rendering

In Transform1Paint, draw a cube:

procedure TForm1.Transform1Paint(Sender: TObject);	
begin	
  glColor3f(0.2,0.4,1.0);	
  glPassThrough(1.0);	
  Cube;	
end;

In Transform2Paint, draw a pyramid:

procedure TForm1.Transform2Paint(Sender: TObject);	
begin	
  glColor3f(1.0,0.5,0.2); 	
  glPassThrough(2.0);	
  Pyramid;	
end;	

Run

Save, compile, run, press “2” and “F,” then exit. The file shows a clear distinction between 
the cube’s triangles that show and the pyramid’s one triangle that shows:

Feedback values returned=44	
	
	
GL_PASS_THROUGH_TOKEN: id=1.0000	
GL_POLYGON_TOKEN: Vertices=3	
  x=106.67 y=195.56	
  x= 35.56 y=195.56	


PAGE 420
   x= 35.56 y=124.44	
GL_POLYGON_TOKEN: Vertices=3	
  x=106.67 y=124.44	
  x=106.67 y=195.56	
  x= 35.56 y=124.44	
GL_POLYGON_TOKEN: Vertices=3	
  x=116.36 y=189.09	
  x=106.67 y=195.56	
  x=106.67 y=124.44	
GL_POLYGON_TOKEN: Vertices=3	
  x=116.36 y=130.91	
  x=116.36 y=189.09	
  x=106.67 y=124.44	
	
GL_PASS_THROUGH_TOKEN: id=2.0000	
GL_POLYGON_TOKEN: Vertices=3	
  x=286.06 y=121.21	
  x=266.67 y=160.00	
  x=227.88 y=121.21	

Real Coordinates

Now that you know the layout for a number of the feedback tokens, you can move on to 
using some of the data to extract the actual coordinates of the object.

Project

Under the Chapter.14 directory, create a new directory named Actual. Save a new project in 
it named Actual0.Dpr with a unit named Actual1.Pas. Use the Id0 project as your starting 
point.

Calculate

Once you have the z window coordinate, you can calculate the actual z coordinate. Make a 
change in the ProcessVertexValues method where it returns the z value. Since the calculation 
of the x and y coordinates require the real z value, their values must wait until this point. 
The you must “look back” in the buffer to values you already processed. This may seem 
almost like cheating, but just a little.

    3: with OpenGL1 do	
    begin	
      d := prxFar-prxNear;	
      h := (prxTop-prxBottom)/ClientHeight;	
      w := (prxRight-prxLeft)/ClientWidth;	


PAGE 421
       t := prxNear*prxFar;	
      v := value-prxFar/d;	
      z := t/(d*v);	
      winy := buffer[index-1];	
      winx := buffer[index-2];	
      y := prxBottom+h*winy*z/(-prxNear);	
      x := prxLeft+w*winx*z/(-prxNear);	
      result := Format('z=%6.4f (%6.3f,%6.3f,%6.3f)',[value,x,y,z]);	
    end;	
You need to add some local variables to support the calculations:

var	
  h,w,d,t,	
  x,y,z,v,	
  winx,winy,	
  value: GLfloat;	

Run

Save, compile, run, press “3” and “F,” then exit. The file shows real coordinates as well as 
window coordinates now:

Feedback values returned=59	
	
	
GL_PASS_THROUGH_TOKEN: id=1.0000	
GL_POLYGON_TOKEN: Vertices=3	
  x=106.67 y=195.56 z=0.5714 ( 0.200, 1.200,-3.600)	
  x= 35.56 y=195.56 z=0.5714 (-0.600, 1.200,-3.600)	
  x= 35.56 y=124.44 z=0.5714 (-0.600, 0.400,-3.600)	
GL_POLYGON_TOKEN: Vertices=3	
  x=106.67 y=124.44 z=0.5714 ( 0.200, 0.400,-3.600)	
  x=106.67 y=195.56 z=0.5714 ( 0.200, 1.200,-3.600)	
  x= 35.56 y=124.44 z=0.5714 (-0.600, 0.400,-3.600)	
GL_POLYGON_TOKEN: Vertices=3	
  x=116.36 y=189.09 z=0.7013 ( 0.600, 1.600,-4.400)	
  x=106.67 y=195.56 z=0.5714 ( 0.200, 1.200,-3.600)	
  x=106.67 y=124.44 z=0.5714 ( 0.200, 0.400,-3.600)	
GL_POLYGON_TOKEN: Vertices=3	
  x=116.36 y=130.91 z=0.7013 ( 0.600, 0.800,-4.400)	
  x=116.36 y=189.09 z=0.7013 ( 0.600, 1.600,-4.400)	
  x=106.67 y=124.44 z=0.5714 ( 0.200, 0.400,-3.600)	
	
GL_PASS_THROUGH_TOKEN: id=2.0000	
GL_POLYGON_TOKEN: Vertices=3	
  x=286.06 y=121.21 z=0.5065 ( 1.950, 0.250,-3.300)	
  x=266.67 y=160.00 z=0.4286 ( 1.500, 0.500,-3.000)	
  x=227.88 y=121.21 z=0.5065 ( 1.350, 0.250,-3.300)	

PAGE 422
 Color

This example takes little extra effort. Use the Actual0 project and run it again. This time press 
“C” before you press “F” and exit. This time the file has GL_3D_COLOR information:

Feedback values returned=119	
	
	
GL_PASS_THROUGH_TOKEN: id=1.0000	
GL_POLYGON_TOKEN: Vertices=3	
  x=106.67 y=195.56 z=0.5714 ( 0.200, 1.200,-3.600) r=0.20 g=0.40 b=1.00 a=1.00	
  x= 35.56 y=195.56 z=0.5714 (-0.600, 1.200,-3.600) r=0.20 g=0.40 b=1.00 a=1.00	
  x= 35.56 y=124.44 z=0.5714 (-0.600, 0.400,-3.600) r=0.20 g=0.40 b=1.00 a=1.00	
GL_POLYGON_TOKEN: Vertices=3	
  x=106.67 y=124.44 z=0.5714 ( 0.200, 0.400,-3.600) r=0.20 g=0.40 b=1.00 a=1.00	
  x=106.67 y=195.56 z=0.5714 ( 0.200, 1.200,-3.600) r=0.20 g=0.40 b=1.00 a=1.00	
  x= 35.56 y=124.44 z=0.5714 (-0.600, 0.400,-3.600) r=0.20 g=0.40 b=1.00 a=1.00	
GL_POLYGON_TOKEN: Vertices=3	
  x=116.36 y=189.09 z=0.7013 ( 0.600, 1.600,-4.400) r=0.20 g=0.40 b=1.00 a=1.00	
  x=106.67 y=195.56 z=0.5714 ( 0.200, 1.200,-3.600) r=0.20 g=0.40 b=1.00 a=1.00	
  x=106.67 y=124.44 z=0.5714 ( 0.200, 0.400,-3.600) r=0.20 g=0.40 b=1.00 a=1.00	
GL_POLYGON_TOKEN: Vertices=3	
  x=116.36 y=130.91 z=0.7013 ( 0.600, 0.800,-4.400) r=0.20 g=0.40 b=1.00 a=1.00	
  x=116.36 y=189.09 z=0.7013 ( 0.600, 1.600,-4.400) r=0.20 g=0.40 b=1.00 a=1.00	
  x=106.67 y=124.44 z=0.5714 ( 0.200, 0.400,-3.600) r=0.20 g=0.40 b=1.00 a=1.00	
	
GL_PASS_THROUGH_TOKEN: id=2.0000	
GL_POLYGON_TOKEN: Vertices=3	
  x=286.06 y=121.21 z=0.5065 ( 1.950, 0.250,-3.300) r=1.00 g=0.50 b=0.20 a=1.00	
  x=266.67 y=160.00 z=0.4286 ( 1.500, 0.500,-3.000) r=1.00 g=0.50 b=0.20 a=1.00	
  x=227.88 y=121.21 z=0.5065 ( 1.350, 0.250,-3.300) r=1.00 g=0.50 b=0.20 a=1.00	

Texture

Now, what kind of texture information would be returned in the feedback buffer? To save 
space in the output file, drop back to the Id0 project as the basis for a new project that will 
demonstrate texture information.

Project

Create a new directory under the Chapter.14 directory and name it Texture. Save a new 
project in the new directory and name it Texture0.Dpr with a unit named Texture1.Pas. 
Name the first TTransform component TransformCube. Name the second TTransform 
component TransformLight.


PAGE 423
 Properties

Add a TMaterial component. Set its properties as follows:

  object Material1: 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 = 36	
  end	

Add a TLight component. Set its properties as follows:

  object Light1: TLight	
    Which = 1	
    Cutoff = 65	
    AmbientRed = 0.2	
    AmbientGreen = 0.2	
    AmbientBlue = 0.2	
    DiffuseRed = 1	
    DiffuseGreen = 1	
    DiffuseBlue = 1	
    Direction = True	
    Position = True	
    Spotlight = True	
    Enabled = True	
    Left = 40	
    Top = 4	
  end	

While the properties of TransformCube are the same as the Transform1 properties in the Id0 
project, the TransformLight properties are different from previous TTransform component 
properties. It aims the light so that only part of the cube gets more than ambient light.

  object TransformLight: TTransform	
    AxisY = 1	
    Angle = -55.4	
    ScaleX = 1	
    ScaleY = 1	
    ScaleZ = 1	


PAGE 424
     OnPaint = TransformLightPaint	
    Left = 4	
    Top = 36	
  end	

Also, the Enable property of the TOpenGL component includes lighting.

Load a Texture

At the top of the implementation section, add some constants, types, and variables to 
support your texture:

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	
  sBrick,	
  tBrick: GLsizei;	
  brick: pointer;	
Just below the variables add the ReadBitmap function:

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,	
  size: integer;	
begin	
  assignFile(bmpfile,FilePath);	
  reset(bmpfile,1);	
  size := FileSize(bmpfile)-szh-szi;	
  blockread(bmpfile,bfh,szh);	


PAGE 425
   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;	

Initializing and Finishing

The FormCreate method needs to include code that initializes textures. Be sure to copy fig13-
10.bmp from the CD into the current project’s directory and use the full path to that 
directory in the call to ReadBitmap. Since the scene has only one object, you can turn on -
texturing at the start and leave it:

procedure TForm1.FormCreate(Sender: TObject);	
begin	
  glPixelStorei(GL_UNPACK_ALIGNMENT,1);	
  glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_S,GL_REPEAT);	
  glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_T,GL_REPEAT);	
  glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_NEAREST);	
  glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_NEAREST);	
  glTexEnvf(GL_TEXTURE_ENV,GL_TEXTURE_ENV_MODE,GL_MODULATE);	
  brick := ReadBitmap('fig13-10.bmp',sBrick,tBrick); 	
  glTexImage2D(GL_TEXTURE_2D,Level,ColorComps,sBrick,tBrick,Border,	
    GL_RGB,GL_UNSIGNED_BYTE,Brick);	
  glEnable(GL_TEXTURE_2D);	
  glLightModeli(GL_LIGHT_MODEL_TWO_SIDE,GL_TRUE);	
  Application.OnIdle := Idle;	
  bufType := GL_2D;	
  start := TimeGetTime;	
end;	

Add a FormDestroy method in which to give back the memory you allocated for the texture 
image. Remember the Delphi 2 variation if appropriate.

PAGE 426
 
procedure TForm1.FormDestroy(Sender: TObject);	
begin	
  FreeMem(brick);	
end;	

Paint

In the FormPaint method, be sure to call the Apply method of both TTransform components:

procedure TForm1.FormPaint(Sender: TObject);	
begin	
  glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT);	
	
  glMatrixMode(GL_MODELVIEW);	
  glLoadIdentity;	
  glRotatef(angl,0.0,1.0,0.0);	
  glTranslatef(wx,0.0,wz);	
	
  TransformLight.Apply;	
  TransformCube.Apply;	
	
  OpenGL1.Swap;	
  OpenGL1.ErrorCheck('FormPaint');	
end;	

Be sure to call the TLight component’s Apply method also:

procedure TForm1.TransformLightPaint(Sender: TObject);	
begin	
  Light1.Apply;	
end;	

Keyboard

To the case statement in the FormKeyDown method, add a bufType for the letter “T”:

    ord('T'): begin	
      bufType := GL_3D_COLOR_TEXTURE;	
    end;	

Rendering

The TransformCubePaint method draws a triangle strip to form a cube and tacks on the 
brick texture:

procedure TForm1.TransformCubePaint(Sender: TObject);	
begin	


PAGE 427
   Material1.Apply;	
  glBegin(GL_TRIANGLE_STRIP);	
   glTexCoord2f(00.0,0.0); glVertex3f(-0.4,+0.4,+0.4);	
   glTexCoord2f(00.0,4.0); glVertex3f(-0.4,-0.4,+0.4);	
   glTexCoord2f(04.0,0.0); glVertex3f(+0.4,+0.4,+0.4);	
   glTexCoord2f(04.0,4.0); glVertex3f(+0.4,-0.4,+0.4);	
	
   glTexCoord2f(08.0,0.0); glVertex3f(+0.4,+0.4,-0.4);	
   glTexCoord2f(08.0,4.0); glVertex3f(+0.4,-0.4,-0.4);	
   glTexCoord2f(12.0,0.0); glVertex3f(-0.4,+0.4,-0.4);	
   glTexCoord2f(12.0,4.0); glVertex3f(-0.4,-0.4,-0.4);	
	
   glTexCoord2f(16.0,0.0); glVertex3f(-0.4,+0.4,+0.4);	
   glTexCoord2f(16.0,4.0); glVertex3f(-0.4,-0.4,+0.4);	
  glEnd;	
end;	

Floor

Draw a large rectangle for the floor or ground:

procedure TForm1.TransformFloorPaint(Sender: TObject);	
begin	
  MaterialFloor.Apply;	
  glBegin(GL_POLYGON);	
    glVertex3f(-32.0,0.0,-32.0);	
    glVertex3f(-32.0,0.0,+32.0);	
    glVertex3f(+32.0,0.0,+32.0);	
    glVertex3f(+32.0,0.0,-32.0);	
  glEnd;	
end;	

Light

Position the light and draw a cube around it:

procedure TForm1.TransformLightPaint(Sender: TObject);	
begin	
  MaterialLight.Apply;	
  Light1.Apply;	
  Cube;	
end;	

Vertex Values

Add four lines to the case statement of the ProcessVertexValues method:

    8: result := Format('s=%4.2f',[value]);	
    9: result := Format('t=%4.2f',[value]);	


PAGE 428
    10: result := Format('u=%4.2f',[value]);	
   11: result := Format('v=%4.2f',[value]);	

Also add a line to the case statement of the method:

    GL_3D_COLOR_TEXTURE: result := 11;	

Run

Save, compile, run, press “T” and “F,” then exit. The contents of the file show the s and t 
coordinates for each visible vertex. Since the texture is only 2-D, the u and v texture 
coordinates remain at zero. Notice that the color information applies to the material color 
rather than the color of the texture that actually shows. Instead of shades of red, the colors in 
the feedback buffer are white and dark gray, depending on the lighting.

Feedback values returned=140	
	
GL_POLYGON_TOKEN: Vertices=3	
  x=106.67 y=195.56 z=  0.57 r=0.20 g=0.20 b=0.20 a=1.00 s=4.00 t=0.00 u=0.00 v=1.00	
  x= 35.56 y=195.56 z=  0.57 r=0.20 g=0.20 b=0.20 a=1.00 s=0.00 t=0.00 u=0.00 v=1.00	
  x= 35.56 y=124.44 z=  0.57 r=0.20 g=0.20 b=0.20 a=1.00 s=0.00 t=4.00 u=0.00 v=1.00	
GL_POLYGON_TOKEN: Vertices=3	
  x=106.67 y=124.44 z=  0.57 r=0.20 g=0.20 b=0.20 a=1.00 s=4.00 t=4.00 u=0.00 v=1.00	
  x=106.67 y=195.56 z=  0.57 r=0.20 g=0.20 b=0.20 a=1.00 s=4.00 t=0.00 u=0.00 v=1.00	
  x= 35.56 y=124.44 z=  0.57 r=0.20 g=0.20 b=0.20 a=1.00 s=0.00 t=4.00 u=0.00 v=1.00	
GL_POLYGON_TOKEN: Vertices=3	
  x=116.36 y=189.09 z=  0.70 r=1.00 g=1.00 b=1.00 a=1.00 s=8.00 t=0.00 u=0.00 v=1.00	
  x=106.67 y=195.56 z=  0.57 r=0.20 g=0.20 b=0.20 a=1.00 s=4.00 t=0.00 u=0.00 v=1.00	
  x=106.67 y=124.44 z=  0.57 r=0.20 g=0.20 b=0.20 a=1.00 s=4.00 t=4.00 u=0.00 v=1.00	
GL_POLYGON_TOKEN: Vertices=3	
  x=116.36 y=130.91 z=  0.70 r=1.00 g=1.00 b=1.00 a=1.00 s=8.00 t=4.00 u=0.00 v=1.00	
  x=116.36 y=189.09 z=  0.70 r=1.00 g=1.00 b=1.00 a=1.00 s=8.00 t=0.00 u=0.00 v=1.00	
  x=106.67 y=124.44 z=  0.57 r=0.20 g=0.20 b=0.20 a=1.00 s=4.00 t=4.00 u=0.00 v=1.00	

Shadows

Now that you know how to collect information into the feedback buffer and how to parse 
and interpret it, perhaps you are wondering what to do with all this wonderful information. 
What follows is just one tiny example: a very rudimentary shadow generator.

Project

Create a new directory under the Chapter.14 directory and call it Shadow. In it, save a new 
project called Shadow0.Dpr with a unit named 

PAGE 429
 Shadow1.Pas. Onto the form drop a TOpenGL component, a TLight component, three 
TTransform components, and four TMaterial components.

Properties

Listing 14-2 shows the properties. Notice that the colors for MaterialShadow are half the 
intensity of the colors for MaterialFloor. Since the shadow will be cast on the floor, and there 
is ambient light, the color should be a darker version of the floor color rather than black.

object Form1: TForm1	
  Left = 192	
  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	
  OnKeyDown = FormKeyDown	
  OnKeyUp = FormKeyUp	
  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 = 42	
    ColorBits = 24	
    DepthBits = 32	
    StencilBits = 0	
    Left = 4	


PAGE 430
     Top = 4	
  end	
  object TransformCube: TTransform	
    TranX = -1	
    TranZ = -4	
    ScaleX = 1	
    ScaleY = 1	
    ScaleZ = 1	
    OnPaint = TransformCubePaint	
    Left = 4	
    Top = 68	
  end	
  object MaterialCube: TMaterial	
    ambGreen = 1	
    ambBlue = 1	
    ambAlpha = 1	
    difGreen = 1	
    difBlue = 1	
    difAlpha = 1	
    aFace = faFrontAndBack	
    dFace = faFrontAndBack	
    sFace = faFront	
    Left = 40	
    Top = 68	
  end	
  object Light1: TLight	
    Which = 1	
    Cutoff = 65	
    AmbientRed = 0.2	
    AmbientGreen = 0.2	
    AmbientBlue = 0.2	
    DiffuseRed = 1	
    DiffuseGreen = 1	
    DiffuseBlue = 1	
    Direction = True	
    Position = True	
    Spotlight = True	
    Enabled = True	
    Left = 40	
    Top = 36	
  end	
  object TransformLight: TTransform	
    TranX = -0.5	
    TranY = 9	
    TranZ = -2	
    AxisX = 1	
    Angle = -60	
    ScaleX = 1	
    ScaleY = 1	


PAGE 431
     ScaleZ = 1	
    OnPaint = TransformLightPaint	
    Left = 4	
    Top = 36	
  end	
  object MaterialLight: TMaterial	
    ambRed = 1	
    ambGreen = 1	
    ambBlue = 1	
    ambAlpha = 1	
    difRed = 1	
    difGreen = 1	
    difBlue = 1	
    difAlpha = 1	
    aFace = faFrontAndBack	
    dFace = faFrontAndBack	
    sFace = faFront	
    Left = 72	
    Top = 36	
  end	
  object TransformFloor: TTransform	
    TranY = -1	
    ScaleX = 1	
    ScaleY = 1	
    ScaleZ = 1	
    OnPaint = TransformFloorPaint	
    Left = 4	
    Top = 100	
  end	
  object MaterialFloor: TMaterial	
    ambRed = 1	
    ambGreen = 0.6	
    ambBlue = 0.2	
    ambAlpha = 0.2	
    difRed = 1	
    difGreen = 0.6	
    difBlue = 0.2	
    difAlpha = 0.2	
    aFace = faFrontAndBack	
    dFace = faFrontAndBack	
    sFace = faFront	
    Left = 40	
    Top = 100	
  end	
  object MaterialShadow: TMaterial	
    ambRed = 0.5	
    ambGreen = 0.3	
    ambBlue = 0.2	
    ambAlpha = 1	


PAGE 432
     difRed = 0.5	
    difGreen = 0.3	
    difBlue = 0.2	
    difAlpha = 1	
    aFace = faFront	
    dFace = faFront	
    sFace = faFront	
    Left = 40	
    Top = 132	
  end	
end	

Listing 14-2. Shadow0 project properties.

Implementation

While the interface section is just like the last project (with additional declarations for the 
added components and lack of a FormDestroy declaration), the implementation section has 
substantial differences. This project has no constants, types, variables, or non-method 
functions global to the implementation section since it does not use textures, but it does keep 
the pi180 initialized variable. Methods that remain the same are FormResize, FormKeyUp, 
Adjust, Feedback, DumpBuffer, ParseFeedback, ValuesPerVertex, and ProcessVertexValues.
The private section of the form type declaration follows:

  private	
    angl,	
    rspeed,	
    speed,	
    tran,	
    rot,	
    wx,	
    wy,	
    wz: GLfloat;	
    bufType: GLenum;	
    how_many: GLsizei;	
    frames,	
    elapsed,	
    start: integer;	
    buffer: array[1..256] of GLfloat;	
    procedure Adjust;	
    procedure Feedback;	
    procedure DumpBuffer;	
    function ParseFeedback(var lastToken,values,verts,kind:integer;	
      index:integer):string;	
    function ValuesPerVertex:integer;	
    function ProcessVertexValues(var lastToken,values,kind:integer;	


PAGE 433
       index:integer):string;	
    procedure Idle(Sender:TObject;var Done:boolean);	
  public	

Initialize

The FormCreate method is much smaller, but notice the addition of glBlendFunc, which will 
help you see through the floor when you are below it:

procedure TForm1.FormCreate(Sender: TObject);	
begin	
  glLightModeli(GL_LIGHT_MODEL_TWO_SIDE,GL_TRUE);	
  Application.OnIdle := Idle;	
  bufType := GL_2D;	
  start := TimeGetTime;	
  glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA);	
end;	

Paint

The noteworthy feature of the FormPaint method is the two different ways of positioning the 
viewer. The original way kept the wy value at zero and rotated about the y-axis for traveling 
around on the floor, but once the value of wy changes from zero, an alternate set of 
transformations applies that uses wy for translation and rotates about the x-axis. For wy 
equal to zero it disables blending so the floor is opaque, but for non-zero values, it enables 
blending so the low alpha values allow you to see through the floor.

procedure TForm1.FormPaint(Sender: TObject);	
begin	
  glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT);	
  glMatrixMode(GL_MODELVIEW);	
  glLoadIdentity;	
  if wy=0 then	
  begin	
    glRotatef(angl,0.0,1.0,0.0);	
    glTranslatef(wx,0.0,wz);	
    glDisable(GL_BLEND);	
  end else begin	
    glRotatef(angl,1.0,0.0,0.0);	
    glTranslatef(wx,wy,wz);     	
    glEnable(GL_BLEND);	
  end;	
  TransformLight.Apply;	
  TransformCube.Apply;	
  TransformFloor.Apply;	


PAGE 434
   {Draw shadow here}	
	
  OpenGL1.Swap;	
  OpenGL1.ErrorCheck('FormPaint');	
end;	

New Keys

You need to use the FormKeyDown method from the last project and add some local 
variable declarations to it:

var	
  deltaX,	
  deltaY,	
  deltaZ,	
  slopeY,	
  slopeZ,	
  x,y,z,h,w,	
  x1,y1,z1,	
  x2,y2,z2,	
  by,bz: GLfloat; 	
  ix,v: integer;	

The FormKeyDown method introduces responses to some additional keystrokes. The 
response to the “X” key is an addition to the case statement that allows the user to shift the x 
coordinate directly. In conjunction with a Shift key it will increase the value of wx; otherwise 
it will decrease the value of wx. This section of code also makes the form caption reflect the 
viewer position and orientation.

    ord('X'): begin	
      if ssShift in Shift then	
        wx := wx+0.1	
      else	
        wx := wx-0.1;	
      Caption := Format('wx=%0.2f wy=%0.2f wz=%0.2f a=%0.2f',	
        [wx,wy,wz,angl]);	
    end;	

Similarly, the “Y” key directly adjusts the value of wy and the “Z” key changes the value of 
wz:

    ord('Y'): begin	
      if ssShift in Shift then	
        wy := wy+0.1	
      else	
        wy := wy-0.1;	
      Caption := Format('wx=%0.2f wy=%0.2f wz=%0.2f a=%0.2f',     	
        [wx,wy,wz,angl]);	


PAGE 435
     end;	
    ord('Z'): begin	
      if ssShift in Shift then	
        wz := wz+0.1	
      else	
        wz := wz-0.1;	
      Caption := Format('wx=%0.2f wy=%0.2f wz=%0.2f a=%0.2f',	
        [wx,wy,wz,angl]);	
    end;	

The “A” key modifies the value of angl:

    ord('A'): begin	
      if ssShift in Shift then	
        angl := angl+1.0	
      else	
        angl := angl-1.0;	
      Caption := Format('wx=%0.2f wy=%0.2f wz=%0.2f a=%0.2f',	
        [wx,wy,wz,angl]);	
    end;	

The “B” key performs a special service. Consider the position of the light and the position of 
the cube hovering just above the floor. Two points are all you need to define a straight line, 
even in three-dimensional space, so imagine a line running from the light to the center of the 
cube and extending to the floor. The place where that line meets the floor would be the 
center of a shadow if OpenGL made shadows.

Notice the section begins with a comment containing the famous equation of a line on an x-y 
plane. Immediately afterwards, a comment and some code define point 1 as the center of the 
colored cube. The Shapes unit draws a cube around the origin, so the position of the cube is 
completely defined by the fields of the transformation component.

    ord('B'): begin {y=mx+b}	
      {point 1 is cube center}	
          with TransformCube do	
      begin	
        x1 := TranX;	
        y1 := TranY;	
        z1 := TranZ;	
      end;	

Point 2 is the position of the light:

      {point 2 is light center}	
          with TransformLight do	
      begin	
        x2 := TranX;	
        y2 := TranY;	


PAGE 436
         z2 := TranZ;	
      end;	

Of course, the famous slope-intercept equation of a line applies to two dimensions, but you 
can use the equation twice, once for the x-y plane and once for the x-z plane. To determine 
the slopes for the two equations, you need the differences between the coordinates of the 
two points. The delta variables store those differences. The two slopes (rise over run) are 
ratios of deltas:

      deltaX := x2-x1;	
      deltaY := y2-y1;	
      deltaZ := z2-z1;	
      slopeY := deltaY/deltaX;	
      slopeZ := deltaZ/deltaX;	

Since the coordinates of two points on the line are known, you can recast the equations to 
solve for the constant (b) terms in the equations:

      by := y1-x1*slopeY;	
      bz := z1-x1*slopeZ;	

In a comment is the slope-intercept equation again, using the Delphi variable names. Rather 
than solving for y, set y to –1, since that is the value it will have where the line meets the 
floor, because the y value is –1 everywhere on the floor. You can rearrange the equation to 
solve for x in terms of the known y value. Once the equation for the x-y plane has 
determined the x value, you can use the equation of the line for the x-z plane to solve for z. 
Now you have all three coordinates of the point where the line meets the floor. The caption 
assignment reveals the values to you:

      y := -1.0;	
      {y := slopeY*x+by;}	
      x := (y-by)/slopeY;	
      z := slopeZ*x+bz;	
      Caption := Format('x=%0.3f y=%0.3f z=%0.3f',[x,y,z]);	
    end; {Build Shadow}	

Rendering

The TransformCubePaint method includes two glPassthrough commands to surround the 
rendering of the cube, so that the feedback buffer will identifiably delimit the coordinates 
that belong to the cube:

procedure TForm1.TransformCubePaint(Sender: TObject);	
begin	
  MaterialCube.Apply;	
  glPassthrough(-21.00);	
  Cube;	


PAGE 437
   glPassthrough(-22.00);	
end;	

Idle

The Idle method is almost identical to the one in the last project, except for a slight change in 
the caption. It has a new variable to help:

var	
  fps: GLfloat;	

The new caption preserves the position information when the frame rate information shows:

    fps := rollover*mspersec/(elapsed-start);	
    Caption := Format('f=%0.1f x=%0.2f y=%0.2f z=%0.2f a=%0.2f',	
      [fps,wx,wy,wz,angl]);	

Run

There is more to do, but for now use the work you have already done to reveal where the 
line leads. Save, compile, and run. Press “B.” Figure 14-3 shows the result.




        Figure 14-3. Where the imaginary
        line meets the floor.

Now use Shift+X to move the x coordinate viewpoint to 1.1. Remember that the viewer 
coordinates have the opposite sign from each of the coordinates in the scene. Use Shift+Z to 
advance through the cube to 4.3. Press Shift+Y and drop the viewer below the floor until wy 
equals 3.0. Press “A” until the angl value reaches –90.0. Figure 14-4 shows the viewer 
looking up through the floor at the bottom of the cube and the light beyond it. While the 
viewer is at y = 3.0, the clipping plane is actually 2.0 units “up.” So the viewport has just 
penetrated the floor at y = –1.0. If you could have positioned the viewer at the exact 
coordinates of the calculation (adjusting 

PAGE 438
 for the signs), you would have the exact position of the shadow on the floor.

Press “F” to get a data capture. The file is shown below. Notice that values for 
GL_PASS_THROUGH_TOKEN surround the polygons for the cube.


           Figure 14-4. Approximate
           position of the shadow.

Feedback values returned=132	
	
GL_POLYGON_TOKEN: Vertices=3	
  x=185.51 y=214.93	
  x=165.10 y=214.93	
  x=165.27 y=234.99	
GL_POLYGON_TOKEN: Vertices=3	
  x=186.35 y=234.99	
  x=185.51 y=214.93	
  x=165.27 y=234.99	
GL_POLYGON_TOKEN: Vertices=3	
  x=187.00 y=207.34	
  x=185.51 y=214.93	
  x=186.35 y=234.99	
GL_POLYGON_TOKEN: Vertices=3	
  x=187.94 y=228.35	
  x=187.00 y=207.34	
  x=186.35 y=234.99	
GL_POLYGON_TOKEN: Vertices=3	
  x=165.40 y=207.34	
  x=187.00 y=207.34	
  x=187.94 y=228.35	
GL_POLYGON_TOKEN: Vertices=3	
  x=165.59 y=228.35	
  x=165.40 y=207.34	
  x=187.94 y=228.35	
GL_POLYGON_TOKEN: Vertices=3	
  x=165.10 y=214.93	
  x=165.40 y=207.34	
  x=165.59 y=228.35	
GL_POLYGON_TOKEN: Vertices=3	
  x=165.27 y=234.99	


PAGE 439
   x=165.10 y=214.93	
  x=165.59 y=228.35	
	
GL_PASS_THROUGH_TOKEN: id=-21.0000	
GL_POLYGON_TOKEN: Vertices=3	
  x=207.06 y=225.88	
  x=131.76 y=225.88	
  x=123.08 y=246.15	
GL_POLYGON_TOKEN: Vertices=3	
  x=221.54 y=246.15	
  x=207.06 y=225.88	
  x=123.08 y=246.15	
GL_POLYGON_TOKEN: Vertices=3	
  x=207.06 y=150.59	
  x=207.06 y=225.88	
  x=221.54 y=246.15	
GL_POLYGON_TOKEN: Vertices=3	
  x=221.54 y=147.69	
  x=207.06 y=150.59	
  x=221.54 y=246.15	
GL_POLYGON_TOKEN: Vertices=3	
  x=131.76 y=150.59	
  x=207.06 y=150.59	
  x=221.54 y=147.69	
GL_POLYGON_TOKEN: Vertices=3	
  x=123.08 y=147.69	
  x=131.76 y=150.59	
  x=221.54 y=147.69	
GL_POLYGON_TOKEN: Vertices=3	
  x=131.76 y=225.88	
  x=131.76 y=150.59	
  x=123.08 y=147.69	
GL_POLYGON_TOKEN: Vertices=3	
  x=123.08 y=246.15	
  x=131.76 y=225.88	
  x=123.08 y=147.69	
	
GL_PASS_THROUGH_TOKEN: id=-22.0000	

Capture Data

Perhaps you see where this is headed. Get feedback data while the viewport is at the 
calculated position and use the coordinates of the cube (ignoring the z values) to draw a 
shadow. To the “B” key processing, add code that sets the viewer coordinates according to 
the calculated values, collects feedback data, and returns the viewer to the starting position, 
even if the viewer position was something else at the time of the keypress:

PAGE 440
 
      Caption := Format('x=%0.3f y=%0.3f z=%0.3f',[x,y,z]);	
      wx := -x;	
      wy := -y+2.0;	
      wz := -z; 	
      angl := -90.0;	
      glFeedbackBuffer(256,GL_2D,@buffer);	
      glRenderMode(GL_FEEDBACK);	
      FormPaint(nil);	
      how_many := glRenderMode(GL_RENDER);	
      wx := 0.0;	
      wy := 0.0;	
      wz := 0.0; 	
      angl := 0.0;	
Add a loop that searches the feedback buffer for the first passthrough token value. These are 
the only possible negative numbers in the buffer, so the search is easy.

      ShadStart := 0;	
      repeat	
        inc(ShadStart);	
      until buffer[ShadStart]<0.0;	
Declare the variable in the private section of the form type declaration:

    ShadStart: integer;	
To eliminate the possibility of fooling the system if you press “F” some time after you press 
“B,” add code to the Feedback method to clear ShadStart:

  DumpBuffer;	
  ShadStart := 0;	
end;	
Meanwhile, back at the “B” processing, you need a loop to convert window coordinates to 
scene coordinates. Start with the first vertex. Remember the cube was all triangles, so even if 
your implementation of OpenGL does not break all polygons into triangles, this set of 
polygons will still consist of triangles. Conversion is simplified in this case because the 
position of the shadow is right on the clipping plane at the viewport. In other words, the 
projected image is being treated as if it were the actual image. The y coordinates of the 
feedback become z coordinates of the shadow projection. Both x and z coordinates must be 
offset by the viewer position (line intercepting the plane position) at the time of the snapshot.

      with OpenGL1 do	
      begin	
        h := (prxTop-prxBottom)/ClientHeight;	
        w := (prxRight-prxLeft)/ClientWidth;	
        ix := ShadStart+2;	


PAGE 441
         repeat	
                  for v := 1 to 3 do	
          begin	
            buffer[ix] := x+prxLeft+buffer[ix]*w; 	
            buffer[ix+1] := z+prxBottom+buffer[ix+1]*h;	
            inc(ix,2);	
          end;	
          inc(ix,2);	
        until (ix>=how_many) or (buffer[ix]<-20.0);	
      end; {with}	
    end; {Build Shadow}	

Draw Shadow

The FormPaint method needs an addition of a similar loop to use the feedback data if 
ShadStart indicates the relevant data is available:

  TransformFloor.Apply;	
  {Draw shadow here}	
    if ShadStart>0 then	
  begin	
    ix := ShadStart+2;	
    MaterialShadow.Apply;	
    glBegin(GL_TRIANGLES);	
    repeat	
      for v := 1 to 3 do	
      begin	
        glVertex3f(buffer[ix],-1.0,buffer[ix+1]);	
        inc(ix,2);	
      end;	
      inc(ix,2);	
    until (ix>=how_many) or (buffer[ix]<-20.0);	
    glEnd;	
  end; {if}	
	
  OpenGL1.Swap;	
  OpenGL1.ErrorCheck('FormPaint');	
end;	

Of course you need to declare the loop variables:

procedure TForm1.FormPaint(Sender: TObject);	
var	
  v,	
  ix: integer;	
begin	

PAGE 442
 Run Again

Save, compile, and run. Press “B” and behold the sudden appearance of a crude shadow as 
in Figure 14-5. This is a rough and simplistic method of shadow generation that will not 
really bear up well under close scrutiny, but it does illustrate programmatic use of feedback 
data.

Identifier Reference
Identifier			Description
glFeedbackBuffer
An OpenGL command that determines the kind of feedback and identifies the buffer to 
receive feedback data.
glPassThrough
An OpenGL command that places a marker in the feedback buffer for easy identification of 
sets of data.
glPointSize
An OpenGL command that determines the approximate pixel width for drawing a point.
GL_2D
An OpenGL constant for feedback that requires only x and y coordinates for each vertex.
GL_3D
An OpenGL constant for feedback that requires x, y, and z coordinates for each vertex.

PAGE 443
 
Identifier			Description
GL_3D_COLOR
An OpenGL constant for feedback that requires x, y, 
and z coordinates plus color data for each vertex.
GL_3D_COLOR_
TEXTURE
An OpenGL constant for feedback that requires x, y, 
and z coordinates plus color data plus texture s, t, u, 
and v coordinates for each vertex.
GL_4D_COLOR_
TEXTURE
An OpenGL constant for feedback that requires x, y, z, 
and w (clip) coordinates plus color data plus texture s, 
t, u, and v coordinates for each vertex.
GL_BITMAP_TOKEN
An OpenGL constant used in examining a feedback 
buffer for the vertex of an OpenGL bitmap position.
GL_COPY_PIXEL_
TOKEN
An OpenGL constant used to examine a feedback 
buffer for the vertex that identifies the position of a 
copied pixel rectangle.
GL_DRAW_PIXEL_
TOKEN
An OpenGL constant used to examine a feedback 
buffer for the vertex that identifies the position of a 
drawn pixel rectangle.
GL_LINE_RESET_
TOKEN
An OpenGL constant used in examining a feedback 
buffer for vertices of endpoints of line segments. This 
marker occurs for each line segment generated with 
GL_LINES and for the first line segment generated 
with GL_LINE_STRIP or GL_LINE_LOOP.
GL_LINE_TOKEN
An OpenGL constant used in examining a feedback 
buffer for vertices of endpoints of line segments. This 
marker occurs for each line segment (after the first) 
generated with GL_LINE_STRIP or GL_LINE_LOOP.
GL_PASS_THROUGH_
TOKEN
An OpenGL constant used in examining a feedback 
buffer for programmer-defined markers.
GL_POINTS
An OpenGL constant to pass to the glBegin command 
to render each subsequent vertex before the next glEnd 
command as individual points.
GL_POINT_TOKEN
An OpenGL constant used in examining a feedback 
buffer for the vertex of a point.
GL_POLYGON_TOKEN
An OpenGL constant used in examining a feedback 
buffer for the size and vertices of polygons.

PAGE 444
 Summary
In this chapter you learned to put OpenGL into feedback mode and return with data in a 
feedback buffer. You learned how to set aside a block of memory as a feedback buffer and to 
control what kind of feedback data OpenGL returns. You also learned how to parse and 
interpret the contents of a feedback buffer and developed an example of using the feedback 
data programmatically.


PAGE 445
 

PAGE 446

Chapter 15

The End—the Beginning

Well, you have covered a lot of territory in OpenGL and now have the basic tools you need 
to render and even animate great OpenGL scenes. Most importantly you have seen how to 
integrate OpenGL with Delphi. Take a moment to catch your breath, then congratulate 
yourself, then brace yourself. Although this book has given you a lot of OpenGL information 
and practice, it barely scratches the surface of what you can do with OpenGL.

More Buffers

Many OpenGL features have not even been explored in this book. You have read about 
several buffers in OpenGL, for example, but they are not the only ones. There are interesting 
things you can do with the stencil buffer, such as blocking out parts of a scene, that are 
outside the scope of this book that you may want to explore. You could use still another 
buffer, the accumulation buffer, to simulate motion blur.

More Curves

The curved surfaces you produced with small polygons and smooth shading are not the only 
way you can make curves in OpenGL. You could draw Bézier curves and surfaces or even 
Nurbs (Non-uniform rational B-splines).

More Effects

While this book did briefly introduce a simple shadow technique, there are many other ways 
available to produce shadows that are much better, albeit with serious performance hits. 
Special techniques such as rendering in more than one pass, with different states turned on 
or off in each pass, can produce significant effects such as highlighting on a texture.


PAGE 447
 More Math

In this book you needed nothing more than beginning algebra, and that rarely. You have 
seen that you can do a lot without getting very mathematical. OpenGL handles the gory 
details quite well. But, face it, if you really want to get fancy with three-dimensional 
graphics, you will have to get your hands dirty with math.

Matrix Math

Obviously, by the matrix nature of OpenGL, a good understanding of matrix manipulation 
and uses of matrices in three-dimensional graphics will help.

Algebra

A really solid grasp of algebra, trigonometry, and geometry will certainly be a plus. The 
more you know, the easier you can produce algorithms to reach your goals. You may be able 
to solve many problems without much knowledge in these areas, but you will have to work 
harder to get there.

Esoterica

Other branches of mathematics can also help you with some very special effects. Calculus 
and symmetry theory, for example, have been used to solve some difficult graphics 
problems.

More Information

There is more to know about the general nature of OpenGL, such as the basic steps that 
OpenGL takes to produce a pattern of colored pixels on the screen that you perceive as a 
scene.

Kinds of Data
There are two kinds of data that enter OpenGL’s processing: pixel data and geometry data. 
These two kinds of data go through two different pipelines until they merge near the end to 
produce the final step of pixels in the frame buffer.

PAGE 448
 Geometry Data

Geometric primitives can be points, lines, or polygons, which always resolve to vertices. 
These can come from the program in immediate mode (commands executed directly), from 
display lists, or even from evaluators for Béziers and Nurbs. The latter may start out as curve 
specifications, but they eventually turn into vertices. As you saw from the feedback chapter, 
vertex data consists of a lot more than just space coordinates.

In whatever way the vertex data arrives, it goes to vertex operations and primitive assembly. 
In the operations on each vertex, OpenGL transforms the vertex by the ModelView matrix, 
then by the projection matrix, along with clipping. Then OpenGL performs perspective 
division, which is dividing by the w coordinate. Finally OpenGL performs depth range and 
culling operations as appropriate and the viewport transformations to produce window 
coordinates. At this time OpenGL performs lighting calculations on the transformed 
coordinates.

The next step is rasterization. OpenGL determines which squares in a grid of window 
coordinates are occupied. The fragments produced here go to the fragment operations.

In the operations on each fragment, if texturing is involved, OpenGL generates texels. It then 
performs fog calculations as appropriate. OpenGL performs the scissor test, the alpha test, 
the stencil test, the depth-buffer test, and blending, if enabled. The results are written into 
the appropriate buffer and stored as pixels in the frame buffer.

Pixel Data

Pixel data such as bitmaps and images are unpacked into memory with the appropriate 
layout. Then operations you specify with OpenGL commands are applied. From that point, 
the pixel data may go directly to texture memory or proceed to the rasterization step just as 
if it came from geometry data. Interestingly, image data may come originally from the frame 
buffer, packed into an appropriate format, and sent back around. It almost sounds like 
having a cow drink milk for recycling, but it actually has its uses.

More Efficiency

When you want animation, you want efficiency, and three-dimensional graphics is the very 
antithesis of that. A huge amount of computation goes into rendering a 3-D scene. 
Fortunately, modern PCs have an astonishing 

PAGE 449
 amount of “horsepower” and modern video cards typically include hardware acceleration 
support for OpenGL.

Still, you do not have to introduce very much complication into an OpenGL scene to start 
bogging down your frame rate. As you advance in your knowledge of OpenGL internals, 
you can develop techniques in your coding that improve the efficiency of the code you write 
for using OpenGL capabilities.

You will, for example, want to do such things as minimize the number of state changes per 
frame. OpenGL has to perform internal calculations and realign pointers, etc., when you 
change states. Suppose you have to enable texturing for a certain part of your scene, and 
leave it disabled for the rest of the scene, except for one part where it uses a different texture. 
You may have to perform several state changes relating to the texturing before each of those 
two parts. You may find efficiency is greater if you leave the glEnable (GL_TEXTURE_2D) as 
the last change. While texturing is disabled and you make texture-related state changes, 
OpenGL only has to store the fact of the new state for each call. When you make the glEnable 
call, OpenGL performs all its calculations and adjustments only once. If, instead, you 
enabled texturing first, and the other texturing state changes afterward, OpenGL would 
have to perform its internal acrobatics with each state change you make.

With this one fact alone, you can go back and improve the frame rate of several of the 
example programs. Imagine what you can do if you really get into it!

More Resources

Naturally, you can expand your knowledge of various kinds of mathematics and general 
graphics principles with textbooks and reference books, but there is also a wealth of 
information on the Internet.

There are newsgroups and mailing lists devoted to OpenGL, to writing games, to general 
graphics topics, and much more. There are many web sites containing treatises on related 
subjects. On the CD included with this book is an HTML file with links to many such 
resources. You can expand your knowledge just as far as you want to go.

In conclusion, you may have reached the end of this book and obtained just about all you 
want, because it is enough for you to do great things with three-dimensional graphics. If, 
however, you want to go beyond even that, your learning adventure has just begun.

PAGE 450

