/****************************************************************************

 Copyright (C) 2002-2014 Gilles Debunne. All rights reserved.

 This file is part of the QGLViewer library version 2.6.2.

 http://www.libqglviewer.com - contact@libqglviewer.com

 This file may be used under the terms of the GNU General Public License 
 versions 2.0 or 3.0 as published by the Free Software Foundation and
 appearing in the LICENSE file included in the packaging of this file.
 In addition, as a special exception, Gilles Debunne gives you certain 
 additional rights, described in the file GPL_EXCEPTION in this package.

 libQGLViewer uses dual licensing. Commercial/proprietary software must
 purchase a libQGLViewer Commercial License.

 This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
 WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.

*****************************************************************************/

// TP OpenGL: Joerg Liebelt, Serigne Sow

// Required to have glLockArraysEXT
#define GL_GLEXT_PROTOTYPES

#include "water.h"
#include <qimage.h>


//initialiser le "filet" des vertex d'eau
void WATER::Init(float myWorldSize, float scaleHeight)
{
  Vec dx, dy;
  int j, k, x, z, *indexPtr;

  worldSize= myWorldSize;
  numIndices = SQR( ( WATER_RESOLUTION-1 ) )*6;
  numVertices= SQR( WATER_RESOLUTION );

  //calculer la position du premier vertex
  dx[0]= worldSize/( WATER_RESOLUTION-1 );
  dx[1]= 0.0f;
  dx[2]= 0.0f/( WATER_RESOLUTION-1 );
  dy[0]= 0.0f/( WATER_RESOLUTION-1 );
  dy[1]= 0.0f;
  dy[2]= worldSize/( WATER_RESOLUTION-1 );

  for( j=0; j<WATER_RESOLUTION; j++ )
    {
      for( k=0; k<WATER_RESOLUTION; k++ )
	{
	  //positionner chaque vertex
	  vertArray[( j*WATER_RESOLUTION )+k].setValue( 0.0f + dx[0]*k + dy[0]*j,	//-1.0f
							(0.0f + dx[1]*k + dy[1]*j)*scaleHeight,												0.0f + dx[2]*k + dy[2]*j );	//-1.0f
	}
    }

  //calculer les indices des polygons
  x= 0;
  z= WATER_RESOLUTION;
  indexPtr= polyIndexArray;
  for( j=0; j<WATER_RESOLUTION-1; j++ )
    {
      for( k=0; k<WATER_RESOLUTION-1; k++ )
	{
	  indexPtr[0]= x;
	  indexPtr[1]= x+1;
	  indexPtr[2]= z;
	  indexPtr[3]= z;
	  indexPtr[4]= x+1;
	  indexPtr[5]= z+1;

	  indexPtr+= 6;
	  x++;
	  z++;
	}
      x++;
      z++;
    }

  //lancer une vague a partir d'une position aleatoire dans l'eau par changement de valeur d'hauteur
  vertArray[rand( )%( SQR( WATER_RESOLUTION ) )][1]= 1.0f*scaleHeight*1.5;	//quelques vagues...
}

