//
//  centipede.cpp
//
//    Written by Tom Oldani
//
//    Some code taken from robot.cpp and solarsystem.cpp

#include <iostream>
#include <glut.h>
#include <math.h>
#include <stdlib.h>
#include <time.h>

#define M_PI 3.14

using namespace std;

double round(double x)
{
if(x>=0.5){return ceil(x);}else{return floor(x);}
}

GLfloat white_light_diff[] = { 1., 1., 1., 1. };
GLfloat white_light_amb[] = { .3, .3, .3, 1. };
GLfloat light_posn[] = { 0., 1., 0., 0. };

// mob1 material
GLfloat mat_diffuse1[] = { 0.1, 0.1, 0.8, 1.0 };
GLfloat mat_ambient1[] = { 0.0, 0.0, 0.2, 1.0 };

// mob2 material
GLfloat mat_diffuse2[] = { 0.8, 0.1, 0.1, 1.0 };
GLfloat mat_ambient2[] = { 0.2, 0.0, 0.0, 1.0 };

// mob3 material
GLfloat mat_diffuse3[] = { 0.8, 0.1, 1.0, 1.0 };
GLfloat mat_ambient3[] = { 0.2, 0.0, 0.3, 1.0 };

// mob4 material
GLfloat mat_diffuse4[] = { 0.1, 0.8, 0.1, 1.0 };
GLfloat mat_ambient4[] = { 0.0, 0.1, 0.0, 1.0 };

// ground material
GLfloat mat_diffuse0[] = { 0.1, 0.6, 0.1, 1.0 };
GLfloat mat_ambient0[] = { 0.0, 0.3, 0.0, 1.0 };

GLfloat spot_light_diff[] = { 1., 1., 1., 1. };
GLfloat spot_posn[] = { 2., 25., 5., 1. };
GLfloat spot_direcn[] = { 0., -1., 0. };
GLfloat spot_spec[] = { 0.6, 0.6, 0.6, 1. };
float spot_cutoff = 45.;
float spot_expo = 0.0;


// centipede class, used to make monsters
class centipede {
public:
    float v, x, z, clickLength;
    float mobTurnRate, mobAngle, headAngle, newSpeed;
    int legs, n, clicksSinceTurning;
	float segLength;

    // in order to draw the back segments of the centipede, the path it takes needs to be
    // stored.  it's stored as a long array of positions and angles, given from the origin.
    float theta[510];
    float pathx[510];
    float pathz[510];
    int index, pathLength;
    bool changingSpeed;
    float mobHeight, maxAngle;

	 //   
    public:
    centipede( float x, float z, float direction, int segments );
    ~centipede(){};
    void setSpeed( float speed );
    void draw();
    void turn( float modifier );
    void iterate();
    float getx( int m );
    float getz( int m );
    float getangle( int m );
    int queueGet( int place );
    void queueAdd( float angle, float px, float pz );
    void drawSegment( int seg );
};

centipede::centipede( float px, float pz, float direction, int segments )
{
    
	segLength = 4.0f;
	mobHeight = 4.0;
	maxAngle = 360.;
	
	v = .4;
    x = px;
    z = pz;
    changingSpeed = false;
    clickLength = segLength / 50.;
    n = round( segLength/clickLength );
    mobAngle = direction;

    mobTurnRate = 1.1;
    index = 0;
    pathLength = 0;

    // legs = segments, unless segments is >10, then legs = 10
    legs = segments>10 ? 10 : segments;

    // the total length of the centipede in "clicks", ie, queue elements
    pathLength = ceilf( legs * (segLength / clickLength) ) + 1;

    // come up with the initial coordinates for the centipede.  the body starts straight
    for( int i=0; i<=pathLength; i++ )
        queueAdd( direction, px - (pathLength-i)*clickLength*sinf(direction), pz + (pathLength-i)*clickLength*cosf(direction) );
}

// gets the array position for the nth item in the queue
int centipede::queueGet( int place )
{
    return place<index ? index-place : pathLength-(place-index);
}

// adds an item to the queue (first-in first-out)
void centipede::queueAdd( float angle, float px, float pz )
{
    index = ( index >= pathLength ? 0 : index + 1 );
    theta[index] = angle;
    pathx[index] = px;
    pathz[index] = pz;
}

