/* Computer Graphics, Assignment, Shading
 *
 * Filename ........ main.c
 * Description ..... Creates OpenGL window and draws the scene.
 * Date ............ 19.08.2008
 */

#include <stdio.h>
#include <stdlib.h>
#include <GL/glut.h>
#include <GL/gl.h>
#include <GL/glu.h>
#include <math.h>

#ifndef M_PI
#define M_PI 3.1415926535897932384626
#endif

#include "polys.h"
#include "normals.h"

polys   *polylistFlat = NULL;
polys   *polylistGouraud = NULL;

float   camDistance = 25.0;
float   camRotZ = 45.0, camAzimuth = 20.0;
float   saved_camRotZ, saved_camAzimuth, saved_camDistance;
int     mouse_mode = 0;
int     mx, my;

#define color_roof   {1.0,0.2,0.0,0.0}
#define color_wall   {0.7,0.7,0.7,0.0}
#define color_garden {0.1,1.0,0.0,0.0}

#define base_NW {-1.5, -1.0, 1.0}
#define base_NE { 1.5, -1.0, 1.0}
#define base_SW {-1.5, -1.0,-1.0}
#define base_SE { 1.5, -1.0,-1.0}

#define base2_NW {-1.5, 1.5, 1.0}
#define base2_NE { 1.5, 1.5, 1.0}
#define base2_SW {-1.5, 1.5,-1.0}
#define base2_SE { 1.5, 1.5,-1.0}

#define base2_W  {-1.5, 2.5, 0.0}
#define base2_E  { 1.5, 2.5, 0.0}

#define roof_NW {-1.8, 1.3, 1.2}
#define roof_NE { 1.8, 1.3, 1.2}
#define roof_SW {-1.8, 1.3,-1.2}
#define roof_SE { 1.8, 1.3,-1.2}

#define roof_W  {-1.8, 2.5, 0.0}
#define roof_E  { 1.8, 2.5, 0.0}

#define garden_E  {-4.5, 0.0, 0.0}
#define garden_NE {-3.5, 0.0, 3.5}
#define garden_NW { 3.5, 0.0, 3.5}
#define garden_W  { 4.5, 0.0, 0.0}
#define garden_SW { 3.5, 0.0,-3.5}
#define garden_SE {-3.5, 0.0,-3.5}

#define n       { 0.0, 0.0, 0.0}
#define nn	    { n, n, n, n, n, n}

#define center  {0.0,0.0,0.0}

poly garden = {6, {garden_E,garden_NE,garden_NW,garden_W,garden_SW,garden_SE}, nn, n, center, color_garden};
poly wall_E = {5, {base_NE,base_SE,base2_SE,base2_E,base2_NE,n}, nn, n, center, color_wall};
poly wall_W = {5, {base_NW,base_SW,base2_SW,base2_W,base2_NW,n}, nn, n, center, color_wall};
poly wall_N = {4, {base_NW,base_NE,base2_NE,base2_NW,n,n}, nn, n, center, color_wall};
poly wall_S = {4, {base_SW,base_SE,base2_SE,base2_SW,n,n}, nn, n, center, color_wall};
poly roof_N = {4, {roof_NE,roof_NW,roof_W,roof_E,n,n}, nn, n, center, color_roof};
poly roof_S = {4, {roof_SE,roof_SW,roof_W,roof_E,n,n}, nn, n, center, color_roof};

GLfloat lightPosition[] = {2.0f, 10.0f, 2.0f, 0.0f};
GLfloat lightAmbient[]  = {0.3f, 0.3f, 0.3f, 1.0f};
GLfloat lightDiffuse[]  = {2.0f, 2.0f, 2.0f, 1.0f};
GLfloat lightSpecular[] = {1.0f, 1.0f, 1.0f, 1.0f};

