#include "filter_sdfgpu.h"
#include <common/GLExtensionsManager.h>

#include <vcg/complex/complex.h>
#include <vcg/complex/algorithms/intersection.h>
#include <vcg/space/index/grid_static_ptr.h>
#include <vcg/space/index/spatial_hashing.h>
#include <wrap/qt/to_string.h>
#include <vcg/math/gen_normal.h>
#include <wrap/qt/checkGLError.h>
#include <stdio.h>
#include <assert.h>
using namespace std;
using namespace vcg;

#define SDF_MAX_TEXTURE_SIZE 1024
#define PI 3.14159265358979323846264;
#define USEVBO_BY_DEFAULT true
#define PIXEL_COUNT_THRESHOLD 100

SdfGpuPlugin::SdfGpuPlugin()
	: mPeelingTextureSize(256),
	  mTempDepthComplexity(0),
	  mDepthComplexity(0),
	  mDepthComplexityWarning(false)
{
	typeList = {
		SDF_SDF,
		SDF_DEPTH_COMPLEXITY,
		SDF_OBSCURANCE
	};

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

QString SdfGpuPlugin::pluginName() const
{
	return "FilterSDFGPU";
}

RichParameterList SdfGpuPlugin::initParameterList(const QAction *action, const MeshModel &/*m*/)
{
	RichParameterList par;
	QStringList onPrimitive; onPrimitive.push_back("On vertices"); onPrimitive.push_back("On Faces");
	par.addParam( RichEnum("onPrimitive", 0, onPrimitive, "Metric:",
						   "Choose whether to trace rays from faces or from vertices. " ));
	par.addParam(  RichInt("numberRays",128, "Number of rays: ",
						   "The number of rays that will be casted around "
        "the normals."));
	par.addParam(RichInt("DepthTextureSize", 512, "Depth texture size",
						 "Size of the depth texture for depth peeling. Higher resolutions provide better sampling of the mesh, with a small performance penalty."));
	par.addParam(RichInt("peelingIteration", 10, "Peeling Iteration",
						 "Number of depth peeling iteration. Actually is the maximum number of layers that a ray can hit while traversing the mesh. "
        "For example, in the case of a sphere, you should specify 2 in this parameter. For a torus, specify 4. "
        "<b>For more complex geometry you should run the depth complexity filter to know the exact value</b>."));
	par.addParam(RichFloat("peelingTolerance", 0.0000001f, "Peeling Tolerance",
						   "Depth tolerance used during depth peeling. This is the threshold used to differentiate layers between each others."
        "Two elements whose distance is below this value will be considered as belonging to the same layer."));

	if(ID(action) != SDF_DEPTH_COMPLEXITY)
		par.addParam(RichFloat("coneAngle",120,"Cone amplitude", "Cone amplitude around normals in degrees. Rays are traced within this cone."));



	switch(ID(action))
	{
	case SDF_OBSCURANCE:
		par.addParam(RichFloat("obscuranceExponent", 0.1f, "Obscurance Exponent",
							   "This parameter controls the spatial decay term in the obscurance formula. "
            "The greater the exponent, the greater the influence of distance; that is: "
            "even if a ray is blocked by an occluder its contribution to the obscurance term is non zero, but proportional to this parameter. "
            "It turs out that if you choose a value of zero, you get the standard ambient occlusion term. "
            "<b>(In this case, only a value of two, in the peeling iteration parameter, has a sense)</b>"));
		break;

	default:
	{
		break;
	}
	}

	if(ID(action) == SDF_SDF)
	{
		par.addParam(RichBool("removeFalse",true,"Remove false intersections","For each"
            "ray we check the normal at the point of intersection,"
            "and ignore intersections where the normal at the intersection"
            "points is in the same direction as the point-of-origin"
            "(the same direction is defined as an angle difference less"
            "than 90) "));

		par.addParam(RichBool("removeOutliers",false,"Remove outliers","The outliers removal is made on the fly with a supersampling of the depth buffer. "
            "For each ray that we trace, we take multiple depth values near the point of intersection and we output only the median of these values. "
            "Some mesh can benefit from this additional calculation. "));
	}
	return par;
}

QString SdfGpuPlugin::filterName(ActionIDType filterId) const
{
	switch (filterId) {
	case SDF_SDF: return QString("Shape Diameter Function");
	case SDF_DEPTH_COMPLEXITY: return QString("Depth complexity");
	case SDF_OBSCURANCE: return QString("Volumetric obscurance");

	default: assert(0); return QString();
	}
}

QString SdfGpuPlugin::pythonFilterName(ActionIDType f) const
{
	switch (f) {
	case SDF_SDF: return QString("compute_scalar_by_shape_diameter_function_per_vertex");
	case SDF_DEPTH_COMPLEXITY: return QString("get_depth_complexity");
	case SDF_OBSCURANCE: return QString("compute_scalar_by_volumetric_obscurance");

	default: assert(0); return QString();
	}
}

QString SdfGpuPlugin::filterInfo(ActionIDType filterId) const
{
	switch(filterId)
	{
	case SDF_SDF                   :  return QString("Calculate the SDF (<b>shape diameter function</b>) on the mesh, you can visualize the result colorizing the mesh. "
                                          "The SDF is a scalar function on the mesh surface and represents the neighborhood diameter of the object at each point. "
                                          "Given a point on the mesh surface,"
                                          "several rays are sent inside a cone, centered around the point's inward-normal, to the other side of the mesh. The result is a weighted sum of all rays lengths. "
                                          "For further details, see the reference paper:<br>"
                                          "<b>Shapira Shamir Cohen-Or,<br>"
                                          "Consistent Mesh Partitioning and Skeletonisation using the shaper diamter function, Visual Comput. J. (2008)</b> ");
	case SDF_DEPTH_COMPLEXITY      :  return QString("Calculate the depth complexity of the mesh, that is: the maximum number of layers that a ray can hit while traversing the mesh. To have a correct value, you should specify and high value in the peeling iteration parameter. "
                                          "You can read the result in the MeshLab log window. <b>If warnings are not present, you have the exact value, otherwise try increasing the peeling iteration parameter. After having calculated the correct value,"
                                          "you can ignore further warnings that you may get using that value.</b>. ");
	case SDF_OBSCURANCE            :  return QString("Calculates obscurance coefficients for the mesh. Obscurance is introduced to avoid the "
                                          "disadvantages of both classical ambient term and ambient occlusion. "
                                          "In ambient occlusion, totally occluded parts of the mesh are black. "
                                          "Instead obscurance, despite still based on a perfectly diffuse light coming "
                                          "from everywhere, accounts for multiple bounces of indirect illumination by means "
                                          "of a function of both the openness of a point and the distance to his occluder (if any). "
                                          "Obscurance is inversely proportional to the number of ray casted from the point "
                                          "that hit an occluder and proportional to the distance a ray travels before hitting the occluder. "
                                          "You can control how much the distance factor influences the final result with the obscurance exponenent (see help below). "
                                          "Obscurance is a value in the range [0,1]. For further details see the reference paper:<br>"
                                          "<b>Iones Krupkin Sbert Zhukov <br> "
                                          "Fast, Realistic Lighting for Video Games <br>"
                                          "IEEECG&A 2003</b> ");

	default : assert(0);
	}

	return QString("");
}

std::map<std::string, QVariant> SdfGpuPlugin::applyFilter(
		const QAction* action,
		const RichParameterList & pars,
		MeshDocument &md,
		unsigned int& /*postConditionMask*/,
		vcg::CallBackPos *cb)
{
	if (glContext == nullptr){
		throw MLException("Fatal error: glContext not initialized");
	}
	MeshModel* mm = md.mm();

	//RETRIEVE PARAMETERS
	mOnPrimitive  = (ONPRIMITIVE) pars.getEnum("onPrimitive");
	// assert( mOnPrimitive==ON_VERTICES && "Face mode not supported yet" );
	unsigned int numViews     = pars.getInt("numberRays");
	int          peel         = pars.getInt("peelingIteration");
	mTolerance                = pars.getFloat("peelingTolerance");
	mPeelingTextureSize       = pars.getInt("DepthTextureSize");

	if(ID(action) != SDF_DEPTH_COMPLEXITY)
		mMinCos                 = vcg::math::Cos(math::ToRad(pars.getFloat("coneAngle")/2.0));

	std::vector<Point3f> coneDirVec;

	if(ID(action) == SDF_OBSCURANCE)
		mTau = pars.getFloat("obscuranceExponent");
	else if(ID(action)==SDF_SDF)
	{
		mRemoveFalse     = pars.getBool("removeFalse");
		mRemoveOutliers  = pars.getBool("removeOutliers");
	}
	//MESH CLEAN UP
	setupMesh( md, mOnPrimitive );

	//glContext->makeCurrent();
	//GL INIT
	if(!initGL(*mm))
		throw MLException("Failed GL initialization.");

	//
	if(mOnPrimitive==ON_VERTICES)
		vertexDataToTexture(*mm);
	else
		faceDataToTexture(*mm);

	//Uniform sampling of directions over a sphere
	std::vector<Point3f> unifDirVec;
	GenNormal<float>::Fibonacci(numViews,unifDirVec);

	log(GLLogStream::SYSTEM, "Number of rays: %i ", unifDirVec.size() );
	log(GLLogStream::SYSTEM, "Number of rays for GPU outliers removal: %i ", coneDirVec.size() );

	coneDirVec.clear();

	vector<int>  mDepthDistrib(peel,0);
	//Do the actual calculation of sdf or obscurance for each ray
	unsigned int tracedRays = 0;
	for(vector<vcg::Point3f>::iterator vi = unifDirVec.begin(); vi != unifDirVec.end(); vi++)
	{
		(*vi).Normalize();
		TraceRay(action, peel, (*vi), md.mm());
		cb(100*((float)tracedRays/(float)unifDirVec.size()), "Tracing rays...");

		glContext->makeCurrent();

		++tracedRays;
		mDepthComplexity = std::max(mDepthComplexity, mTempDepthComplexity);

		mDepthDistrib[mTempDepthComplexity]++;
		mTempDepthComplexity = 0;
	}

	//read back the result texture and store result in the mesh
	if(ID(action) == SDF_OBSCURANCE)
	{
		if(mOnPrimitive == ON_VERTICES)
			applyObscurancePerVertex(*mm,unifDirVec.size());
		else
			applyObscurancePerFace(*mm,unifDirVec.size());
	}
	else if(ID(action) == SDF_SDF)
	{
		if(mOnPrimitive == ON_VERTICES)
			applySdfPerVertex(*mm);
		else
			applySdfPerFace(*mm);

	}



	log(GLLogStream::SYSTEM, "Mesh depth complexity %i (The accuracy of the result depends on the value you provided for the max number of peeling iterations, \n if you get warnings try increasing"
        " the peeling iteration parameter)\n", mDepthComplexity );

	//Depth complexity distribution log. Useful to know which is the probability to find a number of layers looking at the mesh or scene.
	log(GLLogStream::SYSTEM, "Depth complexity             NumberOfViews\n", mDepthComplexity );
	for(int j = 0; j < peel; j++)
	{
		log(GLLogStream::SYSTEM, "   %i                             %i\n", j, mDepthDistrib[j] );
	}

	//Clean & Exit
	releaseGL(*mm);
	//glContext->doneCurrent();
	mDepthComplexity = 0;

	return std::map<std::string, QVariant>();
}

bool SdfGpuPlugin::initGL(MeshModel& mm)
{
	const unsigned int numVertices = mm.cm.vn;
	const unsigned int numFaces    = mm.cm.fn;

	unsigned int numElems;
	if(mOnPrimitive==ON_VERTICES)
		numElems = numVertices;
	else
		numElems = numFaces;

	this->glContext->makeCurrent();

	//SET DEFAULT OPENGL STUFF
	glEnable( GL_DEPTH_TEST );
	glEnable( GL_TEXTURE_2D );
	glDisable(GL_BLEND);
	glDisable(GL_LIGHTING);
	glDisable(GL_ALPHA_TEST);
	glEnable(GL_NORMALIZE);
	glDisable(GL_COLOR_MATERIAL);
	glClearColor(0,0,0,0);
	glClearDepth(1.0);

	if (!GLExtensionsManager::initializeGLextensions_notThrowing())
	{
		log(GLLogStream::SYSTEM, "Error initializing OpenGL extensions.");
		return false;
	}

	//CHECK HARDWARE CAPABILITIES
	if (!glewIsSupported("GL_ARB_vertex_shader GL_ARB_fragment_shader"))
	{
		if (!glewIsSupported("GL_EXT_vertex_shader GL_EXT_fragment_shader"))
		{
			log(GLLogStream::SYSTEM, "Your hardware doesn't support Shaders, which are required for hw occlusion");
			return false;
		}
	}
	if ( !glewIsSupported("GL_EXT_framebuffer_object") )
	{
		log(GLLogStream::SYSTEM, "Your hardware doesn't support FBOs, which are required for hw occlusion");
		return false;
	}

	if ( glewIsSupported("GL_ARB_texture_float") )
	{
		if ( !glewIsSupported("GL_EXT_gpu_shader4") )   //Only DX10-grade cards support FP32 blending
		{
			log(GLLogStream::SYSTEM,"Your hardware can't do FP32 blending, and currently the FP16 version is not yet implemented.");
			return false;
		}
	}
	else
	{
		log(GLLogStream::SYSTEM,"Your hardware doesn't support floating point textures, which are required for hw occlusion");
		return false;
	}

	GLint maxColorAttachments;
	glGetIntegerv(GL_MAX_COLOR_ATTACHMENTS_EXT, &maxColorAttachments);



	//INIT FBOs AND TEXs
	for(int i = 0; i < 3; i++)
	{
		mFboArray[i] = new FramebufferObject();
	}

	mFboResult = new FramebufferObject();

	unsigned int maxTexSize;
	glGetIntegerv(GL_MAX_TEXTURE_SIZE, reinterpret_cast<GLint*>(&maxTexSize) );
	log(GLLogStream::SYSTEM, "QUERY HARDWARE FOR: MAX TEX SIZE: %i ", maxTexSize );

	//CHECK MODEL SIZE
	if ((maxTexSize*maxTexSize) < numElems)
	{
		log(GLLogStream::SYSTEM, "That's a really huge model, I can't handle it in hardware, sorry..");
		return false;
	}

	for( mResTextureDim = 16; mResTextureDim*mResTextureDim < numElems; mResTextureDim *= 2 ){}

	mNumberOfTexRows = ceil( ((float)numElems) / ((float)mResTextureDim));

	log(GLLogStream::SYSTEM, "Mesh has %i vertices\n", numVertices );
	log(GLLogStream::SYSTEM, "Mesh has %i faces\n", numFaces);
	log(GLLogStream::SYSTEM, "Number of tex rows used %i",mNumberOfTexRows);
	log(GLLogStream::SYSTEM, "Result texture is %i X %i = %i", mResTextureDim, mResTextureDim, mResTextureDim*mResTextureDim);

	mVertexCoordsTexture  = new FloatTexture2D( TextureFormat( GL_TEXTURE_2D, mResTextureDim, mResTextureDim, GL_RGBA32F_ARB, GL_RGBA, GL_FLOAT ), TextureParams( GL_NEAREST, GL_NEAREST ) );
	mVertexNormalsTexture = new FloatTexture2D( TextureFormat( GL_TEXTURE_2D, mResTextureDim, mResTextureDim, GL_RGBA32F_ARB, GL_RGBA, GL_FLOAT ), TextureParams( GL_NEAREST, GL_NEAREST ) );

	mResultTexture      = new FloatTexture2D( TextureFormat( GL_TEXTURE_2D, mResTextureDim, mResTextureDim, GL_RGBA32F_ARB, GL_RGBA, GL_FLOAT ), TextureParams( GL_NEAREST, GL_NEAREST ) );
	mDirsResultTexture  = new FloatTexture2D( TextureFormat( GL_TEXTURE_2D, mResTextureDim, mResTextureDim, GL_RGBA32F_ARB, GL_RGBA, GL_FLOAT ), TextureParams( GL_NEAREST, GL_NEAREST ) );


	mFboResult->attachTexture( mResultTexture->format().target(), mResultTexture->id(), GL_COLOR_ATTACHMENT0_EXT );
	mFboResult->attachTexture( mDirsResultTexture->format().target(), mDirsResultTexture->id(), GL_COLOR_ATTACHMENT1_EXT );

	//clear first time
	mFboResult->bind();
	GLenum buffers[] = { GL_COLOR_ATTACHMENT0_EXT, GL_COLOR_ATTACHMENT1_EXT };
	glDrawBuffers(2, buffers);
	glClear(GL_COLOR_BUFFER_BIT);
	mFboResult->unbind();

	//We use 3 FBOs to avoid z-fighting in sdf and obscurance calculation, see TraceRays function for details
	for(int i = 0; i < 3; i++)
	{
		mDepthTextureArray[i]  = new FloatTexture2D( TextureFormat( GL_TEXTURE_2D, mPeelingTextureSize, mPeelingTextureSize, GL_DEPTH_COMPONENT24, GL_DEPTH_COMPONENT, GL_FLOAT ),
													 TextureParams( GL_NEAREST, GL_NEAREST, GL_CLAMP_TO_EDGE, GL_CLAMP_TO_EDGE, GL_CLAMP_TO_EDGE ) );

		mColorTextureArray[i] = new FloatTexture2D( TextureFormat( GL_TEXTURE_2D, mPeelingTextureSize, mPeelingTextureSize, GL_RGBA32F_ARB, GL_RGBA, GL_FLOAT ), TextureParams( GL_NEAREST, GL_NEAREST ) );

		mFboArray[i]->attachTexture( mColorTextureArray[i]->format().target(), mColorTextureArray[i]->id(), GL_COLOR_ATTACHMENT0_EXT );
		mFboArray[i]->attachTexture( mDepthTextureArray[i]->format().target(), mDepthTextureArray[i]->id(), GL_DEPTH_ATTACHMENT  );

	}

	//Depth peeling shader used for both sdf and obscurance
	mDeepthPeelingProgram = new GPUProgram(":/SdfGpu/shaders/vertexShaderDepthPeeling.vs",":/SdfGpu/shaders/shaderDepthPeeling.fs","");
	mDeepthPeelingProgram->enable();
	mDeepthPeelingProgram->addUniform("textureLastDepth");
	mDeepthPeelingProgram->addUniform("tolerance");
	mDeepthPeelingProgram->addUniform("oneOverBufSize");
	mDeepthPeelingProgram->disable();

	//Sdf shader
	mSDFProgram = new GPUProgram("",":/SdfGpu/shaders/calculateSdf.frag","");
	mSDFProgram->enable();
	mSDFProgram->addUniform("vTexture");
	mSDFProgram->addUniform("nTexture");
	mSDFProgram->addUniform("depthTextureFront");
	mSDFProgram->addUniform("depthTextureBack");
	mSDFProgram->addUniform("viewDirection");
	mSDFProgram->addUniform("mvprMatrix");
	mSDFProgram->addUniform("viewpSize");
	mSDFProgram->addUniform("texSize");
	mSDFProgram->addUniform("minCos");
	mSDFProgram->addUniform("depthTexturePrevBack");
	mSDFProgram->addUniform("firstRendering");
	mSDFProgram->addUniform("mvprMatrixINV");
	mSDFProgram->addUniform("removeFalse");
	mSDFProgram->addUniform("coneRays");
	mSDFProgram->addUniform("removeOutliers");
	mSDFProgram->addUniform("normalTextureBack");
	mSDFProgram->disable();

	//Obscurance shader
	mObscuranceProgram = new GPUProgram("",":/SdfGpu/shaders/obscurances.frag","");
	mObscuranceProgram->enable();
	mObscuranceProgram->addUniform("vTexture");
	mObscuranceProgram->addUniform("nTexture");
	mObscuranceProgram->addUniform("depthTextureFront");
	mObscuranceProgram->addUniform("depthTextureBack");
	mObscuranceProgram->addUniform("depthTextureNextBack");
	mObscuranceProgram->addUniform("viewDirection");
	mObscuranceProgram->addUniform("mvprMatrix");
	mObscuranceProgram->addUniform("viewpSize");
	mObscuranceProgram->addUniform("texSize");
	mObscuranceProgram->addUniform("tau");
	mObscuranceProgram->addUniform("firstRendering");
	mObscuranceProgram->addUniform("maxDist");      //mesh BB diagonal
	mObscuranceProgram->disable();

	assert(mFboResult->isValid());
	assert(mFboArray[0]->isValid());
	assert(mFboArray[1]->isValid());
	assert(mFboArray[2]->isValid());


	glGenQueriesARB( 1, &mOcclusionQuery );

	checkGLError::debugInfo("GL Init failed");

	return true;
}

void SdfGpuPlugin::faceDataToTexture(MeshModel &m)
{
	unsigned int texSize = mResTextureDim*mResTextureDim*4;

	GLfloat *facePosition= new GLfloat[texSize];
	GLfloat *faceNormals = new GLfloat[texSize];
	vcg::Point3<CMeshO::ScalarType> n;

	//Copies each face's position and normal in new vectors
	for (int i=0; i < m.cm.fn; ++i)
	{
		//face position
		facePosition[i*4+0] = (m.cm.face[i].P(0).X() + m.cm.face[i].P(1).X() + m.cm.face[i].P(2).X())*(1.0/3.0);
		facePosition[i*4+1] = (m.cm.face[i].P(0).Y() + m.cm.face[i].P(1).Y() + m.cm.face[i].P(2).Y())*(1.0/3.0);
		facePosition[i*4+2] = (m.cm.face[i].P(0).Z() + m.cm.face[i].P(1).Z() + m.cm.face[i].P(2).Z())*(1.0/3.0);
		facePosition[i*4+3] = 1.0;

		//Normal vector for each face
		n = m.cm.face[i].N();

		faceNormals[i*4+0] = n.X();
		faceNormals[i*4+1] = n.Y();
		faceNormals[i*4+2] = n.Z();
		faceNormals[i*4+3] = 0.0;
	}

	//Write vertex coordinates
	mVertexCoordsTexture->bind();
	glTexImage2D (GL_TEXTURE_2D, 0, GL_RGBA32F_ARB, mResTextureDim, mResTextureDim, 0, GL_RGBA, GL_FLOAT, facePosition);

	//Write normal directions
	mVertexNormalsTexture->bind();
	glTexImage2D (GL_TEXTURE_2D, 0, GL_RGBA32F_ARB, mResTextureDim, mResTextureDim, 0, GL_RGBA, GL_FLOAT, faceNormals);

	delete [] faceNormals;
	delete [] facePosition;


}

void SdfGpuPlugin::vertexDataToTexture(MeshModel &m)
{
	unsigned int texSize = mResTextureDim*mResTextureDim*4;

	GLfloat *vertexPosition= new GLfloat[texSize];
	GLfloat *vertexNormals = new GLfloat[texSize];
	vcg::Point3<CMeshO::ScalarType> vn;

	//Copies each vertex's position and normal in new vectors
	for (int i=0; i < m.cm.vn; ++i)
	{
		//Vertex position
		vertexPosition[i*4+0] = m.cm.vert[i].P().X();
		vertexPosition[i*4+1] = m.cm.vert[i].P().Y();
		vertexPosition[i*4+2] = m.cm.vert[i].P().Z();
		vertexPosition[i*4+3] = 1.0;

		//Normal vector for each vertex
		vn = m.cm.vert[i].N();
		vertexNormals[i*4+0] = vn.X();
		vertexNormals[i*4+1] = vn.Y();
		vertexNormals[i*4+2] = vn.Z();
		vertexNormals[i*4+3] = 0.0;
	}

	//Write vertex coordinates
	mVertexCoordsTexture->bind();
	glTexImage2D (GL_TEXTURE_2D, 0, GL_RGBA32F_ARB, mResTextureDim, mResTextureDim, 0, GL_RGBA, GL_FLOAT, vertexPosition);

	//Write normal directions
	mVertexNormalsTexture->bind();
	glTexImage2D (GL_TEXTURE_2D, 0, GL_RGBA32F_ARB, mResTextureDim, mResTextureDim, 0, GL_RGBA, GL_FLOAT, vertexNormals);

	delete [] vertexNormals;
	delete [] vertexPosition;
}

void SdfGpuPlugin::releaseGL(MeshModel &/*m*/)
{
	glUseProgram(0);
	glBindFramebuffer(GL_FRAMEBUFFER, 0);

	delete mDeepthPeelingProgram;
	delete mSDFProgram;
	delete mObscuranceProgram;
	delete mFboResult;
	delete mResultTexture;
	delete mVertexCoordsTexture;
	delete mVertexNormalsTexture;

	for(int i = 0; i < 3; i++)
	{
		delete mFboArray[i];
		delete mDepthTextureArray[i];
		delete mColorTextureArray[i];
	}

	glDeleteQueriesARB( 1, &mOcclusionQuery );

	checkGLError::debugInfo("GL release failed");

	this->glContext->doneCurrent();
}

void SdfGpuPlugin::fillFrameBuffer(bool front,  MeshModel* mm)
{
	glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT);
	glEnable(GL_CULL_FACE);
	glCullFace((front)?GL_BACK:GL_FRONT);
	//the most recent GPUs can do double speed Z-only rendering
	//(also alpha test must be turned off, depth replace and texkill must not be used in fragment shader)
	//glColorMask(0, 0, 0, 0);
	if (mm != NULL)
	{
		MLRenderingData dt;
		MLRenderingData::RendAtts atts;
		atts[MLRenderingData::ATT_NAMES::ATT_VERTPOSITION] = true;
		atts[MLRenderingData::ATT_NAMES::ATT_VERTNORMAL] = true;
		dt.set(MLRenderingData::PR_SOLID,atts);
		glContext->setRenderingData(mm->id(),dt);
		glContext->drawMeshModel(mm->id());
	}
	//mm->glw.DrawFill<GLW::NMPerVert, GLW::CMNone, GLW::TMNone>();
	//glColorMask(1, 1, 1, 1);
	//delete wid;
	glDisable(GL_CULL_FACE);
}

