
/* envmap.c - David Blythe, SGI */

/* Texture environment mapping demo. */

#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#ifndef WIN32
#include <unistd.h>
#endif
#if defined(WIN32) || defined(__BEOS__)
#define drand48() (((float) rand())/((float) RAND_MAX))
#endif
#include <string.h>
#include <GL/glut.h>
#include "texture.h"

/* Some <math.h> files do not define M_PI... */
#ifndef M_PI
#define M_PI 3.14159265
#endif

#define OB_CUBE    0
#define OB_SPHERE  1
#define OB_SQUARE  2
#define OB_CYL     3
#define OB_TORUS   4
#define OB_HSPHERE 5

#define NOBJS 5
#define TDRAW 10

#define LIST_BASE 100
#define TOBJ_BASE 200
#define TEXSIZE   256   /* default texture-size */

typedef struct _vector {
  float x, y, z;
} vector_t;

typedef struct _face {
  char *filename;
  unsigned *buf;
  int width;
  int height;
  int components;
  vector_t u, v, n, o;  /* plane equation */
  float angle1;
  vector_t axis1;       /* for rotation */
  float angle2;
  vector_t axis2;
} face_t;

typedef struct _color {
  float r, g, b;
} color_t;

struct {                /* command-line options */
  int use_spheremap;
  char *spheremap_file;
  int hw;
  int size;
  int samples;
  int object;
  char *outfile;
  int tessellation;
} opts = {

  0, 0, 0, TEXSIZE, 4, OB_CUBE, 0, 30
};                      /* default settings */

void display(void);
void init(void);
void build_lists(void);
unsigned *render_spheremap(int *width, int *height,
  int *components, int doalloc);

void
vadd(vector_t * a, vector_t * b, vector_t * sum)
{
  sum->x = a->x + b->x;
  sum->y = a->y + b->y;
  sum->z = a->z + b->z;
}

void
vsub(vector_t * a, vector_t * b, vector_t * diff)
{
  diff->x = a->x - b->x;
  diff->y = a->y - b->y;
  diff->z = a->z - b->z;
}

void
vscale(vector_t * v, float scale)
{
  v->x *= scale;
  v->y *= scale;
  v->z *= scale;
}

float
vdot(vector_t * u, vector_t * v)
{
  return (u->x * v->x + u->y * v->y + u->z * v->z);
}

void
vreflect(vector_t * axis, vector_t * v, vector_t * r)
{
  vector_t t = *axis;

  vscale(&t, 2 * vdot(axis, v));
  vsub(&t, v, r);
}

int
intersect(vector_t * v)
{
  int f;
  float x, y, z;

  x = fabs(v->x);
  y = fabs(v->y);
  z = fabs(v->z);
  if (x >= y && x >= z)
    f = (v->x > 0) ? 2 : 0;
  else if (y >= x && y >= z)
    f = (v->y > 0) ? 4 : 5;
  else
    f = (v->z > 0) ? 3 : 1;

  return f;
}

face_t face[6] =
{
  {"00.rgb", 0, 0, 0, 0,
    {0, 0, -1},
    {0, 1, 0},
    {-1, 0, 0},
    {-0.5, -0.5, 0.5}, 90.0,
    {0, 1, 0}, 0,
    {0, 0, 0}},
  {"01.rgb", 0, 0, 0, 0,
    {1, 0, 0},
    {0, 1, 0},
    {0, 0, -1},
    {-0.5, -0.5, -0.5}, 180.0,
    {0, 1, 0}, 0,
    {0, 0, 0}},
  {"02.rgb", 0, 0, 0, 0,
    {0, 0, 1},
    {0, 1, 0},
    {1, 0, 0},
    {0.5, -0.5, -0.5}, 270.0,
    {0, 1, 0}, 0,
    {0, 0, 0}},
  {"03.rgb", 0, 0, 0, 0,
    {-1, 0, 0},
    {0, 1, 0},
    {0, 0, 1},
    {0.5, -0.5, 0.5}, 0.0,
    {0, 1, 0}, 0,
    {0, 0, 0}},
  {"04.rgb", 0, 0, 0, 0,
    {1, 0, 0},
    {0, 0, 1},
    {0, 1, 0},
    {-0.5, 0.5, -0.5}, 90.0,
    {1, 0, 0}, 180.0,
    {0, 1, 0}},
  {"05.rgb", 0, 0, 0, 0,
    {1, 0, 0},
    {0, 0, -1},
    {0, -1, 0},
    {-0.5, -0.5, 0.5}, -90.0,
    {1, 0, 0}, 180.0,
    {0, 1, 0}}
};

