//===========================================================================//
// File: CvExport.cpp 
// Project: MechWarrior 4
// Contents: Plugin to export Collision Volume objects
//---------------------------------------------------------------------------//
// Date Who Modification //
// -------- --- ---------------------------------------------------------- //
// 07/29/99 jkyle
//---------------------------------------------------------------------------//
// Copyright (C) 1998, 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 "GeoExporter.h"
#include "cvobject.h"
#include <GameOS\ToolOS.hpp>
#include <Stuff\StuffHeaders.hpp>
#include <adept\adept.hpp>
#include <adept\collisionvolume.hpp>
#include "CvExport.h"
#include <DLLPlatform\DLLPlatform.hpp>

#include "MaxProxies\MaxProxyHeaders.hpp"
#include "mw4.hpp"
#include "ExportUtil.h"

//undef Verify

#ifndef _DEBUG //This is a hack To make it link in Release
#undef Spew
//void Spew() {}
#endif

static CString g_name_WORLD[] = {"joint_SOLID", "joint_WORLD", "cv_SOLID", "cv_WORLD", ""};
static CString g_name_ROOT[]  = {"joint_HIERARCHICAL", "joint_ROOT", "cv_HIERARCHICAL", "cv_ROOT",""};

/*******************************************************************************
/* function name: DoExport
/* description:
/*******************************************************************************/
int CvExp::DoExport(const TCHAR *name,ExpInterface *ei,Interface *i, BOOL suppressPrompts,DWORD options)
{ 
    int return_value;
    __try
    {
        return_value = DoExportWrap(name, ei, i, suppressPrompts);
        //return_value = true;
        GetSystemSetting(SYSSET_CLEAR_UNDO);
        Verify(!theHold.Holding());
    }
    __except(ProcessException(GetExceptionInformation()))
    {
    }
    
    return return_value;
}

/*******************************************************************************
/* function name: DoExportWrap
/* description: 
/*******************************************************************************/
int CvExp::DoExportWrap(const TCHAR *name,ExpInterface *ei,Interface *ip, BOOL suppressPrompts)
{
    
    m_exp.StartExport(ip, CString("Exporting OBB data."), suppressPrompts?true:false);

	m_ip = ip;

    // Load stuff from plugins.ini
	if (!LoadPlugCfg(ip))
		return false;

	bool bOBBSaved = false;
    
    MaxCollisionVolume *pSolidCv = NULL;
    MaxCollisionVolume *pHierarchicalCv = NULL;
	m_exp.UpdateProgress(ip, 0, CString("Constructing cv hierarchy."));
	ConstructCVs(ip, &pSolidCv, &pHierarchicalCv, suppressPrompts?true:false, m_bRemapMaterial);
		
	// delete the existing obb file
	if (::gos_DoesFileExist(name)) {
		::gos_DeleteFile(name);
	}

	// save the hierarchical obb if found		
	if (pHierarchicalCv) {			
		
		m_exp.UpdateProgress(ip, 30, CString("Saving hierarchical cv."));
		SaveHierarchicalOBB(name, pHierarchicalCv);
		bOBBSaved = true;
		Unregister_Object(pHierarchicalCv);
		delete pHierarchicalCv;
		pHierarchicalCv = NULL;
	}
	
	// save the solid obb if found	
	if (pSolidCv) {	
		
		m_exp.UpdateProgress(ip, 60, CString("Saving solid cv."));
		pSolidCv = OptimizeCV(pSolidCv);
		SaveSolidOBB(name, pSolidCv);
		bOBBSaved = true;
		Unregister_Object(pSolidCv);
		delete pSolidCv;
		pSolidCv = NULL;	
	}
				
    // No obb has been saved
    if (!bOBBSaved) {
        ReportError(CString(name)+": Does not contain any collision volumes",suppressPrompts?true:false);
    }

	else if (m_bRemapMaterial) {
		ip->FileSave();
	}
    
	m_exp.EndExport(ip);
    return true;
}

