Windows2003-3790/windows/advcore/duser/engine/ctrl/sequence.cpp
2020-09-30 16:53:55 +02:00

1236 lines
32 KiB
C++

#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<GMSG_DESTROY *>(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<DUser::KeyFrame *> (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<AniData *>(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