#include "gllandscape.h"

#include <math.h>

#include "fbm.h"

#ifndef PI
#define PI 3.14159
#endif

#if defined(Q_CC_MSVC)
#pragma warning(disable:4305) // init: truncation from const double to float
#pragma warning(disable:4244) // init: truncation from const double to float
#endif

GLLandscape::GLLandscape( QWidget * parent, const char * name )
    : QGLWidget( parent, name )
{
    mouseButtonDown  = FALSE;
    animationRunning = FALSE;
    oldX = oldY = oldZ = 0.0;
    landscape     = 0;
    vertexNormals = 0;
    normals       = 0;
    wave          = 0;
    wt            = 0;
    createGrid( 50 );
    setWireframe( 0 );
}

GLLandscape::~GLLandscape()
{
    destroyGrid();
}

void GLLandscape::initializeGL()
{
    glMatrixMode( GL_MODELVIEW );
    glLoadIdentity();
    glTranslatef( 0.0, 0.0, -50.0 );
    glRotatef( -45, 1, 0, 0 );
    glRotatef( -45, 0, 0, 1 );
    glGetFloatv( GL_MODELVIEW_MATRIX,(GLfloat *) views[CurrentView].model );
    glGetFloatv( GL_MODELVIEW_MATRIX,(GLfloat *) views[DefaultView].model );

    glMatrixMode( GL_PROJECTION );
    glLoadIdentity();
    /* Use GL utility library function to obtain desired view */
    gluPerspective( 60, 1, 1, 250 );
    glGetFloatv( GL_PROJECTION_MATRIX, (GLfloat *)views[CurrentView].projection );
    glGetFloatv( GL_PROJECTION_MATRIX, (GLfloat *)views[DefaultView].projection );


    qglClearColor( black );
    glDepthFunc( GL_LESS );
    calculateVertexNormals();
}

void GLLandscape::resizeGL( int width, int height )
{
    glViewport( 0, 0, width, height );
}

void GLLandscape::paintGL()
{
    QString str;
    GLboolean lighting;
    glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
    switch ( mode ) {
	case Wireframe:
	    drawWireframe();
	    str = tr( "Wireframe" );
	    break;
	case Filled:
	    drawFilled();
	    str = tr( "Flat shaded" );
	    break;
	case SmoothShaded:
	    str = tr( "Smooth shaded" );
	    drawSmoothShaded();
	    break;
	case Landscape:
	    drawSmoothShaded();
	    str = tr( "Landscape" );
	    break;
    }
    glGetBooleanv( GL_LIGHTING, &lighting );
    if ( lighting ) {
	glDisable( GL_LIGHTING );
    }
    qglColor( white );
    renderText( 15, height() - 15, str );
    if ( lighting ) {
	glEnable( GL_LIGHTING );
    }
}

void GLLandscape::drawWireframe()
{
    qglColor( white );
    glBegin( GL_LINES );
    {
	for ( int y = 0; y < (gridSize-1); y++ )
	    for ( int x = 0; x < (gridSize-1); x++) {
		glVertex3f( x-gridHalf, y-gridHalf, landscape[x][y] );
		glVertex3f( x+1-gridHalf, y-gridHalf, landscape[x+1][y] );
		glVertex3f( x-gridHalf, y-gridHalf, landscape[x][y] );
		glVertex3f( x+1-gridHalf, y+1-gridHalf, landscape[x+1][y+1] );

		glVertex3f( x-gridHalf, y-gridHalf, landscape[x][y] );
		glVertex3f( x-gridHalf, y+1-gridHalf, landscape[x][y+1] );
	    }
    }
    glEnd();
    glBegin( GL_LINE_STRIP );
    {
	for ( int x = 0; x < gridSize; x++ ) {
	    glVertex3f( x-gridHalf, gridHalf-1, landscape[x][gridSize-1] );
	}
    }
    glEnd();
    glBegin( GL_LINE_STRIP );
    {
	for ( int y = 0; y < gridSize; y++ ) {
	    glVertex3f( gridHalf-1, y-gridHalf, landscape[gridSize-1][y] );
	}
    }
    glEnd();
}