/*******************************************************************************
/* function name: SaveSolidOBB
/* description: save the solid OBB for collision
/*******************************************************************************/
bool CvExp::SaveSolidOBB(const TCHAR *name, MaxCollisionVolume *pCv)
{
	// create the Solid obb;
	char drive[MAX_PATH];
	char path[MAX_PATH];
	char fname[MAX_PATH];
	char fext[MAX_PATH];
	_splitpath(name,drive,path,fname,fext);
	char solid_file_name[MAX_PATH];

	if (m_bUseNewNames) {
		sprintf(solid_file_name,"%s%s%s_SOLID%s",drive,path,fname,fext);
	}
	else{
		sprintf(solid_file_name,"%s%s%s%s",drive,path,fname,fext);
		
		// if name is already used to save the hierarchical obb
		// then save solid obb as object_SOLID.obb
		if (::gos_DoesFileExist(name)) {
			sprintf(solid_file_name,"%s%s%s_SOLID%s",drive,path,fname,fext);
		}
	}

	// now we want the cv_root and the joint_root.
	MaxCollisionVolume* cv_world = NULL;
	MaxCollisionVolume* joint_world = NULL;
	
	for (int i=0; g_name_WORLD[i]!= ""; i++) {
		if (!joint_world)
			joint_world = pCv->FindJointNode(g_name_WORLD[i]);
		if (!cv_world)
			cv_world = pCv->FindCVNode(g_name_WORLD[i]);
	}

	if (cv_world && joint_world) {
		Check_Object(cv_world);
		Check_Object(joint_world);
		
		// transfer all the joint_world children to cv_world and 
		// and delete from the joint_world; 
		ChainIteratorOf<Adept::CollisionVolume*> children(&joint_world->m_children);
		MaxCollisionVolume *cv;
		while((cv = (MaxCollisionVolume*)children.GetCurrent()) != NULL)
		{
			// don't make a cyclic link
			if (cv != cv_world)
			{
				// this is a stationary object
				cv_world->m_children.Add(cv);
			}
			children.Remove();
		}
	}

	if (!cv_world) {
		// this is a flat object
		cv_world = pCv;
	}


	// check to see if cv_world has any children, 
	// if so, the object is stationary, and cannot be a collider
	// also force set GoodEnough flag
	ChainIteratorOf<Adept::CollisionVolume*> children(&cv_world->m_children);
	if(children.GetCurrent() != NULL) {
		cv_world->MakeGoodEnough();
		if (m_bUseNewNames)
			sprintf(solid_file_name,"%s%s%s_STATIONARY%s",drive,path,fname,fext);
	}

	Check_Object(cv_world);
	FileStream file_stream(solid_file_name,Stuff::FileStream::WriteOnly);
	Adept::CollisionVolume::WriteOBBVersion(&file_stream);
	cv_world->Save(&file_stream);
	file_stream.Close();

	if (cv_world!= pCv) delete cv_world;
	if (joint_world && joint_world!= pCv) delete joint_world;
	return true;
}

/*******************************************************************************
/* function name: SaveHierarchicalOBB
/* description: Save the hierarchial OBB for ray-casting
/*******************************************************************************/
bool CvExp::SaveHierarchicalOBB(const TCHAR *name, MaxCollisionVolume *pCv)
{
	// now we want the cv_root and the joint_root.
	MaxCollisionVolume* cv_root = NULL;
	MaxCollisionVolume* joint_root = NULL;
	
	for (int i=0; g_name_ROOT[i]!= ""; i++) {
		if (!joint_root)
			joint_root = pCv->FindJointNode(g_name_ROOT[i]);
		if (!cv_root)
			cv_root = pCv->FindCVNode(g_name_ROOT[i]);
	}

	if (!cv_root) {
		ReportError(CString(name)+": Has no hierarchical collision volume");
		return false; 
	}

	Check_Object(cv_root);

	if (joint_root) {
		Check_Object(joint_root);
		
		// transfer all the joint root children to cv_root and 
		// and delete from the joint_root; 
		ChainIteratorOf<Adept::CollisionVolume*> children(&joint_root->m_children);
		MaxCollisionVolume *cv;
		while((cv = (MaxCollisionVolume*)children.GetCurrent()) != NULL)
		{
			// don't make a cyclic link
			if (cv != cv_root)
			{
				cv_root->m_children.Add(cv);
			}
			children.Remove();
		}
	}

	// check to see if cv_root has any children, 
	// if so, force set GoodEnough flag
	ChainIteratorOf<Adept::CollisionVolume*> children(&cv_root->m_children);
	if (children.GetCurrent() != NULL) {
		cv_root->MakeGoodEnough();
	}

	//joint root is now "empty"
	// and cv_root has all the children
	FileStream f(name,Stuff::FileStream::WriteOnly);
	Adept::CollisionVolume::WriteOBBVersion(&f);
	cv_root->Save(&f);
	f.Close();
	
	if (cv_root!= pCv) delete cv_root;
	if (joint_root && joint_root!= pCv) delete joint_root;
	return true;
}

