///////////////////////////////////////////////////////////////////////
// Copyright (C) Andru 2007                                          //
// mail: dr.andru@gmail.com                                          //
// site: http://andru.2x4.ru                                         //
//                                                                   //
// OMEGA Engine is free software.                                    //
//                                                                   //
// You may redistribute it and/or modify it under the terms of the   //
// GNU General Public License, as published by the Free Software     //
// Foundation; either version 2 of the License, or (at your option)  //
// any later version.                                                //
//                                                                   //
// this module is distributed in the hope that it will be useful,    //
// but WITHOUT ANY WARRANTY; without even the implied warranty of    //
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.              //
// See the GNU General Public License for more details.              //
//                                                                   //
// You should have received a copy of the GNU General Public License //
// along with this module.  If not, write to:                        //
// 	The Free Software Foundation, Inc.,                              //
// 	51 Franklin Street, Fifth Floor                                  //
// 	Boston, MA  02110-1301, USA.                                     //
///////////////////////////////////////////////////////////////////////
unit TEXTURE_LOADER;

{$I DEFINES.pas}

interface
uses
  Windows,
  OpenGL,
  minClasses,
  minFile,

  ENGINE_TYPES,
  ENGINE_VAR,
  ENGINE_CONST,
  ENGINE_UTILS,

  TEXTURE_FORMATS,
  TEXTURE_TGA,
  TEXTURE_MANAGER,
  TEXTURE_EFFECTS,

  MATH_FAST;

const
  TEX_MIPMAP            = $000001;
  TEX_CLAMP             = $000002;
  TEX_REPEAT            = $001000;
  TEX_COMPRESS          = $000004;
  TEX_OMEGA_TO_2X2      = $000008;

  TEX_GRAYSCALE         = $000010;
  TEX_INVERT            = $000020;
  TEX_USEMASK           = $000040;

  TEX_FILTER_NEAREST    = $000080;
  TEX_FILTER_LINEAR     = $000100;
  TEX_FILTER_BILINEAR   = $000200;
  TEX_FILTER_TRILINEAR  = $000400;
  TEX_FILTER_ANISOTROPY = $000800;

  TEX_QUALITY_MEDIUM    = $800000;
  TEX_QUALITY_LOW       = $400000;


const
  TEXTYPE_BJGWI = $000000;
  TEXTYPE_TGA   = $000001;

procedure Tex_SetAnisotropyLevel( Level : Integer ); stdcall;

procedure Tex_Filter( var Texture : PTexture; Flags : Integer ); stdcall;
function Tex_Create(
                      Data          : Pointer;
                      Width, Height : Integer;
                      U, V          : Single;
                      Flags         : Integer;
                      c1, c2        : Cardinal;
                      f             : Cardinal
                    ) : PTexture; stdcall;
function Tex_CreateZero( Width, Height, Flags : Integer ) : PTexture; stdcall;

function Tex_LoadFromFile(
                           FileName         : PChar;
                           TransparentColor : Integer;
                           Flags            : Integer
                         ) : PTexture; stdcall;

function Tex_LoadFromMemory(
                             Memory           : PMemory;
                             FormatType       : Integer;
                             TransparentColor : Integer;
                             Flags            : Integer
                           ) : PTexture; stdcall;

procedure Tex_SetFramesSize( Texture     : PTexture;
                             FrameWidth  : Word;
                             FrameHeight : Word ); stdcall;

function Tex_PostProcess( var pData : Pointer;
                          var Info : TTexture;
                          TransparentColor : Integer ) : PTexture; stdcall;                             

//----------------------------------------------------------------------------//
                                 IMPLEMENTATION
//----------------------------------------------------------------------------//
uses
  ENGINE_LOG,

  UTILS;

procedure Tex_SetAnisotropyLevel;
Begin
ogl_Anisotropy := Level;
End;

procedure Tex_Filter;
Begin
Texture.Flags := Flags;
glBindTexture( GL_TEXTURE_2D, Texture.ID );

if Flags and TEX_CLAMP > 0 Then
  begin
    glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE );
    glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE );
  end;
if Flags and TEX_REPEAT > 0 Then
  begin
    glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT );
    glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT );
  end;
