/*==LICENSE==*

CyanWorlds.com Engine - MMOG client, server and tools
Copyright (C) 2011  Cyan Worlds, Inc.

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.

You can contact Cyan Worlds, Inc. by email legal@cyan.com
 or by snail mail at:
      Cyan Worlds, Inc.
      14617 N Newport Hwy
      Mead, WA   99021

*==LICENSE==*/
#include "HeadSpin.h"
#include "max.h"
#include "iparamm2.h"
#include "iparamb2.h"
#include "ISkin.h"
#include "MNMath.h"

#include "plMaxNode.h"
//#include "../MaxComponent/resource.h"
#include "GlobalUtility.h"

#include "plgDispatch.h"
#include "plPluginResManager.h"
#include "plMaxNodeData.h"
#include "hsUtils.h"

#include "../MaxConvert/plConvert.h"
#include "hsTemplates.h"
#include "hsStringTokenizer.h"

#include "../MaxConvert/hsConverterUtils.h"
#include "../MaxConvert/hsControlConverter.h"
#include "../MaxConvert/plMeshConverter.h"
#include "../MaxConvert/hsMaterialConverter.h"
#include "../MaxConvert/plLayerConverter.h"
#include "../MaxConvert/UserPropMgr.h"
#include "../MaxExport/plErrorMsg.h"
#include "../MaxConvert/hsVertexShader.h"
#include "../MaxConvert/plLightMapGen.h"
#include "plMaxMeshExtractor.h"
#include "../MaxPlasmaMtls/Layers/plLayerTex.h"

#include "../pnKeyedObject/plKey.h"

#include "../pnSceneObject/plSceneObject.h"
#include "../plScene/plSceneNode.h"
#include "../plPhysX/plPXPhysical.h"
#include "../plDrawable/plInstanceDrawInterface.h"
#include "../plDrawable/plSharedMesh.h"
#include "../pnSceneObject/plSimulationInterface.h"
#include "../pnSceneObject/plAudioInterface.h"
#include "../pnSceneObject/plCoordinateInterface.h"
#include "../pfAnimation/plFilterCoordInterface.h"
#include "../plParticleSystem/plBoundInterface.h"
#include "../plPhysical/plPickingDetector.h"
#include "../plModifier/plLogicModifier.h"
#include "../plModifier/plResponderModifier.h"
#include "../plModifier/plInterfaceInfoModifier.h"
#include "../pfAnimation/plLightModifier.h"
#include "../pfCharacter/plPlayerModifier.h"
#include "../plAvatar/plAGModifier.h"
#include "../plAvatar/plAGAnim.h"
#include "../plAvatar/plPointChannel.h"
#include "../plAvatar/plScalarChannel.h"
#include "../plAvatar/plAGMasterMod.h"
#include "../plMessage/plReplaceGeometryMsg.h"
#include "../plGImage/plMipmap.h"
#include "../plModifier/plSpawnModifier.h"
#include "../plInterp/plController.h"
#include "../plInterp/hsInterp.h"
#include "../pnMessage/plTimeMsg.h"
#include "../pfAnimation/plViewFaceModifier.h" // mf horse temp hack testing to be thrown away

#include "../plScene/plOccluder.h"
#include "hsFastMath.h"


#include "../plDrawable/plDrawableSpans.h"
#include "../plDrawable/plGeometrySpan.h"
#include "../plPipeline/plFogEnvironment.h"

#include "../plGLight/plLightInfo.h"
#include "../plGLight/plLightKonstants.h"
#include "../plSurface/plLayerInterface.h"
#include "../plSurface/plLayer.h"
#include "../plSurface/hsGMaterial.h"

#include "../pnMessage/plObjRefMsg.h"
#include "../pnMessage/plNodeRefMsg.h"
#include "../pnMessage/plIntRefMsg.h"

#include "../MaxExport/plExportProgressBar.h"

#include "../MaxPlasmaMtls/Materials/plDecalMtl.h"

#include "../MaxComponent/plComponentTools.h"
#include "../MaxComponent/plComponent.h"
#include "../MaxComponent/plComponentExt.h"
#include "../MaxComponent/plFlexibilityComponent.h"
#include "../MaxComponent/plLightMapComponent.h"
#include "../MaxComponent/plXImposter.h"
#include "../MaxComponent/plMiscComponents.h"

#include "../plParticleSystem/plParticleSystem.h"
#include "../plParticleSystem/plParticleEmitter.h"
#include "../plParticleSystem/plParticleEffect.h"
#include "../plParticleSystem/plParticleGenerator.h"
#include "../plParticleSystem/plConvexVolume.h"

#include "../MaxPlasmaLights/plRealTimeLightBase.h"
#include "../MaxPlasmaLights/plRTProjDirLight.h"


extern UserPropMgr gUserPropMgr;

hsBool ThreePlaneIntersect(const hsVector3& norm0, const hsPoint3& point0, 
						 const hsVector3& norm1, const hsPoint3& point1, 
						 const hsVector3& norm2, const hsPoint3& point2, hsPoint3& loc);

// Begin external component toolbox ///////////////////////////////////////////////////////////////
static plKey ExternAddModifier(plMaxNodeBase *node, plModifier *mod)
{
	return nil;//((plMaxNode*)node)->AddModifier(mod);
}

static plKey ExternGetNewKey(const char *name, plModifier *mod, plLocation loc)
{
	return nil;//hsgResMgr::ResMgr()->NewKey(name, mod, loc);
}

// In plResponderComponent (for no apparent reason).
int GetMatAnimModKey(Mtl* mtl, plMaxNodeBase* node, const char *segName, hsTArray<plKey>& keys);
// In plAudioComponents
int GetSoundNameAndIdx(plComponentBase *comp, plMaxNodeBase *node, const char*& name);

#include "../MaxComponent/plAnimComponent.h"

static const char *GetAnimCompAnimName(plComponentBase *comp)
{
	if (comp->ClassID() == ANIM_COMP_CID || comp->ClassID() == ANIM_GROUP_COMP_CID)
		return ((plAnimComponentBase*)comp)->GetAnimName();
	return nil;
}

static plKey GetAnimCompModKey(plComponentBase *comp, plMaxNodeBase *node)
{
	if (comp->ClassID() == ANIM_COMP_CID || comp->ClassID() == ANIM_GROUP_COMP_CID)
		return ((plAnimComponentBase*)comp)->GetModKey((plMaxNode*)node);
	return nil;
}

plComponentTools gComponentTools(ExternAddModifier, 
								ExternGetNewKey, 
								nil, 
								GetAnimCompModKey,
								GetAnimCompAnimName,
								GetMatAnimModKey,
								GetSoundNameAndIdx);

// End external component toolbox //////////////////////////////////////////////////////////////////

void plMaxBoneMap::AddBone(plMaxNodeBase *bone)
{
	char *dbgNodeName = bone->GetName();
	if (fBones.find(bone) == fBones.end())
		fBones[bone] = fNumBones++;
}

void plMaxBoneMap::FillBoneArray(plMaxNodeBase **boneArray)
{
	BoneMap::const_iterator boneIt = fBones.begin();
	for (; boneIt != fBones.end(); boneIt++)
		boneArray[(*boneIt).second] = (*boneIt).first;
}

UInt8 plMaxBoneMap::GetIndex(plMaxNodeBase *bone)
{
	hsAssert(fBones.find(bone) != fBones.end(), "Bone missing in remap!");
	return fBones[bone];
}

UInt32 plMaxBoneMap::GetBaseMatrixIndex(plDrawable *draw)
{
	if (fBaseMatrices.find(draw) == fBaseMatrices.end())
		return (UInt32)-1;

	return fBaseMatrices[draw];
}

void plMaxBoneMap::SetBaseMatrixIndex(plDrawable *draw, UInt32 idx)
{
	fBaseMatrices[draw] = idx;
}

// Don't call this after you've started assigning indices to spans, or
// you'll be hosed (duh).
void plMaxBoneMap::SortBones()
{
	plMaxNodeBase **tempBones = TRACKED_NEW plMaxNodeBase*[fNumBones];	
	FillBoneArray(tempBones);

	// Look ma! An n^2 bubble sort!
	// (It's a 1-time thing for an array of less than 100 items. Speed is not essential here)
	int i,j;
	for (i = 0; i < fNumBones; i++)
	{
		hsBool swap = false;		
		for (j = i + 1; j < fNumBones; j++)
		{
			if (strcmp(tempBones[i]->GetName(), tempBones[j]->GetName()) > 0)
			{
				plMaxNodeBase *temp = tempBones[i];
				tempBones[i] = tempBones[j];
				tempBones[j] = temp;
				swap = true;
			}
		}
		if (!swap)
			break;
	}

	for (i = 0; i < fNumBones; i++)
		fBones[tempBones[i]] = i;

	delete [] tempBones;
}

///////////////////////////////////////////////////////////////////////////////////////////////////

plKey plMaxNode::AddModifier(plModifier *pMod, const char* name)
{
	plKey modKey = pMod->GetKey();
	if (!modKey)
		modKey = hsgResMgr::ResMgr()->NewKey(name, pMod, GetLocation());
	hsgResMgr::ResMgr()->AddViaNotify(modKey, TRACKED_NEW plObjRefMsg(GetKey(), plRefMsg::kOnCreate, -1, plObjRefMsg::kModifier), plRefFlags::kActiveRef);
	return modKey;
}

hsBool plMaxNode::DoRecur(PMaxNodeFunc pDoFunction,plErrorMsg *pErrMsg, plConvertSettings *settings,plExportProgressBar*bar)
{
#ifdef HS_DEBUGGING
	const char *tmpName = GetName();
#endif

	// If there is a progess bar, update it with the current node
	// If the user cancels during the update, set bogus so we'll exit
	if (bar && bar->Update(GetName()))
	{
		pErrMsg->Set(true);
		return false;
	}

	// If we can't convert (and we aren't the root node) stop recursing.
	if (!IsRootNode() && !CanConvert())
		return false;

	(this->*pDoFunction)(pErrMsg, settings);
	
	for (int i = 0; (!pErrMsg || !pErrMsg->IsBogus()) && i < NumberOfChildren(); i++)
	{
		plMaxNode *pChild = (plMaxNode *)GetChildNode(i);
		pChild->DoRecur(pDoFunction, pErrMsg, settings, bar);
	}
	return true;
}

// This is the same as DoRecur except that it ignores the canconvert field.  We
// need this for things like clearing the old data, where we need to ignore the old value.
hsBool plMaxNode::DoAllRecur(PMaxNodeFunc pDoFunction,plErrorMsg *pErrMsg, plConvertSettings *settings,plExportProgressBar*bar)
{
#ifdef HS_DEBUGGING
	const char *tmpName = GetName();
#endif

	// If there is a progess bar, update it with the current node
	// If the user cancels during the update, set bogus so we'll exit
	if (bar && bar->Update(GetName()))
	{
		pErrMsg->Set(true);
		return false;
	}

	(this->*pDoFunction)(pErrMsg, settings);
	
	for (int i = 0; (!pErrMsg || !pErrMsg->IsBogus()) && i < NumberOfChildren(); i++)
	{
		plMaxNode *pChild = (plMaxNode *)GetChildNode(i);
		pChild->DoAllRecur(pDoFunction, pErrMsg, settings, bar);
	}
	return true;
}

hsBool plMaxNode::ConvertValidate(plErrorMsg *pErrMsg, plConvertSettings *settings)
{
	TimeValue	t = hsConverterUtils::Instance().GetTime(GetInterface());
	Object *obj = EvalWorldState( t ).obj;
	const char* dbgName = GetName();

	// Always want to recalculate if this object can convert at this point.
	// In general there won't be any cached flag anyway, but in the SceneViewer
	// there can be if we're reconverting.
	hsBool canConvert = CanConvert(true);

	plMaxNodeData thisNodeData;		// Extra data stored for each node

	if (IsTarget())
	{
		plMaxNode* targetNode = ((plMaxNode*)GetLookatNode());
		if (targetNode)
		{
			Object* targObj = targetNode->EvalWorldState( 0 ).obj;
			
			if (targObj && targObj->SuperClassID() == CAMERA_CLASS_ID)
				canConvert = true;
			else
				canConvert = false;
		}
		else
			canConvert = false;	
	}	
	if (canConvert && obj->SuperClassID() == LIGHT_CLASS_ID)
	{
		thisNodeData.SetDrawable(false);
		thisNodeData.SetRunTimeLight(true);
		thisNodeData.SetForceLocal(true);
	}

	if (UserPropExists("Occluder"))
	{
//		thisNodeData.SetDrawable(false);
	}
	if( UserPropExists("PSRunTimeLight") )
		thisNodeData.SetRunTimeLight(true);

	if (GetParticleRelated())
		thisNodeData.SetForceLocal(true);
//	if (UserPropExists("cloth"))
//	{
//		thisNodeData.SetForceLocal(true);
//	}

	// If we want a physicals only world, set everything to not drawable by default.
	if (settings->fPhysicalsOnly)
		thisNodeData.SetDrawable(false);

	// Remember info in MaxNodeData block for later
	thisNodeData.SetCanConvert(canConvert);

	SetMaxNodeData(&thisNodeData);

#define MF_DISABLE_INSTANCING
#ifndef MF_DISABLE_INSTANCING
	// Send this node off to the instance list, to see if we're instanced
	if( CanMakeMesh( obj, pErrMsg, settings ) ) 
	{
		hsTArray<plMaxNode *> nodes;
		UInt32 numInstances = IBuildInstanceList( GetObjectRef(), t, nodes );
		if( numInstances > 1 )
		{
			/// INSTANCED. Make sure to force local on us
			SetForceLocal( true );
			SetInstanced( true );
		}
	}
#endif // MF_DISABLE_INSTANCING

	// If this is for the SceneViewer, turn off the dirty flags so we won't try
	// reconverting this node again.
	if (settings->fSceneViewer)
		SetDirty(kAllDirty, false);

	return canConvert;
}

hsBool plMaxNode::ClearMaxNodeData(plErrorMsg *pErrMsg, plConvertSettings *settings)
{
	// The right place to delete the boneMap is really in ~plMaxNodeData, but that class
	// is only allowed to know about stuff in nucleusLib.
	if (GetBoneMap() && GetBoneMap()->fOwner == this)
		delete GetBoneMap();

	if (GetSwappableGeom())
	{
		// Ref and unref it, so it goes away if no one kept it around (i.e. we just
		// looked at the mesh during export for reference, but don't want to keep it.)
		GetSwappableGeom()->GetKey()->RefObject();
		GetSwappableGeom()->GetKey()->UnRefObject();
	}

	SetMaxNodeData( nil );
	return true;
}

#include "plGetLocationDlg.h"

//
// Helper for setting synchedObject options, until we have a GUI
//
#include "../plResMgr/plKeyFinder.h"
#include "plMaxCFGFile.h"
#include "../plAgeDescription/plAgeDescription.h"
#include "../plResMgr/plPageInfo.h"
#include "../pnNetCommon/plSDLTypes.h"

void plMaxNode::CheckSynchOptions(plSynchedObject* so)
{
	if (so)
	{
		//////////////////////////////////////////////////////////////////////////
		// TEMP - remove
		//
		TSTR sdata,sdataList[128];
		int i,num;

		//
		// check for LocalOnly or DontPersist props
		//
		if (gUserPropMgr.UserPropExists(this, "LocalOnly"))
			so->SetLocalOnly(true);	// disable net synching and persistence
		else
		if (gUserPropMgr.UserPropExists(this, "DontPersistAny"))	// disable all types of persistence
			so->SetSynchFlagsBit(plSynchedObject::kExcludeAllPersistentState);
		else
		{
			if (gUserPropMgr.GetUserPropStringList(this, "DontPersist", num, sdataList))
			{
				for(i=0;i<num;i++)
					so->AddToSDLExcludeList(sdataList[i]);	// disable a type of persistence
			}
		}

		//
		// Check for Volatile prop
		//
		if (gUserPropMgr.UserPropExists(this, "VolatileAll"))	// make all sdl types on this object Volatile
			so->SetSynchFlagsBit(plSynchedObject::kAllStateIsVolatile);
		else
		{
			if (gUserPropMgr.GetUserPropStringList(this, "Volatile", num, sdataList))
			{
				for(i=0;i<num;i++)
					so->AddToSDLVolatileList(sdataList[i]);	// make volatile a type of persistence
			}
		}

		bool tempOldOverride = (gUserPropMgr.UserPropExists(this, "OverrideHighLevelSDL") != 0);

		//
		// TEMP - remove
		//////////////////////////////////////////////////////////////////////////

		// If this object isn't in a global room, turn off sync flags
		if ((!tempOldOverride && !GetOverrideHighLevelSDL()) && !GetLocation().IsReserved())
		{
			bool isDynSim = GetPhysicalProps()->GetGroup() == plSimDefs::kGroupDynamic;
			bool hasPFC = false;
			int count = NumAttachedComponents();
			for (UInt32 x = 0; x < count; x++)
			{
				plComponentBase *comp = GetAttachedComponent(x);
				if (comp->ClassID() == Class_ID(0x670d3629, 0x559e4f11))
				{	
					hasPFC = true;
					break;
				}
			}
			if (!isDynSim && !hasPFC)
			{	
				so->SetSynchFlagsBit(plSynchedObject::kExcludeAllPersistentState);
			}
			else
			{
				so->AddToSDLExcludeList(kSDLAGMaster);
				so->AddToSDLExcludeList(kSDLResponder);
				so->AddToSDLExcludeList(kSDLLayer);
				so->AddToSDLExcludeList(kSDLSound);
				so->AddToSDLExcludeList(kSDLXRegion);
			}
		}
	}
}

hsBool plMaxNode::MakeSceneObject(plErrorMsg *pErrMsg, plConvertSettings *settings)
{
	const char* dbgName = GetName();
	if (!CanConvert()) 
		return false;

	plLocation nodeLoc = GetLocation();//GetLocFromStrings();	// After this we can use GetLocation()
	if (!nodeLoc.IsValid())
	{
		// If we are reconverting, we don't want to bother the user about a room.
		// In most cases, if it doesn't have a room we are in the middle of creating
		// it.  We don't want to pop up a dialog at that point.
		if (settings->fReconvert)
		{
			SetCanConvert(false);
			return false;
		}

		if (!plGetLocationDlg::Instance().GetLocation(this, pErrMsg))
			return false;
		nodeLoc = GetLocation();
	}

	plSceneObject* pso;
	plKey objKey;

	// Handle this as a SceneObject
	pso = TRACKED_NEW plSceneObject;
	objKey = hsgResMgr::ResMgr()->NewKey(GetName(), pso, nodeLoc, GetLoadMask());

	// Remember info in MaxNodeData block for later
	plMaxNodeData *pDat = GetMaxNodeData();
	pDat->SetKey(objKey);
	pDat->SetSceneObject(pso);

	CheckSynchOptions(pso);
	
	return true;
}

