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
