/*
  gcc -Wall -lglut -lGLU -lGL -lXm -lXi -lX11 unproject.c -o unproject
*/

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <GL/glut.h>

void Idle();
void Display();
void Reshape(int w,int h);
void Mouse(int button,int state,int x,int y);
void Motion(int x, int y);
void MotionPassive(int x, int y);
void Keyboard(unsigned char key, int x, int y);
void Unproject(int x, int y,
			   GLdouble *x3d_ret, GLdouble *y3d_ret, GLdouble *z3d_ret);

int mx=0, my=0;						/* mouse x, y */
GLdouble ox=0.0,oy=0.0,oz=0.0;
GLdouble rot=0.0;
int rotating=1;
int texgen_mode = 0;

int main(int argc,char **argv) {
  glutInit(&argc,argv);
  glutInitDisplayString("rgba red>=4 green>=4 blue>=4 depth>=16 double=1");
  glutInitWindowSize(500,500);
  glutInitWindowPosition(100,100);
  glutCreateWindow(argv[0]);
  glutDisplayFunc(Display);
  glutReshapeFunc(Reshape);
  glutMouseFunc(Mouse);
  glutPassiveMotionFunc(MotionPassive);
  glutMotionFunc(Motion);
  glutKeyboardFunc(Keyboard);
  glutIdleFunc(Idle);

  glEnable(GL_LIGHTING);
  glEnable(GL_LIGHT0);
  glEnable(GL_DEPTH_TEST);

  glTexParameterf(GL_TEXTURE_3D, GL_TEXTURE_WRAP_S, GL_REPEAT);
  glTexParameterf(GL_TEXTURE_3D, GL_TEXTURE_WRAP_T, GL_REPEAT);
  glTexParameterf(GL_TEXTURE_3D, GL_TEXTURE_WRAP_R, GL_REPEAT);
  glTexParameterf(GL_TEXTURE_3D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
  glTexParameterf(GL_TEXTURE_3D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
  {
	  enum { max = 63 };			/* must be the set of integers (2^n - 1) */
	  static char tex[max+1][max+1][max+1][3];
	  int x, y, z;
	  for(        x = 0 ; x <= max ; ++x)
		  for(    y = 0 ; y <= max ; ++y)
			  for(z = 0 ; z <= max ; ++z)
			  {
				  tex[x][y][z][0] = (((0 < x)&&(x < max)) ? 0x80 : 0xff);
				  tex[x][y][z][1] = (((0 < y)&&(y < max)) ? 0x80 : 0xff);
				  tex[x][y][z][2] = (((0 < z)&&(z < max)) ? 0x80 : 0xff);
			  }
	  glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
	  glTexImage3D(GL_TEXTURE_3D,			/* target, */
				   0, 3,					/* level, internalformat*/
				   max+1, max+1, max+1,		/* sizes*/
				   0, GL_RGB,				/* border, format */
				   GL_UNSIGNED_BYTE, &tex[0][0][0]);
  }         
  glutMainLoop();
  return 0;
}

void Idle() {
  GLdouble x3, y3, z3;
  if(rotating) {
	  rot += 1; if(rot > 360) rot = 0.0;
  }
  Unproject(mx, my, &x3, &y3, &z3);
  printf("coörds: %lf, %lf, %lf\n", x3, y3, z3);
  glutPostRedisplay();
  usleep(5000);
}

void Display() {
  glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
  glLoadIdentity();
  gluLookAt(20, 20, 20,		/* eye coörds */
			0,  0,  0,		/* center coörds */
			0,  1,  0);		/* up vector */

  GLint gen_objeye_pl; /* 3: GL_TEXTURE_GEN_MODE or GL_{OBJECT,EYE}_PLANE */
  GLint gen;           /* 3: GL_{OBJECT,EYE}_LINEAR or GL_SPHERE_MAP */

  switch(texgen_mode)
  {
  default: texgen_mode = 0;  	/* fallthrough */
  case 0: gen_objeye_pl = GL_TEXTURE_GEN_MODE; gen = GL_OBJECT_LINEAR; break;
  case 1: gen_objeye_pl = GL_TEXTURE_GEN_MODE; gen = GL_EYE_LINEAR;    break;
  case 2: gen_objeye_pl = GL_TEXTURE_GEN_MODE; gen = GL_SPHERE_MAP;    break;
  case 3: gen_objeye_pl = GL_OBJECT_PLANE;     gen = GL_NONE;          break;
  case 4: gen_objeye_pl = GL_EYE_PLANE;        gen = GL_NONE;          break;
  }

  glEnable(GL_NORMALIZE);

#if 1
  glPushMatrix();
  glScalef(10,10,10);
  glEnable(GL_COLOR_MATERIAL);
  glBegin(GL_QUADS);
  glColor3f(1,0,0);
  glVertex3f(0,-1,1);glVertex3f(0,-1,-1);glVertex3f(0,1,-1);glVertex3f(0,1,1);
  glColor3f(0,1,0);
  glVertex3f(1,0,1);glVertex3f(1,0,-1);glVertex3f(-1,0,-1);glVertex3f(-1,0,1); 
  glColor3f(0,0,1);
  glVertex3f(1,1,0);glVertex3f(1,-1,0);glVertex3f(-1,-1,0);glVertex3f(-1,1,0); 
  glEnd();
  glPopMatrix();
#endif
  glDisable(GL_COLOR_MATERIAL);

  glEnable(GL_TEXTURE_3D);
  glEnable(GL_TEXTURE_GEN_S);
  glEnable(GL_TEXTURE_GEN_T);
  glEnable(GL_TEXTURE_GEN_R);

  glPushMatrix();
  glTranslated(ox, oy, oz);
  glutSolidSphere(0.5, 5, 5);	/* radius, slices, stacks */
  glPopMatrix();

  glPushMatrix();
  glRotatef(rot, 1,0,0);			/* rotation around +x */
  glRotatef(1.5*rot, 0,1,0);		/* rotation around +y */

  glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL);

  glTexGeniv(GL_S,GL_TEXTURE_GEN_MODE,(GLint*)((GLint[1]){GL_EYE_LINEAR}));
  glTexGeniv(GL_T,GL_TEXTURE_GEN_MODE,(GLint*)((GLint[1]){GL_EYE_LINEAR}));
  glTexGeniv(GL_R,GL_TEXTURE_GEN_MODE,(GLint*)((GLint[1]){GL_EYE_LINEAR}));
  
  if(GL_TEXTURE_GEN_MODE == gen_objeye_pl)
  {
	  glTexGeniv( GL_S, gen_objeye_pl, (GLint*)((GLint[1]){gen}));
	  glTexGeniv( GL_T, gen_objeye_pl, (GLint*)((GLint[1]){gen}));
	  glTexGeniv( GL_R, gen_objeye_pl, (GLint*)((GLint[1]){gen}));
  }

 {
	  glTexGeniv( GL_S, gen_objeye_pl, (GLint*)((GLint[4]){1,0,0,0}));
	  glTexGeniv( GL_T, gen_objeye_pl, (GLint*)((GLint[4]){0,1,0,0}));
	  glTexGeniv( GL_R, gen_objeye_pl, (GLint*)((GLint[4]){0,0,1,0}));
  }

  glutSolidTorus(4,5,20,20); /* inner radius, outer radius, rings, r'facets */
  glClear(GL_DEPTH_BUFFER_BIT);
  glColorMask(GL_FALSE,GL_FALSE,GL_FALSE,GL_FALSE);
  glutSolidTorus(4,5,20,20); /* inner radius, outer radius, rings, r'facets */
  glColorMask(GL_TRUE,GL_TRUE,GL_TRUE,GL_TRUE);
  glPopMatrix();

  glDisable(GL_TEXTURE_3D);
  glDisable(GL_TEXTURE_GEN_S);
  glDisable(GL_TEXTURE_GEN_T);
  glDisable(GL_TEXTURE_GEN_R);

  glutSwapBuffers();
  glFlush();
}

void Reshape(int w,int h) {
  glViewport(0,0,w,h);
  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();
  gluPerspective(45.0,(float)w/(float)h,1.0,50.0);
  glMatrixMode(GL_MODELVIEW);
}

void Unproject(int x, int y,
			   GLdouble *x3d_ret, GLdouble *y3d_ret, GLdouble *z3d_ret)
{
  GLint viewport[4];
  GLdouble modelview[16],projection[16];
  GLfloat wx = x, wy, wz;
  mx = x, my = y;
  glGetIntegerv(GL_VIEWPORT,viewport);
  wy = y = viewport[3]-y;
  glGetDoublev(GL_MODELVIEW_MATRIX,modelview);
  glGetDoublev(GL_PROJECTION_MATRIX,projection);
  glReadPixels(x,y,1,1,GL_DEPTH_COMPONENT,GL_FLOAT,&wz);
  /* wx, wy, and wz are ready now */
  gluUnProject(wx,wy,wz,modelview,projection,viewport,
			   x3d_ret, y3d_ret, z3d_ret);
  printf("coörds: %f, %f, %f\n", *x3d_ret, *y3d_ret, *z3d_ret);
}

void Mouse(int button,int state,int x,int y) {
  mx = x, my = y;
  if(GLUT_DOWN == state)
	  switch(button)
	  {
	  case GLUT_LEFT_BUTTON:    Unproject(x, y, &ox, &oy, &oz); break;
	  case GLUT_MIDDLE_BUTTON:	rotating = ! rotating;          break;
	  case GLUT_RIGHT_BUTTON:   exit(0);                        break;
	  }
  glutPostRedisplay();
}

void Motion(int x, int y)
{
  mx = x, my = y;
  Unproject(x, y, &ox, &oy, &oz);
  printf("coörds: %lf, %lf, %lf\n", ox, oy, oz);
  glutPostRedisplay();
}

void MotionPassive(int x, int y)
{
  mx = x, my = y;
  GLdouble x3, y3, z3;
  Unproject(x, y, &x3, &y3, &z3);
  printf("coörds: %lf, %lf, %lf\n", x3, y3, z3);
  glutPostRedisplay();
}

void Keyboard(unsigned char key, int x, int y)
{
  mx = x, my = y;
  if(' ' == key)
	  ++texgen_mode;
  glutPostRedisplay();
}

