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;
    constructor Create(Faces: TFaces; Vertex: TVertexes; Textures: TTextures); overload;   
    function IsMeshCorrect: Boolean;
    function Translate(translateVector: TVector3): TMesh;   
    function AttachMesh(Mesh: TMesh): TMesh;
    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;
  public
    // default meshes
    class function CreateCube(Size: Single = 1): TMesh;
    class function CreateSphere(Radius: Single = 1; Details: Byte = 8): TMesh;
  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(Mask: Cardinal); 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;

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

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

function TMesh.Translate(translateVector: TVector3): TMesh;
var
  i: Cardinal;
begin
  for i := 0 to FVertexCount - 1 do
    FVertex[i] := FVertex[i] + translateVector;
  Result := Self;
end;

function TMesh.AttachMesh(Mesh: TMesh): TMesh;
var
  Vertex:   TVertexes;
  Normals:  TNormals;
  Textures: TTextures;    
  Colors:   TColors;
  Faces:    TFaces;
  i: Cardinal;
begin
  SetLength(Vertex,   Self.FVertexCount + Mesh.FVertexCount);
  SetLength(Textures, Self.FVertexCount + Mesh.FVertexCount);
  SetLength(Normals,  Self.FVertexCount + Mesh.FVertexCount);
  SetLength(Colors,   Self.FVertexCount + Mesh.FVertexCount);
  SetLength(Faces,    Self.FFaceCount + Mesh.FFaceCount);

  for i := 0 to Self.FVertexCount - 1 do
  begin
    Vertex[i]   := Self.FVertex[i];
    Textures[i] := Self.FTextures[i];
    Normals[i]  := Self.FNormals[i];
    Colors[i]   := Self.FColors[i];
  end;
  for i := Self.FVertexCount to Self.FVertexCount + Mesh.FVertexCount - 1 do
  begin
    Vertex[i]   := Mesh.FVertex[i - Self.FVertexCount];
    Textures[i] := Mesh.FTextures[i - Self.FVertexCount];
    Normals[i]  := Mesh.FNormals[i - Self.FVertexCount];
    Colors[i]   := Mesh.FColors[i - Self.FVertexCount];
  end;       
  for i := 0 to Self.FFaceCount - 1 do
    Faces[i]   := Self.FFaces[i];     
  for i := Self.FFaceCount to Self.FFaceCount + Mesh.FFaceCount - 1 do
  begin
    Faces[i]   := Mesh.FFaces[i - Self.FFaceCount];
    Faces[i][0] := Faces[i][0] + Self.FVertexCount;
    Faces[i][1] := Faces[i][1] + Self.FVertexCount;
    Faces[i][2] := Faces[i][2] + Self.FVertexCount;
  end;

  SetLength(FVertex,   0);
  SetLength(FTextures, 0);
  SetLength(FNormals,  0);
  SetLength(FColors,   0);
  SetLength(FFaces,    0);

  FVertexCount  := Length(Vertex);
  FNormalCount  := Length(Normals);
  FTextureCount := Length(Textures);
  FColorCount   := Length(Colors);
  FFaceCount    := Length(Faces);

  FVertex   := Copy(Vertex);
  FNormals  := Copy(Normals);
  FTextures := Copy(Textures);
  FColors   := Copy(Colors);
  FFaces    := Copy(Faces);   

  SetLength(Vertex,   0);
  SetLength(Textures, 0);
  SetLength(Normals,  0);
  SetLength(Colors,   0);
  SetLength(Faces,    0);

  Result := Self;
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 'Default Meshes'}
function CreateCubeMesh(Size: Single = 0.2): TMesh;
var
  Vertex:   TVertexes;
  Normals:  TNormals;
  Textures: TTextures;    
  Colors:   TColors;
  Faces: TFaces;
  S: Single;
