// Example planar shadows with stencil buffer
//
// Author: Mel

#include <GL/glut.h>
#include <math.h>
#include <stdio.h>

struct Pos
{
	float x,y,z;
};

struct Light
{
	Pos pos;
};

// pretend we have a light defined with 3 floats
Light g_Light0 = { 0.0f, 10.0f, -5.0f};
int		g_nWidth, g_nHeight;
float	g_fRot=0.0f;	// simple user rotation

// matrix to store our calculations
// last column stores -A/E -B/E -C/E
// where E = D + Ax + By + Cz
float M[] = 
{ 1, 0, 0, 0,
  0, 1, 0, 0,
  0, 0, 1, 0,
  0, 0, 0, 0};

// Draws a cube with different colors for each face
void drawCube()
{
	glBegin(GL_QUADS);
	glColor3f(1,0,0);
	glVertex3f(-1,-1,1);	glVertex3f(1,-1,1);
	glVertex3f(1,1,1);		glVertex3f(-1,1,1);

	glColor3f(0,1,0);
	glVertex3f(1,-1,1);		glVertex3f(1,-1,-1);
	glVertex3f(1,1,-1);		glVertex3f(1,1,1);

	glColor3f(0,0,1);
	glVertex3f(1,-1,-1);	glVertex3f(1,1,-1);
	glVertex3f(-1,1,-1);	glVertex3f(-1,-1,-1);

	glColor3f(1,1,0);
	glVertex3f(-1,-1,1);	glVertex3f(-1,1,1);
	glVertex3f(-1,1,-1);	glVertex3f(-1,-1,-1);

	glColor3f(1,0,1);
	glVertex3f(-1,1,1);		glVertex3f(1,1,1);
	glVertex3f(1,1,-1);		glVertex3f(-1,1,-1);

	glColor3f(0,1,1);
	glVertex3f(-1,-1,1);	glVertex3f(1,-1,1);
	glVertex3f(1,-1,-1);	glVertex3f(-1,-1,-1);
	glEnd();
}

// the display
void display()
{
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);

	glLoadIdentity();
	glRotatef(g_fRot, 1,0,0);		// perform user transformations

	// draw the plane where our shadow will be located at y=-5,
	glDisable(GL_DEPTH_TEST);	// don't write to depth buffer to avoid conflicts with shadow
	glBegin(GL_QUADS);
	glColor3f(1,1,1);
	glVertex3f(-20,-5, 20);	glVertex3f(20, -5, 20);
	glVertex3f(20,-5, -20);	glVertex3f(-20,-5, -20);
	glEnd();
	glEnable(GL_DEPTH_TEST);	// re-enable depth test

	/////////////////////////
	// Draw the shadow first
	/////////////////////////
	// Idea is to create a stencil showing where
	// the shadow should be drawn.  Then use the stencil
	// to just fill in the shadow color

	/////////
	// Step 1
	/////////
	glEnable(GL_STENCIL_TEST);
	glDisable(GL_DEPTH_TEST);	// don't make the shadow draw into the depth buffer
	glStencilFunc(GL_ALWAYS, 1,1);  // write a 1 where the shadow will appear
	glStencilOp(GL_REPLACE, GL_REPLACE, GL_REPLACE); // replace the values in the stencil buffer
	
	// setup matrix M
	Pos p = g_Light0.pos;
	M[7] = (p.y== 0.0) ? 0 : -1/(p.y+5);  // our plane is at -5

	// Transformations for shadow
	glPushMatrix();
	glTranslatef(p.x,p.y,p.z);
	glMultMatrixf(M);
	glTranslatef(-p.x, -p.y, -p.z);
		
	// Write to the stencil buffer so we know where our
	// shadow is
	drawCube();
	glPopMatrix();

	//////////
	// Step 2
	//////////

	// Now use the stencil buffer information
	glStencilFunc(GL_EQUAL, 0x1, 0x1);	// only draw when stencil is equal to 0x1
	
	// draw a big quad in our scene to color
	// where the shadows appear
	glPushMatrix();
	glLoadIdentity();
	glColor3f(.2, .2, .2);	// shadow color

	glBegin(GL_QUADS);  // a primitive that covers the extent of all shadow areas
	glVertex3f(-10,-10,4);	glVertex3f(10,-10,4);
	glVertex3f(10,10,4);	glVertex3f(-10,10,4);
	glEnd();
	glPopMatrix();
	
	glDisable(GL_STENCIL_TEST); // done with stencil
	glEnable(GL_DEPTH_TEST);	// re-enable depth test
	
	/////////
	// Step 3
	/////////

	// now draw the scene
	drawCube();

	glutSwapBuffers();
}

void idle()
{
	// spin the hypothetical light
	static float rot = 0.0f;
	rot += 0.01f;
	g_Light0.pos.x = g_Light0.pos.y * cos(rot);
	g_Light0.pos.z = g_Light0.pos.y * sin(rot);
	glutPostRedisplay();
}

void specialKeys(int key, int x, int y)
{
	switch(key)
	{
	case GLUT_KEY_UP:
		g_fRot += 2.0f;
		break;

	case GLUT_KEY_DOWN:
		g_fRot += -2.0f;
		break;
	}

}

void reshape(int w, int h)
{
	g_nWidth = w;
	g_nHeight= h;
	glViewport(0,0,w,h);
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	gluPerspective(60,(float)w/h, .1, 100);
	gluLookAt(0,0,10 ,0,0,0,0,1,0);
	glMatrixMode(GL_MODELVIEW);
}

void main()
{
	glutInitWindowSize(500,500);
	glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE | GLUT_DEPTH);
	glutCreateWindow("Shadow Test");
	glutDisplayFunc(display);
	glutReshapeFunc(reshape);
	glutSpecialFunc(specialKeys);
	glutIdleFunc(idle);

	glClearColor(0,0,0,0);
	glEnable(GL_DEPTH_TEST);
	glutMainLoop();
}