#include <iostream>
#include <fstream>
#include <map>

#include <i3d/draw.h>
#include <i3d/morphology.h>
#include <i3d/transform.h>
#include <i3d/DistanceTransform.h>
#include <i3d/filters.h>

#include "TriangleMesh.h"
#include "../cmath3d_v/TriangleMesh_v.h"
#include "../src/rnd_generators.h"
#include "../src/texture/texture.h"

#undef min
#undef max

//some debug-code enabling triggers
//#define SAVE_INTERMEDIATE_IMAGES

//'multiple' should be ideally 10^desired_decimal_accuracy
int inline RoundTo(const float val, const float multiple=1000.f)
{
	return ( int(floorf(val*multiple)) );
}

//puts v1 into Pos, with mPos being a helper structure preventing having
//v1 multiple times inside the Pos
long unsigned int Enlist(
	const Vector3FC& v1,
	std::vector<Vector3FC>& Pos,
	std::map< int,std::map< int,std::map< int,long unsigned int > > >& mPos)
{
	long unsigned int o1; //ret val

	std::map< int,std::map< int,long unsigned int > >& mY=mPos[RoundTo(v1.x)];
	if (mY.empty())
	{
		Pos.push_back(v1);
		o1=Pos.size();

		//add reference to this vertex in the mPos structure
		std::map< int,long unsigned int > mZ;
		mZ[RoundTo(v1.z)]=o1;
		std::map< int,std::map< int,long unsigned int > > my;
		my[RoundTo(v1.y)]=mZ;
		mPos[RoundTo(v1.x)]=my;
	}
	else
	{
		std::map< int,long unsigned int >& mZ=mY[RoundTo(v1.y)];
		if (mZ.empty())
		{
			Pos.push_back(v1);
			o1=Pos.size();

			//add reference to this vertex in the mPos structure
			std::map< int,long unsigned int > mZ;
			mZ[RoundTo(v1.z)]=o1;
			mY[RoundTo(v1.y)]=mZ;
		}
		else
		{
			if (mZ[RoundTo(v1.z)] == 0)
			{
				Pos.push_back(v1);
				o1=Pos.size();

				//add reference to this vertex in the mPos structure
				mZ[RoundTo(v1.z)]=o1;
			}
			else
			{
				o1=mZ[RoundTo(v1.z)];
			}
		}
	}

	return o1;
}


int ActiveMesh::ImportSTL(const char *filename)
{
	Pos.clear();
	ID.clear();
	norm.clear();

	//a helper map to (efficiently) search for already stored vertices inside Pos
	std::map< int,std::map< int,std::map< int,long unsigned int > > > mPos;
	//         x             y             z  offset+1 in Pos

	//try to open the file
	std::ifstream file(filename);
	if (!file.is_open()) return 1;

	//read the "header" line
	char tmp[1024];
	file >> tmp; //dangerous...
	//check tmp for "solid" or complain
	if (tmp[0] != 's'
	 || tmp[1] != 'o'
	 || tmp[2] != 'l'
	 || tmp[3] != 'i'
	 || tmp[4] != 'd') { file.close(); return(2); }
	//read (and skip) the rest of the header line
	file.ignore(10240,'\n');

	//read facet by facet
	while (file >> tmp)
	{
		//check tmp for "facet" or "endsolid" or complain
		if (tmp[0] != 'f'
		 || tmp[1] != 'a'
		 || tmp[2] != 'c'
		 || tmp[3] != 'e'
		 || tmp[4] != 't')
		{
		//no new face starting, end of file then?
			if (tmp[0] != 'e'
			 || tmp[1] != 'n'
			 || tmp[2] != 'd'
			 || tmp[3] != 's'
			 || tmp[4] != 'o') { file.close(); return(3); }
			else break;
		}

		//read normal
		file >> tmp; //"normal" keyword
		float x,y,z;
		file >> x >> y >> z;
		Vector3F normal(x,y,z);

		//read triangle vertices
		file >> tmp;
		//check tmp for "outer" or complain
		if (tmp[0] != 'o'
		 || tmp[1] != 'u'
		 || tmp[2] != 't'
		 || tmp[3] != 'e'
		 || tmp[4] != 'r') { file.close(); return(4); }
		file >> tmp; //"loop" keyword

		file >> tmp; //"vertex" keyword
		file >> x >> y >> z;
		Vector3FC v1(x,y,z);

		file >> tmp;
		file >> x >> y >> z;
		Vector3FC v2(x,y,z);

		file >> tmp;
		file >> x >> y >> z;
		Vector3FC v3(x,y,z);

		file >> tmp; //"endloop" keyword
		file >> tmp; //"endfacet" keyword

		//add this triangle to the ActiveMesh data structures
		//we need to:
		// scale, round and use this for comparison against already
		// discovered vertices to avoid for having the same vertex saved twice
		long unsigned int o1,o2,o3;
		o1=Enlist(v1,Pos,mPos);
		o2=Enlist(v2,Pos,mPos);
		o3=Enlist(v3,Pos,mPos);
		// 
		// three offsets to the Pos array should be output
		// add them to the ID array
		ID.push_back(o1-1);
		ID.push_back(o2-1);
		ID.push_back(o3-1);
		// add normal to the norm array
		norm.push_back(normal);

		/*
		std::cout << "v1: " << v1.x << "," << v1.y << "," << v1.z << " -- o1=" << o1 << "\n";
		std::cout << "v2: " << v2.x << "," << v2.y << "," << v2.z << " -- o2=" << o2 << "\n";
		std::cout << "v3: " << v3.x << "," << v3.y << "," << v3.z << " -- o3=" << o3 << "\n";
		std::cout << "normal: " << normal.x << "," << normal.y << "," << normal.z << "\n\n";
		*/
	}
	
	file.close();
	return(0);
}

int ActiveMesh::ExportSTL(const char *filename)
{
	//try to open the file
	std::ofstream file(filename);
	if (!file.is_open()) return(1);

	file << "solid Vladimir Ulman - meshSurface testing app\n";

	for (unsigned int i=0; i < ID.size(); i+=3)
	{
		file << "facet normal " << norm[i/3].x << " " << norm[i/3].y << " " << norm[i/3].z << "\n";
		file << "outer loop\n";
		file << "vertex " << Pos[ID[i+0]].x << " " << Pos[ID[i+0]].y << " " << Pos[ID[i+0]].z << "\n";
		file << "vertex " << Pos[ID[i+1]].x << " " << Pos[ID[i+1]].y << " " << Pos[ID[i+1]].z << "\n";
		file << "vertex " << Pos[ID[i+2]].x << " " << Pos[ID[i+2]].y << " " << Pos[ID[i+2]].z << "\n";
		file << "endloop\nendfacet\n";
	}

	file.close();
	return(0);
}


int ActiveMesh::ImportVTK(const char *filename) //surface version
{
	Pos.clear();
	ID.clear();
	norm.clear();

	//try to open the file
	std::ifstream file(filename);
	if (!file.is_open()) return 1;

	//read the "header" line
	char tmp[1024];
	file >> tmp >> tmp; //dangerous...
	//check tmp for "vtk" or complain
	if (tmp[0] != 'v'
	 || tmp[1] != 't'
	 || tmp[2] != 'k') { file.close(); return(2); }
	//read (and skip) the rest of the header line
	file.ignore(10240,'\n');

	//ignore "vtk output"
	file.ignore(10240,'\n');

	//read "ASCII"
	file >> tmp;
	if (tmp[0] != 'A'
	 || tmp[1] != 'S'
	 || tmp[2] != 'C'
	 || tmp[3] != 'I'
	 || tmp[4] != 'I') { file.close(); return(3); }
	file.ignore(10240,'\n');

	//search until DATASET lines is found
	int counter=0;
	file >> tmp;
	file.ignore(10240,'\n');
	while (tmp[0] != 'D' || tmp[1] != 'A' || tmp[2] != 'T'
	    || tmp[3] != 'A' || tmp[4] != 'S' || tmp[5] != 'E')
	{
		file >> tmp;
		file.ignore(10240,'\n');
		++counter;
		if (counter == 10) { file.close(); return(35); }
	}

	//read points header
	int itemCount;
	file >> tmp >> itemCount;;
	if (tmp[0] != 'P'
	 || tmp[1] != 'O'
	 || tmp[2] != 'I'
	 || tmp[3] != 'N'
	 || tmp[4] != 'T'
	 || tmp[5] != 'S') { file.close(); return(4); }
	file.ignore(10240,'\n');

	//std::cout << "reading " << itemCount << " point coordinates\n";
	Pos.reserve(itemCount);

	//read all points...
	float x,y,z;
	while (itemCount > 0 && file >> x)
	{
		file >> y >> z;

		//... and save them
		Vector3FC v1(x,y,z);
		//v1*=100.f;
		Pos.push_back(v1);

		--itemCount;
	}
	//std::cout << "last coordinate was: " << x << "," << y << "," << z << "\n";

	//prepare "information about faces normals"
	Vector3F fictiveNormal(1.f,0.f,0.f);

	//read polyhedra header
	file >> tmp >> itemCount;
	if ((tmp[0] != 'P'
	  || tmp[1] != 'O'
	  || tmp[2] != 'L'
	  || tmp[3] != 'Y'
	  || tmp[4] != 'G'
	  || tmp[5] != 'O'
	  || tmp[6] != 'N'
	  || tmp[7] != 'S')
	         &&
	    (tmp[0] != 'C'
	  || tmp[1] != 'E'
	  || tmp[2] != 'L'
	  || tmp[3] != 'L'
	  || tmp[4] != 'S')) { file.close(); return(5); }
	file.ignore(10240,'\n');

	//std::cout << "reading " << itemCount << " triangles\n";
	ID.reserve(3*itemCount);
	norm.reserve(itemCount);

	//read all polyhedra vertices
	int ignore,v1,v2,v3;
	while (itemCount > 0 && file >> ignore && ignore == 3)
	{
		file >> v1 >> v2 >> v3;

		//save v1,v2,v3 (TODO: if not already saved...)
		//make triangles use CW winding order
		ID.push_back(v1);
		ID.push_back(v3);
		ID.push_back(v2);
		norm.push_back(fictiveNormal);

		--itemCount;
	}
	//std::cout << "last triangle was: " << v1 << "," << v2 << "," << v3 << "\n";

	file.close();
	return(0);
}


