unit Frost.Renderers;

interface       

uses
  OpenGL,
  _Geometry,
  Frost.GL,
  Frost.GameObjects,
  Frost.RenderEngine;
  
type
TFace = array [0..2] of Cardinal;
TQuad = array [0..3] of Cardinal;
TFaces = array of TFace;
TVertexes = array of TVector3;
TNormals  = array of TVector3;
TTextures = array of TVector2;  
TColors   = array of TVector4;  
TQuadFace = array of TQuad;

TMeshComponentFlags = packed record
  Colors: Boolean;
  Normals: Boolean;
  Textures: Boolean;
end;

TMesh = class
  private
    FVertexCount,
    FNormalCount,
    FTextureCount,  
    FColorCount,
    FFaceCount: Cardinal;
    FVertex:   TVertexes;
    FNormals:  TNormals;
    FTextures: TTextures;
    FColors:   TColors;
    FFaces:    TFaces;
    FRenderFlags: TMeshComponentFlags; 
    procedure Draw;
  protected
    procedure CreateMesh(Faces: TFaces; Vertex: TVertexes; Textures: TTextures; Normals: TNormals; Colors: TColors);
  public
    constructor Create(Faces: TFaces; Vertex: TVertexes; Textures: TTextures; Normals: TNormals; Colors: TColors); overload;
    constructor Create(Faces: TFaces; Vertex: TVertexes; Textures: TTextures; Normals: TNormals); overload;
    function IsMeshCorrect: Boolean;    
    property Vertex:   TVertexes  read FVertex;
    property Normals:  TNormals   read FNormals;
    property Textures: TTextures  read FTextures;
    property Colors:   TColors    read FColors;   
    property Faces:    TFaces     read FFaces;
  end;

TMeshRenderer = class (TComponent)
  private
    FMaterial: TMaterial;
    FMesh: TMesh;
  public
    class function CreateMeshRenderer(Mesh: TMesh; Material: TMaterial): TMeshRenderer;
    procedure Render;
    procedure OnStart; override;
    procedure OnDestroy; override;
    property Material: TMaterial read FMaterial write FMaterial;
    property Mesh: TMesh read FMesh write FMesh;
  end;

TMeshRendererLayer = class sealed (TRenderLayer)
  private
    class var FInstance: TMeshRendererLayer;  
  private
    FRendererCount,
    FMaxRenderers: Integer;
    FRenderer: array of TMeshRenderer;
    procedure AddRenderer(Renderer: TMeshRenderer);  
    procedure RemoveRenderer(Renderer: TMeshRenderer);
  protected
    procedure Render; override;
  public
    constructor Create;
    destructor Destroy; override;
  end;

function Face(A,B,C: Cardinal): TFace;

implementation
{$REGION 'Face'}
function Face(A,B,C: Cardinal): TFace;
begin
  Result[0] := A;
  Result[1] := B;
  Result[2] := C;
end;
{$ENDREGION}

{$REGION 'TMesh'}
procedure TMesh.CreateMesh(Faces: TFaces; Vertex: TVertexes; Textures: TTextures; Normals: TNormals; Colors: TColors);
begin
  FRenderFlags.Colors := False;   
  FRenderFlags.Normals := False;
  FRenderFlags.Textures := False;

  FFaceCount    := Length(Faces);
  FVertexCount  := Length(Vertex);
  FNormalCount  := Length(Normals);
  FTextureCount := Length(Textures);
  FColorCount   := Length(Colors);
  if (FFaceCount > 0) then
  begin
    FFaces := Copy(Faces);
  end;
  if (FVertexCount > 0) then
  begin
    FVertex := Copy(Vertex);
  end;
  if (FNormalCount > 0) then
  begin
    FNormals := Copy(Normals);
    FRenderFlags.Normals := True;
  end;
  if (FTextureCount > 0) then
  begin
    FTextures := Copy(Textures);
    FRenderFlags.Textures := True;
  end;
  if (FColorCount > 0) then
  begin
    FColors := Copy(Colors);
    FRenderFlags.Colors := True;
  end;
end;

constructor TMesh.Create(Faces: TFaces; Vertex: TVertexes; Textures: TTextures; Normals: TNormals; Colors: TColors);
begin
  CreateMesh(Faces, Vertex, Textures, Normals, Colors);
end;

constructor TMesh.Create(Faces: TFaces; Vertex: TVertexes; Textures: TTextures; Normals: TNormals);
begin
  CreateMesh(Faces, Vertex, Textures, Normals, nil);
end;

function TMesh.IsMeshCorrect: Boolean;
begin
  //TODO
  Result := False;
end;

