/**
* Timeline
* @version: 01-29-2005
* @author Joseph 'Pcjoe' Florencio
*/

#include "../hud.h"
#include "../cl_util.h"
#include "../studio_util.h"
#include "../vgui_TeamFortressViewport.h"
#include "../vgui_fxtoolpanel.h"
#include "event_api.h"
#include "fxsys.h"
#include "../mdlcontrol.h"
#include "../mdlblur.h"

extern CModelBlurManager gModelBlur;


/*
* Timed Event
*/
// Read XML
void CTimedEvent::readXML( TiXmlElement *pElement )
{	setInterpTo(xmlFindBool(pElement,"interpolate"));
}

// Write XML
TiXmlElement *CTimedEvent::writeXML( void )
{	TiXmlElement *pTimedEvent = new TiXmlElement("timedevent");
	pTimedEvent->LinkEndChild(xmlCreateBool("interpolate",canInterpTo()));
	return pTimedEvent;
}

// xmlCreate
TiXmlElement *CTimedEvent::xmlCreateFloat( string strName, float flValue )
{	TiXmlElement *pFloat = new TiXmlElement("float");
	pFloat->SetAttribute("name",strName.c_str());
	char szFloat[16];
	sprintf(szFloat,"%f",flValue);
	pFloat->SetAttribute("value",szFloat);
	return pFloat;
}
TiXmlElement *CTimedEvent::xmlCreateInt( string strName, int iValue )
{	TiXmlElement *pInt = new TiXmlElement("int");
	pInt->SetAttribute("name",strName.c_str());
	pInt->SetAttribute("value",iValue);
	return pInt;
}
TiXmlElement *CTimedEvent::xmlCreateBool( string strName, bool bValue )
{	TiXmlElement *pBool = new TiXmlElement("bool");
	pBool->SetAttribute("name",strName.c_str());
	if(bValue)
		pBool->SetAttribute("value","true");
	else
		pBool->SetAttribute("value","false");
	return pBool;
}
TiXmlElement *CTimedEvent::xmlCreateString( string strName, std::string strString )
{	TiXmlElement *pString = new TiXmlElement("string");
	pString->SetAttribute("name",strName.c_str());
	pString->SetAttribute("value",strString.c_str());
	return pString;
}
TiXmlElement *CTimedEvent::xmlCreateVector( string strName, vec3_t vecValue )
{	TiXmlElement *pVector = new TiXmlElement("vector");
	pVector->SetAttribute("name",strName.c_str());
	char szFloat[16];
	sprintf(szFloat,"%f",vecValue.x);
	pVector->SetAttribute("x",szFloat);
	sprintf(szFloat,"%f",vecValue.y);
	pVector->SetAttribute("y",szFloat);
	sprintf(szFloat,"%f",vecValue.z);
	pVector->SetAttribute("z",szFloat);
	return pVector;
}

// xmlFind
float CTimedEvent::xmlFindFloat( TiXmlElement *pTE, string strName )
{	TiXmlElement *pSearch = NULL;
	IterateXML(pSearch,pTE)
	{	if(pSearch->Value())
		{	if((string(pSearch->Value()) == "float") && (string(pSearch->Attribute("name")) == strName))
			{	return atof(pSearch->Attribute("value"));
			}
		}
	}
	return 0;
}
int CTimedEvent::xmlFindInt( TiXmlElement *pTE, string strName )
{	TiXmlElement *pSearch = NULL;
	IterateXML(pSearch,pTE)
	{	if(pSearch->Value())
		{	if((string(pSearch->Value()) == "int") && (string(pSearch->Attribute("name")) == strName))
			{	return atoi(pSearch->Attribute("value"));
			}
		}
	}
	return 0;
}
bool CTimedEvent::xmlFindBool( TiXmlElement *pTE, string strName )
{	TiXmlElement *pSearch = NULL;
	IterateXML(pSearch,pTE)
	{	if(pSearch->Value())
		{	if((string(pSearch->Value()) == "bool") && (string(pSearch->Attribute("name")) == strName))
			{	string strBool = pSearch->Attribute("value");
				if(strBool == "true")
					return true;
				else
					return false;
			}
		}
	}
	return false;
}
std::string CTimedEvent::xmlFindString( TiXmlElement *pTE, string strName )
{	TiXmlElement *pSearch = NULL;
	IterateXML(pSearch,pTE)
	{	if(pSearch->Value())
		{	if((string(pSearch->Value()) == "string") && (string(pSearch->Attribute("name")) == strName))
			{	return string(pSearch->Attribute("value"));
			}
		}
	}
	return std::string("");
}
vec3_t CTimedEvent::xmlFindVector( TiXmlElement *pTE, string strName )
{	vec3_t vecVector;
	TiXmlElement *pSearch = NULL;
	IterateXML(pSearch,pTE)
	{	if(pSearch->Value())
		{	if((string(pSearch->Value()) == "vector") && (string(pSearch->Attribute("name")) == strName))
			{	vecVector.x = atof(pSearch->Attribute("x"));
				vecVector.y = atof(pSearch->Attribute("y"));
				vecVector.z = atof(pSearch->Attribute("z"));
			}
		}
	}
	return vecVector;
}

// Constructor
CTimeline::CTimeline()
{	setTimelineManager(NULL);
}

CTimeline::~CTimeline()
{	std::map<float,CTimedEvent*>::iterator iterCurrent;
	ForEachIn(iterCurrent, m_TimedEvents)
	{	delete iterCurrent->second;
	}
	m_TimedEvents.clear();
}

