/****************************/
/*   	BG3D EXPORT.C 		*/
/* (c)2003 Pangea Software  */
/* By Brian Greenstone      */
/****************************/


/****************************/
/*    EXTERNALS             */
/****************************/



extern	long	gNumMaterials;
extern	MaterialType	gMaterialsList[];
extern	MOGroupObject	*gTheBG3DGroup;

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

static void WriteBG3DHeader(short refNum);
static void WriteBG3DMaterials(short refNum);
static void WriteBG3DGroup(short refNum);
static void WriteNormalArray(short refNum, OGLVector3D *vectors, int numPoints);
static void WriteColorArray(short refNum, OGLColorRGBA *colors, int numPoints);
static void WriteBG3DEndTag(short refNum);
static void WriteTexture(short refNum, MOMaterialData *data);
static void WriteBG3DGroup_Recurse(short refNum, MetaObjectHeader *obj);

static void WriteAVertexArray(short refNum, MOMeshObject *obj);
static void WriteUVArray(short refNum, OGLTextureCoord *uvs, int numPoints);

static void WriteBG3DMaterials(short refNum);

static Boolean TextureContainsAlpha(short width, short height, u_long *data);


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

#define	MAX_GEOMETRIES	1000


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

ImageDescriptionHandle	gQTImageDescHandle = nil; 
Ptr						gQTCompressedImageData = nil;


/********************** SAVE BG3D MODEL **************************/

void SaveBG3DModel(FSSpec *theSpec)
{
OSErr	iErr;
short	refNum;

			/* OPEN FILE */
		
	FSpDelete(theSpec);									// delete any old one
	FSpCreate(theSpec, 'BGLk', 'BG3D', smSystemScript);	// create new file
	iErr = FSpOpenDF(theSpec, fsWrPerm, &refNum);		// open file
	if (iErr)
		DoFatalAlert("\pSaveBG3DModel: FSpOpenDF failed!");
				
	
			/* WRITE ALL THE COMPONENTS */
			
	WriteBG3DHeader(refNum);
	WriteBG3DMaterials(refNum);
	WriteBG3DGroup(refNum);

	
			/* CLOSE FILE */
			
	WriteBG3DEndTag(refNum);
	FSClose(refNum);
}


#pragma mark -

/********************** WRITE BG3D HEADER **************************/

static void WriteBG3DHeader(short refNum)
{
BG3DHeaderType	headerData;
char			s[16] = "BG3D 1.0        ";
long			count;


			/* SET HEADER FIELDS */
			
	BlockMove(&s[0], &headerData.headerString[0], 16);		// set header string
	
	headerData.version.majorRev 		= 0x01;
	headerData.version.minorAndBugRev 	= 0x00;
	headerData.version.stage 			= 0x00;
	headerData.version.nonRelRev	 	= 0x00;


			/* WRITE HEADER TO FILE */
	
	count = sizeof(BG3DHeaderType);			
	if (FSWrite(refNum, &count, &headerData) != noErr)
		DoFatalAlert("\pWriteBG3DHeader: FSWrite failed!");
}


/********************** WRITE BG3D END TAG **************************/

static void WriteBG3DEndTag(short refNum)
{
u_long			tag;
long			count;

	tag = BG3D_TAGTYPE_ENDFILE;
	count = sizeof(tag);
	FSWrite(refNum, &count, &tag);							// write tag
}
	
/********************** WRITE BG3D MATERIALS **************************/

static void WriteBG3DMaterials(short refNum)
{
long			i;
long			count;
u_long			tag,flags;
OSErr			iErr;
			
	for (i = 0; i < gNumMaterials; i++)
	{
		MOMaterialObject	*obj = gMaterialsList[i].materialObj;
		MOMaterialData		*data = &obj->objectData;
		Boolean				hasAlpha;

			/*************************/
			/* WRITE MATERIALS FLAGS */
			/*************************/


				/* SEE IF IS TEXTURED */
				
		hasAlpha = false;												// assume no alpha channel
		if (data->hasTexture)
		{
			flags = BG3D_MATERIALFLAG_TEXTURED;							// set textured flag
			hasAlpha = TextureContainsAlpha(data->width, data->height, data->texturePixels);	// if texture has an active alpha channel then also set the blend flag
			if (hasAlpha)
				flags |= BG3D_MATERIALFLAG_ALWAYSBLEND;
		}
		else
			flags = 0;
		
		
			/* WRITE TAG & FLAGS TO FILE */
		
		tag 	= BG3D_TAGTYPE_MATERIALFLAGS;
		count 	= sizeof(tag);
		iErr = FSWrite(refNum, &count, &tag);							// write tag
		count 	= sizeof(flags);
		iErr |= FSWrite(refNum, &count, &flags);						// write flags


			/****************************/
			/* WRITE DIFFUSE COLOR INFO */
			/****************************/
						
		tag 	= BG3D_TAGTYPE_MATERIALDIFFUSECOLOR;
		count 	= sizeof(tag);
		iErr |= FSWrite(refNum, &count, &tag);							// write tag
		count 	= sizeof(float) * 4;
		iErr |= FSWrite(refNum, &count, &data->diffuseColor);			// write diffuse color
			
		if (iErr)
			DoFatalAlert("\pWriteBG3DMaterials: FSWrite failed!");
			
			/**********************/
			/* WRITE TEXTURE INFO */
			/**********************/
			
		if (flags & BG3D_MATERIALFLAG_TEXTURED)
		{		
			WriteTexture(refNum, data);
		}			
		
	}
}