begin
  S := Size;
  SetLength(Vertex, 24);   
  SetLength(Textures, 24);
  SetLength(Normals, 24);  
  SetLength(Colors, 24);
  SetLength(Faces, 12);
  Vertex[0 ] := Vector3( S, -S,  S); //up
  Vertex[1 ] := Vector3( S,  S,  S);
  Vertex[2 ] := Vector3(-S,  S,  S);
  Vertex[3 ] := Vector3(-S, -S,  S);
  Vertex[4 ] := Vector3( S,  S, -S); //down
  Vertex[5 ] := Vector3( S, -S, -S);
  Vertex[6 ] := Vector3(-S, -S, -S);
  Vertex[7 ] := Vector3(-S,  S, -S);
  Vertex[8 ] := Vector3( S,  S,  S); //forward
  Vertex[9 ] := Vector3( S,  S, -S);
  Vertex[10] := Vector3(-S,  S, -S);
  Vertex[11] := Vector3(-S,  S,  S);
  Vertex[12] := Vector3( S, -S, -S); //backward
  Vertex[13] := Vector3( S, -S,  S);
  Vertex[14] := Vector3(-S, -S,  S);
  Vertex[15] := Vector3(-S, -S, -S);
  Vertex[16] := Vector3( S,  S, -S); //left
  Vertex[17] := Vector3( S,  S,  S);
  Vertex[18] := Vector3( S, -S,  S);
  Vertex[19] := Vector3( S, -S, -S);
  Vertex[20] := Vector3(-S,  S,  S); //right
  Vertex[21] := Vector3(-S,  S, -S);
  Vertex[22] := Vector3(-S, -S, -S);
  Vertex[23] := Vector3(-S, -S,  S);

  Normals[0 ] := Vector3( 0,  0,  1); //up
  Normals[1 ] := Vector3( 0,  0,  1);
  Normals[2 ] := Vector3( 0,  0,  1);
  Normals[3 ] := Vector3( 0,  0,  1);
  Normals[4 ] := Vector3( 0,  0, -1); //down
  Normals[5 ] := Vector3( 0,  0, -1);
  Normals[6 ] := Vector3( 0,  0, -1);
  Normals[7 ] := Vector3( 0,  0, -1);
  Normals[8 ] := Vector3( 0,  1,  0); //forward
  Normals[9 ] := Vector3( 0,  1,  0);
  Normals[10] := Vector3( 0,  1,  0);
  Normals[11] := Vector3( 0,  1,  0);
  Normals[12] := Vector3( 0, -1,  0); //backward
  Normals[13] := Vector3( 0, -1,  0);
  Normals[14] := Vector3( 0, -1,  0);
  Normals[15] := Vector3( 0, -1,  0);
  Normals[16] := Vector3( 1,  0,  0); //left
  Normals[17] := Vector3( 1,  0,  0);
  Normals[18] := Vector3( 1,  0,  0);
  Normals[19] := Vector3( 1,  0,  0);
  Normals[20] := Vector3(-1,  0,  0); //right
  Normals[21] := Vector3(-1,  0,  0);
  Normals[22] := Vector3(-1,  0,  0);
  Normals[23] := Vector3(-1,  0,  0);

  Textures[0 ] := Vector2(0, 0); //up
  Textures[1 ] := Vector2(1, 0);
  Textures[2 ] := Vector2(1, 1);
  Textures[3 ] := Vector2(0, 1);
  Textures[4 ] := Vector2(0, 0); //down
  Textures[5 ] := Vector2(1, 0);
  Textures[6 ] := Vector2(1, 1);
  Textures[7 ] := Vector2(0, 1);
  Textures[8 ] := Vector2(1, 0); //forward
  Textures[9 ] := Vector2(0, 0);
  Textures[10] := Vector2(0, 1);
  Textures[11] := Vector2(1, 1);
  Textures[12] := Vector2(1, 0);
  Textures[13] := Vector2(0, 0); //backward
  Textures[14] := Vector2(0, 1);
  Textures[15] := Vector2(1, 1);
  Textures[16] := Vector2(0, 0); //left
  Textures[17] := Vector2(1, 0);
  Textures[18] := Vector2(1, 1);
  Textures[19] := Vector2(0, 1);
  Textures[20] := Vector2(0, 0); //right
  Textures[21] := Vector2(1, 0);
  Textures[22] := Vector2(1, 1);
  Textures[23] := Vector2(0, 1);
  
  Colors[0 ] := Vector4(1, 1, 1, 1); //up
  Colors[1 ] := Vector4(1, 1, 1, 1);
  Colors[2 ] := Vector4(1, 1, 1, 1);
  Colors[3 ] := Vector4(1, 1, 1, 1);
  Colors[4 ] := Vector4(1, 1, 1, 1); //down
  Colors[5 ] := Vector4(1, 1, 1, 1);
  Colors[6 ] := Vector4(1, 1, 1, 1);
  Colors[7 ] := Vector4(1, 1, 1, 1);
  Colors[8 ] := Vector4(1, 1, 1, 1); //forward
  Colors[9 ] := Vector4(1, 1, 1, 1);
  Colors[10] := Vector4(1, 1, 1, 1);
  Colors[11] := Vector4(1, 1, 1, 1);
  Colors[12] := Vector4(1, 1, 1, 1);
  Colors[13] := Vector4(1, 1, 1, 1); //backward
  Colors[14] := Vector4(1, 1, 1, 1);
  Colors[15] := Vector4(1, 1, 1, 1);
  Colors[16] := Vector4(1, 1, 1, 1); //left
  Colors[17] := Vector4(1, 1, 1, 1);
  Colors[18] := Vector4(1, 1, 1, 1);
  Colors[19] := Vector4(1, 1, 1, 1);
  Colors[20] := Vector4(1, 1, 1, 1); //right
  Colors[21] := Vector4(1, 1, 1, 1);
  Colors[22] := Vector4(1, 1, 1, 1);
  Colors[23] := Vector4(1, 1, 1, 1);

  Faces[ 0] := Face( 0, 1, 2);
  Faces[ 1] := Face( 0, 2, 3);
  Faces[ 2] := Face( 4, 5, 6);
  Faces[ 3] := Face( 4, 6, 7);
  Faces[ 4] := Face( 8, 9,10);
  Faces[ 5] := Face( 8,10,11);
  Faces[ 6] := Face(12,13,14);
  Faces[ 7] := Face(12,14,15);
  Faces[ 8] := Face(16,17,18);
  Faces[ 9] := Face(16,18,19);
  Faces[10] := Face(20,21,22);
  Faces[11] := Face(20,22,23);

  Result := TMesh.Create(Faces, Vertex, Textures, Normals, Colors);
  SetLength(Vertex, 0);
  SetLength(Textures, 0);
  SetLength(Normals, 0);
  SetLength(Colors, 0);
  SetLength(Faces, 0);
