/*
THE COMPUTER CODE CONTAINED HEREIN IS THE SOLE PROPERTY OF PARALLAX
SOFTWARE CORPORATION ("PARALLAX").  PARALLAX, IN DISTRIBUTING THE CODE TO
END-USERS, AND SUBJECT TO ALL OF THE TERMS AND CONDITIONS HEREIN, GRANTS A
ROYALTY-FREE, PERPETUAL LICENSE TO SUCH END-USERS FOR USE BY SUCH END-USERS
IN USING, DISPLAYING,  AND CREATING DERIVATIVE WORKS THEREOF, SO LONG AS
SUCH USE, DISPLAY OR CREATION IS FOR NON-COMMERCIAL, ROYALTY OR REVENUE
FREE PURPOSES.  IN NO EVENT SHALL THE END-USER USE THE COMPUTER CODE
CONTAINED HEREIN FOR REVENUE-BEARING PURPOSES.  THE END-USER UNDERSTANDS
AND AGREES TO THE TERMS HEREIN AND ACCEPTS THE SAME BY USE OF THIS FILE.  
COPYRIGHT 1993-1999 PARALLAX SOFTWARE CORPORATION.  ALL RIGHTS RESERVED.
*/

char _3dfx_rcsid[] = "$Id: 3dfx.c 1.4 1996/03/16 01:26:20 bwh Exp $";

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#include "mono.h"
#include "bm.h"
#include "gr.h"
#include "3d.h"
#include "segment.h"
#include "piggy.h"
#include "3dfx_des.h"
#include "game.h"

#ifdef _3DFX

GrHwConfiguration  hwconfig;
int                _3dfx_triangles_rendered_pre_clip;
int                _3dfx_triangles_rendered_post_clip;
int                _3dfx_tex_combine, _3dfx_tex_combine_top, _3dfx_tex_combine_bottom;
int                _3dfx_current_handle;
int                _3dfx_handle_to_index[_3DFX_MAX_HANDLES];
int                _3dfx_bytes_downloaded_this_frame;
int                _3dfx_download_requests_made;
int                _3dfx_download_requests_granted;
int                _3dfx_drawing_polygon_model;
int                _3dfx_current_bitmap_index;
int                _3dfx_skip_ddraw, _3dfx_no_texture, _3dfx_allow_transparency;
float              _3dfx_oo_stencil;
float              _3dfx_stencil;
int                _3dfx_should_sync;
int                _3dfx_rendering_poly_obj;
int                _3dfx_tex_combine_orientation;
int                _3dfx_tex_combine_superx;
int                _3dfx_tex_combine_top_flags,
                   _3dfx_tex_combine_bottom_flags;
float              _3dfx_stencil;
int                _3dfx_no_bilinear;
int                _3dfx_available;

typedef struct BitmapFile       {
	char                    name[15];
} BitmapFile;
extern BitmapFile AllBitmaps[ MAX_BITMAP_FILES ];

_3dfxTextureInfo   _3dfx_texture_info[MAX_BITMAP_FILES];

void MyErrorHandler( const char *s, FxBool fatal )
{
   mprintf( ( 0, "3Dfx err_handler - %s\n", s ) );
   if ( fatal )
   {
      exit( 2 );
   }
}

