/****************************************************************************
* 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.                                                         *
*                                                                           *
****************************************************************************/
/****************************************************************************
  History
$Log: not supported by cvs2svn $
Revision 1.10  2006/12/07 00:39:22  cignoni
Added a class prototype for avoiding the inclusion of tetra.h

Revision 1.9  2006/12/04 09:27:13  cignoni
Removed useless include <tetra.h>

****************************************************************************/
#ifndef __PICK______H
#define __PICK______H

#include <vector>
#include <algorithm>
namespace vcg{

template <class MESH_TYPE>
class GLPickTri
{
	typedef typename MESH_TYPE::FaceIterator FaceIterator;
  typedef typename MESH_TYPE::VertexIterator VertexIterator;
  typedef typename MESH_TYPE::FacePointer  FacePointer;
  typedef typename MESH_TYPE::VertexPointer  VertexPointer;
  typedef typename MESH_TYPE::VertexType  VertexType;

public:

	static bool PickNearestFace(int x, int y, MESH_TYPE &m, FacePointer &fi,int width=4, int height=4)
	{
		std::vector<FacePointer> result;
		int val=PickFace(x,y,m,result,width,height);
		if(val!=0)
		{
			fi=result[0];
			return true;
		}
		fi=NULL;
		return false; 
	}
  static int PickVert(int x, int y, MESH_TYPE &m, std::vector<VertexPointer> &result, int width=4, int height=4,bool sorted=true)
  {
      result.clear();
      if(width==0 ||height==0) return 0;
      long hits;
      int sz=m.vert.size()*5;
      GLuint *selectBuf =new GLuint[sz];
      glSelectBuffer(sz, selectBuf);
      glRenderMode(GL_SELECT);
      glInitNames();

      /* Because LoadName() won't work with no names on the stack */
      glPushName(-1);
      double mp[16];

      GLint viewport[4];
      glGetIntegerv(GL_VIEWPORT,viewport);
      glMatrixMode(GL_PROJECTION);
      glGetDoublev(GL_PROJECTION_MATRIX ,mp);
      glPushMatrix();
      glLoadIdentity();
      gluPickMatrix(x, y, width, height, viewport);
      glMultMatrixd(mp);

      glMatrixMode(GL_MODELVIEW);
      glPushMatrix();
      int vcnt=0;
      VertexIterator vi;
      for(vi=m.vert.begin();vi!=m.vert.end();++vi)
      {
        if(!(*vi).IsD())
        {
          glLoadName(vcnt);
          glBegin(GL_POINTS);
            glVertex( (*vi).P() );
          glEnd();
        }
        vcnt++; // the counter should advance even for deleted faces!
      }

      glPopMatrix();
      glMatrixMode(GL_PROJECTION);
      glPopMatrix();
      glMatrixMode(GL_MODELVIEW);
      hits = glRenderMode(GL_RENDER);
      std::vector< std::pair<double,unsigned int> > H;
      for(long ii=0;ii<hits;ii++){
        H.push_back( std::pair<double,unsigned int>(selectBuf[ii*4+1]/4294967295.0,selectBuf[ii*4+3]));
      }
      if(sorted)
        std::sort(H.begin(),H.end());
      result.resize(H.size());
      for(long ii=0;ii<hits;ii++){
        VertexIterator vi=m.vert.begin();
        advance(vi ,H[ii].second);
        result[ii]=&*vi;
      }

      delete [] selectBuf;
      return result.size();
  }
  
	static int PickFace(int x, int y, MESH_TYPE &m, std::vector<FacePointer> &result, int width=4, int height=4,bool sorted=true)
	{
		result.clear();
    if(width==0 ||height==0) return 0; 
		long hits;	
		int sz=m.face.size()*5;
		GLuint *selectBuf =new GLuint[sz];
		//  static unsigned int selectBuf[16384];
		glSelectBuffer(sz, selectBuf);
		glRenderMode(GL_SELECT);
		glInitNames();

		/* Because LoadName() won't work with no names on the stack */
		glPushName(-1);
		double mp[16];

		GLint viewport[4];
		glGetIntegerv(GL_VIEWPORT,viewport);
		glMatrixMode(GL_PROJECTION);
		glGetDoublev(GL_PROJECTION_MATRIX ,mp);
		glPushMatrix();
		glLoadIdentity();
		//gluPickMatrix(x, viewport[3]-y, 4, 4, viewport);
		gluPickMatrix(x, y, width, height, viewport);
		glMultMatrixd(mp);

		glMatrixMode(GL_MODELVIEW);
		glPushMatrix();
		int fcnt=0; 
		FaceIterator fi;
		for(fi=m.face.begin();fi!=m.face.end();++fi)
		{
			if(!(*fi).IsD())
			{
				glLoadName(fcnt);
				glBegin(GL_TRIANGLES);
					glVertex( (*fi).V(0)->P() );
					glVertex( (*fi).V(1)->P() );
					glVertex( (*fi).V(2)->P() );
				glEnd();
			}
      fcnt++; // the counter should advance even for deleted faces!      
		}

		glPopMatrix();
		glMatrixMode(GL_PROJECTION);
		glPopMatrix();
		glMatrixMode(GL_MODELVIEW);
		hits = glRenderMode(GL_RENDER);
		//xstring buf;
		//if (hits <= 0)     return 0;
		std::vector< std::pair<double,unsigned int> > H;
		for(long ii=0;ii<hits;ii++){
			//TRACE("%ui %ui %ui %ui\n",selectBuf[ii*4],selectBuf[ii*4+1],selectBuf[ii*4+2],selectBuf[ii*4+3]);
			H.push_back( std::pair<double,unsigned int>(selectBuf[ii*4+1]/4294967295.0,selectBuf[ii*4+3]));
		}
		if(sorted) 
      std::sort(H.begin(),H.end());
		//  if(H.size()>0) TRACE("\n Closest is %i\n",H[0].second);
		result.resize(H.size());
		for(long ii=0;ii<hits;ii++){
			FaceIterator fi=m.face.begin();
			advance(fi ,H[ii].second);
			result[ii]=&*fi;
		}

		delete [] selectBuf;
		return result.size();
	}

