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


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


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

static void ReadBG3DHeader(short refNum);
static void ParseBG3DFile(short refNum);
static void ReadMaterialFlags(short refNum);
static void ReadMaterialDiffuseColor(short refNum);
static void ReadMaterialTextureMap(short refNum);
static void ReadGroup(void);
static MetaObjectPtr ReadNewMesh(short refNum);
static void InitBG3DContainer(void);
static void EndGroup(void);
static void ReadVertexArray(short refNum);
static void ReadNormalArray(short refNum);
static void ReadUVArray(short refNum);
static void ReadVertexColorArray(short refNum);
static void ReadTriangleArray(short refNum);


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

#define	BG3D_GROUP_STACK_SIZE	50						// max nesting depth of groups



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

static int					gBG3D_GroupStackIndex;
static MOGroupObject		*gBG3D_GroupStack[BG3D_GROUP_STACK_SIZE];
static MOGroupObject		*gBG3D_CurrentGroup;

static MOMaterialObject		*gBG3D_CurrentMaterialObj;			// note: this variable contains an illegal ref to the object.  The real ref is in the file container material list.
static MOMeshObject	*gBG3D_CurrentGeometryObj;

static BG3DFileContainer	*gBG3D_CurrentContainer;


BG3DFileContainer		*gBG3DContainerList[MAX_BG3D_GROUPS];
MetaObjectPtr			gBG3DGroupList[MAX_BG3D_GROUPS][MAX_OBJECTS_IN_GROUP];		// ILLEGAL references!!!
int						gNumObjectsInBG3DGroupList[MAX_BG3D_GROUPS];
OGLBoundingBox			gObjectGroupBBoxList[MAX_BG3D_GROUPS][MAX_OBJECTS_IN_GROUP];


/****************** INIT BG3D MANAGER ***********************/

void InitBG3DManager(void)
{
int	i;

	for (i = 0; i < MAX_BG3D_GROUPS; i++)
	{
		gBG3DContainerList[i] = nil;
	}
}


/********************** IMPORT BG3D **************************/
//
// NOTE:  	All BG3D models must be imported AFTER the draw context has been created,
//			because all imported textures are named with OpenGL and loaded into OpenGL!
//

void ImportBG3D(FSSpec *spec, int groupNum)
{
short				refNum;
int					i;
MetaObjectHeader	*header;
MOGroupObject		*group;
MOGroupData			*data;

	if (groupNum >= MAX_BG3D_GROUPS)
		DoFatalAlert("\pImportBG3D: illegal groupNum");

			/* INIT SOME VARIABLES */
				
	gBG3D_CurrentMaterialObj 	= nil;
	gBG3D_CurrentGeometryObj	= nil;
	gBG3D_GroupStackIndex		= 0;			// init the group stack
	InitBG3DContainer();


		/************************/	
		/* OPEN THE FILE & READ */
		/************************/	

	if (FSpOpenDF(spec, fsRdPerm, &refNum) != noErr)
		DoFatalAlert("\pImportBG3D: FSpOpenDF failed");

	ReadBG3DHeader(refNum);	
	ParseBG3DFile(refNum);
	
			/* CLOSE FILE */
			
	FSClose(refNum);
	
	
			/********************/
			/* SETUP GROUP INFO */
			/********************/
			//
			// A model "group" is a grouping of 3D models.
			//
			
	gBG3DContainerList[groupNum] = gBG3D_CurrentContainer;			// save container into list
	
	header = gBG3D_CurrentContainer->root;							// point to root object in container
	if (header == nil)
		DoFatalAlert("\pImportBG3D: header == nil");
	
	if (header->type != MO_TYPE_GROUP)								// root should be a group
		DoFatalAlert("\pImportBG3D: root isnt a group!");
	
		
			/* PARSE GROUP */
			//
			// Create a list of all of the models inside this file
			//
			
	group = gBG3D_CurrentContainer->root;
	data = &group->objectData;
	
	for (i = 0; i < data->numObjectsInGroup; i++)
	{		
		gBG3DGroupList[groupNum][i] = data->groupContents[i];			// copy ILLEGAL ref to this object				
	}
	
	gNumObjectsInBG3DGroupList[groupNum] = data->numObjectsInGroup;
}


/********************** READ BG3D HEADER **************************/

