/****************************/
/*   	PROCESS.C     		*/
/* (c)1995 Pangea Software  */
/* By Brian Greenstone      */
/****************************/


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

#include <QD3D.h>
#include <QD3DCamera.h>
#include <QD3DDrawContext.h>
#include <QD3DErrors.h>
#include <QD3DGeometry.h>
#include <QD3DGroup.h>
#include <QD3DIO.h>
#include <QD3DLight.h>
#include "QD3DMath.h"
#include "QD3DPick.h"
#include "QD3DRenderer.h"
#include "QD3DShader.h"
#include "QD3DStyle.h"
#include "QD3DView.h"
#include	<QD3DTransform.h>

#include <timer.h>
#include "myglobals.h"
#include "objects.h"
#include "menus.h"
#include "windows.h"
#include "events.h"
#include "misc.h"
#include "process.h"
#include "qd3d_support.h"
#include "3dmf.h"
#include "dialogStuff.h"

extern	EventRecord			gTheEvent;
extern	NewObjectDefinitionType	gNewObjectDefinition;
extern	ObjNode				*gCurrentNode;
extern	float				gFramesPerSecond;
extern	TQ3Point3D			gRoadSplinePoints[];


typedef struct
{
	TQ3MeshVertex		meshVertex;
	TQ3Point3D			vertexCoord;
	TQ3AttributeSet		attributeSet;
	long				switchNum;
}DupeVertexType;

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

static void MoveCamera(void);
static void MoveModel(void);
static Boolean InitTextStreamOutput(void);
static void WriteModelGeometry(QD3DSetupOutputType *setupInfo);
static void PreTransformGeometry(void);
static void PreTransformGeometry_Recurse(TQ3Object obj);
static void PreTransformMesh(TQ3GeometryObject theMesh);
static void PreTransformBox(TQ3GeometryObject theMesh);
static void PreTransformAttribute(TQ3AttributeSet theAttribute);
static void RemapFaceVertecies(TQ3GeometryObject theMesh,long numVertecies,DupeVertexType *vertDupeList);
static void RemoveMismatchedNormalAttributes(TQ3AttributeSet attr1, TQ3AttributeSet attr2);
static void CalculateMeshVertexNormals(TQ3GeometryObject theMesh);
static void PreTransformGeneralPolygon(TQ3GeometryObject thePolygon);
static void ReverseMeshVertexOrder(TQ3GeometryObject theMesh);


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

#define	MODEL_WIND_ID			400

#define	TARGET_SIZE				7.5


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

WindowPtr				gModelWindow = nil;
QD3DSetupOutputType		gModelViewInfo;
ObjNode					*gCameraTrackerObj;
TQ3Point3D				gCurrentCameraCoords;
Boolean					gShowContentStructure = TRUE;
Boolean					gShow3DMFSummary = TRUE;
Boolean					gSaveTextToFile = FALSE;
FSSpec					gStreamFSSpec;
short					gStreamRefNum;

Boolean					gFuseAllMeshes = FALSE;
Boolean					gPreTransform = TRUE;
Boolean					gReverseBackfaces = FALSE;
Boolean					gSaveAsBinary = TRUE;
Boolean					gCalcVertexNormals = TRUE;
Boolean					gRemoveDupeVerts = TRUE;

ObjNode		*gModelObjNode = nil;

TQ3Object			gModelGroup = nil,gTempGroup;

TQ3Matrix4x4 		gWorkMatrix;


/************** INIT MODEL WINDOW *******************/

void InitModelWindow(void)
{
QD3DSetupInputType	viewDef;
TQ3Point3D			cameraFrom = { 0,00, 10 };
TQ3Point3D			cameraTo = { 0.0, 0, 0 };
TQ3Vector3D			cameraUp = { 0.0, 1.0, 0.0 };
TQ3ColorARGB		clearColor = {1,0,0,.5};
TQ3ColorRGB			ambientColor = { 1.0, 1.0, 1.0 };
TQ3Vector3D			fillDirection1 = {1, -.3, -.8 };
TQ3Vector3D			fillDirection2 = { -1, -.1, -.2 };


			/* CREATE THE WINDOW */
			
	gModelWindow = GetNewCWindow(MODEL_WIND_ID, NIL_POINTER,MOVE_TO_FRONT);
	if (gModelWindow == nil)
		DoFatalAlert("\pWhere did the GameWindow window go?");					
	SetPort((GrafPtr)gModelWindow);


			/* SET QD3D PARAMETERS */
			
	viewDef.view.displayWindow 		= gModelWindow;
	viewDef.view.rendererType 		= kQ3RendererTypeInteractive;
	viewDef.view.clearColor 		= clearColor;
	viewDef.view.paneClip.left 		= 
	viewDef.view.paneClip.right 	= 
	viewDef.view.paneClip.top 		= 0;
	viewDef.view.paneClip.bottom 	= 0; 

	viewDef.styles.interpolation 	= kQ3InterpolationStyleVertex; 
	viewDef.styles.backfacing 		= kQ3BackfacingStyleRemove; //Both; 
	viewDef.styles.fill				= kQ3FillStyleFilled; 
	viewDef.styles.usePhong 		= TRUE; 

	viewDef.camera.from 			= cameraFrom;
	viewDef.camera.to 				= cameraTo;
	viewDef.camera.up 				= cameraUp;
	viewDef.camera.hither 			= 0.01;
	viewDef.camera.yon 				= 400;
	viewDef.camera.fov 				= 0.9;

	viewDef.lights.ambientBrightness = 0.1;
	viewDef.lights.ambientColor 	= ambientColor;
	viewDef.lights.numFillLights 	= 2;
	viewDef.lights.fillDirection[0] = fillDirection1;
	viewDef.lights.fillDirection[1] = fillDirection2;
	viewDef.lights.fillColor[0] 	= ambientColor;
	viewDef.lights.fillColor[1] 	= ambientColor;
	viewDef.lights.fillBrightness[0] = 1.1;
	viewDef.lights.fillBrightness[1] = 0.6;


	QD3D_SetupWindow(&viewDef, &gModelViewInfo);
	
	
		/* MAKE CAMERA TRACKKER OBJECT */
		
	gNewObjectDefinition.genre = EVENT_GENRE;
	gNewObjectDefinition.flags = 0;
	gNewObjectDefinition.slot = 5;
	gNewObjectDefinition.moveCall = MoveCamera;
	gCameraTrackerObj = MakeNewDisplayGroupObject(&gNewObjectDefinition);
	if (gCameraTrackerObj == nil)
		return;
		
}



