//===========================================================================//
// File:	SkeletonVerify.cpp                                                 
// Project: MechWarrior 4
// Contents: Plugin to verify mech skeletons
//---------------------------------------------------------------------------//
//   Date    Who  Modification                                               //
// --------  ---  ---------------------------------------------------------- //
// 06/10/99  jkyle  Initial coding, 
//---------------------------------------------------------------------------//
// Copyright (C) 1999, Fasa Interactive, Inc.
// All Rights reserved worldwide
// This unpublished sourcecode is PROPRIETARY and CONFIDENTIAL
//===========================================================================//


#include "stdafx.h"

#ifdef STRICT							// Already defined by stdafx,
#undef STRICT							// so we avoid warning linking msg
#endif
#ifdef _MBCS							// The same as above
#undef _MBCS
#endif


#include "SkeletonVerify.hpp"
#include "MaxProxies\MaxProxyHeaders.hpp"
#include "errordlg.h"
#include <Stuff\StuffHeaders.hpp>
#include <adept\collisionvolume.hpp>
#include "cvexport.h"
#include "animationsuite.h"
#include "exportUtil.h"
#include <GameOS\ToolOS.hpp>

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif


//////////////////////////////////////////////////////////////////////
// ProtoTypes
#define FLOAT_EPSILON 0.00001f		// (FLOAT)0.0 is close to this

enum ERR_ENUM {
	ERR_INVALIDNODE = 1,
	ERR_INVALIDCHILD,
	ERR_NOZEROROOTPOSITION,
	ERR_NOZEROROTATION,
	ERR_NOJOINTWORLD,
	ERR_HASSCALE,
	ERR_NOPLUGINS,
	ERR_DATAERRORS
};

int VerifyCallbackFn(int,INode *,LPVOID);
int g_maxTextSize;


extern void ConstructCVs(Interface* ip, 
						 MaxCollisionVolume **pSolidCv,
						 MaxCollisionVolume **pHierarchicalCv,
						 bool bSuppressPrompts,
						 bool bRemapMaterial);


//////////////////////////////////////////////////////////////////////
// Global Variables
SkeletonVerifyPanel g_skeletonVerifyPanel;
CMapStringToOb g_mapJoints;  // Maps a string  to a CStringList - used for verifying models
CMapStringToOb g_mapJoints_Compare;	// Maps a string to CStringList - used for comparing models