void SdfGpuPlugin::setupMesh(MeshDocument& md, ONPRIMITIVE onPrimitive )
{
	MeshModel* mm = md.mm();
	CMeshO& m     = mm->cm;


	//If on vertices, do some cleaning first
	if( onPrimitive == ON_VERTICES )
	{
		int dup = tri::Clean<CMeshO>::RemoveDuplicateVertex(m);
		int unref =  tri::Clean<CMeshO>::RemoveUnreferencedVertex(m);
		if (dup > 0 || unref > 0) log("Removed %i duplicate and %i unreferenced vertices\n",dup,unref);
	}

	//Updating mesh metadata
	vcg::tri::Allocator<CMeshO>::CompactVertexVector(m);
	vcg::tri::Allocator<CMeshO>::CompactFaceVector(m);
	vcg::tri::UpdateNormal<CMeshO>::PerVertexAngleWeighted(m);
	tri::UpdateBounding<CMeshO>::Box(m);

	//Enable & Reset the necessary attributes
	switch(onPrimitive)
	{
	case ON_VERTICES:
		mm->updateDataMask(MeshModel::MM_VERTQUALITY);
		tri::UpdateQuality<CMeshO>::VertexConstant(m,0);
		break;
	case ON_FACES:
		mm->updateDataMask(MeshModel::MM_FACEQUALITY);
		mm->updateDataMask(MeshModel::MM_FACENORMAL);
		mm->updateDataMask(MeshModel::MM_FACECOLOR);
		tri::UpdateQuality<CMeshO>::FaceConstant(m,0);
		break;
	}

	if(!vcg::tri::HasPerVertexAttribute(m,"maxQualityDir") && onPrimitive == ON_VERTICES)
		mMaxQualityDirPerVertex = vcg::tri::Allocator<CMeshO>::AddPerVertexAttribute<Point3f>(m,std::string("maxQualityDir"));
	else if(!vcg::tri::HasPerFaceAttribute(m,"maxQualityDir") && onPrimitive == ON_FACES)
		mMaxQualityDirPerFace = vcg::tri::Allocator<CMeshO>::AddPerFaceAttribute<Point3f>(m,std::string("maxQualityDir"));

	glContext->meshAttributesUpdated(mm->id(),true,MLRenderingData::RendAtts());

}