/******************* DRAW MODEL WINDOW *********************/
//
// Calls the support function QD3D_DrawScene.
// It passes the view info and a pointer to the game draw callback.
//

void DrawModelWindow(void)
{
	QD3D_DrawScene(&gModelViewInfo,(void *)DrawObjects);
}



/*************** DO MODEL WINDOW NULL EVENT **********************/

void DoModelWindowNullEvent(void)
{
		QD3D_CalcFramesPerSecond();					

		ReadKeyboard();
		MoveObjects();
		DrawModelWindow();
}

/*************** DO MODEL KEY DOWN ********************/

void DoModelKeyDown(char key)
{
	key = key;

}


/****************** UPDATE MODEL WINDOW ******************/
//
// Called when there is an update event from the event manager
//

void UpdateModelWindow(void)
{
	
			/* RE-RENDER SCENE */
			
	DrawModelWindow();
	
}



/************ MOVE CAMERA ******************/

static void MoveCamera(void)
{

}

/****************** OPEN NEW MODEL **********************/

void OpenNewModel(void)
{
TQ3Object	newModel;
ObjNode		*newObj;
float 		scale;

			/* DO READ OPTIONS */
			
	if (Do3DMFOpenOptions())
		return;

			/* SEE IF INIT BREAKDOWN DISPLAY */
			
	if (gShowContentStructure)
	{
		InitBreakdownWindow();
		if (gSaveTextToFile)
		{
			if (InitTextStreamOutput())
				return;
		}
	}
	
				/* SEE IF GET RID OF ANY EXISTING MODEL */
				
	if (gModelObjNode != nil)
	{
		DeleteObject(gModelObjNode);
		gModelObjNode = nil;
	}

			/* LOAD THE MODEL & DISPLAY CONTENTS */
			
	newModel = Load3DMFModel(nil);
	if (newModel == nil)
		return;

			/* CLOSE FILE (IF ANY) */
			
	if (gShowContentStructure && gSaveTextToFile)
		FSClose(gStreamRefNum);


				/* SHOW THE OBJECT */

	gNewObjectDefinition.genre = DISPLAY_GROUP_GENRE;
	gNewObjectDefinition.coord.x = 0;
	gNewObjectDefinition.coord.y = 0;
	gNewObjectDefinition.coord.z = 0;
	gNewObjectDefinition.flags = OBJ_MODE_DRAW;
	gNewObjectDefinition.slot = 100;
	gNewObjectDefinition.moveCall = MoveModel;
	newObj = MakeNewDisplayGroupObject(&gNewObjectDefinition);
	if (newObj == nil)
		return;

	gModelObjNode = newObj;									// remember this
	gModelGroup = newModel;

	scale = CalcScaleToMakeFit(&gModelViewInfo,newModel, TARGET_SIZE);			// calc scale 
	newObj->ScaleX = newObj->ScaleY = newObj->ScaleZ = scale;

	AttachGeometryToDisplayGroupObject(newObj,newModel);
	
	
	
}


/***************** MOVE MODEL *********************/

static void MoveModel(void)
{
ObjNode	*theNode = gCurrentNode;

	theNode->RotY += 0.4/gFramesPerSecond;
	theNode->RotZ += 0.5/gFramesPerSecond;
	theNode->RotX += 0.6/gFramesPerSecond;
	UpdateObjectTransforms(theNode);
}


/***************** INIT TEXT STREAM OUTPUT **********************/
//
// OUTPUT: true = cancelled
//