/*******************************************************************************
/* function name: LoadPlugCfg
/* description: Load additional OBB related settings from the plugin.ini file
/*******************************************************************************/
bool CvExp::LoadPlugCfg(Interface *i)
{
	if (!m_exp.LoadPlugCfg(i))
		return false;

	m_bUseNewNames = false;
	m_bRemapMaterial = false;
	CString ini_file = m_ip->GetDir(APP_PLUGCFG_DIR);
	ini_file += "\\Plugins.ini";
	NotationFile inifile(ini_file);
	
	Page* page = inifile.FindPage("OBBData");
	if (page)
	{
		page->GetEntry("RemapMaterial", &m_bRemapMaterial);
		page->GetEntry("UseNewNames", &m_bUseNewNames);
	}

	return true;
}

/*******************************************************************************
/* function name: FindJointNode
/* description: Find a joint node by name
/*******************************************************************************/
MaxCollisionVolume *MaxCollisionVolume::FindJointNode(const char *name)
{
    
    // first pass this could be us!!!
    const char *my_name = GetName();
    if (my_name != NULL)
    {
        if (!stricmp(my_name,name) && 
			!strnicmp(my_name,"joint_",6))

        {
            return this;
        }
    }
    
    
    ChainIteratorOf<Adept::CollisionVolume*> children(&m_children);
    
    MaxCollisionVolume *cv;
    
    while((cv = (MaxCollisionVolume*)children.ReadAndNext()) != NULL)
    {
        const char *debug_name = cv->GetName();
        if (debug_name != NULL)
        {
            if (!stricmp(debug_name,name))
            {
                return cv;
            }
        }
        
        // if this hasn't returned then we see if one of our children has it
        MaxCollisionVolume *found_cv = NULL;
        found_cv = cv->FindJointNode(name);
        if (found_cv != NULL)
        {
            return found_cv;
        }
    }
    
    return NULL;
}

/*******************************************************************************
/* function name: FindCVNode
/* description: Find a CV node by name
/*******************************************************************************/
MaxCollisionVolume *MaxCollisionVolume::FindCVNode(const char *name)
{
    
    // first pass this could be us!!!
	if (m_pNode) {
		const char *my_name = m_pNode->GetName();
		if (my_name != NULL)
		{
			if (!stricmp(my_name,name) && 
				!strnicmp(my_name,"cv_",3))
			{
				return this;
			}
		}
	}
		
    // otherwise we are searching for the first cv of that joint name    
    
    MaxCollisionVolume *joint = FindJointNode(name);
    if (joint == NULL)
        return NULL;
    
    Check_Object(joint);
    
    ChainIteratorOf<Adept::CollisionVolume*> cv_list(&joint->m_children);
    MaxCollisionVolume *cv;
    while((cv = (MaxCollisionVolume*)cv_list.ReadAndNext()) != NULL)
    {
        if (cv->m_localSpaceBounds.sphereRadius > 0.00001)
            return cv;
    }
    
    
    return NULL;
}

/*******************************************************************************
/* function name: GetName
/* description: 
/*******************************************************************************/
const char* MaxCollisionVolume::GetName()
{
    // Note: if the Material == MechWarrior4::MaterialCount then the radius should also be 0 
    // and a name should be returned
    Check_Object(this);
    if (this->m_localSpaceBounds.sphereRadius < 0.00001)//MechWarrior4::MaterialCount != this->m_material)
    {
        ChainIteratorOf<Adept::CollisionVolume*> children(&m_children);
        //CollisionVolume *volume;
        if (children.ReadAndNext() == NULL)
        {
            // We have a node here with 0 radius and 0 children. This is an error
            ReportError(CString(m_pNode->GetName())+": Collision volume has zero radius and no children");
            int x=1;
        }
        if (m_pNode)
            return m_pNode->GetName();
    }
    return NULL;
}

