/****************************************************************************
* MeshLab                                                           o o     *
* A versatile mesh processing toolbox                             o     o   *
*                                                                _   O  _   *
* Copyright(C) 2005                                                \/)\/    *
* 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.                                                         *
*                                                                           *
****************************************************************************/

#include <QFileDialog>

#include <cmath>
#include <cstdlib>
#include <ctime>

#include "filter_color_projection.h"

#include "floatbuffer.cpp"

#include "render_helper.cpp"

#include "pushpull.h"
#include "rastering.h"
#include <vcg/complex/algorithms/update/texture.h>

using namespace std;
using namespace vcg;

// utility---------------------------------

#define CheckError(x,y); if ((x)) {this->errorMessage = (y); return false;}

static QString extractFilenameWOExt(MeshModel* mm)
{
    QFileInfo fi(mm->fullName());
    return fi.baseName();
}
//-----------------------------------------

// Constructor
FilterColorProjectionPlugin::FilterColorProjectionPlugin()
{
	typeList 
    <<  FP_SINGLEIMAGEPROJ
    <<  FP_MULTIIMAGETRIVIALPROJ
    <<  FP_MULTIIMAGETRIVIALPROJTEXTURE;

	foreach(FilterIDType tt , types())
		actionList << new QAction(filterName(tt), this);
}

// ST() return the very short string describing each filtering action
QString FilterColorProjectionPlugin::filterName(FilterIDType filterId) const
{
	switch(filterId) {
		case FP_SINGLEIMAGEPROJ	:	return QString("Project current raster color to current mesh");
		case FP_MULTIIMAGETRIVIALPROJ	:	return QString("Project active rasters color to current mesh");
		case FP_MULTIIMAGETRIVIALPROJTEXTURE	:	return QString("Project active rasters color to current mesh, filling the texture");
		default : assert(0);
	}
}

// Info() return the longer string describing each filtering action
 QString FilterColorProjectionPlugin::filterInfo(FilterIDType filterId) const
{
	switch(filterId) {
		case FP_SINGLEIMAGEPROJ	:	return QString("Color information from the current raster is perspective-projected on the current mesh");
		case FP_MULTIIMAGETRIVIALPROJ	:	return QString("Color information from all the active rasters is perspective-projected on the current mesh using basic weighting");
		case FP_MULTIIMAGETRIVIALPROJTEXTURE  :	return QString("Color information from all the active rasters is perspective-projected on the current mesh, filling the texture, using basic weighting");
    default : assert(0);
	}
}

// What "new" properties the plugin requires
int FilterColorProjectionPlugin::getRequirements(QAction *action){
  switch(ID(action)){
    case FP_SINGLEIMAGEPROJ:  return MeshModel::MM_VERTCOLOR;
    case FP_MULTIIMAGETRIVIALPROJ:  return MeshModel::MM_VERTCOLOR;
    case FP_MULTIIMAGETRIVIALPROJTEXTURE : return 0;
    default: assert(0); return 0;
  }
  return 0;
}


