#include "StdAfx.h"
#include "mayacrymeshutil.h"
#include "MayaCryUtils.h"
#include "CryMeshCompact.h"

// this flag determines whether the maya-to-cryvertex mapping will be implemented.
// if false, then one maya vertex corresponds exactly to a single CryVertex
// otherwise, a maya vertex that has several normals associated will have several
// associated vertices - one per normal.
static bool g_bEnableMayaCryVertexMapping = true;

CMayaCryMeshUtil::CMayaCryMeshUtil(const MDagPath& pathMesh, MSpace::Space nSpace):
	m_nSpace (nSpace),
	m_pathMesh(pathMesh)
{
	MStatus status = m_fnMesh.setObject(pathMesh);

	m_numMayaVerts   = m_fnMesh.numVertices (&status);
	m_numMayaFaces   = m_fnMesh.numPolygons (&status);
	m_numTVerts  = m_fnMesh.numUVs (&status);
	m_numNormals = m_fnMesh.numNormals (&status);

	if (!status)
		throw status;

	status = m_fnMesh.getPoints (m_arrMayaVerts, nSpace);
	if (!status)
		throw status;

	status = m_fnMesh.getNormals (m_arrMayaNormals, nSpace);
	if (!status)
		throw status;

	// init m_arrMayaFaceMtlId and corresponding array of material objects
	initMaterialMap();

	// convert the UV coordinate to Cry UV array
	initCryUVs();

	// convert this into the CryEngine internal representation where the vertex is
	// CryVertex with both the normal and the point vertex
	initCryVerts();

	// create the cry vertex color array
	initCryVertColors();

	// to avoid invalid results in the case oferrors with texture retrieval..
	if (m_arrCryTexFaces.size () != m_arrCryFaces.size())
	{
		m_arrCryUVs.clear();
		m_arrCryTexFaces.clear();
	}

	compactCryUVs();
	compactCryVerts();
}



CMayaCryMeshUtil::~CMayaCryMeshUtil(void)
{
}


//////////////////////////////////////////////////////////////////////////
// computes the CryVertex array out of decoupled vertex/normal representation of Maya
void CMayaCryMeshUtil::initCryVerts()
{
	MStatus status;
	MItMeshPolygon itMesh (m_pathMesh, MObject::kNullObj, &status);
	if (!status)
		throw status;

	int numNonTriangles = 0;

	// these arrays will hold the polygon vertices and UV texture vertex indices
	int arrCryPolyVerts[3], arrCryPolyUVs[3];

	bool bUVs = !m_arrCryUVs.empty();// are there texture coordinates in this mesh?

	// Mesh Vtx Idx -> Poly Vtx Idx map
	// on each non-triangular polygon, some of these cells get filled with
	// index of the point within the local polygon point enumeration
	std::vector<int> arrMeshToPolyVertIdx;
	arrMeshToPolyVertIdx.resize (m_numMayaVerts);

	// add each vertex to the array of vertices and the array of polygons
	for (; !itMesh.isDone (); itMesh.next ())
	{
		int nVert, numVerts = itMesh.polygonVertexCount ();
		if (numVerts < 3)
			continue; // degenerate polygon

		if (numVerts == 3)
		{
			// scan through the vertices belonging to this polygon
			// and fill the array of cryvertex indices in
			for (nVert = 0; nVert < 3; ++nVert)
			{
				arrCryPolyVerts[nVert] = getCryVertexIndex (itMesh.vertexIndex (nVert), itMesh.normalIndex (nVert));
				if (bUVs)
				{
					if (!itMesh.getUVIndex(nVert, arrCryPolyUVs[nVert]))
						bUVs = false;
				}
			}

			addSingleCryFace (arrCryPolyVerts, bUVs ? arrCryPolyUVs : NULL, 1, m_arrMayaFaceMtlId[itMesh.index ()]);
			continue; // go on to the next face
		}

		if (!itMesh.hasValidTriangulation (&status) || !status)
			continue;

		int numTriangles;
    status = itMesh.numTriangles (numTriangles);
		if (!status)
			throw status;

		// fill the global-to-local vertex index map
		for (nVert = 0; nVert < numVerts; ++nVert)
			arrMeshToPolyVertIdx[itMesh.vertexIndex (nVert)] = nVert;

		MPointArray arrTriPoints;
		MIntArray arrTriIndices;
		status = itMesh.getTriangles (arrTriPoints, arrTriIndices, m_nSpace);
		if (!status)
			throw status;

		int numTriPoints = arrTriPoints.length ();
		int numTriIndices = arrTriIndices.length ();

		if (arrTriIndices.length () != 3 * numTriangles)
		{
			assert (0);
			Log("*ERROR* there are %d triangulation indices on a %d-vertex polygon. Skipping the polygon", arrTriIndices.length (), numTriangles);
			continue;
		}

		// fill in the Mesh Vtx Idx -> Poly Vtx Idx map

		// scan through all triangles in the polygon triangulation
		for (int nTriangle = 0; nTriangle < numTriangles; ++nTriangle)
		{
			// scan through the vertices belonging to this polygon
			// and fill the array of cryvertex indices in
			for (int nVert = 0; nVert < 3; ++nVert)
			{
				// the vertex index inside the mesh is nMeshVertIdx
				// it's the nVert-th vertex inside the nTriangle-th triangle
				// inside the itMesh.index()-th polygon of the mesh
				int nMeshVertIdx = arrTriIndices[nVert+3*nTriangle];
			
				// find the normal corresponding to the vertex
				int nPolyVertIdx = arrMeshToPolyVertIdx[nMeshVertIdx];
				assert (nPolyVertIdx >= 0 && nPolyVertIdx < numVerts);
				assert (itMesh.vertexIndex (nPolyVertIdx) == nMeshVertIdx);

				arrCryPolyVerts[nVert] = getCryVertexIndex (nMeshVertIdx, itMesh.normalIndex (nPolyVertIdx));
				if (bUVs)
				{
					if (!itMesh.getUVIndex(nPolyVertIdx, arrCryPolyUVs[nVert]))
						bUVs = false;
				}
			}

			addSingleCryFace (arrCryPolyVerts, bUVs ? arrCryPolyUVs : NULL, 1, m_arrMayaFaceMtlId[itMesh.index ()]);
		}
	}

	if (!bUVs)
	{
		m_arrCryUVs.clear();
		m_arrCryTexFaces.clear();
	}

	if (numNonTriangles > 0)
		std::cerr << numNonTriangles<< " non triangular faces were encountered from " << name().asChar() << ". They were exported, but it is recommended to triangulate and clean the mesh up before exporting.\n";
}