void SdfGpuPlugin::setCamera(Point3f camDir, Box3f meshBBox)
{
	GLfloat d = (meshBBox.Diag()/2.0),
			k = 0.1f;
	Point3f eye = meshBBox.Center() + camDir * (d+k);

	mScale = 2*k+(2.0*d);

	glViewport(0.0, 0.0, mPeelingTextureSize, mPeelingTextureSize);

	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	glOrtho(-d, d, -d, d, /*k*/0, mScale );

	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();
	gluLookAt(eye.X(), eye.Y(), eye.Z(),
			  meshBBox.Center().X(), meshBBox.Center().Y(), meshBBox.Center().Z(),
			  0.0, 1.0, 0.0);
}

void SdfGpuPlugin::useDepthPeelingShader(FramebufferObject* fbo)
{
	glUseProgram(mDeepthPeelingProgram->id());
	mDeepthPeelingProgram->setUniform1f("tolerance", mTolerance);
	mDeepthPeelingProgram->setUniform2f("oneOverBufSize", 1.0f/mPeelingTextureSize, 1.0f/mPeelingTextureSize);
	glActiveTexture(GL_TEXTURE0);
	glBindTexture(GL_TEXTURE_2D, fbo->getAttachedId(GL_DEPTH_ATTACHMENT));
	mDeepthPeelingProgram->setUniform1i("textureLastDepth",0);
}

