// EMERGENT GAME TECHNOLOGIES PROPRIETARY INFORMATION
//
// This software is supplied under the terms of a license agreement or
// nondisclosure agreement with Emergent Game Technologies and may not
// be copied or disclosed except in accordance with the terms of that
// agreement.
//
//      Copyright (c) 1996-2006 Emergent Game Technologies.
//      All Rights Reserved.
//
// Emergent Game Technologies, Chapel Hill, North Carolina 27517
// http://www.emergent.net


#include "StdAfx.h"
#include "actormanagerForPartition.h"
#include "ResourceManager.h"

/// 조건식이 상수일때 생기는 경고 ex)while(true)
#pragma warning(disable:4127) 

#include "ModelInstance.h"

const cActorManagerForPartition::SequenceID cActorManagerForPartition::ANY_SEQUENCE_ID =
(unsigned int) -1;
const cActorManagerForPartition::SequenceID cActorManagerForPartition::INVALID_SEQUENCE_ID =
(unsigned int) -2;
const float cActorManagerForPartition::INVALID_TIME = -FLT_MAX;

const cActorManagerForPartition::SequenceID cActorManagerForPartition::ANY_EVENT_CODE =
cActorManagerForPartition::ANY_SEQUENCE_ID;
const cActorManagerForPartition::SequenceID cActorManagerForPartition::INVALID_EVENT_CODE =
cActorManagerForPartition::INVALID_SEQUENCE_ID;

NiAllocatorDeclareStatics(cActorManagerForPartition::TimelineData, 256)
//---------------------------------------------------------------------------
cActorManagerForPartition::cActorManagerForPartition(NiKFMTool* pkKFMTool,
													 bool bCumulativeAnimations) : 