void CMayaCryMeshUtil::initCryVertColors()
{
	MColorArray arrMayaColors;
	m_fnMesh.getVertexColors (arrMayaColors);
	if (arrMayaColors.length () != m_numMayaVerts)
		return; // no vertex colors

	assert (m_arrMayaToCryVertMap.size() == m_arrCryVerts.size());
	m_arrCryVertColors.resize (m_arrMayaToCryVertMap.size());

	// set each color
	for (unsigned i = 0; i < m_arrMayaToCryVertMap.size(); ++i)
	{
		unsigned nMayaVertex = m_arrMayaToCryVertMap[i];
		const MColor& mrgbVertexColor = arrMayaColors[nMayaVertex];
		
		CryIRGB& rgb = m_arrCryVertColors[i];
		
		if (mrgbVertexColor.g < 0)
			rgb.r = rgb.g = rgb.b = 0xC0;
		else
			rgb = MayaToCryIRGB (mrgbVertexColor);
	}
}



// calculates the bounding box of the given polygon in Maya vertices
void CMayaCryMeshUtil::getCryFaceBBox (int* pCryVerts, int numVerts, Vec3& ptBBoxMin, Vec3& ptBBoxMax)
{
	ptBBoxMin = ptBBoxMax = m_arrCryVerts[pCryVerts[0]].p;
	int nVert, nCoord;

	for (nVert = 1; nVert < numVerts; ++nVert)
	{
		Vec3 pt = m_arrCryVerts[pCryVerts[nVert]].p;
		for (nCoord = 0; nCoord < 3; ++nCoord)
		{
			if (pt[nCoord] < ptBBoxMin[nCoord])
				ptBBoxMin[nCoord] = pt[nCoord];
			else
				if (pt[nCoord] > ptBBoxMax[nCoord])
					ptBBoxMax[nCoord] = pt[nCoord];
		}
	}
}


// calculates the bounding box projection along the given axis area
float CMayaCryMeshUtil::getBBoxAreaAlongAxis (Vec3& ptBBoxMin, Vec3& ptBBoxMax, int nAxis)
{
	int nCoordX = (nAxis+1)%3, nCoordY = (nAxis+2)%3;
	return (ptBBoxMax[nCoordX]-ptBBoxMin[nCoordX])*(ptBBoxMax[nCoordY]-ptBBoxMin[nCoordY]);
}


