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

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

#ifndef M_PI
#define M_PI 3.141592653589793238462643350279
#endif

polys   *polylist = NULL;

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

point   line_start = { 0, -3, 0 };
point   line_end = { -3, 3.5, 0 };

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

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

#define roof_NW {-1.5, 1.5, 1.0}
#define roof_NE { 1.5, 1.5, 1.0}
#define roof_SW {-1.5, 1.5,-1.0}
#define roof_SE { 1.5, 1.5,-1.0}

#define roof_W  {-1.5, 2.5, 0.0}
#define roof_E  { 1.5, 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 z       { 0, 0, 0 }

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

#undef n
#undef nn
#undef z

void
InitializePolygonlist(void)
{
    polylist = CreatePolylist(10);
    AddPolyToPolylist(polylist, garden);
    AddPolyToPolylist(polylist, wall_E);
    AddPolyToPolylist(polylist, wall_W);
    AddPolyToPolylist(polylist, wall_S);
    AddPolyToPolylist(polylist, wall_N);
    AddPolyToPolylist(polylist, roof_S);
    AddPolyToPolylist(polylist, roof_N);

    printf("%d polygons\n", polylist->length);
}

void
InitGL(void)
{
    glClearColor(1.0f, 1.0f, 1.0f, 0.0f);
    glClearDepth(1.0);
    glDepthFunc(GL_LESS);
    glEnable(GL_DEPTH_TEST);
    glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
    glShadeModel(GL_SMOOTH);
    glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
}

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);
}

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)
{
    int     i,j;
    poly    p;

    glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT);

    SetupCamera();

    // Draw the polygons

    for (i = 0; i < polylist->length; i++)
    {
        p = polylist->items[i];
        glColor3f(p.color[0], p.color[1], p.color[2]);
        glBegin(GL_POLYGON);
        for (j = 0; j < p.points; j++)
            glVertex3f(p.pts[j].x, p.pts[j].y, p.pts[j].z);
        glEnd();
    }

    // Draw the intersecting line

    glColor3f(0, 0, 1);

    glBegin(GL_LINES);
        glVertex3f(line_start.x, line_start.y, line_start.z);
        glVertex3f(line_end.x, line_end.y, line_end.z);
    glEnd();

    // Intersect the line with the polygons, intersect() will
    // draw an intersect point marker

    intersect(polylist, line_start, line_end);

    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)
{
    switch(key)
    {
    case GLUT_KEY_LEFT:
        line_end.x += 0.1;
        break;
    case GLUT_KEY_RIGHT:
        line_end.x -= 0.1;
        break;
    case GLUT_KEY_UP:
        line_end.z += 0.1;
        break;
    case GLUT_KEY_DOWN:
        line_end.z -= 0.1;
        break;
    case GLUT_KEY_PAGE_UP:
        line_end.y += 0.1;
        break;
    case GLUT_KEY_PAGE_DOWN:
        line_end.y -= 0.1;
        break;
    }

    glutPostRedisplay();
}

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("Intersection");

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

    InitGL();

    glutMainLoop();

    return 1;
}
