PDA

View Full Version : First try at OOP oriented OpenGL



Robert Kosek
01-12-2004, 04:18 PM
I got sick of rewritting everything for lighting so I wrote a unit to save me time. For usage rights check the embedded readme/license/terms etc. It's a simple controller for the OpenGL lights and is quite versital. I'd like your critique.

This was written in Delphi 6 with the current DOT Toolkit (http://www.delphi3d.net/dot/).

{
This unit was written by Robert Kosek. (C) December 2004, All rights reserved.
Use at your own risk/inconvenience. The author is not responsible for any
problems, instability, stress or weight gain caused by the use of this unit. :)

This unit, OOLights.pas, is freely usable in commercial/shareware applications
so long as the filename is unchanged and the Author (Robert Kosek) is credited.

Any modifications should be submitted to me via PM at the http://www.dgdev.tk
forums.

** TAKEN FROM THE REDBOOK CHAPTER 6 **
http://fly.cc.fer.hr/~unreal/theredbook/chapter06.html

Parameter Name Default Value Meaning
GL_AMBIENT (0.0, 0.0, 0.0, 1.0) ambient RGBA intensity of light
GL_DIFFUSE (1.0, 1.0, 1.0, 1.0) diffuse RGBA intensity of light
GL_SPECULAR (1.0, 1.0, 1.0, 1.0) specular RGBA intensity of light
GL_POSITION (0.0, 0.0, 1.0, 0.0) (x, y, z, w) position of light
GL_SPOT_DIRECTION (0.0, 0.0, -1.0) (x, y, z) direction of spotlight
GL_SPOT_EXPONENT 0.0 spotlight exponent
GL_SPOT_CUTOFF 180.0 spotlight cutoff angle
GL_CONSTANT_ATTENUATION 1.0 constant attenuation factor
GL_LINEAR_ATTENUATION 0.0 linear attenuation factor
GL_QUADRATIC_ATTENUATION 0.0 quadratic attenuation factor
}

unit OOLights;

interface

uses
Windows, SysUtils, Classes, GL, GLu, DotMath;

type
TLightItem = class(TCollectionItem)
private
FLightNumber: GLUint;
FAmbient,FDiffuse,FSpecular,FPosition: TDotVector4;
FSpotDirection: TDotVector3;
FSpotExponent: GLFloat;
FSpotCutOff: GLFloat;
FConstantAttenuation: GLfloat;
FLinearAttenuation: GLfloat;
FQuadraticAttenuation: GLfloat;
public
property LightNumber: GLUint read FLightNumber write FLightNumber;
property Ambient: TDotVector4 read FAmbient write FAmbient;
property Diffuse: TDotVector4 read FDiffuse write FDiffuse;
property Specular: TDotVector4 read FSpecular write FSpecular;
property Position: TDotVector4 read FPosition write FPosition;
property SpotDirection: TDotVector3 read FSpotDirection write FSpotDirection;
property SpotExponent: GLfloat read FSpotExponent write FSpotExponent;
property SpotCutOff: GLfloat read FSpotCutOff write FSpotCutOff;
property ConstantAttenuation: GLfloat read FConstantAttenuation write FConstantAttenuation;
property LinearAttenuation: GLfloat read FLinearAttenuation write FLinearAttenuation;
property QuadraticAttenuation: GLfloat read FQuadraticAttenuation write FQuadraticAttenuation;
constructor Create(Collection: TCollection); override;
procedure UpdateGL(visible: boolean);
end;

TLightSystem = class(TCollection)
private
function GetItem(Index: integer): TLightItem;
procedure SetItem(Index: integer; const Value: TLightItem);
public
property Items[Index: Integer]: TLightItem read GetItem write
SetItem; default;
constructor Create(ItemClass: TCollectionItemClass);
function AddNewLight: TLightItem;
end;

implementation

{ TLightSystem }

function TLightSystem.AddNewLight: TLightItem;
begin
if Count < 8 then
result := TLightItem.Create(Self)
else
result := nil;
end;