static Boolean InitTextStreamOutput(void)
{
StandardFileReply	reply;
OSErr		iErr;
		
	StandardPutFile("\pSave Stream TEXT as...","\pstream.txt",&reply);
	if (!reply.sfGood)								// see if cancelled 
		return(TRUE);

	gStreamFSSpec = reply.sfFile;	
	
	if (reply.sfReplacing)
		FSpDelete(&gStreamFSSpec);									// delete any old one
	
				/* CREATE TEXT FILE */
				
	iErr = FSpCreate(&gStreamFSSpec, 'CWIE', 'TEXT', smSystemScript);		// create new file
	if (iErr)
	{
		DoAlert("\pError Creating Output text file.");
		return(TRUE);
	}

			/* OPEN FILE FOR WRITING */
			
	iErr = FSpOpenDF(&gStreamFSSpec, fsCurPerm, &gStreamRefNum);
	if (iErr)
		DoFatalAlert("\pError Opening Output text file.");

	return(FALSE);
}


/****************** SAVE OPTIMIZED MODEL **********************/

void SaveOptimizedModel(void)
{
			/* DO SAVE OPTIONS */
			
	if (Do3DMFSaveOptions())
		return;

	ShowWaitingWindow();

			/* PRE TRANSFORM MESH VERTECIES */
			
	if (gPreTransform)
	{
		PreTransformGeometry();
	}


	KillWaitingWindow();

			/* SAVE THE FILE OUT */

	Save3DMFModel(&gModelViewInfo,nil,WriteModelGeometry);	
}


/**************** WRITE MODEL GEOMETRY ********************/

static void WriteModelGeometry(QD3DSetupOutputType *setupInfo)
{
TQ3Status				status;
TQ3GroupPosition	position;
TQ3Object   		object;

	if (gModelObjNode == nil)
		return;

		
		
		/* IF BASE IS GROUP, THEN WRITE OUT OBJECTS IN GROUP SEPARATELY */
	
	if (Q3Object_IsType(gModelGroup,kQ3ShapeTypeGroup))
 	{
  		for (Q3Group_GetFirstPosition(gModelGroup, &position); position != nil; Q3Group_GetNextPosition(gModelGroup, &position))	// scan all objects in group
 		{
   			Q3Group_GetPositionObject (gModelGroup, position, &object);		// get object from group
			if (object != NULL)
   			{
				status = Q3Object_Submit(object,setupInfo->viewObject);		// write it
				if (status == kQ3Failure)
					DoFatalAlert("\pWriting Model Failed!");
   				Q3Object_Dispose(object);
   			}
  		}
	}
	
		/* BASE ISNT A GROUP, SO JUST WRITE WHATEVER IT IS OUT */
	else
	{
		status = Q3DisplayGroup_Submit(gModelGroup,setupInfo->viewObject);		// write it
		if (status == kQ3Failure)
			DoFatalAlert("\pWriting Model Failed!");
	}
		
		
		
		
		
}


/****************** PRE TRANSFORM GEOMETRY ***********************/

static void PreTransformGeometry(void)
{
ObjNode	*newObj;
float	scale;

	
			/* CREATE A NEW GROUP OBJECT TO HOLD CONVERTED GEOMETRIES */
				
	gTempGroup = Q3DisplayGroup_New();
	if (gTempGroup == nil)
		DoFatalAlert("\p Couldnt allocate gTempGroup!");

			/* MODIFY DATA & ADD TO NEW GROUP (recursive) */
			
	Q3Matrix4x4_SetIdentity(&gWorkMatrix);
	PreTransformGeometry_Recurse(gModelGroup);
	
	
			/* SET NEW GROUP AS USED GROUP */
			
	DeleteObject(gModelObjNode);								// delete old ObjNode
	
	gNewObjectDefinition.genre = DISPLAY_GROUP_GENRE;			// make new one
	gNewObjectDefinition.coord.x = 0;
	gNewObjectDefinition.coord.y = 0;
	gNewObjectDefinition.coord.z = 0;
	gNewObjectDefinition.flags = OBJ_MODE_DRAW;
	gNewObjectDefinition.slot = 100;
	gNewObjectDefinition.moveCall = MoveModel;
	newObj = MakeNewDisplayGroupObject(&gNewObjectDefinition);

	gModelObjNode = newObj;										// remember this
	gModelGroup = gTempGroup;

	scale = CalcScaleToMakeFit(&gModelViewInfo,gTempGroup, TARGET_SIZE);			// calc scale 
	newObj->ScaleX = newObj->ScaleY = newObj->ScaleZ = scale;


	AttachGeometryToDisplayGroupObject(newObj,gTempGroup);
}


/****************** PRE TRANSFORM GEOMETRY - RECURSE ***********************/