m_spMODELTool(pkKFMTool),
m_pkCallbackObject(0),
m_bCumulativeAnimations(bCumulativeAnimations)
{
	Reset();
	m_kCallbacks.SetSize(3);
	m_kCallbacks.SetGrowBy(3);
}
//---------------------------------------------------------------------------
cActorManagerForPartition::~cActorManagerForPartition()
{
	Reset();
	RemoveAllPartsArray();

	m_spBoneLOD = 0;
	m_spManager = 0;

	m_spModelRoot = 0;
}
//---------------------------------------------------------------------------
bool cActorManagerForPartition::AddSequence(SequenceID eSequenceID, NiStream& kStream,
											bool bLoadKFFile)
{
	if (!m_spManager)
	{
		return false;
	}

	// Get sequence information.
	NiKFMTool::Sequence* pkKFMSequence = m_spMODELTool->GetSequence(
		eSequenceID);
	if (!pkKFMSequence)
	{
		return false;
	}

	// Get KF filename.
	NiFixedString kKFFilename = m_spMODELTool->GetFullKFFilename(eSequenceID);
	assert(kKFFilename.Exists());

	// Load file, if specified.
	if (bLoadKFFile)
	{
		bool bSuccess = kStream.Load(kKFFilename);
		if (!bSuccess)
		{
			NILOG(NIMESSAGE_GENERAL_0, "cActorManagerForPartition: Failed to load "
				"KF File: %s\n", (const char*) kKFFilename);
			return false;
		}
	}

	int iAnimIndex = pkKFMSequence->GetAnimIndex();
	NiControllerSequencePtr spSequence =
		NiControllerSequence::CreateSequenceFromFile(kStream, iAnimIndex);
	if (!spSequence)
	{
		NILOG(NIMESSAGE_GENERAL_0, "cActorManagerForPartition: Failed to add "
			"sequence at index %d in %s\n", iAnimIndex, 
			(const char*) kKFFilename);
		return false;
	}

	return ChangeSequence(eSequenceID, spSequence);
}
//---------------------------------------------------------------------------
NiControllerManager* cActorManagerForPartition::FindControllerManager(
	NiAVObject* pkObject)
{
	if( pkObject == 0 )
		return 0;

	NiControllerManager* pkManager = NiGetController(NiControllerManager,
		pkObject);
	if (pkManager)
	{
		return pkManager;
	}

	NiNode* pkNode = NiDynamicCast(NiNode, pkObject);
	if (pkNode)
	{
		for (unsigned int ui = 0; ui < pkNode->GetArrayCount(); ui++)
		{
			NiAVObject* pkChild = pkNode->GetAt(ui);
			if (pkChild)
			{
				pkManager = FindControllerManager(pkChild);
				if (pkManager)
				{
					return pkManager;
				}
			}
		}
	}

	return 0;
}
//---------------------------------------------------------------------------
NiBoneLODController* cActorManagerForPartition::FindBoneLODController(
	NiAVObject* pkObject)
{
	if( pkObject == 0 )
		return 0;

	NiBoneLODController* pkBoneLOD = NiGetController(NiBoneLODController,
		pkObject);
	if (pkBoneLOD)
	{
		return pkBoneLOD;
	}

	NiNode* pkNode = NiDynamicCast(NiNode, pkObject);
	if (pkNode)
	{
		for (unsigned int ui = 0; ui < pkNode->GetArrayCount(); ui++)
		{
			NiAVObject* pkChild = pkNode->GetAt(ui);
			if (pkChild)
			{
				pkBoneLOD = FindBoneLODController(pkChild);
				if (pkBoneLOD)
				{
					return pkBoneLOD;
				}
			}
		}
	}

	return 0;
}
//---------------------------------------------------------------------------
NiGeomMorpherController* cActorManagerForPartition::FindMorpherController(NiAVObject* pkObject)
{
	if( pkObject == 0 )
		return 0;

	NiGeomMorpherController* pkMorpher = NiGetController(NiGeomMorpherController, pkObject);
	if (pkMorpher)
		return pkMorpher;

	NiNode* pkNode = NiDynamicCast(NiNode, pkObject);
	if (pkNode)
	{
		for (unsigned int ui = 0; ui < pkNode->GetArrayCount(); ui++)
		{
			NiAVObject* pkChild = pkNode->GetAt(ui);
			if (pkChild)
			{
				pkMorpher = FindMorpherController(pkChild);
				if (pkMorpher)
					return pkMorpher;
			}
		}
	}
	return 0;
}
//---------------------------------------------------------------------------
void cActorManagerForPartition::Update(float fTime)
{
	NIMETRICS_ANIMATION_SCOPETIMER(AM_UPDATE_TIME);

	NiKFMTool::KFM_RC eRC;

	m_fTime = fTime;

	ProcessCallbacks();

	if (m_eTransitionState != NO_TRANSITION)
	{
		if (m_bStartTransition && m_fTime >= m_fTransStartTime)
		{
			if (m_eTransitionState == BLENDING)
			{
				float fDuration = m_fTransEndTime - m_fTransStartTime;
				bool bSuccess = m_spManager->BlendFromSequence(
					m_pkCurSequence, m_pkNextSequence, fDuration,
					m_pcTargetKey);
				assert(bSuccess);bSuccess;
				m_fTransStartTime = m_fTime;
				m_fTransEndTime = m_fTransStartTime + fDuration;
			}
			m_bStartTransition = false;
		}

		if (m_fTime > m_fTransEndTime)
		{
			m_eTransitionState = NO_TRANSITION;

			m_eCurID = m_eNextID;
			m_pkCurSequence = m_pkNextSequence;
			m_eNextID = INVALID_SEQUENCE_ID;
			m_pkNextSequence = 0;
			m_bBuildTimeline = true;
		}

		return;
	}

	if (m_fTime > m_fTimelineRefreshTime)
	{
		m_bBuildTimeline = false;
		BuildTimeline();
	}

	SequenceID eDesiredID;

	// Handle animation chains.
	if (m_bChainActive)
	{
		if (m_fNextChainTime == INVALID_TIME)
		{
			assert(!m_kChainIDs.IsEmpty() && !m_kChainDurations.IsEmpty());
			eDesiredID = m_kChainIDs.RemoveHead();
			float fDuration = m_kChainDurations.RemoveHead();
			if (!m_kChainIDs.IsEmpty())
			{
				if (fDuration == NiKFMTool::MAX_DURATION)
				{
					NiControllerSequence* pkSequence = GetSequence(
						eDesiredID);
					assert(pkSequence);

					// Get the time until the end of the sequence.
					assert(eDesiredID == m_eCurID);
					fDuration = pkSequence->GetTimeAt( NiActorManager::GetDefaultEndTextKey(),
						m_fTime) - m_fTime;

					// This is to compensate for the fact that there
					// is a frame delay. We assume no worse than 30fps.
					fDuration -= 0.0333f;
					if (fDuration < 0.0f)
						fDuration = 0.0f;
				}
				m_fNextChainTime = m_fTime + fDuration;
				return;
			}
			else
			{
				assert(m_kChainDurations.IsEmpty());
				m_fNextChainTime = INVALID_TIME;
				m_bChainActive = false;
			}
		}
		else if (m_fTime > m_fNextChainTime)
		{
			assert(!m_kChainIDs.IsEmpty() && !m_kChainDurations.IsEmpty());
			eDesiredID = m_kChainIDs.GetHead();
			m_fNextChainTime = INVALID_TIME;
		}
		else
		{
			return;
		}
	}
	else
	{
		eDesiredID = m_eTargetID;
	}

	// If desired sequence is already animating, don't do anything.
	if (eDesiredID == m_eCurID)
	{
		if (m_bBuildTimeline)
		{
			m_bBuildTimeline = false;
			BuildTimeline();
		}
		return;
	}

	// If target sequence is invalid, deactivate all sequences.
	if (eDesiredID == INVALID_SEQUENCE_ID)
	{
		RaiseAnimDeactivatedEvents(m_eCurID);

		m_eCurID = INVALID_SEQUENCE_ID;
		if (m_pkCurSequence)
		{
			m_spManager->DeactivateSequence(m_pkCurSequence);
			m_pkCurSequence = 0;
		}
		return;
	}

	// If there is no current sequence, activate the desired one and return.
	if (m_eCurID == INVALID_SEQUENCE_ID)
	{
		NiControllerSequence* pkDesiredSequence = GetSequence(eDesiredID);
		assert(pkDesiredSequence);
		NIVERIFY(m_spManager->ActivateSequence(pkDesiredSequence, 0, true));

		m_eCurID = eDesiredID;
		m_pkCurSequence = pkDesiredSequence;

		RaiseAnimActivatedEvents(m_eCurID);

		m_spManager->Update(m_fTime);
		m_bBuildTimeline = false;
		BuildTimeline();
		ProcessCallbacks();

		return;
	}

	// Determine if transition is allowed.
	bool bTransAllowed;
	eRC = m_spMODELTool->IsTransitionAllowed(m_eCurID, eDesiredID,
		bTransAllowed);
	assert(eRC == NiKFMTool::KFM_SUCCESS);
	if (bTransAllowed)
	{
		// Get desired sequence.
		NiControllerSequence* pkDesiredSequence = GetSequence(eDesiredID);
		assert(pkDesiredSequence);

		// Retrieve the desired transition.
		NiKFMTool::Transition* pkTransition = m_spMODELTool->GetTransition(
			m_eCurID, eDesiredID);
		assert(pkTransition);

		// Store the chain animations.
		if (pkTransition->GetType() == NiKFMTool::TYPE_CHAIN)
		{
			// Assert that a chain is not already active. Nested chains are
			// not supported.
			assert(!m_bChainActive);

			// Assert that this transition does not contain an infinite chain.
			// This check can be expensive, so only do for debug builds.
			assert(m_spMODELTool->IsValidChainTransition(m_eCurID, eDesiredID,
				pkTransition));

			m_kChainIDs.RemoveAll();
			m_kChainDurations.RemoveAll();

			unsigned int uiNumChainAnims =
				pkTransition->GetChainInfo().GetSize();
			assert(uiNumChainAnims > 0);

			for (unsigned int ui = 0; ui < uiNumChainAnims; ui++)
			{
				unsigned int uiChainID = pkTransition->GetChainInfo()
					.GetAt(ui).GetSequenceID();
				float fDuration = pkTransition->GetChainInfo().GetAt(ui)
					.GetDuration();

				m_kChainIDs.AddTail(uiChainID);
				m_kChainDurations.AddTail(fDuration);
			}
			m_kChainIDs.AddTail(eDesiredID);
			m_kChainDurations.AddTail(NiKFMTool::MAX_DURATION);

			eDesiredID = m_kChainIDs.GetHead();
			pkDesiredSequence = GetSequence(eDesiredID);
			assert(pkDesiredSequence);
			pkTransition = m_spMODELTool->GetTransition(m_eCurID, eDesiredID);
			assert(pkTransition);
			m_fNextChainTime = INVALID_TIME;
			m_bChainActive = true;
		}

		// Determine start time of transition.
		m_fTransStartTime = m_fTime;
		m_pcTargetKey = 0;
		if (pkTransition->GetType() == NiKFMTool::TYPE_BLEND)
		{
			if (pkTransition->GetBlendPairs().GetSize() > 0)
			{
				// This update is to ensure that GetTimeAt returns an
				// accurate time.
				m_pkCurSequence->Update(m_fTime, false);

				m_fTransStartTime = NI_INFINITY;
				for (unsigned int ui = 0;
					ui < pkTransition->GetBlendPairs().GetSize(); ui++)
				{
					NiKFMTool::Transition::BlendPair* pkPair =
						pkTransition->GetBlendPairs().GetAt(ui);

					if (!pkPair->GetStartKey())
					{
						m_fTransStartTime = m_fTime;
						m_pcTargetKey = pkPair->GetTargetKey();
						break;
					}

					float fTempTime = m_pkCurSequence->GetTimeAt(
						pkPair->GetStartKey(), m_fTime);
					if (fTempTime != NiControllerSequence::INVALID_TIME &&
						fTempTime >= m_fTime &&
						fTempTime < m_fTransStartTime)
					{
						m_fTransStartTime = fTempTime;
						m_pcTargetKey = pkPair->GetTargetKey();
					}
				}
			}
		}

		// Determine end time of transition.
		assert(pkTransition->GetDuration() > 0.0f);
		m_fTransEndTime = m_fTransStartTime + pkTransition->GetDuration();

		// Update the destination sequence to ensure that any sequences that
		// are easing out are given a chance to complete.
		pkDesiredSequence->Update(m_fTime, false);

		switch (pkTransition->GetType())
		{
		case NiKFMTool::TYPE_BLEND:
			m_fTransStartFrameTime = m_pkCurSequence->GetLastScaledTime();
			if (m_fTransStartTime == m_fTime)
			{
				/// 추가된 코드..
				if(pkDesiredSequence->GetState() == NiControllerSequence::TRANSSOURCE )
					pkDesiredSequence->Deactivate(0.0f, false);

				NIVERIFY(m_spManager->BlendFromSequence(
					m_pkCurSequence, pkDesiredSequence,
					pkTransition->GetDuration(), m_pcTargetKey));
			}
			else
			{
				m_fTransStartFrameTime += m_pkCurSequence
					->ComputeScaledTime(m_fTransStartTime - m_fTime,
					false);
				m_bStartTransition = true;
			}
			m_eTransitionState = BLENDING;
			break;
		case NiKFMTool::TYPE_MORPH:
			{
				/// 추가된 코드..
				if(pkDesiredSequence->GetState() == NiControllerSequence::TRANSSOURCE )
					pkDesiredSequence->Deactivate(0.0f, false);

				m_fTransStartFrameTime = m_pkCurSequence->GetLastScaledTime();
				NIVERIFY(m_spManager->Morph(m_pkCurSequence,
					pkDesiredSequence, pkTransition->GetDuration()));
				m_eTransitionState = MORPHING;
				break;
			}
		case NiKFMTool::TYPE_CROSSFADE:
			{
				/// 추가된 코드..
				if(pkDesiredSequence->GetState() == NiControllerSequence::TRANSSOURCE )
					pkDesiredSequence->Deactivate(0.0f, false);

				m_fTransStartFrameTime = m_pkCurSequence->GetLastScaledTime();
				NIVERIFY(m_spManager->CrossFade(m_pkCurSequence,
					pkDesiredSequence, pkTransition->GetDuration(), 0, true));
				m_eTransitionState = CROSSFADING;
				break;
			}
		default:
			assert(false);
			break;
		}

		m_eNextID = eDesiredID;
		m_pkNextSequence = pkDesiredSequence;
		m_bBuildTimeline = true;
	}

	if (m_bBuildTimeline)
	{
		m_bBuildTimeline = false;
		BuildTimeline();
	}
}
//---------------------------------------------------------------------------
cActorManagerForPartition::SequenceID cActorManagerForPartition::FindSequenceID(
	const char* pcName) const
{
	// pcName must be non-null
	assert(pcName);

	NiTMapIterator kItr = m_kSequenceMap.GetFirstPos();
	while (kItr)
	{
		SequenceID eID;
		NiControllerSequence* pkCSeq;
		m_kSequenceMap.GetNext(kItr, eID, pkCSeq);
		if (pkCSeq->GetName() && !strcmp(pcName, pkCSeq->GetName()))
		{
			// found the first one, return it
			return eID;
		}
	}

	// didn't find any with that name
	return INVALID_SEQUENCE_ID;
}
//---------------------------------------------------------------------------
bool cActorManagerForPartition::RegisterCallback(EventType eEventType,
												 SequenceID eSequenceID, const NiFixedString& kTextKey)
{
	NiTextKeyMatch* pkMatch = 0;

	if (eEventType == TEXT_KEY_EVENT)
	{
		if (kTextKey.Exists())
			pkMatch = NiNew NiTextKeyMatch( kTextKey );
		else
			return false;
	}

	return RegisterCallback(eEventType, eSequenceID, pkMatch);
}
//---------------------------------------------------------------------------
void cActorManagerForPartition::UnregisterCallback(EventType eEventType,
												   SequenceID eSequenceID, const NiFixedString& kTextKey)
{
	for (unsigned int ui = 0; ui < m_kCallbacks.GetSize(); ui++)
	{
		CallbackData* pkData = m_kCallbacks.GetAt(ui);

		if (pkData == 0)
			continue;

		if (pkData->m_eEventType == eEventType &&
			pkData->m_eSequenceID == eSequenceID)
		{
			NiTextKeyMatch* pkCurrMatch = pkData->GetMatchObject();

			// pkCurrMatch will only exist if it is not a text key event,
			// in which case we do not need to check for a match.
			if (pkCurrMatch == 0 || 
				(pkCurrMatch != 0 && pkCurrMatch->IsKeyMatch(kTextKey)))
			{
				RemoveTimelineDataObjects(eEventType, eSequenceID,
					pkCurrMatch);
				m_kCallbacks.RemoveAt(ui);
				break;
			}
		}
	}
}
//---------------------------------------------------------------------------
bool cActorManagerForPartition::RegisterCallback(EventType eEventType,
												 SequenceID eSequenceID, NiTextKeyMatch* pkMatchObject)
{
	if (eEventType == TEXT_KEY_EVENT)
	{
		if (pkMatchObject == 0)
			return false;
	}
	else
	{
		pkMatchObject = 0;
	}

	UnregisterCallback(eEventType, eSequenceID, pkMatchObject);
	m_kCallbacks.AddFirstEmpty(NiNew CallbackData(eEventType,
		eSequenceID, pkMatchObject));
	m_bBuildTimeline = true;

	return true;
}
//---------------------------------------------------------------------------
void cActorManagerForPartition::UnregisterCallback(EventType eEventType,
												   SequenceID eSequenceID, NiTextKeyMatch* pkMatchObject)
{
	if (eEventType != TEXT_KEY_EVENT)
	{
		pkMatchObject = 0;
	}

	for (unsigned int ui = 0; ui < m_kCallbacks.GetSize(); ui++)
	{
		CallbackData* pkData = m_kCallbacks.GetAt(ui);

		if (pkData && pkData->m_eEventType == eEventType &&
			pkData->m_eSequenceID == eSequenceID &&
			pkData->GetMatchObject() == pkMatchObject)
		{
			RemoveTimelineDataObjects(eEventType, eSequenceID, pkMatchObject);
			m_kCallbacks.RemoveAt(ui);
			break;
		}
	}
}
//---------------------------------------------------------------------------
void cActorManagerForPartition::BuildTimeline()
{
	assert(m_fTime != INVALID_TIME);

	RemoveInactiveExtraSequences();

	m_kTimeline.RemoveAll();

	// Find next event time for each registered callback and add to timeline.
	for (unsigned int ui = 0; ui < m_kCallbacks.GetSize(); ui++)
	{
		CallbackData* pkCallbackData = m_kCallbacks.GetAt(ui);
		if (pkCallbackData)
		{
			SequenceID eSequenceID = pkCallbackData->m_eSequenceID;
			if (eSequenceID == ANY_SEQUENCE_ID)
			{
				NiTMapIterator spos = m_kSequenceMap.GetFirstPos();
				while (spos)
				{
					NiControllerSequence* pkSequence;
					m_kSequenceMap.GetNext(spos, eSequenceID, pkSequence);

					AddTimelineDataObject(pkCallbackData->m_eEventType,
						eSequenceID, pkCallbackData->GetMatchObject());
				}
			}
			else
			{
				AddTimelineDataObject(pkCallbackData->m_eEventType,
					eSequenceID, pkCallbackData->GetMatchObject());
			}

		}
	}

	NiTListIterator kIter = m_kTimeline.GetTailPos();
	if (kIter)
	{
		m_fTimelineRefreshTime = m_kTimeline.GetTail()->m_fEventTime;
	}
	else
	{
		m_fTimelineRefreshTime = NI_INFINITY;
	}
}
//---------------------------------------------------------------------------
void cActorManagerForPartition::AddTimelineDataObject(EventType eEventType,
													  SequenceID eSequenceID, NiTextKeyMatch* pkMatchObject )
{
	float fEventTime = 
		GetNextEventTime(eEventType, eSequenceID, pkMatchObject);

	if (fEventTime != INVALID_TIME)
	{
		bool bInsertedObject = false;
		TimelineData* pkNewTimelineData = 0;
		NiTListIterator kIter = m_kTimeline.GetHeadPos();
		while (kIter)
		{
			TimelineData* pkOldTimelineData = m_kTimeline.Get(kIter);

			if (pkOldTimelineData->m_fEventTime > fEventTime)
			{
				pkNewTimelineData = 
					m_kTimeline.InsertNewBefore(kIter);

				bInsertedObject = true;
				break;
			}
			kIter = m_kTimeline.GetNextPos(kIter);
		}
		if (!bInsertedObject)
		{
			pkNewTimelineData = m_kTimeline.AddNewTail();
		}

		const char* pcTextKeyMatched = 0;

		if (pkMatchObject)
		{
			pcTextKeyMatched = pkMatchObject->GetLastMatchedKey();
		}

		pkNewTimelineData->SetValues(fEventTime, eEventType, eSequenceID,
			pkMatchObject, pcTextKeyMatched);
	}
}
//---------------------------------------------------------------------------
void cActorManagerForPartition::RemoveTimelineDataObjects(EventType eEventType,
														  SequenceID eSequenceID, NiTextKeyMatch* pkMatchObject)
{
	if (eEventType != TEXT_KEY_EVENT)
	{
		pkMatchObject = 0;
	}

	NiTListIterator kIter = m_kTimeline.GetHeadPos();
	while (kIter)
	{
		NiTListIterator kOldIter = kIter;
		TimelineData* pkData = m_kTimeline.GetNext(kIter);
		if (pkData->m_eEventType == eEventType &&
			(pkData->m_eSequenceID == eSequenceID ||
			eSequenceID == ANY_SEQUENCE_ID) &&
			(!pkMatchObject || pkData->m_spMatchObject == pkMatchObject))
		{
			m_kTimeline.RemovePos(kOldIter);
		}
	}
}
//---------------------------------------------------------------------------
void cActorManagerForPartition::ProcessCallbacks()
{
	if (!m_pkCallbackObject || m_kTimeline.IsEmpty())
	{
		return;
	}

	bool bMadeCallback = false;
	while (!m_kTimeline.IsEmpty())
	{
		TimelineData* pkTimelineData = m_kTimeline.GetHead();
		NiTListIterator posHead = m_kTimeline.GetHeadPos();
		if (m_fTime > pkTimelineData->m_fEventTime)
		{
			switch (pkTimelineData->m_eEventType)
			{
			case ANIM_ACTIVATED:
				m_pkCallbackObject->AnimActivated(this,
					pkTimelineData->m_eSequenceID, m_fTime,
					pkTimelineData->m_fEventTime);
				break;
			case ANIM_DEACTIVATED:
				m_pkCallbackObject->AnimDeactivated(this,
					pkTimelineData->m_eSequenceID, m_fTime,
					pkTimelineData->m_fEventTime);
				break;
			case TEXT_KEY_EVENT:
				m_pkCallbackObject->TextKeyEvent(this,
					pkTimelineData->m_eSequenceID,
					pkTimelineData->m_kTextKeyMatched,
					pkTimelineData->m_spMatchObject,
					m_fTime, pkTimelineData->m_fEventTime);
				break;
			case END_OF_SEQUENCE:
				m_pkCallbackObject->EndOfSequence(this,
					pkTimelineData->m_eSequenceID, m_fTime,
					pkTimelineData->m_fEventTime);
				break;
			}
			// The head position is checked here because one of the callback
			// functions could have called UnregisterCallback, which would
			// result in items being removed from m_kTimeline. If the head
			// item has already been removed, removing the head again could
			// result in missed callbacks. Thus, we check here that the head
			// of the list is still what is was earlier before removing it.
			if (posHead == m_kTimeline.GetHeadPos())
			{
				m_kTimeline.RemoveHead();
			}
			bMadeCallback = true;
		}
		else
		{
			break;
		}
	}

	if (bMadeCallback)
	{
		m_bBuildTimeline = true;
	}
}
//---------------------------------------------------------------------------
bool cActorManagerForPartition::ActivateSequence(SequenceID eSequenceID, int iPriority,
												 bool bStartOver, float fWeight, float fEaseInTime,
												 SequenceID eTimeSyncSeqID)
{
	NiControllerSequence* pkSequence = GetSequence(eSequenceID);
	if (!pkSequence)
	{
		return false;
	}

	NiControllerSequence* pkTimeSyncSeq = 0;
	if (eTimeSyncSeqID != NiKFMTool::SYNC_SEQUENCE_ID_NONE)
	{
		pkTimeSyncSeq = GetSequence(eTimeSyncSeqID);
		if (!pkTimeSyncSeq)
		{
			return false;
		}
	}

	// Update the sequence without updating interpolators to give it a chance
	// to finish easing out, if necessary. This will prevent failure to
	// activate due to the fact that the sequence will not actually be fully
	// inactive until the next Update.
	if (m_fTime != INVALID_TIME)
	{
		pkSequence->Update(m_fTime, false);
	}

	bool bSuccess = m_spManager->ActivateSequence(pkSequence, iPriority,
		bStartOver, fWeight, fEaseInTime, pkTimeSyncSeq);
	if (bSuccess)
	{
		m_bBuildTimeline = true;

		// Update the sequence here to activate it immediately. This is
		// necessary in order to catch events because the timeline is built
		// before the sequence will be next updated.
		if (m_fTime != INVALID_TIME)
		{
			pkSequence->Update(m_fTime, false);
		}

		// We need to use AddUnique here instead of Add because it is possible
		// that the sequence ID already exists in the set (if the sequence
		// was deactivated but not yet removed from the set).
		m_kExtraSequences.AddUnique(eSequenceID);

		if (fEaseInTime == 0.0f)
		{
			// If the ease-in time is 0.0, we need to immediately raise any
			// AnimActivated events for the sequence.
			RaiseAnimActivatedEvents(eSequenceID);
		}
	}

	return bSuccess;
}
//---------------------------------------------------------------------------
bool cActorManagerForPartition::DeactivateSequence(SequenceID eSequenceID,
												   float fEaseOutTime)
{
	NiControllerSequence* pkSequence = GetSequence(eSequenceID);
	if (!pkSequence)
	{
		return false;
	}

	if (pkSequence->GetState() == NiControllerSequence::EASEOUT ||
		pkSequence->GetState() == NiControllerSequence::EASEIN)
	{
		return false;
	}

	// Update the sequence without updating interpolators to give it a chance
	// to finish easing in, if necessary. This will prevent failure to
	// deactivate due to the fact that the sequence will not actually be fully
	// active until the next Update.
	if (m_fTime != INVALID_TIME)
	{
		pkSequence->Update(m_fTime, false);
	}

	bool bSuccess = m_spManager->DeactivateSequence(pkSequence, 
		fEaseOutTime);
	if (bSuccess)
	{
		m_bBuildTimeline = true;

		// Update the sequence here to deactivate it immediately. This is
		// necessary in order to catch events because the timeline is built
		// before the sequence will be next updated.
		if (m_fTime != INVALID_TIME)
		{
			pkSequence->Update(m_fTime, false);
		}

		if (fEaseOutTime == 0.0f)
		{
			// If the ease-out time is 0.0, we can immediately remove the
			// sequence from the extra sequences set.
			int iIndex = m_kExtraSequences.Find(eSequenceID);
			assert(iIndex != -1);
			if (iIndex != -1)
			{
				m_kExtraSequences.RemoveAt(iIndex);
			}

			// If the ease-out time is 0.0, we need to immediately raise any
			// AnimDeactivated events for the sequence.
			RaiseAnimDeactivatedEvents(eSequenceID);
		}
	}

	return bSuccess;
}
//---------------------------------------------------------------------------
NiKFMTool::KFM_RC cActorManagerForPartition::ChangeSequenceID(SequenceID eOldID,
															  SequenceID eNewID)
{
	// Update MODEL tool.
	NiKFMTool::KFM_RC eRC = m_spMODELTool->UpdateSequenceID(eOldID, eNewID);

	// Update stored IDs.
	if (m_eTargetID == eOldID)
	{
		m_eTargetID = eNewID;
	}
	if (m_eCurID == eOldID)
	{
		m_eCurID = eNewID;
	}
	if (m_eNextID == eOldID)
	{
		m_eNextID = eNewID;
	}

	// Update chain IDs.
	NiTListIterator pos_chain = m_kChainIDs.GetHeadPos();
	while (pos_chain)
	{
		unsigned int uiSequenceID = m_kChainIDs.Get(pos_chain);
		if (uiSequenceID == eOldID)
		{
			NiTListIterator pos_current = m_kChainIDs.InsertAfter(pos_chain,
				eNewID);
			m_kChainIDs.RemovePos(pos_chain);
			pos_chain = pos_current;
		}
		pos_chain = m_kChainIDs.GetNextPos(pos_chain);
	}

	// Update sequence map.
	NiTMapIterator pos_seq = m_kSequenceMap.GetFirstPos();
	while (pos_seq)
	{
		SequenceID eSequenceID;
		NiControllerSequence* pkSequence;
		m_kSequenceMap.GetNext(pos_seq, eSequenceID, pkSequence);
		if (eSequenceID == eOldID)
		{
			m_kSequenceMap.RemoveAt(eSequenceID);
			m_kSequenceMap.SetAt(eNewID, pkSequence);
		}
	}

	// Update callback data.
	unsigned int ui;
	for (ui = 0; ui < m_kCallbacks.GetSize(); ui++)
	{
		CallbackData* pkCallbackData = m_kCallbacks.GetAt(ui);
		if (pkCallbackData && pkCallbackData->m_eSequenceID == eOldID)
		{
			pkCallbackData->m_eSequenceID = eNewID;
		}
	}

	// Update timeline data.
	while (!m_kTimeline.IsEmpty())
	{
		TimelineData* pkTimelineData = m_kTimeline.GetHead();
		if (pkTimelineData->m_eSequenceID == eOldID)
		{
			pkTimelineData->m_eSequenceID = eNewID;
		}
	}

	// Update extra sequences.
	for (ui = 0; ui < m_kExtraSequences.GetSize(); ui++)
	{
		if (m_kExtraSequences.GetAt(ui) == eOldID)
		{
			m_kExtraSequences.ReplaceAt(ui, eNewID);
		}
	}

	return eRC;
}
//---------------------------------------------------------------------------
bool cActorManagerForPartition::ChangeSequence(SequenceID eSequenceID,
											   NiControllerSequence* pkSequence)
{
	// Remove old sequence mapped to this ID, if it exists.
	UnloadSequence(eSequenceID);

	bool bSuccess = m_spManager->AddSequence(pkSequence);
	if (bSuccess)	
	{
		m_kSequenceMap.SetAt(eSequenceID, pkSequence);
	}

	return bSuccess;
}
//---------------------------------------------------------------------------
void cActorManagerForPartition::UnloadSequence(SequenceID eSequenceID)
{
	assert(m_spManager);

	NiControllerSequence* pkSequence = GetSequence(eSequenceID);
	if (!pkSequence)
	{
		return;
	}

	int iIndex = m_kExtraSequences.Find(eSequenceID);

	if (eSequenceID == m_eCurID || eSequenceID == m_eNextID ||
		eSequenceID == m_eTargetID || iIndex > -1)
	{
		Reset();
	}

	m_spManager->RemoveSequence(pkSequence);
	m_kSequenceMap.RemoveAt(eSequenceID);

	// m_kExtraSequences is emptied by Reset, so the sequence need not be
	// removed from it here.
}
//---------------------------------------------------------------------------
cActorManagerForPartition::CompletionInfoPtr
cActorManagerForPartition::FindTimeForAnimationToCompleteTransition(
	unsigned int uiTransSrcID, unsigned int uiTransDesID,
	NiKFMTool::Transition* pkTransition, float fBeginFrame,
	float fDesiredTransitionFrame)
{
	CompletionInfoPtr spCompletionInfo = NiNew CompletionInfo();

	assert(pkTransition);
	NiControllerSequence* pkSrcSequence = GetSequence(uiTransSrcID);
	NiControllerSequence* pkDesSequence = GetSequence(uiTransDesID);
	assert(pkSrcSequence && pkDesSequence);

	assert(fDesiredTransitionFrame >= 0.0);
	assert(fDesiredTransitionFrame >= fBeginFrame);

	switch (pkTransition->GetType())
	{
	case NiKFMTool::TYPE_BLEND:
		{
			bool bImmediateBlend;
			if (pkTransition->GetBlendPairs().GetSize() == 0)
			{
				bImmediateBlend = true;
			}
			else if (pkTransition->GetBlendPairs().GetSize() == 1 &&
				!pkTransition->GetBlendPairs().GetAt(0)->
				GetStartKey().Exists())
			{
				bImmediateBlend = true;
			}
			else
			{
				bImmediateBlend = false;
			}

			if (bImmediateBlend)
			{
				// Handle immediate blend.
				float fBeginToTransitionFrameTime = 
					fDesiredTransitionFrame - fBeginFrame;

				const char* pcImmediateOffsetTextKey = "start";
				if (pkTransition->GetBlendPairs().GetSize() == 1)
				{
					pcImmediateOffsetTextKey = pkTransition->GetBlendPairs()
						.GetAt(0)->GetTargetKey();
				}

				spCompletionInfo->m_fFrameInDestWhenTransitionCompletes =
					pkDesSequence->GetKeyTimeAt(pcImmediateOffsetTextKey);

				// TimeToCompleteTransition is in RealTime
				spCompletionInfo->m_fTimeToCompleteTransition =
					pkSrcSequence->TimeDivFreq(fBeginToTransitionFrameTime) + 
					pkTransition->GetDuration();

				spCompletionInfo->m_fFrameTransitionOccursInSrc =
					fDesiredTransitionFrame;
			}
			else
			{
				// Handle delayed blend.

				// Given the time and minimal step time, determine what the
				// next blend pair will be and the time it'll take to reach
				// it.
				float fActualTransitionUnboundedFrame = 0;
				NiKFMTool::Transition::BlendPair* pkBlendPair =
					GetNextBlendPair(uiTransSrcID, uiTransDesID,
					pkTransition, fDesiredTransitionFrame,
					fActualTransitionUnboundedFrame);
				spCompletionInfo->m_pkBlendPair = pkBlendPair;

				if (pkBlendPair == 0)
				{
					// This could happen if the transition time was beyond
					// the length of the sequence and the sequence was
					// CLAMPED.

					spCompletionInfo->m_fFrameTransitionOccursInSrc =
						pkSrcSequence->GetEndKeyTime();

					spCompletionInfo->m_fFrameInDestWhenTransitionCompletes = 
						pkDesSequence->GetBeginKeyTime();

					spCompletionInfo->m_fTimeToCompleteTransition =
						pkSrcSequence->TimeDivFreq(
						fActualTransitionUnboundedFrame - fBeginFrame) +
						pkTransition->GetDuration();
				}
				else
				{
					const char* pcStartKey = pkBlendPair->GetStartKey();
					if (pcStartKey == 0)
					{
						pcStartKey = "start";
					}

					float fActualTransitionFrame =
						pkSrcSequence->GetKeyTimeAt(pcStartKey);
					assert(fActualTransitionFrame !=
						NiControllerSequence::INVALID_TIME);

					spCompletionInfo->m_fFrameTransitionOccursInSrc =
						fActualTransitionFrame;

					const char* pcTargetKey = pkBlendPair->GetTargetKey();
					if (pcTargetKey == 0)
					{
						pcTargetKey = "start";
					}
					spCompletionInfo->m_fFrameInDestWhenTransitionCompletes = 
						pkDesSequence->GetKeyTimeAt(pcTargetKey);

					spCompletionInfo->m_fTimeToCompleteTransition =
						pkSrcSequence->TimeDivFreq(
						fActualTransitionUnboundedFrame - fBeginFrame) +
						pkTransition->GetDuration();
				}
			}

			break;
		}
	case NiKFMTool::TYPE_MORPH:
		{
			spCompletionInfo->m_fFrameTransitionOccursInSrc =
				fDesiredTransitionFrame;
			spCompletionInfo->m_fTimeToCompleteTransition =
				pkSrcSequence->TimeDivFreq(fDesiredTransitionFrame -
				fBeginFrame) + pkTransition->GetDuration();

			float fDesiredTransitionTime =
				pkSrcSequence->TimeDivFreq(fDesiredTransitionFrame);

			float fDestTransitionStartFrame = 
				pkDesSequence->FindCorrespondingMorphFrame(
				pkSrcSequence, fDesiredTransitionTime);

			float fPartialDestSeqFrameLength = 
				pkDesSequence->GetEndKeyTime()- fDestTransitionStartFrame;

			float fTranDuration = pkTransition->GetDuration();
			float fTranFrameDuration;

			if (fTranDuration == NiKFMTool::MAX_DURATION)
			{
				// The time of transition duration should never be
				// negative... This code is being pessimistic...
				// we should never hit this block.
				assert(fTranDuration != NiKFMTool::MAX_DURATION);
				fTranFrameDuration = (pkDesSequence->GetEndKeyTime() -
					pkDesSequence->GetBeginKeyTime()) - fBeginFrame;
			}
			else
			{
				fTranFrameDuration = pkDesSequence->TimeMultFreq(
					fTranDuration);
			}


			if (fTranFrameDuration > fPartialDestSeqFrameLength)
			{
				// We have to loop ... not as easy as a cross fade because
				// we can start anywhere in the destination sequence.
				float fDesFrameLength = pkDesSequence->GetEndKeyTime() -
					pkDesSequence->GetBeginKeyTime();

				float fRemainingFrameDuration = fTranFrameDuration -
					fPartialDestSeqFrameLength;

				float fCeiling = 
					ceilf(fRemainingFrameDuration / fDesFrameLength);

				float fTimeOfDestLoops = (fCeiling * fDesFrameLength);

				float fFinal = (fDesFrameLength - fDestTransitionStartFrame) +
					fTimeOfDestLoops;
				spCompletionInfo->m_fFrameInDestWhenTransitionCompletes =
					fDesFrameLength - (fFinal - fTranFrameDuration);
			}
			else
			{
				spCompletionInfo->m_fFrameInDestWhenTransitionCompletes =
					fDestTransitionStartFrame + fTranFrameDuration;
			}

			break;
		}
	case NiKFMTool::TYPE_CROSSFADE:
		{
			spCompletionInfo->m_fFrameTransitionOccursInSrc =
				fDesiredTransitionFrame;

			float fTranDuration = pkTransition->GetDuration();
			spCompletionInfo->m_fTimeToCompleteTransition =
				pkSrcSequence->TimeDivFreq(fDesiredTransitionFrame -
				fBeginFrame) + fTranDuration;

			// Assume that we always start cross fade at start of destination.
			// If a cross fade has a duration that is longer than the
			// destination sequence, then the destination sequence should loop
			// and our time to start in the destination will depend on where
			// the duration ends within the loop...
			float fDesFrameLength = pkDesSequence->GetEndKeyTime() -
				pkDesSequence->GetBeginKeyTime();
			float fTranFrameDuration = pkDesSequence->TimeMultFreq(
				fTranDuration);

			if (fTranFrameDuration > fDesFrameLength)
			{
				if (pkDesSequence->GetCycleType() == NiTimeController::CLAMP)
				{
					spCompletionInfo->m_fFrameInDestWhenTransitionCompletes = 
						fDesFrameLength;
				}
				else
				{
					float fCeiling = ceilf(fTranFrameDuration /
						fDesFrameLength);
					float fTimeOfDestLoops = (fCeiling * fDesFrameLength);

					// Note that this is an unbounded frame time
					spCompletionInfo->m_fFrameInDestWhenTransitionCompletes = 
						fDesFrameLength - (fTimeOfDestLoops -
						fTranFrameDuration);
				}
			}
			else
			{
				spCompletionInfo->m_fFrameInDestWhenTransitionCompletes =
					fTranFrameDuration;
			}

			break;
		}
	case NiKFMTool::TYPE_CHAIN:
		{
			float fTotalTime = 0.0f;

			// Ensure that Chains exist
			unsigned int uiChainInfoSize = 
				pkTransition->GetChainInfo().GetSize();

			assert(uiChainInfoSize > 0);

			// Get first chain and its transition information
			NiControllerSequence* pkSrcSeq = pkSrcSequence;
			unsigned int uiSrcID = uiTransSrcID;
			NiKFMTool::Transition::ChainInfo* pkChainInfo;

			// Find completion info of first transition of chain
			float fSrcBeginFrame = pkSrcSeq->GetBeginKeyTime();
			ChainCompletionInfo* pkCCI;
			float fFrameDuration = fDesiredTransitionFrame;

			unsigned int uiIndex;
			for(uiIndex = 0; uiIndex < uiChainInfoSize; uiIndex++)
			{
				pkChainInfo = &pkTransition->GetChainInfo().GetAt(uiIndex);
				unsigned int uiDesID = pkChainInfo->GetSequenceID();
				NiControllerSequence* pkDesSeq = GetSequence(uiDesID);

				pkCCI =
					FillChainComplete(uiSrcID, uiDesID, fFrameDuration,
					fSrcBeginFrame, fTotalTime);

				spCompletionInfo->m_kChainCompletionInfoSet.Add(pkCCI);

				// now make this chain the source...
				pkSrcSeq = pkDesSeq;
				uiSrcID = uiDesID;

				// Place Duration in unbounded frame time (a bit weird)
				fFrameDuration = pkChainInfo->GetDuration();
				if (fFrameDuration == NiKFMTool::MAX_DURATION)
				{
					fFrameDuration = (pkSrcSeq->GetEndKeyTime() -
						pkSrcSeq->GetBeginKeyTime()) - fSrcBeginFrame;
				}
				else
				{
					// scale duration so that it is in frame time
					fFrameDuration = pkDesSeq->TimeMultFreq(
						fFrameDuration);
				}
			}

			pkCCI = FillChainComplete(uiSrcID, uiTransDesID,
				fFrameDuration, fSrcBeginFrame, fTotalTime);
			spCompletionInfo->m_kChainCompletionInfoSet.Add(pkCCI);

			pkSrcSeq = GetSequence(uiSrcID);
			fTotalTime += 
				pkDesSequence->GetEndKeyTimeDivFreq() - 
				pkSrcSeq->TimeDivFreq(fSrcBeginFrame);
			spCompletionInfo->m_fTimeForChainToComplete = fTotalTime;
			break;
		}
	default:
		assert(false);
		spCompletionInfo = 0;
		break;
	}

	return spCompletionInfo;
}
//---------------------------------------------------------------------------
float cActorManagerForPartition::GetNextAnimActivatedTime(SequenceID eSequenceID)
{
	// This function returns the next time that the indicated animation will
	// be fully activated (i.e., not being blended or morphed). If the
	// animation is already active or the next activation time cannot be
	// determined, INVALID_TIME is returned.

	assert(m_fTime != INVALID_TIME && eSequenceID != INVALID_SEQUENCE_ID);

	// Search for sequence in the extra sequences.
	int iIndex = m_kExtraSequences.Find(eSequenceID);
	if (iIndex != -1)
	{
		NiControllerSequence* pkExtraSeq = GetSequence(eSequenceID);

		// If sequence is inactive, remove it.
		if (pkExtraSeq->GetState() == NiControllerSequence::INACTIVE)
		{
			m_kExtraSequences.RemoveAt(iIndex);
			return INVALID_TIME;
		}

		// If sequence is easing in, return the time when the ease-in will
		// be complete.
		if (pkExtraSeq->GetState() == NiControllerSequence::EASEIN)
		{
			float fEndTime = pkExtraSeq->GetEaseEndTime();
			if (fEndTime > m_fTime)
			{
				return fEndTime;
			}
			else
			{
				return INVALID_TIME;
			}
		}

		// Extra sequence is already activated and will not be activated
		// again in the foreseeable future.
		return INVALID_TIME;
	}

	if (eSequenceID == m_eCurID)
	{
		return INVALID_TIME;
	}

	if (eSequenceID == m_eNextID)
	{
		return m_fTransEndTime;
	}

	if (m_eNextID == INVALID_SEQUENCE_ID ||
		!m_kChainIDs.FindPos(eSequenceID))
	{
		return INVALID_TIME;
	}

	float fAnimActivatedTime = m_fTransEndTime;
	NiKFMTool::Transition* pkTransition = m_spMODELTool->GetTransition(m_eCurID,
		m_eNextID);
	assert(pkTransition && pkTransition->GetType() != NiKFMTool::TYPE_CHAIN);
	float fTransStartFrameTime = m_fTransStartFrameTime;
	if (fTransStartFrameTime == INVALID_TIME)
	{
		fTransStartFrameTime = 0.0f;
	}
	CompletionInfoPtr spInfo = FindTimeForAnimationToCompleteTransition(
		m_eCurID, m_eNextID, pkTransition, fTransStartFrameTime,
		fTransStartFrameTime);
	assert(spInfo);

	SequenceID eSrcID = m_eNextID;
	NiControllerSequence* pkSrcSequence = m_pkNextSequence;
	float fStartFrame = spInfo->m_fFrameInDestWhenTransitionCompletes;

	float fTransitionFrame = 0.0f;
	NiTListIterator posID = m_kChainIDs.GetHeadPos();
	NiTListIterator posDuration = m_kChainDurations.GetHeadPos();
	if (!m_kChainDurations.IsEmpty())
	{
		float fDuration = m_kChainDurations.GetHead();
		if (fDuration == NiKFMTool::MAX_DURATION)
		{
			fDuration = (m_pkNextSequence->GetEndKeyTime() -
				m_pkNextSequence->GetBeginKeyTime()) - fStartFrame;
		}
		else
		{
			// scale duration so that it is in frame time
			fDuration = m_pkNextSequence->TimeMultFreq(fDuration);
		}

		fTransitionFrame = fStartFrame + fDuration;

		assert(posID && posDuration);
		posID = m_kChainIDs.GetNextPos(posID);
		posDuration = m_kChainDurations.GetNextPos(posDuration);
	}

	while (true)
	{
		if (eSequenceID == eSrcID)
		{
			return fAnimActivatedTime;
		}

		if (!posID)
		{
			// This statement will break out of this loop when there are no
			// more intermediate animations.
			return INVALID_TIME;
		}
		assert(posDuration);

		SequenceID eChainID = m_kChainIDs.GetNext(posID);
		float fDuration = m_kChainDurations.GetNext(posDuration);
		NiControllerSequence* pkChainSeq = GetSequence(eChainID);
		assert(pkChainSeq);

		NiKFMTool::Transition* pkTransition = m_spMODELTool->GetTransition(
			eSrcID, eChainID);
		assert(pkTransition &&
			pkTransition->GetType() != NiKFMTool::TYPE_CHAIN);
		spInfo = FindTimeForAnimationToCompleteTransition(eSrcID, eChainID,
			pkTransition, fStartFrame, fTransitionFrame);
		assert(spInfo);
		fAnimActivatedTime += spInfo->m_fTimeToCompleteTransition;

		eSrcID = eChainID;
		pkSrcSequence = pkChainSeq;
		fStartFrame = spInfo->m_fFrameInDestWhenTransitionCompletes;

		if (fDuration == NiKFMTool::MAX_DURATION)
		{
			fDuration = (pkChainSeq->GetEndKeyTime() -
				pkChainSeq->GetBeginKeyTime()) - fStartFrame;
		}
		else
		{
			// scale duration so that it is in frame time
			fDuration = pkChainSeq->TimeMultFreq(fDuration);
		}

		fTransitionFrame = fStartFrame + fDuration;
	}

	return INVALID_TIME;
}
//---------------------------------------------------------------------------
float cActorManagerForPartition::GetNextAnimDeactivatedTime(SequenceID eSequenceID)
{
	// This function returns the next time that the indicated animation will
	// be fully deactivated (i.e., not being blended or morphed). If the next
	// deactivation time cannot be determined, INVALID_TIME is returned.

	assert(m_fTime != INVALID_TIME && eSequenceID != INVALID_SEQUENCE_ID);

	// Search for sequence in the extra sequences.
	int iIndex = m_kExtraSequences.Find(eSequenceID);
	if (iIndex != -1)
	{
		NiControllerSequence* pkExtraSeq = GetSequence(eSequenceID);

		// If sequence is inactive, remove it.
		if (pkExtraSeq->GetState() == NiControllerSequence::INACTIVE)
		{
			m_kExtraSequences.RemoveAt(iIndex);
			return INVALID_TIME;
		}

		// If sequence is easing out, return the time when the ease-out will
		// be complete.
		if (pkExtraSeq->GetState() == NiControllerSequence::EASEOUT)
		{
			float fEndTime = pkExtraSeq->GetEaseEndTime();
			if (fEndTime > m_fTime)
			{
				return fEndTime;
			}
			else
			{
				return INVALID_TIME;
			}
		}

		// Extra sequences are deactivated manually, so we cannot predict when
		// that will happen.
		return INVALID_TIME;
	}

	if (eSequenceID == m_eCurID)
	{
		if (m_eNextID == INVALID_SEQUENCE_ID)
		{
			return INVALID_TIME;
		}

		if (m_fTime > m_fTransStartTime)
		{
			return INVALID_TIME;
		}

		return m_fTransStartTime;
	}

	if (m_eNextID == INVALID_SEQUENCE_ID ||
		(eSequenceID == m_eNextID && m_kChainIDs.IsEmpty()) ||
		!m_kChainIDs.FindPos(eSequenceID))
	{
		return INVALID_TIME;
	}

	float fAnimDeactivatedTime = m_fTransStartTime;
	NiKFMTool::Transition* pkTransition = m_spMODELTool->GetTransition(
		m_eCurID,m_eNextID);
	assert(pkTransition && pkTransition->GetType() != NiKFMTool::TYPE_CHAIN);
	float fTransStartFrameTime = m_fTransStartFrameTime;
	if (fTransStartFrameTime == INVALID_TIME)
	{
		fTransStartFrameTime = 0.0f;
	}
	CompletionInfoPtr spInfo = FindTimeForAnimationToCompleteTransition(
		m_eCurID, m_eNextID, pkTransition, fTransStartFrameTime,
		fTransStartFrameTime);
	assert(spInfo);

	SequenceID eSrcID = m_eNextID;
	NiControllerSequence* pkSrcSequence = m_pkNextSequence;
	float fStartFrame = spInfo->m_fFrameInDestWhenTransitionCompletes;

	float fTransitionFrame = 0.0f;
	float fTransitionDuration = pkTransition->GetDuration();
	NiTListIterator posID = m_kChainIDs.GetHeadPos();
	NiTListIterator posDuration = m_kChainDurations.GetHeadPos();
	if (!m_kChainDurations.IsEmpty())
	{
		float fDuration = m_kChainDurations.GetHead();
		if (fDuration == NiKFMTool::MAX_DURATION)
		{
			fDuration = (m_pkNextSequence->GetEndKeyTime() -
				m_pkNextSequence->GetBeginKeyTime()) - fStartFrame;
		}
		else
		{
			// scale duration so that it is in frame time
			fDuration = m_pkNextSequence->TimeMultFreq(fDuration);
		}

		fTransitionFrame = fStartFrame + fDuration;

		assert(posID && posDuration);
		posID = m_kChainIDs.GetNextPos(posID);
		posDuration = m_kChainDurations.GetNextPos(posDuration);
	}

	while (true)
	{
		if (!posID)
		{
			// This statement will break out of this loop when there are no
			// more intermediate animations.
			return INVALID_TIME;
		}
		assert(posDuration);

		SequenceID eChainID = m_kChainIDs.GetNext(posID);
		float fDuration = m_kChainDurations.GetNext(posDuration);
		NiControllerSequence* pkChainSeq = GetSequence(eChainID);
		assert(pkChainSeq);

		NiKFMTool::Transition* pkTransition = m_spMODELTool->GetTransition(
			eSrcID, eChainID);
		assert(pkTransition &&
			pkTransition->GetType() != NiKFMTool::TYPE_CHAIN);
		spInfo = FindTimeForAnimationToCompleteTransition(eSrcID, eChainID,
			pkTransition, fStartFrame, fTransitionFrame);
		assert(spInfo);
		fAnimDeactivatedTime += (spInfo->m_fFrameTransitionOccursInSrc -
			fStartFrame) + fTransitionDuration;

		if (eSequenceID == eSrcID)
		{
			return fAnimDeactivatedTime;
		}

		eSrcID = eChainID;
		pkSrcSequence = pkChainSeq;
		fStartFrame = spInfo->m_fFrameInDestWhenTransitionCompletes;

		if (fDuration == NiKFMTool::MAX_DURATION)
		{
			fDuration = (pkChainSeq->GetEndKeyTime() -
				pkChainSeq->GetBeginKeyTime()) - fStartFrame;
		}
		else
		{
			// scale duration so that it is in frame time
			fDuration = pkChainSeq->TimeMultFreq(fDuration);
		}

		fTransitionFrame = fStartFrame + fDuration;
		fTransitionDuration = pkTransition->GetDuration();
	}

	return INVALID_TIME;
}
//---------------------------------------------------------------------------
float cActorManagerForPartition::GetNextTextKeyEventTime(SequenceID eSequenceID,
														 const NiFixedString& kTextKey)
{
	NiTextKeyMatch kMatchObject(kTextKey);
	return GetNextTextKeyEventTime(eSequenceID, &kMatchObject);
}
//---------------------------------------------------------------------------
float cActorManagerForPartition::GetNextTextKeyEventTime(SequenceID eSequenceID,
														 NiTextKeyMatch* pkMatchObject )
{
	// This function returns the next time that the specified text key in the
	// indicated animation will occur. If the next text key event time cannot
	// be determined or the text key cannot be found, INVALID_TIME is
	// returned.

	assert(pkMatchObject && m_fTime != INVALID_TIME &&
		eSequenceID != INVALID_SEQUENCE_ID);

	float fTextKeyTime;

	// Search for sequence in the extra sequences.
	int iIndex = m_kExtraSequences.Find(eSequenceID);
	if (iIndex != -1)
	{
		NiControllerSequence* pkExtraSeq = GetSequence(eSequenceID);
		if (pkExtraSeq->GetState() == NiControllerSequence::INACTIVE)
		{
			m_kExtraSequences.RemoveAt(iIndex);
			return INVALID_TIME;
		}

		fTextKeyTime = pkExtraSeq->GetTimeAt(pkMatchObject, m_fTime);
		if (fTextKeyTime == NiControllerSequence::INVALID_TIME ||
			fTextKeyTime < m_fTime ||
			(pkExtraSeq->GetState() == NiControllerSequence::EASEOUT &&
			fTextKeyTime > pkExtraSeq->GetEaseEndTime()))
		{
			return INVALID_TIME;
		}

		return fTextKeyTime;
	}

	if (eSequenceID == m_eCurID)
	{
		fTextKeyTime = m_pkCurSequence->GetTimeAt(pkMatchObject, m_fTime);
		if (fTextKeyTime == NiControllerSequence::INVALID_TIME ||
			fTextKeyTime < m_fTime ||
			(fTextKeyTime == m_fTime &&
			m_pkCurSequence->GetKeyTimeAt(pkMatchObject) !=
			m_pkCurSequence->GetBeginKeyTime()))
		{
			return INVALID_TIME;
		}

		if (m_eNextID != INVALID_SEQUENCE_ID &&
			fTextKeyTime > m_fTransStartTime)
		{
			return INVALID_TIME;
		}

		return fTextKeyTime;
	}

	if (m_eNextID == INVALID_SEQUENCE_ID ||
		(eSequenceID != m_eNextID && m_kChainIDs.IsEmpty()) ||
		(eSequenceID != m_eNextID && !m_kChainIDs.FindPos(eSequenceID)))
	{
		return INVALID_TIME;
	}

	fTextKeyTime = m_fTransEndTime;
	NiKFMTool::Transition* pkTransition = m_spMODELTool->GetTransition(
		m_eCurID, m_eNextID);
	assert(pkTransition && pkTransition->GetType() != NiKFMTool::TYPE_CHAIN);
	float fTransStartFrameTime = m_fTransStartFrameTime;
	if (fTransStartFrameTime == INVALID_TIME)
	{
		fTransStartFrameTime = 0.0f;
	}
	CompletionInfoPtr spInfo = FindTimeForAnimationToCompleteTransition(
		m_eCurID, m_eNextID, pkTransition, fTransStartFrameTime,
		fTransStartFrameTime);
	assert(spInfo);

	SequenceID eSrcID = m_eNextID;
	NiControllerSequence* pkSrcSequence = m_pkNextSequence;
	float fStartFrame = spInfo->m_fFrameInDestWhenTransitionCompletes;

	float fTransitionFrame = 0.0f;
	NiTListIterator posID = m_kChainIDs.GetHeadPos();
	NiTListIterator posDuration = m_kChainDurations.GetHeadPos();
	if (!m_kChainDurations.IsEmpty())
	{
		float fDuration = m_kChainDurations.GetHead();
		if (fDuration == NiKFMTool::MAX_DURATION)
		{
			fDuration = (m_pkNextSequence->GetEndKeyTime() -
				m_pkNextSequence->GetBeginKeyTime()) - fStartFrame;
		}
		else
		{
			// scale duration so that it is in frame time
			fDuration = m_pkNextSequence->TimeMultFreq(fDuration);
		}

		fTransitionFrame = fStartFrame + fDuration;

		assert(posID && posDuration);
		posID = m_kChainIDs.GetNextPos(posID);
		posDuration = m_kChainDurations.GetNextPos(posDuration);
	}

	while (true)
	{
		if (eSequenceID == eSrcID)
		{
			float fTextKeyFrame = pkSrcSequence->GetKeyTimeAt(pkMatchObject);
			if (fTextKeyFrame == NiControllerSequence::INVALID_TIME ||
				(posID && fTextKeyFrame > fTransitionFrame))
			{
				return INVALID_TIME;
			}

			float fDelayTime = fTextKeyFrame - fStartFrame;
			if (fDelayTime < 0.0f)
			{
				fDelayTime += pkSrcSequence->GetEndKeyTime() -
					pkSrcSequence->GetBeginKeyTime();
			}
			float fUnscaledTime = fDelayTime / pkSrcSequence->GetFrequency();
			fTextKeyTime += fUnscaledTime;

			return fTextKeyTime;
		}

		if (!posID)
		{
			// This statement will break out of this loop when there are no
			// more intermediate animations.
			return INVALID_TIME;
		}
		assert(posDuration);

		SequenceID eChainID = m_kChainIDs.GetNext(posID);
		float fDuration = m_kChainDurations.GetNext(posDuration);
		NiControllerSequence* pkChainSeq = GetSequence(eChainID);
		assert(pkChainSeq);

		NiKFMTool::Transition* pkTransition = m_spMODELTool->GetTransition(
			eSrcID, eChainID);
		assert(pkTransition &&
			pkTransition->GetType() != NiKFMTool::TYPE_CHAIN);
		spInfo = FindTimeForAnimationToCompleteTransition(eSrcID, eChainID,
			pkTransition, fStartFrame, fTransitionFrame);
		assert(spInfo);
		fTextKeyTime += spInfo->m_fTimeToCompleteTransition;

		eSrcID = eChainID;
		pkSrcSequence = pkChainSeq;
		fStartFrame = spInfo->m_fFrameInDestWhenTransitionCompletes;

		if (fDuration == NiKFMTool::MAX_DURATION)
		{
			fDuration = (pkChainSeq->GetEndKeyTime() - 
				pkChainSeq->GetBeginKeyTime()) - fStartFrame;
		}
		else
		{
			// scale duration so that it is in frame time
			fDuration = pkChainSeq->TimeMultFreq(fDuration);
		}

		fTransitionFrame = fStartFrame + fDuration;
	}

	return INVALID_TIME;
}
//---------------------------------------------------------------------------
cActorManagerForPartition::ChainCompletionInfo* cActorManagerForPartition::FillChainComplete( 
	unsigned int uiSrcID, unsigned int uiDesID,
	float fFrameDuration, float& fSrcBeginFrame, float& fTotalTime)
{
	NiControllerSequence* pkSrcSeq = GetSequence(uiSrcID);
	NiControllerSequence* pkDesSeq = GetSequence(uiDesID);

	// Get the Transition... call recurively
	NiKFMTool::Transition* pkCurTransition = 
		m_spMODELTool->GetTransition(uiSrcID, uiDesID);
	assert(pkCurTransition);

	float fNewTransitionFrame;
	if (fFrameDuration == NiKFMTool::MAX_DURATION)
	{
		fNewTransitionFrame = 
			pkSrcSeq->GetEndKeyTime();

		if (fNewTransitionFrame < fSrcBeginFrame)
			fNewTransitionFrame = fSrcBeginFrame;
	}
	else
	{
		fNewTransitionFrame = 
			fSrcBeginFrame + fFrameDuration;
	}

	CompletionInfoPtr spCI;
	spCI = FindTimeForAnimationToCompleteTransition(uiSrcID, uiDesID,
		pkCurTransition, fSrcBeginFrame, fNewTransitionFrame);

	ChainCompletionInfo* pkCCI = NiNew ChainCompletionInfo;
	pkCCI->SetName(pkSrcSeq->GetName());
	pkCCI->m_fSeqStart = fTotalTime;
	pkCCI->m_fInSeqBeginFrame = fSrcBeginFrame;

	fTotalTime += spCI->m_fTimeToCompleteTransition;

	pkCCI->m_fTransStart = fTotalTime - pkCurTransition
		->GetDuration();
	pkCCI->m_fTransEnd = fTotalTime;
	pkCCI->m_pkTransition = pkCurTransition;
	pkCCI->m_uiSrcID = uiSrcID;
	pkCCI->m_uiDesID = uiDesID;
	pkCCI->SetNextName(pkDesSeq->GetName());

	fSrcBeginFrame = spCI->m_fFrameInDestWhenTransitionCompletes;

	return pkCCI;
}
//---------------------------------------------------------------------------
NiKFMTool::Transition::BlendPair* cActorManagerForPartition::GetNextBlendPair(
	unsigned int uiTransSrcID, unsigned int /*uiTransDesID*/,
	NiKFMTool::Transition* pkTransition, float fFrameTime,
	float& fActualUnboundedFrame)
{
	// This function finds the lowest blend pair after fFrameTime.

	if (pkTransition->GetType() != NiKFMTool::TYPE_BLEND ||
		pkTransition->GetBlendPairs().GetSize() == 0 ||
		!pkTransition->GetBlendPairs().GetAt(0)->GetStartKey().Exists())
	{
		return 0;
	}

	NiControllerSequence* pkSrcSequence = GetSequence(uiTransSrcID);

	float fBeginFrame = pkSrcSequence->GetBeginKeyTime();
	float fEndFrame = pkSrcSequence->GetEndKeyTime();
	float fSrcFrameLength = fEndFrame - fBeginFrame;

	assert(fFrameTime >= fBeginFrame);

	fActualUnboundedFrame = fFrameTime;
	if (fFrameTime > fEndFrame)
	{
		if (pkSrcSequence->GetCycleType() == NiTimeController::CLAMP)
		{
			return 0;
		}
		else
		{
			// this can happen, for example with durations of chains...
			float fFloor = floorf(fFrameTime / fSrcFrameLength);
			float fTimeForLooping = (fFloor * fSrcFrameLength);
			fFrameTime = fFrameTime - fTimeForLooping;
		}
	}

	NiKFMTool::Transition::BlendPair* pkSmallestBP = 0;
	float fSmallestBPFrameTime = NI_INFINITY;

	for (unsigned int ui = 0; ui < pkTransition->GetBlendPairs().GetSize();
		ui++)
	{
		NiKFMTool::Transition::BlendPair* pkBP = pkTransition
			->GetBlendPairs().GetAt(ui);
		float fBPFrameTime = pkSrcSequence->GetKeyTimeAt(pkBP->GetStartKey())
			- fFrameTime;
		if (fBPFrameTime < 0.0f)
		{
			fBPFrameTime += fSrcFrameLength;
		}

		if (fBPFrameTime < fSmallestBPFrameTime)
		{
			fSmallestBPFrameTime = fBPFrameTime;
			pkSmallestBP = pkBP;
		}
	}

	fActualUnboundedFrame += fSmallestBPFrameTime;

	assert(pkSmallestBP != 0);
	return pkSmallestBP;
}
//---------------------------------------------------------------------------
void cActorManagerForPartition::RaiseAnimActivatedEvents(SequenceID eEventSeqID)
{
	// This function is only called when no animation is active and an
	// an animation is activated. It catches a case that is not handled by
	// the standard event handling code.

	for (unsigned int ui = 0; ui < m_kCallbacks.GetSize(); ui++)
	{
		CallbackData* pkCallbackData = m_kCallbacks.GetAt(ui);
		if (pkCallbackData)
		{
			if (pkCallbackData->m_eEventType == ANIM_ACTIVATED)
			{
				SequenceID eSequenceID = pkCallbackData->m_eSequenceID;
				if (eSequenceID == ANY_SEQUENCE_ID)
				{
					NiTMapIterator spos = m_kSequenceMap.GetFirstPos();
					while (spos)
					{
						NiControllerSequence* pkSequence;
						m_kSequenceMap.GetNext(spos, eSequenceID, pkSequence);

						if (eSequenceID == eEventSeqID)
						{
							m_pkCallbackObject->AnimActivated(this,
								eSequenceID, m_fTime, m_fTime);
						}
					}
				}
				else if (eSequenceID == eEventSeqID)
				{
					m_pkCallbackObject->AnimActivated(this, eSequenceID,
						m_fTime, m_fTime);
				}
			}
		}
	}
}
//---------------------------------------------------------------------------
void cActorManagerForPartition::RaiseAnimDeactivatedEvents(SequenceID eEventSeqID)
{
	// This function is only called when the last remaining animation is
	// deactivated on the manager. It catches a case that is not handled by
	// the standard event handling code.

	for (unsigned int ui = 0; ui < m_kCallbacks.GetSize(); ui++)
	{
		CallbackData* pkCallbackData = m_kCallbacks.GetAt(ui);
		if (pkCallbackData)
		{
			if (pkCallbackData->m_eEventType == ANIM_DEACTIVATED)
			{
				SequenceID eSequenceID = pkCallbackData->m_eSequenceID;
				if (eSequenceID == ANY_SEQUENCE_ID)
				{
					NiTMapIterator spos = m_kSequenceMap.GetFirstPos();
					while (spos)
					{
						NiControllerSequence* pkSequence;
						m_kSequenceMap.GetNext(spos, eSequenceID, 
							pkSequence);

						if (eSequenceID == eEventSeqID)
						{
							m_pkCallbackObject->AnimDeactivated(this,
								eSequenceID, m_fTime, m_fTime);
						}
					}
				}
				else if (eSequenceID == eEventSeqID)
				{
					m_pkCallbackObject->AnimDeactivated(this, eSequenceID,
						m_fTime, m_fTime);
				}
			}
		}
	}
}
//---------------------------------------------------------------------------
void cActorManagerForPartition::RemoveInactiveExtraSequences()
{
	unsigned int ui = 0;
	while (ui < m_kExtraSequences.GetSize())
	{
		if (GetSequence(m_kExtraSequences.GetAt(ui))->GetState() ==
			NiControllerSequence::INACTIVE)
		{
			m_kExtraSequences.RemoveAt(ui);
			ui = 0;
		}
		else
		{
			ui++;
		}
	}
}
//---------------------------------------------------------------------------
cActorManagerForPartition::CallbackObject::~CallbackObject()
{
	// virtual destructor defined in case derived classes need virtual
	// destruction
}
//---------------------------------------------------------------------------

