//-----------------------------------------------------------------------------
// file: lightsabre.cpp
// desc: rendering the light sabre (win32 version)
// url: http://www.cs.princeton.edu/~gewang/projects/darth/
//
// author: Ge Wang (gewang@cs.princeton.edu)
//         Perry R. Cook (prc@cs.princeton.edu)
//         Brandon Braunstein (bbraunst@princeton.edu)  
//         Douglas Corey Campbell (campbell@princeton.edu)
//         Candice Hebden (chebden@princeton.edu)
//         Paul Simbi (psimbi@princeton.edu)
//
// (thanks to 'turret' sample by Andreas Gustafsson)
// date: autumn 2002
// version: 1.2
//-----------------------------------------------------------------------------
#include <windows.h>
#include <stdlib.h>
#include <stdio.h>
#include <math.h>
#include <time.h>
#include <memory.h>
#include <GL/gl.h>
#include <GL/glu.h>




//-----------------------------------------------------------------------------
// function prototypes
//-----------------------------------------------------------------------------
LRESULT CALLBACK wnd_proc( HWND, UINT, WPARAM, LPARAM );
void on_initdialog( HWND hDlg );
void on_palettechanged( );
void set_dc_pixel_format( HDC hdc );
HPALETTE get_opengl_palette( HDC hdc );
void on_resize( GLsizei w, GLsizei h );

void init_gl( );
void render_loop( );
void render( );

struct LightState;
void draw_sabre( LightState * state );
void draw_light( LightState * state );
void draw_hilt( LightState * state );

GLboolean load_texture( const char * name, GLuint * tex );
GLboolean checkTexDim( int x );

struct GeImageData;
GLboolean ge_read_image( const char * filename, GeImageData * pImgData );
unsigned * ge_read_rgb( const char * name, int * width, 
                        int * height, int * comp );




//-----------------------------------------------------------------------------
// name: struct LightState
// desc: light sabre render state
//-----------------------------------------------------------------------------
struct LightState
{
    float length;
    float max_length;
    float color[3];
    double freq;

    double freq_data;
    unsigned layers;
    float intensity;

    LightState( float * c3 = NULL, float max_len = 1.0f, double f = 1.0, 
                double fd = 0.0, unsigned lay = 8, float inten = 1.5 )
    {
        // set the color
        if( c3 ) memcpy( color, c3, sizeof(color) );
        else { color[0] = 1.0f; color[1] = .3f; color[2] = .3f; }

        length = max_length = max_len;
        layers = lay;
        freq = f;
        freq_data = fd;
        intensity = inten;
    }
};




//-----------------------------------------------------------------------------
// name: struct GeImageData
// desc: holds basic image data
//-----------------------------------------------------------------------------
struct GeImageData
{
    int width;
    int height;
    unsigned * bits;

    GeImageData( int w = 0, int h = 0, unsigned * p = 0 )
        : width( w ), height( h ), bits( p )
    { }
};




//-----------------------------------------------------------------------------
// global variables and #defines
//-----------------------------------------------------------------------------
// width and height of the window
GLsizei g_width = 800;
GLsizei g_height = 400;

// whether to animate
GLboolean g_rotate = GL_TRUE;
// fill mode
GLenum g_fillmode = GL_FILL;
// light 0 position
GLfloat g_light0_pos[4] = { 2.0f, 1.2f, 4.0f, 1.0f };
// light 1 parameters
GLfloat g_light1_ambient[] = { .2f, .2f, .2f, 1.0f };
GLfloat g_light1_diffuse[] = { 1.0f, 1.0f, 1.0f, 1.0f };
GLfloat g_light1_specular[] = { 1.0f, 1.0f, 1.0f, 1.0f };
GLfloat g_light1_pos[4] = { -2.0f, 0.0f, -4.0f, 1.0f };

// texture and glu data
GLUquadricObj * g_quadric = NULL;
GeImageData g_imgData;
GLuint g_img[100];

char g_classname[] = "Win32::Darth";
BOOL g_winActive = GL_TRUE;
HPALETTE g_hpalette = NULL;
HDC g_hDC = NULL;
HGLRC g_hglrc = NULL;