hsBool plMaxNode::PrepareSkin(plErrorMsg *pErrMsg, plConvertSettings *settings)
{
	if( !IFindBones(pErrMsg, settings) )
		return false;

	if( !NumBones() )
		return true;

	int i;
	for( i = 0; i < NumBones(); i++ )
	{
		GetBone(i)->SetForceLocal(true);
		GetBone(i)->SetDrawable(false);
	}

	return true;
}

hsBool plMaxNode::IFindBones(plErrorMsg *pErrMsg, plConvertSettings *settings)
{
	if( !CanConvert() )
		return false;

	if (UserPropExists("Bone"))
	{
		AddBone(this);
		SetForceLocal(true);
	}

	ISkin* skin = FindSkinModifier();
	if( skin && skin->GetNumBones() )
	{
		char *dbgNodeName = GetName();

		// BoneUpdate
		//SetForceLocal(true);
		int i;
		for( i = 0; i < skin->GetNumBones(); i++ )
		{
			plMaxNode* bone = (plMaxNode*)skin->GetBone(i);
			if( bone )
			{
				if( !bone->CanConvert() || !bone->GetMaxNodeData() )
				{
					if( pErrMsg->Set(true, GetName(), "Trouble connecting to bone %s - skipping", bone->GetName()).CheckAndAsk() )
						SetDrawable(false);
				}
				else
				{
					AddBone(bone);
					bone->SetForceLocal(true);
				}
			}
			else
			{
				if( pErrMsg->Set(true, GetName(), "Trouble finding bone - skipping").CheckAndAsk() )
					SetDrawable(false);
			}
		}
	}

	return true;
}

#include "plMaxMeshExtractor.h"
#include "plPhysXCooking.h"
#include "../plPhysX/plPXStream.h"
#include "../plPhysX/plSimulationMgr.h"
#include "hsSTLStream.h"

hsBool plMaxNode::MakePhysical(plErrorMsg *pErrMsg, plConvertSettings *settings)
{
	const char* dbgNodeName = GetName();

	if( !CanConvert() )
		return false;

	if( !GetPhysical() )
		return true;

	plPhysicalProps *physProps = GetPhysicalProps();

	if (!physProps->IsUsed())
		return true;

	//hsStatusMessageF("Making phys for %s", dbgNodeName);

	plSimDefs::Group group = (plSimDefs::Group)physProps->GetGroup();
	plSimDefs::Bounds bounds = (plSimDefs::Bounds)physProps->GetBoundsType();
	float mass = physProps->GetMass();

	plMaxNode *proxyNode = physProps->GetProxyNode();
	if (!proxyNode)
		proxyNode = this;

	// We want to draw solid physicals only.  If it is something avatars bounce off,
	// set it to drawable.
	if (settings->fPhysicalsOnly)
	{
		if (group == plSimDefs::kGroupStatic ||
			group == plSimDefs::kGroupAvatarBlocker ||
			group == plSimDefs::kGroupDynamic)
			proxyNode->SetDrawable(true);
	}

	// If mass is zero and we're animated, set the mass to 1 so it will get a rigid
	// body.  Otherwise PhysX will make assumptions about the physical which will
	// fail when it gets moved.
	if (physProps->GetPhysAnim() && mass == 0.f)
		mass = 1.f;

	TSTR sdata;
	plMaxNode* baseNode = this;	
	while (!baseNode->GetParentNode()->IsRootNode())
		baseNode = (plMaxNode*)baseNode->GetParentNode();
	plKey roomKey = baseNode->GetRoomKey();
	if (!roomKey)
	{
		pErrMsg->Set(true, "Room Processing Error - Physics" "The Room that physics component %s is attached to should have already been\nprocessed.", GetName());
		return false;
	}

	plMaxNode* subworld = physProps->GetSubworld();

	PhysRecipe recipe;
	recipe.mass = mass;
	recipe.friction = physProps->GetFriction();
	recipe.restitution = physProps->GetRestitution();
	recipe.bounds = (plSimDefs::Bounds)physProps->GetBoundsType();
	recipe.group = group;
	recipe.reportsOn = physProps->GetReportGroup();
	recipe.objectKey = GetKey();
	recipe.sceneNode = roomKey;
	recipe.worldKey = subworld ? subworld->GetKey() : nil;

	plMaxMeshExtractor::NeutralMesh mesh;
	plMaxMeshExtractor::Extract(mesh, proxyNode, bounds == plSimDefs::kBoxBounds, this);

	if (subworld)
		recipe.l2s = subworld->GetWorldToLocal44() * mesh.fL2W;
	else
		recipe.l2s = mesh.fL2W;

	switch (bounds)
	{
	case plSimDefs::kBoxBounds:
		{
			hsPoint3 minV(FLT_MAX, FLT_MAX, FLT_MAX), maxV(-FLT_MAX, -FLT_MAX, -FLT_MAX);
			for (int i = 0; i < mesh.fNumVerts; i++)
			{
				minV.fX = hsMinimum(mesh.fVerts[i].fX, minV.fX);
				minV.fY = hsMinimum(mesh.fVerts[i].fY, minV.fY);
				minV.fZ = hsMinimum(mesh.fVerts[i].fZ, minV.fZ);
				maxV.fX = hsMaximum(mesh.fVerts[i].fX, maxV.fX);
				maxV.fY = hsMaximum(mesh.fVerts[i].fY, maxV.fY);
				maxV.fZ = hsMaximum(mesh.fVerts[i].fZ, maxV.fZ);
			}
			hsPoint3 width = maxV - minV;
			recipe.bDimensions = width / 2;
			recipe.bOffset = minV + (width / 2.f);
		}
		break;
	case plSimDefs::kProxyBounds:
	case plSimDefs::kExplicitBounds:
		{
			// if this is a detector then try to convert to a convex hull first... if that doesn't succeed then do it as an exact
			if ( group == plSimDefs::kGroupDetector )
			{
				// try converting to a convex hull mesh
				recipe.meshStream = plPhysXCooking::CookHull(mesh.fNumVerts, mesh.fVerts,false);
				if (recipe.meshStream)
				{
					plPXStream pxs(recipe.meshStream);
					recipe.convexMesh = plSimulationMgr::GetInstance()->GetSDK()->createConvexMesh(pxs);
					recipe.bounds = plSimDefs::kHullBounds;
					// then test to see if the original mesh was convex (unless they said to skip 'em)
#ifdef WARNINGS_ON_CONCAVE_PHYSX_WORKAROUND
					if ( !plPhysXCooking::fSkipErrors )
					{
						if ( !plPhysXCooking::TestIfConvex(recipe.convexMesh, mesh.fNumVerts, mesh.fVerts) )
						{
							int retStatus = pErrMsg->Set(true, "Physics Warning: PhysX workaround", "Detector region that is marked as exact and is concave but switching to convex hull for PhysX: %s", GetName()).CheckAskOrCancel();
							pErrMsg->Set();
							if ( retStatus == 1 )  // cancel?
								plPhysXCooking::fSkipErrors = true;
						}
					}
#endif  // WARNINGS_ON_CONCAVE_PHYSX_WORKAROUND
				}
				if (!recipe.meshStream)
				{
					if ( !pErrMsg->Set(true, "Physics Warning", "Detector region exact failed to be made a Hull, trying trimesh: %s", GetName()).Show() )
						pErrMsg->Set();
					recipe.meshStream = plPhysXCooking::CookTrimesh(mesh.fNumVerts, mesh.fVerts, mesh.fNumFaces, mesh.fFaces);
					if (!recipe.meshStream)
					{
						pErrMsg->Set(true, "Physics Error", "Trimesh creation failed for physical %s", GetName()).Show();
						return false;
					}
					plPXStream pxs(recipe.meshStream);
					recipe.triMesh = plSimulationMgr::GetInstance()->GetSDK()->createTriangleMesh(pxs);
				}
			}
			else
			{
				recipe.meshStream = plPhysXCooking::CookTrimesh(mesh.fNumVerts, mesh.fVerts, mesh.fNumFaces, mesh.fFaces);
				if (!recipe.meshStream)
				{
					pErrMsg->Set(true, "Physics Error", "Trimesh creation failed for physical %s", GetName()).Show();
					return false;
				}
				plPXStream pxs(recipe.meshStream);
				recipe.triMesh = plSimulationMgr::GetInstance()->GetSDK()->createTriangleMesh(pxs);
			}
		}
		break;
	case plSimDefs::kSphereBounds:
		{
			hsPoint3 minV(FLT_MAX, FLT_MAX, FLT_MAX), maxV(-FLT_MAX, -FLT_MAX, -FLT_MAX);
			for (int i = 0; i < mesh.fNumVerts; i++)
			{
				minV.fX = hsMinimum(mesh.fVerts[i].fX, minV.fX);
				minV.fY = hsMinimum(mesh.fVerts[i].fY, minV.fY);
				minV.fZ = hsMinimum(mesh.fVerts[i].fZ, minV.fZ);
				maxV.fX = hsMaximum(mesh.fVerts[i].fX, maxV.fX);
				maxV.fY = hsMaximum(mesh.fVerts[i].fY, maxV.fY);
				maxV.fZ = hsMaximum(mesh.fVerts[i].fZ, maxV.fZ);
			}
			hsPoint3 width = maxV - minV;
			recipe.radius = hsMaximum(width.fX, hsMaximum(width.fY, width.fZ));
			recipe.radius /= 2.f;
			recipe.offset = minV + (width / 2.f);
		}
		break;
	case plSimDefs::kHullBounds:
		{
			
			
			
			if ( group == plSimDefs::kGroupDynamic )
			{
				
							
				recipe.meshStream = plPhysXCooking::IMakePolytope(mesh);
				
				
				if (!recipe.meshStream)
				{
					pErrMsg->Set(true, "Physics Error", "polyTope-convexhull failed for physical %s", GetName()).Show();
					return false;
				}
			}
			else
			{
				recipe.meshStream = plPhysXCooking::CookHull(mesh.fNumVerts, mesh.fVerts,false);
				if(!recipe.meshStream)
				{
					pErrMsg->Set(true, "Physics Error", "Convex hull creation failed for physical %s", GetName()).Show();
					return false;
				}
			}
			plPXStream pxs(recipe.meshStream);
			recipe.convexMesh = plSimulationMgr::GetInstance()->GetSDK()->createConvexMesh(pxs);
		}
		break;
	}

	delete [] mesh.fFaces;
	delete [] mesh.fVerts;

	//
	// Create the physical
	//
	plPXPhysical* physical = TRACKED_NEW plPXPhysical;

	// add the object to the resource manager, keyed to the new name
	plLocation nodeLoc = GetKey()->GetUoid().GetLocation();
	const char *objName = GetKey()->GetName();
	plKey physKey = hsgResMgr::ResMgr()->NewKey(objName, physical, nodeLoc, GetLoadMask());

	if (!physical->Init(recipe))
	{
		pErrMsg->Set(true, "Physics Error", "Physical creation failed for object %s", GetName()).Show();
		physKey->RefObject();
		physKey->UnRefObject();
		return false;
	}

	physical->SetProperty(plSimulationInterface::kPinned, physProps->GetPinned());
	physical->SetProperty(plSimulationInterface::kPhysAnim, physProps->GetPhysAnim());
	physical->SetProperty(plSimulationInterface::kNoSynchronize, (physProps->GetNoSynchronize() != 0));
	physical->SetProperty(plSimulationInterface::kStartInactive, (physProps->GetStartInactive() != 0));
	physical->SetProperty(plSimulationInterface::kAvAnimPushable, (physProps->GetAvAnimPushable() != 0));

	if(physProps->GetLOSBlockCamera())
		physical->AddLOSDB(plSimDefs::kLOSDBCameraBlockers);
	if(physProps->GetLOSBlockUI())
		physical->AddLOSDB(plSimDefs::kLOSDBUIBlockers);
	if(physProps->GetLOSBlockCustom())
		physical->AddLOSDB(plSimDefs::kLOSDBCustom);
	if(physProps->GetLOSUIItem())
		physical->AddLOSDB(plSimDefs::kLOSDBUIItems);
	if(physProps->GetLOSShootable())
		physical->AddLOSDB(plSimDefs::kLOSDBShootableItems);
	if(physProps->GetLOSAvatarWalkable())
		physical->AddLOSDB(plSimDefs::kLOSDBAvatarWalkable);
	if(physProps->GetLOSSwimRegion())
		physical->AddLOSDB(plSimDefs::kLOSDBSwimRegion);
	
	plSimulationInterface* si = TRACKED_NEW plSimulationInterface;
	plKey pSiKey = hsgResMgr::ResMgr()->NewKey(objName, si, nodeLoc, GetLoadMask());

	// link the simulation interface to the scene object
	hsgResMgr::ResMgr()->AddViaNotify(pSiKey, TRACKED_NEW plObjRefMsg(GetKey(), plRefMsg::kOnCreate, 0, plObjRefMsg::kInterface), plRefFlags::kActiveRef);

	// add the physical to the simulation interface
	hsgResMgr::ResMgr()->AddViaNotify(physKey , TRACKED_NEW plIntRefMsg(pSiKey, plRefMsg::kOnCreate, 0, plIntRefMsg::kPhysical), plRefFlags::kActiveRef);

	return true;
}

hsBool plMaxNode::MakeController(plErrorMsg *pErrMsg, plConvertSettings *settings)
{
	if (!CanConvert()) 
		return false;

	hsBool forceLocal = hsControlConverter::Instance().ForceLocal(this);
		// Rember the force Local setting
	hsBool CurrForceLocal = GetForceLocal();					// dont want to clobber it with false if componentPass made it true
	forceLocal = (CurrForceLocal || forceLocal) ? true : false;		// if it was set before, or is true now, make it true... 
	SetForceLocal(forceLocal);

	if( IsTMAnimated() && (!GetParentNode()->IsRootNode()) )
	{
		((plMaxNode*)GetParentNode())->SetForceLocal(true);
	}

	return true;
}

hsBool plMaxNode::MakeCoordinateInterface(plErrorMsg *pErrMsg, plConvertSettings *settings)
{
	const char* dbgNodeName = GetName();
	if (!CanConvert()) 
		return false;
	plCoordinateInterface* ci = nil;

	hsBool forceLocal = GetForceLocal();

	hsBool needCI = (!GetParentNode()->IsRootNode())
		|| NumberOfChildren()
		|| forceLocal;
	// If we have a transform, set up a coordinateinterface
	if( needCI )
	{
		hsMatrix44 loc2Par = GetLocalToParent44();
		hsMatrix44 par2Loc = GetParentToLocal44();
		if( GetFilterInherit() )
			ci = TRACKED_NEW plFilterCoordInterface;
		else
			ci = TRACKED_NEW plCoordinateInterface;
		//-------------------------
		// Get data from Node, then its key, then its name
		//-------------------------
		plKey pNodeKey = GetKey();
		hsAssert(pNodeKey, "Missing key for this Object");
		const char *pName =	pNodeKey->GetName();
		plLocation nodeLoc = GetLocation();

		plKey pCiKey = hsgResMgr::ResMgr()->NewKey(pName, ci,nodeLoc, GetLoadMask());
		ci->SetLocalToParent(loc2Par, par2Loc);

		hsBool usesPhysics = GetPhysicalProps()->IsUsed();
		ci->SetProperty(plCoordinateInterface::kCanEverDelayTransform, !usesPhysics);
		ci->SetProperty(plCoordinateInterface::kDelayedTransformEval, !usesPhysics);

		hsgResMgr::ResMgr()->AddViaNotify(pCiKey, TRACKED_NEW plObjRefMsg(pNodeKey, plRefMsg::kOnCreate, 0, plObjRefMsg::kInterface), plRefFlags::kActiveRef);
	}
	return true;
}

hsBool plMaxNode::MakeModifiers(plErrorMsg *pErrMsg, plConvertSettings *settings)
{
	if (!CanConvert()) 
		return false;
	
	hsBool forceLocal = GetForceLocal();
	const char *dbgNodeName = GetName();

	hsBool addMods = (!GetParentNode()->IsRootNode())
		|| forceLocal;

	if (addMods)
	{
	// create / add modifiers

		// mf horse hack testing ViewFace which is already obsolete
		if ( UserPropExists("ViewFacing") )
		{
			plViewFaceModifier* pMod = TRACKED_NEW plViewFaceModifier;
			if( UserPropExists("VFPivotFavorY") )
				pMod->SetFlag(plViewFaceModifier::kPivotFavorY);
			else if( UserPropExists("VFPivotY") )
				pMod->SetFlag(plViewFaceModifier::kPivotY);
			else if( UserPropExists("VFPivotTumble") )
				pMod->SetFlag(plViewFaceModifier::kPivotTumble);
			else
				pMod->SetFlag(plViewFaceModifier::kPivotFace);
			if( UserPropExists("VFScale") )
			{
				pMod->SetFlag(plViewFaceModifier::kScale);
				TSTR sdata;
				GetUserPropString("VFScale",sdata);
				hsStringTokenizer toker;
				toker.Reset(sdata, hsConverterUtils::fTagSeps);
				int nGot = 0;
				char* token;
				hsVector3 scale;
				scale.Set(1.f,1.f,1.f);
				while( (nGot < 3) && (token = toker.next()) )
				{
					switch( nGot )
					{
					case 0:
						scale.fZ = hsScalar(atof(token));
						break;
					case 1:
						scale.fX = scale.fZ;
						scale.fY = hsScalar(atof(token));
						scale.fZ = 1.f;
						break;
					case 2:
						scale.fZ = hsScalar(atof(token));
						break;
					}
					nGot++;
				}
				pMod->SetScale(scale);
			}
			AddModifier(pMod, GetName());
		}
	}
	return true;

}

hsBool plMaxNode::MakeParentOrRoomConnection(plErrorMsg *pErrMsg, plConvertSettings *settings)
{
	if (!CanConvert()) 
		return false;

	char *dbgNodeName = GetName();
	plSceneObject *pso = GetSceneObject();
	if( !GetParentNode()->IsRootNode() )
	{
		plKey parKey = GetParentKey();
		plCoordinateInterface* ci = const_cast <plCoordinateInterface*> (pso->GetCoordinateInterface());
		hsAssert(ci,"Missing CI");


		plIntRefMsg* msg = TRACKED_NEW plIntRefMsg(parKey, plRefMsg::kOnCreate, -1, plIntRefMsg::kChildObject);
		msg->SetRef(pso);
		hsgResMgr::ResMgr()->AddViaNotify(msg, plRefFlags::kPassiveRef);
	}

	hsgResMgr::ResMgr()->AddViaNotify(pso->GetKey(), TRACKED_NEW plNodeRefMsg(GetRoomKey(), plRefMsg::kOnCreate, -1, plNodeRefMsg::kObject), plRefFlags::kActiveRef);
	return true;
}