void GLLandscape::drawFilled()
{
    for ( int y = 0; y < gridSize-1; y++ )
	for ( int x = 0; x < gridSize-1; x++ ) {
	    qglColor( red );
	    glBegin( GL_TRIANGLE_STRIP );
	    {
		glVertex3f(x-gridHalf,y-gridHalf, landscape[x][y]);
		glVertex3f(x+1-gridHalf,y-gridHalf, landscape[x+1][y]);
		glVertex3f(x-gridHalf,y+1-gridHalf, landscape[x][y+1]);
	    }
	    glEnd();
	    qglColor( white );
	    glBegin( GL_TRIANGLE_STRIP );
	    {
		glVertex3f(x+1-gridHalf,y-gridHalf, landscape[x+1][y]);
		glVertex3f(x+1-gridHalf,y+1-gridHalf, landscape[x+1][y+1]);
		glVertex3f(x-gridHalf,y+1-gridHalf, landscape[x][y+1]);
	    }
	    glEnd();
	}
}

void GLLandscape::drawSmoothShaded()
{
    if ( mode == SmoothShaded ) {
	GLfloat materialAmbient[]   = { 0.00, 0.00, 1.0, 0.0 };
	GLfloat materialShininess[] = { 128.0 };
	GLfloat materialSpecular[]  = { 1.0, 1.0, 1.0, 0.0 };

	glMaterialfv( GL_FRONT, GL_SPECULAR, materialSpecular );
	glMaterialfv( GL_FRONT, GL_AMBIENT, materialAmbient );
	glMaterialfv( GL_FRONT, GL_SHININESS, materialShininess );
    } else {
	GLfloat materialAmbient[]   = { 0.20, 0.33, 0.20, 0.0 };
	GLfloat materialShininess[] = { 1.0 };
	GLfloat materialSpecular[]  = { 0.1, 0.1, 0.1, 0.1 };

	glMaterialfv( GL_FRONT, GL_SPECULAR, materialSpecular );
	glMaterialfv( GL_FRONT, GL_AMBIENT, materialAmbient );
	glMaterialfv( GL_FRONT, GL_SHININESS, materialShininess );
    }

    for ( int y = 0; y < gridSize-1; y++ )
	for ( int x = 0; x < gridSize-1; x++ ) {
	    glBegin( GL_POLYGON );
	    {
		glNormal3dv(vertexNormals[x][y].n);
		glVertex3f(x-gridHalf,y-gridHalf,landscape[x][y]);

		glNormal3dv(vertexNormals[x+1][y].n);
		glVertex3f(x+1-gridHalf, y-gridHalf, landscape[x+1][y]);

		glNormal3dv(vertexNormals[x+1][y+1].n);
		glVertex3f(x+1-gridHalf, y+1-gridHalf, landscape[x+1][y+1]);
	    }
	    glEnd();

	    glBegin( GL_POLYGON );
	    {
		glNormal3dv(vertexNormals[x][y].n);
		glVertex3f(x-gridHalf,y-gridHalf, landscape[x][y]);

		glNormal3dv(vertexNormals[x+1][y+1].n);
		glVertex3f(x+1-gridHalf,y+1-gridHalf, landscape[x+1][y+1]);

		glNormal3dv(vertexNormals[x][y+1].n);
		glVertex3f(x-gridHalf,y+1-gridHalf, landscape[x][y+1]);
	    }
	    glEnd();
	}

    // Draw water
    if ( mode == Landscape ) {
	GLfloat materialAmbient[]   = { 0.00, 0.00, 1.0, 0.0 };
	GLfloat materialShininess[] = { 128.0 };
	GLfloat materialSpecular[]  = { 1.0, 1.0, 1.0, 0.0 };

	glMaterialfv( GL_FRONT, GL_SPECULAR, materialSpecular );
	glMaterialfv( GL_FRONT, GL_AMBIENT, materialAmbient );
	glMaterialfv( GL_FRONT, GL_SHININESS, materialShininess );
	
	glEnable( GL_BLEND );
	glBegin( GL_POLYGON );
	{
	    glNormal3f( 0, 0, 1 );
	    glVertex3f( -gridHalf, -gridHalf, .2 );
	    glNormal3f( 0, 0, 1 );
	    glVertex3f( -gridHalf, gridHalf, .2 );
	    glNormal3f( 0, 0, 1 );
	    glVertex3f( gridHalf, gridHalf, .2 );
	    glNormal3f( 0, 0, 1 );
	    glVertex3f( gridHalf, -gridHalf, .2 );
	
	}
	glEnd();
	glDisable( GL_BLEND );
    }
}