// This function define the needed parameters for each filter.
void FilterColorProjectionPlugin::initParameterSet(QAction *action, MeshDocument &md, RichParameterSet & parlst)
{
	MeshModel *mm=md.mm();
	
	switch(ID(action))
	{
		case FP_SINGLEIMAGEPROJ :
		{
			parlst.addParam(new RichBool ("usedepth",
				true,
				"Use depth for projection",
				"If true, depth is used to restrict projection on visible faces"));
			parlst.addParam(new RichFloat ("deptheta",
				0.5,
				"depth threshold",
				"threshold value for depth buffer projection (shadow buffer)"));
			parlst.addParam(new RichBool ("onselection",
				false,
				"Only on selecton",
				"If true, projection is only done for selected vertices"));
		}
		break;

		case FP_MULTIIMAGETRIVIALPROJ :
		{
			parlst.addParam(new RichFloat ("deptheta",
				0.5,
				"depth threshold",
				"threshold value for depth buffer projection (shadow buffer)"));
			parlst.addParam(new RichBool ("onselection",
				false,
				"Only on selecton",
				"If true, projection is only done for selected vertices"));
			parlst.addParam(new RichBool ("useangle",
				true,
				"use angle weight",
				"If true, color contribution is weighted by pixel view angle"));
			parlst.addParam(new RichBool ("usedistance",
				true,
				"use distance weight",
				"If true, color contribution is weighted by pixel view distance"));
			parlst.addParam(new RichBool ("useborders",
				true,
				"use image borders weight",
				"If true, color contribution is weighted by pixel distance from image boundaries"));
			parlst.addParam(new RichBool ("usesilhouettes",
				true,
				"use depth discontinuities weight",
				"If true, color contribution is weighted by pixel distance from depth discontinuities (external and internal silhouettes)"));
			parlst.addParam(new RichBool ("usealpha",
				false,
				"use image alpha weight",
				"If true, alpha channel of the image is used as additional weight. In this way it is possible to mask-out parts of the images that should not be projected on the mesh. Please note this is not a transparency effect, but just influences the weigthing between different images"));
		}
		break;

		case FP_MULTIIMAGETRIVIALPROJTEXTURE :
		{
      QString fileName = extractFilenameWOExt(md.mm());
      fileName = fileName.append("_color.png");
      parlst.addParam(new RichString("textName", 
        fileName, 
        "Texture file", 
        "The texture file to be created"));
			parlst.addParam(new RichInt ("texsize",
				1024,
				"pixel size of texture image",
				"pixel size of texture image, the image will be a square tsize X tsize, most applications do require that tsize is a power of 2"));
			parlst.addParam(new RichBool ("dorefill",
				true,
				"fill atlas gaps",
				"If true, unfilled areas of the mesh are interpolated, to avoid visible seams while mipmapping"));
			parlst.addParam(new RichFloat ("deptheta",
				0.5,
				"depth threshold",
				"threshold value for depth buffer projection (shadow buffer)"));
			parlst.addParam(new RichBool ("onselection",
				false,
				"Only on selecton",
				"If true, projection is only done for selected vertices"));
			parlst.addParam(new RichBool ("useangle",
				true,
				"use angle weight",
				"If true, color contribution is weighted by pixel view angle"));
			parlst.addParam(new RichBool ("usedistance",
				true,
				"use distance weight",
				"If true, color contribution is weighted by pixel view distance"));
			parlst.addParam(new RichBool ("useborders",
				true,
				"use image borders weight",
				"If true, color contribution is weighted by pixel distance from image boundaries"));
			parlst.addParam(new RichBool ("usesilhouettes",
				true,
				"use depth discontinuities weight",
				"If true, color contribution is weighted by pixel distance from depth discontinuities (external and internal silhouettes)"));
			parlst.addParam(new RichBool ("usealpha",
				false,
				"use image alpha weight",
				"If true, alpha channel of the image is used as additional weight. In this way it is possible to mask-out parts of the images that should not be projected on the mesh. Please note this is not a transparency effect, but just influences the weigthing between different images"));
		}
		break;

		default: break; // do not add any parameter for the other filters
	}
}

