Génération de terrain (2/2)
Par Xavier Michelon


 
   
Code Source


/************************************************************/
/*                        terrain2.c                        */
/************************************************************/
/* Generation de terrain correctement illumine a partir     */ 
/* d'une image JPEG                                         */
/************************************************************/



#include <GL/glut.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <jpeglib.h>
#include <jerror.h>
#include <math.h>

#define NB_SUBDIV_INIT 32
#define NB_SUBDIV_MAX 64
#define ECHELLE_VERT_INIT 0.1
#define ECHELLE_VERT_MAX 1.0
#define ECHELLE_NORMALES 0.1
#define DISTANCE_INIT 4.0
#define DISTANCE_MAX 15.0

/* Definition du type sommet */
typedef struct {
  float x;
  float y;
  float z;
  float nx;
  float ny;
  float nz;
  char nb;
} vertex;

/* Variables globales */

unsigned char image[256][256]; /* l'image du terrain */
unsigned char afficheRepere=TRUE; /* Affichage du repere */
unsigned char faceArriere=FALSE; /* Affichage des faces arrieres de polygones */
unsigned char areteTransv=FALSE; /* Affichage de l'arete transversale */
unsigned char afficheNormales=FALSE; /* Affichage des normales */
unsigned char afficheLampes=TRUE; /* Affichage des lampes */
unsigned char modePlein=TRUE; /* Affichage en mode plein ou fil de fer */
int repere=0,terrain=0,normales=0,lampes=0; /* Identifiants des listes d'affichage */
int nbSubdiv=NB_SUBDIV_INIT; /* Nombre de subdivisions du maillage */
float echelleVert=ECHELLE_VERT_INIT; /* echelle verticale du relief */
char b_gauche=0,b_droit=0; /* bouton de souris presse ? */
int theta=-30,phi=300; /* Position de l'observateur */
int xprec,yprec; /* sauvegarde de la position de la souris */
float distance=DISTANCE_INIT; /* distance de l'observateur a l'origine */

/* Parametres d'éclairage */
GLfloat L0pos[]={ 0.0,2.0,1.0};
GLfloat L0dif[]={ 0.3,0.3,0.8};
GLfloat L1pos[]={ 2.0,2.0,2.0};
GLfloat L1dif[]={ 0.5,0.5,0.5};
GLfloat Mspec[]={0.5,0.5,0.5};
GLfloat Mshiny=50;


/* Prototypes des fonctions */

void init();
void affichage();
void clavier(unsigned char touche,int x,int y);
void souris(int bouton,int etat,int x,int y);
void mouvement(int x,int y);
void redim(int l,int h);
void creeRepere();
void creeTerrain();
void creeNormales(vertex *T);
void creeLampes();
void drawVertex(int i,int j,vertex *T);
float elevation(int i,int j);
void loadJpegImage(char *filename);



int main(int argc,char **argv)
{
  /* Initialisation de glut */
  glutInit(&argc,argv);
  glutInitDisplayMode(GLUT_RGB | GLUT_DOUBLE | GLUT_DEPTH);
  glutInitWindowSize(500,500);
  glutCreateWindow(argv[0]);

  /*Initialisation d'OpenGL */
  init();
  loadJpegImage(argv[1]);

  /* Creation des objets */
  creeRepere();
  creeLampes();
  creeTerrain();

  glutMainLoop();
  return 0;
}



/* Initialisation d'openGL */
void init()
{
  glClearColor(0.8,0.8,0.8,1.0);
  glEnable(GL_DEPTH_TEST);
  if (modePlein)
    glPolygonMode(GL_FRONT_AND_BACK,GL_FILL);
  else
    glPolygonMode(GL_FRONT_AND_BACK,GL_LINE);
  glCullFace(GL_BACK);
  if (faceArriere)
    glDisable(GL_CULL_FACE);
  else
    glEnable(GL_CULL_FACE);

  /* Mise en place de la perspective */
  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();  
  gluPerspective(45.0,1.0,0.1,20.0);
  glMatrixMode(GL_MODELVIEW);  

  /* Parametrage de l'éclairage */
  glLightModeli(GL_LIGHT_MODEL_LOCAL_VIEWER,GL_TRUE);
  glEnable(GL_LIGHTING);
  glEnable(GL_LIGHT0);
  glEnable(GL_LIGHT1);
  glLightfv(GL_LIGHT0,GL_DIFFUSE,L0dif);
  glLightfv(GL_LIGHT0,GL_SPECULAR,L0dif);
  glLightfv(GL_LIGHT1,GL_DIFFUSE,L1dif);
  glLightfv(GL_LIGHT1,GL_SPECULAR,L1dif); 

  /* Paramétrage du matériau */
  glMaterialfv(GL_FRONT_AND_BACK,GL_SPECULAR,Mspec);
  glMaterialf(GL_FRONT_AND_BACK,GL_SHININESS,Mshiny);

  /* Mise en place des fonction de rappel */
  glutDisplayFunc(affichage);
  glutKeyboardFunc(clavier);
  glutMouseFunc(souris);
  glutMotionFunc(mouvement);
  glutReshapeFunc(redim);
}