/*******************************************************************************
/* function name: Contructor
/* description: 
/*******************************************************************************/
MaxCollisionVolume::MaxCollisionVolume(INode *pNode, bool bRemapMaterial)
{
    m_pNode = pNode;
    Object *pObj = pNode->GetObjectRef();
	IParamArray *pblock = NULL;

    Point3 boxDim(0.0,0.0,0.0);
    int material = Adept::MaterialCount;
    if (pObj && (pObj->ClassID() == CVOBJECT_CLASS_ID))
    {
        // Get the Max data
        pblock = pObj->GetParamBlock();
        pblock->GetValue(CV_MATERIAL,0,(int &)material,FOREVER);
        pblock->GetValue(CV_SIZE,0,boxDim,FOREVER);
        boxDim *= 0.5;
    }
    
	// Transform Max to MechWarrior
    Matrix3 mat;
    Point3 p;
    Quat q;
    Point3 s;
    
    Matrix3 TMParentToWorld, TMLocalToWorld, TMLocalToParent;
    TMLocalToWorld = pNode->GetNodeTM(0);
    // Don't transform cv_ROOT because it will be shuffled to be above joint_WORLD
    // DEPENDENCY on MECH EXPORT from exportwrap (above)
    if (strnicmp(pNode->GetName(),"cv_ROOT",7))
    {
        TMParentToWorld = pNode->GetParentTM(0);
        TMLocalToParent = TMLocalToWorld*Inverse(TMParentToWorld);
    }
    else
    {
        TMLocalToParent = TMLocalToWorld;
    }
    
    //
    // Because joints move independently but their children don't we end up with a need to flatten the transforms so that no 
    // matter how deeply nested the collision volumes are, their coordinates are always relative to their parent joint.
    //
    INode *pParentWalker = NULL;
    INode *pParent = pNode->GetParentNode();
    pParentWalker = pParent;
    while (pParentWalker)
    {
        //
        // Get the transform for this collision volume from it's parent.
        //
        if (strnicmp(pParentWalker->GetName(),"cv_",3))
        {
            
            break;
        } // if parentwalker is cv
        
        
        //
        // Multiply this parents transform into the matrix
        //
        Matrix3 TMParentLocalToWorld, TMParentLocalToParent, TMGrandParentLocalToWorld;
        TMParentLocalToWorld = pParentWalker->GetNodeTM(0);
        INode * pGrandParent = pParentWalker->GetParentNode();
        //
        // Our parent may be the scene, and if so, we don't need to go against his parent
        //
        
        //
        // If we have a grandparent, and it is a CV, add it's LocalToParent to our own
        //
        
        if (pGrandParent && !strnicmp(pGrandParent->GetName(), "cv_", 3))
        {
            TMGrandParentLocalToWorld = pGrandParent->GetNodeTM(0);
            
            TMParentLocalToParent = TMParentLocalToWorld * Inverse(TMGrandParentLocalToWorld);
            TMLocalToParent = TMLocalToParent * TMParentLocalToParent;
            
        }
        //
        // It turns out that for the buildings, the grandparent at this point is the scene, and the scene potentially has
        // a transform on it. With the mech, the joint takes care of it, but with the buildings
        // we have to special case and go up one additional level to include this transform.
        // So we need some check to make sure this is the case.
        //
        else if (pGrandParent && !strnicmp(pGrandParent->GetName(), "Scene", 5))
        {
            TMParentLocalToParent = TMParentLocalToWorld ;
            TMLocalToParent = TMLocalToParent * TMParentLocalToParent;
        }
        
        
        
        //
        // If we have a GrandParent, but it's not a CV, we don't want to fold in a transform at all
        // I can't tell why this is the case, but it seems to work for mech legs and torso's correctly
        // when the two cases above don't.
        //
        // else
        // {
        // TMParentLocalToParent = TMParentLocalToWorld ;
        // TMLocalToParent = TMLocalToParent * TMParentLocalToParent;
        // }
        
        
        pParentWalker = pGrandParent;
    }
    
    AffineParts ap;
    decomp_affine(TMLocalToParent, &ap);
    p = ap.t;
    q = ap.q;
    
    
    
    // *******Show assert if Scale (s) is not 1.0, 1.0, 1.0*********
    
    Stuff::UnitQuaternion mw_q = ConvertMaxToMW(q);
    Stuff::Point3D mw_p = ConvertMaxToMW(p);
    Stuff::LinearMatrix4D mw_mat;
    mw_mat.BuildRotation(mw_q);
    mw_mat.BuildTranslation(mw_p);
    
    //Fill in the CollisionVolume dat
    m_entity = NULL;
    // Swapped z and y axis
    m_localSpaceBounds.axisExtents = ConvertMaxToMW(boxDim);
    m_localSpaceBounds.axisExtents.x = (float)fabs(m_localSpaceBounds.axisExtents.x);
    m_localSpaceBounds.axisExtents.y = (float)fabs(m_localSpaceBounds.axisExtents.y);
    m_localSpaceBounds.axisExtents.z = (float)fabs(m_localSpaceBounds.axisExtents.z);
    
    // ********************Does sqrt(0) crash???***************************************
    m_localSpaceBounds.sphereRadius = Length(boxDim);
    m_localSpaceBounds.localToParent = mw_mat;
	m_material = material;

	// Remap material if needed
	if (bRemapMaterial) {
		switch (m_material&0x1f)
		{
		case 0: //DirtMaterial,
			m_material |= Adept::BrownDirtMaterial;
			break;
		case 2: //RockMaterial,
			m_material &= ~0x1f;
			m_material |= Adept::RockMaterial;
			break;
		case 3: //SteelMaterial,
			m_material &= ~0x1f;
			m_material |= Adept::SteelMaterial;
			break;
		case 4: //BlacktopMaterial,
			m_material &= ~0x1f;
			m_material |= Adept::BlacktopMaterial;
			break;
		case 5: //UsMaterial,
			m_material &= ~0x1f;
			m_material |= Adept::UsMaterial;
			break;
		case 6: //ThemMaterial,
			m_material &= ~0x1f;
			m_material |= Adept::ThemMaterial;
			break;
		case 7: //VehicleMaterial,
			m_material &= ~0x1f;
			m_material |= Adept::SteelMaterial;
			break;
		case 8: //GlassMaterial,
			m_material &= ~0x1f;
			m_material |= Adept::GlassMaterial;
			break;
		case 9: //BrickMaterial,
			m_material &= ~0x1f;
			m_material |= Adept::ConcreteMaterial;
			break;
		case 10: //GrassMaterial,
			m_material &= ~0x1f;
			m_material |= Adept::GrassMaterial;
			break;
		case 11: //WoodMaterial,
			m_material &= ~0x1f;
			m_material |= Adept::WoodMaterial;
			break;
		case 12: //TreeMaterial,
			m_material &= ~0x1f;
			m_material |= Adept::WoodMaterial;
			break;
		case 13: //SwampMaterial,
			m_material &= ~0x1f;
			m_material |= Adept::GrassMaterial;
			break;
		case 14: //ConcreteMaterial,
			m_material &= ~0x1f;
			m_material |= Adept::ConcreteMaterial;
			break;
		case 15: //RoughMaterial,
			m_material &= ~0x1f;
			m_material |= Adept::GrassMaterial;
			break;
		case 16: //SnowMaterial,
			m_material &= ~0x1f;
			m_material |= Adept::SnowMaterial;
			break;
		case 17: //UnderbrushMaterial,
			m_material &= ~0x1f;
			m_material |= Adept::GrassMaterial;
			break;
		case 18: //ShallowWaterMaterial,
			m_material &= ~0x1f;
			m_material |= Adept::WaterMaterial;
			break;
		case 19: //MidWaterMaterial,
			m_material &= ~0x1f;
			m_material |= Adept::WaterMaterial;
			break;
		case 20: //DesertMaterial,
			m_material &= ~0x1f;
			m_material |= Adept::BrownDirtMaterial;
			break;
		case 21: //FlatSwampMaterial,
			m_material &= ~0x1f;
			m_material |= Adept::GrassMaterial;
			break;
		case 22: //ThickSwampMaterial,
			m_material &= ~0x1f;
			m_material |= Adept::GrassMaterial;
			break;
		case 23: //RedRockMaterial,
			m_material &= ~0x1f;
			m_material |= Adept::RockMaterial;
			break;
		case 24: //GreyDirtMaterial,
			m_material &= ~0x1f;
			m_material |= Adept::GreyDirtMaterial;
			break;
		case 25: //DarkDirtMaterial,
			m_material &= ~0x1f;
			m_material |= Adept::DarkBrownDirtMaterial;
			break;
		case 26: //DarkDesertMaterial,
			m_material &= ~0x1f;
			m_material |= Adept::DarkBrownDirtMaterial;
			break;
		case 27: //DarkRedRockMaterial,
			m_material &= ~0x1f;
			m_material |= Adept::DarkRockMaterial;
			break;
		case 28: //DarkRockMaterial,
			m_material &= ~0x1f;
			m_material |= Adept::DarkRockMaterial;
			break;
		case 29: //DarkConcreteMaterial,
			m_material &= ~0x1f;
			m_material |= Adept::DarkConcreteMaterial;
			break;
		case 30: //DarkGreyDirtMaterial,
			m_material &= ~0x1f;
			m_material |= Adept::DarkGreyDirtMaterial;
			break;
		}
	
		if (pblock)
			pblock->SetValue(CV_MATERIAL,0,m_material);
	} /* end if m_bRemapMaterial */
}