static void PreTransformGeometry_Recurse(TQ3Object obj)
{
TQ3Matrix4x4		transform;
TQ3GroupPosition	position;
TQ3Object   		object;
TQ3Matrix4x4  		stash;
TQ3ObjectType	oType;


				/* SEE IF ACCUMULATE TRANSFORM */
				
	if (Q3Object_IsType(obj,kQ3ShapeTypeTransform))
	{
  		Q3Transform_GetMatrix(obj,&transform);
  		Q3Matrix4x4_Multiply(&transform,&gWorkMatrix,&gWorkMatrix);
 	}
	else
				/* SEE IF FOUND GEOMETRY */

	if (Q3Object_IsType(obj,kQ3ShapeTypeGeometry))
	{
		oType = Q3Geometry_GetType(obj);						// get geometry type
		switch(oType)
		{
			case	kQ3GeometryTypeMesh:
					PreTransformMesh(obj);						// pre-transform mesh object & add to gTempGroup
					if (gReverseBackfaces)
						ReverseMeshVertexOrder(obj);			// reverse backfaces
					if (gCalcVertexNormals)
						CalculateMeshVertexNormals(obj);		// calc vertex normals for mesh
					break;
					
			case	kQ3GeometryTypeBox:
					PreTransformBox(obj);						// pre-transform box object & add to gTempGroup
					break;

			case	kQ3GeometryTypeGeneralPolygon:
					PreTransformGeneralPolygon(obj);
					break;

		}
	}
	else
				/* SEE IF FOUND ATTRIBUTE */

	if (Q3Object_IsType(obj,kQ3SetTypeAttribute))
	{
		position = Q3Group_AddObject(gTempGroup, obj);			// add attrib temp group
		if (position == 0)
			DoFatalAlert("\p Q3Group_AddObject Failed!");
	}
	else
				/* SEE IF FOUND SHADER */

	if (Q3Object_IsType(obj,kQ3ShapeTypeShader))
	{
		position = Q3Group_AddObject(gTempGroup, obj);			// add shader temp group
		if (position == 0)
			DoFatalAlert("\p Q3Group_AddObject Failed!");
	}
	else
	
			/* SEE IF RECURSE SUB-GROUP */

	if (Q3Object_IsType(obj,kQ3ShapeTypeGroup))
 	{
  		stash = gWorkMatrix;										// push matrix
  		for (Q3Group_GetFirstPosition(obj, &position); position != nil; Q3Group_GetNextPosition(obj, &position))	// scan all objects in group
 		{
   			Q3Group_GetPositionObject (obj, position, &object);		// get object from group
			if (object != NULL)
   			{
    			PreTransformGeometry_Recurse(object);				// sub-recurse this object
    			Q3Object_Dispose(object);
   			}
  		}
  		gWorkMatrix = stash;										// pop matrix
	}

}



/****************** PRETRANSFORM MESH *********************/


static void PreTransformMesh(TQ3GeometryObject theMesh)
{
TQ3GroupPosition	myGroupPosition;
TQ3Status			status;
unsigned long		numVertecies,vertNum,i;
TQ3MeshIterator		iterator;
TQ3MeshVertex		meshVertex;
TQ3Point3D			vertexCoord;
TQ3AttributeSet		attributeSet;
DupeVertexType		*vertDupeList;

	status = Q3Mesh_DelayUpdates(theMesh);

	status = Q3Mesh_GetNumVertices(theMesh, &numVertecies);				// get # vertices
	
	
				/******************/
				/* SCAN VERTECIES */
				/******************/
				
	if (numVertecies > 0)
	{
				/* ALLOC MEM FOR VERTEX COORD LIST */
				
		vertDupeList = (DupeVertexType *)AllocPtr(sizeof(DupeVertexType)*numVertecies);
		if (vertDupeList == nil)
			DoFatalAlert("\pCouldn't alloc memory for DupeVertexType");
		vertNum = 0;
	
		meshVertex = Q3Mesh_FirstMeshVertex(theMesh, &iterator);							// get 1st vertex in mesh	
		do
		{
					/* GET COORDS OF VERTEX */
					
			status = Q3Mesh_GetVertexCoordinates(theMesh, meshVertex, &vertexCoord);
			if (status != kQ3Success)
				DoFatalAlert("\pQ3Mesh_GetVertexCoordinates failed!");
				
					/* APPLY CURRENT TRANSFORM MATRIX TO IT */
						
			Q3Point3D_Transform(&vertexCoord, &gWorkMatrix, &vertexCoord);				
				
					/* SET NEW COORDS OF VERTEX */
					
			status = Q3Mesh_SetVertexCoordinates(theMesh, meshVertex, &vertexCoord);				
			if (status != kQ3Success)
				DoFatalAlert("\pQ3Mesh_SetVertexCoordinates failed!");


				/* APPLY MATRIX TO VERTEX ATTRIBUTES */
				
			status = Q3Mesh_GetVertexAttributeSet(theMesh, meshVertex, &attributeSet);
			if ((status == kQ3Success) && (attributeSet != nil))
			{
				PreTransformAttribute(attributeSet);
				vertDupeList[vertNum].attributeSet = attributeSet;
				Q3Object_Dispose(attributeSet);
			}
			else
				vertDupeList[vertNum].attributeSet = nil;
			
						/* KEEP INFO */
						
			vertDupeList[vertNum].vertexCoord = vertexCoord;				
			vertDupeList[vertNum].meshVertex = meshVertex;	
			vertDupeList[vertNum].switchNum	= -1;			
			vertNum++;

			meshVertex = Q3Mesh_NextMeshVertex(&iterator);									// get next vertex
		}while(meshVertex != nil);

						/* SEE IF REMOVE DUPE VERTS */
						
		if (gRemoveDupeVerts)
		{
						/****************************/		
						/* MARK DUPLICATE VERTECIES */
						/****************************/		
			
			for (vertNum = 0; vertNum < numVertecies; vertNum++)
			{
				for (i = vertNum+1; i < numVertecies; i++)
				{
					if ((vertDupeList[vertNum].vertexCoord.x == vertDupeList[i].vertexCoord.x) &&	// check if coords match
						(vertDupeList[vertNum].vertexCoord.y == vertDupeList[i].vertexCoord.y) &&
						(vertDupeList[vertNum].vertexCoord.z == vertDupeList[i].vertexCoord.z))
					{
						vertDupeList[vertNum].switchNum = i;										// set switch to the dupe
						RemoveMismatchedNormalAttributes(vertDupeList[vertNum].attributeSet,		// if normal attributes don't match, then just remove them
														vertDupeList[i].attributeSet);
						
						break;																		// skip to next vert
					}	
				}
			}
			
			for (vertNum = 0; vertNum < numVertecies; vertNum++)						// clean switch paths
			{
				if (vertDupeList[vertNum].switchNum != -1)								// see if this has a switch
				{
					i = vertDupeList[vertNum].switchNum;
					while(vertDupeList[i].switchNum != -1)								// clean sub-switching
					{
						i = vertDupeList[vertNum].switchNum = vertDupeList[i].switchNum;
					}
				}
			}
	
			
						/****************************/		
						/*   REMAP FACE VERTECIES   */
						/****************************/		
			
			RemapFaceVertecies(theMesh,numVertecies,vertDupeList);
			
	
						/* DELETE DUPLICATE VERTECIES */
						
			for (vertNum = 0; vertNum < numVertecies; vertNum++)
			{
				if (vertDupeList[vertNum].switchNum != -1)								// see if this has a switch
				{
					status = Q3Mesh_VertexDelete(theMesh, vertDupeList[vertNum].meshVertex);
				}
			}
		}
		
		DisposePtr((Ptr)vertDupeList);
	}

	status = Q3Mesh_ResumeUpdates(theMesh);		


			/* ADD MODIFIED MESH TO THE TEMP GROUP */

	myGroupPosition = Q3Group_AddObject(gTempGroup, theMesh);	// add to temp group
	if (myGroupPosition == 0)
		DoFatalAlert("\p Q3Group_AddObject Failed!");
		
}