void GLLandscape::setGridSize( int size )
{
    destroyGrid();      // destroy old grid
    createGrid( size ); // create new grid
    initializeGL();
    updateGL();
}

void GLLandscape::createGrid( int size )
{
    if ( (size % 2) != 0 )
	size++;
    gridSize = size;
    gridHalf = gridSize / 2;
    initFractals  = TRUE;
    landscape     = new double*[gridSize];
    normals       = new gridNormals*[gridSize];
    vertexNormals = new avgNormals*[gridSize];
    wt            = new double*[gridSize];
    wave          = new double*[gridSize];
    for ( int i = 0; i < gridSize; i++ ) {
	landscape[i]     = new double[gridSize];
	normals[i]       = new gridNormals[gridSize];
	vertexNormals[i] = new avgNormals[gridSize];
	wt[i]   = new double[gridSize];
	wave[i] = new double[gridSize];

	memset( landscape[i], 0, gridSize*sizeof(double) );
	memset( normals[i], 0, gridSize*sizeof(gridNormals) );
	memset( vertexNormals[i], 0, gridSize*sizeof(avgNormals) );
	memset( wt[i], 0, gridSize*sizeof(double) );
	memset( wave[i], 0, gridSize*sizeof(double) );
    }
}

void GLLandscape::destroyGrid()
{
    if ( landscape != NULL ) {
	for( int i = 0; i < gridSize; i++ ) {
	    delete[] landscape[i];
	    delete[] normals[i];
	    delete[] vertexNormals[i];
	    delete[] wt[i];
	    delete[] wave[i];
	}
	delete[] landscape;
	delete[] normals;
	delete[] vertexNormals;
	delete[] wt;
	delete[] wave;
    }
    landscape = 0;
}

void GLLandscape::rotate( GLfloat deg, Axis axis )
{
    makeCurrent();
    glMatrixMode( GL_MODELVIEW );
    for ( int i = 0; i < 2; i++ ) {
	glLoadMatrixf((GLfloat *) views[i].model);
	if ( axis == XAxis )
	    glRotatef( deg, 1, 0, 0 );
	else if ( axis == YAxis )
	    glRotatef( deg, 0, 1, 0 );
	else
	    glRotatef( deg, 0, 0, 1 );
	glGetFloatv(GL_MODELVIEW_MATRIX, (GLfloat *) views[i].model);
    }
    glLoadMatrixf((GLfloat *) views[CurrentView].model);
}

void GLLandscape::rotateX( int deg )
{
    static int oldDeg = 0;

    rotate( deg-oldDeg, XAxis );
    oldDeg = deg;
    updateGL();
}

void GLLandscape::rotateY( int deg )
{
    static int oldDeg = 0;

    rotate( deg-oldDeg, YAxis );
    oldDeg = deg;
    updateGL();
}

void GLLandscape::rotateZ( int deg )
{
    static int oldDeg = 0;

    rotate( deg-oldDeg, ZAxis );
    oldDeg = deg;
    updateGL();
}

void GLLandscape::zoom( int z )
{
    float zoom;
    if ( z < 100 ) {
	zoom = 1 + 4.99 - (z*5.0 / 100.0);
    } else {
	z = 200 - z;
	zoom = z / 100.0;
    }
    makeCurrent();
    glMatrixMode( GL_MODELVIEW );
    // Always scale the original model matrix
    glLoadMatrixf((GLfloat *) views[DefaultView].model);
    glScalef( zoom, zoom, zoom );
    glGetFloatv(GL_MODELVIEW_MATRIX, (GLfloat *) views[CurrentView].model);
    updateGL();
}

void GLLandscape::resetGrid()
{
    setGridSize( gridSize );
}