// draws one segment of the centipede's body
void centipede::drawSegment( int seg )
{
    int p = queueGet( seg * n ); // the queue index where the appropriate data for this segment is
    float q = ((720. * p) / pathLength); // an angle, corresponding to the leg movement cycle

    // make sure q is in the range of 0-360 degrees
    while( q >= 360. )
        q -= 360.;

    // this makes it so when the centipede stops, the left legs are all on the ground
    q = v==0 ? 180. : q;

    glTranslatef( pathx[p], mobHeight, pathz[p] );
    glRotatef( theta[p], 0., 1., 0. );
    glutSolidCone( 1.0, 5.0, 4, 1 );
    glutSolidSphere( 2., 8, 8 );

    // this draws the left leg
    glPushMatrix();
        glRotatef( 30., 0., 0., 1. );

        // this causes the leg to move in a semicircular path
        if( cosf( q * M_PI / 180 ) > 0 )
        {
            glRotatef( q, 1., -.5, 0. );
            glRotatef( -q, 1., 0., 0. );
        }
        else
        {
            glRotatef( -30., 0., 0., 1. );
            glRotatef( (q/3.)+300., 0., 1., 0. );
        }
        glTranslatef( 3., 0., 0. );
        glPushMatrix();
            glScalef( 4., .4, .4 );
            glutSolidCube( 1. );
        glPopMatrix();

        // the second leg segment rotates forward and back
        glTranslatef( 2., -0.1, 0. );
        glRotatef( 75., 1., 0., 0. );
        glRotatef( 45., 0., 0., 1. );
        glRotatef( 15. * sinf(q*M_PI/180.) - 10., -1., 1., 0. );
        glutSolidCone( .3, 4., 4, 1 );
    glPopMatrix();

    // this makes it so when the centipede stops, the right legs are all on the ground
    q = v==0 ? 0. : q;

    // this draws the right leg
    glPushMatrix();
        glRotatef( 30., 0., 0., 1. );

        // this makes the leg rotate in a semicircular path
        if( cosf( q * M_PI / 180 ) < 0 )
        {
            glRotatef( q, 1., -.5, 0. );
            glRotatef( -q, 1., 0., 0. );
        }
        else
        {
            glRotatef( -30., 0., 0., 1. );
            glRotatef( q<180 ? -q/3 : (-q+360.)/3., 0., 1., 0. );
        }
        glTranslatef( -3., 0., 0. );
        glPushMatrix();
            glScalef( 4., .4, .4 );
            glutSolidCube( 1. );
        glPopMatrix();
        glTranslatef( -2., -0.1, 0. );
        glRotatef( 75., 1., 0., 0. );
        glRotatef( 45., 0., 0., 1. );
        glRotatef( -15. * sinf(q*M_PI/180.) - 10., -1., 1., 0. );
        glutSolidCone( .3, 4., 4, 1 );
    glPopMatrix();
}

// this changes the centipede's velocity
void centipede::setSpeed( float speed )
{
    newSpeed = speed;
    changingSpeed = true;
    // if the centipede will move in a circle with a circumference less than its length, don't let it
    if( speed * (maxAngle / fabs(mobTurnRate)) < clickLength * pathLength && speed != 0 )
        mobTurnRate = (maxAngle * speed) / (segLength*(legs+1) * ( 2 * rand() %2 - 1 ) );
}

// draws the centipede
void centipede::draw()
{
    glTranslatef( x, mobHeight, z );

    // draw the head; the centipede turns its head side to side when it's stopped
    glRotatef( mobAngle + 15. * sinf(headAngle), 0., 1., 0. );
    glTranslatef( 0., 0., segLength / -3. );
    glutSolidCone( 2.5, 5.0, 6, 1 );
    glPopMatrix();

    // draw all the body segments after the head
    for( int i=1; i<=legs; i++ )
    {
        glPushMatrix();
            drawSegment( i );
        glPopMatrix();
    }
}