/* ------------------------------------------------------------------
* 함수이름 :	Create(CModelInstance* pModel, NiTPointerMap<unsigned int, const char*>& kMap)
* 목    적 :	객체를 위한 3D데이터를 구성한다.
* 주의사항 :	
* ------------------------------------------------------------------ */
cActorManagerForPartition* cActorManagerForPartition::Create( cModelInstance* pModel, NiTPointerMap<unsigned int, const char*>& partsMap )
{
	assert(!partsMap.IsEmpty());
	if(partsMap.IsEmpty()) 
	{
		return 0;
	}

	/// Get NiKFMTool Instance
	NiKFMTool* pMODELTool = pModel->GetKFMTool();
	assert(pMODELTool);
	if(pMODELTool == 0) 
	{
		return 0;
	}

	/// Get Model File Path
	const char* modelPath = pMODELTool->GetModelPath();
	assert(modelPath);
	if ( !modelPath ) 
	{
		return 0;
	}

	/// Create ActorManager
	cActorManagerForPartition* pActorManager = NiNew cActorManagerForPartition( pMODELTool, false );
	assert( pActorManager );
	if( !pActorManager ) 
	{
		return 0;
	}

	/// Alloc Object Parts Memory set
	pActorManager->SetTagetArrayCount( partsMap.GetCount() );

	/// Get Model Data
	//NiNode* pModelRoot = RESOURCEMAN->CloneObjectByName( modelPath );
	NiNode* pModelRoot = RESOURCEMAN->CloneNiNode( modelPath );

	if( pModelRoot )
	{
		/// Setting Parts Instance
		const char* partsName = 0;
		NiAVObject* pObj = 0;
		for( unsigned int i=0; i<partsMap.GetCount(); ++i )
		{
			/// eParts_max
			if( i >= 12 ) break;
			if( partsMap.GetAt( i, partsName ) == false )
			{
				assert(0);
				continue;
			}
			pObj = NiDynamicCast( NiAVObject, pModelRoot->GetObjectByName( partsName ) );
			if( !pObj ) 
			{
				//			assert( 0 );
				continue;
			}

			pActorManager->AddPartsObject( i, pObj );
		}

		/// Init ActorManager
		pActorManager->Initialize( pModelRoot, pModel );
	}

	return pActorManager;
}

