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


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

extern	MetaObjectPtr				gBG3DGroupList[NUM_MODEL_GROUPS][MAX_OBJECTS_IN_GROUP];
extern	Boolean						gMyState_Lighting;


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

static MetaObjectPtr AllocateEmptyMetaObject(u_long type, u_long subType);
static void SetMetaObjectToGroup(MOGroupObject *groupObj);
static void SetMetaObjectToMaterial(MOMaterialObject *matObj, MOMaterialData *inData);
static void SetMetaObjectToMesh(MOMeshObject *geoObj, MOMeshData *data);
static void SetMetaObjectToMatrix(MOMatrixObject *matObj, OGLMatrix4x4 *inData);
static void MO_DetachFromLinkedList(MetaObjectPtr obj);
static void MO_DisposeObject_Group(MOGroupObject *group);
static void MO_DeleteObjectInfo_Material(MOMaterialObject *obj);


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





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

MetaObjectHeader	*gFirstMetaObject = nil;
MetaObjectHeader 	*gLastMetaObject = nil;
int					gNumMetaObjects = 0;

float				gGlobalTransparency = 1;			// 0 == clear, 1 = opaque
OGLColorRGB			gGlobalColorFilter = {1,1,1};
u_long				gGlobalMaterialFlags = 0;

MOMaterialObject	*gMostRecentMaterial;


/***************** INIT META OBJECT HANDLER ******************/

void MO_InitHandler(void)
{
	gFirstMetaObject = nil;				// no meta object nodes yet
	gLastMetaObject = nil;
	gNumMetaObjects = 0;
}


#pragma mark -

/****************** MO: CREATE NEW OBJECT OF TYPE ****************/
//
// INPUT:	type = type of mo to create
//			subType = subtype to create (optional)
//			data = pointer to any data needed to create the mo (optional)
//


MetaObjectPtr	MO_CreateNewObjectOfType(u_long type, u_long subType, void *data)
{
MetaObjectPtr	mo;

			/* ALLOCATE EMPTY OBJECT */
			
	mo = AllocateEmptyMetaObject(type,subType);
	if (mo == nil)
		return(nil);


			/* SET OBJECT INFO */
			
	switch(type)
	{
		case	MO_TYPE_GROUP:
				SetMetaObjectToGroup(mo);
				break;
				
		case	MO_TYPE_MESH:
				SetMetaObjectToMesh(mo, data);
				break;
				
		case	MO_TYPE_MATERIAL:
				SetMetaObjectToMaterial(mo, data);
				break;
				
		case	MO_TYPE_MATRIX:
				SetMetaObjectToMatrix(mo, data);
				break;
				
		default:
				DoFatalAlert("\pMO_CreateNewObjectOfType: object type not recognized");
	}
	
	return(mo);
}


/****************** ALLOCATE EMPTY META OBJECT *****************/
//
// allocates an empty meta object and connects it to the linked list
//

static MetaObjectPtr AllocateEmptyMetaObject(u_long type, u_long subType)
{
MetaObjectHeader	*mo;
int					size;

			/* DETERMINE SIZE OF DATA TO ALLOC */
			
	switch(type)
	{
		case	MO_TYPE_GROUP:
				size = sizeof(MOGroupObject);
				break;
				
		case	MO_TYPE_MESH:
				size = sizeof(MOMeshObject);
				break;
				
		case	MO_TYPE_MATERIAL:
				size = sizeof(MOMaterialObject);
				break;
				
		case	MO_TYPE_MATRIX:
				size = sizeof(MOMatrixObject);
				break;

		default:
				DoFatalAlert("\pAllocateEmptyMetaObject: object type not recognized");
				return(nil);
	}
			

			/* ALLOC MEMORY FOR META OBJECT */
			
	mo = AllocPtr(size);	
	if (mo == nil)
		DoFatalAlert("\pAllocateEmptyMetaObject: AllocPtr failed!");


			/* INIT STRUCTURE */
			
	mo->cookie 		= MO_COOKIE;
	mo->type 		= type;
	mo->subType 	= subType;
	mo->data 		= nil;
	mo->nextNode 	= nil;		
	mo->refCount	= 1;							// initial reference count is always 1
	mo->parentGroup = nil;


		/***************************/
		/* ADD NODE TO LINKED LIST */
		/***************************/

		/* SEE IF IS ONLY NODE */
					
	if (gFirstMetaObject == nil)
	{
		if (gLastMetaObject)
			DoFatalAlert("\pAllocateEmptyMetaObject: gFirstMetaObject & gLastMetaObject should be nil");
	
		mo->prevNode = nil;
		gFirstMetaObject = gLastMetaObject = mo;
		gNumMetaObjects = 1;
	}
	
		/* ADD TO END OF LINKED LIST */
		
	else
	{		
		mo->prevNode = gLastMetaObject;		// point new prev to last
		gLastMetaObject->nextNode = mo;		// point old last to new
		gLastMetaObject = mo;				// set new last
		gNumMetaObjects++;
	}

	return(mo);
}

