/****************************************************************************
* VCGLib                                                            o o     *
* Visual and Computer Graphics Library                            o     o   *
*                                                                _   O  _   *
* Copyright(C) 2004                                                \/)\/    *
* Visual Computing Lab                                            /\/|      *
* ISTI - Italian National Research Council                           |      *
*                                                                    \      *
* All rights reserved.                                                      *
*                                                                           *
* 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 2 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 (http://www.gnu.org/licenses/gpl.txt)          *
* for more details.                                                         *
*                                                                           *
****************************************************************************/

#ifndef __VCGLIB_FACE_DISTANCE
#define __VCGLIB_FACE_DISTANCE

#include <vcg/math/base.h>
#include <vcg/space/point3.h>
#include <vcg/space/segment3.h>
#include <vcg/space/distance3.h>

namespace vcg {
	namespace face{
/*
  Basic Wrapper for getting point-triangular face distance
  distance is unsigned;

  return true if the closest point <q> on <f> is nearer than the passed <dist>;
  return false otherwiswe (and q is not valid)

  This wrapper requires that your face has
  - Per Face Flags well initialized
  - Per Face EdgePlane component initialized.
  Initialization must be done with:

      tri::UpdateEdges<MeshType>::Set(yourMesh);
 */
    template <class FaceType>
    bool PointDistanceEP(	const FaceType &f,
                        const vcg::Point3<typename FaceType::ScalarType> & q,
                        typename FaceType::ScalarType & dist,
                        vcg::Point3<typename FaceType::ScalarType> & p )
    {
      typedef typename FaceType::ScalarType ScalarType;
      const ScalarType EPS = ScalarType( 0.000001);

		ScalarType b,b0,b1,b2;

		ScalarType d = SignedDistancePlanePoint( f.cPlane(), q );
		if( d>dist || d<-dist ) return false;

		Point3<ScalarType> t = f.cPlane().Direction();
		p = q - t*d; // p is the projection of q on the face plane
		// Now Choose the best plane and test to see if p is inside the triangle
		    
		switch( f.Flags() & (FaceType::NORMX|FaceType::NORMY|FaceType::NORMZ) )
		{
		case FaceType::NORMX:
			b0 = f.cEdge(1)[1]*(p[2] - f.cP(1)[2]) - f.cEdge(1)[2]*(p[1] - f.cP(1)[1]);
			if(b0<=0)
			{
				b0 = PSDist(q,f.V(1)->cP(),f.V(2)->cP(),p);
				if(dist>b0) { dist = b0; return true; }
				else return false;
			}
			b1 = f.cEdge(2)[1]*(p[2] - f.cP(2)[2]) - f.cEdge(2)[2]*(p[1] - f.cP(2)[1]);
			if(b1<=0)
			{
				b1 = PSDist(q,f.V(2)->cP(),f.V(0)->cP(),p);
				if(dist>b1) { dist = b1; return true; }
				else return false;
			}
			b2 = f.cEdge(0)[1]*(p[2] - f.cP(0)[2]) - f.cEdge(0)[2]*(p[1] - f.cP(0)[1]);
			if(b2<=0)
			{
				b2 = PSDist(q,f.V(0)->cP(),f.V(1)->cP(),p);
				if(dist>b2) { dist = b2; return true; }
				else return false;
			}
			// if all these tests failed the projection p should be inside.
			// Some further tests for more robustness...
			if( (b=std::min(b0,std::min(b1,b2)) ) < EPS*DoubleArea(f))
			{
			  ScalarType bt;
			  if(b==b0) 	    bt = PSDist(q,f.V(1)->cP(),f.V(2)->cP(),p);
			  else if(b==b1) 	bt = PSDist(q,f.V(2)->cP(),f.V(0)->cP(),p);
			  else {          assert(b==b2);
				bt = PSDist(q,f.V(0)->cP(),f.V(1)->cP(),p);
			  }
			  if(dist>bt) { dist = bt; return true; }
			  else return false;
			}
			break;

		  case FaceType::NORMY:
			b0 = f.cEdge(1)[2]*(p[0] - f.cP(1)[0]) - f.cEdge(1)[0]*(p[2] - f.cP(1)[2]);
			if(b0<=0)
			{
				b0 = PSDist(q,f.V(1)->cP(),f.V(2)->cP(),p);
				if(dist>b0) { dist = b0; return true; }
				else return false;
			}
			b1 = f.cEdge(2)[2]*(p[0] - f.cP(2)[0]) - f.cEdge(2)[0]*(p[2] - f.cP(2)[2]);
			if(b1<=0)
			{
				b1 = PSDist(q,f.V(2)->cP(),f.V(0)->cP(),p);
				if(dist>b1) { dist = b1; return true; }
				else return false;
			}
			b2 = f.cEdge(0)[2]*(p[0] - f.cP(0)[0]) - f.cEdge(0)[0]*(p[2] - f.cP(0)[2]);
			if(b2<=0)
			{
				b2 = PSDist(q,f.V(0)->cP(),f.V(1)->cP(),p);
				if(dist>b2) { dist = b2; return true; }
				else return false;
			}
			if( (b=math::Min<ScalarType>(b0,b1,b2)) < EPS*DoubleArea(f))
			{
			  ScalarType bt;
			  if(b==b0) 	    bt = PSDist(q,f.V(1)->cP(),f.V(2)->cP(),p);
			  else if(b==b1) 	bt = PSDist(q,f.V(2)->cP(),f.V(0)->cP(),p);
			  else { assert(b==b2);
				bt = PSDist(q,f.V(0)->cP(),f.V(1)->cP(),p);
			  }
			  if(dist>bt) { dist = bt; return true; }
				else return false;
			}
			break;

		  case FaceType::NORMZ:
			b0 = f.cEdge(1)[0]*(p[1] - f.cP(1)[1]) - f.cEdge(1)[1]*(p[0] - f.cP(1)[0]);
			if(b0<=0)
			{
				b0 = PSDist(q,f.V(1)->cP(),f.V(2)->cP(),p);
				if(dist>b0) { dist = b0; return true; }
				else return false;
			}
			b1 = f.cEdge(2)[0]*(p[1] - f.cP(2)[1]) - f.cEdge(2)[1]*(p[0] - f.cP(2)[0]);
			if(b1<=0)
			{
				b1 = PSDist(q,f.V(2)->cP(),f.V(0)->cP(),p);
				if(dist>b1) { dist = b1; return true; }
				else return false;
			}
			b2 = f.cEdge(0)[0]*(p[1] - f.cP(0)[1]) - f.cEdge(0)[1]*(p[0] - f.cP(0)[0]);
			if(b2<=0)
			{
				b2 = PSDist(q,f.V(0)->cP(),f.V(1)->cP(),p);
				if(dist>b2) { dist = b2; return true; }
				else return false;
			}
			if( (b=math::Min<ScalarType>(b0,b1,b2)) < EPS*DoubleArea(f))
			{
			  ScalarType bt;
			  if(b==b0) 	    bt = PSDist(q,f.V(1)->cP(),f.V(2)->cP(),p);
			  else if(b==b1) 	bt = PSDist(q,f.V(2)->cP(),f.V(0)->cP(),p);
			  else { assert(b==b2);
				bt = PSDist(q,f.V(0)->cP(),f.V(1)->cP(),p);
			  }

			  if(dist>bt) { dist = bt; return true; }
				else return false;
			}
			break;

		} // end switch

		dist = ScalarType(fabs(d));
		return true;
	}

