/*********************************************/
/*     BG3D EXPORTER FOR MAYA PLUG-IN  		 */
/*       	  Decompose.c                    */
/* 		(c)2002 Pangea Software  			 */
/* 			By Brian Greenstone      		 */
/*********************************************/

#include "3dmath.h"


extern	Boolean		gExportLayersSeparate,gUseVertexColors;
extern	OGLSetupOutputType	*gPreviewContext;
extern	float			gPreviewScale;
extern	FSSpec			gExportSpec;


#if DEBUG
extern	WindowPtr	gDebugWindow;
#endif



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

static void InitBins(void);
static void EmptyBins(void);
static void ConvertBinsToVertexArrays(void);
static void *ReadImageIntoBuffer(GWorldPtr gworld, int width, int height);


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


#define	MAX_TRIANGLES_PER_MATERIAL	40000
#define	MAX_VERTICES_PER_MATERIAL	(MAX_TRIANGLES_PER_MATERIAL * 3)	


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

int				gCurrentLayerNum = 0;
int				gNumLayers = 0;
LayerInfoType	gLayerInfoList[MAX_LAYERS];

static	MOTriangleIndecies	*gMasterTriangles = nil;
static	OGLPoint3D			*gMasterPoints = nil;


			/* BINS */

int							gNumMaterials = 0;							// note:  surfaces and materials are the same thing
MOMaterialObject			*gMaterials[MAX_SURFACES];

MOMeshObject			*gVertexArrayObjects[MAX_SURFACES];			// array of metaobjects built from the bins below

static	int					gNumTrianglesInBin[MAX_SURFACES];
static	int					gNumPointsInBin[MAX_SURFACES];
static	MOTriangleIndecies	**gTriangleBin = nil;				// 2D array of triangles
static	OGLPoint3D			**gPointBin = nil;					// 2D array of points
static	OGLTextureCoord		**gUVBin = nil;						// 2D array of UV coords
static	OGLVector3D			**gNormalBin = nil;					// 2D array of vertex normals
static  OGLColorRGBA_Byte		**gColorsBin = nil;					

int			gNumDuplicateVerts = 0;		
int			gNumTotalPoints,gNumTotalTriangles,gNumTotalVertexArrays;


/*********************** PROCESS MAYA DATA ************************/
//
// This preps us for saving out the geometry data.
//

void ProcessMayaData(void)
{
OSErr	iErr;
int		i;

	InitBins();											// init the bins for dumping triangles and points for each material
		
				/* GATHER LAYER INFO */

	iErr = Maya_GetLayersInfo();	
	if (iErr)										
		return;

			/*****************************/
			/* SAVE LAYERS INDEPENDENTLY */
			/*****************************/

	if (gExportLayersSeparate)
	{
		/* LET USER SPECIFY WHERE TO PUT FILES (THE ACTUAL FILENAME IS IGNORED) */

	   	if (GetExportFileName() != noErr)
   			return;
  
			/* TURN EACH LAYER INTO A DIFFERENT FILE */

		for (gCurrentLayerNum = 0; gCurrentLayerNum <= gNumLayers; gCurrentLayerNum++)
		{
			if (gLayerInfoList[gCurrentLayerNum].isVisible)			// only export if the layer is visible
			{
				InitBins();											// init the bins for dumping triangles and points for each material
				
							/* BUILD OUR GEOMETRY */
							
				if (Maya_ExtractFacesAndMaterials() != noErr)		// break down the meshes and materials & put data into bins
					return;
				
				ConvertBinsToVertexArrays();						// build MetaObjects for each bin geometry				
				if (gNumTotalTriangles > 0)							// see if empty group
				{					
					ShowPreview(false);								// show it for sanity
										
								/* SAVE THE FILE */
								
					CopyPStr (gLayerInfoList[gCurrentLayerNum].name, gExportSpec.name);		// copy layer name into FSSpec filename		
					i = gExportSpec.name[0];
					gExportSpec.name[++i] = '.';					// append suffix
					gExportSpec.name[++i] = 'b';
					gExportSpec.name[++i] = 'g';
					gExportSpec.name[++i] = '3';
					gExportSpec.name[++i] = 'd';
					gExportSpec.name[0] = i;
					
		   			SaveBG3DModel();								// save it
		   		}
	   			EmptyBins();
			}
		}   			
	}

			/*******************************/
			/* SAVE ALL LAYERS AS ONE FILE */
			/*******************************/
	else
	{
			
		InitBins();													// init the bins for dumping triangles and points for each material
	
		gCurrentLayerNum = -1;										// do all layers
		
		if (Maya_ExtractFacesAndMaterials() != noErr)				// break down the meshes and materials & put data into bins
			return;


		ConvertBinsToVertexArrays();								// build MetaObjects for each bin geometry

		ShowPreview(true);


				/* GET EXPORT FILENAME & SAVE BG3D */
				
		if (GetExportFileName() == noErr)
   			SaveBG3DModel();

		EmptyBins();
	}





			/***********/
			/* CLEANUP */
			/***********/
			
	DisposePtr((Ptr)gMasterPoints);
	gMasterPoints = nil;
	DisposePtr((Ptr)gMasterTriangles);	
	gMasterTriangles = nil;		

}