/******************* SET META OBJECT TO GROUP ********************/
//
// INPUT:	mo = meta object which has already been allocated and added to linked list.
//

static void SetMetaObjectToGroup(MOGroupObject *groupObj)
{
		
			/* INIT THE DATA */
			
	groupObj->objectData.numObjectsInGroup = 0;
}


/*************** SET META OBJECT TO MESH ***************/
//
// INPUT:	mo = meta object which has already been allocated and added to linked list.
//
// This takes the given input data and copies it.  It also boosts the ref count of
// any referenced items.
//

static void SetMetaObjectToMesh(MOMeshObject *geoObj, MOMeshData *data)
{
		
			/* INIT THE DATA */
		
	geoObj->objectData = *data;									// copy from input data		

	
		/* INCREASE MATERIAL REFERENCE COUNT */
			
	if (data->materials != nil)									// make sure this material ref is valid
		MO_GetNewReference(data->materials);
		
}


/******************* SET META OBJECT TO MATERIAL ********************/
//
// INPUT:	mo = meta object which has already been allocated and added to linked list.
//
// This takes the given input data and copies it. 
//

static void SetMetaObjectToMaterial(MOMaterialObject *matObj, MOMaterialData *inData)
{	
	
		/* COPY INPUT DATA */
		
	matObj->objectData = *inData;
}


/******************* SET META OBJECT TO MATRIX ********************/
//
// INPUT:	mo = meta object which has already been allocated and added to linked list.
//
// This takes the given input data and copies it. 
//

static void SetMetaObjectToMatrix(MOMatrixObject *matObj, OGLMatrix4x4 *inData)
{	
	
		/* COPY INPUT DATA */
		
	matObj->matrix = *inData;
}



#pragma mark -


/********************** MO: APPEND TO GROUP ****************/
//
// Attach new object to end of group
//

void MO_AppendToGroup(MOGroupObject *group, MetaObjectPtr newObject)
{
int	i;

			/* VERIFY COOKIE */

	if ((group->objectHeader.cookie != MO_COOKIE) || (((MetaObjectHeader *)newObject)->cookie != MO_COOKIE))
		DoFatalAlert("\pMO_AppendToGroup: cookie is invalid!");


	i = group->objectData.numObjectsInGroup++;		// get index into group list
	if (i >= MO_MAX_ITEMS_IN_GROUP)
		DoFatalAlert("\pMO_AppendToGroup: too many objects in group!");

	MO_GetNewReference(newObject);					// get new reference to object
	group->objectData.groupContents[i] = newObject;	// save object reference in group
}



#pragma mark -


/******************** MO: DRAW OBJECT ***********************/
//
// This recursive function will draw any objects submitted and parses groups.
//

void MO_DrawObject(const MetaObjectPtr object)
{
MetaObjectHeader	*objHead = object;
MOMeshObject	*vObj;

			/* VERIFY COOKIE */

	if (objHead->cookie != MO_COOKIE)
		DoFatalAlert("\pMO_DrawObject: cookie is invalid!");


			/* HANDLE TYPE */

	switch(objHead->type)
	{
		case	MO_TYPE_MESH:
				vObj = object;
				MO_DrawMesh(&vObj->objectData);				
				break;
	
		case	MO_TYPE_MATERIAL:
				MO_DrawMaterial(object);				
				break;

		case	MO_TYPE_GROUP:
				MO_DrawGroup(object);				
				break;
				
		case	MO_TYPE_MATRIX:
				MO_DrawMatrix(object);
				break;
				
		default:
			DoFatalAlert("\pMO_DrawObject: unknown type!");		
	}
}


/******************** MO_DRAW GROUP *************************/