void GLLandscape::fractalize()
{
    Vector p;
    double value;
    double roughness = 0.5;
    int frequency    = 50;

    p.x = p.y = p.z = 0;
    // Initialise fbm routine
    if ( initFractals ) {
	initFractals = FALSE;
	value = fBm( p, roughness, 2.0, 8.0, 1 );
    }

    // Fractalize grid
    for ( int x = 0; x < gridSize; x++ ) {
	for ( int y = 0; y < gridSize; y++ ) {
	    p.x = (double) x / (101 - frequency);
	    p.y = (double) y / (101 - frequency);
	    p.z = (double) landscape[x][y] / (101 - frequency);
	    value = fBm(p, roughness, 2.0, 8.0, 0);
	    landscape[x][y] += value;
	}
    }
    calculateVertexNormals();
    updateGL();
}


//
// Calculate the vector cross product of v and w, store result in n.
//
static void crossProduct( double v[3], double w[3], double n[3] )
{
    n[0] = v[1]*w[2]-w[1]*v[2];
    n[1] = w[0]*v[2]-v[0]*w[2];
    n[2] = v[0]*w[1]-w[0]*v[1];
}

void GLLandscape::calculateVertexNormals()
{
    double len, v[3], v2[3], w[3], w2[3], n[3], n2[3];

    // Calculate the surface normals for all polygons in the
    // height field
    for ( int i = 0; i < (gridSize-1); i++ )
	for ( int k = 0; k < (gridSize-1); k++ ) {
	    /* Lower poly normal */
	    v[0] = 1; // (i+1)-i
	    v[1] = 0; // k-k
	    v[2] = landscape[i+1][k]-landscape[i][k];
	    w[0] = 1; // (i+1)-i
	    w[1] = 1; // (k+1)-k
	    w[2] = landscape[i+1][k+1]-landscape[i][k];
	    crossProduct( v, w, n );
	    len = sqrt(n[0]*n[0]+n[1]*n[1]+n[2]*n[2]);
	    normals[i][k].l[0] = n[0]/len;
	    normals[i][k].l[1] = n[1]/len;
	    normals[i][k].l[2] = n[2]/len;

	    /* Upper poly normal */
	    v2[0] = -1.0; // i-(i+1);
	    v2[1] = 0.0;  // (k+1)-(k+1);
	    v2[2] = landscape[i][k+1]-landscape[i+1][k+1];
	    w2[0] = -1.0; // i-(i+1);
	    w2[1] = -1.0; // k-(k+1);
	    w2[2] = landscape[i][k]-landscape[i+1][k+1];
	    crossProduct( v2, w2, n2 );
	    len = sqrt(n2[0]*n2[0]+n2[1]*n2[1]+n2[2]*n2[2]);
	    normals[i][k].u[0] = n2[0]/len;
	    normals[i][k].u[1] = n2[1]/len;
	    normals[i][k].u[2] = n2[2]/len;
	}

    // Calculate proper vertex normals
    averageNormals();
}

void GLLandscape::averageNormals()
{
    // Calculate the average surface normal for a vertex based on
    // the normals of the surrounding polygons
    for ( int i = 0; i < gridSize; i++ )
	for ( int k = 0; k < gridSize; k++ ) {
	    if ( i > 0 && k > 0 && i < (gridSize-1) && k < (gridSize-1) ) {
		// For vertices that are *not* on the edge of the height field
		for ( int t = 0; t < 3; t++ ) // X, Y and Z components
		    vertexNormals[i][k].n[t] = ( normals[i][k].u[t] +
						 normals[i][k].l[t] +
						 normals[i][k-1].u[t] +
						 normals[i-1][k-1].u[t] +
						 normals[i-1][k-1].l[t] +
						 normals[i-1][k].l[t] )/6.0;
	    } else {
		// Vertices that are on the edge of the height field require
		// special attention..
		if ( i == 0 && k == 0 ) {
		    for ( int t = 0; t < 3; t++ )
			vertexNormals[i][k].n[t] = ( normals[i][k].u[t] +
						     normals[i][k].l[t] )/2.0;
		} else if ( i == gridSize-1 && k == gridSize-1 ) {
		    for ( int t = 0; t < 3; t++ )
			vertexNormals[i][k].n[t] = ( normals[i][k].u[t] +
						     normals[i][k].l[t] )/2.0;
		} else if ( i == gridSize-1) {
		    for ( int t = 0; t < 3; t++ )
			vertexNormals[i][k].n[t] = vertexNormals[i-1][k].n[t];
		} else if ( k == gridSize-1 ) {
		    for ( int t = 0; t < 3; t++ )
			vertexNormals[i][k].n[t] = vertexNormals[i][k-1].n[t];
		} else if ( k > 0 ) {
		    for ( int t = 0; t < 3; t++ )
			vertexNormals[i][k].n[t] = (normals[i][k].u[t] +
						    normals[i][k].l[t] +
			                            normals[i][k-1].u[t])/3.0;
		} else if ( i > 0 ) {
		    for ( int t = 0; t < 3; t++ )
			vertexNormals[i][k].n[t] = (normals[i][k].u[t] +
						    normals[i][k].l[t] +
			                            normals[i-1][k].l[t])/3.0;
		}
	    }
	}
}