void plMaxNode::IWipeBranchDrawable(hsBool b)
{
	SetDrawable(b);
	for (int i = 0; i < NumberOfChildren(); i++)
	{
		plMaxNode *pChild = (plMaxNode *)GetChildNode(i);
		pChild->IWipeBranchDrawable(b);
	}
}

//// CanMakeMesh /////////////////////////////////////////////////////////////
//	Returns true if MakeMesh() on this node will result in spans being stored
//	on a drawable. Takes in the object pointer to avoid having to do redundant
//	work to get it.
//	9.25.2001 mcn - Made public so components can figure out if this node is
//	meshable.

hsBool	plMaxNode::CanMakeMesh( Object *obj, plErrorMsg *pErrMsg, plConvertSettings *settings )
{
	if( obj == nil )
		return false;

	if( UserPropExists( "Plasma2_Camera" ) )
		return false;

	if( !GetSwappableGeom() && !GetDrawable() )
		return false;

	if( GetParticleRelated() )
		return false;
	
	if( obj->CanConvertToType( triObjectClassID ) )
		return true;

	return false;
}

void ITestAdjacencyRecur(const hsTArray<int>* vertList, int iVert, hsBitVector& adjVerts)
{
	adjVerts.SetBit(iVert);

	int i;
	for( i = 0; i < vertList[iVert].GetCount(); i++ )
	{
		if( !adjVerts.IsBitSet(vertList[iVert][i]) )
		{
			ITestAdjacencyRecur(vertList, vertList[iVert][i], adjVerts);
		}
	}
}

hsBool ITestAdjacency(const hsTArray<int>* vertList, int numVerts)
{
	hsBitVector adjVerts;
	ITestAdjacencyRecur(vertList, 0, adjVerts);

	int i;
	for( i = 0; i < numVerts; i++ )
	{
		if( !adjVerts.IsBitSet(i) )
			return false;
	}
	return true;
}

int IsGeoSpanConvexExhaust(const plGeometrySpan* span)
{
	// Brute force, check every point against every face

	UInt16* idx = span->fIndexData;
	int numFaces = span->fNumIndices / 3;

	UInt32 stride = span->GetVertexSize(span->fFormat);

	UInt8* vertData = span->fVertexData;
	int numVerts = span->fNumVerts;

	hsBool someIn = false;
	hsBool someOut = false;

	int i;
	for( i = 0; i < numFaces; i++ )
	{
		// compute norm and dist for face
		hsPoint3* pos[3];
		pos[0] = (hsPoint3*)(vertData + idx[0] * stride);
		pos[1] = (hsPoint3*)(vertData + idx[1] * stride);
		pos[2] = (hsPoint3*)(vertData + idx[2] * stride);

		hsVector3 edge01(pos[1], pos[0]);
		hsVector3 edge02(pos[2], pos[0]);

		hsVector3 faceNorm = edge01 % edge02;
		hsFastMath::NormalizeAppr(faceNorm);
		hsScalar faceDist = faceNorm.InnerProduct(pos[0]);

		int j;
		for( j = 0; j < numVerts; j++ )
		{
			hsPoint3* p = (hsPoint3*)(vertData + idx[0] * stride);


			hsScalar dist = p->InnerProduct(faceNorm) - faceDist;

			const hsScalar kSmall = 1.e-3f;
			if( dist < -kSmall )
				someIn = true;
			else if( dist > kSmall )
				someOut = true;

			if( someIn && someOut )
				return false;
		}

		idx += 3;
	}
	return true;
}

int IsGeoSpanConvex(plMaxNode* node, const plGeometrySpan* span)
{
	static int skipTest = false;
	if( skipTest )
		return 0;

	// May not be now, but could become.
	if( span->fFormat & plGeometrySpan::kSkinWeightMask )
		return 0;

	// May not be now, but could become.
	if( node->GetConcave() || node->UserPropExists("XXXWaterColor") )
		return 0;

	if( span->fMaterial && span->fMaterial->GetLayer(0) && (span->fMaterial->GetLayer(0)->GetMiscFlags() & hsGMatState::kMiscTwoSided) )
		return 0;

	int numVerts = span->fNumVerts;
	if( !numVerts )
		return 0;

	int numFaces = span->fNumIndices / 3;
	if( !numFaces )
		return 0;

	const int kSmallNumFaces = 20;
	if( numFaces <= kSmallNumFaces )
		return IsGeoSpanConvexExhaust(span);

	hsTArray<int>*	vertList = TRACKED_NEW hsTArray<int> [numVerts];

	hsTArray<hsVector3>* normList = TRACKED_NEW hsTArray<hsVector3> [numVerts];
	hsTArray<hsScalar>* distList = TRACKED_NEW hsTArray<hsScalar> [numVerts];

	UInt16* idx = span->fIndexData;

	UInt32 stride = span->GetVertexSize(span->fFormat);

	UInt8* vertData = span->fVertexData;

	// For each face
	int iFace;
	for( iFace = 0; iFace < numFaces; iFace++ )
	{
		// compute norm and dist for face
		hsPoint3* pos[3];
		pos[0] = (hsPoint3*)(vertData + idx[0] * stride);
		pos[1] = (hsPoint3*)(vertData + idx[1] * stride);
		pos[2] = (hsPoint3*)(vertData + idx[2] * stride);

		hsVector3 edge01(pos[1], pos[0]);
		hsVector3 edge02(pos[2], pos[0]);

		hsVector3 faceNorm = edge01 % edge02;
		hsFastMath::NormalizeAppr(faceNorm);
		hsScalar faceDist = faceNorm.InnerProduct(pos[0]);


		// For each vert
		int iVtx;
		for( iVtx = 0; iVtx < 3; iVtx++ )
		{
			int jVtx;
			for( jVtx = 0; jVtx < 3; jVtx++ )
			{
				if( iVtx != jVtx )
				{
					// if idx[jVtx] not in list vertList[idx[iVtx]], add it
					if( vertList[idx[iVtx]].kMissingIndex == vertList[idx[iVtx]].Find(idx[jVtx]) )
						vertList[idx[iVtx]].Append(idx[jVtx]);
				}
			}
			normList[idx[iVtx]].Append(faceNorm);
			distList[idx[iVtx]].Append(faceDist);

		}
		idx += 3;
	}

	hsBool someIn = false;
	hsBool someOut = false;
	int i;
	for( i = 0; i < numVerts; i++ )
	{
		int k;
		for( k = 0; k < normList[i].GetCount(); k++ )
		{
			int j;
			for( j = 0; j < vertList[i].GetCount(); j++ )
			{
				hsPoint3* pos = (hsPoint3*)(vertData + vertList[i][j] * stride);
				hsScalar dist = pos->InnerProduct(normList[i][k]) - distList[i][k];

				const hsScalar kSmall = 1.e-3f;
				if( dist < -kSmall )
					someIn = true;
				else if( dist > kSmall )
					someOut = true;

				if( someIn && someOut )
					goto cleanUp;
			}
		}
	}
	
	if( !ITestAdjacency(vertList, numVerts) )
		someIn = someOut = true;

cleanUp:
	delete [] vertList;
	delete [] normList;
	delete [] distList;

	if( someIn && someOut )
		return 0;

	return someIn ? -1 : 1;
}

// Returns nil if there isn't a sceneobject and a drawinterface.
plDrawInterface* plMaxNode::GetDrawInterface()
{
	plDrawInterface* di = nil;
	plSceneObject* obj = GetSceneObject();
	if( obj )
	{
		di = obj->GetVolatileDrawInterface();
	}
	return di;
}

hsBool plMaxNode::MakeMesh(plErrorMsg *pErrMsg, plConvertSettings *settings)
{
	hsTArray<plGeometrySpan *>	spanArray;
	plDrawInterface				*newDI = nil;

	hsBool		gotMade = false;
	hsBool		haveAddedToSceneNode = false;
	hsGMesh		*myMesh = nil;
	UInt32		i, triMeshIndex = (UInt32)-1;
	const char	*dbgNodeName = GetName();
	TSTR sdata;
	hsStringTokenizer toker;
	plLocation nodeLoc = GetLocation();
	
	if (!GetSwappableGeom())
	{
		if (!CanConvert()) 
			return false;

		if( UserPropExists( "Plasma2_Camera" ) || !GetDrawable()  )
		{
			SetMesh( nil );
			return true;
		}
	}
	
	if( GetSwappableGeomTarget() != (UInt32)-1)
	{
		// This node has no geometry on export, but will have some added at runtime,
		// so it needs a special drawInterface

		plInstanceDrawInterface *newDI = TRACKED_NEW plInstanceDrawInterface;
		newDI->fTargetID = GetSwappableGeomTarget();
		plKey pDiKey = hsgResMgr::ResMgr()->NewKey( GetKey()->GetName(), newDI, nodeLoc, GetLoadMask() );
		hsgResMgr::ResMgr()->AddViaNotify(pDiKey, TRACKED_NEW plObjRefMsg(GetKey(), plRefMsg::kOnCreate, 0, plObjRefMsg::kInterface), plRefFlags::kActiveRef);

		plSwapSpansRefMsg *sMsg = TRACKED_NEW plSwapSpansRefMsg(pDiKey, plRefMsg::kOnCreate, -1, -1);
		plDrawableSpans *drawable = IGetSceneNodeSpans(IGetDrawableSceneNode(pErrMsg), true, true);
		hsgResMgr::ResMgr()->AddViaNotify(drawable->GetKey(), sMsg, plRefFlags::kActiveRef);

		return true;
	}

	if( GetInstanced() )
	{
		hsTArray<plMaxNode *>	nodes;
		TimeValue	t = hsConverterUtils::Instance().GetTime(GetInterface());
		UInt32		numInstances = IBuildInstanceList( GetObjectRef(), t, nodes, true );

		/// Instanced, find an iNode in the list that's been converted already
		for( i = 0; i < numInstances; i++ )
		{
			if( nodes[ i ]->GetSceneObject() && nodes[ i ]->GetSceneObject()->GetDrawInterface() )
			{
				/// Found it!
				if( !IMakeInstanceSpans( nodes[ i ], spanArray, pErrMsg, settings ) )
					return false;

				gotMade = true;
				break;
			}
		}
		/// If we didn't find anything, nothing's got converted yet, so convert the first one
		/// like normal
	}

	// This has the side effect of calling SetMovable(true) if it should be and
	// isn't already. So it needs to be before we make the mesh (and material).
	// (Really, whatever makes it movable should do so then, but that has the potential
	// to break other stuff, which I don't want to do 2 weeks before we ship).
	hsBool movable = IsMovable();

	if( !gotMade )
	{
		if( !plMeshConverter::Instance().CreateSpans( this, spanArray, !settings->fDoPreshade ) )
			return false;
	}
	if( !spanArray.GetCount() )
		return true;

	for( i = 0; i < spanArray.GetCount(); i++ )
		spanArray[i]->fMaxOwner = GetKey()->GetName();

	UInt32 shadeFlags = 0;
	if( GetNoPreShade() )
		shadeFlags |= plGeometrySpan::kPropNoPreShade;
	if( GetRunTimeLight() )
		shadeFlags |= plGeometrySpan::kPropRunTimeLight;
	if( GetNoShadow() )
		shadeFlags |= plGeometrySpan::kPropNoShadow;
	if( GetForceShadow() || GetAvatarSO() )
		shadeFlags |= plGeometrySpan::kPropForceShadow;
	if( GetReverseSort() )
		shadeFlags |= plGeometrySpan::kPropReverseSort;
	if( GetForceVisLOS() )
		shadeFlags |= plGeometrySpan::kVisLOS;
	if( shadeFlags )
	{
		for( i = 0; i < spanArray.GetCount(); i++ )
			spanArray[ i ]->fProps |= shadeFlags;
	}

	hsBool DecalMat = false;
	hsBool NonDecalMat = false;
		
	for (i = 0; i < spanArray.GetCount(); i++)
	{
		if (spanArray[i]->fMaterial->IsDecal())
			DecalMat = true;
		else
			NonDecalMat = true;					
	}
	if (!(DecalMat ^ NonDecalMat))
	{
		for( i = 0; i < spanArray.GetCount(); i++ )
			spanArray[ i ]->ClearBuffers();

		if (pErrMsg->Set((plConvert::Instance().fWarned & plConvert::kWarnedDecalAndNonDecal) == 0, GetName(), 
			"This node has both regular and decal materials, and thus will be ignored.").CheckAskOrCancel())
		{
			plConvert::Instance().fWarned |= plConvert::kWarnedDecalAndNonDecal;
		}
		pErrMsg->Set(false);

		return false;
	}

	hsBool isDecal = IsLegalDecal(false); // Don't complain about the parent

	/// Get some stuff
	hsBool forceLocal = GetForceLocal();

	hsMatrix44 l2w = GetLocalToWorld44();
	hsMatrix44 w2l = GetWorldToLocal44();

	/// 4.17.2001 mcn - TEMP HACK to test fog by adding a key to a bogus fogEnviron object to ALL spans
/*		plFogEnvironment	*myFog = nil;
	plKey				myFogKey = hsgResMgr::ResMgr()->FindExportAlias( "HACK_FOG", plFogEnvironment::Index() );	
	if( myFogKey != nil )
		myFog = plFogEnvironment::ConvertNoRef( myFogKey->GetObjectPtr() );
	else
	{
		hsColorRGBA		color;
		color.Set( 0.5, 0.5, 1, 1 );

		// Exp fog
		myFog = TRACKED_NEW plFogEnvironment( plFogEnvironment::kExpFog, 700.f, 1.f, color );
		myFogKey = hsgResMgr::ResMgr()->NewKey( "HACK_FOG", myFog, nodeLoc );
		hsgResMgr::ResMgr()->AddExportAlias( "HACK_FOG", plFogEnvironment::Index(), myFogKey );
	}

	for( int j = 0; j < spanArray.GetCount(); j++ )
	{
		spanArray[ j ].fFogEnviron = myFog;
	}
*/		/// 4.17.2001 mcn - TEMP HACK end


	plDrawable* drawable = nil;
	plSceneNode* tmpNode = nil;

	/// Find the ice to add it to

	if (GetSwappableGeom()) // We just want to make a geo span, not actually add it to a drawable(interface)
	{
		plMaxNode *drawableSource = (plMaxNode *)(GetParentNode()->IsRootNode() ? this : GetParentNode());
		plSceneNode *tmpNode = drawableSource->IGetDrawableSceneNode(pErrMsg);

		plDrawableSpans *drawable = IGetSceneNodeSpans(tmpNode, true, true);
		ISetupBones(drawable, spanArray, l2w, w2l, pErrMsg, settings);

		hsTArray<plGeometrySpan *> *swapSpans = &GetSwappableGeom()->fSpans;
		for (i = 0; i < spanArray.GetCount(); i++)
			swapSpans->Append(spanArray.Get(i));

		char tmpName[256];
		sprintf(tmpName, "%s_SMsh", GetName());
		hsgResMgr::ResMgr()->NewKey(tmpName, GetSwappableGeom(), GetLocation(), GetLoadMask());
				
		return true;
	}

	plMaxNode *nonDecalParent = this;
	if( GetRoomKey() )
	{
		tmpNode = plSceneNode::ConvertNoRef( GetRoomKey()->GetObjectPtr() );

		if (isDecal) // If we're a decal, we just want to use our parent's drawable
		{	
			plMaxNode *parent = (plMaxNode *)GetParentNode();
							
			SetDecalLevel(parent->GetDecalLevel() + 1);
			for( i = 0; i < spanArray.GetCount(); i++ )
				spanArray[ i ]->fDecalLevel = GetDecalLevel();
		}

		{
			/// Make a new drawInterface (will assign stuff to it later)
			newDI = TRACKED_NEW plDrawInterface;
			plKey pDiKey = hsgResMgr::ResMgr()->NewKey( GetKey()->GetName(), newDI, nodeLoc, GetLoadMask() );
			hsgResMgr::ResMgr()->AddViaNotify(pDiKey, TRACKED_NEW plObjRefMsg(GetKey(), plRefMsg::kOnCreate, 0, plObjRefMsg::kInterface), plRefFlags::kActiveRef);

			/// Attach the processed spans to the DI (through drawables)
			IAssignSpansToDrawables( spanArray, newDI, pErrMsg, settings );
		}
	}

	return true;
}

plSceneNode *plMaxNode::IGetDrawableSceneNode(plErrorMsg *pErrMsg)
{
	plSceneNode *sn = nil;

	sn = plSceneNode::ConvertNoRef( GetRoomKey()->GetObjectPtr() );

	return sn;
}

//// IAssignSpansToDrawables /////////////////////////////////////////////////
//	Given a span array, adds it to the node's drawables, creating them if
//	necessary. Then it takes the resulting indices and drawable pointers
//	and assigns them to the given drawInterface.