/* Fonction de rappel pour l'affichage */
void affichage()
{
  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
  glLoadIdentity();
  gluLookAt(0.0,0.0,distance,0.0,0.0,0.0,0.0,1.0,0.0);
  glRotatef(phi,1.0,0.0,0.0);
  glRotatef(theta,0.0,0.0,1.0);
  glLightfv(GL_LIGHT0,GL_POSITION,L0pos);
  glLightfv(GL_LIGHT1,GL_POSITION,L1pos);
  if (afficheLampes)
    glCallList(lampes);
  glCallList(terrain);
  if (afficheNormales)
    glCallList(normales);
  if (afficheRepere)
    glCallList(repere);
  glutSwapBuffers();
}



/* Fonction de rappel pour le clavier */
void clavier(unsigned char touche,int x,int y)
{
  switch (touche) {
  case 27: /* touche 'ESC' pour quitter */
    exit(0);
  case '+': /* augmentation du nombre de subdivisions */
    nbSubdiv++;
    if (nbSubdiv>NB_SUBDIV_MAX)
      nbSubdiv=NB_SUBDIV_MAX;
    creeTerrain();
    glutPostRedisplay();
    break;
  case '-': /* diminution du nombre de subdivisions*/
    nbSubdiv--;
    if (nbSubdiv<1)
      nbSubdiv=1;
    creeTerrain();
    glutPostRedisplay();
    break;
  case 'p': /* augmentation de l'echelle verticale */
    echelleVert+=0.02;
    if (echelleVert>ECHELLE_VERT_MAX)
      echelleVert=ECHELLE_VERT_MAX;
    creeTerrain();
    glutPostRedisplay();
    break;
  case 'o': /* diminution de l'echelle verticale */
    echelleVert-=0.02;
    if (echelleVert<-ECHELLE_VERT_MAX)
      echelleVert=-ECHELLE_VERT_MAX;
    creeTerrain();
    glutPostRedisplay();
    break;
  case 'f':
    modePlein=1-modePlein;
    if (modePlein)
      glPolygonMode(GL_FRONT_AND_BACK,GL_FILL);
    else
      glPolygonMode(GL_FRONT_AND_BACK,GL_LINE);
    glutPostRedisplay();
    break;
  case 'n': /* Affichage des normales ON/OFF */
    afficheNormales=1-afficheNormales;
    glutPostRedisplay();
    break;
    
  case 'r': /* Affichage du repere ON/OFF */
    afficheRepere=1-afficheRepere;
    glutPostRedisplay();
    break;
  case 'l' : /* Affichage des lampes ON/OFF */
    afficheLampes=1-afficheLampes;
    glutPostRedisplay();
    break;
  case 'c': /* affichage des faces arrieres ON/OFF */
    faceArriere=1-faceArriere;
  if (faceArriere)
    glDisable(GL_CULL_FACE);
  else
    glEnable(GL_CULL_FACE);
  glutPostRedisplay();
  break;
  case 't': /* affichage des aretes transversales */
    areteTransv=1-areteTransv;
    creeTerrain();
    glutPostRedisplay();
    break;
  }
}



/* fonction de rappel pour l'appui sur les boutons de souris*/
void souris(int bouton,int etat,int x,int y)
{
  
  if (bouton == GLUT_LEFT_BUTTON && etat == GLUT_DOWN) {
    b_gauche = 1;
    xprec = x; 
    yprec=y;
  }
  if (bouton == GLUT_LEFT_BUTTON && etat == GLUT_UP) 
    b_gauche=0; 

  if (bouton == GLUT_RIGHT_BUTTON && etat == GLUT_DOWN) {
    b_droit = 1;
    yprec=y;
  }
  if (bouton == GLUT_RIGHT_BUTTON && etat == GLUT_UP) 
    b_droit=0; 
}



/* Fonction de rappel pour les mouvements de souris */
void mouvement(int x,int y)
{
  /* si le bouton gauche est presse */
  if (b_gauche) {
    theta+=x-xprec;
    if (theta>=360)
      while (theta>=360)
	theta-=360;
    phi+=y-yprec;
    if (phi<0)
      while (phi<0)
	phi+=360;
    xprec=x;
    yprec=y;
    glutPostRedisplay();
  }

  /* si le bouton droit est presse */
  if (b_droit) {
    distance+=((float)(y-yprec))/50.0;
    if (distance<1.0)
      distance=1.0;
    if (distance>DISTANCE_MAX)
      distance=DISTANCE_MAX;
    glutPostRedisplay();
    yprec=y;
  }
}