 // Same of above but it also assumes that you want only visible faces.
 // Visibility is computed according to the current depth buffer. 
	static int PickFaceVisible(int x, int y, MESH_TYPE &m, std::vector<FacePointer> &resultZ, int width=4, int height=4, bool sorted=true)
	{
		// First step 
		
		double mm[16];
		double mp[16];
		GLint vp[4];
		glGetIntegerv(GL_VIEWPORT,vp);
		glGetDoublev(GL_MODELVIEW_MATRIX ,mm);
		glGetDoublev(GL_PROJECTION_MATRIX ,mp);
		int screenW = 		vp[2]-vp[0];
		int screenH = 		vp[3]-vp[1];
		
		GLfloat *buffer = new GLfloat[screenW*screenH];
		glReadPixels(vp[0],vp[1],vp[2],vp[3],GL_DEPTH_COMPONENT,GL_FLOAT,buffer);
		
		std::vector<FacePointer> result;
		PickFace(x,y,m,result,width,height,sorted);
		float LocalEpsilon = 0.001f;
    for(size_t i =0;i<result.size();++i)
		{	
			typename VertexType::CoordType v=Barycenter(*(result[i]));
			GLdouble tx,ty,tz;
			gluProject(v.X(),v.Y(),v.Z(), mm,mp,vp, &tx,&ty,&tz);
			if(tx >=0 && tx<screenW && ty >=0 && ty<screenH)
			{
					float bufZ = buffer[int(tx)+int(ty)*screenW]; 
					//qDebug("face %i txyz (%f %f %f)  bufz %f",i,tx,ty,tz,bufZ);
					if(bufZ + LocalEpsilon >= tz)
								resultZ.push_back(result[i]);
			}
		}
		
		delete [] buffer;
		return resultZ.size();
	}

};


//////////////////////////////////////////////////////////////////////////
template <class TETRA_MESH_TYPE>
class GLPickTetra
{
	typedef typename TETRA_MESH_TYPE::TetraIterator TetraIterator;
	typedef typename TETRA_MESH_TYPE::TetraPointer  TetraPointer;
	typedef typename TETRA_MESH_TYPE::VertexType  VertexType;

public:
static bool PickNearestTetra(int x, int y,TETRA_MESH_TYPE &m, TetraIterator &ti,int width=4, int height=4)
{
 std::vector<TetraPointer> result;
 int val=PickTetra(x,y,m,result,width,height);
 if(val!=0)
 {
  ti=result[0];
	return true;
 }
 ti=0;
 return false; 
}


// class prototype needed for avoid the inclusion of tetra.h
class Tetra;

static int PickTetra(int x, int y, TETRA_MESH_TYPE &m, std::vector<TetraPointer> &result, int width=4, int height=4)
{
	result.clear();
	long hits;	
  int sz=m.tetra.size()*5;
	unsigned int *selectBuf =new unsigned int[sz];
	//  static unsigned int selectBuf[16384];
  glSelectBuffer(sz, selectBuf);
  glRenderMode(GL_SELECT);
  glInitNames();

  /* Because LoadName() won't work with no names on the stack */
  glPushName(-1);
	double mp[16];
	
  int viewport[4];
  glGetIntegerv(GL_VIEWPORT,viewport);
	glMatrixMode(GL_PROJECTION);
	glGetDoublev(GL_PROJECTION_MATRIX ,mp);
	glPushMatrix();
  glLoadIdentity();
  //gluPickMatrix(x, viewport[3]-y, 4, 4, viewport);
  gluPickMatrix(x, y, width, height, viewport);
	glMultMatrixd(mp);

	glMatrixMode(GL_MODELVIEW);
  glPushMatrix();
  int tetracnt=0; 
	TetraIterator ti;
	for(ti=m.tetra.begin();ti!=m.tetra.end();++ti)
	{
		if(!(*ti).IsD())
		{
			glLoadName(tetracnt);
			glBegin(GL_TRIANGLES);
			for (int face=0;face<4;face++)
			{
				//glLoadName(tetracnt);
				VertexType *v0=ti->V(Tetra::VofF(face,0));
				VertexType *v1=ti->V(Tetra::VofF(face,1));
				VertexType *v2=ti->V(Tetra::VofF(face,2));
				glVertex(v0->P());
				glVertex(v1->P());
				glVertex(v2->P());
				
			}
			glEnd();
			tetracnt++;
		}
		
	}

  glPopMatrix();
  glMatrixMode(GL_PROJECTION);
	glPopMatrix();
  glMatrixMode(GL_MODELVIEW);
  hits = glRenderMode(GL_RENDER);
	//xstring buf;
	//if (hits <= 0)     return 0;
	std::vector< std::pair<double,unsigned int> > H;
	int ii;
	for(ii=0;ii<hits;ii++){
		//TRACE("%ui %ui %ui %ui\n",selectBuf[ii*4],selectBuf[ii*4+1],selectBuf[ii*4+2],selectBuf[ii*4+3]);
		H.push_back( std::pair<double,unsigned int>(selectBuf[ii*4+1]/4294967295.0,selectBuf[ii*4+3]));
	}
	std::sort(H.begin(),H.end());
//  if(H.size()>0) TRACE("\n Closest is %i\n",H[0].second);
  result.resize(H.size());
	for(ii=0;ii<hits;ii++){
		TetraIterator ti=m.tetra.begin();
		advance(ti ,H[ii].second);
		result[ii]=&*ti;
	}
	
	delete [] selectBuf;
  return result.size();
}

static bool PickNearestTetraFace(int x, int y,TETRA_MESH_TYPE &m, TetraIterator &ti,int &face,int width=4, int height=4)
{
 std::vector<std::pair<TetraPointer,int> > result;
 int val=PickTetraFace(x,y,m,result,width,height);
 if(val!=0)
 {
  ti=result[0].first;
  face=result[0].second;
  return true;
 }
 ti=0;
 face=-1;
 return false; 
}

static int PickTetraFace(int x, int y, TETRA_MESH_TYPE &m, std::vector<std::pair<TetraPointer,int> > &result, int width=4, int height=4)
{
	result.clear();
	long hits;	
  int sz=(m.tetra.size()*4)*5;
	unsigned int *selectBuf =new unsigned int[sz];
	//  static unsigned int selectBuf[16384];
  glSelectBuffer(sz, selectBuf);
  glRenderMode(GL_SELECT);
  glInitNames();

  /* Because LoadName() won't work with no names on the stack */
  glPushName(-1);
	double mp[16];
	
  int viewport[4];
  glGetIntegerv(GL_VIEWPORT,viewport);
	glMatrixMode(GL_PROJECTION);
	glGetDoublev(GL_PROJECTION_MATRIX ,mp);
	glPushMatrix();
  glLoadIdentity();
  //gluPickMatrix(x, viewport[3]-y, 4, 4, viewport);
  gluPickMatrix(x, y, width, height, viewport);
	glMultMatrixd(mp);
	glMatrixMode(GL_MODELVIEW);
  glPushMatrix();
  int tetracnt=0; 
	TetraIterator ti;
	VertexType *v0;
	VertexType *v1;
	VertexType *v2;
	int face;
	for(ti=m.tetra.begin();ti!=m.tetra.end();++ti)
	{
		if(!(*ti).IsD())
		{
			for (face=0;face<4;face++){
				v0=ti->V(Tetra::VofF(face,0));
				v1=ti->V(Tetra::VofF(face,1));
				v2=ti->V(Tetra::VofF(face,2));
				glLoadName(tetracnt);
				glBegin(GL_TRIANGLES);
					glVertex(v0->P());
					glVertex(v1->P());
					glVertex(v2->P());
				glEnd();
				tetracnt++;
			}
		}
	}

  glPopMatrix();
  glMatrixMode(GL_PROJECTION);
	glPopMatrix();
  glMatrixMode(GL_MODELVIEW);
  hits = glRenderMode(GL_RENDER);
	//xstring buf;
	//if (hits <= 0)     return 0;
	std::vector< std::pair<double,unsigned int> > H;
	int ii;
	for(ii=0;ii<hits;ii++){
		//TRACE("%ui %ui %ui %ui\n",selectBuf[ii*4],selectBuf[ii*4+1],selectBuf[ii*4+2],selectBuf[ii*4+3]);
		H.push_back( std::pair<double,unsigned int>(selectBuf[ii*4+1]/4294967295.0,selectBuf[ii*4+3]));
	}
	std::sort(H.begin(),H.end());
//  if(H.size()>0) TRACE("\n Closest is %i\n",H[0].second);
  result.resize(H.size());
	for(ii=0;ii<hits;ii++){
		TetraIterator ti=m.tetra.begin();
		int index=H[ii].second;
		advance(ti ,(int)(index/4));
		result[ii]=std::pair<TetraPointer,int>(&*ti,index%4);
	}
	
	delete [] selectBuf;
  return result.size();
}

};


}

#endif