void SdfGpuPlugin::calculateSdfHW(FramebufferObject* fboFront, FramebufferObject* fboBack, FramebufferObject* fboPrevBack, const vcg::Point3f& cameraDir)
{
	mFboResult->bind();

	glEnable(GL_SCISSOR_TEST);
	glScissor(0,0,mResTextureDim,mNumberOfTexRows);

	GLenum buffers[] = { GL_COLOR_ATTACHMENT0_EXT, GL_COLOR_ATTACHMENT1_EXT };
	glDrawBuffers(2, buffers);

	glViewport(0, 0, mResTextureDim, mResTextureDim);
	GLfloat mv_pr_Matrix_f[16];  // modelview-projection matrix

	glGetFloatv(GL_MODELVIEW_MATRIX, mv_pr_Matrix_f);
	glMatrixMode(GL_PROJECTION);
	glMultMatrixf(mv_pr_Matrix_f);
	glGetFloatv(GL_PROJECTION_MATRIX, mv_pr_Matrix_f);

	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	glOrtho(-1.0, 1.0, -1.0, 1.0, -1.0, 1.0);

	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();

	glDisable(GL_DEPTH_TEST);
	// glDepthMask(GL_FALSE);

	glEnable (GL_BLEND);
	glBlendFunc (GL_ONE, GL_ONE);
	glBlendEquation(GL_FUNC_ADD);


	glUseProgram(mSDFProgram->id());

	glActiveTexture(GL_TEXTURE0);
	glBindTexture(GL_TEXTURE_2D, fboFront->getAttachedId(GL_DEPTH_ATTACHMENT));
	mSDFProgram->setUniform1i("depthTextureFront",0);

	glActiveTexture(GL_TEXTURE1);
	glBindTexture(GL_TEXTURE_2D, fboBack->getAttachedId(GL_DEPTH_ATTACHMENT));
	mSDFProgram->setUniform1i("depthTextureBack",1);

	glActiveTexture(GL_TEXTURE2);
	glBindTexture(GL_TEXTURE_2D, fboBack->getAttachedId(GL_COLOR_ATTACHMENT0));
	mSDFProgram->setUniform1i("normalTextureBack",2);

	glActiveTexture(GL_TEXTURE3);
	glBindTexture(GL_TEXTURE_2D, mVertexCoordsTexture->id());
	mSDFProgram->setUniform1i("vTexture",3);

	glActiveTexture(GL_TEXTURE4);
	glBindTexture(GL_TEXTURE_2D, mVertexNormalsTexture->id());
	mSDFProgram->setUniform1i("nTexture",4);

	//previous depth layer, see TraceRays function for an explanation
	if(fboPrevBack)
	{
		glActiveTexture(GL_TEXTURE5);
		glBindTexture(GL_TEXTURE_2D, fboPrevBack->getAttachedId(GL_DEPTH_ATTACHMENT));
		mSDFProgram->setUniform1i("depthTexturePrevBack",5);

	}



	// Set view direction
	mSDFProgram->setUniform3f("viewDirection", cameraDir.X(), cameraDir.Y(), cameraDir.Z());

	// Set ModelView-Projection Matrix
	mSDFProgram->setUniformMatrix4fv( "mvprMatrix", mv_pr_Matrix_f, 1, GL_FALSE );

	vcg::Matrix44f mvprINV(mv_pr_Matrix_f);
	mvprINV.transposeInPlace();
	mvprINV=vcg::Inverse(mvprINV);
	mSDFProgram->setUniformMatrix4fv( "mvprMatrixINV", mvprINV.V(), 1, GL_TRUE );


	// Set texture Size
	mSDFProgram->setUniform1f("texSize", mPeelingTextureSize);

	// Set viewport Size
	mSDFProgram->setUniform1f("viewpSize", mResTextureDim );

	mSDFProgram->setUniform1f("minCos", mMinCos);

	//just a flag to know how many layers to use for z-fighting removal
	if(fboPrevBack == NULL)
		mSDFProgram->setUniform1i("firstRendering",1);
	else
		mSDFProgram->setUniform1i("firstRendering",0);

	if(mRemoveFalse)
		mSDFProgram->setUniform1i("removeFalse",1);
	else
		mSDFProgram->setUniform1i("removeFalse",0);

	if(mRemoveOutliers)
		mSDFProgram->setUniform1i("removeOutliers",1);
	else
		mSDFProgram->setUniform1i("removeOutliers",0);

	// Screen-aligned Quad
	glBegin(GL_QUADS);
	glVertex3f(-1.0f, -1.0f, 0.0f); //L-L
	glVertex3f( 1.0f, -1.0f, 0.0f); //L-R
	glVertex3f( 1.0f,  1.0f, 0.0f); //U-R
	glVertex3f(-1.0f,  1.0f, 0.0f); //U-L
	glEnd();

	mFboResult->unbind();
	glEnable(GL_DEPTH_TEST);
	//  glDepthMask(GL_TRUE);
	glDisable(GL_BLEND);
	glDisable(GL_SCISSOR_TEST);

}