	template <class S>
	class PointDistanceEPFunctor {
	public:
		typedef S ScalarType;
		typedef Point3<ScalarType> QueryType;
		static inline const Point3<ScalarType> &  Pos(const QueryType & qt)  {return qt;}

		template <class FACETYPE, class SCALARTYPE>
		inline bool operator () (const FACETYPE & f, const Point3<SCALARTYPE> & p, SCALARTYPE & minDist, Point3<SCALARTYPE> & q) {
			const Point3<typename FACETYPE::ScalarType> fp = Point3<typename FACETYPE::ScalarType>::Construct(p);
			Point3<typename FACETYPE::ScalarType> fq;
			typename FACETYPE::ScalarType md = (typename FACETYPE::ScalarType)(minDist);
			const bool ret = PointDistanceEP(f, fp, md, fq);
			minDist = (SCALARTYPE)(md);
			q = Point3<SCALARTYPE>::Construct(fq);
			return (ret);
		}
	};



	template <class S>
	class PointNormalDistanceFunctor {
	public:
		typedef typename S::ScalarType ScalarType;
		typedef S QueryType;
		static inline const Point3<ScalarType> &  Pos(const QueryType & qt)  {return qt.P();}


		static ScalarType & Alpha(){static ScalarType alpha = 1.0; return alpha;}
		static ScalarType & Beta (){static ScalarType beta  = 1.0; return beta;}
		static ScalarType & Gamma(){static ScalarType gamma = 1.0; return gamma;}
		static ScalarType & InterPoint(){static ScalarType interpoint= 1.0; return interpoint;} 