/************************ WRITE TEXTURE **********************/

static void WriteTexture(short refNum, MOMaterialData *data)
{
long		size;
int			w,h;
long		count;
u_long		tag;
BG3DTextureHeader	textureHeader;
OSErr		iErr;

	w = data->width;
	h = data->height;
	size = w * h * 4;


		/* SETUP TEXTURE HEADER */

	textureHeader.width 			= w;
	textureHeader.height 			= h;
	textureHeader.bufferSize 		= size;
	

	/* WRITE TAG, HEADER, AND TEXTURE TO FILE */
	
	tag 	= BG3D_TAGTYPE_TEXTUREMAP;
	count 	= sizeof(tag);
	iErr = FSWrite(refNum, &count, &tag);						// write tag
	
	count 	= sizeof(BG3DTextureHeader);
	iErr |= FSWrite(refNum, &count, &textureHeader);			// write header

	iErr |= FSWrite(refNum, &size, data->texturePixels);		// write texture

	if (iErr)
		DoFatalAlert("\pWriteTexture: FSWrite failed!");

}





/******************** WRITE BG3D GEOMETRY ***********************/

static void WriteBG3DGroup(short refNum)
{
int	i;

	if (gTheBG3DGroup == nil)
		return;
	
	
		/* WRITE ALL CONTENTS OF GROUP */
		//
		// We dont pass the group into the recusive function because we dont want to write
		// this temporary group itself, just its contents.
		//
		
	for (i = 0; i < gTheBG3DGroup->objectData.numObjectsInGroup; i++)
	{
		MetaObjectPtr	obj = gTheBG3DGroup->objectData.groupContents[i];	// get the next object in group

		WriteBG3DGroup_Recurse(refNum,obj);								// sub-recurse this object
	}
}



/***************** WRITE BG3D GROUP: RECURSE *******************/

static void WriteBG3DGroup_Recurse(short refNum, MetaObjectHeader *obj)
{
long				count;
u_long				tag;
int					i;
MOGroupObject		*groupObj;
MOGroupData			*groupData;

	switch(obj->type)
	{
				/**********/
				/*  MESH  */
				/**********/

		case	MO_TYPE_MESH:
				WriteAVertexArray(refNum, (MOMeshObject *)obj);
				break;
	

					/*********/
					/* GROUP */
					/*********/
						
		case	MO_TYPE_GROUP:

					/* WRITE NEW GROUP START TAG */
						
				tag 	= BG3D_TAGTYPE_GROUPSTART;
				count 	= sizeof(tag);
				FSWrite(refNum, &count, &tag);
 			
 			
		 			/* SUBRECURSE CONTENTS OF GROUP */
		 			
		 		groupObj = (MOGroupObject *)obj;
		 		groupData = &groupObj->objectData;
		 		
				for (i = 0; i < groupData->numObjectsInGroup; i++)
	    			WriteBG3DGroup_Recurse(refNum, groupData->groupContents[i]);			// sub-recurse this object
  		
					/* WRITE END GROUP TAG */
						
				tag = BG3D_TAGTYPE_GROUPEND;
				count = sizeof(tag);
				FSWrite(refNum, &count, &tag);  		
				break;
				
		default:
				DoFatalAlert("\pWriteBG3DGroup_Recurse: what the hell is this?");
	}
}


/********************** WRITE A VERTEX ARRAY **************************/