////////////////////////////////////////////////////////////////////////////////
// SkeletonVerifyPanel Dialog Procedure to handle User and System Interactions
static BOOL CALLBACK SkeletonVerifyPanelProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
	AFX_MANAGE_STATE(AfxGetStaticModuleState());
	
	switch (msg) 
	{
	case WM_QUICKFILE_BEGIN:
		{
			//g_skeletonVerifyPanel.Init(hWnd);
			SendDlgItemMessage(hWnd,IDC_SKELETONVERIFY_LISTBOX,LB_RESETCONTENT,0,0);
			SendDlgItemMessage(hWnd,IDC_SKELETONVERIFY_LISTBOX,LB_SETHORIZONTALEXTENT,300,0);
			break;
		}
	case WM_QUICKFILE_END:
		{
			g_skeletonVerifyPanel.m_ip->ForceCompleteRedraw();
			break;
		}
	case WM_QUICKFILE_APPLYFILE:
		{
			char *name = (char *)wParam;
			char *path = (char *)lParam;
			
			g_skeletonVerifyPanel.m_ip->LoadFromFile(path);
			//			g_skeletonVerifyPanel.m_ip->ForceCompleteRedraw();
			int iCheck = SendDlgItemMessage(hWnd,IDC_CHECK_COMPARE,BM_GETCHECK,0,0);
			if (BST_CHECKED == iCheck)
			{
				int status = g_skeletonVerifyPanel.SkeletonCompare(VerifyCallbackFn,(LPVOID)GetDlgItem(hWnd,IDC_SKELETONVERIFY_LISTBOX));
			}
			else
			{
				int status = g_skeletonVerifyPanel.SkeletonVerify(VerifyCallbackFn,(LPVOID)GetDlgItem(hWnd,IDC_SKELETONVERIFY_LISTBOX));
			}
			break;
		}
		
	case WM_INITDIALOG:						//Panel's extra initialization
		g_skeletonVerifyPanel.Init(hWnd);
		break;
	case WM_DESTROY:						//Panel's extra destruction
		g_skeletonVerifyPanel.Destroy(hWnd);
		break;
	case WM_COMMAND :
		{   //Handle Panel's controls
			HWND hwndCtrl = (HWND)lParam;
			switch (LOWORD(wParam)) 
			{
			case IDC_CHECKEXPORT:
				{
					g_skeletonVerifyPanel.OnErrorCheck(VerifyCallbackFn);
					break;
				}
			case IDC_CHECK_COMPARE:
				{
					switch (HIWORD(wParam))
					{
					case BN_CLICKED:
						{
							int iCheck = SendMessage(hwndCtrl,BM_GETCHECK,0,0);
							if (BST_CHECKED == iCheck)
							{
								SetWindowText(GetDlgItem(hWnd,IDC_SKELETONVERIFY_EXECUTE),"Compare");
							}
							else
							{
								SetWindowText(GetDlgItem(hWnd,IDC_SKELETONVERIFY_EXECUTE),"Verify");
							}
							break;
						}
					}
					break;
				}
			case IDC_SKELETONVERIFY_COMPARE:
				{
					CFileDialog fd(TRUE,NULL,NULL,0,"Max Files (*.max)|*.max||",CWnd::FromHandle(hWnd));
					char *filebuf = new char[MAX_PATH*200];
					filebuf[0] = 0;
					fd.m_ofn.lpstrFile = filebuf;
					fd.m_ofn.nMaxFile = MAX_PATH*200;
					if (fd.DoModal()==IDOK)
					{
						CString path = fd.GetPathName();
						char fname[20];
						_splitpath((LPCTSTR)path,NULL,NULL,fname,NULL);
						g_skeletonVerifyPanel.m_ip->LoadFromFile(path);
						g_skeletonVerifyPanel.m_ip->ForceCompleteRedraw();
						
						EnableWindow(GetDlgItem(hWnd,IDC_CHECK_COMPARE),true);
						CString str;
						str.Format("Compare to: %s",fname);
						SetDlgItemText(hWnd,IDC_CHECK_COMPARE,str);
						SendDlgItemMessage(hWnd,IDC_CHECK_COMPARE,BM_SETCHECK,BST_CHECKED,0);
						
						SetWindowText(GetDlgItem(hWnd,IDC_SKELETONVERIFY_EXECUTE),"Compare");
						
						g_skeletonVerifyPanel.InitCompareMap();
					}
					
					break;
				}
			case IDC_QUICKFILE_LISTBOX:
				{
					switch (HIWORD(wParam))
					{
					case LBN_SELCHANGE:
						{
							HWND hListBox = (HWND)lParam;
							
							int index = SendMessage(hListBox,LB_GETCURSEL,0,0);
							char *path = (char *)SendMessage(hListBox,LB_GETITEMDATA,index,0);
							
							SendDlgItemMessage(hWnd,IDC_SKELETONVERIFY_LISTBOX,LB_RESETCONTENT,0,0);
							SendDlgItemMessage(hWnd,IDC_SKELETONVERIFY_LISTBOX,LB_SETHORIZONTALEXTENT,300,0);
							g_skeletonVerifyPanel.m_ip->LoadFromFile(path);
							g_skeletonVerifyPanel.m_ip->ForceCompleteRedraw();
							
							int iCheck = SendDlgItemMessage(hWnd,IDC_CHECK_COMPARE,BM_GETCHECK,0,0);
							if (BST_CHECKED == iCheck)
							{
								int status = g_skeletonVerifyPanel.SkeletonCompare(VerifyCallbackFn,(LPVOID)GetDlgItem(hWnd,IDC_SKELETONVERIFY_LISTBOX));
							}
							else
							{
								//g_skeletonVerifyPanel.Init(hWnd);
								int status = g_skeletonVerifyPanel.SkeletonVerify(VerifyCallbackFn,(LPVOID)GetDlgItem(hWnd,IDC_SKELETONVERIFY_LISTBOX));
							}
							
							break;
						}
					}
					break;
				}
			case IDC_ABOUT: {
				CAbout dlg;
				dlg.DoModal();
				break;}
			case IDC_SKELETONVERIFY_EXECUTE:
				{
					SendDlgItemMessage(hWnd,IDC_SKELETONVERIFY_LISTBOX,LB_RESETCONTENT,0,0);
					SendDlgItemMessage(hWnd,IDC_SKELETONVERIFY_LISTBOX,LB_SETHORIZONTALEXTENT,300,0);
					g_maxTextSize = 0;
					int iCheck = SendDlgItemMessage(hWnd,IDC_CHECK_COMPARE,BM_GETCHECK,0,0);
					if (BST_CHECKED == iCheck)
					{
						int status = g_skeletonVerifyPanel.SkeletonCompare(VerifyCallbackFn,(LPVOID)GetDlgItem(hWnd,IDC_SKELETONVERIFY_LISTBOX));
					}
					else
					{
						//g_skeletonVerifyPanel.Init(hWnd);
						int status = g_skeletonVerifyPanel.SkeletonVerify(VerifyCallbackFn,(LPVOID)GetDlgItem(hWnd,IDC_SKELETONVERIFY_LISTBOX));
					}
					//SendDlgItemMessage(hWnd,IDC_SKELETONVERIFY_LISTBOX,LB_SETHORIZONTALEXTENT,g_maxTextSize,0);
					//MessageBox(hWnd,"Skeleton Verification Complete","Skeleton Verify",MB_OK);
					break;
				}
			case IDC_SKELETONVERIFY_LISTBOX:
				{
					switch (HIWORD(wParam))
					{
					case LBN_SELCHANGE:
						{
							g_skeletonVerifyPanel.HandleSelChange(hwndCtrl);
							break;
						}
					}
					break;
				}
			}
			break;
		}
	
	////////////////////////////////////////////////////////////////////////
	//The following lines are not necessary for MAX2 Plugins
	case WM_LBUTTONDOWN:
	case WM_LBUTTONUP:
	case WM_MOUSEMOVE:
		g_skeletonVerifyPanel.m_ip->RollupMouseMessage(hWnd, msg, wParam, lParam);
		break;
	////////////////////////////////////////////////////////////////////////
		
	default:
		return FALSE;
	}
	return TRUE;
}




////////////////////////////////////////////////////////////////////////////////
// Display the Plugin's Panel
void SkeletonVerifyPanel::BeginEditParams(Interface* ip, IUtil* iu)
{
	m_ip = ip;								//Get the Plugin's Interface
	m_iu = iu;								//Get the Utility Plugin's Interface
	
	//Display the plugin's Rollup Dialog
	AFX_MANAGE_STATE(AfxGetStaticModuleState());
	HINSTANCE loc_hInstance = AfxGetResourceHandle ();
	
	m_hPanel = m_ip->AddRollupPage(loc_hInstance, MAKEINTRESOURCE(IDD_SKELETONVERIFY_PANEL),
		SkeletonVerifyPanelProc, GetString(IDS_SKELETONVERIFY_NAME),0);
	
	m_hQuickFile = EnableQuickFile(m_ip,m_hPanel);	
}

////////////////////////////////////////////////////////////////////////////////
// Free the Plugins' Panel
void SkeletonVerifyPanel::EndEditParams(Interface *ip, IUtil *iu)
{
	DisableQuickFile(ip,m_hQuickFile);
	m_iu = NULL;
	m_ip = NULL;
	
	//Remove the Rollup Dialog
	ip->DeleteRollupPage(m_hPanel);
	m_hPanel = NULL;
}