int _3dfx_Init( void )
{
   int         i;

   mprintf( ( 0, "Initializing 3Dfx Interactive Voodoo Graphics hardware\n" ) );
   grGlideInit();
   grErrorSetCallback( MyErrorHandler );

   if ( !grSstQueryHardware( &hwconfig ) )
   {
      mprintf( ( 0, "3Dfx Interactive Voodoo Graphics not found!" ) );
      return 0;
   }
   grSstSelect( 0 );
   if ( !grSstOpen( GR_RESOLUTION_640x480,
                    GR_REFRESH_60Hz,
                    GR_COLORFORMAT_ARGB,
                    GR_ORIGIN_UPPER_LEFT,
                    GR_SMOOTHING_ENABLE,
                    2 ) )
   {
      mprintf( ( 0, "3Dfx Interactive Voodoo Graphics not opened!" ) );
      return 0;
   }

   if ( getenv( "GLIDE_OFF" ) != 0 )
   {
      mprintf( ( 0, "3Dfx Interactive Voodoo Graphics disabled!" ) );
      return 0;
   }

   grDepthBufferMode( GR_DEPTHBUFFER_DISABLE );
   grBufferClear( 0x00000000, 0, 0 );
   grCullMode( GR_CULL_DISABLE );

   /*
   ** configure environment variable controlled options
   */
   if ( getenv( "GLIDE_NO_BILINEAR" ) )
      _3dfx_no_bilinear = 1;
   if ( getenv( "GLIDE_NO_DDRAW" ) )
      _3dfx_skip_ddraw = 1;
   if ( getenv( "GLIDE_NO_TEXTURE" ) )
      _3dfx_no_texture = 1;
   if ( getenv( "GLIDE_NO_TRANSPARENCY" ) )
      _3dfx_allow_transparency = 0;
   else
      _3dfx_allow_transparency = 1;
   if ( getenv( "GLIDE_NO_SYNC" ) )
      _3dfx_should_sync = 0;
   else
      _3dfx_should_sync = 1;

   /*
   ** allocate texture memory
   */
   for ( i = 0; i < MAX_BITMAP_FILES; i++ )
   {
      _3dfx_texture_info[i].handle = GR_NULL_MIPMAP_HANDLE;
   }

   _3dfx_current_handle = 0;
   for ( i = 0; i < _3DFX_MAX_HANDLES; i++ )
   {
      GrMipMapId_t id;

      id = grTexAllocateMemory( GR_TMU0,
                                GR_MIPMAPLEVELMASK_BOTH,
                                64, 64,
                                GR_TEXFMT_ARGB_1555,
                                GR_MIPMAP_NEAREST,
                                GR_LOD_1, GR_LOD_64,
                                GR_ASPECT_1x1,
                                GR_TEXTURECLAMP_WRAP, GR_TEXTURECLAMP_WRAP,
                                GR_TEXTUREFILTER_BILINEAR, _3dfx_no_bilinear ? GR_TEXTUREFILTER_POINT_SAMPLED : GR_TEXTUREFILTER_BILINEAR,
                                1.0F,
                                FXFALSE );

      if ( id == GR_NULL_MIPMAP_HANDLE )
      {
         mprintf( ( 0, "            - unexpected null mmid returned\n" ) );
         exit( 1 );
      }
   }

   return 1;
}

void _3dfx_DownloadTexture( int index )
{
   GrMipMapId_t      id   = _3dfx_current_handle;
   _3dfxTextureInfo *info = &_3dfx_texture_info[index];
   int               texture_being_replaced;
   GrTextureFormat_t fmt;

   /*
   ** make sure the texture is in main memory
   */
   _3dfx_download_requests_made++;

   if ( !( info->flags & _3DFX_TF_IN_MEMORY ) )
   {
      _3dfx_LoadTexture( index, AllBitmaps[index].name );

      if ( !( info->flags & _3DFX_TF_IN_MEMORY ) )
         return;
   }

   /*
   ** if it's already in memory then we're cool
   */
   if ( info->flags & _3DFX_TF_DOWNLOADED )
   {
      return;
   }

   /*
   ** otherwise we gotta do a swaperoo.  So, find the old texture and
   ** give it the bad news.
   */
   texture_being_replaced = _3dfx_handle_to_index[id];

   _3dfx_texture_info[texture_being_replaced].flags &= ~_3DFX_TF_DOWNLOADED;

   /*
   ** update the new texture
   */
   _3dfx_handle_to_index[id] = index;
   info->handle              = id;
   info->flags              |= _3DFX_TF_DOWNLOADED;

   if ( info->flags & _3DFX_TF_SUPERX )
      fmt = GR_TEXFMT_ARGB_4444;
   else if ( info->flags & _3DFX_TF_TRANSPARENT )
      fmt = GR_TEXFMT_ARGB_1555;
   else
      fmt = GR_TEXFMT_RGB_565;

   grTexChangeAttributes( id,
                          64, 64,
                          fmt,
                          GR_MIPMAP_NEAREST,
                          GR_LOD_1, GR_LOD_64,
                          GR_ASPECT_1x1,
                          GR_TEXTURECLAMP_WRAP, GR_TEXTURECLAMP_WRAP,
                          -1, -1 );

   grTexDownloadMipMap( id, info->data, 0 );

   if ( ++_3dfx_current_handle >= _3DFX_MAX_HANDLES )
      _3dfx_current_handle = 0;

   _3dfx_bytes_downloaded_this_frame += SIZEOF_64x64_16BIT_TEXTURE;

   _3dfx_download_requests_granted++;
}

