/*
    Amiga port by Oliver Gantert

    27.04.2000 - fixed some compiler warnings
*/
/*

ORBIT, a freeware space combat simulator
Copyright (C) 1999  Steve Belczyk <steve1@genesis.nred.ma.us>

This program is free software; you can 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 program 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 program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

*/

#include "orbit.h"

/*
 *  Stuff to load AC3D files
 *
 *  Most of this is based on ac-to-gl, by Steve Baker <sjbaker1@airmail.net>
 */

int DoMaterial (char *s);
int DoObject (char *s);
int DoACName (char *s);
int DoData (char *s);
int DoTexture (char *s);
int DoTexrep (char *s);
int DoRot (char *s);
int DoLoc (char *s);
int DoUrl (char *s);
int DoNumvert (char *s);
int DoNumsurf (char *s);
int DoSurf (char *s);
int DoMat (char *s);
int DoRefs (char *s);
int DoKids (char *s);

int DoObjWorld (char *s);
int DoObjPoly (char *s);
int DoObjGroup (char *s);
void SkipSpaces (char **s);
void SkipQuotes (char **s);
void RemoveQuotes (char *s);
void load_sgi_texture (char *s);

#define MAX_TEXTURES 1000    /* This *ought* to be enough! */
char *texture_fnames [MAX_TEXTURES];

int acsmooth;  /* Smooth surface? */
int acsurfpass;  /* Pass through surface */
#define PASS_NORMAL (0) /* Get normals */
#define PASS_RENDER (1) /* Render */

struct
{
  int id;   /* Texture id */
  char name[64]; /* Name */
}
tex[MAX_TEXTURES];
int ntex;

struct ACVertex
{
  float v[3];     /* Vertex coords */
  float normal[3]; /* Normal */
  float n;        /* Number of normals to average */
}
*vtab;

int num_materials = 0;
int num_textures = 0;
int nv;   /* Number of vertices in vtab */

int last_flags = -1;
int last_mat;
int last_num_kids = -1;
int current_flags = -1;
int texture_enabled = 0;
int need_texture = 0;
int firsttime2;

#define PARSE_CONT   0
#define PARSE_POP    1

int matlist[1024];
int modelnum;
FILE *acfd;

struct Tag
{
  char *token;
  int (*func) (char *s);
}
;

struct Tag top_tags [] =
{
  {
"MATERIAL", DoMaterial}
  ,
  {
"OBJECT"  , DoObject  }
  ,
}
;

void InitTextures()
{
  int t;

  for (t=0; t<MAX_TEXTURES; t++)
  {
    tex[t].id = (-1);
    tex[t].name[0] = 0;
  }

  ntex = 0;
}

int LoadAC3D (char *name)
{
  char buffer[1024], *s, fn[128];
  int firsttime, m;

  /* See if it's already loaded */
  for (m=0; m<NMODELS; m++)
  {
    if (!strcmp (model[m].name, name)) return (m);
  }

  Log ("LoadAC3D: Loading model %s", name);

  /* Find an unused model index */
  m = modelnum = FindModel();
  strcpy (model[m].name, name);
  model[m].in_use = 1;

  /* Init bounding box */
  model[m].lobound[0] = model[m].lobound[1] = model[m].lobound[2] = 0.0;
  model[m].hibound[0] = model[m].hibound[1] = model[m].hibound[2] = 0.0;

  /* Get a list id */
  model[m].list = glGenLists (1);

  /* Construct file name */
  sprintf (fn, "models/%s", name);

  /* Open it */
  if (NULL == (acfd = fopen (fn, "rt")))
  {
    Log ("LoadAC3D: Can't open %s, giving up!", fn);
    return (-1);
  }

  firsttime = 1;
  firsttime2 = 1;

  num_materials = 0;
  num_textures = 0;
  vtab = NULL;
  last_flags = -1 ;
  last_num_kids = -1 ;
  current_flags = -1 ;
  texture_enabled = 0;
  need_texture = 0;
  last_mat = -1;

  /* Look for and define textures */
  ScanForTextures();

  while (fgets (buffer, 1024, acfd) != NULL)
  {
    s = buffer ;

    /* Skip leading whitespace */
    SkipSpaces (&s);

    /* Skip blank lines and comments */
    if ( *s < ' ' && *s != '\t' ) continue;
    if ( *s == '#' || *s == ';' ) continue;

    if (firsttime)
    {
      firsttime = 0;

      if (strncmp (s, "AC3D", 4) != 0)
      {
        Log ("LoadAC3d: %s is not an AC3D format file", fn);
        return (-1);
      }
    }
    else
    {
      Search (top_tags, s);
    }
  }

  glShadeModel (GL_FLAT);
  glEndList();
  fclose (acfd);

  Log ("LoadAC3D: Lo bounds: %lf %lf %lf", model[m].lobound[0],
  model[m].lobound[1], model[m].lobound[2]);
  Log ("LoadAC3D: Hi bounds: %lf %lf %lf", model[m].hibound[0],
  model[m].hibound[1], model[m].hibound[2]);

  /* All is well */
  return m;
}