void	plMaxNode::IAssignSpansToDrawables( hsTArray<plGeometrySpan *> &spanArray, plDrawInterface *di,
											plErrorMsg *pErrMsg, plConvertSettings *settings )
{
	hsTArray<plGeometrySpan *>	opaqueArray, blendingArray, sortingArray;
	plDrawableSpans				*oSpans = nil, *bSpans = nil, *sSpans = nil;

	int			sCount, oCount, bCount, i;
	plSceneNode	*tmpNode = nil;
	hsMatrix44	l2w = GetLocalToWorld44();
	hsMatrix44	w2l = GetWorldToLocal44();
	UInt32		oIndex = (UInt32)-1, bIndex = (UInt32)-1, sIndex = UInt32(-1);

	tmpNode = IGetDrawableSceneNode(pErrMsg);
/*
	/// Get sceneNode. If we're itinerant and not the parent node, this won't just
	/// be GetRoomKey()->GetObjectPtr()....
	if( GetItinerant() && !GetParentNode()->IsRootNode() )
	{
		/// Step up to the top of the chain
		plMaxNode *baseNode = this;
		while( !baseNode->GetParentNode()->IsRootNode() )
			baseNode = (plMaxNode *)baseNode->GetParentNode();

		if( baseNode->GetItinerant() )
			tmpNode = plSceneNode::ConvertNoRef( baseNode->GetRoomKey()->GetObjectPtr() );
		else
		{
			tmpNode = plSceneNode::ConvertNoRef( GetRoomKey()->GetObjectPtr() );

			/// Warn, since we should only be itinerant if our parent is as well
			pErrMsg->Set( true, "Warning", "Itinerant flag in child '%s' of non-itinerant tree. This should never happen. You should inform a programmer...", GetName() ).Show();
		}		
	}
	else
		tmpNode = plSceneNode::ConvertNoRef( GetRoomKey()->GetObjectPtr() );
*/

	hsBitVector convexBits;
	/// Separate the array into two arrays, one opaque and one blending
	for( sCount = 0, oCount = 0, bCount = 0, i = 0; i < spanArray.GetCount(); i++ )
	{
		if( spanArray[ i ]->fProps & plGeometrySpan::kRequiresBlending )
		{
			hsBool needFaceSort = !GetNoFaceSort() && !IsGeoSpanConvex(this, spanArray[i]);
			if( needFaceSort )
			{
				sCount++;
			}
			else
			{
				convexBits.SetBit(i);
				bCount++;
			}
		}
		else
			oCount++;
	}

	// Done this way, since expanding an hsTArray has the nasty side effect of just copying data, which we don't
	// want when we have memory pointers...
	opaqueArray.SetCount( oCount );
	blendingArray.SetCount( bCount );
	sortingArray.SetCount( sCount );
	for( sCount = 0, oCount = 0, bCount = 0, i = 0; i < spanArray.GetCount(); i++ )
	{
		if( spanArray[ i ]->fProps & plGeometrySpan::kRequiresBlending )
		{
			if( convexBits.IsBitSet(i) )
				blendingArray[ bCount++ ] = spanArray[ i ];
			else
				sortingArray [ sCount++ ] = spanArray[ i ];
		}
		else
			opaqueArray[ oCount++ ] = spanArray[ i ];
	}

	/// Get some drawable pointers
	if( opaqueArray.GetCount() > 0 )
		oSpans = plDrawableSpans::ConvertNoRef( IGetSceneNodeSpans( tmpNode, false ) );
	if( blendingArray.GetCount() > 0 )
		bSpans = plDrawableSpans::ConvertNoRef( IGetSceneNodeSpans( tmpNode, true, false ) );
	if( sortingArray.GetCount() > 0 )
		sSpans = plDrawableSpans::ConvertNoRef( IGetSceneNodeSpans( tmpNode, true, true ) );

	if( oSpans != nil )
		IAssignSpan( oSpans, opaqueArray, oIndex, l2w, w2l, pErrMsg, settings );
	if( bSpans != nil )
		IAssignSpan( bSpans, blendingArray, bIndex, l2w, w2l, pErrMsg, settings );
	if( sSpans )
		IAssignSpan( sSpans, sortingArray, sIndex, l2w, w2l, pErrMsg, settings );

	/// Now assign to the interface
	if( oSpans )
	{
		UInt8 iDraw = di->GetNumDrawables();
		di->SetDrawable( iDraw, oSpans );
		di->SetDrawableMeshIndex( iDraw, oIndex );
	}

	if( bSpans )
	{
		UInt8 iDraw = di->GetNumDrawables();
		di->SetDrawable( iDraw, bSpans );
		di->SetDrawableMeshIndex( iDraw, bIndex );
	}

	if( sSpans )
	{
		UInt8 iDraw = di->GetNumDrawables();
		di->SetDrawable( iDraw, sSpans );
		di->SetDrawableMeshIndex( iDraw, sIndex );
	}

}

//// IAssignSpan /////////////////////////////////////////////////////////////
//	Small utility function for IAssignSpansToDrawables, just does some of
//	the low-down work that's identical for each drawable/spans/etc.

void	plMaxNode::IAssignSpan( plDrawableSpans *drawable, hsTArray<plGeometrySpan *> &spanArray, UInt32 &index,
								hsMatrix44 &l2w, hsMatrix44 &w2l,
								plErrorMsg *pErrMsg, plConvertSettings *settings )
{
	if( NumBones() )
		ISetupBones( drawable, spanArray, l2w, w2l, pErrMsg, settings );

	// Assign spans to the drawables, plus set the volatile flag on the 
	// drawables for the SceneViewer, just in case it hasn't been set yet
	if( settings->fSceneViewer )
	{
		drawable->SetNativeProperty( plDrawable::kPropVolatile, true );
		index = drawable->AppendDISpans( spanArray, index, false );
	}
	else
		index = drawable->AddDISpans( spanArray, index );

	if( GetItinerant() )
		drawable->SetNativeProperty(plDrawable::kPropCharacter, true);
}

// Tiny helper for the function below
void SetSpansBoneInfo(hsTArray<plGeometrySpan *> &spanArray, UInt32 baseMatrix, UInt32 numMatrices)
{
	int i;
	for( i = 0; i < spanArray.GetCount(); i++ )
	{
		spanArray[ i ]->fBaseMatrix = baseMatrix;
		spanArray[ i ]->fNumMatrices = numMatrices;
	}
}

//// ISetupBones /////////////////////////////////////////////////////////////
//	Adds the given bones to the given drawable, then sets up the given spans
//	with the right indices and sets the initial bone positions.
void	plMaxNode::ISetupBones(plDrawableSpans *drawable, hsTArray<plGeometrySpan *> &spanArray,
								hsMatrix44 &l2w, hsMatrix44 &w2l,
								plErrorMsg *pErrMsg, plConvertSettings *settings)
{
	const char* dbgNodeName = GetName();

	if( !NumBones() )
		return;

	plMaxBoneMap *boneMap = GetBoneMap();
	if (boneMap && boneMap->GetBaseMatrixIndex(drawable) != (UInt32)-1)
	{
		SetSpansBoneInfo(spanArray, boneMap->GetBaseMatrixIndex(drawable), boneMap->fNumBones);
		return;
	}
	
	int	baseMatrix, i;

	UInt8 numBones = (boneMap ? boneMap->fNumBones : NumBones()) + 1;
	plMaxNodeBase **boneArray = TRACKED_NEW plMaxNodeBase*[numBones];

	if (boneMap)
		boneMap->FillBoneArray(boneArray);
	else
	{
		for (i = 0; i < NumBones(); i++)
		{
			boneArray[i] = GetBone(i);
		}
	}

	hsTArray<hsMatrix44>	initialB2W;
	hsTArray<hsMatrix44>	initialW2B;
	initialB2W.SetCount(numBones);
	initialW2B.SetCount(numBones);

	hsTArray<hsMatrix44>	initialL2B;
	hsTArray<hsMatrix44>	initialB2L;
	initialL2B.SetCount(numBones);
	initialB2L.SetCount(numBones);

	initialB2W[0].Reset();
	initialW2B[0].Reset();

	initialL2B[0].Reset();
	initialB2L[0].Reset();

	for( i = 1; i < numBones; i++ )
	{
		hsMatrix44 b2w;
		hsMatrix44 w2b;
		hsMatrix44 l2b;
		hsMatrix44 b2l;

		plMaxNodeBase *bone = boneArray[i-1];
		const char* dbgBoneName = bone->GetName();

		Matrix3 localTM = bone->GetNodeTM(TimeValue(0));

		b2w = Matrix3ToMatrix44(localTM);
		b2w.GetInverse(&w2b);

		l2b = w2b * l2w;
		b2l = w2l * b2w;

		initialB2W[i] = b2w;
		initialW2B[i] = w2b;

		initialL2B[i] = l2b;
		initialB2L[i] = b2l;
	}

	// First, see if the bones are already set up appropriately.
	// Appropriately means:
	// a) Associated with the correct drawable (maybe others too, we don't care).
	// b) InitialBone transforms match. If we (or another user of the same bone)
	//		are force localed, Our InitialBone won't match, because it also includes
	//		our transform as well as the bone's. If we've been flattened into world
	//		space, our transform is ident and we can share. This is the normal case
	//		in scene boning. So InitialBones have to match in count and matrix value.
	baseMatrix = drawable->FindBoneBaseMatrix(initialL2B, GetSwappableGeom() != nil);
	if( baseMatrix != UInt32(-1) )
	{
		SetSpansBoneInfo(spanArray, baseMatrix, numBones);
		delete [] boneArray;
		return;
	}
	
	baseMatrix = drawable->AppendDIMatrixSpans(numBones);
	SetSpansBoneInfo(spanArray, baseMatrix, numBones);
	if (boneMap)
		boneMap->SetBaseMatrixIndex(drawable, baseMatrix);

	for( i = 1; i < numBones; i++ )
	{
		plMaxNodeBase *bone = boneArray[i-1];
		plSceneObject* obj = bone->GetSceneObject();
		const char	*dbgBoneName = bone->GetName();

		// Pick which drawable to point the DI to
		UInt8 iDraw = 0;

		/// Now create the actual bone DI, or grab it if it's already created
		plDrawInterface *di = obj->GetVolatileDrawInterface();
		if( di )
		{
			for( iDraw = 0; iDraw < di->GetNumDrawables(); iDraw++ )
			{
				if( di->GetDrawable(iDraw) == drawable )
					break;
			}
		}
		else
		{
			plLocation nodeLoc = bone->GetLocation();
			di = TRACKED_NEW plDrawInterface;
			plKey diKey = hsgResMgr::ResMgr()->NewKey(GetKey()->GetName(), di, nodeLoc, GetLoadMask());
			hsgResMgr::ResMgr()->AddViaNotify(diKey, TRACKED_NEW plObjRefMsg(obj->GetKey(), plRefMsg::kOnCreate, 0, plObjRefMsg::kInterface), plRefFlags::kActiveRef);
		}

		if( di->GetNumDrawables() <= iDraw )
		{
			UInt32 diIndex = drawable->NewDIMatrixIndex();
			di->SetDrawableMeshIndex(iDraw, diIndex);

			di->SetDrawable(iDraw, drawable);
		}


		plDISpanIndex& skinIndices = drawable->GetDISpans(di->GetDrawableMeshIndex(iDraw));
		skinIndices.Append(baseMatrix + i);

		drawable->SetInitialBone(baseMatrix + i, initialL2B[i], initialB2L[i]);
		di->SetTransform(initialB2W[i], initialW2B[i]);
	}
	delete [] boneArray;
}

//// IMakeInstanceSpans //////////////////////////////////////////////////////
//	Given an instance node, instances the geoSpans that the node owns and
//	stores them in the given array.

hsBool	plMaxNode::IMakeInstanceSpans( plMaxNode *node, hsTArray<plGeometrySpan *> &spanArray,
									   plErrorMsg *pErrMsg, plConvertSettings *settings )
{
	UInt8	iDraw;
	int		index, i;

	
	plSceneObject *obj = node->GetSceneObject();
	if( !obj )
		return false;

	const plDrawInterface *di = obj->GetDrawInterface();
	if( !di )
		return false;

	hsBool setVisDists = false;
	hsScalar minDist, maxDist;
	if( hsMaterialConverter::HasVisDists(this, 0, minDist, maxDist) )
	{
		setVisDists = true;
	}

	index = 0;
	spanArray.Reset();
	for( iDraw = 0; iDraw < di->GetNumDrawables(); iDraw++ )
	{
		plDrawableSpans* dr = plDrawableSpans::ConvertNoRef(di->GetDrawable(iDraw));
		if( !dr )
			continue;
		if( di->GetDrawableMeshIndex(iDraw) == (UInt32)-1 )
			continue;

		plDISpanIndex disi = dr->GetDISpans(di->GetDrawableMeshIndex(iDraw));

		spanArray.ExpandAndZero( spanArray.GetCount() + disi.fIndices.GetCount() );
		for( i = 0; i < disi.fIndices.GetCount(); i++ )
		{
			spanArray[ index ] = TRACKED_NEW plGeometrySpan;
			spanArray[ index ]->MakeInstanceOf( dr->GetGeometrySpan( disi.fIndices[ i ] ) );

			if( setVisDists )
			{
				spanArray[ index ]->fMinDist = (minDist);
				spanArray[ index ]->fMaxDist = (maxDist);
			}

			dr->GetGeometrySpan(disi.fIndices[i])->fProps |= plGeometrySpan::kInstanced;

			spanArray[ index++ ]->fProps |= plGeometrySpan::kInstanced;
		}
	}

	// Now that we have all of our instanced spans, we need to make sure we
	// have the right materials. Why? There are some isolated cases (such as when "force
	// material copy" is set) where we want to still instance the geometry but we want
	// separate materials. In this case, GetMaterialArray() will return the right array of
	// materials for our instanced node. However, since we've tossed everything except the
	// final plGeometrySpans from MakeMesh(), we have to do a reverse lookup to see what
	// materials get assigned to whom. GetMaterialArray() is guaranteed (according to Bob)
	// to return materials in the same order for instanced nodes, so what we do is call
	// GMA() for the old node and the new node (the old one should just be a lookup), then
	// for each geoSpan look its old material up in the old array, find the matching material
	// in the new array (i.e. same position) and assign that new material to the span.
#if 1		// Change this to 0 to just always use the same materials on instances (old, incorrect way)
	Mtl *newMtl = GetMtl(), *origMtl = node->GetMtl();
	if( newMtl != nil && newMtl == origMtl )	// newMtl should == origMtl, but check just in case
	{
		hsTArray<hsGMaterial *>	oldMaterials, newMaterials;

		if( hsMaterialConverter::IsMultiMat( newMtl ) )
		{
			for( i = 0; i < newMtl->NumSubMtls(); i++ )
			{
				hsMaterialConverter::Instance().GetMaterialArray( origMtl->GetSubMtl( i ), node, oldMaterials );
				hsMaterialConverter::Instance().GetMaterialArray( newMtl->GetSubMtl( i ), this, newMaterials );
			}
		}
		else
		{
			hsMaterialConverter::Instance().GetMaterialArray( origMtl, node, oldMaterials );
			hsMaterialConverter::Instance().GetMaterialArray( newMtl, this, newMaterials );
		}

		/// Now we have two arrays to let us map, so walk through our geoSpans and translate them!
		/// The good thing is that this is all done before the spans are added to the drawable,
		/// so we don't have to worry about reffing or unreffing or any of that messiness; all of
		/// that will be done for us as part of the normal AppendDISpans() process.
		for( i = 0; i < spanArray.GetCount(); i++ )
		{
			int		j;


			// Find the span's original material
			for( j = 0; j < oldMaterials.GetCount(); j++ )
			{
				if( spanArray[ i ]->fMaterial == oldMaterials[ j ] )
				{
					spanArray[ i ]->fMaterial = newMaterials[ j ];
					break;
				}
			}

		}
	}
#endif

	return true;
}

//// IBuildInstanceList //////////////////////////////////////////////////////
//	For the given object, builds a list of all the iNodes that have that
//	object as their object. Returns the total node count

UInt32	plMaxNode::IBuildInstanceList( Object *obj, TimeValue t, hsTArray<plMaxNode *> &nodes, hsBool beMoreAccurate )
{
	Object				*thisObj = EvalWorldState( t ).obj;
	DependentIterator	di( obj );
	ReferenceMaker		*rm;
	plMaxNode			*node;
	plKey				sceneNodeKey = GetRoomKey();


	/// Use the DependentIterator to loop through all the dependents of the object,
	/// looking for nodes that use it
	nodes.Reset();
	while( rm = di.Next() )
	{
		if( rm->SuperClassID() == BASENODE_CLASS_ID )
		{
			node = (plMaxNode *)rm;
			if( node->EvalWorldState( t ).obj == thisObj )
			{
				// Note: we CANNOT instance across pages (i.e. sceneNodes), so we need to make sure this
				// INode will be in the same page as our master object

				// Also note: RoomKeys will be nil until we've finished the first component pass, so when
				// we test this in ConvertValidate(), the keys will be nil and all objects will be "in the
				// same room", even though they're not. This is not too bad, though, since the worst that
				// could happen is the object gets forced local even when there ends up not being any other
				// instances of it in the same page. Ooooh.

				if( sceneNodeKey == node->GetRoomKey() )
				{
					// Make sure the materials generated for both of these nodes will be the same
					if( IMaterialsMatch( node, beMoreAccurate ) )
						nodes.Append( node );
				}
			}
		}
	}

	return nodes.GetCount();
}

//// IMaterialsMatch /////////////////////////////////////////////////////////
//	Given two nodes that are instances of each other, this function determines
//	whether the resulting exported materials for both will be the same or not.
//	If not, we need to not instance/share the geometry, since the UV channels
//	could (and most likely will) be different.
//	To test this, all we really need to do is check the return values of
//	AlphaHackLayersNeeded(), since all the other material parameters will be
//	identical due to these nodes being instances of each other.

hsBool	plMaxNode::IMaterialsMatch( plMaxNode *otherNode, hsBool beMoreAccurate )
{
	Mtl *mtl = GetMtl(), *otherMtl = otherNode->GetMtl();
	if( mtl != otherMtl )
		return false;	// The two objects have different materials, no way we
						// can try to instance them now
	if( mtl == nil )
		return true;	// Both nodes have no material, works for me

	// If we're not told to be accurate, then we just quit here. This is because
	// in the early passes, we *can't* be more accurate, since we won't have all
	// the info yet, so we don't bother checking it
	if( !beMoreAccurate )
		return true;

	if( hsMaterialConverter::IsMultiMat( mtl ) )
	{
		int		i;
		for( i = 0; i < mtl->NumSubMtls(); i++ )
		{
			if( AlphaHackLayersNeeded( i ) != otherNode->AlphaHackLayersNeeded( i ) )
				return false;
		}
	}
	else
	{
		if( AlphaHackLayersNeeded( -1 ) != otherNode->AlphaHackLayersNeeded( -1 ) )
			return false;
	}

	// They're close enough!
	return true;
}

hsBool plMaxNode::ShadeMesh(plErrorMsg *pErrMsg, plConvertSettings *settings)
{
	const char* dbgNodeName = GetName();

	hsTArray<plGeometrySpan *> spanArray;

	if( !(CanConvert() && GetDrawable()) ) 
		return true;

	plSceneObject* obj = GetSceneObject();
	if( !obj )
		return true;

	const plDrawInterface* di = obj->GetDrawInterface();
	if( !di )
		return true;

	UInt8 iDraw;
	for( iDraw = 0; iDraw < di->GetNumDrawables(); iDraw++ )
	{
		plDrawableSpans* dr = plDrawableSpans::ConvertNoRef(di->GetDrawable(iDraw));
		if( !dr )
			continue;
		if( di->GetDrawableMeshIndex(iDraw) == (UInt32)-1 )
			continue;

		plDISpanIndex disi = dr->GetDISpans(di->GetDrawableMeshIndex(iDraw));

		int i;
		for( i = 0; i < disi.fIndices.GetCount(); i++ )
		{
			spanArray.Append( dr->GetGeometrySpan( disi.fIndices[ i ] ) );
		}

		hsMatrix44 l2w = GetLocalToWorld44();
		hsMatrix44 w2l = GetWorldToLocal44();

		/// Shade the spans now
		// Either do vertex shading or generate a light map.
		if( GetLightMapComponent() )
		{
			plLightMapGen::Instance().MakeMaps(this, l2w, w2l, spanArray, pErrMsg, nil);

			// Since they were already pointers to the geometry spans, we don't have
			// to re-stuff them. Horray!
		}
		else
		{
			hsVertexShader::Instance().ShadeNode(this, l2w, w2l, spanArray);
		}

		if (settings && settings->fSceneViewer)
			dr->RefreshDISpans(di->GetDrawableMeshIndex(iDraw));
	}
	return true;
}