//mise a jour des vertex, pas de temps = delta
void WATER::Update(float delta)
{
  float d, tempF, vert;
  int x, z;

  //calculer les forces qui influencent l'eau a chaque position
  for( z=1; z<WATER_RESOLUTION-1; z++ )
    {
      for( x=1; x<WATER_RESOLUTION-1; x++ )
	{
	  //on a les forces suivantes autour d'une position de l'eau
	  //
	  //    X | X | X
	  //   -----------
	  //    X | 0 | X
	  //   -----------
	  //    X | X | X
	  //
	  tempF= forceArray[( z*WATER_RESOLUTION )+x];
	  vert= vertArray[( z*WATER_RESOLUTION )+x][1];

	  //difference d'hauteur = force (simplification d'une vague...)

	  //bas
	  d= vert - vertArray[( ( z-1 )*WATER_RESOLUTION )+x][1];
	  forceArray[( ( z-1 )*WATER_RESOLUTION )+x]+= d;
	  tempF-= d;

	  //gauche
	  d= vert- vertArray[( z*WATER_RESOLUTION )+( x-1 )][1];
	  forceArray[( z*WATER_RESOLUTION )+( x-1 )]+= d;
	  tempF-= d;

	  //haut
	  d= ( vert- vertArray[( ( z+1 )*WATER_RESOLUTION )+x][1] );
	  forceArray[( ( z+1 )*WATER_RESOLUTION )+x]+= d;
	  tempF-= d;

	  //droite
	  d= ( vert- vertArray[( z*WATER_RESOLUTION )+( x+1 )][1] );
	  forceArray[( z*WATER_RESOLUTION )+( x+1 )]+= d;
	  tempF-= d;

	  //haut droite
	  d= ( vert- vertArray[( ( z+1 )*WATER_RESOLUTION )+( x+1 )][1] )*5;
	  forceArray[( ( z+1 )*WATER_RESOLUTION )+( x+1 )]+= d;
	  tempF-= d;

	  //bas gauche
	  d= ( vert- vertArray[( ( z-1 )*WATER_RESOLUTION )+( x-1 )][1] )*5;
	  forceArray[( ( z-1 )*WATER_RESOLUTION )+( x-1 )]+= d;
	  tempF-= d;

	  //bas droite
	  d= ( vert- vertArray[( ( z-1 )*WATER_RESOLUTION )+( x+1 )][1] )*5;
	  forceArray[( ( z-1 )*WATER_RESOLUTION )+( x+1 )]+= d;
	  tempF-= d;

	  //haut gauche
	  d= ( vert- vertArray[( ( z+1 )*WATER_RESOLUTION )+( x-1 )][1] )*5;
	  forceArray[( ( z+1 )*WATER_RESOLUTION )+( x-1 )]+= d;
	  tempF-= d;

	  forceArray[( z*WATER_RESOLUTION )+x]= tempF;
	}
    }

  //calculer les vitesses (en fct. de la force et du temps qui s'est ecoule
  for (x=0; x<numVertices; x++)
    {
      velArray[x]+= ( forceArray[x]*delta );
      vertArray[x][1]+= velArray[x];
      forceArray[x]= 0.0f;
    }
}