int _3dfx_LoadTextureFromDisk( const char *name, int index )
{
   Gu3dfInfo info;
   int       success_or_fail = 0;

   if ( gu3dfGetInfo( name, &info ) )
   {
      _3dfx_texture_info[index].flags = 0;

      if ( info.header.large_lod != GR_LOD_64 )
      {
         goto done;
      }

      if ( info.header.aspect_ratio != GR_ASPECT_1x1 )
      {
         goto done;
      }

      #ifdef malloc
      #  undef malloc
      #endif
      info.data = ( void * ) malloc( info.mem_required );

      if ( info.data == 0 )
      {
         mprintf( ( 0, "info.data == 0\n" ) );
         goto done;
      }

      if ( !gu3dfLoad( name, &info ) )
      {
         mprintf( ( 0, "couldn't gu3dfLoad() '%s'\n", name ) );
         free( info.data );
         goto done;
      }

      _3dfx_texture_info[index].data         = info.data;
      _3dfx_texture_info[index].flags        = _3DFX_TF_IN_MEMORY;
      _3dfx_texture_info[index].mem_required = info.mem_required;

      if ( info.header.format == GR_TEXFMT_ARGB_1555 )
         _3dfx_texture_info[index].flags |= _3DFX_TF_TRANSPARENT;
      else if ( info.header.format == GR_TEXFMT_ARGB_4444 )
         _3dfx_texture_info[index].flags |= ( _3DFX_TF_SUPERX | _3DFX_TF_TRANSPARENT );
   }

   success_or_fail = 1;
done:
   return success_or_fail;
}

/*
** _3dfx_LoadTexture
*/
void _3dfx_LoadTexture( int index, const char *_name )
{
   /*
   ** load a non-ABM file
   */
   if ( !strchr( _name, '#' ) )
   {
      char name[256];

      strcpy( name, _name );
      sprintf( name, "3DF\\%s.3df", _name );

      _3dfx_LoadTextureFromDisk( name, index );
   }
   /*
   ** handle ABM files
   */
   else
   {
      char _3dfx_filename[256], _3dfx_name[256], _3dfx_extension[256];

      strcpy( _3dfx_name, _name );
     *strchr( _3dfx_name, '#' ) = 0;
      strcpy( _3dfx_extension, strchr( _name, '#' ) + 1 );
      sprintf( _3dfx_filename, "ABM\\%s.%03s", _3dfx_name, _3dfx_extension );

      _3dfx_LoadTextureFromDisk( _3dfx_filename, index );
   }
}

/*
** _3dfx_BufferSwap
*/
void _3dfx_BufferSwap( void )
{
   if ( _3dfx_available )
   {
      while ( ( ( grSstStatus() >> 28 ) & 7 ) > 0 )
         ;

      grBufferSwap( _3dfx_should_sync );
      grBufferClear( 0xFFFF0000, 0, 0 );
   }

   _3dfx_bytes_downloaded_this_frame  = 0;
   _3dfx_download_requests_made       = 0;
   _3dfx_download_requests_granted    = 0;
   _3dfx_triangles_rendered_pre_clip  = 0;
   _3dfx_triangles_rendered_post_clip = 0;
}

/*
** _3dfx_DrawFlatShadedPoly
*/
#define SNAP( a ) ( ( fix ) ( (a) & ( 0xFFFFF000L ) ) )