void
createSphere(polys* list, double sx, double sy, double sz,
    double ox, double oy, double oz, double r, double g, double b)
{
    int     latitude,longitude;
    double  dToR = M_PI/180.0;
    GLfloat x, y, z;
    poly    p;

    p.points = 4;

    p.color[0] = r;
    p.color[1] = g;
    p.color[2] = b;
    p.color[3] = 0;

    p.inside.x = ox;
    p.inside.y = oy;
    p.inside.z = oz;

    for (latitude = -90; latitude < 90; latitude += 10)
    {
        for (longitude = 0; longitude <= 360; longitude += 10)
        {
            x = ox + sin(longitude * dToR) * cos(latitude * dToR)*sx;
            y = oy + sin(latitude * dToR)*sy;
            z = oz + cos(longitude * dToR) * cos(latitude * dToR)*sz;

            p.pts[0] = p.pts[3];
            p.pts[1] = p.pts[2];
            p.pts[2].x = x;
            p.pts[2].y = y;
            p.pts[2].z = z;

            x = ox + sin(longitude * dToR) * cos((latitude+10) * dToR)*sx;
            y = oy + sin((latitude+10) * dToR)*sy;
            z = oz + cos(longitude * dToR) * cos((latitude+10) * dToR)*sz;

            p.pts[3].x = x;
            p.pts[3].y = y;
            p.pts[3].z = z;

            if (longitude != 0)
                AddPolyToPolylist(list,p);
        }
    }
}


void
InitializePolygonlist(void)
{
    polylistFlat = CreatePolylist(6);
    AddPolyToPolylist(polylistFlat, wall_E);
    AddPolyToPolylist(polylistFlat, wall_W);
    AddPolyToPolylist(polylistFlat, wall_S);
    AddPolyToPolylist(polylistFlat, wall_N);
    AddPolyToPolylist(polylistFlat, roof_S);
    AddPolyToPolylist(polylistFlat, roof_N);

    polylistGouraud = CreatePolylist(10);
    createSphere(polylistGouraud,6,3,6,0,-3,0,0,1,0);

    printf("%d polygons to flat shade\n", polylistFlat->length);
    printf("%d polygons to gouraud shade\n", polylistGouraud->length);

    calcNormalsFlat(polylistFlat);
    calcNormalsGouraud(polylistGouraud);
}


void
InitGL(void)
{
    GLfloat light_ambient[] = {0.4,0.4,0.4,0.0};
    GLfloat mat_shininess[] = { 50.0 };
    glClearColor(0.7,0.7,1,1);
    glMaterialfv(GL_FRONT, GL_SHININESS, mat_shininess);
    glLightfv(GL_LIGHT0, GL_AMBIENT, light_ambient);
    glEnable(GL_LIGHTING);
    glEnable(GL_LIGHT0);
    glDepthFunc(GL_LEQUAL);
    glEnable(GL_DEPTH_TEST);
    glCullFace(GL_BACK);
    glEnable(GL_COLOR_MATERIAL);
}

void
ReSizeGLScene(int Width, int Height)
{
    if (Height == 0)
        Height=1;

    glViewport(0, 0, Width, Height);

    glMatrixMode(GL_PROJECTION);

    glLoadIdentity();
    gluPerspective(45.0f,(GLfloat)Width/(GLfloat)Height,0.1f,100.0f);


    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity ();
}

void
drawPolylist(polys* list)
{
    int     i,j;
    poly    p;

    for (i = 0; i < list->length; i++)
    {
        p = list->items[i];

        glColor3f(p.color[0], p.color[1], p.color[2]);
        glBegin(GL_POLYGON);

        for (j = 0; j < p.points; j++)
        {
            glNormal3f(p.normal[j].x, p.normal[j].y, p.normal[j].z);
            glVertex3f(p.pts[j].x, p.pts[j].y, p.pts[j].z);
        }

        glEnd();
    }
}