void GLLandscape::setWireframe( int state )
{
    if ( state != 1 ) {
	// Enable line antialiasing
	makeCurrent();
	glEnable( GL_LINE_SMOOTH );
	glEnable( GL_BLEND );
	glDisable( GL_DEPTH_TEST );
	glDisable( GL_LIGHTING );
	glDisable( GL_NORMALIZE );

	glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );
	glHint( GL_LINE_SMOOTH_HINT, GL_DONT_CARE );

	mode = Wireframe;
	updateGL();
    }
}

void GLLandscape::setFilled( int state )
{
    if ( state != 1 ) {
	makeCurrent();
	glEnable( GL_DEPTH_TEST );
	glDisable( GL_LINE_SMOOTH );
	glDisable( GL_BLEND );
	glDisable( GL_LIGHTING );
	glDisable( GL_NORMALIZE );

	mode = Filled;
	updateGL();
    }
}

void GLLandscape::setSmoothShaded( int state )
{
    if ( state != 1 ) {
	makeCurrent();
	glEnable( GL_DEPTH_TEST );
	glEnable( GL_LIGHTING );
	glEnable( GL_LIGHT0 );
	glEnable( GL_NORMALIZE );
	glDisable( GL_LINE_SMOOTH );
	glDisable( GL_BLEND );

	glShadeModel( GL_SMOOTH );
	glLightModeli( GL_LIGHT_MODEL_LOCAL_VIEWER, GL_TRUE );

	// Setup lighting and material properties
	GLfloat position[] = { 15.0, -15.0, 15.0, 0.0 };
	GLfloat ambient[]  = { 0.50, 0.50, 0.50, 0.0 };
	GLfloat diffuse[]  = { 1.00, 1.00, 1.00, 0.0 };
	GLfloat specular[] = { 1.0, 1.0, 1.0, 0.0 };
	GLfloat materialAmbient[]   = { 0.00, 0.00, 1.0, 0.0 };
        // GLfloat materialDiffuse[]   = { 1.00, 1.00, 1.0, 0.0 };
	GLfloat materialShininess[] = { 128.0 };
	GLfloat materialSpecular[]  = { 1.0, 1.0, 1.0, 0.0 };

	glMaterialfv( GL_FRONT, GL_SPECULAR, materialSpecular );
        // glMaterialfv( GL_FRONT, GL_DIFFUSE, materialDiffuse );
	glMaterialfv( GL_FRONT, GL_AMBIENT, materialAmbient );
	glMaterialfv( GL_FRONT, GL_SHININESS, materialShininess );

	glLightfv( GL_LIGHT0, GL_POSITION, position );
	glLightfv( GL_LIGHT0, GL_AMBIENT, ambient );
	glLightfv( GL_LIGHT0, GL_DIFFUSE, diffuse );
	glLightfv( GL_LIGHT0, GL_SPECULAR, specular );

	mode = SmoothShaded;
	calculateVertexNormals();
	updateGL();
    }
}