void
sample(int facenum, float s, float t, color_t * c)
{
  face_t *f = &face[facenum];
  int xpos, ypos;
  unsigned char *p;

  xpos = s * f->width;
  ypos = t * f->height;

  p = (unsigned char *) &f->buf[ypos * f->width + xpos];
  c->r = p[0] / 255.0;
  c->g = p[1] / 255.0;
  c->b = p[2] / 255.0;
}

unsigned *
construct_spheremap(int *width, int *height,
  int *components)
{
  int i, j, x, y, f;
  unsigned *spheremap;
  unsigned char *lptr;
  int size = opts.size;
  color_t c, texel;
  vector_t v, r, p;
  float s, t, temp, k;
  int samples;

  /* Read in the 6 faces of the environment */
  for (i = 0; i < 6; i++) {
    face[i].buf = read_texture(face[i].filename, &face[i].width,
      &face[i].height, &face[i].components);
    if (!face[i].buf) {
      fprintf(stderr, "Error: cannot load image %s\n", face[i].filename);
      exit(1);
    }
  }

  *components = face[0].components;
  *width = *height = size;
  samples = opts.samples;

  spheremap = (unsigned *) malloc(size * size * sizeof(unsigned));
  if (!spheremap) {
    perror("malloc");
    exit(1);
  }
  lptr = (unsigned char *) spheremap;

  /* Calculate sphere-map by rendering a perfectly reflective solid sphere. */

  for (y = 0; y < size; y++)
    for (x = 0; x < size; x++) {

      texel.r = texel.g = texel.b = 0.0;
      for (j = 0; j < samples; j++) {
        s = (x + (float) drand48()) / size - 0.5;
        t = (y + (float) drand48()) / size - 0.5;

        temp = s * s + t * t;
        if (temp >= 0.25) {  /* point not on sphere */
          c.r = c.g = c.b = 0;
          continue;
        }
        /* get point on sphere */
        p.x = s;
        p.y = t;
        p.z = sqrt(0.25 - temp);
        vscale(&p, 2.0);
        /* ray from infinity (eyepoint) to surface */
        v.x = 0.0;
        v.y = 0.0;
        v.z = 1.0;

        /* get reflected ray */
        vreflect(&p, &v, &r);

        /* Intersect reflected ray with cube */
        f = intersect(&r);
        k = vdot(&face[f].o, &face[f].n) / vdot(&r, &face[f].n);
        vscale(&r, k);
        vsub(&r, &face[f].o, &v);

        /* Get texture map-indices */
        s = vdot(&v, &face[f].u);
        t = vdot(&v, &face[f].v);

        /* Sample to get color */
        sample(f, s, t, &c);

        texel.r += c.r;
        texel.g += c.g;
        texel.b += c.b;
      }

      lptr[0] = 255 * texel.r / samples;
      lptr[1] = 255 * texel.g / samples;
      lptr[2] = 255 * texel.b / samples;
      lptr[3] = 0xff;
      lptr += 4;
    }

  return (unsigned *) spheremap;
}

void
texture_init(void)
{
  unsigned *buf;
  int width, height, components;
  char filename[80];

  if (opts.use_spheremap) {
    strcpy(filename, opts.spheremap_file);
    buf = read_texture(filename, &width, &height, &components);
    if (components == 3)
      components++;
    if (!buf) {
      fprintf(stderr, "Error: cannot load image %s\n", filename);
      exit(1);
    }
  } else {
    buf = (opts.hw) ?
      render_spheremap(&width, &height, &components, 1) :
      construct_spheremap(&width, &height, &components);

    if (!buf) {
      fprintf(stderr, "Error: Cannot construct spheremap\n");
      exit(1);
    }
  }

#ifdef GL_EXT_texture_object
  glBindTextureEXT(GL_TEXTURE_2D, TOBJ_BASE + TDRAW);
#endif
  glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
  glEnable(GL_TEXTURE_2D);
  glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL);
  gluBuild2DMipmaps(GL_TEXTURE_2D, components, width, height,
    GL_RGBA, GL_UNSIGNED_BYTE, buf);

  glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
  glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,
    GL_LINEAR_MIPMAP_LINEAR);

  glTexGenf(GL_S, GL_TEXTURE_GEN_MODE, GL_SPHERE_MAP);
  glTexGenf(GL_T, GL_TEXTURE_GEN_MODE, GL_SPHERE_MAP);
  glEnable(GL_TEXTURE_GEN_S);
  glEnable(GL_TEXTURE_GEN_T);

  free(buf);
}