int ActiveMesh::ImportVTK_Volumetric(const char *filename,bool saveAlsoTetrahedra)
{
	Pos.clear();
	ID.clear();
	norm.clear();

	if (saveAlsoTetrahedra) VolID.clear();

	//try to open the file
	std::ifstream file(filename);
	if (!file.is_open()) return 1;

	//read the "header" line
	char tmp[1024];
	file >> tmp >> tmp; //dangerous...
	//check tmp for "vtk" or complain
	if (tmp[0] != 'v'
	 || tmp[1] != 't'
	 || tmp[2] != 'k') { file.close(); return(2); }
	//read (and skip) the rest of the header line
	file.ignore(10240,'\n');

	//ignore "vtk output"
	file.ignore(10240,'\n');

	//read "ASCII"
	file >> tmp;
	if (tmp[0] != 'A'
	 || tmp[1] != 'S'
	 || tmp[2] != 'C'
	 || tmp[3] != 'I'
	 || tmp[4] != 'I') { file.close(); return(3); }
	file.ignore(10240,'\n');

	//search until DATASET lines is found
	int counter=0;
	file >> tmp;
	file.ignore(10240,'\n');
	while (tmp[0] != 'D' || tmp[1] != 'A' || tmp[2] != 'T'
	    || tmp[3] != 'A' || tmp[4] != 'S' || tmp[5] != 'E')
	{
		file >> tmp;
		file.ignore(10240,'\n');
		++counter;
		if (counter == 10) { file.close(); return(35); }
	}

	//read points header
	int itemCount;
	file >> tmp >> itemCount;;
	if (tmp[0] != 'P'
	 || tmp[1] != 'O'
	 || tmp[2] != 'I'
	 || tmp[3] != 'N'
	 || tmp[4] != 'T'
	 || tmp[5] != 'S') { file.close(); return(4); }
	file.ignore(10240,'\n');

	//std::cout << "reading " << itemCount << " point coordinates\n";
	Pos.reserve(itemCount);

	//read all points...
	float x,y,z;
	while (itemCount > 0 && file >> x)
	{
		file >> y >> z;

		//... and save them
		Vector3FC v1(x,y,z);
		//v1*=100.f;
		Pos.push_back(v1);

		--itemCount;
	}
	//std::cout << "last coordinate was: " << x << "," << y << "," << z << "\n";

	//prepare "information about faces normals"
	Vector3F fictiveNormal(1.f,0.f,0.f);

	//read polyhedra header
	file >> tmp >> itemCount;
	if (tmp[0] != 'C'
	 || tmp[1] != 'E'
	 || tmp[2] != 'L'
	 || tmp[3] != 'L'
	 || tmp[4] != 'S') { file.close(); return(5); }
	file.ignore(10240,'\n');

	//std::cout << "reading " << itemCount << " polyhedra\n";
	ID.reserve(3*itemCount);
	norm.reserve(itemCount);

	//read all polyhedra vertices
	int ignore,v1,v2,v3,v4;
	while (itemCount > 0 && file >> ignore && ignore == 4)
	{
		file >> v1 >> v2 >> v3 >> v4;

		//save v1,v2,v3 (TODO: if not already saved...)
		ID.push_back(v1);
		ID.push_back(v2);
		ID.push_back(v3);
		norm.push_back(fictiveNormal);

		//save v1,v2,v4 (TODO: if not already saved...)
		ID.push_back(v1);
		ID.push_back(v2);
		ID.push_back(v4);
		norm.push_back(fictiveNormal);

		//save v1,v4,v3 (TODO: if not already saved...)
		ID.push_back(v1);
		ID.push_back(v4);
		ID.push_back(v3);
		norm.push_back(fictiveNormal);

		//save v4,v2,v3 (TODO: if not already saved...)
		ID.push_back(v4);
		ID.push_back(v2);
		ID.push_back(v3);
		norm.push_back(fictiveNormal);

		if (saveAlsoTetrahedra)
		{
			VolID.push_back(v1);
			VolID.push_back(v2);
			VolID.push_back(v3);
			VolID.push_back(v4);
		}

		--itemCount;
	}
	//std::cout << "last polyhedron was: " << v1 << "," << v2 << "," << v3 << "," << v4 << "\n";

	file.close();
	return(0);
}