LightState g_light_state;
char g_filename[128] = "darth.rgb";
float g_len_inc = 0.0f;




//-----------------------------------------------------------------------------
// name: print()
// desc: ...
//-----------------------------------------------------------------------------
void print( const char * msg )
{
    MessageBox( NULL, msg, "Message!", MB_OK );
}




//-----------------------------------------------------------------------------
// name: WinMain()
// desc: win32 entry point
//-----------------------------------------------------------------------------
int APIENTRY WinMain( HINSTANCE hInstance,
                      HINSTANCE hPrevInstance,
                      LPSTR     lpCmdLine,
                      int       nCmdShow )
{
    WNDCLASSEX wcex;
    HWND hwnd;
    MSG msg;

    // window class
    wcex.cbSize         = sizeof( WNDCLASSEX ); 
    wcex.style          = CS_OWNDC | CS_HREDRAW | CS_VREDRAW;
    wcex.lpfnWndProc    = ( WNDPROC )wnd_proc;
    wcex.cbClsExtra     = 0;
    wcex.cbWndExtra     = 0;
    wcex.hInstance      = hInstance;
    wcex.hIcon          = LoadIcon( hInstance, (LPCTSTR)IDI_APPLICATION );
    wcex.hCursor        = LoadCursor( NULL, IDC_ARROW );
    wcex.hbrBackground  = (HBRUSH)( COLOR_WINDOW+1 );
    wcex.lpszMenuName   = NULL;
    wcex.lpszClassName  = g_classname;
    wcex.hIconSm        = LoadIcon( wcex.hInstance, (LPCTSTR)IDI_APPLICATION );

    // register the class
    RegisterClassEx( &wcex );

    // create the window
    hwnd = CreateWindow(
        g_classname,
        g_classname,
        WS_OVERLAPPEDWINDOW | WS_VISIBLE | WS_CLIPCHILDREN | WS_CLIPSIBLINGS,
        CW_USEDEFAULT, CW_USEDEFAULT, 
        g_width, g_height, 
        NULL, NULL, 
        hInstance, 
        NULL
    );

    // check
    if (!hwnd)
      return GL_FALSE;

    // show the window
    ShowWindow( hwnd, nCmdShow );

    // update the window
    UpdateWindow( hwnd );

    // fast render loop
    while( GL_TRUE )
    {
        if ( PeekMessage( &msg, NULL, 0, 0, PM_NOREMOVE ) )
        {
            if ( !GetMessage(&msg, NULL, 0, 0) )
                return msg.wParam;

            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
        else if ( g_winActive == GL_TRUE )
			render_loop();
        else
            WaitMessage();
    }

    return 0;
}




//-----------------------------------------------------------------------------
// name: wnd_proc()
// desc: ...
//-----------------------------------------------------------------------------
LRESULT CALLBACK wnd_proc( HWND hwnd, UINT uMsg, 
                           WPARAM wParam, LPARAM lParam )
{
   switch( uMsg )
    {
    case WM_CREATE:
        on_initdialog( hwnd );
        init_gl( );
        break;

    case WM_ACTIVATEAPP:
        g_winActive = ( wParam != 0 );
        break;

    case WM_SIZE:
        // event - window resized
        on_resize( LOWORD( lParam ), HIWORD( lParam ) );
        break;
        
    case WM_CLOSE:
        PostQuitMessage( 0 );
        break;

    case WM_PALETTECHANGED:
        // This window may set the palette, even though it is not the 
        // currently active window.
        // Don't do anything if the palette does not exist, or if
        // this is the window that changed the palette.
        if( g_hpalette && (HWND)wParam != hwnd )
            on_palettechanged();
	    break;

    case WM_LBUTTONDOWN:
        switch( wParam )
        {
        case MK_LBUTTON:
            if( g_light_state.length == g_light_state.max_length )
                g_len_inc = -.05f;
            else
                g_len_inc = .05f;
            break;
        }
        break;

    default:
        return DefWindowProc( hwnd, uMsg, wParam, lParam );
    }

    return 0;
}




//-----------------------------------------------------------------------------
// name: on_initdialog
// desc: initialization for dialog
//-----------------------------------------------------------------------------
void on_initdialog( HWND hwnd )
{
    g_hDC = GetDC( hwnd );  

    // Select the pixel format
    set_dc_pixel_format( g_hDC );

    // get opengl palette
    g_hpalette = get_opengl_palette( g_hDC );

    // Create the rendering context and make it current
    g_hglrc = wglCreateContext( g_hDC );
    wglMakeCurrent( g_hDC, g_hglrc );

    g_winActive = GL_TRUE;
}





//-----------------------------------------------------------------------------
// name: on_palettechanged( )
// desc: handles palette
//-----------------------------------------------------------------------------
void on_palettechanged()
{
    if( !g_hpalette )
        return;

    // select palette to the device context
    SelectPalette( g_hDC, g_hpalette, GL_FALSE );

    // map entries to system pal
    RealizePalette( g_hDC );

    // remap the current colors to the newly realized palette
    UpdateColors( g_hDC );
}




//-----------------------------------------------------------------------------
// name: init_gl( )
// desc: opengl
//-----------------------------------------------------------------------------
void init_gl( )
{
    // set the GL clear color - use when the color buffer is cleared
    glClearColor( 0.0f, 0.0f, 0.0f, 1.0f );
    // set the shading model to 'smooth'
    glShadeModel( GL_SMOOTH );
    // enable depth
    glEnable( GL_DEPTH_TEST );
    // set the front faces of polygons
    glFrontFace( GL_CCW );
    // set fill mode
    glPolygonMode( GL_FRONT_AND_BACK, g_fillmode );

    // enable lighting
    glEnable( GL_LIGHTING );
    // enable lighting for front
    glLightModeli( GL_FRONT_AND_BACK, GL_TRUE );
    // material have diffuse and ambient lighting 
    glColorMaterial( GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE );
    // enable color
    glEnable( GL_COLOR_MATERIAL );

    // initialize our sphere
    g_quadric = gluNewQuadric( );
    gluQuadricNormals( g_quadric, ( GLenum )GLU_SMOOTH );
    gluQuadricTexture( g_quadric, GL_TRUE );

    // set texture state
    glPixelStorei( GL_UNPACK_ALIGNMENT, 4 );
    glTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE );
    glHint( GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST );

    if( !load_texture( "light.rgb", &g_img[0] ) ) exit(1);
    if( !load_texture( "hilt.rgb", &g_img[1] ) ) exit(1);

    // enable light 0
    glEnable( GL_LIGHT0 );

    // setup and enable light 1
    glLightfv( GL_LIGHT1, GL_AMBIENT, g_light1_ambient );
    glLightfv( GL_LIGHT1, GL_DIFFUSE, g_light1_diffuse );
    glLightfv( GL_LIGHT1, GL_SPECULAR, g_light1_specular );
    glEnable( GL_LIGHT1 );

    // light state
    g_light_state.length = 0.0f;
    g_light_state.freq = 1.5;

    printf( "-------------------------------------------------------\n" );
    printf( "light sabre sample\n" );
    printf( "project: darth\n" );
    printf( "url: http://www.cs.princeton.edu/~gewang/projects/darth/\n" );
    printf( "\n" );
    printf( "author: Ge Wang (gewang@cs.princeton.edu)\n" );
    printf( "        Perry R. Cook (prc@cs.princeton.edu)\n" );
    printf( "        Brandon Braunstein (bbraunst@princeton.edu)\n" );  
    printf( "        Douglas Corey Campbell (campbell@princeton.edu)\n" );
    printf( "        Candice Hebden (chebden@princeton.edu)\n" );
    printf( "        Paul Simbi (psimbi@princeton.edu)\n" );
    printf( "        (thanks to 'turret' sample by Andreas Gustafsson)\n" );
    printf( "\n" );
    printf( "press [L/R] mouse button to activate/deactivate\n" );
    printf( "-------------------------------------------------------\n" );
    printf( "\n" );

}




