/*==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 . 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 // 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); }