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