static void ReadBG3DHeader(short refNum)
{
BG3DHeaderType	headerData;
long			count;


	count = sizeof(BG3DHeaderType);

	if (FSRead(refNum, &count, &headerData) != noErr)
		DoFatalAlert("\pReadBG3DHeader: FSRead failed");
	
			/* VERIFY FILE */
			
	if ((headerData.headerString[0] != 'B') ||
		(headerData.headerString[1] != 'G') ||
		(headerData.headerString[2] != '3') ||
		(headerData.headerString[3] != 'D'))
	{
		DoFatalAlert("\pReadBG3DHeader: BG3D file has invalid header.");	
	}
}


/****************** PARSE BG3D FILE ***********************/

static void ParseBG3DFile(short refNum)
{
u_long			tag;
long			count;
Boolean			done = false;
MetaObjectPtr 	newObj;

	do
	{
			/* READ A TAG */
		
		count = sizeof(tag);
		if (FSRead(refNum, &count, &tag) != noErr)
			DoFatalAlert("\pParseBG3DFile: FSRead failed");
			
			
			/* HANDLE THE TAG */
			
		switch(tag)
		{
			case	BG3D_TAGTYPE_MATERIALFLAGS:
					ReadMaterialFlags(refNum);
					break;
					
			case	BG3D_TAGTYPE_MATERIALDIFFUSECOLOR:
					ReadMaterialDiffuseColor(refNum);
					break;
					
			case	BG3D_TAGTYPE_TEXTUREMAP:
					ReadMaterialTextureMap(refNum);
					break;
					
			case	BG3D_TAGTYPE_GROUPSTART:
					ReadGroup();
					break;

			case	BG3D_TAGTYPE_GROUPEND:
					EndGroup();
					break;
					
			case	BG3D_TAGTYPE_MESH:
					newObj = ReadNewMesh(refNum);
					if (gBG3D_CurrentGroup)								// add new geometry to current group
					{
						MO_AppendToGroup(gBG3D_CurrentGroup, newObj);
						MO_DisposeObjectReference(newObj);								// nuke the extra reference						
					}
					break;
					
			case	BG3D_TAGTYPE_VERTEXARRAY:
					ReadVertexArray(refNum);
					break;
					
			case	BG3D_TAGTYPE_NORMALARRAY:
					ReadNormalArray(refNum);
					break;
					
			case	BG3D_TAGTYPE_UVARRAY:
					ReadUVArray(refNum);
					break;
					
			case	BG3D_TAGTYPE_COLORARRAY:
					ReadVertexColorArray(refNum);
					break;
					
			case	BG3D_TAGTYPE_TRIANGLEARRAY:
					ReadTriangleArray(refNum);
					break;

			case	BG3D_TAGTYPE_ENDFILE:
					done = true;
					break;
			
			default:
					DoFatalAlert("\pParseBG3DFile: unrecognized tag");
		}		
	}while(!done);
}
	

/******************* READ MATERIAL FLAGS ***********************/
//
// Reading new material flags indicatest the start of a new material.
//
// NOTE:  	the materials are the first things read from a BG3D file since we need
// 			them before we can read geometry which uses them
//

static void ReadMaterialFlags(short refNum)
{
long				count,i;
MOMaterialData		data;
u_long				flags;

			/* READ FLAGS */
		
	count = sizeof(flags);
	if (FSRead(refNum, &count, &flags) != noErr)
		DoFatalAlert("\pReadMaterialFlags: FSRead failed");


		/* INIT NEW MATERIAL DATA */

	data.flags 					= flags;
	data.diffuseColor.r			= 1;
	data.diffuseColor.g			= 1;
	data.diffuseColor.b			= 1;
	data.diffuseColor.a			= 1;
	data.hasTexture				= false;				
	
	/* CREATE NEW MATERIAL OBJECT */
			
	gBG3D_CurrentMaterialObj = MO_CreateNewObjectOfType(MO_TYPE_MATERIAL, 0, &data);
	
	
	/* ADD THIS MATERIAL TO THE FILE CONTAINER */
	
	
	i = gBG3D_CurrentContainer->numMaterials++;									// get index into file container's material list

	gBG3D_CurrentContainer->materials[i] = gBG3D_CurrentMaterialObj;			// stores the 1 reference here.
}


/*************** READ MATERIAL DIFFUSE COLOR **********************/