// set the centipede's turning angle
void centipede::turn( float modifier )
{
    mobTurnRate = modifier;
    clicksSinceTurning = 0;
    if( newSpeed * (maxAngle / fabs(mobTurnRate)) < clickLength * pathLength && newSpeed != 0 )
        mobTurnRate = (maxAngle * newSpeed) / (segLength*(legs+1) * ( 2 * rand() %2 - 1 ) );
}

// move the centipede forward one animation frame
void centipede::iterate()
{
    if( v > 0 )
    {
        x += v * sinf( mobAngle * M_PI / 180. );
        z += v * cosf( mobAngle * M_PI / 180. );
        mobAngle += mobTurnRate;
        int j = round(v/clickLength);

        // this prevents the centipede from running in circles for too long
        clicksSinceTurning++;
        if( clicksSinceTurning > 240. / fabs(mobTurnRate) )
        {
            mobTurnRate = -mobTurnRate;
            clicksSinceTurning = 0;
        }

        headAngle = 0.;

        // adds some elements to the queue.  adding multiple elements allows the position
        // to be interpolated if the velocity changes later
        for(int i=1; i<=j; i++)
            queueAdd( mobAngle, pathx[index] + (x-pathx[index]) * (1.*i)/j, pathz[index] + (z-pathz[index]) * (1.*i)/j );
    }
    else
        headAngle += 0.05;

    // generate a random number
    int a = rand() %400;

    // randomly change velocity (1 in 400 iterations)
    if( a == 0 )
        setSpeed( rand() %5 == 0 ? 0. : (rand() %10 +5) / 10.  );

    // randomly change direction (3 in 400 iterations)
    if( a >= 397 )
        turn( ((rand() %60) - 30.) / 5. );

    // this makes it so the centipedes change velocity gradually
    if( changingSpeed )
    {
        if( fabs(v-newSpeed) < 0.01 )
        {
            v = newSpeed;
            changingSpeed = false;
        }
        else
            v += v<newSpeed ? 0.01 : -0.01;
    }
}

// get the x position of a given part of the centipede
float centipede::getx( int m )
{
    if( m==0 )
        return x;
    else
        return pathx[queueGet(m)];
}

// get the z position of a given part of the centipede
float centipede::getz( int m )
{
    if( m==0 )
        return z;
    else
        return pathz[queueGet(m)];
}

// get the angle that a part of the centipede is pointing
float centipede::getangle( int m )
{
    if( m==0 )
        return mobAngle;
    else
        return theta[queueGet(m)];
}






// centipede class is done, now for global variables
bool light0 = true, light1 = false;
bool keyL = false, keyR = false, keyF = false, keyB = false;

float cameraAngle;

float cameraX = -5, cameraY = 7, cameraZ = -10;
float lookX = 0, lookY = 0, lookZ = 0;
float upY = 1, upX = 0;
int mobCam = 0, dayTime = 0, spotMob = 1;

// create four centipedes pointing random directions, between 4 and 10 (inclusive) segments long
centipede mob1( 5.0, -4.0, 1. * (rand() %360), rand() %7 +4 );
centipede mob2( 7.0, 3.0, 1. * (rand() %360), rand() %7 +4 );
centipede mob3( -6.0, -4.0, 1. * (rand() %360), rand() %7 +4 );
centipede mob4( -8.0, 3.0, 1. * (rand() %360), rand() %7 +4 );

static const float cameraTurnSpeed = 0.1, cameraMoveSpeed = 1.0;

