/****************************************************************************
* 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 <vcg/space/colorspace.h>

#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)) {throw MLException((y));}

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

// Constructor
FilterColorProjectionPlugin::FilterColorProjectionPlugin()
{
	typeList = {
		FP_SINGLEIMAGEPROJ,
		FP_MULTIIMAGETRIVIALPROJ,
		FP_MULTIIMAGETRIVIALPROJTEXTURE
	};

	for(ActionIDType tt : types())
		actionList.push_back(new QAction(filterName(tt), this));
}

QString FilterColorProjectionPlugin::pluginName() const
{
    return "FilterColorProjection";
}

// ST() return the very short string describing each filtering action
QString FilterColorProjectionPlugin::filterName(ActionIDType 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);
    }
	return NULL;
}

// Info() return the longer string describing each filtering action
QString FilterColorProjectionPlugin::filterInfo(ActionIDType 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);
    }
	return NULL;
}

// What "new" properties the plugin requires
int FilterColorProjectionPlugin::getRequirements(const 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;
}

bool FilterColorProjectionPlugin::requiresGLContext(const QAction* action) const
{
	switch(ID(action)){
	case FP_SINGLEIMAGEPROJ:
	case FP_MULTIIMAGETRIVIALPROJ:
	case FP_MULTIIMAGETRIVIALPROJTEXTURE :
		return true;
	default: 
		assert(0);
	}
	return false;
}


// This function define the needed parameters for each filter.
RichParameterList FilterColorProjectionPlugin::initParameterList(const QAction *action, const MeshDocument &md)
{
    RichParameterList parlst;
    switch(ID(action))
    {
    case FP_SINGLEIMAGEPROJ :
        {
            parlst.addParam(RichBool ("usedepth",
                true,
                "Use depth for projection",
                "If true, depth is used to restrict projection on visible faces"));
            parlst.addParam(RichFloat ("deptheta",
                0.5,
                "depth threshold",
                "threshold value for depth buffer projection (shadow buffer)"));
            parlst.addParam(RichBool ("onselection",
                false,
                "Only on selection",
                "If true, projection is only done for selected vertices"));
			QColor color1 = QColor(0, 0, 0, 255);
			parlst.addParam(RichColor("blankColor", color1, "Color for unprojected areas", "Areas that cannot be projected willb e filled using this color. If R=0 G=0 B=0 A=0 old color is preserved"));
        }
        break;

    case FP_MULTIIMAGETRIVIALPROJ :
        {
            parlst.addParam(RichFloat ("deptheta",
                0.5,
                "depth threshold",
                "threshold value for depth buffer projection (shadow buffer)"));
            parlst.addParam(RichBool ("onselection",
                false,
                "Only on selection",
                "If true, projection is only done for selected vertices"));
            parlst.addParam(RichBool ("useangle",
                true,
                "use angle weight",
                "If true, color contribution is weighted by pixel view angle"));
            parlst.addParam(RichBool ("usedistance",
                true,
                "use distance weight",
                "If true, color contribution is weighted by pixel view distance"));
            parlst.addParam(RichBool ("useborders",
                true,
                "use image borders weight",
                "If true, color contribution is weighted by pixel distance from image boundaries"));
            parlst.addParam(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(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"));
			QColor color1 = QColor(0, 0, 0, 255);
			parlst.addParam(RichColor("blankColor", color1, "Color for unprojected areas", "Areas that cannot be projected willb e filled using this color. If R=0 G=0 B=0 A=0 old color is preserved"));
        }
        break;

    case FP_MULTIIMAGETRIVIALPROJTEXTURE :
        {
            QString fileName = extractFilenameWOExt(md.mm());
            fileName = fileName.append("_color.png");
            parlst.addParam(RichString("textName",
                fileName,
                "Texture file",
                "The texture file to be created"));
            parlst.addParam(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(RichBool ("dorefill",
                true,
                "fill atlas gaps",
                "If true, unfilled areas of the mesh are interpolated, to avoid visible seams while mipmapping"));
            parlst.addParam(RichFloat ("deptheta",
                0.5,
                "depth threshold",
                "threshold value for depth buffer projection (shadow buffer)"));
            parlst.addParam(RichBool ("onselection",
                false,
                "Only on selection",
                "If true, projection is only done for selected vertices"));
            parlst.addParam(RichBool ("useangle",
                true,
                "use angle weight",
                "If true, color contribution is weighted by pixel view angle"));
            parlst.addParam(RichBool ("usedistance",
                true,
                "use distance weight",
                "If true, color contribution is weighted by pixel view distance"));
            parlst.addParam(RichBool ("useborders",
                true,
                "use image borders weight",
                "If true, color contribution is weighted by pixel distance from image boundaries"));
            parlst.addParam(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(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
    }
    return parlst;
}

// Core Function doing the actual mesh processing.
std::map<std::string, QVariant> FilterColorProjectionPlugin::applyFilter(const QAction *filter, const RichParameterList & par, MeshDocument &md, unsigned int& /*postConditionMask*/, vcg::CallBackPos *cb)
{
	if (glContext != nullptr){
		//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");
				Scalarm eta = par.getFloat("deptheta");
				QColor blank = par.getColor("blankColor");

				Scalarm depth=0;     // depth of point (distance from camera)
				Scalarm 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 || !raster->shot.IsValid()) {
					throw MLException("Raster or camera not valid.");
				}

				// 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 )
					{
						delete rendermanager;
						throw MLException("Failed on initializing GL rendermanager.");
					}
					log("init GL");
					//if( rendermanager->initializeMeshBuffers(model, cb) != 0 )
					//    return false;
					//Log("init Buffers");

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

				// 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()))
					{
						Point2m pp = raster->shot.Project((*vi).P());
						// pray is the vector from the point-to-be-colored to the camera center
						Point3m pray = (raster->shot.GetViewPoint() - (*vi).P()).Normalize();

						if ((blank.red() != 0) || (blank.green() != 0) || (blank.blue() != 0) || (blank.alpha() != 0))
							(*vi).C() = vcg::Color4b(blank.red(), blank.green(), blank.blue(), blank.alpha());

						//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");
				Scalarm 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");
				QColor blank = par.getColor("blankColor");

				Scalarm  depth=0;     // depth of point (distance from camera)
				Scalarm  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;

				// 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;
				cam_ind = 0;
				for(const RasterModel& rm : md.rasterIterator()) {
					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(rm.shot.Intrinsics.ViewportPx[0] * rm.shot.Intrinsics.ViewportPx[1]));
					if (imgdiag > allcammaximagesize)
						allcammaximagesize = imgdiag;
					cam_ind++;
				}

				//-- cycle all cameras
				cam_ind = 0;
				for(const RasterModel& raster : md.rasterIterator()){
					if(raster.isVisible()) {
						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 ){
								delete rendermanager;
								throw MLException("Failed on initializing GL rendermanager.");
							}
							log("init GL");
							/*if( rendermanager->initializeMeshBuffers(model, cb) != 0 )
								return false;
							Log("init Buffers");*/

							// render normal & depth
							rendermanager->renderScene(raster.shot, model, RenderHelper::NORMAL, glContext, 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
									Point2m pp = raster.shot.Project((*vi).P());
									// pray is the vector from the point-to-be-colored to the camera center
									Point3m 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) {
													Point3m pixnorm = (*vi).N();
													Point3m viewaxis  = raster.shot.GetViewPoint() - (*vi).P();
													pixnorm.Normalize();
													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);
						}
						else
						{
							if ((blank.red() != 0) || (blank.green() != 0) || (blank.blue() != 0) || (blank.alpha() != 0))
								(*vi).C() = vcg::Color4b(blank.red(), blank.green(), blank.blue(), blank.alpha());
						}
					}
					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))
				{
					throw MLException("Error: nothing have been done. Mesh has no Texture Coordinates.");
				}

				//bool onselection = par.getBool("onselection");
				int texsize = par.getInt("texsize");
				bool  dorefill = par.getBool("dorefill");
				Scalarm 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;

				Scalarm  depth=0;     // depth of point (distance from camera)
				Scalarm  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;

				// 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;
				cam_ind = 0;
				for(const RasterModel& rm : md.rasterIterator()) {
					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(rm.shot.Intrinsics.ViewportPx[0] * rm.shot.Intrinsics.ViewportPx[1]));
					if (imgdiag > allcammaximagesize)
						allcammaximagesize = imgdiag;
					cam_ind++;
				}

				//-- cycle all cameras
				cam_ind = 0;
				for(const RasterModel& raster : md.rasterIterator()) {
					if(raster.isVisible()) {
						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 ) {
								delete rendermanager;
								throw MLException("Failed on initializing GL rendermanager.");
							}
							log("init GL");
							/*if( rendermanager->initializeMeshBuffers(model, cb) != 0 )
								return false;
							Log("init Buffers");*/

							// render normal & depth
							rendermanager->renderScene(raster.shot, model, RenderHelper::NORMAL, glContext, 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(size_t texcount=0; texcount < texels.size(); texcount++) {
								Point2m pp = raster.shot.Project(texels[texcount].meshpoint);
								// pray is the vector from the point-to-be-colored to the camera center
								Point3m 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) {
												Point3m pixnorm = texels[texcount].meshnormal;
												pixnorm.Normalize();

												Point3m 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(size_t 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);
				}

				// Assign texture
				cb(90, "Assigning texture ...");
				model->clearTextures();
				model->addTexture(textName.toStdString(), img);

				// 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;
		default:
			wrongActionCalled(filter);
		}
		return std::map<std::string, QVariant>();
	}
	else {
		throw MLException("Fatal error: glContext not initialized");
	}
}

FilterColorProjectionPlugin::FilterClass FilterColorProjectionPlugin::getClass(const QAction *a) const
{
    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 FilterPlugin::Generic;
    }
}

int FilterColorProjectionPlugin::postCondition( const 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_WEDGTEXCOORD;
        break;
    default: 
        return MeshModel::MM_ALL;
    }
}

//--- 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;

	if(near_acc != nullptr) {
		near_acc->clear();
		near_acc->resize(md.rasterNumber());
	}
	else
		return -1;

	if(far_acc != nullptr) {
		far_acc->clear();
		far_acc->resize(md.rasterNumber());
	}
	else
		return -1;

	// init near and far vectors
	for(unsigned int rasterindex = 0; rasterindex < md.rasterNumber(); 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;
			for(const RasterModel& raster: md.rasterIterator()) {
				if(raster.shot.IsValid()) {
					Point2m pp = raster.shot.Project((*vi).P());

					Point3m 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(unsigned rasterindex = 0; rasterindex < md.rasterNumber(); 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)