/***************** REMAP FACE VERTECIES ***************************/

static void RemapFaceVertecies(TQ3GeometryObject theMesh,long numVertecies,DupeVertexType *vertDupeList)
{
TQ3Status			status;
unsigned long		numFaces;
TQ3MeshFace			meshFace;
TQ3MeshIterator		vertIterator,faceIterator;
TQ3MeshVertex		meshVertex[1024];
long				vertNum,numVertsInFace,i,j;
TQ3AttributeSet		attributeSet;
Boolean				remaped;

	status = Q3Mesh_GetNumFaces(theMesh, &numFaces);					// get # faces
	if (numFaces == 0)
		return;
		
				/* SCAN FACES */
				
	meshFace = Q3Mesh_FirstMeshFace(theMesh, &faceIterator);			// get 1st face in mesh
	do
	{
				/* GET FACE ATTRIB SET */
				
		status = Q3Mesh_GetFaceAttributeSet(theMesh, meshFace, &attributeSet);
		if (status != kQ3Success)
			attributeSet = nil;	
	
	
				/* BUILD VERTEX LIST FOR FACE */
				
		numVertsInFace = 0;
		meshVertex[0] = Q3Mesh_FirstFaceVertex(meshFace, &vertIterator);			// get 1st vertex in face
		do
		{
			numVertsInFace++;
			if (numVertsInFace >= 1024)
				DoFatalAlert("\pError, face has > 1024 vertecies which is all this app can handle!");
			meshVertex[numVertsInFace] = Q3Mesh_NextFaceVertex(&vertIterator);		// get vertex
		}while(meshVertex[numVertsInFace] != nil);
		
		
				/* REMAP THE FACE VERTS */
		
		remaped = FALSE;
		for (vertNum = 0; vertNum < numVertsInFace; vertNum++)
		{
			for (i = 0; i < numVertecies; i++)									// scan for match in dupe list
			{
				if (meshVertex[vertNum] == vertDupeList[i].meshVertex)			// see if found  a match
				{
					if (vertDupeList[i].switchNum != -1)						// see if need to switch
					{
						j = vertDupeList[i].switchNum;							// get index to vert to remap to
						meshVertex[vertNum] = vertDupeList[j].meshVertex;
						remaped = TRUE;
						break;													// skip to next vertex
					}
				}
			}
		}
		
				/* CREATE NEW FACE WITH REMAPED VERTS */
				
		if (remaped)
		{
			Q3Mesh_FaceNew(theMesh, numVertsInFace, &meshVertex[0], attributeSet);		// make new
			Q3Mesh_FaceDelete(theMesh, meshFace);										// delete old face
		}
		
		if (attributeSet != nil)
			Q3Object_Dispose(attributeSet);
		
		meshFace = Q3Mesh_NextMeshFace(&faceIterator);								// get next face
	}while(meshFace != nil);
}