// first word is the parent, the rest is the list of children
char g_szJointMap[][100] = 
{
	"joint_WORLD joint_VEL handle_rfoot handle_lfoot",
	"joint_VEL joint_ROOT",
	"joint_ROOT joint_luleg joint_ruleg joint_hipbelow",
	"joint_luleg joint_lmleg joint_ldleg luleg",
	"joint_lmleg joint_ldleg",
	"joint_ldleg joint_lankle site_ldjetport site_lfoot",
	"joint_lankle joint_ltoe joint_lftoe joint_lbtoe joint_litoe joint_lotoe lfoot site_lfoot",
	"joint_ruleg joint_rmleg joint_rdleg ruleg",
	"joint_rmleg joint_rdleg",
	"joint_rdleg joint_rankle site_rdjetport site_rfoot",
	"joint_rankle joint_rtoe joint_rftoe joint_rbtoe joint_ritoe joint_rotoe rfoot site_rfoot",
	"joint_hipbelow joint_hip",
	"joint_hip joint_hipabove joint_torso",
	"joint_hipabove joint_torsobelow hip",
	"joint_torsobelow joint_torso",
	"joint_torso joint_luarm joint_ruarm joint_torsoabove",
	
	"joint_torsoabove joint_head joint_centertorsofront joint_centertorsorear",
	"joint_torsoabove joint_righttorsofront joint_lefttorsofront",
	"joint_torsoabove joint_luarmabove joint_ruarmabove joint_missile torso site_eyepoint",
	"joint_torsoabove site_eject site_light site_radar site_coolantflush site_lmissileport",
	"joint_torsoabove site_rmissileport site_missileport site_lexhaust site_rexhaust site_exhaust",
	"joint_torsoabove site_rujetport site_lujetport site_torsoport site_ltorsoport site_lutorsoport",
	"joint_torsoabove site_ldtorsoport site_rtorsoport site_rutorsoport site_rdtorsoport",
	"joint_torsoabove joint_cage joint_lmissile joint_rmissile",
	"joint_torsoabove site_jcattach site_jcjetport01 site_jcjetport02 site_jcjetport03 site_jcjetport04",
	"joint_cage site_cageleft site_cageright site_light",

	"joint_luarmabove joint_luarm",
	"joint_ruarmabove joint_ruarm",
	"joint_luarm joint_lmarm joint_lgunabove joint_lgun luarm",
	"joint_lmarm joint_ldarm",
	"joint_ldarm joint_lgunabove",
	"joint_lgunabove joint_lgun",
	"joint_ruarm joint_rmarm joint_rgunabove joint_rgun ruarm",
	"joint_rmarm joint_rdarm",
	"joint_rdarm joint_rgunabove",
	"joint_rgunabove joint_rgun",
	
	"joint_lgun site_ldgunport site_lugunport site_lgunport site_lshellport site_logunport",
	"joint_lgun site_ligunport site_lgunexhaust lgun",
	"joint_rgun site_rdgunport site_rugunport site_rgunport site_rshellport site_rogunport",
	"joint_rgun site_rdgunport site_rgunexhaust rgun",
	"joint_missile missile site_exhaust site_missileport",

	"joint_righttorsofront site_rmissileport site_rtorsoport site_rdtorsoport site_rujetport",
	"joint_lefttorsofront site_lmissileport site_ltorsoport site_ldtorsoport site_lujetport",
	"joint_head site_lutorsoport",
	"joint_centertorsofront site_ldtorsoport",
	"joint_specialone",
	"joint_specialtwo"};

// These are the valid geometry names
char g_szGeomSuffix[] = 
"lgun luarm rgun ruarm torso hip rftoe ritoe rotoe rfoot rdleg ruleg \
lftoe litoe lotoe lfoot ldleg luleg rmleg lmleg lbtoe rbtoe ltoe rtoe \
runninglights searchlight specialone specialtwo \
Team_Left Team_Right Pilot_Left Pilot_Right";

// These are the joints that should have 0 world state rotation
char g_szJointNoRotate[] = "WORLD hipbelow hipabove torsobelow torsoabove ruarmabove luarmabove rgunabove lgunabove";
CStringList g_listJointNoRotate;
CStringList g_listGeomSuffix;

////////////////////////////////////////////////////////////////////////////////
// Panel Initialization callback
void SkeletonVerifyPanel::Init(HWND hWnd)
{
	
	HWND hList = GetDlgItem(hWnd,IDC_SKELETONVERIFY_LISTBOX);
	LONG lStyle = GetWindowLong(hList,GWL_STYLE);
	lStyle |= WS_HSCROLL;
	SetWindowLong(hList,GWL_STYLE,lStyle);
	
	// Initialize the map
	{
		for (int x=0;x<(sizeof(g_szJointMap)/100);x++)
		{
			char szData[100];
			strcpy(szData,g_szJointMap[x]);
			char *pname = strtok(szData," ");
			if (pname)
			{
				
				CString strName = CString(pname);
				strName.MakeLower();
				CStringList *pList;
				if (g_mapJoints[strName])
					pList = (CStringList *)g_mapJoints[strName];
				else
					g_mapJoints[strName] = pList = new CStringList;
				while (char *p = strtok(NULL," "))
				{
					CString str1 = CString(p);
					str1.MakeLower();
					pList->AddTail(str1);
					if (!g_mapJoints[str1])
						g_mapJoints[str1] = new CStringList;
				}				
			}
		}
	}
	
	//Initialize the zero rotation list
	{
		g_listJointNoRotate.RemoveAll();
		char *dup = strdup(g_szJointNoRotate);
		char *str = strtok(dup," ");
		while (str)
		{
			g_listJointNoRotate.AddTail(CString("joint_")+CString(str));
			str = strtok(NULL," ");
		}
		delete dup;
	}
	
	//Initialize the geometry names
	{
		g_listGeomSuffix.RemoveAll();
		char *dup = strdup(g_szGeomSuffix);
		char *str = strtok(dup," ");
		while (str)
		{
			CString name(str);
			name.MakeLower();
			g_listGeomSuffix.AddTail(name);
			str = strtok(NULL," ");
		}
		delete dup;
	}
	// Initialize the compare map if a model is currently loaded
	{
	}
}