hsBool plMaxNode::MakeOccluder(plErrorMsg *pErrMsg, plConvertSettings *settings)
{
	if( !UserPropExists("Occluder") )
		return true;

	hsBool twoSided = UserPropExists("OccTwoSided");
	hsBool isHole = UserPropExists("OccHole");

	return ConvertToOccluder(pErrMsg, twoSided, isHole);
}

static void IRemoveCollinearPoints(hsTArray<Point3>& facePts)
{
	int i;
	for( i = 0; i < facePts.GetCount(); )
	{
		int j = i + 1 >= facePts.GetCount() ? 0 : i + 1;
		int k = j + 1 >= facePts.GetCount() ? 0 : j + 1;
		Point3 ab = FNormalize(facePts[i] - facePts[j]);
		Point3 bc = FNormalize(facePts[j] - facePts[k]);

		const float kDotCutoff = 1.f - 1.e-3f;
		float dot = DotProd(ab, bc);
		if( (dot < kDotCutoff) && (dot > -kDotCutoff) )
		{
			i++;
		}
		else
		{
			facePts.Remove(j);
		}
	}
}

hsBool plMaxNode::ConvertToOccluder(plErrorMsg* pErrMsg, hsBool twoSided, hsBool isHole)
{
	if( !CanConvert() )
		return false;

	/// Get some stuff
	plLocation nodeLoc = GetLocation();

	hsBool moving = IsMovable();
	if( moving )
		moving++;

	Matrix3 tmp(true);

	Matrix3 maxL2V = GetLocalToVert(TimeValue(0));
	Matrix3 maxV2L = GetVertToLocal(TimeValue(0));

	hsTArray<plCullPoly> polys;

	UInt32 polyInitFlags = plCullPoly::kNone;
	if( isHole )
		polyInitFlags |= plCullPoly::kHole;
	else
	if( twoSided )
		polyInitFlags |= plCullPoly::kTwoSided;

	Object *obj = EvalWorldState(TimeValue(0)).obj;
	if( obj->CanConvertToType(triObjectClassID) )
	{
		TriObject	*meshObj = (TriObject *)obj->ConvertToType(TimeValue(0), triObjectClassID);
		if( meshObj )
		{

			Mesh mesh(meshObj->mesh);
			
			const float kNormThresh = hsScalarPI / 20.f;
			const float kEdgeThresh = hsScalarPI / 20.f;
			const float kBias = 0.1f;
			const float kMaxEdge = -1.f;
			const DWORD kOptFlags = OPTIMIZE_SAVESMOOTHBOUNDRIES; 

			mesh.Optimize(
				kNormThresh, // threshold of normal differences to preserve
				kEdgeThresh, // When the angle between adjacent surface normals is less than this value the auto edge is performed (if the OPTIMIZE_AUTOEDGE flag is set). This angle is specified in radians.
				kBias, // Increasing the bias parameter keeps triangles from becoming degenerate. range [0..1] (0 = no bias).
				kMaxEdge, // This will prevent the optimize function from creating edges longer than this value. If this parameter is <=0 no limit is placed on the length of the edges.
				kOptFlags, // Let them input using smoothing groups, but nothing else.
				NULL); // progress bar

			
			MNMesh mnMesh(mesh);

			mnMesh.EliminateCollinearVerts();
			mnMesh.EliminateCoincidentVerts(0.1f);

			// Documentation recommends MakeConvexPolyMesh over MakePolyMesh. Naturally, MakePolyMesh works better.
//			mnMesh.MakeConvexPolyMesh();
			mnMesh.MakePolyMesh();
			mnMesh.MakeConvex();
//			mnMesh.MakePlanar(1.f * hsScalarPI / 180.f); // Completely ineffective. Winding up with majorly non-planar polys.

			mnMesh.Transform(maxV2L);

			polys.SetCount(mesh.getNumFaces());
			polys.SetCount(0);

			// Unfortunate problem here. Max is assuming that eventually this will get rendered, and so
			// we need to avoid T-junctions. Fact is, T-junctions don't bother us at all, where-as colinear
			// verts within a poly do (just as added overhead).
			// So, to make this as painless (ha ha) as possible, we could detach each poly as we go to
			// its own mnMesh, then eliminate colinear verts on that single poly mesh. Except
			// EliminateCollinearVerts doesn't seem to actually do that. So we'll just have to
			// manually detect and skip collinear verts.
			hsTArray<Point3> facePts;
			int i;
			for( i = 0; i < mnMesh.numf; i++ )
			{
				MNFace& face = mnMesh.f[i];

				facePts.SetCount(0);
				int j;
				for( j = 0; j < face.deg; j++ )
				{
					facePts.Append(mnMesh.v[face.vtx[j]].p);
				}
				IRemoveCollinearPoints(facePts);

				if( facePts.GetCount() < 3 )
					continue;

				int lastAdded = 2;

				plCullPoly* poly = polys.Push();
				poly->fVerts.SetCount(0);

				Point3 p;
				hsPoint3 pt;

				p = facePts[0];
				pt.Set(p.x, p.y, p.z);
				poly->fVerts.Append(pt);

				p = facePts[1];
				pt.Set(p.x, p.y, p.z);
				poly->fVerts.Append(pt);

				p = facePts[2];
				pt.Set(p.x, p.y, p.z);
				poly->fVerts.Append(pt);

				for( j = lastAdded+1; j < facePts.GetCount(); j++ )
				{
					p = facePts[j];
					pt.Set(p.x, p.y, p.z);

					hsVector3 a = hsVector3(&pt, &poly->fVerts[0]);
					hsVector3 b = hsVector3(&poly->fVerts[lastAdded], &poly->fVerts[0]);
					hsVector3 c = hsVector3(&poly->fVerts[lastAdded-1], &poly->fVerts[0]);

					hsVector3 aXb = a % b;
					hsVector3 bXc = b % c;

					hsFastMath::Normalize(aXb);
					hsFastMath::Normalize(bXc);


					hsScalar dotSq = aXb.InnerProduct(bXc);
					dotSq *= dotSq;

					const hsScalar kMinLenSq = 1.e-8f;
					const hsScalar kMinDotFracSq = 0.998f * 0.998f;

					hsScalar lenSq = aXb.MagnitudeSquared() * bXc.MagnitudeSquared();
					if( lenSq < kMinLenSq )
						continue;

					// If not planar, move to new poly.
					if( dotSq < lenSq * kMinDotFracSq )
					{
						poly->InitFromVerts(polyInitFlags);

						poly = polys.Push();
						plCullPoly* lastPoly = &polys[polys.GetCount()-2];
						poly->fVerts.SetCount(0);
						poly->fVerts.Append(lastPoly->fVerts[0]);
						poly->fVerts.Append(lastPoly->fVerts[lastAdded]);
	
						lastAdded = 1;
					}

					poly->fVerts.Append(pt);
					lastAdded++;
				}

				poly->InitFromVerts(polyInitFlags);
			}
		}
	}

	if( polys.GetCount() )
	{
		plOccluder* occ = nil;
		plMobileOccluder* mob = nil;
		if( moving )
		{
			mob = TRACKED_NEW plMobileOccluder;
			occ = mob;
		}
		else
		{
			occ = TRACKED_NEW plOccluder;
		}

		occ->SetPolyList(polys);
		occ->ComputeFromPolys();

		// Register it.
		char tmpName[256];
		if( GetKey() && GetKey()->GetName() && *GetKey()->GetName() )
		{
			sprintf(tmpName, "%s_%s", GetKey()->GetName(), "Occluder");
		}
		else
		{
			static int numOcc = 0;
			sprintf(tmpName, "%s_%4.4d", "Occluder", numOcc);
		}
		plKey key = hsgResMgr::ResMgr()->NewKey( tmpName, occ, nodeLoc, GetLoadMask() );

		hsgResMgr::ResMgr()->AddViaNotify(occ->GetKey(), TRACKED_NEW plObjRefMsg(GetKey(), plRefMsg::kOnCreate, -1, plObjRefMsg::kInterface), plRefFlags::kActiveRef);

	}
	return true;
}

hsBool plMaxNode::MakeLight(plErrorMsg *pErrMsg, plConvertSettings *settings)
{

	if (!CanConvert()) 
		return false;

	if (!GetRunTimeLight())
		return true;

	/// Get some stuff
	plLocation nodeLoc = GetLocation();
	hsBool forceLocal = GetForceLocal();

	hsMatrix44 l2w = GetLocalToWorld44();
	hsMatrix44 w2l = GetWorldToLocal44();

	hsMatrix44 lt2l = GetVertToLocal44();
	hsMatrix44 l2lt = GetLocalToVert44();


	plLightInfo* liInfo = nil;


	liInfo = IMakeLight(pErrMsg, settings);

	if( liInfo )
	{
		// 12.03.01 mcn - Um, we want RT lights to affect static objects if they're animated. So 
		// why wasn't this here a long time ago? :~
		if( IsMovable() || IsAnimatedLight() )
			liInfo->SetProperty(plLightInfo::kLPMovable, true);

		liInfo->SetTransform(l2w, w2l);
		liInfo->SetLocalToLight(l2lt, lt2l);

		plKey key = hsgResMgr::ResMgr()->NewKey( GetKey()->GetName(), liInfo, nodeLoc, GetLoadMask() );

		hsgResMgr::ResMgr()->AddViaNotify(liInfo->GetKey(), TRACKED_NEW plObjRefMsg(GetKey(), plRefMsg::kOnCreate, -1, plObjRefMsg::kInterface), plRefFlags::kActiveRef);


		// Only support projection for spots and dir lights for now.
		if( plLimitedDirLightInfo::ConvertNoRef(liInfo) || plSpotLightInfo::ConvertNoRef(liInfo) )
		{
			// Have to do this after the drawable gets a key.
			IGetProjection(liInfo, pErrMsg);

		}
	}

	return true;
}

plLightInfo* plMaxNode::IMakeLight(plErrorMsg *pErrMsg, plConvertSettings *settings)
{
	TimeValue timeVal = hsConverterUtils::Instance().GetTime(GetInterface());
	plLightInfo* liInfo = nil;
	Object *obj = EvalWorldState(timeVal).obj;
	if( obj->ClassID() == Class_ID(OMNI_LIGHT_CLASS_ID, 0) )
		liInfo = IMakeOmni(pErrMsg, settings);
	else 
	if( (obj->ClassID() == Class_ID(SPOT_LIGHT_CLASS_ID, 0)) || (obj->ClassID() == Class_ID(FSPOT_LIGHT_CLASS_ID, 0)) )
		liInfo = IMakeSpot(pErrMsg, settings);
	else 
	if( (obj->ClassID() == Class_ID(DIR_LIGHT_CLASS_ID, 0)) || (obj->ClassID() == Class_ID(TDIR_LIGHT_CLASS_ID, 0)) )
		liInfo = IMakeDirectional(pErrMsg, settings);
	else
	if( obj->ClassID() == RTOMNI_LIGHT_CLASSID )
		liInfo = IMakeRTOmni(pErrMsg, settings);
	else
	if( (obj->ClassID() == RTSPOT_LIGHT_CLASSID) ) //|| (obj->ClassID() == Class_ID(FSPOT_LIGHT_CLASS_ID, 0)) )
		liInfo = IMakeRTSpot(pErrMsg, settings);
	else 
	if( (obj->ClassID() == RTDIR_LIGHT_CLASSID) ) //|| (obj->ClassID() == Class_ID(FSPOT_LIGHT_CLASS_ID, 0)) )
		liInfo = IMakeRTDirectional(pErrMsg, settings);
	else
	if( obj->ClassID() == RTPDIR_LIGHT_CLASSID )
		liInfo = IMakeRTProjDirectional( pErrMsg, settings );

	return liInfo;
}

void plMaxNode::IGetLightAttenuation(plOmniLightInfo* liInfo, LightObject* light, LightState& ls)
{
	TimeValue timeVal = hsConverterUtils::Instance().GetTime(GetInterface());

	hsScalar attenConst, attenLinear, attenQuadratic;

	float intens = ls.intens >= 0 ? ls.intens : -ls.intens;
	float attenEnd = ls.attenEnd;
	// Decay type 0:None, 1:Linear, 2:Squared
	if( ls.useAtten )
	{
		switch(((GenLight*)light)->GetDecayType())
		{
		case 0:
		case 1:
			attenConst = 1.f;
			attenLinear = (intens * plSillyLightKonstants::GetFarPowerKonst() - 1.f) / attenEnd;
			attenQuadratic = 0;
			break;
		case 2:
			attenConst = 1.f;
			attenLinear = 0;
			attenQuadratic = (intens * plSillyLightKonstants::GetFarPowerKonst() -1.f) / (attenEnd * attenEnd);
			break;
		case 3:
			attenConst = intens;		
			attenLinear = 0.f;
			attenQuadratic = 0.f;
			liInfo->SetCutoffAttenuation( ( (GenLight *)light )->GetDecayRadius( timeVal ) );
			break;
		}
	}
	else
	{
		attenConst = 1.f;
		attenLinear = 0.f;
		attenQuadratic = 0.f;
	}

	liInfo->SetConstantAttenuation(attenConst);
	liInfo->SetLinearAttenuation(attenLinear);
	liInfo->SetQuadraticAttenuation(attenQuadratic);

}

hsBool plMaxNode::IGetRTLightAttenValues(IParamBlock2* ProperPB, hsScalar& attenConst, hsScalar& attenLinear, hsScalar& attenQuadratic, hsScalar &attenCutoff )
{
	TimeValue timeVal = hsConverterUtils::Instance().GetTime(GetInterface());

	float intens = ProperPB->GetFloat(plRTLightBase::kIntensity, timeVal);
	if( intens < 0 )
		intens = -intens;
	float attenEnd;
	
	attenEnd = ProperPB->GetFloat(plRTLightBase::kAttenMaxFalloffEdit, timeVal);//ls.attenEnd;

	// Decay Type New == 0 for Linear and 1 for Squared.... OLD and OBSOLETE:Decay type 0:None, 1:Linear, 2:Squared
	// Oh, and now 2 = cutoff attenuation
	if( ProperPB->GetInt(plRTLightBase::kUseAttenuationBool, timeVal))
	{
		switch(ProperPB->GetInt(plRTLightBase::kAttenTypeRadio, timeVal))//((GenLight*)light)->GetDecayType())
		{
		case 0:
			attenConst = 1.f;
			attenLinear = (intens * plSillyLightKonstants::GetFarPowerKonst() - 1.f) / attenEnd;
			if( attenLinear < 0 )
				attenLinear = 0;
			attenQuadratic = 0;
			attenCutoff = attenEnd;
			break;
		case 1:
			attenConst = 1.f;
			attenLinear = 0;
			attenQuadratic = (intens * plSillyLightKonstants::GetFarPowerKonst() - 1.f) / (attenEnd * attenEnd);
			if( attenQuadratic < 0 )
				attenQuadratic = 0;
			attenCutoff = attenEnd;
			break;
		case 2:
			attenConst = intens;
			attenLinear = 0.f;
			attenQuadratic = 0.f;
			attenCutoff = attenEnd;
			break;
		}
		return true;
	}
	else
	{
		attenConst = 1.f;
		attenLinear = 0.f;
		attenQuadratic = 0.f;
		attenCutoff = 0.f;
		return true;
	}

	return false;
}

void plMaxNode::IGetRTLightAttenuation(plOmniLightInfo* liInfo, IParamBlock2* ProperPB)
{
	hsScalar attenConst, attenLinear, attenQuadratic, attenCutoff;

	if( IGetRTLightAttenValues(ProperPB, attenConst, attenLinear, attenQuadratic, attenCutoff) )
	{
		liInfo->SetConstantAttenuation(attenConst);
		liInfo->SetLinearAttenuation(attenLinear);
		liInfo->SetQuadraticAttenuation(attenQuadratic);
		liInfo->SetCutoffAttenuation( attenCutoff );
	}
}

void plMaxNode::IGetLightColors(plLightInfo* liInfo, LightObject* light, LightState& ls)
{
	TimeValue timeVal = hsConverterUtils::Instance().GetTime(GetInterface());

	Point3 color = light->GetRGBColor(timeVal);
	float intensity = light->GetIntensity(timeVal);

	color *= intensity;

	liInfo->SetAmbient(hsColorRGBA().Set(0,0,0,1.f));
	if( ls.affectDiffuse )
		liInfo->SetDiffuse(hsColorRGBA().Set(color.x, color.y, color.z, intensity));
	else
		liInfo->SetDiffuse(hsColorRGBA().Set(0,0,0,intensity));
	if( ls.affectSpecular )
		liInfo->SetSpecular(hsColorRGBA().Set(color.x, color.y, color.z, intensity));
	else
		liInfo->SetSpecular(hsColorRGBA().Set(0,0,0,intensity));

}

void plMaxNode::IGetRTLightColors(plLightInfo* liInfo, IParamBlock2* ProperPB)
{
	TimeValue timeVal = hsConverterUtils::Instance().GetTime(GetInterface());

	Point3 color = ProperPB->GetPoint3(plRTLightBase::kLightColor, timeVal);//light->GetRGBColor(timeVal);
	float intensity = ProperPB->GetFloat(plRTLightBase::kIntensity, timeVal); //light->GetIntensity(timeVal);

	color *= intensity;

	liInfo->SetAmbient(hsColorRGBA().Set(0,0,0,1.f));
	if( ProperPB->GetInt( plRTLightBase::kAffectDiffuse, timeVal ) )
		liInfo->SetDiffuse(hsColorRGBA().Set(color.x, color.y, color.z, intensity));
	else
		liInfo->SetDiffuse(hsColorRGBA().Set(0,0,0,intensity));
	if( ProperPB->GetInt(plRTLightBase::kSpec, timeVal)) //ls.affectSpecular )
	{
		Color spec = ProperPB->GetColor(plRTLightBase::kSpecularColorSwatch);
		liInfo->SetSpecular(hsColorRGBA().Set(spec.r, spec.g, spec.b, intensity));
	}
	else
		liInfo->SetSpecular(hsColorRGBA().Set(0,0,0,intensity));
}

void plMaxNode::IGetCone(plSpotLightInfo* liInfo, LightObject* light, LightState& ls)
{

	hsScalar inner = hsScalarDegToRad(ls.hotsize);
	hsScalar outer = hsScalarDegToRad(ls.fallsize);

	/// 4.26.2001 mcn - MAX gives us full angles, but we want to store half angles
	liInfo->SetSpotInner( inner / 2.0f );
	liInfo->SetSpotOuter( outer / 2.0f );
	liInfo->SetFalloff(1.f);
}

