// MESHDumpMeshTEP.cpp: implementation of the CMESHDumpMeshTEP class.
//
//////////////////////////////////////////////////////////////////////

#include "MESHDumpMeshTEP.h"

//////////////////////////////////////////////////////////////////////
// Construction/Destruction
//////////////////////////////////////////////////////////////////////

#include "..\..\Rasterizer\MathLibrary.h"

#include "MESHExportClass.h"

CMESHDumpMeshTEP::CMESHDumpMeshTEP()
{

}

CMESHDumpMeshTEP::~CMESHDumpMeshTEP()
{

}

// Return a pointer to a TriObject given an INode or return NULL
// if the node cannot be converted to a TriObject
TriObject* CMESHDumpMeshTEP::GetTriObjectFromNode(INode* lpNode, TimeValue t, int &deleteIt)
{
	deleteIt = FALSE;

	Object* pObject = lpNode->EvalWorldState(t).obj;
	if (pObject->CanConvertToType(Class_ID(TRIOBJ_CLASS_ID, 0)))
	{ 
		TriObject *pTriObject = (TriObject *) pObject->ConvertToType(t, Class_ID(TRIOBJ_CLASS_ID, 0));
		// Note that the TriObject should only be deleted
		// if the pointer to it is not equal to the object
		// pointer that called ConvertToType()
		if (pObject != pTriObject) 
			deleteIt = TRUE;

		return pTriObject;
	}
	else
		return NULL;
}