//calculer les normales pour chaque vertex (utilise pour l'ombrage/reflections)
// .. etant la somme des normales des vertex autour; principe "l'hauteur se translate en direction" (ca pousse..)
void WATER::CalcNormals()
{
  float tmpf;
  int i, j;

  for( i=0; i<WATER_RESOLUTION; i++ )
    {
      for( j=0; j<WATER_RESOLUTION; j++ )
	{
	  //initialiser
	  normalArray[(i*WATER_RESOLUTION)+j][0]= 0.0f;
	  normalArray[(i*WATER_RESOLUTION)+j][1]= 1.0f;
	  normalArray[(i*WATER_RESOLUTION)+j][2]= 0.0f;

	  //4 "particules d'eau" au-dessus
	  if( i!=0 )
	    {
	      if( j!=0 )
		{
		  normalArray[(i*WATER_RESOLUTION)+j][0]+= 												-vertArray[((i-1)*WATER_RESOLUTION)+j-1][1];
		  normalArray[(i*WATER_RESOLUTION)+j][2]+=
		    -vertArray[((i-1)*WATER_RESOLUTION)+j-1][1];
		}
	      else	//cas de bord
		{
		  normalArray[(i*WATER_RESOLUTION)+j][0]+=
		    -vertArray[((i-1)*WATER_RESOLUTION)+j][1];
		  normalArray[(i*WATER_RESOLUTION)+j][2]+=
		    -vertArray[((i-1)*WATER_RESOLUTION)+j][1];
		}

	      normalArray[(i*WATER_RESOLUTION)+j][0] +=
		-vertArray[((i-1)*WATER_RESOLUTION)+j][1]*2.0f;

	      if( j!=( WATER_RESOLUTION-1 ) )
		{
		  normalArray[(i*WATER_RESOLUTION)+j][0]+=
		    -vertArray[((i-1)*WATER_RESOLUTION)+j+1][1];
		  normalArray[(i*WATER_RESOLUTION)+j][2]+=
		    vertArray[((i-1)*WATER_RESOLUTION)+j+1][1];
		}
	      else  //cas de bord
		{
		  normalArray[(i*WATER_RESOLUTION)+j][0]+=
		    -vertArray[((i-1)*WATER_RESOLUTION)+j][1];
		  normalArray[(i*WATER_RESOLUTION)+j][2]+=
		    vertArray[((i-1)*WATER_RESOLUTION)+j][1];
		}
	    }
	  else //cas de bord
	    {
	      normalArray[(i*WATER_RESOLUTION)+j][0]+= -vertArray[(i*WATER_RESOLUTION)+j][1]*4.0f;
	      normalArray[(i*WATER_RESOLUTION)+j][2]+= 0;
	    }

	  //autres deux cas
	  if( j!=0 )
	    normalArray[(i*WATER_RESOLUTION)+j][2]+= -vertArray[(i*WATER_RESOLUTION)+j-1][1]*2.0f;
	  else
	    normalArray[(i*WATER_RESOLUTION)+j][2]+= -vertArray[(i*WATER_RESOLUTION)+j][1]*2.0f;

	  if( j!=( WATER_RESOLUTION-1 ) )
	    normalArray[(i*WATER_RESOLUTION)+j][2]+= vertArray[(i*WATER_RESOLUTION)+j+1][1]*2.0f;
	  else
	    normalArray[(i*WATER_RESOLUTION)+j][2]+= vertArray[(i*WATER_RESOLUTION)+j][1]*2.0f;

	  //4 "particules d'eau" au-dessous
	  if( i!=( WATER_RESOLUTION-1 ) )
	    {
	      if( j!=0 )
		{
		  normalArray[(i*WATER_RESOLUTION)+j][0]+=
		    vertArray[((i+1)*WATER_RESOLUTION)+j-1][1];
		  normalArray[(i*WATER_RESOLUTION)+j][2]+=
		    -vertArray[((i+1)*WATER_RESOLUTION)+j-1][1];
		}
	      else
		{
		  normalArray[(i*WATER_RESOLUTION)+j][0]+=
		    vertArray[((i+1)*WATER_RESOLUTION)+j][1];
		  normalArray[(i*WATER_RESOLUTION)+j][2]+=
		    -vertArray[((i+1)*WATER_RESOLUTION)+j][1];
		}

	      normalArray[(i*WATER_RESOLUTION)+j][0] +=
		vertArray[((i+1)*WATER_RESOLUTION)+j][1]*2.0f;

	      if( j!=WATER_RESOLUTION-1 )
		{
		  normalArray[(i*WATER_RESOLUTION)+j][0] +=
		    vertArray[((i+1)*WATER_RESOLUTION)+j+1][1];
		  normalArray[(i*WATER_RESOLUTION)+j][2] +=
		    vertArray[((i+1)*WATER_RESOLUTION)+j+1][1];
		}
	      else
		{
		  normalArray[(i*WATER_RESOLUTION)+j][0] +=
		    vertArray[((i+1)*WATER_RESOLUTION)+j][1];
		  normalArray[(i*WATER_RESOLUTION)+j][2] +=
		    vertArray[((i+1)*WATER_RESOLUTION)+j][1];
		}
	    }
	  else	//CAS DE BORD
	    {
	      normalArray[(i*WATER_RESOLUTION)+j][0]+= vertArray[(i*WATER_RESOLUTION)+j][1]*4.0f;
	      normalArray[(i*WATER_RESOLUTION)+j][2]+= 0;
	    }

	  //normaliser, creer la normale a partir des sommes d'influences autour
	  tmpf= 1.0f/( float )sqrt(
				   normalArray[(i*WATER_RESOLUTION)+j][0]*normalArray[(i*WATER_RESOLUTION)+j][0] +
				   normalArray[(i*WATER_RESOLUTION)+j][2]*normalArray[(i*WATER_RESOLUTION)+j][2] +
				   1.0f );
	  normalArray[(i*WATER_RESOLUTION)+j][0]*= tmpf;
	  normalArray[(i*WATER_RESOLUTION)+j][1]*= tmpf;
	  normalArray[(i*WATER_RESOLUTION)+j][2]*= tmpf;
	}
    }
}

