
// This file is part of the Herezh++ application.
//
// The finite element software Herezh++ is dedicated to the field
// of mechanics for large transformations of solid structures.
// It is developed by Gérard Rio (APP: IDDN.FR.010.0106078.000.R.P.2006.035.20600)
// INSTITUT DE RECHERCHE DUPUY DE LÔME (IRDL) <https://www.irdl.fr/>.
//
// Herezh++ is distributed under GPL 3 license ou ultérieure.
//
// Copyright (C) 1997-2022 Université Bretagne Sud (France)
// AUTHOR : Gérard Rio
// E-MAIL  : gerardrio56@free.fr
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License,
// or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty
// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
// See the GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
//
// For more information, please consult: <https://herezh.irdl.fr/>.

#include "Cercle.h"
#include "MathUtil.h"
#include "ParaGlob.h"

    // CONSTRUCTEURS :
// par defaut
Cercle::Cercle () :
  centre(),rayon(0.),N(NULL)
   { if (ParaGlob::Dimension()== 1)
	   { cout << "\nErreur : la dimension est 1 et on veut utiliser un cercle ???  ";
		    cout << "\nCercle::Cercle ()" << endl;
		    Sortie(1);
    };
   };
// avec les datas
Cercle::Cercle ( const Coordonnee& B, const double& r , const Coordonnee* No):
  centre(B),rayon(r),N(NULL)
 { if (ParaGlob::Dimension()== 1)
	   { cout << "\nErreur : la dimension est 1 et on veut utiliser un cercle ???  ";
		    cout << "\nCercle::Cercle ()" << endl;
		    Sortie(1);
    };
   if (rayon < 0.)
   	{ cout << "\n erreur dans la construction d'un Cercle, le rayon " << rayon 
   	       << " est negatif !! ";
   	  Sortie(1);     
   	};
   if (ParaGlob::Dimension() == 3)
     if (No != NULL)
   	  	 { N = new Coordonnee(*No);
          double norme = N->Norme();
          if (norme < ConstMath::petit)
   	        { cout << "\n erreur dans la construction d'un Cercle, le vecteur normal " 
                   << " a une norme (=" << norme <<") trop petite !! "  ;
   	          Sortie(1);     
   	        };
          (*N) /= norme; // on normalise le vecteur
        };
   	   // sinon la normale n'existe pas, il faudra la définir   
   };
// avec la dimension
Cercle::Cercle (int dim):
  centre(dim),rayon(0.),N(NULL)
 { if (ParaGlob::Dimension()== 1)
	   { cout << "\nErreur : la dimension est 1 et on veut utiliser un cercle ???  ";
		    cout << "\nCercle::Cercle ()" << endl;
		    Sortie(1);
    };
 };
    // de copie
Cercle::Cercle ( const Cercle& a):
  centre(a.centre),rayon(a.rayon),N(NULL)
   { if (ParaGlob::Dimension() == 3)
   	   if (a.N != NULL) // a priori il est normé
   	  	 { N = new Coordonnee(*(a.N));};
   	   // sinon la normale n'existe pas, il faudra la définir   
   };
   
    // DESTRUCTEUR :
Cercle::~Cercle () 
  { if (N != NULL)
      {delete N;N=NULL;};
  };

// surcharge des operator
Cercle& Cercle::operator = ( const Cercle & cer)
 { centre = cer.centre; rayon = cer.rayon; 
   if (ParaGlob::Dimension() == 3)
      if (cer.N != NULL) // a priori la normale est normée
   	  	 { N = new Coordonnee(*(cer.N));};
   	   // sinon la normale n'existe pas, il faudra la définir   
   return *this;
 };
    
// METHODES PUBLIQUES :

// change le centre du Cercle
void Cercle::Change_centre( const Coordonnee& B)
 { 
    #ifdef MISE_AU_POINT	 	 
     if  (B.Dimension() != centre.Dimension())
	     { cout << "\nErreur : les dimensions du centre et du vecteur ne sont pas identique !";
	       cout <<"\ndim B = " <<B.Dimension() <<", dim centre =" << centre.Dimension(); 
		      cout << "\nCercle::Change_centre( const Coordonnee& B)" << endl;
		      Sortie(1);
		    };
    #endif 
    centre = B;
  };

