/*==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==*/ #include "hsTypes.h" #include "plAnimEaseTypes.h" #include "plAnimTimeConvert.h" #include "../plAvatar/plAGAnim.h" #include "hsTimer.h" #include "hsStream.h" #include "../pnMessage/plEventCallbackMsg.h" #include "../plMessage/plAnimCmdMsg.h" #include "../plAvatar/plAGMasterSDLModifier.h" #include "../plAvatar/plAGMasterMod.h" #include "../plModifier/plLayerSDLModifier.h" #include "../plSurface/plLayerAnimation.h" #include "hsResMgr.h" #include "plgDispatch.h" plAnimTimeConvert::plAnimTimeConvert() : fCurrentAnimTime(0), fLastEvalWorldTime(0), fBegin(0), fEnd(0), fLoopEnd(0), fLoopBegin(0), fSpeed(1.f), fFlags(0), fOwner(nil), fEaseInCurve(nil), fEaseOutCurve(nil), fSpeedEaseCurve(nil), fCurrentEaseCurve(nil), fInitialBegin(0), fInitialEnd(0), fWrapTime(0) //fDirtyNotifier(nil) { } plAnimTimeConvert::~plAnimTimeConvert() { int i; for( i = 0; i < fCallbackMsgs.GetCount(); i++ ) hsRefCnt_SafeUnRef(fCallbackMsgs[i]); delete fEaseInCurve; delete fEaseOutCurve; delete fSpeedEaseCurve; //delete fDirtyNotifier; IClearAllStates(); } void plAnimTimeConvert::Init(plATCAnim *anim, plAGAnimInstance *instance, plAGMasterMod *master) { // Set up our eval callbacks plAGInstanceCallbackMsg *instMsg; instMsg = TRACKED_NEW plAGInstanceCallbackMsg(master->GetKey(), kStart); instMsg->fInstance = instance; AddCallback(instMsg); hsRefCnt_SafeUnRef(instMsg); instMsg = TRACKED_NEW plAGInstanceCallbackMsg(master->GetKey(), kStop); instMsg->fInstance = instance; AddCallback(instMsg); hsRefCnt_SafeUnRef(instMsg); instMsg = TRACKED_NEW plAGInstanceCallbackMsg(master->GetKey(), kSingleFrameAdjust); instMsg->fInstance = instance; AddCallback(instMsg); hsRefCnt_SafeUnRef(instMsg); SetOwner(master); ClearFlags(); for (int i = 0; i < anim->NumStopPoints(); i++) GetStopPoints().Append(anim->GetStopPoint(i)); SetBegin(anim->GetStart()); SetEnd(anim->GetEnd()); fInitialBegin = fBegin; fInitialEnd = fEnd; if (anim->GetInitial() != -1) SetCurrentAnimTime(anim->GetInitial()); else SetCurrentAnimTime(anim->GetStart()); SetLoopPoints(anim->GetLoopStart(), anim->GetLoopEnd()); Loop(anim->GetLoop()); SetSpeed(1.f); SetEase(true, anim->GetEaseInType(), anim->GetEaseInMin(), anim->GetEaseInMax(), anim->GetEaseInLength()); SetEase(false, anim->GetEaseOutType(), anim->GetEaseOutMin(), anim->GetEaseOutMax(), anim->GetEaseOutLength()); // set up our time converter based on the animation's specs... // ... after we've set all of its other state values. if (anim->GetAutoStart()) { plSynchEnabler ps(true); // enable dirty tracking so that autostart will send out a state update Start(); } else InitStop(); } // // 0=nil, 1=easeIn, 2=easeOut, 3=speed // void plAnimTimeConvert::SetCurrentEaseCurve(int x) { switch(x) { default: hsAssert(false, "invalid arg to SetCurrentEaseCurve"); break; case kEaseNone: fCurrentEaseCurve=nil; break; case kEaseIn: fCurrentEaseCurve=fEaseInCurve; break; case kEaseOut: fCurrentEaseCurve=fEaseOutCurve; break; case kEaseSpeed: fCurrentEaseCurve=fSpeedEaseCurve; break; } } int plAnimTimeConvert::GetCurrentEaseCurve() const { if (fCurrentEaseCurve==nil) return kEaseNone; if (fCurrentEaseCurve==fEaseInCurve) return kEaseIn; if (fCurrentEaseCurve==fEaseOutCurve) return kEaseOut; if (fCurrentEaseCurve==fSpeedEaseCurve) return kEaseSpeed; hsAssert(false, "unknown ease curve"); return 0; } // // set the number of ATCStates we have // void plAnimTimeConvert::ResizeStates(int cnt) { while (fStates.size() > cnt) { delete fStates.back(); fStates.pop_back(); } while (cnt>fStates.size()) { fStates.push_back(TRACKED_NEW plATCState); } hsAssert(fStates.size()==cnt, "state resize mismatch"); } void plAnimTimeConvert::ResetWrap() { fBegin = fInitialBegin; fEnd = fInitialEnd; Forewards(); fFlags &= (~kWrap & ~kNeedsReset); } hsScalar plAnimTimeConvert::ICalcEaseTime(const plATCEaseCurve *curve, double start, double end) { start -= curve->fBeginWorldTime; end -= curve->fBeginWorldTime; // Clamp to the curve's range if (start < 0) start = 0; if (end > curve->fLength) end = curve->fLength; hsScalar delSecs = 0; if (start < curve->fLength) { // Redundant eval... but only when easing. delSecs = curve->PositionGivenTime((hsScalar)end) - curve->PositionGivenTime((hsScalar)start); } return delSecs; } void plAnimTimeConvert::IClearSpeedEase() { if (fCurrentEaseCurve == fSpeedEaseCurve) fCurrentEaseCurve = nil; delete fSpeedEaseCurve; fSpeedEaseCurve = nil; } void plAnimTimeConvert::ICheckTimeCallbacks(hsScalar frameStart, hsScalar frameStop) { int i; for( i = fCallbackMsgs.GetCount()-1; i >= 0; --i ) { if (fCallbackMsgs[i]->fEvent == kTime) { if( ITimeInFrame(fCallbackMsgs[i]->fEventTime, frameStart, frameStop) ) ISendCallback(i); } else if (fCallbackMsgs[i]->fEvent == kBegin && ITimeInFrame(fBegin, frameStart, frameStop) ) ISendCallback(i); else if (fCallbackMsgs[i]->fEvent == kEnd && ITimeInFrame(fEnd, frameStart, frameStop) ) ISendCallback(i); } } hsBool plAnimTimeConvert::ITimeInFrame(hsScalar secs, hsScalar start, hsScalar stop) { if (secs == start && secs == stop) return true; if( IsBackwards() ) { if( start < stop ) { // We've just wrapped. Careful to exclude markers outside current loop. if( ((secs <= start) && (secs >= fLoopBegin)) || ((secs >= stop) && (secs <= fLoopEnd)) ) return true; } else { if( (secs <= start) && (secs >= stop) ) return true; } } else { if( start > stop ) { // We've just wrapped. Careful to exclude markers outside current loop. if( ((secs >= start) && (secs <= fLoopEnd)) || ((secs <= stop) && (secs >= fLoopBegin)) ) return true; } else { if( (secs >= start) && (secs <= stop) ) return true; } } return false; } void plAnimTimeConvert::ISendCallback(int i) { // Check if callbacks are disabled this frame (i.e. when we're loading in state) if (fFlags & kNoCallbacks) return; // send callback if msg is local or if we are the local master if (!fCallbackMsgs[i]->HasBCastFlag(plMessage::kNetPropagate) || !fOwner || fOwner->IsLocallyOwned()==plSynchedObject::kYes) { plEventCallbackMsg *temp = fCallbackMsgs[i]; fCallbackMsgs[i]->SetSender(fOwner ? fOwner->GetKey() : nil); hsRefCnt_SafeRef(fCallbackMsgs[i]); plgDispatch::MsgSend(fCallbackMsgs[i]); // No more repeats, remove this callback from our list if (fCallbackMsgs[i]->fRepeats == 0) { hsRefCnt_SafeUnRef(fCallbackMsgs[i]); fCallbackMsgs.Remove(i); } // If this isn't infinite, decrement the number of repeats else if (fCallbackMsgs[i]->fRepeats > 0) fCallbackMsgs[i]->fRepeats--; } } plAnimTimeConvert& plAnimTimeConvert::IStop(double time, hsScalar animTime) { if( IsStopped() ) return *this; IClearSpeedEase(); // If we had one queued up, clear it. It will automatically take effect when we start SetFlag(kEasingIn, false); SetFlag(kStopped, true); if (fFlags & kNeedsReset) ResetWrap(); IProcessStateChange(time, animTime); int i; for( i = fCallbackMsgs.GetCount()-1; i >= 0; --i ) { if (fCallbackMsgs[i]->fEvent == kStop) { ISendCallback(i); } } return *this; } plAnimTimeConvert& plAnimTimeConvert::IProcessStateChange(double worldTime, hsScalar animTime /* = -1 */) { if (fStates.size() > 0 && worldTime < fStates.front()->fStartWorldTime) return *this; // Sorry... state saves only work in the forward direction fLastStateChange = worldTime; plATCState *state = TRACKED_NEW plATCState; state->fStartWorldTime = fLastStateChange; state->fStartAnimTime = (animTime < 0 ? WorldToAnimTimeNoUpdate(fLastStateChange) : animTime); state->fFlags = (UInt8)fFlags; state->fBegin = fBegin; state->fEnd = fEnd; state->fLoopBegin = fLoopBegin; state->fLoopEnd = fLoopEnd; state->fSpeed = fSpeed; state->fWrapTime = fWrapTime; state->fEaseCurve = (fCurrentEaseCurve == nil ? nil : fCurrentEaseCurve->Clone()); fStates.push_front(state); IFlushOldStates(); const char* sdlName=nil; if (plLayerAnimation::ConvertNoRef(fOwner)) sdlName=kSDLLayer; else if (plAGMasterMod::ConvertNoRef(fOwner)) sdlName=kSDLAGMaster; else { hsAssert(false, "unknown sdl owner"); } fOwner->DirtySynchState(sdlName, 0); // Send SDL state update to server return *this; } // Remove any out-of-date plATCStates // Where "out-of-date" means, "More than 1 frame old" void plAnimTimeConvert::IFlushOldStates() { plATCState *state; plATCStateList::const_iterator i = fStates.begin(); UInt32 count = 0; for (i; i != fStates.end(); i++) { count++; state = *i; if (fLastEvalWorldTime - hsTimer::GetDelSysSeconds() >= state->fStartWorldTime) break; } while (fStates.size() > count) { delete fStates.back(); fStates.pop_back(); } } void plAnimTimeConvert::IClearAllStates() { while (fStates.size() > 0) { delete fStates.back(); fStates.pop_back(); } } plATCState *plAnimTimeConvert::IGetState(double wSecs) const { plATCState *state; plATCStateList::const_iterator i = fStates.begin(); for (i; i != fStates.end(); i++) { state = *i; if (wSecs >= state->fStartWorldTime) return state; } return nil; } plATCState *plAnimTimeConvert::IGetLatestState() const { return fStates.front(); } void plAnimTimeConvert::SetOwner(plSynchedObject* o) { fOwner = o; } hsBool plAnimTimeConvert::IIsStoppedAt(const double &wSecs, const UInt32 &flags, const plATCEaseCurve *curve) const { if (flags & kStopped) return !(flags & kForcedMove); // If someone called SetCurrentAnimTime(), we need to say we moved. return false; } hsBool plAnimTimeConvert::IsStoppedAt(double wSecs) const { if (wSecs > fLastStateChange) return IIsStoppedAt(wSecs, fFlags, fCurrentEaseCurve); plATCState *state = IGetState(wSecs); if ( !state ) return true; return IIsStoppedAt(wSecs, state->fFlags, state->fEaseCurve); } hsScalar plAnimTimeConvert::WorldToAnimTime(double wSecs) { //hsAssert(wSecs >= fLastEvalWorldTime, "Tried to eval a time that's earlier than the last eval time."); double d = wSecs - fLastEvalWorldTime; hsScalar f = fCurrentAnimTime; if (wSecs < fLastStateChange) { fCurrentAnimTime = IWorldToAnimTimeBeforeState(wSecs); fLastEvalWorldTime = wSecs; return fCurrentAnimTime; } if (fLastEvalWorldTime <= fLastStateChange) // Crossing into the latest state { fLastEvalWorldTime = fLastStateChange; fCurrentAnimTime = IGetLatestState()->fStartAnimTime; } if( (fFlags & kStopped) || (wSecs == fLastEvalWorldTime) ) { if (fFlags & kForcedMove) { int i; for( i = fCallbackMsgs.GetCount()-1; i >= 0; --i ) { if (fCallbackMsgs[i]->fEvent == kSingleFrameEval) { ISendCallback(i); } } } fFlags &= ~kForcedMove; fLastEvalWorldTime = wSecs; return fCurrentAnimTime; } hsScalar note = fCurrentAnimTime - f; hsScalar secs = 0, delSecs = 0; if (fCurrentEaseCurve != nil) { delSecs += ICalcEaseTime(fCurrentEaseCurve, fLastEvalWorldTime, wSecs); if (wSecs > fCurrentEaseCurve->GetEndWorldTime()) { if (fFlags & kEasingIn) delSecs += hsScalar(wSecs - fCurrentEaseCurve->GetEndWorldTime()) * fSpeed; IClearSpeedEase(); fCurrentEaseCurve = nil; } } else { // The easy case... playing the animation at a constant speed. delSecs = hsScalar(wSecs - fLastEvalWorldTime) * fSpeed; } if (fFlags & kBackwards) delSecs = -delSecs; secs = fCurrentAnimTime + delSecs; // At this point, "secs" is the pre-wrapped (before looping) anim time. // "delSecs" is the change in anim time // if our speed is < 0, then checking for the kBackwards flag isn't enough // so we base our decision on the direction of the actual change we've computed. hsBool forewards = delSecs >= 0; if (fFlags & kLoop) { hsBool wrapped = false; if (forewards) { if (IGetLatestState()->fStartAnimTime > fLoopEnd) { // Our animation started past the loop. Play to the end. if (secs > fEnd) { secs = fEnd; IStop(wSecs, secs); } } else { if (secs > fLoopEnd) { secs = fmodf(secs - fLoopBegin, fLoopEnd - fLoopBegin) + fLoopBegin; wrapped = true; } } } else { if (IGetLatestState()->fStartAnimTime < fLoopBegin) { if (secs < fBegin) { secs = fBegin; IStop(wSecs, secs); } } else { if (secs < fLoopBegin) { secs = fLoopEnd - fmodf(fLoopEnd - secs, fLoopEnd - fLoopBegin); wrapped = true; } } } if (fFlags & kWrap) { // possible options, representing each line: // 1. We wrapped around the the beginning of the anim, so stop at the wrap point if we're past it. // 2. Same as #1, but in the backwards case. // 3. We started before the wrap point, now we're after it. Stop. // 4. Same as #3, backwards. if ((wrapped && (forewards && secs >= fWrapTime) || (!forewards && secs <= fWrapTime)) || (forewards && fCurrentAnimTime < fWrapTime && secs >= fWrapTime) || (!forewards && fCurrentAnimTime > fWrapTime && secs <= fWrapTime)) { secs = fWrapTime; IStop(wSecs, secs); } } } else // Not looping { if ((secs < fBegin) || (secs > fEnd)) { secs = forewards ? fEnd : fBegin; IStop(wSecs, secs); } } ICheckTimeCallbacks(fCurrentAnimTime, secs); fLastEvalWorldTime = wSecs; if (fEaseOutCurve != nil && !(fFlags & kEasingIn) && wSecs >= fEaseOutCurve->GetEndWorldTime()) IStop(wSecs, secs); return fCurrentAnimTime = secs; } hsScalar plAnimTimeConvert::WorldToAnimTimeNoUpdate(double wSecs) const { return IWorldToAnimTimeNoUpdate(wSecs, IGetState(wSecs)); } hsScalar plAnimTimeConvert::IWorldToAnimTimeNoUpdate(double wSecs, plATCState *state) { //hsAssert(wSecs >= fLastEvalWorldTime, "Tried to eval a time that's earlier than the last eval time."); if (state == nil) return 0; if (state->fFlags & kStopped) return state->fStartAnimTime; hsScalar secs = 0, delSecs = 0; if (state->fEaseCurve != nil) { delSecs += ICalcEaseTime(state->fEaseCurve, state->fStartWorldTime, wSecs); if (wSecs > state->fEaseCurve->GetEndWorldTime()) { if (state->fFlags & kEasingIn) delSecs += hsScalar(wSecs - state->fEaseCurve->GetEndWorldTime()) * state->fSpeed; } } else { // The easy case... playing the animation at a constant speed. delSecs = hsScalar(wSecs - state->fStartWorldTime) * state->fSpeed; } if (state->fFlags & kBackwards) delSecs = -delSecs; secs = state->fStartAnimTime + delSecs; // At this point, "secs" is the pre-wrapped (before looping) anim time. // "delSecs" is the change in anim time hsBool forewards = delSecs >= 0; if (state->fFlags & kLoop) { hsBool wrapped = false; if (forewards) { if (state->fStartAnimTime > state->fLoopEnd) { // Our animation started past the loop. Play to the end. if (secs > state->fEnd) { secs = state->fEnd; } } else { if (secs > state->fLoopEnd) { secs = fmodf(secs - state->fLoopBegin, state->fLoopEnd - state->fLoopBegin) + state->fLoopBegin; wrapped = true; } } } else { if (state->fStartAnimTime < state->fLoopBegin) { if (secs < state->fBegin) { secs = state->fBegin; } } else { if (secs < state->fLoopBegin) { secs = state->fLoopEnd - fmodf(state->fLoopEnd - secs, state->fLoopEnd - state->fLoopBegin); wrapped = true; } } } if (state->fFlags & kWrap) { if ((wrapped && (forewards && secs >= state->fWrapTime) || (!forewards && secs <= state->fWrapTime)) || (forewards && state->fStartAnimTime < state->fWrapTime && secs >= state->fWrapTime) || (!forewards && state->fStartAnimTime > state->fWrapTime && secs <= state->fWrapTime)) { secs = state->fWrapTime; } } } else { if ((secs < state->fBegin) || (secs > state->fEnd)) { secs = forewards ? state->fEnd : state->fBegin; } } return secs; } hsScalar plAnimTimeConvert::IWorldToAnimTimeBeforeState(double wSecs) const { return IWorldToAnimTimeNoUpdate(wSecs, IGetState(wSecs)); } void plAnimTimeConvert::SetCurrentAnimTime(hsScalar s, hsBool jump /* = false */) { // We're setting the anim value for whenever we last evaluated. fFlags |= kForcedMove; if (!jump) ICheckTimeCallbacks(fCurrentAnimTime, s); fCurrentAnimTime = s; int i; for( i = fCallbackMsgs.GetCount()-1; i >= 0; --i ) { if (fCallbackMsgs[i]->fEvent == kSingleFrameAdjust) { ISendCallback(i); } } IProcessStateChange(hsTimer::GetSysSeconds(), fCurrentAnimTime); } void plAnimTimeConvert::SetEase(hsBool easeIn, UInt8 type, hsScalar minLength, hsScalar maxLength, hsScalar normLength) { if (easeIn) { delete fEaseInCurve; fEaseInCurve = plATCEaseCurve::CreateEaseCurve(type, minLength, maxLength, normLength, 0, fSpeed); } else { delete fEaseOutCurve; fEaseOutCurve = plATCEaseCurve::CreateEaseCurve(type, minLength, maxLength, normLength, fSpeed, 0); } } hsScalar plAnimTimeConvert::GetBestStopDist(hsScalar min, hsScalar max, hsScalar norm, hsScalar time) const { hsScalar bestTime = -1; hsScalar bestDist = -1; if (fStopPoints.GetCount() == 0) return norm; hsScalar curTime; hsScalar curDist; int i; for (i = 0; i < fStopPoints.GetCount(); i++) { hsScalar stop = fStopPoints.Get(i); if (IsLooped()) { hsScalar loopDist; if (IsBackwards()) { if ((time >= fLoopBegin && stop < fLoopBegin) || (time < fLoopBegin && stop > fLoopBegin)) continue; loopDist = -(fLoopEnd - fLoopBegin); } else { if ((time <= fLoopEnd && stop > fLoopEnd) || (time > fLoopEnd && stop < fLoopEnd)) continue; // we'll never reach it. loopDist = fLoopEnd - fLoopBegin; } if (stop <= fLoopEnd && stop >= fLoopBegin) { while (true) { curTime = stop - time; if (IsBackwards()) curTime = -curTime; if (curTime > max) break; curDist = curTime - norm; if (curDist < 0) curDist = -curDist; if (curTime >= min && curTime <= max && (bestDist == -1 || bestDist > curDist)) { bestDist = curDist; bestTime = curTime; } stop += loopDist; } continue; } } curTime = stop - time; if (IsBackwards()) curTime = -curTime; curDist = curTime - norm; if (curDist < 0) curDist = -curDist; if (curTime >= min && curTime <= max && (bestDist == -1 || bestDist > curDist)) { bestDist = curDist; bestTime = curTime; } } hsStatusMessageF("found stop point %f\n", bestTime); if (bestTime == -1) bestTime = norm; return bestTime; } // Passing in a rate of zero specifies an immediate change. void plAnimTimeConvert::SetSpeed(hsScalar goal, hsScalar rate /* = 0 */) { hsScalar curSpeed = fSpeed; fSpeed = goal; if (rate == 0) { IClearSpeedEase(); fCurrentEaseCurve = nil; } // Skip if we're either stopped or stopping. We'll take the new speed into account next time we start up. else if ((fFlags & kEasingIn)) { double curTime = hsTimer::GetSysSeconds(); if (fCurrentEaseCurve != nil) { double easeTime = curTime - fCurrentEaseCurve->fBeginWorldTime; curSpeed = fCurrentEaseCurve->VelocityGivenTime((hsScalar)easeTime); } if (fSpeedEaseCurve != nil) { fSpeedEaseCurve->RecalcToSpeed(curSpeed, goal); fSpeedEaseCurve->SetLengthOnRate(rate); } else { hsScalar length; length = (goal - curSpeed) / rate; if (length < 0) length = -length; fSpeedEaseCurve = plATCEaseCurve::CreateEaseCurve(plAnimEaseTypes::kConstAccel, length, length, length, curSpeed, goal); } fSpeedEaseCurve->fBeginWorldTime = curTime; fCurrentEaseCurve = fSpeedEaseCurve; } IProcessStateChange(hsTimer::GetSysSeconds()); } void plAnimTimeConvert::Read(hsStream* s, hsResMgr* mgr) { plCreatable::Read(s, mgr); fFlags = (UInt16)(s->ReadSwap32()); fBegin = fInitialBegin = s->ReadSwapScalar(); fEnd = fInitialEnd = s->ReadSwapScalar(); fLoopEnd = s->ReadSwapScalar(); fLoopBegin = s->ReadSwapScalar(); fSpeed = s->ReadSwapScalar(); fEaseInCurve = plATCEaseCurve::ConvertNoRef(mgr->ReadCreatable(s)); fEaseOutCurve = plATCEaseCurve::ConvertNoRef(mgr->ReadCreatable(s)); fSpeedEaseCurve = plATCEaseCurve::ConvertNoRef(mgr->ReadCreatable(s)); fCurrentAnimTime = s->ReadSwapScalar(); fLastEvalWorldTime = s->ReadSwapDouble(); // load other non-synched data; int count = s->ReadSwap32(); fCallbackMsgs.SetCountAndZero(count); int i; for (i = 0; i < count; i++) { plEventCallbackMsg* msg = plEventCallbackMsg::ConvertNoRef(mgr->ReadCreatable(s)); fCallbackMsgs[i] = msg; } count = s->ReadSwap32(); for (i = 0; i < count; i++) { fStopPoints.Append(s->ReadSwapScalar()); } IProcessStateChange(0, fBegin); } void plAnimTimeConvert::Write(hsStream* s, hsResMgr* mgr) { plCreatable::Write(s, mgr); s->WriteSwap32(fFlags); s->WriteSwapScalar(fBegin); s->WriteSwapScalar(fEnd); s->WriteSwapScalar(fLoopEnd); s->WriteSwapScalar(fLoopBegin); s->WriteSwapScalar(fSpeed); mgr->WriteCreatable(s, fEaseInCurve); mgr->WriteCreatable(s, fEaseOutCurve); mgr->WriteCreatable(s, fSpeedEaseCurve); s->WriteSwapScalar(fCurrentAnimTime); s->WriteSwapDouble(fLastEvalWorldTime); // save out other non-synched important data s->WriteSwap32(fCallbackMsgs.Count()); int i; for (i = 0; i < fCallbackMsgs.Count(); i++) mgr->WriteCreatable(s, fCallbackMsgs[i]); s->WriteSwap32(fStopPoints.GetCount()); for (i = 0; i < fStopPoints.GetCount(); i++) { s->WriteSwapScalar(fStopPoints.Get(i)); } } plAnimTimeConvert& plAnimTimeConvert::InitStop() { return IStop(hsTimer::GetSysSeconds(), fCurrentAnimTime); } plAnimTimeConvert& plAnimTimeConvert::Stop(hsBool on) { if( on ) return Stop(); else return Start(); } plAnimTimeConvert& plAnimTimeConvert::Stop(double stopTime) { if( IsStopped() || (fEaseOutCurve != nil && !(fFlags & kEasingIn)) ) return *this; if (stopTime < 0) stopTime = hsTimer::GetSysSeconds(); hsScalar stopAnimTime = WorldToAnimTimeNoUpdate(stopTime); SetFlag(kEasingIn, false); if( fEaseOutCurve == nil ) { return IStop(stopTime, fCurrentAnimTime); } hsScalar currSpeed; if (fCurrentEaseCurve == nil || stopTime >= fCurrentEaseCurve->GetEndWorldTime()) currSpeed = fSpeed; else currSpeed = fCurrentEaseCurve->VelocityGivenTime((hsScalar)(stopTime - fCurrentEaseCurve->fBeginWorldTime)); fEaseOutCurve->RecalcToSpeed(currSpeed > fSpeed ? currSpeed : fSpeed, 0); fEaseOutCurve->SetLengthOnDistance(GetBestStopDist(fEaseOutCurve->GetMinDistance(), fEaseOutCurve->GetMaxDistance(), fEaseOutCurve->GetNormDistance(), stopAnimTime)); fEaseOutCurve->fBeginWorldTime = stopTime - fEaseOutCurve->TimeGivenVelocity(currSpeed); fCurrentEaseCurve = fEaseOutCurve; return IProcessStateChange(stopTime); } plAnimTimeConvert& plAnimTimeConvert::Start(double startTime) { // If start has not been called since the last stop, kEasingIn will not be set if( (fFlags & kEasingIn) && (startTime == fLastStateChange) ) return *this; SetFlag(kEasingIn, true); if (startTime < 0) startTime = hsTimer::GetSysSeconds(); if (fEaseInCurve != nil) { hsScalar currSpeed; if (fCurrentEaseCurve == nil || startTime >= fCurrentEaseCurve->GetEndWorldTime()) currSpeed = 0; else currSpeed = fCurrentEaseCurve->VelocityGivenTime((hsScalar)(startTime - fCurrentEaseCurve->fBeginWorldTime)); if (currSpeed <= fSpeed) { fEaseInCurve->RecalcToSpeed(0, fSpeed); fEaseInCurve->fBeginWorldTime = startTime - fEaseInCurve->TimeGivenVelocity(currSpeed); fCurrentEaseCurve = fEaseInCurve; } else { // We eased out in the middle of a speed change, but were told to start again before slowing past // the target speed, so the "ease in" is really a slow down. SetSpeed(fSpeed, fEaseInCurve->fSpeed / fEaseInCurve->fLength); } } // check for a start callback int i; for( i = fCallbackMsgs.GetCount()-1; i >= 0; --i ) { if (fCallbackMsgs[i]->fEvent == kStart) { ISendCallback(i); } } SetFlag(kStopped, false); if (fFlags & kBackwards) { if (fCurrentAnimTime == fBegin) return IProcessStateChange(startTime, fEnd); } else { if (fCurrentAnimTime == fEnd) return IProcessStateChange(startTime, fBegin); } return IProcessStateChange(startTime); } plAnimTimeConvert& plAnimTimeConvert::Backwards(hsBool on) { return on ? Backwards() : Forewards(); } plAnimTimeConvert& plAnimTimeConvert::Backwards() { if( IsBackwards() ) return *this; // check for a reverse callback int i; for( i = fCallbackMsgs.GetCount()-1; i >= 0; --i ) { if (fCallbackMsgs[i]->fEvent == kReverse) { ISendCallback(i); } } SetFlag(kBackwards, true); // Record state changes IProcessStateChange(hsTimer::GetSysSeconds(), fCurrentAnimTime); return *this; } plAnimTimeConvert& plAnimTimeConvert::Forewards() { if( !IsBackwards() ) return *this; // check for a reverse callback int i; for( i = fCallbackMsgs.GetCount()-1; i >= 0; --i ) { if (fCallbackMsgs[i]->fEvent == kReverse) { ISendCallback(i); } } SetFlag(kBackwards, false); // Record state changes IProcessStateChange(hsTimer::GetSysSeconds(), fCurrentAnimTime); return *this; } plAnimTimeConvert& plAnimTimeConvert::Loop(hsBool on) { SetFlag(kLoop, on); // Record state changes IProcessStateChange(hsTimer::GetSysSeconds(), fCurrentAnimTime); return *this; } plAnimTimeConvert& plAnimTimeConvert::PlayToTime(hsScalar time) { fFlags |= kNeedsReset; if (fCurrentAnimTime > time) { if (fFlags & kLoop) { fWrapTime = time; fFlags |= kWrap; } else { fBegin = time; Backwards(); } } else { fEnd = time; } Start(); return *this; } plAnimTimeConvert& plAnimTimeConvert::PlayToPercentage(hsScalar percent) { return PlayToTime(fBegin + (fEnd - fBegin) * percent); } void plAnimTimeConvert::RemoveCallback(plEventCallbackMsg* pMsg) { int idx = fCallbackMsgs.Find(pMsg); if( idx != fCallbackMsgs.kMissingIndex ) { hsRefCnt_SafeUnRef(fCallbackMsgs[idx]); fCallbackMsgs.Remove(idx); } } hsBool plAnimTimeConvert::HandleCmd(plAnimCmdMsg* modMsg) { if (fFlags & kNeedsReset) ResetWrap(); // The net msg screener is already checking for callbacks, // I'm just being extra safe. if (!modMsg->HasBCastFlag(plMessage::kNetCreatedRemotely)) { if( modMsg->Cmd(plAnimCmdMsg::kAddCallbacks) ) { int i; for( i = 0; i < modMsg->GetNumCallbacks(); i++ ) { AddCallback(plEventCallbackMsg::ConvertNoRef(modMsg->GetEventCallback(i))); } } if( modMsg->Cmd(plAnimCmdMsg::kRemoveCallbacks) ) { int i; for( i = 0; i < modMsg->GetNumCallbacks(); i++ ) { RemoveCallback(modMsg->GetEventCallback(i)); } } } if( modMsg->Cmd(plAnimCmdMsg::kSetBackwards) ) { Backwards(); } if( modMsg->Cmd(plAnimCmdMsg::kSetForewards) ) { Forewards(); } if( modMsg->Cmd(plAnimCmdMsg::kStop) ) Stop(); if( modMsg->Cmd(plAnimCmdMsg::kSetLooping) ) Loop(); if( modMsg->Cmd(plAnimCmdMsg::kUnSetLooping) ) NoLoop(); if (modMsg->Cmd(plAnimCmdMsg::kSetBegin)) { if (modMsg->fBegin >= fInitialBegin) SetBegin(modMsg->fBegin); else SetBegin(fInitialBegin); } if (modMsg->Cmd(plAnimCmdMsg::kSetEnd)) { if (modMsg->fEnd <= fInitialEnd) SetEnd(modMsg->fEnd); else SetEnd(fInitialEnd); } if (fBegin > fEnd) { fBegin = fInitialBegin; fEnd = fInitialEnd; } if( modMsg->Cmd(plAnimCmdMsg::kSetLoopEnd) ) SetLoopEnd(modMsg->fLoopEnd); if( modMsg->Cmd(plAnimCmdMsg::kSetLoopBegin) ) SetLoopBegin(modMsg->fLoopBegin); if( modMsg->Cmd(plAnimCmdMsg::kSetSpeed) ) SetSpeed(modMsg->fSpeed, modMsg->fSpeedChangeRate); if( modMsg->Cmd(plAnimCmdMsg::kGoToTime) ) { if (modMsg->fTime < fBegin) SetCurrentAnimTime(fBegin, true); else if (modMsg->fTime > fEnd) SetCurrentAnimTime(fEnd, true); else SetCurrentAnimTime(modMsg->fTime, true); } if ( modMsg->Cmd(plAnimCmdMsg::kGoToPercent) ) SetCurrentAnimTime(fBegin + (fEnd - fBegin) * modMsg->fTime); if( modMsg->Cmd(plAnimCmdMsg::kGoToBegin) ) SetCurrentAnimTime(fBegin, true); if( modMsg->Cmd(plAnimCmdMsg::kGoToEnd) ) SetCurrentAnimTime(fEnd, true); if( modMsg->Cmd(plAnimCmdMsg::kGoToLoopBegin) ) SetCurrentAnimTime(fLoopBegin, true); if( modMsg->Cmd(plAnimCmdMsg::kGoToLoopEnd) ) SetCurrentAnimTime(fLoopEnd, true); if( modMsg->Cmd(plAnimCmdMsg::kToggleState) ) { if( IsStopped() ) { Start(); } else { Stop(); } } if( modMsg->Cmd(plAnimCmdMsg::kContinue) ) { Start(); } if( modMsg->Cmd(plAnimCmdMsg::kIncrementForward) ) { if (fCurrentAnimTime == fEnd) return true; double currTime = hsTimer::GetSysSeconds(); hsScalar newTime = fCurrentAnimTime + hsTimer::GetDelSysSeconds(); if (newTime > fEnd) { newTime = fEnd; } Forewards(); SetCurrentAnimTime(newTime); } if( modMsg->Cmd(plAnimCmdMsg::kIncrementBackward) ) { if (fCurrentAnimTime == fBegin) return true; double currTime = hsTimer::GetSysSeconds(); hsScalar newTime = fCurrentAnimTime - hsTimer::GetDelSysSeconds(); if (newTime < fBegin) { newTime = fBegin; } Backwards(); SetCurrentAnimTime(newTime); } if (modMsg->Cmd(plAnimCmdMsg::kPlayToTime)) PlayToTime(modMsg->fTime); if (modMsg->Cmd(plAnimCmdMsg::kPlayToPercentage)) PlayToPercentage(modMsg->fTime); // Basically, simulate what would happen if we played the animation if (modMsg->Cmd(plAnimCmdMsg::kFastForward)) { if (IsForewards()) SetCurrentAnimTime(fEnd, true); else SetCurrentAnimTime(fBegin, true); // but if it should continue to play, statr it. if (IsLooped()) Start(); } return true; } void plAnimTimeConvert::AddCallback(plEventCallbackMsg* pMsg) { hsRefCnt_SafeRef(pMsg); fCallbackMsgs.Append(pMsg); } void plAnimTimeConvert::ClearCallbacks() { for (int i = 0; iReadSwapDouble(); fStartAnimTime = s->ReadSwapScalar(); fFlags = (UInt8)(s->ReadSwap32()); fEnd = s->ReadSwapScalar(); fLoopBegin = s->ReadSwapScalar(); fLoopEnd = s->ReadSwapScalar(); fSpeed = s->ReadSwapScalar(); fWrapTime = s->ReadSwapScalar(); if (s->ReadBool()) fEaseCurve = plATCEaseCurve::ConvertNoRef(mgr->ReadCreatable(s)); } void plATCState::Write(hsStream *s, hsResMgr *mgr) { s->WriteSwapDouble(fStartWorldTime); s->WriteSwapScalar(fStartAnimTime); s->WriteSwap32(fFlags); s->WriteSwapScalar(fEnd); s->WriteSwapScalar(fLoopBegin); s->WriteSwapScalar(fLoopEnd); s->WriteSwapScalar(fSpeed); s->WriteSwapScalar(fWrapTime); if (fEaseCurve != nil) { s->WriteBool(true); mgr->WriteCreatable(s, fEaseCurve); } else s->WriteBool(false); }