/*
        DETECTION DE COLLISIONS - COMPARAISON DIFFERENTS TESTS DE COLLISIONS
*/

#include <allegro.h>
#include <stdio.h>
#include <time.h>

#define ECRAN_X	800
#define ECRAN_Y	600
#define MODE    GFX_AUTODETECT_WINDOWED

#define ERREUR(msg) {\
   set_gfx_mode(GFX_TEXT,0,0,0,0);\
   allegro_message("erreur ligne %d, fichier %s: %s\n",__LINE__, __FILE__,msg);\
   allegro_exit();\
   exit(EXIT_FAILURE);\
}

#define NB_IMAGE      32    // nombre total d'images de l'animation météorite
#define NB_MAX        10    // nombre de sequences d'animation


// généralisation controle du sprite 
typedef struct SPRITE{
    // le deplacement
    int x,y;         // position
    int tx,ty;       // taille
    int px,py;        // pas du deplacement
    int xwait,ywait; // pour retard avancement en x et y
    int xcmpt,ycmpt; // pour compter le retard
    // l'image
    int courant;     // l'image courante
    int nb_image;   // le nombre max des images de l'animation
    int dir;        // sens de l'animation (à reculons ou avancer)
    int tmps;       // pour compter le temps d'affichage de l'image
    int maxtmps;    // temps maxi pour chaque image
    
}t_sprite ;

// pour l'animation
BITMAP* recup_sprites                (BITMAP*scr, int w, int h, int startx, 
                                      int starty,int col, int i);
void    avance                       (t_sprite *s);
void    cntl_bords                   (t_sprite *s);
// détection collision avec les différents tests
void    collision                    (int test,int s1,t_sprite *s[]);
int     dedans                       (int x, int y, 
                                      int top,int left,int right,int bottom);
