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

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


PAGE 197
   OnMouseMove = FormMouseMove
  OnMouseUp = FormMouseUp
  OnPaint = FormPaint
  OnResize = FormResize
  PixelsPerInch = 96
  TextHeight = 13
  object OpenGL1: TOpenGL
    pfdFlags = [Draw_To_Window, Support_OpenGL]
    PixelType = RGBA
    Projection = Perspective
    Enable = [depthTest, Lighting]
    ClearRed = 0.6
    ClearGreen = 0.8
    ClearBlue = 0.8
    prxLeft = -1
    prxRight = 1
    prxBottom = -1
    prxTop = 1
    prxNear = 2
    prxFar = 9
    ColorBits = 24
    DepthBits = 32
    Left = 4
    Top = 4
  end
  object Transform1: TTransform
    TranX = 0.6
    TranY = -0.6
    TranZ = -2.2
    AxisX = 0.2
    AxisY = 1
    Angle = 15
    ScaleX = 1
    ScaleY = 1
    ScaleZ = 1
    OnPaint = Transform1Paint
    Left = 4
    Top = 36
  end
  object Light1: TLight
    Which = 1
    Cutoff = 65
    AmbientRed = 0.2
    AmbientGreen = 0.2
    AmbientBlue = 0.2
    DiffuseRed = 1
    DiffuseGreen = 1
    DiffuseBlue = 1
    Direction = True


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


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

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

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

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

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

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

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


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

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

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

procedure TForm1.FormResize(Sender: TObject);	
begin	
  if OpenGL1.Smaller then	
    FormPaint(Sender);	
end;	
	
procedure TForm1.FormPaint(Sender: TObject);	
begin	
  glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT);	
  Transform1.Apply;	


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

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

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


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

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

  startX := X;	
  startY := Y;	


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

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

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

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

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

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

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

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


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

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

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


PAGE 208
   Caption = 'Form1'
  Font.Charset = DEFAULT_CHARSET
  Font.Color = clWindowText
  Font.Height = -11
  Font.Name = 'MS Sans Serif'
  Font.Style = []
  OnCreate = FormCreate
  OnPaint = FormPaint
  OnResize = FormResize
  PixelsPerInch = 96
  TextHeight = 13
  object OpenGL1: TOpenGL
    pfdFlags = [DoubleBuffer, Draw_To_Window, Support_OpenGL]
    PixelType = RGBA
    Projection = Perspective
    Enable = [depthTest, Lighting]
    ClearRed = 0.6
    ClearGreen = 0.8
    ClearBlue = 0.8
    prxLeft = -1
    prxRight = 1
    prxBottom = -1
    prxTop = 1
    prxNear = 2
    prxFar = 9
    ColorBits = 24
    DepthBits = 32
    Left = 8
    Top = 4
  end
  object Transform1: TTransform
    TranX = 0.6
    TranY = -0.6
    TranZ = -2.2
    AxisX = 0.2
    AxisY = 1
    Angle = 15
    ScaleX = 1
    ScaleY = 1
    ScaleZ = 1
    OnPaint = Transform1Paint
    Left = 8
    Top = 40
  end
  object Light1: TLight
    Which = 1
    Cutoff = 65
    AmbientRed = 0.2
    AmbientGreen = 0.2


PAGE 209
     AmbientBlue = 0.2
    DiffuseRed = 1
    DiffuseGreen = 1
    DiffuseBlue = 1
    SpecularRed = 1
    SpecularGreen = 1
    SpecularBlue = 1
    Direction = True
    Position = True
    Spotlight = True
    Enabled = True
    Left = 44
    Top = 40
  end
  object Transform2: TTransform
    TranZ = -3
    AxisX = 0.2
    AxisY = 1
    ScaleX = 1
    ScaleY = 1
    ScaleZ = 1
    OnPaint = Transform2Paint
    Left = 8
    Top = 76
  end
  object Material1: TMaterial
    ambRed = 1
    ambAlpha = 1
    difRed = 1
    difAlpha = 1
    aFace = faFront
    dFace = faFront
    sFace = faFront
    mShine = 128
    Left = 44
    Top = 76
  end
  object Material2: TMaterial
    ambBlue = 1
    ambAlpha = 1
    difBlue = 1
    difAlpha = 1
    aFace = faBack
    dFace = faBack
    sFace = faBack
    mShine = 128
    Left = 80
    Top = 76


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

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



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

procedure TForm1.Idle(Sender:TObject;var Done:boolean);	
var	
  ratio: GLfloat;	
begin	
  with OpenGL1,Transform2 do	
  begin	
    if Angle<359.5 then	
      Angle := Angle+0.5	
    else	
      Angle := 0;	
    ratio := abs(tranZ/prxNear);	
	
    if (-TranZ<prxNear) then	
      signZ := -1.0	
    else	
      if (-TranZ>prxFar) then	
        signZ := 1.0;	
    TranZ := TranZ+deltaZ*signZ;	
	
    if (TranY<prxBottom*ratio) then	
      signY := 1.0	
    else	
      if (TranY>prxTop*ratio) then	
        signY := -1.0;	
    TranY := TranY+deltaY*signY;	
	
    if (TranX<prxLeft*ratio) then	
      signX := 1.0	
    else	
      if (TranX>prxRight*ratio) then	


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

procedure TForm1.FormResize(Sender: TObject);	
begin	
  if OpenGL1.Smaller then	
    FormPaint(Sender);	
end;	
	
procedure TForm1.FormPaint(Sender: TObject);	
begin	
  glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT);	
  Transform1.Apply;	
  Transform2.Apply;	
  {glFlush}OpenGL1.Swap;	
  OpenGL1.ErrorCheck('FormPaint');	
end;	
	
procedure TForm1.Transform1Paint(Sender: TObject);	
begin	
  Light1.Apply;	
end;	
	
procedure TForm1.Transform2Paint(Sender: TObject);	
begin	
  Material1.Apply;	
  Material2.Apply;	
  Cylinder;	
end;	
Listing 9-3. Auto project methods.

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

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


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

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

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

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

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

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


PAGE 217
 

PAGE 218

