/*********************************************/
/*     BG3D EXPORTER FOR MAYA API			 */
/*       		   Maya.cpp                  */
/* 		(c)2002 Pangea Software  			 */
/* 			By Brian Greenstone      		 */
/*********************************************/


#include "3dmath.h"


extern	int					gNumMaterials, gCurrentLayerNum;
extern	Boolean				gUseVertexColors;
extern	MOMaterialObject	*gMaterials[MAX_SURFACES];
extern	int					gNumLayers;
extern	LayerInfoType		gLayerInfoList[MAX_LAYERS];

/************************/
/*      PROTOTYPES      */
/************************/

MStatus initializePlugin(MObject obj);
MStatus uninitializePlugin(MObject obj);

static int	Maya_ExtractMaterialFromShader(MObject shaderObj);
static Boolean	Maya_IsMeshLayerVisible(Str255	meshName);



/************************/
/*      VARIABLES       */
/************************/


static MObject	gShaderMObjects[MAX_SURFACES];

/************************/
/*      CONSTANTS       */
/************************/

#define kQuietFlag		"-q"
#define kQuietFlagLong	"-quiet"


/********************* INTERFACE PLUGIN WITH MAYA ************************/


///////////////////////////////////////////////////
//
// Command class declaration
//
///////////////////////////////////////////////////

class bg3d : public MPxCommand
{
public:
					bg3d();
	virtual			~bg3d();
	
	MStatus			doIt(const MArgList& args);
	static MSyntax	newSyntax();
	static void*	creator();
		
private:
	void			printType( const MObject& node, const MString& prefix );
	bool			quiet;
		
};



///////////////////////////////////////////////////
//
// Command class implementation
//
///////////////////////////////////////////////////


bg3d::bg3d()
	: quiet (false)
{}

bg3d::~bg3d()
{}


MSyntax bg3d::newSyntax()
{
	 MSyntax syntax;
	 syntax.addFlag(kQuietFlag, kQuietFlagLong);
	 return syntax;
}



void* bg3d::creator()
{
	return new bg3d;
}


/////////////////////////////////////////////////////////////////
//
// The following routines are used to register/unregister
// the command we are creating within Maya
//
/////////////////////////////////////////////////////////////////


MStatus initializePlugin(MObject obj)
{
	MStatus		status;
	MFnPlugin	plugin( obj, "Alias", "3.0", "Any" );


	status = plugin.registerCommand( "bg3d",
									bg3d::creator,
									bg3d::newSyntax );
	if (!status)
	{
		status.perror("registerCommand");
		return status;
	}

	return status;
}


MStatus uninitializePlugin(MObject obj)
{
	MStatus	  status;
	MFnPlugin plugin( obj );

	status = plugin.deregisterCommand( "bg3d" );
	if (!status) {
		status.perror("deregisterCommand");
		return status;
	}

	return status;
}






/******************** DOIT *************************/
//
// This is the function that Maya calls when the plugin is called
//

MStatus bg3d::doIt( const MArgList& args )
{
#pragma unused (args) 

         	/* ISSUE COMMAND TO TRIANGULATE EVERYTHING */

	MString cmd = "polyCleanupArgList 3 { \"1\",\"1\",\"1\",\"1\",\"1\",\"1\",\"1\",\"1\",\"1\",\"1e-05\",\"0\",\"1e-05\",\"0\",\"1e-05\",\"0\",\"-1\",\"0\" };";
	MGlobal::executeCommand(cmd, true, true);
	MGlobal::executeCommand(cmd, true, true);			// do it twice since sometimes it needs it.


			/* PROCESS PLUGIN */
			
	MayaDisplayMessage("Calling PluginEntry()");
	PluginEntry();


         	/* ISSUE COMMAND TO UNDO TRIANGULATE */

	MString cmd2 = "Undo";	
	MGlobal::executeCommand(cmd2, true, true);
	MGlobal::executeCommand(cmd2, true, true);


	return MS::kSuccess;
}


#pragma mark -



/************************* MAYA:  GET LAYERS INFO ***********************************/
//
// This scans all of the layers in the scene and builds our own database of information
// since Mays kinda sucks at getting this info easily later.
//