/*******************************************************************************
/* function name: ConstructCVHeirarchy
/* description: 
/*******************************************************************************/
MaxCollisionVolume *ConstructCVHeirarchy(INode *pNode, bool bRemapMaterial)
{
    MaxCollisionVolume *this_cv = NULL;
    
    bool is_joint = false;
    if (!strnicmp(pNode->GetName(),"joint_",6))
        is_joint = true;
    
    bool is_cv = false;
    if (!strnicmp(pNode->GetName(),"cv_",3))
        is_cv = true;
    
    int number_of_children = 0;
    
    // only create collision volumes for joints and cv's
    if ( is_joint || is_cv )
    {
        
        // create our collision volume
        this_cv = new MaxCollisionVolume(pNode, bRemapMaterial);
        Register_Object(this_cv);
        
        
        for (int x=0;x<pNode->NumberOfChildren();x++)
        {
            // recurse our children and have them make collision volumes 
            INode *pChild = pNode->GetChildNode(x);
            MaxCollisionVolume *children = ConstructCVHeirarchy(pChild, bRemapMaterial);
            
            if (children != NULL)
            {
                // if our child made a collision volume add it to our children chain
                Check_Object(children);
                Check_Object(this_cv);
                this_cv->m_children.Add(children);
                
                // if our child is not a joint
                if (children->m_localSpaceBounds.sphereRadius != 0.0f)
                {
                    // verify that the parent joint only has one child that is 
                    // a collision volume. A joint is allowed to have multiple
                    // joint children but only one cv child.
                    if (is_joint)
                    {
                        ++number_of_children;
                        if (number_of_children > 1)
                        {
							ReportError(CString(pNode->GetName())+": Has more than one cv child");
                        }
                    }
                    else
                    {
                        int z=1;
                    }
                }
            }
        }
    }
    
    return this_cv;
}