////////////////////////////////////////////////////////////////////////////////
// Panel Destruction callback
void SkeletonVerifyPanel::Destroy(HWND hWnd)
{
	//destroy the map list pointer data
	{
		POSITION pos = g_mapJoints.GetStartPosition();
		while (pos)
		{
			CString str;
			CObject *pObj;
			g_mapJoints.GetNextAssoc(pos,str,pObj);
			str.MakeLower();
			CStringList *pList = (CStringList *)pObj;
			pList->RemoveAll();
			delete pList;
		}
		g_mapJoints.RemoveAll();
		
		pos = g_mapJoints_Compare.GetStartPosition();
		while (pos)
		{
			CString str;
			CObject *pObj;
			g_mapJoints_Compare.GetNextAssoc(pos,str,pObj);
			str.MakeLower();
			CStringList *pList = (CStringList *)pObj;
			pList->RemoveAll();
			delete pList;
		}
	}
}

////////////////////////////////////////////////////////////////////////////////
// Functions
int SkeletonVerifyPanel::SkeletonVerify(LPSKELETONVERIFY_CALLBACKFN pFn,LPVOID lpVoid)
{
	//	INode *pRoot = m_ip->GetRootNode();	
	INode *pRoot = m_ip->GetINodeByName("joint_WORLD");
	
	if (NULL == pRoot)
	{
		if (pFn)
			pFn(ERR_NOJOINTWORLD,pRoot,lpVoid);
		else
			MessageBox(NULL,"joint_WORLD not found","World Node error",MB_OK);
		return 0;
	}
	// Check for world position at 0,0,0
	{
		//	Point3 offset = pRoot->GetObjOffsetPos();
		Matrix3 mat = pRoot->GetObjTMAfterWSM(0);
		Point3 offset = mat.GetTrans();
		if ((offset.x != 0.0) && (offset.y != 0.0) && (offset.z != 0.0))
		{
			if (pFn)
				pFn(ERR_NOZEROROOTPOSITION,pRoot,lpVoid);
			else
				MessageBox(NULL,"Root not positioned at 0","Root Node error",MB_OK);
		}
	}
	
	// Verify the joint hiearchy
	{
		int status = VerifyNode(pRoot,pFn,lpVoid);
	}
	return 0;
}

int SkeletonVerifyPanel::VerifyNode(INode *pNode,LPSKELETONVERIFY_CALLBACKFN pFn,LPVOID lpVoid)
{
	int status = 0;
	
	CString strName(pNode->GetName());
	strName.MakeLower();
	CStringList *pList = (CStringList *)g_mapJoints[strName];
	if (NULL == pList)
	{
		if (pFn)
			pFn(ERR_INVALIDNODE,pNode,lpVoid);
		else
		{
			CString str = "Invalid node naming: "+strName;
			MessageBox(NULL,str,"Node error",MB_OK);
		}
		// Invalid node strName
		return 1;
	}

	//Verify that the node has no rotation if it is not supposed to
	{
		if (g_listJointNoRotate.Find(strName))
		{
			Matrix3 mat = pNode->GetObjTMAfterWSM(0);
			bool isrot = FALSE;
			float val;
			for (int x = 0;x<3;x++)
				for (int y = 0;y<3;y++)
					if (x==y)
					{
						if (FLOAT_EPSILON <= ((val = mat.GetRow(x)[y])-1.0f))
							isrot = TRUE;
					}
					else
						if (FLOAT_EPSILON <= fabs(val = mat.GetRow(x)[y]))
							isrot =TRUE;

			if (isrot)
			{
				if (pFn)
					pFn(ERR_NOZEROROTATION,pNode,lpVoid);
				else
					MessageBox(NULL,"Node requires zero rotation","Node error",MB_OK);
			}
		}
	}

	//Verify that the node has no scale if it is not supposed to
	{
		Matrix3 parentTM, nodeTM, localTM;
		nodeTM = pNode->GetNodeTM(0);
		parentTM = pNode->GetParentTM(0);
		localTM = nodeTM*Inverse(parentTM);

		AffineParts ap;
		decomp_affine(localTM, &ap);
		Point3 s = ap.k;
		BOOL bError = false;
		for (int x=0;x<3;x++)
			if (fabs(s[x]-1.0)>0.00001)
				bError = true;
		if (bError)
		{
			if (pFn)
				pFn(ERR_HASSCALE,pNode,lpVoid);
			else
				MessageBox(NULL,"Node requires zero scale","Node error",MB_OK);
		}
	}
	for (int x=0;x<pNode->NumberOfChildren();x++)
	{
		INode *pChild = pNode->GetChildNode(x);
		CString strChildName(pChild->GetName());
		strChildName.MakeLower();
		strChildName.Replace(" ", "");

		if (pList->Find(strChildName,NULL))
		{
			status |= VerifyNode(pChild,pFn,lpVoid);
		}
		else 
		{
			CString geoName = strChildName;
			int n = strChildName.Find("_", 1);
			if (n>0)
				geoName = strChildName.Right(strChildName.GetLength()-n-1);

			if (g_listGeomSuffix.Find(geoName) ||
				strChildName.Find("cv_")>=0)
			{
				// This is a piece of geometry, or CV
				// ignore the name_ (prefix) and don't check chidlren
			}
			else
			{
				if (pFn)
					pFn(ERR_INVALIDCHILD,pChild,lpVoid);
				else
				{
					CString str = "Invalid child of "+strName+" : "+strChildName;
					MessageBox(NULL,str,"Node error",MB_OK);
				}
				// pChild is not a valid child of pNode
				status |= 2;
			}
		}
	}
	return status;
}