		template <class FACETYPE, class SCALARTYPE>
		inline bool operator () (const FACETYPE &f, const typename FACETYPE::VertexType &p, 
			SCALARTYPE & minDist,Point3<SCALARTYPE> & q)
		{		
			const Point3<typename FACETYPE::ScalarType> fp = Point3<typename FACETYPE::ScalarType>::Construct(p.cP());
			const Point3<typename FACETYPE::ScalarType> fn = Point3<typename FACETYPE::ScalarType>::Construct(p.cN());
			Point3<typename FACETYPE::ScalarType> fq;
			typename FACETYPE::ScalarType md = (typename FACETYPE::ScalarType)(minDist);
			const bool ret=PointDistance(f,fp,md,fq);

			SCALARTYPE  dev=InterPoint()*(pow((ScalarType)(1-f.cN().dot(fn)),(ScalarType)Beta())/(Gamma()*md+0.1));

			if (md+dev < minDist){
				minDist = (SCALARTYPE)(md+dev);
				q = Point3<SCALARTYPE>::Construct(fq);
				//q.N() = f.N();
				return (ret);
			}
			return false;
		}
	};
		
		/// BASIC VERSION of the Point-face distance that does not require the EdgePlane Additional data.
		/// Given a face and a point, returns the closest point of the face to p.
		
