/*
Copyright (c) 2010-2016, Mathieu Labbe - IntRoLab - Universite de Sherbrooke
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
    * Redistributions of source code must retain the above copyright
      notice, this list of conditions and the following disclaimer.
    * Redistributions in binary form must reproduce the above copyright
      notice, this list of conditions and the following disclaimer in the
      documentation and/or other materials provided with the distribution.
    * Neither the name of the Universite de Sherbrooke nor the
      names of its contributors may be used to endorse or promote products
      derived from this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

#include "rtabmap/core/util3d_surface.h"
#include "rtabmap/core/util3d_filtering.h"
#include "rtabmap/core/util3d.h"
#include "rtabmap/core/util2d.h"
#include "rtabmap/core/Memory.h"
#include "rtabmap/core/DBDriver.h"
#include "rtabmap/core/Compression.h"
#include "rtabmap/utilite/ULogger.h"
#include "rtabmap/utilite/UDirectory.h"
#include "rtabmap/utilite/UConversion.h"
#include "rtabmap/utilite/UMath.h"
#include "rtabmap/utilite/UTimer.h"
#include <pcl/search/kdtree.h>
#include <pcl/surface/gp3.h>
#include <pcl/features/normal_3d_omp.h>
#include <pcl/surface/mls.h>
#include <pcl18/surface/texture_mapping.h>
#include <pcl/features/integral_image_normal.h>

#ifndef DISABLE_VTK
#include <pcl/surface/vtk_smoothing/vtk_mesh_quadric_decimation.h>
#endif

#if PCL_VERSION_COMPARE(<, 1, 8, 0)
#include "pcl18/surface/organized_fast_mesh.h"
#else
#include <pcl/surface/organized_fast_mesh.h>
#include <pcl/surface/impl/marching_cubes.hpp>
#include <pcl/surface/impl/organized_fast_mesh.hpp>
#include <pcl/impl/instantiate.hpp>
#include <pcl/point_types.h>

// Instantiations of specific point types
PCL_INSTANTIATE(OrganizedFastMesh, (pcl::PointXYZRGBNormal))

#include <pcl/features/impl/normal_3d_omp.hpp>
#if PCL_VERSION_COMPARE(<=, 1, 8, 0)
#ifdef PCL_ONLY_CORE_POINT_TYPES
PCL_INSTANTIATE_PRODUCT(NormalEstimationOMP, ((pcl::PointXYZRGB))((pcl::Normal)))
#endif
#endif
#endif

namespace rtabmap
{

namespace util3d
{

void createPolygonIndexes(
		const std::vector<pcl::Vertices> & polygons,
		int cloudSize,
		std::vector<std::set<int> > & neighbors,
		std::vector<std::set<int> > & vertexToPolygons)
{
	vertexToPolygons = std::vector<std::set<int> >(cloudSize);
	neighbors = std::vector<std::set<int> >(polygons.size());

	for(unsigned int i=0; i<polygons.size(); ++i)
	{
		std::set<int> vertices(polygons[i].vertices.begin(), polygons[i].vertices.end());

		for(unsigned int j=0; j<polygons[i].vertices.size(); ++j)
		{
			int v = polygons[i].vertices.at(j);
			for(std::set<int>::iterator iter=vertexToPolygons[v].begin(); iter!=vertexToPolygons[v].end(); ++iter)
			{
				int numSharedVertices = 0;
				for(unsigned int k=0; k<polygons.at(*iter).vertices.size() && numSharedVertices<2; ++k)
				{
					if(vertices.find(polygons.at(*iter).vertices.at(k)) != vertices.end())
					{
						++numSharedVertices;
					}
				}
				if(numSharedVertices >= 2)
				{
					neighbors[*iter].insert(i);
					neighbors[i].insert(*iter);
				}
			}
			vertexToPolygons[v].insert(i);
		}
	}
}

std::list<std::list<int> > clusterPolygons(
		const std::vector<std::set<int> > & neighborPolygons,
		int minClusterSize)
{
	std::set<int> polygonsChecked;

	std::list<std::list<int> > clusters;

	for(unsigned int i=0; i<neighborPolygons.size(); ++i)
	{
		if(polygonsChecked.find(i) == polygonsChecked.end())
		{
			std::list<int> currentCluster;
			currentCluster.push_back(i);
			polygonsChecked.insert(i);

			for(std::list<int>::iterator iter=currentCluster.begin(); iter!=currentCluster.end(); ++iter)
			{
				// get neighbor polygons
				std::set<int> neighbors = neighborPolygons[*iter];
				for(std::set<int>::iterator jter=neighbors.begin(); jter!=neighbors.end(); ++jter)
				{
					if(polygonsChecked.insert(*jter).second)
					{
						currentCluster.push_back(*jter);
					}
				}
			}
			if((int)currentCluster.size() > minClusterSize)
			{
				clusters.push_back(currentCluster);
			}
		}
	}
	return clusters;
}

std::vector<pcl::Vertices> organizedFastMesh(
		const pcl::PointCloud<pcl::PointXYZ>::Ptr & cloud,
		double angleTolerance,
		bool quad,
		int trianglePixelSize,
		const Eigen::Vector3f & viewpoint)
{
	UDEBUG("size=%d angle=%f quad=%d triangleSize=%d", (int)cloud->size(), angleTolerance, quad?1:0, trianglePixelSize);
	UASSERT(cloud->is_dense == false);
	UASSERT(cloud->width > 1 && cloud->height > 1);

	pcl::OrganizedFastMesh<pcl::PointXYZ> ofm;
	ofm.setTrianglePixelSize (trianglePixelSize);
	ofm.setTriangulationType (quad?pcl::OrganizedFastMesh<pcl::PointXYZ>::QUAD_MESH:pcl::OrganizedFastMesh<pcl::PointXYZ>::TRIANGLE_RIGHT_CUT);
	ofm.setInputCloud (cloud);
	ofm.setAngleTolerance(angleTolerance);
	ofm.setViewpoint(viewpoint);

	std::vector<pcl::Vertices> vertices;
	ofm.reconstruct (vertices);

	if(quad)
	{
		//flip all polygons (right handed)
		std::vector<pcl::Vertices> output(vertices.size());
		for(unsigned int i=0; i<vertices.size(); ++i)
		{
			output[i].vertices.resize(4);
			output[i].vertices[0] = vertices[i].vertices[0];
			output[i].vertices[3] = vertices[i].vertices[1];
			output[i].vertices[2] = vertices[i].vertices[2];
			output[i].vertices[1] = vertices[i].vertices[3];
		}
		return output;
	}

	return vertices;
}
std::vector<pcl::Vertices> organizedFastMesh(
		const pcl::PointCloud<pcl::PointXYZRGB>::Ptr & cloud,
		double angleTolerance,
		bool quad,
		int trianglePixelSize,
		const Eigen::Vector3f & viewpoint)
{
	UDEBUG("size=%d angle=%f quad=%d triangleSize=%d", (int)cloud->size(), angleTolerance, quad?1:0, trianglePixelSize);
	UASSERT(cloud->is_dense == false);
	UASSERT(cloud->width > 1 && cloud->height > 1);

	pcl::OrganizedFastMesh<pcl::PointXYZRGB> ofm;
	ofm.setTrianglePixelSize (trianglePixelSize);
	ofm.setTriangulationType (quad?pcl::OrganizedFastMesh<pcl::PointXYZRGB>::QUAD_MESH:pcl::OrganizedFastMesh<pcl::PointXYZRGB>::TRIANGLE_RIGHT_CUT);
	ofm.setInputCloud (cloud);
	ofm.setAngleTolerance(angleTolerance);
	ofm.setViewpoint(viewpoint);

	std::vector<pcl::Vertices> vertices;
	ofm.reconstruct (vertices);

	if(quad)
	{
		//flip all polygons (right handed)
		std::vector<pcl::Vertices> output(vertices.size());
		for(unsigned int i=0; i<vertices.size(); ++i)
		{
			output[i].vertices.resize(4);
			output[i].vertices[0] = vertices[i].vertices[0];
			output[i].vertices[3] = vertices[i].vertices[1];
			output[i].vertices[2] = vertices[i].vertices[2];
			output[i].vertices[1] = vertices[i].vertices[3];
		}
		return output;
	}

	return vertices;
}
std::vector<pcl::Vertices> organizedFastMesh(
		const pcl::PointCloud<pcl::PointXYZRGBNormal>::Ptr & cloud,
		double angleTolerance,
		bool quad,
		int trianglePixelSize,
		const Eigen::Vector3f & viewpoint)
{
	UDEBUG("size=%d angle=%f quad=%d triangleSize=%d", (int)cloud->size(), angleTolerance, quad?1:0, trianglePixelSize);
	UASSERT(cloud->is_dense == false);
	UASSERT(cloud->width > 1 && cloud->height > 1);

	pcl::OrganizedFastMesh<pcl::PointXYZRGBNormal> ofm;
	ofm.setTrianglePixelSize (trianglePixelSize);
	ofm.setTriangulationType (quad?pcl::OrganizedFastMesh<pcl::PointXYZRGBNormal>::QUAD_MESH:pcl::OrganizedFastMesh<pcl::PointXYZRGBNormal>::TRIANGLE_RIGHT_CUT);
	ofm.setInputCloud (cloud);
	ofm.setAngleTolerance(angleTolerance);
	ofm.setViewpoint(viewpoint);

	std::vector<pcl::Vertices> vertices;
	ofm.reconstruct (vertices);

	if(quad)
	{
		//flip all polygons (right handed)
		std::vector<pcl::Vertices> output(vertices.size());
		for(unsigned int i=0; i<vertices.size(); ++i)
		{
			output[i].vertices.resize(4);
			output[i].vertices[0] = vertices[i].vertices[0];
			output[i].vertices[3] = vertices[i].vertices[1];
			output[i].vertices[2] = vertices[i].vertices[2];
			output[i].vertices[1] = vertices[i].vertices[3];
		}
		return output;
	}

	return vertices;
}

void appendMesh(
		pcl::PointCloud<pcl::PointXYZRGBNormal> & cloudA,
		std::vector<pcl::Vertices> & polygonsA,
		const pcl::PointCloud<pcl::PointXYZRGBNormal> & cloudB,
		const std::vector<pcl::Vertices> & polygonsB)
{
	UDEBUG("cloudA=%d polygonsA=%d cloudB=%d polygonsB=%d", (int)cloudA.size(), (int)polygonsA.size(), (int)cloudB.size(), (int)polygonsB.size());
	UASSERT(!cloudA.isOrganized() && !cloudB.isOrganized());

	int sizeA = (int)cloudA.size();
	cloudA += cloudB;

	int sizePolygonsA = (int)polygonsA.size();
	polygonsA.resize(sizePolygonsA+polygonsB.size());

	for(unsigned int i=0; i<polygonsB.size(); ++i)
	{
		pcl::Vertices vertices = polygonsB[i];
		for(unsigned int j=0; j<vertices.vertices.size(); ++j)
		{
			vertices.vertices[j] += sizeA;
		}
		polygonsA[i+sizePolygonsA] = vertices;
	}
}

void appendMesh(
		pcl::PointCloud<pcl::PointXYZRGB> & cloudA,
		std::vector<pcl::Vertices> & polygonsA,
		const pcl::PointCloud<pcl::PointXYZRGB> & cloudB,
		const std::vector<pcl::Vertices> & polygonsB)
{
	UDEBUG("cloudA=%d polygonsA=%d cloudB=%d polygonsB=%d", (int)cloudA.size(), (int)polygonsA.size(), (int)cloudB.size(), (int)polygonsB.size());
	UASSERT(!cloudA.isOrganized() && !cloudB.isOrganized());

	int sizeA = (int)cloudA.size();
	cloudA += cloudB;

	int sizePolygonsA = (int)polygonsA.size();
	polygonsA.resize(sizePolygonsA+polygonsB.size());

	for(unsigned int i=0; i<polygonsB.size(); ++i)
	{
		pcl::Vertices vertices = polygonsB[i];
		for(unsigned int j=0; j<vertices.vertices.size(); ++j)
		{
			vertices.vertices[j] += sizeA;
		}
		polygonsA[i+sizePolygonsA] = vertices;
	}
}

std::vector<int> filterNotUsedVerticesFromMesh(
		const pcl::PointCloud<pcl::PointXYZRGBNormal> & cloud,
		const std::vector<pcl::Vertices> & polygons,
		pcl::PointCloud<pcl::PointXYZRGBNormal> & outputCloud,
		std::vector<pcl::Vertices> & outputPolygons)
{
	UDEBUG("size=%d polygons=%d", (int)cloud.size(), (int)polygons.size());
	std::map<int, int> addedVertices; //<oldIndex, newIndex>
	std::vector<int> output; //<oldIndex>
	output.resize(cloud.size());
	outputCloud.resize(cloud.size());
	outputCloud.is_dense = true;
	outputPolygons.resize(polygons.size());
	int oi = 0;
	for(unsigned int i=0; i<polygons.size(); ++i)
	{
		pcl::Vertices & v = outputPolygons[i];
		v.vertices.resize(polygons[i].vertices.size());
		for(unsigned int j=0; j<polygons[i].vertices.size(); ++j)
		{
			std::map<int, int>::iterator iter = addedVertices.find(polygons[i].vertices[j]);
			if(iter == addedVertices.end())
			{
				outputCloud[oi] = cloud.at(polygons[i].vertices[j]);
				addedVertices.insert(std::make_pair(polygons[i].vertices[j], oi));
				output[oi] = polygons[i].vertices[j];
				v.vertices[j] = oi++;
			}
			else
			{
				v.vertices[j] = iter->second;
			}
		}
	}
	outputCloud.resize(oi);
	output.resize(oi);

	return output;
}

std::vector<int> filterNotUsedVerticesFromMesh(
		const pcl::PointCloud<pcl::PointXYZRGB> & cloud,
		const std::vector<pcl::Vertices> & polygons,
		pcl::PointCloud<pcl::PointXYZRGB> & outputCloud,
		std::vector<pcl::Vertices> & outputPolygons)
{
	UDEBUG("size=%d polygons=%d", (int)cloud.size(), (int)polygons.size());
	std::map<int, int> addedVertices; //<oldIndex, newIndex>
	std::vector<int> output; //<oldIndex>
	output.resize(cloud.size());
	outputCloud.resize(cloud.size());
	outputCloud.is_dense = true;
	outputPolygons.resize(polygons.size());
	int oi = 0;
	for(unsigned int i=0; i<polygons.size(); ++i)
	{
		pcl::Vertices & v = outputPolygons[i];
		v.vertices.resize(polygons[i].vertices.size());
		for(unsigned int j=0; j<polygons[i].vertices.size(); ++j)
		{
			std::map<int, int>::iterator iter = addedVertices.find(polygons[i].vertices[j]);
			if(iter == addedVertices.end())
			{
				outputCloud[oi] = cloud.at(polygons[i].vertices[j]);
				addedVertices.insert(std::make_pair(polygons[i].vertices[j], oi));
				output[oi] = polygons[i].vertices[j];
				v.vertices[j] = oi++;
			}
			else
			{
				v.vertices[j] = iter->second;
			}
		}
	}
	outputCloud.resize(oi);
	output.resize(oi);

	return output;
}

std::vector<int> filterNaNPointsFromMesh(
		const pcl::PointCloud<pcl::PointXYZRGB> & cloud,
		const std::vector<pcl::Vertices> & polygons,
		pcl::PointCloud<pcl::PointXYZRGB> & outputCloud,
		std::vector<pcl::Vertices> & outputPolygons)
{
	UDEBUG("size=%d polygons=%d", (int)cloud.size(), (int)polygons.size());
	std::map<int, int> addedVertices; //<oldIndex, newIndex>
	std::vector<int> output; //<oldIndex>
	output.resize(cloud.size());
	outputCloud.resize(cloud.size());
	outputCloud.is_dense = true;
	std::vector<int> organizedToDense(cloud.size(), -1);

	int oi = 0;
	for(unsigned int i=0; i<cloud.size(); ++i)
	{
		if(pcl::isFinite(cloud.at(i)))
		{
			outputCloud.at(oi) = cloud.at(i);
			output[oi] = i;
			organizedToDense[i] = oi;
			++oi;
 		}
	}
	outputCloud.resize(oi);
	output.resize(oi);

	// remap polygons to dense cloud
	outputPolygons = polygons;
	for(unsigned int i=0; i<outputPolygons.size(); ++i)
	{
		pcl::Vertices & v = outputPolygons[i];
		for(unsigned int j=0; j<v.vertices.size(); ++j)
		{
			UASSERT(organizedToDense[v.vertices[j]] >= 0);
			v.vertices[j] = organizedToDense[v.vertices[j]];
		}
	}

	return output;
}

std::vector<pcl::Vertices> filterCloseVerticesFromMesh(
		const pcl::PointCloud<pcl::PointXYZRGBNormal>::Ptr cloud,
		const std::vector<pcl::Vertices> & polygons,
		float radius,
		float angle, // FIXME angle not used
		bool keepLatestInRadius)
{
	UDEBUG("size=%d polygons=%d radius=%f angle=%f keepLatest=%d",
			(int)cloud->size(), (int)polygons.size(), radius, angle, keepLatestInRadius?1:0);
	std::vector<pcl::Vertices> outputPolygons;
	pcl::KdTreeFLANN<pcl::PointXYZRGBNormal>::Ptr kdtree(new pcl::KdTreeFLANN<pcl::PointXYZRGBNormal>);
	kdtree->setInputCloud(cloud);

	std::map<int, int> verticesDone;
	outputPolygons = polygons;
	for(unsigned int i=0; i<outputPolygons.size(); ++i)
	{
		pcl::Vertices & polygon = outputPolygons[i];
		for(unsigned int j=0; j<polygon.vertices.size(); ++j)
		{
			std::map<int, int>::iterator iter = verticesDone.find(polygon.vertices[j]);
			if(iter != verticesDone.end())
			{
				polygon.vertices[j] = iter->second;
			}
			else
			{
				std::vector<int> kIndices;
				std::vector<float> kDistances;
				kdtree->radiusSearch(polygon.vertices[j], radius, kIndices, kDistances);
				if(kIndices.size())
				{
					int reference = -1;
					for(unsigned int z=0; z<kIndices.size(); ++z)
					{
						if(reference == -1)
						{
							reference = kIndices[z];
						}
						else if(keepLatestInRadius)
						{
							if(kIndices[z] < reference)
							{
								reference = kIndices[z];
							}
						}
						else
						{
							if(kIndices[z] > reference)
							{
								reference = kIndices[z];
							}
						}
					}
					if(reference >= 0)
					{
						for(unsigned int z=0; z<kIndices.size(); ++z)
						{
							verticesDone.insert(std::make_pair(kIndices[j], reference));
						}
						polygon.vertices[j] = reference;
					}
				}
				else
				{
					verticesDone.insert(std::make_pair(polygon.vertices[j], polygon.vertices[j]));
				}
			}
		}
	}
	return outputPolygons;
}

std::vector<pcl::Vertices> filterInvalidPolygons(const std::vector<pcl::Vertices> & polygons)
{
	std::vector<pcl::Vertices> output(polygons.size());
	int oi=0;
	for(unsigned int i=0; i<polygons.size(); ++i)
	{
		bool valid = true;
		for(unsigned int j=0; j<polygons[i].vertices.size(); ++j)
		{
			if(polygons[i].vertices[j] == polygons[i].vertices[(j+1)%polygons[i].vertices.size()])
			{
				valid = false;
				break;
			}
		}
		if(valid)
		{
			output[oi++] = polygons[i];
		}
	}
	output.resize(oi);
	return output;
}

pcl::PolygonMesh::Ptr createMesh(
		const pcl::PointCloud<pcl::PointXYZRGBNormal>::Ptr & cloudWithNormals,
		float gp3SearchRadius,
		float gp3Mu,
		int gp3MaximumNearestNeighbors,
		float gp3MaximumSurfaceAngle,
		float gp3MinimumAngle,
		float gp3MaximumAngle,
		bool gp3NormalConsistency)
{
	pcl::PointCloud<pcl::PointXYZRGBNormal>::Ptr cloudWithNormalsNoNaN = removeNaNNormalsFromPointCloud(cloudWithNormals);

	// Create search tree*
	pcl::search::KdTree<pcl::PointXYZRGBNormal>::Ptr tree2 (new pcl::search::KdTree<pcl::PointXYZRGBNormal>);
	tree2->setInputCloud (cloudWithNormalsNoNaN);

	// Initialize objects
	pcl::GreedyProjectionTriangulation<pcl::PointXYZRGBNormal> gp3;
	pcl::PolygonMesh::Ptr mesh(new pcl::PolygonMesh);

	// Set the maximum distance between connected points (maximum edge length)
	gp3.setSearchRadius (gp3SearchRadius);

	// Set typical values for the parameters
	gp3.setMu (gp3Mu);
	gp3.setMaximumNearestNeighbors (gp3MaximumNearestNeighbors);
	gp3.setMaximumSurfaceAngle(gp3MaximumSurfaceAngle); // 45 degrees
	gp3.setMinimumAngle(gp3MinimumAngle); // 10 degrees
	gp3.setMaximumAngle(gp3MaximumAngle); // 120 degrees
	gp3.setNormalConsistency(gp3NormalConsistency);
	gp3.setConsistentVertexOrdering(gp3NormalConsistency);

	// Get result
	gp3.setInputCloud (cloudWithNormalsNoNaN);
	gp3.setSearchMethod (tree2);
	gp3.reconstruct (*mesh);

	//UASSERT(mesh->cloud.data.size()/mesh->cloud.point_step == cloudWithNormalsNoNaN->size());
	//mesh->polygons = normalizePolygonsSide(*cloudWithNormalsNoNaN, mesh->polygons);

	return mesh;
}

pcl::texture_mapping::CameraVector createTextureCameras(
		const std::map<int, Transform> & poses,
		const std::map<int, std::vector<CameraModel> > & cameraModels,
		const std::map<int, cv::Mat> & cameraDepths,
		const std::vector<float> & roiRatios)
{
	UASSERT_MSG(poses.size() == cameraModels.size(), uFormat("%d vs %d", (int)poses.size(), (int)cameraModels.size()).c_str());
	UASSERT(roiRatios.empty() || roiRatios.size() == 4);
	pcl::texture_mapping::CameraVector cameras;
	std::map<int, Transform>::const_iterator poseIter=poses.begin();
	std::map<int, std::vector<CameraModel> >::const_iterator modelIter=cameraModels.begin();
	for(; poseIter!=poses.end(); ++poseIter, ++modelIter)
	{
		UASSERT(poseIter->first == modelIter->first);

		std::map<int, cv::Mat>::const_iterator depthIter = cameraDepths.find(poseIter->first);

		// for each sub camera
		for(unsigned int i=0; i<modelIter->second.size(); ++i)
		{
			pcl::TextureMapping<pcl::PointXYZ>::Camera cam;
			// should be in camera frame
			UASSERT(!modelIter->second[i].localTransform().isNull() && !poseIter->second.isNull());
			Transform t = poseIter->second*modelIter->second[i].localTransform();

			cam.pose = t.toEigen3f();

			if(modelIter->second[i].imageHeight() <=0 || modelIter->second[i].imageWidth() <=0)
			{
				UERROR("Should have camera models with width/height set to create texture cameras!");
				return pcl::texture_mapping::CameraVector();
			}

			UASSERT(modelIter->second[i].fx()>0 && modelIter->second[i].imageHeight()>0 && modelIter->second[i].imageWidth()>0);
			cam.focal_length=modelIter->second[i].fx();
			cam.height=modelIter->second[i].imageHeight();
			cam.width=modelIter->second[i].imageWidth();
			if(modelIter->second.size() == 1)
			{
				cam.texture_file = uFormat("%d", poseIter->first); // camera index
			}
			else
			{
				cam.texture_file = uFormat("%d_%d", poseIter->first, (int)i); // camera index, sub camera model index
			}
			if(!roiRatios.empty())
			{
				cam.roi.resize(4);
				cam.roi[0] = cam.width * roiRatios[0]; // left -> x
				cam.roi[1] = cam.height * roiRatios[2]; // top -> y
				cam.roi[2] = cam.width * (1.0 - roiRatios[1]) - cam.roi[0]; // right -> width
				cam.roi[3] = cam.height * (1.0 - roiRatios[3]) - cam.roi[1]; // bottom -> height
			}

			if(depthIter != cameraDepths.end() && !depthIter->second.empty())
			{
				UASSERT(depthIter->second.type() == CV_32FC1 || depthIter->second.type() == CV_16UC1);
				UASSERT(depthIter->second.cols % modelIter->second.size() == 0);
				int subWidth = depthIter->second.cols/(modelIter->second.size());
				cam.depth = cv::Mat(depthIter->second, cv::Range(0, depthIter->second.rows), cv::Range(subWidth*i, subWidth*(i+1)));
			}

			UDEBUG("%f", cam.focal_length);
			UDEBUG("%f", cam.height);
			UDEBUG("%f", cam.width);
			UDEBUG("cam.pose=%s", t.prettyPrint().c_str());

			cameras.push_back(cam);
		}
	}
	return cameras;
}

pcl::TextureMesh::Ptr createTextureMesh(
		const pcl::PolygonMesh::Ptr & mesh,
		const std::map<int, Transform> & poses,
		const std::map<int, CameraModel> & cameraModels,
		const std::map<int, cv::Mat> & cameraDepths,
		float maxDistance,
		float maxDepthError,
		float maxAngle,
		int minClusterSize,
		const std::vector<float> & roiRatios,
		const ProgressState * state,
		std::vector<std::map<int, pcl::PointXY> > * vertexToPixels)
{
	std::map<int, std::vector<CameraModel> > cameraSubModels;
	for(std::map<int, CameraModel>::const_iterator iter=cameraModels.begin(); iter!=cameraModels.end(); ++iter)
	{
		std::vector<CameraModel> models;
		models.push_back(iter->second);
		cameraSubModels.insert(std::make_pair(iter->first, models));
	}

	return createTextureMesh(
			mesh,
			poses,
			cameraSubModels,
			cameraDepths,
			maxDistance,
			maxDepthError,
			maxAngle,
			minClusterSize,
			roiRatios,
			state,
			vertexToPixels);
}

pcl::TextureMesh::Ptr createTextureMesh(
		const pcl::PolygonMesh::Ptr & mesh,
		const std::map<int, Transform> & poses,
		const std::map<int, std::vector<CameraModel> > & cameraModels,
		const std::map<int, cv::Mat> & cameraDepths,
		float maxDistance,
		float maxDepthError,
		float maxAngle,
		int minClusterSize,
		const std::vector<float> & roiRatios,
		const ProgressState * state,
		std::vector<std::map<int, pcl::PointXY> > * vertexToPixels)
{
	UASSERT(mesh->polygons.size());
	pcl::TextureMesh::Ptr textureMesh(new pcl::TextureMesh);
	textureMesh->cloud = mesh->cloud;
	textureMesh->tex_polygons.push_back(mesh->polygons);

	// Original from pcl/gpu/kinfu_large_scale/tools/standalone_texture_mapping.cpp:
	// Author: Raphael Favier, Technical University Eindhoven, (r.mysurname <aT> tue.nl)

	// Create the texturemesh object that will contain our UV-mapped mesh

	// create cameras
	pcl::texture_mapping::CameraVector cameras = createTextureCameras(
			poses,
			cameraModels,
			cameraDepths,
			roiRatios);

	// Create materials for each texture (and one extra for occluded faces)
	textureMesh->tex_materials.resize (cameras.size () + 1);
	for(unsigned int i = 0 ; i <= cameras.size() ; ++i)
	{
		pcl::TexMaterial mesh_material;
		mesh_material.tex_Ka.r = 0.2f;
		mesh_material.tex_Ka.g = 0.2f;
		mesh_material.tex_Ka.b = 0.2f;

		mesh_material.tex_Kd.r = 0.8f;
		mesh_material.tex_Kd.g = 0.8f;
		mesh_material.tex_Kd.b = 0.8f;

		mesh_material.tex_Ks.r = 1.0f;
		mesh_material.tex_Ks.g = 1.0f;
		mesh_material.tex_Ks.b = 1.0f;

		mesh_material.tex_d = 1.0f;
		mesh_material.tex_Ns = 75.0f;
		mesh_material.tex_illum = 2;

		std::stringstream tex_name;
		tex_name << "material_" << i;
		tex_name >> mesh_material.tex_name;

		if(i < cameras.size ())
		{
			mesh_material.tex_file = cameras[i].texture_file;
		}
		else
		{
			mesh_material.tex_file = "occluded";
		}

		textureMesh->tex_materials[i] = mesh_material;
	}

	// Texture by projection
	pcl::TextureMapping<pcl::PointXYZ> tm; // TextureMapping object that will perform the sort
	tm.setMaxDistance(maxDistance);
	tm.setMaxAngle(maxAngle);
	if(maxDepthError > 0.0f)
	{
		tm.setMaxDepthError(maxDepthError);
	}
	tm.setMinClusterSize(minClusterSize);
	if(tm.textureMeshwithMultipleCameras2(*textureMesh, cameras, state, vertexToPixels))
	{
		// compute normals for the mesh if not already here
		bool hasNormals = false;
		bool hasColors = false;
		for(unsigned int i=0; i<textureMesh->cloud.fields.size(); ++i)
		{
			if(textureMesh->cloud.fields[i].name.compare("normal_x") == 0)
			{
				hasNormals = true;
			}
			else if(textureMesh->cloud.fields[i].name.compare("rgb") == 0)
			{
				hasColors = true;
			}
		}
		if(!hasNormals)
		{
			// use polygons
			if(hasColors)
			{
				pcl::PointCloud<pcl::PointXYZRGBNormal>::Ptr cloud (new pcl::PointCloud<pcl::PointXYZRGBNormal>);
				pcl::fromPCLPointCloud2(mesh->cloud, *cloud);

				for(unsigned int i=0; i<mesh->polygons.size(); ++i)
				{
					pcl::Vertices & v = mesh->polygons[i];
					UASSERT(v.vertices.size()>2);
					Eigen::Vector3f v0(
							cloud->at(v.vertices[1]).x - cloud->at(v.vertices[0]).x,
							cloud->at(v.vertices[1]).y - cloud->at(v.vertices[0]).y,
							cloud->at(v.vertices[1]).z - cloud->at(v.vertices[0]).z);
					int last = v.vertices.size()-1;
					Eigen::Vector3f v1(
							cloud->at(v.vertices[last]).x - cloud->at(v.vertices[0]).x,
							cloud->at(v.vertices[last]).y - cloud->at(v.vertices[0]).y,
							cloud->at(v.vertices[last]).z - cloud->at(v.vertices[0]).z);
					Eigen::Vector3f normal = v0.cross(v1);
					normal.normalize();
					// flat normal (per face)
					for(unsigned int j=0; j<v.vertices.size(); ++j)
					{
						cloud->at(v.vertices[j]).normal_x = normal[0];
						cloud->at(v.vertices[j]).normal_y = normal[1];
						cloud->at(v.vertices[j]).normal_z = normal[2];
					}
				}
				pcl::toPCLPointCloud2 (*cloud, textureMesh->cloud);
			}
			else
			{
				pcl::PointCloud<pcl::PointNormal>::Ptr cloud (new pcl::PointCloud<pcl::PointNormal>);
				pcl::fromPCLPointCloud2(mesh->cloud, *cloud);

				for(unsigned int i=0; i<mesh->polygons.size(); ++i)
				{
					pcl::Vertices & v = mesh->polygons[i];
					UASSERT(v.vertices.size()>2);
					Eigen::Vector3f v0(
							cloud->at(v.vertices[1]).x - cloud->at(v.vertices[0]).x,
							cloud->at(v.vertices[1]).y - cloud->at(v.vertices[0]).y,
							cloud->at(v.vertices[1]).z - cloud->at(v.vertices[0]).z);
					int last = v.vertices.size()-1;
					Eigen::Vector3f v1(
							cloud->at(v.vertices[last]).x - cloud->at(v.vertices[0]).x,
							cloud->at(v.vertices[last]).y - cloud->at(v.vertices[0]).y,
							cloud->at(v.vertices[last]).z - cloud->at(v.vertices[0]).z);
					Eigen::Vector3f normal = v0.cross(v1);
					normal.normalize();
					// flat normal (per face)
					for(unsigned int j=0; j<v.vertices.size(); ++j)
					{
						cloud->at(v.vertices[j]).normal_x = normal[0];
						cloud->at(v.vertices[j]).normal_y = normal[1];
						cloud->at(v.vertices[j]).normal_z = normal[2];
					}
				}
				pcl::toPCLPointCloud2 (*cloud, textureMesh->cloud);
			}
		}
	}
	return textureMesh;
}

void cleanTextureMesh(
		pcl::TextureMesh & textureMesh,
		int minClusterSize)
{
	UDEBUG("minClusterSize=%d", minClusterSize);
	// Remove occluded polygons (polygons with no texture)
	if(textureMesh.tex_coordinates.size())
	{
		// assume last texture is the occluded texture
		textureMesh.tex_coordinates.pop_back();
		textureMesh.tex_polygons.pop_back();
		textureMesh.tex_materials.pop_back();

		if(minClusterSize!=0)
		{
			// concatenate all polygons
			unsigned int totalSize = 0;
			for(unsigned int t=0; t<textureMesh.tex_polygons.size(); ++t)
			{
				totalSize+=textureMesh.tex_polygons[t].size();
			}
			std::vector<pcl::Vertices> allPolygons(totalSize);
			int oi=0;
			for(unsigned int t=0; t<textureMesh.tex_polygons.size(); ++t)
			{
				for(unsigned int i=0; i<textureMesh.tex_polygons[t].size(); ++i)
				{
					allPolygons[oi++] =  textureMesh.tex_polygons[t][i];
				}
			}

			// filter polygons
			std::vector<std::set<int> > neighbors;
			std::vector<std::set<int> > vertexToPolygons;
			util3d::createPolygonIndexes(allPolygons,
					(int)textureMesh.cloud.data.size()/textureMesh.cloud.point_step,
					neighbors,
					vertexToPolygons);

			std::list<std::list<int> > clusters = util3d::clusterPolygons(
					neighbors,
					minClusterSize<0?0:minClusterSize);

			std::set<int> validPolygons;
			if(minClusterSize < 0)
			{
				// only keep the biggest cluster
				std::list<std::list<int> >::iterator biggestClusterIndex = clusters.end();
				unsigned int biggestClusterSize = 0;
				for(std::list<std::list<int> >::iterator iter=clusters.begin(); iter!=clusters.end(); ++iter)
				{
					if(iter->size() > biggestClusterSize)
					{
						biggestClusterIndex = iter;
						biggestClusterSize = iter->size();
					}
				}
				if(biggestClusterIndex != clusters.end())
				{
					for(std::list<int>::iterator jter=biggestClusterIndex->begin(); jter!=biggestClusterIndex->end(); ++jter)
					{
						validPolygons.insert(*jter);
					}
				}
			}
			else
			{
				for(std::list<std::list<int> >::iterator iter=clusters.begin(); iter!=clusters.end(); ++iter)
				{
					for(std::list<int>::iterator jter=iter->begin(); jter!=iter->end(); ++jter)
					{
						validPolygons.insert(*jter);
					}
				}
			}

			if(validPolygons.size() == 0)
			{
				UWARN("All %d polygons filtered after polygon cluster filtering. Cluster minimum size is %d.",totalSize, minClusterSize);
			}

			// for each texture
			unsigned int allPolygonsIndex = 0;
			for(unsigned int t=0; t<textureMesh.tex_polygons.size(); ++t)
			{
				std::vector<pcl::Vertices> filteredPolygons(textureMesh.tex_polygons[t].size());
#if PCL_VERSION_COMPARE(>=, 1, 8, 0)
				std::vector<Eigen::Vector2f, Eigen::aligned_allocator<Eigen::Vector2f> > filteredCoordinates(textureMesh.tex_coordinates[t].size());
#else
				std::vector<Eigen::Vector2f> filteredCoordinates(textureMesh.tex_coordinates[t].size());
#endif

				if(textureMesh.tex_polygons[t].size())
				{
					UASSERT_MSG(allPolygonsIndex < allPolygons.size(), uFormat("%d vs %d", (int)allPolygonsIndex, (int)allPolygons.size()).c_str());

					// make index polygon to coordinate
					std::vector<unsigned int> polygonToCoord(textureMesh.tex_polygons[t].size());
					unsigned int totalCoord = 0;
					for(unsigned int i=0; i<textureMesh.tex_polygons[t].size(); ++i)
					{
						polygonToCoord[i] = totalCoord;
						totalCoord+=textureMesh.tex_polygons[t][i].vertices.size();
					}
					UASSERT_MSG(totalCoord == textureMesh.tex_coordinates[t].size(), uFormat("%d vs %d", totalCoord, (int)textureMesh.tex_coordinates[t].size()).c_str());

					int oi=0;
					int ci=0;
					for(unsigned int i=0; i<textureMesh.tex_polygons[t].size(); ++i)
					{
						if(validPolygons.find(allPolygonsIndex) != validPolygons.end())
						{
							filteredPolygons[oi] = textureMesh.tex_polygons[t].at(i);
							for(unsigned int j=0; j<filteredPolygons[oi].vertices.size(); ++j)
							{
								UASSERT(polygonToCoord[i] < textureMesh.tex_coordinates[t].size());
								filteredCoordinates[ci] = textureMesh.tex_coordinates[t][polygonToCoord[i]+j];
								++ci;
							}
							++oi;
						}
						++allPolygonsIndex;
					}
					filteredPolygons.resize(oi);
					filteredCoordinates.resize(ci);
					textureMesh.tex_polygons[t] = filteredPolygons;
					textureMesh.tex_coordinates[t] = filteredCoordinates;
				}
			}
		}
	}
}

pcl::TextureMesh::Ptr concatenateTextureMeshes(const std::list<pcl::TextureMesh::Ptr> & meshes)
{
	pcl::TextureMesh::Ptr output(new pcl::TextureMesh);
	std::map<std::string, int> addedMaterials; //<file, index>
	for(std::list<pcl::TextureMesh::Ptr>::const_iterator iter = meshes.begin(); iter!=meshes.end(); ++iter)
	{
		if((*iter)->cloud.point_step &&
		   (*iter)->cloud.data.size()/(*iter)->cloud.point_step &&
		   (*iter)->tex_polygons.size() &&
		   (*iter)->tex_coordinates.size())
		{
			// append point cloud
			int polygonStep = output->cloud.height * output->cloud.width;
			pcl::PCLPointCloud2 tmp;
			pcl::concatenatePointCloud(output->cloud, iter->get()->cloud, tmp);
			output->cloud = tmp;

			UASSERT((*iter)->tex_polygons.size() == (*iter)->tex_coordinates.size() &&
					(*iter)->tex_polygons.size() == (*iter)->tex_materials.size());

			int materialCount = (*iter)->tex_polygons.size();
			for(int i=0; i<materialCount; ++i)
			{
				std::map<std::string, int>::iterator jter = addedMaterials.find((*iter)->tex_materials[i].tex_file);
				int index;
				if(jter != addedMaterials.end())
				{
					index = jter->second;
				}
				else
				{
					addedMaterials.insert(std::make_pair((*iter)->tex_materials[i].tex_file, output->tex_materials.size()));
					index = output->tex_materials.size();
					output->tex_materials.push_back((*iter)->tex_materials[i]);
					output->tex_materials.back().tex_name = uFormat("material_%d", index);
					output->tex_polygons.resize(output->tex_polygons.size() + 1);
					output->tex_coordinates.resize(output->tex_coordinates.size() + 1);
				}

				// update and append polygon indices
				int oi = output->tex_polygons[index].size();
				output->tex_polygons[index].resize(output->tex_polygons[index].size() + (*iter)->tex_polygons[i].size());
				for(unsigned int j=0; j<(*iter)->tex_polygons[i].size(); ++j)
				{
					pcl::Vertices polygon = (*iter)->tex_polygons[i][j];
					for(unsigned int k=0; k<polygon.vertices.size(); ++k)
					{
						polygon.vertices[k] += polygonStep;
					}
					output->tex_polygons[index][oi+j] = polygon;
				}

				// append uv coordinates
				oi = output->tex_coordinates[index].size();
				output->tex_coordinates[index].resize(output->tex_coordinates[index].size() + (*iter)->tex_coordinates[i].size());
				for(unsigned int j=0; j<(*iter)->tex_coordinates[i].size(); ++j)
				{
					output->tex_coordinates[index][oi+j] = (*iter)->tex_coordinates[i][j];
				}
			}
		}
	}
	return output;
}

int gcd(int a, int b) {
    return b == 0 ? a : gcd(b, a % b);
}

void concatenateTextureMaterials(pcl::TextureMesh & mesh, const cv::Size & imageSize, int textureSize, int maxTextures, float & scale, std::vector<bool> * materialsKept)
{
	UASSERT(textureSize>0 && imageSize.width>0 && imageSize.height>0);
	if(maxTextures < 1)
	{
		maxTextures = 1;
	}
	int materials = 0;
	for(unsigned int i=0; i<mesh.tex_materials.size(); ++i)
	{
		if(mesh.tex_polygons.size())
		{
			++materials;
		}
	}
	if(materials)
	{
		int w = imageSize.width; // 640
		int h = imageSize.height; // 480
		int g = gcd(w,h); // 160
		int a = w/g; // 4=640/160
		int b = h/g; // 3=480/160
		UDEBUG("w=%d h=%d g=%d a=%d b=%d", w, h, g, a, b);
		int colCount = 0;
		int rowCount = 0;
		float factor = 0.1f;
		float epsilon = 0.001f;
		scale = 1.0f;
		while((colCount*rowCount)*maxTextures < materials || (factor == 0.1f || scale > 1.0f))
		{
			// first run try scale = 1 (no scaling)
			if(factor!=0.1f)
			{
				scale = float(textureSize)/float(w*b*factor);
			}
			colCount = float(textureSize)/(scale*float(w));
			rowCount = float(textureSize)/(scale*float(h));
			factor+=epsilon; // search the maximum perfect fit
		}
		int outputTextures = (materials / (colCount*rowCount)) + (materials % (colCount*rowCount) > 0?1:0);
		UDEBUG("materials=%d col=%d row=%d output textures=%d factor=%f scale=%f", materials, colCount, rowCount, outputTextures, factor-epsilon, scale);

		UASSERT(mesh.tex_coordinates.size() == mesh.tex_materials.size() && mesh.tex_polygons.size() == mesh.tex_materials.size());

		// prepare size
		std::vector<int> totalPolygons(outputTextures, 0);
		std::vector<int> totalCoordinates(outputTextures, 0);
		int count = 0;
		for(unsigned int i=0; i<mesh.tex_materials.size(); ++i)
		{
			if(mesh.tex_polygons[i].size())
			{
				int indexMaterial = count / (colCount*rowCount);
				UASSERT(indexMaterial < outputTextures);

				totalPolygons[indexMaterial]+=mesh.tex_polygons[i].size();
				totalCoordinates[indexMaterial]+=mesh.tex_coordinates[i].size();

				++count;
			}
		}

		pcl::TextureMesh outputMesh;

		int pi = 0;
		int ci = 0;
		int ti=0;
		float scaledHeight = float(int(scale*float(h)))/float(textureSize);
		float scaledWidth = float(int(scale*float(w)))/float(textureSize);
		float lowerBorderSize = 1.0f - scaledHeight*float(rowCount);
		UDEBUG("scaledWidth=%f scaledHeight=%f lowerBorderSize=%f", scaledWidth, scaledHeight, lowerBorderSize);
		if(materialsKept)
		{
			materialsKept->resize(mesh.tex_materials.size(), false);
		}
		for(unsigned int t=0; t<mesh.tex_materials.size(); ++t)
		{
			if(mesh.tex_polygons[t].size())
			{
				int indexMaterial = ti / (colCount*rowCount);
				UASSERT(indexMaterial < outputTextures);
                                if((int)outputMesh.tex_polygons.size() <= indexMaterial)
				{
					std::vector<pcl::Vertices> newPolygons(totalPolygons[indexMaterial]);
#if PCL_VERSION_COMPARE(>=, 1, 8, 0)
					std::vector<Eigen::Vector2f, Eigen::aligned_allocator<Eigen::Vector2f> > newCoordinates(totalCoordinates[indexMaterial]); // UV coordinates
#else
					std::vector<Eigen::Vector2f> newCoordinates(totalCoordinates[indexMaterial]); // UV coordinates
#endif
					outputMesh.tex_polygons.push_back(newPolygons);
					outputMesh.tex_coordinates.push_back(newCoordinates);

					pi=0;
					ci=0;
				}

				int row = (ti/colCount) % rowCount;
				int col = ti%colCount;
				float offsetU = scaledWidth * float(col);
				float offsetV = scaledHeight * float((rowCount - 1) - row) + lowerBorderSize;
				// Texture coords have lower-left origin

				for(unsigned int i=0; i<mesh.tex_polygons[t].size(); ++i)
				{
					UASSERT(pi < (int)outputMesh.tex_polygons[indexMaterial].size());
					outputMesh.tex_polygons[indexMaterial][pi++] = mesh.tex_polygons[t].at(i);
				}

				for(unsigned int i=0; i<mesh.tex_coordinates[t].size(); ++i)
				{
					const Eigen::Vector2f & v = mesh.tex_coordinates[t].at(i);
					if(v[0] >= 0 && v[1] >=0)
					{
						outputMesh.tex_coordinates[indexMaterial][ci][0] = v[0]*scaledWidth + offsetU;
						outputMesh.tex_coordinates[indexMaterial][ci][1] = v[1]*scaledHeight + offsetV;
					}
					else
					{
						outputMesh.tex_coordinates[indexMaterial][ci] = v;
					}
					++ci;
				}
				++ti;
				if(materialsKept)
				{
					materialsKept->at(t) = true;
				}
			}
		}
		pcl::TexMaterial m = mesh.tex_materials.front();
		mesh.tex_materials.clear();
		for(int i=0; i<outputTextures; ++i)
		{
			m.tex_file = "texture";
			m.tex_name = "material";
			if(outputTextures > 1)
			{
				m.tex_file += uNumber2Str(i);
				m.tex_name += uNumber2Str(i);
			}

			mesh.tex_materials.push_back(m);
		}
		mesh.tex_coordinates = outputMesh.tex_coordinates;
		mesh.tex_polygons = outputMesh.tex_polygons;
	}
}

std::vector<std::vector<unsigned int> > convertPolygonsFromPCL(const std::vector<pcl::Vertices> & polygons)
{
	std::vector<std::vector<unsigned int> > polygonsOut(polygons.size());
	for(unsigned int p=0; p<polygons.size(); ++p)
	{
		polygonsOut[p] = polygons[p].vertices;
	}
	return polygonsOut;
}
std::vector<std::vector<std::vector<unsigned int> > > convertPolygonsFromPCL(const std::vector<std::vector<pcl::Vertices> > & tex_polygons)
{
	std::vector<std::vector<std::vector<unsigned int> > > polygonsOut(tex_polygons.size());
	for(unsigned int t=0; t<tex_polygons.size(); ++t)
	{
		polygonsOut[t].resize(tex_polygons[t].size());
		for(unsigned int p=0; p<tex_polygons[t].size(); ++p)
		{
			polygonsOut[t][p] = tex_polygons[t][p].vertices;
		}
	}
	return polygonsOut;
}
std::vector<pcl::Vertices> convertPolygonsToPCL(const std::vector<std::vector<unsigned int> > & polygons)
{
	std::vector<pcl::Vertices> polygonsOut(polygons.size());
	for(unsigned int p=0; p<polygons.size(); ++p)
	{
		polygonsOut[p].vertices = polygons[p];
	}
	return polygonsOut;
}
std::vector<std::vector<pcl::Vertices> > convertPolygonsToPCL(const std::vector<std::vector<std::vector<unsigned int> > > & tex_polygons)
{
	std::vector<std::vector<pcl::Vertices> > polygonsOut(tex_polygons.size());
	for(unsigned int t=0; t<tex_polygons.size(); ++t)
	{
		polygonsOut[t].resize(tex_polygons[t].size());
		for(unsigned int p=0; p<tex_polygons[t].size(); ++p)
		{
			polygonsOut[t][p].vertices = tex_polygons[t][p];
		}
	}
	return polygonsOut;
}

pcl::TextureMesh::Ptr assembleTextureMesh(
		const cv::Mat & cloudMat,
		const std::vector<std::vector<std::vector<unsigned int> > > & polygons,
#if PCL_VERSION_COMPARE(>=, 1, 8, 0)
		const std::vector<std::vector<Eigen::Vector2f, Eigen::aligned_allocator<Eigen::Vector2f> > > & texCoords,
#else
		const std::vector<std::vector<Eigen::Vector2f> > & texCoords,
#endif
		cv::Mat & textures,
		bool mergeTextures)
{
	pcl::TextureMesh::Ptr textureMesh(new pcl::TextureMesh);

	if(cloudMat.channels() <= 3)
	{
		pcl::PointCloud<pcl::PointXYZ>::Ptr cloud = rtabmap::util3d::laserScanToPointCloud(LaserScan::backwardCompatibility(cloudMat));
		pcl::toPCLPointCloud2(*cloud, textureMesh->cloud);
	}
	else if(cloudMat.channels() == 4)
	{
		pcl::PointCloud<pcl::PointXYZRGB>::Ptr cloud = rtabmap::util3d::laserScanToPointCloudRGB(LaserScan::backwardCompatibility(cloudMat));
		pcl::toPCLPointCloud2(*cloud, textureMesh->cloud);
	}
	else if(cloudMat.channels() == 6)
	{
		pcl::PointCloud<pcl::PointNormal>::Ptr cloud = rtabmap::util3d::laserScanToPointCloudNormal(LaserScan::backwardCompatibility(cloudMat));
		pcl::toPCLPointCloud2(*cloud, textureMesh->cloud);
	}
	else if(cloudMat.channels() == 7)
	{
		pcl::PointCloud<pcl::PointXYZRGBNormal>::Ptr cloud = rtabmap::util3d::laserScanToPointCloudRGBNormal(LaserScan::backwardCompatibility(cloudMat));
		pcl::toPCLPointCloud2(*cloud, textureMesh->cloud);
	}

	if(textureMesh->cloud.data.size() && polygons.size())
	{
		textureMesh->tex_polygons.resize(polygons.size());
		for(unsigned int t=0; t<polygons.size(); ++t)
		{
			textureMesh->tex_polygons[t].resize(polygons[t].size());
			for(unsigned int p=0; p<polygons[t].size(); ++p)
			{
				textureMesh->tex_polygons[t][p].vertices = polygons[t][p];
			}
		}

		if(!texCoords.empty() && !textures.empty())
		{
			textureMesh->tex_coordinates = texCoords;

			textureMesh->tex_materials.resize (textureMesh->tex_coordinates.size());
			for(unsigned int i = 0 ; i < textureMesh->tex_coordinates.size() ; ++i)
			{
				pcl::TexMaterial mesh_material;
				mesh_material.tex_Ka.r = 0.2f;
				mesh_material.tex_Ka.g = 0.2f;
				mesh_material.tex_Ka.b = 0.2f;

				mesh_material.tex_Kd.r = 0.8f;
				mesh_material.tex_Kd.g = 0.8f;
				mesh_material.tex_Kd.b = 0.8f;

				mesh_material.tex_Ks.r = 1.0f;
				mesh_material.tex_Ks.g = 1.0f;
				mesh_material.tex_Ks.b = 1.0f;

				mesh_material.tex_d = 1.0f;
				mesh_material.tex_Ns = 75.0f;
				mesh_material.tex_illum = 2;

				std::stringstream tex_name;
				tex_name << "material_" << i;
				tex_name >> mesh_material.tex_name;

				mesh_material.tex_file = uFormat("%d", i);

				textureMesh->tex_materials[i] = mesh_material;
			}

			if(mergeTextures && textures.cols/textures.rows > 1)
			{
				UASSERT(textures.cols % textures.rows == 0 && textures.cols/textures.rows == (int)textureMesh->tex_coordinates.size());
				std::vector<bool> materialsKept;
				float scale = 0.0f;
				cv::Size imageSize(textures.rows, textures.rows);
				int imageType = textures.type();
				rtabmap::util3d::concatenateTextureMaterials(*textureMesh, imageSize, textures.rows, 1, scale, &materialsKept);
				if(scale && textureMesh->tex_materials.size() == 1)
				{
					int cols = float(textures.rows)/(scale*imageSize.width);
					int rows = float(textures.rows)/(scale*imageSize.height);

					cv::Mat mergedTextures = cv::Mat(textures.rows, textures.rows, imageType, cv::Scalar::all(255));

					// make a blank texture
					cv::Size resizedImageSize(int(imageSize.width*scale), int(imageSize.height*scale));
					int oi=0;
					for(int i=0; i<(int)materialsKept.size(); ++i)
					{
						if(materialsKept.at(i))
						{
							int u = oi%cols * resizedImageSize.width;
							int v = ((oi/cols) % rows ) * resizedImageSize.height;
							UASSERT(u < textures.rows-resizedImageSize.width);
							UASSERT(v < textures.rows-resizedImageSize.height);

							cv::Mat resizedImage;
							cv::resize(textures(cv::Range::all(), cv::Range(i*textures.rows, (i+1)*textures.rows)), resizedImage, resizedImageSize, 0.0f, 0.0f, cv::INTER_AREA);

							UASSERT(resizedImage.type() == mergedTextures.type());
							resizedImage.copyTo(mergedTextures(cv::Rect(u, v, resizedImage.cols, resizedImage.rows)));

							++oi;
						}
					}
					textures = mergedTextures;
				}
			}
		}
	}
	return textureMesh;
}

pcl::PolygonMesh::Ptr assemblePolygonMesh(
		const cv::Mat & cloudMat,
		const std::vector<std::vector<unsigned int> > & polygons)
{
	pcl::PolygonMesh::Ptr polygonMesh(new pcl::PolygonMesh);

	if(cloudMat.channels() <= 3)
	{
		pcl::PointCloud<pcl::PointXYZ>::Ptr cloud = rtabmap::util3d::laserScanToPointCloud(LaserScan::backwardCompatibility(cloudMat));
		pcl::toPCLPointCloud2(*cloud, polygonMesh->cloud);
	}
	else if(cloudMat.channels() == 4)
	{
		pcl::PointCloud<pcl::PointXYZRGB>::Ptr cloud = rtabmap::util3d::laserScanToPointCloudRGB(LaserScan::backwardCompatibility(cloudMat));
		pcl::toPCLPointCloud2(*cloud, polygonMesh->cloud);
	}
	else if(cloudMat.channels() == 6)
	{
		pcl::PointCloud<pcl::PointNormal>::Ptr cloud = rtabmap::util3d::laserScanToPointCloudNormal(LaserScan::backwardCompatibility(cloudMat));
		pcl::toPCLPointCloud2(*cloud, polygonMesh->cloud);
	}
	else if(cloudMat.channels() == 7)
	{
		pcl::PointCloud<pcl::PointXYZRGBNormal>::Ptr cloud = rtabmap::util3d::laserScanToPointCloudRGBNormal(LaserScan::backwardCompatibility(cloudMat));
		pcl::toPCLPointCloud2(*cloud, polygonMesh->cloud);
	}

	if(polygonMesh->cloud.data.size() && polygons.size())
	{
		polygonMesh->polygons.resize(polygons.size());
		for(unsigned int p=0; p<polygons.size(); ++p)
		{
			polygonMesh->polygons[p].vertices = polygons[p];
		}
	}
	return polygonMesh;
}

double sqr(uchar v)
{
	return double(v)*double(v);
}

cv::Mat mergeTextures(
		pcl::TextureMesh & mesh,
		const std::map<int, cv::Mat> & images,
		const std::map<int, CameraModel> & calibrations,
		const Memory * memory,
		const DBDriver * dbDriver,
		int textureSize,
		int textureCount,
		const std::vector<std::map<int, pcl::PointXY> > & vertexToPixels,
		bool gainCompensation,
		float gainBeta,
		bool gainRGB,
		bool blending,
		int blendingDecimation,
		int brightnessContrastRatioLow,
		int brightnessContrastRatioHigh,
		bool exposureFusion,
		const ProgressState * state)
{
	std::map<int, std::vector<CameraModel> > calibVectors;
	for(std::map<int, CameraModel>::const_iterator iter=calibrations.begin(); iter!=calibrations.end(); ++iter)
	{
		std::vector<CameraModel> m;
		m.push_back(iter->second);
		calibVectors.insert(std::make_pair(iter->first, m));
	}
	return mergeTextures(mesh,
			images,
			calibVectors,
			memory,
			dbDriver,
			textureSize,
			textureCount,
			vertexToPixels,
			gainCompensation,
			gainBeta,
			gainRGB,
			blending,
			blendingDecimation,
			brightnessContrastRatioLow,
			brightnessContrastRatioHigh,
			exposureFusion,
			state);
}
cv::Mat mergeTextures(
		pcl::TextureMesh & mesh,
		const std::map<int, cv::Mat> & images,
		const std::map<int, std::vector<CameraModel> > & calibrations,
		const Memory * memory,
		const DBDriver * dbDriver,
		int textureSize,
		int textureCount,
		const std::vector<std::map<int, pcl::PointXY> > & vertexToPixels,
		bool gainCompensation,
		float gainBeta,
		bool gainRGB,
		bool blending,
		int blendingDecimation,
		int brightnessContrastRatioLow,
		int brightnessContrastRatioHigh,
		bool exposureFusion,
		const ProgressState * state)
{
	//get texture size, if disabled use default 1024
	UASSERT(textureSize%256 == 0);
	UDEBUG("textureSize = %d", textureSize);
	cv::Mat globalTextures;
	if(mesh.tex_materials.size() > 1)
	{
		std::vector<std::pair<int, int> > textures(mesh.tex_materials.size(), std::pair<int, int>(-1,-1));
		cv::Size imageSize;
		const int imageType=CV_8UC3;

		UDEBUG("");
		for(unsigned int i=0; i<mesh.tex_materials.size(); ++i)
		{
			std::list<std::string> texFileSplit = uSplit(mesh.tex_materials[i].tex_file, '_');
			if(!mesh.tex_materials[i].tex_file.empty() &&
				mesh.tex_polygons[i].size() &&
			   uIsInteger(texFileSplit.front(), false))
			{
				textures[i].first = uStr2Int(texFileSplit.front());
				if(texFileSplit.size() == 2 &&
				   uIsInteger(texFileSplit.back(), false)	)
				{
					textures[i].second = uStr2Int(texFileSplit.back());
				}

				int textureId = textures[i].first;
				if(imageSize.width == 0 || imageSize.height == 0)
				{
					if(images.find(textureId) != images.end() &&
						!images.find(textureId)->second.empty() &&
						calibrations.find(textureId) != calibrations.end())
					{
						const std::vector<CameraModel> & models = calibrations.find(textureId)->second;
						UASSERT(models.size()>=1);
						if(	models[0].imageHeight()>0 &&
							models[0].imageWidth()>0)
						{
							imageSize = models[0].imageSize();
						}
						else if(images.find(textureId)!=images.end())
						{
							// backward compatibility for image size not set in CameraModel
							cv::Mat image = images.find(textureId)->second;
							if(image.rows == 1 && image.type() == CV_8UC1)
							{
								image = uncompressImage(image);
							}
							UASSERT(!image.empty());
							imageSize = image.size();
							if(models.size()>1)
							{
								imageSize.width/=models.size();
							}
						}
					}
					else if(memory)
					{
						SensorData data = memory->getSignatureDataConst(textureId, true, false, false, false);
						std::vector<CameraModel> models = data.cameraModels();
						StereoCameraModel stereoModel = data.stereoCameraModel();
						if(models.size()>=1 &&
							models[0].imageHeight()>0 &&
							models[0].imageWidth()>0)
						{
							imageSize = models[0].imageSize();
						}
						else if(stereoModel.left().imageHeight() > 0 &&
								stereoModel.left().imageWidth() > 0)
						{
							imageSize = stereoModel.left().imageSize();
						}
						else // backward compatibility for image size not set in CameraModel
						{
							cv::Mat image;
							data.uncompressDataConst(&image, 0);
							UASSERT(!image.empty());
							imageSize = image.size();
							if(data.cameraModels().size()>1)
							{
								imageSize.width/=data.cameraModels().size();
							}
						}
					}
					else if(dbDriver)
					{
						std::vector<CameraModel> models;
						StereoCameraModel stereoModel;
						dbDriver->getCalibration(textureId, models, stereoModel);
						if(models.size()>=1 &&
							models[0].imageHeight()>0 &&
							models[0].imageWidth()>0)
						{
							imageSize = models[0].imageSize();
						}
						else if(stereoModel.left().imageHeight() > 0 &&
								stereoModel.left().imageWidth() > 0)
						{
							imageSize = stereoModel.left().imageSize();
						}
						else // backward compatibility for image size not set in CameraModel
						{
							SensorData data;
							dbDriver->getNodeData(textureId, data, true, false, false, false);
							cv::Mat image;
							data.uncompressDataConst(&image, 0);
							UASSERT(!image.empty());
							imageSize = image.size();
							if(data.cameraModels().size()>1)
							{
								imageSize.width/=data.cameraModels().size();
							}
						}
					}
				}
			}
			else if(mesh.tex_polygons[i].size() && mesh.tex_materials[i].tex_file.compare("occluded")!=0)
			{
				UWARN("Failed parsing texture file name: %s", mesh.tex_materials[i].tex_file.c_str());
			}
		}
		UDEBUG("textures=%d imageSize=%dx%d", (int)textures.size(), imageSize.height, imageSize.width);
		if(textures.size() && imageSize.height>0 && imageSize.width>0)
		{
			float scale = 0.0f;
			UDEBUG("");
			std::vector<bool> materialsKept;
			util3d::concatenateTextureMaterials(mesh, imageSize, textureSize, textureCount, scale, &materialsKept);
			if(scale && mesh.tex_materials.size())
			{
				int materials = (int)mesh.tex_materials.size();
				int cols = float(textureSize)/(scale*imageSize.width);
				int rows = float(textureSize)/(scale*imageSize.height);

				globalTextures = cv::Mat(textureSize, materials*textureSize, imageType, cv::Scalar::all(255));
				cv::Mat globalTextureMasks = cv::Mat(textureSize, materials*textureSize, CV_8UC1, cv::Scalar::all(0));

				// used for multi camera texturing, to avoid reloading same texture for sub cameras
				cv::Mat previousImage;
				int previousTextureId = 0;
				std::vector<CameraModel> previousCameraModels;

				// make a blank texture
				cv::Mat emptyImage(int(imageSize.height*scale), int(imageSize.width*scale), imageType, cv::Scalar::all(255));
				cv::Mat emptyImageMask(int(imageSize.height*scale), int(imageSize.width*scale), CV_8UC1, cv::Scalar::all(255));
				int oi=0;
				std::vector<cv::Point2i> imageOrigin(textures.size());
				std::vector<int> newCamIndex(textures.size(), -1);
				for(int t=0; t<(int)textures.size(); ++t)
				{
					if(materialsKept.at(t))
					{
						int indexMaterial = oi / (cols*rows);
						UASSERT(indexMaterial < materials);

						newCamIndex[t] = oi;
						int u = oi%cols * emptyImage.cols;
						int v = ((oi/cols) % rows ) * emptyImage.rows;
						UASSERT_MSG(u < textureSize-emptyImage.cols, uFormat("u=%d textureSize=%d emptyImage.cols=%d", u, textureSize, emptyImage.cols).c_str());
						UASSERT_MSG(v < textureSize-emptyImage.rows, uFormat("v=%d textureSize=%d emptyImage.rows=%d", v, textureSize, emptyImage.rows).c_str());
						imageOrigin[t].x = u;
						imageOrigin[t].y = v;
						if(textures[t].first>=0)
						{
							cv::Mat image;
							std::vector<CameraModel> models;

							if(textures[t].first == previousTextureId)
							{
								image = previousImage;
								models = previousCameraModels;
							}
							else
							{
								if(images.find(textures[t].first) != images.end() &&
									!images.find(textures[t].first)->second.empty() &&
									calibrations.find(textures[t].first) != calibrations.end())
								{
									image = images.find(textures[t].first)->second;
									if(image.rows == 1 && image.type() == CV_8UC1)
									{
										image = uncompressImage(image);
									}
									models = calibrations.find(textures[t].first)->second;
								}
								else if(memory)
								{
									SensorData data = memory->getSignatureDataConst(textures[t].first, true, false, false, false);
									models = data.cameraModels();
									data.uncompressDataConst(&image, 0);
								}
								else if(dbDriver)
								{
									SensorData data;
									dbDriver->getNodeData(textures[t].first, data, true, false, false, false);
									data.uncompressDataConst(&image, 0);
									StereoCameraModel stereoModel;
									dbDriver->getCalibration(textures[t].first, models, stereoModel);
								}

								previousImage = image;
								previousCameraModels = models;
								previousTextureId = textures[t].first;
							}

							UASSERT(!image.empty());

							if(textures[t].second>=0)
							{
								UASSERT(textures[t].second < (int)models.size());
								int width = image.cols/models.size();
								image = image.colRange(width*textures[t].second, width*(textures[t].second+1));
							}

							cv::Mat resizedImage;
							cv::resize(image, resizedImage, emptyImage.size(), 0.0f, 0.0f, cv::INTER_AREA);
							UASSERT(resizedImage.type() == CV_8UC1 || resizedImage.type() == CV_8UC3);
							if(resizedImage.type() == CV_8UC1)
							{
								cv::Mat resizedImageColor;
								cv::cvtColor(resizedImage, resizedImageColor, CV_GRAY2BGR);
								resizedImage = resizedImageColor;
							}
							UASSERT(resizedImage.type() == globalTextures.type());
							resizedImage.copyTo(globalTextures(cv::Rect(u+indexMaterial*globalTextures.rows, v, resizedImage.cols, resizedImage.rows)));
							emptyImageMask.copyTo(globalTextureMasks(cv::Rect(u+indexMaterial*globalTextureMasks.rows, v, resizedImage.cols, resizedImage.rows)));
						}
						else
						{
							emptyImage.copyTo(globalTextures(cv::Rect(u+indexMaterial*globalTextures.rows, v, emptyImage.cols, emptyImage.rows)));
						}
						++oi;
					}

					if(state)
					{
						if(state->isCanceled())
						{
							return cv::Mat();
						}
						state->callback(uFormat("Assembled texture %d/%d.", t+1, (int)textures.size()));
					}
				}

				UTimer timer;
				if(vertexToPixels.size())
				{
					//UWARN("Saving original.png", globalTexture);
					//cv::imwrite("original.png", globalTexture);

					if(gainCompensation)
					{
						/**
						 * Original code from OpenCV: GainCompensator
						 */

						const int num_images = static_cast<int>(oi);
						cv::Mat_<int> N(num_images, num_images); N.setTo(0);
						cv::Mat_<double> I(num_images, num_images); I.setTo(0);

						cv::Mat_<double> IR(num_images, num_images); IR.setTo(0);
						cv::Mat_<double> IG(num_images, num_images); IG.setTo(0);
						cv::Mat_<double> IB(num_images, num_images); IB.setTo(0);

						// Adjust UV coordinates to globalTexture
						for(unsigned int p=0; p<vertexToPixels.size(); ++p)
						{
							for(std::map<int, pcl::PointXY>::const_iterator iter=vertexToPixels[p].begin(); iter!=vertexToPixels[p].end(); ++iter)
							{
								if(materialsKept.at(iter->first))
								{
									N(newCamIndex[iter->first], newCamIndex[iter->first]) +=1;

									std::map<int, pcl::PointXY>::const_iterator jter=iter;
									++jter;
									int k = 1;
									for(; jter!=vertexToPixels[p].end(); ++jter, ++k)
									{
										if(materialsKept.at(jter->first))
										{
											int i = newCamIndex[iter->first];
											int j = newCamIndex[jter->first];

											N(i, j) += 1;
											N(j, i) += 1;

											int indexMaterial = i / (cols*rows);

											// uv in globalTexture
											int ui = iter->second.x*emptyImage.cols + imageOrigin[iter->first].x;
											int vi = (1.0-iter->second.y)*emptyImage.rows + imageOrigin[iter->first].y;
											int uj = jter->second.x*emptyImage.cols + imageOrigin[jter->first].x;
											int vj = (1.0-jter->second.y)*emptyImage.rows + imageOrigin[jter->first].y;
											cv::Vec3b * pt1 = globalTextures.ptr<cv::Vec3b>(vi,ui+indexMaterial*globalTextures.rows);
											cv::Vec3b * pt2 = globalTextures.ptr<cv::Vec3b>(vj,uj+indexMaterial*globalTextures.rows);

											I(i, j) += std::sqrt(static_cast<double>(sqr(pt1->val[0]) + sqr(pt1->val[1]) + sqr(pt1->val[2])));
											I(j, i) += std::sqrt(static_cast<double>(sqr(pt2->val[0]) + sqr(pt2->val[1]) + sqr(pt2->val[2])));

											IR(i, j) += static_cast<double>(pt1->val[2]);
											IR(j, i) += static_cast<double>(pt2->val[2]);
											IG(i, j) += static_cast<double>(pt1->val[1]);
											IG(j, i) += static_cast<double>(pt2->val[1]);
											IB(i, j) += static_cast<double>(pt1->val[0]);
											IB(j, i) += static_cast<double>(pt2->val[0]);
										}
									}
								}
							}
						}

						for(int i=0; i<num_images; ++i)
						{
							for(int j=i; j<num_images; ++j)
							{
								if(i == j)
								{
									if(N(i,j) == 0)
									{
										N(i,j) = 1;
									}
								}
								else if(N(i, j))
								{
									I(i, j) /= N(i, j);
									I(j, i) /= N(j, i);

									IR(i, j) /= N(i, j);
									IR(j, i) /= N(j, i);
									IG(i, j) /= N(i, j);
									IG(j, i) /= N(j, i);
									IB(i, j) /= N(i, j);
									IB(j, i) /= N(j, i);
								}
							}
						}

						cv::Mat_<double> A(num_images, num_images); A.setTo(0);
						cv::Mat_<double> b(num_images, 1); b.setTo(0);
						cv::Mat_<double> AR(num_images, num_images); AR.setTo(0);
						cv::Mat_<double> AG(num_images, num_images); AG.setTo(0);
						cv::Mat_<double> AB(num_images, num_images); AB.setTo(0);
						double alpha = 0.01;
						double beta = gainBeta;
						for (int i = 0; i < num_images; ++i)
						{
							for (int j = 0; j < num_images; ++j)
							{
								b(i, 0) += beta * N(i, j);
								A(i, i) += beta * N(i, j);
								AR(i, i) += beta * N(i, j);
								AG(i, i) += beta * N(i, j);
								AB(i, i) += beta * N(i, j);
								if (j == i) continue;
								A(i, i) += 2 * alpha * I(i, j) * I(i, j) * N(i, j);
								A(i, j) -= 2 * alpha * I(i, j) * I(j, i) * N(i, j);

								AR(i, i) += 2 * alpha * IR(i, j) * IR(i, j) * N(i, j);
								AR(i, j) -= 2 * alpha * IR(i, j) * IR(j, i) * N(i, j);

								AG(i, i) += 2 * alpha * IG(i, j) * IG(i, j) * N(i, j);
								AG(i, j) -= 2 * alpha * IG(i, j) * IG(j, i) * N(i, j);

								AB(i, i) += 2 * alpha * IB(i, j) * IB(i, j) * N(i, j);
								AB(i, j) -= 2 * alpha * IB(i, j) * IB(j, i) * N(i, j);
							}
						}

						cv::Mat_<double> gainsGray, gainsR, gainsG, gainsB;
						cv::solve(A, b, gainsGray);

						cv::solve(AR, b, gainsR);
						cv::solve(AG, b, gainsG);
						cv::solve(AB, b, gainsB);

						cv::Mat_<double> gains(gainsGray.rows, 4);
						gainsGray.copyTo(gains.col(0));
						gainsR.copyTo(gains.col(1));
						gainsG.copyTo(gains.col(2));
						gainsB.copyTo(gains.col(3));

						for(int t=0; t<(int)textures.size(); ++t)
						{
							//break;
							if(materialsKept.at(t))
							{
								int u = imageOrigin[t].x;
								int v = imageOrigin[t].y;

								UDEBUG("Gain cam%d = %f", newCamIndex[t], gainsGray(newCamIndex[t], 0));

								int indexMaterial = newCamIndex[t] / (cols*rows);
								cv::Mat roi = globalTextures(cv::Rect(u+indexMaterial*globalTextures.rows, v, emptyImage.cols, emptyImage.rows));

								std::vector<cv::Mat> channels;
								cv::split(roi, channels);

								// assuming BGR
								cv::multiply(channels[0], gains(newCamIndex[t], gainRGB?3:0), channels[0]);
								cv::multiply(channels[1], gains(newCamIndex[t], gainRGB?2:0), channels[1]);
								cv::multiply(channels[2], gains(newCamIndex[t], gainRGB?1:0), channels[2]);

								cv::merge(channels, roi);
							}
						}
						//UWARN("Saving gain.png", globalTexture);
						//cv::imwrite("gain.png", globalTexture);
						if(state) state->callback(uFormat("Gain compensation %fs", timer.ticks()));
					}

					if(blending)
					{
						// blending BGR
						int decimation = 1;
						if(blendingDecimation <= 0)
						{
							// determinate decimation to apply
							std::vector<float> edgeLengths;
							if(mesh.tex_coordinates.size() && mesh.tex_coordinates[0].size())
							{
								UASSERT(mesh.tex_polygons.size() && mesh.tex_polygons[0].size() && mesh.tex_polygons[0][0].vertices.size());
								int polygonSize = mesh.tex_polygons[0][0].vertices.size();
								UDEBUG("polygon size=%d", polygonSize);

								for(unsigned int k=0; k<mesh.tex_coordinates.size(); ++k)
								{
									for(unsigned int i=0; i<mesh.tex_coordinates[k].size(); i+=polygonSize)
									{
										for(int j=0; j<polygonSize; ++j)
										{
											const Eigen::Vector2f & uc1 = mesh.tex_coordinates[k][i + j];
											const Eigen::Vector2f & uc2 = mesh.tex_coordinates[k][i + (j+1)%polygonSize];
											Eigen::Vector2f edge = (uc1-uc2)*textureSize;
											edgeLengths.push_back(fabs(edge[0]));
											edgeLengths.push_back(fabs(edge[1]));
										}
									}
								}
								float edgeLength = 0.0f;
								if(edgeLengths.size())
								{
									std::sort(edgeLengths.begin(), edgeLengths.end());
									float m = uMean(edgeLengths.data(), edgeLengths.size());
									float stddev = std::sqrt(uVariance(edgeLengths.data(), edgeLengths.size(), m));
									edgeLength = m+stddev;
									decimation = 1 << 6;
									for(int i=1; i<=6; ++i)
									{
										if(float(1 << i) >= edgeLength)
										{
											decimation = 1 << i;
											break;
										}
									}
								}

								UDEBUG("edge length=%f decimation=%d", edgeLength, decimation);
							}
						}
						else
						{
							if(blendingDecimation > 1)
							{
								UASSERT(textureSize % blendingDecimation == 0);
							}
							decimation = blendingDecimation;
							UDEBUG("decimation=%d", decimation);
						}

						std::vector<cv::Mat> blendGains(materials);
						for(int i=0; i<materials;++i)
						{
							blendGains[i] = cv::Mat(globalTextures.rows/decimation, globalTextures.rows/decimation, CV_32FC3, cv::Scalar::all(1.0f));
						}

						for(unsigned int p=0; p<vertexToPixels.size(); ++p)
						{
							if(vertexToPixels[p].size() > 1)
							{
								std::vector<float> gainsB(vertexToPixels[p].size());
								std::vector<float> gainsG(vertexToPixels[p].size());
								std::vector<float> gainsR(vertexToPixels[p].size());
								float sumWeight = 0.0f;
								int k=0;
								for(std::map<int, pcl::PointXY>::const_iterator iter=vertexToPixels[p].begin(); iter!=vertexToPixels[p].end(); ++iter)
								{
									if(materialsKept.at(iter->first))
									{
										int u = iter->second.x*emptyImage.cols + imageOrigin[iter->first].x;
										int v = (1.0-iter->second.y)*emptyImage.rows + imageOrigin[iter->first].y;
										float x = iter->second.x - 0.5f;
										float y = iter->second.y - 0.5f;
										float weight = 0.7f - sqrt(x*x+y*y);
										if(weight<0.0f)
										{
											weight = 0.0f;
										}
										int indexMaterial = newCamIndex[iter->first] / (cols*rows);
										cv::Vec3b * pt = globalTextures.ptr<cv::Vec3b>(v,u+indexMaterial*globalTextures.rows);
										gainsB[k] = static_cast<double>(pt->val[0]) * weight;
										gainsG[k] = static_cast<double>(pt->val[1]) * weight;
										gainsR[k] = static_cast<double>(pt->val[2]) * weight;
										sumWeight += weight;
										++k;
									}
								}
								gainsB.resize(k);
								gainsG.resize(k);
								gainsR.resize(k);

								if(sumWeight > 0)
								{
									float targetColor[3];
									targetColor[0] = uSum(gainsB.data(), gainsB.size()) / sumWeight;
									targetColor[1] = uSum(gainsG.data(), gainsG.size()) / sumWeight;
									targetColor[2] = uSum(gainsR.data(), gainsR.size()) / sumWeight;
									for(std::map<int, pcl::PointXY>::const_iterator iter=vertexToPixels[p].begin(); iter!=vertexToPixels[p].end(); ++iter)
									{
										if(materialsKept.at(iter->first))
										{
											int u = iter->second.x*emptyImage.cols + imageOrigin[iter->first].x;
											int v = (1.0-iter->second.y)*emptyImage.rows + imageOrigin[iter->first].y;
											int indexMaterial = newCamIndex[iter->first] / (cols*rows);
											cv::Vec3b * pt = globalTextures.ptr<cv::Vec3b>(v,u+indexMaterial*globalTextures.rows);
											float gB = targetColor[0]/(pt->val[0]==0?1.0f:pt->val[0]);
											float gG = targetColor[1]/(pt->val[1]==0?1.0f:pt->val[1]);
											float gR = targetColor[2]/(pt->val[2]==0?1.0f:pt->val[2]);
											cv::Vec3f * ptr = blendGains[indexMaterial].ptr<cv::Vec3f>(v/decimation, u/decimation);
											ptr->val[0] = (gB>1.3f)?1.3f:(gB<0.7f)?0.7f:gB;
											ptr->val[1] = (gG>1.3f)?1.3f:(gG<0.7f)?0.7f:gG;
											ptr->val[2] = (gR>1.3f)?1.3f:(gR<0.7f)?0.7f:gR;
										}
									}
								}
							}
						}

						for(int i=0; i<materials; ++i)
						{
							/*std::vector<cv::Mat> channels;
							cv::split(blendGains, channels);
							cv::Mat img;
							channels[0].convertTo(img,CV_8U,128.0,0);
							cv::imwrite("blendSmallB.png", img);
							channels[1].convertTo(img,CV_8U,128.0,0);
							cv::imwrite("blendSmallG.png", img);
							channels[2].convertTo(img,CV_8U,128.0,0);
							cv::imwrite("blendSmallR.png", img);*/

							cv::Mat globalTexturesROI = globalTextures(cv::Range::all(), cv::Range(i*globalTextures.rows, (i+1)*globalTextures.rows));
							cv::Mat dst;
							cv::blur(blendGains[i], dst, cv::Size(3,3));
							cv::resize(dst, blendGains[i], globalTexturesROI.size(), 0, 0, cv::INTER_LINEAR);

							/*cv::split(blendGains, channels);
							channels[0].convertTo(img,CV_8U,128.0,0);
							cv::imwrite("blendFullB.png", img);
							channels[1].convertTo(img,CV_8U,128.0,0);
							cv::imwrite("blendFullG.png", img);
							channels[2].convertTo(img,CV_8U,128.0,0);
							cv::imwrite("blendFullR.png", img);*/

							cv::multiply(globalTexturesROI, blendGains[i], globalTexturesROI, 1.0, CV_8UC3);

							//UWARN("Saving blending.png", globalTexture);
							//cv::imwrite("blending.png", globalTexture);
						}

						if(state) state->callback(uFormat("Blending (decimation=%d) %fs", decimation, timer.ticks()));
					}
				}

				if(brightnessContrastRatioLow > 0 || brightnessContrastRatioHigh > 0)
				{
					for(int i=0; i<materials; ++i)
					{
						cv::Mat globalTexturesROI = globalTextures(cv::Range::all(), cv::Range(i*globalTextures.rows, (i+1)*globalTextures.rows));
						cv::Mat globalTextureMasksROI = globalTextureMasks(cv::Range::all(), cv::Range(i*globalTextureMasks.rows, (i+1)*globalTextureMasks.rows));
						if(exposureFusion)
						{
							std::vector<cv::Mat> images;
							images.push_back(globalTexturesROI);
							if (brightnessContrastRatioLow > 0)
							{
								images.push_back(util2d::brightnessAndContrastAuto(
									globalTexturesROI,
									globalTextureMasksROI,
									(float)brightnessContrastRatioLow,
									0.0f));
							}
							if (brightnessContrastRatioHigh > 0)
							{
								images.push_back(util2d::brightnessAndContrastAuto(
									globalTexturesROI,
									globalTextureMasksROI,
									0.0f,
									(float)brightnessContrastRatioHigh));
							}

							util2d::exposureFusion(images).copyTo(globalTexturesROI);
						}
						else
						{
							util2d::brightnessAndContrastAuto(
								globalTexturesROI,
								globalTextureMasksROI,
								(float)brightnessContrastRatioLow,
								(float)brightnessContrastRatioHigh).copyTo(globalTexturesROI);
						}
					}
					if(state) state->callback(uFormat("Brightness and contrast auto %fs", timer.ticks()));
				}
			}
		}
	}
	UDEBUG("globalTextures=%d", globalTextures.cols?globalTextures.cols / globalTextures.rows:0);
	return globalTextures;
}