void SdfGpuPlugin::applySdfPerVertex(MeshModel &m)
{
	const unsigned int texelNum = mResTextureDim*mResTextureDim;

	GLfloat *result = new GLfloat[texelNum*4];

	mFboResult->bind();

	glReadBuffer(GL_COLOR_ATTACHMENT0_EXT);
	glReadPixels(0, 0, mResTextureDim, mResTextureDim, GL_RGBA, GL_FLOAT, result);

	for (int i=0; i < m.cm.vn; ++i)
	{
		//weighted average: sdf sum is in the red channel and the weights sum in the green one
		m.cm.vert[i].Q() = mScale*((result[i*4+1]>0.0) ? (result[i*4] / result[i*4+1]) : 0.0);
	}

	glReadBuffer(GL_COLOR_ATTACHMENT1_EXT);
	glReadPixels(0, 0, mResTextureDim, mResTextureDim, GL_RGBA, GL_FLOAT, result);

	for (int i=0; i < m.cm.vn; ++i)
	{
		//weighted average: sdf sum is in the red channel and the weights sum in the green one
		Point3f dir = Point3f(result[i*4], result[i*4+1], result[i*4+2]);
		vcg::Normalize(dir);
		mMaxQualityDirPerVertex[i] = dir;
		//  Log(0,"vertice %i: %f %f %f",i,dir.X(),dir.Y(),dir.Z());
	}


	mFboResult->unbind();

	delete [] result;
}