// change le rayon du Cercle
void Cercle::Change_rayon( const double & r)
   {if (r < 0.)
     	{ cout << "\n erreur dans la construction d'un Cercle, le rayon " << r
     	       << " est negatif !! "
     	       << "\n Cercle::Change_rayon( const double & r) ";
     	};
   	rayon  = r;     
   };

// change la normale au  Cercle en 3D, sinon c'est inutile, warning
void Cercle::Change_Normale( const Coordonnee& No)
 { if (ParaGlob::Dimension() != 3)
	   { cout << "\nwarning : on n'est pas en 3D, la normale n'est pas utile pour le cercle !";
		    cout << "\nCercle::Change_Normale( const Coordonnee& No)" << endl;
    }
   else
   {		
    #ifdef MISE_AU_POINT	 	 
     if  (No.Dimension() != 3)
	      { cout << "\nErreur : la dimension de la normale devrait etre 3 alors qu'elle est : "
	             << No.Dimension() <<" "; 
         cout << "\nCercle::Change_Normale( const Coordonnee& No)" << endl;
		       Sortie(1);
		     };
    #endif
    if (N == NULL) 
      { N = new Coordonnee (No);}
    else 
      { *N = No;}; 
    double norme = N->Norme();
    if (norme < ConstMath::petit)
   	  { cout << "\n erreur dans la construction d'un Cercle, le vecteur normal " 
             << " a une norme (=" << norme <<") trop petite !! "  ;
        cout << "\nCercle::Change_Normale( const Coordonnee& No)" << endl;
   	    Sortie(1);     
   	  };
    (*N) /= norme; // on normalise le vecteur
   };
  };



// change toutes les donnees
void Cercle::change_donnees( const Coordonnee& B, const double& r,const Coordonnee* No)
 {
    #ifdef MISE_AU_POINT	 	 
     if  (B.Dimension() != centre.Dimension())
	     { cout << "\nErreur : les dimensions du centre et du vecteur ne sont pas identique !";
	       cout <<"\ndim B = " <<B.Dimension() <<", dim centre =" << centre.Dimension(); 
		      cout << "\nCercle::change_donnees( const Coordonnee& B, const double& r)" << endl;
		      Sortie(1);
		    };
    #endif 
    centre = B;
    if (r < 0.)
     	{ cout << "\n erreur dans la construction d'un Cercle, le rayon " << r
     	       << " est negatif !! "
     	       << "\n Cercle::change_donnees( ) ";
     	};
   	rayon  = r;
   	// maintenant cas de la normale 
   	if ((ParaGlob::Dimension() != 3) && (No != NULL))
	    { cout << "\nwarning : on n'est pas en 3D, la normale n'est pas utile pour le cercle !";
		     cout << "\nCercle::change_donnees( const Coordonnee& No)" << endl;
		   }
   	else if ((ParaGlob::Dimension() == 3) && (No == NULL))
	    { cout << "\nwarning : la normale au cercle n'est pas defini !";
		     cout << "\nCercle::change_donnees()" << endl;
       if (N != NULL)
         delete N;
		   }
    else if (ParaGlob::Dimension() == 3) // cas de la dimension 3 et No non null
     {		
     #ifdef MISE_AU_POINT	 	 
     if  (No->Dimension() != 3)
	     { cout << "\nErreur : la dimension de la normale devrait etre 3 alors qu'elle est : "
	            <<No->Dimension() <<" "; 
        cout << "\nCercle::change_donnees()" << endl;
		      Sortie(1);
		    };
     #endif
     if (N == NULL) 
      { N = new Coordonnee (*No);}
     else 
      { *N = *No;}; 
     double norme = N->Norme();
     if (norme < ConstMath::petit)
   	  { cout << "\n erreur dans la construction d'un Cercle, le vecteur normal " 
             << " a une norme (=" << norme <<") trop petite !! "  ;
        cout << "\nCercle::change_donnees(.." << endl;
   	    Sortie(1);     
   	  };
     (*N) /= norme; // on normalise le vecteur
    };
   	     
 };