static int VerifyCallbackFn(int status,INode *pNode,LPVOID lpVoid)
{
	HWND hWnd = (HWND)lpVoid;
	char str[100];
	switch (status)
	{
	case ERR_INVALIDNODE:
		{
			strcpy(str,"Invalid Node Naming");
			break;
		}
	case ERR_INVALIDCHILD:
		{
			INode *pParent = pNode->GetParentNode();
			if (pParent)
				sprintf(str,"(%s) ",pParent->GetName());
			strcat(str,"Invalid Child Node");
			break;
		}
	case ERR_NOZEROROOTPOSITION:
		{
			strcpy(str,"Root Position not (0,0,0)");
			break;
		}
	case ERR_NOZEROROTATION:
		{
			strcpy(str,"Requires Zero Rotation");
			break;
		}
	case ERR_NOJOINTWORLD:
		{
			strcpy(str,"joint_WORLD node not found");
			break;
		}
	case ERR_HASSCALE:
		{
			strcpy(str,"Node has scale");
			break;
		}
	case ERR_NOPLUGINS:
		{
			strcpy(str,"Plugins.ini not found");
		}
	case ERR_DATAERRORS:
		{
			strcpy(str,"Found geometry data errors");
		}
	default:
		{
		}
	}
	char errstr[100];
	sprintf(errstr,"%-20s - %s",pNode?pNode->GetName():"NULL",str);
	int index = SendMessage(hWnd,LB_ADDSTRING,NULL,(LPARAM)errstr);
	SIZE size;
	HDC hDC = GetDC(hWnd);
	GetTextExtentPoint32(hDC,errstr,strlen(errstr),&size);
	ReleaseDC(hWnd,hDC);
	//int width = SendDlgItemMessage(hWnd,IDC_SKELETONVERIFY_LISTBOX,LB_GETHORIZONTALEXTENT,0,0);
	if (g_maxTextSize < size.cx)
		g_maxTextSize = size.cx;
		//SendDlgItemMessage(hWnd,IDC_SKELETONVERIFY_LISTBOX,LB_SETHORIZONTALEXTENT,size.cx,0);
	SendMessage(hWnd,LB_SETITEMDATA,index,(LPARAM)pNode);
	return 0;
}

// When the ListBox control recieves a selchange notification message, handle
int SkeletonVerifyPanel::HandleSelChange(HWND hWnd)
{
	int selcount = SendMessage(hWnd,LB_GETSELCOUNT,0,0);
	if (selcount>0)
	{
		int *selarray = new int[selcount];
		int result = SendMessage(hWnd,LB_GETSELITEMS,selcount,(LPARAM)selarray);
		// result should be equal to selcount
		
		m_ip->ClearNodeSelection(FALSE);
		for (int x=0;x<selcount;x++)
		{
			INode *pNode = (INode *)SendMessage(hWnd,LB_GETITEMDATA,selarray[x],0);
			if (pNode)
			{
				m_ip->SelectNode(pNode,FALSE);
			}
		}
		m_ip->ForceCompleteRedraw();
	}
	return 0;
}

int SkeletonVerifyPanel::AddNode(INode *pNode)
{
	if (pNode == NULL)
		return 1;
	CStringList *pList;
	CString strName = pNode->GetName();
	strName.MakeLower();

	if (g_mapJoints_Compare[strName])
		pList = (CStringList *)g_mapJoints_Compare[strName];
	else
		g_mapJoints_Compare[strName] = pList = new CStringList;

	for (int x=0;x<pNode->NumberOfChildren();x++)
	{
		INode *pChild = pNode->GetChildNode(x);
		CString strChild = pChild->GetName();
		strChild.MakeLower();
		pList->AddTail(strChild);
		if (!g_mapJoints_Compare[strChild])
			g_mapJoints_Compare[strChild] = new CStringList;
		AddNode(pChild);
	}
	return 0;
}

int SkeletonVerifyPanel::InitCompareMap()
{
	// Duplicate code from the :Destroy
	POSITION pos = g_mapJoints_Compare.GetStartPosition();
	while (pos)
	{
		CString str;
		CObject *pObj;
		g_mapJoints_Compare.GetNextAssoc(pos,str,pObj);
		str.MakeLower();
		CStringList *pList = (CStringList *)pObj;
		pList->RemoveAll();
		delete pList;
	}
	g_mapJoints_Compare.RemoveAll();

	// add the joints
	INode *pRoot = m_ip->GetINodeByName("joint_WORLD");
	AddNode(pRoot);
	return 0;
}

int SkeletonVerifyPanel::CompareNode(INode *pNode,LPSKELETONVERIFY_CALLBACKFN pFn,LPVOID lpVoid)
{
	int status = 0;

	CString strName(pNode->GetName());
	CStringList *pList = (CStringList *)g_mapJoints_Compare[strName];
	if (NULL == pList)
	{
		if (pFn)
			pFn(ERR_INVALIDNODE,pNode,lpVoid);
		else
		{
			CString str = "Invalid node naming: "+strName;
			MessageBox(NULL,str,"Node naming error",MB_OK);
		}
		// Invalid node strName
		return 1;
	}

	//Verify that the node has no rotation if it is not supposed to
/*	{
		if (g_listJointNoRotate.Find(strName))
		{
			Matrix3 mat = pNode->GetObjTMAfterWSM(0);
			bool isrot = FALSE;
			float val;
			for (int x = 0;x<3;x++)
				for (int y = 0;y<3;y++)
					if (x==y)
					{
						if (FLOAT_EPSILON <= ((val = mat.GetRow(x)[y])-1.0f))
							isrot = TRUE;
					}
					else
						if (FLOAT_EPSILON <= fabs(val = mat.GetRow(x)[y]))
							isrot =TRUE;

			if (isrot)
			{
				if (pFn)
					pFn(ERR_NOZEROROTATION,pNode,lpVoid);
				else
					MessageBox(NULL,"Node requires zero rotation","Node error",MB_OK);
			}
		}
	}
*/

	for (int x=0;x<pNode->NumberOfChildren();x++)
	{
		INode *pChild = pNode->GetChildNode(x);
		CString strChildName(pChild->GetName());
		if (pList->Find(strChildName,NULL))
		{
			status |= CompareNode(pChild,pFn,lpVoid);
		}
		else
		{
			if (pFn)
				pFn(ERR_INVALIDCHILD,pChild,lpVoid);
			else
			{
				CString str = strChildName+"is not a valid child of "+strName;
				MessageBox(NULL,str,"Node error",MB_OK);
			}
			// pChild is not a valid child of pNode
			status |= 2;
		}
	}
	return status;
}