//-----------------------------------------------------------------------------
// name: on_resize( )
// desc: called when window size changes
//-----------------------------------------------------------------------------
void on_resize( GLsizei w, GLsizei h )
{
    // save the new window size
    g_width = w; g_height = h;
    // map the view port to the client area
    glViewport( 0, 0, w, h );
    // set the matrix mode to project
    glMatrixMode( GL_PROJECTION );
    // load the identity matrix
    glLoadIdentity( );
    // create the viewing frustum
    gluPerspective( 70.0, (GLfloat) w / (GLfloat) h, .1, 300.0 );
    // set the matrix mode to modelview
    glMatrixMode( GL_MODELVIEW );
    // load the identity matrix
    glLoadIdentity( );
    // position the view point
    gluLookAt( 0.0f, -2.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f );

    // set the position of the lights
    glLightfv( GL_LIGHT0, GL_POSITION, g_light0_pos );
}




//-----------------------------------------------------------------------------
// name: render_loop( )
// desc: callback function invoked to draw the client area
//-----------------------------------------------------------------------------
void render_loop( )
{
    if( g_hDC == NULL ) 
        return;

    // clear the color and depth buffers
    glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );

    // save the current matrix state, so transformation will
    // not persist across displayFunc calls, since we
    // will do a glPopMatrix() at the end of this function
    glPushMatrix( );
    // render the scene
    render( );
    // pop the matrix
    glPopMatrix();

    // flush
    glFlush( );

    // swap the double buffer
    SwapBuffers( g_hDC );
}



