/*****************************************************************
  solid.cpp
  by Dave Pape
  7 April 2003

 This program demonstrates the use of springs to animate a solid
 object (as if it were made of jello).
 It animates a cube - it creates 8 PointMasses (one for each vertex
 of the cube), and then links them with springs along the edges.
 Additional springs running diagonally through the cube are needed
 to keep it from collapsing flat.

*****************************************************************/
#include <stdlib.h>
#include <math.h>
#include <unistd.h>
#include <stdio.h>
#include <GL/glut.h>
#include <GL/gl.h>
#include <dms/dms.h>
#include <vector>
#include "PointMass.h"
#include "Spring.h"

using namespace dms;

void createScene(Object& root);
void drawEverything(void);

void key(unsigned char k, int x, int y);
void specialkey(int k, int x, int y);
void idle(void);


PerspCamera camera;
Light light;
Object root;

bool active = false;
bool windActive = false;

vector<PointMass> points;
vector<Spring> springs;
Vector3 gravity(0, -10, 0);
Vector3 wind(2, 0, -2);

Texture2D cubetex("flower2.jpg");


int main(int argc, char *argv[])
    {
    glutInit(&argc, argv);
    glutInitDisplayMode(GLUT_RGB | GLUT_DOUBLE | GLUT_DEPTH);
    glutInitWindowSize(512,512);
    glutCreateWindow(argv[0]);
    
    glutDisplayFunc(drawEverything);
    glutKeyboardFunc(key);
    glutSpecialFunc(specialkey);
    glutIdleFunc(idle);
    
    camera.setPosition(0, 4, 20);
    light.setInfinitePosition(-5, 4, 1);
    createScene(root);
    points.push_back(PointMass(Vector3(-2, 10, -2)));
    points.push_back(PointMass(Vector3(2, 10, -2)));
    points.push_back(PointMass(Vector3(2, 10, 2)));
    points.push_back(PointMass(Vector3(-2, 10, 2)));
    points.push_back(PointMass(Vector3(-2, 14, -2)));
    points.push_back(PointMass(Vector3(2, 14, -2)));
    points.push_back(PointMass(Vector3(2, 14, 2)));
    points.push_back(PointMass(Vector3(-2, 14, 2)));
    for (int i=0; i < points.size(); i++)
        points[i].setDrag(0.2);
    springs.push_back(Spring(points[0], points[1], 100));
    springs.push_back(Spring(points[1], points[2], 100));
    springs.push_back(Spring(points[2], points[3], 100));
    springs.push_back(Spring(points[3], points[0], 100));
    springs.push_back(Spring(points[4], points[5], 100));
    springs.push_back(Spring(points[5], points[6], 100));
    springs.push_back(Spring(points[6], points[7], 100));
    springs.push_back(Spring(points[7], points[4], 100));
    springs.push_back(Spring(points[0], points[4], 100));
    springs.push_back(Spring(points[1], points[5], 100));
    springs.push_back(Spring(points[2], points[6], 100));
    springs.push_back(Spring(points[3], points[7], 100));

    springs.push_back(Spring(points[0], points[6], 1000));
    springs.push_back(Spring(points[1], points[7], 1000));
    springs.push_back(Spring(points[2], points[4], 1000));
    springs.push_back(Spring(points[3], points[5], 1000));
    
    glLineWidth(2);
    
    glutMainLoop();
    return 0;
    }


void createScene(Object& root)
    {
    Square * floor = new Square(-50, -50, 50, 50, DMS_Y);
    floor->setMaterial(*(new Material(Color::White)));
    floor->setTexture(*(new Texture2D("grass.jpg")));
    root.attach(*floor);
    }