int     test_tous_coins_in_rect      (t_sprite*s1,t_sprite *s2);
int     test_segments_inside         (t_sprite*s1,t_sprite *s2);
int     test_segments_outside        (t_sprite*s1,t_sprite *s2);
int     test_centre_a_centre         (t_sprite*s1,t_sprite *s2);
int     test_segments_inside_optimise(t_sprite*s1,t_sprite *s2);
// en cas de collision modification de la trajectoire
void modif_trajectoire(t_sprite*s1,t_sprite *s2);
/******************************************************************************
*******************************************************************************/
int main()
{
t_sprite *s[NB_MAX];
BITMAP *page,*tmp,*im[NB_IMAGE];
char buf[255]={'\0'};
int i,test=0;

   allegro_init();
   install_keyboard(); 
   srand(time(NULL));
     
   set_color_depth(16);
   if (set_gfx_mode(MODE, ECRAN_X, ECRAN_Y, 0, 0) != 0) 
      ERREUR(allegro_error);
   
   // récup de l'image qui contient tout
   tmp=load_bitmap("sphere.bmp",NULL);
   if (!tmp)
      ERREUR("recup image ");
   
   // récupération des 64 images de l'animation dans le tableau im[]
   // remarque : finalement il y a une seule animation mais des entées 
   // différentes seront attribuées à chaque sequence. 
   for (i=0;i<NB_IMAGE;i++){
     im[i]=recup_sprites(tmp,64,64,0,0,8,i);
     if (!im[i])
        ERREUR("recup images");
   }
   // l'image tmp n'est plus utile en mémoire
   destroy_bitmap(tmp);   
   
   // création buffer
   page=create_bitmap(SCREEN_W,SCREEN_H);
   if (!page)
      ERREUR("creation buffer")  
   
   // initialisation de chaque séquence d'animation, les sprites
   for (i=0;i<NB_MAX;i++){
      s[i]=(t_sprite*)malloc(sizeof(t_sprite));
      s[i]->x=rand()%(SCREEN_W-im[0]->w);
      s[i]->y=rand()%(SCREEN_H-im[0]->h);
      s[i]->tx=im[0]->w;
      s[i]->ty=im[0]->h;
      s[i]->xwait=rand()%2+1;
      s[i]->ywait=rand()%2+1;
      s[i]->xcmpt=0;
      s[i]->ycmpt=0;
      s[i]->px=(rand()%11)-5;
      s[i]->py= rand()%2+4;
      // une entrée différente dans l'animation pour chaque sequence
      s[i]->courant=rand()%NB_IMAGE;  
      s[i]->nb_image=NB_IMAGE-1;
      s[i]->tmps=0;
      s[i]->maxtmps=rand()%3+1;
      s[i]->dir=1;
   }   

   while (!key[KEY_ESC]){
      
      // choix du test et collecte infos pour affichage
      if (key[KEY_F1]){
         test=1;
         sprintf(buf,"test %d actif : au moins un coin du rect source dans le rect destination",test);
      }    
      if (key[KEY_F2]){
         test=2;
         sprintf(buf,"test %d actif : intersections segments verticaux et horizontaux. Version inside.",test);
      }           
      if (key[KEY_F3]){
         test=3;
         sprintf(buf,"test %d actif : intersections segments verticaux et horizontaux. Version outside.",test);
      }    
      if (key[KEY_F4]){
         test=4;
         sprintf(buf,"test %d actif : distance centre a centre comparee aux demi longueurs et hauteurs des rects",test); 
      } 
      if (key[KEY_F5]){
         test=5;
         sprintf(buf,"test %d actif : optimisation test 2, sur intersections segments horizontaux et verticaux",test); 
      }       
      if (key[KEY_F6]){
         test=0;
         sprintf(buf,"Pas de test actif");     
      }    
      // effacer buffer
      clear_to_color(page,0);
      
      // pour chaque sprite
      for (i=0;i<NB_MAX;i++){
         // avancer
         avance(s[i]); 
         // controler bords (écran circulair)
         cntl_bords(s[i]);
         // contrôle collisions du sprite i avec tous les autres, collision 
         // modifie les trajectoires si deux sprites se rencontrent  
         collision(test,i,s);
      }
      
      // affichages de sprites : si touche enter pressée pas de masque et 
      // possibilité de voir le sprite entier
      for (i=0;i<NB_MAX;i++){
         if ( !key[KEY_ENTER])
            draw_sprite(page,im[s[i]->courant],s[i]->x,s[i]->y); 
         else
            blit(im[s[i]->courant],page,0,0,s[i]->x,s[i]->y,s[i]->tx,s[i]->ty); 
         // infos test 
         textout_ex(page,font,"choix test : F1,F2,F3,F4,F5,F6, ESC pour quitter",
         10,10,makecol(255,255,0),-1);
         textout_ex(page,font,buf,10,25,makecol(0,255,0),-1);
		 textout_ex(page,font,"Press ENTER pour voir la totalite du sprite",
	     10,40,makecol(0,255,255),-1);
      }    
      //affichage ecran
      blit(page,screen,0,0,0,0,SCREEN_W,SCREEN_H);
      rest(10);  
   }
   
   destroy_bitmap(page);
   for (i=0;i<NB_IMAGE;i++)
      destroy_bitmap(im[i]);
   for (i=0;i<NB_MAX;i++)
      free(s[i]);
      
   exit(EXIT_SUCCESS);
   
}
END_OF_MAIN();
/******************************************************************************
*******************************************************************************/
BITMAP* recup_sprites(  BITMAP*scr, // bitmap origine
                        int w, int h, // taille element
                        int startx, int starty, // à partir de 
                        int col, int i)// nombre de colones, élément i
{
BITMAP *bmp;
int x,y;
     
   bmp=create_bitmap(w,h);
   if (bmp!=NULL){
      x= startx+(i%col)*w;    
      y= starty+(i/col)*h; 
      blit(scr,bmp,x,y,0,0,w,h);     
   }    
   return bmp;
}
/******************************************************************************
*******************************************************************************/
void avance(t_sprite *s)
{
   // petit compte pour retard avance en x
   s->xcmpt=(++s->xcmpt)%s->xwait;
   if (!s->xcmpt)
      s->x+=s->px;
   // idem en y
   s->ycmpt=(++s->ycmpt)%s->ywait;
   if (!s->ycmpt)
      s->y+=s->py;
   
   // compte pour affichage image
   s->tmps=(++s->tmps)%s->maxtmps;
   if (!s->tmps){
      s->courant+=s->dir;
      s->courant=(s->courant<0) ? s->nb_image : s->courant;
      s->courant=(s->courant>s->nb_image) ? 0 : s->courant;
   }        
}
/******************************************************************************
Controle des bords dans écran circulaire
*******************************************************************************/
void cntl_bords(t_sprite *s)
{
// controle mouvement x
   if (s->x+s->tx<0)
      s->x=SCREEN_W-1;
   else if (s->x>SCREEN_W)
      s->x= -(s->tx);  
   
   // controle mouvement y
   if (s->y+s->ty<0)
      s->y=SCREEN_H-1;
   else if (s->y>SCREEN_H)
      s->y= -(s->ty);  
}
/*******************************************************************************
la fonction collision prend en argument le numéro du test souhaité "test", le 
numéro du sprite qui est testé "s1" et le tableau des sprites. Si le numéro de 
test ne correspond à aucun des tests il n'y a pas de détection. 
si le sprite s1 percute un autre il y a modification des trajectoires 
*******************************************************************************/
void collision(int test,int s1,t_sprite *s[])
{
int i,res=0;

   for (i=0;i<NB_MAX;i++)
      if (i!=s1){
         switch (test){
            case 1 : res=test_tous_coins_in_rect(s[s1],s[i]);        break; 
            case 2 : res=test_segments_inside(s[s1],s[i]);           break;
            case 3 : res=test_segments_outside(s[s1],s[i]);          break;
            case 4 : res=test_centre_a_centre(s[s1],s[i]);           break;  
            case 5 : res=test_segments_inside_optimise(s[s1],s[i]);  break;
            default :break; 
         } 
         if (res)
            modif_trajectoire(s[s1],s[i]);
      }         
}
/*******************************************************************************
1 Tester chaque coin d'un rectangle et voir s'il est dans l'autre rectangles
necessite une fonction ou une macro "dedans" qui qui renvoie 1 si le point (x,y) 
passé en argument est dans le rect (top, left,right,bottom)
*******************************************************************************/
int dedans(int x, int y, int top,int left,int right,int bottom)
{
int res=0;
   if (x>left && x<right && y>top && y<bottom)
      res=1;
   return res;      
}
//
int test_tous_coins_in_rect(t_sprite*s1,t_sprite *s2)
{
// les coordonnées des deux rectangles 
//(facilitation lecture)
int x1=s1->x;
int y1=s1->y;
int w1=x1+s1->tx;
int h1=y1+s1->ty;

int x2=s2->x;
int y2=s2->y;
int w2=x2+s2->tx;
int h2=y2+s2->ty;
// pour résultat du test
int res=0;
   
   if (dedans(x1,y1,y2,x2,w2,h2)||
       dedans(w1,y1,y2,x2,w2,h2)||
       dedans(x1,h1,y2,x2,w2,h2)||
       dedans(w1,y1,y2,x2,w2,h2))
       res=1;
   return res;      
}
/*******************************************************************************
2 Tester les intersections entre segments qui composent les rectangles
version inside, retourne 1 si intersection
*******************************************************************************/
int test_segments_inside(t_sprite*s1,t_sprite *s2)
{
// les coordonnées des deux rectangles 
//(facilitation lecture)
int x1=s1->x;
int y1=s1->y;
int w1=x1+s1->tx;
int h1=y1+s1->ty;

int x2=s2->x;
int y2=s2->y;
int w2=x2+s2->tx;
int h2=y2+s2->ty;
// pour résultat du test
int res=0;
   
   if ( x1<w2 && w1>x2 && y1<h2 && h1>y2)
      res=1;
         
   return res; 
}
/*******************************************************************************
3 Tester les intersections entre segments qui composent les rectangles
version outside 
*******************************************************************************/
int test_segments_outside(t_sprite*s1,t_sprite *s2)
{
// les coordonnées des deux rectangles 
int x1=s1->x;
int y1=s1->y;
int w1=x1+s1->tx;
int h1=y1+s1->ty;

int x2=s2->x;
int y2=s2->y;
int w2=x2+s2->tx;
int h2=y2+s2->ty;

// pour résultat du test
int res=1;
   if ( x1>w2 || w1<x2 || y1>h2 || h1<y2)
      res=0;
   return res;   
}
/*******************************************************************************
4 Distance de centre à centre : regarder la proximité des centres par rapport
aux longueurs. Retourne 1 si intersection
*******************************************************************************/
int test_centre_a_centre(t_sprite*s1,t_sprite *s2)
{
// segments moitié taille en x et y
int w1=s1->tx/2;
int h1=s1->ty/2;
// centre à partir position coin haut gauche
int x1=s1->x+w1;
int y1=s1->y+h1;

int w2=s2->tx/2;
int h2=s2->ty/2;
int x2=s2->x+w2;
int y2=s2->y+h2;

int dx,dy;
int res=0;
   
   // distance centre à centre avec une légère diminution de 4 pixels
   dx=ABS(x1-x2);
   dy=ABS(y1-y2);
   if (dx<w1+w2-4 && dy<h1+h2-4)
      res=1;
   return res;
}
/*******************************************************************************
5 Optimisation tests 2  sur les intersections entre segments horizontaux et 
verticaux version inside retourne 1 si intersection
*******************************************************************************/
int test_segments_inside_optimise(t_sprite*s1,t_sprite *s2)
{
// les coordonnées des deux rectangles 
//(facilitation lecture)
int x1=s1->x;
int y1=s1->y;
int w1=x1+s1->tx;
int h1=y1+s1->ty;

int x2=s2->x;
int y2=s2->y;
int w2=x2+s2->tx;
int h2=y2+s2->ty;
// pour résultat du test
//int res=0;

   //if ( !(  x1>w2  || w1<x2  || y1>h2  || h1<y2) )
   return !( (x1>w2) | (w1<x2) | (y1>h2) | (h1<y2)  );

      
   // si l'implémentation des nombre négatifs le permet  on devrait savoir si 
   // un nombre est négatif avec un test sur le bit de signe et dans ce cas 
   // pouvoir écrire :
   // if ( !((x1-w2>0) | (x2-w1>0) | (y1-h2>0) | (y2-h1>0)))
   // puis encore :
   // if ( !(((x1-w2) | (x2-w1) | (y1-h2) | (y2-h1)) & 0X80000000))
   //   res=1;
   // return res;
       
}
/*******************************************************************************
si collision, fonction pour la modification de trajectoires (faire dessin pour
voir les différentes options : soit inverser soit continuer et avec nouvelle 
vitesse dans les deux cas)
*******************************************************************************/
void modif_trajectoire(t_sprite*s1,t_sprite *s2)
{
// Les positions des centres des deux sprites
int cx1=s1->x+s1->tx/2;
int cy1=s1->y+s1->ty/2;
int cx2=s2->x+s2->tx/2;
int cy2=s2->y+s2->ty/2;
   
   // le sens des trajectoires et inversion
   // s1 arrive à gauche
   if (cx1<=cx2){   
      s1->px= -1*rand()%6+1;
      s2->px= rand()%6+1;
      /*// si s1 arrive en haut
      if (cy1<=cy2){
         s1->py= -1*rand()%6+1;
         s2->py= rand()%6+1;
      }
      //Si s1 arrive en bas
      else{
         s1->py= rand()%6+1;
         s2->py= -1*rand()%6+1;
      } */         
   }
   // si s1 arrive à droite
   else{
      s1->px= rand()%6+1;
      s2->px= -1*rand()%6+1;
      /*// si s1 arrive en haut
      if (cy1<=cy2){
         s1->py= rand()%6+1;
         s2->py= -1*rand()%6+1;
      }
      //Si s1 arrive en bas
      else{
         s1->py= -1*rand()%6+1;
         s2->py= rand()%6+1;
      } */         
   } 
   if (cy1<=cy2){
      s1->py= -1*rand()%6+1;
      s2->py= rand()%6+1;
   }
   //Si s1 arrive en bas
   else{
      s1->py= rand()%6+1;
      s2->py= -1*rand()%6+1;
   }
             
                                 
}
/******************************************************************************
RAPPEL LINKAGE lib static :
pour dev :
dans menu : Projet/otpions du projet/paramètres/éditeur de liens , là, 
copier la liste suivante :
  -lalleg_s -lgdi32 -lwinmm -lole32 -ldxguid -ldinput -lddraw -ldsound
et dans la colonne "compilateur" à gauche de l'éditeur de liens copier
  -DALLEGRO_STATICLINK
cliquer "OK"
*******************************************************************************/