// Core Function doing the actual mesh processing.
bool FilterColorProjectionPlugin::applyFilter(QAction *filter, MeshDocument &md, RichParameterSet & par, vcg::CallBackPos *cb)
{
	CMeshO::FaceIterator fi;
  CMeshO::VertexIterator vi;

  RenderHelper *rendermanager=NULL;

	switch(ID(filter))
	{

    ////--------------------------- project single trivial ----------------------------------

	case FP_SINGLEIMAGEPROJ :
	{
		bool use_depth = par.getBool("usedepth");
		bool onselection = par.getBool("onselection");
		float eta = par.getFloat("deptheta");

		Point2f  pp;       // projected point
		float depth=0;     // depth of point (distance from camera)
		float pdepth=0;    // depth value of projected point (from depth map)

		// get current raster and model
		RasterModel *raster   = md.rm();
		MeshModel   *model    = md.mm();

		// no projection if camera not valid
		if(!raster->shot.IsValid())  
			return false;

		// the mesh has to be correctly transformed before mapping
		tri::UpdatePosition<CMeshO>::Matrix(model->cm,model->cm.Tr,true);
		tri::UpdateBounding<CMeshO>::Box(model->cm);

		// making context current
		glContext->makeCurrent();

		if(use_depth)
		{
			// init rendermanager
			rendermanager = new RenderHelper();
			if( rendermanager->initializeGL(cb) != 0 )
				return false;
			Log("init GL");
			if( rendermanager->initializeMeshBuffers(model, cb) != 0 )
				return false;
			Log("init Buffers");

			// render depth
			rendermanager->renderScene(raster->shot, model, RenderHelper::FLAT);
		}

		// unmaking context current
		glContext->doneCurrent();

		qDebug("Viewport %i %i",raster->shot.Intrinsics.ViewportPx[0],raster->shot.Intrinsics.ViewportPx[1]);
		for(vi=model->cm.vert.begin();vi!=model->cm.vert.end();++vi)
		{
			if(!(*vi).IsD() && (!onselection || (*vi).IsS()))
			{
				pp = raster->shot.Project((*vi).P());
				// pray is the vector from the point-to-be-colored to the camera center
				Point3f pray = (raster->shot.GetViewPoint() - (*vi).P()).Normalize();

				//if inside image
				if(pp[0]>0 && pp[1]>0 && pp[0]<raster->shot.Intrinsics.ViewportPx[0] && pp[1]<raster->shot.Intrinsics.ViewportPx[1])
				{
					if((pray.dot(-raster->shot.Axis(2))) <= 0.0)
					{
						if(use_depth)
						{
							depth  = raster->shot.Depth((*vi).P());
							pdepth = rendermanager->depth->getval(int(pp[0]), int(pp[1]));//rendermanager->depth[(int(pp[1]) * raster->shot.Intrinsics.ViewportPx[0]) + int(pp[0])];           
						}

						if(!use_depth || (depth <= (pdepth + eta)))
						{
							QRgb pcolor = raster->currentPlane->image.pixel(pp[0],raster->shot.Intrinsics.ViewportPx[1] - pp[1]);
							(*vi).C() = vcg::Color4b(qRed(pcolor), qGreen(pcolor), qBlue(pcolor), 255);          
						}
					}
				}
			}
		}

		// the mesh has to return to its original position
		tri::UpdatePosition<CMeshO>::Matrix(model->cm,Inverse(model->cm.Tr),true);
		tri::UpdateBounding<CMeshO>::Box(model->cm);

		// delete rendermanager
		if(rendermanager != NULL)
			delete rendermanager;
		}

		break;


    ////--------------------------- project multi trivial ----------------------------------

    case FP_MULTIIMAGETRIVIALPROJ :
    {

		
      bool onselection = par.getBool("onselection");
      float eta = par.getFloat("deptheta");
      bool  useangle = par.getBool("useangle"); 
      bool  usedistance = par.getBool("usedistance"); 
      bool  useborders = par.getBool("useborders");
      bool  usesilhouettes = par.getBool("usesilhouettes");
      bool  usealphamask =  par.getBool("usealpha");

      Point2f  pp;        // projected point
      float  depth=0;     // depth of point (distance from camera)
      float  pdepth=0;    // depth value of projected point (from depth map)
      double pweight;     // pixel weight
      MeshModel *model;
      bool do_project;
      int cam_ind;

      // min max depth for depth weight normalization
      float allcammaxdepth;
      float allcammindepth;

      // max image size for border weight normalization
      float allcammaximagesize;

      // accumulation buffers for colors and weights
      int buff_ind;
      double *weights;
      double *acc_red;
      double *acc_grn;
      double *acc_blu;

      // filename for debug dump
      char dumpFileName[1024];

      // get current model
      model = md.mm();

      // the mesh has to be correctly transformed before mapping
      tri::UpdatePosition<CMeshO>::Matrix(model->cm,model->cm.Tr,true);
      tri::UpdateBounding<CMeshO>::Box(model->cm);

      // init accumulation buffers for colors and weights
      Log("init color accumulation buffers");
      weights = new double[model->cm.vn];
      acc_red = new double[model->cm.vn];
      acc_grn = new double[model->cm.vn];
      acc_blu = new double[model->cm.vn];
      for(int buff_ind=0; buff_ind<model->cm.vn; buff_ind++)
      {
        weights[buff_ind] = 0.0;
        acc_red[buff_ind] = 0.0;
        acc_grn[buff_ind] = 0.0;
        acc_blu[buff_ind] = 0.0;
      }

      // calculate accuratenear/far for all cameras
      std::vector<float> my_near;
      std::vector<float> my_far;
      calculateNearFarAccurate(md, &my_near, &my_far);

      allcammaxdepth =  -1000000;
      allcammindepth =   1000000;
      allcammaximagesize = -1000000;
      for(cam_ind = 0; cam_ind < md.rasterList.size(); cam_ind++)
      {
        if(my_far[cam_ind] > allcammaxdepth)
          allcammaxdepth = my_far[cam_ind];
        if(my_near[cam_ind] < allcammindepth)
          allcammindepth = my_near[cam_ind];

        float imgdiag = sqrt(double(md.rasterList[cam_ind]->shot.Intrinsics.ViewportPx[0] * md.rasterList[cam_ind]->shot.Intrinsics.ViewportPx[1]));
        if (imgdiag > allcammaximagesize)
          allcammaximagesize = imgdiag;
      }

      //-- cycle all cameras
      cam_ind = 0;
      foreach(RasterModel *raster, md.rasterList)
        if(raster->visible)
        {
          do_project = true;

          // no drawing if camera not valid
          if(!raster->shot.IsValid())  
            do_project = false;

          // no drawing if raster is not active
          //if(!raster->shot.IsValid())  
          //  do_project = false;

          if(do_project)
          {
			// making context current
			glContext->makeCurrent();

            // delete & reinit rendermanager
            if(rendermanager != NULL)
              delete rendermanager;
            rendermanager = new RenderHelper();
            if( rendermanager->initializeGL(cb) != 0 )
              return false;
            Log("init GL");
            if( rendermanager->initializeMeshBuffers(model, cb) != 0 )
              return false;
            Log("init Buffers");

            // render normal & depth
            rendermanager->renderScene(raster->shot, model, RenderHelper::NORMAL, my_near[cam_ind]*0.5, my_far[cam_ind]*1.25);

			// unmaking context current
			glContext->doneCurrent();

            buff_ind=0;

            // If should be used silhouette weighting, it is needed to compute depth discontinuities
            // and per-pixel distance from detected borders on the entire image here
            // the weight is then applied later, per-vertex, when needed
            floatbuffer *silhouette_buff=NULL;
            float maxsildist = rendermanager->depth->sx + rendermanager->depth->sy;
            if(usesilhouettes)
            {
              silhouette_buff = new floatbuffer();
              silhouette_buff->init(rendermanager->depth->sx, rendermanager->depth->sy);

              silhouette_buff->applysobel(rendermanager->depth);
		          //sprintf(dumpFileName,"Abord%i.pfm",cam_ind);
		          //silhouette_buff->dumppfm(dumpFileName);

		          silhouette_buff->initborder(rendermanager->depth);
		          //sprintf(dumpFileName,"Bbord%i.pfm",cam_ind);
		          //silhouette_buff->dumppfm(dumpFileName);

		          maxsildist = silhouette_buff->distancefield();
		          //sprintf(dumpFileName,"Cbord%i.pfm",cam_ind);
		          //silhouette_buff->dumppfm(dumpFileName);
            }

            for(vi=model->cm.vert.begin();vi!=model->cm.vert.end();++vi)
            {
              if(!(*vi).IsD() && (!onselection || (*vi).IsS()))
              {
                // pp is the projected point in image space
                pp = raster->shot.Project((*vi).P());
                // pray is the vector from the point-to-be-colored to the camera center
	              Point3f pray = (raster->shot.GetViewPoint() - (*vi).P()).Normalize();


                //if inside image
                if(pp[0]>=0 && pp[1]>=0 && pp[0]<raster->shot.Intrinsics.ViewportPx[0] && pp[1]<raster->shot.Intrinsics.ViewportPx[1])
                {
                  if((pray.dot(-raster->shot.Axis(2))) <= 0.0)
                  {

                    depth  = raster->shot.Depth((*vi).P());
                    pdepth = rendermanager->depth->getval(int(pp[0]), int(pp[1])); //  rendermanager->depth[(int(pp[1]) * raster->shot.Intrinsics.ViewportPx[0]) + int(pp[0])]; 

                    if(depth <= (pdepth + eta))
                    {
                      // determine color
                      QRgb pcolor = raster->currentPlane->image.pixel(pp[0],raster->shot.Intrinsics.ViewportPx[1] - pp[1]);
                      // determine weight
                      pweight = 1.0;
                      
                      if(useangle)
                      {
                        Point3f pixnorm;
                        Point3f viewaxis;

                        pixnorm = (*vi).N();
                        pixnorm.Normalize();

                        viewaxis = raster->shot.GetViewPoint() - (*vi).P();
                        viewaxis.Normalize();

                        float ang = abs(pixnorm * viewaxis);
                        ang = min(1.0f, ang);

                        pweight *= ang;
                      }
                      
                      if(usedistance)
                      {
                        float distw = depth;
                        distw = 1.0 - (distw - (allcammindepth*0.99)) / ((allcammaxdepth*1.01) - (allcammindepth*0.99)); 

                        pweight *= distw;
                        pweight *= distw;
                      }

                      if(useborders)
                      {
                        double xdist = 1.0 - (abs(pp[0] - (raster->shot.Intrinsics.ViewportPx[0] / 2.0)) / (raster->shot.Intrinsics.ViewportPx[0] / 2.0));
                        double ydist = 1.0 - (abs(pp[1] - (raster->shot.Intrinsics.ViewportPx[1] / 2.0)) / (raster->shot.Intrinsics.ViewportPx[1] / 2.0));
                        double borderw = min (xdist , ydist);
                        //borderw = min(1.0,borderw); //debug debug
                        //borderw = max(0.0,borderw); //debug debug

                        pweight *= borderw;
                      }                  

                      if(usesilhouettes)
                      {
                        // here the silhouette weight is applied, but it is calculated before, on a per-image basis
                        float silw = 1.0;
                        silw = silhouette_buff->getval(int(pp[0]), int(pp[1])) / maxsildist;
                        //silw = min(1.0f,silw); //debug debug
                        //silw = max(0.0f,silw); //debug debug 

                        pweight *= silw;
                      }

                      if(usealphamask) //alpha channel of image is an additional mask
                      {
                        pweight *= (qAlpha(pcolor) / 255.0);
                      }

                      weights[buff_ind] += pweight;
                      acc_red[buff_ind] += (qRed(pcolor) * pweight / 255.0);
                      acc_grn[buff_ind] += (qGreen(pcolor) * pweight / 255.0);
                      acc_blu[buff_ind] += (qBlue(pcolor) * pweight / 255.0);
                    }
                  }
                }
              }
              buff_ind++;
            }
            cam_ind ++;

            if(usesilhouettes)
            {
              delete silhouette_buff;
            }

          } // end foreach camera
        } // end foreach camera 

      buff_ind = 0;
      for(vi=model->cm.vert.begin();vi!=model->cm.vert.end();++vi)
      {
        if(!(*vi).IsD() && (!onselection || (*vi).IsS()))
        {
          if (weights[buff_ind] != 0) // if 0, it has not found any valid projection on any camera
          {
            (*vi).C() = vcg::Color4b( (acc_red[buff_ind] / weights[buff_ind]) *255.0,
                                      (acc_grn[buff_ind] / weights[buff_ind]) *255.0,
                                      (acc_blu[buff_ind] / weights[buff_ind]) *255.0,
                                      255);
          }
        }
        buff_ind++;
      }

      // the mesh has to return to its original position
      tri::UpdatePosition<CMeshO>::Matrix(model->cm,Inverse(model->cm.Tr),true);
      tri::UpdateBounding<CMeshO>::Box(model->cm);

      // delete rendermanager
      if(rendermanager != NULL)
        delete rendermanager;

      // delete accumulation buffers
      delete[]  weights;
      delete[]  acc_red;
      delete[]  acc_grn;
      delete[]  acc_blu;

		} break;


		case FP_MULTIIMAGETRIVIALPROJTEXTURE :
		{

			if(!tri::HasPerWedgeTexCoord(md.mm()->cm))
			{
				errorMessage="Warning: nothing have been done. Mesh has no Texture Coordinates.";
				return false;
			}

		  bool onselection = par.getBool("onselection");
		  int texsize = par.getInt("texsize");
		  bool  dorefill = par.getBool("dorefill"); 
		  float eta = par.getFloat("deptheta");
		  bool  useangle = par.getBool("useangle"); 
		  bool  usedistance = par.getBool("usedistance"); 
		  bool  useborders = par.getBool("useborders");
		  bool  usesilhouettes = par.getBool("usesilhouettes");
		  bool  usealphamask =  par.getBool("usealpha");
		  QString textName = par.getString("textName");
	      
	      
		  int textW = texsize;   
		  int textH = texsize;

		  Point2f  pp;        // projected point
		  float  depth=0;     // depth of point (distance from camera)
		  float  pdepth=0;    // depth value of projected point (from depth map)
		  double pweight;     // pixel weight
		  MeshModel *model;
		  bool do_project;
		  int cam_ind;
		  int texcount = 0;   // current texel index

		  // min max depth for depth weight normalization
		  float allcammaxdepth;
		  float allcammindepth;

		  // max image size for border weight normalization
		  float allcammaximagesize;

		  // get the working model
		  model = md.mm();

		  // the mesh has to be correctly transformed before mapping
		  tri::UpdatePosition<CMeshO>::Matrix(model->cm,model->cm.Tr,true);
		  tri::UpdateBounding<CMeshO>::Box(model->cm);

		  // texture file name
		  QString filePath(model->fullName());
		  filePath = filePath.left(std::max<int>(filePath.lastIndexOf('\\'),filePath.lastIndexOf('/'))+1);
		  // Check textName and eventually add .png ext
		  CheckError(textName.length() == 0, "Texture file not specified");
		  CheckError(std::max<int>(textName.lastIndexOf("\\"),textName.lastIndexOf("/")) != -1, "Path in Texture file not allowed");
		  if (!textName.endsWith(".png", Qt::CaseInsensitive))
			textName.append(".png");
		  filePath.append(textName);

		  // Image creation
		  CheckError(textW <= 0, "Texture Width has an incorrect value");
		  CheckError(textH <= 0, "Texture Height has an incorrect value");
		  QImage img(QSize(textW,textH), QImage::Format_ARGB32);
		  img.fill(qRgba(0,0,0,0)); // transparent black

		  // Compute (texture-space) border edges
		  if(dorefill)
		  {
			model->updateDataMask(MeshModel::MM_FACEFACETOPO);
			tri::UpdateTopology<CMeshO>::FaceFaceFromTexCoord(model->cm);
			tri::UpdateFlags<CMeshO>::FaceBorderFromFF(model->cm);
		  }

		  // create a list of to-be-filled texels and accumulators
		  // storing texel 2d coords, texel mesh-space point, texel mesh normal

		  vector<TexelDesc> texels;
		  texels.clear();
		  texels.reserve(textW*textH);  // just to avoid the 2x reallocate rule...

		  vector<TexelAccum> accums;
		  accums.clear();
		  accums.reserve(textW*textH);  // just to avoid the 2x reallocate rule...

		  // Rasterizing triangles in the list of voxels
		  TexFillerSampler tfs(img);
		  tfs.texelspointer = &texels;
		  tfs.accumpointer  = &accums;
		  tfs.InitCallback(cb, model->cm.fn, 0, 80);
		  tri::SurfaceSampling<CMeshO,TexFillerSampler>::Texture(model->cm,tfs,textW,textH,true);

		  // Revert alpha values for border edge pixels to 255
		  cb(81, "Cleaning up texture ...");
		  for (int y=0; y<textH; ++y)
			  for (int x=0; x<textW; ++x)
			  {
			  QRgb px = img.pixel(x,y);
			  if (qAlpha(px) < 255 && qAlpha(px) > 0)
				  img.setPixel(x,y, px | 0xff000000);
		  }

		  // calculate accuratenear/far for all cameras
		  std::vector<float> my_near;
		  std::vector<float> my_far;
		  calculateNearFarAccurate(md, &my_near, &my_far);

		  allcammaxdepth =  -1000000;
		  allcammindepth =   1000000;
		  allcammaximagesize = -1000000;
		  for(cam_ind = 0; cam_ind < md.rasterList.size(); cam_ind++)
		  {
			if(my_far[cam_ind] > allcammaxdepth)
			  allcammaxdepth = my_far[cam_ind];
			if(my_near[cam_ind] < allcammindepth)
			  allcammindepth = my_near[cam_ind];

			float imgdiag = sqrt(double(md.rasterList[cam_ind]->shot.Intrinsics.ViewportPx[0] * md.rasterList[cam_ind]->shot.Intrinsics.ViewportPx[1]));
			if (imgdiag > allcammaximagesize)
			  allcammaximagesize = imgdiag;
		  }

		  //-- cycle all cameras
		  cam_ind = 0;
		  foreach(RasterModel *raster, md.rasterList)
			if(raster->visible)
			{
			  do_project = true;

			  // no drawing if camera not valid
			  if(!raster->shot.IsValid())  
				do_project = false;

			  // no drawing if raster is not active
			  //if(!raster->shot.IsValid())  
			  //  do_project = false;

			  if(do_project)
			  {
				// making context current
				glContext->makeCurrent();

				// delete & reinit rendermanager
				if(rendermanager != NULL)
				  delete rendermanager;
				rendermanager = new RenderHelper();
				if( rendermanager->initializeGL(cb) != 0 )
				  return false;
				Log("init GL");
				if( rendermanager->initializeMeshBuffers(model, cb) != 0 )
				  return false;
				Log("init Buffers");

				// render normal & depth
				rendermanager->renderScene(raster->shot, model, RenderHelper::NORMAL, my_near[cam_ind]*0.5, my_far[cam_ind]*1.25);

				// unmaking context current
				glContext->doneCurrent();

				// If should be used silhouette weighting, it is needed to compute depth discontinuities
				// and per-pixel distance from detected borders on the entire image here
				// the weight is then applied later, per-vertex, when needed
				floatbuffer *silhouette_buff=NULL;
				float maxsildist = rendermanager->depth->sx + rendermanager->depth->sy;
				if(usesilhouettes)
				{
				  silhouette_buff = new floatbuffer();
				  silhouette_buff->init(rendermanager->depth->sx, rendermanager->depth->sy);

				  silhouette_buff->applysobel(rendermanager->depth);
					  //sprintf(dumpFileName,"Abord%i.bmp",cam_ind);
					  //silhouette_buff->dumpbmp(dumpFileName);

					  silhouette_buff->initborder(rendermanager->depth);
					  //sprintf(dumpFileName,"Bbord%i.bmp",cam_ind);
					  //silhouette_buff->dumpbmp(dumpFileName);

					  maxsildist = silhouette_buff->distancefield();
					  //sprintf(dumpFileName,"Cbord%i.bmp",cam_ind);
					  //silhouette_buff->dumpbmp(dumpFileName);
				}

				for(texcount=0; texcount < texels.size(); texcount++)
				{
				  pp = raster->shot.Project(texels[texcount].meshpoint);
				  // pray is the vector from the point-to-be-colored to the camera center
				  Point3f pray = (raster->shot.GetViewPoint() - texels[texcount].meshpoint).Normalize();

				  //if inside image
				  if(pp[0]>0 && pp[1]>0 && pp[0]<raster->shot.Intrinsics.ViewportPx[0] && pp[1]<raster->shot.Intrinsics.ViewportPx[1])
				  {
					if((pray.dot(-raster->shot.Axis(2))) <= 0.0)
					{

					  depth  = raster->shot.Depth(texels[texcount].meshpoint);
					  pdepth = rendermanager->depth->getval(int(pp[0]), int(pp[1])); //  rendermanager->depth[(int(pp[1]) * raster->shot.Intrinsics.ViewportPx[0]) + int(pp[0])]; 

					  if(depth <= (pdepth + eta))
					  {
						// determine color
						QRgb pcolor = raster->currentPlane->image.pixel(pp[0],raster->shot.Intrinsics.ViewportPx[1] - pp[1]);
						// determine weight
						pweight = 1.0;
	                    
						if(useangle)
						{
						  Point3f pixnorm;
						  Point3f viewaxis;

						  pixnorm = texels[texcount].meshnormal;
						  pixnorm.Normalize();

						  viewaxis = raster->shot.GetViewPoint() - texels[texcount].meshpoint;
						  viewaxis.Normalize();

						  float ang = abs(pixnorm * viewaxis);
						  ang = min(1.0f, ang);

						  pweight *= ang;
						}
	                    
						if(usedistance)
						{
						  float distw = depth;
						  distw = 1.0 - (distw - (allcammindepth*0.99)) / ((allcammaxdepth*1.01) - (allcammindepth*0.99)); 

						  pweight *= distw;
						  pweight *= distw;
						}

						if(useborders)
						{
						  double xdist = 1.0 - (abs(pp[0] - (raster->shot.Intrinsics.ViewportPx[0] / 2.0)) / (raster->shot.Intrinsics.ViewportPx[0] / 2.0));
						  double ydist = 1.0 - (abs(pp[1] - (raster->shot.Intrinsics.ViewportPx[1] / 2.0)) / (raster->shot.Intrinsics.ViewportPx[1] / 2.0));
						  double borderw = min (xdist , ydist);

						  pweight *= borderw;
						}                  

						if(usesilhouettes)
						{
						  // here the silhouette weight is applied, but it is calculated before, on a per-image basis
						  float silw = 1.0;
						  silw = silhouette_buff->getval(int(pp[0]), int(pp[1])) / maxsildist;
						  pweight *= silw;
						} 

						if(usealphamask) //alpha channel of image is an additional mask
						{
						  pweight *= (qAlpha(pcolor) / 255.0);
						}

						accums[texcount].weights += pweight;
						accums[texcount].acc_red += (qRed(pcolor) * pweight / 255.0);
						accums[texcount].acc_grn += (qGreen(pcolor) * pweight / 255.0);
						accums[texcount].acc_blu += (qBlue(pcolor) * pweight / 255.0);
					  }
					}
				  }
	              
				} // end foreach texel
				cam_ind ++;

				if(usesilhouettes)
				{
				  delete silhouette_buff;
				}

			  } // end if(do_project)

			} // end foreach camera 

		  // for each texel.... divide accumulated values by weight and write to texture
		  for(texcount=0; texcount < texels.size(); texcount++)
		  {
			if(accums[texcount].weights > 0.0)
			{
			  float texel_red   =  accums[texcount].acc_red / accums[texcount].weights;
			  float texel_green =  accums[texcount].acc_grn / accums[texcount].weights;
			  float texel_blue  =  accums[texcount].acc_blu / accums[texcount].weights;        

			  img.setPixel(texels[texcount].texcoord.X(), img.height() - 1 - texels[texcount].texcoord.Y(), qRgba(texel_red*255.0, texel_green*255.0, texel_blue*255.0, 255));
			}
			else         // if no projected data available, black (to be refilled later on
			{
			  img.setPixel(texels[texcount].texcoord.X(), img.height() - 1 - texels[texcount].texcoord.Y(), qRgba(0, 0, 0, 0));
			}        
		  }

		  // cleaning 
		  texels.clear();
		  accums.clear();

		  // PullPush
		  if(dorefill)
		  {
			cb(85, "Filling texture holes...");
	        
			PullPush(img, qRgba(0,0,0,0));      // atlas gaps
		  }

		  // Undo topology changes
		  if(dorefill)
		  {
			tri::UpdateTopology<CMeshO>::FaceFace(model->cm);
			tri::UpdateFlags<CMeshO>::FaceBorderFromFF(model->cm);
		  }

		  // Save texture
		  cb(90, "Saving texture ...");
		  CheckError(!img.save(filePath), "Texture file cannot be saved");
		  Log( "Texture \"%s\" Created", filePath.toStdString().c_str());
		  assert(QFile(filePath).exists());

		  // Assign texture
		  model->cm.textures.clear();
		  model->cm.textures.push_back(textName.toStdString());

		  // the mesh has to return to its original position
		  tri::UpdatePosition<CMeshO>::Matrix(model->cm,Inverse(model->cm.Tr),true);
		  tri::UpdateBounding<CMeshO>::Box(model->cm);


		} break;


	}
	
	return true;
}