void SdfGpuPlugin::applySdfPerFace(MeshModel &m)
{
	const unsigned int texelNum = mResTextureDim*mResTextureDim;

	GLfloat *result = new GLfloat[texelNum*4];

	mFboResult->bind();

	glReadBuffer(GL_COLOR_ATTACHMENT0_EXT);
	glReadPixels(0, 0, mResTextureDim, mResTextureDim, GL_RGBA, GL_FLOAT, result);

	for (int i=0; i < m.cm.fn; ++i)
	{
		//weighted average: sdf sum is in the red channel and the weights sum in the green one
		m.cm.face[i].Q() = mScale*((result[i*4+1]>0.0) ? (result[i*4] / result[i*4+1]) : 0.0);
	}

	glReadBuffer(GL_COLOR_ATTACHMENT1_EXT);
	glReadPixels(0, 0, mResTextureDim, mResTextureDim, GL_RGBA, GL_FLOAT, result);

	for (int i=0; i < m.cm.fn; ++i)
	{
		//weighted average: sdf sum is in the red channel and the weights sum in the green one
		Point3f dir = Point3f(result[i*4], result[i*4+1], result[i*4+2]);
		vcg::Normalize(dir);
		mMaxQualityDirPerFace[i] = dir;
		//  Log(0,"vertice %i: %f %f %f",i,dir.X(),dir.Y(),dir.Z());
	}


	mFboResult->unbind();

	delete [] result;
}