void ScanForTextures()
/*
 *  Look through the AC3D file for all "texture" lines
 */
{
  char buf[1024], fn[256];

  /* Look for "texture" lines */
  while (NULL != fgets (buf, 1024, acfd))
  {
    if (1 == sscanf (buf, "texture %s", fn))
    {
      RemoveQuotes (fn);
      LoadTexture (fn);
    }
  }

  /* Rewind the file */
  fseek (acfd, 0, SEEK_SET);
}

void LoadTexture (char *s)
/*
 *  Load a texture file
 */
{
  char fn[256];

  Log ("LoadTexture: Loading texture %s", s);

  /* Construct file name */
  sprintf (fn, "models/%s", s);

  /* Get a texture id */
  glGenTextures (1, &tex[ntex].id);
  strcpy (tex[ntex].name, s);

  /* Define the texture */
  glBindTexture (GL_TEXTURE_2D, tex[ntex].id);
  glPixelStorei (GL_UNPACK_ALIGNMENT, 1);
  load_sgi_texture (fn);
  glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
  glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
  glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
  glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);

  ntex++;
}

void ACNormalize (float *v)
{
  float mag;

  mag = sqrt (v[0]*v[0] + v[1]*v[1] + v[2]*v[2]);

  if (mag == 0.0f)
  {
    v[0] = v[1] = 0.0f;
    v[2] = 1.0f;
    return;
  }

  v[0] /= mag;
  v[1] /= mag;
  v[2] /= mag;
}

void CrossProduct (float *dst, float *a, float *b)
{
  dst[0] = a[1] * b[2] - a[2] * b[1];
  dst[1] = a[2] * b[0] - a[0] * b[2];
  dst[2] = a[0] * b[1] - a[1] * b[0];
}

void MakeNormal (float *dst, float *a, float *b, float *c)
{
  float ab[3];
  float ac[3];

  ab[0] = b[0] - a[0];
  ab[1] = b[1] - a[1];
  ab[2] = b[2] - a[2];
  ACNormalize (ab);

  ac[0] = c[0] - a[0];
  ac[1] = c[1] - a[1];
  ac[2] = c[2] - a[2];
  ACNormalize (ac);

  CrossProduct (dst, ab, ac);
  ACNormalize (dst);
}

void SkipSpaces (char **s)
{
  while (**s == ' ' || **s == '\t') (*s)++;
}

void SkipQuotes (char **s)
{
  char *t;

  SkipSpaces (s);

  if (**s == '\"')
  {
    (*s)++;
    t = *s;
    while (*t != '\0' && *t != '\"') t++;

    if ( *t != '\"' )
    Log ("SkipQuotes: Mismatched double-quote in '%s'\n", *s);

    *t = '\0';
  }
  else
  {
    Log ("SkipQuuotes: Expected double-quote in '%s'", *s);
  }
}

void RemoveQuotes (char *s)
/*
 *  Remove quotes from string
 */
{
  char *t;
  int i, j, len;

  len = strlen (s);
  t = (char *) malloc (len+1);
  strcpy (t, s);
  j = 0;

  for (i=0; i<len; i++)
  {
    if (t[i] != '"')
    {
      s[j++] = t[i];
    }
  }
  s[j] = 0;

  free (t);
}

int Search (struct Tag *tags, char *s)
{
  int i;

  SkipSpaces (&s);

  for (i=0; tags[i].token != NULL; i++)
  {
    if (!strncasecmp (tags[i].token, s, strlen (tags[i].token)))
    {
      s += strlen (tags[i].token);
      SkipSpaces (&s);
      return (*(tags[i].func))(s);
    }
  }

  Log ("Search: Unrecognised token '%s'", s);
  exit (1);
  return 0;
}

struct Tag object_tags[] =
{
  {
"name"   , DoACName}
  ,
  {
"data"   , DoData}
  ,
  {
"texture", DoTexture}
  ,
  {
"texrep" , DoTexrep}
  ,
  {
"rot"    , DoRot}
  ,
  {
"loc"    , DoLoc}
  ,
  {
"url"    , DoUrl}
  ,
  {
"numvert", DoNumvert}
  ,
  {
"numsurf", DoNumsurf}
  ,
  {
"kids"   , DoKids}
  ,
  {
    NULL, NULL }
}
;

struct Tag surf_tag[] =
{
  {
"SURF", DoSurf}
  ,
  {
    NULL, NULL}
}
;

struct Tag surface_tags[] =
{
  {
"mat" , DoMat }
  ,
  {
"refs", DoRefs}
  ,
  {
    NULL, NULL}
}
;