/* ------------------------------------------------------------------
* 함수이름 :	Initialize( NiAVObject* pActorRoot, cModelInstance* pModel )
* 목    적 :	관리자의 초기화 (본및 애니정보를 셋팅한다.)
* 주의사항 :	
* ------------------------------------------------------------------ */
bool cActorManagerForPartition::Initialize( NiNode* pModelRoot, cModelInstance* pModel )
{
	m_spModelRoot = pModelRoot;

	if( !pModel || !pModelRoot )
	{
		assert(0);
		return false;
	}

	Reset();

	NiAVObject* pActorRoot = pModelRoot->GetObjectByName( m_spMODELTool->GetModelRoot() );

	/// Create the controller manager.
	NiControllerManager* pManager = NiNew NiControllerManager( pActorRoot, m_bCumulativeAnimations );
	assert(pManager);
	if( !pManager ) 
	{
		return false;
	}

	//m_spModelRoot = pModelRoot;
	m_spManager = pManager;
	m_spBoneLOD = FindBoneLODController( m_spModelRoot );

	/// Setting Sequence
	SequenceID* pSequenceIDs;
	unsigned int numIDs;
	m_spMODELTool->GetSequenceIDs( pSequenceIDs, numIDs );

	NiControllerSequence* pp = 0;
	for( unsigned int i=0; i<numIDs; ++i )
	{
		pp = pModel->CloneSequence( pSequenceIDs[i] );
		if(!pp) 
		{
			continue;
		}

		ChangeSequence( pSequenceIDs[i], pp );
	}

	NiFree( pSequenceIDs );

	return true;
}