void SdfGpuPlugin::calculateObscurance(FramebufferObject* fboFront, FramebufferObject* fboBack, FramebufferObject* nextBack, const vcg::Point3f& cameraDir, float bbDiag)
{
	mFboResult->bind();

	glEnable(GL_SCISSOR_TEST);
	glScissor(0,0,mResTextureDim,mNumberOfTexRows);

	GLenum buffers[] = { GL_COLOR_ATTACHMENT0_EXT, GL_COLOR_ATTACHMENT1_EXT };
	glDrawBuffers(2, buffers);

	glViewport(0, 0, mResTextureDim, mResTextureDim);

	GLfloat mv_pr_Matrix_f[16];  // modelview-projection matrix

	glGetFloatv(GL_MODELVIEW_MATRIX, mv_pr_Matrix_f);
	glMatrixMode(GL_PROJECTION);
	glMultMatrixf(mv_pr_Matrix_f);
	glGetFloatv(GL_PROJECTION_MATRIX, mv_pr_Matrix_f);

	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	glOrtho(-1.0, 1.0, -1.0, 1.0, -1.0, 1.0);

	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();

	glDepthMask(GL_FALSE);
	glDisable(GL_DEPTH_TEST);

	glEnable (GL_BLEND);
	glBlendFunc (GL_ONE, GL_ONE);
	glBlendEquation(GL_FUNC_ADD);


	glUseProgram(mObscuranceProgram->id());
	assert(glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT)==GL_FRAMEBUFFER_COMPLETE_EXT && "before draw");


	glActiveTexture(GL_TEXTURE0);
	glBindTexture(GL_TEXTURE_2D, fboFront->getAttachedId(GL_DEPTH_ATTACHMENT));
	mObscuranceProgram->setUniform1i("depthTextureFront",0);

	glActiveTexture(GL_TEXTURE1);
	glBindTexture(GL_TEXTURE_2D, fboBack->getAttachedId(GL_DEPTH_ATTACHMENT));
	mObscuranceProgram->setUniform1i("depthTextureBack",1);

	glActiveTexture(GL_TEXTURE2);
	glBindTexture(GL_TEXTURE_2D, mVertexCoordsTexture->id());
	mObscuranceProgram->setUniform1i("vTexture",2);

	glActiveTexture(GL_TEXTURE3);
	glBindTexture(GL_TEXTURE_2D, mVertexNormalsTexture->id());
	mObscuranceProgram->setUniform1i("nTexture",3);

	//next depth layer, see TraceRays function for an explanation
	if(nextBack != NULL)
	{
		glActiveTexture(GL_TEXTURE4);
		glBindTexture(GL_TEXTURE_2D, nextBack->getAttachedId(GL_DEPTH_ATTACHMENT));
		mObscuranceProgram->setUniform1i("depthTextureNextBack",4);
	}

	// Set view direction
	mObscuranceProgram->setUniform3f("viewDirection", cameraDir.X(), cameraDir.Y(), cameraDir.Z());

	// Set ModelView-Projection Matrix
	mObscuranceProgram->setUniformMatrix4fv( "mvprMatrix", mv_pr_Matrix_f, 1, GL_FALSE );

	// Set texture Size
	mObscuranceProgram->setUniform1f("texSize", mPeelingTextureSize);

	// Set viewport Size
	mObscuranceProgram->setUniform1f("viewpSize", mResTextureDim );

	mObscuranceProgram->setUniform1f("tau", mTau);

	mObscuranceProgram->setUniform1f("maxDist", bbDiag);

	//just a flag to know how many layers to use for z-fighting removal
	if(nextBack == NULL)
		mObscuranceProgram->setUniform1i("firstRendering",1);
	else
		mObscuranceProgram->setUniform1i("firstRendering",0);

	// Screen-aligned Quad
	glBegin(GL_QUADS);
	glVertex3f(-1.0f, -1.0f, 0.0f); //L-L
	glVertex3f( 1.0f, -1.0f, 0.0f); //L-R
	glVertex3f( 1.0f,  1.0f, 0.0f); //U-R
	glVertex3f(-1.0f,  1.0f, 0.0f); //U-L
	glEnd();

	assert(glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT)==GL_FRAMEBUFFER_COMPLETE_EXT && "after draw");

	mFboResult->unbind();

	glEnable(GL_DEPTH_TEST);
	glDepthMask(GL_TRUE);
	glDisable(GL_BLEND);
	glDisable(GL_SCISSOR_TEST);
}