struct Tag obj_type_tags[] =
{
  {
"world", DoObjWorld}
  ,
  {
"poly" , DoObjPoly }
  ,
  {
"group", DoObjGroup}
  ,
  {
    NULL, NULL}
}
;

#define OBJ_WORLD  0
#define OBJ_POLY   1
#define OBJ_GROUP  2

int DoObjWorld (char *s)
{
  return OBJ_WORLD;
}

int DoObjPoly (char *s)
{
  return OBJ_POLY;
}

int DoObjGroup (char *s)
{
  return OBJ_GROUP;
}

void DoBegin (int flags)
{
  if (last_flags == -1) last_flags = ~flags;

  if (((flags>>4) & 0x01) != ((last_flags>>4) & 0x01))
  {
    if ((flags>>4) & 0x01)
    {
      /* Smooth Shaded */
      acsmooth = 1;
    }
    else
    {
      /* Flat Shaded */
      acsmooth = 0;
    }
  }
  /**
 if (((flags>>4) & 0x02) != ((last_flags>>4) & 0x02))
 {
  if ((flags>>4) & 0x02)
  {
   if (acsurfpass == PASS_RENDER) glDisable (GL_CULL_FACE);
  }
  else
  {
   if (acsurfpass == PASS_RENDER) glEnable (GL_CULL_FACE);
  }
 }

 if ((last_flags & 0x0F) == 0  && (flags & 0x0F) != 0)
 {
  if (acsurfpass == PASS_RENDER) glDisable (GL_LIGHTING);
 }
 else if ((last_flags & 0x0F) != 0  && (flags & 0x0F) == 0)
 {
  if (acsurfpass == PASS_RENDER) glEnable (GL_LIGHTING);
 }
**/
  last_flags = flags;

  switch (flags & 0x0F)
  {
    case 0: if (acsurfpass == PASS_RENDER) glBegin (GL_POLYGON);
    break;

    case 1: if (acsurfpass == PASS_RENDER) glBegin (GL_LINE_LOOP);
    break;

    case 2: if (acsurfpass == PASS_RENDER) glBegin (GL_LINE_STRIP);
    break;

    default:
    Log ("DoBegin: Illegal surface type 0x%02x", flags);
    break;
  }
}

int DoMaterial (char *s)
{
  char name[1024], *nm;
  float rgb[3], amb[3], emis[3], spec[3], trans;
  int shi;

  if (15 != sscanf (s,
  "%s rgb %f %f %f amb %f %f %f emis %f %f %f spec %f %f %f shi %d trans %f",
  name, &rgb[0], &rgb[1], &rgb[2], &amb[0], &amb[1], &amb[2],
  &emis[0], &emis[1], &emis[2], &spec[0], &spec[1], &spec[2], &shi, &trans))
 {
  Log ("DoMaterial: Can't parse this MATERIAL: %s", s);
 }
 else
 {
  nm = name;
  SkipQuotes (&nm);

  /* Get a list for this material */
  matlist[num_materials] = glGenLists (1);

  glNewList (matlist[num_materials], GL_COMPILE );
  glMaterialfv (GL_FRONT_AND_BACK, GL_AMBIENT, amb);
  glMaterialfv (GL_FRONT_AND_BACK, GL_DIFFUSE, rgb);
  glMaterialfv (GL_FRONT_AND_BACK, GL_SPECULAR, spec);
  glMaterialfv (GL_FRONT_AND_BACK, GL_EMISSION, emis);
  glMaterialf  (GL_FRONT_AND_BACK, GL_SHININESS, shi);
  glEndList();
 }
 num_materials++;

 return PARSE_CONT;
}

int DoObject (char *s)
{
 int i, obj_type, num_kids;
 char buffer[1024];

 if (firsttime2)
 {
  glNewList (model[modelnum].list, GL_COMPILE);
  glShadeModel (GL_SMOOTH);
  firsttime2 = 0;
 }

 obj_type = Search (obj_type_tags, s);  

 switch (obj_type)
 {
 case OBJ_WORLD:
  break;

 case OBJ_POLY:
  break;

 case OBJ_GROUP:
  break;
 }

 need_texture = 0;

 glPushMatrix();

 while (NULL != fgets (buffer, 1024, acfd))
 {
  if (Search (object_tags, buffer) == PARSE_POP) break;
 }

 num_kids = last_num_kids;

 for (i=0; i<num_kids; i++)
 {
  fgets (buffer, 1024, acfd);
  Search (top_tags, buffer);
 }

 glPopMatrix();

 return PARSE_CONT;
}

int DoACName (char *s)
{
 SkipQuotes (&s);
 return PARSE_CONT;
}

int DoData (char *s)
{
 int i, len;

 len = strtol (s, NULL, 0);
 Log ("DoData: WARNING - data string encountered");
 for (i=0; i<len; i++) fgetc (acfd);

 return PARSE_CONT;
}