#pragma mark -


/*************************** INIT BINS *****************************/
//
// This initializes the "bins" which are the triangle, point, and attribute
// arrays for each material.  We fill these bins with triangles as we decompose
// each mesh.  There is one bin for each material.
//

static void InitBins(void)
{
int		i;
		
			
			/* ALLOC BIN ARRAYS */
			
	 Alloc_2d_array(MOTriangleIndecies, gTriangleBin, MAX_SURFACES, MAX_TRIANGLES_PER_MATERIAL);	// allocate Triangle bin
	 Alloc_2d_array(OGLPoint3D, gPointBin, MAX_SURFACES, MAX_VERTICES_PER_MATERIAL);				// allocate Points bin
	 Alloc_2d_array(OGLTextureCoord, gUVBin, MAX_SURFACES, MAX_VERTICES_PER_MATERIAL);				// allocate UVs bin
	 Alloc_2d_array(OGLVector3D, gNormalBin, MAX_SURFACES, MAX_VERTICES_PER_MATERIAL);				// allocate vertex normal bin
	 Alloc_2d_array(OGLColorRGBA_Byte, gColorsBin, MAX_SURFACES, MAX_VERTICES_PER_MATERIAL);				// allocate color bin
	 
	 
	
	for (i = 0; i < MAX_SURFACES; i++)
	{
		gNumTrianglesInBin[i] =
		gNumPointsInBin[i] = 0;
	}
	
	gNumMaterials = 0;					// no materials found yet
	
	gNumDuplicateVerts = 0;
}



/******************** EMPTY BINS *****************************/

static void EmptyBins(void)
{
int		i;


		/* DISPOSE OF THE VERTEX ARRAY METAOBJECTS */
			
	for (i = 0; i < gNumMaterials; i++)
	{
		if (gVertexArrayObjects[i])
		{
			MO_DisposeObjectReference(gVertexArrayObjects[i]);
			gVertexArrayObjects[i] = nil;
		}
	}



			/* DISPOSE OF BIN ARRAYS */
			
	if (gTriangleBin)
		Free_2d_array(gTriangleBin);

	if (gPointBin)
		Free_2d_array(gPointBin);

	if (gUVBin)
		Free_2d_array(gUVBin);

	if (gNormalBin)
		Free_2d_array(gNormalBin);
		
	if (gColorsBin)
		Free_2d_array(gColorsBin);
	


}


/********************** ADD TRIANGLE TO BIN ***********************/