// Get FX Object
CFXObject *CTimeline::getFXObject( void )
{	return getTimelineManager()->getFXObject();
}

// Read XML
void CTimeline::readXML( TiXmlElement *pElement )
{	TiXmlElement *pChildren=NULL;
	IterateXML(pChildren,pElement)
	{	CTimedEvent *pAdd = NULL;
		int iID = atoi(pChildren->Attribute("teid"));
		float flTime = atof(pChildren->Attribute("time"));
		switch(iID)
		{
		case TEID_ROTATION:
			pAdd = new CTimedEventRotation();
			break;
		case TEID_ROTATION_ADD:
			pAdd = new CTimedEventAddRotation();
			break;
		case TEID_SIZE:
			pAdd = new CTimedEventSize();
			break;
		case TEID_ALPHA:
			pAdd = new CTimedEventAlpha();
			break;
		case TEID_COLOR:
			pAdd = new CTimedEventColor();
			break;
		case TEID_SOUND:
			pAdd = new CTimedEventSound();
			break;
		case TEID_POSITION:
			pAdd = new CTimedEventPosition();
			break;
		case TEID_VELOCITY:
			pAdd = new CTimedEventVelocity();
			break;
		case TEID_SEQUENCE:
			pAdd = new CTimedEventSequence();
			break;
		case TEID_FRAMEMULTI:
			pAdd = new CTimedEventFrameMulti();
			break;
		case TEID_BODYPART:
			pAdd = new CTimedEventBodyPart();
			break;
		case TEID_MDLBLUR:
			pAdd = new CTimedEventModelBlur();
			break;
		case TEID_RADIALBLUR:
			pAdd = new CTimedEventRadialBlur();
			break;
		case TEID_SCREENSHAKE:
		//	pAdd = new CTimedEventFrameMulti(1.0));
			break;
		case TEID_SNDSTREAM:
			pAdd = new CTimedEventSoundStream();
			break;
		case TEID_SNDVOL:
			pAdd = new CTimedEventVolume();
			break;
		}
		if(pAdd)
		{	pAdd->readXML(pChildren);
			addTimedEvent(flTime,pAdd);
		}
	}
}

// Write XML
TiXmlElement *CTimeline::writeXML( void )
{	TiXmlElement *pTimeline = new TiXmlElement("timeline");
	pTimeline->SetAttribute("name", getName());
	pTimeline->SetAttribute("tlid", getID());

	std::map<float,CTimedEvent*>::iterator iterCurrent;
	ForEachIn(iterCurrent, m_TimedEvents)
	{	TiXmlElement *pElement = iterCurrent->second->writeXML();
		if(pElement)
		{	char szTime[16];
			sprintf(szTime,"%f",iterCurrent->first);
			pElement->SetAttribute("time",szTime);
			pElement->SetAttribute("teid",iterCurrent->second->getID());
		}
		pTimeline->LinkEndChild(pElement);
	}
	return pTimeline;
}

// Get Timed Event Previous
std::map<float,CTimedEvent*>::iterator CTimeline::getTimedEventPrev( float flTime )
{	std::map<float,CTimedEvent*>::iterator iterCurrent;
	std::map<float,CTimedEvent*>::iterator iterSave=NULL;
	ForEachIn(iterCurrent, m_TimedEvents)
	{	// Get event before current time
		if(iterSave == NULL || ((iterCurrent->first > iterSave->first) && (iterCurrent->first <= flTime)))
		{	if((iterSave == NULL) ? iterCurrent->first <= flTime : true)
			{	iterSave=iterCurrent;
			}
		}
	}
	return iterSave;
}

// Get Timed Event After
std::map<float,CTimedEvent*>::iterator CTimeline::getTimedEventAfter( float flTime )
{	std::map<float,CTimedEvent*>::iterator iterCurrent;
	std::map<float,CTimedEvent*>::iterator iterSave=NULL;
	ForEachIn(iterCurrent, m_TimedEvents)
	{	// Get event after current time
		if(iterSave == NULL || ((iterCurrent->first < iterSave->first) && (iterCurrent->first > flTime)))
		{	if((iterSave == NULL) ? iterCurrent->first > flTime : true)
			{	iterSave=iterCurrent;
			}
		}
	}
	return iterSave;
}

// Update time ( delete updated iterator, return new iterator with same data, but new time )
std::map<float,CTimedEvent*>::iterator CTimeline::updateTime( std::map<float,CTimedEvent*>::iterator iterUpdate, float flTime )
{	// Make sure iterator exist
	if(iterUpdate==NULL)
	{	return iterUpdate;
	}

	// Make sure nothing exist at the new time
	if(getTimedEvents()->find(flTime) != getTimedEvents()->end())
	{	return iterUpdate;
	}

	// Store off copy of old timed event and get a memory copy
	CTimedEvent *pDelete =iterUpdate->second;
	CTimedEvent *pCopy = iterUpdate->second->getCopy();
	if(!pCopy)
	{	return iterUpdate;
	}

	// Remove old iterator
	getTimedEvents()->erase(iterUpdate);

	// Make new and get iterator
	addTimedEvent(flTime,pCopy);
	std::map<float,CTimedEvent*>::iterator iterNew = getTimedEvents()->find(flTime);
	// should never happen
	if(iterNew == getTimedEvents()->end())
	{	delete pCopy;
		return NULL;
	}
	
	// Delete old data
	delete pDelete;

	// Return updated iterator
	return iterNew;
}