int GetTexture (char *s)
{
 int t;

 for (t=0; t<ntex; t++)
 {
  if (!strcasecmp (s, tex[t].name)) return t;
 }

 return 0;
}

int DoTexture (char *s)
{
 static int last_tex = -1;
 int t;

 SkipQuotes (&s);

 t = GetTexture (s);

 if (t != last_tex)
 {
  glBindTexture (GL_TEXTURE_2D, tex[t].id);
 
  last_tex = t;
 }

 need_texture = 1;

 return PARSE_CONT;
}

int DoTexrep (char *s)
{
 float texrep[2];

 if (2 != sscanf (s, "%f %f", &texrep[0], &texrep[1]))
  Log ("DoTextrep: Illegal texrep record: %s", s);

 return PARSE_CONT;
}

int DoRot (char *s)
{
 float mat[4][4];

 mat[0][3] = mat[1][3] = mat[2][3] = mat[3][0] = mat[3][1] = mat[3][2] = 0.0f;
 mat[3][3] = 1.0f; 

 if (9 != sscanf (s, "%f %f %f %f %f %f %f %f %f",
  &mat[0][0], &mat[0][1], &mat[0][2],
  &mat[1][0], &mat[1][1], &mat[1][2],
  &mat[2][0], &mat[2][1], &mat[2][2]))
 {
  Log ("DoRot: Illegal rot record: %s", s);
 }

 glMultMatrixf ((float *)mat);

 return PARSE_CONT ;
}

int DoLoc (char *s)
{
 float loc[3];

 if (3 != sscanf (s, "%f %f %f", &loc[0], &loc[1], &loc[2]))
 {
  Log ("DoLoc: Illegal loc record: %s", s);
 }

 loc[0] /= 100.0;
 loc[1] /= 100.0;
 loc[2] /= 100.0;
 glTranslatef (loc[0], loc[1], loc[2]);

 return PARSE_CONT;
}

int DoUrl (char *s)
{
 SkipQuotes (&s);
 return PARSE_CONT;
}

int DoNumvert (char *s)
{
 char buffer[1024];
 int i;

 nv = strtol (s, NULL, 0);
 
 if (vtab != NULL) free (vtab) ;

 vtab = (struct ACVertex *) malloc (3*nv*sizeof(struct ACVertex));

 for (i=0; i<nv; i++)
 {
  fgets (buffer, 1024, acfd);

  if (3 != sscanf (buffer, "%f %f %f", &vtab[i].v[0], &vtab[i].v[1], &vtab[i].v[2]))
  {
   Log ("DoNumvert: Illegal vertex record: %s", buffer);
  }
  vtab[i].v[0] /= 100.0;
  vtab[i].v[1] /= 100.0;
  vtab[i].v[2] /= 100.0;

  vtab[i].normal[0] = vtab[i].normal[1] = vtab[i].normal[2] = 0.0;
  vtab[i].n = 0.0;

  if (vtab[i].v[0] < model[modelnum].lobound[0]) model[modelnum].lobound[0] = vtab[i].v[0];
  if (vtab[i].v[1] < model[modelnum].lobound[1]) model[modelnum].lobound[1] = vtab[i].v[1];
  if (vtab[i].v[2] < model[modelnum].lobound[2]) model[modelnum].lobound[2] = vtab[i].v[2];
  if (vtab[i].v[0] > model[modelnum].hibound[0]) model[modelnum].hibound[0] = vtab[i].v[0];
  if (vtab[i].v[1] > model[modelnum].hibound[1]) model[modelnum].hibound[1] = vtab[i].v[1];
  if (vtab[i].v[2] > model[modelnum].hibound[2]) model[modelnum].hibound[2] = vtab[i].v[2];
 }

 return PARSE_CONT;
}

int DoNumsurf (char *s)
{
 int i, ns, v;
 long pos, p;
 char buffer[1024];

 ns = strtol (s, NULL, 0);

 /* Do first pass to average normals */
 pos = ftell (acfd);
 acsurfpass = PASS_NORMAL;
 for (i=0; i<ns; i++)
 {
  p = ftell (acfd);
  fgets (buffer, 1024, acfd);
  Search (surf_tag, buffer);
 }

 /* Back to beginning of object */
 fseek (acfd, pos, SEEK_SET);

 /* Average normals */
 for (v=0; v<nv; v++)
 {
  vtab[v].normal[0] /= vtab[v].n;
  vtab[v].normal[1] /= vtab[v].n;
  vtab[v].normal[2] /= vtab[v].n;
 }

 /* Now render */
 acsurfpass = PASS_RENDER;
 last_mat = (-1);

 for (i=0; i<ns; i++)
 {
  p = ftell (acfd);
  fgets (buffer, 1024, acfd);
  Search (surf_tag, buffer);
 }

 return PARSE_CONT;
}