		template <class FaceType>
			bool PointDistanceBase(
													const FaceType &f,																		/// the face to be tested
													const vcg::Point3<typename FaceType::ScalarType> & q, /// the point tested
													typename FaceType::ScalarType & dist,                 /// bailout distance. It must be initialized with the max admittable value. 
													vcg::Point3<typename FaceType::ScalarType> & p )      
		{
				typedef typename FaceType::ScalarType ScalarType;

                if(f.cN()==Point3<ScalarType>(0,0,0)) // to correctly manage the case of degenerate triangles we consider them as segments.
                {
                  Box3<ScalarType> bb;
                  f.GetBBox(bb);
                  Segment3<ScalarType> degenTri(bb.min,bb.max);
                  Point3<ScalarType> closest;
                  ScalarType d;
                  if(bb.Diag()>0)
                    vcg::SegmentPointDistance<ScalarType>(degenTri,q,closest,d);
                  else // very degenerate triangle (just a point)
                  {
                    closest = bb.min;
                    d=Distance(q,closest);
                  }
                  if( d>dist) return false;
                  dist=d;
                  p=closest;
                  assert(!math::IsNAN(dist));
                  return true;
                }

				Plane3<ScalarType,true> fPlane;
				fPlane.Init(f.cP(0),f.cN());
				const ScalarType EPS = ScalarType( 0.000001);
				ScalarType b,b0,b1,b2;
				// Calcolo distanza punto piano
				ScalarType d = SignedDistancePlanePoint( fPlane, q );
				if( d>dist || d<-dist )			// Risultato peggiore: niente di fatto
					return false;
				
				// Projection of query point onto the triangle plane
				p = q - fPlane.Direction()*d;

				Point3<ScalarType> fEdge[3];				
				fEdge[0] = f.cP(1); fEdge[0] -= f.cP(0);
				fEdge[1] = f.cP(2); fEdge[1] -= f.cP(1);
				fEdge[2] = f.cP(0); fEdge[2] -= f.cP(2);
				
				/* 
				This piece of code is part of the EdgePlane initialization structure: note that the edges are scaled!. 
				 
				if(nx>ny && nx>nz) { f.Flags() |= FaceType::NORMX; d = 1/f.Plane().Direction()[0]; }
				else if(ny>nz)     { f.Flags() |= FaceType::NORMY; d = 1/f.Plane().Direction()[1]; }
				else               { f.Flags() |= FaceType::NORMZ; d = 1/f.Plane().Direction()[2]; }
				f.Edge(0)*=d; f.Edge(1)*=d;f.Edge(2)*=d;
				 
				So we must apply the same scaling according to the plane orientation, eg in the case of NORMX
				  scaleFactor= 1/fPlane.Direction()[0];
				  fEdge[0]*=d; fEdge[1]*=d;fEdge[2]*=d;
				*/

				int bestAxis;
				if(fabs(f.cN()[0])>fabs(f.cN()[1]))
				{
				  if(fabs(f.cN()[0])>fabs(f.cN()[2])) bestAxis = 0;
				  else bestAxis = 2;
				} else {
				  if(fabs(f.cN()[1])>fabs(f.cN()[2])) bestAxis=1; /* 1 > 0 ? 2 */
				  else bestAxis=2; /* 2 > 1 ? 2 */
				}

				ScalarType scaleFactor;

				switch( bestAxis )
				{
					case 0:  /************* X AXIS **************/
						scaleFactor= 1/fPlane.Direction()[0];
						fEdge[0]*=scaleFactor; fEdge[1]*=scaleFactor; fEdge[2]*=scaleFactor;
						
						b0 = fEdge[1][1]*(p[2] - f.cP(1)[2]) - fEdge[1][2]*(p[1] - f.cP(1)[1]);
						if(b0<=0)
						{
							b0 = PSDist(q,f.V(1)->cP(),f.V(2)->cP(),p);
							if(dist>b0) { dist = b0; return true; }
							else return false;
						}
							b1 = fEdge[2][1]*(p[2] - f.cP(2)[2]) - fEdge[2][2]*(p[1] - f.cP(2)[1]);
						if(b1<=0)
						{
							b1 = PSDist(q,f.V(2)->cP(),f.V(0)->cP(),p);
							if(dist>b1) { dist = b1; return true; }
							else return false;
						}
							b2 = fEdge[0][1]*(p[2] - f.cP(0)[2]) - fEdge[0][2]*(p[1] - f.cP(0)[1]);
						if(b2<=0)
						{
							b2 = PSDist(q,f.V(0)->cP(),f.V(1)->cP(),p);
							if(dist>b2) { dist = b2; return true; }
							else return false;
						}
							// sono tutti e tre > 0 quindi dovrebbe essere dentro;
							// per sicurezza se il piu' piccolo dei tre e' < epsilon (scalato rispetto all'area della faccia
							// per renderlo dimension independent.) allora si usa ancora la distanza punto 
							// segmento che e' piu robusta della punto piano, e si fa dalla parte a cui siamo piu' 
							// vicini (come prodotto vettore)
							// Nota: si potrebbe rendere un pochino piu' veloce sostituendo Area()
							// con il prodotto vettore dei due edge in 2d lungo il piano migliore.
						if( (b=vcg::math::Min<ScalarType>(b0,b1,b2)) < EPS*DoubleArea(f))
							{
								ScalarType bt;
								if(b==b0) 	    bt = PSDist(q,f.V(1)->cP(),f.V(2)->cP(),p);
								else if(b==b1) 	bt = PSDist(q,f.V(2)->cP(),f.V(0)->cP(),p);
								else if(b==b2) 	bt = PSDist(q,f.V(0)->cP(),f.V(1)->cP(),p);
								//printf("Warning area:%g %g %g %g thr:%g bt:%g\n",Area(), b0,b1,b2,EPSILON*Area(),bt);
								if(dist>bt) { dist = bt; return true; }
								else return false;
							}
							break;
						
					case 1:  /************* Y AXIS **************/
						scaleFactor= 1/fPlane.Direction()[1];
						fEdge[0]*=scaleFactor; fEdge[1]*=scaleFactor; fEdge[2]*=scaleFactor;
						
						b0 = fEdge[1][2]*(p[0] - f.cP(1)[0]) - fEdge[1][0]*(p[2] - f.cP(1)[2]);
						if(b0<=0)
						{
							b0 = PSDist(q,f.V(1)->cP(),f.V(2)->cP(),p);
							if(dist>b0) { dist = b0; return true; }
							else return false;
						}
							b1 = fEdge[2][2]*(p[0] - f.cP(2)[0]) - fEdge[2][0]*(p[2] - f.cP(2)[2]);
						if(b1<=0)
						{
							b1 = PSDist(q,f.V(2)->cP(),f.V(0)->cP(),p);
							if(dist>b1) { dist = b1; return true; }
							else return false;
						}
							b2 = fEdge[0][2]*(p[0] - f.cP(0)[0]) - fEdge[0][0]*(p[2] - f.cP(0)[2]);
						if(b2<=0)
						{
							b2 = PSDist(q,f.V(0)->cP(),f.V(1)->cP(),p);
							if(dist>b2) { dist = b2; return true; }
							else return false;
						}
            if( (b=vcg::math::Min<ScalarType>(b0,b1,b2)) < EPS*DoubleArea(f))
							{
								ScalarType bt;
								if(b==b0) 	    bt = PSDist(q,f.V(1)->cP(),f.V(2)->cP(),p);
								else if(b==b1) 	bt = PSDist(q,f.V(2)->cP(),f.V(0)->cP(),p);
								else if(b==b2) 	bt = PSDist(q,f.V(0)->cP(),f.V(1)->cP(),p);
								//printf("Warning area:%g %g %g %g thr:%g bt:%g\n",Area(), b0,b1,b2,EPSILON*Area(),bt);
								if(dist>bt) { dist = bt; return true; }
								else return false;
							}
							break;
						
					case 2:  /************* Z AXIS **************/
						scaleFactor= 1/fPlane.Direction()[2];
						fEdge[0]*=scaleFactor; fEdge[1]*=scaleFactor; fEdge[2]*=scaleFactor;
						
						b0 = fEdge[1][0]*(p[1] - f.cP(1)[1]) - fEdge[1][1]*(p[0] - f.cP(1)[0]);
						if(b0<=0)
						{
							b0 = PSDist(q,f.V(1)->cP(),f.V(2)->cP(),p);
							if(dist>b0) { dist = b0; return true; }
							else return false;
						}
							b1 = fEdge[2][0]*(p[1] - f.cP(2)[1]) - fEdge[2][1]*(p[0] - f.cP(2)[0]);
						if(b1<=0)
						{
							b1 = PSDist(q,f.V(2)->cP(),f.V(0)->cP(),p);
							if(dist>b1) { dist = b1; return true; }
							else return false;
						}
							b2 = fEdge[0][0]*(p[1] - f.cP(0)[1]) - fEdge[0][1]*(p[0] - f.cP(0)[0]);
						if(b2<=0)
						{
							b2 = PSDist(q,f.V(0)->cP(),f.V(1)->cP(),p);
							if(dist>b2) { dist = b2; return true; }
							else return false;
						}
            if( (b=vcg::math::Min<ScalarType>(b0,b1,b2)) < EPS*DoubleArea(f))
							{
								ScalarType bt;
								if(b==b0) 	    bt = PSDist(q,f.V(1)->cP(),f.V(2)->cP(),p);
								else if(b==b1) 	bt = PSDist(q,f.V(2)->cP(),f.V(0)->cP(),p);
								else if(b==b2) 	bt = PSDist(q,f.V(0)->cP(),f.V(1)->cP(),p);
								//printf("Warning area:%g %g %g %g thr:%g bt:%g\n",Area(), b0,b1,b2,EPSILON*Area(),bt);
								
								if(dist>bt) { dist = bt; return true; }
								else return false;
							}
							break;
						default: assert(0); // if you get this assert it means that you forgot to set the required UpdateFlags<MeshType>::FaceProjection(m);

				}
				
				dist = ScalarType(fabs(d));
				//dist = Distance(p,q);
				return true;
		}
		
	template <class S>
	class PointDistanceBaseFunctor {
public:
			typedef S ScalarType;
			typedef Point3<ScalarType> QueryType;

 		  static inline const Point3<ScalarType> & Pos(const Point3<ScalarType> & qt)  {return qt;}
			template <class FACETYPE, class SCALARTYPE>
			inline bool operator () (const FACETYPE & f, const Point3<SCALARTYPE> & p, SCALARTYPE & minDist, Point3<SCALARTYPE> & q) {
				const Point3<typename FACETYPE::ScalarType> fp = Point3<typename FACETYPE::ScalarType>::Construct(p);
				Point3<typename FACETYPE::ScalarType> fq;
				typename FACETYPE::ScalarType md = (typename FACETYPE::ScalarType)(minDist);
				const bool ret = PointDistanceBase(f, fp, md, fq);
				minDist = (SCALARTYPE)(md);
				q = Point3<SCALARTYPE>::Construct(fq);
				return (ret);
			}
		};
		
		
		
}	 // end namespace face
	
}	 // end namespace vcg


#endif