void keys( unsigned char key, int x, int y )
{
	switch( key )
	{
    // Disable main light source
	case 'm':
		if( light0 )
		{
		    light0 = false;
			glDisable( GL_LIGHT0 );
		}
		else
		{
		    light0 = true;
            glEnable( GL_LIGHT0 );
		}
		break;

    // disable the spotlight
	case 'n':
		if( light1 )
		{
		    light1 = false;
			glDisable( GL_LIGHT1 );
		}
		else
		{
		    light1 = true;
            glEnable( GL_LIGHT1 );
		}
		break;

    // quit
    case 'q':
        exit(0);
        break;

    // give mob1 a random turning angle
    case 'r':
        mob1.turn( (rand() %100 - 50.) / 5. );
        break;

    // make mob1 go straight
    case 's':
        mob1.turn(0);
        break;

    // give mob1 a random velocity
    case 'v':
        mob1.setSpeed( (rand() %20) / 10. );
        break;

    // stop mob1
    case 't':
        mob1.setSpeed(0);
        break;

    // alternate between cameras (sky and FPS)
    case 'f':
        if( mobCam )
        {
            mobCam = false;
            upY = 0.0;
        }
        if( upY )
        {
            upX = 1.0;
            upY = 0.0;
        }
        else
        {
            upX = 0.0;
            upY = 1.0;
        }
        break;

    // put the mobCam on the first mob
    case '1':
        upX = 0.;
        upY = 1.;
        mobCam = 1;
        break;

    // put the mobCam on the second mob
    case '2':
        upX = 0.;
        upY = 1.;
        mobCam = 2;
        break;

    // put the mobCam on the third mob
    case '3':
        upX = 0.;
        upY = 1.;
        mobCam = 3;
        break;

    // put the mobCam on the fourth mob
    case '4':
        upX = 0.;
        upY = 1.;
        mobCam = 4;
        break;
	}
	glutPostRedisplay();
}

// manages the arrow keys for FPS mode
void keys2( int key, int x, int y )
{
    if( key == GLUT_KEY_LEFT )
        keyL = true;

    if( key == GLUT_KEY_RIGHT )
        keyR = true;

    if( key == GLUT_KEY_UP )
        keyF = true;

    if( key == GLUT_KEY_DOWN )
        keyB = true;

    switch( key )
    {
        case GLUT_KEY_F1:
            spotMob = 1;
            glDisable( GL_LIGHT0 );
            glEnable( GL_LIGHT1 );
            break;

        case GLUT_KEY_F2:
            spotMob = 2;
            glDisable( GL_LIGHT0 );
            glEnable( GL_LIGHT1 );
            break;

        case GLUT_KEY_F3:
            spotMob = 3;
            glDisable( GL_LIGHT0 );
            glEnable( GL_LIGHT1 );
            break;

        case GLUT_KEY_F4:
            spotMob = 4;
            glDisable( GL_LIGHT0 );
            glEnable( GL_LIGHT1 );
            break;
    }
}

// checks when arrow keys are released
void upkeys2( int key, int x, int y )
{
    if( key == GLUT_KEY_LEFT )
        keyL = false;

    if( key == GLUT_KEY_RIGHT )
        keyR = false;

    if( key == GLUT_KEY_UP )
        keyF = false;

    if( key == GLUT_KEY_DOWN )
        keyB = false;
}

void idle( void )
{
	glutPostRedisplay();
}

// draw coordinate axes
void drawAxes()
{
	glBegin( GL_LINES );
		glColor3f( 1., 0., 0. );
		glVertex3f( -500., .1, 0. );
		glVertex3f( 500., .1, 0. );
	glEnd();

	glBegin( GL_LINES );
		glColor3f( 0., 1., 0. );
		glVertex3f( 0., -500., 0. );
		glVertex3f( 0., 500., 0. );
	glEnd();

	glBegin( GL_LINES );
		glColor3f( 0., 0., 1. );
		glVertex3f( 0., .1, -500. );
		glVertex3f( 0., .1, 500. );
	glEnd();
}

// draws a square plane with a width of w and d subdivisions
void drawPlane(float w, int d)
{
    float x1 = -w/2, z1 = -w/2;
    float x2 = x1 + (w/d), z2 = z1 + (w/d);

    for(int i=0; i<d; i++)
    {
        for(int j=0; j<d; j++)
        {
            glBegin( GL_QUADS );
            glVertex3i( x1, 0, z1 );
            glVertex3i( x1, 0, z2 );
            glVertex3i( x2, 0, z2 );
            glVertex3i( x2, 0, z1 );
            glEnd();
            x1 = x2;
            x2 += (w/d);
        }
        x1 = -w/2;
        x2 = x1 + (w/d);
        z1 = z2;
        z2 += (w/d);
    }
}

