//-
// ==========================================================================
// Copyright 1995,2006,2008 Autodesk, Inc. All rights reserved.
//
// Use of this software is subject to the terms of the Autodesk
// license agreement provided at the time of installation or download,
// or which otherwise accompanies this software in either electronic
// or hard copy form.
// ==========================================================================
//+

////////////////////////////////////////////////////////////////////////
//
// peltOverlapCmd.cpp
//
// This plugin checks and returns the overlapping faces in pairs for a given
// list of shading groups. The faces can be from the same mesh or different meshes, i.e.
//
// 1) Return the first overlapping faces associated to "shadingGroupName1"
// peltOverlap "shadingGroupName1";
//
// 2) Return the first 100 overlapping faces associated to "shadingGroupName1" 
//       and the first 100 overlapping faces associated to "shadingGroupName2".
// peltOverlap -exitAfterNthError 100 "shadingGroupName1" "shadingGroupName2";
//
////////////////////////////////////////////////////////////////////////

#include <maya/MIOStream.h>
#include <maya/MFnPlugin.h>
#include <maya/MString.h>
#include <maya/MFloatArray.h>
#include <maya/MArgList.h>

#include <maya/MPxCommand.h>
#include <maya/MSyntax.h>
#include <maya/MArgDatabase.h>

#include <maya/MGlobal.h>
#include <maya/MDagPath.h>
#include <maya/MItSelectionList.h>
#include <maya/MSelectionList.h>
#include <maya/MItMeshPolygon.h>

#include <maya/MFnDependencyNode.h>
#include <maya/MFnSet.h>
#include <maya/MPlugArray.h>
#include <maya/MPlug.h>

#include <math.h>

#define kExitFlag	"-ea"
#define kExitFlagLong	"-exitAfterNthPairs"

///////////////////////////////////////////////////
//
// Command class declaration
//
///////////////////////////////////////////////////
class peltOverlap : public MPxCommand
{
public:
                         peltOverlap();
        virtual		~peltOverlap(); 

        MStatus		doIt( const MArgList& args );

	static MSyntax	newSyntax();
	static void*	creator();

private:

	MStatus		parseArgs( const MArgList &args );
        void            createBoundingCircle(const MStringArray &flattenFaces, MFloatArray &center, MFloatArray &radius);
        bool            createRayGivenFace(const MString &face, MFloatArray &orig, MFloatArray &vec);
        float           area(const MFloatArray &orig);
        unsigned int    checkCrossingEdges(MFloatArray &face1Orig, 
					   MFloatArray &face1Vec, 
					   MFloatArray &face2Orig, 
					   MFloatArray &face2Vec);
        void            numOverlapUVFaces(const MString &shadingGroup, MStringArray &flattenFaces);

	unsigned int	fNthPairs;
        MStringArray    fShadingGroups;
};

peltOverlap::peltOverlap()
	: fNthPairs (1)
{}

peltOverlap::~peltOverlap() {}

void* peltOverlap::creator()
{
	return new peltOverlap;
}

MSyntax peltOverlap::newSyntax()
{
	 MSyntax syntax;
	 syntax.addFlag(kExitFlag, kExitFlagLong, MSyntax::kUnsigned);
	 syntax.setObjectType(MSyntax::kStringObjects);
	 return syntax;
}

MStatus peltOverlap::parseArgs( const MArgList& args )
{
	MStatus		status = MS::kSuccess;
	MArgDatabase	argData(syntax(), args);

	if (argData.isFlagSet(kExitFlag)) {
	    status = argData.getFlagArgument(kExitFlag, 0, fNthPairs);
	    if (status != MS::kSuccess) {
	        MGlobal::displayError("-ea/exitAfterNthPairs is missing an int argument");
		return status;
	    }
	}

	status = argData.getObjects(fShadingGroups);
	if (status != MS::kSuccess || fShadingGroups.length() < 1) {
	        MGlobal::displayError("Missing shading group(s) input");
		status = MS::kFailure;
	}

	return status;
}