void AddTriangleToBin(int materialNum, OGLPoint3D *points, OGLVector3D *normals, OGLTextureCoord *uvs, OGLColorRGBA_Byte *colors, OGLVector3D *mayaFaceNormal)
{
int			v,i,numPointsInBin;
OGLVector3D	faceNormal;
float		angle;
MOTriangleIndecies triangle;

	numPointsInBin = gNumPointsInBin[materialNum];							// get # points already in this bin


			/***************************************/
			/* SCAN THE BINS FOR A MATCHING VERTEX */
			/***************************************/
			//
			// We're optimizing on-the-fly, so as we add the data to the 
			// bins, we first check to see if there is already duplicate data in there
			// so that we end up getting our vertices shared when possible.
			//

	for (v = 0; v < 3; v++)													// check each of the 3 vertices
	{
		for (i = 0; i < numPointsInBin; i++)								// scan the bin for a match
		{
					/* SEE IF POINT COORDINATES MATCH */
					
			if (!PointsAreCloseEnough(&gPointBin[materialNum][i], &points[v]))
				continue;
						
					
					/* SEE IF VERTEX NORMALS MATCH */
					
			if (!VectorsAreCloseEnough(&gNormalBin[materialNum][i], &normals[v]))
				continue;


					/* SEE IF VERTEX COLORS MATCH */
					
			if (gUseVertexColors)
				if (!ColorsAreCloseEnough(&gColorsBin[materialNum][i], &colors[v]))
					continue;

					
					/* SEE IF UV'S MATCH - ONLY IF TEXTURED MATERIAL */
					
			if (gMaterials[materialNum]->objectData.hasTexture)
			{
				if (gUVBin[materialNum][i].u != uvs[v].u)
					continue;
				if (gUVBin[materialNum][i].v != uvs[v].v)
					continue;			
			}
		
		
					/* IF GETS HERE THEN ITS A MATCH */

			triangle.vertexIndices[v] = i;					// remap triangle's vertex index to this point since it's already in the list
			gNumDuplicateVerts++;
			goto next_vertex;
		}

			/************************************************************/
			/* IF GETS HERE THEN THIS IS A UNIQUE VERTEX, SO ADD TO BIN */
			/************************************************************/

		triangle.vertexIndices[v] = numPointsInBin;			// remap triangle's vertex index to the new point

		gPointBin[materialNum][numPointsInBin] 		= points[v];
		gNormalBin[materialNum][numPointsInBin] 	= normals[v];
		gUVBin[materialNum][numPointsInBin] 		= uvs[v];
		gColorsBin[materialNum][numPointsInBin] 	= colors[v];
		
		gNumPointsInBin[materialNum]++;
		numPointsInBin++;
		
next_vertex:;		
	}
	
			/***********************************/
			/* NOW ADD THE TRIANGLE TO THE BIN */
			/***********************************/
			//
			// This is how we verify that our front-faces are correct.
			// We take the face normal that we got from Maya and compare
			// it with the one we calculate here.  If they don't match
			// then we need to reverse the vertex order to flip the poly.
			//
				
	CalcFaceNormal_NotNormalized(&points[0], &points[1], &points[2], &faceNormal);		// manually calc poly face normal
	OGLVector3D_Normalize(&faceNormal, &faceNormal);
	angle = OGLVector3D_Dot(&faceNormal, mayaFaceNormal);							// calc angle between two vectors
	if (angle < 0.0f)															// if not aligned then something is amiss
	{
		int	temp = triangle.vertexIndices[0];
	
		triangle.vertexIndices[0] = triangle.vertexIndices[2];
		triangle.vertexIndices[2] = temp;
	}
	


	gTriangleBin[materialNum][gNumTrianglesInBin[materialNum]] = triangle;		// copy the triangle vertex indecies into the triangle bin
	gNumTrianglesInBin[materialNum]++;											// inc # triangles in the bin

}


/********************** CONVERT BINS TO VERTEX ARRAYS *****************************/

static void ConvertBinsToVertexArrays(void)
{
int					i;
MOMeshData	data;

	data.material	= nil;
	data.uvs		= nil;

	gNumTotalPoints = 0;
	gNumTotalTriangles = 0;
	gNumTotalVertexArrays = 0;

			/**********************************************/
			/* BUILD A VERTEX ARRAY FOR EACH MATERIAL BIN */
			/**********************************************/
			
	for (i = 0; i < gNumMaterials; i++)
	{
		if (gNumTrianglesInBin[i] == 0)							// see if the bin is empty
		{
			gVertexArrayObjects[i] = nil;
			continue;
		}
		
				/* BUILD THE DATA */
				
		data.material		= gMaterials[i];					// (this illegal ref is made legal when obj is created)
		data.numPoints		= gNumPointsInBin[i];
		data.numTriangles	= gNumTrianglesInBin[i];
		data.triangles 		= gTriangleBin[i];
		data.normals		= gNormalBin[i];
		data.points			= gPointBin[i];
		
		if (gMaterials[i]->objectData.hasTexture)				// if has texture, then has UV's
			data.uvs		= gUVBin[i];
		else
			data.uvs		= nil;
			
		data.colorsFloat		= nil;
	
		if (gUseVertexColors)
			data.colorsByte	= gColorsBin[i];
		else
			data.colorsByte	= nil;
				
				
				
				/* MAKE THE METAOBJ */
				
		gVertexArrayObjects[i] = MO_CreateNewObjectOfType(MO_TYPE_MESH, 0, &data);	// create metaobject

	
				/* SET ITS BBOX */
			
		MO_CalcBoundingBox(gVertexArrayObjects[i], &gVertexArrayObjects[i]->objectData.bBox);
		
		
		gNumTotalPoints += data.numPoints;
		gNumTotalTriangles += data.numTriangles;
		gNumTotalVertexArrays++;

	}

			/*********************************************/
			/* CALCULATE THE ROUGH SCALE FOR THE PREVIEW */
			/*********************************************/

	gPreviewScale = 0;
	for (i = 0; i < gNumMaterials; i++)
	{
		OGLBoundingBox *bb;
		
		if (!gVertexArrayObjects[i])							// see if nothing here
			continue;


		bb = &gVertexArrayObjects[i]->objectData.bBox;			// get ptr to bbox
		
		if (fabs(bb->max.x) > gPreviewScale)
			gPreviewScale = fabs(bb->max.x);
		if (fabs(bb->max.y) > gPreviewScale)
			gPreviewScale = fabs(bb->max.y);
		if (fabs(bb->max.z) > gPreviewScale)
			gPreviewScale = fabs(bb->max.z);
		
		if (fabs(bb->min.x) > gPreviewScale)
			gPreviewScale = fabs(bb->min.x);
		if (fabs(bb->min.y) > gPreviewScale)
			gPreviewScale = fabs(bb->min.y);
		if (fabs(bb->min.z) > gPreviewScale)
			gPreviewScale = fabs(bb->min.z);
	}

	gPreviewScale = 110.0f / gPreviewScale;

}