// adds a single 3-vertex cry face
void CMayaCryMeshUtil::addSingleCryFace (int pCryVerts[3], int pCryUVs[3], int nSmoothingGroup , int nMatID )
{
	{
		CryFace face;
		face.SmGroup = nSmoothingGroup;
		face.MatID   = nMatID;
		face.v0      = pCryVerts[0];
		face.v1      = pCryVerts[1];
		face.v2      = pCryVerts[2];
		m_arrCryFaces.push_back (face);
	}

	if (pCryUVs)
	{
		CryTexFace face;
		face.t0 = pCryUVs[0];
		face.t1 = pCryUVs[1];
		face.t2 = pCryUVs[2];
		m_arrCryTexFaces.push_back (face);
	}
}



//////////////////////////////////////////////////////////////////////////
// adds multivertex polygon (after triangulation) to the array of CryFaces.
// this may take 0 (if the polygon is degraded), 1 (if it's a triangle) or more (if it has
// more than 3 vertices) CryFace records in the cry face array.
// returns the number of CryFace records added
int CMayaCryMeshUtil::addCryFaces (int* pCryVerts, int* pCryUVs, int numVerts, int nMayaFaceIndex)
{
	return 0;
}



//////////////////////////////////////////////////////////////////////////
// if necessary, adds the CryVertex to the m_arrCryVerts and returns its index
int CMayaCryMeshUtil::getCryVertexIndex (int nMayaVertex, int nMayaNormal)
{
	if (g_bEnableMayaCryVertexMapping)
	{
		MayaCryVertex Vertex (nMayaVertex, nMayaNormal);
		MayaCryVertMap::iterator it = m_mapMayaCryVerts.find (Vertex);
		int nCryVertex;
		if (it == m_mapMayaCryVerts.end())
		{
			// if we didn't find such pair vertex/normal, then add it now
			nCryVertex = m_arrCryVerts.size();
			CryVertex cryVertex;
			MPoint& ptMayaVertex = m_arrMayaVerts[nMayaVertex];
			MFloatVector& vMayaNormal = m_arrMayaNormals[nMayaNormal];
			m_mapMayaCryVerts.insert (MayaCryVertMap::value_type(Vertex, nCryVertex));

			for (int nCoord = 0; nCoord < 3; ++nCoord)
			{
				cryVertex.p[nCoord] = (float)ptMayaVertex[nCoord];
				cryVertex.n[nCoord] = (float)vMayaNormal[nCoord];
			}
			m_arrCryVerts.push_back (cryVertex);
			m_arrMayaToCryVertMap.push_back (nMayaVertex);
		}
		else
			// there's already a cached pair of vertex/normal, so return its index now
			nCryVertex = it->second;

		return nCryVertex;
	}
	else
	{
		unsigned nOldSize = m_arrCryVerts.size();
		if ((int)nOldSize <= nMayaVertex)
		{
			m_arrCryVerts.resize (nMayaVertex+1);
			m_arrMayaToCryVertMap.resize (nMayaVertex+1);

			for (unsigned i = nOldSize; (int)i <= nMayaVertex; ++i)
			{
				m_arrMayaToCryVertMap[i] = i;
				assign(m_arrCryVerts[i].p, m_arrMayaVerts[i]);
				MVector vMayaNormal;
				m_fnMesh.getVertexNormal (i, vMayaNormal, m_nSpace);
				assign(m_arrCryVerts[i].n, vMayaNormal);
			}
		}
		return nMayaVertex;
	}
}