int DoSurf (char *s)
{
 char buffer[1024];

 current_flags = strtol (s, NULL, 0);

 while (NULL != fgets (buffer, 1024, acfd))
 {
  if (Search (surface_tags, buffer) == PARSE_POP) break;
 }

 return PARSE_CONT ;
}

int DoMat (char *s)
{
 int mat;

 mat = strtol (s, NULL, 0);

 if (mat != last_mat)
 {
  if (acsurfpass == PASS_RENDER) glCallList (matlist[mat]);
  last_mat = mat;
 }

 return PARSE_CONT ;
}

int DoRefs (char *s)
{
 int i;
 int nrefs;
 char buffer[1024] ;
 int *vlist;
 float *tlistu, *tlistv, nrm[3];

 nrefs = strtol (s, NULL, 0);

 if (nrefs == 0) return PARSE_POP;

 if (need_texture && !texture_enabled)
 {
  if (acsurfpass == PASS_RENDER) glEnable (GL_TEXTURE_2D);
  texture_enabled = 1;
 }
 else if (!need_texture && texture_enabled)
 {
  if (acsurfpass == PASS_RENDER) glDisable (GL_TEXTURE_2D);
  texture_enabled = 0;
 }

 vlist  = (int *) malloc (sizeof(int)*nrefs);
 tlistu = (float *) malloc (sizeof(float)*nrefs) ;
 tlistv = (float *) malloc (sizeof(float)*nrefs) ;
 
 DoBegin (current_flags);

 for (i=0; i<nrefs; i++)
 {
  fgets (buffer, 1024, acfd);

  if (3 != sscanf (buffer, "%d %f %f", &vlist[i], &tlistu[i], &tlistv[i]))
  {
   Log ("DoRefs: Illegal ref record: %s", buffer);
   exit (1);
  }
 }

 if (nrefs >= 3)
 {
  MakeNormal (nrm, vtab[vlist[0]].v, vtab[vlist[1]].v, vtab[vlist[2]].v);

  if (!acsmooth && (acsurfpass == PASS_RENDER))
  {
   glNormal3fv (nrm);
  }
 }

 for (i=0; i<nrefs; i++)
 {
  /* Maintain normal first pass */
  if (acsmooth && (acsurfpass == PASS_NORMAL))
  {
   MaintainNormal (vlist[i], nrm);
  }

  /* Render second pass */
  if (acsmooth && (acsurfpass == PASS_RENDER))
  {
   glNormal3fv (vtab[vlist[i]].normal);
  }

  if (acsurfpass == PASS_RENDER)
  {
   glTexCoord2f (tlistu[i], tlistv[i]);
   glVertex3fv (vtab[vlist[i]].v);
  }
 }

 free (vlist);
 free (tlistu);
 free (tlistv);

 if (acsurfpass == PASS_RENDER) glEnd();

 return PARSE_POP;
}

int DoKids (char *s)
{
 last_num_kids = strtol (s, NULL, 0);
 return PARSE_POP;
}

void MaintainNormal (int i, float *n)
/*
 *  Maintain a vertex normal
 */
{
 vtab[i].n += 1.0;

 vtab[i].normal[0] += n[0];
 vtab[i].normal[1] += n[1];
 vtab[i].normal[2] += n[2];
}

/** ------------- The rest loads SGI texture files --------------- **/

/* Some magic constants in the file header. */

#define SGI_IMG_MAGIC           0x01DA
#define SGI_IMG_SWABBED_MAGIC   0xDA01   /* This is how it appears on a PC */
#define SGI_IMG_VERBATIM        0
#define SGI_IMG_RLE             1

static int            sgi_max ;
static int            sgi_min ;
static int            sgi_colormap ;
static unsigned short sgi_magic ;
static char           sgi_type ;
static char           sgi_bpp ;
static unsigned int  *sgi_start ;
static int           *sgi_leng ;
static unsigned char *sgi_temp ;
static int            sgi_swapped ;
static FILE          *sgi_fd ;
static unsigned short sgi_dim ;
static unsigned short sgi_xsize ;
static unsigned short sgi_ysize ;
static unsigned short sgi_zsize ;