int CMESHDumpMeshTEP::callback(INode *lpNode)
{
	// clear physique export parameters
	m_mcExport = NULL;
	m_phyExport = NULL;
    m_phyMod = NULL;

	INode* pNode = lpNode; // Hungarian
	if (pNode->IsRootNode())
		return TREE_ABORT;

	if (::FNodeMarkedToSkip(pNode))
		return TREE_CONTINUE;

	int nNode = ::GetIndexOfINode(pNode);
	TSTR strNodeName(pNode->GetName());

	// Helper nodes don't have meshes
	Object*	pObject = pNode->GetObjectRef();
	if (pObject->SuperClassID() == HELPER_CLASS_ID)
		return TREE_CONTINUE;

	// The model's root is a child of the real "scene root"
	INode* pParentNode = pNode->GetParentNode();
	BOOL bRootNode = pParentNode->IsRootNode();

	// Get node's material: should be a multi/sub (if it has a material at all)
	Mtl* pMtl = pNode->GetMtl();
	if (!pMtl)
	{
		return TREE_CONTINUE;
	}
	else if (!(pMtl->ClassID() == Class_ID(MULTI_CLASS_ID, 0) && pMtl->IsMultiMtl()))
	{
		return TREE_CONTINUE;
	}


	///////////////////////////////////////////////////////////////////////////////////////////////

	// Get Node's object, convert to a triangle-mesh object, so I can access the Faces
	ObjectState os = pNode->EvalWorldState(m_tvToDump);
	pObject = os.obj;

	BOOL needDel;
	TriObject* pTriObject = GetTriObjectFromNode(pNode, m_tvToDump, needDel);
	if (!pTriObject)
		return TREE_CONTINUE;

	// Shouldn't have gotten this far if it's a helper object
	if (pObject->SuperClassID() == HELPER_CLASS_ID)
		return TREE_ABORT;

	Mesh* pMesh = &pTriObject->mesh;

	// Ensure that the vertex normals are up-to-date
	pMesh->buildNormals();
	
	// We want the vertex coordinates in World-space, not object-space
	Matrix3 mat3ObjectTM = pNode->GetObjectTM(m_tvToDump);

	BOOL negScale = TMNegParity(mat3ObjectTM);
	int vx1, vx2, vx3;
	
	// Order of the vertices. Get 'em counter clockwise if the objects is
	// negatively scaled.
	if (negScale) 
	{
		vx1 = 2;
		vx2 = 1;
		vx3 = 0;
	}
	else 
	{
		vx1 = 0;
		vx2 = 1;
		vx3 = 2;
	}


	// initialize physique export parameters
	m_phyMod = FindPhysiqueModifier(pNode);
	if (m_phyMod)
	{
		// Physique Modifier exists for given Node
		m_phyExport = (IPhysiqueExport*) m_phyMod->GetInterface(I_PHYINTERFACE);
		if (m_phyExport)
		{
			// create a ModContext Export Interface for the specific node of the Physique Modifier
			m_mcExport = (IPhyContextExport*) m_phyExport->GetContextInterface(pNode);
			if (m_mcExport)
			{
				// convert all vertices to Rigid 
				m_mcExport->ConvertToRigid(TRUE);
			}
		}
	}

	// Get Node's time-zero Transformation Matrices
	Matrix3 mat3NodeTM	 = pNode->GetNodeTM(0);
	Matrix3 mat3ParentTM = pParentNode->GetNodeTM(0);

	mat3NodeTM.NoScale();		// Clear these out because they apparently
	mat3ParentTM.NoScale();		// screw up the following calculation.

	Matrix3 mat3NodeLocalTM	= mat3NodeTM * Inverse(mat3ParentTM);
	
	Point3 rowTrans = mat3NodeLocalTM.GetTrans();

	// Get the rotation (via decomposition into "affine parts", then quaternion-to-Euler)
	// Apparently the order of rotations returned by QuatToEuler() is X, then Y, then Z.
	AffineParts affparts;
	float rgflXYZRotations[3];

	decomp_affine(mat3NodeLocalTM, &affparts);
	QuatToEuler(affparts.q, rgflXYZRotations);

	float xRot = rgflXYZRotations[0];		// in radians
	float yRot = rgflXYZRotations[1];		// in radians
	float zRot = rgflXYZRotations[2];		// in radians

	///////////////////////////////////////////////////////////////////////////////////////////////
	VECTOR3D translate, rotate;

	// Get rotations in the -2pi...2pi range
	// convert to degrees
	rotate.x = ::FlReduceRotation(xRot) * (180.0f / Q_PI);
	rotate.y = ::FlReduceRotation(yRot) * (180.0f / Q_PI);
	rotate.z = ::FlReduceRotation(zRot) * (180.0f / Q_PI);

	translate.x = rowTrans.x;
	translate.y = rowTrans.y;
	translate.z = rowTrans.z;

	CMESHExportClass* pExportClass = (CMESHExportClass*) m_pExportClass;

	CMaxMesh* pMaxMesh = pExportClass->GetMeshFrame()->LookupMesh(strNodeName);
	if (!pMaxMesh)
		return TREE_ABORT;

	// Calc Rotation Matrices
	ClipRotations(&rotate);
	AngleIMatrix(&rotate, &pMaxMesh->m_im);

	// Quaternion
	AngleQuaternion(&rotate, &pMaxMesh->m_quaternion);

	// Translate
	CopyVector(&translate, &pMaxMesh->m_translate);

	
	// Dump the triangle face info
	int nNumFaces = pMesh->getNumFaces();
	for (int i=0; i<nNumFaces; i++)
	{
		Face*	pFace		= &pMesh->faces[i];
		TVFace*	pTVFace		= &pMesh->tvFace[i];
		DWORD	smGroupFace	= pFace->getSmGroup();
		
		// Get face's 3 indexes into the Mesh's vertex array(s).
		DWORD dwVertex0 = pFace->getVert(vx1);
		DWORD dwVertex1 = pFace->getVert(vx2);
		DWORD dwVertex2 = pFace->getVert(vx3);

		// Get the 3 Vertex's for this face
		Point3 pt3Vertex0 = pMesh->getVert(dwVertex0);
		Point3 pt3Vertex1 = pMesh->getVert(dwVertex1);
		Point3 pt3Vertex2 = pMesh->getVert(dwVertex2);

		// Get the 3 RVertex's for this face
		// NOTE: I'm using getRVertPtr instead of getRVert to work around a 3DSMax bug
		RVertex* pRVertex0 = pMesh->getRVertPtr(dwVertex0);
		RVertex* pRVertex1 = pMesh->getRVertPtr(dwVertex1);
		RVertex* pRVertex2 = pMesh->getRVertPtr(dwVertex2);
		
		// Find appropriate normals for each RVertex
		// A vertex can be part of multiple faces, so the "smoothing group"
		// is used to locate the normal for this face's use of the vertex.
		Point3 pt3Vertex0Normal;
		Point3 pt3Vertex1Normal;
		Point3 pt3Vertex2Normal;
		if (smGroupFace) 
		{
			pt3Vertex0Normal = Pt3GetRVertexNormal(pRVertex0, smGroupFace);
			pt3Vertex1Normal = Pt3GetRVertexNormal(pRVertex1, smGroupFace);
			pt3Vertex2Normal = Pt3GetRVertexNormal(pRVertex2, smGroupFace);
		}
		else 
		{
			pt3Vertex0Normal = pMesh->getFaceNormal(i);
			pt3Vertex1Normal = pMesh->getFaceNormal(i);
			pt3Vertex2Normal = pMesh->getFaceNormal(i);
		}

		// Get Face's sub-material from node's material, to get the bitmap name.
		// And no, there isn't a simpler way to get the bitmap name, you have to
		// dig down through all these levels.
		MtlID mtlId = pFace->getMatID();
		if (mtlId >= pMtl->NumSubMtls())
			mtlId = 0;

		Mtl* pSubMtl = pMtl->GetSubMtl(mtlId);
		if ((pSubMtl->ClassID() == Class_ID(MULTI_CLASS_ID, 0) && pSubMtl->IsMultiMtl()))
		{
			// it's a sub-sub material.  Gads.
			pSubMtl = pSubMtl->GetSubMtl(mtlId);
		}

		// Copy Material Name
		CMaxMaterial material;
		if (!material.Create(pSubMtl->GetName()))
			return TREE_ABORT;
		
		material.m_ambient.red   = pSubMtl->GetAmbient().r;
		material.m_ambient.green = pSubMtl->GetAmbient().g;
		material.m_ambient.blue  = pSubMtl->GetAmbient().b;
		material.m_ambient.alpha = 1.0f;

		material.m_diffuse.red   = pSubMtl->GetDiffuse().r;
		material.m_diffuse.green = pSubMtl->GetDiffuse().g;
		material.m_diffuse.blue  = pSubMtl->GetDiffuse().b;
		material.m_diffuse.alpha = 1.0f;

		material.m_specular.red   = pSubMtl->GetSpecular().r;
		material.m_specular.green = pSubMtl->GetSpecular().g;
		material.m_specular.blue  = pSubMtl->GetSpecular().b;
		material.m_specular.alpha = 1.0f;

		material.m_shininess = pSubMtl->GetShininess() * 1.28f;
		
		if (pSubMtl->ClassID() == Class_ID(DMTL_CLASS_ID, 0))
		{
			StdMat* pStdMtl = (StdMat*) pSubMtl;

			Texmap* pTexmap = pStdMtl->GetSubTexmap(ID_DI);
			if (pTexmap != NULL) 
			{
				if (pTexmap->ClassID() == Class_ID(BMTEX_CLASS_ID, 0))
				{
					char szBitmapName[256];

					BitmapTex* pBitmapTex = (BitmapTex*) pTexmap;
					strcpy(szBitmapName, pBitmapTex->GetMapName());

					TSTR strPath, strFile;
					SplitPathFile(TSTR(szBitmapName), &strPath, &strFile);
					strcpy(szBitmapName, strFile);

					// trim ext ".ext"
					char* ptr = strchr(szBitmapName, '.');
					*ptr = 0;

					material.SelectTexture(szBitmapName);
				}
			}
		}

		if (!pMaxMesh->LookupMaterial(&material))
			return TREE_ABORT;

		material.Destroy();

		// All faces must have textures assigned to them
		UVVert UVvertex0(0, 0, 0);
		UVVert UVvertex1(1, 0, 0);
		UVVert UVvertex2(0, 1, 0);

		if (pFace->flags & HAS_TVERTS)
		{
			// Get TVface's 3 indexes into the Mesh's TVertex array(s).
			DWORD dwTVertex0 = pTVFace->getTVert(vx1);
			DWORD dwTVertex1 = pTVFace->getTVert(vx2);
			DWORD dwTVertex2 = pTVFace->getTVert(vx3);

			// Get the 3 TVertex's for this TVFace
			// NOTE: I'm using getRVertPtr instead of getRVert to work around a 3DSMax bug
			UVvertex0 = pMesh->getTVert(dwTVertex0);
			UVvertex1 = pMesh->getTVert(dwTVertex1);
			UVvertex2 = pMesh->getTVert(dwTVertex2);
		}

		// Determine owning bones for the vertices.
		nNode = ::GetIndexOfINode(pNode);
		int nNodeV0, nNodeV1, nNodeV2;

		if (m_mcExport)
		{
			// The Physique add-in allows vertices to be assigned to bones arbitrarily
			nNodeV0 = INodeOfPhysiqueNode(dwVertex0);
			nNodeV1 = INodeOfPhysiqueNode(dwVertex1);
			nNodeV2 = INodeOfPhysiqueNode(dwVertex2);
		}
		else
		{
			// Simple 3dsMax model: the vertices are owned by the object, and hence the node
			nNodeV0 = nNode;
			nNodeV1 = nNode;
			nNodeV2 = nNode;
		}

		// Rotate the face vertices out of object-space, and into world-space space
		Point3 v0 = pt3Vertex0 * mat3ObjectTM;
		Point3 v1 = pt3Vertex1 * mat3ObjectTM;
		Point3 v2 = pt3Vertex2 * mat3ObjectTM;

		Matrix3 mat3ObjectNTM = mat3ObjectTM;
		mat3ObjectNTM.NoScale();

		pt3Vertex0Normal = VectorTransform(mat3ObjectNTM, pt3Vertex0Normal);
		pt3Vertex1Normal = VectorTransform(mat3ObjectNTM, pt3Vertex1Normal);
		pt3Vertex2Normal = VectorTransform(mat3ObjectNTM, pt3Vertex2Normal);

		///////////////////////////////////////////////////////////////////////////////////////////

		VERTEX vertex;

		// vertex 2
		///////////////////////////////////////////////////////////////////////////////////////////
		vertex.tu = UVvertex2.x;
		vertex.tv = UVvertex2.y;

		vertex.nx = pt3Vertex2Normal.x;
		vertex.ny = pt3Vertex2Normal.y;
		vertex.nz = pt3Vertex2Normal.z;

		vertex.x = v2.x;
		vertex.y = v2.y;
		vertex.z = v2.z;

		//pMaxMesh->LookupVertex(&vertex);
		if (!LookupVertex(pMaxMesh, &vertex, nNodeV2))
			return TREE_ABORT;

		// vertex 1
		///////////////////////////////////////////////////////////////////////////////////////////
		vertex.tu = UVvertex1.x;
		vertex.tv = UVvertex1.y;

		vertex.nx = pt3Vertex1Normal.x;
		vertex.ny = pt3Vertex1Normal.y;
		vertex.nz = pt3Vertex1Normal.z;

		vertex.x = v1.x;
		vertex.y = v1.y;
		vertex.z = v1.z;

		//pMaxMesh->LookupVertex(&vertex);
		if (!LookupVertex(pMaxMesh, &vertex, nNodeV1))
			return TREE_ABORT;

		// vertex 0
		///////////////////////////////////////////////////////////////////////////////////////////
		vertex.tu = UVvertex0.x;
		vertex.tv = UVvertex0.y;

		vertex.nx = pt3Vertex0Normal.x;
		vertex.ny = pt3Vertex0Normal.y;
		vertex.nz = pt3Vertex0Normal.z;

		vertex.x = v0.x;
		vertex.y = v0.y;
		vertex.z = v0.z;

		//pMaxMesh->LookupVertex(&vertex);
		if (!LookupVertex(pMaxMesh, &vertex, nNodeV0))
			return TREE_ABORT;
	}

	// cleanup
	if (m_phyMod && m_phyExport)
	{
		if (m_mcExport)
		{
			m_phyExport->ReleaseContextInterface(m_mcExport);
			m_mcExport = NULL;
        }
        
		m_phyMod->ReleaseInterface(I_PHYINTERFACE, m_phyExport);
		m_phyMod = NULL;
		
		m_phyExport = NULL;
	}

	if (needDel)
		delete pTriObject;

	return TREE_CONTINUE;
}