void fixTextureMeshForVisualization(pcl::TextureMesh & textureMesh)
{
	// VTK issue:
	//  tex_coordinates should be linked to points, not
	//  polygon vertices. Points linked to multiple different TCoords (different textures) should
	//  be duplicated.
	for (unsigned int t = 0; t < textureMesh.tex_coordinates.size(); ++t)
	{
		if(textureMesh.tex_polygons[t].size())
		{
			pcl::PointCloud<pcl::PointXYZ>::Ptr originalCloud(new pcl::PointCloud<pcl::PointXYZ>);
			pcl::fromPCLPointCloud2(textureMesh.cloud, *originalCloud);

			// make a cloud with as many points than polygon vertices
			unsigned int nPoints = textureMesh.tex_coordinates[t].size();
			UASSERT(nPoints == textureMesh.tex_polygons[t].size()*textureMesh.tex_polygons[t][0].vertices.size()); // assuming polygon size is constant!

			pcl::PointCloud<pcl::PointXYZ>::Ptr newCloud(new pcl::PointCloud<pcl::PointXYZ>);
			newCloud->resize(nPoints);

			unsigned int oi = 0;
			for (unsigned int i = 0; i < textureMesh.tex_polygons[t].size(); ++i)
			{
				pcl::Vertices & vertices = textureMesh.tex_polygons[t][i];

				for(unsigned int j=0; j<vertices.vertices.size(); ++j)
				{
					UASSERT(oi < newCloud->size());
					UASSERT_MSG(vertices.vertices[j] < originalCloud->size(), uFormat("%d vs %d", vertices.vertices[j], (int)originalCloud->size()).c_str());
					newCloud->at(oi) = originalCloud->at(vertices.vertices[j]);
					vertices.vertices[j] = oi; // new vertex index
					++oi;
				}
			}
			pcl::toPCLPointCloud2(*newCloud, textureMesh.cloud);
		}
	}
}