int ActiveMesh::ImportVTK_Ftree(const char *filename,const int idx,
                                const float stretch,bool resetMesh)
{
	const int PointsOnRadiusPeriphery=10;
	const float radiusCorrection=stretch;

	//make sure the F-tree array is long enough to host the requested index
	while (Ftree.size() < idx+1) Ftree.push_back(mesh_t());
	mesh_t& filoMesh=Ftree[idx];

	if (resetMesh)
	{
		filoMesh.Pos.clear();
		filoMesh.ID.clear();
		filoMesh.norm.clear();
	}
	
	//local/temporary list of vertices of segments that make up the f-tree
	//later, we convert these to triangles and save these guys instead
	fPoints.clear();
	segFromPoint.clear();
	segToPoint.clear();
	segFromRadius.clear();

	//try to open the file
	std::ifstream file(filename);
	if (!file.is_open()) return 1;

	//read the "header" line
	char tmp[1024];
	file >> tmp >> tmp; //dangerous...
	//check tmp for "vtk" or complain
	if (tmp[0] != 'v'
	 || tmp[1] != 't'
	 || tmp[2] != 'k') { file.close(); return(2); }
	//read (and skip) the rest of the header line
	file.ignore(10240,'\n');

	//ignore "vtk output"
	file.ignore(10240,'\n');

	//read "ASCII"
	file >> tmp;
	if (tmp[0] != 'A'
	 || tmp[1] != 'S'
	 || tmp[2] != 'C'
	 || tmp[3] != 'I'
	 || tmp[4] != 'I') { file.close(); return(3); }
	file.ignore(10240,'\n');

	//search until DATASET lines is found
	int counter=0;
	file >> tmp;
	file.ignore(10240,'\n');
	while (tmp[0] != 'D' || tmp[1] != 'A' || tmp[2] != 'T'
	    || tmp[3] != 'A' || tmp[4] != 'S' || tmp[5] != 'E')
	{
		file >> tmp;
		file.ignore(10240,'\n');
		++counter;
		if (counter == 10) { file.close(); return(35); }
	}

	//read points header
	int itemCount;
	file >> tmp >> itemCount;;
	if (tmp[0] != 'P'
	 || tmp[1] != 'O'
	 || tmp[2] != 'I'
	 || tmp[3] != 'N'
	 || tmp[4] != 'T'
	 || tmp[5] != 'S') { file.close(); return(4); }
	file.ignore(10240,'\n');

	//std::cout << "reading " << itemCount << " point coordinates\n";
	fPoints.reserve(itemCount);

	//read all points...
	float x,y,z;
	while (itemCount > 0 && file >> x)
	{
		file >> y >> z;

		//... and save them
		Vector3FC v1(x,y,z);
		//v1*=100.f;
		fPoints.push_back(v1);

		--itemCount;
	}
	//std::cout << "last coordinate was: " << x << "," << y << "," << z << "\n";

	//read segments header
	file >> tmp >> itemCount;
	if (tmp[0] != 'C'
	 || tmp[1] != 'E'
	 || tmp[2] != 'L'
	 || tmp[3] != 'L'
	 || tmp[4] != 'S') { file.close(); return(5); }
	file.ignore(10240,'\n');

	//std::cout << "reading " << itemCount << " segments\n";
	segFromPoint.reserve(itemCount);
	  segToPoint.reserve(itemCount);
	segFromRadius.reserve(itemCount);

	//read all segments vertices
	int ignore,v1,v2;
	while (itemCount > 0 && file >> ignore && ignore == 2)
	{
		file >> v1 >> v2;

		segFromPoint.push_back(v1);
		  segToPoint.push_back(v2);

		--itemCount;
	}
	//std::cout << "last segment was: " << v1 << "," << v2 << "\n";

	//"ignore" cell types header, body 
	file >> tmp >> itemCount;
	if (tmp[0] != 'C'
	 || tmp[1] != 'E'
	 || tmp[2] != 'L'
	 || tmp[3] != 'L'
	 || tmp[4] != '_'
	 || tmp[5] != 'T'
	 || tmp[6] != 'Y') { file.close(); return(6); }
	file.ignore(10240,'\n');
	while (itemCount > 0)
	{
		file.ignore(10240,'\n');
		--itemCount;
	}

	//read radii, i.e. CELL_DATA header
	file >> tmp >> itemCount;
	if (tmp[0] != 'C'
	 || tmp[1] != 'E'
	 || tmp[2] != 'L'
	 || tmp[3] != 'L'
	 || tmp[4] != '_'
	 || tmp[5] != 'D'
	 || tmp[6] != 'A') { file.close(); return(7); }
	file.ignore(10240,'\n');
	file.ignore(10240,'\n'); //ignore SCALARS..
	file.ignore(10240,'\n'); //ignore LOOKUP_TABLE

	//read the radii
	v1=0;
	while (itemCount > 0 && file >> x)
	{
		segFromRadius.push_back(x*radiusCorrection);

		--itemCount;
		++v1;
	}
	file.close();

	//now widen segments and save triangles...

	//prepare "information about faces normals"
	Vector3F fictiveNormal(1.f,0.f,0.f);

	size_t firstRadiusPoint=filoMesh.Pos.size();
	PointsFirstOffset=firstRadiusPoint;

	//for all bending points except the last one (tip)
	for (unsigned int i=0; i < segFromPoint.size(); ++i)
	{
		//we need to construct rotation matrix that would
		//rotate points on a circle which lays in the XZ plane
		//(Y axis is normal to it then) such that the points
		//would lay in the plane to which this new Y axis
		//would be normal
		//
		//new Y axis 
		Vector3F nYaxis=fPoints[segToPoint[i]];
		if (i == 0) nYaxis-=fPoints[segFromPoint[i]];
		else
		{
			nYaxis-=fPoints[segFromPoint[i-1]];
			nYaxis/=2.f; //unnecessary scaling
		}

		Vector3F nZaxis(0.f,1.f,0.f); //in fact it is original Yaxis _for now_
		Vector3F nXaxis;
		//new X axis is perpendicular to the original and new Y axis
		Mul(nYaxis,nZaxis,nXaxis);

		//new Z axis is perpendicular to the new X and Y axes
		Mul(nYaxis,nXaxis,nZaxis);

		//normalize...
		nXaxis/=nXaxis.Len();
		nZaxis/=nZaxis.Len();

		//now render the points on the circle and project them into the scene
		for (int p=0; p < PointsOnRadiusPeriphery; ++p)
		{
			//the point in its original position
			float x=segFromRadius[i]*cosf(6.28f*float(p)/float(PointsOnRadiusPeriphery));
			float z=segFromRadius[i]*sinf(6.28f*float(p)/float(PointsOnRadiusPeriphery));

			//rotate
			Vector3FC V(x*nXaxis);
			V+=z*nZaxis;

			//shift to the centre and save
			V+=fPoints[segFromPoint[i]];
			filoMesh.Pos.push_back(V);
		}
	}

	//finally, add the tip point
	filoMesh.Pos.push_back(fPoints[segToPoint.back()]);

	//now create triangles for all segment strips
	//except the last one (that leads to the tip)
	for (unsigned int i=1; i < segFromPoint.size(); ++i)
	{
		int p=0;
		for (; p < PointsOnRadiusPeriphery-1; ++p)
		{
			filoMesh.ID.push_back(firstRadiusPoint+p);
			filoMesh.ID.push_back(firstRadiusPoint+p+1);
			filoMesh.ID.push_back(firstRadiusPoint+p+PointsOnRadiusPeriphery);
			filoMesh.norm.push_back(fictiveNormal);

			filoMesh.ID.push_back(firstRadiusPoint+p+PointsOnRadiusPeriphery+1);
			filoMesh.ID.push_back(firstRadiusPoint+p+PointsOnRadiusPeriphery);
			filoMesh.ID.push_back(firstRadiusPoint+p+1);
			filoMesh.norm.push_back(fictiveNormal);
		}

		filoMesh.ID.push_back(firstRadiusPoint+p);
		filoMesh.ID.push_back(firstRadiusPoint);
		filoMesh.ID.push_back(firstRadiusPoint+p+PointsOnRadiusPeriphery);
		filoMesh.norm.push_back(fictiveNormal);

		filoMesh.ID.push_back(firstRadiusPoint  +PointsOnRadiusPeriphery);
		filoMesh.ID.push_back(firstRadiusPoint+p+PointsOnRadiusPeriphery);
		filoMesh.ID.push_back(firstRadiusPoint);
		filoMesh.norm.push_back(fictiveNormal);

		firstRadiusPoint+=PointsOnRadiusPeriphery;
	}

	//the last segment...
	int p=0;
	for (; p < PointsOnRadiusPeriphery-1; ++p)
	{
		filoMesh.ID.push_back(firstRadiusPoint+p);
		filoMesh.ID.push_back(firstRadiusPoint+p+1);
		filoMesh.ID.push_back(firstRadiusPoint+PointsOnRadiusPeriphery);
		filoMesh.norm.push_back(fictiveNormal);
	}

	filoMesh.ID.push_back(firstRadiusPoint+p);
	filoMesh.ID.push_back(firstRadiusPoint);
	filoMesh.ID.push_back(firstRadiusPoint+PointsOnRadiusPeriphery);
	filoMesh.norm.push_back(fictiveNormal);

	//render the filopodium texture points along its skeleton
	//the amount of points is determined from the radius, from circle area -> square of radius
	for (size_t i=0; i < segFromPoint.size(); ++i)
	{
		//time-savers...
		const Vector3F& fP=fPoints[segFromPoint[i]];
		const Vector3F& tP=fPoints[segToPoint[i]];

		const float length=(tP-fP).Len(); //full segment has length = 0.005

		//stepping along the axis
		const int S=(int)ceil(5.f*length/0.005); //"fraction factor"
		std::cout << "S=" << S << ", length=" << length << "\n";
		for (int s=0; s < S; ++s)
		{
			//ratios "from" and "to"
			const float fS=float(S-s)/float(S);
			const float tS=float(s)/float(S);

			//target radius
			const float target_r=(i+1 < segFromPoint.size())? segFromRadius[i+1] : 0.f;

			//current radius
			float r=fS*segFromRadius[i] + tS*target_r;

			//current position
			Vector3F p =fS*fP;
			         p+=tS*tP;

			//modify coordinates exactly the same as mesh coordinates will be modified
			p.x*=180.f;
			p.y*=180.f;
			p.z*=250.f;
			p.x+=13.75f;
			p.y+=13.75f;
			p.z+=11.00f;

			//insert fl. dots
			r*=4000.f;

			std::cout << s << ": r=" << r << ", r*r=" << r*r << "\n";

			//float dotsCount=10.f*r*r;
			float dotsCount=100.f*r;
			int qM=(dotsCount > 1300.f)? 1300 : (int)dotsCount;

			for (int q=0; q < qM; ++q)
			{
				dots_filo.push_back(p);
				dots_filo.push_back(p+Vector3F(0.0f,0.0f,+0.1f));
				dots_filo.push_back(p+Vector3F(0.0f,0.0f,-0.1f));
			}
		}
	}

	return(0);
}