end;

function CreateNormalizedSphereMesh(Radius: Single; DetailsNum: Byte = 8): TMesh;
var
  Vertex:   TVertexes;
  Normals:  TNormals;
  Colors:   TColors;
  Textures: TTextures;
  Faces: TFaces;
  S: Single;
  i, j, z: Integer;
  F, H: Integer;
begin
  F := DetailsNum;
  S := 1.5;
  H := (F + 1) * (F + 1);
  SetLength(Vertex, H * 6);
  SetLength(Colors, H * 6);
  SetLength(Normals, H * 6);
  SetLength(Textures, H * 6);
  SetLength(Faces, F * F * 2 * 6);
  for i := 0 to F do
    for j := 0 to F do
      begin
        Normals[i + j * (F + 1) + 0 * (H)] := Vector3( 1 - 2 * i / (F), 1 - 2 * j / (F), 1).Normalize;
        Normals[i + j * (F + 1) + 3 * (H)] := Vector3(-1 + 2 * i / (F), 1 - 2 * j / (F),-1).Normalize;
        Normals[i + j * (F + 1) + 1 * (H)] := Vector3(-1 + 2 * j / (F),-1, 1 - 2 * i / (F)).Normalize;
        Normals[i + j * (F + 1) + 4 * (H)] := Vector3(-1 + 2 * j / (F), 1,-1 + 2 * i / (F)).Normalize;
        Normals[i + j * (F + 1) + 2 * (H)] := Vector3( 1, 1 - 2 * i / (F), 1 - 2 * j / (F)).Normalize;
        Normals[i + j * (F + 1) + 5 * (H)] := Vector3(-1,-1 + 2 * i / (F), 1 - 2 * j / (F)).Normalize;
      end;

  for z := 0 to 5 do
    for i := 0 to F do
      for j := 0 to F do
      begin
        Vertex[i + j * (F + 1) + z * (H)] := Normals[i + j * (F + 1) + z * (H)] * Radius;      
        Colors[i + j * (F + 1) + z * (H)] := Vector4(1, 1, 1, 1);
        Textures[i + j * (F + 1) + z * (H)] := Vector2(1 * i / (F), 1 * j / (F));// + TexShift[z];  
        //Textures[i + j * (F + 1) + z * (H)].X := Textures[i + j * (F + 1) + z * (H)].X * 0.5;
        //Textures[i + j * (F + 1) + z * (H)].Y := Textures[i + j * (F + 1) + z * (H)].Y * 0.25;
      end;

  for z := 0 to 5 do
    for i := 0 to F - 1 do
      for j := 0 to F - 1 do
      begin
        Faces[i + j * F + (z * 2 + 0) * (F * F)] := Face(
          i + j * (F + 1) + z * (H),
          i + 1 + (j) * (F + 1) + z * (H),
          i + (j + 1) * (F + 1) + z * (H));
        Faces[i + j * F + (z * 2 + 1) * (F * F)] := Face(
          i + (j + 1) * (F + 1) + z * (H),
          i + 1 + (j) * (F + 1) + z * (H),
          i + 1 + (j + 1) * (F + 1) + z * (H));
      end;
  // TODO
  Result := TMesh.Create(Faces, Vertex, Textures, Normals);//, Colors);
end;

class function TMesh.CreateCube(Size: Single = 1): TMesh;
begin
  Result := CreateCubeMesh(Size);
end;

class function TMesh.CreateSphere(Radius: Single = 1; Details: Byte = 8): TMesh;
begin
  Result := CreateNormalizedSphereMesh(Radius, Details);
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;
        FRenderer[i] := FRenderer[FRendererCount - 1];
        Dec(FRendererCount);
        break;
      end;
end;
 
procedure TMeshRendererLayer.Render(Mask: Cardinal);
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
      if (Mask and (1 shl FRenderer[i].GameObject.Layer) > 0) 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_NORMAL_ARRAY);
  glDisableClientState(GL_COLOR_ARRAY);
  glDisableClientState(GL_VERTEX_ARRAY);
end;
{$ENDREGION}

end.