// Update Data
void CTimeInterpolate::updateData( float flTime )
{	// Nothing has happend yet, dont update
	if(noOccuredEvent(flTime))
	{	calcTimedEventNone(flTime);
	}
	// Update only with previous event
	else if(usePreviousEventOnly(flTime))
	{	calcTimedEventPrev(flTime);
	}
	// Interpolate both events
	else if(canInterpolTwoEvents(flTime))
	{	calcTimedEventInterpol(flTime);
	}
}

// Get percent between both events
float CTimeInterpolate::getPercentBetween( std::map<float,CTimedEvent*>::iterator iterStart, std::map<float,CTimedEvent*>::iterator iterEnd, float flTime )
{	if(iterStart == NULL || iterEnd == NULL)
	{	return 0;
	}
	float flTotal = abs(iterEnd->first - iterStart->first);
	return ((flTime-iterStart->first)/flTotal);
}

// Can Interpolate Two Events (will make use of prev and after events)
bool CTimeInterpolate::canInterpolTwoEvents( float flTime )
{	std::map<float,CTimedEvent*>::iterator iterPrev=NULL,iterAfter=NULL;
	// Get before and after timed events
	iterPrev=getTimedEventPrev(flTime);
	iterAfter=getTimedEventAfter(flTime);
	// Missing one of the events
	if(iterPrev == NULL || iterAfter == NULL)
	{	return false;
	}
	// We have both events, check to see if after event can interpolate
	else if(!iterAfter->second->canInterpTo())
	{	return false;
	}
	// Interpolate between both points
	return true;
}

// No event has occured at this time
bool CTimeInterpolate::noOccuredEvent( float flTime )
{	// Nothing happend yet
	if(getTimedEventPrev(flTime) == NULL)
	{	return true;
	}
	return false;
}

// Only use previous event (no after event, or no interpolation used on after event)
bool CTimeInterpolate::usePreviousEventOnly( float flTime )
{	std::map<float,CTimedEvent*>::iterator iterPrev=NULL,iterAfter=NULL;
	// Get before and after timed events
	iterPrev=getTimedEventPrev(flTime);
	iterAfter=getTimedEventAfter(flTime);
	if(iterPrev == NULL)
	{	return false;
	}
	// After event exist, and we want to interpolate to it
	else if((iterAfter != NULL) && iterAfter->second->canInterpTo())
	{	return false;
	}
	return true;
}


/*
* Timeline Rotation
*/

// Account for regular rotation and additive rotational
bool CTimeRotation::usePreviousEventOnly( float flTime )
{	std::map<float,CTimedEvent*>::iterator iterPrev=NULL,iterAfter=NULL;
	// Get before and after timed events
	iterPrev=getTimedEventPrev(flTime);
	iterAfter=getTimedEventAfter(flTime);
	if(iterPrev == NULL)
	{	return false;
	}
	// After event exist
	else if(iterAfter != NULL)
	{	// New: Don't interpolate if previous event is a timed velocity ( impossible to blend from )
		if(((CTimedEventRotation*)iterPrev->second)->isTimedAddRotation())
		{	return true;
		}
		// After event exist, and we want to interpolate to it
		else if(iterAfter->second->canInterpTo())
		{	return false;
		}
	}
	return true;
}

// Calculate previous event
void CTimeRotation::calcTimedEventPrev( float flTime )
{	// Get previous event, set data
	std::map<float,CTimedEvent*>::iterator iterTimedRot = getTimedEventPrev(flTime);
	CTimedEventRotation *pTimedRot = ((CTimedEventRotation*)iterTimedRot->second);

	// Timed Add Rotation
	if(pTimedRot->isTimedAddRotation())
	{	// convert
		CTimedEventAddRotation *pTimedAddRot = (CTimedEventAddRotation*)pTimedRot;
		
		// Calculate and add rotation
	/*	vec3_t vecRot = pTimedAddRot->getAddedRotation() * getTimelineManager()->getTimeDelta();
		getFXObject()->getRotation()->Multiply(CQuaternion(vecRot));*/

		// Has initial position, use that
		if(pTimedAddRot->hasInitialRotation())
		{	vec3_t vecRot = pTimedAddRot->getRotation()->QuaternionToEuler();
			vecRot = vecRot + pTimedAddRot->getAddedRotation() * (flTime-iterTimedRot->first);
			if(getFXObject3D())
				getFXObject3D()->getRotation()->setQuaternion(CQuaternion(vecRot));
		}
		// Resumes from left off rotation, get it
		else
		{	
		}
	}
	// Regular rotation data, apply normally
	else
	{	// Get previous event, set data
		if(getFXObject3D())
            getFXObject3D()->getRotation()->setQuaternion((*pTimedRot->getRotation()));
	}
}

// Calculate interpol event
void CTimeRotation::calcTimedEventInterpol( float flTime )
{	// Get before and after
	std::map<float,CTimedEvent*>::iterator iterPrev=NULL,iterAfter=NULL;
	iterPrev = getTimedEventPrev(flTime);
	iterAfter = getTimedEventAfter(flTime);
	// get percent
	float flPercent = getPercentBetween(iterPrev,iterAfter, flTime);
	// get previous and after size
	CQuaternion qPrevR = (*((CTimedEventRotation*)iterPrev->second)->getRotation());
	CQuaternion qAfterR = (*((CTimedEventRotation*)iterAfter->second)->getRotation());
	// apply changes (SLERP)
	if(getFXObject3D())
		getFXObject3D()->getRotation()->QuaternionSlerp(qPrevR, qAfterR, flPercent);
}