void ActiveMesh::RenderMask_body(i3d::Image3d<i3d::GRAY16>& mask,const bool showTriangles)
{
	//time savers: resolution
	const float xRes=mask.GetResolution().GetX();
	const float yRes=mask.GetResolution().GetY();
	const float zRes=mask.GetResolution().GetZ();

	//time savers: offset
	const float xOff=mask.GetOffset().x;
	const float yOff=mask.GetOffset().y;
	const float zOff=mask.GetOffset().z;

	//create "aside" empty image similar to the input-output one
	i3d::Image3d<i3d::GRAY16> tmpI;
	tmpI.CopyMetaData(mask);
	tmpI.GetVoxelData()=0;

	//over all triangles
	for (unsigned int i=0; i < ID.size()/3; ++i)
	//for (unsigned int i=0; i < 15; ++i)
	{
		const Vector3F& v1=Pos[ID[3*i+0]];
		const Vector3F& v2=Pos[ID[3*i+1]];
		const Vector3F& v3=Pos[ID[3*i+2]];

		//sweeping (and rendering) the triangle
		for (float c=0.f; c <= 1.0f; c += 0.1f)
		for (float b=0.f; b <= (1.0f-c); b += 0.1f)
		{
			float a=1.0f -b -c;

			Vector3F v=a*v1;
			v+=b*v2;
			v+=c*v3;
			
			//std::cout << "ID #" << i << ": v=(" << v.x << "," << v.y << "," << v.z << ")\n";

			//nearest neighbor pixel coordinate
			const int x=(int)roundf( (v.x-xOff) *xRes);
			const int y=(int)roundf( (v.y-yOff) *yRes);
			const int z=(int)roundf( (v.z-zOff) *zRes);

			short val=(showTriangles)? short(i%5 *30 +100) : 100;
			if (tmpI.Include(x,y,z)) tmpI.SetVoxel(x,y,z,val);
		}
	}

	//this floods cell exterior from the image corner
	i3d::Image3d<i3d::GRAY16> tmpMask(tmpI);
	i3d::Dilation(tmpMask,tmpI,i3d::nb3D_o18);
	i3d::FloodFill(tmpI,(i3d::GRAY16)50,0);

	//this removes the flooding while filling
	//everything else (and closing holes in this was)
	i3d::GRAY16* pI=tmpI.GetFirstVoxelAddr();
	i3d::GRAY16* pM=mask.GetFirstVoxelAddr();
	i3d::GRAY16* const pL=pM+mask.GetImageSize();
	while (pM != pL)
	{
		*pM=(*pI != 50)? std::max(*pI,i3d::GRAY16(100)) : *pM;
		++pI; ++pM;
	}
}

void ActiveMesh::RenderMask_filo(i3d::Image3d<i3d::GRAY16>& mask,const int idx,const bool showTriangles)
{
	//if index out of range, do nothing
	if (idx >= Ftree.size()) return;

	//time savers: resolution
	const float xRes=mask.GetResolution().GetX();
	const float yRes=mask.GetResolution().GetY();
	const float zRes=mask.GetResolution().GetZ();

	//time savers: offset
	const float xOff=mask.GetOffset().x;
	const float yOff=mask.GetOffset().y;
	const float zOff=mask.GetOffset().z;

	//create "aside" empty image similar to the input-output one
	i3d::Image3d<i3d::GRAY16> tmpI;
	tmpI.CopyMetaData(mask);
	tmpI.GetVoxelData()=0;

	//over all triangles of the given filopodia
	const mesh_t& filoMesh=Ftree[idx];
	for (unsigned int i=0; i < filoMesh.ID.size()/3; ++i)
	{
		const Vector3F& v1=filoMesh.Pos[filoMesh.ID[3*i+0]];
		const Vector3F& v2=filoMesh.Pos[filoMesh.ID[3*i+1]];
		const Vector3F& v3=filoMesh.Pos[filoMesh.ID[3*i+2]];

		//sweeping (and rendering) the triangle
		for (float c=0.f; c <= 1.0f; c += 0.1f)
		for (float b=0.f; b <= (1.0f-c); b += 0.1f)
		{
			float a=1.0f -b -c;

			Vector3F v=a*v1;
			v+=b*v2;
			v+=c*v3;
			
			//nearest neighbor pixel coordinate
			const int x=(int)roundf( (v.x-xOff) *xRes);
			const int y=(int)roundf( (v.y-yOff) *yRes);
			const int z=(int)roundf( (v.z-zOff) *zRes);

			short val=(showTriangles)? short(i%5 *30 +100) : 101+idx;
			if (tmpI.Include(x,y,z)) tmpI.SetVoxel(x,y,z,val);
		}
	}

	//this floods cell exterior from the image corner
	i3d::Image3d<i3d::GRAY16> tmpMask(tmpI);
	i3d::Dilation(tmpMask,tmpI,i3d::nb3D_o18);
	i3d::FloodFill(tmpI,(i3d::GRAY16)50,0);

	//this removes the flooding while filling
	//everything else (and closing holes in this was)
	i3d::GRAY16* pI=tmpI.GetFirstVoxelAddr();
	i3d::GRAY16* pM=mask.GetFirstVoxelAddr();
	i3d::GRAY16* const pL=pM+mask.GetImageSize();
	while (pM != pL)
	{
		*pM=(*pI != 50)? std::max(*pI,i3d::GRAY16(101+idx)) : *pM;
		++pI; ++pM;
	}
}


void ActiveMesh::RenderMaskB(i3d::Image3d<i3d::GRAY16>& mask)
{
	//time savers: resolution
	const float xRes=mask.GetResolution().GetX();
	const float yRes=mask.GetResolution().GetY();
	const float zRes=mask.GetResolution().GetZ();

	//time savers: offset
	const float xOff=mask.GetOffset().x;
	const float yOff=mask.GetOffset().y;
	const float zOff=mask.GetOffset().z;

	//over all triangles
	//for (unsigned int i=0; i < ID.size()/3; ++i)
	for (unsigned int i=0; i < 15; ++i)
	{
		const Vector3F& v1=Pos[ID[3*i+0]];
		const Vector3F& v2=Pos[ID[3*i+1]];
		const Vector3F& v3=Pos[ID[3*i+2]];

		//Vector3F e12=v2-v1; //ID=0
		//Vector3F e13=v3-v1; //ID=1
		//Vector3F e23=v3-v2; //ID=2
		Vector3F edges[3]={v2-v1,v3-v1,v3-v2};

		short order[3]={0,1,2};
		if (edges[1].LenQ() < edges[0].LenQ()) { order[0]=1; order[1]=0; }
		if (edges[2].LenQ() < edges[order[1]].LenQ())
		{
			order[2]=order[1];
			order[1]=2;

			if (edges[2].LenQ() < edges[order[0]].LenQ())
			{
				order[1]=order[0];
				order[0]=2;
			}
		}

		//point within a triangle will of the form: vv + b*vb + c*vc
		//vb is the shortest edge, vc is the longest edge
		//vv is their common vertex
		//vb,vc are oriented outward from this vertex
		Vector3F vv,vb,vc;
		if (order[0]==0 && order[2]==1)
		{
			//order[0] "points" at shortest edge
			vv=v1;
			vb=edges[0];
			vc=edges[1];
		}
		else
		if (order[0]==0 && order[2]==2)
		{
			vv=v2;
			vb=-edges[0];
			vc=edges[2];
		}
		else
		if (order[0]==1 && order[2]==0)
		{
			vv=v1;
			vb=edges[1];
			vc=edges[0];
		}
		else
		if (order[0]==1 && order[2]==2)
		{
			vv=v3;
			vb=-edges[1];
			vc=-edges[2];
		}
		else
		if (order[0]==2 && order[2]==0)
		{
			vv=v2;
			vb=edges[2];
			vc=-edges[0];
		}
		else
		if (order[0]==2 && order[2]==1)
		{
			vv=v3;
			vb=-edges[2];
			vc=-edges[1];
		}

		//optimal increment for the short and long edge, respectively
		float db=(vb.x*xRes > vb.y*yRes)? vb.x*xRes : vb.y*yRes;
		db=(vb.z*zRes > db)? vb.z*zRes : db;
		db=1.f/db;

		float dc=(vc.x*xRes > vc.y*yRes)? vc.x*xRes : vc.y*yRes;
		dc=(vc.z*zRes > dc)? vc.z*zRes : dc;
		dc=1.f/dc;

		std::cout << "\nID #" << i << ": v1=(" << v1.x << "," << v1.y << "," << v1.z << ")\n";
		std::cout << "ID #" << i << ": v2=(" << v2.x << "," << v2.y << "," << v2.z << ")\n";
		std::cout << "ID #" << i << ": v3=(" << v3.x << "," << v3.y << "," << v3.z << ")\n";
		
		//sweeping (and rendering) the triangle
		for (float c=0.f; c <= 1.0f; c += dc)
		for (float b=0.f; b <= (1.0f-c); b += db)
		{
			Vector3F v=vv;
			v+=b*vb;
			v+=c*vc;
			
			std::cout << "ID #" << i << ": v=(" << v.x << "," << v.y << "," << v.z << ")\n";

			//nearest neighbor pixel coordinate
			const int x=(int)roundf( (v.x-xOff) *xRes);
			const int y=(int)roundf( (v.y-yOff) *yRes);
			const int z=(int)roundf( (v.z-zOff) *zRes);

			if (mask.Include(x,y,z)) mask.SetVoxel(x,y,z,short(i));
		}
	}
}