FilterColorProjectionPlugin::FilterClass FilterColorProjectionPlugin::getClass(QAction *a)
{
  switch(ID(a)) {
    case FP_SINGLEIMAGEPROJ:
      return FilterClass(Camera + VertexColoring);
      break;
    case FP_MULTIIMAGETRIVIALPROJ:
      return FilterClass(Camera + VertexColoring);
      break;
    case FP_MULTIIMAGETRIVIALPROJTEXTURE:
      return FilterClass(Camera + Texture);
      break;
    default :  assert(0);
      return MeshFilterInterface::Generic;
  }
}

int FilterColorProjectionPlugin::postCondition( QAction* a ) const{
  switch(ID(a)) {
    case FP_SINGLEIMAGEPROJ:
      return MeshModel::MM_VERTCOLOR;
      break;
    case FP_MULTIIMAGETRIVIALPROJ:
      return MeshModel::MM_VERTCOLOR;
      break;
    case FP_MULTIIMAGETRIVIALPROJTEXTURE:
      return MeshModel::MM_UNKNOWN;
      break;
    default: assert(0);
      return MeshModel::MM_NONE;
	}
}




// function to compute sobel on depth image, to find discontinuities
/*
int FilterColorProjectionPlugin::applysobel()
{
  int xx,yy;
  float val;
  float accum;

  for(xx=0; xx<sx; xx++)
	for(yy=0; yy<sy; yy++)
		data[(yy * sx) + xx] = 0;

  for(xx=1; xx<sx-1; xx++)
	for(yy=1; yy<sy-1; yy++)
		if (from->getval(xx, yy) != 0)
			{
				accum=0;
				accum += -1.0 * from->getval(xx-1, yy-1);
				accum += -2.0 * from->getval(xx-1, yy  );
				accum += -1.0 * from->getval(xx-1, yy-1);
				accum += +1.0 * from->getval(xx+1, yy-1);
				accum += +2.0 * from->getval(xx+1, yy  );
				accum += +1.0 * from->getval(xx+1, yy-1);

				data[(yy * sx) + xx] += abs(accum);
			}
		

  for(xx=1; xx<sx-1; xx++)
	for(yy=1; yy<sy-1; yy++)
		if (from->getval(xx, yy) != 0)
			{
				accum=0;
				accum += -1.0 * from->getval(xx-1, yy-1);
				accum += -2.0 * from->getval(xx  , yy-1);
				accum += -1.0 * from->getval(xx-1, yy-1);
				accum += +1.0 * from->getval(xx+1, yy+1);
				accum += +2.0 * from->getval(xx  , yy+1);
				accum += +1.0 * from->getval(xx+1, yy+1);

				data[(yy * sx) + xx] += abs(accum);
			}
		

  return 1;
}
*/