/*
* Timeline Size
*/

// Calculate previous event
void CTimeSize::calcTimedEventPrev( float flTime )
{	// Get previous event, set data
	if(getFXObjectRender())
		getFXObjectRender()->setSize(((CTimedEventSize*)getTimedEventPrev(flTime)->second)->getSize());
}

// Calculate interpol event
void CTimeSize::calcTimedEventInterpol( float flTime )
{	// Get before and after
	std::map<float,CTimedEvent*>::iterator iterPrev=NULL,iterAfter=NULL;
	iterPrev = getTimedEventPrev(flTime);
	iterAfter = getTimedEventAfter(flTime);
	// get percent
	float flPercent = getPercentBetween(iterPrev,iterAfter, flTime);
	// get previous and after size
	float flPrevSize = ((CTimedEventSize*)iterPrev->second)->getSize();
	float flAfterSize = ((CTimedEventSize*)iterAfter->second)->getSize();
	// apply changes
	if(getFXObjectRender())
		getFXObjectRender()->setSize(flPrevSize * float(float(1)-flPercent) + flAfterSize * flPercent);
}

/*
* Timeline Alpha
*/

// Calculate previous event
void CTimeAlpha::calcTimedEventPrev( float flTime )
{	// Get previous event, set data
	if(getFXObjectRender())
		getFXObjectRender()->setAlpha(((CTimedEventAlpha*)getTimedEventPrev(flTime)->second)->getAlpha());
}

// Calculate interpol event
void CTimeAlpha::calcTimedEventInterpol( float flTime )
{	// Get before and after
	std::map<float,CTimedEvent*>::iterator iterPrev=NULL,iterAfter=NULL;
	iterPrev = getTimedEventPrev(flTime);
	iterAfter = getTimedEventAfter(flTime);
	// get percent
	float flPercent = getPercentBetween(iterPrev,iterAfter, flTime);
	// get previous and after alpha
	float flPrevAlpha = ((CTimedEventAlpha*)iterPrev->second)->getAlpha();
	float flAfterAlpha = ((CTimedEventAlpha*)iterAfter->second)->getAlpha();
	// apply changes
	if(getFXObjectRender())
		getFXObjectRender()->setAlpha(float(flPrevAlpha * float(float(1)-flPercent) + flAfterAlpha * flPercent));
}

/*
* Timeline Color
*/

// Calculate previous event
void CTimeColor::calcTimedEventPrev( float flTime )
{	// Get previous event, set data
	if(getFXObjectRender())
		getFXObjectRender()->getColor()->setColor((float*)((CTimedEventColor*)getTimedEventPrev(flTime)->second)->getColor());
}

// Calculate interpol event
void CTimeColor::calcTimedEventInterpol( float flTime )
{	// Get before and after
	std::map<float,CTimedEvent*>::iterator iterPrev=NULL,iterAfter=NULL;
	iterPrev = getTimedEventPrev(flTime);
	iterAfter = getTimedEventAfter(flTime);
	// get percent
	float flPercent = getPercentBetween(iterPrev,iterAfter, flTime);
	// get previous and after alpha
	CFXColor cAfter = (*((CTimedEventColor*)iterAfter->second)->getColor());
	CFXColor cInterpol = ((CTimedEventColor*)iterPrev->second)->getColor()->interpolateTo(cAfter , flPercent);
	// apply changes
	if(getFXObjectRender())
		getFXObjectRender()->getColor()->setColor((float*)&cInterpol);
}

/*
* Timeline Sound
*/
// Update Data
void CTimeSound::updateData( float flTime )
{	// Only shoot off event if we're in play mode
//	if(gViewPort->m_pFXToolPanel->getTimeline()->getTimelineMeter()->getPlayStatus() == TIMELINE_PLAY)
	{	std::map<float,CTimedEvent*>::iterator iterPrev = getTimedEventPrev(flTime);
		// Shoot off trigger
		if(iterPrev != NULL && !((CTimedEventSound*)iterPrev->second)->getTriggerStatus())
		{	CTimedEventSound *pSound = ((CTimedEventSound*)iterPrev->second);
			// Set sound trigger
			pSound->setTrigger();

			// Get object and precache element
			CFXObject3D *pObj = ((CFXObject3D*)getFXObject());
			CFXPrecache *pSample = g_ParseMain.getPrecache(pSound->getSound().c_str());
			if(pObj && pSample)
			{	// Get Channel that corresponds with channel index
				int iChannel = pObj->channelExist(pSound->getChannelIndex());
				// Channel is already created, emit sound
				if(iChannel)
				{	// Must create new channel, fmod deleted our old one
					if(g_FMODWrapper.FWRAPPER_PlaySound(iChannel, pSample->getSample()) == -1)
					{//	if(g_FMODWrapper.FWRAPPER_GetError() == FMOD_ERR_INVALID_HANDLE)
						{	iChannel = g_FMODWrapper.FWRAPPER_PlaySound(FSOUND_FREE, pSample->getSample());
							pObj->createChannel(pSound->getChannelIndex(), iChannel);
						}
					}
				}
				// Channel hasn't been created yet, create and emite sound
				else
				{	iChannel = g_FMODWrapper.FWRAPPER_PlaySound(FSOUND_FREE, pSample->getSample());
					pObj->createChannel(pSound->getChannelIndex(), iChannel);
				}
				// Setup channel
				g_FMODWrapper.FWRAPPER_3D_SetMinMaxDistance(iChannel, 500, 10000);
				g_FMODWrapper.FWRAPPER_3D_SetAttributes(iChannel, pObj->getPosition(), NULL);
				// Play
				g_FMODWrapper.FWRAPPER_SetPaused(iChannel, FALSE);
			}
		}
	}
}