LaserScan computeNormals(
		const LaserScan & laserScan,
		int searchK,
		float searchRadius)
{
	if(laserScan.isEmpty())
	{
		return laserScan;
	}

	pcl::PointCloud<pcl::Normal>::Ptr normals;
	// convert to compatible PCL format and filter it
	if(laserScan.hasRGB())
	{
		pcl::PointCloud<pcl::PointXYZRGB>::Ptr cloud = laserScanToPointCloudRGB(laserScan);
		if(cloud->size())
		{
			UASSERT(!laserScan.is2d());
			pcl::PointCloud<pcl::Normal>::Ptr normals = util3d::computeNormals(cloud, searchK, searchRadius);
			return LaserScan(laserScanFromPointCloud(*cloud, *normals), laserScan.maxPoints(), laserScan.maxRange(), LaserScan::kXYZRGBNormal, laserScan.localTransform());
		}
	}
	else if(laserScan.hasIntensity())
	{
		pcl::PointCloud<pcl::PointXYZI>::Ptr cloud = laserScanToPointCloudI(laserScan);
		if(cloud->size())
		{
			if(laserScan.is2d())
			{
				pcl::PointCloud<pcl::Normal>::Ptr normals = util3d::computeNormals2D(cloud, searchK, searchRadius);
				return LaserScan(laserScan2dFromPointCloud(*cloud, *normals), laserScan.maxPoints(), laserScan.maxRange(), LaserScan::kXYZRGBNormal, laserScan.localTransform());
			}
			else
			{
				pcl::PointCloud<pcl::Normal>::Ptr normals = util3d::computeNormals(cloud, searchK, searchRadius);
				return LaserScan(laserScanFromPointCloud(*cloud, *normals), laserScan.maxPoints(), laserScan.maxRange(), LaserScan::kXYZRGBNormal, laserScan.localTransform());
			}
		}
	}
	else
	{
		pcl::PointCloud<pcl::PointXYZ>::Ptr cloud = laserScanToPointCloud(laserScan);
		if(cloud->size())
		{
			if(laserScan.is2d())
			{
				pcl::PointCloud<pcl::Normal>::Ptr normals = util3d::computeNormals2D(cloud, searchK, searchRadius);
				return LaserScan(laserScan2dFromPointCloud(*cloud, *normals), laserScan.maxPoints(), laserScan.maxRange(), LaserScan::kXYZRGBNormal, laserScan.localTransform());
			}
			else
			{
				pcl::PointCloud<pcl::Normal>::Ptr normals = util3d::computeNormals(cloud, searchK, searchRadius);
				return LaserScan(laserScanFromPointCloud(*cloud, *normals), laserScan.maxPoints(), laserScan.maxRange(), LaserScan::kXYZRGBNormal, laserScan.localTransform());
			}
		}
	}
	return LaserScan();
}