#pragma mark -


/************************** GET TEXTURE MAP *********************************/

OSErr GetTextureMap(int matIndex, FSSpec *spec, MOMaterialData *matData)
{
OSErr		iErr;
GWorldPtr	gworld;
int			width, height;
Rect		r;
Boolean		hasAlpha;

			/********************************/
			/* LOAD THE IMAGE INTO A GWORLD */
			/********************************/
			
	iErr = DrawPictureIntoGWorld(spec, &gworld, 32);
	if (iErr)
		return(iErr);


	matData->flags |= BG3D_MATERIALFLAG_TEXTURED;					// set textured flag & mipmap count
	matData->hasTexture = true;					


				/* GET IMAGE DIMENSIONS */
						
	GetPortBounds(gworld, &r);
	width = matData->width 	= r.right - r.left;
	height = matData->height = r.bottom - r.top;


			/******************************************/
			/* READ IMAGE PIXELS INTO BUFFER & OPENGL */
			/******************************************/
		
	matData->texturePixels = ReadImageIntoBuffer(gworld, width, height);												// load into buffer
						
	matData->textureName	= OGL_TextureMap_Load(matData->texturePixels, width, height);		// load 32bit into OpenGL
					

			/* IF TEXTURE HAS ALPHA THEN SET BLEND FLAG */
			
	hasAlpha = TextureARGBContainsAlpha(width, height, matData->texturePixels);	// if texture has an active alpha channel then also set the blend flag
	if (hasAlpha)
		matData->flags |= BG3D_MATERIALFLAG_ALWAYSBLEND;
		
					
			/**************************************************************/
			/* CREATE THE MATERIAL OBJECT FROM THE DATA WE'VE ACCUMULATED */
			/**************************************************************/
				
	gMaterials[matIndex] = MO_CreateNewObjectOfType(MO_TYPE_MATERIAL, 0, &matData);
	
				
	
	return(noErr);
}



/********************** READ IMAGE INTO BUFFER *************************/

static void *ReadImageIntoBuffer(GWorldPtr gworld, int width, int height)
{
u_long	pictRowBytes;
void	*buffer;
int		x,y;
PixMapHandle 	hPixMap;
Ptr			pictMapAddr;
u_long		pixel32,*buffPtr32;
u_long		*srcPtr32, *destPtr32;

				/* GET GWORLD INFO */
				
	hPixMap = GetGWorldPixMap(gworld);
	pictMapAddr = GetPixBaseAddr(hPixMap);
	pictRowBytes = (u_long)(**hPixMap).rowBytes & 0x3fff;

	
			/* ALLOCATE BUFFER FOR 32-BIT TEXTURE MAP */
			
	buffPtr32 = buffer = (u_long *)AllocPtr(width * height * 4);
	destPtr32 = buffPtr32 + (width * (height-1));				// start dest @ bottom so as to flip the image
	srcPtr32 = (u_long *)pictMapAddr;


	for (y = 0; y < height; y++)							
	{
		for (x = 0; x < width; x++)
		{
			pixel32 = srcPtr32[x];					// read 32-bit pixel from gworld (ARGB format)
			destPtr32[x] = pixel32;					// write pixel to buffer					
		}
		
		srcPtr32 += pictRowBytes/4;					// next row in gworld
		destPtr32 -= width;							// next row in buffer
	}

	return(buffer);
}