/************* REMOVE MISMATCHED NORMAL ATTRIBUTES *************************/

static void RemoveMismatchedNormalAttributes(TQ3AttributeSet attr1, TQ3AttributeSet attr2)
{
TQ3Vector3D	v1,v2;

			/* SEE IF IS ATTRIB SET */
			
	if ((attr1 == nil) || (attr2 == nil))
		return;


			/* IF NEITHER HAVE NORMALS, THEN RETURN */
			
	if ((!Q3AttributeSet_Contains(attr1, kQ3AttributeTypeNormal)) && 
		(!Q3AttributeSet_Contains(attr2, kQ3AttributeTypeNormal))) 
		return;

				/* IF BOTH HAVE NORMALS, THEN COMPARE */
				
	if (Q3AttributeSet_Contains(attr1, kQ3AttributeTypeNormal) && 
		(Q3AttributeSet_Contains(attr2, kQ3AttributeTypeNormal)))
	{
		Q3AttributeSet_Get(attr1, kQ3AttributeTypeNormal, &v1);				// get both normal vectors
		Q3AttributeSet_Get(attr2, kQ3AttributeTypeNormal, &v2);
	
		if (v1.x != v2.x)													// if don't match, then delete normals
			goto kill_normals;
		if (v1.y != v2.y)									
			goto kill_normals;
		if (v1.z != v2.z)									
			goto kill_normals;
	
	
		 
	
		return;																// normals match, so keep 'em
	}

			/* REMOVE ANY NORMAL ATTRIBS FROM IT */
			
kill_normals:
	Q3AttributeSet_Clear(attr1, kQ3AttributeTypeNormal);
	Q3AttributeSet_Clear(attr2, kQ3AttributeTypeNormal);
}




/****************** REVERSE MESH VERTEX ORDER *********************/
//
// Should invert backfaces
//

static void ReverseMeshVertexOrder(TQ3GeometryObject theMesh)
{
long				numVertsInFace;
short				i;
TQ3AttributeSet		attributeSet;
TQ3Status			status;
unsigned long		numFaces;
TQ3MeshFace			meshFace;
TQ3MeshIterator		vertIterator,faceIterator;
TQ3MeshVertex		meshVertex[1024];
TQ3MeshVertex		meshVertexRev[1024];

	if (!gReverseBackfaces)
		return;

	status = Q3Mesh_GetNumFaces(theMesh, &numFaces);					// get # faces
	if (numFaces == 0)
		return;
		
	status = Q3Mesh_DelayUpdates(theMesh);

				/* SCAN FACES */
				
	meshFace = Q3Mesh_FirstMeshFace(theMesh, &faceIterator);			// get 1st face in mesh
	do
	{
				/* GET FACE ATTRIB SET */
				
		status = Q3Mesh_GetFaceAttributeSet(theMesh, meshFace, &attributeSet);
		if (status != kQ3Success)
			attributeSet = nil;	
	
	
				/* BUILD VERTEX LIST FOR FACE */
				
		numVertsInFace = 0;
		meshVertex[0] = Q3Mesh_FirstFaceVertex(meshFace, &vertIterator);			// get 1st vertex in face
		do
		{
			numVertsInFace++;
			if (numVertsInFace >= 1024)
				DoFatalAlert("\pError, face has > 1024 vertecies which is all this app can handle!");
			meshVertex[numVertsInFace] = Q3Mesh_NextFaceVertex(&vertIterator);		// get vertex


		}while(meshVertex[numVertsInFace] != nil);
		
		if (numVertsInFace < 3)
		{
			DoAlert("\pWarning:  Found a face with less than 3 vertecies.");
			goto del;
		}
		
				/* INVERT LIST */
				
		for (i=0; i < numVertsInFace; i++)
			meshVertexRev[numVertsInFace-i-1] = meshVertex[i];
			
			
				
				/* CREATE NEW FACE WITH REORDERED VERTS */
				
		Q3Mesh_FaceNew(theMesh, numVertsInFace, &meshVertexRev[0], attributeSet);		// make new
del:
		Q3Mesh_FaceDelete(theMesh, meshFace);											// delete old face
		
		if (attributeSet != nil)
			Q3Object_Dispose(attributeSet);
		
		meshFace = Q3Mesh_NextMeshFace(&faceIterator);									// get next face
	}while(meshFace != nil);
	
	status = Q3Mesh_ResumeUpdates(theMesh);
	
}


/****************** PRETRANSFORM BOX *********************/