// Reset triggers
void CTimeSound::resetTriggers( float flReleaseAfterTime )
{	std::map<float,CTimedEvent*>::iterator iterCurrent;
	ForEachIn(iterCurrent,m_TimedEvents)
	{	if(iterCurrent->first >= flReleaseAfterTime)
			((CTimedEventSound*)iterCurrent->second)->releaseTrigger();
		else
			((CTimedEventSound*)iterCurrent->second)->setTrigger();
	}
}

/*
* Timeline Sound Stream
*/
// Update Data
void CTimeSoundStream::updateData( float flTime )
{	// Only shoot off event if we're in play mode
//	if(gViewPort->m_pFXToolPanel->getTimeline()->getTimelineMeter()->getPlayStatus() == TIMELINE_PLAY)
	{	std::map<float,CTimedEvent*>::iterator iterPrev = getTimedEventPrev(flTime);
		// Shoot off trigger
		if(iterPrev != NULL && !((CTimedEventSound*)iterPrev->second)->getTriggerStatus())
		{	CTimedEventSoundStream *pSoundStream = ((CTimedEventSoundStream*)iterPrev->second);
			// Set sound trigger
			pSoundStream->setTrigger();
	
			// Get object and precache element
			CFXObjectSound *pObj = ((CFXObjectSound*)getFXObject());
			if(pObj)
			{	// Get filepath
				string sFileName = gEngfuncs.pfnGetGameDirectory();
				sFileName.append("/");
				sFileName.append(pSoundStream->getStreamFile());
		
				// Close old stream
				if(getStream(pObj))
				{	g_FMODWrapper.FWRAPPER_Stream_Stop(getStream(pObj));
					g_FMODWrapper.FWRAPPER_Stream_Close(getStream(pObj));
				}

				// Create new stream
				setStream(pObj,g_FMODWrapper.FWRAPPER_Stream_Open(sFileName.c_str(), FSOUND_NORMAL | FSOUND_LOOP_OFF,0,0));
				if(getStream(pObj))
				{	// Set time and play
					g_FMODWrapper.FWRAPPER_Stream_SetTime(getStream(pObj),(flTime-iterPrev->first)*1000);
					int iChan = g_FMODWrapper.FWRAPPER_Stream_Play(FSOUND_FREE, getStream(pObj));
					g_FMODWrapper.FWRAPPER_SetPan(iChan, FSOUND_STEREOPAN);
	
					// Set Channel
					setChannel(pObj, iChan);
				}
			}
		}
	}
}

// Reset triggers
void CTimeSoundStream::resetTriggers( void )
{	std::map<float,CTimedEvent*>::iterator iterCurrent;
	ForEachIn(iterCurrent,m_TimedEvents)
	{	((CTimedEventSoundStream*)iterCurrent->second)->releaseTrigger();
	}
}
// Stream
FSOUND_STREAM *CTimeSoundStream1::getStream( CFXObjectSound *pObj )
{	return (pObj ? pObj->getStream1() : NULL);
}
void CTimeSoundStream1::setStream( CFXObjectSound *pObj, FSOUND_STREAM *pStream )
{	if(pObj)
	{	pObj->setStream1(pStream);
	}
}

// Channel
int CTimeSoundStream1::getChannel( CFXObjectSound *pObj )
{	return (pObj ? pObj->getChannel1() : 0);
}
void CTimeSoundStream1::setChannel( CFXObjectSound *pObj, int iChan )
{	if(pObj)
	{	pObj->setChannel1(iChan);
	}
}
// Stream
FSOUND_STREAM *CTimeSoundStream2::getStream( CFXObjectSound *pObj )
{	return (pObj ? pObj->getStream2() : NULL);
}
void CTimeSoundStream2::setStream( CFXObjectSound *pObj, FSOUND_STREAM *pStream )
{	if(pObj)
	{	pObj->setStream2(pStream);
	}
}

// Channel
int CTimeSoundStream2::getChannel( CFXObjectSound *pObj )
{	return (pObj ? pObj->getChannel2() : 0);
}
void CTimeSoundStream2::setChannel( CFXObjectSound *pObj, int iChan )
{	if(pObj)
	{	pObj->setChannel2(iChan);
	}
}

/*
* Timeline Volume
*/

// Calculate previous event
void CTimeVolume::calcTimedEventPrev( float flTime )
{	CFXObjectSound *pObj = ((CFXObjectSound*)getFXObject());
	if(!pObj || !getChannel(pObj))
		return;
	// Get previous event, set volume
	g_FMODWrapper.FWRAPPER_SetVolumeAbsolute(getChannel(pObj), ((CTimedEventVolume*)getTimedEventPrev(flTime)->second)->getVolume());
}