//-----------------------------------------------------------------------------
// name: render( )
// desc: render the scene
//-----------------------------------------------------------------------------
void render()
{
    static float x = 0.0f;
    x += .001f;

    // update light state
    g_light_state.length += g_len_inc;
    if( g_light_state.length > g_light_state.max_length )
    {
        g_light_state.length = g_light_state.max_length;
        g_len_inc = 0.0f;
    }
    else if( g_light_state.length < 0.0f )
    {
        g_light_state.length = 0.0f;
        g_len_inc = 0.0f;
    }

    glRotatef( 65.0f, 1.0f, 0.0f, 0.0f );
    glTranslatef( 0.0f, -.7f, 0.0f );
    glRotatef( (float)cos(x) * 360.0f, 0.0f, -1.0f, -1.0f );
    draw_sabre( &g_light_state );
}




//-----------------------------------------------------------------------------
// Name: draw_sabre( )
// Desc: draw light sabre
//-----------------------------------------------------------------------------
void draw_sabre( LightState * ls )
{
    glPushMatrix();
    draw_hilt( ls );
    draw_light( ls );
    glPopMatrix();
}




//-----------------------------------------------------------------------------
// Name: draw_light( )
// Desc: draw light beam
//-----------------------------------------------------------------------------
void draw_light( LightState * ls )
{
    unsigned i;
    float intensity = ls->intensity + (float)cos(ls->freq_data) * .2f;
    float factor = ls->layers / intensity;
    ls->freq_data += 1.5;

    glColor3f( 1.0f / factor, .3f / factor, .2f / factor );
    glPushMatrix( );

    glEnable( GL_NORMALIZE );
    glDepthMask( 0 );
    glDisable( GL_LIGHTING );
    glEnable( GL_BLEND );
    glBlendFunc( GL_ONE, GL_ONE );

    glBindTexture( GL_TEXTURE_2D, g_img[0] );
    glEnable( GL_TEXTURE_2D );

    glScalef( .07f, 1.0f, .07f );

    for( i = 0; i < ls->layers; i++ )
    {
      glBegin( GL_QUADS );
        glTexCoord2f( 0, 0 );
        glVertex3f( -1.0f, 0.0f, 0.0f ); 
        glTexCoord2f( 0, 1 );
        glVertex3f( -1.0f, 2.0f * ls->length, 0.0f );
        glTexCoord2f( 1, 1 );
        glVertex3f( 1.0f, 2.0f * ls->length, 0.0f ); 
        glTexCoord2f( 1, 0 );
        glVertex3f( 1.0f, 0.0f, 0.0f ); 
      glEnd();
      glRotatef( 180.0f / ls->layers, 0.0f, 1.0f, 0.0f );
    }

    glDisable( GL_TEXTURE_2D );
    glEnable( GL_LIGHTING );
    glDisable( GL_BLEND );
    glDepthMask( 1 );
    glDisable( GL_NORMALIZE );

    glPopMatrix();
}