//////////////////////////////////////////////////////////////////////////
// init m_arrMayaFaceMtlId and corresponding array of material objects
void CMayaCryMeshUtil::initMaterialMap()
{
	MObjectArray arrShaders;
	MStatus status = m_fnMesh.getConnectedShaders (m_pathMesh.instanceNumber (), arrShaders, m_arrMayaFaceMtlId);
	if (!status)
		throw status;

	std::string strShaders;
	m_arrShaders.clear();
	for (unsigned i = 0; i < arrShaders.length (); ++i)
	{
		MFnDependencyNode fnShaderGroup(arrShaders[i]);

		if (i) strShaders += ",";
		strShaders += fnShaderGroup.name().asChar ();
		strShaders += ":";
		strShaders += arrShaders[i].apiTypeStr ();

		// find the surface shader port for the given shader group
		MPlug plugSurfaceShaderPort = fnShaderGroup.findPlug ("surfaceShader", &status);
		MObject objShader = MObject::kNullObj; // the i-th material
		if (!status)
			strShaders += "(no surface shader; ignored)";
		else
		{
			strShaders += "(plug name:";
			strShaders += plugSurfaceShaderPort.name ().asChar ();
			strShaders += ", connected plugs:";
			MPlugArray arrInPlugs;
			bool bConnected = plugSurfaceShaderPort.connectedTo (arrInPlugs, true, false, &status);
			if (!status)
				throw status;
			if (bConnected && arrInPlugs.length () > 0)
			{
				if (arrInPlugs.length () > 1)
					Log ("*WARNING* more than 1 shaders connected to the shading group");
				for (unsigned nInPlug = 0; nInPlug < arrInPlugs.length (); ++nInPlug)
				{
					MPlug plug = arrInPlugs[nInPlug];
					if (nInPlug) strShaders += ",";
					strShaders += plug.name ().asChar ();

					if (objShader == MObject::kNullObj)
					{
						objShader = plug.node();
						// TODO: check if the node is actually a shader, and set objShader back to null if no
					}
				}
			}
			else
			{
				strShaders += "#none#";
			}
			strShaders += ")";
		}
		m_arrShaders.append (objShader);
	}
	Log ("Found %d shader group(s) attached to %s: %s", arrShaders.length (), m_fnMesh.name().asChar (), strShaders.c_str ());
}


unsigned CMayaCryMeshUtil::getMayaVertByCryVert (unsigned nMayaVert)const 
{
	return m_arrMayaToCryVertMap[nMayaVert];
}


//////////////////////////////////////////////////////////////////////////
// convert the UV coordinate to Cry UV array. Initializes vertices only.
void CMayaCryMeshUtil::initCryUVs()
{
	// Retrieve the number of UV vertices
	MStatus status;
	unsigned i, numUVs = m_fnMesh.numUVs (&status);
	if (!status)
	{
		std::cerr << "Failed to get number of UVs for " << m_fnMesh.name().asChar () << "\n";
		throw status;
	}

	// retrieve the UV vertices
	m_arrCryUVs.resize (numUVs);
	for (i = 0; i < numUVs; ++i)
	{
		CryUV& cryUV = m_arrCryUVs[i];
		status = m_fnMesh.getUV(i, cryUV.u, cryUV.v);
		if (!status)
		{
			std::cerr << "Failed to get UV #" << i << " for " << m_fnMesh.name().asChar () << "\n";
			throw status;
		}
	}
}

// flips all CryFaces
void CMayaCryMeshUtil::flipCryFaces ()
{
	for (CryFaceArray::iterator itCryFace = m_arrCryFaces.begin(); itCryFace != m_arrCryFaces.end(); ++itCryFace)
	{
		std::swap (itCryFace->v1, itCryFace->v2);
	}

//	for (CryVertexArray::iterator itCryVertex = m_arrCryVerts.begin(); itCryVertex != m_arrCryVerts.end(); ++itCryVertex)
//	{
//		itCryVertex->n = -itCryVertex->n;
//	}
}

// deletes unused UVs
void CMayaCryMeshUtil::compactCryUVs()
{
	if (m_arrCryTexFaces.empty() || m_arrCryUVs.empty()
	 || !ValidateCryUVs (&m_arrCryTexFaces[0], m_arrCryTexFaces.size(), m_arrCryUVs.size()))
	{
		m_arrCryUVs.clear();
		m_arrCryTexFaces.clear();
		return;
	}

	unsigned numNewUVs = CompactCryUVs (&m_arrCryTexFaces[0], m_arrCryTexFaces.size(), &m_arrCryUVs[0], m_arrCryUVs.size());
	assert (numNewUVs <= m_arrCryUVs.size());
	m_arrCryUVs.resize (numNewUVs);
}

// deletes ununsed vertices
void CMayaCryMeshUtil::compactCryVerts()
{
	if (!ValidateCryVerts (&m_arrCryFaces[0], m_arrCryFaces.size(), m_arrCryVerts.size()))
	{
		m_arrCryVerts.clear();
		m_arrCryFaces.clear();
		Log ("*ERROR* Cannot export mesh %s: it is invalid", m_pathMesh.fullPathName().asChar());
		return;
	}

	unsigned numNewVerts = CompactCryVerts (&m_arrCryFaces[0], m_arrCryFaces.size(), &m_arrCryVerts[0], m_arrCryVerts.size());
	assert (numNewVerts <= m_arrCryVerts.size());
	m_arrCryVerts.resize (numNewVerts);
}