// Calculate interpol event
void CTimeVolume::calcTimedEventInterpol( float flTime )
{	CFXObjectSound *pObj = ((CFXObjectSound*)getFXObject());
	if(!pObj || !getChannel(pObj))
		return;
	// Get before and after
	std::map<float,CTimedEvent*>::iterator iterPrev=NULL,iterAfter=NULL;
	iterPrev = getTimedEventPrev(flTime);
	iterAfter = getTimedEventAfter(flTime);
	// get percent
	float flPercent = getPercentBetween(iterPrev,iterAfter, flTime);
	// get previous and after Volume
	float flPrevVolume = ((CTimedEventVolume*)iterPrev->second)->getVolume();
	float flAfterVolume = ((CTimedEventVolume*)iterAfter->second)->getVolume();
	float flVol = float(flPrevVolume * float(float(1)-flPercent) + flAfterVolume * flPercent);
	// apply changes
	if(pObj)
		g_FMODWrapper.FWRAPPER_SetVolumeAbsolute(getChannel(pObj), int(flVol));
}

// Channel
int CTimeVolume1::getChannel( CFXObjectSound *pObj )
{	return (pObj ? pObj->getChannel1() : 0);
}
void CTimeVolume1::setChannel( CFXObjectSound *pObj, int iChan )
{	if(pObj)
	{	pObj->setChannel1(iChan);
	}
}

// Channel
int CTimeVolume2::getChannel( CFXObjectSound *pObj )
{	return (pObj ? pObj->getChannel2() : 0);
}
void CTimeVolume2::setChannel( CFXObjectSound *pObj, int iChan )
{	if(pObj)
	{	pObj->setChannel2(iChan);
	}
}

/*
* Timeline Position
*/

// Overwrite to account for multiple timelines, only use previous event if after event is on another timeline
// or if previous event is in the form of a constant velocity along the path
bool CTimePosition::usePreviousEventOnly( float flTime )
{	std::map<float,CTimedEvent*>::iterator iterPrev=NULL,iterAfter=NULL;
	// Get before and after timed events
	iterPrev=getTimedEventPrev(flTime);
	iterAfter=getTimedEventAfter(flTime);
	if(iterPrev == NULL)
	{	return false;
	}
	// After event exist
	else if(iterAfter != NULL)
	{	// New: Dont interpolate to after event if they're not on the same path manager as the prev
		if(((CTimedEventPosition*)iterPrev->second)->getPathManager() != ((CTimedEventPosition*)iterAfter->second)->getPathManager())
		{	return true;
		}
		// New: Don't interpolate if previous event is a timed velocity ( impossible to blend from )
		else if(((CTimedEventPosition*)iterPrev->second)->isTimedVelocity())
		{	return true;
		}
		// After event exist, and we want to interpolate to it
		else if(iterAfter->second->canInterpTo())
		{	return false;
		}
	}
	return true;
}

// Calculate previous event
void CTimePosition::calcTimedEventPrev( float flTime )
{	// Get previous event, set data
	std::map<float,CTimedEvent*>::iterator iterTimedPos = getTimedEventPrev(flTime);
	CTimedEventPosition *pTimedPos = ((CTimedEventPosition*)iterTimedPos->second);

	// Timed Velocity
	if(pTimedPos->isTimedVelocity())
	{	// convert
		CTimedEventVelocity *pTimedVelocity = (CTimedEventVelocity*)pTimedPos;

		// Paths arn't the same
		if(getFXObject3D()->getPath() != pTimedVelocity->getPathManager())
		{	// Set path and reset distance
			getFXObject3D()->setPath(pTimedVelocity->getPathManager());
			getFXObject3D()->setDistance(pTimedVelocity->getDistance());
		}
		
		// Calculate and add velocity
	//	float flVelocity = pTimedVelocity->getVelocity()*getTimelineManager()->getTimeDelta();
	//	getFXObject()->setDistance(getFXObject()->getDistance()+flVelocity);
		float flDistance = pTimedVelocity->getDistance();
		flDistance += pTimedVelocity->getVelocity() * (flTime-iterTimedPos->first);
		getFXObject3D()->setDistance(flDistance);

		// Get path
		CPathManager *pPath = getFXObject3D()->getPathSystem()->getManager(getFXObject3D()->getPath());
		if(pPath)
		{	// Get total distance
			float flTotalDist = pPath->getLength();
			// Check for loop
			if(pTimedVelocity->canLoopPath())
			{	// Loop
				while(getFXObject3D()->getDistance() > flTotalDist)
				{	getFXObject3D()->setDistance(getFXObject3D()->getDistance()-flTotalDist);
				}
			}
			// Just cap off if we overstepped
			else
			{	if(getFXObject3D()->getDistance() > flTotalDist)
				{	getFXObject3D()->setDistance(flTotalDist);
				}
			}
		}
	}
	// Regular positional data, just use point normally
	else
	{	getFXObject3D()->setPath(pTimedPos->getPathManager());
		getFXObject3D()->setDistance(pTimedPos->getDistance());
	}
}