static void ReadMaterialDiffuseColor(short refNum)
{
long			count;
GLfloat			color[4];
MOMaterialData	*data;

	if (gBG3D_CurrentMaterialObj == nil)
		DoFatalAlert("\pReadMaterialDiffuseColor: gBG3D_CurrentMaterialObj == nil");
	

			/* READ COLOR VALUE */
		
	count = sizeof(GLfloat) * 4;
	if (FSRead(refNum, &count, color) != noErr)
		DoFatalAlert("\pReadMaterialDiffuseColor: FSRead failed");


		/* ASSIGN COLOR TO CURRENT MATERIAL */
			
	data = &gBG3D_CurrentMaterialObj->objectData; 							// get ptr to material data
	
	data->diffuseColor.r = color[0];
	data->diffuseColor.g = color[1];
	data->diffuseColor.b = color[2];
	data->diffuseColor.a = color[3];
}


/******************* READ MATERIAL TEXTURE MAP ***********************/
//
// NOTE:  This may get called multiple times - once for each mipmap associated with the
//			material.
//

static void ReadMaterialTextureMap(short refNum)
{
BG3DTextureHeader	textureHeader;
long		count;
void		*texturePixels;
MOMaterialData	*data;

			/* GET PTR TO CURRENT MATERIAL */
			
	if (gBG3D_CurrentMaterialObj == nil)
		DoFatalAlert("\pReadMaterialTextureMap: gBG3D_CurrentMaterialObj == nil");

	data = &gBG3D_CurrentMaterialObj->objectData; 	// get ptr to material data


			/***********************/
			/* READ TEXTURE HEADER */
			/***********************/
			
	count = sizeof(BG3DTextureHeader);
	FSRead(refNum, &count, &textureHeader);		// read header

			/* COPY BASIC INFO */
	
	data->width 			= textureHeader.width;
	data->height	 		= textureHeader.height;
	data->hasTexture		= true;

		/***************************/
		/* READ THE TEXTURE PIXELS */
		/***************************/
						
	count = textureHeader.bufferSize;			// get size of buffer to load
	
	texturePixels = AllocPtr(count);			// alloc memory for buffer
	if (texturePixels == nil)
		DoFatalAlert("\pReadMaterialTextureMap: AllocPtr failed");

	FSRead(refNum, &count, texturePixels);		// read pixel data


		/* ASSIGN PIXELS TO CURRENT MATERIAL */

	data->texturePixels = texturePixels;		// set ptr to pixelmap						

}



/******************* READ GROUP ***********************/
//
// Called when GROUPSTART tag is found.  There must be a matching GROUPEND tag later.
//

static void ReadGroup(void)
{
MOGroupObject	*newGroup;

			/* CREATE NEW GROUP OBJECT */
			
	newGroup = MO_CreateNewObjectOfType(MO_TYPE_GROUP, 0, nil);
	if (newGroup == nil)
		DoFatalAlert("\pReadGroup: MO_CreateNewObjectOfType failed");	
				

		/*************************/		
		/* PUSH ONTO GROUP STACK */
		/*************************/		
		
	if (gBG3D_GroupStackIndex >= (BG3D_GROUP_STACK_SIZE-1))
		DoFatalAlert("\pReadGroup: gBG3D_GroupStackIndex overflow!");	
			
		/* SEE IF THIS IS FIRST GROUP */
		
	if (gBG3D_CurrentGroup == nil)					// no parent
	{
		gBG3D_CurrentContainer->root = newGroup;	// set container's root to this group
	}
		
		/* ADD TO PARENT GROUP */
		
	else
	{
		gBG3D_GroupStack[gBG3D_GroupStackIndex++] = gBG3D_CurrentGroup;		// push the old group onto group stack	
		MO_AppendToGroup(gBG3D_CurrentGroup, newGroup);						// add new group to existing group (which creates new ref)
		MO_DisposeObjectReference(newGroup);								// nuke the extra reference
	}

	gBG3D_CurrentGroup = newGroup;				// current group == this group		
}


/******************* END GROUP ***********************/
//
// Signifies the end of a GROUPSTART tag group.
//