/*******************************************************************************
/* function name: OptimizeCV
/* description: 
/*******************************************************************************/
MaxCollisionVolume *OptimizeCV(MaxCollisionVolume *pCV)
{
    const char *debug_name = pCV->m_pNode->GetName();
    
    int count = 0;
    ChainIteratorOf<Adept::CollisionVolume*> children(&pCV->m_children);
    MaxCollisionVolume *cv;
    
    children.First();
    while((cv = (MaxCollisionVolume*)children.GetCurrent()) != NULL)
    {
        
        MaxCollisionVolume *child = OptimizeCV(cv);
        
        if (child == NULL)
        {
            children.Remove();
            Unregister_Object(cv);
            delete cv;
        }
        else
        {
            ++count;
            children.Next();
        }
    }
    
    if (0 == count && (pCV->m_localSpaceBounds.sphereRadius < 0.00001))
        return NULL;
    return pCV;
}

/*******************************************************************************
/* function name: CountMaterials
/* description: 
/*******************************************************************************/
void CountMaterials(MaxCollisionVolume *pCV, Stuff::Page *logPage, CMapStringToPtr &matCounts)
{
	if (!pCV)
		return;

	Check_Object(pCV);
	Check_Object(logPage);
    const char *debug_name = pCV->m_pNode->GetName();
    
    int count = 0;
    ChainIteratorOf<Adept::CollisionVolume*> children(&pCV->m_children);
    MaxCollisionVolume *cv;
    
    children.First();
    while((cv = (MaxCollisionVolume*)children.GetCurrent()) != NULL)
    {
		CountMaterials(cv, logPage, matCounts);
		CString name = cv->m_pNode->GetName();
		name.MakeLower();
		if (name.Find("cv_")>=0) {
			
			const char *material = Adept::MaterialTypeAsciiToText(cv->m_material&0x1f);
			LPVOID lpVoid = NULL;
			if (lpVoid = matCounts[material]) {
				int *count = (int*) lpVoid;
				(*count)++;
			}
			else {
				int *count = new int;
				*count = 1;
				matCounts[material] = count;
			}
		}
        children.Next();
    }
}