template<typename PointT>
pcl::PointCloud<pcl::Normal>::Ptr computeNormalsImpl(
		const typename pcl::PointCloud<PointT>::Ptr & cloud,
		const pcl::IndicesPtr & indices,
		int searchK,
		float searchRadius,
		const Eigen::Vector3f & viewPoint)
{
	typename pcl::search::KdTree<PointT>::Ptr tree (new pcl::search::KdTree<PointT>);
	if(indices->size())
	{
		tree->setInputCloud(cloud, indices);
	}
	else
	{
		tree->setInputCloud (cloud);
	}

	// Normal estimation*
#ifdef PCL_OMP
	pcl::NormalEstimationOMP<PointT, pcl::Normal> n;
#else
	pcl::NormalEstimation<PointT, pcl::Normal> n;
#endif
	pcl::PointCloud<pcl::Normal>::Ptr normals (new pcl::PointCloud<pcl::Normal>);
	n.setInputCloud (cloud);
	// Commented: Keep the output normals size the same as the input cloud
	//if(indices->size())
	//{
	//	n.setIndices(indices);
	//}
	n.setSearchMethod (tree);
	n.setKSearch (searchK);
	n.setRadiusSearch (searchRadius);
	n.setViewPoint(viewPoint[0], viewPoint[1], viewPoint[2]);
	n.compute (*normals);

	return normals;
}
pcl::PointCloud<pcl::Normal>::Ptr computeNormals(
		const pcl::PointCloud<pcl::PointXYZ>::Ptr & cloud,
		int searchK,
		float searchRadius,
		const Eigen::Vector3f & viewPoint)
{
	pcl::IndicesPtr indices(new std::vector<int>);
	return computeNormals(cloud, indices, searchK, searchRadius, viewPoint);
}
pcl::PointCloud<pcl::Normal>::Ptr computeNormals(
		const pcl::PointCloud<pcl::PointXYZRGB>::Ptr & cloud,
		int searchK,
		float searchRadius,
		const Eigen::Vector3f & viewPoint)
{
	pcl::IndicesPtr indices(new std::vector<int>);
	return computeNormals(cloud, indices, searchK, searchRadius, viewPoint);
}
pcl::PointCloud<pcl::Normal>::Ptr computeNormals(
		const pcl::PointCloud<pcl::PointXYZI>::Ptr & cloud,
		int searchK,
		float searchRadius,
		const Eigen::Vector3f & viewPoint)
{
	pcl::IndicesPtr indices(new std::vector<int>);
	return computeNormals(cloud, indices, searchK, searchRadius, viewPoint);
}
pcl::PointCloud<pcl::Normal>::Ptr computeNormals(
		const pcl::PointCloud<pcl::PointXYZ>::Ptr & cloud,
		const pcl::IndicesPtr & indices,
		int searchK,
		float searchRadius,
		const Eigen::Vector3f & viewPoint)
{
	return computeNormalsImpl<pcl::PointXYZ>(cloud, indices, searchK, searchRadius, viewPoint);
}
pcl::PointCloud<pcl::Normal>::Ptr computeNormals(
		const pcl::PointCloud<pcl::PointXYZRGB>::Ptr & cloud,
		const pcl::IndicesPtr & indices,
		int searchK,
		float searchRadius,
		const Eigen::Vector3f & viewPoint)
{
	return computeNormalsImpl<pcl::PointXYZRGB>(cloud, indices, searchK, searchRadius, viewPoint);
}
pcl::PointCloud<pcl::Normal>::Ptr computeNormals(
		const pcl::PointCloud<pcl::PointXYZI>::Ptr & cloud,
		const pcl::IndicesPtr & indices,
		int searchK,
		float searchRadius,
		const Eigen::Vector3f & viewPoint)
{
	return computeNormalsImpl<pcl::PointXYZI>(cloud, indices, searchK, searchRadius, viewPoint);
}