OSErr Maya_GetLayersInfo(void)
{
int				i, j;
MStatus 		stat;
bool			visible;
MStringArray 	layerMemberString;	         		

	gNumLayers = 0;									// init layer count


		/***********************************************************/
		/* SCAN THRU ALL DISPLAY LAYER DG NODES TO GET ATTRIB INFO */
		/***********************************************************/
		//
		// The DG node for each layer only seems to provide us with some
		// basic attribute information.
		//

			/* START ITERATION LOOKING ONLY FOR DISPLAY LAYERS */
				
	MItDependencyNodes dgIter(MFn::kDisplayLayer, &stat);		
	if (stat != MS::kSuccess)
	{
		MGlobal::displayError("Maya_GetLayersInfo: MItDependencyNodes dgIter(MFn::kDisplayLayer, &stat)");
		return(1);
	}
		
				/* ITERATE THRU ALL LAYERS */
			
	for ( ; !dgIter.isDone(); dgIter.next())
	{
		MObject 			dgItem = dgIter.item();						// get this layer's MObject		
		MFnDependencyNode 	fnNode(dgItem);								// get the dependency node of this Object

				/* GET THE LAYER'S NAME */				

		MString	layerName = fnNode.name();								// get layer name from the node
		c2pstrcpy(gLayerInfoList[gNumLayers].name, layerName.asChar());	// convert layer name to a pascal string that we can use


				/* SEE IF THIS LAYER IS VISIBLE */								

		MPlug visPlug = fnNode.findPlug("visibility", &stat);			// get visibility attribute of the node
		stat = visPlug.getValue(visible);								// get value of the attribute
		gLayerInfoList[gNumLayers].isVisible = visible;
		
		gNumLayers++;
	}


		/******************************************************************/
		/* FIND THE NAMES OF ALL OF THE OBJECTS THAT BELONG TO EACH LAYER */
		/******************************************************************/
		//
		// Apparently there's no way to find the objects that are in a Layer with the API.
		// So, we've got to issue a MEL command which will return the "names" of the objects
		// in each layer which we also have to identify by name.
		//


			/* SCAN EACH LAYER WE GOT ABOVE */
			
	for (i = 0; i < gNumLayers; i++)
	{
		char layerName[255];												
		long		numMembers;

         	/* ISSUE COMMAND TO GET DISPLAY LAYER MEMBERS */
         	//
         	// FYI:  this will return both the shapes and the transforms for each shape
         	//

		MString cmd = "editDisplayLayerMembers -q ";						// build command minus the layer's name		
		p2cstrcpy(layerName, gLayerInfoList[i].name);						// convert layer name to C string
		cmd += layerName;													// append layer name to MEL command
		
				/* GET NAMES OF OBJECTS IN THIS LAYER */
				
		stat = MGlobal::executeCommand(cmd, layerMemberString, false, false);
		if (stat != MS::kSuccess)
		{
			MGlobal::displayError("executeCommand:  editDisplayLayerMembers(layerMemberString) failed!");
			return(1);
		}
				
		numMembers = layerMemberString.length();							// count # of strings returned into the array
		gLayerInfoList[i].numMembers = numMembers;							// save # into our list

				/* SAVE THE NAMES OF ALL THOSE OBJECTS INTO OUR LIST */
				
		for (j = 0; j < numMembers; j++)
		{
			const char *memberName = layerMemberString[j].asChar();			// get C string name of the object in this layer
			c2pstrcpy(gLayerInfoList[i].memberNames[j], memberName);		// convert and save as Pascal string
		}	
	}
	
	
	return(noErr);
}


/*************************** MAYA: EXTRACT FACES AND MATERIALS *********************/