static void makeConsistentHeader ()
{
  /*
    Sanity checks - and a workaround for buggy RGB files generated by
    the MultiGen Paint program because it will sometimes get confused
    about the way to represent maps with more than one component.

    eg   Y > 1, Number of dimensions == 1
         Z > 1, Number of dimensions == 2
  */

  if ( sgi_ysize > 1 && sgi_dim < 2 ) sgi_dim = 2 ;
  if ( sgi_zsize > 1 && sgi_dim < 3 ) sgi_dim = 3 ;
  if ( sgi_dim < 1 ) sgi_ysize = 1 ;
  if ( sgi_dim < 2 ) sgi_zsize = 1 ;
  if ( sgi_dim > 3 ) sgi_dim   = 3 ;
  if ( sgi_zsize < 1 && sgi_ysize == 1 ) sgi_dim = 1 ;
  if ( sgi_zsize < 1 && sgi_ysize != 1 ) sgi_dim = 2 ;
  if ( sgi_zsize >= 1 ) sgi_dim = 3 ;

  /*
    A very few SGI image files have 2 bytes per component - this
    tool cannot deal with those kinds of files. 
  */

  if ( sgi_bpp == 2 )
  {
    Log ("makeConsistentHeader: Can't work with SGI images with %d bpp", sgi_bpp);
    exit (1);
  }

  sgi_bpp = 1 ;
  sgi_min = 0 ;
  sgi_max = 255 ;
  sgi_magic = SGI_IMG_MAGIC ;
  sgi_colormap = 0 ;
}

static void swab_short ( unsigned short *x )
{
  if ( sgi_swapped )
    *x = (( *x >>  8 ) & 0x00FF ) | 
         (( *x <<  8 ) & 0xFF00 ) ;
}

static void swab_int ( unsigned int *x )
{
  if ( sgi_swapped )
    *x = (( *x >> 24 ) & 0x000000FF ) | 
         (( *x >>  8 ) & 0x0000FF00 ) | 
         (( *x <<  8 ) & 0x00FF0000 ) | 
         (( *x << 24 ) & 0xFF000000 ) ;
}

static void swab_int_array ( int *x, int leng )
{
  int i;

  if ( ! sgi_swapped )
    return ;

  for ( i = 0 ; i < leng ; i++ )
  {
    *x = (( *x >> 24 ) & 0x000000FF ) | 
         (( *x >>  8 ) & 0x0000FF00 ) | 
         (( *x <<  8 ) & 0x00FF0000 ) | 
         (( *x << 24 ) & 0xFF000000 ) ;
    x++ ;
  }
}


static unsigned char readByte ()
{
  unsigned char x ;
  fread ( & x, sizeof(unsigned char), 1, sgi_fd ) ;
  return x ;
}

static unsigned short readShort ()
{
  unsigned short x ;
  fread ( & x, sizeof(unsigned short), 1, sgi_fd ) ;
  swab_short ( & x ) ;
  return x ;
}

static unsigned int readInt ()
{
  unsigned int x ;
  fread ( & x, sizeof(unsigned int), 1, sgi_fd ) ;
  swab_int ( & x ) ;
  return x ;
}


static void getRow ( unsigned char *buf, int y, int z )
{
    unsigned char pixel, count ;

  if ( y >= sgi_ysize ) y = sgi_ysize - 1 ;
  if ( z >= sgi_zsize ) z = sgi_zsize - 1 ;

  fseek ( sgi_fd, sgi_start [ z * sgi_ysize + y ], SEEK_SET ) ;

  if ( sgi_type == SGI_IMG_RLE )
  {
    unsigned char *tmpp = sgi_temp ;
    unsigned char *bufp = buf ;

    fread ( sgi_temp, 1, sgi_leng [ z * sgi_ysize + y ], sgi_fd ) ;

    while ( 1 )
    {
      pixel = *tmpp++ ;

      count = ( pixel & 0x7f ) ;

      if ( count == 0 )
 break ;

      if ( pixel & 0x80 )
      {
        while ( count-- )
   *bufp++ = *tmpp++ ;
      }
      else
      {
        pixel = *tmpp++ ;

 while ( count-- )
          *bufp++ = pixel ;
      }
    }
  }
  else
    fread ( buf, 1, sgi_xsize, sgi_fd ) ;
}


static void getPlane ( unsigned char *buf, int z )
{
  int y;
  if ( sgi_fd == NULL )
    return ;

  if ( z >= sgi_zsize ) z = sgi_zsize - 1 ;

  for ( y = 0 ; y < sgi_ysize ; y++ )
    getRow ( & buf [ y * sgi_xsize ], y, z ) ;
}



static void getImage ( unsigned char *buf )
{
  int y, z;

  if ( sgi_fd == NULL )
    return ;

  for (y = 0 ; y < sgi_ysize ; y++ )
    for (z = 0 ; z < sgi_zsize ; z++ )
      getRow ( & buf [ ( z * sgi_ysize + y ) * sgi_xsize ], y, z ) ;
}