static void PreTransformBox(TQ3GeometryObject theBox)
{
TQ3GroupPosition	myGroupPosition;
TQ3Status			status;
TQ3Vector3D			orientation;
TQ3Point3D			origin;

	status = Q3Box_GetOrientation(theBox, &orientation);
	if (status != kQ3Success)
		DoFatalAlert("\pQ3Box_GetOrientation failed!");

	Q3Vector3D_Transform(&orientation, &gWorkMatrix, &orientation);				
								
	status = Q3Box_SetOrientation(theBox, &orientation);				
	if (status != kQ3Success)
		DoFatalAlert("\pQ3Box_SetOrientation failed!");

	status = Q3Box_GetMajorAxis(theBox, &orientation);
	if (status != kQ3Success)
		DoFatalAlert("\pQ3Box_GetOrientation failed!");

	Q3Vector3D_Transform(&orientation, &gWorkMatrix, &orientation);				
								
	status = Q3Box_SetMajorAxis(theBox, &orientation);				
	if (status != kQ3Success)
		DoFatalAlert("\pQ3Box_SetOrientation failed!");


	status = Q3Box_GetMinorAxis(theBox, &orientation);
	if (status != kQ3Success)
		DoFatalAlert("\pQ3Box_GetOrientation failed!");

	Q3Vector3D_Transform(&orientation, &gWorkMatrix, &orientation);				
								
	status = Q3Box_SetMinorAxis(theBox, &orientation);				
	if (status != kQ3Success)
		DoFatalAlert("\pQ3Box_SetOrientation failed!");



	status = Q3Box_GetOrigin(theBox, &origin);
	if (status != kQ3Success)
		DoFatalAlert("\pQ3Box_GetOrientation failed!");

	Q3Point3D_Transform(&origin, &gWorkMatrix, &origin);				
								
	status = Q3Box_SetOrigin(theBox, &origin);				
	if (status != kQ3Success)
		DoFatalAlert("\pQ3Box_SetOrientation failed!");


			/* ADD MODIFIED BOX TO THE TEMP GROUP */

	myGroupPosition = Q3Group_AddObject(gTempGroup, theBox);	// add to temp group
	if (myGroupPosition == 0)
		DoFatalAlert("\p Q3Group_AddObject Failed!");
}



/****************** PRETRANSFORM ATTRIBUTE *********************/

static void PreTransformAttribute(TQ3AttributeSet theAttribute)
{
TQ3Status			status;
TQ3AttributeType	attribType;
TQ3Vector3D			vector;
TQ3Tangent2D		tangent;

				/* GET 1ST ATTRIBUTE TYPE */
				
	attribType = kQ3AttributeTypeNone;
	status = Q3AttributeSet_GetNextAttributeType(theAttribute, &attribType);

			/* ITERATE THRU ALL ATTRIBS LOOKING FOR SOMETHING TO ROTATE */
				
	if ((status == kQ3Success) && (attribType != kQ3AttributeTypeNone))
	{
		do
		{
			switch(attribType)
			{
				case	kQ3AttributeTypeNormal:
						Q3AttributeSet_Get(theAttribute, attribType, &vector);	
						Q3Vector3D_Transform(&vector, &gWorkMatrix, &vector);				
						status = Q3AttributeSet_Add(theAttribute, attribType, &vector);
						if (status != kQ3Success)
							DoFatalAlert("\pQ3AttributeSet_Add failed!");
						break;	

				case	kQ3AttributeTypeSurfaceTangent:
						Q3AttributeSet_Get(theAttribute, attribType, &tangent);
						Q3Vector3D_Transform(&tangent.uTangent, &gWorkMatrix, &tangent.uTangent);				
						Q3Vector3D_Transform(&tangent.vTangent, &gWorkMatrix, &tangent.vTangent);				
						status = Q3AttributeSet_Add(theAttribute, attribType, &tangent);
						if (status != kQ3Success)
							DoFatalAlert("\pQ3AttributeSet_Add failed!");
						break;	
						
												
			}
			status = Q3AttributeSet_GetNextAttributeType(theAttribute, &attribType);	// get next attrib type
			if (status != kQ3Success)
				break;
		}while(attribType != kQ3AttributeTypeNone);
	}
}


/******************* CALCULATE MESH VERTEX NORMALS ***************************/