void SetupCamera(void)
{
    float	cx, cy, cz;
    float	t;
    float 	beta, gamma;

    glLoadIdentity();

    // degrees -> radians
    beta = camAzimuth / 180.0 * M_PI;
    gamma = camRotZ / 180.0 * M_PI;

    cx = camDistance;
    cy = cz = 0.0;

    // Rotate around Z
    t = cx;
    cx = cx * cos(beta) + cy * sin(beta);
    cy = t * sin(beta) + cy * cos(beta);

    // Rotate around Y
    t = cx;
    cx = cx * cos(gamma) - cz * sin(gamma);
    cz = t * sin(gamma) + cz * cos(gamma);

    gluLookAt (cx, cy, cz,  0.0, 0.0, 0.0,  0.0, 1.0, 0.0);
}

void
DrawGLScene(void)
{
    GLfloat mat_specular[] = { 1.0, 1.0, 1.0, 1.0 };
    GLfloat mat_no_specular[] = { 0.0, 0.0, 0.0, 1.0 };

    GLfloat light_position[] = { 5.0, 5.0, 5.0, 0.0 };

    glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT);

    SetupCamera();

    glLightfv(GL_LIGHT0, GL_POSITION, light_position);

    glMaterialfv(GL_FRONT, GL_SPECULAR, mat_no_specular);
    drawPolylist(polylistFlat);

    glMaterialfv(GL_FRONT, GL_SPECULAR, mat_specular);
    drawPolylist(polylistGouraud);

    glutSwapBuffers();
}

void
keyPressed(unsigned char key, int x, int y)
{
    switch (key)
    {
        case 'q':
            exit(-1);
            break;
    }
}

void
specialKeyPressed(int key, int x, int y)
{
}

static void
mouseFunc(int button, int state, int x, int y)
{
    // guard against both left and right buttons being pressed at the same time,
    // by only responding when a mouse button is pressed while another one
    // hasn't been pressed yet
    if (state == GLUT_DOWN && mouse_mode == 0)
    {
        if (button == GLUT_LEFT_BUTTON)
        {
            mouse_mode = GLUT_LEFT_BUTTON;

            saved_camRotZ = camRotZ;
            saved_camAzimuth = camAzimuth;

            mx = x;
            my = y;
        }
        else if (button == GLUT_RIGHT_BUTTON)
        {
            mouse_mode = GLUT_RIGHT_BUTTON;

            saved_camDistance = camDistance;

            my = y;
        }
    }
    else if (state == GLUT_UP && button == mouse_mode)
    {
        // pressed button released
        mouse_mode = 0;
    }

}

static void
motionFunc(int x, int y)
{
    int dx, dy;

    if (mouse_mode == GLUT_LEFT_BUTTON)
    {
        dx = mx - x;
        dy = my - y;

        camRotZ = saved_camRotZ - dx * 0.25;
        camAzimuth = saved_camAzimuth - dy * 0.25;

        if (camAzimuth > 89.99)
            camAzimuth = 89.99;
        else if (camAzimuth < -89.99)
            camAzimuth = -89.99;
    }
    else if (mouse_mode == GLUT_RIGHT_BUTTON)
    {
        dy = my - y;

        camDistance = saved_camDistance - dy * 0.5;
        if (camDistance < 0.5)
            camDistance = 0.5;
        else if (camDistance > 100.0)
            camDistance = 100.0;
    }
}

int
main(int argc, char **argv)
{
    InitializePolygonlist();
    glutInit(&argc, argv);

    glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE | GLUT_ALPHA | GLUT_DEPTH);
    glutInitWindowSize(640, 480);
    glutInitWindowPosition(0, 0);
    glutCreateWindow("Shading");

    glutDisplayFunc(&DrawGLScene);
    glutIdleFunc(&DrawGLScene);
    glutReshapeFunc(&ReSizeGLScene);
    glutSpecialFunc(&specialKeyPressed);
    glutKeyboardFunc(&keyPressed);
    glutMotionFunc(&motionFunc);
    glutMouseFunc(&mouseFunc);

    InitGL();

    glutMainLoop();
}