void MO_DrawGroup(const MOGroupObject *object)
{
int	numChildren,i;

				/* VERIFY OBJECT TYPE */
				
	if (object->objectHeader.type != MO_TYPE_GROUP)
		DoFatalAlert("\pMO_DrawGroup: this isnt a group!");		


			/*************************************/
			/* PUSH CURRENT STATE ON STATE STACK */
			/*************************************/


	OGL_PushState();


				/***************/
				/* PARSE GROUP */
				/***************/

	numChildren = object->objectData.numObjectsInGroup;			// get # objects in group
	
	for (i = 0; i < numChildren; i++)
	{
		MO_DrawObject(object->objectData.groupContents[i]);
	}


			/******************************/
			/* POP OLD STATE OFF OF STACK */
			/******************************/
			
	OGL_PopState();
}


/******************** MO: DRAW MESH *************************/

void MO_DrawMesh(const MOMeshData *data)
{
Boolean		useTexture = false;
u_long 		materialFlags;
Boolean		needNormals;
	
			/**********************/
			/* SETUP VERTEX ARRAY */
			/**********************/
						
	glEnableClientState(GL_VERTEX_ARRAY);				// enable vertex arrays
	glVertexPointer(3, GL_FLOAT, 0, data->points);		// point to points array



			/***********************/
			/* SETUP VERTEX COLORS */
			/***********************/

	if (data->colorsFloat)									// do we have float colors?
	{
		glColorPointer(4, GL_FLOAT, 0, data->colorsFloat);
		glEnableClientState(GL_COLOR_ARRAY);				// enable color arrays
	}
	else
		glDisableClientState(GL_COLOR_ARRAY);				// no color data, so disable



			/****************************/
			/* SEE IF ACTIVATE MATERIAL */
			/****************************/

	if (data->materials != nil)									// are there any materials?	
	{		
		MO_DrawMaterial(data->materials);						// submit material

			/* IF TEXTURED, THEN ALSO ACTIVATE UV ARRAY */

		materialFlags = gMostRecentMaterial->objectData.flags;	// get material flags
		if (materialFlags & BG3D_MATERIALFLAG_TEXTURED)
		{
			if (data->uvs)
			{
				glTexCoordPointer(2, GL_FLOAT, 0,data->uvs);
				glEnableClientState(GL_TEXTURE_COORD_ARRAY);	// enable uv arrays
				
				useTexture = true;
			}			
		}
	}	
	else
		OGL_DisableTexture2D();							// no materials, thus no texture, thus turn this off
		

		/* WE DONT HAVE ENOUGH INFO TO DO TEXTURES, SO DISABLE */
								
	if (!useTexture)
	{
		glDisableClientState(GL_TEXTURE_COORD_ARRAY);
	}

			/************************/
			/* SETUP VERTEX NORMALS */
			/************************/
			//
			// We do this last because we need to know some things
			// before we can determine if normals are actually needed
			//
			
	if (data->normals == nil)							// see if we even have normals to pass
		needNormals = false;
	else
	if (!gMyState_Lighting)								// if no lighting, then probably don't need to pass normals
		needNormals = false;
	else
		needNormals = true;								// we need normals since lighting is on
		
	if (needNormals)
	{
		glNormalPointer(GL_FLOAT, 0, data->normals);
		glEnableClientState(GL_NORMAL_ARRAY);			// enable normal arrays			
	}
	else
		glDisableClientState(GL_NORMAL_ARRAY);			// disable normal arrays


			/***********/
			/* DRAW IT */
			/***********/
		
	glDrawElements(GL_TRIANGLES,data->numTriangles*3,GL_UNSIGNED_INT,&data->triangles[0]);


}





/************************ DRAW MATERIAL **************************/