//-----------------------------------------------------------------------------
// Name: draw_hilt( )
// Desc: draw light beam
//-----------------------------------------------------------------------------
void draw_hilt( LightState * ls )
{
    glPushMatrix();
        glColor3f( .8f, .8f, .8f );
        glRotatef( 90, 1.0f, 0.0f, 0.0f );
        glBindTexture( GL_TEXTURE_2D, g_img[1] );
        glEnable( GL_TEXTURE_2D );
        gluCylinder( g_quadric, .045, .045, .06, 10, 1 );
        glTranslatef( 0.0f, 0.0f, 0.015f );
        gluCylinder( g_quadric, .035, .035, .3, 10, 1 );
        glTranslatef( 0.0f, 0.0f, 0.285f );
        gluCylinder( g_quadric, .04, .04, .06, 10, 1 );
        glDisable( GL_TEXTURE_2D );
    glPopMatrix();
}




//-----------------------------------------------------------------------------
// name: set_dc_pixel_format
// desc: select the pixel format for a given device context
//-----------------------------------------------------------------------------
void set_dc_pixel_format( HDC hDC )
{
    int nPixelFormat;

    static PIXELFORMATDESCRIPTOR pfd = {
        sizeof(PIXELFORMATDESCRIPTOR),          // Size of this structure
        1,                                      // Version of this structure    
        PFD_DRAW_TO_WINDOW |                    // Draw to Window (not to bitmap)
        PFD_SUPPORT_OPENGL |                    // Support OpenGL calls in window
        PFD_DOUBLEBUFFER,                       // Double buffered
        PFD_TYPE_RGBA,                          // RGBA Color mode
        32,                                     // Want 24bit color 
        0,0,0,0,0,0,                            // Not used to select mode
        0,0,                                    // Not used to select mode
        0,0,0,0,0,                              // Not used to select mode
        16,                                     // Size of depth buffer
        0,                                      // Not used to select mode
        0,                                      // Not used to select mode
        PFD_MAIN_PLANE,                         // Draw in main plane
        0,                                      // Not used to select mode
        0,0,0 };                                // Not used to select mode

    // Choose a pixel format that best matches that described in pfd
    nPixelFormat = ChoosePixelFormat(hDC, &pfd);

    // Set the pixel format for the device context
    SetPixelFormat(hDC, nPixelFormat, &pfd);
}