// Calculate interpol event
void CTimePosition::calcTimedEventInterpol( float flTime )
{	// Get before and after
	std::map<float,CTimedEvent*>::iterator iterPrev=NULL,iterAfter=NULL;
	iterPrev = getTimedEventPrev(flTime);
	iterAfter = getTimedEventAfter(flTime);
	// get percent
	float flPercent = getPercentBetween(iterPrev,iterAfter, flTime);
	// get previous and after distance, and get path
	int iPath = ((CTimedEventPosition*)iterPrev->second)->getPathManager();
	float flPrevDistance = ((CTimedEventPosition*)iterPrev->second)->getDistance();
	float flAfterDistance = ((CTimedEventPosition*)iterAfter->second)->getDistance();
	// apply changes
	getFXObject3D()->setPath(iPath);
	getFXObject3D()->setDistance(float(flPrevDistance * float(float(1)-flPercent) + flAfterDistance * flPercent));
}

/*
* Timeline Sequence
*/
// Calculate previous event
void CTimedSequence::calcTimedEventPrev( float flTime )
{	string strSquence = ((CTimedEventSequence*)getTimedEventPrev(flTime)->second)->getSequence();
	if(getFXObjectMDL())
	{	CMDLControl *pCtrl = getFXObjectMDL()->getModelControl();
		if(pCtrl)
		{	pCtrl->setSequence(strSquence);
			pCtrl->setInterpolPercent(0);
			pCtrl->setInterpolSequence("");
		}
	}
}

// Calculate interpol event
#define INTERPOL_TIME	float(0.1)
void CTimedSequence::calcTimedEventInterpol( float flTime )
{	// Get before and after
	std::map<float,CTimedEvent*>::iterator iterPrev=NULL,iterAfter=NULL;
	iterPrev = getTimedEventPrev(flTime);
	iterAfter = getTimedEventAfter(flTime);
	string strPrevSeq = ((CTimedEventSequence*)iterPrev->second)->getSequence();
	string strAfterSeq = ((CTimedEventSequence*)iterAfter->second)->getSequence();

	float flDifference = abs(iterAfter->first-flTime);
	CMDLControl *pCtrl = getFXObjectMDL()->getModelControl();
	if(pCtrl)
	{	// Almost hit after sequence, interpolate
		if(flDifference <= INTERPOL_TIME)
		{	float s = 1 - (flDifference/INTERPOL_TIME);
			pCtrl->setSequence(strPrevSeq);
			pCtrl->setInterpolSequence(strAfterSeq);
			pCtrl->setInterpolPercent(s);
		}
		// Dont interpolate, only use prev sequence
		else
		{	pCtrl->setSequence(strPrevSeq);
			pCtrl->setInterpolSequence("");
			pCtrl->setInterpolPercent(0);
		}
	}
}

/*
* Timeline Framrate Multiply (sets model frame as well)
*/

// Get Previous CTimedEventSequence
std::map<float,CTimedEvent*>::iterator CTimedFrameMulti::getPrevSeq( float flTime )
{	CTimedSequence *pTimeSeq = (CTimedSequence*)getFXObject()->getTimelineManager()->getTimeline(TLID_SEQUENCE);
	if(pTimeSeq)
	{	return pTimeSeq->getTimedEventPrev(flTime);
	}
	return NULL;
}

// Calculate no occured event
void CTimedFrameMulti::calcTimedEventNone( float flTime )
{	std::map<float,CTimedEvent*>::iterator iterSeq = getPrevSeq(flTime);
	if(iterSeq == NULL)
	{	return;
	}
	CTimedEventSequence *pSeq = ((CTimedEventSequence*)iterSeq->second);
	float flDurration = flTime-iterSeq->first;
	if(getFXObjectMDL())
	{	CMDLControl *pCtrl = getFXObjectMDL()->getModelControl();
		if(pCtrl)
		{	int iSeq = pCtrl->getSequenceNum();
			float flFrame = flDurration*pCtrl->fpsLookup(iSeq);
			int iMaxFrame = pCtrl->numFramesLookup(iSeq);
			// Check loop
			if(pSeq->canLoop())
			{	while(flFrame >= iMaxFrame)
					flFrame -= iMaxFrame;
			}
			else
			{	if(flFrame > (iMaxFrame-1))
					flFrame = (iMaxFrame-1);
			}
			// Set frame
			pCtrl->setFrame(flFrame);
		}
	}
}

// Calculate previous event
void CTimedFrameMulti::calcTimedEventPrev( float flTime )
{	std::map<float,CTimedEvent*>::iterator iterSeq = getPrevSeq(flTime);
	if(iterSeq == NULL)
	{	return;
	}
	// Get start time
	float flStartTime = iterSeq->first;

	// Get frame multiplier before this sequence
	std::map<float,CTimedEvent*>::iterator iterFirstMulti = getTimedEventPrev(flTime);
	if(iterFirstMulti->first > flStartTime)
	{	std::map<float,CTimedEvent*>::iterator iterPrev;
		while(iterFirstMulti != m_TimedEvents.begin())
		{	iterFirstMulti--;
			if(iterFirstMulti->first <= flStartTime)
			{	break;
			}
		}
	}
	CMDLControl *pCtrl = getFXObjectMDL()->getModelControl();
	if(!pCtrl)
	{	return;
	}
	float flFrame = 0;
	float flFPS = pCtrl->fpsLookup(pCtrl->getSequenceNum());
	bool bUseNext = false;
	while(true)
	{	float flMulti = ((CTimedEventFrameMulti*)iterFirstMulti->second)->getMutli();
		std::map<float,CTimedEvent*>::iterator iterNext = iterFirstMulti;
		iterNext++;
		bool bUseNext = false;
		if(iterNext != m_TimedEvents.end())
		{	// Need to take this one into account, under our specified time
			if(iterNext->first < flTime)
			{	bUseNext = true;
			}
		}
		// Multiple multi's lined up
		if(bUseNext)
		{	float flDurration = 0;
			if(iterSeq->first > iterFirstMulti->first)
				flDurration = iterNext->first-iterSeq->first;
			else
                flDurration = iterNext->first-iterFirstMulti->first;
			iterFirstMulti = iterNext;
			flFrame += flDurration * flFPS * flMulti;
		}
		// That's all, add frame and cut out
		else
		{	float flDurration = 0;
			if(iterSeq->first > iterFirstMulti->first)
				flDurration = flTime-iterSeq->first;
			else
				flDurration = flTime-iterFirstMulti->first;
			flFrame += flDurration * flFPS * flMulti;
			break;
		}
	}
	CTimedEventSequence *pSeq = ((CTimedEventSequence*)iterSeq->second);
	// Check loop
	int iMaxFrame = pCtrl->numFramesLookup(pCtrl->getSequenceNum());
	if(iMaxFrame > 0)
	{
		if(pSeq->canLoop())
		{	while(flFrame >= iMaxFrame)
				flFrame -= iMaxFrame;
		}
		else
		{	if(flFrame > (iMaxFrame-1))
				flFrame = (iMaxFrame-1);
		}
	}
	// Set Frame
	pCtrl->setFrame(flFrame);
}