constructor TLightSystem.Create(ItemClass: TCollectionItemClass);
begin
inherited;
glEnable(GL_LIGHTING);
end;

function TLightSystem.GetItem(Index: Integer): TLightItem;
begin
Result := TLightItem(inherited GetItem(Index));
end;

procedure TLightSystem.SetItem(Index: Integer;
const Value: TLightItem);
begin
inherited SetItem(Index, Value);
end;

{ TLightItem }

{ tracks the lighting var and sets the default parameters }
constructor TLightItem.Create(Collection: TCollection);
const gll: array[0..7] of GLUInt = (
GL_LIGHT0,GL_LIGHT1,GL_LIGHT2,GL_LIGHT3,GL_LIGHT4,
GL_LIGHT5,GL_LIGHT6,GL_LIGHT7
);
begin
inherited Create(Collection);

if Collection.Count-1 > 7 then raise EInvalidOperation.CreateFmt('Cannot have more than 8 light definitions. Count: %d',[Collection.Count]);
FLightNumber := gll[Collection.Count-1];
glEnable(FLightNumber);

FAmbient := dotVector4(0.0,0.0,0.0,1.0);
FDiffuse := dotVector4(1.0,1.0,1.0,1.0);
FSpecular := dotVector4(1.0,1.0,1.0,1.0);
FPosition := dotVector4(0.0,0.0,0.0,0.0);

FSpotDirection := dotVector3(0.0,0.0,-1.0);

FSpotExponent := 0.0;
FSpotCutOff := 180.0;

FConstantAttenuation := 1.0;
FLinearAttenuation := 0.0;
FQuadraticAttenuation := 1.0;
end;