if Flags and TEX_MIPMAP > 0 Then
  begin
    if Flags and TEX_FILTER_NEAREST > 0 Then
      begin
        glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST );
        glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST );
      end else
        if Flags and TEX_FILTER_LINEAR > 0 Then
          begin
            glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR );
            glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
          end else
            if Flags and TEX_FILTER_BILINEAR > 0 Then
              begin
                glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST );
                glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
              end else
                if Flags and TEX_FILTER_TRILINEAR > 0 Then
                  begin
                    glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR );
                    glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
                  end else
                    if Flags and TEX_FILTER_ANISOTROPY > 0 Then
                      begin
                        glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR );
                        glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
                        glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, ogl_Anisotropy );
                      end;
  end else
    begin
      if ( Flags and TEX_FILTER_NEAREST      > 0 ) or
         ( ( Flags and TEX_FILTER_LINEAR     = 0 ) and
           ( Flags and TEX_FILTER_BILINEAR   = 0 ) and
           ( Flags and TEX_FILTER_TRILINEAR  = 0 ) and
           ( Flags and TEX_FILTER_ANISOTROPY = 0 ) ) Then
        begin
          glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST );
          glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST );
        end else
          begin
            glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR );
            glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
          end;
    end;

glBindTexture( GL_TEXTURE_2D, 0 );
End;

function Tex_Create;
  var
    Info  : TTexture;
    InfoP : PTexture;
Begin
InfoP := @Info;
Info.U      := U;
Info.V      := V;
Info.Width  := Width;
Info.Height := Height;
Info.Flags  := Flags;

glEnable( GL_TEXTURE_2D );
glGenTextures( 1, @Info.ID );

Tex_Filter( InfoP, Flags );
glBindTexture( GL_TEXTURE_2D, Info.ID );

if Flags and TEX_COMPRESS >= 1 Then
  if not GL_ARB_Texture_Compression Then
    Flags := Flags xor TEX_COMPRESS;

if Flags and TEX_MIPMAP = 0 Then
  begin
    if Flags and TEX_COMPRESS = 0 Then
      glTexImage2D( GL_TEXTURE_2D, 0, c1, Info.Width, Info.Height, 0, f, GL_UNSIGNED_BYTE, Data )
    else
      glTexImage2D( GL_TEXTURE_2D, 0, c2, Info.Width, Info.Height, 0, f, GL_UNSIGNED_BYTE, Data );
  end else
    begin
      if Flags and TEX_COMPRESS = 0 Then
        gluBuild2DMipmaps( GL_TEXTURE_2D, c1, Info.Width, Info.Height, f, GL_UNSIGNED_BYTE, Data )
      else
        gluBuild2DMipmaps( GL_TEXTURE_2D, c2, Info.Width, Info.Height, f, GL_UNSIGNED_BYTE, Data )
    end;

Info.Width  := M_Round( Info.Width  * Info.U );
Info.Height := M_Round( Info.Height * Info.V );

glDisable( GL_TEXTURE_2D );

Info.FramesX := 1;
Info.FramesY := 1;
Result := Tex_Add( Info );
End;

function Tex_PostProcess;
Begin
Info.U := 1;
Info.V := 1;

if not GL_EXT_bgra Then
  Tex_CalcBGRA(  pData, Info.Width, Info.Height );

if TransparentColor > -1 Then
  Tex_CalcTransparent( pData, TransparentColor, Info.Width, Info.Height );

if Info.Flags and TEX_INVERT > 0 Then
  Tex_CalcInvert( pData, Info.Width, Info.Height );

if Info.Flags and TEX_GRAYSCALE > 0 Then
  Tex_CalcGrayScale( pData, Info.Width, Info.Height );

if Info.Flags and TEX_USEMASK > 0 Then
  begin
    if Info.Flags and TEX_OMEGA_TO_2X2 > 0 Then
      Tex_CalcMask( FALSE, pData, Info.Width, Info.Height )
    else
      Tex_CalcMask( TRUE, pData, M_Round( Info.Width * Info.U ), M_Round( Info.Height * Info.V ) );
  end;

if Info.Flags and TEX_OMEGA_TO_2X2 > 0 Then
  Tex_Calc2x2( pData, Info.Width, Info.Height, Info.U, Info.V );

if Info.Flags and TEX_QUALITY_MEDIUM > 0 Then
  Tex_CalcQuality( pData, Info.Width, Info.Height, 2 )
else
  if Info.Flags and TEX_QUALITY_LOW > 0 Then
    Tex_CalcQuality( pData, Info.Width, Info.Height, 4 );

if ( Info.Width > ogl_MaxTextureSize ) or ( Info.Height > ogl_MaxTextureSize ) Then
  begin
    E_Error( ERR_TEXBIG_RESIZE );
    Tex_CalcQuality( pData, Info.Width, Info.Height, 2 );
    while ( Info.Width > ogl_MaxTextureSize ) or ( Info.Height > ogl_MaxTextureSize ) do
      Tex_CalcQuality( pData, Info.Width, Info.Height, 2 );
  end;