// calcul les deux intercections  M1 et M2  d'une droite avec le Cercle,
// ramene  0 s'il n'y a pas d'intersection, ramene -1 si l'intersection 
// ne peut pas etre calculee
// et 1 s'il y a deux points d'intercection
int Cercle::Intersection( const Droite & dr,Coordonnee& M1, Coordonnee& M2)
{switch (ParaGlob::Dimension())
		{case 2:
		 {// C le centre du cercle, H la projection de C sur la droite
		  // M1 et M2 les deux intersections potentielles
		  const Coordonnee& A = dr.PointDroite(); // pour simplifier
		  const Coordonnee& U = dr.VecDroite();
		  Coordonnee CA = A - centre;
		  double ah = - CA * U;
		  Coordonnee CH = CA + ah * U;
		  double ch_2 = CH * CH;
		  if (sqrt(ch_2) > rayon)
		    return 0; // cas pas d'intersection
		  // sinon
		  double hm = sqrt(rayon*rayon - ch_2);
		  M1 = centre + CH + hm * U;
		  M2 = centre + CH - hm * U;
	   return 1; 
	   break;
		 }
  case 3:
   {
   #ifdef MISE_AU_POINT	 	 
    if  (N==NULL)
       { cout << "\nErreur : la normale au cercle n'est pas defini,  "
	             << " on ne peut pas calculer la distance au cercle ";
		       cout << "\nCercle::Distance_au_Cercle()" << endl;
		       Sortie(1);
       };
   #endif
    // on calcul le projeté sur le plan d'un point de la droite
			 const Coordonnee& A = dr.PointDroite(); // pour simplifier
			 const Coordonnee& U = dr.VecDroite();
    Coordonnee CA = A-centre;
    double ha = CA*(*N); // distance du point au plan du cercle
    if (ha > ConstMath::pasmalpetit)
		  return 0; // cas pas d'intersection
		  // sinon le point appartient au plan: même procédure qu'en 2D
		  // C le centre du cercle, H la projection de C sur la droite
			 // M1 et M2 les deux intersections potentielles
			 double ah = - CA * U;
			 Coordonnee CH = CA + ah * U;
			 double ch_2 = CH * CH;
			 if (sqrt(ch_2) > rayon)
			   return 0; // cas pas d'intersection
			 // sinon
			 double hm = sqrt(rayon*rayon - ch_2);
			 M1 = centre + CH + hm * U;
			 M2 = centre + CH - hm * U;
		  return 1; 
		  break;
   }
		 case 1:
     { cout << "\nErreur : la dimension est 1 et on utilise un cercle ???  ";
		     cout << "\nCercle::Intersection(.." << endl;
		     Sortie(1);
     };
		};
  // normalement on n'arrive jamais ici
  return -1;	
};

// calcul la distance d'un point au Cercle
double Cercle::Distance_au_Cercle(const Coordonnee& M) const
	{ switch (ParaGlob::Dimension())
		{case 2:
     return Dabs((centre - M).Norme()-rayon);break;
		 case 3:
    {
    #ifdef MISE_AU_POINT	 	 
     if  (N==NULL)
	      { cout << "\nErreur : la normale au cercle n'est pas defini,  "
	             << " on ne peut pas calculer la distance au cercle ";
		       cout << "\nCercle::Distance_au_Cercle()" << endl;
		       Sortie(1);
		     };
    #endif
     // on calcul le projeté sur le plan
     Coordonnee CM = M-centre;
     double cm = (CM*(*N));
     Coordonnee CH = CM - cm * (*N);
     // calcul du point intersection CH avec le cercle -> E
     double ch = CH.Norme();
     if (ch < ConstMath::trespetit)
     	{ // cela veut dire que le point M est sur l'axe du cercle
     	  return (sqrt(cm*cm + rayon*rayon));
     	}
     else
      	{ Coordonnee CE = CH * (rayon/ch);
         return (CM-CE).Norme();
      	};	
     break;
		   }
		 case 1:
     { cout << "\nErreur : la dimension est 1 et on utilise un cercle ???  ";
		     cout << "\nCercle::Distance_au_Cercle()" << endl;
		     Sortie(1);
     };
		};
	  // normalement on n'arrive jamais ici
	  return ConstMath::tresgrand;	
	};
    