void
texture_init_from_spheremap(void)
{
  int w, h, components;

  render_spheremap(&w, &h, &components, 0);

  glReadBuffer(GL_BACK);
#ifdef GL_EXT_texture_object
  glBindTextureEXT(GL_TEXTURE_2D, TOBJ_BASE + TDRAW);
#endif
#ifdef GL_EXT_copy_texture
  glCopyTexSubImage2DEXT(GL_TEXTURE_2D, 0,
    0, 0, 0, 0, w, h);
#endif
  glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
  glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);

  glTexGenf(GL_S, GL_TEXTURE_GEN_MODE, GL_SPHERE_MAP);
  glTexGenf(GL_T, GL_TEXTURE_GEN_MODE, GL_SPHERE_MAP);
  glEnable(GL_TEXTURE_GEN_S);
  glEnable(GL_TEXTURE_GEN_T);
}

void
parse(int argc, char *argv[])
{
  int i = 1;
  char *usage =
  "Usage: map [-size n] [-samples n] [-help] \n\
\t[-sphere filename | -cubemap [0-5].rgb]\n\
\n\
\t-size n : specify size of sphere-map (when generated from cubemap)\n\
\t-samples n : #samples to use per pixel of the spheremap\n\
\t-sphere file.rgb  : specify spheremap-image\n\
\t-cubemap [0-5].rgb: specify 6 cubemap files\n\
\t-out file.rgb: save generated spheremap to file\n\
\t-hw   : Use hardware texture mapping to create sphere-map\n\
\t-help : print this message\n";

#define check_arg(i, n, str) \
  if (argc < i+n) {       \
    fprintf(stderr, "%s needs an argument\n", str);  \
    fprintf(stderr, usage); \
    exit(1); \
  }

  while (i < argc) {
    if (!strcmp(argv[i], "-size")) {
      check_arg(i, 1, "-size");
      opts.size = atoi(argv[++i]);

    } else if (!strcmp(argv[i], "-samples")) {
      check_arg(i, 1, "-samples");
      opts.samples = atoi(argv[++i]);

    } else if (!strcmp(argv[i], "-out")) {
      check_arg(i, 1, "-out");
      opts.outfile = strdup(argv[++i]);

    } else if (!strcmp(argv[i], "-sphere")) {
      opts.use_spheremap = 1;

    } else if (!strcmp(argv[i], "-cubemap")) {
      int j;
      check_arg(i, 6, "-cubemap");
      for (j = 0; j < 6; j++)
        face[j].filename = strdup(argv[++i]);

    } else if (!strcmp(argv[i], "-help")) {
      fprintf(stderr, usage);
      exit(0);

    } else if (!strcmp(argv[i], "-hw")) {
      opts.hw = 1;

    } else {
      if (opts.use_spheremap && !opts.spheremap_file)
        opts.spheremap_file = strdup(argv[i]);
      else {
        fprintf(stderr, "Error: unrecognized option %s\n", argv[i]);
        fprintf(stderr, usage);
        exit(1);
      }
    }
    i++;
  }                     /* end-while */
}

#ifdef use_copytex
static int currwidth = TEXSIZE;
static int currheight = TEXSIZE;
#else
static int currwidth = 400;
static int currheight = 400;
#endif

static int do_spheremap = 0, do_alloc = 0;
static GLfloat rotv[] =
{0., 0., 0.};
static GLfloat rots[] =
{0., 0., 0.};
static GLfloat plane[4][3] =
{
  {1.0, -1.0, 0.0},
  {1.0, 1.0, 0.0},
  {-1.0, 1.0, 0.0},
  {-1.0, -1.0, 0.0}
};