//-----------------------------------------------------------------------------
// name: get_opengl_palette( )
// f necessary, creates a 3-3-2 palette for the device context listed.
//-----------------------------------------------------------------------------
HPALETTE get_opengl_palette( HDC hDC )
{
    HPALETTE hRetPal = NULL;    // Handle to palette to be created
    PIXELFORMATDESCRIPTOR pfd;  // Pixel Format Descriptor
    LOGPALETTE *pPal;           // Pointer to memory for logical palette
    int nPixelFormat;           // Pixel format index
    int nColors;                // Number of entries in palette
    int i;                      // Counting variable
    BYTE RedRange,GreenRange,BlueRange;
                                // Range for each color entry (7,7,and 3)


    // Get the pixel format index and retrieve the pixel format description
    nPixelFormat = GetPixelFormat( hDC );
    DescribePixelFormat( hDC, nPixelFormat, sizeof(PIXELFORMATDESCRIPTOR), &pfd );

    // Does this pixel format require a palette?  If not, do not create a
    // palette and just return NULL
    if(!(pfd.dwFlags & PFD_NEED_PALETTE))
        return NULL;

    // Number of entries in palette.  8 bits yeilds 256 entries
    nColors = 1 << pfd.cColorBits;  

    // Allocate space for a logical palette structure plus all the palette entries
    pPal = (LOGPALETTE*)malloc(sizeof(LOGPALETTE) +nColors*sizeof(PALETTEENTRY));

    // Fill in palette header 
    pPal->palVersion = 0x300;       // Windows 3.0
    pPal->palNumEntries = nColors; // table size

    // Build mask of all 1's.  This creates a number represented by having
    // the low order x bits set, where x = pfd.cRedBits, pfd.cGreenBits, and
    // pfd.cBlueBits.  
    RedRange = (1 << pfd.cRedBits) -1;
    GreenRange = (1 << pfd.cGreenBits) - 1;
    BlueRange = (1 << pfd.cBlueBits) -1;

    // Loop through all the palette entries
    for(i = 0; i < nColors; i++)
    {
        // Fill in the 8-bit equivalents for each component
        pPal->palPalEntry[i].peRed = (i >> pfd.cRedShift) & RedRange;
        pPal->palPalEntry[i].peRed = (unsigned char)(
            (double) pPal->palPalEntry[i].peRed * 255.0 / RedRange);

        pPal->palPalEntry[i].peGreen = (i >> pfd.cGreenShift) & GreenRange;
        pPal->palPalEntry[i].peGreen = (unsigned char)(
            (double)pPal->palPalEntry[i].peGreen * 255.0 / GreenRange);

        pPal->palPalEntry[i].peBlue = (i >> pfd.cBlueShift) & BlueRange;
        pPal->palPalEntry[i].peBlue = (unsigned char)(
            (double)pPal->palPalEntry[i].peBlue * 255.0 / BlueRange);

        pPal->palPalEntry[i].peFlags = (unsigned char) NULL;
    }
        

    // Create the palette
    hRetPal = CreatePalette( pPal );

    // Go ahead and select and realize the palette for this device context
    SelectPalette( hDC, hRetPal, GL_FALSE );
    RealizePalette( hDC );

    // Free the memory used for the logical palette structure
    free( pPal );

    // Return the handle to the new palette
    return hRetPal;
}




//-----------------------------------------------------------------------------
// name: load_texture()
// desc: ...
//-----------------------------------------------------------------------------
GLboolean load_texture( const char * name, GLuint * tex )
{
    static int x = 0;
    static char buffer[1024];

    // read the texture
    if( !ge_read_image( name, &g_imgData ) )
    {
        sprintf( buffer, "couldn't open file '%s'...\n", name );
        print( buffer );
        return GL_FALSE;
    }

    // check dim
    if( !checkTexDim( g_imgData.width ) || !checkTexDim( g_imgData.height ) )
    {
        sprintf( buffer, "img dimensions: %dx%d\n", g_imgData.width, g_imgData.height );
        strcat( buffer, "must use image file with dimensions that are powers of 2\n" );
        print( buffer );
        return GL_FALSE;
    }

    // generate OpenGL texture
    glGenTextures( ++x, tex );
    glBindTexture( GL_TEXTURE_2D, *tex );
    glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
    glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR );
    glTexImage2D( 
        GL_TEXTURE_2D,
        0,
        GL_RGBA,
        g_imgData.width,
        g_imgData.height,
        0,
        GL_RGBA,
        GL_UNSIGNED_BYTE,
        g_imgData.bits
        );

    return GL_TRUE;
}




//-----------------------------------------------------------------------------
// name: checkTexDim( )
// desc: checks to see if a dim is a valid opengl texture dimension
//-----------------------------------------------------------------------------
GLboolean checkTexDim( int dim )
{
    if( dim < 0 ) 
        return false;

    int i, count = 0;

    // count bits
    for( i = 0; i < 31; i++ )
        if( dim & ( 0x1 << i ) )
            count++;

    // this is true only if dim is power of 2
    return count == 1;
}