template<typename PointT>
pcl::PointCloud<pcl::Normal>::Ptr computeNormals2DImpl(
		const typename pcl::PointCloud<PointT>::Ptr & cloud,
		int searchK,
		float searchRadius,
		const Eigen::Vector3f & viewPoint)
{
	UASSERT(searchK>0 || searchRadius>0.0f);
	pcl::PointCloud<pcl::Normal>::Ptr normals (new pcl::PointCloud<pcl::Normal>);

	typename pcl::search::KdTree<PointT>::Ptr tree (new pcl::search::KdTree<PointT>);
	tree->setInputCloud (cloud);

	normals->resize(cloud->size());

	float bad_point = std::numeric_limits<float>::quiet_NaN ();

	// assuming that points are ordered
	for(unsigned int i=0; i<cloud->size(); ++i)
	{
		const PointT & pt = cloud->at(i);
		std::vector<Eigen::Vector3f> neighborNormals;
		Eigen::Vector3f direction;
		direction[0] = viewPoint[0] - pt.x;
		direction[1] = viewPoint[1] - pt.y;
		direction[2] = viewPoint[2] - pt.z;

		std::vector<int> k_indices;
		std::vector<float> k_sqr_distances;
		if(searchRadius>0.0f)
		{
			tree->radiusSearch(cloud->at(i), searchRadius, k_indices, k_sqr_distances, searchK);
		}
		else
		{
			tree->nearestKSearch(cloud->at(i), searchK, k_indices, k_sqr_distances);
		}

		for(unsigned int j=0; j<k_indices.size(); ++j)
		{
			if(k_indices.at(j) != (int)i)
			{
				const PointT & pt2 = cloud->at(k_indices.at(j));
				Eigen::Vector3f v(pt2.x-pt.x, pt2.y - pt.y, pt2.z - pt.z);
				Eigen::Vector3f up = v.cross(direction);
				Eigen::Vector3f n = up.cross(v);
				n.normalize();
				neighborNormals.push_back(n);
			}
		}

		if(neighborNormals.empty())
		{
			normals->at(i).normal_x = bad_point;
			normals->at(i).normal_y = bad_point;
			normals->at(i).normal_z = bad_point;
		}
		else
		{
			Eigen::Vector3f meanNormal(0,0,0);
			for(unsigned int j=0; j<neighborNormals.size(); ++j)
			{
				meanNormal+=neighborNormals[j];
			}
			meanNormal /= (float)neighborNormals.size();
			meanNormal.normalize();
			normals->at(i).normal_x = meanNormal[0];
			normals->at(i).normal_y = meanNormal[1];
			normals->at(i).normal_z = meanNormal[2];
		}
	}

	return normals;
}
pcl::PointCloud<pcl::Normal>::Ptr computeNormals2D(
		const pcl::PointCloud<pcl::PointXYZ>::Ptr & cloud,
		int searchK,
		float searchRadius,
		const Eigen::Vector3f & viewPoint)
{
	return computeNormals2DImpl<pcl::PointXYZ>(cloud, searchK, searchRadius, viewPoint);
}
pcl::PointCloud<pcl::Normal>::Ptr computeNormals2D(
		const pcl::PointCloud<pcl::PointXYZI>::Ptr & cloud,
		int searchK,
		float searchRadius,
		const Eigen::Vector3f & viewPoint)
{
	return computeNormals2DImpl<pcl::PointXYZI>(cloud, searchK, searchRadius, viewPoint);
}