void GLLandscape::setLandscape( int state )
{
    if ( state != 1 ) {
	makeCurrent();
	glEnable( GL_DEPTH_TEST );
	glEnable( GL_LIGHTING );
	glEnable( GL_LIGHT0 );
	glEnable( GL_NORMALIZE );
	glDisable( GL_LINE_SMOOTH );
	glDisable( GL_BLEND );

	glShadeModel( GL_SMOOTH );
	glLightModeli( GL_LIGHT_MODEL_LOCAL_VIEWER, GL_TRUE );

	// Setup lighting and material properties
	GLfloat position[] = { 15.0, -15.0, 15.0, 0.0 };
	GLfloat ambient[]  = { 0.50, 0.50, 0.50, 0.0 };
	GLfloat diffuse[]  = { 1.00, 1.00, 1.00, 0.0 };
	GLfloat specular[] = { 1.0, 1.0, 1.0, 0.0 };
	
	glLightfv( GL_LIGHT0, GL_POSITION, position );
	glLightfv( GL_LIGHT0, GL_AMBIENT, ambient );
	glLightfv( GL_LIGHT0, GL_DIFFUSE, diffuse );
	glLightfv( GL_LIGHT0, GL_SPECULAR, specular );

	glBlendFunc(GL_SRC_ALPHA, GL_SRC_COLOR );

	mode = Landscape;
	calculateVertexNormals();
	updateGL();
    }
}

void GLLandscape::mousePressEvent( QMouseEvent *e )
{
    oldPos = e->pos();
    mouseButtonDown = TRUE;
}

void GLLandscape::mouseReleaseEvent( QMouseEvent *e )
{
    oldPos = e->pos();
    mouseButtonDown = FALSE;
}

void GLLandscape::mouseMoveEvent( QMouseEvent *e )
{
    GLfloat rx = (GLfloat) (e->x() - oldPos.x()) / width();
    GLfloat ry = (GLfloat) (e->y() - oldPos.y()) / height();

    if ( e->state() == LeftButton ) {
	// Left button down - rotate around X and Y axes
	oldX = 180*ry;
	oldY = 180*rx;
	rotate( oldX, XAxis );
	rotate( oldY, YAxis );
	updateGL();
    } else if ( e->state() == RightButton ) {
	// Right button down - rotate around X and Z axes
	oldX = 180*ry;
	oldZ = 180*rx;
	rotate( oldX, XAxis );
	rotate( oldZ, ZAxis );
	updateGL();
    }
    oldPos = e->pos();
}

void GLLandscape::timerEvent( QTimerEvent * )
{
    int dx, dy; // disturbance point
    float s, v, W, t;
    int i, j;

    if ( mode == Landscape ) {
	dx = dy = 0;
    } else {
	dx = dy = gridSize >> 1;
    }
    W = 0.3;
    v = -4; // wave speed

    for ( i = 0; i < gridSize; i++ )
	for ( j = 0; j < gridSize; j++ )
	{
	    s = sqrt( (double) ( (j - dx) * (j - dx) + (i - dy) * (i - dy) ) );
	    wt[i][j] += 0.1;
	    t = s / v;

	    if ( mode == Landscape ) {
		if ( (landscape[i][j] + wave[i][j]) < 0 )
		    landscape[i][j] -= wave[i][j];
		if ( (dy - j != 0) || (dx - i != 0) )
		    wave[i][j] = (3 * sin( 2 * PI * W * (wt[i][j] + t ))) / 
				 (0.2*(sqrt( pow((double)(dx-i), 2) + pow((double)(dy-j), 2))+2));
		else
		    wave[i][j] = ( 3 * sin( 2 * PI * W * ( wt[i][j] + t ) ) );
		if ( landscape[i][j] + wave[i][j] < 0 )
		    landscape[i][j] += wave[i][j];

	    } else {
		landscape[i][j] -= wave[i][j];

		if ( s != 0 )
		    wave[i][j] = 2 * sin(2 * PI * W * ( wt[i][j] + t )) / 
				 (0.2*(s + 2));
		else
		    wave[i][j] = 2 * sin( 2 * PI * W * ( wt[i][j] + t ) );
		landscape[i][j] += wave[i][j];
	    }
	    
	}
    if ( mode == SmoothShaded || mode == Landscape )
	calculateVertexNormals();
    updateGL();
}

void GLLandscape::toggleWaveAnimation( bool state )
{
    if ( state ) {
 	startTimer( 20 );
	animationRunning = TRUE;
    } else {
	killTimers();
	animationRunning = FALSE;
    }
}

void GLLandscape::showEvent( QShowEvent * )
{
    if ( animationRunning )
 	startTimer( 20 );	
}

void GLLandscape::hideEvent( QHideEvent * )
{
    if ( animationRunning )
	killTimers();
}