procedure TMesh.Draw;
begin
  if FRenderFlags.Colors then
    glColorPointer(4, GL_FLOAT, 0, FColors);
  if FRenderFlags.Normals then
    glNormalPointer(GL_FLOAT, 0, FNormals);
  if FRenderFlags.Textures then
    glTexCoordPointer(2, GL_FLOAT, 0, FTextures);
    
  glVertexPointer(3, GL_FLOAT, 0, FVertex);

  glDrawElements(GL_TRIANGLES, (FFaceCount) * 3, GL_UNSIGNED_INT, FFaces);
end;
{$ENDREGION}

{$REGION 'TMeshRenderer'} 
class function TMeshRenderer.CreateMeshRenderer(Mesh: TMesh; Material: TMaterial): TMeshRenderer;
var
  MeshRenderer: TMeshRenderer;
begin
  MeshRenderer := TMeshRenderer.Create(nil);
  MeshRenderer.FMesh := Mesh;          
  MeshRenderer.FMaterial := Material;
  Result := MeshRenderer;
end;

procedure TMeshRenderer.Render;
begin           
  if FMaterial <> nil then
    FMaterial.Use;
  if FMesh <> nil then
    FMesh.Draw;
end;

procedure TMeshRenderer.OnStart;
begin
  if (TMeshRendererLayer.FInstance <> nil) then
    TMeshRendererLayer.FInstance.AddRenderer(Self);
end;

procedure TMeshRenderer.OnDestroy;
begin                         
  if (TMeshRendererLayer.FInstance <> nil) then
    TMeshRendererLayer.FInstance.RemoveRenderer(Self);
end;
{$ENDREGION}

{$REGION 'TMeshRendererLayer'}
constructor TMeshRendererLayer.Create;
begin
  FRendererCount := 0;
  FMaxRenderers := 0;
  FInstance := Self;
end;

destructor TMeshRendererLayer.Destroy;
begin
  inherited;
end;

procedure TMeshRendererLayer.AddRenderer(Renderer: TMeshRenderer);
begin                 
  if Renderer = nil then
    Exit;
  if (FRendererCount >= FMaxRenderers) then
    begin
      FMaxRenderers := FMaxRenderers + 5;
      SetLength(FRenderer, FMaxRenderers + 1);
    end;               
  FRenderer[FRendererCount] := Renderer;
  Inc(FRendererCount);
end;

procedure TMeshRendererLayer.RemoveRenderer(Renderer: TMeshRenderer);
var
  i: Word;
begin
  for i := 0 to FRendererCount - 1 do
    if (FRenderer[i] = Renderer) then
      begin
        FRenderer[i] := nil;
        break;
      end;
end;
 
procedure TMeshRendererLayer.Render;
var
  i: Word;
  CurrentFlags: TMeshComponentFlags;
  procedure SetFlags(NeedFlag: TMeshComponentFlags; var CurrentFlags: TMeshComponentFlags); inline;
  begin
    if NeedFlag.Textures <> CurrentFlags.Textures then
    begin                                  
      CurrentFlags.Textures := NeedFlag.Textures;
      if CurrentFlags.Textures then
        glEnableClientState(GL_TEXTURE_COORD_ARRAY)
      else                                 
        glDisableClientState(GL_TEXTURE_COORD_ARRAY);
    end;
    if NeedFlag.Normals <> CurrentFlags.Normals then
    begin
      CurrentFlags.Normals := NeedFlag.Normals;
      if CurrentFlags.Normals then
        glEnableClientState(GL_NORMAL_ARRAY)
      else
        glDisableClientState(GL_NORMAL_ARRAY);
    end;
    if NeedFlag.Colors <> CurrentFlags.Colors then
    begin
      CurrentFlags.Colors := NeedFlag.Colors;
      if CurrentFlags.Colors then
        glEnableClientState(GL_COLOR_ARRAY)
      else
        glDisableClientState(GL_COLOR_ARRAY);
    end;
    
  end;
begin
  CurrentFlags.Colors   := False;
  CurrentFlags.Normals  := False;
  CurrentFlags.Textures := False;

  glEnableClientState(GL_VERTEX_ARRAY);
  //glEnableClientState(GL_TEXTURE_COORD_ARRAY);
  //glEnableClientState(GL_NORMAL_ARRAY);
  //glEnableClientState(GL_COLOR_ARRAY);
  for i := 0 to FRendererCount - 1 do
    if (FRenderer[i] <> nil) and (FRenderer[i].FEnable) and (FRenderer[i].FMesh <> nil) then
    begin
      SetFlags(FRenderer[i].FMesh.FRenderFlags, CurrentFlags);
      glPushMatrix;
      glMultMatrixf(@FRenderer[i].GameObject.View);
      FRenderer[i].Render;  
      glPopMatrix;
    end;
  glDisableClientState(GL_TEXTURE_COORD_ARRAY);
  glDisableClientState(GL_VERTEX_ARRAY);
  glDisableClientState(GL_NORMAL_ARRAY);
  glDisableClientState(GL_COLOR_ARRAY);
end;
{$ENDREGION}

end.