void plMaxNode::IGetRTCone(plSpotLightInfo* liInfo, IParamBlock2* ProperPB)
{

	//TimeValue timeVal = hsConverterUtils::Instance().GetTime(GetInterface());
	TimeValue timeVal = hsConverterUtils::Instance().GetTime(GetInterface());
	hsScalar inner, outer;

	inner = hsScalarDegToRad(ProperPB->GetFloat(plRTLightBase::kHotSpot, timeVal)); //ls.hotsize);
	outer = hsScalarDegToRad(ProperPB->GetFloat(plRTLightBase::kFallOff, timeVal)); //ls.fallsize);

	/// 4.26.2001 mcn - MAX gives us full angles, but we want to store half angles
	liInfo->SetSpotInner( inner / 2.0f );
	liInfo->SetSpotOuter( outer / 2.0f );
	liInfo->SetFalloff(1.f);

}

plLightInfo* plMaxNode::IMakeSpot(plErrorMsg* pErrMsg, plConvertSettings* settings)
{
	TimeValue timeVal = hsConverterUtils::Instance().GetTime(GetInterface());
	Object *obj = EvalWorldState(timeVal).obj;
	LightObject *light = (LightObject*)obj->ConvertToType(timeVal, Class_ID(SPOT_LIGHT_CLASS_ID,0));
	if( !light )
		light = (LightObject*)obj->ConvertToType(timeVal, Class_ID(FSPOT_LIGHT_CLASS_ID,0)); 

	LightState ls;
	if (!(REF_SUCCEED == light->EvalLightState(timeVal, Interval(timeVal, timeVal), &ls)))
	{
		pErrMsg->Set(true, GetName(), "Trouble evaluating light").CheckAndAsk();
		return nil;
	}

	plSpotLightInfo* spot = TRACKED_NEW plSpotLightInfo;

	IGetLightColors(spot, light, ls);

	IGetLightAttenuation(spot, light, ls);

	IGetCone(spot, light, ls);

	if( obj != light )
		light->DeleteThis();

	return spot;
}

plLightInfo* plMaxNode::IMakeOmni(plErrorMsg* pErrMsg, plConvertSettings* settings)
{
	TimeValue timeVal = hsConverterUtils::Instance().GetTime(GetInterface());

	Object *obj = EvalWorldState(timeVal).obj;
	LightObject *light = (LightObject*)obj->ConvertToType(timeVal, 
		Class_ID(OMNI_LIGHT_CLASS_ID,0));

	LightState ls;
	if (!(REF_SUCCEED == light->EvalLightState(timeVal, Interval(timeVal, timeVal), &ls)))
	{
		pErrMsg->Set(true, GetName(), "Trouble evaluating light").CheckAndAsk();
		return nil;
	}

	plOmniLightInfo* omni = TRACKED_NEW plOmniLightInfo;

	IGetLightAttenuation(omni, light, ls);

	IGetLightColors(omni, light, ls);

	if( obj != light )
		light->DeleteThis();

	return omni;
}

plLightInfo* plMaxNode::IMakeDirectional(plErrorMsg* pErrMsg, plConvertSettings* settings)
{
	TimeValue timeVal = hsConverterUtils::Instance().GetTime(GetInterface());

	Object *obj = EvalWorldState(timeVal).obj;
	LightObject *light = (LightObject*)obj->ConvertToType(timeVal, Class_ID(DIR_LIGHT_CLASS_ID,0));
	if( !light )
		light = (LightObject*)obj->ConvertToType(timeVal, Class_ID(TDIR_LIGHT_CLASS_ID,0)); 

	LightState ls;
	if (!(REF_SUCCEED == light->EvalLightState(timeVal, Interval(timeVal, timeVal), &ls)))
	{
		pErrMsg->Set(true, GetName(), "Trouble evaluating light").CheckAndAsk();
		return nil;
	}

	plLightInfo* plasLight = nil;
	if( light->GetProjMap() )
	{
		plLimitedDirLightInfo* ldl = TRACKED_NEW plLimitedDirLightInfo;

		float sz = light->GetFallsize(timeVal, FOREVER);
		float depth = 1000.f;
		ldl->SetWidth(sz);
		ldl->SetHeight(sz);
		ldl->SetDepth(depth);

		plasLight = ldl;
	}
	else
	{

		plDirectionalLightInfo* direct = TRACKED_NEW plDirectionalLightInfo;
		plasLight = direct;
	}

	IGetLightColors(plasLight, light, ls);

	if( obj != light )
		light->DeleteThis();

	return plasLight;
}

plLightInfo* plMaxNode::IMakeRTSpot(plErrorMsg* pErrMsg, plConvertSettings* settings)
{
	TimeValue timeVal = hsConverterUtils::Instance().GetTime(GetInterface());
	Object *obj = EvalWorldState(timeVal).obj;
	Object *ThisObj = ((INode*)this)->GetObjectRef();
	IParamBlock2* ThisObjPB = ThisObj->GetParamBlockByID(plRTLightBase::kBlkSpotLight);


	if(!obj->CanConvertToType(RTSPOT_LIGHT_CLASSID))
	{
		pErrMsg->Set(true, GetName(), "Trouble evaluating light, improper classID").CheckAndAsk();
		return nil;

	}

	plSpotLightInfo* spot = TRACKED_NEW plSpotLightInfo;

	if(!ThisObjPB->GetInt(plRTLightBase::kLightOn))
		spot->SetProperty(plLightInfo::kDisable, true); 

	IGetRTLightColors(spot,ThisObjPB);

	IGetRTLightAttenuation(spot,ThisObjPB);

	IGetRTCone(spot, ThisObjPB);

	//plSpotModifier* liMod = TRACKED_NEW plSpotModifier;

	//GetRTLightColAnim(ThisObjPB, liMod);
	//GetRTLightAttenAnim(ThisObjPB, liMod);
	//GetRTConeAnim(ThisObjPB, liMod);

	//IAttachRTLightModifier(liMod);

//	if( obj != light )
	//	light->DeleteThis();

	return spot;
}


plLightInfo* plMaxNode::IMakeRTOmni(plErrorMsg* pErrMsg, plConvertSettings* settings)
{
	TimeValue timeVal = hsConverterUtils::Instance().GetTime(GetInterface());

	Object *obj = EvalWorldState(timeVal).obj;
	
	Object *ThisObj = ((INode*)this)->GetObjectRef();
	IParamBlock2* ThisObjPB = ThisObj->GetParamBlockByID(plRTLightBase::kBlkOmniLight);

	plOmniLightInfo* omni = TRACKED_NEW plOmniLightInfo;

	if(!ThisObjPB->GetInt(plRTLightBase::kLightOn))
		omni->SetProperty(plLightInfo::kDisable, true); 

	IGetRTLightAttenuation(omni, ThisObjPB);

	IGetRTLightColors(omni, ThisObjPB);

	//plOmniModifier* liMod = TRACKED_NEW plOmniModifier;

	//GetRTLightColAnim(ThisObjPB, liMod);
	//GetRTLightAttenAnim(ThisObjPB, liMod);

	//IAttachRTLightModifier(liMod);


//	if( obj != light )
//		light->DeleteThis();

	return omni;
}


plLightInfo* plMaxNode::IMakeRTDirectional(plErrorMsg* pErrMsg, plConvertSettings* settings)
{
	TimeValue timeVal = hsConverterUtils::Instance().GetTime(GetInterface());

	Object *obj = EvalWorldState(timeVal).obj;
	Object *ThisObj = ((INode*)this)->GetObjectRef();
	IParamBlock2* ThisObjPB = ThisObj->GetParamBlockByID(plRTLightBase::kBlkTSpotLight);


	plDirectionalLightInfo* direct = TRACKED_NEW plDirectionalLightInfo;

	if(!ThisObjPB->GetInt(plRTLightBase::kLightOn))
		direct->SetProperty(plLightInfo::kDisable, true); 

	IGetRTLightColors(direct, ThisObjPB);

	//plLightModifier* liMod = TRACKED_NEW plLightModifier;

	//GetRTLightColAnim(ThisObjPB, liMod);

	//IAttachRTLightModifier(liMod);


//	if( obj != light )
//		light->DeleteThis();

	return direct;
}

//// IMakeRTProjDirectional //////////////////////////////////////////////////
//	Conversion function for RT Projected Directional lights

plLightInfo	*plMaxNode::IMakeRTProjDirectional( plErrorMsg *pErrMsg, plConvertSettings *settings )
{
	TimeValue timeVal = hsConverterUtils::Instance().GetTime(GetInterface());

	Object *obj = EvalWorldState(timeVal).obj;
	Object *ThisObj = ((INode*)this)->GetObjectRef();

	IParamBlock2	*mainPB = ThisObj->GetParamBlockByID( plRTLightBase::kBlkMain );
	IParamBlock2	*projPB = ThisObj->GetParamBlockByID( plRTProjDirLight::kBlkProj );

	plLimitedDirLightInfo *light = TRACKED_NEW plLimitedDirLightInfo;

	light->SetWidth( projPB->GetFloat( plRTProjDirLight::kWidth ) );
	light->SetHeight( projPB->GetFloat( plRTProjDirLight::kHeight ) );
	light->SetDepth( projPB->GetFloat( plRTProjDirLight::kRange ) );

	if( !mainPB->GetInt( plRTLightBase::kLightOn ) )
		light->SetProperty( plLightInfo::kDisable, true ); 

	IGetRTLightColors( light, mainPB );

	//plLightModifier *liMod = TRACKED_NEW plLightModifier;

	//GetRTLightColAnim( mainPB, liMod );

	//IAttachRTLightModifier(liMod);

	return light;
}

hsBool plMaxNode::IGetProjection(plLightInfo* li, plErrorMsg* pErrMsg)
{
	hsBool persp = false;
	TimeValue timeVal = hsConverterUtils::Instance().GetTime(GetInterface());
	Object *obj = EvalWorldState(timeVal).obj;
	LightObject *light = (LightObject*)obj->ConvertToType(timeVal, RTSPOT_LIGHT_CLASSID);

	if( light )
		persp = true;
	
	if( !light )
		light = (LightObject*)obj->ConvertToType(timeVal, RTPDIR_LIGHT_CLASSID);

	if( !light )
		return false;

	hsBool retVal = false;
	Texmap* projMap = light->GetProjMap();
	if( !projMap )
		return false;

	plConvert& convert = plConvert::Instance();
	if( projMap->ClassID() != LAYER_TEX_CLASS_ID )
	{
		if( pErrMsg->Set(!(convert.fWarned & plConvert::kWarnedWrongProj), GetName(),
					"Only Plasma Layers supported for projection").CheckAskOrCancel() )
		{
			convert.fWarned |= plConvert::kWarnedWrongProj;
		}
		pErrMsg->Set(false);
		return false;
	}

	IParamBlock2 *pb = nil;

	Class_ID cid = obj->ClassID();

	// Get the paramblock
	if (cid == RTSPOT_LIGHT_CLASSID)
		pb = obj->GetParamBlockByID(plRTLightBase::kBlkSpotLight);
	else if (cid == RTOMNI_LIGHT_CLASSID)
		pb = obj->GetParamBlockByID(plRTLightBase::kBlkOmniLight);
	else if (cid == RTDIR_LIGHT_CLASSID)
		pb = obj->GetParamBlockByID(plRTLightBase::kBlkTSpotLight);
	else if (cid == RTPDIR_LIGHT_CLASSID)
		pb = obj->GetParamBlockByID(plRTProjDirLight::kBlkProj);

	// Have the layer converter process this layer directly
	plLayerConverter::Instance().MuteWarnings();
	plLayerInterface* proj = plLayerConverter::Instance().ConvertTexmap( projMap, this, 0, true, false );
	plLayerConverter::Instance().UnmuteWarnings();
	if( proj )
	{
		plLayer* projLay = plLayer::ConvertNoRef(proj->BottomOfStack());
		if( projLay && projLay->GetTexture() )
		{
			if( persp )
				projLay->SetMiscFlags(projLay->GetMiscFlags() | hsGMatState::kMiscPerspProjection);
			else
				projLay->SetMiscFlags(projLay->GetMiscFlags() | hsGMatState::kMiscOrthoProjection);
			projLay->SetUVWSrc(projLay->GetUVWSrc() | plLayerInterface::kUVWPosition);
			projLay->SetClampFlags(hsGMatState::kClampTexture);
			projLay->SetZFlags(hsGMatState::kZNoZWrite);

			switch( pb->GetInt(plRTLightBase::kProjTypeRadio) )
			{
			default:
			case plRTLightBase::kIlluminate:
				projLay->SetBlendFlags(hsGMatState::kBlendMult);
				li->SetProperty(plLightInfo::kLPOverAll, false);
			break;
			case plRTLightBase::kAdd:
				projLay->SetBlendFlags(hsGMatState::kBlendAdd);
				li->SetProperty(plLightInfo::kLPOverAll, true);
			break;
			case plRTLightBase::kMult:
				projLay->SetBlendFlags(hsGMatState::kBlendMult | hsGMatState::kBlendInvertColor | hsGMatState::kBlendInvertFinalColor);
				li->SetProperty(plLightInfo::kLPOverAll, true);
			break;
			case plRTLightBase::kMADD:
				projLay->SetBlendFlags(hsGMatState::kBlendMADD);
				li->SetProperty(plLightInfo::kLPOverAll, true);
			break;
			}

			hsgResMgr::ResMgr()->AddViaNotify(proj->GetKey(), TRACKED_NEW plGenRefMsg(li->GetKey(), plRefMsg::kOnCreate, 0, 0), plRefFlags::kActiveRef);

			li->SetShadowCaster(false);

			li->SetProperty(plLightInfo::kLPMovable, true);


			retVal = true;
		}
		else
		{
			char buff[256];
			if( projMap && projMap->GetName() && *projMap->GetName() )
				sprintf(buff, "Can't find projected bitmap - %s", projMap->GetName());
			else
				sprintf(buff, "Can't find projected bitmap - <unknown>");
			if( pErrMsg->Set(!(convert.fWarned & plConvert::kWarnedMissingProj), GetName(),
					buff).CheckAskOrCancel() )
				convert.fWarned |= plConvert::kWarnedMissingProj;
			pErrMsg->Set(false);
			retVal = false;
		}
	}
	else
	{
		if( pErrMsg->Set(!(convert.fWarned & plConvert::kWarnedWrongProj), GetName(),
					"Failure to convert projection map - check type.").CheckAskOrCancel() )
			convert.fWarned |= plConvert::kWarnedWrongProj;
		pErrMsg->Set(false);
		retVal = false;
	}

	if( light != obj )
		light->DeleteThis();

	return retVal;
}
/*
hsBool plMaxNode::IAttachRTLightModifier(plLightModifier* liMod)
{
	if( liMod->HasAnima() )
	{
		liMod->DefaultAnimation();
		CreateModifierKey(liMod, "_ANIMA");
		AddModifier(liMod);
	}
	else
	{
		delete liMod;
		return false;
	}
	return true;
}
*/
bool plMaxNode::IsAnimatedLight()
{
	Object *obj = GetObjectRef();
	if (!obj)
		return false;

	const char* dbgNodeName = GetName();

	Class_ID cid = obj->ClassID();

	if (!(cid == RTSPOT_LIGHT_CLASSID ||
		cid == RTOMNI_LIGHT_CLASSID ||
		cid == RTDIR_LIGHT_CLASSID ||
		cid == RTPDIR_LIGHT_CLASSID))
		return false;

	IParamBlock2 *pb = nil;

	// Get the paramblock
	if (cid == RTSPOT_LIGHT_CLASSID)
		pb = obj->GetParamBlockByID(plRTLightBase::kBlkSpotLight);
	else if (cid == RTOMNI_LIGHT_CLASSID)
		pb = obj->GetParamBlockByID(plRTLightBase::kBlkOmniLight);
	else if (cid == RTDIR_LIGHT_CLASSID)
		pb = obj->GetParamBlockByID(plRTLightBase::kBlkTSpotLight);
	else if (cid == RTPDIR_LIGHT_CLASSID)
		pb = obj->GetParamBlockByID(plRTLightBase::kBlkMain);

	hsControlConverter& cc = hsControlConverter::Instance();

	// Is the color animated?
	Control *colorCtl = pb->GetController( ParamID( plRTLightBase::kLightColor ) );
	if (colorCtl && cc.HasKeyTimes(colorCtl))
		return true;

	// Is the specularity animated?
	Control *specCtl = pb->GetController( ParamID( plRTLightBase::kSpecularColorSwatch ) );
	if (specCtl && cc.HasKeyTimes(specCtl))
		return true;

	// Is the attenuation animated?  (Spot and Omni lights only)
	if (cid == RTSPOT_LIGHT_CLASSID || cid == RTOMNI_LIGHT_CLASSID)
	{
		Control *falloffCtl = pb->GetController( ParamID( plRTLightBase::kAttenMaxFalloffEdit ) );
		if (falloffCtl && cc.HasKeyTimes(falloffCtl))
			return true;
	}

	// Is the cone animated? (Spot only)
	if (cid == RTSPOT_LIGHT_CLASSID)
	{
		Control *innerCtl = pb->GetController( ParamID( plRTLightBase::kHotSpot ) );
		if (innerCtl && cc.HasKeyTimes(innerCtl))
			return true;

		Control *outerCtl = pb->GetController( ParamID( plRTLightBase::kFallOff ) );
		if (outerCtl && cc.HasKeyTimes(outerCtl))
			return true;
	}

	return false;
}