void drawEverything(void)
    {
    glClearColor(0.5, 0.7, 1.0, 0.0);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glEnable(GL_DEPTH_TEST);
    
    camera.apply();
    light.apply();
    root.drawAll();
    light.disable();


/*************** CODE OF INTEREST ******************************/
/* This code draws a solid, textured GL cube using the point   */
/* masses' positions for its vertices.                         */
/*                                                             */
    Color::White.apply();
    cubetex.apply();
    glBegin(GL_QUADS);
     glTexCoord2f(0, 0);
     glVertex3fv(points[0].position().vec);
     glTexCoord2f(1, 0);
     glVertex3fv(points[1].position().vec);
     glTexCoord2f(1, 1);
     glVertex3fv(points[2].position().vec);
     glTexCoord2f(0, 1);
     glVertex3fv(points[3].position().vec);

     glTexCoord2f(0, 0);
     glVertex3fv(points[4].position().vec);
     glTexCoord2f(1, 0);
     glVertex3fv(points[5].position().vec);
     glTexCoord2f(1, 1);
     glVertex3fv(points[6].position().vec);
     glTexCoord2f(0, 1);
     glVertex3fv(points[7].position().vec);

     glTexCoord2f(0, 0);
     glVertex3fv(points[0].position().vec);
     glTexCoord2f(1, 0);
     glVertex3fv(points[1].position().vec);
     glTexCoord2f(1, 1);
     glVertex3fv(points[5].position().vec);
     glTexCoord2f(0, 1);
     glVertex3fv(points[4].position().vec);

     glTexCoord2f(0, 0);
     glVertex3fv(points[1].position().vec);
     glTexCoord2f(1, 0);
     glVertex3fv(points[2].position().vec);
     glTexCoord2f(1, 1);
     glVertex3fv(points[6].position().vec);
     glTexCoord2f(0, 1);
     glVertex3fv(points[5].position().vec);

     glTexCoord2f(0, 0);
     glVertex3fv(points[2].position().vec);
     glTexCoord2f(1, 0);
     glVertex3fv(points[3].position().vec);
     glTexCoord2f(1, 1);
     glVertex3fv(points[7].position().vec);
     glTexCoord2f(0, 1);
     glVertex3fv(points[6].position().vec);

     glTexCoord2f(0, 0);
     glVertex3fv(points[3].position().vec);
     glTexCoord2f(1, 0);
     glVertex3fv(points[0].position().vec);
     glTexCoord2f(1, 1);
     glVertex3fv(points[4].position().vec);
     glTexCoord2f(0, 1);
     glVertex3fv(points[7].position().vec);
    glEnd();
    cubetex.disable();


    Color::Red.apply();
    for (int i=0; i < springs.size(); i++)
        springs[i].drawLine();

    glutSwapBuffers();

    checkGLError("end-of-frame");
    }


void key(unsigned char k, int x, int y)
    {
    if (k == 27)
        exit(0);
    else if (k == ' ')
        active = !active;
    else if (k == 13)
        windActive = !windActive;
    }


void specialkey(int k, int x, int y)
    {
    if (k == GLUT_KEY_LEFT)
        camera.turn(3);
    else if (k == GLUT_KEY_RIGHT)
        camera.turn(-3);
    else if (k == GLUT_KEY_UP)
        camera.pitch(2);
    else if (k == GLUT_KEY_DOWN)
        camera.pitch(-2);
    else if (k == GLUT_KEY_HOME)
        camera.moveForward(0.25);
    else if (k == GLUT_KEY_END)
        camera.moveForward(-0.25);
    else if (k == GLUT_KEY_PAGE_UP)
        camera.zoom(-1);
    else if (k == GLUT_KEY_PAGE_DOWN)
        camera.zoom(1);
    }


/*************** CODE OF INTEREST ******************************/
/* This generic update is the same as that in springs.cpp,     */
/* except for the addition of a test that will make points     */
/* bounce off of the ground (the y=0 plane).                   */
/*                                                             */
void idle(void)
    {
    int i;
    beginFrame();
    if (active)
        {
        for (i=0; i < points.size(); i++)
            {
/*************** CODE OF INTEREST ******************************/
/* If a point is below ground (y < 0), bounce it off the ground*/
/* by flipping the y value of its position & velocity.  This   */
/* puts it in the appropriate point above ground, and makes it */
/* move upward now instead of down.  The velocity is also      */
/* reduced (multiplied by 0.8), to simulate a slightly         */
/* inelastic collision.                                        */
/*                                                             */
            if (points[i].position().vec[1] < 0)
                {
                Vector3 p(points[i].position());
                p[1] = -p[1];
                points[i].setPosition(p);
                Vector3 v(points[i].velocity());
                if (v[1] < 0)
                    v[1] = -v[1];
                v *= 0.8;
                points[i].setVelocity(v);
                }
            points[i].clearForces();
            points[i].applyForce(gravity);
            if (windActive)
                points[i].applyForce(wind);
            }
        for (i=0; i < springs.size(); i++)
            springs[i].apply();
        for (i=0; i < points.size(); i++)
            points[i].update(deltaTime());
        }
    glutPostRedisplay();
    }