// this executes all the animation code 50 times a second
void timer( int t )
{
    if( mobCam )
    {
        // hitch a ride on a centipede!
        switch( mobCam )
        {
            case 1:
            cameraX = mob1.getx(150);
            cameraZ = mob1.getz(150);
            lookX = mob1.getx(0);
            lookZ = mob1.getz(0);
            break;

            case 2:
            cameraX = mob2.getx(150);
            cameraZ = mob2.getz(150);
            lookX = mob2.getx(0);
            lookZ = mob2.getz(0);
            break;

            case 3:
            cameraX = mob3.getx(150);
            cameraZ = mob3.getz(150);
            lookX = mob3.getx(0);
            lookZ = mob3.getz(0);
            break;

            case 4:
            cameraX = mob4.getx(150);
            cameraZ = mob4.getz(150);
            lookX = mob4.getx(0);
            lookZ = mob4.getz(0);
            break;
        }

        cameraY = 8;
        lookY = 6;
        cameraAngle = atan2f( lookZ-cameraZ, lookX-cameraX );
    }
    else if( upY ) // y-axis is up and mobcam is off, hence FPS mode
    {
        if( keyR )
            cameraAngle += cameraTurnSpeed;
        if( keyL )
            cameraAngle -= cameraTurnSpeed;
        if( keyF )
        {
            cameraX += cameraMoveSpeed * cosf( cameraAngle );
            cameraZ += cameraMoveSpeed * sinf( cameraAngle );
        }
        if( keyB )
        {
            cameraX -= cameraMoveSpeed * cosf( cameraAngle );
            cameraZ -= cameraMoveSpeed * sinf( cameraAngle );
        }

        cameraY = 7;
        lookX = cameraX + cosf( cameraAngle );
        lookY = cameraY;
        lookZ = cameraZ + sinf( cameraAngle );
    }
    else // if you're here, you're in sky camera mode
    {
        if( keyF )
            cameraX += cameraMoveSpeed;
        if( keyB )
            cameraX -= cameraMoveSpeed;
        if( keyR )
            cameraZ += cameraMoveSpeed;
        if( keyL )
            cameraZ -= cameraMoveSpeed;

        cameraY = 100.;
        lookX = cameraX;
        lookY = cameraY - 10.;
        lookZ = cameraZ;
    }

    // iterate the four mobs
    mob1.iterate();
    mob2.iterate();
    mob3.iterate();
    mob4.iterate();

    dayTime = dayTime > 3000 ? 0 : dayTime + 1;

    glutTimerFunc( 20, timer, 0 );
}

void reshape( int w, int h )
{
    if( w < h )
        glViewport( 0, 0, h, h);
    else
        glViewport( 0, 0, w, w);
	glMatrixMode( GL_PROJECTION );
	glLoadIdentity();
	gluPerspective( 80, 1., .3, 2500 );
	glMatrixMode( GL_MODELVIEW );

	glutPostRedisplay();
}