/* ------------------------------------------------------------------
* 함수이름 :	SetTagetArrayCount( unsigned int arraySize, unsigned int growSize )
* 목    적 :	파트의 메모리를 잡는다.
* 주의사항 :	
* ------------------------------------------------------------------ */
void cActorManagerForPartition::SetTagetArrayCount( unsigned int arraySize, unsigned int growSize )
{
	mArrayParts.SetSize( arraySize );
	mArrayParts.SetGrowBy( growSize );

	mArrayKeepParts.SetSize( arraySize );
	mArrayKeepParts.SetGrowBy( growSize );
}

/* ------------------------------------------------------------------
* 함수이름 :	AddPartsObject( unsigned int partIdx, NiAVObject* pObj )
* 목    적 :	ArrayParts에 객체를 최초 추가한다.
* 주의사항 :	
* ------------------------------------------------------------------ */
void cActorManagerForPartition::AddPartsObject( unsigned int partIdx, NiAVObject* pObj )
{
	assert(pObj);
	assert( partIdx < mArrayParts.GetAllocatedSize() );

	mArrayParts.RemoveAt( partIdx );

	mArrayParts.SetAt( partIdx, pObj );
}

/* ------------------------------------------------------------------
* 함수이름 :	ChangePartsObject( unsigned int partIdx, NiAVObject* pObj )
* 목    적 :	모델의 참조 포인터를 추가한다.
* 주의사항 :	제거시 파트는 관리차원의 객체이고, 실제로 Node에서 Detach해 주어야 한다.
* ------------------------------------------------------------------ */
void cActorManagerForPartition::ChangePartsObject( unsigned int partIdx, NiAVObject* pObj )
{
	assert( partIdx < mArrayParts.GetAllocatedSize() );
	if( !pObj )
	{
		assert(0);
		return;
	}

	NiNode* pRoot = GetNifRoot();
	if( !pRoot )
	{
		assert(0);
		return;
	}

	NiAVObject* pOldObj = mArrayParts.RemoveAt( partIdx );
	if( pOldObj )
	{
		/// 정보가 있다면 Node에서 제거
		pRoot->DetachChild(pOldObj);
	}
	else
	{
		/// 정보가 없다면 Keep정보를 제거한다.
		NiAVObjectPtr delObj = mArrayKeepParts.RemoveAt( partIdx );
		delObj = 0;
	}

	/// 새로운 객체를 넣는다.
	pRoot->AttachChild( pObj );
	mArrayParts.SetAt( partIdx, pObj );

	/// 변경사항을 적용하기 위해 Update호출
	bool isSelectedUpdate = true;
	bool isRigid = false;
	pObj->SetSelectiveUpdateFlags( isSelectedUpdate, true, isRigid );

	pObj->Update( 0.0f );
	pObj->UpdateProperties();
	pObj->UpdateEffects();
	pRoot->UpdateNodeBound();
}