/* Fonction de rappel pour le redimensionnement de fenetre */
void redim(int l,int h)
{
  if (l<h)
    glViewport(0,(h-l)/2,l,l);
  else 
    glViewport((l-h)/2,0,h,h);
}



/* Creation de la liste d'affichage pour le repere */
void creeRepere()
{
   repere=glGenLists(1);
  glNewList(repere,GL_COMPILE);
  glDisable(GL_LIGHTING);
  glLineWidth(2.0);
    glBegin(GL_LINES);
      glColor3f(1.0,0.0,0.0);
      glVertex3f(0.0,0.0,0.0);
      glVertex3f(0.3,0.0,0.0);
      glColor3f(0.0,1.0,0.0);
      glVertex3f(0.0,0.0,0.0);
      glVertex3f(0.0,0.3,0.0);
      glColor3f(0.0,0.0,1.0);
      glVertex3f(0.0,0.0,0.0);
      glVertex3f(0.0,0.0,0.3);
    glEnd();
    glEnable(GL_LIGHTING);
  glEndList();
} 



/* Creation de la liste d'afffichage pour le terrain */
void creeTerrain()
{
  int i,j;
  float pas=2.0/nbSubdiv;
  vertex *T;
  vertex *P1,*P2,*P3,*P4;
  vertex V1,V2,V3;
  float incx,incy,incz,norme;
  T=(vertex *)malloc((nbSubdiv+1)*(nbSubdiv+1)*sizeof(vertex));

  /* Initialisation des normales */
  for (i=0;i<=nbSubdiv;i++)
    for (j=0;j<=nbSubdiv;j++) {
      T[i*(nbSubdiv+1)+j].nx=0.0;
      T[i*(nbSubdiv+1)+j].ny=0.0;
      T[i*(nbSubdiv+1)+j].nz=0.0;
    }
  /* remplissage du tableau Tvertex */
  for (i=0;i<=nbSubdiv;i++)
    for (j=0;j<=nbSubdiv;j++) {
      T[i*(nbSubdiv+1)+j].x=-1.0+i*pas;
      T[i*(nbSubdiv+1)+j].y=-1.0+j*pas;
      T[i*(nbSubdiv+1)+j].z=elevation(i,j);
    }
  for (i=0;i<nbSubdiv;i++)
    for (j=0;j<nbSubdiv;j++) {
      P1=&T[i*(nbSubdiv+1)+j];
      P2=&T[(i+1)*(nbSubdiv+1)+j];
      P3=&T[(i+1)*(nbSubdiv+1)+j+1];
      P4=&T[i*(nbSubdiv+1)+j+1];
      
      V1.x=P2->x-P1->x; V1.y=P2->y-P1->y; V1.z=P2->z-P1->z;
      V2.x=P3->x-P1->x; V2.y=P3->y-P1->y; V2.z=P3->z-P1->z;
      V3.x=P4->x-P1->x; V3.y=P4->y-P1->y; V3.z=P4->z-P1->z;

      incx=V2.y*V1.z-V1.y*V2.z;
      incy=V2.z*V1.x-V1.z*V2.x;
      incz=V2.x*V1.y-V1.x*V2.y;
      norme=sqrt(incx*incx+incy*incy+incz*incz);
      incx/=norme; incy/=norme; incz/=norme;
      P1->nx-=incx; P1->ny-=incy; P1->nz-=incz;
      P2->nx-=incx; P2->ny-=incy; P2->nz-=incz;
      P3->nx-=incx; P3->ny-=incy; P3->nz-=incz;
      

      incx=V3.y*V2.z-V2.y*V3.z;
      incy=V3.z*V2.x-V2.z*V3.x;
      incz=V3.x-V2.y-V2.x*V3.y;
      P1->nx-=incx; P1->ny-=incy; P1->nz-=incz;
      P3->nx-=incx; P3->ny-=incy; P3->nz-=incz;
      P4->nx-=incx; P4->ny-=incy; P4->nz-=incz;
    }
  
  /* normalisation des normales */
  for (i=0;i<=nbSubdiv;i++)
    for (j=0;j<=nbSubdiv;j++) {
      P1=&T[i*(nbSubdiv+1)+j];
      norme=sqrt(P1->nx*P1->nx+P1->ny*P1->ny+P1->nz*P1->nz);
      P1->nx/=norme;
      P1->ny/=norme;
      P1->nz/=norme;
    }
  
  /* Liste pour l'objet terrain */
  if (glIsList(terrain))
    glDeleteLists(terrain,1);
  terrain=glGenLists(1);
  glNewList(terrain,GL_COMPILE);
  glColor3f(0.0,0.0,0.0);
  glLineWidth(1.0);
  for (i=0;i<nbSubdiv;i++)
    for (j=0;j<nbSubdiv;j++) {
      glBegin(GL_TRIANGLES);
      /* triangle 1 */
      glEdgeFlag(TRUE);
      drawVertex(i,j,T);
      drawVertex(i+1,j,T);
      if (!areteTransv)
	glEdgeFlag(FALSE);
      drawVertex(i+1,j+1,T);

      /*triangle 2 */
      drawVertex(i,j,T);
      if (!areteTransv)
	glEdgeFlag(TRUE);
      drawVertex(i+1,j+1,T);
      drawVertex(i,j+1,T);
      glEnd();
    }
  glEndList();

  /* Generation de la liste d'affichage des normales*/
  creeNormales(T);
  
  /* Liberation de Tvertex */
  free(T);
}


