/*
Função 3D com TRIANGLE_STRIP
Normais calculadas ao triângulo

  ©2001 Filipe Pacheco
  ffp@dei.isep.ipp.pt

*/

#include <stdlib.h>
#include <stdio.h>
#include <glut.h>
#include <math.h>
#include <time.h>


/*
A nossa função matemática
*/
float calc_function1(int x, int y)
{
	return (sin(1.0*x/3)+cos(1.0*y/3))*.5;
}


/*
Definir normal a um triângulo
*/
void calc_normal(float x0, float y0, float z0, float x1, float y1, float z1, float x2, float y2, float z2)
{
	float x10=x1-x0;
	float y10=y1-y0;
	float z10=z1-z0;

	float x12=x1-x2;
	float y12=y1-y2;
	float z12=z1-z2;


	float cpx = (z10*y12) - (y10*z12);
	float cpy = (x10*z12) - (z10*x12);
	float cpz = (y10*x12) - (x10*y12);

	float r = sqrt(cpx*cpx + cpy*cpy + cpz*cpz);

	float nx = cpx/r;
	float ny = cpy/r;
	float nz = cpz/r;

	glNormal3f(nx,ny,nz);

}

/*
Desenha a função cf
que tem de ser definida como:

  float <nome>(int x, int y)

*/

void draw_function(float (*cf)(int x, int y))
{
//	double pi;
	float x0, y0, z0;
	float x1, y1, z1;
	float x2, y2, z2;
	int i, j;

	int nseg=20;

	glPushMatrix();

	
	for (i=-nseg;i<=nseg;i++)
	{
		glBegin(GL_TRIANGLE_STRIP);
			
		for (j=-nseg;j<=nseg;j++)
		{
			

			/*
			Para facilitar a compreensão usa-se
			x0,y0,z0,x1...

			Na realidade os valores de X e Y
			podem ser calculados facilmente 
			sem utilizar as variáveis 
			temporárias.
			*/
			
			x2=(j)*.1;
			y2=(i)*.1;
			z2=(*cf)(j,i);
			
			/*
			nos primeiro ciclo apenas calcula
			valores, os pontos respectivos
			serão desenhados no ciclo seguinte
			*/

			if (j>-nseg) 
			{
			calc_normal(x0, y0, z0, x1, y1, z1, x2, y2, z2);
			glVertex3f(x0, y0, z0);
			}


			x0=x1;
			y0=y1;
			z0=z1;
			x1=x2;
			y1=y2;
			z1=z2;
			

			x2=(j)*.1;
			y2=(i+1)*.1;
			z2=(*cf)(j,i+1);
			
			if (j>-nseg) 
			{
			calc_normal(x0, y0, z0, x2, y2, z2, x1, y1, z1);
			glVertex3f(x0, y0, z0);
			}

			x0=x1;
			y0=y1;
			z0=z1;
			x1=x2;
			y1=y2;
			z1=z2;
		}			
		glEnd();
	}

	glPopMatrix();

}

/*
Desenhar cilindo (extra)
*/
void draw_cilinder(float radiusb, float radiust, float height, int nseg)
{
	double pi;
	float x1, y1;
	int i;
	glPushMatrix();

	pi = 4*atan(1.0);
	x1 = cos(2*pi/nseg);
	y1 = sin(2*pi/nseg);

	for (i=1;i<=nseg;i++)
	{
		glBegin(GL_POLYGON);
			calc_normal(x1*radiust, y1*radiust, height, radiust,0,height, radiusb,0,0);
			glVertex3f(radiust, 0, height);
			glVertex3f(radiusb, 0, 0);
			calc_normal(x1*radiusb, y1*radiusb, 0, x1*radiust, y1*radiust, height, radiust,0,height);
			glVertex3f(x1*radiusb, y1*radiusb, 0);
			glVertex3f(x1*radiust, y1*radiust, height);
		glEnd();
		glRotatef(360/nseg,0.0,0.0,1.0);
	}

	glPopMatrix();

}

/*
Para rotação da cena
*/

static GLfloat theta[] = {0.0,0.0,0.0};
static GLint axis = 2;