void MO_DrawMaterial(MOMaterialObject *matObj)
{
MOMaterialData		*matData;
OGLColorRGBA		*diffuseColor,diffColor2;
Boolean				alreadySet;
u_long				matFlags;

					
	matData = &matObj->objectData;									// point to material data

	if (matObj->objectHeader.cookie != MO_COOKIE)					// verify cookie			
		DoFatalAlert("\pMO_DrawMaterial: bad cookie!");


				/****************************/
				/* SEE IF TEXTURED MATERIAL */
				/****************************/
				
	matFlags = matData->flags | gGlobalMaterialFlags;				// check flags of material & global
				
	if (matFlags & BG3D_MATERIALFLAG_TEXTURED)
	{	
				/* ACTIVATE MATERIAL */
				
		alreadySet = (matObj == gMostRecentMaterial);
		if (alreadySet)												// see if even need to bother resetting this
		{
			OGL_EnableTexture2D();									// just make sure textures are on
		}
		else
		{		
			OGL_Texture_SetOpenGLTexture(matData->textureName);	// set this texture active			
		}
		
				/*****************************/
				/* SET TEXTURE WRAPPING MODE */
				/*****************************/
				
						/* U */
						
		if (matFlags & BG3D_MATERIALFLAG_CLAMP_U)										// we want to clamp the U
		{
			if (!(matData->flags & BG3D_MATERIALFLAG_CLAMP_U_TRUE))						// see if clamping needs to be enabled
			{
			    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);	// nope, so set clamping
			    matData->flags |= BG3D_MATERIALFLAG_CLAMP_U_TRUE;						// and remember that we set it
			}
		}
		else																			// we DONT want to clamp U
		{
			if (matData->flags & BG3D_MATERIALFLAG_CLAMP_U_TRUE)						// see clamping is still enabled
			{
			    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
			    matData->flags &= ~BG3D_MATERIALFLAG_CLAMP_U_TRUE;						// and remember that we cleared it
			}
		}

						/* V */
						
		if (matFlags & BG3D_MATERIALFLAG_CLAMP_V)										// we want to clamp the V
		{
			if (!(matData->flags & BG3D_MATERIALFLAG_CLAMP_V_TRUE))						// see if clamping needs to be enabled
			{
			    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);	// nope, so set clamping
			    matData->flags |= BG3D_MATERIALFLAG_CLAMP_V_TRUE;						// and remember that we set it
			}
		}
		else																			// we DONT want to clamp V
		{
			if (matData->flags & BG3D_MATERIALFLAG_CLAMP_V_TRUE)						// see clamping is still enabled
			{
			    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
			    matData->flags &= ~BG3D_MATERIALFLAG_CLAMP_V_TRUE;						// and remember that we cleared it
			}
		}
	
	}
	else
		OGL_DisableTexture2D();						// not textured, so disable textures
	

			/********************/
			/* COLORED MATERIAL */
			/********************/

	diffuseColor = &matData->diffuseColor;			// point to diffuse color
		
	if (gGlobalTransparency != 1.0f)				// see if need to factor in global transparency
	{
		diffColor2.r = diffuseColor->r;
		diffColor2.g = diffuseColor->g;
		diffColor2.b = diffuseColor->b;
		diffColor2.a = diffuseColor->a * gGlobalTransparency;		
	}
	else
		diffColor2 = *diffuseColor;					// copy to local so we can apply filter to it without munging original


			/* APPLY COLOR FILTER */
			
	diffColor2.r *= gGlobalColorFilter.r;
	diffColor2.g *= gGlobalColorFilter.g;
	diffColor2.b *= gGlobalColorFilter.b;

	OGL_SetColor4fv(&diffColor2);						// set current diffuse color


		/* SEE IF NEED TO ENABLE BLENDING */

		
	if ((diffColor2.a != 1.0f) || (matFlags & BG3D_MATERIALFLAG_ALWAYSBLEND))		// if has alpha, then we need blending on	
		OGL_EnableBlend();
	else
		OGL_DisableBlend();


			/* SAVE THIS STUFF */
			
	gMostRecentMaterial = matObj;
}


/************************ DRAW MATRIX **************************/

void MO_DrawMatrix(const MOMatrixObject *matObj)
{
const OGLMatrix4x4		*m;

	m = &matObj->matrix;							// point to matrix

				/* MULTIPLY CURRENT MATRIX BY THIS */
	
	glMultMatrixf((GLfloat *)m);
}


#pragma mark -

/******************** MO GET NEW REFERENCE *********************/

MetaObjectPtr MO_GetNewReference(MetaObjectPtr mo)
{
MetaObjectHeader *h = mo;

	h->refCount++;
	
	return(mo);
}




/********************** MO DISPOSE OBJECT REFERENCE ******************************/
//
// NOTE:  	Groups and other objects are NOT sub-recursed.  When a group is de-referenced, only that group object is affected.
//