// Calculate interpol event
void CTimedFrameMulti::calcTimedEventInterpol( float flTime )
{	calcTimedEventPrev(flTime);
}

/*
* Timeline Body Part
*/
void CTimedBodyPart::updateData( float flTime )
{	// Nothing has happend yet, dont update
	if(noOccuredEvent(flTime))
	{	return;
	}
	// Something occured, always set prev bodypart, cant interpolate these
	std::map<float,CTimedEvent*>::iterator iterPrev = getTimedEventPrev(flTime);
	if(iterPrev != NULL)
	{	int iBodyPart = ((CTimedEventBodyPart*)iterPrev->second)->getBodyPart();
		CFXObjectMDL	*pObj = getFXObjectMDL();
		// Set Bodypart
		if(pObj->getModelControl())
			pObj->getModelControl()->setBodyPart(iBodyPart);
	}
}

/*
* Timeline Model Blur
*/
void CTimedModelBlur::updateData( float flTime )
{	// Nothing has happend yet, dont update
	if(noOccuredEvent(flTime))
	{	return;
	}
	// Something occured, always set prev blue effect, cant interpolate these
	std::map<float,CTimedEvent*>::iterator iterPrev = getTimedEventPrev(flTime);
	if(iterPrev != NULL)
	{	bool bBlur = ((CTimedEventModelBlur*)iterPrev->second)->getBlur();
		CFXObjectMDL	*pObj = getFXObjectMDL();
		// Blur enabled, make sure it's on
		if(bBlur)
		{	if(!pObj->getModelBlur())
				pObj->setModelBlur(gModelBlur.addBlur(pObj->getIndex(), 0.05, 0, 3.5));
		}
		// Blur off, make sure it's off
		else
		{	if(pObj->getModelBlur())
			{	gModelBlur.removeBlur(pObj->getModelBlur()->getIndex());
				pObj->setModelBlur(NULL);
			}
		}
	}
}

/*
* Timeline RadialBlur
*/
// Destructor
CTimeRadialBlur::~CTimeRadialBlur()
{	// Disable radial blur
	g_ScreenEffects.getRadialBlur()->setRadialBlurAlpha(0);
	g_ScreenEffects.getRadialBlur()->setRadialBlurStatus(false);
}

// Set Blur
void CTimeRadialBlur::setBlur( float flAlpha )
{	CFXEvent *pEvent = getFXObject()->getFXEvent();
	CFXObjectCamera *pCam = pEvent->getCameraObject();
	// Only use blur when we're inside of the camera
	if(pCam && pCam->canUse(pEvent->getTime()-pEvent->getStartTime()))
	{	if(flAlpha>0)
		{	g_ScreenEffects.getRadialBlur()->setRadialBlurAlpha(flAlpha);
			g_ScreenEffects.getRadialBlur()->setRadialBlurStatus(true);
		}
		else
		{	g_ScreenEffects.getRadialBlur()->setRadialBlurAlpha(0);
			g_ScreenEffects.getRadialBlur()->setRadialBlurStatus(false);
		}
	}
}

// Calculate previous event
void CTimeRadialBlur::calcTimedEventPrev( float flTime )
{	// Get previous event, set data
	setBlur(((CTimedEventRadialBlur*)getTimedEventPrev(flTime)->second)->getBlur());
}

// Calculate interpol event
void CTimeRadialBlur::calcTimedEventInterpol( float flTime )
{	// Get before and after
	std::map<float,CTimedEvent*>::iterator iterPrev=NULL,iterAfter=NULL;
	iterPrev = getTimedEventPrev(flTime);
	iterAfter = getTimedEventAfter(flTime);
	// get percent
	float flPercent = getPercentBetween(iterPrev,iterAfter, flTime);
	// get previous and after alpha
	float flPrevBlur = ((CTimedEventRadialBlur*)iterPrev->second)->getBlur();
	float flAfterBlur = ((CTimedEventRadialBlur*)iterAfter->second)->getBlur();
	// apply changes
	setBlur(float(flPrevBlur * float(float(1)-flPercent) + flAfterBlur * flPercent));
}