-Writing a Camera class-
Here is the tut on the camera class. I will, ver the course of this tut, present a camera class that can be used (and extended) quite easily. It can also be adapted quite easily for first person viewing, although I will only be using it as a 3rd person cam. First, let us cover a little ground on camera transformations. I presume most of you reading this tut have used gluLookAt. If not, I suggest you use that for a bit, to get the hang of viewing without the matrix math involved. Ihave always found it better to think of the scene in terms of world axes, and leaving the viewing to a separate mechanism, which in this case would be a camera class. That way, you never end up thinking in the "opposite" way (world-to-view, not view-to-everything-else).
For those of you who *have* used gluLookAt, it is analogous to a target camera in MAX. I mean, you tell where it is supposed to be, which co ordinates to look at, and you're all set. gluLookAt, by the way accomplishes this using a set of glRotatef's and glTranslatef's. I have found it better to construct the entire matrix on my own, and glMatrixMult it at one go.
The camera we construct will be a "free camera" in the MAX sense. I mean, we can tell it where to position itself, and which direction it has to orient itself. You cant target it to a co-ordinate or anything (not the base class).

I will now briefly cover the camera model we are going to use. The working model that I implemented for my engine was from a splendid tutorial by Tom Nuydens of Delphi3D. It's called the RUV model (thats what I call it, anyway). If we were to use a translation, and X,Y,Z rotation to represent the camera, we would suffer from the problem of gimbal lock (it is really exasperating to code a lot and then discover you were wrong to start with). I am not going to go into what gimbal lock is, or anything. You can find a lot of atricles on it. This camera overcomes this drawback, and that should be good enough.

First let me explain what the 3 axes R,U and V mean. Perhaps a diagram can do it better than words.
R, could quite simply be Right (of the camera that is.) U is, you guessed it Up. V (no points for guessing this one) is View. So these, along with the origin of this axis system constitutes an entire description of camera orientation. This, actually is the famous axis-angle system. This is not a math paper, so I wont go into the specifics. Since all rotations happen relative to the axes, there will be no gimbal lock. The base TRUVCamera class (that's what we're going to call it), will implement only the basics. A property to set the current translation of the camera axis system (or the camera if you prefer it) relative to the world origin, and a set of three methods that rotate the camera about its R, U and V axes respectively.
Here is a snippet of code that shows the declaration of the camera class. Actually, it is better to declare a base camera class, that implements basic stuff that any camera would require. You got it, that would be a viewing volume. So whats shown below is the interface of the base TCamera class. We will derive other cameras from this, so TRUVCamera will be be a descendant of TCamera.
    //Base camera class to derive custom cameras from
    TCamera=class
                 private
                 fName:shortstring;
                 fViewVolume:TViewVolume;
                 public
                 constructor Create;
                 procedure SetView;virtual;
                 property Name:shortstring
                          read fName
                          write fName;
                 property ViewVolume:TViewVolume
                          read fViewVolume
                          write fViewVolume;
    end;
The virtual method SetView sets the viewing volume using the instance of TViewVolume declared. All descendants, then, have to call inherited SetView before tending to their business. The implementation is trivial, and I'm not going to go into it here.
So then, we finally arrive at the declaration of the TRUVCamera class. OH! Another thing. Starting from now, we will be using two units, one with basic types, called (for want of imagination) Basetypes.pas and the other with basic math routines BaseMath.pas respectively. There are a lot of types and functions in the two, but i'll explain only what is used in the tuts. That should keep things simpler (for me!:)
    TRUVCamera=class(TCamera)
                             private
                             fViewpoint:TVector;
                             fR:TVector;
                             fU:TVector;
                             fV:TVector;
                             protected
                             public
                             constructor Create;
                             property R:TVector
                                      read fR
                                      write fR;
                             property U:TVector
                                      read fU
                                      write fU;
                             property V:TVector
                                      read fV
                                      write fV;
                             public
                             procedure RotateAboutR(amt:TFloat);
                             procedure RotateAboutU(amt:TFloat);
                             procedure RotateAboutV(amt:TFloat);
                             function GetView:TMatrix;virtual;
                             procedure SetView;override;
                             property Viewpoint:TVector
                                      read fViewpoint
                                      write fViewpoint;
    end;