static void EndGroup(void)
{
	gBG3D_GroupStackIndex--;

	if (gBG3D_GroupStackIndex < 0)									// must be something on group stack
		DoFatalAlert("\pEndGroup: stack is empty!");	

	gBG3D_CurrentGroup = gBG3D_GroupStack[gBG3D_GroupStackIndex++]; // get previous group off of stack	
}

#pragma mark -

/******************* READ NEW MESH ***********************/

static MetaObjectPtr ReadNewMesh(short refNum)
{
BG3DMeshHeader		geoHeader;
long				count;
MOMeshData	vertexArrayData;
int					materialNum;

			/* READ MESH HEADER */
			
	count = sizeof(BG3DMeshHeader);
	FSRead(refNum, &count, &geoHeader);		// read header


			/* SETUP DATA */

	vertexArrayData.numPoints 		= geoHeader.numPoints;
	vertexArrayData.numTriangles 	= geoHeader.numTriangles;
	vertexArrayData.points 			= nil;							// these arrays havnt been read in yet
	vertexArrayData.normals 		= nil;	
	vertexArrayData.uvs				= nil;
	
	vertexArrayData.colorsByte 		= nil;
	vertexArrayData.colorsFloat		= nil;
	vertexArrayData.triangles		= nil;
	

	/* SETUP MATERIAL LIST */
	//
	// These start as illegal references.  The ref count is incremented during the Object Creation function.
	//

	materialNum = geoHeader.materialNum;

	vertexArrayData.material = gBG3D_CurrentContainer->materials[materialNum];
	
	
		/* CREATE THE NEW GEO OBJECT */
			
	gBG3D_CurrentGeometryObj = MO_CreateNewObjectOfType(MO_TYPE_MESH, 0, &vertexArrayData);
	
	return(gBG3D_CurrentGeometryObj);
}


/******************* READ VERTEX ARRAY *************************/

static void ReadVertexArray(short refNum)
{
long				count;
int					numPoints;
MOMeshData	*data;
OGLPoint3D			*pointList;

	data = &gBG3D_CurrentGeometryObj->objectData;					// point to geometry data
	if (data->points)												// see if points already assigned
		DoFatalAlert("\pReadVertexArray: points already assigned!");

	numPoints = data->numPoints;									// get # points to expect to read
			
	count = sizeof(OGLPoint3D) * numPoints;							// calc size of data to read
	pointList = AllocPtr(count);									// alloc buffer to hold points
	if (pointList == nil)
		DoFatalAlert("\pReadVertexArray: AllocPtr failed!");
	
	FSRead(refNum, &count, pointList);								// read the data

	data->points = pointList;										// assign point array to geometry header
}


/******************* READ NORMAL ARRAY *************************/

static void ReadNormalArray(short refNum)
{
long				count;
int					numPoints;
MOMeshData	*data;
OGLVector3D			*normalList;

	data = &gBG3D_CurrentGeometryObj->objectData;					// point to geometry data
	numPoints = data->numPoints;									// get # normals to expect to read
			
	count = sizeof(OGLVector3D) * numPoints;						// calc size of data to read
	normalList = AllocPtr(count);									// alloc buffer to hold normals
	if (normalList == nil)
		DoFatalAlert("\pReadNormalArray: AllocPtr failed!");
	
	FSRead(refNum, &count, normalList);								// read the data

	data->normals = normalList;										// assign normal array to geometry header
}


/******************* READ UV ARRAY *************************/

static void ReadUVArray(short refNum)
{
long				count;
int					numPoints;
MOMeshData	*data;
OGLTextureCoord		*uvList;

	data = &gBG3D_CurrentGeometryObj->objectData;					// point to geometry data
	numPoints = data->numPoints;									// get # uv's to expect to read

	count = sizeof(OGLTextureCoord) * numPoints;					// calc size of data to read
	uvList = AllocPtr(count);										// alloc buffer to hold uv's
	if (uvList == nil)
		DoFatalAlert("\pReadUVArray: AllocPtr failed!");
	
	FSRead(refNum, &count, uvList);									// read the data

	data->uvs = uvList;												// assign uv array to geometry header
}


/******************* READ VERTEX COLOR ARRAY *************************/