void load_sgi_texture ( char *fname )
{
  int i, tablen, x, y, l, maxlen;
  GLubyte *texels [ 20 ], *ptr ;
  unsigned char *rbuf, *gbuf, *bbuf, *abuf;
  int lev, map_level;
  int j, x2, y2, c;

  sgi_dim   = 0 ;
  sgi_start = NULL ;
  sgi_leng  = NULL ;
  sgi_temp  = NULL ;

  sgi_swapped = 0 ;

  sgi_fd = fopen ( fname, "rb" ) ;

  if ( sgi_fd == NULL )
  {
    Log ("load_sgi_texture: Failed to open '%s' for reading.", fname ) ;
    return ;
  }

  sgi_magic = readShort () ;

  if ( sgi_magic != SGI_IMG_MAGIC && sgi_magic != SGI_IMG_SWABBED_MAGIC )
  {
    Log ("load_sgi_texture: %s: Unrecognised magic number 0x%04x", fname, sgi_magic ) ;
    exit ( 1 ) ;
  }

  if ( sgi_magic == SGI_IMG_SWABBED_MAGIC )
  {
    sgi_swapped = 1 ;
    swab_short ( & sgi_magic ) ;
  }

  sgi_type  = readByte  () ;
  sgi_bpp   = readByte  () ;
  sgi_dim   = readShort () ;

  /*
    This is a backstop test - if for some reason the magic number isn't swabbed, this
    test will still catch a swabbed file. Of course images with more than 256 dimensions
    are not catered for :-)
  */

  if ( sgi_dim > 255 )
  {
    Log ("load_sgi_texture: %s: Bad swabbing?!?", fname ) ;
    sgi_swapped = ! sgi_swapped ;
    swab_short ( & sgi_dim ) ;
    sgi_magic = SGI_IMG_MAGIC ;
  }

  sgi_xsize = readShort () ;
  sgi_ysize = readShort () ;
  sgi_zsize = readShort () ;
  sgi_min   = readInt   () ;  
  sgi_max   = readInt   () ;  
              readInt   () ;  /* Dummy field */

  for (i = 0 ; i < 80 ; i++ )
    readByte () ;         /* Name field */

  sgi_colormap = readInt () ;

  for (i = 0 ; i < 404 ; i++ )
    readByte () ;         /* Dummy field */

  makeConsistentHeader () ;

  tablen = sgi_ysize * sgi_zsize ;
  sgi_start = (unsigned int *) malloc (tablen*sizeof(unsigned int)) ;
  sgi_leng  = (int *) malloc (sizeof(int)*tablen) ;

  if ( sgi_type == SGI_IMG_RLE )
  {
    fread ( sgi_start, sizeof ( unsigned int ), tablen, sgi_fd ) ;
    fread ( sgi_leng , sizeof (  int ), tablen, sgi_fd ) ;
    swab_int_array ( (int *) sgi_start, tablen ) ;
    swab_int_array ( (int *) sgi_leng , tablen ) ;

    maxlen = 0 ;

    for (i = 0 ; i < tablen ; i++ )
      if ( sgi_leng [ i ] > maxlen )
        maxlen = sgi_leng [ i ] ;

    sgi_temp = (unsigned char *) malloc (maxlen) ;
  }
  else
  {
    sgi_temp = NULL ;

    for (i = 0 ; i < sgi_zsize ; i++ )
      for (j = 0 ; j < sgi_ysize ; j++ )
      {
        sgi_start [ i * sgi_ysize + j ] = sgi_xsize * ( i * sgi_ysize + j ) + 512 ;
        sgi_leng  [ i * sgi_ysize + j ] = sgi_xsize ;
      }
  }

  if ( sgi_zsize <= 0 || sgi_zsize > 4 )
  {
    Log ("load_sgi_texture: %s: Not an int/inta/rgb/rgba image?!?", fname ) ;
    exit ( 1 ) ;
  }


  for (i = 0 ; i < 20 ; i++ )
    texels [ 0 ] = NULL ;

  texels [ 0 ] = (GLubyte *) malloc (sizeof(GLubyte)* sgi_xsize * sgi_ysize * sgi_zsize ) ;

  ptr = texels [ 0 ] ;

  rbuf = malloc (sgi_xsize);
  gbuf = (sgi_zsize>1) ? malloc(sgi_xsize): (unsigned char *) NULL ;
  bbuf = (sgi_zsize>2) ? malloc(sgi_xsize): (unsigned char *) NULL ;
  abuf = (sgi_zsize>3) ? malloc(sgi_xsize): (unsigned char *) NULL ;

  for ( y = 0 ; y < sgi_ysize ; y++ )
  {
    switch ( sgi_zsize )
    {
      case 1 :
 getRow ( rbuf, y, 0 ) ;

 for ( x = 0 ; x < sgi_xsize ; x++ )
   *ptr++ = rbuf [ x ] ;

 break ;

      case 2 :
 getRow ( rbuf, y, 0 ) ;
 getRow ( gbuf, y, 1 ) ;

 for (x = 0 ; x < sgi_xsize ; x++ )
 {
   *ptr++ = rbuf [ x ] ;
   *ptr++ = gbuf [ x ] ;
 }
 break ;

      case 3 :
        getRow ( rbuf, y, 0 ) ;
 getRow ( gbuf, y, 1 ) ;
 getRow ( bbuf, y, 2 ) ;

 for (x = 0 ; x < sgi_xsize ; x++ )
 {
   *ptr++ = rbuf [ x ] ;
   *ptr++ = gbuf [ x ] ;
   *ptr++ = bbuf [ x ] ;
 }
 break ;

      case 4 :
        getRow ( rbuf, y, 0 ) ;
 getRow ( gbuf, y, 1 ) ;
 getRow ( bbuf, y, 2 ) ;
 getRow ( abuf, y, 3 ) ;

 for (x = 0 ; x < sgi_xsize ; x++ )
 {
   *ptr++ = rbuf [ x ] ;
   *ptr++ = gbuf [ x ] ;
   *ptr++ = bbuf [ x ] ;
   *ptr++ = abuf [ x ] ;
 }
 break ;
    }
  }

  for ( lev = 0 ; (( sgi_xsize >> (lev+1) ) != 0 ||
                   ( sgi_ysize >> (lev+1) ) != 0 ) ; lev++ )
  {
    /* Suffix '1' is the higher level map, suffix '2' is the lower level. */

    int l1 =  lev  ;
    int l2 = lev+1 ;
    int w1 = sgi_xsize >> l1 ;
    int h1 = sgi_ysize >> l1 ;
    int w2 = sgi_xsize >> l2 ;
    int h2 = sgi_ysize >> l2 ;

    if ( w1 <= 0 ) w1 = 1 ;
    if ( h1 <= 0 ) h1 = 1 ;
    if ( w2 <= 0 ) w2 = 1 ;
    if ( h2 <= 0 ) h2 = 1 ;

    texels[l2] = (GLubyte *) malloc (sizeof(GLubyte)*  w2 * h2 * sgi_zsize ) ;

    for (x2 = 0 ; x2 < w2 ; x2++ )
      for (y2 = 0 ; y2 < h2 ; y2++ )
        for (c = 0 ; c < sgi_zsize ; c++ )
        {
          int x1   = x2 + x2 ;
          int x1_1 = ( x1 + 1 ) % w1 ;
          int y1   = y2 + y2 ;
          int y1_1 = ( y1 + 1 ) % h1 ;

   int t1 = texels [ l1 ] [ (y1   * w1 + x1  ) * sgi_zsize + c ] ;
   int t2 = texels [ l1 ] [ (y1_1 * w1 + x1  ) * sgi_zsize + c ] ;
   int t3 = texels [ l1 ] [ (y1   * w1 + x1_1) * sgi_zsize + c ] ;
   int t4 = texels [ l1 ] [ (y1_1 * w1 + x1_1) * sgi_zsize + c ] ;

          texels [ l2 ] [ (y2 * w2 + x2) * sgi_zsize + c ] =
                                           ( t1 + t2 + t3 + t4 ) / 4 ;
        }
  }

  texels [ lev+1 ] = NULL ;

  if (rbuf != NULL) free (rbuf)  ;
  if (gbuf != NULL) free (gbuf)  ;
  if (bbuf != NULL) free (bbuf)  ;
  if (abuf != NULL) free (abuf)  ;

  if ( ! ((sgi_xsize & (sgi_xsize-1))==0) ||
       ! ((sgi_ysize & (sgi_ysize-1))==0) )
  {
    Log ("load_sgi_texture: %s: Map is not a power-of-two in size!", fname ) ;
    exit ( 1 ) ;
  }

  glPixelStorei ( GL_UNPACK_ALIGNMENT, 1 ) ;

  map_level = 0 ;

  if ( sgi_xsize > 256 || sgi_ysize > 256 )
  {
    while ( sgi_xsize > 256 || sgi_ysize > 256 )
    {
      if (texels[0] != NULL) free (texels[0]) ;
      sgi_xsize >>= 1 ;
      sgi_ysize >>= 1 ;

      for (l = 0 ; texels [ l ] != NULL ; l++ )
 texels [ l ] = texels [ l+1 ] ;
    }
  }

  for (i = 0 ; texels [ i ] != NULL ; i++ )
  {
    int w = sgi_xsize>>i ;
    int h = sgi_ysize>>i ;

    if ( w <= 0 ) w = 1 ;
    if ( h <= 0 ) h = 1 ;

    glTexImage2D  ( GL_TEXTURE_2D,
                     map_level, sgi_zsize, w, h, 0 /* Border */,
                            (sgi_zsize==1)?GL_LUMINANCE:
                            (sgi_zsize==2)?GL_LUMINANCE_ALPHA:
                            (sgi_zsize==3)?GL_RGB:
                                           GL_RGBA,
                            GL_UNSIGNED_BYTE, (GLvoid *) texels[i] ) ;
    map_level++ ;
  }

  fclose (sgi_fd);
}