static void CalculateMeshVertexNormals(TQ3GeometryObject theMesh)
{
TQ3Status			status;
unsigned long		numFaces,numVertecies;
TQ3MeshFace			meshFace;
TQ3MeshIterator		vertIterator,faceIterator,currentVertIterator;
TQ3MeshVertex		meshVertex[1024],currentVertex;
long				numVertsInFace,faceShareCount,i;
TQ3AttributeSet		attributeSet;
TQ3Vector3D			normal;
TQ3PlaneEquation	planeEquation;


	status = Q3Mesh_GetNumFaces(theMesh, &numFaces);					// get # faces
	if (numFaces == 0)
		return;

	status = Q3Mesh_GetNumFaces(theMesh, &numVertecies);				// get # vertecies
	if (numVertecies == 0)
		return;
		
	status = Q3Mesh_DelayUpdates(theMesh);
		
				/**********************************/
				/* SCAN THRU ALL OF THE VERTECIES */
				/**********************************/
				
	currentVertex = Q3Mesh_FirstMeshVertex(theMesh, &currentVertIterator);				// get 1st vertex in mesh
	do
	{
					/* LOOK FOR FACES WHICH USE THIS VERTEX */

		faceShareCount = 0;																// init normal stuff
		Q3Vector3D_Set(&normal, 0, 0, 0);		
		
		meshFace = Q3Mesh_FirstMeshFace(theMesh, &faceIterator);						// get 1st face in mesh
		do
		{		
					/* BUILD VERTEX LIST FOR FACE */
					
			numVertsInFace = 0;
			meshVertex[0] = Q3Mesh_FirstFaceVertex(meshFace, &vertIterator);			// get 1st vertex in face
			do
			{
				numVertsInFace++;
				if (numVertsInFace >= 1024)
					DoFatalAlert("\pError, face has > 1024 vertecies which is all this app can handle!");
				meshVertex[numVertsInFace] = Q3Mesh_NextFaceVertex(&vertIterator);		// get vertex
			}while(meshVertex[numVertsInFace] != nil);
			

					/* LOOK FOR MATCHING VERTECIES */
					
			for (i=0; i < numVertsInFace; i++)
			{
				if (meshVertex[i] == currentVertex)										// see if found match
				{
					Q3Mesh_GetFacePlaneEquation(theMesh, meshFace, &planeEquation);		// get normal off of face
					Q3Vector3D_Add(&planeEquation.normal, &normal, &normal);			// add to existing normal
					faceShareCount++;
					break;																// try next face
				}
			}
			
			meshFace = Q3Mesh_NextMeshFace(&faceIterator);								// get next face
		}while(meshFace != nil);

				/* AVERAGE THE VECTOR(s) & APPLY NORMAL TO VERTEX ATTRIBUTE */
				
		if (faceShareCount > 0)
		{
			normal.x /= faceShareCount;
			normal.y /= faceShareCount;
			normal.z /= faceShareCount;
			Q3Vector3D_Normalize(&normal, &normal);
			
			status = Q3Mesh_GetVertexAttributeSet(theMesh, currentVertex, &attributeSet);	// get current attribs
			if ((status != kQ3Success) || (attributeSet == nil))							// if err, then make new attrib set
				attributeSet = Q3AttributeSet_New();		
			
			status = Q3AttributeSet_Add(attributeSet, kQ3AttributeTypeNormal, &normal);		// set normal
			status = Q3Mesh_SetVertexAttributeSet(theMesh, currentVertex, attributeSet);	// set new attribs
			Q3Object_Dispose(attributeSet);
		}
				

		currentVertex = Q3Mesh_NextMeshVertex(&currentVertIterator);							// get next vertex in mesh
	}while(currentVertex != nil);

	status = Q3Mesh_ResumeUpdates(theMesh);		
}





/****************** PRETRANSFORM GENERAL POLYGON *********************/


static void PreTransformGeneralPolygon(TQ3GeometryObject thePolygon)
{
TQ3Status			status;
unsigned long		numContours,vertNum,numVertecies,contourNum;
TQ3Point3D			vertexCoord;
TQ3GeneralPolygonData	*generalPolygonDataPtr = nil;
TQ3GroupPosition	myGroupPosition;
TQ3AttributeSet		attributeSet;


				/* GET DATA */
			
	status = Q3GeneralPolygon_GetData(thePolygon, generalPolygonDataPtr);
	if (status != kQ3Success) 
		DoFatalAlert("\pQ3GeneralPolygon_GetData failed!");

	numContours = generalPolygonDataPtr->numContours;				// get # contours
		
				/* SCAN ALL COUNTOURS */
				
	for (contourNum = 0; contourNum < numContours; contourNum++)
	{
		numVertecies = generalPolygonDataPtr->contours[contourNum].numVertices;			// get # vertecies in contour
		
				/* SCAN ALL VERTS IN THIS CONTOUR */
				
		for (vertNum = 0; vertNum < numVertecies; vertNum++)
		{
			status = Q3GeneralPolygon_GetVertexPosition(thePolygon, contourNum, vertNum, &vertexCoord);	// get vert coords
			if (status != kQ3Success) 
				DoFatalAlert("\pQ3GeneralPolygon_GetVertexPosition failed!");

			Q3Point3D_Transform(&vertexCoord, &gWorkMatrix, &vertexCoord);								// apply matrix
			
			status = Q3GeneralPolygon_SetVertexPosition(thePolygon, contourNum, vertNum, &vertexCoord);	// set new coord
			if (status != kQ3Success) 
				DoFatalAlert("\pQ3GeneralPolygon_SetVertexPosition failed!");
			
					/* ALSO TRANSFORM ANY VERTEX ATTRIBUTES */
						
			status = Q3GeneralPolygon_GetVertexAttributeSet(thePolygon, contourNum, vertNum, &attributeSet);
			if ((status == kQ3Success) && (attributeSet != nil))
			{
				PreTransformAttribute(attributeSet);
				Q3Object_Dispose(attributeSet);
			}			
		}
	}	
		
				/* FREE DATA */
	
	status = Q3GeneralPolygon_EmptyData(generalPolygonDataPtr);
	if (status != kQ3Success) 
		DoFatalAlert("\pQ3GeneralPolygon_EmptyData failed!");
		
		
			/* ADD MODIFIED BOX TO THE TEMP GROUP */

	myGroupPosition = Q3Group_AddObject(gTempGroup, thePolygon);	// add to temp group
	if (myGroupPosition == 0)
		DoFatalAlert("\p Q3Group_AddObject Failed!");
		
}




