template<typename PointT>
pcl::PointCloud<pcl::Normal>::Ptr computeFastOrganizedNormals2DImpl(
		const typename pcl::PointCloud<PointT>::Ptr & cloud,
		int searchK,
		float searchRadius,
		const Eigen::Vector3f & viewPoint)
{
	UASSERT(searchK>0);
	pcl::PointCloud<pcl::Normal>::Ptr normals (new pcl::PointCloud<pcl::Normal>);

	normals->resize(cloud->size());
	searchRadius *= searchRadius; // squared distance

	float bad_point = std::numeric_limits<float>::quiet_NaN ();

	// assuming that points are ordered
	for(int i=0; i<(int)cloud->size(); ++i)
	{
		int li = i-searchK;
		if(li<0)
		{
			li=0;
		}
		int hi = i+searchK;
		if(hi>=(int)cloud->size())
		{
			hi=(int)cloud->size()-1;
		}

		// get points before not too far
		const PointT & pt = cloud->at(i);
		std::vector<Eigen::Vector3f> neighborNormals;
		Eigen::Vector3f direction;
		direction[0] = viewPoint[0] - cloud->at(i).x;
		direction[1] = viewPoint[1] - cloud->at(i).y;
		direction[2] = viewPoint[2] - cloud->at(i).z;
		for(int j=i-1; j>=li; --j)
		{
			const PointT & pt2 = cloud->at(j);
			Eigen::Vector3f vd(pt2.x-pt.x, pt2.y - pt.y, pt2.z - pt.z);
			if(searchRadius<=0.0f || (vd[0]*vd[0] + vd[1]*vd[1] + vd[2]*vd[2]) < searchRadius)
			{
				Eigen::Vector3f v(pt2.x-pt.x, pt2.y - pt.y, pt2.z - pt.z);
				Eigen::Vector3f up = v.cross(direction);
				Eigen::Vector3f n = up.cross(v);
				n.normalize();
				neighborNormals.push_back(n);
			}
			else
			{
				break;
			}
		}
		for(int j=i+1; j<=hi; ++j)
		{
			const PointT & pt2 = cloud->at(j);
			Eigen::Vector3f vd(pt2.x-pt.x, pt2.y - pt.y, pt2.z - pt.z);
			if(searchRadius<=0.0f || (vd[0]*vd[0] + vd[1]*vd[1] + vd[2]*vd[2]) < searchRadius)
			{
				Eigen::Vector3f v(pt2.x-pt.x, pt2.y - pt.y, pt2.z - pt.z);
				Eigen::Vector3f up = v[2]==0.0f?Eigen::Vector3f(0,0,1):v.cross(direction);
				Eigen::Vector3f n = up.cross(v);
				n.normalize();
				neighborNormals.push_back(n);
			}
			else
			{
				break;
			}
		}

		if(neighborNormals.empty())
		{
			normals->at(i).normal_x = bad_point;
			normals->at(i).normal_y = bad_point;
			normals->at(i).normal_z = bad_point;
		}
		else
		{
			Eigen::Vector3f meanNormal(0,0,0);
			for(unsigned int j=0; j<neighborNormals.size(); ++j)
			{
				meanNormal+=neighborNormals[j];
			}
			meanNormal /= (float)neighborNormals.size();
			meanNormal.normalize();
			normals->at(i).normal_x = meanNormal[0];
			normals->at(i).normal_y = meanNormal[1];
			normals->at(i).normal_z = meanNormal[2];
		}
	}

	return normals;
}

