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.
 
 
 
 
 

811 lines
20 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==*/
// singular
#include "plAnimStage.h"
// local
#include "plAvatarMgr.h"
#include "plAGAnim.h"
#include "plArmatureMod.h"
#include "plAGAnimInstance.h"
#include "plMatrixChannel.h"
#include "plAvBrainGeneric.h"
#include "plMultiStageBehMod.h"
// global
#include "hsUtils.h"
#include "hsStlUtils.h"
#include "hsResMgr.h"
#include "hsTimer.h"
#include <cstdio>
// other
#include "../pnSceneObject/plSceneObject.h"
#include "../plMessage/plSimStateMsg.h"
#include "../plStatusLog/plStatusLog.h"
#include "../pnMessage/plNotifyMsg.h"
#include "../plPipeline/plDebugText.h"
#ifdef DEBUG_MULTISTAGE
#include "plAvatarMgr.h"
#include "../plStatusLog/plStatusLog.h"
#endif
class plAGAnim;
// PLANIMSTAGE default ctor
plAnimStage::plAnimStage()
: fAnimName(nil),
fNotify(0),
fArmature(nil),
fBrain(nil),
fForwardType(kForwardNone),
fBackType(kBackNone),
fAdvanceType(kAdvanceNone),
fRegressType(kRegressNone),
fLoops(0),
fAnimInstance(nil),
fLocalTime(0.0f),
fLength(0.0f),
fCurLoop(0),
fAttached(false),
fDoAdvanceTo(false),
fAdvanceTo(0),
fDoRegressTo(false),
fRegressTo(0),
fMod(nil),
fSentNotifies(0),
fReverseOnIdle(false),
fDone(false)
{
}
plAnimStage::plAnimStage(const char *animName, UInt8 notify)
: fNotify(notify),
fArmature(nil),
fBrain(nil),
fForwardType(kForwardAuto), // different from default
fBackType(kBackNone),
fAdvanceType(kAdvanceAuto), // different from default
fRegressType(kRegressNone),
fLoops(0),
fAnimInstance(nil),
fLocalTime(0.0f),
fLength(0.0f),
fCurLoop(0),
fAttached(false),
fDoAdvanceTo(false),
fAdvanceTo(0),
fDoRegressTo(false),
fRegressTo(0),
fMod(nil),
fSentNotifies(0),
fReverseOnIdle(false),
fDone(false)
{
fAnimName = hsStrcpy(animName);
}
// PLANIMSTAGE canonical ctor
plAnimStage::plAnimStage(const char *animName,
UInt8 notify,
ForwardType forward,
BackType back,
AdvanceType advance,
RegressType regress,
int loops)
: fArmature(nil),
fBrain(nil),
fNotify(notify),
fForwardType(forward),
fBackType(back),
fAdvanceType(advance),
fRegressType(regress),
fLoops(loops),
fAnimInstance(nil),
fLocalTime(0.0f),
fLength(0.0f),
fCurLoop(0),
fAttached(false),
fDoAdvanceTo(false),
fAdvanceTo(0),
fDoRegressTo(false),
fRegressTo(0),
fMod(nil),
fSentNotifies(0),
fReverseOnIdle(false),
fDone(false)
{
fAnimName = hsStrcpy(animName);
}
plAnimStage::plAnimStage(const char *animName,
UInt8 notify,
ForwardType forward,
BackType back,
AdvanceType advance,
RegressType regress,
int loops,
bool doAdvanceTo,
int advanceTo,
bool doRegressTo,
int regressTo)
: fArmature(nil),
fBrain(nil),
fNotify(notify),
fForwardType(forward),
fBackType(back),
fAdvanceType(advance),
fRegressType(regress),
fLoops(loops),
fAnimInstance(nil),
fLocalTime(0.0f),
fLength(0.0f),
fCurLoop(0),
fAttached(false),
fDoAdvanceTo(doAdvanceTo),
fAdvanceTo(advanceTo),
fDoRegressTo(doRegressTo),
fRegressTo(regressTo),
fMod(nil),
fSentNotifies(0),
fReverseOnIdle(false),
fDone(false)
{
fAnimName = hsStrcpy(animName);
}
// PLANIMSTAGE dtor
plAnimStage::~plAnimStage()
{
if(fAnimName)
delete[] fAnimName;
hsAssert(fAnimInstance == nil, "plAnimStage still has anim instance during destruction. (that's bad.)");
// we could delete the animation instance here, but it should have been deleted already...
// *** check back in a while....
}
// operator= ----------------------------------------------------
// ----------
const plAnimStage& plAnimStage::operator=(const plAnimStage& src)
{
fAnimName = hsStrcpy(src.fAnimName);
fNotify = src.fNotify;
fForwardType = src.fForwardType;
fBackType = src.fBackType;
fAdvanceType = src.fAdvanceType;
fRegressType = src.fRegressType;
fLoops = src.fLoops;
fDoAdvanceTo = src.fDoAdvanceTo;
fAdvanceTo = src.fAdvanceTo;
fDoRegressTo = src.fDoRegressTo;
fRegressTo = src.fRegressTo;
fMod = src.fMod;
fAnimInstance = nil;
fLocalTime = 0.0f;
fLength = 0.0f;
fCurLoop = 0;
fAttached = false;
fReverseOnIdle = src.fReverseOnIdle;
return *this;
}
// attach --------------------------------------------------------------------------------------------------------
// -------
plAGAnimInstance * plAnimStage::Attach(plArmatureMod *armature, plArmatureBrain *brain, float initialBlend, double time)
{
// NOTE: you need to be able to detach an animstage and then re-attach it and have
// it wind up in exactly the same state it was in before - for loading and saving.
fBrain = brain;
fSentNotifies = 0;
fArmature = armature;
if(fAnimInstance)
{
fAnimInstance->SetBlend(initialBlend);
} else {
plAGAnim *anim = armature->FindCustomAnim(fAnimName);
if(anim)
{
fLength = anim->GetEnd();
fAnimInstance = armature->AttachAnimationBlended(anim, initialBlend);
fAnimInstance->SetCurrentTime(fLocalTime);
#ifdef DEBUG_MULTISTAGE
char sbuf[256];
sprintf(sbuf,"AnimStage::Attach - attaching stage %s",fAnimName);
plAvatarMgr::GetInstance()->GetLog()->AddLine(sbuf);
#endif
} else {
char buf[256];
sprintf(buf, "Can't find animation <%s> for animation stage. Anything could happen.", fAnimName);
hsAssert(false, buf);
#ifdef DEBUG_MULTISTAGE
plAvatarMgr::GetInstance()->GetLog()->AddLine(buf);
#endif
}
}
if(fAnimInstance)
{
fAnimInstance->Stop(); // we'll be setting the time directly.
fAnimatedHandle = (fAnimInstance->GetAnimation()->GetChannel("Handle") != nil);
fAttached = true;
// this is too early to send the enter notify. we're attached, but we may not
// have faded in yet.
// XXX ISendNotify(kNotifyEnter, proEventData::kEnterStage, armature, brain);
}
return fAnimInstance;
}
// SENDNOTIFY
hsBool plAnimStage::ISendNotify(UInt32 notifyMask, UInt32 notifyType, plArmatureMod *armature, plArmatureBrain *brain)
{
// make sure the user has requested this type of notify
if(fNotify & notifyMask)
{
plKey avKey = armature->GetTarget(0)->GetKey();
if (fMod)
avKey = fMod->GetKey();
plNotifyMsg *msg = TRACKED_NEW plNotifyMsg();
msg->SetSender(avKey);
if (fMod)
{
msg->SetBCastFlag(plMessage::kNetPropagate, fMod->NetProp());
msg->SetBCastFlag(plMessage::kNetForce, fMod->NetForce());
}
else
{
msg->SetBCastFlag(plMessage::kNetPropagate, false);
msg->SetBCastFlag(plMessage::kNetForce, false);
}
plAvBrainGeneric *genBrain = plAvBrainGeneric::ConvertNoRef(brain);
int stageNum = genBrain ? genBrain->GetStageNum(this) : -1;
msg->AddMultiStageEvent(stageNum, notifyType, armature->GetTarget(0)->GetKey());
if (stageNum < 0 || !genBrain || !genBrain->RelayNotifyMsg(msg) )
{
msg->UnRef(); // couldn't send; destroy...
}
return true;
}
return false;
}
// DETACH
hsBool plAnimStage::Detach(plArmatureMod *armature)
{
hsBool result = false;
#ifdef DEBUG_MULTISTAGE
char sbuf[256];
sprintf(sbuf,"AnimStage::Detach - detaching stage %s",fAnimName);
plAvatarMgr::GetInstance()->GetLog()->AddLine(sbuf);
#endif
// hsStatusMessageF("Detaching plAnimStage <%s>", fAnimName);
if(fArmature) {
fArmature = nil;
if(fAnimInstance) {
armature->DetachAnimation(fAnimInstance); // detach instantly
fAnimInstance = nil;
result = true;
}
#ifdef DEBUG_MULTISTAGE
} else {
char sbuf[256];
sprintf(sbuf,"AnimStage::Detach: stage already detached");
plAvatarMgr::GetInstance()->GetLog()->AddLine(sbuf);
#endif
// hsStatusMessageF("Detach: stage already detached.");
}
fBrain = nil;
fAttached = false;
return result;
}
void plAnimStage::Reset(double time, plArmatureMod *avMod, bool atStart)
{
if(atStart)
SetLocalTime(0.0f, true);
else
SetLocalTime(fLength, true);
avMod->GetRootAnimator()->Reset(time);
}
void plAnimStage::ResetAtTime(double globalTime, float localTime, plArmatureMod *avMod)
{
SetLocalTime(localTime, true);
avMod->GetRootAnimator()->Reset(globalTime);
}
// MoveRelative ------------------------------
// -------------
// A true result means that the stage is done.
bool plAnimStage::MoveRelative(double time, float delta, float &overage, plArmatureMod *avMod)
{
bool result; // true means the stage is done
if(fLocalTime == 0.0f && delta >= 0.0f && !hsCheckBits(fSentNotifies, kNotifyEnter))
{
// we send the "enter" notify if we're at the start and either moving forward
// or standing still.
ISendNotify(kNotifyEnter, proEventData::kEnterStage, avMod, fBrain);
hsSetBits(fSentNotifies, kNotifyEnter);
}
// aborting...
if( fAdvanceType == kAdvanceOnMove && (avMod->HasMovementFlag() || avMod->ExitModeKeyDown()))
{ // special case: advance when any key is pressed, regardless of position in stage.
ISendNotify(kNotifyAdvance, proEventData::kAdvanceNextStage, avMod, fBrain);
result = true;
} else {
if(delta == 0.0f)
{
return false;
}
else
if(delta < 0.0f)
result = IMoveBackward(time, delta, overage, avMod);
else
result = IMoveForward(time, delta, overage, avMod);
}
return result;
}
// IMoveBackward ------------------------------------------------------------------------------
// --------------
bool plAnimStage::IMoveBackward(double time, float delta, float &overrun, plArmatureMod *avMod)
{
if (fLocalTime <= 0 && fRegressType == kRegressNone)
{
// If we're at the beginning, but not allowed to regress, we don't want to keep processing
// (and firing triggers).
return false;
}
float target = fLocalTime + delta;
bool infiniteLoop = fLoops == -1;
bool loopsRemain = fCurLoop > 0 || infiniteLoop;
// If we don't have this animation, just pretend to have worked.
// Otherwise, we crash the client.
if (!fAnimInstance)
{
hsAssert(false, "AnimInstance nil");
return true;
}
// This must be here before we set the local time.
if (fAnimInstance->GetTimeConvert())
fAnimInstance->GetTimeConvert()->Backwards();
if(target < 0)
{
SetLocalTime(0); // animation to beginning
avMod->GetRootAGMod()->Apply(time); // move avatar to beginning
if(loopsRemain)
{
// If a callback is on the last frame, it'll get triggered twice. Once for setting
// the anim at the end, and once when we play again. So we don't fire callbacks here.
SetLocalTime(fLength, true); // animation wraps to end
avMod->GetRootAnimator()->Reset(time); // reset the root animator at the end
fCurLoop = infiniteLoop ? 0 : fCurLoop - 1;
target = fLength - (fmodf(-target, fLength)); // modularize negative number to discard
// extra loops (only one allowed)
} else {
// overrun = target + fLength;
// now we want to make sure that overrun goes negative when appropriate, rather than modularizing
overrun = target;
fDone = true;
return ITryRegress(avMod);
}
}
overrun = 0.0f;
fLocalTime = target;
fAnimInstance->SetCurrentTime(fLocalTime);
return false; // not done
}
// IMoveForward ------------------------------------------------------------------------------
// -------------
// It's currently not supported to advance the animation by more than its length in one frame.
// It wouldn't be too hard to add, but it clutters things up and it hasn't been shown to
// be necessary.
bool plAnimStage::IMoveForward(double time, float delta, float &overrun, plArmatureMod *avMod)
{
if (fLocalTime >= fLength && fAdvanceType == kAdvanceNone)
{
// If we're at the end, but not allowed to advance, we don't want to keep processing
// (and firing triggers).
return false;
}
// first get the target time in local time, ignoring overruns
float target = fLocalTime + delta;
// If we don't have this animation, just pretend to have worked.
// Otherwise, we crash the client.
if (!fAnimInstance)
{
hsAssert(false, "AnimInstance nil");
return true;
}
if (fAnimInstance->GetTimeConvert())
fAnimInstance->GetTimeConvert()->Forewards();
if (target > fLength)
{
// we're going to the end for sure, so do that first
SetLocalTime(fLength);
// we're going to swap in a new animation before the next eval, so force
// an apply of this one to make sure the avatar gets the necessary movement.
// we only apply on the root node to get the movement -- no need to animate
// the fingers as they'll be overwritten when the next animation is swapped in.
avMod->GetRootAGMod()->Apply(time);
// are there *any* loops to be had?
bool loopsRemain = fCurLoop < fLoops || fLoops == -1;
if(loopsRemain)
{
SetLocalTime(0.0f, true); // animation back to beginning
avMod->GetRootAnimator()->Reset(time); // reset the root animator's frame cache
fCurLoop++;
target = fmodf(target, fLength); // discard extra loops (only one at a time allowed)
// target -= fLength;
} else {
overrun = target - fLength;
fDone = true;
return ITryAdvance(avMod);
}
}
overrun = 0.0f;
fLocalTime = target;
avMod->GetRootAGMod()->Apply(time);
fAnimInstance->SetCurrentTime(fLocalTime);
return false; // not done
}
bool plAnimStage::ITryAdvance(plArmatureMod *avMod)
{
bool stageDone = false;
// hsStatusMessageF("Sending advance message for stage <%s>\n", fAnimName);
if(fAdvanceType == kAdvanceAuto || fAdvanceType == kAdvanceOnMove) {
stageDone = true;
}
if(!hsCheckBits(fSentNotifies, kNotifyAdvance))
{
// we send the advance message at the point where we *would* advance, whether
// or not we actually do. this is misleading but better suited to actual current usage.
// we may want to rename this to "ReachedStageEnd"
ISendNotify(kNotifyAdvance, proEventData::kAdvanceNextStage, avMod, fBrain);
hsSetBits(fSentNotifies, kNotifyAdvance);
}
return stageDone;
}
bool plAnimStage::ITryRegress(plArmatureMod *avMod)
{
bool stageDone = false;
// we send the advance message at the point where we *would* advance, whether
// or not we actually do. this is misleading but better suited to actual current usage.
// we may want to rename this to "ReachedStageEnd"
ISendNotify(kNotifyRegress, proEventData::kRegressPrevStage, avMod, fBrain);
// hsStatusMessageF("Sending regress message for stage <%s>\n", fAnimName);
if(fRegressType == kRegressAuto) {
stageDone = true;
}
return stageDone;
}
// GETANIMNAME
const char * plAnimStage::GetAnimName()
{
return fAnimName;
}
// GETFORWARDTYPE
plAnimStage::ForwardType plAnimStage::GetForwardType()
{
return fForwardType;
}
// SETFORWARDTYPE
void plAnimStage::SetForwardType(ForwardType t)
{
fForwardType = t;
}
// GETBACKTYPE
plAnimStage::BackType plAnimStage::GetBackType()
{
return fBackType;
}
// SETBACKTYPE
void plAnimStage::SetBackType(BackType t)
{
fBackType = t;
}
// GETADVANCETYPE
plAnimStage::AdvanceType plAnimStage::GetAdvanceType()
{
return fAdvanceType;
}
// SETADVANCETYPE
void plAnimStage::SetAdvanceType(AdvanceType t)
{
fAdvanceType = t;
}
// GETREGRESSTYPE
plAnimStage::RegressType plAnimStage::GetRegressType()
{
return fRegressType;
}
// SETREGRESSTYPE
void plAnimStage::SetRegresstype(RegressType t)
{
fRegressType = t;
}
// GETNOTIFYFLAGS
UInt32 plAnimStage::GetNotifyFlags()
{
return fNotify;
}
// SETNOTIFYFLAGS
void plAnimStage::SetNotifyFlags(UInt32 newFlags)
{
fNotify = (UInt8)newFlags;
}
// GETNUMLOOPS
int plAnimStage::GetNumLoops()
{
return fLoops;
}
// SETNUMLOOPS
void plAnimStage::SetNumLoops(int loops)
{
// I'm very suspicious of this if statement...
// 1. It's preceded by an assert whose condition is the opposite the error message
// (which I've now commented out)
// 2. It only matters if the avatar is currently in the stage.
// 3. It doesn't seem intuitive that if I'm currently on loop 3 and you
// change the number of loops to 6, that I should jump to the end.
// BUT...
// It's been like this for ages, so I'm not touching it until a break shows a problem.
//
//hsAssert(loops < fCurLoop, "Setting loopcount below current loop");
if(loops >= fCurLoop) {
fCurLoop = loops;
}
fLoops = loops;
}
// GETLOOPVALUE
int plAnimStage::GetLoopValue()
{
return fCurLoop;
}
// SETLOOPVALUE
void plAnimStage::SetLoopValue(int value)
{
fCurLoop = value;
}
// GETLOCALTIME
float plAnimStage::GetLocalTime()
{
return fLocalTime;
}
// SETLOCALTIME
void plAnimStage::SetLocalTime(float time, hsBool noCallbacks /* = false */)
{
fLocalTime = time;
if(fAnimInstance)
fAnimInstance->SetCurrentTime(time, noCallbacks);
}
// GETLENGTH
float plAnimStage::GetLength()
{
return fLength;
}
// GETISATTACHED
bool plAnimStage::GetIsAttached()
{
return fAttached;
}
// SETISATTACHED
void plAnimStage::SetIsAttached(bool status)
{
fAttached = status;
}
// GETNEXTSTAGE
int plAnimStage::GetNextStage(int curStage)
{
if(fDoAdvanceTo)
{
return fAdvanceTo;
} else {
return curStage + 1;
}
}
// GETPREVSTAGE
int plAnimStage::GetPrevStage(int curStage)
{
if(fDoRegressTo)
{
return fRegressTo;
} else {
return curStage - 1;
}
}
// DUMPDEBUG
void plAnimStage::DumpDebug(bool active, int &x, int &y, int lineHeight, char *strBuf, plDebugText &debugTxt)
{
std::string str;
str += fAnimName;
str += " ";
if(fLoops) {
sprintf(strBuf, "loop(%d/%d)", fCurLoop, fLoops);
str += strBuf;
}
sprintf(strBuf, "time: (%f/%f)", fLocalTime, fLength);
str += strBuf;
if(active)
debugTxt.DrawString(x, y, str.c_str(), 0, 255, 0);
else if(fAnimInstance)
debugTxt.DrawString(x, y, str.c_str());
else
debugTxt.DrawString(x, y, str.c_str(), 255, 255, 0);
y += lineHeight;
}
// READ
void plAnimStage::Read(hsStream *stream, hsResMgr *mgr)
{
delete [] fAnimName;
fAnimName = stream->ReadSafeString();
fNotify = stream->ReadByte();
fForwardType = (ForwardType)stream->ReadSwap32();
fBackType = (BackType)stream->ReadSwap32();
fAdvanceType = (AdvanceType)stream->ReadSwap32();
fRegressType = (RegressType)stream->ReadSwap32();
fLoops = stream->ReadSwap32();
fDoAdvanceTo = stream->Readbool();
fAdvanceTo = stream->ReadSwap32();
fDoRegressTo = stream->Readbool();
fRegressTo = stream->ReadSwap32();
}
void plAnimStage::Write(hsStream *stream, hsResMgr *mgr)
{
stream->WriteSafeString(fAnimName);
stream->WriteByte(fNotify);
stream->WriteSwap32(fForwardType);
stream->WriteSwap32(fBackType);
stream->WriteSwap32(fAdvanceType);
stream->WriteSwap32(fRegressType);
stream->WriteSwap32(fLoops);
stream->Writebool(fDoAdvanceTo);
stream->WriteSwap32(fAdvanceTo);
stream->Writebool(fDoRegressTo);
stream->WriteSwap32(fRegressTo);
}
// SAVEAUX
void plAnimStage::SaveAux(hsStream *stream, hsResMgr *mgr)
{
stream->WriteSwapScalar(fLocalTime);
stream->WriteSwapScalar(fLength);
stream->WriteSwap32(fCurLoop);
stream->Writebool(fAttached);
// no ephemeral stage at the moment
}
// LOADAUX
void plAnimStage::LoadAux(hsStream *stream, hsResMgr *mgr, double time)
{
fLocalTime = stream->ReadSwapScalar();
fLength = stream->ReadSwapScalar();
fCurLoop = stream->ReadSwap32();
// This should actually be Readbool (lowercase), but I won't fix it since that
// would require a version change
fAttached = (stream->Readbool() != 0);
}