void _3dfx_DrawFlatShadedPoly( g3ds_tmap *Tmap1, unsigned long argb )
{
   GrVertex a, b, c;
   int      i;
   float    lowest_y = 10000, highest_y = 0;
   float    lowest_x = 10000, highest_x = 0;

   for ( i = 0; i < Tmap1->nv; i++ )
   {
      float x = SNAP( Tmap1->verts[i].x2d ) * fix_to_float;
      float y = SNAP( Tmap1->verts[i].y2d ) * fix_to_float;

      if ( x < lowest_x ) lowest_x = x;
      if ( x > highest_x ) highest_x = x;
      if ( y < lowest_y ) lowest_y = y;
      if ( y > highest_y ) highest_y = y;
   }

   if ( highest_x < 0 || highest_y < 0 || lowest_x > 639 || lowest_y > 479 )
   {
      mprintf( ( 0, "3dfx - rejecting fspoly way off screen\n" ) );
      return;
   }

   guColorCombineFunction( GR_COLORCOMBINE_CCRGB );
   grConstantColorValue( argb );

   a.x = SNAP( Tmap1->verts[0].x2d ) * fix_to_float;
   a.y = SNAP( Tmap1->verts[0].y2d ) * fix_to_float;

   for ( i = 1; i < Tmap1->nv - 1; i++ )
   {
      b.x   = SNAP( Tmap1->verts[i].x2d ) * fix_to_float;
      b.y   = SNAP( Tmap1->verts[i].y2d ) * fix_to_float;
      c.x   = SNAP( Tmap1->verts[i+1].x2d ) * fix_to_float;
      c.y   = SNAP( Tmap1->verts[i+1].y2d ) * fix_to_float;

      grDrawTriangle( &a, &b, &c );
   }
}

/*
** _3dfx_DownloadAndUseTexture
*/
void _3dfx_DownloadAndUseTexture( unsigned long index )
{
   _3dfx_DownloadTexture( index );
   if ( _3dfx_texture_info[index].handle != GR_NULL_MIPMAP_HANDLE )
      grTexSource( _3dfx_texture_info[index].handle );
}

/*
** _3dfx_decompose_tmap_and_draw
*/
void _3dfx_decompose_tmap_and_draw( int nv, g3s_point **pointlist, uvl *uvl_copy, grs_bitmap *bm )
{
   int        i;
   uvl        uvl_copy_3dfx[8];
   g3s_point *pointlist2[8];

   pointlist2[0]    = pointlist[0];
   uvl_copy_3dfx[0] = uvl_copy[0];

   _3dfx_DownloadAndUseTexture( bm->bm_handle );

   for ( i = 0; i < nv - 2; i++ )
   {
      int j;

      for ( j = 1; j < 3; j++ )
      {
         pointlist2[j]    = pointlist[i+j];
         uvl_copy_3dfx[j] = uvl_copy[i+j];
      }

      if ( _3dfx_available )
      {
         g3_draw_tmap( 3, pointlist2, (g3s_uvl *) uvl_copy_3dfx, bm );
      }
      _3dfx_triangles_rendered_pre_clip++;
   }
}

/*
** _3dfx_PaletteToARGB
*/
unsigned long _3dfx_PaletteToARGB( int pindex )
{
   unsigned long result = 0;

   result |= ( ( unsigned long ) gr_palette[pindex*3+0] ) << 18;
   result |= ( ( unsigned long ) gr_palette[pindex*3+1] ) << 10;
   result |= ( ( unsigned long ) gr_palette[pindex*3+2] ) << 2;

   return result;
}

/*
** _3dfx_BlitScale
*/
#define _3DFX_FB_WIDTH 1024