OSErr Maya_ExtractFacesAndMaterials(void)
{
MStatus 			stat;
int					faceNum, whichMaterial, i, q;
u_long				s;
OGLPoint3D			triCoords[3];
OGLVector3D			triNormals[3];
OGLTextureCoord		triUVs[3];
OGLColorRGBA_Byte	triColors[3];
static OGLVector3D	faceNormal;
MOTriangleIndecies  triInd;
Str255				meshName;
  

			/************************/
			/* SCAN THRU ALL MESHES */
			/************************/
			
	MItDag dagIter( MItDag::kBreadthFirst, MFn::kMesh, &stat );		// start iteration of only kMesh objects

	for ( ; !dagIter.isDone(); dagIter.next())
	{
		MDagPath dagPath;											// get path to this Mesh
		stat = dagIter.getPath( dagPath );
		if (stat != MS::kSuccess)
			continue;
     
     			/* VERIFY THAT IT'S REALLY A MESH */
     			
        MFnDagNode dagNode(dagPath, &stat);							// get dagNode for the Mesh

        if ( dagNode.isIntermediateObject())
        	continue;
        if ( !dagPath.hasFn( MFn::kMesh ))
        	continue;
        if ( dagPath.hasFn( MFn::kTransform ))
        	continue;

				
				/* GET THE MESH FUNCTIONS */
				
        MFnMesh fnMesh( dagPath );
        
        
        			/****************************************/
        			/* ONLY EXTRACT MESHES ON CURRENT LAYER */
        			/****************************************/
        			
		if (gCurrentLayerNum != -1)											// see if we care
        {
		    MString 	 meshPathMString = dagNode.partialPathName();		// get the name of this mesh
 			const char 	*meshPathNamePtr = meshPathMString.asChar();
        	c2pstrcpy(meshName, meshPathNamePtr);			

					/* IS THIS OBJECT A MEMBER OF THE CURRENT LAYER? */
					
			for (q = 0; q < gLayerInfoList[gCurrentLayerNum].numMembers; q++)
			{
				if (EqualString(meshName, gLayerInfoList[gCurrentLayerNum].memberNames[q], true, true))		// match?
					goto on_this_layer;
			} 
			
			continue;														// this mesh isn't on this layer, so skip it       
        }
        
        			/* SEE IF THIS MESH'S LAYER IS VISIBLE */
        else
        {        
		    MString 	 meshPathMString = dagNode.partialPathName();	// get the name of this mesh.  NOTE:  Sometimes maya returns the FULL pathname here even though we asked for a partial, so we've got to manually strip it off
			const char 	*meshPathNamePtr = meshPathMString.asChar();
			c2pstrcpy(meshName, meshPathNamePtr);

			if (!Maya_IsMeshLayerVisible(meshName))
				continue;
        }
        
on_this_layer:        


			/****************************************************/
			/* FIND ALL THE SURFACE SHADERS THAT THIS MESH USES */
			/****************************************************/
        	//
        	// Get all the nodes connected to this mesh, whether they are on the DAG or not.
        	// This will include all the materials.
        	//	
        
        unsigned instanceNumber = dagPath.instanceNumber();
        
		MObjectArray	sets;
		MObjectArray	comps;		
		
				/* ITERATE OVER ALL THE CONNECTED SETS AND LOOK FOR MATERIALS */
				
		fnMesh.getConnectedSetsAndMembers(instanceNumber, sets, comps, true);
		for (s = 0; s < sets.length(); s++)
		{
			MObject	set = sets[s];
			MObject comp = comps[s];

			MFnSet				fnSet(set);
			MFnDependencyNode	dnSet(set);

			MObject				ssAttr = dnSet.attribute(MString("surfaceShader"));
			MPlug				ssPlug(set, ssAttr);
			
			MPlugArray			srcPlugArray;
			ssPlug.connectedTo(srcPlugArray, true, false);
			
			if (srcPlugArray.length() == 0)
				continue;

			MObject	shaderObject = srcPlugArray[0].node();					// get the shader Object, weee!
									
			whichMaterial = Maya_ExtractMaterialFromShader(shaderObject);	// this will create the new material if needed or just return an index into our MO material list
			if (whichMaterial == -1)										// see if something went wrong
				return(1);


					/*********************************************************/
					/* LOOP THRU EACH FACE OF THE MESH THAT THIS SHADER USES */
					/*********************************************************/
					
	       MItMeshPolygon piter(dagPath, comp);							// start iterating thru mesh polygons
	       for (faceNum = 0 ; !piter.isDone(); piter.next(), faceNum++)	// NOTE: faceNum also serves as an index into the shaderObjects and shaderIndecies arrays
	       {						
				unsigned numPoints = piter.polygonVertexCount();			// verify that this was a triangle
				if (numPoints != 3)
				{
					MGlobal::displayError("Maya_ExtractFacesAndMaterials found a non-triangle face!");
					return(1);
				}
	       		
	       				/* GET VERTEX INDECIES */
	       						
				triInd.vertexIndices[0] = piter.vertexIndex(0);	
				triInd.vertexIndices[1] = piter.vertexIndex(1);
				triInd.vertexIndices[2] = piter.vertexIndex(2);

				
						/* GET POINTS */
						//
						// NOTE:  We divide each xyz coord by 100.0 to convert millimeters to meters.
						//		I do this because when I'm working in Maya I tend to be in Meter mode where
						//		coordinates are expresses as meters.  But in the plug-in architecture, everything
						//		is in millimeters, so if a vertex has a coordinate of 45.0 meters in Maya, the API will report 4500.0.
						//		So, if you're working in millimeters in Maya then you don't need the divide by 100.
						//
						
				for (i = 0; i < 3; i++)
				{
					MPoint	mpoint = piter.point(i, MSpace::kWorld);
					float	pointtemp[4];
					mpoint.get(pointtemp);
					triCoords[i].x = pointtemp[0] / 100.0f;		
					triCoords[i].y = pointtemp[1] / 100.0f;
					triCoords[i].z = pointtemp[2] / 100.0f;			
				}				


						/* GET NORMALS */
						
				for (i = 0; i < 3; i++)
				{
					MVector	mvec;
					piter.getNormal(i, mvec, MSpace::kWorld);
					double	normaltemp[3];
					mvec.get(normaltemp);
					triNormals[i].x = normaltemp[0];
					triNormals[i].y = normaltemp[1];
					triNormals[i].z = normaltemp[2];
					
					OGLVector3D_Normalize(&triNormals[i], &triNormals[i]);			// make sure it's normalized
				}				

						/* GET UVs */
						
				for (i = 0; i < 3; i++)
				{
					float2 tempUV;
					piter.getUV(i, tempUV);
					triUVs[i].u = tempUV[0];
					triUVs[i].v = tempUV[1];
				}				


						/* GET VERTEX COLORS */
						
				if (gUseVertexColors)
				{
					for (i = 0; i < 3; i++)
					{
						if (piter.hasColor(i, &stat))				// does this vertex have a color?
						{					
							MColor	mcolor;
							piter.getColor(mcolor, i);						// get color for this vertex

							triColors[i].r = (GLubyte)(mcolor.r * 255.0f);	// convert to Byte colors since those are smaller and faster
							triColors[i].g = (GLubyte)(mcolor.g * 255.0f);
							triColors[i].b = (GLubyte)(mcolor.b * 255.0f);
							triColors[i].a = (GLubyte)(mcolor.a * 255.0f);	
						}
						else										// no color assigned to this vertex so just give it 1,1,1,1
						{
							triColors[i].r = 						// convert to Byte colors since those are smaller and faster
							triColors[i].g = 
							triColors[i].b =
							triColors[i].a = 255;
						}
					}				
				}


						/* GET FACE NORMAL */
						
				double	normaltemp[3];
				MVector	mayaFaceNormal;
				stat = piter.getNormal(mayaFaceNormal, MSpace::kWorld);
				mayaFaceNormal.get(normaltemp);
				faceNormal.x = normaltemp[0];
				faceNormal.y = normaltemp[1];
				faceNormal.z = normaltemp[2];	
				OGLVector3D_Normalize(&faceNormal, &faceNormal);		


						/* ADD THE TRIANGLE */
						//
						// We don't use the triangle indecies which determine the ordering of the vertex data.
						// Instead, the AddTriangle call will figure it out based on the direction of the vertex
						// normals - it's easier this way than trying to re-order the vertex data here.
						//

				AddTriangleToBin(whichMaterial, triCoords, &triNormals[0], &triUVs[0], &triColors[0], &faceNormal);
			}

       }					
	}
	
	
	return(noErr);
}