//-----------------------------------------------------------------------------
// Name: ge_read_image( )
// Desc: reads an RGB file into pImgData
//-----------------------------------------------------------------------------
GLboolean ge_read_image( const char * filename, GeImageData * pImgData )
{
    int c;
    if( !filename || !pImgData )
        return false;

    // zero out the memory
    memset( pImgData, 0, sizeof( GeImageData ) );

    pImgData->bits = ge_read_rgb( filename, &pImgData->width, 
                                  &pImgData->height, &c );
    
    return ( pImgData->bits != 0 );
}




//-----------------------------------------------------------------------------
/* texture loading - by David Blythe, SGI */
/* a simplistic routine for reading an SGI .rgb image file. */
//-----------------------------------------------------------------------------
void
bwtorgba(unsigned char *b,unsigned char *l,int n) {
    while(n--) {
        l[0] = *b;
        l[1] = *b;
        l[2] = *b;
        l[3] = 0xff;
        l += 4; b++;
    }
}

void
rgbtorgba(unsigned char *r,unsigned char *g,unsigned char *b,
          unsigned char *l,int n) {
    while(n--) {
        l[0] = r[0];
        l[1] = g[0];
        l[2] = b[0];
        l[3] = 0xff;
        l += 4; r++; g++; b++;
    }
}

void
rgbatorgba(unsigned char *r,unsigned char *g,unsigned char *b,
           unsigned char *a,unsigned char *l,int n) {
    while(n--) {
        l[0] = r[0];
        l[1] = g[0];
        l[2] = b[0];
        l[3] = a[0];
        l += 4; r++; g++; b++; a++;
    }
}

typedef struct _ImageRec {
    unsigned short imagic;
    unsigned short type;
    unsigned short dim;
    unsigned short xsize, ysize, zsize;
    unsigned int min, max;
    unsigned int wasteBytes;
    char name[80];
    unsigned long colorMap;
    FILE *file;
    unsigned char *tmp, *tmpR, *tmpG, *tmpB;
    unsigned long rleEnd;
    unsigned int *rowStart;
    int *rowSize;
} ImageRec;

static void
ConvertShort(unsigned short *array, unsigned int length) {
    unsigned short b1, b2;
    unsigned char *ptr;

    ptr = (unsigned char *)array;
    while (length--) {
        b1 = *ptr++;
        b2 = *ptr++;
        *array++ = (b1 << 8) | (b2);
    }
}

static void
ConvertUint(unsigned *array, unsigned int length) {
    unsigned int b1, b2, b3, b4;
    unsigned char *ptr;

    ptr = (unsigned char *)array;
    while (length--) {
        b1 = *ptr++;
        b2 = *ptr++;
        b3 = *ptr++;
        b4 = *ptr++;
        *array++ = (b1 << 24) | (b2 << 16) | (b3 << 8) | (b4);
    }
}

static ImageRec *ImageOpen(const char *fileName)
{
    union {
        int testWord;
        char testByte[4];
    } endianTest;
    ImageRec *image;
    int swapFlag;
    int x;

    endianTest.testWord = 1;
    if (endianTest.testByte[0] == 1) {
        swapFlag = 1;
    } else {
        swapFlag = 0;
    }

    image = (ImageRec *)malloc(sizeof(ImageRec));
    if (image == NULL) {
        fprintf(stderr, "Out of memory!\n");
        exit(1);
    }
    if ((image->file = fopen(fileName, "rb")) == NULL) {
        perror(fileName);
        //exit(1);
        return NULL;
    }

    fread(image, 1, 12, image->file);

    if (swapFlag) {
        ConvertShort(&image->imagic, 6);
    }

    image->tmp = (unsigned char *)malloc(image->xsize*256);
    image->tmpR = (unsigned char *)malloc(image->xsize*256);
    image->tmpG = (unsigned char *)malloc(image->xsize*256);
    image->tmpB = (unsigned char *)malloc(image->xsize*256);
    if (image->tmp == NULL || image->tmpR == NULL || image->tmpG == NULL ||
        image->tmpB == NULL) {
        fprintf(stderr, "Out of memory!\n");
        exit(1);
    }

    if ((image->type & 0xFF00) == 0x0100) {
        x = image->ysize * image->zsize * (int) sizeof(unsigned);
        image->rowStart = (unsigned *)malloc(x);
        image->rowSize = (int *)malloc(x);
        if (image->rowStart == NULL || image->rowSize == NULL) {
            fprintf(stderr, "Out of memory!\n");
            exit(1);
        }
        image->rleEnd = 512 + (2 * x);
        fseek(image->file, 512, SEEK_SET);
        fread(image->rowStart, 1, x, image->file);
        fread(image->rowSize, 1, x, image->file);
        if (swapFlag) {
            ConvertUint(image->rowStart, x/(int) sizeof(unsigned));
            ConvertUint((unsigned *)image->rowSize, x/(int) sizeof(int));
        }
    }
    return image;
}