void _3dfx_BlitScale( grs_bitmap *source_bmp, grs_bitmap *dest_bmp,
                      int x0, int y0, int x1, int y1,
                      fix u0, fix v0, fix u1, fix v1, int orient )
{
   if ( _3dfx_available )
   {
      if ( source_bmp->bm_flags & BM_FLAG_RLE )
      {
         fix u, v, du, dv;
         int x, y;
         ubyte * sbits;
         unsigned long * dbits;
         unsigned long * fb;

         du = (u1-u0) / (x1-x0);
         dv = (v1-v0) / (y1-y0);

         v = v0;

         grLfbBegin();

         grLfbBypassMode( GR_LFBBYPASS_ENABLE );

         fb = ( unsigned long * ) grLfbGetWritePtr( GR_BUFFER_BACKBUFFER );
         grLfbWriteMode( GR_LFBWRITEMODE_8888 );

         guColorCombineFunction( GR_COLORCOMBINE_DECAL_TEXTURE );

         for ( y = y0; y <= y1; y++, v += dv )
         {
            extern ubyte scale_rle_data[];

            decode_row( source_bmp, f2i( v ) );

            sbits = scale_rle_data + f2i( u0 );
            dbits = fb + _3DFX_FB_WIDTH * y + x0;

            u = u0;

            for ( x = x0; x <= x1; x++ )
            {
               int pindex = sbits[ u >> 16 ];

               if ( pindex != 255 )
                  *dbits = _3dfx_PaletteToARGB( pindex );

               dbits++;
               u += du;
            }
         }
         grLfbBypassMode( GR_LFBBYPASS_DISABLE );

         grLfbEnd();
      }
      else
      {
         mprintf( ( 0, "_3dfx_BlitScale() - non RLE not implemented yet\n" ) );
      }
   }
}

/*
** _3dfx_Blit
*/
#define _3DFX_FB_WIDTH 1024

void _3dfx_Blit( int x, int y, grs_bitmap *bm )
{
   if ( _3dfx_available )
   {
      if ( bm->bm_flags & BM_FLAG_RLE )
      {
         ubyte * sbits;
         unsigned long * dbits;
         unsigned long * fb;
         int             v;

         grLfbBegin();

         grLfbBypassMode( GR_LFBBYPASS_ENABLE );

         fb = ( unsigned long * ) grLfbGetWritePtr( GR_BUFFER_BACKBUFFER );
         grLfbWriteMode( GR_LFBWRITEMODE_8888 );

         guColorCombineFunction( GR_COLORCOMBINE_DECAL_TEXTURE );

         for ( v = 0; v < bm->bm_h; v++ )
         {
            extern ubyte scale_rle_data[];
            int          u;

            decode_row( bm, v );

            sbits = scale_rle_data;
            dbits = fb + _3DFX_FB_WIDTH * ( y + v ) + x;

            for ( u = 0; u < bm->bm_w; u++ )
            {
               int pindex = sbits[ u ];

               if ( pindex != 255 )
                  *dbits = _3dfx_PaletteToARGB( pindex );
               dbits++;
            }
         }
         grLfbBypassMode( GR_LFBBYPASS_DISABLE );

         grLfbEnd();
      }
      else
      {
         mprintf( ( 0, "_3dfx_Blit() - non RLE not implemented yet\n" ) );
      }
   }
}

/*
** _3dfx_InitFogForPaletteTricks
*/
void _3dfx_InitFogForPaletteTricks( void )
{
   GrFog_t _3dfx_fog_table[GR_FOG_TABLE_SIZE];

   if ( PaletteRedAdd + PaletteGreenAdd + PaletteBlueAdd )
   {
      int i;
      unsigned long fog_color_value;
      unsigned long max_palette_add = PaletteRedAdd;
      float         palette_add_intensity;
      float         palette_to_255 = 255.0 / 31.0;

      if ( PaletteGreenAdd > max_palette_add ) max_palette_add = PaletteGreenAdd;
      if ( PaletteBlueAdd > max_palette_add )  max_palette_add = PaletteBlueAdd;

      palette_add_intensity = max_palette_add / 31.0;

      fog_color_value = ( ( ( unsigned long ) ( palette_to_255 * PaletteRedAdd ) << 16 ) |
                          ( ( unsigned long ) ( palette_to_255 * PaletteGreenAdd ) << 8 ) |
                          ( ( unsigned long ) ( palette_to_255 * PaletteBlueAdd ) ) );

      grFogMode( GR_FOG_WITH_TABLE );
      grFogColorValue( fog_color_value );

      for ( i = 0; i < GR_FOG_TABLE_SIZE; i++ )
      {
         _3dfx_fog_table[i] = 256 * palette_add_intensity;
      }
      grFogTable( _3dfx_fog_table );
   }
   else
   {
      grFogMode( GR_FOG_DISABLE );
   }
}


#endif