void plMaxNode::GetRTLightAttenAnim(IParamBlock2* ProperPB, plAGAnim *anim)
{
	if( ProperPB->GetInt(plRTLightBase::kUseAttenuationBool, TimeValue(0)) )
	{
		Control* falloffCtl = ProperPB->GetController(ParamID(plRTLightBase::kAttenMaxFalloffEdit));
		if( falloffCtl )
		{
			plLeafController* subCtl;
			if (!strcmp(anim->GetName(), ENTIRE_ANIMATION_NAME))
				subCtl = hsControlConverter::Instance().MakeScalarController(falloffCtl, this);
			else
				subCtl = hsControlConverter::Instance().MakeScalarController(falloffCtl, this,
																			anim->GetStart(), anim->GetEnd());

			if( subCtl )
			{
				if( ProperPB->GetInt(plRTLightBase::kAttenTypeRadio, TimeValue(0)) == 2 )
				{
					// Animation of a cutoff attenuation, which only needs a scalar channel
					plOmniCutoffApplicator *app = TRACKED_NEW plOmniCutoffApplicator();
					app->SetChannelName(GetName());
					plScalarControllerChannel *chan = TRACKED_NEW plScalarControllerChannel(subCtl);
					app->SetChannel(chan);
					anim->AddApplicator(app);
					if (!strcmp(anim->GetName(), ENTIRE_ANIMATION_NAME))
						anim->ExtendToLength(subCtl->GetLength());
				}
				else
				{
					hsBool distSq = ProperPB->GetInt(plRTLightBase::kAttenTypeRadio, TimeValue(0));

					int i;
					for( i = 0; i < subCtl->GetNumKeys(); i++ )
					{
						hsScalarKey *key = subCtl->GetScalarKey(i);
						if (key)
						{
							hsScalar attenEnd = key->fValue;
							TimeValue tv = key->fFrame * MAX_TICKS_PER_FRAME;
							hsScalar intens = ProperPB->GetFloat(plRTLightBase::kIntensity, tv);
							hsScalar newVal = (intens * plSillyLightKonstants::GetFarPowerKonst() - 1.f) / attenEnd;
							if( distSq )
								newVal /= attenEnd;

							key->fValue = newVal;
						}
						hsBezScalarKey *bezKey = subCtl->GetBezScalarKey(i);
						if (bezKey)
						{
							hsScalar attenEnd = bezKey->fValue;
							TimeValue tv = bezKey->fFrame * MAX_TICKS_PER_FRAME;
							hsScalar intens = ProperPB->GetFloat(plRTLightBase::kIntensity, tv);
							hsScalar newVal = (intens * plSillyLightKonstants::GetFarPowerKonst() - 1.f) / attenEnd;
							if( distSq )
								newVal /= attenEnd;

							/// From the chain rule, fix our tangents.
							bezKey->fInTan *= -(intens * plSillyLightKonstants::GetFarPowerKonst() - 1.f) / (attenEnd*attenEnd);
							if( distSq )
								bezKey->fInTan *= 2.f / attenEnd;

							bezKey->fOutTan *= -(intens * plSillyLightKonstants::GetFarPowerKonst() - 1.f) / (attenEnd*attenEnd);
							if( distSq )
								bezKey->fOutTan *= 2.f / attenEnd;

							bezKey->fValue = newVal;
						}
					}
					plAGApplicator *app;
					if (distSq)
						app = TRACKED_NEW plOmniSqApplicator;
					else
						app = TRACKED_NEW plOmniApplicator;

					app->SetChannelName(GetName());
					plScalarControllerChannel *chan = TRACKED_NEW plScalarControllerChannel(subCtl);
					app->SetChannel(chan);
					anim->AddApplicator(app);
					if (!strcmp(anim->GetName(), ENTIRE_ANIMATION_NAME))
						anim->ExtendToLength(subCtl->GetLength());

					hsScalar attenConst, attenLinear, attenQuadratic, attenCutoff;
					IGetRTLightAttenValues(ProperPB, attenConst, attenLinear, attenQuadratic, attenCutoff);

					plOmniLightInfo *info = plOmniLightInfo::ConvertNoRef(GetSceneObject()->GetGenericInterface(plOmniLightInfo::Index()));
					if (info)
					{
						hsPoint3 initAtten(attenConst, attenLinear, attenQuadratic);
						info->SetConstantAttenuation(attenConst);
						info->SetLinearAttenuation(attenLinear);
						info->SetQuadraticAttenuation(attenQuadratic);
					}
					else
						hsAssert(false, "Failed to find light info");
				}
			}
		}
	}
}

void plMaxNode::IAdjustRTColorByIntensity(plController* ctl, IParamBlock2* ProperPB)
{
	plLeafController* simp = plLeafController::ConvertNoRef(ctl);
	plCompoundController* comp;
	if( simp )
	{
		int i;
		for( i = 0; i < simp->GetNumKeys(); i++ )
		{
			hsPoint3Key* key = simp->GetPoint3Key(i);
			if (key)
			{
				TimeValue tv = key->fFrame * MAX_TICKS_PER_FRAME;
				hsScalar intens = ProperPB->GetFloat(plRTLightBase::kIntensity, tv);
				key->fValue *= intens;
			}
			hsBezPoint3Key* bezKey = simp->GetBezPoint3Key(i);
			if (bezKey)
			{
				TimeValue tv = bezKey->fFrame * MAX_TICKS_PER_FRAME;
				hsScalar intens = ProperPB->GetFloat(plRTLightBase::kIntensity, tv);
				bezKey->fInTan *= intens;
				bezKey->fOutTan *= intens;
				bezKey->fValue *= intens;
			}
		}
	}
	else if( comp = plCompoundController::ConvertNoRef(ctl) )
	{
		int j;
		for( j = 0; j < 3; j++ )
		{
			IAdjustRTColorByIntensity(comp->GetController(j), ProperPB);
		}
	}
}

void plMaxNode::GetRTLightColAnim(IParamBlock2* ProperPB, plAGAnim *anim)
{
	Control* ambientCtl = nil; // Ambient not currently supported
	Control* colorCtl = ProperPB->GetController(ParamID(plRTLightBase::kLightColor));
	Control* specCtl = ProperPB->GetController(ParamID(plRTLightBase::kSpecularColorSwatch));
	plPointControllerChannel *chan;

	if( ambientCtl )
	{
		plController* ctl;
		if (!strcmp(anim->GetName(), ENTIRE_ANIMATION_NAME))
			ctl = hsControlConverter::Instance().MakeColorController(ambientCtl, this);
		else
			ctl = hsControlConverter::Instance().MakeColorController(ambientCtl, this, anim->GetStart(), anim->GetEnd());

		if( ctl )
		{
			plLightAmbientApplicator *app = TRACKED_NEW plLightAmbientApplicator();
			app->SetChannelName(GetName());
			chan = TRACKED_NEW plPointControllerChannel(ctl);
			app->SetChannel(chan);
			anim->AddApplicator(app);
			if (!strcmp(anim->GetName(), ENTIRE_ANIMATION_NAME))
				anim->ExtendToLength(ctl->GetLength());
		}
	}
	if( colorCtl )
	{
		plController* ctl;
		if (!strcmp(anim->GetName(), ENTIRE_ANIMATION_NAME))
			ctl = hsControlConverter::Instance().MakeColorController(colorCtl, this);
		else
			ctl = hsControlConverter::Instance().MakeColorController(colorCtl, this, anim->GetStart(), anim->GetEnd());
						
		if( ctl )
		{
			IAdjustRTColorByIntensity(ctl, ProperPB);
			plLightDiffuseApplicator *app = TRACKED_NEW plLightDiffuseApplicator();
			app->SetChannelName(GetName());
			chan = TRACKED_NEW plPointControllerChannel(ctl);
			app->SetChannel(chan);
			anim->AddApplicator(app);
			if (!strcmp(anim->GetName(), ENTIRE_ANIMATION_NAME))
				anim->ExtendToLength(ctl->GetLength());
		}
	}
	if( specCtl )
	{
		plController* ctl;
		if (!strcmp(anim->GetName(), ENTIRE_ANIMATION_NAME))
			ctl = hsControlConverter::Instance().MakeColorController(specCtl, this);
		else
			ctl = hsControlConverter::Instance().MakeColorController(specCtl, this, anim->GetStart(), anim->GetEnd());
		
		if( ctl )
		{
			plLightSpecularApplicator *app = TRACKED_NEW plLightSpecularApplicator();
			app->SetChannelName(GetName());
			chan = TRACKED_NEW plPointControllerChannel(ctl);
			app->SetChannel(chan);
			anim->AddApplicator(app);
			if (!strcmp(anim->GetName(), ENTIRE_ANIMATION_NAME))
				anim->ExtendToLength(ctl->GetLength());
		}
	}
}

void plMaxNode::GetRTConeAnim(IParamBlock2* ProperPB, plAGAnim *anim)
{
	Control* innerCtl = ProperPB->GetController(ParamID(plRTLightBase::kHotSpot));
	Control* outerCtl = ProperPB->GetController(ParamID(plRTLightBase::kFallOff));
	plScalarControllerChannel *chan;

	if( innerCtl )
	{
		plLeafController* ctl;
		if (!strcmp(anim->GetName(), ENTIRE_ANIMATION_NAME))
			ctl = hsControlConverter::Instance().MakeScalarController(innerCtl, this);
		else
			ctl = hsControlConverter::Instance().MakeScalarController(innerCtl, this, anim->GetStart(), anim->GetEnd());
			
		if( ctl )
		{
			plSpotInnerApplicator *app = TRACKED_NEW plSpotInnerApplicator();
			app->SetChannelName(GetName());
			chan = TRACKED_NEW plScalarControllerChannel(ctl);
			app->SetChannel(chan);
			anim->AddApplicator(app);
			if (!strcmp(anim->GetName(), ENTIRE_ANIMATION_NAME))
				anim->ExtendToLength(ctl->GetLength());
		}
	}
	if( outerCtl )
	{
		plController* ctl;
		if (!strcmp(anim->GetName(), ENTIRE_ANIMATION_NAME))
			ctl = hsControlConverter::Instance().MakeScalarController(outerCtl, this);
		else
			ctl = hsControlConverter::Instance().MakeScalarController(outerCtl, this, anim->GetStart(), anim->GetEnd());

		if( ctl )
		{
			plSpotOuterApplicator *app = TRACKED_NEW plSpotOuterApplicator();
			app->SetChannelName(GetName());
			chan = TRACKED_NEW plScalarControllerChannel(ctl);
			app->SetChannel(chan);
			anim->AddApplicator(app);
			if (!strcmp(anim->GetName(), ENTIRE_ANIMATION_NAME))
				anim->ExtendToLength(ctl->GetLength());
		}
	}
}

plXImposterComp* plMaxNode::GetXImposterComp()
{
	int count = NumAttachedComponents();
	int i;
	for( i = 0; i < count; i++ )
	{
		// See if any are a x-imposter component.
		plComponentBase *comp = GetAttachedComponent(i);
		if( comp && (comp->ClassID() == XIMPOSTER_COMP_CID) )
		{
			plXImposterComp* ximp = (plXImposterComp*)comp;
			return ximp;
		}
	}
	return nil;
}

Point3 plMaxNode::GetFlexibility()
{
	UInt32 count = NumAttachedComponents();

	// Go through all the components attached to this node
	for (UInt32 i = 0; i < count; i++)
	{
		// See if any are a flexibility component.
		plComponentBase *comp = GetAttachedComponent(i);
		if( comp && (comp->ClassID() == FLEXIBILITY_COMP_CID) )
		{
			plFlexibilityComponent* flex = (plFlexibilityComponent*)comp;
			return flex->GetFlexibility();
		}
	}
	return Point3(0.f, 0.f, 0.f);
}

plLightMapComponent* plMaxNode::GetLightMapComponent()
{
	UInt32 count = NumAttachedComponents();

	// Go through all the components attached to this node
	for (UInt32 i = 0; i < count; i++)
	{
		// See if any are a flexibility component.
		plComponentBase *comp = GetAttachedComponent(i);
		if( comp && (comp->ClassID() == LIGHTMAP_COMP_CID) )
		{
			plLightMapComponent* lmap = (plLightMapComponent*)comp;
			return lmap;
		}
	}
	return nil;
}

plDrawableCriteria plMaxNode::GetDrawableCriteria(hsBool needBlending, hsBool needSorting)
{
	plRenderLevel level = needBlending ? GetRenderLevel(needBlending) : plRenderLevel::OpaqueRenderLevel();

	if( GetSortAsOpaque() )
		level.Set(plRenderLevel::kOpaqueMajorLevel, level.Minor());

	UInt32 crit = 0;
	if( needBlending )
	{
		if( needSorting && !GetNoFaceSort() )
			crit |= plDrawable::kCritSortFaces;
		if( !GetNoSpanSort() )
			crit |= plDrawable::kCritSortSpans;
	}

	if( GetItinerant() )
		crit |= plDrawable::kCritCharacter;

	plDrawableCriteria retVal(crit, level, GetLoadMask());

	if( GetEnviron() )
		retVal.fType |= plDrawable::kEnviron;
	if( GetEnvironOnly() )
		retVal.fType &= ~plDrawable::kNormal;

	return retVal;
}

//// IGetSceneNodeSpans //////////////////////////////////////////////////////
//	Gets the required drawableSpans from a sceneNode. Creates a new one
//	if it can't find one.

plDrawableSpans	*plMaxNode::IGetSceneNodeSpans( plSceneNode *node, hsBool needBlending, hsBool needSorting )
{

	plDrawableSpans	*spans;
	char			tmpName[ 512 ];
	plLocation		nodeLoc = GetLocation();  
	
	if( !needBlending )
		needSorting = false;

	plDrawableCriteria			crit = GetDrawableCriteria(needBlending, needSorting);

	spans = plDrawableSpans::ConvertNoRef( node->GetMatchingDrawable( crit ) );

	if( spans != nil )
	{
		if( GetNoSpanReSort() )
		{
			spans->SetNativeProperty(plDrawable::kPropNoReSort, true);
		}
		return spans;
	}


	/// Couldn't find--create and return it
	spans = TRACKED_NEW plDrawableSpans;
	if( needBlending )
	{
		/// Blending (deferred) spans
		spans->SetCriteria( crit );
		sprintf( tmpName, "%s_%8.8x_%xBlendSpans", node->GetKeyName(), crit.fLevel.fLevel, crit.fCriteria);
	}
	else
	{
		/// Normal spans
		spans->SetCriteria( crit );
		sprintf( tmpName, "%s_%8.8x_%xSpans", node->GetKeyName(), crit.fLevel.fLevel, crit.fCriteria);
	}

	if (GetSwappableGeomTarget() != (UInt32)-1 || GetSwappableGeom()) // We intend to swap geometry with this node... flag the drawable as volatile
	{
		if( GetItinerant() )
			spans->SetNativeProperty(plDrawable::kPropCharacter, true);

		spans->SetNativeProperty( plDrawable::kPropVolatile, true );
	}

	// Add a key for the spans
	plKey key = hsgResMgr::ResMgr()->NewKey( tmpName, spans, nodeLoc, GetLoadMask() );

	spans->SetSceneNode(node->GetKey());

	/// Created! Return it now...
	if( GetNoSpanReSort() )
		spans->SetNativeProperty(plDrawable::kPropNoReSort, true);

	return spans;
}

hsBool plMaxNode::SetupPropertiesPass(plErrorMsg *pErrMsg, plConvertSettings *settings)
{
	// TEMP
	if (IsComponent())
		return false;
	// End TEMP

	hsBool ret = true;
	
		UInt32 count = NumAttachedComponents();

	// Go through all the components attached to this node
	for (UInt32 i = 0; i < count; i++)
	{
		// For each one, call the requested function.  If any of the attached components
		// return false this function will return false.
		plComponentBase *comp = GetAttachedComponent(i);

		if (comp->IsExternal())
		{
			if (!((plComponentExt*)comp)->SetupProperties(this, &gComponentTools, pErrMsg))
				ret = false;
		}
		else
		{
			if (!((plComponent*)comp)->SetupProperties(this, pErrMsg))
				ret = false;
		}
	}

	if( ret )
	{
		// Now loop through all the plPassMtlBase-derived materials that are applied to this node
		Mtl *mtl = GetMtl();
		if( mtl != nil && !GetParticleRelated() )
		{
			if( hsMaterialConverter::IsMultiMat( mtl ) || hsMaterialConverter::IsMultipassMat( mtl ) || hsMaterialConverter::IsCompositeMat( mtl ) )
			{
				int i;
				for (i = 0; i < mtl->NumSubMtls(); i++)
				{
					plPassMtlBase *pass = plPassMtlBase::ConvertToPassMtl( mtl->GetSubMtl( i ) );
					if( pass != nil )
					{
						if( !pass->SetupProperties( this, pErrMsg ) )
							ret = false;
					}
				}
			}
			else
			{
				plPassMtlBase *pass = plPassMtlBase::ConvertToPassMtl( mtl );
				if( pass != nil )
				{
					if( !pass->SetupProperties( this, pErrMsg ) )
						ret = false;
				}
			}
		}
	}
	if( ret )
	{
		plMaxNode* parent = (plMaxNode*)GetParentNode();
		if( parent && IsLegalDecal(false) )
		{
			AddRenderDependency(parent);
			SetNoSpanSort(true);
			SetNoFaceSort(true);
			SetNoDeferDraw(true);
		}
	}

	return ret;
}

hsBool plMaxNode::FirstComponentPass(plErrorMsg *pErrMsg, plConvertSettings *settings)
{
	// TEMP
	if (IsComponent())
		return false;
	// End TEMP

	hsBool ret = true;
	
	if (!CanConvert())
		return ret;
	UInt32 count = NumAttachedComponents();

	// Go through all the components attached to this node
	for (UInt32 i = 0; i < count; i++)
	{
		// For each one, call the requested function.  If any of the attached components
		// return false this function will return false.
		plComponentBase *comp = GetAttachedComponent(i);

		if (comp->IsExternal())
		{
			if (!((plComponentExt*)comp)->PreConvert(this, &gComponentTools, pErrMsg))
				ret = false;
		}
		else
		{
			if (!((plComponent*)comp)->PreConvert(this, pErrMsg))
				ret = false;
		}
	}

	return ret;
}

hsBool plMaxNode::ConvertComponents(plErrorMsg *pErrMsg, plConvertSettings *settings)
{
	// TEMP
	if (IsComponent())
		return false;
	// End TEMP

	hsBool ret = true;

	char *dbgNodeName = GetName();
	if (!CanConvert())
		return ret;

	UInt32 count = NumAttachedComponents();

	// Go through all the components attached to this node
	for (UInt32 i = 0; i < count; i++)
	{
		// For each one, call the requested function.  If any of the attached components
		// return false this function will return false.
		plComponentBase *comp = GetAttachedComponent(i);

		if (comp->IsExternal())
		{
			if (!((plComponentExt*)comp)->Convert(this, &gComponentTools, pErrMsg))
				ret = false;
		}
		else
		{
			if (!((plComponent*)comp)->Convert(this, pErrMsg))
				ret = false;
		}
	}

	return ret;
}