/*
Materiais, Luzes
*/
static GLfloat mat_specular[]={0.5, 1.0, 1.0, 1.0};
static GLfloat mat_diffuse[]={1.0, 1.0, 0.0, 1.0};
static GLfloat mat_diffuse2[]={0.0, 1.0, 0.0, 1.0};
static GLfloat mat_diffuse_back[]={1.0, 0.0, 0.0, 1.0};
static GLfloat mat_ambient[]={1.0, 1.0, 1.0, 1.0};
static GLfloat mat_shininess={50.0};
static GLfloat light_ambient[]={0.0, 0.0, 0.0, 1.0};
static GLfloat light_diffuse[]={1.0, 1.0, 1.0, 1.0};
static GLfloat light_specular[]={1.0, 1.0, 1.0, 1.0};
static GLfloat light_position[]={2.0, 2.0, 2.0, 0.0};

   

/*
Desenhar Janela
*/
void display(void)
{


/*
Limpar buffer
*/
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    
/*
definir LIGHT0
*/

    glLightfv(GL_LIGHT0, GL_AMBIENT, light_ambient);
    glLightfv(GL_LIGHT0, GL_DIFFUSE, light_diffuse);
    glLightfv(GL_LIGHT0, GL_SPECULAR, light_specular);

/* 
definir propriedades dos materiais	
*/

    glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, mat_specular);
    glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT, mat_ambient);
    glMaterialf(GL_FRONT_AND_BACK, GL_SHININESS, mat_shininess);
	glMaterialfv(GL_FRONT, GL_DIFFUSE, mat_diffuse);
    glMaterialfv(GL_BACK, GL_DIFFUSE, mat_diffuse_back);

	glLightModeli(GL_LIGHT_MODEL_TWO_SIDE,1);

/*
Outros parâmetros
*/

    glEnable(GL_SMOOTH); 
    glEnable(GL_LIGHTING); 
    glEnable(GL_LIGHT0);  
    glEnable(GL_DEPTH_TEST); 

    glClearColor (0.0, 0.0, 0.0, 0.0);

/*
Iniciar desenho
*/

	glLoadIdentity();
	
/*
Rotação da cena
*/
	
	glRotatef(theta[0], 1.0, 0.0, 0.0);
	glRotatef(theta[1], 0.0, 1.0, 0.0);
	glRotatef(theta[2], 0.0, 0.0, 1.0);


/*
Desenhar função
*/

	draw_function(&calc_function1);

/*
Terminar desenho
*/
	
    glFlush();
	glutSwapBuffers();
}

/*
Rotação da cena
*/
void spinCube()
{
	theta[axis] += 2.0;
	if( theta[axis] > 360.0 ) theta[axis] -= 360.0;
	display();
}

/*
Tratamento do Rato
*/
void mouse(int btn, int state, int x, int y)
{
	if(btn==GLUT_LEFT_BUTTON && state == GLUT_DOWN) axis = 0;
	if(btn==GLUT_MIDDLE_BUTTON && state == GLUT_DOWN) axis = 1;
	if(btn==GLUT_RIGHT_BUTTON && state == GLUT_DOWN) axis = 2;
}

/*
Alteração nas dimensões da janela
*/
void myReshape(int w, int h)
{
    glViewport(0, 0, w, h);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    if (w <= h)
        glOrtho(-2.0, 2.0, -2.0 * (GLfloat) h / (GLfloat) w,
            2.0 * (GLfloat) h / (GLfloat) w, -10.0, 10.0);
    else
        glOrtho(-2.0 * (GLfloat) w / (GLfloat) h,
            2.0 * (GLfloat) w / (GLfloat) h, -2.0, 2.0, -10.0, 10.0);
    glMatrixMode(GL_MODELVIEW);
}


/*
main
*/
void main(int argc, char **argv)
{
    glutInit(&argc, argv);

/* Double Buffering | RGB Color Space | Z Buffer */

    glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH);

/* Janela, registo de funções GLUT */

    glutInitWindowSize(250, 250);
    glutCreateWindow("3dfunc");
    glutReshapeFunc(myReshape);
    glutDisplayFunc(display);
	glutIdleFunc(spinCube);
	glutMouseFunc(mouse);
	
/* Entrar no ciclo principal */
    glutMainLoop();
}