static GLfloat cube[6][4][3] =
{
  {
    {1.0, -1.0, -1.0},  /* counter-clockwise faces */
    {-1.0, -1.0, -1.0},
    {-1.0, 1.0, -1.0},
    {1.0, 1.0, -1.0}
  },
  {
    {1.0, -1.0, 1.0},
    {1.0, -1.0, -1.0},
    {1.0, 1.0, -1.0},
    {1.0, 1.0, 1.0}
  },
  {
    {-1.0, -1.0, 1.0},
    {1.0, -1.0, 1.0},
    {1.0, 1.0, 1.0},
    {-1.0, 1.0, 1.0}
  },
  {
    {-1.0, -1.0, -1.0},
    {-1.0, -1.0, 1.0},
    {-1.0, 1.0, 1.0},
    {-1.0, 1.0, -1.0}
  },
  {
    {1.0, 1.0, 1.0},
    {1.0, 1.0, -1.0},
    {-1.0, 1.0, -1.0},
    {-1.0, 1.0, 1.0}
  },
  {
    {1.0, -1.0, -1.0},
    {1.0, -1.0, 1.0},
    {-1.0, -1.0, 1.0},
    {-1.0, -1.0, -1.0}
  }
};

static float norm[6][3] =
{
  {0.0, 0.0, -1.0},
  {1.0, 0.0, 0.0},
  {0.0, 0.0, 1.0},
  {-1.0, 0.0, 0.0},
  {0.0, 1.0, 0.0},
  {0.0, -1.0, 0.0}
};

void
reshape(int w, int h)
{
  currwidth = w;
  currheight = h;
  glViewport(0, 0, w, h);
  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();
  gluPerspective(40.0, (GLfloat) w / (GLfloat) h, 1.0, 20.0);

  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity();
  gluLookAt(0, 0, 6,
    0, 0, 0,
    0, 1, 0);
}

/* ARGSUSED1 */
void
keys(unsigned char key, int x, int y)
{
  switch (key) {

  case 'o':            /* switch between objects */
    opts.object = (opts.object + 1) % NOBJS;
    glutPostRedisplay();
    break;

  case '+':            /* change tessellation */
    opts.tessellation += 2;
    build_lists();
    glutPostRedisplay();
    break;

  case '-':
    opts.tessellation -= 2;
    if (opts.tessellation < 4)
      opts.tessellation = 4;
    build_lists();
    glutPostRedisplay();
    break;

  case 's':            /* toggle between spheremap generation */
    /* mode and display mode */
    do_spheremap ^= 1;
    if (!do_spheremap)  /* switch back to normal mode */
      do_alloc = 1;
    glutPostRedisplay();
    break;

  case 'h':
  case 'H':
    printf("\nKey functions\n");
    printf("\to: switch objects\n");
    printf("\ts: switch to spheremap generation mode\n");
    printf("\t+: increase tessellation\n");
    printf("\t-: decrease tessellation\n");
    printf("\th: help\n");
    printf("\tESC: quit\n");
    printf("\tleft/right arrow-keys: Rotate around X axis\n");
    printf("\tup/down arrow-keys: Rotate around Y axis\n");
    printf("\tpage-up/pgdown arrow-keys: Rotate around Z axis\n");
    break;

  case 27:
    exit(0);
  }
}

/* ARGSUSED1 */
void
special_keys(int key, int x, int y)
{
  GLfloat *vect;

  if (do_spheremap)
    vect = rots;
  else
    vect = rotv;

  switch (key) {
  case GLUT_KEY_LEFT:
    vect[1] -= 0.5;
    glutPostRedisplay();
    break;
  case GLUT_KEY_RIGHT:
    vect[1] += 0.5;
    glutPostRedisplay();
    break;
  case GLUT_KEY_UP:
    vect[0] -= 0.5;
    glutPostRedisplay();
    break;
  case GLUT_KEY_DOWN:
    vect[0] += 0.5;
    glutPostRedisplay();
    break;
  case GLUT_KEY_PAGE_UP:
    vect[2] -= 0.5;
    glutPostRedisplay();
    break;
  case GLUT_KEY_PAGE_DOWN:
    vect[2] += 0.5;
    glutPostRedisplay();
    break;
  }
}

/* Use mouse buttons to generate spheremap on the fly while the objects are
   being displayed.  */
static void
motion(int x, int y)
{
  rots[1] = 180.0 * x / currwidth - 90.0;
  rots[0] = 180.0 * y / currheight - 90.0;
#ifdef use_copytex
  if (!do_spheremap)
    texture_init_from_spheremap();
#endif
  glutPostRedisplay();
}