pcl::PointCloud<pcl::Normal>::Ptr computeFastOrganizedNormals2D(
		const pcl::PointCloud<pcl::PointXYZ>::Ptr & cloud,
		int searchK,
		float searchRadius,
		const Eigen::Vector3f & viewPoint)
{
	return computeFastOrganizedNormals2DImpl<pcl::PointXYZ>(cloud, searchK, searchRadius, viewPoint);
}
pcl::PointCloud<pcl::Normal>::Ptr computeFastOrganizedNormals2D(
		const pcl::PointCloud<pcl::PointXYZI>::Ptr & cloud,
		int searchK,
		float searchRadius,
		const Eigen::Vector3f & viewPoint)
{
	return computeFastOrganizedNormals2DImpl<pcl::PointXYZI>(cloud, searchK, searchRadius, viewPoint);
}

pcl::PointCloud<pcl::Normal>::Ptr computeFastOrganizedNormals(
		const pcl::PointCloud<pcl::PointXYZRGB>::Ptr & cloud,
		float maxDepthChangeFactor,
		float normalSmoothingSize,
		const Eigen::Vector3f & viewPoint)
{
	pcl::IndicesPtr indices(new std::vector<int>);
	return computeFastOrganizedNormals(cloud, indices, maxDepthChangeFactor, normalSmoothingSize, viewPoint);
}
pcl::PointCloud<pcl::Normal>::Ptr computeFastOrganizedNormals(
		const pcl::PointCloud<pcl::PointXYZRGB>::Ptr & cloud,
		const pcl::IndicesPtr & indices,
		float maxDepthChangeFactor,
		float normalSmoothingSize,
		const Eigen::Vector3f & viewPoint)
{
	UASSERT(cloud->isOrganized());

	pcl::search::KdTree<pcl::PointXYZRGB>::Ptr tree (new pcl::search::KdTree<pcl::PointXYZRGB>);
	if(indices->size())
	{
		tree->setInputCloud(cloud, indices);
	}
	else
	{
		tree->setInputCloud (cloud);
	}

	// Normal estimation
	pcl::PointCloud<pcl::Normal>::Ptr normals (new pcl::PointCloud<pcl::Normal>);
	pcl::IntegralImageNormalEstimation<pcl::PointXYZRGB, pcl::Normal> ne;
	ne.setNormalEstimationMethod (ne.AVERAGE_3D_GRADIENT);
	ne.setMaxDepthChangeFactor(maxDepthChangeFactor);
	ne.setNormalSmoothingSize(normalSmoothingSize);
	ne.setBorderPolicy(ne.BORDER_POLICY_MIRROR);
	ne.setInputCloud(cloud);
	// Commented: Keep the output normals size the same as the input cloud
	//if(indices->size())
	//{
	//	ne.setIndices(indices);
	//}
	ne.setSearchMethod(tree);
	ne.setViewPoint(viewPoint[0], viewPoint[1], viewPoint[2]);
	ne.compute(*normals);

	return normals;
}

float computeNormalsComplexity(
		const LaserScan & scan,
		cv::Mat * pcaEigenVectors,
		cv::Mat * pcaEigenValues)
{
	if(!scan.isEmpty() && (scan.hasNormals()))
	{
		 //Construct a buffer used by the pca analysis
		int sz = static_cast<int>(scan.size()*2);
		bool is2d = scan.is2d();
		cv::Mat data_normals = cv::Mat::zeros(sz, is2d?2:3, CV_32FC1);
		int oi = 0;
		int nOffset = 0;
		if(!scan.is2d())
		{
			nOffset+=1;
		}
		if(scan.hasIntensity() || scan.hasRGB())
		{
			nOffset+=1;
		}
		for (int i = 0; i < scan.size(); ++i)
		{
			const float * ptrScan = scan.data().ptr<float>(0, i);

			if(is2d)
			{
				if(uIsFinite(ptrScan[nOffset+2]) && uIsFinite(ptrScan[nOffset+3]))
				{
					float * ptr = data_normals.ptr<float>(oi++, 0);
					ptr[0] = ptrScan[2];
					ptr[1] = ptrScan[3];
				}
			}
			else
			{
				if(uIsFinite(ptrScan[nOffset+2]) && uIsFinite(ptrScan[nOffset+3]) && uIsFinite(ptrScan[nOffset+4]))
				{
					float * ptr = data_normals.ptr<float>(oi++, 0);
					ptr[0] = ptrScan[3];
					ptr[1] = ptrScan[4];
					ptr[2] = ptrScan[5];
				}
			}
		}
		if(oi>1)
		{
			cv::PCA pca_analysis(cv::Mat(data_normals, cv::Range(0, oi*2)), cv::Mat(), CV_PCA_DATA_AS_ROW);

			if(pcaEigenVectors)
			{
				*pcaEigenVectors = pca_analysis.eigenvectors;
			}
			if(pcaEigenValues)
			{
				*pcaEigenValues = pca_analysis.eigenvalues;
			}

			// Get last eigen value, scale between 0 and 1: 0=low complexity, 1=high complexity
			return pca_analysis.eigenvalues.at<float>(0, is2d?1:2)*(is2d?2.0f:3.0f);
		}
	}
	else if(!scan.isEmpty())
	{
		UERROR("Scan doesn't have normals!");
	}
	return 0.0f;
}

float computeNormalsComplexity(
		const pcl::PointCloud<pcl::PointNormal> & cloud,
		bool is2d,
		cv::Mat * pcaEigenVectors,
		cv::Mat * pcaEigenValues)
{
	 //Construct a buffer used by the pca analysis
	int sz = static_cast<int>(cloud.size()*2);
	cv::Mat data_normals = cv::Mat::zeros(sz, is2d?2:3, CV_32FC1);
	int oi = 0;
	for (unsigned int i = 0; i < cloud.size(); ++i)
	{
		const pcl::PointNormal & pt = cloud.at(i);
		if(uIsFinite(pt.normal_x) && uIsFinite(pt.normal_y) && uIsFinite(pt.normal_z))
		{
			float * ptr = data_normals.ptr<float>(oi++, 0);
			ptr[0] = pt.normal_x;
			ptr[1] = pt.normal_y;
			if(!is2d)
			{
				ptr[2] = pt.normal_z;
			}
		}
	}
	if(oi>1)
	{
		cv::PCA pca_analysis(cv::Mat(data_normals, cv::Range(0, oi*2)), cv::Mat(), CV_PCA_DATA_AS_ROW);

		if(pcaEigenVectors)
		{
			*pcaEigenVectors = pca_analysis.eigenvectors;
		}
		if(pcaEigenValues)
		{
			*pcaEigenValues = pca_analysis.eigenvalues;
		}

		// Get last eigen value, scale between 0 and 1: 0=low complexity, 1=high complexity
		return pca_analysis.eigenvalues.at<float>(0, is2d?1:2)*(is2d?2.0f:3.0f);
	}
	return 0.0f;
}