bool CMESHDumpMeshTEP::LookupVertex(CMaxMesh *lpMesh, LPVERTEX lpVertex, int nNode)
{
	CMESHExportClass* pExportClass = (CMESHExportClass*) m_pExportClass;

	LPKEYNODEFIXUP pKeyNodeFixup = pExportClass->GetMeshFrame()->GetKeyNodeFixup(nNode);
	if (!pKeyNodeFixup)
		return false;

	VECTOR3D v1, v2, vector, normal;

	v1.x = lpVertex->x;
	v1.y = lpVertex->y;
	v1.z = lpVertex->z;

	// move vertex position to object space.
	SubtractVector(&v1, &pKeyNodeFixup->world, &v2);
	TransformVector(&v2, &pKeyNodeFixup->im, &vector);

	// Copy Vector
	lpVertex->x = vector.x;
	lpVertex->y = vector.y;
	lpVertex->z = vector.z;


	v1.x = lpVertex->nx;
	v1.y = lpVertex->ny;
	v1.z = lpVertex->nz;

	// move normal to object space.
	TransformVector(&v1, &pKeyNodeFixup->im, &normal);
	NormalizeVector(&normal);

	// Copy Normal
	lpVertex->nx = normal.x;
	lpVertex->ny = normal.y;
	lpVertex->nz = normal.z;

	lpMesh->LookupVertex(lpVertex, nNode);

	return true;
}

int CMESHDumpMeshTEP::INodeOfPhysiqueNode(DWORD dwVertex)
{
	int	nNode = 0;

	IPhyVertexExport* vtxExport = m_mcExport->GetVertexInterface(dwVertex);
	if (vtxExport)
	{
		//need to check if vertex has blending
		if (vtxExport->GetVertexType() & BLENDED_TYPE)
		{
			//
			IPhyBlendedRigidVertex *vtxBlend = (IPhyBlendedRigidVertex *) vtxExport;
			
			INode* pNode = vtxBlend->GetNode(0);
			nNode = GetIndexOfINode(pNode);

	        //weight = vtxBlend->GetWeight(n);
		}
		else 
		{
			IPhyRigidVertex* vtxNoBlend = (IPhyRigidVertex *) vtxExport;

			INode* pNode = vtxNoBlend->GetNode();
			nNode = GetIndexOfINode(pNode);

			//weight = 1.0f;
		}

		m_mcExport->ReleaseVertexInterface(vtxExport);
	}

	return nNode;
}
