You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
622 lines
16 KiB
622 lines
16 KiB
/*==LICENSE==* |
|
|
|
CyanWorlds.com Engine - MMOG client, server and tools |
|
Copyright (C) 2011 Cyan Worlds, Inc. |
|
|
|
This program is free software: you can redistribute it and/or modify |
|
it under the terms of the GNU General Public License as published by |
|
the Free Software Foundation, either version 3 of the License, or |
|
(at your option) any later version. |
|
|
|
This program is distributed in the hope that it will be useful, |
|
but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
GNU General Public License for more details. |
|
|
|
You should have received a copy of the GNU General Public License |
|
along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
|
|
Additional permissions under GNU GPL version 3 section 7 |
|
|
|
If you modify this Program, or any covered work, by linking or |
|
combining it with any of RAD Game Tools Bink SDK, Autodesk 3ds Max SDK, |
|
NVIDIA PhysX SDK, Microsoft DirectX SDK, OpenSSL library, Independent |
|
JPEG Group JPEG library, Microsoft Windows Media SDK, or Apple QuickTime SDK |
|
(or a modified version of those libraries), |
|
containing parts covered by the terms of the Bink SDK EULA, 3ds Max EULA, |
|
PhysX SDK EULA, DirectX SDK EULA, OpenSSL and SSLeay licenses, IJG |
|
JPEG Library README, Windows Media SDK EULA, or QuickTime SDK EULA, the |
|
licensors of this Program grant you additional |
|
permission to convey the resulting work. Corresponding Source for a |
|
non-source form of such a combination shall include the source code for |
|
the parts of OpenSSL and IJG JPEG Library used as well as that of the covered |
|
work. |
|
|
|
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<plAGChannel *, agAlloc *> 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; |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|