/*
 * This program is under the GNU GPL.
 * Use at your own risk.
 *
 * written by David Bucciarelli (tech.hmw@plus.it)
 *            Humanware s.r.l.
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>

#ifdef WIN32
#include <windows.h>
#endif

#include <GL/glut.h>

#ifdef XMESA
#include "GL/xmesa.h"
static int fullscreen = 1;
#endif

static int WIDTH = 640;
static int HEIGHT = 480;

static GLint T0 = 0;
static GLint Frames = 0;

#define BASESIZE 7.5f
#define SPHERE_RADIUS 0.75f

#define TEX_CHECK_WIDTH 256
#define TEX_CHECK_HEIGHT 256
#define TEX_CHECK_SLOT_SIZE (TEX_CHECK_HEIGHT/16)
#define TEX_CHECK_NUMSLOT (TEX_CHECK_HEIGHT/TEX_CHECK_SLOT_SIZE)

#define TEX_REFLECT_WIDTH 256
#define TEX_REFLECT_HEIGHT 256
#define TEX_REFLECT_SLOT_SIZE (TEX_REFLECT_HEIGHT/16)
#define TEX_REFLECT_NUMSLOT (TEX_REFLECT_HEIGHT/TEX_REFLECT_SLOT_SIZE)

#ifndef M_PI
#define M_PI 3.1415926535
#endif

#define EPSILON 0.0001

#define clamp255(a)  ( (a)<(0.0f) ? (0.0f) : ((a)>(255.0f) ? (255.0f) : (a)) )

#ifndef fabs
#define fabs(x) ((x)<0.0f?-(x):(x))
#endif

#define vequ(a,b) { (a)[0]=(b)[0]; (a)[1]=(b)[1]; (a)[2]=(b)[2]; }
#define vsub(a,b,c) { (a)[0]=(b)[0]-(c)[0]; (a)[1]=(b)[1]-(c)[1]; (a)[2]=(b)[2]-(c)[2]; }
#define	dprod(a,b) ((a)[0]*(b)[0]+(a)[1]*(b)[1]+(a)[2]*(b)[2])
#define vnormalize(a,b) { \
  register float m_norm; \
  m_norm=sqrt((double)dprod((a),(a))); \
  (a)[0] /=m_norm; \
  (a)[1] /=m_norm; \
  (a)[2] /=m_norm; }

static GLubyte checkmap[TEX_CHECK_HEIGHT][TEX_CHECK_WIDTH][3];
static GLuint checkid;
static int checkmap_currentslot = 0;

static GLubyte reflectmap[TEX_REFLECT_HEIGHT][TEX_REFLECT_WIDTH][3];
static GLuint reflectid;
static int reflectmap_currentslot = 0;

static GLuint lightdlist;
static GLuint objdlist;

static float lightpos[3] = { 2.1, 2.1, 2.8 };
static float objpos[3] = { 0.0, 0.0, 1.0 };

static float sphere_pos[TEX_CHECK_HEIGHT][TEX_REFLECT_WIDTH][3];

static int win = 0;

static float fogcolor[4] = { 0.05, 0.05, 0.05, 1.0 };

static float obs[3] = { 7.0, 0.0, 2.0 };
static float dir[3];
static float v = 0.0;
static float alpha = -90.0;
static float beta = 90.0;

static int fog = 1;
static int bfcull = 1;
static int poutline = 0;
static int help = 1;
static int showcheckmap = 1;
static int showreflectmap = 1;
static int joyavailable = 0;
static int joyactive = 0;

static void
calcposobs(void)
{
   dir[0] = sin(alpha * M_PI / 180.0);
   dir[1] = cos(alpha * M_PI / 180.0) * sin(beta * M_PI / 180.0);
   dir[2] = cos(beta * M_PI / 180.0);

   if (dir[0] < 1.0e-5 && dir[0] > -1.0e-5)
      dir[0] = 0;
   if (dir[1] < 1.0e-5 && dir[1] > -1.0e-5)
      dir[1] = 0;
   if (dir[2] < 1.0e-5 && dir[2] > -1.0e-5)
      dir[2] = 0;

   obs[0] += v * dir[0];
   obs[1] += v * dir[1];
   obs[2] += v * dir[2];
}

static void
special(int k, int x, int y)
{
   switch (k) {
   case GLUT_KEY_LEFT:
      alpha -= 2.0;
      break;
   case GLUT_KEY_RIGHT:
      alpha += 2.0;
      break;
   case GLUT_KEY_DOWN:
      beta -= 2.0;
      break;
   case GLUT_KEY_UP:
      beta += 2.0;
      break;
   }
}

static void
key(unsigned char k, int x, int y)
{
   switch (k) {
   case 27:
      exit(0);
      break;

   case 's':
      lightpos[1] -= 0.1;
      break;
   case 'd':
      lightpos[1] += 0.1;
      break;
   case 'e':
      lightpos[0] -= 0.1;
      break;
   case 'x':
      lightpos[0] += 0.1;
      break;
   case 'w':
      lightpos[2] -= 0.1;
      break;
   case 'r':
      lightpos[2] += 0.1;
      break;

   case 'j':
      objpos[1] -= 0.1;
      break;
   case 'k':
      objpos[1] += 0.1;
      break;
   case 'i':
      objpos[0] -= 0.1;
      break;
   case 'm':
      objpos[0] += 0.1;
      break;
   case 'u':
      objpos[2] -= 0.1;
      break;
   case 'o':
      objpos[2] += 0.1;
      break;

   case 'a':
      v += 0.005;
      break;
   case 'z':
      v -= 0.005;
      break;

   case 'g':
      joyactive = (!joyactive);
      break;
   case 'h':
      help = (!help);
      break;
   case 'f':
      fog = (!fog);
      break;

   case '1':
      showcheckmap = (!showcheckmap);
      break;
   case '2':
      showreflectmap = (!showreflectmap);
      break;

   case 'b':
      if (bfcull) {
	 glDisable(GL_CULL_FACE);
	 bfcull = 0;
      }
      else {
	 glEnable(GL_CULL_FACE);
	 bfcull = 1;
      }
      break;
   case 'p':
      if (poutline) {
	 glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
	 poutline = 0;
      }
      else {
	 glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
	 poutline = 1;
      }
      break;
#ifdef XMESA
   case ' ':
      XMesaSetFXmode(fullscreen ? XMESA_FX_FULLSCREEN : XMESA_FX_WINDOW);
      fullscreen = (!fullscreen);
      break;
#endif
   }
}

static void
reshape(int w, int h)
{
   WIDTH = w;
   HEIGHT = h;
   glViewport(0, 0, w, h);
   glMatrixMode(GL_PROJECTION);
   glLoadIdentity();
   gluPerspective(45.0, w / (float) h, 0.8, 40.0);
   glMatrixMode(GL_MODELVIEW);
   glLoadIdentity();
}

static void
printstring(void *font, char *string)
{
   int len, i;

   len = (int) strlen(string);
   for (i = 0; i < len; i++)
      glutBitmapCharacter(font, string[i]);
}

static void
printhelp(void)
{
   glEnable(GL_BLEND);
   glColor4f(0.5, 0.5, 0.5, 0.5);
   glRecti(40, 40, 600, 440);
   glDisable(GL_BLEND);

   glColor3f(0.0, 0.0, 1.0);
   glRasterPos2i(300, 420);
   printstring(GLUT_BITMAP_HELVETICA_18, "Help");

   glRasterPos2i(60, 390);
   printstring(GLUT_BITMAP_HELVETICA_12, "h - Toggle Help");
   glRasterPos2i(60, 370);
   printstring(GLUT_BITMAP_HELVETICA_12, "f - Toggle Fog");
   glRasterPos2i(60, 350);
   printstring(GLUT_BITMAP_HELVETICA_12, "b - Toggle Back face culling");
   glRasterPos2i(60, 330);
   printstring(GLUT_BITMAP_HELVETICA_12, "p - Toggle Wire frame");
   glRasterPos2i(60, 310);
   printstring(GLUT_BITMAP_HELVETICA_12, "Arrow Keys - Rotate");
   glRasterPos2i(60, 290);
   printstring(GLUT_BITMAP_HELVETICA_12, "a - Increase velocity");
   glRasterPos2i(60, 270);
   printstring(GLUT_BITMAP_HELVETICA_12, "z - Decrease velocity");

   glRasterPos2i(60, 250);
   if (joyavailable)
      printstring(GLUT_BITMAP_HELVETICA_12,
		  "j - Toggle jostick control (Joystick control available)");
   else
      printstring(GLUT_BITMAP_HELVETICA_12,
		  "(No Joystick control available)");

   glRasterPos2i(60, 230);
   printstring(GLUT_BITMAP_HELVETICA_12,
	       "To move the light source: s - left,  d - right,  e - far,  x - near,  w - down r - up");
   glRasterPos2i(60, 210);
   printstring(GLUT_BITMAP_HELVETICA_12,
	       "To move the mirror sphere: j - left,  k - right,  i - far,  m - near,  u - down o - up");

   glRasterPos2i(60, 190);
   printstring(GLUT_BITMAP_HELVETICA_12,
	       "1 - Toggle the plane texture map window");

   glRasterPos2i(60, 170);
   printstring(GLUT_BITMAP_HELVETICA_12,
	       "2 - Toggle the sphere texture map window");
}

static GLboolean
seelight(float p[3], float dir[3])
{
   float c[3], b, a, d, t, dist[3];

   vsub(c, p, objpos);
   b = -dprod(c, dir);
   a = dprod(c, c) - SPHERE_RADIUS * SPHERE_RADIUS;

   if ((d = b * b - a) < 0.0 || (b < 0.0 && a > 0.0))
      return GL_FALSE;

   d = sqrt(d);

   t = b - d;

   if (t < EPSILON) {
      t = b + d;
      if (t < EPSILON)
	 return GL_FALSE;
   }

   vsub(dist, lightpos, p);
   if (dprod(dist, dist) < t * t)
      return GL_FALSE;

   return GL_TRUE;
}

static int
colorcheckmap(float ppos[3], float c[3])
{
   static float norm[3] = { 0.0f, 0.0f, 1.0f };
   float ldir[3], vdir[3], h[3], dfact, kfact, r, g, b;
   int x, y;

   x = (int) ((ppos[0] + BASESIZE / 2) * (10.0f / BASESIZE));
   if ((x < 0) || (x > 10))
      return GL_FALSE;

   y = (int) ((ppos[1] + BASESIZE / 2) * (10.0f / BASESIZE));
   if ((y < 0) || (y > 10))
      return GL_FALSE;

   r = 255.0f;
   if (y & 1) {
      if (x & 1)
	 g = 255.0f;
      else
	 g = 0.0f;
   }
   else {
      if (x & 1)
	 g = 0.0f;
      else
	 g = 255.0f;
   }
   b = 0.0f;

   vsub(ldir, lightpos, ppos);
   vnormalize(ldir, ldir);

   if (seelight(ppos, ldir)) {
      c[0] = r * 0.05f;
      c[1] = g * 0.05f;
      c[2] = b * 0.05f;

      return GL_TRUE;
   }

   dfact = dprod(ldir, norm);
   if (dfact < 0.0f)
      dfact = 0.0f;

   vsub(vdir, obs, ppos);
   vnormalize(vdir, vdir);
   h[0] = 0.5f * (vdir[0] + ldir[0]);
   h[1] = 0.5f * (vdir[1] + ldir[1]);
   h[2] = 0.5f * (vdir[2] + ldir[2]);
   kfact = dprod(h, norm);
   kfact = pow(kfact, 6.0) * 7.0 * 255.0;

   r = r * dfact + kfact;
   g = g * dfact + kfact;
   b = b * dfact + kfact;

   c[0] = clamp255(r);
   c[1] = clamp255(g);
   c[2] = clamp255(b);

   return GL_TRUE;
}

static void
updatecheckmap(int slot)
{
   float c[3], ppos[3];
   int x, y;

   glBindTexture(GL_TEXTURE_2D, checkid);

   ppos[2] = 0.0f;
   for (y = slot * TEX_CHECK_SLOT_SIZE; y < (slot + 1) * TEX_CHECK_SLOT_SIZE;
	y++) {
      ppos[1] = (y / (float) TEX_CHECK_HEIGHT) * BASESIZE - BASESIZE / 2;

      for (x = 0; x < TEX_CHECK_WIDTH; x++) {
	 ppos[0] = (x / (float) TEX_CHECK_WIDTH) * BASESIZE - BASESIZE / 2;

	 colorcheckmap(ppos, c);
	 checkmap[y][x][0] = (GLubyte) c[0];
	 checkmap[y][x][1] = (GLubyte) c[1];
	 checkmap[y][x][2] = (GLubyte) c[2];
      }
   }

   glTexSubImage2D(GL_TEXTURE_2D, 0, 0, slot * TEX_CHECK_SLOT_SIZE,
		   TEX_CHECK_WIDTH, TEX_CHECK_SLOT_SIZE, GL_RGB,
		   GL_UNSIGNED_BYTE,
		   &checkmap[slot * TEX_CHECK_SLOT_SIZE][0][0]);

}

static void
updatereflectmap(int slot)
{
   float rf, r, g, b, t, dfact, kfact, rdir[3];
   float rcol[3], ppos[3], norm[3], ldir[3], h[3], vdir[3], planepos[3];
   int x, y;

   glBindTexture(GL_TEXTURE_2D, reflectid);

   for (y = slot * TEX_REFLECT_SLOT_SIZE;
	y < (slot + 1) * TEX_REFLECT_SLOT_SIZE; y++)
      for (x = 0; x < TEX_REFLECT_WIDTH; x++) {
	 ppos[0] = sphere_pos[y][x][0] + objpos[0];
	 ppos[1] = sphere_pos[y][x][1] + objpos[1];
	 ppos[2] = sphere_pos[y][x][2] + objpos[2];

	 vsub(norm, ppos, objpos);
	 vnormalize(norm, norm);

	 vsub(ldir, lightpos, ppos);
	 vnormalize(ldir, ldir);
	 vsub(vdir, obs, ppos);
	 vnormalize(vdir, vdir);

	 rf = 2.0f * dprod(norm, vdir);
	 if (rf > EPSILON) {
	    rdir[0] = rf * norm[0] - vdir[0];
	    rdir[1] = rf * norm[1] - vdir[1];
	    rdir[2] = rf * norm[2] - vdir[2];

	    t = -objpos[2] / rdir[2];

	    if (t > EPSILON) {
	       planepos[0] = objpos[0] + t * rdir[0];
	       planepos[1] = objpos[1] + t * rdir[1];
	       planepos[2] = 0.0f;

	       if (!colorcheckmap(planepos, rcol))
		  rcol[0] = rcol[1] = rcol[2] = 0.0f;
	    }
	    else
	       rcol[0] = rcol[1] = rcol[2] = 0.0f;
	 }
	 else
	    rcol[0] = rcol[1] = rcol[2] = 0.0f;

	 dfact = 0.1f * dprod(ldir, norm);

	 if (dfact < 0.0f) {
	    dfact = 0.0f;
	    kfact = 0.0f;
	 }
	 else {
	    h[0] = 0.5f * (vdir[0] + ldir[0]);
	    h[1] = 0.5f * (vdir[1] + ldir[1]);
	    h[2] = 0.5f * (vdir[2] + ldir[2]);
	    kfact = dprod(h, norm);
            kfact = pow(kfact, 4.0);
            if (kfact < 1.0e-10)
               kfact = 0.0;
         }

	 r = dfact + kfact;
	 g = dfact + kfact;
	 b = dfact + kfact;

	 r *= 255.0f;
	 g *= 255.0f;
	 b *= 255.0f;

	 r += rcol[0];
	 g += rcol[1];
	 b += rcol[2];

	 r = clamp255(r);
	 g = clamp255(g);
	 b = clamp255(b);

	 reflectmap[y][x][0] = (GLubyte) r;
	 reflectmap[y][x][1] = (GLubyte) g;
	 reflectmap[y][x][2] = (GLubyte) b;
      }

   glTexSubImage2D(GL_TEXTURE_2D, 0, 0, slot * TEX_REFLECT_SLOT_SIZE,
		   TEX_REFLECT_WIDTH, TEX_REFLECT_SLOT_SIZE, GL_RGB,
		   GL_UNSIGNED_BYTE,
		   &reflectmap[slot * TEX_REFLECT_SLOT_SIZE][0][0]);
}

static void
drawbase(void)
{
   glColor3f(0.0, 0.0, 0.0);
   glBindTexture(GL_TEXTURE_2D, checkid);
   glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);

   glBegin(GL_QUADS);
   glTexCoord2f(0.0f, 0.0f);
   glVertex3f(-BASESIZE / 2.0f, -BASESIZE / 2.0f, 0.0f);

   glTexCoord2f(1.0f, 0.0f);
   glVertex3f(BASESIZE / 2.0f, -BASESIZE / 2.0f, 0.0f);

   glTexCoord2f(1.0f, 1.0f);
   glVertex3f(BASESIZE / 2.0f, BASESIZE / 2.0f, 0.0f);

   glTexCoord2f(0.0f, 1.0f);
   glVertex3f(-BASESIZE / 2.0f, BASESIZE / 2.0f, 0.0f);

   glEnd();
}

static void
drawobj(void)
{
   glColor3f(0.0, 0.0, 0.0);
   glBindTexture(GL_TEXTURE_2D, reflectid);
   glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);

   glPushMatrix();
   glTranslatef(objpos[0], objpos[1], objpos[2]);
   glCallList(objdlist);
   glPopMatrix();
}

static void
dojoy(void)
{
#ifdef WIN32
   static UINT max[2] = { 0, 0 };
   static UINT min[2] = { 0xffffffff, 0xffffffff }, center[2];
   MMRESULT res;
   JOYINFO joy;

   res = joyGetPos(JOYSTICKID1, &joy);

   if (res == JOYERR_NOERROR) {
      joyavailable = 1;

      if (max[0] < joy.wXpos)
	 max[0] = joy.wXpos;
      if (min[0] > joy.wXpos)
	 min[0] = joy.wXpos;
      center[0] = (max[0] + min[0]) / 2;

      if (max[1] < joy.wYpos)
	 max[1] = joy.wYpos;
      if (min[1] > joy.wYpos)
	 min[1] = joy.wYpos;
      center[1] = (max[1] + min[1]) / 2;

      if (joyactive) {
	 if (fabs(center[0] - (float) joy.wXpos) > 0.1 * (max[0] - min[0]))
	    alpha -=
	       2.5 * (center[0] - (float) joy.wXpos) / (max[0] - min[0]);
	 if (fabs(center[1] - (float) joy.wYpos) > 0.1 * (max[1] - min[1]))
	    beta += 2.5 * (center[1] - (float) joy.wYpos) / (max[1] - min[1]);

	 if (joy.wButtons & JOY_BUTTON1)
	    v += 0.005;
	 if (joy.wButtons & JOY_BUTTON2)
	    v -= 0.005;
      }
   }
   else
      joyavailable = 0;
#endif
}

static void
updatemaps(void)
{
   updatecheckmap(checkmap_currentslot);
   checkmap_currentslot = (checkmap_currentslot + 1) % TEX_CHECK_NUMSLOT;

   updatereflectmap(reflectmap_currentslot);
   reflectmap_currentslot =
      (reflectmap_currentslot + 1) % TEX_REFLECT_NUMSLOT;
}

static void
draw(void)
{
   static char frbuf[80] = "";

   dojoy();

   glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

   glEnable(GL_TEXTURE_2D);
   glEnable(GL_DEPTH_TEST);
   if (fog)
      glEnable(GL_FOG);
   else
      glDisable(GL_FOG);

   glPushMatrix();
   calcposobs();

   gluLookAt(obs[0], obs[1], obs[2],
	     obs[0] + dir[0], obs[1] + dir[1], obs[2] + dir[2],
	     0.0, 0.0, 1.0);

   drawbase();
   drawobj();

   glColor3f(1.0, 1.0, 1.0);
   glDisable(GL_TEXTURE_2D);

   glPushMatrix();
   glTranslatef(lightpos[0], lightpos[1], lightpos[2]);
   glCallList(lightdlist);
   glPopMatrix();

   glPopMatrix();

   glDisable(GL_DEPTH_TEST);
   glDisable(GL_FOG);

   glMatrixMode(GL_PROJECTION);
   glPushMatrix();
   glLoadIdentity();
   glOrtho(-0.5, 639.5, -0.5, 479.5, -1.0, 1.0);
   glMatrixMode(GL_MODELVIEW);

   glColor3f(0.0f, 0.3f, 1.0f);

   if (showcheckmap) {
      glEnable(GL_TEXTURE_2D);
      glBindTexture(GL_TEXTURE_2D, checkid);
      glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);

      glBegin(GL_QUADS);
      glTexCoord2f(1.0f, 0.0f);
      glVertex2i(10, 30);
      glTexCoord2f(1.0f, 1.0f);
      glVertex2i(10 + 90, 30);
      glTexCoord2f(0.0f, 1.0f);
      glVertex2i(10 + 90, 30 + 90);
      glTexCoord2f(0.0f, 0.0f);
      glVertex2i(10, 30 + 90);
      glEnd();

      glDisable(GL_TEXTURE_2D);
      glBegin(GL_LINE_LOOP);
      glVertex2i(10, 30);
      glVertex2i(10 + 90, 30);
      glVertex2i(10 + 90, 30 + 90);
      glVertex2i(10, 30 + 90);
      glEnd();
      glRasterPos2i(105, 65);
      printstring(GLUT_BITMAP_HELVETICA_18, "Plane Texture Map");
   }

   if (showreflectmap) {
      glEnable(GL_TEXTURE_2D);
      glBindTexture(GL_TEXTURE_2D, reflectid);
      glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);

      glBegin(GL_QUADS);
      glTexCoord2f(1.0f, 0.0f);
      glVertex2i(540, 30);
      glTexCoord2f(1.0f, 1.0f);
      glVertex2i(540 + 90, 30);
      glTexCoord2f(0.0f, 1.0f);
      glVertex2i(540 + 90, 30 + 90);
      glTexCoord2f(0.0f, 0.0f);
      glVertex2i(540, 30 + 90);
      glEnd();

      glDisable(GL_TEXTURE_2D);
      glBegin(GL_LINE_LOOP);
      glVertex2i(540, 30);
      glVertex2i(540 + 90, 30);
      glVertex2i(540 + 90, 30 + 90);
      glVertex2i(540, 30 + 90);
      glEnd();
      glRasterPos2i(360, 65);
      printstring(GLUT_BITMAP_HELVETICA_18, "Sphere Texture Map");
   }

   glDisable(GL_TEXTURE_2D);

   glRasterPos2i(10, 10);
   printstring(GLUT_BITMAP_HELVETICA_18, frbuf);
   glRasterPos2i(360, 470);
   printstring(GLUT_BITMAP_HELVETICA_10,
	       "Ray V1.0 Written by David Bucciarelli (tech.hmw@plus.it)");

   if (help)
      printhelp();

   glMatrixMode(GL_PROJECTION);
   glPopMatrix();
   glMatrixMode(GL_MODELVIEW);

   updatemaps();

   glutSwapBuffers();

   Frames++;
   {
      GLint t = glutGet(GLUT_ELAPSED_TIME);
      if (t - T0 >= 2000) {
         GLfloat seconds = (t - T0) / 1000.0;
         GLfloat fps = Frames / seconds;
         sprintf(frbuf, "Frame rate: %f", fps);
         T0 = t;
         Frames = 0;
      }
   }
}

static void
inittextures(void)
{
   int y;

   glGenTextures(1, &checkid);
   glBindTexture(GL_TEXTURE_2D, checkid);

   glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
   glTexImage2D(GL_TEXTURE_2D, 0, 3, TEX_CHECK_WIDTH, TEX_CHECK_HEIGHT,
		0, GL_RGB, GL_UNSIGNED_BYTE, checkmap);

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

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

   for (y = 0; y < TEX_CHECK_NUMSLOT; y++)
      updatecheckmap(y);



   glGenTextures(1, &reflectid);
   glBindTexture(GL_TEXTURE_2D, reflectid);

   glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
   glTexImage2D(GL_TEXTURE_2D, 0, 3, TEX_REFLECT_WIDTH, TEX_REFLECT_HEIGHT,
		0, GL_RGB, GL_UNSIGNED_BYTE, reflectmap);

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

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

   for (y = 0; y < TEX_REFLECT_NUMSLOT; y++)
      updatereflectmap(y);


}

static void
initspherepos(void)
{
   float alpha, beta, sa, ca, sb, cb;
   int x, y;

   for (y = 0; y < TEX_REFLECT_HEIGHT; y++) {
      beta = M_PI - y * (M_PI / TEX_REFLECT_HEIGHT);

      for (x = 0; x < TEX_REFLECT_WIDTH; x++) {
	 alpha = -x * (2.0f * M_PI / TEX_REFLECT_WIDTH);

	 sa = sin(alpha);
	 ca = cos(alpha);

	 sb = sin(beta);
	 cb = cos(beta);

	 sphere_pos[y][x][0] = SPHERE_RADIUS * sa * sb;
	 sphere_pos[y][x][1] = SPHERE_RADIUS * ca * sb;
	 sphere_pos[y][x][2] = SPHERE_RADIUS * cb;
      }
   }
}

static void
initdlists(void)
{
   GLUquadricObj *obj;

   obj = gluNewQuadric();

   lightdlist = glGenLists(1);
   glNewList(lightdlist, GL_COMPILE);
   gluQuadricDrawStyle(obj, GLU_FILL);
   gluQuadricNormals(obj, GLU_NONE);
   gluQuadricTexture(obj, GL_TRUE);
   gluSphere(obj, 0.25f, 6, 6);
   glEndList();

   objdlist = glGenLists(1);
   glNewList(objdlist, GL_COMPILE);
   gluQuadricDrawStyle(obj, GLU_FILL);
   gluQuadricNormals(obj, GLU_NONE);
   gluQuadricTexture(obj, GL_TRUE);
   gluSphere(obj, SPHERE_RADIUS, 16, 16);
   glEndList();
}

int
main(int ac, char **av)
{
   fprintf(stderr,
	   "Ray V1.0\nWritten by David Bucciarelli (tech.hmw@plus.it)\n");

   /*
      if(!SetPriorityClass(GetCurrentProcess(),REALTIME_PRIORITY_CLASS)) {
      fprintf(stderr,"Error setting the process class.\n");
      return 0;
      }

      if(!SetThreadPriority(GetCurrentThread(),THREAD_PRIORITY_TIME_CRITICAL)) {
      fprintf(stderr,"Error setting the process priority.\n");
      return 0;
      }
    */

   glutInitWindowPosition(0, 0);
   glutInitWindowSize(WIDTH, HEIGHT);
   glutInit(&ac, av);

   glutInitDisplayMode(GLUT_RGB | GLUT_DEPTH | GLUT_DOUBLE);

   if (!(win = glutCreateWindow("Ray"))) {
      fprintf(stderr, "Error, couldn't open window\n");
      return -1;
   }

   reshape(WIDTH, HEIGHT);

   glShadeModel(GL_FLAT);
   glEnable(GL_DEPTH_TEST);
   glDepthFunc(GL_LEQUAL);
   glEnable(GL_CULL_FACE);
   glEnable(GL_TEXTURE_2D);
   glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

   glEnable(GL_FOG);
   glFogi(GL_FOG_MODE, GL_EXP2);
   glFogfv(GL_FOG_COLOR, fogcolor);

   glFogf(GL_FOG_DENSITY, 0.01);
#ifdef FX
   glHint(GL_FOG_HINT, GL_NICEST);
#endif

   calcposobs();

   initspherepos();

   inittextures();
   initdlists();

   glClearColor(fogcolor[0], fogcolor[1], fogcolor[2], fogcolor[3]);

   glutReshapeFunc(reshape);
   glutDisplayFunc(draw);
   glutKeyboardFunc(key);
   glutSpecialFunc(special);
   glutIdleFunc(draw);

   glutMainLoop();

   return 0;
}