int SkeletonVerifyPanel::CheckCv(INode *pNode)
{
	int err = 0;
	CString name = pNode->GetName();
	if (strnicmp(name,"cv_",3)==0)
	{
		// Check that it is a cv node class
		Object *pObj = pNode->GetObjectRef();
		if (pObj && (pObj->ClassID() != CVOBJECT_CLASS_ID))
		{
			CString str;
			str.Format("%s is not a collision volume",name);
			ReportError(str);
			err++;
		}
	}
	int count = pNode->NumberOfChildren();
	for (int x=0;x>count;x++)
		err+= CheckCv(pNode->GetChildNode(x));
	return err;
}

int SkeletonVerifyPanel::SkeletonCompare(LPSKELETONVERIFY_CALLBACKFN pFn,LPVOID lpVoid)
{
//	INode *pRoot = m_ip->GetRootNode();	
	INode *pRoot = m_ip->GetINodeByName("joint_WORLD");

	if (NULL == pRoot)
	{
		if (pFn)
			pFn(ERR_NOJOINTWORLD,pRoot,lpVoid);
		else
			MessageBox(NULL,"joint_WORLD not found","World Node error",MB_OK);
	}
	// Check for world position at 0,0,0
/*	{
		Matrix3 mat = pRoot->GetObjTMAfterWSM(0);
		Point3 offset = mat.GetTrans();
		if ((offset.x != 0.0) && (offset.y != 0.0) && (offset.z != 0.0))
		{
			if (pFn)
				pFn(ERR_NOZEROROOTPOSITION,pRoot,lpVoid);
			else
				MessageBox(NULL,"Root not positioned at 0","Root Node error",MB_OK);
		}
	}
*/
	// Verify the joint hiearchy
	{
		int status = CompareNode(pRoot,pFn,lpVoid);
	}
	return 0;
}

void SkeletonVerifyPanel::OnErrorCheck(LPSKELETONVERIFY_CALLBACKFN pFn)
{
	CErrorDlg dlg;
	if (IDOK == dlg.DoModal())
	{
		TSTR maxDataPath = m_ip->GetDir(APP_PLUGCFG_DIR);
		CString ini_path = Stuff::MString(maxDataPath)+"\\PlugIns.ini";
		bool ini_file_exists = gos_DoesFileExist(ini_path);

		if (!ini_file_exists)
		{
			if (pFn)
				pFn(ERR_NOPLUGINS,NULL,(LPVOID)m_ip->GetMAXHWnd());
			else
				ReportError("Plugins.Ini file does not exist");
		}

		if (dlg.m_bAnim)
		{
			// Recursively - check the nodes
/*			
			JointPreference *prefs = JointEditDialog::GetJointPrefrence(node);
			if(prefs != NULL)
			{
				if (prefs->excludeJoint == JointPreference::IncludeJoint)
				{
					include_joint = true;
				}
				else if (prefs->excludeJoint == JointPreference::AutoJoint)
				{
					if(dialog->NodeAnimated(node))
					{
						include_joint = true;
					}
				}
			}

			Control *tm_controler = node->GetTMController();
			Control *translation_controler = tm_controler->GetPositionController();
			Control *rotation_controler = tm_controler->GetRotationController();
			if (c->ClassID() == Class_ID(LININTERP_POSITION_CLASS_ID,0))
			{
				return MW4Animation::JointAnimData::Point3DLinearKeyType;
			}
			else if (c->ClassID() == Class_ID(LININTERP_ROTATION_CLASS_ID,0))
			{
				return MW4Animation::JointAnimData::QuaternionLinearKeyType;
			}
				
			STOP(("INVALID KEY TYPE!!!!!"));
*/
		}

		if (dlg.m_bBatch)
		{
			CFileDialog fd(TRUE,NULL,NULL,0,"Batch Files (*.batch)|*.batch||",NULL);
			if (IDOK == fd.DoModal())
			{
				// Check the Batch notation file
				int err = CheckBatch(pFn,m_ip,(char *)(LPCTSTR)fd.GetPathName());
			}

			// Load the skeleton and save the names of the geometry nodes (mesh nodes)
			// Verify that all Geometry node names in LOD are in skeleton
		}

		// Check that the OBB file has collision volumes - run skeleton verify too?
		if (dlg.m_bObb)
		{ 
			int err = CheckCv(m_ip->GetRootNode());

			// Check that the OBB file has collision volumes - run skeleton verify too?
			// This is the CV exporter code:
		    
			MaxCollisionVolume *pSolidCv = NULL;
			MaxCollisionVolume *pHierarchicalCv = NULL;
			ConstructCVs(m_ip, &pSolidCv, &pHierarchicalCv);


			if (!pSolidCv && !pHierarchicalCv)
			{
				CString str = CString(m_ip->GetCurFileName())+" does not contain any collision volumes.";
				ReportError(str);
			}
			if (pHierarchicalCv)
				delete pHierarchicalCv;
			if (pSolidCv)
				delete pSolidCv;
		}

		if (dlg.m_bLights)
		{
			// Check that the lights are a valid type
		}

		// Do this last cuz it loads files
		if (dlg.m_bErf)
		{
			MAXProxies::MAXScene *scene = MAXProxies::MAXScene::MakeProxy(m_ip,"");
			Check_Object(scene);

			Stuff::NotationFile config_file(ini_path);
			MAXProxies::FindMAXErrors process_errors(&config_file,m_ip,0,false,(LPVOID)&ReportError);

			int err;
			if (err = scene->FindErrors(&process_errors)> 0)
			{
				if (pFn)
					pFn(ERR_DATAERRORS,NULL,(LPVOID)m_ip->GetMAXHWnd());
				else
					ReportError("Found Data Errors: Verification FAILED!");
			}
		}

		MessageBox(NULL,"Error Check Done","Check For Errors",MB_OK);
	}
}

