/*==LICENSE==*
CyanWorlds.com Engine - MMOG client, server and tools
Copyright (C) 2011 Cyan Worlds, Inc.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
You can contact Cyan Worlds, Inc. by email legal@cyan.com
or by snail mail at:
Cyan Worlds, Inc.
14617 N Newport Hwy
Mead, WA 99021
*==LICENSE==*/
/////////////////////////////////////////////////////////////////////////////////////////
//
// INCLUDES
//
/////////////////////////////////////////////////////////////////////////////////////////
// singular
#include "plAGAnimInstance.h"
// local
#include "plAGAnim.h"
#include "plAGModifier.h"
#include "plAGMasterMod.h"
// global
#include "hsTimer.h" // just when debugging for GetSysSeconds
// other
#include "../pnNetCommon/plSDLTypes.h"
#include "../plMessage/plAnimCmdMsg.h"
#include "../plMessage/plOneShotCallbacks.h"
#include "../plModifier/plSDLModifier.h"
#include "../plSDL/plSDL.h"
/////////////////////////////////////////////////////////////////////////////////////////
//
// FLAGS
//
/////////////////////////////////////////////////////////////////////////////////////////
// enable this to show blend trees before and after attaches and detaches
// #define SHOW_AG_CHANGES
/////////////////////////////////////////////////////////////////////////////////////////
//
// STATIC
//
/////////////////////////////////////////////////////////////////////////////////////////
#ifdef TRACK_AG_ALLOCS
extern const char *gGlobalAnimName = nil;
extern const char *gGlobalChannelName = nil;
#endif // TRACK_AG_ALLOCS
/////////////////////////////////////////////////////////////////////////////////////////
//
// plAGAnimInstance
//
/////////////////////////////////////////////////////////////////////////////////////////
// ctor -------------------------------------------------------------------
// -----
plAGAnimInstance::plAGAnimInstance(plAGAnim * anim, plAGMasterMod * master,
hsScalar blend, UInt16 blendPriority, hsBool cache,
bool useAmplitude)
: fAnimation(anim),
fMaster(master),
fBlend(blend),
fAmplitude(useAmplitude ? 1.0f : -1.0f)
{
int i;
fTimeConvert = nil;
plScalarChannel *timeChan = nil;
#ifdef TRACK_AG_ALLOCS
gGlobalAnimName = anim->GetName(); // for debug tracking...
#endif // TRACK_AG_ALLOCS
plATCAnim *atcAnim = plATCAnim::ConvertNoRef(anim);
if (atcAnim)
{
fTimeConvert = TRACKED_NEW plAnimTimeConvert();
fTimeConvert->Init(atcAnim, this, master);
timeChan = TRACKED_NEW plATCChannel(fTimeConvert);
}
else
{
timeChan = TRACKED_NEW plScalarSDLChannel(anim->GetLength());
fSDLChannels.push_back((plScalarSDLChannel *)timeChan);
}
int nInChannels = anim->GetChannelCount();
fCleanupChannels.push_back(timeChan);
#ifdef SHOW_AG_CHANGES
hsStatusMessageF("\nAbout to Attach anim <%s>", GetName());
fMaster->DumpAniGraph("bone_pelvis", false, hsTimer::GetSysSeconds());
#endif
for (i = 0; i < nInChannels; i++)
{
plAGApplicator * app = fAnimation->GetApplicator(i);
plAGChannel * inChannel = app->GetChannel();
const char * channelName = app->GetChannelName();
plAGModifier * channelMod = master->GetChannelMod(channelName);
if(channelMod) {
#ifdef TRACK_AG_ALLOCS
gGlobalChannelName = channelName;
#endif // TRACK_AG_ALLOCS
// we're going to be accumulating a chain of channels.
// curChannel will always point to the top one...
plAGChannel *topNode = inChannel;
if(cache)
{
topNode = topNode->MakeCacheChannel(fTimeConvert);
IRegisterDetach(channelName, topNode);
}
if(useAmplitude)
{
// amplitude is rarely used and expensive, so only alloc if asked
// first build a static copy of the incoming channel...
plAGChannel *zeroState = inChannel->MakeZeroState();
IRegisterDetach(channelName, zeroState);
// now make a blend node to blend the anim with its static copy
topNode = zeroState->MakeBlend(topNode, &fAmplitude, -1);
}
// make a time scaler to localize time for this instance
topNode = topNode->MakeTimeScale(timeChan);
IRegisterDetach(channelName, topNode);
channelMod->MergeChannel(app, topNode, &fBlend, this, blendPriority);
}
else
hsAssert(false, "Adding an animation with an invalid channel.");
}
fFadeBlend = fFadeAmp = false;
#ifdef TRACK_AG_ALLOCS
gGlobalAnimName = nil;
#endif // TRACK_AG_ALLOCS
}
// dtor -----------------------------
// -----
plAGAnimInstance::~plAGAnimInstance()
{
delete fTimeConvert;
}
// SearchForGlobals ---------------------
// -----------------
void plAGAnimInstance::SearchForGlobals()
{
const plAgeGlobalAnim *ageAnim = plAgeGlobalAnim::ConvertNoRef(fAnimation);
if (ageAnim != nil && fSDLChannels.size() > 0)
{
extern const plSDLModifier *ExternFindAgeSDL();
const plSDLModifier *sdlMod = ExternFindAgeSDL();
if (!sdlMod)
return;
plSimpleStateVariable *var = sdlMod->GetStateCache()->FindVar(ageAnim->GetGlobalVarName());
if (!var)
return;
sdlMod->AddNotifyForVar(fMaster->GetKey(), ageAnim->GetGlobalVarName(), 0);
int i;
for (i = 0; i < fSDLChannels.size(); i++)
fSDLChannels[i]->SetVar(var);
}
}
void plAGAnimInstance::IRegisterDetach(const char *channelName, plAGChannel *channel)
{
plDetachMap::value_type newPair(channelName, channel);
fManualDetachChannels.insert(newPair);
}
// SetCurrentTime ---------------------------------------------------------------
// ---------------
void plAGAnimInstance::SetCurrentTime(hsScalar localT, hsBool jump /* = false */)
{
if (fTimeConvert)
fTimeConvert->SetCurrentAnimTime(localT, jump);
}
// SeekRelative ------------------------------------
// -------------
void plAGAnimInstance::SeekRelative (hsScalar delta, hsBool jump)
{
if(fTimeConvert)
{
hsScalar now = fTimeConvert->CurrentAnimTime();
fTimeConvert->SetCurrentAnimTime (now + delta, jump);
}
}
// Detach ---------------------
// -------
void plAGAnimInstance::Detach()
{
fMaster->DetachAnimation(this);
}
// DetachChannels ---------------------
// ---------------
void plAGAnimInstance::DetachChannels()
{
#ifdef SHOW_AG_CHANGES
hsStatusMessageF("\nAbout to DETACH anim <%s>", GetName());
fMaster->DumpAniGraph("bone_pelvis", false, hsTimer::GetSysSeconds());
#endif
plDetachMap::iterator i = fManualDetachChannels.begin();
while(i != fManualDetachChannels.end())
{
const char *channelName = (*i).first;
plAGModifier *channelMod = fMaster->GetChannelMod(channelName, true);
if(channelMod)
{
do {
plAGChannel *channel = (*i).second;
channelMod->DetachChannel(channel);
} while (i != fManualDetachChannels.end() && (*++i).first == channelName);
} else {
do {
} while (i != fManualDetachChannels.end() && (*++i).first == channelName);
}
}
int cleanCount = fCleanupChannels.size();
hsAssert(cleanCount, "No time controls when deleting animation");
for (int j = 0; j < cleanCount; j++)
{
delete fCleanupChannels[j];
}
fCleanupChannels.clear();
#ifdef SHOW_AG_CHANGES
hsStatusMessageF("\nFinished DETACHING anim <%s>", GetName());
fMaster->DumpAniGraph("bone_pelvis", false, hsTimer::GetSysSeconds());
#endif
}
// SetBlend ---------------------------------------
// ---------
hsScalar plAGAnimInstance::SetBlend(hsScalar blend)
{
float oldBlend = fBlend.Value(0.0, true);
if(oldBlend != blend &&
(oldBlend == 0.0f ||
blend == 0.0f ||
oldBlend == 1.0f ||
blend == 1.0f))
{
fMaster->SetNeedCompile(true);
}
fBlend.Set(blend);
return blend;
}
// GetBlend -------------------------
// ---------
hsScalar plAGAnimInstance::GetBlend()
{
return fBlend.Value(0);
}
// SetAmplitude -------------------------------------
// -------------
hsScalar plAGAnimInstance::SetAmplitude(hsScalar amp)
{
if(fAmplitude.Get() != -1.0f)
{
fAmplitude.Set(amp);
}
return amp;
}
// GetAmplitude -------------------------
// -------------
hsScalar plAGAnimInstance::GetAmplitude()
{
return fAmplitude.Value(0);
}
// GetName -----------------------------
// --------
const char * plAGAnimInstance::GetName()
{
if(fAnimation)
return fAnimation->GetName();
else
return nil;
}
// SetLoop ----------------------------------
// --------
void plAGAnimInstance::SetLoop(hsBool status)
{
if (fTimeConvert)
fTimeConvert->Loop(status);
}
// HandleCmd ----------------------------------------
// ----------
hsBool plAGAnimInstance::HandleCmd(plAnimCmdMsg *msg)
{
if (fTimeConvert)
return fTimeConvert->HandleCmd(msg);
return false;
}
// IsFinished -----------------------
// -----------
hsBool plAGAnimInstance::IsFinished()
{
if (fTimeConvert)
return fTimeConvert->IsStopped();
return false;
}
// IsAtEnd -----------------------
// --------
hsBool plAGAnimInstance::IsAtEnd()
{
if(fTimeConvert)
{
return fTimeConvert->CurrentAnimTime() == fTimeConvert->GetEnd();
}
else
return false;
}
// Start -----------------------------------
// ------
void plAGAnimInstance::Start(double timeNow)
{
if (fTimeConvert)
{
if (timeNow < 0)
fTimeConvert->Start();
else
fTimeConvert->Start(timeNow);
}
}
// Stop ---------------------
// -----
void plAGAnimInstance::Stop()
{
if (fTimeConvert)
fTimeConvert->Stop();
}
// AttachCallbacks --------------------------------------------------
// ----------------
void plAGAnimInstance::AttachCallbacks(plOneShotCallbacks *callbacks)
{
const plATCAnim *anim = plATCAnim::ConvertNoRef(fAnimation);
if (callbacks && anim)
{
plAnimCmdMsg animMsg;
animMsg.SetCmd(plAnimCmdMsg::kAddCallbacks);
for (int i = 0; i < callbacks->GetNumCallbacks(); i++)
{
plOneShotCallbacks::plOneShotCallback& cb = callbacks->GetCallback(i);
plEventCallbackMsg *eventMsg = TRACKED_NEW plEventCallbackMsg;
eventMsg->AddReceiver(cb.fReceiver);
eventMsg->fRepeats = 0;
eventMsg->fUser = cb.fUser;
if (cb.fMarker)
{
float marker = anim->GetMarker(cb.fMarker);
hsAssert(marker != -1, "Bad marker name");
eventMsg->fEventTime = marker;
eventMsg->fEvent = kTime;
}
else
{
eventMsg->fEvent = kStop;
}
animMsg.AddCallback(eventMsg);
hsRefCnt_SafeUnRef(eventMsg);
}
fTimeConvert->HandleCmd(&animMsg);
}
}
// ProcessFade -------------------------------------
// ------------
void plAGAnimInstance::ProcessFade(hsScalar elapsed)
{
if (fFadeBlend) {
hsScalar newBlend = ICalcFade(fFadeBlend, GetBlend(), fFadeBlendGoal, fFadeBlendRate, elapsed);
SetBlend(newBlend);
if(fFadeDetach && (newBlend == fFadeBlendGoal) && (fFadeBlendGoal == 0.0f) )
{
fMaster->DetachAnimation(this);
return;
}
}
if (fFadeAmp && fAmplitude.Get() != -1.0f) {
hsScalar curAmp = GetAmplitude();
hsScalar newAmp = ICalcFade(fFadeAmp, curAmp, fFadeAmpGoal, fFadeAmpRate, elapsed);
SetAmplitude(newAmp);
}
}
// ICalcFade ---------------------------------------------------------------------
// ----------
hsScalar plAGAnimInstance::ICalcFade(hsBool &fade, hsScalar curVal, hsScalar goal,
hsScalar rate, hsScalar elapsed)
{
hsScalar newVal;
hsScalar curStep = rate * elapsed;
if(rate > 0) {
newVal = __min(goal, curVal + curStep);
} else {
newVal = __max(goal, curVal + curStep);
}
if(newVal == goal)
{
fade = false;
fMaster->DirtySynchState(kSDLAGMaster, 0); // send SDL state update to server
}
return newVal;
}
// FadeAndDetach -------------------------------------------------
// --------------
void plAGAnimInstance::FadeAndDetach(hsScalar goal, hsScalar rate)
{
ISetupFade(goal, rate, true, kFadeBlend);
}
// Fade --------------------------------------------------------------------------------
// -----
void plAGAnimInstance::Fade(hsScalar goal, hsScalar rate, UInt8 type /* = kFadeBlend */)
{
ISetupFade(goal, rate, false, type);
}
// ISetupFade --------------------------------------------------------------------------
// -----------
void plAGAnimInstance::ISetupFade(hsScalar goal, hsScalar rate, bool detach, UInt8 type)
{
if (rate == 0)
{
if (type == kFadeBlend)
{
SetBlend(goal);
fFadeBlend = false;
if(detach) {
fMaster->DetachAnimation(this);
}
}
else if (type == kFadeAmp)
{
SetAmplitude(goal);
fFadeAmp = false;
}
return;
}
rate = (rate > 0 ? rate : -rate); // For old code that sends negative values
hsScalar curVal = 0;
switch (type)
{
case kFadeBlend:
curVal = GetBlend();
break;
case kFadeAmp:
curVal = GetAmplitude();
break;
}
if (curVal > goal)
rate = -rate;
switch (type)
{
case kFadeBlend:
fFadeBlend = true;
fFadeBlendGoal = goal;
fFadeBlendRate = rate;
fFadeDetach = detach;
break;
case kFadeAmp:
fFadeAmp = true;
fFadeAmpGoal = goal;
fFadeAmpRate = rate;
fFadeDetach = false; // only detach on blend fades, for the moment.
break;
}
}
class agAlloc
{
public:
agAlloc(plAGChannel *object, const char *chanName, const char *animName, UInt16 classIndex)
: fObject(object),
fClassIndex(classIndex)
{
fChannelName = hsStrcpy(chanName);
fAnimName = hsStrcpy(animName);
}
~agAlloc()
{
delete[] fChannelName;
delete[] fAnimName;
}
plAGChannel *fObject;
char *fChannelName;
char *fAnimName;
UInt16 fClassIndex;
};
typedef std::map agAllocMap;
static agAllocMap gAGAllocs;
void RegisterAGAlloc(plAGChannel *object, const char *chanName, const char *animName, UInt16 classIndex)
{
gAGAllocs[object] = TRACKED_NEW agAlloc(object, chanName, animName, classIndex);
}
void DumpAGAllocs()
{
agAllocMap::iterator i = gAGAllocs.begin();
agAllocMap::iterator theEnd = gAGAllocs.end();
hsStatusMessage("DUMPING AG ALLOCATIONS ================================================");
for ( ; i != theEnd; i++)
{
agAlloc * al = (*i).second;
UInt16 realClassIndex = al->fObject->ClassIndex();
hsStatusMessageF("agAlloc: an: %s ch: %s, cl: %s", al->fAnimName, al->fChannelName, plFactory::GetNameOfClass(realClassIndex));
}
// it's not fast but it's safe and simple..
i = gAGAllocs.begin();
while(i != gAGAllocs.end())
{
agAlloc * al = (*i).second;
delete al;
i = gAGAllocs.erase(i);
}
hsStatusMessage("FINISHED DUMPING AG ALLOCATIONS *********************************************");
}
void UnRegisterAGAlloc(plAGChannel *object)
{
agAllocMap::iterator i = gAGAllocs.find(object);
if(i != gAGAllocs.end())
{
agAlloc * al = (*i).second;
gAGAllocs.erase(i);
delete al;
}
}