int
main(int argc, char *argv[])
{
  parse(argc, argv);
  glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA | GLUT_DEPTH);
  glutInitWindowSize(currwidth, currheight);
  glutCreateWindow("Environment map");

  if(!glutExtensionSupported("GL_EXT_subtexture") ||
    !glutExtensionSupported("GL_EXT_texture_object") ||
    !glutExtensionSupported("GL_EXT_copy_texture")) {
    fprintf(stderr,
      "envmap: This example requires the OpenGL EXT_subtexture, EXT_texture_object, and EXT_copy_texture extensions.\n");
    exit(1);
  }

  init();

  glutReshapeFunc(reshape);
  glutKeyboardFunc(keys);
  glutSpecialFunc(special_keys);
  if (opts.hw)
    glutMotionFunc(motion);
  glutDisplayFunc(display);
  glutMainLoop();
  return 0;             /* ANSI C requires main to return int. */
}

void
build_cube(void)
{
  int i;

  glNewList(LIST_BASE + OB_CUBE, GL_COMPILE);
  for (i = 0; i < 6; i++) {
    glBegin(GL_QUADS);
    glNormal3fv(norm[i]);
    glVertex3fv(cube[i][0]);
    glVertex3fv(cube[i][1]);
    glVertex3fv(cube[i][2]);
    glVertex3fv(cube[i][3]);
    glEnd();
  }
  glEndList();
}

void
build_sphere(int tess)
{
  float r = 1.0, r1, r2, z1, z2;
  float theta, phi;
  int nlon = tess, nlat = tess;
  int i, j;

  glNewList(LIST_BASE + OB_SPHERE, GL_COMPILE);
  glBegin(GL_TRIANGLE_FAN);
  theta = M_PI * 1.0 / nlat;
  r2 = r * sin(theta);
  z2 = r * cos(theta);
  glNormal3f(0.0, 0.0, 1.0);
  glVertex3f(0.0, 0.0, r);
  for (j = 0, phi = 0.0; j <= nlon; j++, phi = 2 * M_PI * j / nlon) {
    glNormal3f(r2 * cos(phi), r2 * sin(phi), z2);
    glVertex3f(r2 * cos(phi), r2 * sin(phi), z2);  /* top */
  }
  glEnd();

  for (i = 2; i < nlat; i++) {
    theta = M_PI * i / nlat;
    r1 = r * sin(M_PI * (i - 1) / nlat);
    z1 = r * cos(M_PI * (i - 1) / nlat);
    r2 = r * sin(theta);
    z2 = r * cos(theta);
    glBegin(GL_QUAD_STRIP);
    for (j = 0, phi = 0; j <= nlat; j++, phi = 2 * M_PI * j / nlon) {
      glNormal3f(r1 * cos(phi), r1 * sin(phi), z1);
      glVertex3f(r1 * cos(phi), r1 * sin(phi), z1);
      glNormal3f(r2 * cos(phi), r2 * sin(phi), z2);
      glVertex3f(r2 * cos(phi), r2 * sin(phi), z2);
    }
    glEnd();
  }

  glBegin(GL_TRIANGLE_FAN);
  theta = M_PI * (nlat - 1) / nlat;
  r2 = r * sin(theta);
  z2 = r * cos(theta);
  glNormal3f(0.0, 0.0, -1.0);
  glVertex3f(0.0, 0.0, -r);
  for (j = nlon, phi = 0.0; j >= 0; j--, phi = 2 * M_PI * j / nlon) {
    glNormal3f(r2 * cos(phi), r2 * sin(phi), z2);
    glVertex3f(r2 * cos(phi), r2 * sin(phi), z2);  /* bottom */
  }
  glEnd();
  glEndList();
}

/* Same as above routine except that we use homogeneous co-ordinates. Each
   component (including w) is multiplied by z */