static bool SetBit(WORD &flag,int bit)
{
	WORD val = 1 << bit;
	if (flag & val)
		return true;
	flag &= val;
	return false;
}

/*
int FindGeometry(CStringList *list,INode *pNode)
{
	int count =0;
	if(IsNodeGeometry(pNode))
	{
		count ++;
		CString name = pNode->GetName();
		name.MakeLower();
		if (NULL == list->Find(name))
			list->AddHead(name);
	}
	
	for (int c = 0; c < pNode->NumberOfChildren(); c++) 
	{
		count += FindGeometry(list,pNode->GetChildNode(c)
	}
	return count;
}
*/

int SkeletonVerifyPanel::CheckBatch(LPSKELETONVERIFY_CALLBACKFN pFn,Interface *m_ip,char *batch_filename)
{
	Stuff::NotationFile notefile(batch_filename);

	Stuff::NotationFile::PageIterator *pages = notefile.MakePageIterator();
	Check_Object(pages);

	Stuff::Page *page;

	// Enumerate through the pages
	// Each page - look for LODSkel, LODCHAR, LODDAM,OBB, ANIM - that files exist
	// See that there are the same number of LODCHAR as LODDAM

	while((page = pages->ReadAndNext()) != NULL)
	{
		Check_Object(page);

		const char *pagename = page->GetName();

		CStringList listChar,listDam,listDist,listShape;
		CString path,obb,anim,skel;

		WORD charFlag=0,damFlag=0,distFlag=0,shapeFlag=0;

		// Gather Data
		Stuff::Page::NoteIterator *notes = page->MakeNoteIterator();
		Check_Object(notes);

		Stuff::Note *note;
		// Enumerate the entries in the section
		while ((note = notes->ReadAndNext()) != NULL)
		{
			Check_Object(note);
			
			const char *notename = note->GetName();
			const char *entryname;

			note->GetEntry(&entryname);

			/************************/
			// Compile the data
			if (0==stricmp(notename,"path"))
			{
				if (path.GetLength())
				{
					ReportError(CString(pagename)+":"+CString(notename)+" set multiple times.");
					// Error: This was already set
				}
				path = entryname;
			}
			else if (0==stricmp(notename,"anim"))
			{
				if (anim.GetLength())
				{
					// Error: This was already set
					ReportError(CString(pagename)+":"+CString(notename)+" set multiple times.");
				}
				anim = entryname;
			}
			else if (0==stricmp(notename,"obb"))
			{
				if (obb.GetLength())
				{
					// Error: This was already set
					ReportError(CString(pagename)+":"+CString(notename)+" set multiple times.");
				}
				obb = entryname;
			}
			else if (0==stricmp(notename,"LODskel"))
			{
				if (skel.GetLength())
				{
					// Error: This was already set
					ReportError(CString(pagename)+":"+CString(notename)+" set multiple times.");
				}
				skel = entryname;
			}
			else if (0==stricmp(notename,"LODShape"))
			{
				listShape.AddTail(entryname);
				int i = atoi(entryname+8);
				if (SetBit(shapeFlag,i))
				{
					// Error: This was already set
					ReportError(CString(pagename)+":"+CString(notename)+" set multiple times.");
				}
			}
			else if (0==stricmp(notename,"LODDist"))
			{
				listDist.AddTail(entryname);
				int i = atoi(entryname+7);
				if (SetBit(distFlag,i))
				{
					// Error: This was already set
					ReportError(CString(pagename)+":"+CString(notename)+" set multiple times.");
				}
			}
			else if (0==stricmp(notename,"LODChar"))
			{
				listChar.AddTail(entryname);
				int i = atoi(entryname+7);
				if (SetBit(charFlag,i))
				{
					// Error: This was already set
					ReportError(CString(pagename)+":"+CString(notename)+" set multiple times.");
				}
			}
			else if (0==stricmp(notename,"LODDam"))
			{
				listDam.AddTail(entryname);
				int i = atoi(entryname+6);
				if (SetBit(damFlag,i))
				{
					// Error: This was already set
					ReportError(CString(pagename)+":"+CString(notename)+" set multiple times.");
				}
			}
			/************************/
		}
		Check_Object(notes);
		delete notes;

		/************************/
		// Cross reference data
		
		// Check that the LOD distances are sequential
		{
			WORD w = distFlag;
			while (w & 1)
			{
				w = w>>1;
			}
			if (w)
			{
				// Error: LODDist is not sequential
				ReportError(CString(pagename)+": LODDist is not sequential.");
			}
		}

		// Check that we are doing either a shape or a character
		bool bCharacter = (1 == listChar.GetCount());
		if (listShape.GetCount())
		{
			if (bCharacter)
			{
				// Error: Specified both character and shape
				ReportError(CString(pagename)+": Specified both character and shape.");
			}
		}
		else
		{
			if (!bCharacter)
			{
				// Error: Specified neither character or shape
				ReportError(CString(pagename)+": Needs to specify LOD Character or Shape");
			}
		}

		// Check that if any LOD damage is specified that they match the distances
		if (listDam.GetCount())
		{
			if (damFlag != distFlag)
			{
				// Error: LOD Damage does not match LOD Distances
				ReportError(CString(pagename)+": LODDam does not match LODDist");
			}
			// Check that these files exist
			POSITION pos = listDam.GetHeadPosition();
			while (pos)
			{
				CString name = listDam.GetNext(pos);
				if (!gos_DoesFileExist(name))
				{
					// Error: File Does not exist
					ReportError(CString(pagename)+": "+name+" Does not exist.");
				}
			}
		}

		// Check that if any LOD char is specified that they match the distances
		if (listChar.GetCount())
		{
			if (charFlag != distFlag)
			{
				// Error: LODChar does not match LODDist
				ReportError(CString(pagename)+": LODChar does not match LODDist");
			}
			// LODSkel is required
			if (0 == skel.GetLength())
			{
				// Error: LODSkel is required for Character export
				ReportError(CString(pagename)+": LODSkel is required for character export.");
			}

			// Note: Anim and OBB are optional for Character export
			// Check that these files exist
			POSITION pos = listChar.GetHeadPosition();
			while (pos)
			{
				CString name = listChar.GetNext(pos);
				if (!gos_DoesFileExist(name))
				{
					// Error: File Does not exist
					ReportError(CString(pagename)+": "+name+" Does not exist.");
				}
			}
		}

		// Check that if any LOD shape is specified that they match the distances
		if (listShape.GetCount())
		{
			if (shapeFlag != distFlag)
			{
				// Error: LODShape does not match LODDist
				ReportError(CString(pagename)+": LODShape does not match LODDist.");
			}
			if (skel.GetLength())
			{
				// Error: LODSkel not needed for shape export
				ReportError(CString(pagename)+": LODSkel not needed for shape export.");
			}
			if (anim.GetLength())
			{
				// Error: anim not needed for shape export
				ReportError(CString(pagename)+": "+anim+" not needed for shape export.");
			}

			// Note: OBB is optional for Shape export

			// Check that these files exist
			POSITION pos = listShape.GetHeadPosition();
			while (pos)
			{
				CString name = listShape.GetNext(pos);
				if (!gos_DoesFileExist(name))
				{
					// Error: File Does not exist
					ReportError(CString(pagename)+": "+name+" Does not exist.");
				}
			}
		}

		if (path.GetLength())
		{
			if (!gos_DoesFileExist(path))
			{
				// Error: File Does not exist
				ReportError(CString(pagename)+": "+path+" Does not exist.");
			}
		}
		else
		{
			// Error: Path is required
			ReportError(CString(pagename)+": Path is required.");
		}

		if (skel.GetLength())
		{
			if (!gos_DoesFileExist(skel))
			{
				// Error: File Does not exist
				ReportError(CString(pagename)+": "+skel+" Does not exist.");
			}
		}

		if (anim.GetLength())
		{
			if (!gos_DoesFileExist(anim))
			{
				// Error: File Does not exist
				ReportError(CString(pagename)+": "+anim+" Does not exist.");
			}
		}

		if (obb.GetLength())
		{
			if (!gos_DoesFileExist(obb))
			{
				// Error: File Does not exist
				ReportError(CString(pagename)+": "+obb+" Does not exist.");
			}
		}

		int err;
		if (skel.GetLength())
			err = m_ip->LoadFromFile(skel);
		
		// Load the skeleton and save the names of the geometry nodes (mesh nodes)
		/*CStringList meshList;
		int count = FindGeometry(&meshList,m_ip->GetRootNode());
*/
		// Verify that all Geometry node names in LOD are in skeleton

		// Check that the OBB file has collision volumes - run skeleton verify too?
		// This is the CV exporter code:

		static int runcv = 0;
		if (runcv)
		{
			MaxCollisionVolume *pCv = NULL;

			CString name_world("joint_WORLD");
			INode *pRoot = FindNodeByName(m_ip->GetRootNode(),name_world);
			if (pRoot != NULL)
			{
				pCv = ConstructCVHeirarchy(pRoot);
				Check_Object(pCv);
			}
			else
			{
				// this is not a heiracrhied object so recurse the first level children looking for 
				// cv's.
				int cv_children = 0;
				for (int x=0;x<m_ip->GetRootNode()->NumberOfChildren();x++)
				{
					INode *pChild = m_ip->GetRootNode()->GetChildNode(x);
					
					if (!strnicmp(pChild->GetName(),"cv_",3))
					{
						MaxCollisionVolume *child_cv = ConstructCVHeirarchy(pChild);
						
						if (child_cv != NULL)
						{
							// make sure we haven't found more than one base child that is a cv.
							Check_Object(child_cv);
							++cv_children;
							if (cv_children > 1)
							{
								// Error: Objects are only allowed to have one base cv...
								CString str = CString(pChild->GetName())+"Objects are only allowed to have one base cv .  All other CV's must be childed off of that base parent";
								ReportError(str);
								//STOP(("Objects are only allowed to have one base cv .  All other CV's must be childed off of that base parent"));
							}

							pCv = child_cv;
						}
					}
				}

			}
			if (pCv)
			{
				if (pRoot != NULL)
				{
					// pVel is now the root of the heiarchy so far, let's export the SOLID obb
					
					{
						// find the velocity colision volume
						MaxCollisionVolume* cv_velocity = pCv->FindCVNode("joint_WORLD");
						if (NULL == cv_velocity)
						{
							// Error: ...
							// NEED to report the error
							//STOP(("Exporting OBB of a mech with no collision volumes.  TODO: report this error and continue for batch export."));
							//CString str = "Error exporting OBB: "+CString(name)+" has no collision volume under joint_WORLD";
							//AFX_MANAGE_STATE(AfxGetStaticModuleState( ));
							//AfxMessageBox(str, MB_OK);
							delete pCv;
							return false;	
						}
						Check_Object(cv_velocity);
						//deleteing velocity automaticly removes it from the heirarchy.
						// this is a behavior of chain.
						delete cv_velocity;
						Unregister_Pointer(cv_velocity);
					}
				}
				delete pCv;
			}
			else
			{
				CString str = CString(skel)+" does not contain any collision volumes.";
				ReportError(str);
			}
		}

		/************************/
	}
	delete pages;
	return 1;
}