void ActiveMesh::RenderOneTimeTexture(const i3d::Image3d<i3d::GRAY16>& mask,
                                      i3d::Image3d<i3d::GRAY16>& texture)
{
	i3d::Image3d<float> dt;
	i3d::GrayToFloat(mask,dt);

	i3d::EDM(dt,0,100.f,false);
#ifdef SAVE_INTERMEDIATE_IMAGES
	dt.SaveImage("1_DTAlone.ics");
#endif

	i3d::Image3d<float> perlinInner,perlinOutside;
	perlinInner.CopyMetaData(mask);
	DoPerlin3D(perlinInner,5.0,0.8*1.5,0.7*1.5,6);
#ifdef SAVE_INTERMEDIATE_IMAGES
	//perlinInner.SaveImage("2_PerlinAlone_Inner.ics");
#endif

	perlinOutside.CopyMetaData(mask);
	DoPerlin3D(perlinOutside,1.8,0.8*1.5,0.7*1.5,6);
#ifdef SAVE_INTERMEDIATE_IMAGES
	//perlinOutside.SaveImage("2_PerlinAlone_Outside.ics");
#endif

	i3d::Image3d<i3d::GRAY16> erroded;
	i3d::ErosionO(mask,erroded,1);

	//initial object intensity levels
	float* p=dt.GetFirstVoxelAddr();
	float* const pL=p+dt.GetImageSize();
	const float* pI=perlinInner.GetFirstVoxelAddr();
	const float* pO=perlinOutside.GetFirstVoxelAddr();
	const i3d::GRAY16* er=erroded.GetFirstVoxelAddr();
	while (p != pL)
	{
		//are we within the mask?
		if (*p > 0.f)
		{
			//close to the surface?
			if (*p < 0.3f || *er == 0) *p=2000.f + 5000.f*(*pO); //corona
			else                       *p=600.f  + 600.f*(*pI);  //inside
			if (*er == 0) *p=2000.f; //std::max(*p,2000.f);                //corona

			if (*p < 0.f) *p=0.f;
		}
		++p; ++pI; ++pO; ++er;
	}
#ifdef SAVE_INTERMEDIATE_IMAGES
	dt.SaveImage("3_texture.ics");
#endif

	perlinInner.DisposeData();
	perlinOutside.DisposeData();
	erroded.DisposeData();

	i3d::GaussIIR(dt,3.f,3.f,2.5f);
#ifdef SAVE_INTERMEDIATE_IMAGES
	dt.SaveImage("4_texture_filtered.ics");
#endif


	//downsample now:
	float factor[3]={1.f,1.f,0.125f};
	i3d::Image3d<float>* tmp=NULL;
	i3d::lanczos_resample(&dt,tmp,factor,2);
#ifdef SAVE_INTERMEDIATE_IMAGES
	tmp->SaveImage("5_texture_filtered_resampled.ics");
#endif
	dt=*tmp;
	delete tmp;


	p=dt.GetFirstVoxelAddr();
	for (int z=0; z < (signed)dt.GetSizeZ(); ++z)
	for (int y=0; y < (signed)dt.GetSizeY(); ++y)
	for (int x=0; x < (signed)dt.GetSizeX(); ++x, ++p)
	{
		//background signal:
		float distSq=((float)x-110.f)*((float)x-110.f) + ((float)y-110.f)*((float)y-110.f);
		*p+=150.f*expf(-0.5f * distSq / 2500.f);

		//uncertainty in the number of incoming photons
		const float noiseMean = sqrtf(*p), // from statistics: shot noise = sqrt(signal) 
				      noiseVar = noiseMean;  // for Poisson distribution E(X) = D(X)

		*p+=40.f*((float)GetRandomPoisson(noiseMean) - noiseVar);

		//constants are parameters of Andor iXon camera provided from vendor:
		//photon shot noise: dark current
		*p+=(float)GetRandomPoisson(0.06f);

		//read-out noise:
		//  variance up to 25.f (old camera on ILBIT)
		//  variance about 1.f (for the new camera on ILBIT)
		*p+=GetRandomGauss(480.f,20.f);
		//*p+=530.f;
	}
#ifdef SAVE_INTERMEDIATE_IMAGES
	dt.SaveImage("6_texture_filtered_resampled_finalized.ics");
#endif

	//obtain final GRAY16 image
	i3d::FloatToGrayNoWeight(dt,texture);
}


void ActiveMesh::CenterMesh(const Vector3F& newCentre)
{
	//calc geom. centre
	double x=0.,y=0.,z=0.;
	for (unsigned int i=0; i < Pos.size(); ++i)
	{
		x+=Pos[i].x;
		y+=Pos[i].y;
		z+=Pos[i].z;
	}
	x/=double(Pos.size());
	y/=double(Pos.size());
	z/=double(Pos.size());

	//std::cout << "mesh centre is: " << x << "," << y << "," << z << "\n";

	x-=newCentre.x;
	y-=newCentre.y;
	z-=newCentre.z;

	//shift the centre to point (0,0,0)
	for (unsigned int i=0; i < Pos.size(); ++i)
	{
		Pos[i].x-=float(x);
		Pos[i].y-=float(y);
		Pos[i].z-=float(z);
	}
	for (unsigned int i=0; i < fPoints.size(); ++i)
	{
		fPoints[i].x-=float(x);
		fPoints[i].y-=float(y);
		fPoints[i].z-=float(z);
	}
}


void ActiveMesh::ScaleMesh(const Vector3F& scale)
{
	//cell body
	for (unsigned int i=0; i < Pos.size(); ++i)
	{
		Pos[i].x*=scale.x;
		Pos[i].y*=scale.y;
		Pos[i].z*=scale.z;
	}

	//filopodia
	for (size_t f=0; f < Ftree.size(); ++f)
	{
		mesh_t& filoMesh=Ftree[f];
		for (unsigned int i=0; i < filoMesh.Pos.size(); ++i)
		{
			filoMesh.Pos[i].x*=scale.x;
			filoMesh.Pos[i].y*=scale.y;
			filoMesh.Pos[i].z*=scale.z;
		}
	}
}


void ActiveMesh::TranslateMesh(const Vector3F& shift)
{
	//cell body
	for (unsigned int i=0; i < Pos.size(); ++i)
	{
		Pos[i].x+=shift.x;
		Pos[i].y+=shift.y;
		Pos[i].z+=shift.z;
	}

	//filopodia
	for (size_t f=0; f < Ftree.size(); ++f)
	{
		mesh_t& filoMesh=Ftree[f];
		for (unsigned int i=0; i < filoMesh.Pos.size(); ++i)
		{
			filoMesh.Pos[i].x+=shift.x;
			filoMesh.Pos[i].y+=shift.y;
			filoMesh.Pos[i].z+=shift.z;
		}
	}
}


// ============== surface fitting ============== 
int ActiveMesh::CalcQuadricSurface_Taubin(const int vertexID,
                                          float (&coeffs)[10])
{
	//determine some reasonable number of nearest neighbors
	std::vector< std::vector<size_t> > neigsLeveled;
	ulm::getVertexNeighbours(*this,vertexID,2,neigsLeveled);

	//make it flat...
	std::vector<size_t> neigs;
	for (unsigned int l=0; l < neigsLeveled.size(); ++l)
		for (unsigned int i=0; i < neigsLeveled[l].size(); ++i)
			neigs.push_back(neigsLeveled[l][i]);
	neigsLeveled.clear();

	//V be the vector of [x,y,z] combinations, a counterpart to coeffs
	float V[10],V1[10],V2[10],V3[10];

	//M be the matrix holding initially sum of (square matrices) V*V'
	//N is similar, just a sum of three different Vs is used
	float M[100],N[100];
	for (int i=0; i < 100; ++i) M[i]=N[i]=0.f;

	//over all neighbors (including the centre vertex itself),
	//
	//this order garuantees that array V will be relevant for input
	//vertexID after the cycles are over -- will become handy later
	for (signed int n=(int)neigs.size()-1; n >= 0; --n)
	{
		//shortcut to the current point
		const Vector3FC& v=Pos[neigs[n]];

		//get Vs for the given point 'v'
		V[0]=1.f;       V1[0]=0.f;      V2[0]=0.f;      V3[0]=0.f;
		V[1]=v.x;       V1[1]=1.f;      V2[1]=0.f;      V3[1]=0.f;
		V[2]=v.y;       V1[2]=0.f;      V2[2]=1.f;      V3[2]=0.f;
		V[3]=v.z;       V1[3]=0.f;      V2[3]=0.f;      V3[3]=1.f;
		V[4]=v.x*v.y;   V1[4]=v.y;      V2[4]=v.x;      V3[4]=0.f;
		V[5]=v.x*v.z;   V1[5]=v.z;      V2[5]=0.f;      V3[5]=v.x;
		V[6]=v.y*v.z;   V1[6]=0.f;      V2[6]=v.z;      V3[6]=v.y;
		V[7]=v.x*v.x;   V1[7]=2.f*v.x;  V2[7]=0.f;      V3[7]=0.f;
		V[8]=v.y*v.y;   V1[8]=0.f;      V2[8]=2.f*v.y;  V3[8]=0.f;
		V[9]=v.z*v.z;   V1[9]=0.f;      V2[9]=0.f;      V3[9]=2.f*v.z;

		//construct V*V' and add it to M and N
		for (int j=0; j < 9; ++j)  //for column
		 for (int i=0; i < 9; ++i) //for row;  note the order optimal for Fortran
		 {
			//C order                 (row-major):  M_i,j -> M[i][j] -> &M +i*STRIDE +j
			//Lapack/Fortran order (column-major):  M_i,j -> M[i][j] -> &M +j*STRIDE +i
			//const int off=i*10 +j; //C
			const int off=j*9 +i; //Fortran

			M[off]+= V[j+1]* V[i+1];
			N[off]+=V1[j+1]*V1[i+1];
			N[off]+=V2[j+1]*V2[i+1];
			N[off]+=V3[j+1]*V3[i+1];
		 }
	}

	//now, solve the generalized eigenvector of the matrix pair (M,N):
	//MC = nNC
	//
	//M,N are (reduced) 9x9 matrices constructed above,
	//C is vector (infact, the coeff), n is scalar Lagrange multiplier
	//
	//if M,N were (full-size) 10x10 matrices, the N would be singular
	//as the 1st row would contain only zeros,
	//it is therefore reduced to 9x9 sacrifing the first row
	//
	//according to netlib (Lapack) docs, http://www.netlib.org/lapack/lug/node34.html
	//type 1, Az=lBz -- A=M, B=N, z=C
	//function: SSYGV

/*

	lapack_int itype=1;
	char jobz='V';
	char uplo='U';
	lapack_int n=9;
	float w[10];
	float work[512];
	lapack_int lwork=512;
	lapack_int info;
	LAPACK_ssygv(&itype,&jobz,&uplo,&n,M,&n,N,&n,w,work,&lwork,&info);

	std::cout << "vertices considered: " << neigs.size() << "\n";
	std::cout << "info=" << info << " (0 is OK)\n";
	std::cout << "work(1)=" << work[0] << " (should be below 512)\n";
	
	//if some error, report it to the caller
	if (info != 0) return info;

	//M is now matrix of eigenvectors
	//it should hold (according to Lapack docs):
	//Z^T N Z = I where Z is one eigenvector, I is identity matrix
	//
	//w holds eigenvalues in ascending order

	//our result c[1]...c[9] is the eigenvector
	//corresponding to the smallest non-negative eigenvalue, so the j-th eigenvector
	int j=0;
	while (j < 9 && w[j] < 0.f) ++j;
	//have we found some non-negative eigenvalue?
	if (j == 9) return(-9999);

	//also:
	//the last missing coefficient c[0] we will determine by submitting
	//the given input vertex to the algebraic expresion of the surface
	//(given with coeffs) and equating it to zero:
	coeffs[0]=0.f;
	for (int i=0; i < 9; ++i)
	{
		coeffs[i+1]=M[j*9 +i];          //copy eigenvector
		coeffs[0]-=coeffs[i+1]*V[i+1];  //determine c[0]
	}

	std::cout << "w(j)=" << w[j] << ", j=" << j << "\n";

*/

	return 0;
}