void
build_special_sphere(int tess)
{
  float r = 1.0, r1, r2, z1, z2;
  float theta, phi;
  int nlon = tess, nlat = tess;
  int i, j;

  glNewList(LIST_BASE + OB_HSPHERE, GL_COMPILE);
  glBegin(GL_TRIANGLE_FAN);
  theta = M_PI * 1.0 / nlat;
  r2 = r * sin(theta);
  z2 = r * cos(theta);
  glNormal3f(0.0, 0.0, 1.0);
  glVertex4f(0.0, 0.0, r * r, r);
  for (j = 0, phi = 0.0; j <= nlon; j++, phi = 2 * M_PI * j / nlon) {
    glNormal3f(r2 * cos(phi), r2 * sin(phi), z2);
    glVertex4f(r2 * cos(phi) * z2, r2 * sin(phi) * z2, z2 * z2, z2);  /* top */
  }
  glEnd();

  for (i = 2; i < nlat; i++) {
    theta = M_PI * i / nlat;
    r1 = r * sin(M_PI * (i - 1) / nlat);
    z1 = r * cos(M_PI * (i - 1) / nlat);
    r2 = r * sin(theta);
    z2 = r * cos(theta);

    if (fabs(z1) < 0.01 || fabs(z2) < 0.01)
      break;

    glBegin(GL_QUAD_STRIP);
    for (j = 0, phi = 0; j <= nlat; j++, phi = 2 * M_PI * j / nlon) {
      glNormal3f(r1 * cos(phi), r1 * sin(phi), z1);
      glVertex4f(r1 * cos(phi) * z1, r1 * sin(phi) * z1, z1 * z1, z1);
      glNormal3f(r2 * cos(phi), r2 * sin(phi), z2);
      glVertex4f(r2 * cos(phi) * z2, r2 * sin(phi) * z2, z2 * z2, z2);
    }
    glEnd();
  }

  glBegin(GL_TRIANGLE_FAN);
  theta = M_PI * (nlat - 1) / nlat;
  r2 = r * sin(theta);
  z2 = r * cos(theta);
  glNormal3f(0.0, 0.0, -1.0);
  glVertex4f(0.0, 0.0, -r * -r, -r);
  for (j = nlon, phi = 0.0; j >= 0; j--, phi = 2 * M_PI * j / nlon) {
    glNormal3f(r2 * cos(phi), r2 * sin(phi), z2);
    glVertex4f(r2 * cos(phi) * z2, r2 * sin(phi) * z2, z2 * z2, z2);  /* bottom 

                                                                       */
  }
  glEnd();
  glEndList();
}

void
build_square(void)
{
  glNewList(LIST_BASE + OB_SQUARE, GL_COMPILE);
  glBegin(GL_POLYGON);
  glNormal3f(0.0, 0.0, 1.0);
  glVertex3fv(plane[0]);
  glVertex3fv(plane[1]);
  glVertex3fv(plane[2]);
  glVertex3fv(plane[3]);
  glEnd();
  glEndList();
}

void
build_cylinder(int tess)
{
  int slices = tess, stacks = tess;
  int i, j;
  GLfloat phi, z1, r, z2;

  glNewList(LIST_BASE + OB_CYL, GL_COMPILE);
  z1 = 2.0;
  r = 1.0;
  glBegin(GL_TRIANGLE_FAN);
  glNormal3f(0.0, 0.0, 1.0);
  glVertex3f(0.0, 0.0, z1);
  for (i = 0; i <= slices; i++) {
    phi = M_PI * 2.0 * i / slices;
    glVertex3f(r * cos(phi), r * sin(phi), z1);
  }
  glEnd();

  for (i = 0, z2 = 0.0, z1 = 2.0 / stacks; i < stacks; i++) {
    glBegin(GL_QUAD_STRIP);
    for (j = 0, phi = 0; j <= slices; j++, phi = M_PI * 2.0 * j / slices) {
      glNormal3f(r * cos(phi), r * sin(phi), 0);
      glVertex3f(r * cos(phi), r * sin(phi), z1);
      glVertex3f(r * cos(phi), r * sin(phi), z2);
    }
    glEnd();
    z1 += 2.0 / stacks;
    z2 += 2.0 / stacks;
  }

  z2 = 0.0;
  glBegin(GL_TRIANGLE_FAN);
  glNormal3f(0.0, 0.0, -1.0);
  glVertex3f(0.0, 0.0, z2);
  for (i = slices; i >= 0; i--) {
    phi = M_PI * 2.0 * i / slices;
    glVertex3f(r * cos(phi), r * sin(phi), z2);
  }
  glEnd();
  glEndList();
}

void
build_torus(int tess)
{
  int i, j, k, l;
  int numg = 1.2 * tess;
  int nums = tess;
  GLfloat x, y, z;
  GLfloat theta, phi;
  const GLfloat twopi = 2.0 * M_PI;
  const GLfloat rg = 2.0, rs = 1.0;

  glNewList(LIST_BASE + OB_TORUS, GL_COMPILE);
  for (i = 0; i < numg; i++) {

    glBegin(GL_QUAD_STRIP);
    for (j = 0; j <= nums; j++) {
      phi = twopi * j / nums;

      for (k = 0; k <= 1; k++) {
        l = (i + k) % numg;
        theta = twopi * l / numg;

        glNormal3f(rs * cos(phi) * cos(theta),
          rs * cos(phi) * sin(theta),
          rs * sin(phi));

        x = (rg + rs * cos(phi)) * cos(theta);
        y = (rg + rs * cos(phi)) * sin(theta);
        z = rs * sin(phi);
        glVertex3f(x, y, z);
      }
    }
    glEnd();
  }
  glEndList();
}