float computeNormalsComplexity(
		const pcl::PointCloud<pcl::Normal> & normals,
		bool is2d,
		cv::Mat * pcaEigenVectors,
		cv::Mat * pcaEigenValues)
{
	 //Construct a buffer used by the pca analysis
	int sz = static_cast<int>(normals.size()*2);
	cv::Mat data_normals = cv::Mat::zeros(sz, is2d?2:3, CV_32FC1);
	int oi = 0;
	for (unsigned int i = 0; i < normals.size(); ++i)
	{
		const pcl::Normal & pt = normals.at(i);
		if(uIsFinite(pt.normal_x) && uIsFinite(pt.normal_y) && uIsFinite(pt.normal_z))
		{
			float * ptr = data_normals.ptr<float>(oi++, 0);
			ptr[0] = pt.normal_x;
			ptr[1] = pt.normal_y;
			if(!is2d)
			{
				ptr[2] = pt.normal_z;
			}
		}
	}
	if(oi>1)
	{
		cv::PCA pca_analysis(cv::Mat(data_normals, cv::Range(0, oi*2)), cv::Mat(), CV_PCA_DATA_AS_ROW);

		if(pcaEigenVectors)
		{
			*pcaEigenVectors = pca_analysis.eigenvectors;
		}
		if(pcaEigenValues)
		{
			*pcaEigenValues = pca_analysis.eigenvalues;
		}

		// Get last eigen value, scale between 0 and 1: 0=low complexity, 1=high complexity
		return pca_analysis.eigenvalues.at<float>(0, is2d?1:2)*(is2d?2.0f:3.0f);
	}
	return 0.0f;
}

float computeNormalsComplexity(
		const pcl::PointCloud<pcl::PointXYZRGBNormal> & cloud,
		bool is2d,
		cv::Mat * pcaEigenVectors,
		cv::Mat * pcaEigenValues)
{
	 //Construct a buffer used by the pca analysis
	int sz = static_cast<int>(cloud.size()*2);
	cv::Mat data_normals = cv::Mat::zeros(sz, is2d?2:3, CV_32FC1);
	int oi = 0;
	for (unsigned int i = 0; i < cloud.size(); ++i)
	{
		const pcl::PointXYZRGBNormal & pt = cloud.at(i);
		if(uIsFinite(pt.normal_x) && uIsFinite(pt.normal_y) && uIsFinite(pt.normal_z))
		{
			float * ptr = data_normals.ptr<float>(oi++, 0);
			ptr[0] = pt.normal_x;
			ptr[1] = pt.normal_y;
			if(!is2d)
			{
				ptr[2] = pt.normal_z;
			}
		}
	}
	if(oi>1)
	{
		cv::PCA pca_analysis(cv::Mat(data_normals, cv::Range(0, oi*2)), cv::Mat(), CV_PCA_DATA_AS_ROW);

		if(pcaEigenVectors)
		{
			*pcaEigenVectors = pca_analysis.eigenvectors;
		}
		if(pcaEigenValues)
		{
			*pcaEigenValues = pca_analysis.eigenvalues;
		}

		// Get last eigen value, scale between 0 and 1: 0=low complexity, 1=high complexity
		return pca_analysis.eigenvalues.at<float>(0, is2d?1:2)*(is2d?2.0f:3.0f);
	}
	return 0.0f;
}

pcl::PointCloud<pcl::PointXYZRGBNormal>::Ptr mls(
		const pcl::PointCloud<pcl::PointXYZRGB>::Ptr & cloud,
		float searchRadius,
		int polygonialOrder,
		int upsamplingMethod, // NONE, DISTINCT_CLOUD, SAMPLE_LOCAL_PLANE, RANDOM_UNIFORM_DENSITY, VOXEL_GRID_DILATION
		float upsamplingRadius,      // SAMPLE_LOCAL_PLANE
		float upsamplingStep,        // SAMPLE_LOCAL_PLANE
		int pointDensity,             // RANDOM_UNIFORM_DENSITY
		float dilationVoxelSize,     // VOXEL_GRID_DILATION
		int dilationIterations)       // VOXEL_GRID_DILATION
{
	pcl::IndicesPtr indices(new std::vector<int>);
	return mls(cloud,
			indices,
			searchRadius,
			polygonialOrder,
			upsamplingMethod,
			upsamplingRadius,
			upsamplingStep,
			pointDensity,
			dilationVoxelSize,
			dilationIterations);
}

pcl::PointCloud<pcl::PointXYZRGBNormal>::Ptr mls(
		const pcl::PointCloud<pcl::PointXYZRGB>::Ptr & cloud,
		const pcl::IndicesPtr & indices,
		float searchRadius,
		int polygonialOrder,
		int upsamplingMethod, // NONE, DISTINCT_CLOUD, SAMPLE_LOCAL_PLANE, RANDOM_UNIFORM_DENSITY, VOXEL_GRID_DILATION
		float upsamplingRadius,      // SAMPLE_LOCAL_PLANE
		float upsamplingStep,        // SAMPLE_LOCAL_PLANE
		int pointDensity,             // RANDOM_UNIFORM_DENSITY
		float dilationVoxelSize,     // VOXEL_GRID_DILATION
		int dilationIterations)       // VOXEL_GRID_DILATION
{
	pcl::PointCloud<pcl::PointXYZRGBNormal>::Ptr cloud_with_normals(new pcl::PointCloud<pcl::PointXYZRGBNormal>);
	pcl::search::KdTree<pcl::PointXYZRGB>::Ptr tree (new pcl::search::KdTree<pcl::PointXYZRGB>);
	if(indices->size())
	{
		tree->setInputCloud (cloud, indices);
	}
	else
	{
		tree->setInputCloud (cloud);
	}

	// Init object (second point type is for the normals)
	pcl::MovingLeastSquares<pcl::PointXYZRGB, pcl::PointXYZRGBNormal> mls;

	// Set parameters
	mls.setComputeNormals (true);
	if(polygonialOrder > 0)
	{
		mls.setPolynomialFit (true);
		mls.setPolynomialOrder(polygonialOrder);
	}
	else
	{
		mls.setPolynomialFit (false);
	}
	UASSERT(upsamplingMethod >= mls.NONE &&
			upsamplingMethod <= mls.VOXEL_GRID_DILATION);
	mls.setUpsamplingMethod((pcl::MovingLeastSquares<pcl::PointXYZRGB, pcl::PointXYZRGBNormal>::UpsamplingMethod)upsamplingMethod);
	mls.setSearchRadius(searchRadius);
	mls.setUpsamplingRadius(upsamplingRadius);
	mls.setUpsamplingStepSize(upsamplingStep);
	mls.setPointDensity(pointDensity);
	mls.setDilationVoxelSize(dilationVoxelSize);
	mls.setDilationIterations(dilationIterations);

	// Reconstruct
	mls.setInputCloud (cloud);
	if(indices->size())
	{
		mls.setIndices(indices);
	}
	mls.setSearchMethod (tree);
	mls.process (*cloud_with_normals);

	// It seems that returned normals are not normalized!? FIXME: Is it a bug only in PCL 1.7.1?
	for(unsigned int i=0; i<cloud_with_normals->size(); ++i)
	{
		Eigen::Vector3f normal(cloud_with_normals->at(i).normal_x, cloud_with_normals->at(i).normal_y, cloud_with_normals->at(i).normal_z);
		normal.normalize();
		cloud_with_normals->at(i).normal_x = normal[0];
		cloud_with_normals->at(i).normal_y = normal[1];
		cloud_with_normals->at(i).normal_z = normal[2];
	}

	return cloud_with_normals;
}

LaserScan adjustNormalsToViewPoint(
		const LaserScan & scan,
		const Eigen::Vector3f & viewpoint,
		bool forceGroundNormalsUp)
{
	if(scan.size() && !scan.is2d() && scan.hasNormals())
	{
		int nx = scan.getNormalsOffset();
		int ny = nx+1;
		int nz = ny+1;
		cv::Mat output = scan.data().clone();
		for(int i=0; i<scan.size(); ++i)
		{
			float * ptr = output.ptr<float>(0, i);
			if(uIsFinite(ptr[nx]) && uIsFinite(ptr[ny]) && uIsFinite(ptr[nz]))
			{
				Eigen::Vector3f v = viewpoint - Eigen::Vector3f(ptr[0], ptr[1], ptr[2]);
				Eigen::Vector3f n(ptr[nx], ptr[ny], ptr[nz]);

				float result = v.dot(n);
				if(result < 0
				 || (forceGroundNormalsUp && ptr[nz] < -0.8 && ptr[2] < viewpoint[3])) // some far velodyne rays on road can have normals toward ground
				{
					//reverse normal
					ptr[nx] *= -1.0f;
					ptr[ny] *= -1.0f;
					ptr[nz] *= -1.0f;
				}
			}
		}
		return LaserScan(output, scan.maxPoints(), scan.maxRange(), scan.format(), scan.localTransform());
	}
	return scan;
}

void adjustNormalsToViewPoint(
		pcl::PointCloud<pcl::PointNormal>::Ptr & cloud,
		const Eigen::Vector3f & viewpoint,
		bool forceGroundNormalsUp)
{
	for(unsigned int i=0; i<cloud->size(); ++i)
	{
		pcl::PointXYZ normal(cloud->points[i].normal_x, cloud->points[i].normal_y, cloud->points[i].normal_z);
		if(pcl::isFinite(normal))
		{
			Eigen::Vector3f v = viewpoint - cloud->points[i].getVector3fMap();
			Eigen::Vector3f n(normal.x, normal.y, normal.z);

			float result = v.dot(n);
			if(result < 0
			 || (forceGroundNormalsUp && normal.z < -0.8 && cloud->points[i].z < viewpoint[3])) // some far velodyne rays on road can have normals toward ground
			{
				//reverse normal
				cloud->points[i].normal_x *= -1.0f;
				cloud->points[i].normal_y *= -1.0f;
				cloud->points[i].normal_z *= -1.0f;
			}
		}
	}
}

void adjustNormalsToViewPoint(
		pcl::PointCloud<pcl::PointXYZRGBNormal>::Ptr & cloud,
		const Eigen::Vector3f & viewpoint,
		bool forceGroundNormalsUp)
{
	for(unsigned int i=0; i<cloud->size(); ++i)
	{
		pcl::PointXYZ normal(cloud->points[i].normal_x, cloud->points[i].normal_y, cloud->points[i].normal_z);
		if(pcl::isFinite(normal))
		{
			Eigen::Vector3f v = viewpoint - cloud->points[i].getVector3fMap();
			Eigen::Vector3f n(normal.x, normal.y, normal.z);

			float result = v.dot(n);
			if(result < 0
				|| (forceGroundNormalsUp && normal.z < -0.8 && cloud->points[i].z < viewpoint[3])) // some far velodyne rays on road can have normals toward ground
			{
				//reverse normal
				cloud->points[i].normal_x *= -1.0f;
				cloud->points[i].normal_y *= -1.0f;
				cloud->points[i].normal_z *= -1.0f;
			}
		}
	}
}

void adjustNormalsToViewPoints(
		const std::map<int, Transform> & poses,
		const pcl::PointCloud<pcl::PointXYZ>::Ptr & rawCloud,
		const std::vector<int> & rawCameraIndices,
		pcl::PointCloud<pcl::PointNormal>::Ptr & cloud)
{
	if(poses.size() && rawCloud->size() && rawCloud->size() == rawCameraIndices.size() && cloud->size())
	{
		pcl::search::KdTree<pcl::PointXYZ>::Ptr rawTree (new pcl::search::KdTree<pcl::PointXYZ>);
		rawTree->setInputCloud (rawCloud);

		for(unsigned int i=0; i<cloud->size(); ++i)
		{
			pcl::PointXYZ normal(cloud->points[i].normal_x, cloud->points[i].normal_y, cloud->points[i].normal_z);
			if(pcl::isFinite(normal))
			{
				std::vector<int> indices;
				std::vector<float> dist;
				rawTree->nearestKSearch(pcl::PointXYZ(cloud->points[i].x, cloud->points[i].y, cloud->points[i].z), 1, indices, dist);
				UASSERT(indices.size() == 1);
				if(indices.size() && indices[0]>=0)
				{
					Transform p = poses.at(rawCameraIndices[indices[0]]);
					pcl::PointXYZ viewpoint(p.x(), p.y(), p.z());
					Eigen::Vector3f v = viewpoint.getVector3fMap() - cloud->points[i].getVector3fMap();

					Eigen::Vector3f n(normal.x, normal.y, normal.z);

					float result = v.dot(n);
					if(result < 0)
					{
						//reverse normal
						cloud->points[i].normal_x *= -1.0f;
						cloud->points[i].normal_y *= -1.0f;
						cloud->points[i].normal_z *= -1.0f;
					}
				}
				else
				{
					UWARN("Not found camera viewpoint for point %d", i);
				}
			}
		}
	}
}

void adjustNormalsToViewPoints(
		const std::map<int, Transform> & poses,
		const pcl::PointCloud<pcl::PointXYZ>::Ptr & rawCloud,
		const std::vector<int> & rawCameraIndices,
		pcl::PointCloud<pcl::PointXYZRGBNormal>::Ptr & cloud)
{
	UASSERT(rawCloud.get() && cloud.get());
	UDEBUG("poses=%d, rawCloud=%d, rawCameraIndices=%d, cloud=%d", (int)poses.size(), (int)rawCloud->size(), (int)rawCameraIndices.size(), (int)cloud->size());
	if(poses.size() && rawCloud->size() && rawCloud->size() == rawCameraIndices.size() && cloud->size())
	{
		pcl::search::KdTree<pcl::PointXYZ>::Ptr rawTree (new pcl::search::KdTree<pcl::PointXYZ>);
		rawTree->setInputCloud (rawCloud);
		for(unsigned int i=0; i<cloud->size(); ++i)
		{
			pcl::PointXYZ normal(cloud->points[i].normal_x, cloud->points[i].normal_y, cloud->points[i].normal_z);
			if(pcl::isFinite(normal))
			{
				std::vector<int> indices;
				std::vector<float> dist;
				rawTree->nearestKSearch(pcl::PointXYZ(cloud->points[i].x, cloud->points[i].y, cloud->points[i].z), 1, indices, dist);
				if(indices.size() && indices[0]>=0)
				{
					UASSERT_MSG(indices[0]<(int)rawCameraIndices.size(), uFormat("indices[0]=%d rawCameraIndices.size()=%d", indices[0], (int)rawCameraIndices.size()).c_str());
					UASSERT(uContains(poses, rawCameraIndices[indices[0]]));
					Transform p = poses.at(rawCameraIndices[indices[0]]);
					pcl::PointXYZ viewpoint(p.x(), p.y(), p.z());
					Eigen::Vector3f v = viewpoint.getVector3fMap() - cloud->points[i].getVector3fMap();

					Eigen::Vector3f n(normal.x, normal.y, normal.z);

					float result = v.dot(n);
					if(result < 0)
					{
						//reverse normal
						cloud->points[i].normal_x *= -1.0f;
						cloud->points[i].normal_y *= -1.0f;
						cloud->points[i].normal_z *= -1.0f;
					}
				}
				else
				{
					UWARN("Not found camera viewpoint for point %d!?", i);
				}
			}
		}
	}
}

pcl::PolygonMesh::Ptr meshDecimation(const pcl::PolygonMesh::Ptr & mesh, float factor)
{
	pcl::PolygonMesh::Ptr output(new pcl::PolygonMesh);
#ifndef DISABLE_VTK
	pcl::MeshQuadricDecimationVTK mqd;
	mqd.setTargetReductionFactor(factor);
	mqd.setInputMesh(mesh);
	mqd.process (*output);
#else
	UWARN("RTAB-Map is not built with VTK module so mesh decimation cannot be used!");
	*output = *mesh;
#endif
	return output;
}

}

}