/* Cree la liste d'affichage pour les normales */
void creeNormales(vertex *T)
{
  int i,j;
  vertex *P;
  if (glIsList(normales))
    glDeleteLists(normales,1);
  normales=glGenLists(1);
  glNewList(normales,GL_COMPILE);
  glDisable(GL_LIGHTING);
  glLineWidth(1.0);
  glColor3f(1.0,1.0,1.0);
  glBegin(GL_LINES);
  for (i=0;i<=nbSubdiv;i++)
    for (j=0;j<=nbSubdiv;j++) {
      P=&T[i*(nbSubdiv+1)+j];
      glVertex3fv(&P->x);
      glVertex3f(P->x+ECHELLE_NORMALES*P->nx,
		 P->y+ECHELLE_NORMALES*P->ny,
		 P->z+ECHELLE_NORMALES*P->nz);
    }
  glEnd();
  glEnable(GL_LIGHTING);
  glEndList();
  
}

/* Creation de la Liste d'affichage pour les lampes */
void creeLampes()
{
  lampes=glGenLists(1);
  glNewList(lampes,GL_COMPILE);
  glDisable(GL_LIGHTING);
  glColor3f(1.0,1.0,1.0);
  glPointSize(6.0);
  glBegin(GL_POINTS);
  glVertex3fv(L0pos);
  glVertex3fv(L1pos);
  glEnd();
  glLineWidth(1.0);
  glBegin(GL_LINES);
  glVertex3fv(L0pos);
  glVertex3f(0.0,0.0,0.0);
  glVertex3fv(L1pos);
  glVertex3f(0.0,0.0,0.0);
  glEnd();
  glEnable(GL_LIGHTING);
  glEndList();
}



/* Affiche le sommet (i,j) du maillage */
void drawVertex(int i,int j,vertex *T)
{
  glNormal3fv(&(T[i*(nbSubdiv+1)+j].nx));
  glVertex3fv(&(T[i*(nbSubdiv+1)+j].x));
}



/* Calcul de la hauteur d'un point */
float elevation(int i,int j)
{
  int valeur=image[(int)((float)i/nbSubdiv*255)][(int)((float)j/nbSubdiv*255)];
  return ((float)valeur/128.0-1.0)*echelleVert;
}



/* Chargement d'une image jpeg */
void loadJpegImage(char *filename)
{
  FILE *file;
  struct jpeg_decompress_struct cinfo;
  struct jpeg_error_mgr jerr;
  unsigned char *im=(unsigned char *)image,*ligne;
  
  cinfo.err = jpeg_std_error(&jerr);
  jpeg_create_decompress(&cinfo);

  /* On mets en place une image par defaut si filename=NULL*/
  if (filename==NULL){
    filename=(char *)malloc(128);
    strcpy(filename,"terrain.jpg");
  }

  if (!(file=fopen(filename,"rb"))) {
    fprintf(stderr,"Erreur : impossible d'ouvrir %s\n",filename);
    exit(1);
  }
  jpeg_stdio_src(&cinfo, file);
  jpeg_read_header(&cinfo, TRUE);
  if ((cinfo.image_width!=256) || (cinfo.image_height!=256)) {
    fprintf(stderr,"Erreur : l'image doit etre de taille 256x256\n");
    exit(1);
  }
  if (cinfo.out_color_space!=JCS_GRAYSCALE){
    fprintf(stderr,"Error : l'image doit etre en niveaux de gris\n");
    exit(1);
  }
  jpeg_start_decompress(&cinfo);

  while (cinfo.output_scanline<256){

    ligne=im+256*cinfo.output_scanline;
    jpeg_read_scanlines(&cinfo,&ligne,1);
  }

  jpeg_finish_decompress(&cinfo);
  jpeg_destroy_decompress(&cinfo);
}