//--- this function calculates the near and far values 
int FilterColorProjectionPlugin::calculateNearFarAccurate(MeshDocument &md, std::vector<float> *near_acc, std::vector<float> *far_acc)
{
  CMeshO::VertexIterator vi;
  int rasterindex;
  Point2f  pp;        // projected point
  Point3f  viewaxis;

  if(near_acc != NULL)
  {
    near_acc->clear();
    near_acc->resize(md.rasterList.size());
  }
  else
    return -1;

  if(far_acc != NULL)
  {
    far_acc->clear();
    far_acc->resize(md.rasterList.size());
  }
  else
    return -1;
  
  // init near and far vectors
  for(rasterindex = 0; rasterindex < md.rasterList.size(); rasterindex++)
  {
    (*near_acc)[rasterindex] =  1000000;
    (*far_acc)[rasterindex]  = -1000000;
  }

  // current model
  MeshModel *model = md.mm();

  // scan all vertices
  for(vi=model->cm.vert.begin();vi!=model->cm.vert.end();++vi)
  {
    if(!(*vi).IsD())
    {
      // check against all cameras
      rasterindex = 0;
      foreach(RasterModel *raster, md.rasterList)
      {    
        if(raster->shot.IsValid())  
        {
          pp = raster->shot.Project((*vi).P());
 
          viewaxis = raster->shot.GetViewPoint() - (*vi).P();
          viewaxis.Normalize();
          
          //if(viewaxis * raster->shot.GetViewDir() > 0.0)     // if facing
            if(pp[0]>0 && pp[1]>0 && pp[0]<raster->shot.Intrinsics.ViewportPx[0] && pp[1]<raster->shot.Intrinsics.ViewportPx[1]) // if inside image
            {
              // then update near and far
              if(raster->shot.Depth((*vi).P()) < (*near_acc)[rasterindex])
                (*near_acc)[rasterindex] = raster->shot.Depth((*vi).P());
              if(raster->shot.Depth((*vi).P()) > (*far_acc)[rasterindex])
                (*far_acc)[rasterindex]  = raster->shot.Depth((*vi).P());
            }

        }
        rasterindex++;
      }
    }
  }

  for(rasterindex = 0; rasterindex < md.rasterList.size(); rasterindex++) // set to 0 0 invalid and "strange" cameras
  {
    if ( ((*near_acc)[rasterindex] == 1000000)  ||  ((*far_acc)[rasterindex] == -1000000) )
    {
      (*near_acc)[rasterindex] = 0;
      (*far_acc)[rasterindex]  = 0;
    }
  }

  return 0;
}




MESHLAB_PLUGIN_NAME_EXPORTER(FilterColorProjectionPlugin)