// calcul du projeté d'un point sur le plan
Coordonnee Cercle::Projete(const Coordonnee& M) const 
	{ switch (ParaGlob::Dimension())
		{case 2: // le projeté est directement M
      return M;break;
		 case 3:
     {
     #ifdef MISE_AU_POINT	 	 
      if  (N==NULL)
	       { cout << "\nErreur : la normale au cercle n'est pas defini,  "
	              << " on ne peut pas calculer la distance au cercle ";
		        cout << "\nCercle::Projete(.." << endl;
		        Sortie(1);
		      };
     #endif
      // on calcul le projeté sur le plan
      return (M - ((M-centre)* (*N))* (*N));
      break;
     }
		 case 1:
     { cout << "\nErreur : la dimension est 1 et on utilise un cercle ???  ";
		     cout << "\nCercle::Projete(.." << endl;
		     Sortie(1);
     };
		};
	  // normalement on n'arrive jamais ici
	  Coordonnee toto;  // on fait en deux fois pour la version linux
	  return (toto);	
	};
    
// fonction valable uniquement en 2D, sinon erreur
// ramène true si le point est à l'intérieur du cercle, false sinon
bool Cercle::Dedans(const Coordonnee& M)const 
{ if (ParaGlob::Dimension() == 2)
	   { return ((M-centre).Norme() < rayon);}
  else
    { cout << "\n erreur, la fonction Cercle::Dedans(... n'est utilisable "
           << " qu'en dimension 2D, alors que l'on est en " << ParaGlob::Dimension() << " ";
      Sortie(1);
      return false; // pour éviter un warning     
    };
 };
    
// surcharge de l'operateur de lecture
istream & operator >> (istream & entree, Cercle & cer)
 {  // vérification du type
    string nom;
    entree >> nom;
   #ifdef MISE_AU_POINT
    if (nom != "_cercle_")
		     {	cout << "\nErreur, en lecture d'une instance  cercle " 
		            << " on attendait _cercle_ et on a lue: " << nom ;
			      cout << "istream & operator >> (istream & entree, Cercle & cer)\n";
			      Sortie(1);
		     };
   #endif
    // puis lecture des différents éléments
    entree >> nom >> cer.centre >> nom >> cer.rayon;
    bool existe_N;
    entree >> existe_N;
    if (existe_N) 
      { if (cer.N == NULL)
          cer.N = new Coordonnee(ParaGlob::Dimension());
        entree >> (*(cer.N));
        double norme = cer.N->Norme();
        if (norme < ConstMath::petit)
   	     { cout << "\n erreur dans la construction d'un Cercle, le vecteur normal " 
                << " a une norme (=" << norme <<") trop petite !! "  ;
           cout << "\nCercle::change_donnees(.." << endl;
   	       Sortie(1);     
   	     };
        (*cer.N) /= norme; // on normalise le vecteur
      }
    else
      { if (cer.N != NULL)
           delete cer.N;
      	};  
    return entree;      
  };
  
// surcharge de l'operateur d'ecriture 
ostream & operator << ( ostream & sort,const  Cercle & cer)
  { // tout d'abord un indicateur donnant le type
    sort << " _cercle_ " ; 
    // puis les différents éléments
    sort << "\n C= " << cer.centre << " r= " << cer.rayon << " ";
    if (cer.N == NULL)
     { sort << 0 ;}
    else 
     { sort << 1 << (*(cer.N)) << " " ;}
    return sort;      
  };
