/****************************************************************************
 * 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 QString();
	}
}

QString FilterColorProjectionPlugin::pythonFilterName(ActionIDType f) const
{
	switch (f) {
	case FP_SINGLEIMAGEPROJ: return QString("compute_color_from_current_raster_projection");
	case FP_MULTIIMAGETRIVIALPROJ: return QString("compute_color_from_active_rasters_projection");
	case FP_MULTIIMAGETRIVIALPROJTEXTURE:
		return QString("compute_color_and_texture_from_active_rasters_projection");
	default: assert(0); return QString();
	}
}

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