/* ------------------------------------------------------------------
* 함수이름 :	KeepPartsObject( unsigned int partIdx )
* 목    적 :	특정 파트의 모델 참조를 별도의 공간에 저장한다.
* 주의사항 :	Remove만을 호출하는 진행은 없으므로 업데이트는 추가에 맞긴다.
*				파트객체는 무조건 비워지면 안되기 때문에 별도의 보관장소에 보관한다.
* ------------------------------------------------------------------ */
void cActorManagerForPartition::KeepPartsObject( unsigned int partIdx )
{
	NiAVObject* pObj = mArrayParts.RemoveAt( partIdx );
	pObj = GetNifRoot()->DetachChild( pObj );

	/// 임시 제거용 데이터를 보관한다.
	mArrayKeepParts.SetAt( partIdx, pObj );
}

/* ------------------------------------------------------------------
* 함수이름 :	RemoveAllPartsArray()
* 목    적 :	모델 참조를 모두 비운다.
* 주의사항 :	주의!! 이 함수는 배열만을 비우지 실제 객체를 Detach시키지 않는다.
* ------------------------------------------------------------------ */
void cActorManagerForPartition::RemoveAllPartsArray()
{
	mArrayParts.RemoveAll();
	mArrayKeepParts.RemoveAll();
}

/* ------------------------------------------------------------------
* 함수이름 :	GetMorpherController()
* 목    적 :	모핑 컨트롤러의 인스턴스 제공
* 주의사항 :	모퍼가 한셋 존재한다고 생각하고 만들었다. (눈에만)
* ------------------------------------------------------------------ */
NiGeomMorpherController* cActorManagerForPartition::GetMorpherController()
{
	NiGeomMorpherController* pmanager = 0;
	for( unsigned int ui=0;ui < mArrayParts.GetSize(); ui++)  
	{
		NiAVObject* pobj = mArrayParts.GetAt(ui);
		if( pobj == 0 )
			continue;

		pmanager = FindMorpherController( pobj );
		if(pmanager)
		{
			return pmanager;
		}
	}
	return 0;
}