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