void SdfGpuPlugin::applyObscurancePerVertex(MeshModel &m, float numberOfRays)
{
	const unsigned int texelNum = mResTextureDim*mResTextureDim;

	GLfloat *result = new GLfloat[texelNum*4];

	mFboResult->bind();

	glReadBuffer(GL_COLOR_ATTACHMENT0_EXT);
	glReadPixels(0, 0, mResTextureDim, mResTextureDim, GL_RGBA, GL_FLOAT, result);

	for( int i = 0; i < m.cm.vn; i++)
	{
		m.cm.vert[i].Q() = result[i*4]/numberOfRays;
	}
	tri::UpdateColor<CMeshO>::PerVertexQualityGray(m.cm,0.0f,0.0f);

	glReadBuffer(GL_COLOR_ATTACHMENT1_EXT);
	glReadPixels(0, 0, mResTextureDim, mResTextureDim, GL_RGBA, GL_FLOAT, result);

	for (int i=0; i < m.cm.vn; ++i)
	{

		Point3f dir = Point3f(result[i*4], result[i*4+1], result[i*4+2]);
		vcg::Normalize(dir);
		mMaxQualityDirPerVertex[i] = dir;

	}

	mFboResult->unbind();
	delete [] result;
}

void SdfGpuPlugin::applyObscurancePerFace(MeshModel &m, float numberOfRays)
{
	const unsigned int texelNum = mResTextureDim*mResTextureDim;

	GLfloat *result = new GLfloat[texelNum*4];

	mFboResult->bind();

	glReadBuffer(GL_COLOR_ATTACHMENT0_EXT);
	glReadPixels(0, 0, mResTextureDim, mResTextureDim, GL_RGBA, GL_FLOAT, result);

	for(int i = 0; i < m.cm.fn; i++)
	{
		m.cm.face[i].Q() = result[i*4]/numberOfRays;
	}

	tri::UpdateColor<CMeshO>::PerFaceQualityGray(m.cm);

	glReadBuffer(GL_COLOR_ATTACHMENT1_EXT);
	glReadPixels(0, 0, mResTextureDim, mResTextureDim, GL_RGBA, GL_FLOAT, result);

	for (int i=0; i < m.cm.fn; ++i)
	{

		Point3f dir = Point3f(result[i*4], result[i*4+1], result[i*4+2]);
		vcg::Normalize(dir);
		mMaxQualityDirPerFace[i] = dir;

	}

	mFboResult->unbind();
	delete [] result;
}


void SdfGpuPlugin::preRender(unsigned int peelingIteration)
{
	if( peelingIteration != 0 )
		glBeginQueryARB( GL_SAMPLES_PASSED_ARB, mOcclusionQuery );

}

bool SdfGpuPlugin::postRender(unsigned int peelingIteration)
{

	if( peelingIteration != 0)
	{
		glEndQueryARB( GL_SAMPLES_PASSED_ARB );

		glGetQueryObjectuivARB( mOcclusionQuery, GL_QUERY_RESULT_ARB, &mPixelCount);
		if(mPixelCount > PIXEL_COUNT_THRESHOLD )
		{
			mTempDepthComplexity++;
			return true;
		}
		else return false;
	}

	return true;
}

void SdfGpuPlugin::TraceRay(const QAction* action, int peelingIteration,const Point3f& dir, MeshModel* mm )
{
	unsigned int j = 0;

	for( int i = 0;  i < peelingIteration; i++ )
	{
		if( i == 0 )
			glUseProgram(0);
		else
		{
			if(j > 0)
				useDepthPeelingShader(mFboArray[j-1]);
			else
				useDepthPeelingShader(mFboArray[2]);
		}

		mFboArray[j]->bind();

		setCamera(dir, Box3f::Construct(mm->cm.bbox));

		preRender(i);

		fillFrameBuffer(i%2==0, mm);

		//Cut off useless passes
		if(!postRender(i))
			return;
		else
			if(i==(peelingIteration-1))
				log(GLLogStream::SYSTEM,"WARNING: You may have underestimated the depth complexity of the mesh. Run the filter with a higher number of peeling iteration.");

		mFboArray[j]->unbind();
		//we use 3 FBOs to avoid z-fighting (Inspired from Woo's shadow mapping method)
		if(i%2)
		{
			//we use the same method as in sdf, see below
			if(ID(action)==SDF_OBSCURANCE )
			{
				if(i>1)
				{
					int prevBack  = (j+1)%3;
					int front     = (j==0)? 2 : (j-1);
					calculateObscurance( mFboArray[front], mFboArray[prevBack], mFboArray[j], dir, mm->cm.bbox.Diag());//front prevBack Back
				}
				else
				{
					assert(j!=0);
					calculateObscurance( mFboArray[j-1], mFboArray[j], NULL, dir, mm->cm.bbox.Diag());//front back nextBack

				}
			}
			else if(ID(action) == SDF_SDF)
			{
				if(i>1)
				{
					//We are interested in vertices belonging to the front layer. Then in the shader, we check that
					//the vertex's depth is greater than the previous depth layer and smaller than the next one.
					int prevBack  = (j+1)%3;
					int prevFront = (j==0)? 2 : (j-1);
					calculateSdfHW( mFboArray[prevFront], mFboArray[j], mFboArray[prevBack],dir );// front back prevback
				}
				else
				{    //we have first and second depth layers, so we can use "second-depth shadow mapping" to avoid z-fighting
					assert(j!=0);
					calculateSdfHW( mFboArray[j-1], mFboArray[j], NULL, dir );// front back prevback
				}
			}


		}

		//increment and wrap around
		j = (j+1) % 3;
	}

	assert(mFboResult->isValid());
	assert(mFboArray[0]->isValid());
	assert(mFboArray[1]->isValid());
	assert(mFboArray[2]->isValid());

	checkGLError::debugInfo("Error during depth peeling");
}

FilterPlugin::FilterArity SdfGpuPlugin::filterArity(const QAction *) const
{
	return FilterPlugin::SINGLE_MESH;
}

bool SdfGpuPlugin::requiresGLContext(const QAction* action) const
{
	switch(ID(action)){
	case SDF_SDF:
	case SDF_DEPTH_COMPLEXITY:
	case SDF_OBSCURANCE:
		return true;
	default:
		assert(0);
	}
	return false;
}

MESHLAB_PLUGIN_NAME_EXPORTER(SdfGpuPlugin)