bool ActiveMesh::GetPointOnQuadricSurface(const float x,const float y,
                                          float &z1, float &z2,
                                          const float (&coeffs)[10])
{
	const float a=coeffs[9];
	const float b=coeffs[3] +coeffs[5]*x +coeffs[6]*y;
	const float c=coeffs[0] +coeffs[1]*x +coeffs[2]*y
	             +coeffs[4]*x*y +coeffs[7]*x*x +coeffs[8]*y*y;

	const float sqArg=b*b - 4*a*c;
	if (sqArg < 0.f) return false;
	if (a == 0.f) return false;

	z1=(-b + sqrtf(sqArg)) / (2.f*a);
	z2=(-b - sqrtf(sqArg)) / (2.f*a);

	return true;
}


float ActiveMesh::GetClosestPointOnQuadricSurface(Vector3F& point,
                                                  const float (&coeffs)[10])
{
	//backup original input coordinate
	const float x=point.x;
	const float y=point.y;
	const float z=point.z;
	float tmp1,tmp2;

	//list of possible coordinates
	std::vector<Vector3F> pointAdepts;

	//took a pair of coordinates, calculate the third one
	//and make it an adept...
	if (GetPointOnQuadricSurface(x,y,tmp1,tmp2,coeffs))
	{
		pointAdepts.push_back(Vector3F(x,y,tmp1));
		pointAdepts.push_back(Vector3F(x,y,tmp2));
	}
	if (GetPointOnQuadricSurface(x,z,tmp1,tmp2,coeffs))
	{
		pointAdepts.push_back(Vector3F(x,tmp1,z));
		pointAdepts.push_back(Vector3F(x,tmp2,z));
	}
	if (GetPointOnQuadricSurface(y,z,tmp1,tmp2,coeffs))
	{
		pointAdepts.push_back(Vector3F(tmp1,y,z));
		pointAdepts.push_back(Vector3F(tmp2,y,z));
	}

	//are we doomed?
	if (pointAdepts.size() == 0)
		return (-999999.f);

	//find the closest
	int closestIndex=ChooseClosestPoint(pointAdepts,point);
	
	//calc distance to it
	point-=pointAdepts[closestIndex];
	tmp1=point.Len();

	//adjust the input/output point
	point=pointAdepts[closestIndex];

	return (tmp1);
}


int ActiveMesh::ChooseClosestPoint(const std::vector<Vector3F>& points,
                                   const Vector3F& point)
{
	int minIndex=-1;
	float minSqDist=9999999999999.f;

	Vector3F p;
	for (unsigned int i=0; i < points.size(); ++i)
	{
		p=point;
		p-=points[i];
		if (p.LenQ() < minSqDist)
		{
			minIndex=i;
			minSqDist=p.LenQ();
		}
	}

	return minIndex;
}


void ActiveMesh::InitDots_body(const i3d::Image3d<i3d::GRAY16>& mask)
{
	i3d::Image3d<float> dt;
	i3d::GrayToFloat(mask,dt);

	i3d::EDM(dt,0,100.f,false);
#ifdef SAVE_INTERMEDIATE_IMAGES
	dt.SaveImage("1_DTAlone.ics");
#endif

	i3d::Image3d<float> perlinInner,perlinOutside;
	perlinInner.CopyMetaData(mask);
	DoPerlin3D(perlinInner,5.0,0.8*1.5,0.7*1.5,6);
#ifdef SAVE_INTERMEDIATE_IMAGES
	//perlinInner.SaveImage("2_PerlinAlone_Inner.ics");
#endif

	perlinOutside.CopyMetaData(mask);
	DoPerlin3D(perlinOutside,1.8,0.8*1.5,0.7*1.5,6);
#ifdef SAVE_INTERMEDIATE_IMAGES
	//perlinOutside.SaveImage("2_PerlinAlone_Outside.ics");
#endif

	i3d::Image3d<i3d::GRAY16> erroded;
	i3d::ErosionO(mask,erroded,1);

	//initial object intensity levels
	float* p=dt.GetFirstVoxelAddr();
	float* const pL=p+dt.GetImageSize();
	const float* pI=perlinInner.GetFirstVoxelAddr();
	const float* pO=perlinOutside.GetFirstVoxelAddr();
	const i3d::GRAY16* er=erroded.GetFirstVoxelAddr();
	while (p != pL)
	{
		//are we within the mask?
		if (*p > 0.f)
		{
			//close to the surface?
			if (*p < 0.3f || *er == 0) *p=2000.f + 5000.f*(*pO); //corona
			else                       *p=600.f  + 600.f*(*pI);  //inside
			if (*er == 0) *p=2000.f; //std::max(*p,2000.f);                //corona

			if (*p < 0.f) *p=0.f;
		}
		++p; ++pI; ++pO; ++er;
	}
#ifdef SAVE_INTERMEDIATE_IMAGES
	dt.SaveImage("3_texture.ics");
#endif

	perlinInner.DisposeData();
	perlinOutside.DisposeData();
	erroded.DisposeData();

	//now, read the "molecules"
	dots.clear();
	dots.reserve(1<<23);

	//time savers...
	const float xRes=mask.GetResolution().GetX();
	const float yRes=mask.GetResolution().GetY();
	const float zRes=mask.GetResolution().GetZ();
	const float xOff=mask.GetOffset().x;
	const float yOff=mask.GetOffset().y;
	const float zOff=mask.GetOffset().z;

	p=dt.GetFirstVoxelAddr();
	for (int z=0; z < (signed)dt.GetSizeZ(); ++z)
	for (int y=0; y < (signed)dt.GetSizeY(); ++y)
	for (int x=0; x < (signed)dt.GetSizeX(); ++x, ++p)
	for (int v=0; v < *p; v+=50)
	{
		//convert px coords into um
		const float X=(float)x/xRes + xOff;
		const float Y=(float)y/yRes + yOff;
		const float Z=(float)z/zRes + zOff;

		dots.push_back(Vector3F(X,Y,Z));
		if (dots.size() == dots.capacity())
		{
			std::cout << "reserving more at position: " << x << "," << y << "," << z << "\n";
			dots.reserve(dots.size()+(1<<20));
		}
	}
//#ifdef SAVE_INTERMEDIATE_IMAGES
	std::cout << "intiated " << dots.size() << " fl. molecules (capacity is for "
	          << dots.capacity() << ")\n";
//#endif
}