static void ReadVertexColorArray(short refNum)
{
long				count,i;
int					numPoints;
MOMeshData	*data;
OGLColorRGBA_Byte	*colorList;
OGLColorRGBA		*colorsF;

	data = &gBG3D_CurrentGeometryObj->objectData;					// point to geometry data
	numPoints = data->numPoints;									// get # colors to expect to read

	count = sizeof(OGLColorRGBA_Byte) * numPoints;					// calc size of data to read
	colorList = AllocPtr(count);									// alloc buffer to hold data
	if (colorList == nil)
		DoFatalAlert("\pReadVertexColorArray: AllocPtr failed!");
	
	FSRead(refNum, &count, colorList);								// read the data

	data->colorsByte = colorList;									// assign color array to geometry header
	
		/* NOW ALSO CREATE COLOR ARRAY IN FLOAT FORMAT */
		//
		// Models have colors in both byte and float formats because
		// it is faster to render with Bytes if no lighting, but faster with floats if doing lights
		//
			
	colorsF = AllocPtr(sizeof(OGLColorRGBA) * numPoints);		
	if (colorsF == nil)
		DoFatalAlert("\pReadVertexColorArray: AllocPtr failed!");
	
	data->colorsFloat = colorsF;									// assign color array to geometry header
	
	for (i = 0; i < numPoints; i++)									// copy & convert bytes to floats
	{
		colorsF[i].r = (float)(colorList[i].r) / 255.0f;
		colorsF[i].g = (float)(colorList[i].g) / 255.0f;
		colorsF[i].b = (float)(colorList[i].b) / 255.0f;
		colorsF[i].a = (float)(colorList[i].a) / 255.0f;	
	}
			
}


/******************* READ TRIANGLE ARRAY *************************/

static void ReadTriangleArray(short refNum)
{
long				count;
int					numTriangles;
MOMeshData	*data;
MOTriangleIndecies	*triList;

	data = &gBG3D_CurrentGeometryObj->objectData;					// point to geometry data
	numTriangles = data->numTriangles;								// get # triangles expect to read

	count = sizeof(MOTriangleIndecies) * numTriangles;				// calc size of data to read
	triList = AllocPtr(count);										// alloc buffer to hold data
	if (triList == nil)
		DoFatalAlert("\pReadTriangleArray: AllocPtr failed!");
	
	FSRead(refNum, &count, triList);								// read the data

	data->triangles = triList;										// assign triangle array to geometry header
}



#pragma mark -


/***************** INIT BG3D CONTAINER *********************/
//
// The container is just a header that tracks all of the crap we
// read from a BG3D file.
//

static void InitBG3DContainer(void)
{
MOGroupObject	*rootGroup;

	gBG3D_CurrentContainer = AllocPtr(sizeof(BG3DFileContainer));
	if (gBG3D_CurrentContainer == nil)
		DoFatalAlert("\pInitBG3DContainer: AllocPtr failed!");

	gBG3D_CurrentContainer->numMaterials =	0;			// no materials yet


			/* CREATE NEW GROUP OBJECT */
			
	rootGroup = MO_CreateNewObjectOfType(MO_TYPE_GROUP, 0, nil);
	if (rootGroup == nil)
		DoFatalAlert("\pInitBG3DContainer: MO_CreateNewObjectOfType failed");	

	gBG3D_CurrentContainer->root 	= rootGroup;		// root is an empty group
	gBG3D_CurrentGroup 				= rootGroup;

}


#pragma mark -

/******************* DISPOSE ALL BG3D CONTAINERS ****************/

void DisposeAllBG3DContainers(void)
{
int	i;

	for (i = 0; i < MAX_BG3D_GROUPS; i++)
	{
		if (gBG3DContainerList[i])
			DisposeBG3DContainer(i);
	}
}


/************************** DISPOSE BG3D *****************************/

void DisposeBG3DContainer(int groupNum)
{
BG3DFileContainer	*file = gBG3DContainerList[groupNum];			// point to this file's container object
int					i;

	if (file == nil)
		return;

			/* DISPOSE OF ALL MATERIALS */
			
	for (i = 0; i < file->numMaterials; i++)
	{
		MO_DisposeObjectReference(file->materials[i]);
	}
	
	
		/* DISPOSE OF ROOT OBJECT/GROUP */
		
	MO_DisposeObjectReference(file->root);
	
	
		/* FREE THE CONTAINER'S MEMORY */
			
	DisposePtr((Ptr)gBG3DContainerList[groupNum]);
	gBG3DContainerList[groupNum] = nil;								// its gone
}