static void
ImageClose(ImageRec *image) {
    fclose(image->file);
    free(image->tmp);
    free(image->tmpR);
    free(image->tmpG);
    free(image->tmpB);
    free(image);
}

static void
ImageGetRow(ImageRec *image, unsigned char *buf, int y, int z) {
    unsigned char *iPtr, *oPtr, pixel;
    int count;

    if ((image->type & 0xFF00) == 0x0100) {
        fseek(image->file, (long) image->rowStart[y+z*image->ysize], 
              SEEK_SET);
        fread(image->tmp, 1, (unsigned int)image->rowSize[y+z*image->ysize],
              image->file);

        iPtr = image->tmp;
        oPtr = buf;
        for (;;) {
            pixel = *iPtr++;
            count = (int)(pixel & 0x7F);
            if (!count) {
                return;
            }
            if (pixel & 0x80) {
                while (count--) {
                    *oPtr++ = *iPtr++;
                }
            } else {
                pixel = *iPtr++;
                while (count--) {
                    *oPtr++ = pixel;
                }
            }
        }
    } else {
        fseek(image->file, 
              512+(y*image->xsize)+(z*image->xsize*image->ysize),
              SEEK_SET);
        fread(buf, 1, image->xsize, image->file);
    }
}

unsigned * ge_read_rgb( const char * name, int * width, int * height, 
                       int * components) {
    unsigned *base, *lptr;
    unsigned char *rbuf, *gbuf, *bbuf, *abuf;
    ImageRec *image;
    int y;

    image = ImageOpen(name);
    
    if(!image)
        return NULL;
    (*width)=image->xsize;
    (*height)=image->ysize;
    (*components)=image->zsize;
    base = (unsigned *)malloc(image->xsize*image->ysize*sizeof(unsigned));
    rbuf = (unsigned char *)malloc(image->xsize*sizeof(unsigned char));
    gbuf = (unsigned char *)malloc(image->xsize*sizeof(unsigned char));
    bbuf = (unsigned char *)malloc(image->xsize*sizeof(unsigned char));
    abuf = (unsigned char *)malloc(image->xsize*sizeof(unsigned char));
    if(!base || !rbuf || !gbuf || !bbuf)
        return NULL;
    lptr = base;
    for(y=0; y<image->ysize; y++) {
        if(image->zsize>=4) {
            ImageGetRow(image,rbuf,y,0);
            ImageGetRow(image,gbuf,y,1);
            ImageGetRow(image,bbuf,y,2);
            ImageGetRow(image,abuf,y,3);
            rgbatorgba(rbuf,gbuf,bbuf,abuf,
              (unsigned char *)lptr,image->xsize);
            lptr += image->xsize;
        } else if(image->zsize==3) {
            ImageGetRow(image,rbuf,y,0);
            ImageGetRow(image,gbuf,y,1);
            ImageGetRow(image,bbuf,y,2);
            rgbtorgba(rbuf,gbuf,bbuf,(unsigned char *)lptr,image->xsize);
            lptr += image->xsize;
        } else {
            ImageGetRow(image,rbuf,y,0);
            bwtorgba(rbuf,(unsigned char *)lptr,image->xsize);
            lptr += image->xsize;
        }
    }
    ImageClose(image);
    free(rbuf);
    free(gbuf);
    free(bbuf);
    free(abuf);

    return (unsigned *) base;
}

