/**************************************************************************** Copyright (C) 2002-2008 Gilles Debunne. All rights reserved. This file is part of the QGLViewer library version 2.3.1. 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 #include <stdio.h> #include <math.h> #include <qfile.h> #include <time.h> #include "terrain.h" bool TERRAIN::LoadHeightMap(const QString& filename, int size) { if( heightMap.arrayHeightMap ) UnloadHeightMap( ); QFile pFile(filename); if (!pFile.open(QIODevice::ReadOnly | QIODevice::Text)) return false; heightMap.arrayHeightMap = new unsigned char [size*size]; if( heightMap.arrayHeightMap==NULL ) return false; //lire la carte d'hauteurs, format RAW QDataStream in(&pFile); for (int i=0; i<sizeHeightMap*sizeHeightMap; i++) in >> heightMap.arrayHeightMap[i]; pFile.close(); sizeHeightMap = size; return true; } bool TERRAIN::SaveHeightMap(const QString& filename ) { QFile pFile(filename); if (!pFile.open(QIODevice::WriteOnly | QIODevice::Text)) return false; if( heightMap.arrayHeightMap==NULL ) return false; QDataStream out(&pFile); for (int i=0; i<sizeHeightMap*sizeHeightMap; i++) out << heightMap.arrayHeightMap[i]; pFile.close(); return true; } bool TERRAIN::UnloadHeightMap( void ) { if( heightMap.arrayHeightMap ) { delete[] heightMap.arrayHeightMap; sizeHeightMap= 0; } return true; } void TERRAIN::Smooth1DTerrain(float* heightData, int kernelSize) //filtrage unidirectionel (moins lisse) { int i,j,kernelPos,effectSize,base; float sum; float* temp = new float [sizeHeightMap*sizeHeightMap]; for (i=0;i<sizeHeightMap*sizeHeightMap; i++ ) { j = 0; sum = 0; effectSize=0; base = i-(kernelSize/2*sizeHeightMap); do { kernelPos = base + j*sizeHeightMap; if (kernelPos>=0 && kernelPos < sizeHeightMap*sizeHeightMap) { sum += heightData[kernelPos]; effectSize++; } j++; } while (j<kernelSize); temp[i]=(float)sum/effectSize; } for (i=0;i<sizeHeightMap*sizeHeightMap; i++ ) heightData[i]=temp[i]; delete temp; } void TERRAIN::SmoothTerrain(float* heightData, int kernelSize) //filtrage 2D moyenne kernelSize^2, bords inclus { int i,j,kernelPos,effectSize,base; float sum; float* temp = new float [sizeHeightMap*sizeHeightMap]; for (i=0;i<sizeHeightMap*sizeHeightMap; i++ ) { j = 0; sum = 0; effectSize=0; base = i-(kernelSize/2*sizeHeightMap)-(kernelSize/2); do { kernelPos = base + (j%kernelSize) + (j/kernelSize)*sizeHeightMap; if (kernelPos>=0 && kernelPos < sizeHeightMap*sizeHeightMap) { sum += heightData[kernelPos]; effectSize++; } j++; } while (j<kernelSize*kernelSize); temp[i]=(float)sum/effectSize; } for (i=0;i<sizeHeightMap*sizeHeightMap; i++ ) heightData[i]=temp[i]; delete[] temp; } void TERRAIN::NormalizeTerrain( float* heightData ) { float min, max; float height; int i; min= heightData[0]; max= heightData[0]; //chercher minimum et maximum des donnees for( i=1; i<sizeHeightMap*sizeHeightMap; i++ ) { if( heightData[i]>max ) max= heightData[i]; else if( heightData[i]<min ) min= heightData[i]; } //si terrain est plat: rien a faire if( max<=min ) return; height= max-min; //scaler les valeurs a une echelle de 0..255 for( i=0; i<sizeHeightMap*sizeHeightMap; i++ ) heightData[i]= ( ( heightData[i]-min )/height )*255.0f; } bool TERRAIN::MakeTerrainFault( int size, int iterations, int min, int max, int smooth ) { float* tempBuffer; int currentIteration; int height; int randX1, randZ1; int randX2, randZ2; int dirX1, dirZ1; int dirX2, dirZ2; int x, z; int i; srand( time(NULL) ); if( heightMap.arrayHeightMap ) UnloadHeightMap( ); sizeHeightMap= size; //reserver de la memoire heightMap.arrayHeightMap = new unsigned char [sizeHeightMap*sizeHeightMap]; tempBuffer= new float [sizeHeightMap*sizeHeightMap]; if( heightMap.arrayHeightMap==NULL ) { return false; } if( tempBuffer==NULL ) { return false; } for( i=0; i<sizeHeightMap*sizeHeightMap; i++ ) tempBuffer[i]= 0; for( currentIteration=0; currentIteration<iterations; currentIteration++ ) { //choisir la hauteur pour cette traversee avec interp. lineaire, //..on reduit la taille des changements effectues avec chaque iteration (grossier->detaille) //.. meme si toujours le meme point est choisi, on ne depasse pas la hauteur max. height= max - ( ( max-min )*currentIteration )/iterations; //choisir deux points de la carte aleatoirement randX1= rand( )%sizeHeightMap; randZ1= rand( )%sizeHeightMap; //...tant qu'ils sont differents do { randX2= rand( )%sizeHeightMap; randZ2= rand( )%sizeHeightMap; } while ( randX2==randX1 && randZ2==randZ1 ); //le vecteur de la ligne aleatoire qui divise la carte en deux dirX1= randX2-randX1; dirZ1= randZ2-randZ1; for( z=0; z<sizeHeightMap; z++ ) { for( x=0; x<sizeHeightMap; x++ ) { //vecteur de position du point traite actuellement dirX2= x-randX1; dirZ2= z-randZ1; //utiliser le signe du produit croise pour decide si on exhausse la hauteur de ce point if( ( dirX2*dirZ1 - dirX1*dirZ2 )>0 ) tempBuffer[( z*sizeHeightMap )+x]+= ( float )height; } } //effectuer erosion SmoothTerrain(tempBuffer,smooth); //MOYENNE //Smooth1DTerrain(tempBuffer,smooth); } //adapter l'echelle de valeurs NormalizeTerrain( tempBuffer ); //sauvegarder le terrain cree dans la variable de la classe TERRAIN, //.. on n'a plus besoin de la precision float for( z=0; z<sizeHeightMap; z++ ) { for( x=0; x<sizeHeightMap; x++ ) SetHeightAtPoint( ( unsigned char )tempBuffer[( z*sizeHeightMap )+x], x, z ); } if( tempBuffer ) { delete[] tempBuffer; } return true; } //calculer combien de pourcent de visibilite un type de texture devrait occuper a une certaine hauteur float TERRAIN::RegionPercent( int type, unsigned char height ) { float temp1, temp2; //afficher la texture la plus basse avec luminosite maximale au-dessous de la hauteur optimale //.. cad. on a pas de texture correspondant a cette hauteur // .. et il se peut qu'on a pas charge toutes les textures... if( !textures.data[LOWEST].isNull( ) ) { if( type==LOWEST && height<textures.region[LOWEST].optimalHeight ) return 1.0f; } else if( !textures.data[MEDIUM].isNull( ) ) { if( type==MEDIUM && height<textures.region[MEDIUM].optimalHeight ) return 1.0f; } else if( !textures.data[HIGH].isNull( ) ) { if( type==HIGH && height<textures.region[HIGH].optimalHeight ) return 1.0f; } else if( !textures.data[HIGHEST].isNull( ) ) { if( type==HIGHEST && height<textures.region[HIGHEST].optimalHeight ) return 1.0f; } //hauteur est au-dessous de l'intervalle d'affichage de la texture souhaite: on n'en utilise rien if( height<textures.region[type].lowHeight ) return 0.0f; //hauteur est au-dessus de l'intervalle d'affichage de la texture souhaite: on n'en utilise rien else if( height>textures.region[type].highHeight ) return 0.0f; //hauteur est au-dessous de la hauteur optimale: on calcule le pourcentage d'utilisation if( height<textures.region[type].optimalHeight ) { temp1= ( float )height-textures.region[type].lowHeight; temp2= ( float )textures.region[type].optimalHeight-textures.region[type].lowHeight; //repartition: hauteur par rapport a la taille de l'intervalle return ( temp1/temp2 ); } //hauteur est egale a l'hauteur ideale: on l'affiche a luminosite maximale else if( height==textures.region[type].optimalHeight ) return 1.0f; //hauteur est au-dessus de la hauteur optimale: on calcule le pourcentage d'utilisation else if( height>textures.region[type].optimalHeight ) { temp1= ( float )textures.region[type].highHeight-textures.region[type].optimalHeight; return ( ( temp1-( height-textures.region[type].optimalHeight ) )/temp1 ); } //default return 0.0f; } // on repete la texture plusieurs fois si on veut remplir un terrain plus grand que la texture elle-meme; // .. afin de savoir quelle pixel de la texture a afficher a une position sur dans le terrain // .. (en tenant compte des repetitions et du fait qu'un pixel de la texture doit harmoniser avec ses voisins),.. // .. on teste combien de repetitions ont deja ete faites et on renvoie la position locale a utiliser sur la texture void TERRAIN::GetTexCoords( QImage texture, unsigned int* x, unsigned int* y ) { unsigned int width = texture.width( ); unsigned int height= texture.height( ); int repeatX= -1; int repeatY= -1; int i= 0; //determiner nombre de repetitions de la texture en x while( repeatX==-1 ) { i++; if( *x<( width*i ) ) repeatX= i-1; } i= 0; //determiner nombre de repetitions de la texture en y while( repeatY==-1 ) { i++; if( *y<( height*i ) ) repeatY= i-1; } //on renvoie la position locale contenant le pixel a utiliser sur la texture *x= *x-( width*repeatX ); *y= *y-( height*repeatY ); } //a l'interieur de chaque texture, on tient compte des variations d'hauteur //.. pour ne pas avoir des regions de couleur identique bien que la hauteur varie //.. pour cela, on doit savoir la hauteur interpole a chaque position // heightToTexRatio: relation taille de map d'hauteur / taille de texture unsigned char TERRAIN::InterpolateHeight( int x, int z, float heightToTexRatio ) { unsigned char Low, HighX, HighZ; float X, Z; float scaledX= x*heightToTexRatio; //echelle d'interpolation necessaire float scaledZ= z*heightToTexRatio; float interpolation; //borne inf Low= GetTrueHeightAtPoint( ( int )scaledX, ( int )scaledZ ); //borne sup x if( ( scaledX+1 )>sizeHeightMap ) return Low; else HighX= GetTrueHeightAtPoint( ( int )scaledX+1, ( int )scaledZ ); //valeur interpolee x interpolation= ( scaledX-( int )scaledX ); X= ( ( HighX- Low )*interpolation )+ Low; //borne sup z if( ( scaledZ+1 )>sizeHeightMap ) return Low; else HighZ= GetTrueHeightAtPoint( ( int )scaledX, ( int )scaledZ+1 ); //valeur interpolee z interpolation= ( scaledZ-( int )scaledZ ); Z= ( ( HighZ- Low )*interpolation )+ Low; //moyenne des deux ~= approx. de la vraie hauteur return ( ( unsigned char )( ( X+ Z )/2 ) ); } //creer une carte de texture en melangeant les quatres types de textures de base void TERRAIN::GenerateTextureMap( unsigned int size ) { unsigned char Red, Green, Blue; unsigned int tempID; unsigned int x, z; unsigned int TexX, TexZ; float totalRed, totalGreen, totalBlue; float blend[4]; float mapRatio; int lastHeight; int i; //determiner le nombre de textures de bases presentes textures.numTextures= 0; for( i=0; i<TRN_NUM_TILES; i++ ) { if( !textures.data[i].isNull( ) ) textures.numTextures++; } //determiner les intervalles d'affichage pour chaque type de texture de base lastHeight= -1; for( i=0; i<TRN_NUM_TILES; i++ ) { if( !textures.data[i].isNull( ) ) { //on a trois valeurs a determiner: inf, opt,sup textures.region[i].lowHeight=lastHeight+1; lastHeight+= 255/textures.numTextures; textures.region[i].optimalHeight=lastHeight; textures.region[i].highHeight= ( lastHeight-textures.region[i].lowHeight )+lastHeight; } } #if QT_VERSION < 0x040000 myTexture.create( size, size, 32 ); #else myTexture = QImage(size, size, QImage::Format_ARGB32); #endif //determiner relation entre resolution de la carte d'hauteur et la res. de la texture //.. en general, la texture aura une res. plus elevee mapRatio= ( float )sizeHeightMap/ size; //creation de texture for( z=0; z< size; z++ ) { for( x=0; x< size; x++ ) { totalRed = 0.0f; totalGreen= 0.0f; totalBlue = 0.0f; //pour chaque texture de base for( i=0; i<TRN_NUM_TILES; i++ ) { if( !textures.data[i].isNull( ) ) { TexX= x; TexZ= z; //quel pixel de texture a choisir pour cette position sur la carte? GetTexCoords( textures.data[i], &TexX, &TexZ ); //quelle est la couleur actuelle a cet endroit? GetColor( textures.data[i], TexX, TexZ, &Red, &Green, &Blue ); //combien de pourcent de cette texture de base faut-il ajouter a cet endroit? //.. on interpole la vraie hauteur pour avoir des textures plus realistes blend[i]= RegionPercent( i, Limit( InterpolateHeight( x, z, mapRatio ) ) ); //ajouter ce pourcentage a la couleur totalRed += Red*blend[i]; totalGreen+= Green*blend[i]; totalBlue += Blue*blend[i]; } } //modifier la couleur de la texture a cet endroit, limiter les valeurs a 0..255 SetColor( myTexture, x, z, Limit( totalRed ),Limit( totalGreen ),Limit( totalBlue ) ); } } //construire la texture glGenTextures( 1, &tempID ); glBindTexture( GL_TEXTURE_2D, tempID ); //utiliser la moyenne ponderee lineairement pour elargir ou reduire les textures glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR ); glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR ); //le plus grand mystere de tout le code: pour une raison completement pourrie, QImage inverse //.. les canaux rouge et bleu lors de l'ecriture dans l'image; mais si j'utilise QGLWidget::convertToGLFormat, //.. l'image est inversee; la solution swapRGB marche (apres 3h de recherche desesperee) pour une raison mysterieuse #if QT_VERSION < 0x040000 myTexture = myTexture.swapRGB(); #else myTexture = myTexture.rgbSwapped(); #endif //definir la source de donnees pour la texture glTexImage2D( GL_TEXTURE_2D, 0, GL_RGBA, size, size, 0, GL_RGBA, GL_UNSIGNED_BYTE, myTexture.bits() ); //associer un no. ID a la texture textureColorID = tempID; myTexture.save("texture.bmp","BMP"); } //on calcule les positions d'ombrages //..idee de la methode: si entre un point et la source de lumiere, dans la direction //..de la lumiere, il y a un autre element du terrain, ce point se trouve dans l'ombre //.. pour cela, on soustrait l'hauteur du vertex voisin de l'hauteur du vertex actuel //.. CEPENDANT, on est limite a des changements de position de lumiere par pas de 45° //.. et on ne tient pas compte de l'hauteur de la source de lumiere! (seulement le vertex directement a cote compte) void TERRAIN::CalculateLighting( void ) { float shade; int x, z; if( lightMap.sizeLightMap!=sizeHeightMap || lightMap.arrayLightMap==NULL ) { delete[] lightMap.arrayLightMap; lightMap.arrayLightMap= new unsigned char [sizeHeightMap*sizeHeightMap]; lightMap.sizeLightMap= sizeHeightMap; } //pour chaque vertex for( z=0; z<sizeHeightMap; z++ ) { for( x=0; x<sizeHeightMap; x++ ) { //pour ne pas depasser des bornes if( z>=directionZ && x>=directionX ) { //comparer les hauteurs, et on rend plus doux les frontieres //ici, on ne fait PAS de calcul genre "tracer les rayons"... shade= 1.0f-( GetTrueHeightAtPoint( x-directionX, z-directionZ ) - GetTrueHeightAtPoint( x, z ) )/lightSoftness; } else shade= 1.0f; if( shade<minBrightness ) shade= minBrightness; if( shade>maxBrightness ) shade= maxBrightness; SetBrightnessAtPoint( x, z, ( unsigned char )( shade*255 ) ); } } } //tourner la lumiere par un pas de 45° void TERRAIN::StepLightingDirection(void) { if ((directionX==-1)&&(directionZ==-1)) {directionX++;} else if ((directionX==0)&&(directionZ==-1)) {directionX++;} else if ((directionX==1)&&(directionZ==-1)) {directionZ++;} else if ((directionX==1)&&(directionZ==0)) {directionZ++;} else if ((directionX==1)&&(directionZ==1)) {directionX--;} else if ((directionX==0)&&(directionZ==1)) {directionX--;} else if ((directionX==-1)&&(directionZ==1)) {directionZ--;} else if ((directionX==-1)&&(directionZ==0)) {directionZ--;} } bool TERRAIN::LoadTexture( const QString& filename ) { int type; if (!myTexture.load(filename)) return false; QImage temp = QGLWidget::convertToGLFormat(myTexture); if( temp.depth()==24 ) //devrait etre toujours 32 type= GL_RGB; else type= GL_RGBA; //construire les textures openGL glGenTextures( 1, &textureColorID ); glBindTexture( GL_TEXTURE_2D, textureColorID ); glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR ); glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR ); glTexImage2D( GL_TEXTURE_2D, 0, type, temp.width(), temp.height(), 0, type, GL_UNSIGNED_BYTE, temp.bits() ); return true; } //besoin d'un fonctionnement different car mipmap utilise bool TERRAIN::LoadDetailMap( const QString& filename ) { int type; if (!myDetailMap.load(filename)) return false; QImage temp = QGLWidget::convertToGLFormat(myDetailMap); if( temp.depth()==24 ) //devrait etre toujours 32 type= GL_RGB; else type= GL_RGBA; //construire les textures openGL glGenTextures( 1, &textureDetailID ); glBindTexture( GL_TEXTURE_2D, textureDetailID ); 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, type, temp.width(), temp.height(), type, GL_UNSIGNED_BYTE, temp.bits() ); return true; }