MStatus peltOverlap::doIt( const MArgList& args )
//
// Description
//     Return the overlapping faces in pairs for given shading groups.
//
{
	MStatus status = parseArgs ( args );
	if ( !status ) return status;

	// Iterate over shading groups
	for (unsigned int i = 0; i < fShadingGroups.length(); i++ ) 
	{
	    // Get a list of faces through mel because it's so much easier this way.
	    // It should be fine because this is not performance critical.
	    MStringArray faces, flattenFaces;
	    MGlobal::executeCommand("sets -q " + fShadingGroups[i], faces, false, false);
	    MGlobal::clearSelectionList();
	    for(unsigned int j = 0; j < faces.length(); j++) 
	    {
		MString nodeType = MGlobal::executeCommandStringResult("nodeType " + faces[j]);
		if (nodeType == "mesh") {
		    MGlobal::selectByName(faces[j]); // add to list
		}
	    }
	    MGlobal::executeCommand("ConvertSelectionToFaces");
	    MGlobal::executeCommand("ls -sl -flatten", flattenFaces, false, false);

	    numOverlapUVFaces(fShadingGroups[i], flattenFaces);
	}
	return MS::kSuccess;
}

void peltOverlap::createBoundingCircle(const MStringArray &flattenFaces, MFloatArray &center, MFloatArray &radius)
//
// Description
//     Represent a face by a center and radius, i.e.
//     center = {center1u, center1v, center2u, center2v, ... }
//     radius = {radius1, radius2,  ... }
//
{
        center.setLength(2 * flattenFaces.length());
        radius.setLength(flattenFaces.length());
	for(unsigned int i = 0; i < flattenFaces.length(); i++) {
		MSelectionList selList;
		selList.add(flattenFaces[i]);
		MDagPath dagPath;
		MObject  comp;
		selList.getDagPath(0, dagPath, comp);
		MItMeshPolygon iter(dagPath, comp);

		MFloatArray uArray, vArray;
		iter.getUVs(uArray, vArray);
		// Loop through all vertices to construct edges/rays
		float cu = 0.f;
		float cv = 0.f;
		unsigned int j;
		for(j = 0; j < uArray.length(); j++) {
		        cu += uArray[j];
			cv += vArray[j];
		}
		cu = cu / uArray.length();
		cv = cv / vArray.length();
		float rsqr = 0.f;
		for(j = 0; j < uArray.length(); j++) {
		        float du = uArray[j] - cu;
			float dv = vArray[j] - cv;
			float dsqr = du*du + dv*dv;
			rsqr = dsqr > rsqr ? dsqr : rsqr;
		}
		center[2*i]   = cu;
		center[2*i+1] = cv;
		radius[i]  = sqrt(rsqr);
	}
}

bool peltOverlap::createRayGivenFace(const MString &face, MFloatArray &orig, MFloatArray &vec)
//
// Description
//     Represent a face by a series of edges(rays), i.e.
//     orig = {orig1u, orig1v, orig2u, orig2v, ... }
//     vec  = {vec1u,  vec1v,  vec2u,  vec2v,  ... }
//
//     return false if no valid uv's. 
{
        MSelectionList selList;
	selList.add(face);
	MDagPath dagPath;
	MObject  comp;
	selList.getDagPath(0, dagPath, comp);
	MItMeshPolygon iter(dagPath, comp);

	MFloatArray uArray, vArray;
	iter.getUVs(uArray, vArray);

	if (uArray.length() == 0 || vArray.length() == 0) return false;

	orig.setLength(2 * uArray.length());
	vec.setLength( 2 * uArray.length());

	// Loop through all vertices to construct edges/rays
	float u = uArray[uArray.length() - 1];
	float v = vArray[vArray.length() - 1];
	for(unsigned int j = 0; j < uArray.length(); j++) {
		orig[2*j]   = uArray[j];
		orig[2*j+1] = vArray[j];
		vec[2*j]    = u - uArray[j];
		vec[2*j+1]  = v - vArray[j];
		u = uArray[j];
		v = vArray[j];
	}
	return true;
}

float peltOverlap::area(const MFloatArray &orig)
{
	float sum = 0.f;
	unsigned int num = orig.length() / 2;
	for (unsigned int i = 0; i < num; i++) {
		unsigned int idx  = 2 * i;
		unsigned int idy  = (i + 1 ) % num;
		idy = 2 * idy + 1;
		unsigned int idy2 = (i + num - 1) % num; 
		idy2 = 2 * idy2 + 1;
		sum += orig[idx] * (orig[idy] - orig[idy2]);
	}
	return fabs(sum) * 0.5f;
}