Result := Tex_Create(
                      pData,
                      Info.Width, Info.Height,
                      Info.U, Info.V,
                      Info.Flags,

                      GL_RGBA   * Byte( scr_BPP = 32 ) or
                      GL_RGBA8  * Byte( scr_BPP  = 8 ) or
                      GL_RGBA16 * Byte( scr_BPP = 16 ),
                      GL_COMPRESSED_RGBA_ARB,
                      Byte( not GL_EXT_bgra ) * GL_RGBA or
                      Byte( GL_EXT_bgra ) * GL_BGRA
                    );
Mem_Free( pData );
End;

function Tex_CreateZero;
  var
    Info  : TTexture;
    pData : Pointer;
Begin
Info.U      := 1;
Info.V      := 1;
Info.Width  := Width;
Info.Height := Height;
Info.Flags  := Flags;

Mem_Get( pData, Width * Height * 4 );

Result := Tex_PostProcess( pData, Info, -1 );
End;

function Tex_LoadFromFile;
  var
    pData : Pointer;
    Info  : TTexture;

    TGA  : TTGAImage;

    S : String;

    Stream : TMemoryStream;
Begin
Result := nil;

FileName := PChar( String( app_Dir ) + String( FileName ) );

if not FileExists( FileName ) Then
  begin
    E_Error( TEXLOAD + ERR_CANT_FIND_FILE + '"' + FileName + '"!' );
    app_Work := FALSE;
    Exit;
  end;

S          := FileName;
Info.Flags := Flags;
if copy( StrUp( S ), length( FileName ) - 3, 4 ) = '.TGA' Then
  begin
    TGA  := TTGAImage.Create;
    TGA.LoadFromFile( FileName );
    // Для полной совместимости конвертируем текстуру в TrueColor-режим
    TGA.ToTrueColor;

    Info.Width  := TGA.Header.ImgSpec.Width;
    Info.Height := TGA.Header.ImgSpec.Height;
    Info.U      := 1;
    Info.V      := 1;

    TGA.Normalize( pData );

    TGA.Free;
  end else
    begin
      pData := nil;
      Stream := TMemoryStream.Create;
      Stream.LoadFromFile( FileName );
      Tex_LoadData( Stream.Memory, Stream.Size, Info, pData );
      Stream.Free;
    end;

if not Assigned( pData ) Then
  begin
    E_Error( ERR_CANT_TEXLOAD + FileName + '!' );
    app_Work := FALSE;
    Exit;
  end;

Result := Tex_PostProcess( pData, Info, TransparentColor );

if app_Log Then
  LOG_AddMessage( LOGT_APP, PChar( LOG_LOADING_TEXTURE + FileName ) );
End;

function Tex_LoadFromMemory;
  var
    pData : Pointer;
    Info  : TTexture;

    TGA  : TTGAImage;

    Stream : TMemoryStream;
Begin
Result := nil;

if Memory^.Ptr = nil Then
  begin
    E_Error( TEXLOAD + ERR_CANT_TEXLOAD + ERR_CANT_FIND_MEMORY );
    app_Work := FALSE;
    Exit;
  end;

Info.Flags := Flags;
Stream := TMemoryStream.Create;
Stream.Write( Memory^.Ptr^, Memory^.Size );
Stream.Seek( 0, 0 );
if FormatType = TEXTYPE_TGA Then
  begin
    TGA  := TTGAImage.Create;
    TGA.LoadFromStream( Stream );
    // Для полной совместимости конвертируем текстуру в TrueColor-режим
    TGA.ToTrueColor;

    Info.Width  := TGA.Header.ImgSpec.Width;
    Info.Height := TGA.Header.ImgSpec.Height;
    Info.U      := 1;
    Info.V      := 1;

    TGA.Normalize( pData );

    TGA.Free;
  end else
    if FormatType = TEXTYPE_BJGWI Then
      begin
        pData := nil;
        Tex_LoadData( Memory^.Ptr, Memory^.Size, Info, pData );
        Stream.Free;
      end;

if not Assigned( pData ) Then
  begin
    E_Error( TEXLOAD + ERR_CANT_TEXLOAD + FROM_MEMORY + '!' );
    app_Work := FALSE;
    Exit;
  end;

Result := Tex_PostProcess( pData, Info, TransparentColor );

if app_Log Then
  LOG_AddMessage( LOGT_APP, PChar( LOG_LOADING_TEXTURE + FROM_MEMORY ) );
End;

procedure Tex_SetFramesSize;
Begin
if Texture^.Flags and TEX_QUALITY_MEDIUM > 0 Then
  begin
    FrameWidth := FrameWidth div 2;
    FrameHeight := FrameHeight div 2;
  end else
    if Texture^.Flags and TEX_QUALITY_LOW > 0 Then
      begin
        FrameWidth := FrameWidth div 4;
        FrameHeight := FrameHeight div 4;
      end;

Texture^.FramesX := M_Round( Texture^.Width  / FrameWidth  );
Texture^.FramesY := M_Round( Texture^.Height / FrameHeight );
End;

end.