hsBool plMaxNode::DeInitComponents(plErrorMsg *pErrMsg, plConvertSettings *settings)
{
	// TEMP
	if (IsComponent())
		return false;
	// End TEMP

	hsBool ret = true;

	char *dbgNodeName = GetName();
	if (!CanConvert())
		return ret;

	UInt32 count = NumAttachedComponents();

	// Go through all the components attached to this node
	for (UInt32 i = 0; i < count; i++)
	{
		// For each one, call the requested function.  If any of the attached components
		// return false this function will return false.
		plComponentBase *comp = GetAttachedComponent(i);

		if (comp->IsExternal())
		{
			if (!((plComponentExt*)comp)->DeInit(this, &gComponentTools, pErrMsg))
				ret = false;
		}
		else
		{
			if (!((plComponent*)comp)->DeInit(this, pErrMsg))
				ret = false;
		}
	}

	if( ret )
	{
		// Now loop through all the plPassMtlBase-derived materials that are applied to this node
		// So we can call ConvertDeInit() on them
		Mtl *mtl = GetMtl();
		if( mtl != nil && !GetParticleRelated() )
		{
			if( hsMaterialConverter::IsMultiMat( mtl ) || hsMaterialConverter::IsMultipassMat( mtl ) || hsMaterialConverter::IsCompositeMat( mtl ) )
			{
				int i;
				for (i = 0; i < mtl->NumSubMtls(); i++)
				{
					plPassMtlBase *pass = plPassMtlBase::ConvertToPassMtl( mtl->GetSubMtl( i ) );
					if( pass != nil )
					{
						if( !pass->ConvertDeInit( this, pErrMsg ) )
							ret = false;
					}
				}
			}
			else
			{
				plPassMtlBase *pass = plPassMtlBase::ConvertToPassMtl( mtl );
				if( pass != nil )
				{
					if( !pass->ConvertDeInit( this, pErrMsg ) )
						ret = false;
				}
			}
		}
	}

	return ret;
}

hsBool plMaxNode::ClearData(plErrorMsg *pErrMsg, plConvertSettings *settings)
{
	RemoveAppDataChunk(PLASMA_MAX_CLASSID, GUP_CLASS_ID, kPlasmaAgeChunk);
	RemoveAppDataChunk(PLASMA_MAX_CLASSID, GUP_CLASS_ID, kPlasmaDistChunk);
	RemoveAppDataChunk(PLASMA_MAX_CLASSID, GUP_CLASS_ID, kPlasmaRoomChunk);

	RemoveAppDataChunk(PLASMA_MAX_CLASSID, GUP_CLASS_ID, kPlasmaMaxNodeDataChunk);
//	RemoveAppDataChunk(PLASMA_MAX_CLASSID, GUP_CLASS_ID, kPlasmaSceneViewerChunk);
	RemoveAppDataChunk(PLASMA_MAX_CLASSID, GUP_CLASS_ID, kPlasmaLightChunk);

	return true;
}

// HASAGMOD
// Little special-purpose thing to see if a node has an animation graph modifier on it.
plAGModifier *plMaxNode::HasAGMod()
{
	char *name = GetName();
	if (CanConvert())
	{
		plSceneObject *SO = GetSceneObject();
		int numMods = SO->GetNumModifiers();

		for (int i = 0; i < numMods; i++)
		{
			const plModifier *mod = SO->GetModifier(i);

			if(plAGModifier::ConvertNoRef(mod)) {
				return (plAGModifier *)mod;
			}
		}
	}
	return nil;
}

plAGMasterMod *plMaxNode::GetAGMasterMod()
{
	char *name = GetName();
	if (CanConvert())
	{
		plSceneObject *SO = GetSceneObject();
		int numMods = SO->GetNumModifiers();

		for (int i = 0; i < numMods; i++)
		{
			const plModifier *mod = SO->GetModifier(i);

			if(plAGMasterMod::ConvertNoRef(mod)) {
				return (plAGMasterMod *)mod;
			}
		}
	}
	return nil;
}


// SETUPBONESALIASESRECUR
void plMaxNode::SetupBonesAliasesRecur(const char *rootName)
{
	if(CanConvert()) {
		if (!HasAGMod()) {
			const char *nameToUse;
			
			// parse UserPropsBuf for entire BoneName line
			char localName[256];
			TSTR propsBuf;
			GetUserPropBuffer(propsBuf);
			char* start=strstr(propsBuf, "BoneName=");
			if (!start)
				start=strstr(propsBuf, "bonename=");
			const int len = hsStrlen("BoneName=");
			if(start && UserPropExists("BoneName"))
			{
				start+=len;
				int i=0;
				while(*start != '\n' && *start != '\0' && *start)
				{
					hsAssert(i<256, "localName overflow");
					localName[i++]=*start++;
				}
				localName[i]=0;

				nameToUse = localName;

			}
			else
			{
				const char *nodeName = GetName();
		//		char str[256];
		//		sprintf(str, "Missing 'BoneName=foo' UserProp, on object %s, using node name", nodeName ? nodeName : "?");
		//		hsAssert(false, str);

				nameToUse = nodeName;
			}

		/*	char aliasName[256];
			sprintf(aliasName, "%s_%s", rootName, nameToUse);

			plUoid* uoid = hsgResMgr::ResMgr()->FindAlias(aliasName, plSceneObject::Index());
			if( !uoid )
			{
				plAliasModifier* pAlMod = TRACKED_NEW plAliasModifier;
				pAlMod->SetAlias(aliasName);
				AddModifier(pAlMod);
			}
		*/
			plAGModifier *mod = TRACKED_NEW plAGModifier(nameToUse);
			AddModifier(mod, GetName());
		}
	}

	int j = 0;
	for( j = 0; j < NumberOfChildren(); j++ )
		((plMaxNode*)GetChildNode(j))->SetupBonesAliasesRecur(rootName);
}

void plMaxNode::SetDISceneNodeSpans( plDrawInterface *di, hsBool needBlending )
{
	// This poorly named function is currently only used by ParticleComponent.
	// di->GetNumDrawables() will always be zero. In general, particles only
	// need a blending drawable, which can always be index zero since it's the only
	// one.
	di->SetDrawable( di->GetNumDrawables(),
						IGetSceneNodeSpans(plSceneNode::ConvertNoRef( GetRoomKey()->GetObjectPtr() ), 
									   needBlending));
}

plMaxNode* plMaxNode::GetBonesRoot()
{
	ISkin* skin = FindSkinModifier();
	if( !skin )
		return nil;

	INode* bone = skin->GetBone(0);

	if( !bone )
		return nil;

	while( !bone->GetParentNode()->IsRootNode() )
		bone = bone->GetParentNode();

	plMaxNode* boneRoot = (plMaxNode*)bone;

	if( !(boneRoot && boneRoot->CanConvert()) )
		return nil;

	return boneRoot;
}

void plMaxNode::GetBonesRootsRecur(hsTArray<plMaxNode*>& nodes)
{
	plMaxNode* bRoot = GetBonesRoot();
	if( bRoot )
	{
		int idx = nodes.Find(bRoot);
		if( idx == nodes.kMissingIndex )
			nodes.Append(bRoot);
	}

	int i;
	for( i = 0; i < NumberOfChildren(); i++ )
		((plMaxNode*)GetChildNode(i))->GetBonesRootsRecur(nodes);
}

plSceneObject* plMaxNode::MakeCharacterHierarchy(plErrorMsg *pErrMsg)
{
	plSceneObject* playerRoot = GetSceneObject();
	if( pErrMsg->Set(playerRoot->GetDrawInterface() != nil, GetName(), "Non-helper as player root").CheckAndAsk() )
		return nil;
	const char *playerRootName = GetName();

	hsTArray<plMaxNode*> bonesRoots;
	int i;
	for( i = 0; i < NumberOfChildren(); i++ )
		((plMaxNode*)GetChildNode(i))->GetBonesRootsRecur(bonesRoots);

	if( pErrMsg->Set(bonesRoots.GetCount() > 1, playerRootName, "Found multiple bones hierarchies").CheckAndAsk() )
		return nil;

	if( bonesRoots.GetCount() )
	{
		bonesRoots[0]->SetupBonesAliasesRecur(playerRootName);

		plSceneObject* boneRootObj = bonesRoots[0]->GetSceneObject();

		if( pErrMsg->Set(boneRootObj == nil, playerRootName, "No scene object for the bones root").CheckAndAsk() )
			return nil;

		if( boneRootObj != playerRoot )
			hsMessageBox("This avatar's bone hierarchy does not have the avatar root node linked as a parent. "
						 "This may cause the avatar draw incorrectly.", playerRootName, hsMessageBoxNormal);
	}

	return playerRoot;
}

// Takes all bones found on this node (and any descendents) and sets up a single palette
void plMaxNode::SetupBoneHierarchyPalette(plMaxBoneMap *bones /* = nil */)
{
	const char* dbgNodeName = GetName();

	if( !CanConvert() )
		return;

	if (GetBoneMap())
		return;
	
	if (bones == nil)
	{
		bones = TRACKED_NEW plMaxBoneMap();
		bones->fOwner = this;
	}
	
	if (UserPropExists("Bone"))
		bones->AddBone(this);

	int i;
	for (i = 0; i < NumBones(); i++)
		bones->AddBone(GetBone(i));

	SetBoneMap(bones);

	for (i = 0; i < NumberOfChildren(); i++)
		((plMaxNode*)GetChildNode(i))->SetupBoneHierarchyPalette(bones);

	// If we were the one to start this whole thing, then sort all the bones now that
	// they've been added.
	if (bones->fOwner == this)
		bones->SortBones();
}

hsBool plMaxNode::IsLegalDecal(hsBool checkParent /* = true */)
{
	Mtl *mtl = GetMtl();
	if (mtl == nil || GetParticleRelated())
		return false;
	if (hsMaterialConverter::IsMultiMat(mtl))
	{
		int i;
		for (i = 0; i < mtl->NumSubMtls(); i++)
		{
			if (!hsMaterialConverter::IsDecalMat(mtl->GetSubMtl(i)))
				return false;
		}
	}
	else if (!hsMaterialConverter::IsDecalMat(mtl))
		return false;

	return true;
}

int plMaxNode::NumUVWChannels()
{
	hsBool deleteIt;

	TriObject* triObj = GetTriObject(deleteIt);
	if( triObj )
	{
		Mesh* mesh = &(triObj->mesh);

		// note: There's been a bit of a change with UV accounting. There are two variables, numChannels
		// and numBlendChannels. The first represents texture UV channels MAX gives us, the latter is
		// the number of extra blending channels the current material uses to handle its effects.

		// numMaps includes map #0, which is the vertex colors. So subtract 1 to get the # of uv maps...
		int numChannels = mesh->getNumMaps() - 1;
		if( numChannels > plGeometrySpan::kMaxNumUVChannels )
			numChannels = plGeometrySpan::kMaxNumUVChannels;

		/// Check the mapping channels. See, MAX likes to tell us we have mapping channels
		/// but the actual channel pointer is nil. When MAX tries to render the scene, it says that the
		/// object in question has no UV channel. So apparently we have to check numChannels *and* 
		/// that each mapChannel is non-nil...
		int i;
		for( i = 0; i < numChannels; i++ )
		{
			// i + 1 is exactly what IGenerateUVs uses, so I'm not questioning it...
			if( mesh->mapFaces( i + 1 ) == nil )
			{
				numChannels = i;
				break;
			}
		}
		int numUsed = hsMaterialConverter::MaxUsedUVWSrc(this, GetMtl());
		plLightMapComponent* lmc = GetLightMapComponent();
		if( lmc )
		{
			if( (lmc->GetUVWSrc() < numChannels) && (lmc->GetUVWSrc() >= numUsed) )
				numUsed = lmc->GetUVWSrc() + 1;
		}
		if( numChannels > numUsed )
			numChannels = numUsed;

		if( GetWaterDecEnv() )
			numChannels = 3;

		if( deleteIt )
			triObj->DeleteThis();

		return numChannels;
	}

	return 0;
}

//// IGet/SetCachedAlphaHackValue ////////////////////////////////////////////
//	Pair of functions to handle accessing the TArray cache on plMaxNodeData.
//	See AlphaHackLayersNeeded() for details.

int	plMaxNode::IGetCachedAlphaHackValue( int iSubMtl )
{
	plMaxNodeData *pDat = GetMaxNodeData();
	if( pDat == nil )
		return -1;

	hsTArray<int>	*cache = pDat->GetAlphaHackLayersCache();
	if( cache == nil )
		return -1;

	iSubMtl++;
	if( iSubMtl >= cache->GetCount() )
		return -1;

	return (*cache)[ iSubMtl ];
}

void	plMaxNode::ISetCachedAlphaHackValue( int iSubMtl, int value )
{
	plMaxNodeData *pDat = GetMaxNodeData();
	if( pDat == nil )
		return;

	hsTArray<int>	*cache = pDat->GetAlphaHackLayersCache();
	if( cache == nil )
	{
		cache = TRACKED_NEW hsTArray<int>;
		pDat->SetAlphaHackLayersCache( cache );
	}

	iSubMtl++;

	if( iSubMtl >= cache->GetCount() )
	{
		int i = cache->GetCount();
		cache->ExpandAndZero( iSubMtl + 1 );
		for( ; i < cache->GetCount(); i++ )
			(*cache)[ i ] = -1;
	}

	(*cache)[ iSubMtl ] = value;
}

//// AlphaHackLayersNeeded ///////////////////////////////////////////////////
//	Updated 8.13.02 mcn - Turns out this function is actually very slow, and
//	it also happens to be used a lot in testing instanced objects and whether
//	they really can be instanced or not. Since the return value of this
//	function will be constant after the SetupProperties() pass (and undefined
//	before), we cache the value now after the first time we calculate it.
//	Note: mf said that putting long comments in are good so long as most of
//	them aren't obscenities, so I'm trying to keep the #*$&(*#$ obscenities
//	to a minimum here.

int plMaxNode::AlphaHackLayersNeeded(int iSubMtl)
{
	const char* dbgNodeName = GetName();

	int cached = IGetCachedAlphaHackValue( iSubMtl );
	if( cached != -1 )
		return cached;

	int numVtxOpacChanAvail = VtxAlphaNotAvailable() ? 0 : 1;

	int numVtxOpacChanNeeded = hsMaterialConverter::NumVertexOpacityChannelsRequired(this, iSubMtl);

	cached = numVtxOpacChanNeeded - numVtxOpacChanAvail;
	ISetCachedAlphaHackValue( iSubMtl, cached );
	return cached;
}

// Will our lighting pay attention to vertex alpha values?
hsBool plMaxNode::VtxAlphaNotAvailable()
{
	if( NonVtxPreshaded() || GetParticleRelated())
		return false;

	return true;
}

hsBool plMaxNode::NonVtxPreshaded()
{
	if( GetForceMatShade() )
		return false;

	if( GetAvatarSO() != nil ||
		hsMaterialConverter::Instance().HasMaterialDiffuseOrOpacityAnimation(this) )
		return false;

	if( GetRunTimeLight() && !hsMaterialConverter::Instance().HasEmissiveLayer(this) )
		return true;

	return( GetLightMapComponent() != nil );
}

TriObject* plMaxNode::GetTriObject(hsBool& deleteIt)
{
	// Get da object
	Object *obj = EvalWorldState(TimeValue(0)).obj;
	if( obj == nil )
		return nil;

	if( !obj->CanConvertToType(triObjectClassID) )
		return nil;

	// Convert to triMesh object
	TriObject	*meshObj = (TriObject *)obj->ConvertToType(TimeValue(0), triObjectClassID);
	if( meshObj == nil )
		return nil;

	deleteIt = meshObj != obj;

	return meshObj;
}

//// GetNextSoundIdx /////////////////////////////////////////////////////////
//	Starting at 0, returns an incrementing index for each maxNode. Useful for 
//	assigning indices to sound objects attached to the node.

UInt32	plMaxNode::GetNextSoundIdx( void )
{
	UInt32	idx = GetSoundIdxCounter();
	SetSoundIdxCounter( idx + 1 );
	return idx;
}

//// IsPhysical //////////////////////////////////////////////////////////////
//	Fun temp hack function to tell if a maxNode is physical. Useful after
//	preConvert (checks for a physical on the simInterface)

hsBool	plMaxNode::IsPhysical( void )
{
	if( GetSceneObject() && GetSceneObject()->GetSimulationInterface() && 
		GetSceneObject()->GetSimulationInterface()->GetPhysical() )
		return true;

	return false;
}


plPhysicalProps *plMaxNode::GetPhysicalProps()
{
	plMaxNodeData *pDat = GetMaxNodeData();
	if (pDat)
		return pDat->GetPhysicalProps();

	return nil;
}

//// FindPageKey /////////////////////////////////////////////////////////////
//	Little helper function. Calls FindKey() in the resManager using the location (page) of this node

plKey	plMaxNode::FindPageKey( UInt16 classIdx, const char *name )
{
	return hsgResMgr::ResMgr()->FindKey( plUoid( GetLocation(), classIdx, name ) );
}

char *plMaxNode::GetAgeName()
{
	int i;
	for (i = 0; i < NumAttachedComponents(); i++)
	{
		plComponentBase *comp = GetAttachedComponent(i);
		if (comp->ClassID() == PAGEINFO_CID)
			return ((plPageInfoComponent*)comp)->GetAgeName();
	}
	return nil;
}

// create a list of keys used by the run-time interface for things like
// determining cursor changes, what kind of object this is, etc.
// we're doing this here because multiple logic triggers can be attached to a 
// single object and tracking down all their run-time counterpart objects (who might
// need a message sent to them) is a huge pain and very ugly.  This will capture anything
// important in a single list.

hsBool plMaxNode::MakeIfaceReferences(plErrorMsg *pErrMsg, plConvertSettings *settings)
{
	hsBool ret = true;

	char *dbgNodeName = GetName();
	if (!CanConvert())
		return ret;
	
	UInt32 count = GetSceneObject()->GetNumModifiers();
	hsTArray<plKey> keys;
	// Go through all the modifiers attached to this node's scene object
	// and grab keys for objects who we would need to send interface messages to
	for (UInt32 i = 0; i < count; i++)
	{
		const plModifier* pMod = GetSceneObject()->GetModifier(i);
		// right now all we care about are these, but I guarentee you we will
		// care about more as the interface gets more complex
		const plPickingDetector* pDet = plPickingDetector::ConvertNoRef(pMod);
		const plLogicModifier* pLog = plLogicModifier::ConvertNoRef(pMod);
		if( pDet )
		{
			for (int j = 0; j < pDet->GetNumReceivers(); j++)
				keys.Append(pDet->GetReceiver(j));
		}
		else
		if( pLog )
		{
			keys.Append(pLog->GetKey());
		}
	}
	// if there is anything there, create an 'interface object modifier' which simply stores 
	// the list in a handy form
	if (keys.Count())
	{
		plInterfaceInfoModifier* pMod = TRACKED_NEW plInterfaceInfoModifier;
		
		plKey modifierKey = hsgResMgr::ResMgr()->NewKey(GetName(), pMod, GetLocation(), GetLoadMask());
		hsgResMgr::ResMgr()->AddViaNotify(modifierKey, TRACKED_NEW plObjRefMsg(GetSceneObject()->GetKey(), plRefMsg::kOnCreate, -1, plObjRefMsg::kModifier), plRefFlags::kActiveRef);
		
		for(int i = 0; i < keys.Count(); i++)
			pMod->AddRefdKey(keys[i]);
	}

	return ret;
}