/*******************************************************************************
/* function name: ConstructCVs
/* description: Construct solid and hierarchical CVs from a max file
/*******************************************************************************/
void ConstructCVs(Interface* ip, 
			      MaxCollisionVolume **pSolidCvOut,
				  MaxCollisionVolume **pHierarchicalCvOut,
				  bool bSuppressPrompts,
				  bool bRemapMaterial)
{

    MaxCollisionVolume *pSolidCv = NULL;
    MaxCollisionVolume *pHierarchicalCv = NULL;

	// Search for joint_WORLD
    INode *pSolid = NULL;
    if (pSolid = FindNodeByNames(ip->GetRootNode(), g_name_WORLD)) {
	
		pSolidCv = ConstructCVHeirarchy(pSolid, bRemapMaterial);
		Check_Object(pSolidCv);
		pSolidCv = OptimizeCV(pSolidCv);

		// find the root colision volume
		for (int i=0; g_name_ROOT[i]!= ""; i++) {
			if (pHierarchicalCv = pSolidCv->FindJointNode(g_name_ROOT[i])) {
				break;
			}
		}	
	}

	// Search for joint_ROOT
	if (!pHierarchicalCv) {
		INode *pHierarchical = NULL;
		if (pHierarchical = FindNodeByNames(ip->GetRootNode(), g_name_ROOT)) {
			
			pHierarchicalCv = ConstructCVHeirarchy(pHierarchical, bRemapMaterial);
			Check_Object(pHierarchicalCv);
			pHierarchicalCv = OptimizeCV(pHierarchicalCv);
		}
	}

	// if pHierarchicalCv doesn't have any children
	// then add first level joints to pHierarchicalCv
	if (pHierarchicalCv) {
		Check_Object(pHierarchicalCv);
		ChainIteratorOf<Adept::CollisionVolume*> children(&pHierarchicalCv->m_children);
		if((MaxCollisionVolume*)children.GetCurrent() == NULL)
		{
			MaxCollisionVolume* pBaseCv = NULL;
			for (int x=0;x<ip->GetRootNode()->NumberOfChildren();x++)
			{
				
				INode *pChild = ip->GetRootNode()->GetChildNode(x);
				
				if (!strnicmp(pChild->GetName(),"joint_",6))
				{
					pBaseCv = ConstructCVHeirarchy(pChild, bRemapMaterial);
					
					if (pBaseCv != NULL)
					{
						Check_Object(pBaseCv);
						pHierarchicalCv->m_children.Add(pBaseCv);
					}
				}
			}
		}
		pHierarchicalCv = OptimizeCV(pHierarchicalCv);
	}

	// No cv_WORLD or cv_ROOT
	// this is not a heiracrhied object so recurse the first level children 
	// looking for the solid cv
	if (!pSolidCv && !pHierarchicalCv)
	{
		
		int cv_children = 0;
		for (int x=0;x<ip->GetRootNode()->NumberOfChildren();x++)
		{
			
			INode *pChild = ip->GetRootNode()->GetChildNode(x);
			
			if (!strnicmp(pChild->GetName(),"cv_",3))
			{
				pSolidCv = ConstructCVHeirarchy(pChild, bRemapMaterial);
				
				if (pSolidCv != NULL)
				{
					// make sure we haven't found more than one base child that is a cv.
					Check_Object(pSolidCv);
					++cv_children;
					if (cv_children > 1)
					{
						ReportError(CString(pChild->GetName())+": Has more than one base cv",bSuppressPrompts);
					}
					Check_Object(pSolidCv);
					pSolidCv = OptimizeCV(pSolidCv);
				}
			}
		}			
	}

	*pSolidCvOut = pSolidCv;
	*pHierarchicalCvOut = pHierarchicalCv;
}

/*******************************************************************************
/* function name: MakeGoodEnough
/* description: Recursively mark a CV and its children as "Good Enough"
/*******************************************************************************/
void MaxCollisionVolume::MakeGoodEnough()
{
	Check_Object(this);
	m_material &= ~Adept::CollisionVolume::e_GoodEnough;
	m_material |= Adept::CollisionVolume::e_GoodEnough;

	ChainIteratorOf<Adept::CollisionVolume*> children(&m_children);
	MaxCollisionVolume *cv;
    while((cv = (MaxCollisionVolume*)children.ReadAndNext()) != NULL)
    {
		cv->MakeGoodEnough();
	}
}