void
display(void)
{
  static int once = 0;

  if (!once && opts.hw) {
    texture_init();
    once = 1;
  }
  if (do_spheremap || do_alloc) {
    int w, h, comp;
    if (do_alloc) {
      texture_init();
      do_alloc = 0;
    } else
      render_spheremap(&w, &h, &comp, 0);

  } else {

    glPushMatrix();
#ifdef GL_EXT_texture_object
    glBindTextureEXT(GL_TEXTURE_2D, TOBJ_BASE + TDRAW);
#endif
    glEnable(GL_DEPTH_TEST);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glRotatef(rotv[0], 1, 0, 0);
    glRotatef(rotv[1], 0, 1, 0);
    glRotatef(rotv[2], 0, 0, 1);

    switch (opts.object) {

    case OB_SQUARE:
    case OB_CUBE:
    case OB_SPHERE:
      glCallList(LIST_BASE + opts.object);
      break;

    case OB_CYL:
      glTranslatef(0.0, 0.0, -1.0);
      glCallList(LIST_BASE + OB_CYL);
      break;

    case OB_TORUS:
      glScalef(0.6, 0.6, 0.6);
      glEnable(GL_NORMALIZE);
      glCallList(LIST_BASE + OB_TORUS);
      glDisable(GL_NORMALIZE);
      break;

    default:
      printf("Eh?\n");
    }

    glLineWidth(2.0);
    glDisable(GL_DEPTH_TEST);
    glDisable(GL_TEXTURE_2D);
    glBegin(GL_LINES);
    glColor3f(1.0, 0.0, 0.0);
    glVertex3f(0.0, 0.0, 0.0);
    glVertex3f(4.0, 0.0, 0.0);

    glColor3f(0.0, 1.0, 0.0);
    glVertex3f(0.0, 0.0, 0.0);
    glVertex3f(0.0, 4.0, 0.0);

    glColor3f(1.0, 1.0, 1.0);
    glVertex3f(0.0, 0.0, 0.0);
    glVertex3f(0.0, 0.0, 4.0);
    glEnd();
    glEnable(GL_TEXTURE_2D);
    glEnable(GL_DEPTH_TEST);
    glPopMatrix();
  }

  glutSwapBuffers();
}

void
build_lists(void)
{
  build_square();
  build_cube();
  build_sphere(opts.tessellation);
  build_cylinder(opts.tessellation);
  build_torus(opts.tessellation);
  build_special_sphere(opts.tessellation + 10);
}

void
init(void)
{
  glEnable(GL_DEPTH_TEST);
  glEnable(GL_CULL_FACE);

  glClearColor(0.0, 0.0, 0.0, 1.0);
  build_lists();
  texture_init();
}

void
err(void)
{
  printf("error=%#x\n", glGetError());
}