{ Updates the light's vars and show/hides should it be needed }
procedure TLightItem.UpdateGL(visible: boolean);
begin
if visible then begin
glEnable(FLightNumber);

glLightfv(FLightNumber, GL_AMBIENT, @FAmbient.xyzw);
glLightfv(FLightNumber, GL_DIFFUSE, @FDiffuse.xyzw);
glLightfv(FLightNumber, GL_SPECULAR, @FSpecular.xyzw);
glLightfv(FLightNumber, GL_POSITION, @FPosition.xyzw);
glLightfv(FLightNumber, GL_SPOT_DIRECTION, @FSpotDirection.xyz);
glLightf( FLightNumber, GL_SPOT_EXPONENT, FSpotExponent);
glLightf( FLightNumber, GL_SPOT_CUTOFF, FSpotCutOff);
glLightf( FLightNumber, GL_CONSTANT_ATTENUATION, FConstantAttenuation);
glLightf( FLightNumber, GL_LINEAR_ATTENUATION, FLinearAttenuation);
glLightf( FLightNumber, GL_QUADRATIC_ATTENUATION, FQuadraticAttenuation);

end else glDisable(FLightNumber);
end;

end.

Robert Kosek
01-12-2004, 07:55 PM
Here's a texture manager I did. So far it's entirely flawless. Based off the textures.pas from Sulaco (http://www.sulaco.ca.za). Would someone please critique these?? I'd like to know how well I'm doing on the Object Orientation side of things.

{
This unit was written by Robert Kosek. (C) December 2004, All rights reserved.
Use at your own risk/inconvenience. The author is not responsible for any
problems, instability, stress or weight gain caused by the use of this unit. :)

This unit, OOTextures.pas, is freely usable in commercial/shareware applications
so long as the filename is unchanged and the Author (Robert Kosek) is credited.

Any modifications should be submitted to me via PM at the http://www.dgdev.tk
forums.
}

unit OOTextures;

interface

uses
Windows, SysUtils, Classes, GL, {GLu, }Textures;

type
TTextureItem = class(TCollectionItem)
private
FTexture: GLuint;
FTexId: string;
public
property Texture: GLuint read FTexture write FTexture;
property TexId: string read FTexId write FTexId;
constructor Create(Collection: TCollection; Filename: string); overload;
end;

TTextureSystem = class(TCollection)
private
function GetItem(Index: integer): TTextureItem;
procedure SetItem(Index: integer; const Value: TTextureItem);
function FindItem(TexID: string): TTextureItem;
public
property Items[Index: Integer]: TTextureItem read GetItem write
SetItem; default;
function AddTexture(filename: string): integer;
end;

implementation

{ TTextureSystem }

function TTextureSystem.AddTexture(filename: string): integer;
var item: TTextureItem;
begin
item := TTextureItem.Create(Self,filename);
item := nil;
result := Count-1;
end;

function TTextureSystem.FindItem(TexID: string): TTextureItem;
var i: integer;
begin
for i := 0 to count-1 do
if Items[i].TexId = TexID then begin
result := Items[i];
break;
end;
end;

function TTextureSystem.GetItem(Index: Integer): TTextureItem;
begin
Result := TTextureItem(inherited GetItem(Index));
end;

procedure TTextureSystem.SetItem(Index: Integer;
const Value: TTextureItem);
begin
inherited SetItem(Index, Value);
end;

{ TTextureItem }

constructor TTextureItem.Create(Collection: TCollection; Filename: string);
begin
inherited Create(Collection);
LoadTexture(filename,ftexture,false);
FTexID := filename;
end;

end.

Traveler
01-12-2004, 08:36 PM
I guess problems would surface only when these are actually used, but from what I can see it looks pretty good.
I'm not entirely sure if its really needed, but I suppose when you have a constructor in there a destructor would be necessary as welll.

Also I noticed the default keyword, but without a value assigned to it. Why is that? And why use it at all? In the book Mastering Delph 5, the author says default values for published properties help reduce the dfm filesize and ultimately the exe filesize.

Robert Kosek
01-12-2004, 08:39 PM
I guess problems would surface only when these are actually used, but from what I can see it looks pretty good. I'm using the texture one without a hitch, but I haven't tested the lighting one. I just got sick of the constant rewriting of code. Just so you know, this isn't a component you drop on the form ... it's all runtime.

OpenGL in it's texture system automatically free's the textures ... though the vectors may need to be freed. I'll look into that.

Useless Hacker
01-12-2004, 11:39 PM
Also I noticed the default keyword, but without a value assigned to it. Why is that? And why use it at all? In the book Mastering Delph 5, the author says default values for published properties help reduce the dfm filesize and ultimately the exe filesize.When default is used in this context with an array property, it allows you to access that property without explicitly naming it, but just using square brackets after the class name, i.e. instead of:
LightSystem.Items[i]
you can use:
LightSystem[i]
Obviously, only one default property may be used per class.

Robert Kosek
01-12-2004, 11:49 PM
True. I've been using:

glBindTexture(GL_TEXTURE_2D,Textures[0].Texture);Without a hitch. It works smoothly and quickly. I'm getting an average of 2000fps with a simple scene (I bought a Radeon 9800 XT recently).

Traveler
02-12-2004, 08:30 AM
Thanks for clearing that up!

savage
02-12-2004, 12:56 PM
Both look very good. My only suggestion is related to cross-platform portability, and possibly speed.

TCollection and TCollectionItem are ideal if you plan to stream the object as it inherits from TPerstent. I would be inclined to write a more specific streaming code and would suggest doing something like...


TLightItem = class( TObject )
public
// All your other properties here
procedure Initialise;
end;

TLightSystem = class( TObjectList )
private
function GetItems( aIndex : Integer ) : TLightItem;
procedure SetItems( aIndex : Integer; const aLightItem : TLightItem );
public
function Add( aLightItem : TLightItem ) : Integer;
function Extract( aLightItem : TLightItem ) : TLightItem;
function Remove( aLightItem : TLightItem ) : Integer;
function IndexOf( aLightItem : TLightItem ) : Integer;
function First : TLightItem;
function Last : TLightItem;
procedure Insert( aIndex : Integer; aLightItem : TLightItem );
property Items[ Index : Integer ] : TLightItem read GetItems write SetItems; default;
end;
...
implementation

{ TLightSystem }

function TLightSystem.Add(aLightItem : TLightItem): Integer;
begin
result := inherited Add( aLightItem);
end;

function TLightSystem.Extract(aLightItem : TLightItem): TLightItem;
begin
result := TLightItem( inherited Extract( aLightItem) );
end;

function TLightSystem.First: TLightItem;
begin
result := TLightItem( inherited First );
end;

function TLightSystem.GetItems(aIndex: Integer): TLightItem;
begin
result := TLightItem( inherited Items[ aIndex ] );
end;


function TLightSystem.IndexOf(aLightItem : TLightItem): Integer;
begin
result := inherited IndexOf( aLightItem);
end;

procedure TLightSystem.Insert(aIndex: Integer; aLightItem : TLightItem);
begin
inherited Insert( aIndex, aLightItem);
end;

function TLightSystem.Last: TLightItem;
begin
result := TLightItem( inherited Last );
end;

function TLightSystem.Remove(aLightItem : TLightItem): Integer;
begin
result := inherited Remove( aLightItem);
end;

procedure TLightSystem.SetItems(aIndex: Integer; const aLightItem : TLightItem);
begin
inherited Items[ aIndex ] := aLightItem;
end;



This should give you a lighter and thus faster ( though I would suggest benchmarking things like Add and Remove to be sure ) class hierachy and should also be portable to FreePascal should you want to port your code later on. The you could add specific Load and Save Methods to the TLightSytem class to handle your streaming.

Anyway it is just a thought.

Robert Kosek
02-12-2004, 03:34 PM
Good I missed that. I'll check it out, and maybe even get a few extra milliseconds back. :D

{MSX}
02-12-2004, 06:16 PM
function TTextureSystem.FindItem(TexID: string): TTextureItem;
var i: integer;
begin
for i := 0 to count-1 do
if Items[i].TexId = TexID then begin
result := Items[i];
break;
end;
end;


Doesn't this give you a warning? result may be undefined if you pass a string that you don't have.
Also, i suggest too to pass to TList (or TObjectList), and what about removing that Windows unit uses that it's probably unuseful ? :P

Bye!

savage
02-12-2004, 09:32 PM
Has anyone done any benchmarks to see if this slightly changed version of the previous code is slightly faster, due to caching the result of the count-1 call?


function TTextureSystem.FindItem(TexID: string): TTextureItem;
var
i : integer;
lCount : integerl
begin
lCount := count - 1;
for i := 0 to lCount do
if Items[i].TexId = TexID then
begin
result := Items[i];
break;
end;
end;

Robert Kosek
02-12-2004, 09:38 PM
Doesn't this give you a warning? result may be undefined if you pass a string that you don't have. If so then you (the programmer) has a problem and not the unit.

I'll benchmark the 2 functions a bit later ... I'm preocuppied at the moment.

cairnswm
03-12-2004, 06:15 AM
savage - I dont think that makes a difference. In Delphi the final value of the loop cannot be changed after the loop has started:


procedure TForm1.Button2Click(Sender: TObject);
Var
I : Integer;
C : Integer;
begin
C := 100;
For I := 1 to C do
Begin
C := C + 1;
End;
Button2.Caption := IntToStr(C);
end;


Gives a final value of?

{MSX}
03-12-2004, 08:06 AM
Doesn't this give you a warning? result may be undefined if you pass a string that you don't have. If so then you (the programmer) has a problem and not the unit.

:shock:

Can I disagree completely with that ? :P
How do you test if the returned object is valid ? In such case you should return nil if you don't find the item.
btw the corrected method is very simple:


function TTextureSystem.FindItem(TexID: string): TTextureItem;
var i: integer;
begin
for i := 0 to count-1 do
if Items[i].TexId = TexID then begin
result := Items[i];
exit;
end;
result:=nil;
end;


Edited: damned Java :P

{MSX}
03-12-2004, 08:09 AM
Has anyone done any benchmarks to see if this slightly changed version of the previous code is slightly faster, due to caching the result of the count-1 call?

I think it calculates the upper limit of the for cycle only once.