void ActiveMesh::InitDots_filo(const i3d::Image3d<i3d::GRAY16>& mask)
{
	//time savers...
	const float xRes=mask.GetResolution().GetX();
	const float yRes=mask.GetResolution().GetY();
	const float zRes=mask.GetResolution().GetZ();
	const float xOff=mask.GetOffset().x;
	const float yOff=mask.GetOffset().y;
	const float zOff=mask.GetOffset().z;

	const i3d::GRAY16* p=mask.GetFirstVoxelAddr();
	for (int z=0; z < (signed)mask.GetSizeZ(); ++z)
	for (int y=0; y < (signed)mask.GetSizeY(); ++y)
	for (int x=0; x < (signed)mask.GetSizeX(); ++x, ++p)
	if (*p > 100)
	{
		//convert px coords into um
		const float X=(float)x/xRes + xOff;
		const float Y=(float)y/yRes + yOff;
		const float Z=(float)z/zRes + zOff;

		for (int q=0; q < 80; ++q) dots.push_back(Vector3F(X,Y,Z));
	}
}


void ActiveMesh::BrownDots(const i3d::Image3d<i3d::GRAY16>& mask)
{
	//TODO REMOVE ME
	for (size_t i=0; i < dots.size(); ++i)
		dots[i].x+=1.0f;
}


template <class VT>
VT GetPixel(i3d::Image3d<VT> const &img,const float x,const float y,const float z)
{
	/*
	//nearest neighbor:
	int X=static_cast<int>(roundf(x));
	int Y=static_cast<int>(roundf(y));
	int Z=static_cast<int>(roundf(z));

	if (img.Include(X,Y,Z)) return(img.GetVoxel(X,Y,Z));
	else return(0);
	*/

	//nearest not-greater integer coordinate, "o" in the picture in docs
	//X,Y,Z will be coordinate of the voxel no. 2
	const int X=static_cast<int>(floorf(x));
	const int Y=static_cast<int>(floorf(y));
	const int Z=static_cast<int>(floorf(z));
	//now we can write only to pixels at [X or X+1,Y or Y+1,Z or Z+1]

	//quit if too far from the "left" borders of the image
	//as we wouldn't be able to draw into the image anyway
	if ((X < -1) || (Y < -1) || (Z < -1)) return (0);

	//residual fraction of the input coordinate
	const float Xfrac=x - static_cast<float>(X);
	const float Yfrac=y - static_cast<float>(Y);
	const float Zfrac=z - static_cast<float>(Z);

	//the weights
	float A=0.0f,B=0.0f,C=0.0f,D=0.0f; //for 2D

	//x axis:
	A=D=Xfrac;
	B=C=1.0f - Xfrac;

	//y axis:
	A*=1.0f - Yfrac;
	B*=1.0f - Yfrac;
	C*=Yfrac;
	D*=Yfrac;

	//z axis:
	float A_=A,B_=B,C_=C,D_=D;
	A*=1.0f - Zfrac;
	B*=1.0f - Zfrac;
	C*=1.0f - Zfrac;
	D*=1.0f - Zfrac;
	A_*=Zfrac;
	B_*=Zfrac;
	C_*=Zfrac;
	D_*=Zfrac;

	//portions of the value in a bit more organized form, w[z][y][x]
	const float w[2][2][2]={{{B ,A },{C ,D }},
				{{B_,A_},{C_,D_}}};

	//the return value
	float v=0;

	//reading from the input image,
	//for (int zi=0; zi < 2; ++zi) if (Z+zi < (signed)img.GetSizeZ()) { //shortcut for 2D cases to avoid some computations...
	for (int zi=0; zi < 2; ++zi)
	  for (int yi=0; yi < 2; ++yi)
	    for (int xi=0; xi < 2; ++xi)
		if (img.Include(X+xi,Y+yi,Z+zi)) {
			//if we got here then we can safely change coordinate types
			v+=(float)img.GetVoxel((size_t)X+xi,(size_t)Y+yi,(size_t)Z+zi) * w[zi][yi][xi];
		}
	//}

	return ( static_cast<VT>(v) );
}

void ActiveMesh::FFDots(const i3d::Image3d<i3d::GRAY16>& mask,
                        const FlowField<float> &FF)
{
	//TODO: tests: FF consistency, same size as mask?

	//time savers...
	const float xRes=mask.GetResolution().GetX();
	const float yRes=mask.GetResolution().GetY();
	const float zRes=mask.GetResolution().GetZ();
	const float xOff=mask.GetOffset().x;
	const float yOff=mask.GetOffset().y;
	const float zOff=mask.GetOffset().z;

	//apply FF on the this->dots (no boundary checking)
	for (size_t i=0; i < dots.size(); ++i)
	{
		//turn micron position into pixel one
		const float X=(dots[i].x -xOff) *xRes;
		const float Y=(dots[i].y -yOff) *yRes;
		const float Z=(dots[i].z -zOff) *zRes;

		//note: GetPixel() returns 0 in case we ask for value outside the image
		//TODO: check against mask
		dots[i].x += GetPixel(*FF.x, X,Y,Z);
		dots[i].y += GetPixel(*FF.y, X,Y,Z);
		dots[i].z += GetPixel(*FF.z, X,Y,Z);
	}
}


void ActiveMesh::RenderDots(const i3d::Image3d<i3d::GRAY16>& mask,
                            i3d::Image3d<i3d::GRAY16>& texture)
{
	texture.CopyMetaData(mask);
	texture.GetVoxelData()=0;

	//time savers...
	const float xRes=texture.GetResolution().GetX();
	const float yRes=texture.GetResolution().GetY();
	const float zRes=texture.GetResolution().GetZ();
	const float xOff=texture.GetOffset().x;
	const float yOff=texture.GetOffset().y;
	const float zOff=texture.GetOffset().z;

	//time-savers for boundary checking...
	const int maxX=(int)texture.GetSizeX()-1;
	const int maxY=(int)texture.GetSizeY()-1;
	const int maxZ=(int)texture.GetSizeZ()-1;

	//time-savers for accessing neigbors...
	const size_t xLine=texture.GetSizeX();
	const size_t Slice=texture.GetSizeY() *xLine;
	i3d::GRAY16* const T=texture.GetFirstVoxelAddr();

	//render the points into the texture image
	for (size_t i=0; i < dots.size(); ++i)
	{
		const int x=(int)roundf( (dots[i].x-xOff) *xRes);
		const int y=(int)roundf( (dots[i].y-yOff) *yRes);
		const int z=(int)roundf( (dots[i].z-zOff) *zRes);

		if ((x > 0) && (y > 0) && (z > 0)
		   && (x < maxX) && (y < maxY) && (z < maxZ)) T[z*Slice +y*xLine +x]+=i3d::GRAY16(50);
	}
	for (size_t i=0; i < dots_filo.size(); ++i)
	{
		const int x=(int)roundf( (dots_filo[i].x-xOff) *xRes);
		const int y=(int)roundf( (dots_filo[i].y-yOff) *yRes);
		const int z=(int)roundf( (dots_filo[i].z-zOff) *zRes);

		if ((x > 0) && (y > 0) && (z > 0)
		   && (x < maxX) && (y < maxY) && (z < maxZ)) T[z*Slice +y*xLine +x]+=i3d::GRAY16(50);
	}
}

void ActiveMesh::PhaseII(const i3d::Image3d<i3d::GRAY16>& texture,
	                      i3d::Image3d<float>& intermediate)
{
	i3d::GrayToFloat(texture,intermediate);

	//i3d::GaussIIR(intermediate,3.f,3.f,2.5f);
	i3d::GaussIIR(intermediate,2.f,2.f,2.f);
#ifdef SAVE_INTERMEDIATE_IMAGES
	intermediate.SaveImage("4_texture_filtered.ics");
#endif
}

void ActiveMesh::PhaseIII(i3d::Image3d<float>& intermediate,
	                       i3d::Image3d<i3d::GRAY16>& texture)
{
#ifdef SAVE_INTERMEDIATE_IMAGES
	intermediate.SaveImage("5_texture_filtered_resampled.ics");
#endif
	float* p=intermediate.GetFirstVoxelAddr();
	for (int z=0; z < (signed)intermediate.GetSizeZ(); ++z)
	for (int y=0; y < (signed)intermediate.GetSizeY(); ++y)
	for (int x=0; x < (signed)intermediate.GetSizeX(); ++x, ++p)
	{
		//background signal:
		float distSq=((float)x-110.f)*((float)x-110.f) + ((float)y-110.f)*((float)y-110.f);
		*p+=150.f*expf(-0.5f * distSq / 2500.f);

		//uncertainty in the number of incoming photons
		const float noiseMean = sqrtf(*p), // from statistics: shot noise = sqrt(signal) 
				      noiseVar = noiseMean;  // for Poisson distribution E(X) = D(X)

		*p+=40.f*((float)GetRandomPoisson(noiseMean) - noiseVar);

		//constants are parameters of Andor iXon camera provided from vendor:
		//photon shot noise: dark current
		*p+=(float)GetRandomPoisson(0.06f);

		//read-out noise:
		//  variance up to 25.f (old camera on ILBIT)
		//  variance about 1.f (for the new camera on ILBIT)
		*p+=GetRandomGauss(480.f,20.f);
		//*p+=530.f;
	}
#ifdef SAVE_INTERMEDIATE_IMAGES
	intermediate.SaveImage("6_texture_filtered_resampled_finalized.ics");
#endif

	//obtain final GRAY16 image
	i3d::FloatToGrayNoWeight(intermediate,texture);
}