The position of the camera, is represented by a simple translation, and hence a TVector. You can see, that the TRUVCamera.SetView overrides the TCamera class's SetView method. The only things that merit description here, are the three methods RotateAboutR, RotateAboutU and RotateAbouV.(There are other methods in the actual declaration, but they quite simple, and do not merit a discussion here. Only the most important methods/properties are listed.)
We'll see what these three do, before we take a look into the innards of the SetView method.
procedure TRUVCamera.RotateAboutR(amt:TFloat);
begin
     fR:=VectorRotateX(fR,amt);
     fU:=VectorRotateX(fU,amt);
     fV:=VectorRotateX(fV,amt);
     fR:=VectorNormalize(fR);
     fU:=VectorNormalize(fU);
     fV:=VectorNormalize(fV);
end;
All this function (the other two are written in a similar vein) does is to rotate the three vectors by the requested angle. I also found wierd things happening if I dont normalize the three vectors. If someone finds a way to do this faster, please let me know.
Here is what the functions VectorRotateX and VectorNormalize do
function VectorRotateX(v:TVector;a:TFloat):TVector;
var
   temp: TVector;
   sine,cosine:TFloat;
begin
     a:=a*DEGTORAD;
     sine:=Sin(a);
     cosine:=Cos(a);     temp[X] := v[x];
     temp[Y]:= (v[Y] * cosine) + (v[Z] * -sine);
     temp[Z] := (v[Y] * sine) + (v[Z] * cosine);
     result := temp;
end;function VectorNormalize(v:TVector):TVector;
var
   val:TFloat;
begin
     val:=v[x]*v[x]+
          v[y]*v[y]+
          v[z]*v[z];
     val:=sqrt(val);
     result[x]:=v[x]/val;
     result[y]:=v[y]/val;
     result[z]:=v[z]/val;
end;
So, after all this, lets go into the SetView method, which is the heartbeat of our camera.
function TRUVCamera.GetView:TMatrix;
var
   rotMatrix,transMatrix:TMatrix;
begin
     transMatrix:=GetIdentity;
     transMatrix[0,3]:=fViewpoint[0];
     transMatrix[1,3]:=fViewpoint[1];
     transMatrix[2,3]:=fViewpoint[2];     rotMatrix:=GetIdentity;     rotMatrix[0,0]:=fR[0];
     rotMatrix[1,0]:=fR[1];
     rotMatrix[2,0]:=fR[2];     rotMatrix[0,1]:=fU[0];
     rotMatrix[1,1]:=fU[1];
     rotMatrix[2,1]:=fU[2];     rotMatrix[0,2]:=fV[0];
     rotMatrix[1,2]:=fV[1];
     rotMatrix[2,2]:=fV[2];     result:=MatrixMultiply(transMatrix,rotMatrix);
end;
procedure TRUVCamera.SetView;
var
   view:TMatrix;
begin
     inherited;
     view:=GetView;
     view:=MatrixTranspose(view);
     glMatrixMode(GL_MODELVIEW);
     glLoadIdentity;
     glMultMatrixf(@view[0,0]);     
end;

SetView internally calls the function GetView, which returns a matrix. GetView constructs the view matrix in three steps.

  • It first makes the translation happen by loading the appropriate values in the Identity matrix.
  • It then loads the appropriate values of the axes R,U,V into another matrix.
  • It finally returns the product of the two matrices as the result.
SetView converts the row major matrix into column major order, and performs a glMultMatrix with the identity matrix to achieve the required view. Simple, huh?!!
We revisit the textured box of the Texture tutorial in the camera class. You might have noticed that in the texture tutorial, sometimes the box moves in an unnatural manner. That is, rotates up when the mouse is moving down, etc. Try rotating the box in this tutorial. You'll find that, no matter how you rotate the box, the sense of direction is maintained. This is because we have gotten rid of gimbal lock (YIPPPEEEE!). Since the example is really similar to the previous one, I'm not going into it here. Besides, the code is heavily commented. Next tutorial, billboards.
>>Download the tutorial source
You'll also need, in case you dont have them already
>>Download the OpenGL 1.2 headers