/* Use projective textures to generate sphere-map */
unsigned *
render_spheremap(int *width, int *height,
  int *components, int doalloc)
{
  int i, j, k;
  GLfloat p[4];
  static unsigned *spheremap = NULL;
  static int texread_done = 0;

  if (!opts.hw)         /* shouldn't come here */
    return NULL;

  for (i = 0; i < 6; i++) {
    if (!texread_done) {
      face[i].buf = read_texture(face[i].filename, &face[i].width,
        &face[i].height, &face[i].components);
      if (!face[i].buf) {
        fprintf(stderr, "Error: cannot load image %s\n", face[i].filename);
        exit(1);
      }
      for (j = 0; j < face[i].height; j++)  /* texture border hack!! */
        for (k = 0; k < face[i].width; k++)
          if (j < 1 || k < 1 ||
            j > face[i].height - 2 || k > face[i].width - 2) {
            unsigned char *p = (unsigned char *) &face[i].buf[face[i].width * j + k];
            p[3] = 0;   /* zero out alpha */
          }
    }
#ifdef GL_EXT_texture_object
    glBindTextureEXT(GL_TEXTURE_2D, TOBJ_BASE + i);
#endif
    glEnable(GL_TEXTURE_2D);
    glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);

    glTexImage2D(GL_TEXTURE_2D, 0, 4,
      face[i].width, face[i].height, 0,
      GL_RGBA, GL_UNSIGNED_BYTE, face[i].buf);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);

    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);

    glTexGenf(GL_S, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR);

    p[0] = 2.0;
    p[1] = p[2] = p[3] = 0.0;  /* 2zx */
    glTexGenfv(GL_S, GL_OBJECT_PLANE, p);

    glTexGenf(GL_T, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR);
    p[0] = 0.0;
    p[1] = 2.0;
    p[2] = p[3] = 0.0;  /* 2zy */
    glTexGenfv(GL_T, GL_OBJECT_PLANE, p);

    glTexGenf(GL_R, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR);
    p[0] = p[1] = 0.0;
    p[2] = 0.0;
    p[3] = 2.0;         /* 2z */
    glTexGenfv(GL_R, GL_OBJECT_PLANE, p);

    glEnable(GL_TEXTURE_GEN_S);
    glEnable(GL_TEXTURE_GEN_T);
    glEnable(GL_TEXTURE_GEN_R);
  }

  texread_done = 1;

  glMatrixMode(GL_PROJECTION);
  glPushMatrix();
  glMatrixMode(GL_MODELVIEW);
  glPushMatrix();
  glMatrixMode(GL_TEXTURE);
  glPushMatrix();

  glColor4f(1.0, 1.0, 1.0, 1.0);

  /* Initialize sphere-map colors, and viewing transformations */

  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();
  glOrtho(-1, 1, -1, 1, 1.0, 100);
  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity();
  gluLookAt(0, 0, 6,
    0, 0, 0,
    0, 1, 0);

  glEnable(GL_DEPTH_TEST);
  glEnable(GL_CULL_FACE);

  glEnable(GL_BLEND);
  glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

  glClearColor(0.0, 0.0, 0.0, 1.0);
  glClearDepth(1.0);
  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

  /* Look at the sphere from the chosen viewing direction and render the
     sphere at origin.  */

  for (i = 0; i < 6; i++) {  /* for all faces */
#ifdef GL_EXT_texture_object
    glBindTextureEXT(GL_TEXTURE_2D, TOBJ_BASE + i);
#endif

    glMatrixMode(GL_TEXTURE);
    glLoadIdentity();
    glScalef(0.5, 0.5, 1.0);
    glTranslatef(1.0, 1.0, 0.0);
    glFrustum(-1.01, 1.01, -1.01, 1.01, 1.0, 100.0);
    if (face[i].angle2 != 0.) {
      glRotatef(face[i].angle2,
        face[i].axis2.x, face[i].axis2.y, face[i].axis2.z);
    }
    glRotatef(face[i].angle1,
      face[i].axis1.x, face[i].axis1.y, face[i].axis1.z);
    glRotatef(rots[0], 1, 0, 0);
    glRotatef(rots[1], 0, 1, 0);
    glRotatef(rots[2], 0, 0, 1);
    glTranslatef(0.0, 0.0, -1.00);

    glMatrixMode(GL_MODELVIEW);
    glClear(GL_DEPTH_BUFFER_BIT);
    glCallList(LIST_BASE + OB_HSPHERE);
  }

  glDisable(GL_BLEND);
  glMatrixMode(GL_PROJECTION);
  glPopMatrix();
  glMatrixMode(GL_MODELVIEW);
  glPopMatrix();
  glMatrixMode(GL_TEXTURE);
  glPopMatrix();
  glMatrixMode(GL_MODELVIEW);

  if (doalloc) {
    /* read in current image and return it to be used as the spheremap */
    unsigned *temp;

    temp = (unsigned *)
      malloc(currwidth * currheight * sizeof(unsigned));
    spheremap = (unsigned *)
      malloc(opts.size * opts.size * sizeof(unsigned));

    glReadBuffer(GL_BACK);
    glReadPixels(0, 0, currwidth, currheight, GL_RGBA,
      GL_UNSIGNED_BYTE, temp);
    gluScaleImage(GL_RGBA,
      currwidth, currheight, GL_UNSIGNED_BYTE, temp,
      opts.size, opts.size, GL_UNSIGNED_BYTE, spheremap);
    free(temp);
  }
  *width = *height = opts.size;
  *components = 4;
  return (unsigned *) spheremap;
}