void MO_DisposeObjectReference(MetaObjectPtr obj)
{
MetaObjectHeader	*header = obj;
MOMeshObject	*vObj;

	if (obj == nil)
		DoFatalAlert("\pMO_DisposeObjectReference: obj == nil");

	if (header->cookie != MO_COOKIE)					// verify cookie			
		DoFatalAlert("\pMO_DisposeObjectReference: bad cookie!");


			/**************************************/
			/* DEC REFERENCE COUNT OF THIS OBJECT */
			/**************************************/
	
	header->refCount--;									// dec ref count
	
	if (header->refCount < 0)							// see if over did it
		DoFatalAlert("\pMO_DisposeObjectReference: refcount < 0!");
		
	if (header->refCount == 0)							// see if we can DELETE this node
	{
			/* NO MORE REFERENCES, SO DELETE DATA */

		switch(header->type)
		{
			case	MO_TYPE_GROUP:
					MO_DisposeObject_Group(obj);
					break;
		
			case	MO_TYPE_MESH:
					vObj = obj;
					MO_DeleteObjectInfo_Mesh(&vObj->objectData);
					break;
					
			case	MO_TYPE_MATERIAL:
					MO_DeleteObjectInfo_Material(obj);
					break;		
					
		}
	
			/* DELETE THE OBJECT NODE */
			
		MO_DetachFromLinkedList(obj);					// detach from linked list

		header->cookie = 0xdeadbeef;					// devalidate cookie
		SafeDisposePtr(obj);								// free memory used by object
		return;
	}		
}



/***************** DETACH FROM LINKED LIST *********************/

static void MO_DetachFromLinkedList(MetaObjectPtr obj)
{
MetaObjectHeader	*header = obj;
MetaObjectHeader	*prev,*next;


			/* VERIFY COOKIE */

	if (header->cookie != MO_COOKIE)
		DoFatalAlert("\pMO_DetachFromLinkedList: cookie is invalid!");


	prev = header->prevNode;
	next = header->nextNode;
	
			/* SEE IF WAS 1ST NODE IN LIST */
			
	if (prev == nil)
	{
		gFirstMetaObject = next;
		if (gFirstMetaObject)
			gFirstMetaObject->prevNode = nil;
	}
	
			/* SEE IF WAS LAST NODE IN LIST */
			
	if (next == nil)
	{
		gLastMetaObject = prev;	
		if (gLastMetaObject)
			gLastMetaObject->nextNode = nil;
	}
	
			/* SOMEWHERE IN THE MIDDLE */
	
	else		
	if (prev != nil)
	{
		prev->nextNode = next;
		next->prevNode = prev;
	}
	
	gNumMetaObjects--;	
	
	if (gNumMetaObjects < 0)
		DoFatalAlert("\pMO_DetachFromLinkedList: counter mismatch");
		
	if (gNumMetaObjects == 0)
	{
		if (prev || next)							// if all gone, then prev & next should be nil
			DoFatalAlert("\pMO_DetachFromLinkedList: prev/next should be nil!");
	
		if (gFirstMetaObject != nil)
			DoFatalAlert("\pMO_DetachFromLinkedList: gFirstMetaObject should be nil!");

		if (gLastMetaObject != nil)
			DoFatalAlert("\pMO_DetachFromLinkedList: gLastMetaObject should be nil!");
	}
}


/**************** DISPOSE OBJECT:  GROUP ************************/
//
// Decrement the references of all objects in the group.
//

static void MO_DisposeObject_Group(MOGroupObject *group)
{
int	i,n;

	n = group->objectData.numObjectsInGroup;				// get # objects in group

	for (i = 0; i < n; i++)
	{
		MO_DisposeObjectReference(group->objectData.groupContents[i]);	// dispose of this object's ref
	}
}


/****************** DELETE OBJECT INFO: MESH *******************/
//
// Assumes the contents (the materials) have already been dereferenced!
//

void MO_DeleteObjectInfo_Mesh(MOMeshData *data)
{

			/* DEREFERENCE ANY MATERIALS */
			
	MO_DisposeObjectReference(data->materials);	// dispose of this object's ref


}



/****************** DELETE OBJECT INFO: MATERIAL *******************/

static void MO_DeleteObjectInfo_Material(MOMaterialObject *obj)
{
MOMaterialData		*data = &obj->objectData;

		/* DISPOSE OF TEXTURE NAMES */
				
	if (data->hasTexture)
		glDeleteTextures(1, &data->textureName);
}