unsigned int peltOverlap::checkCrossingEdges(
MFloatArray &face1Orig, 
MFloatArray &face1Vec, 
MFloatArray &face2Orig, 
MFloatArray &face2Vec
)
// Check if there are crossing edges between two faces. Return true 
// if there are crossing edges and false otherwise. A face is represented
// by a series of edges(rays), i.e. 
// faceOrig[] = {orig1u, orig1v, orig2u, orig2v, ... }
// faceVec[]  = {vec1u,  vec1v,  vec2u,  vec2v,  ... }
{
	unsigned int face1Size = face1Orig.length();
	unsigned int face2Size = face2Orig.length();
	for (unsigned int i = 0; i < face1Size; i += 2) {
		float o1x = face1Orig[i];
		float o1y = face1Orig[i+1];
		float v1x = face1Vec[i];
		float v1y = face1Vec[i+1];
		float n1x =  v1y;
		float n1y = -v1x;
		for (unsigned int j = 0; j < face2Size; j += 2) {
			// Given ray1(O1, V1) and ray2(O2, V2)
			// Normal of ray1 is (V1.y, V1.x)
			float o2x = face2Orig[j];
			float o2y = face2Orig[j+1];
			float v2x = face2Vec[j];
			float v2y = face2Vec[j+1];
			float n2x =  v2y;
			float n2y = -v2x;

			// Find t for ray2
			// t = [(o1x-o2x)n1x + (o1y-o2y)n1y] / (v2x * n1x + v2y * n1y)
			float denum = v2x * n1x + v2y * n1y;
			// Edges are parallel if denum is close to 0.
			if (fabs(denum) < 0.000001f) continue;
			float t2 = ((o1x-o2x)* n1x + (o1y-o2y) * n1y) / denum;
			if (t2 < 0.00001f || t2 > 0.99999f) continue;

			// Find t for ray1
			// t = [(o2x-o1x)n2x + (o2y-o1y)n2y] / (v1x * n2x + v1y * n2y)
			denum = v1x * n2x + v1y * n2y;
			// Edges are parallel if denum is close to 0.
			if (fabs(denum) < 0.000001f) continue;
			float t1 = ((o2x-o1x)* n2x + (o2y-o1y) * n2y) / denum;

			// Edges intersect
			if (t1 > 0.00001f && t1 < 0.99999f) return 1;
		}
	}
	return 0;
}

void peltOverlap::numOverlapUVFaces(const MString& shadingGroup, MStringArray& flattenFaces)
//
// Description
//     Return overlapping faces in pairs for given a shading group and its associated faces.
//
{
	MFloatArray  face1Orig, face1Vec, face2Orig, face2Vec, center, radius;
	// Loop through face i
	unsigned int numOverlap = 0;
	createBoundingCircle(flattenFaces, center, radius);
	for(unsigned int i = 0; i < flattenFaces.length() && numOverlap < fNthPairs; i++) {
	        if(!createRayGivenFace(flattenFaces[i], face1Orig, face1Vec)) continue;
		const float cui  = center[2*i];
		const float cvi  = center[2*i+1];
		const float ri  = radius[i];
		// Exclude the degenerate face
		// if(area(face1Orig) < 0.000001) continue;
		// Loop through face j where j != i
		for(unsigned int j = i+1; j < flattenFaces.length() && numOverlap < fNthPairs; j++) {
			const float &cuj = center[2*j];
			const float &cvj = center[2*j+1];
			const float &rj  = radius[j];
			float du = cuj - cui;
			float dv = cvj - cvi;
			float dsqr = du*du + dv*dv;
			// Quick rejection if bounding circles don't overlap
			if (dsqr >= (ri+rj)*(ri+rj)) continue;

			if(!createRayGivenFace(flattenFaces[j], face2Orig, face2Vec)) continue;
			// Exclude the degenerate face
			// if(area(face2Orig) < 0.000001) continue;
			if (checkCrossingEdges(face1Orig, face1Vec, face2Orig, face2Vec)) {
				numOverlap++;
				appendToResult(flattenFaces[i]);
				appendToResult(flattenFaces[j]);
				continue;
			}
		}
	}
}


/////////////////////////////////////////////////////////////////
//
// The following routines are used to register/unregister
// the command we are creating within Maya
//
/////////////////////////////////////////////////////////////////
MStatus initializePlugin( MObject obj )
{
	MStatus   status;
	MFnPlugin plugin( obj, PLUGIN_COMPANY, "3.0", "Any");

	status = plugin.registerCommand( "peltOverlap",
					 peltOverlap::creator,
					 peltOverlap::newSyntax);
	if (!status) {
		status.perror("registerCommand");
		return status;
	}

	return status;
}

MStatus uninitializePlugin( MObject obj)
{
	MStatus	  status;
	MFnPlugin plugin( obj );

	status = plugin.deregisterCommand( "peltOverlap" );
	if (!status) {
		status.perror("deregisterCommand");
		return status;
	}

	return status;
}