static void WriteAVertexArray(short refNum, MOMeshObject *obj)
{
MOMeshData	*data = &obj->objectData;
long				count;
u_long				tag;
BG3DMeshHeader	header;
int					materialNum;

		/*****************************************************/
		/* LOOKUP THIS OBJECTS MATERIAL IN THE MATERIAL LIST */
		/*****************************************************/
		
	if (data->material != nil)										// make sure there's a material at all
	{
		for (materialNum = 0; materialNum < gNumMaterials; materialNum++)
		{
			if (data->material == gMaterialsList[materialNum].materialObj)			// is this a match?
				goto got_mat;	
		}
		DoFatalAlert("\pWriteAVertexArray: material not found in materials list");
	}
	else
		materialNum = -1;													// no material

got_mat:


		/*************************/
		/* WRITE NEW MESH HEADER */
		/*************************/

	tag 	= BG3D_TAGTYPE_MESH;
	count 	= sizeof(tag);
	FSWrite(refNum, &count, &tag);

	header.materialNum			= materialNum;
	header.flags 				= 0;
	header.numPoints 			= data->numPoints;
	header.numTriangles 		= data->numTriangles;

	count = sizeof(BG3DMeshHeader);
	FSWrite(refNum, &count, &header);


		/**********************/
		/* WRITE VERTEX ARRAY */
		/**********************/
			
			/* WRITE TAG */
			
	tag 	= BG3D_TAGTYPE_VERTEXARRAY;
	count 	= sizeof(tag);
	FSWrite(refNum, &count, &tag);
	

		/* WRITE VERTEX DATA */
		
	count = sizeof(OGLPoint3D) * header.numPoints;
	FSWrite(refNum, &count, data->points);


		/*************************/
		/* WRITE OPTIONAL ARRAYS */
		/*************************/

			/* VERTEX NORMALS */
			
	if (data->normals)
		WriteNormalArray(refNum, data->normals, header.numPoints);

			/* UVS */
			
	if (data->uvs)
		WriteUVArray(refNum, data->uvs, header.numPoints);


			/* VERTEX COLORS */
			
	if (data->colorsFloat)
		WriteColorArray(refNum, data->colorsFloat, header.numPoints);

	

		/******************************/
		/* WRITE TRIANGLE INDEX ARRAY */
		/******************************/

			/* WRITE TAG */
			
	tag 	= BG3D_TAGTYPE_TRIANGLEARRAY;
	count 	= sizeof(tag);
	FSWrite(refNum, &count, &tag);


		/* WRITE INDEX DATA */
		
	count = sizeof(MOTriangleIndecies) * header.numTriangles;
	FSWrite(refNum, &count, data->triangles);


}


/******************* WRITE NORMAL ARRAY **********************/

static void WriteNormalArray(short refNum, OGLVector3D *vectors, int numPoints)
{
long			count;
u_long			tag;
OSErr			iErr;

			/* WRITE TAG */
			
	tag = BG3D_TAGTYPE_NORMALARRAY;
	count = sizeof(tag);
	iErr = FSWrite(refNum, &count, &tag);
	

		/* WRITE VERTEX DATA */

	count = sizeof(OGLVector3D) * numPoints;
	iErr |= FSWrite(refNum, &count, vectors);
	
	if (iErr)
		DoFatalAlert("\pWriteNormalArray: FSWrite failed!");
}


/******************* WRITE UV ARRAY **********************/

static void WriteUVArray(short refNum, OGLTextureCoord *uvs, int numPoints)
{
long			count;
u_long			tag;

			/* WRITE TAG */
			
	tag = BG3D_TAGTYPE_UVARRAY;
	count = sizeof(tag);
	FSWrite(refNum, &count, &tag);
	

		/* WRITE VERTEX DATA */

	count = sizeof(OGLTextureCoord) * numPoints;
	FSWrite(refNum, &count, uvs);
}


/******************* WRITE COLOR ARRAY **********************/

static void WriteColorArray(short refNum, OGLColorRGBA *colors, int numPoints)
{
int				i,j,size;
long			count;
u_long			tag;
u_char			*tempBuff;

		/* CONVERT RGB TO RGBA BYTE */
		
	size = sizeof(u_char) * numPoints * 4;
	tempBuff = (u_char *)AllocPtr(size);						// alloc buffer to hold rgba BYTES
	if (tempBuff == nil)
		DoFatalAlert("\pWriteColorArray: alloc ptr failed!");
	
	for (i = j = 0; i < numPoints; i++)
	{
		tempBuff[j++] = colors[i].r * 255.0f;
		tempBuff[j++] = colors[i].g * 255.0f;
		tempBuff[j++] = colors[i].b * 255.0f;
		tempBuff[j++] = colors[i].a * 255.0f;
	}
	

			/* WRITE TAG */
			
	tag = BG3D_TAGTYPE_COLORARRAY;
	count = sizeof(tag);
	FSWrite(refNum, &count, &tag);
	

		/* WRITE VERTEX DATA */

	count = size;
	FSWrite(refNum, &count, tempBuff);

	DisposePtr((Ptr)tempBuff);
}




#pragma mark -


/***************** TEXTURE CONTAINS ALPHA *********************/
//
// Returns TRUE if the input 32-bit RGBA texture buffer has any non-0xff alphas
//

static Boolean TextureContainsAlpha(short width, short height, u_long *data)
{
long	size, i;
u_long	pixel, alpha;

	size = width * height;

	for (i = 0; i < size; i++)
	{
		pixel = data[i];								// get RGBA pixel
		alpha = pixel & 0xff;							// extract alpha
		if (alpha != 0xff)								// is alpha not 100% opaque?
			return(true);
	}
	return(false);
}