void ActiveMesh::ConstructFF(FlowField<float> &FF)
{
	//erase the flow field
	//iterate:
	//  put displacement vectors
	//  smooth

	//tests: TODO
	//FF must be consistent
	//oldVolPos and newVolPos must be of the same length

	//erase
	FF.x->GetVoxelData()=0;
	FF.y->GetVoxelData()=0;
	FF.z->GetVoxelData()=0;

	//time savers...
	const float xRes=FF.x->GetResolution().GetX();
	const float yRes=FF.x->GetResolution().GetY();
	const float zRes=FF.x->GetResolution().GetZ();
	const float xOff=FF.x->GetOffset().x;
	const float yOff=FF.x->GetOffset().y;
	const float zOff=FF.x->GetOffset().z;

	//time-savers for boundary checking...
	const int maxX=(int)FF.x->GetSizeX()-1;
	const int maxY=(int)FF.x->GetSizeY()-1;
	const int maxZ=(int)FF.x->GetSizeZ()-1;

	//time-savers for accessing neigbors...
	const size_t xLine=FF.x->GetSizeX();
	const size_t Slice=FF.x->GetSizeY() *xLine;
	float* const ffx=FF.x->GetFirstVoxelAddr();
	float* const ffy=FF.y->GetFirstVoxelAddr();
	float* const ffz=FF.z->GetFirstVoxelAddr();

	//inject displacements
	for (size_t i=0; i < oldVolPos.size(); ++i)
	{
		const int x=(int)roundf( (oldVolPos[i].x-xOff) *xRes);
		const int y=(int)roundf( (oldVolPos[i].y-yOff) *yRes);
		const int z=(int)roundf( (oldVolPos[i].z-zOff) *zRes);

		const float dx=newVolPos[i].x - oldVolPos[i].x;
		const float dy=newVolPos[i].y - oldVolPos[i].y;
		const float dz=newVolPos[i].z - oldVolPos[i].z;

		if ((x > 0) && (y > 0) && (z > 0)
			&& (x < maxX) && (y < maxY) && (z < maxZ))
		{
			ffx[z*Slice +y*xLine +x]=dx;
			ffy[z*Slice +y*xLine +x]=dy;
			ffz[z*Slice +y*xLine +x]=dz;
		}
	}

	//smooth
	i3d::GaussIIR(*FF.x,15.0f);
	i3d::GaussIIR(*FF.y,15.0f);
	i3d::GaussIIR(*FF.z,15.0f);

	//multiply (to "correct" after normalized smoothing)
	FF.x->GetVoxelData()*=1240.f;
	FF.y->GetVoxelData()*=1240.f;
	FF.z->GetVoxelData()*=1240.f;
}


void ActiveMesh::ConstructFF_T(FlowField<float> &FF)
{
	//erase the flow field
	//add displacement vectors by "rendering" displacement tetrahedra
	//smooth

	//tests: TODO
	//FF must be consistent
	//oldVolPos and newVolPos must be of the same length
	//length of oldVolPos*4 and length of VolID must be the same

	//erase
	FF.x->GetVoxelData()=0;
	FF.y->GetVoxelData()=0;
	FF.z->GetVoxelData()=0;

	i3d::Image3d<i3d::GRAY16> imgCounts;
	imgCounts.CopyMetaData(*FF.x);
	imgCounts.GetVoxelData()=0;

	//time savers...
	const float xRes=FF.x->GetResolution().GetX();
	const float yRes=FF.x->GetResolution().GetY();
	const float zRes=FF.x->GetResolution().GetZ();
	const float xOff=FF.x->GetOffset().x;
	const float yOff=FF.x->GetOffset().y;
	const float zOff=FF.x->GetOffset().z;

	//time-savers for boundary checking...
	const int maxX=(int)FF.x->GetSizeX()-1;
	const int maxY=(int)FF.x->GetSizeY()-1;
	const int maxZ=(int)FF.x->GetSizeZ()-1;

	//time-savers for accessing neigbors...
	const size_t xLine=FF.x->GetSizeX();
	const size_t Slice=FF.x->GetSizeY() *xLine;
	float* const ffx=FF.x->GetFirstVoxelAddr();
	float* const ffy=FF.y->GetFirstVoxelAddr();
	float* const ffz=FF.z->GetFirstVoxelAddr();
	i3d::GRAY16* const ffC=imgCounts.GetFirstVoxelAddr();

	//over all tetrahedra
	for (size_t i=0; i < VolID.size(); i+=4)
	{
		//tetrahedron to drive positions in the FF
		//(positions tetrahedron)
		const Vector3F& v1=oldVolPos[VolID[i+0]];
		const Vector3F& v2=oldVolPos[VolID[i+1]];
		const Vector3F& v3=oldVolPos[VolID[i+2]];
		const Vector3F& v4=oldVolPos[VolID[i+3]];

		//now iterate over the tetrahedra:
		for (float d=0.f; d <= 1.0f; d += 0.04f)
		for (float c=0.f; c <= (1.0f-d); c += 0.04f)
		for (float b=0.f; b <= (1.0f-d-c); b += 0.04f)
		{
			float a=1.0f -b -c -d;

			//float-point coordinate:
			Vector3F tmp;
			tmp =a*v1;
			tmp+=b*v2;
			tmp+=c*v3;
			tmp+=d*v4;

			//pixel (integer) coordinate:
			const int x=(int)roundf( (tmp.x-xOff) *xRes);
			const int y=(int)roundf( (tmp.y-yOff) *yRes);
			const int z=(int)roundf( (tmp.z-zOff) *zRes);

			if ((x > 0) && (y > 0) && (z > 0)
				&& (x < maxX) && (y < maxY) && (z < maxZ))
			{
				//tetrahedron to drive values to place at these positions
				//(values tetrahedron)
				const Vector3F dv1=newVolPos[VolID[i+0]] - v1;
				const Vector3F dv2=newVolPos[VolID[i+1]] - v2;
				const Vector3F dv3=newVolPos[VolID[i+2]] - v3;
				const Vector3F dv4=newVolPos[VolID[i+3]] - v4;

				//value
				tmp =a*dv1;
				tmp+=b*dv2;
				tmp+=c*dv3;
				tmp+=d*dv4;

				ffx[z*Slice +y*xLine +x]+=tmp.x;
				ffy[z*Slice +y*xLine +x]+=tmp.y;
				ffz[z*Slice +y*xLine +x]+=tmp.z;
				ffC[z*Slice +y*xLine +x]+=1;
			}
		}
	}

	//finish the averaging of FF
	for (size_t i=0; i < imgCounts.GetImageSize(); ++i)
	if (*(ffC+i))
	{
		*(ffx+i)/=float(*(ffC+i));
		*(ffy+i)/=float(*(ffC+i));
		*(ffz+i)/=float(*(ffC+i));
	}
	//imgCounts.SaveImage("counts.ics"); //TODO REMOVE

	//a bit more of fine-tunning:
	// make 2 rounds of 1px dilations into a copy image
	// extract the added 2px wide shell
	// widen the shell into another image with 2 rounds of 1px dilations
	// smooth sigma=1px this wider shell
	// mask/narrow the smoothed shell according to the previous shell
	// and combine with the original FF

	i3d::Image3d<i3d::GRAY16> tmp,shell,shellSmooth;
	i3d::Image3d<float> tmpF;
	tmpF=*FF.x;
	tmpF.GetVoxelData()*=1000.f;
	//tmpF.GetVoxelData()+=32768.f;
	tmpF.GetVoxelData()+=30000.f;
	i3d::FloatToGrayNoWeight(tmpF,shell);
	tmpF.SaveImage("shell0_F.ics");
	shell.SaveImage("shell0.ics");

	// make 2 rounds of 1px dilations into a copy image
	i3d::Dilation(shell,tmp,i3d::nb3D_o18);
	i3d::Dilation(tmp,shell,i3d::nb3D_o18);
	shell.SaveImage("shell1.ics");

	// extract the added 2px wide shell
	i3d::Dilation(shell,tmp,i3d::nb3D_o18);
	i3d::Dilation(tmp,shell,i3d::nb3D_o18);
	shell.SaveImage("shell2.ics");

	GrayToFloat(shell,tmpF);
	tmpF.SaveImage("shell2_F.ics");
	tmpF.GetVoxelData()-=30000.f;
	tmpF.GetVoxelData()/=1000.f;
	tmpF.SaveImage("shell2_normalValues_F.ics");

	// widen the shell into another image with 2 rounds of 1px dilations
	// smooth sigma=1px this wider shell
	// mask/narrow the smoothed shell according to the previous shell
	// and combine with the original FF


}