#include "stdafx.h" #include "Ctrl.h" #include "Sequence.h" #if ENABLE_MSGTABLE_API /***************************************************************************\ ***************************************************************************** * * Global Functions * ***************************************************************************** \***************************************************************************/ //------------------------------------------------------------------------------ inline BOOL IsSameTime(float flA, float flB) { float flDelta = flA - flB; return ((flDelta < 0.005f) && (flDelta > -0.005f)); } /***************************************************************************\ ***************************************************************************** * * class DuSequence * ***************************************************************************** \***************************************************************************/ /***************************************************************************\ * * DuSequence::ApiOnEvent * * ApiOnEvent() processes events. * \***************************************************************************/ HRESULT DuSequence::ApiOnEvent(EventMsg * pmsg) { if (pmsg->nMsg == GM_DESTROY) { GMSG_DESTROY * pmsgD = static_cast(pmsg); switch (GET_EVENT_DEST(pmsgD)) { case GMF_DIRECT: // // We are being destroyed. // Stop(); return DU_S_COMPLETE; case GMF_EVENT: // // Our Subject is being destroyed // Stop(); return DU_S_PARTIAL; } } return SListener::ApiOnEvent(pmsg); } /***************************************************************************\ * * DuSequence::ApiGetLength * * ApiGetLength() returns the length of a sequence, not including the initial * delay. * \***************************************************************************/ HRESULT DuSequence::ApiGetLength(Sequence::GetLengthMsg * pmsg) { int cItems = m_arSeqData.GetSize(); if (cItems <= 0) { pmsg->flLength = 0.0f; } else { pmsg->flLength = m_arSeqData[cItems - 1].flTime; } return S_OK; } /***************************************************************************\ * * DuSequence::ApiGetDelay * * ApiGetDelay() returns the delay to wait before starting the sequence. * \***************************************************************************/ HRESULT DuSequence::ApiGetDelay(Sequence::GetDelayMsg * pmsg) { pmsg->flDelay = m_flDelay; return S_OK; } /***************************************************************************\ * * DuSequence::ApiSetDelay * * ApiSetDelay() changes the delay to wait before starting the sequence. * \***************************************************************************/ HRESULT DuSequence::ApiSetDelay(Sequence::SetDelayMsg * pmsg) { if (pmsg->flDelay < 0.0f) { PromptInvalid("Can not set a delay time in the past"); return E_INVALIDARG; } if (IsPlaying()) { PromptInvalid("Sequence is busy"); return DU_E_BUSY; } m_flDelay = pmsg->flDelay; return S_OK; } /***************************************************************************\ * * DuSequence::ApiGetFlow * * ApiGetFlow() returns the Flow being used through-out the sequence to * modify a given Subject. * \***************************************************************************/ HRESULT DuSequence::ApiGetFlow(Sequence::GetFlowMsg * pmsg) { SafeAddRef(m_pflow); pmsg->pflow = m_pflow; return S_OK; } /***************************************************************************\ * * DuSequence::ApiSetFlow * * ApiSetFlow() changes the Flow being used through-out the sequence to * modify a given Subject. * \***************************************************************************/ HRESULT DuSequence::ApiSetFlow(Sequence::SetFlowMsg * pmsg) { if (IsPlaying()) { PromptInvalid("Sequence is busy"); return DU_E_BUSY; } SafeRelease(m_pflow); SafeAddRef(pmsg->pflow); m_pflow = pmsg->pflow; return S_OK; } /***************************************************************************\ * * DuSequence::ApiGetFramePause * * ApiGetFramePause() returns the default "dwPause" value used for * Animations during playback. * \***************************************************************************/ HRESULT DuSequence::ApiGetFramePause(Sequence::GetFramePauseMsg * pmsg) { pmsg->dwPause = m_dwFramePause; return S_OK; } /***************************************************************************\ * * DuSequence::ApiSetFramePause * * ApiSetFramePause() changes the default "dwPause" value used for * Animations during playback. * \***************************************************************************/ HRESULT DuSequence::ApiSetFramePause(Sequence::SetFramePauseMsg * pmsg) { if (IsPlaying()) { PromptInvalid("Sequence is busy"); return DU_E_BUSY; } m_dwFramePause = pmsg->dwPause; return S_OK; } /***************************************************************************\ * * DuSequence::ApiGetKeyFrameCount * * ApiGetKeyFrameCount() return the number of KeyFrames used in the sequence. * \***************************************************************************/ HRESULT DuSequence::ApiGetKeyFrameCount(Sequence::GetKeyFrameCountMsg * pmsg) { pmsg->cFrames = m_arSeqData.GetSize(); return S_OK; } /***************************************************************************\ * * DuSequence::ApiAddKeyFrame * * ApiAddKeyFrame() adds a new KeyFrame at the specified time. If a KeyFrame * already exists at the given time, that KeyFrame will be returned. * \***************************************************************************/ HRESULT DuSequence::ApiAddKeyFrame(Sequence::AddKeyFrameMsg * pmsg) { if (pmsg->flTime < 0.0f) { PromptInvalid("Can not set a delay time in the past"); return E_INVALIDARG; } if (IsPlaying()) { PromptInvalid("Sequence is busy"); return DU_E_BUSY; } // // Search the sequence to determine what slot to insert the new data in. We // want to keep all time in order. // int cItems = m_arSeqData.GetSize(); int idxAdd = cItems; for (int idx = 0; idx < cItems; idx++) { if (IsSameTime(m_arSeqData[idx].flTime, pmsg->flTime)) { pmsg->idxKeyFrame = idx; return S_OK; } if (m_arSeqData[idx].flTime > pmsg->flTime) { idxAdd = idx; } } // // Add a new KeyFrame at this time // SeqData data; data.flTime = pmsg->flTime; data.pkf = NULL; data.pipol = NULL; if (!m_arSeqData.InsertAt(idxAdd, data)) { return E_OUTOFMEMORY; } pmsg->idxKeyFrame = idxAdd; return S_OK; } /***************************************************************************\ * * DuSequence::ApiRemoveKeyFrame * * ApiRemoveKeyFrame() removes the specified KeyFrame. * \***************************************************************************/ HRESULT DuSequence::ApiRemoveKeyFrame(Sequence::RemoveKeyFrameMsg * pmsg) { if ((pmsg->idxKeyFrame < 0) || (pmsg->idxKeyFrame >= m_arSeqData.GetSize())) { PromptInvalid("Invalid KeyFrame"); return E_INVALIDARG; } if (IsPlaying()) { PromptInvalid("Sequence is busy"); return DU_E_BUSY; } SeqData & data = m_arSeqData[pmsg->idxKeyFrame]; ClientFree(data.pkf); SafeRelease(data.pipol); m_arSeqData.RemoveAt(pmsg->idxKeyFrame); return S_OK; } /***************************************************************************\ * * DuSequence::ApiRemoveAllKeyFrames * * ApiRemoveAllKeyFrames() removes all KeyFrames. * \***************************************************************************/ HRESULT DuSequence::ApiRemoveAllKeyFrames(Sequence::RemoveAllKeyFramesMsg * pmsg) { UNREFERENCED_PARAMETER(pmsg); if (IsPlaying()) { PromptInvalid("Sequence is busy"); return DU_E_BUSY; } RemoveAllKeyFrames(); return S_OK; } /***************************************************************************\ * * DuSequence::ApiFindKeyFrame * * ApiFindKeyFrame() finds the KeyFrame at the given time. * \***************************************************************************/ HRESULT DuSequence::ApiFindKeyFrame(Sequence::FindKeyFrameMsg * pmsg) { FindAtTime(pmsg->flTime, &pmsg->idxKeyFrame); return S_OK; } /***************************************************************************\ * * DuSequence::ApiGetTime * * ApiGetTime() returns the time at a given KeyFrame. * \***************************************************************************/ HRESULT DuSequence::ApiGetTime(Sequence::GetTimeMsg * pmsg) { if ((pmsg->idxKeyFrame < 0) || (pmsg->idxKeyFrame >= m_arSeqData.GetSize())) { PromptInvalid("Invalid KeyFrame"); return E_INVALIDARG; } pmsg->flTime = m_arSeqData[pmsg->idxKeyFrame].flTime; return S_OK; } /***************************************************************************\ * * DuSequence::ApiSetTime * * ApiSetTime() changes the time of a given KeyFrame. This function will * reorder keyframes to maintain proper time order. * \***************************************************************************/ HRESULT DuSequence::ApiSetTime(Sequence::SetTimeMsg * pmsg) { if ((pmsg->idxKeyFrame < 0) || (pmsg->idxKeyFrame >= m_arSeqData.GetSize())) { PromptInvalid("Invalid KeyFrame"); return E_INVALIDARG; } if (pmsg->flTime < 0.0f) { PromptInvalid("Can not set a delay time in the past"); return E_INVALIDARG; } if (IsPlaying()) { PromptInvalid("Sequence is busy"); return DU_E_BUSY; } m_arSeqData[pmsg->idxKeyFrame].flTime = pmsg->flTime; // // We have changed the time of one of the KeyFrames, so we need to re-sort. // SortKeyFrames(); return S_OK; } /***************************************************************************\ * * DuSequence::ApiGetKeyFrame * * ApiGetKeyFrame() returns Flow-specific data at a given KeyFrame. * \***************************************************************************/ HRESULT DuSequence::ApiGetKeyFrame(Sequence::GetKeyFrameMsg * pmsg) { if ((pmsg->idxKeyFrame < 0) || (pmsg->idxKeyFrame >= m_arSeqData.GetSize())) { PromptInvalid("Invalid KeyFrame"); return E_INVALIDARG; } SeqData & data = m_arSeqData[pmsg->idxKeyFrame]; if (data.pkf == NULL) { PromptInvalid("KeyFrame has not been set"); return E_INVALIDARG; } if (pmsg->pkf->cbSize < data.pkf->cbSize) { PromptInvalid("cbSize is not large enough to store KeyFrame"); return E_INVALIDARG; } CopyMemory(pmsg->pkf, data.pkf, data.pkf->cbSize); return S_OK; } /***************************************************************************\ * * DuSequence::ApiSetKeyFrame * * ApiSetKeyFrame() changes Flow-specific data at a given KeyFrame. * \***************************************************************************/ HRESULT DuSequence::ApiSetKeyFrame(Sequence::SetKeyFrameMsg * pmsg) { if ((pmsg->idxKeyFrame < 0) || (pmsg->idxKeyFrame >= m_arSeqData.GetSize())) { PromptInvalid("Invalid KeyFrame"); return E_INVALIDARG; } if (pmsg->pkfSrc->cbSize <= 0) { PromptInvalid("cbSize must be set"); return E_INVALIDARG; } if (IsPlaying()) { PromptInvalid("Sequence is busy"); return DU_E_BUSY; } // // Copy and store the KeyFrame // int cbAlloc = pmsg->pkfSrc->cbSize; DUser::KeyFrame * pkfCopy = reinterpret_cast (ClientAlloc(cbAlloc)); if (pkfCopy == NULL) { return E_OUTOFMEMORY; } SeqData & data = m_arSeqData[pmsg->idxKeyFrame]; ClientFree(data.pkf); CopyMemory(pkfCopy, pmsg->pkfSrc, cbAlloc); data.pkf = pkfCopy; return S_OK; } /***************************************************************************\ * * DuSequence::ApiGetInterpolation * * ApiGetInterpolation() returns the Interpolation used to move to the next * keyframe. * \***************************************************************************/ HRESULT DuSequence::ApiGetInterpolation(Sequence::GetInterpolationMsg * pmsg) { if ((pmsg->idxKeyFrame < 0) || (pmsg->idxKeyFrame >= m_arSeqData.GetSize())) { PromptInvalid("Invalid KeyFrame"); return E_INVALIDARG; } SeqData & data = m_arSeqData[pmsg->idxKeyFrame]; SafeAddRef(data.pipol); pmsg->pipol = data.pipol; return S_OK; } /***************************************************************************\ * * DuSequence::ApiSetInterpolation * * ApiSetInterpolation() changes the Interpolation used to move to the next * keyframe. * \***************************************************************************/ HRESULT DuSequence::ApiSetInterpolation(Sequence::SetInterpolationMsg * pmsg) { if ((pmsg->idxKeyFrame < 0) || (pmsg->idxKeyFrame >= m_arSeqData.GetSize())) { PromptInvalid("Invalid KeyFrame"); return E_INVALIDARG; } if (IsPlaying()) { PromptInvalid("Sequence is busy"); return DU_E_BUSY; } // // Copy and store the KeyFrame // SeqData & data = m_arSeqData[pmsg->idxKeyFrame]; SafeRelease(data.pipol); SafeAddRef(pmsg->pipol); data.pipol = pmsg->pipol; return S_OK; } /***************************************************************************\ * * DuSequence::Play * * ApiPlay() executes the Animation sequence for the given Visual. A * Sequence only supports animating a single Visual at a given time. * Multiple sequences may be created to animate multiple Visuals * simultaneously. * \***************************************************************************/ HRESULT DuSequence::ApiPlay(Sequence::PlayMsg * pmsg) { Assert(DEBUG_IsProperTimeOrder()); // // Setup for animation: // - Validate all information is filled in. // - Ensure no existing Animation. // - Determine parameters for Animation. // HRESULT hr = CheckComplete(); if (FAILED(hr)) { return hr; } Stop(); AssertMsg(m_arAniData.GetSize() == 0, "Must not have pending Animations"); // // Setup for Animation // - Attach as a Listener // - Allocate information necessary to create the Animations. // - Add a reference to allow the Sequence to fully play. // hr = pmsg->pgvSubject->AddHandlerG(GM_DESTROY, GetStub()); if (FAILED(hr)) { return hr; } m_pgvSubject = pmsg->pgvSubject; int cItems = m_arSeqData.GetSize(); if (cItems == 0) { return S_OK; } if (!m_arAniData.SetSize(cItems - 1)) { return E_OUTOFMEMORY; } AddRef(); // // Queue all animations // for (int idx = 0; idx < cItems - 1; idx++) { hr = QueueAnimation(idx); if (FAILED(hr)) { return hr; } } return S_OK; } /***************************************************************************\ * * DuSequence::ApiStop * * ApiStop() stops any executing Animation sequence. * \***************************************************************************/ HRESULT DuSequence::ApiStop(Sequence::StopMsg * pmsg) { UNREFERENCED_PARAMETER(pmsg); Stop(TRUE); return S_OK; } /***************************************************************************\ * * DuSequence::ApiStop * * ApiStop() stops any executing Animation sequence. * \***************************************************************************/ void DuSequence::Stop(BOOL fKillAnimations) { if (IsPlaying()) { // // To prevent re-entrancy, mark that we are no longer playing. However, // don't remove ourself as a listener until we are done. // Visual * pgvSubject = m_pgvSubject; m_pgvSubject = NULL; // // Stop any queued Animations. When doing this, set // m_arAniData[idx].hact to NULL to signal to the Action to not // create the Animation. We need to do this since every Action will // get called back. // if (fKillAnimations) { PRID prid = 0; VerifyHR(m_pflow->GetPRID(&prid)); Animation::Stop(pgvSubject, prid); int cItems = m_arAniData.GetSize(); for (int idx = 0; idx < cItems; idx++) { HACTION hact = m_arAniData[idx].hact; if (hact != NULL) { DeleteHandle(hact); AssertMsg(m_arAniData[idx].hact == NULL, "Ensure Action is destroyed"); } } } AssertMsg(m_cQueuedAni == 0, "All queued animations should be destroyed"); m_arAniData.RemoveAll(); // // Notify any listeners that this sequence has completed. // MSGID msgid = 0; const GUID * rgMsg[] = { &__uuidof(Animation::evComplete) }; if (!FindGadgetMessages(rgMsg, &msgid, 1)) { AssertMsg(0, "Animations have not been properly registered"); } Animation::CompleteEvent msg; msg.cbSize = sizeof(msg); msg.nMsg = msgid; msg.hgadMsg = GetStub()->GetHandle(); msg.fNormal = !fKillAnimations; DUserSendEvent(&msg, 0); // // Remove ourself as a Listener // VerifyHR(pgvSubject->RemoveHandlerG(GM_DESTROY, GetStub())); // // Release outstanding reference from when started Play(). // Release(); } } /***************************************************************************\ * * DuSequence::QueueAnimation * * QueueAnimation() queues an Action to be fired when the specified segment * of the overall sequence is to be animated. Since an Animation can only * animate a single segment, we will build multiple Animations to play the * entire sequence. * \***************************************************************************/ HRESULT DuSequence::QueueAnimation( IN int idxKeyFrame) // KeyFrame to setup { AssertMsg((idxKeyFrame < m_arAniData.GetSize()) && (idxKeyFrame >= 0), "Must have valid, non-final keyframe"); SeqData & data1 = m_arSeqData[idxKeyFrame]; AniData & ad = m_arAniData[idxKeyFrame]; ad.pseq = this; ad.idxFrame = idxKeyFrame; // // Setup the segment. If successful, increment m_cQueuedAni to reflect // that the animation has been "enqueued". We need to wait until all // are dequeued before we can "stop" the Animation and allow applications // to modify the Sequence. // HRESULT hr; if (IsSameTime(data1.flTime, 0.0f)) { // // This segment immediate occurs, so directly build the Animation. // ad.hact = NULL; // No action hr = BuildAnimation(idxKeyFrame); if (SUCCEEDED(hr)) { m_cQueuedAni++; } } else { // // This segment occurs in the future, so build a new Action that will // be signaled when to begin the Animation between the specified // keyframes. // GMA_ACTION act; ZeroMemory(&act, sizeof(act)); act.cbSize = sizeof(act); act.flDelay = data1.flTime; act.flDuration = 0.0; act.flPeriod = 0.0; act.cRepeat = 0; act.dwPause = (DWORD) -1; act.pfnProc = DuSequence::ActionProc; act.pvData = &(m_arAniData[idxKeyFrame]); if ((ad.hact = CreateAction(&act)) != NULL) { m_cQueuedAni++; hr = S_OK; } else { hr = (HRESULT) GetLastError(); } } return hr; } /***************************************************************************\ * * DuSequence::BuildAnimation * * BuildAnimation() builds the actual Animation for a given segment of the * sequence. This function is called by QueueAnimation() (for immediate * segments) and ActionProc() (as future segments become ready). * \***************************************************************************/ HRESULT DuSequence::BuildAnimation( IN int idxKeyFrame) // KeyFrame to animate { // // Setup the actual Animation. // SeqData & data1 = m_arSeqData[idxKeyFrame]; SeqData & data2 = m_arSeqData[idxKeyFrame + 1]; float flDuration = data2.flTime - data1.flTime; AssertMsg(m_pflow != NULL, "Must have valid Flow"); m_pflow->SetKeyFrame(Flow::tBegin, data1.pkf); m_pflow->SetKeyFrame(Flow::tEnd, data2.pkf); Animation::AniCI aci; ZeroMemory(&aci, sizeof(aci)); aci.cbSize = sizeof(aci); aci.act.flDuration = flDuration; aci.act.flPeriod = 1; aci.act.cRepeat = 0; aci.act.dwPause = m_dwFramePause; aci.pgvSubject = m_pgvSubject; aci.pipol = data1.pipol; aci.pgflow = m_pflow; Animation * pani = Animation::Build(&aci); if (pani != NULL) { MSGID msgid = 0; const GUID * rgMsg[] = { &__uuidof(Animation::evComplete) }; if (!FindGadgetMessages(rgMsg, &msgid, 1)) { AssertMsg(0, "Animations have not been properly registered"); } VerifyHR(pani->AddHandlerD(msgid, EVENT_DELEGATE(this, OnAnimationComplete))); pani->Release(); return S_OK; } else { // // Unable to build the Animation, so stop any future Animations. // Stop(); return (HRESULT) GetLastError(); } } /***************************************************************************\ * * DuSequence::ActionProc * * ActionProc() is called when the Animation for a given segment is supposed * to begin. * \***************************************************************************/ void CALLBACK DuSequence::ActionProc( IN GMA_ACTIONINFO * pmai) // Action Information { AniData * pad = reinterpret_cast(pmai->pvData); DuSequence * pseq = pad->pseq; if (pmai->fFinished) { if (pad->hact != NULL) { // // The Animation was never built, so we need to decrement the // number of outstanding Animations. // pad->hact = NULL; AssertMsg(pseq->m_cQueuedAni > 0, "Must have an outstanding Animation"); if (--pseq->m_cQueuedAni == 0) { pseq->Stop(FALSE); } } return; } pad->hact = NULL; pseq->BuildAnimation(pad->idxFrame); } /***************************************************************************\ * * DuSequence::OnAnimationComplete * * OnAnimationComplete() is called when an Animation has been completed and * is no longer attached to the subject. * \***************************************************************************/ UINT CALLBACK DuSequence::OnAnimationComplete(EventMsg * pmsg) { // // If all outstanding Animations have ended, then stop the playback. // UNREFERENCED_PARAMETER(pmsg); AssertMsg(m_cQueuedAni > 0, "Must have an outstanding Animation"); if (--m_cQueuedAni == 0) { Stop(FALSE); } return DU_S_COMPLETE; } /***************************************************************************\ * * DuSequence::ApiReset * * ApiReset() resets the given Visual to the beginning of the sequence. * \***************************************************************************/ HRESULT DuSequence::ApiReset(Sequence::ResetMsg * pmsg) { if (IsPlaying()) { PromptInvalid("Sequence is busy"); return DU_E_BUSY; } HRESULT hr = CheckComplete(); if (FAILED(hr)) { return hr; } if (m_arSeqData.GetSize() > 0) { ResetSubject(pmsg->pgvSubject, 0); } return S_OK; } /***************************************************************************\ * * DuSequence::ApiGotoTime * * ApiGotoTime() applies all keyframes to the given Visual that would applied * at a given time. * \***************************************************************************/ HRESULT DuSequence::ApiGotoTime(Sequence::GotoTimeMsg * pmsg) { if (IsPlaying()) { PromptInvalid("Sequence is busy"); return DU_E_BUSY; } HRESULT hr = CheckComplete(); if (FAILED(hr)) { return hr; } // // Find the keyframe before the time // int cItems = m_arSeqData.GetSize(); if (cItems == 0) { // // No key frames, so nothing to do. // return S_OK; } else if (cItems == 1) { // // Only one keyframe, so just reset the object // ResetSubject(pmsg->pgvSubject, 0); } else { // // Multiple keyframes- need to determine the closest keyframe. // - If land on a keyframe, then "exact" // - If before all keyframes, idxFrame = -1; // - If after all keyframes, idxFrame = cItems // - If in the middle, idxFrame = first frame // int idxFrame = -1; BOOL fExact = FALSE; int cItems = m_arSeqData.GetSize(); if (pmsg->flTime > m_arSeqData[cItems - 1].flTime) { idxFrame = cItems; } else { for (int idx = 0; idx < cItems; idx++) { SeqData & data = m_arSeqData[idx]; if (data.pkf != NULL) { if (IsSameTime(data.flTime, pmsg->flTime)) { idxFrame = idx; fExact = TRUE; break; } else if (data.flTime > pmsg->flTime) { idxFrame = idx - 1; fExact = FALSE; break; } } } } if (fExact) { // // Exactly landed on a keyframe, so set directly // ResetSubject(pmsg->pgvSubject, idxFrame); } else { // // Interpolate between two keyframes. Since this wasn't an exact // match, we need to cap the keyframes. // if (idxFrame < 0) { ResetSubject(pmsg->pgvSubject, 0); } else if (idxFrame >= cItems) { ResetSubject(pmsg->pgvSubject, cItems - 1); } else { SeqData & dataA = m_arSeqData[idxFrame]; SeqData & dataB = m_arSeqData[idxFrame + 1]; float flTimeA = dataA.flTime; float flTimeB = dataB.flTime; float flProgress = (pmsg->flTime - flTimeA) / (flTimeB - flTimeA); if (flProgress > 1.0f) { flProgress = 1.0f; } m_pflow->SetKeyFrame(Flow::tBegin, dataA.pkf); m_pflow->SetKeyFrame(Flow::tEnd, dataB.pkf); m_pflow->OnAction(pmsg->pgvSubject, dataA.pipol, flProgress); } } } return S_OK; } /***************************************************************************\ * * DuSequence::RemoveAllKeyFrames * * RemoveAllKeyFrames() removes all KeyFrames. * \***************************************************************************/ void DuSequence::RemoveAllKeyFrames() { int cItems = m_arSeqData.GetSize(); for (int idx = 0; idx < cItems; idx++) { SeqData & data = m_arSeqData[idx]; ClientFree(data.pkf); SafeRelease(data.pipol); } m_arSeqData.RemoveAll(); } /***************************************************************************\ * * DuSequence::ResetSubject * * ResetSubject() resets the given subject to the beginning of the Sequence. * \***************************************************************************/ void DuSequence::ResetSubject(Visual * pgvSubject, int idxFrame) { m_pflow->SetKeyFrame(Flow::tBegin, m_arSeqData[idxFrame].pkf); m_pflow->OnReset(pgvSubject); } /***************************************************************************\ * * DuSequence::CompareItems * * CompareItems() is called from SortKeyFrames() to sort two individual * KeyFrames by time. * \***************************************************************************/ int DuSequence::CompareItems( IN const void * pva, // First SeqData IN const void * pvb) // Second SeqData { float flTimeA = ((SeqData *) pva)->flTime; float flTimeB = ((SeqData *) pvb)->flTime; if (flTimeA < flTimeB) { return -1; } else if (flTimeA > flTimeB) { return 1; } else { return 0; } } /***************************************************************************\ * * DuSequence::FindAtTime * * FindAtTime() finds the KeyFrame at the given time. * \***************************************************************************/ void DuSequence::FindAtTime( IN float flTime, // Time of KeyFrame OUT int * pidxKeyFrame // KeyFrame, if found ) const { int cItems = m_arSeqData.GetSize(); for (int idx = 0; idx < cItems; idx++) { SeqData & data = m_arSeqData[idx]; if (data.pkf != NULL) { if (IsSameTime(data.flTime, flTime)) { *pidxKeyFrame = idx; return; } } } *pidxKeyFrame = -1; // Not found } /***************************************************************************\ * * DuSequence::CheckComplete * * CheckComplete() determines if all information for the Sequence has been * filled in. This is necessary when use the sequence to play animations. * \***************************************************************************/ HRESULT DuSequence::CheckComplete() const { if (m_pflow == NULL) { PromptInvalid("Flow has not been specified"); return E_INVALIDARG; } int cItems = m_arSeqData.GetSize(); for (int idx = 0; idx < cItems; idx++) { if (m_arSeqData[idx].pkf == NULL) { PromptInvalid("KeyFrame information has not been specified"); return E_INVALIDARG; } if (m_arSeqData[idx].pipol == NULL) { PromptInvalid("Interpolation has not been specified"); return E_INVALIDARG; } } return S_OK; } #if DBG /***************************************************************************\ * * DuSequence::DEBUG_IsProperTimeOrder * * DEBUG_IsProperTimeOrder() validates that all keyframes are in increasing * time order. * \***************************************************************************/ BOOL DuSequence::DEBUG_IsProperTimeOrder() const { float flTime = 0; int cItems = m_arSeqData.GetSize(); for (int idx = 0; idx < cItems; idx++) { if (m_arSeqData[idx].flTime < flTime) { return FALSE; } flTime = m_arSeqData[idx].flTime; } return TRUE; } #endif // DBG #endif // ENABLE_MSGTABLE_API