//rendering de l'eau
void WATER::Render()
{
  //activer la texture de l'eau
  glBindTexture( GL_TEXTURE_2D, refmapID );
  glEnable( GL_TEXTURE_2D );

  //cette fois, on teste "spere mapping" de opengl pour calculer automatiquement des coord. des textures...
  //.. opengl calcule automatiquement les correspondances pq. les textures seront transformer en "sphere"
  //.. ce qui donne l'impression de distortions des reflections par les vagues
  glTexGeni( GL_S, GL_TEXTURE_GEN_MODE, GL_SPHERE_MAP );
  glTexGeni( GL_T, GL_TEXTURE_GEN_MODE, GL_SPHERE_MAP );
  glTexGeni( GL_R, GL_TEXTURE_GEN_MODE, GL_SPHERE_MAP );

  glEnable( GL_BLEND );
  glBlendFunc( GL_SRC_ALPHA, GL_ONE );		//fct. de transparence

  glColor4f(color.red()/255.0, color.green()/255.0, color.blue()/255.0, qAlpha(color.rgb())/255.0);

  //utiliser opengl vertex buffer pointer: actualisation des positions en temps reel (pseudo-tr)
  glEnableClientState( GL_VERTEX_ARRAY );
  glVertexPointer( 3, GL_FLOAT, sizeof( Vec ), &vertArray );

  //utiliser opengl normal buffer pointer: actualisation de la lumiere en temps reel (pseudo-tr)
  glEnableClientState( GL_NORMAL_ARRAY );
  glNormalPointer( GL_FLOAT, sizeof( Vec ), &normalArray );


  //activer generation autmatique de coord. de textures
  glEnable( GL_TEXTURE_GEN_S );
  glEnable( GL_TEXTURE_GEN_T );
  glEnable( GL_TEXTURE_GEN_R );

  //ici, transmission des vertex listes vers la memoire; on passe la liste des vertex sur laquelle on a cree
  //un pointeur avec glVertexPointer; acces exlusif pendant l'operation
  //glLockArraysEXT( 0, numVertices );

  //dessiner l'eau
  glDrawElements( GL_TRIANGLES, numIndices, GL_UNSIGNED_INT, polyIndexArray );

  //glUnlockArraysEXT( );

  //desactiver vertex arrays
  glDisableClientState( GL_VERTEX_ARRAY );
  glDisableClientState( GL_NORMAL_ARRAY );

  glDisable( GL_TEXTURE_2D );
  glDisable( GL_BLEND );

  //desactiver generation automatique de coord. de textures
  glDisable( GL_TEXTURE_GEN_S );
  glDisable( GL_TEXTURE_GEN_T );
  glDisable( GL_TEXTURE_GEN_R );
}

//le reflection map donne l'impression qu'on voit des reflections sur la surface avec distortion par les vagues;
//.. cependant, les reflections sont statiques et ne sont pas actualisees en fct. de la vraie pos. de la lumiere...
void WATER::LoadReflectionMap(const QString& filename)
{
  QImage image;
  if (image.load(filename))
    {
      QImage temp = QGLWidget::convertToGLFormat(image);

      //construire les textures openGL
      glGenTextures( 1, &refmapID );
      glBindTexture( GL_TEXTURE_2D, refmapID );
      glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR );
      glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
      gluBuild2DMipmaps( GL_TEXTURE_2D, GL_RGBA, temp.width(), temp.height(),
			 GL_RGBA, GL_UNSIGNED_BYTE, temp.bits() );
    }
  else
    qWarning("Reflection map loading failed");
}
