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


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

#include "3dmath.h"


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

static MetaObjectPtr AllocateEmptyMetaObject(u_long type, u_long subType);
static void SetMetaObjectToGroup(MOGroupObject *groupObj);
static void SetMetaObjectToMesh(MOMeshObject *geoObj, MOMeshData *data);
static void SetMetaObjectToMaterial(MOMaterialObject *matObj, MOMaterialData *inData);
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);
static void MO_GetNewGroupReference(MOGroupObject *obj);
static void MO_GetNewVertexArrayReference(MOMeshObject *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;

u_long				gCurrentPickID = 0;


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

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


#pragma mark -

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

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

	h->refCount++;
	
		/********************************/
		/* SEE IF NEED TO SUB INCREMENT */
		/********************************/
		
	switch(h->type)
	{
		case	MO_TYPE_GROUP:
				MO_GetNewGroupReference(mo);
				break;
				
				
		case	MO_TYPE_MESH:
				MO_GetNewVertexArrayReference(mo);	
				break;
	}
	
	return(mo);
}


/**************** MO: GET NEW GROUP REFERENCE ******************/
//
// Recursively increment ref counts of everything inside the group
//

static void MO_GetNewGroupReference(MOGroupObject *obj)
{
MOGroupData	*data = &obj->objectData;
int			i;

	for (i = 0; i < data->numObjectsInGroup; i++)
	{
		MO_GetNewReference(data->groupContents[i]);
	}
}


/***************** MO: GET NEW VERTEX ARRAY REFERENCE *************/
//
// Recursively increment ref counts of all texture objects in geometry
//

static void MO_GetNewVertexArrayReference(MOMeshObject *obj)
{
MOMeshData	*data = &obj->objectData;

	MO_GetNewReference(data->material);
}

#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");
	}
			

			/* 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.
//

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

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




/******************* 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
}

/**************** MO: ATTACH TO GROUP START **************/
//
// Attach new object to START of group
//

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

			/* VERIFY COOKIE */

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


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

	MO_GetNewReference(newObject);					// get new reference to object
	
	
			/* SHIFT ALL EXISTING OBJECTS UP */
			
	for (j = i; j > 0; j--)
		group->objectData.groupContents[j] = group->objectData.groupContents[j-1];
	
	group->objectData.groupContents[0] = newObject;	// save object ref into group
}



#pragma mark -

/********************** MO DISPOSE OBJECT REFERENCE ******************************/
//
// NOTE: this is a recursive function in the case of group objects
//
// This will dispose of a single reference to the input object and any sub-objects in the
// case of a group.  If the refCount is 0, then the object is freed.
//

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!");


			/*************************************/
			/* HANDLE SPECIFIC DISPOSE FUNCTIONS */
			/*************************************/
			//
			// This handles the decrementing of sub references such as objects
			// in a group or material references.
			//

	switch(header->type)
	{
		case	MO_TYPE_GROUP:
				MO_DisposeObject_Group(obj);
				break;
				
		case	MO_TYPE_MESH:
				vObj = obj;
				MO_DisposeObject_Geometry_VertexArray(&vObj->objectData);
				break;	

	}

			/**************************************/
			/* 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
	{
			/* DELETE OBJECT'S SPECIFIC INFO */

		switch(header->type)
		{
			case	MO_TYPE_MESH:
					vObj = obj;
					MO_DeleteObjectInfo_Geometry_VertexArray(&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
		DisposePtr(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
	}
}


/****************** DISPOSE OBJECT: GEOMETRY : VERTEX ARRAY *******************/
//
// Decrement references to all of the materials used in this geometry.
//
// NOTE: this function is not static because the Skeleton code calls it for stuff.
//

void MO_DisposeObject_Geometry_VertexArray(MOMeshData *data)
{
	if (data->material != nil)
		MO_DisposeObjectReference(data->material);	// dispose of this object's ref
}


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

void MO_DeleteObjectInfo_Geometry_VertexArray(MOMeshData *data)
{
		/* DISPOSE OF VARIOUS ARRAYS */
		
	if (data->points)
	{
		DisposePtr((Ptr)data->points);
		data->points = nil;
	}

	if (data->normals)
	{
		DisposePtr((Ptr)data->normals);
		data->normals = nil;
	}

	if (data->uvs)
	{
		DisposePtr((Ptr)data->uvs);
		data->uvs = nil;		
	}

	if (data->colorsByte)
	{
		DisposePtr((Ptr)data->colorsByte);
		data->colorsByte = nil;
	}

	if (data->colorsFloat)
	{
		DisposePtr((Ptr)data->colorsFloat);
		data->colorsFloat = nil;
	}

	if (data->triangles)
	{
		DisposePtr((Ptr)data->triangles);
		data->triangles = nil;
	}
}



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

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

		/* DISPOSE OF TEXTURE NAME */
				
	if (data->hasTexture)
	{
		if (data->texturePixels)										// see if still have source buffer, then delete it too
			DisposePtr(data->texturePixels);					
	}
}