#if 0
/********************** FIX PATH STRING **************************/
//
// This checks the meshName to see if it's actually a FULL path instead of a partial path.
// We do this because sometimes Maya returns the full path when we do a dagNode.partialPathName() call.
// I think this is a bug in Maya.
//

static void FixPathString(u_char *meshName)
{
int	n = meshName[0];							// get length of string
int	i,j;

	for (i = n; i > 0; i--)						// scan from the end of the string to the front, looking for the | pathname separator
	{
		if (meshName[i] == '|')					// did we find a path separator?
		{
			meshName[0] = (n -= i);				// shorten string by i characters
			
			for (j = 1; j <= n; j++)				// shift the characters down
				meshName[j] = meshName[i+j];

			return;
		}
	}
}
#endif


/************************ MAYA:  EXTRACT MATERIAL FROM SHADER ******************************/

static int	Maya_ExtractMaterialFromShader(MObject shaderObj)
{
int					i,m;
MOMaterialData		matData;
float				tempColor[3];
MColor				diffuseColor;
MColor				transColor;
MStatus 			status;
Boolean				hasTexture = false;

				/* SEE IF WE ALREADY HAVE THIS SHADER'S MATERIAL */
				
	for (i = 0; i < gNumMaterials; i++)
	{
		if (gShaderMObjects[i] == shaderObj)					// does this match?
		{
			return(i);											// return index into our material list
		}
	}

			/**************************************************/
			/* THIS IS A UNIQUE SHADER, SO LET'S DEAL WITH IT */
			/**************************************************/

				/* ADD SHADER OBJ TO OUR LIST */
				
	m = gNumMaterials;
	gShaderMObjects[m] = shaderObj;
	MFn::Type	apiType = shaderObj.apiType();

				/* DEFAULT SETUP */
			
	matData.setupInfo 		= nil;
	matData.flags 			= 0;
	matData.hasTexture		= false;
	matData.texturePixels	= nil;
				

				/***********************/
				/* EXTRACT TEXTURE MAP */
				/***********************/
	
	MPlug colorPlug = MFnDependencyNode(shaderObj).findPlug("color", &status);
	if (status != MS::kFailure)
	{
		MItDependencyGraph dgIt(colorPlug, MFn::kFileTexture, MItDependencyGraph::kUpstream, MItDependencyGraph::kBreadthFirst,
							   MItDependencyGraph::kNodeLevel, &status);
	
		if (status != MS::kFailure)
		{
			dgIt.disablePruningOnFilter();
		
			if (!dgIt.isDone())										// true == no texture
			{
				const 	char *filePath;
				FSSpec	spec;
				OSErr	iErr;
				Str255	hfsPath;
				
				MObject textureNode = dgIt.thisNode();
		        MPlug filenamePlug = MFnDependencyNode(textureNode).findPlug("fileTextureName");

		        MString textureName;
		        filenamePlug.getValue(textureName);
								
							/* GET FSSPEC OF FILE */
							
				filePath = textureName.asChar();					// get UNIX file path
		
				char	newPath[500];				
				strcpy (newPath, filePath);
				ConvertFileRepresentation(newPath, kCFURLPOSIXPathStyle, kCFURLHFSPathStyle);	// convert newPath to Mac HFS format
				c2pstrcpy(hfsPath, newPath);													// convert name to a pascal string that we can use
				
				iErr = FSMakeFSSpec(0,0, hfsPath, &spec);			// get FSSpec
				if (iErr)
				{
					MGlobal::displayError("Maya_ExtractMaterialFromShader:  FSMakeFSSpec failed!");
					return(-1);
				}

						/* GET THE FILE AND DEAL WITH IT */
						
				GetTextureMap(m, &spec, &matData);

				hasTexture = true;
			}
		}	
	}



	switch(apiType)
	{
				/************************/
				/* EXTRACT PHONG SHADER */
				/************************/
	
	
		case	MFn::kPhong:
				{
				MFnPhongShader	phong(shaderObj);								// get functions for phone shaders
			


							/* GET DIFFUSE COLOR */
						
				if (!hasTexture)												// in Maya, the textures override diffuse color, so assume white
				{	
					diffuseColor 	= phong.color() * phong.diffuseCoeff();	
					diffuseColor.get(tempColor);
					matData.diffuseColor.r = tempColor[0];
					matData.diffuseColor.g = tempColor[1];
					matData.diffuseColor.b = tempColor[2];
				}
				else
				{
					matData.diffuseColor.r = 
					matData.diffuseColor.g = 
					matData.diffuseColor.b = 1.0f;
				}
				
			
							/* GET TRANSPARENCY */
							
				transColor 		= phong.transparency();							// get transparency "color"
				matData.diffuseColor.a = 1.0f - transColor.r;					// (assume all components of transparency color are the same)

//				transColor.get(tempColor);
//				matData.diffuseColor.a = 1.0f - tempColor[0];					// (assume all components of transparency color are the same)

				}
				break;
				
				/**************************/
				/* EXTRACT LAMBERT SHADER */
				/**************************/
				
		case	MFn::kLambert:
				{
				MFnLambertShader	lambert(shaderObj);							// get functions for phone shaders
			
							/* GET DIFFUSE COLOR */
							
				if (!hasTexture)												// in Maya, the textures override diffuse color, so assume white
				{	
					diffuseColor 	= lambert.color() * lambert.diffuseCoeff();	
					diffuseColor.get(tempColor);
					matData.diffuseColor.r = tempColor[0];
					matData.diffuseColor.g = tempColor[1];
					matData.diffuseColor.b = tempColor[2];
				}
				else
				{
					matData.diffuseColor.r = 
					matData.diffuseColor.g = 
					matData.diffuseColor.b = 1.0f;
				}

			
							/* GET TRANSPARENCY */
							
				transColor 		= lambert.transparency();						// get transparency "color"
				matData.diffuseColor.a = 1.0f - transColor.r;					// (assume all components of transparency color are the same)

//				transColor.get(tempColor);
//				matData.diffuseColor.a = 1.0f - tempColor[0];					// (assume all components of transparency color are the same)

				}
				break;
				
	
				/***************************/
				/* UNSUPPORTED SHADER TYPE */
				/***************************/

		default:
				MGlobal::displayError("Maya_ExtractMaterialFromShader unsupported shader type");
				return(-1);			
	}




			/**************************************************************/
			/* CREATE THE MATERIAL OBJECT FROM THE DATA WE'VE ACCUMULATED */
			/**************************************************************/
				
	gMaterials[i] = (MOMaterialObject *) MO_CreateNewObjectOfType(MO_TYPE_MATERIAL, 0, &matData);


	gNumMaterials++;
	return(m);
	
}