void display( )
{
	glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
	glLoadIdentity();
    gluLookAt( cameraX, cameraY, cameraZ, lookX, lookY, lookZ, upX, upY, 0. );

	// Draw the coordinate axes.
	glDisable( GL_LIGHTING );
	drawAxes();
	glEnable( GL_LIGHTING );

    // spotlight
    glLightfv( GL_LIGHT1, GL_DIFFUSE, spot_light_diff );
	glLightfv( GL_LIGHT1, GL_SPECULAR, spot_spec);
	glLightfv( GL_LIGHT1, GL_POSITION, spot_posn );
	glLightfv( GL_LIGHT1, GL_SPOT_DIRECTION, spot_direcn );
	glLightfv( GL_LIGHT1, GL_SPOT_CUTOFF, &spot_cutoff );
	glLightfv( GL_LIGHT1, GL_SPOT_EXPONENT, &spot_expo );

    // ambient light
    glLightfv( GL_LIGHT0, GL_POSITION, light_posn );
    glLightfv( GL_LIGHT0, GL_DIFFUSE, white_light_diff );
	glLightfv( GL_LIGHT0, GL_AMBIENT, white_light_amb );

    // set material to 0 and draw the plane, with width of 10000 and 2 divisions
	glMaterialfv(GL_FRONT, GL_DIFFUSE, mat_diffuse0);
	glMaterialfv(GL_FRONT, GL_AMBIENT, mat_ambient0);
	drawPlane(10000, 1);

    // makes the light change direction and intensity throughout the "day"
	if( dayTime > 1500 )
	{
	    white_light_diff[0] = 0.1;
	    white_light_diff[1] = 0.1;
	    white_light_diff[2] = 0.3;
	    white_light_amb[0] = 0.0;
	    white_light_amb[1] = 0.0;
	    white_light_amb[2] = 0.1;
        light_posn[0] = cosf( (dayTime-1500) / 1500. * M_PI );
        light_posn[1] = sinf( (dayTime-1500) / 1500. * M_PI );
        glClearColor( 0.0, 0.0, 0.1, 0. );
	}
    else
    {
        white_light_diff[0] = 1.0;
	    white_light_diff[1] = 1.0;
	    white_light_diff[3] = 1.0;
	    white_light_amb[0] = 0.3;
	    white_light_amb[1] = 0.3;
	    white_light_amb[2] = 0.3;
        light_posn[0] = cosf( dayTime / 1500. * M_PI );
        light_posn[1] = sinf( dayTime / 1500. * M_PI );
        glClearColor( 0.1, 0.1, 0.6, 0. );
    }

    spot_posn[0] = mob1.getx(0);
    spot_posn[2] = mob1.getz(0);

    switch( spotMob )
    {
        case 1:
            spot_posn[0] = mob1.getx(0);
            spot_posn[2] = mob1.getz(0);
            break;

        case 2:
            spot_posn[0] = mob2.getx(0);
            spot_posn[2] = mob2.getz(0);
            break;

        case 3:
            spot_posn[0] = mob3.getx(0);
            spot_posn[2] = mob3.getz(0);
            break;

        case 4:
            spot_posn[0] = mob4.getx(0);
            spot_posn[2] = mob4.getz(0);
            break;
    }

	// set material to 1 and draw the first mob
	glMaterialfv(GL_FRONT, GL_DIFFUSE, mat_diffuse1);
	glMaterialfv(GL_FRONT, GL_AMBIENT, mat_ambient1);
    glPushMatrix();
        mob1.draw();
    glPopMatrix();

    // set material to 2 and draw the second mob
	glMaterialfv(GL_FRONT, GL_DIFFUSE, mat_diffuse2);
	glMaterialfv(GL_FRONT, GL_AMBIENT, mat_ambient2);
    glPushMatrix();
        mob2.draw();
    glPopMatrix();

    // set material to 3 and draw the third mob
	glMaterialfv(GL_FRONT, GL_DIFFUSE, mat_diffuse3);
	glMaterialfv(GL_FRONT, GL_AMBIENT, mat_ambient3);
    glPushMatrix();
        mob3.draw();
    glPopMatrix();

    // set material to 4 and draw the fourth mob
	glMaterialfv(GL_FRONT, GL_DIFFUSE, mat_diffuse4);
	glMaterialfv(GL_FRONT, GL_AMBIENT, mat_ambient4);
    glPushMatrix();
        mob4.draw();
    glPopMatrix();

	glutSwapBuffers();
}

void init( )
{
	glClearColor( 0., 0., 0.5, 0. );
	glShadeModel( GL_SMOOTH );

	srand( time(NULL) );

	cameraAngle = atan2f( lookX-cameraX, lookZ-cameraZ );
	glutTimerFunc( 100, timer, 0 );

	glEnable( GL_LIGHT0 );
	glEnable( GL_LIGHT1 );

	glDepthFunc(GL_LEQUAL);
    glEnable(GL_DEPTH_TEST);
}

int main(int argc, char* argv[])
{
	glutInit( &argc, argv );
	glutInitDisplayMode( GLUT_DOUBLE | GLUT_RGB );
	glutInitWindowSize( 800, 600 );
	glutInitWindowPosition( 50, 50 );
	glutCreateWindow( "Monsters!" );

	init();

	glutDisplayFunc( display );

	glutReshapeFunc( reshape );

	glutIdleFunc( idle );

	glutKeyboardFunc( keys );
	glutSpecialFunc( keys2 );
	glutSpecialUpFunc( upkeys2 );
	glutIgnoreKeyRepeat(0);

	glutMainLoop();

	return 0;
}