/********************* MAYA: GET CURRENT FILENAME *****************************/

void Maya_GetCurrentFileName(Str255 filename)
{
MFileIO		file;
MString		ss;
const char		*c;
char		newPath[500];				

	ss = file.currentFile();
	
	c = ss.asChar();	
	strcpy (newPath, c);
	ConvertFileRepresentation(newPath, kCFURLPOSIXPathStyle, kCFURLHFSPathStyle);	// convert newPath to Mac HFS format

	CopyCStringToPascal(newPath, filename);				// convert to Pascal string

}


/************************** IS MESH'S LAYER VISIBLE ******************************/

static Boolean	Maya_IsMeshLayerVisible(Str255	meshName)
{
int			layerNum,q;

	
			/* SEE WHICH LAYER THIS MESH IS IN */
			
	for (layerNum = 0; layerNum < gNumLayers; layerNum++)										// scan each layer
	{
		for (q = 0; q < gLayerInfoList[layerNum].numMembers; q++)								// scan each member of this layer
		{
			if (EqualString(meshName, gLayerInfoList[layerNum].memberNames[q], true, true))		// match?
			{
				return (gLayerInfoList[layerNum].isVisible);
			}
		} 
	}

	MGlobal::displayError("Maya_IsMeshLayerVisible should not get here!");
	return(false);								// it should never get here!
}


#pragma mark -

/******************** MAYA DISPLAY MESSAGE ********************/

void MayaDisplayMessage(char *s)
{
	MString ss = s;	
	MGlobal::displayInfo(ss);
}


/******************** MAYA DISPLAY ERROR ********************/

void MayaDisplayError(char *s)
{
	MString ss = s;	
	MGlobal::displayError(ss);
}


