/* ***** BEGIN LICENSE BLOCK *****
 * Source last modified: $Id: animsand.cpp,v 1.2.12.1 2004/07/09 01:58:01 hubbe Exp $
 * 
 * Portions Copyright (c) 1995-2004 RealNetworks, Inc. All Rights Reserved.
 * 
 * The contents of this file, and the files included with this file,
 * are subject to the current version of the RealNetworks Public
 * Source License (the "RPSL") available at
 * http://www.helixcommunity.org/content/rpsl unless you have licensed
 * the file under the current version of the RealNetworks Community
 * Source License (the "RCSL") available at
 * http://www.helixcommunity.org/content/rcsl, in which case the RCSL
 * will apply. You may also obtain the license terms directly from
 * RealNetworks.  You may not use this file except in compliance with
 * the RPSL or, if you have a valid RCSL with RealNetworks applicable
 * to this file, the RCSL.  Please see the applicable RPSL or RCSL for
 * the rights, obligations and limitations governing use of the
 * contents of the file.
 * 
 * Alternatively, the contents of this file may be used under the
 * terms of the GNU General Public License Version 2 or later (the
 * "GPL") in which case the provisions of the GPL are applicable
 * instead of those above. If you wish to allow use of your version of
 * this file only under the terms of the GPL, and not to allow others
 * to use your version of this file under the terms of either the RPSL
 * or RCSL, indicate your decision by deleting the provisions above
 * and replace them with the notice and other provisions required by
 * the GPL. If you do not delete the provisions above, a recipient may
 * use your version of this file under the terms of any one of the
 * RPSL, the RCSL or the GPL.
 * 
 * This file is part of the Helix DNA Technology. RealNetworks is the
 * developer of the Original Code and owns the copyrights in the
 * portions it created.
 * 
 * This file, and the files included with this file, is distributed
 * and made available on an 'AS IS' basis, WITHOUT WARRANTY OF ANY
 * KIND, EITHER EXPRESS OR IMPLIED, AND REALNETWORKS HEREBY DISCLAIMS
 * ALL SUCH WARRANTIES, INCLUDING WITHOUT LIMITATION, ANY WARRANTIES
 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, QUIET
 * ENJOYMENT OR NON-INFRINGEMENT.
 * 
 * Technology Compatibility Kit Test Suite(s) Location:
 *    http://www.helixcommunity.org/content/tck
 * 
 * Contributor(s):
 * 
 * ***** END LICENSE BLOCK ***** */

// system
#include <time.h>
#include <math.h>
// include
#include "hxtypes.h"
#include "hxwintyp.h"
#include "hxcom.h"
#include "hxxml.h"
#include "smiltype.h"
// pnmisc
#include "hxwinver.h"
// pncont
#include "hxslist.h"
// rnxmllib
#include "hxxmlprs.h"
// rmasmil
#include "smlelem.h"
#include "smlparse.h"
#include "animattr.h"
// smlrendr
#include "smlrmlog.h"
#include "animsand.h"
// pndebug
#include "hxheap.h"
#include "debugout.h"
#ifdef _DEBUG
#undef HX_THIS_FILE		
static const char HX_THIS_FILE[] = __FILE__;
#endif

CAnimationSandwichLayer::CAnimationSandwichLayer(CSmilAnimateElement* pElement,
                                                 UINT32               ulAttrName,
                                                 CSmilParser*         pParser)
{
    MLOG_LEAKCHECK("CON CAnimationSandwichLayer this=0x%08x\n", this);
    m_pElement         = NULL;
    m_ulAttrName       = kAttrNameUnknown;
    m_pdPacedDist      = NULL;
    m_pdPacedTime      = NULL;
    m_bRecomputePace   = FALSE;
    m_bFirstTime       = TRUE;
    m_pSmilParser      = pParser;
    m_ulLastTime       = 0;
    m_ulDelay          = 0;
    m_ulActiveDuration = 0;

    if (pElement && pElement->m_pNode)
    {
        // Make sure the attribute name makes sense
        // with this element. If this element
        // is NOT an <animateMotion>, then we expect
        // the attribute name in the element to match
        // the passed-in attribute name. If this 
        // element IS <animateMotion>, then the passed-in
        // attribute should be either "left" or "top".
        if ((pElement->m_pNode->m_tag == SMILAnimateMotion &&
             (ulAttrName == kAttrNameLeft || ulAttrName == kAttrNameTop)) ||
            (pElement->m_pNode->m_tag != SMILAnimateMotion &&
             ulAttrName == (UINT32) pElement->m_ucAttributeName))
        {
            // Assign the members
            m_pElement         = pElement;
            m_ulAttrName       = ulAttrName;
            m_ulDelay          = pElement->m_ulDelay;
            m_ulActiveDuration = pElement->m_ulActiveDuration;
            // Check if the "end" attribute constrains
            // the active duration.
            if (pElement->m_bEndOffsetSet)
            {
                UINT32 ulEndMinusBegin = ((UINT32) pElement->m_lEndOffset) - m_ulDelay;
                if (ulEndMinusBegin < m_ulActiveDuration)
                {
                    m_ulActiveDuration = ulEndMinusBegin;
                }
            }
            // If we are a paced animation, then we need to
            // decide how we are going to compute the D()
            // and T() functions.
            if (pElement->m_ucCalcMode == kCalcModePaced)
            {
                // Now we need to determine if these functions should be
                // re-computed every time. We need to do this IF any
                // attributes are relative.
                BOOL bSomeRelative = FALSE;
                for (UINT32 i = 0; i < pElement->m_ulNumValues; i++)
                {
                    if (pElement->m_ppValue[i])
                    {
                        for (UINT32 j = 0; j < CAttr::kVectorSize; j++)
                        {
                            if (pElement->m_ppValue[i]->IsRelative(j))
                            {
                                bSomeRelative = TRUE;
                            }
                        }
                    }
                }
                if (bSomeRelative)
                {
                    // We will have to recompute the functions
                    // every time we evaluate
                    m_bRecomputePace = TRUE;
                }
            }
        }
    }
}

CAnimationSandwichLayer::~CAnimationSandwichLayer()
{
    MLOG_LEAKCHECK("DES CAnimationSandwichLayer this=0x%08x\n", this);
    HX_VECTOR_DELETE(m_pdPacedDist);
    HX_VECTOR_DELETE(m_pdPacedTime);
}

const char* CAnimationSandwichLayer::GetAnimationElementID() const
{
    const char* pRet = NULL;

    if (m_pElement &&
        m_pElement->m_pNode)
    {
        pRet = (const char*) m_pElement->m_pNode->m_id;
    }

    return pRet;
}

const char* CAnimationSandwichLayer::GetTargetElementID() const
{
    const char* pRet = NULL;

    if (m_pElement && m_pElement->m_pTargetElementID)
    {
        pRet = (const char*) *m_pElement->m_pTargetElementID;
    }

    return pRet;
}

SMILNodeTag CAnimationSandwichLayer::GetTargetElementTag() const
{
    SMILNodeTag eRet = SMILUnknown;

    if (m_pElement)
    {
        eRet = m_pElement->m_eTargetElementTag;
    }

    return eRet;
}

SMILNodeTag CAnimationSandwichLayer::GetAnimationElementTag() const
{
    SMILNodeTag eRet = SMILUnknown;

    if (m_pElement && m_pElement->m_pNode)
    {
        eRet = m_pElement->m_pNode->m_tag;
    }

    return eRet;
}

BOOL CAnimationSandwichLayer::IsActive(UINT32 ulTime) const
{
    BOOL bRet = FALSE;

    if (m_pElement && ulTime >= m_ulDelay)
    {
        if (m_pElement->m_bIndefiniteActiveDuration ||
            ulTime < m_ulDelay + m_ulActiveDuration)
        {
            // During the active duration - it's active
            bRet = TRUE;
        }
    }

    return bRet;
}

BOOL CAnimationSandwichLayer::IsFrozen(UINT32 ulTime) const
{
    BOOL bRet = FALSE;

    if (m_pElement &&
        (m_pElement->m_eActualFill == FillFreeze ||
         m_pElement->m_eActualFill == FillHold))
    {
        if (!m_pElement->m_bIndefiniteActiveDuration)
        {
            if (ulTime >= m_ulDelay + m_ulActiveDuration)
            {
                // XXXMEH - now that we are calculating a remove
                // time for every timed element, we should be able
                // to use the m_ulRemoveTime. However, let's keep
                // the old code around "just in case".
                if (m_pElement->m_ulRemoveTime == ((UINT32) -1))
                {
                    UINT32 ulRemoveTime = 0;
                    HX_RESULT rv = m_pSmilParser->computeRemoveTime(m_pElement->m_pNode->m_id,
                                                                    ulRemoveTime);
                    if (SUCCEEDED(rv))
                    {
                        m_pElement->m_ulRemoveTime = ulRemoveTime;
                    }
                }
                if (ulTime <= m_pElement->m_ulRemoveTime)
                {
                    bRet = TRUE;
                }
            }
        }
    }

    return bRet;
}

BOOL CAnimationSandwichLayer::IsActiveZeroBased(UINT32 ulTime) const
{
    BOOL bRet = FALSE;

    if (m_pElement &&
        (m_pElement->m_bIndefiniteActiveDuration ||
         ulTime < m_ulActiveDuration))
    {
        // During the active duration - it's active
        bRet = TRUE;
    }

    return bRet;
}

BOOL CAnimationSandwichLayer::IsFrozenZeroBased(UINT32 ulTime) const
{
    BOOL bRet = FALSE;

    if (m_pElement &&
        (m_pElement->m_eActualFill == FillFreeze ||
         m_pElement->m_eActualFill == FillHold))
    {
        if (!m_pElement->m_bIndefiniteActiveDuration)
        {
            if (ulTime >= m_ulActiveDuration)
            {
                if (ulTime <= (m_pElement->m_ulRemoveTime - m_ulDelay))
                {
                    bRet = TRUE;
                }
            }
        }
    }

    return bRet;
}

BOOL CAnimationSandwichLayer::IsAdditive() const
{
    BOOL bRet = FALSE;

    if (m_pElement)
    {
        if (m_pElement->m_ucAdditive == kAdditiveSum)
        {
            bRet = TRUE;
        }
    }

    return bRet;
}

BOOL CAnimationSandwichLayer::IsToAnimation() const
{
    BOOL bRet = FALSE;

    if (m_pElement &&
        m_pElement->m_ucAnimationType == kAnimTypeTo)
    {
        bRet = TRUE;
    }

    return bRet;
}

CAttr CAnimationSandwichLayer::AnimationEffectFunction(UINT32 ulTime,
                                                       CAttr* pUnder,
                                                       CAttr* pDepend)
{
    CAttr cRet;

    if (pUnder)
    {
        // Initialize to the underlying value
        cRet = *pUnder;
        // Compute the zero-based time
        UINT32 ulT = ulTime - m_ulDelay; // XXXMEH - is this right?
        // Compute the time after time manipulations
        UINT32 ulTM = ComputeFilteredSimpleTime(ulT);
        // Decide if we're active or frozen
        if (IsActiveZeroBased(ulT))
        {
            // Evaluate the cumulative animation function at this time
            cRet = CumulativeAnimationFunction(ulTM, pUnder, pDepend);
            // Now we need to check if we need to fire a repeatEvent.
            //
            // Compute which iteration the last time was in
            UINT32 ulLastIter = 0;
            if (m_pElement->m_ulSimpleDuration)
            {
                ulLastIter = m_ulLastTime / m_pElement->m_ulSimpleDuration;
            }
            // Compute which iteration we are currently in
            UINT32 ulThisIter = 0;
            if (m_pElement->m_ulSimpleDuration)
            {
                ulThisIter = ulTM / m_pElement->m_ulSimpleDuration;
            }
            // If we are in different iterations, then we will
            // assume that this is the first time in the new iteration
            // and we will fire a "repeatEvent"
            if (ulLastIter != ulThisIter &&
                m_pSmilParser)
            {
                m_pSmilParser->tryToResolveBeginEndEvents("repeatEvent",
                                                          m_pElement->m_pNode->m_id,
                                                          ulTime);
                // XXXMEH - temporarily, we will also fire a "repeat(x)" event
                char szTmp[32]; /* Flawfinder: ignore */
                sprintf(szTmp, "repeat(%lu)", ulThisIter); /* Flawfinder: ignore */
                m_pSmilParser->tryToResolveBeginEndEvents((const char*) szTmp,
                                                          m_pElement->m_pNode->m_id,
                                                          ulTime);
            }
            // Save the last time
            m_ulLastTime = ulTM;
        }
        else if (IsFrozenZeroBased(ulT))
        {
            // We are in the frozen period after the active duration
            //
            // Compute the remainder time
            // XXXMEH - TODO - most of the time this "frozen time"
            // calculation can be done once up front.
            UINT32 ulModTime = 0;
            // m_ulActiveDuration takes into speed modifications. We
            // need to undo this before we compute the remainder time.
            UINT32 ulNewAD = m_ulActiveDuration;
            if (m_pElement->m_dSpeed != 1.0)
            {
                double dNewAD = (double) ulNewAD * fabs(m_pElement->m_dSpeed);
                ulNewAD       = (UINT32) floor(dNewAD + 0.5);
            }
            // m_ulSimpleDuration does NOT take into account autoReverse,
            // but m_ulActiveDuration does. So we need to add in the
            // effect of autoReverse to m_ulSimpleDuration
            UINT32 ulNewSD = m_pElement->m_ulSimpleDuration;
            if (m_pElement->m_bAutoReverse)
            {
                ulNewSD *= 2;
            }
            // Now we can compute the remainder time
            if (ulNewSD)
            {
                ulModTime = ulNewAD % ulNewSD;
            }
            // Now, our active duration an even multiple of simple duration?
            if (ulModTime)
            {
                // Active duration is NOT an even multiple of simple duration,
                // so we just evaluate the cumulative animation function
                // at the end of the active duration. Make sure
                // and pass the time through time manipulations.
                UINT32 ulADTM = ComputeFilteredSimpleTime(m_ulActiveDuration);
                cRet = CumulativeAnimationFunction(ulADTM, pUnder, pDepend);
            }
            else
            {
                // Active duration IS an even multiple of simple duration
                //
                // Are we cumulative or not?
                if (m_pElement->m_ucAccumulate == kAccumulateNone)
                {
                    // We are non-cumulative - simply evaluate at the
                    // simple duration. First pass the simple duration
                    // through time manipulations
                    UINT32 ulSD = m_pElement->m_ulSimpleDuration;
                    if (m_pElement->m_bAutoReverse)
                    {
                        ulSD *= 2;
                    }
                    if (m_pElement->m_dSpeed != 1.0 &&
                        m_pElement->m_dSpeed != 0.0)
                    {
                        double dSDSpeed = (double) ulSD / fabs(m_pElement->m_dSpeed);
                        ulSD = (UINT32) floor(dSDSpeed + 0.5);
                    }
                    UINT32 ulSDTM = ComputeFilteredSimpleTime(ulSD);
                    cRet = SimpleAnimationFunction(ulSDTM, pUnder, pDepend);
                }
                else
                {
                    // We ARE cumulative
                    //
                    // Compute the multiple
                    UINT32 ulMult    = 1;
                    UINT32 ulCumTime = 0;
                    if (m_pElement->m_ulSimpleDuration)
                    {
                        if (m_pElement->m_bAutoReverse)
                        {
                            ulMult    = m_ulActiveDuration / (2 * m_pElement->m_ulSimpleDuration);
                            ulCumTime = 2 * m_pElement->m_ulSimpleDuration * (ulMult - 1);
                        }
                        else
                        {
                            ulMult    = m_ulActiveDuration / m_pElement->m_ulSimpleDuration;
                            ulCumTime = m_pElement->m_ulSimpleDuration * (ulMult - 1);
                        }
                    }
                    // Compute the frozen value
                    UINT32 ulCumTimeTM = ComputeFilteredSimpleTime(ulCumTime);
                    CAttr cCum  = CumulativeAnimationFunction(ulCumTimeTM, pUnder, pDepend);
                    UINT32 ulSDTM = 0;
                    if (m_pElement->m_bAutoReverse)
                    {
                        ulSDTM = ComputeFilteredSimpleTime(2 * m_pElement->m_ulSimpleDuration);
                    }
                    else
                    {
                        ulSDTM = ComputeFilteredSimpleTime(m_pElement->m_ulSimpleDuration);
                    }
                    cRet = SimpleAnimationFunction(ulSDTM, pUnder, pDepend);
                    cRet.Add(&cCum, pDepend);
                }
            }
            // Save the last time
            m_ulLastTime = ulTM;
        }
    }

    return cRet;
}

BOOL CAnimationSandwichLayer::HigherPriority(CAnimationSandwichLayer* pLayer)
{
    BOOL bRet = TRUE;

    if (pLayer)
    {
        if (m_ulDelay > pLayer->m_ulDelay)
        {
            // We start later than pLayer, so we're higher priority
            bRet = TRUE;
        }
        else if (m_ulDelay == pLayer->m_ulDelay)
        {
            // We start at the same time as pLayer, so the spec
            // says we first have to look at sync relationships
            if (m_pElement->m_BeginEventSourceID == pLayer->m_pElement->m_pNode->m_id)
            {
                // pLayer is our syncbase, so we are higher priority
                bRet = TRUE;
            }
            else if (pLayer->m_pElement->m_BeginEventSourceID == m_pElement->m_pNode->m_id)
            {
                // We are pLayer's syncbase, so pLayer is higher priority
                bRet = FALSE;
            }
            else
            {
                // There is no sync relationship between us and pLayer,
                // so we have to look at lexical order. First look at
                // the line the tag starts
                if (m_pElement->m_pNode->m_ulTagStartLine >
                    pLayer->m_pElement->m_pNode->m_ulTagStartLine)
                {
                    // We start lexically later, so we are 
                    // higher priority
                    bRet = TRUE;
                }
                else if (m_pElement->m_pNode->m_ulTagStartLine ==
                         pLayer->m_pElement->m_pNode->m_ulTagStartLine)
                {
                    // We start on the same line, so look
                    // at the starting column
                    if (m_pElement->m_pNode->m_ulTagStartColumn >
                        pLayer->m_pElement->m_pNode->m_ulTagStartColumn)
                    {
                        // We start lexically later, so we are 
                        // higher priority
                        bRet = TRUE;
                    }
                    else if (m_pElement->m_pNode->m_ulTagStartColumn ==
                             pLayer->m_pElement->m_pNode->m_ulTagStartColumn)
                    {
                        // Huh? How can a different element start at
                        // the same line and column that we do? Probably
                        // bogus, so make an arbitrary choice
                        bRet = TRUE;
                    }
                    else
                    {
                        // We start lexically earlier, so we are NOT
                        // higher priority
                        bRet = FALSE;
                    }
                }
                else
                {
                    // We start lexically earlier, so we are NOT
                    // higher priority
                    bRet = FALSE;
                }
            }
        }
        else
        {
            // pLayer starts later than us, so it's higher priority
            bRet = FALSE;
        }
    }

    return bRet;
}

void CAnimationSandwichLayer::AdjustActiveDuration(UINT32 ulTime)
{
    if (ulTime >= m_ulDelay)
    {
        m_ulActiveDuration = ulTime - m_ulDelay;
    }
}

CAttr CAnimationSandwichLayer::SimpleAnimationFunction(UINT32 ulTime,
                                                       CAttr* pUnder,
                                                       CAttr* pDepend)
{
    CAttr cRet;

    if (pUnder)
    {
        // Initialize the return to the underlying value
        cRet = *pUnder;
        // Do we have an indefinite simple duration?
        if (m_pElement->m_bIndefiniteSimpleDuration)
        {
            // Our simple duration is indefinite, so interpolation
            // is not defined. We should set ourselves to value(0).
            //
            // What kind of animation are we?
            if (m_pElement->m_pNode->m_tag == SMILSet)
            {
                // If this is a set, then we know there is
                // only one values, so we need to just
                // set the value to m_ppValue[1]
                if (m_pElement->m_ppValue[1])
                {
                    cRet = *m_pElement->m_ppValue[1];
                }
            }
            else if (m_pElement->m_ucAnimationType == kAnimTypeTo)
            {
                // If this is a to-animation, then value(0)
                // is the underlying value.
                cRet = *pUnder;
            }
            else
            {
                // Assign the value(0) if we have one
                if (m_pElement->m_ppValue[0])
                {
                    cRet = *m_pElement->m_ppValue[0];
                }
            }
        }
        else
        {
            // Our simple duration is not indefinite
            //
            // We are just defined over the simple duration
            if (ulTime <= m_pElement->m_ulSimpleDuration)
            {
                if (m_pElement->m_pNode->m_tag == SMILSet)
                {
                    // If this is a set, then we know there is
                    // only two values, so we need to just
                    // set the value to m_ppValue[1]
                    if (m_pElement->m_ppValue[1])
                    {
                        cRet = *m_pElement->m_ppValue[1];
                    }
                }
                else
                {
                    // What is our calcmode?
                    if (m_pElement->m_ucCalcMode == kCalcModeDiscrete)
                    {
                        // Divide the interval into m_ulNumValues segments
                        UINT32 ulIndex = m_pElement->m_ulNumValues;
                        if (m_pElement->m_ulSimpleDuration)
                        {
                            ulIndex = m_pElement->m_ulNumValues * ulTime / m_pElement->m_ulSimpleDuration;
                        }
                        // Make sure that m_pElement->m_ulSimpleDuration maps
                        // to index m_ulNumValues - 1 and not m_ulNumValues
                        if (ulIndex >= m_pElement->m_ulNumValues)
                        {
                            ulIndex = m_pElement->m_ulNumValues - 1;
                        }
                        // If we are a "to" animation, then m_ppValue[0] is
                        // a placeholder for the underlying value. Therefore,
                        // if we are a "to" animation and have selected index 0,
                        // then we need to replace it by the underlying value
                        if (m_pElement->m_ucAnimationType == kAnimTypeTo &&
                            ulIndex == 0)
                        {
                            cRet = *pUnder;
                        }
                        else if (m_pElement->m_ucAnimationType == kAnimTypeFromBy &&
                                 ulIndex == 1)
                        {
                            // On a from-by animation, you are interpolating
                            // between the from value and the sum of the from
                            // and by values. m_ppValue[0] is the from value.
                            // However, m_ppValue[1] is the by value, not the
                            // sum of the from and by values. So if we have
                            // chosen index 1, then we need to compute the sum
                            // of the from and by values
                            //
                            if (m_pElement->m_ppValue[0] &&
                                m_pElement->m_ppValue[1])
                            {
                                // First assign the from value
                                cRet = *m_pElement->m_ppValue[0];
                                // Then add the by value
                                cRet.Add(m_pElement->m_ppValue[1], pDepend);
                            }
                        }
                        else
                        {
                            // Simply return the proper value
                            if (m_pElement->m_ppValue[ulIndex])
                            {
                                cRet = *m_pElement->m_ppValue[ulIndex];
                            }
                        }
                    }
                    else if (m_pElement->m_ucCalcMode == kCalcModeLinear ||
                             (m_pElement->m_ucCalcMode == kCalcModePaced &&
                              m_pElement->m_ulNumValues == 2))
                    {
                        // Is this a "to" animation?
                        if (m_pElement->m_ucAnimationType == kAnimTypeTo)
                        {
                            // If we are a "to" animation, then we interpolate
                            // between the underlying value and the "to" value,
                            // which is (m_ppValue[1]).
                            if (m_pElement->m_ppValue[1])
                            {
                                // Do the interpolation
                                double t1 = 0.0;
                                double t2 = (double) m_pElement->m_ulSimpleDuration;
                                double t  = ulTime;
                                cRet.Interp(pUnder, t1, m_pElement->m_ppValue[1], t2, t, pDepend);
                            }
                        }
                        else if (m_pElement->m_ucAnimationType == kAnimTypeFromBy)
                        {
                            // On a from-by animation, you are interpolating
                            // between the from value and the sum of the from
                            // and by values. m_ppValue[0] is the from value.
                            // However, m_ppValue[1] is the by value, not the
                            // sum of the from and by values. So we need to
                            // compute the sum of the from and by values
                            if (m_pElement->m_ppValue[0] &&
                                m_pElement->m_ppValue[1])
                            {
                                CAttr cSum = *m_pElement->m_ppValue[0];
                                cSum.Add(m_pElement->m_ppValue[1], pDepend);
                                // Then we interpolate between m_ppValue[0] (the
                                // "from" value) and cSum (the sum of the "from"
                                // and "by" values).
                                double t1 = 0.0;
                                double t2 = (double) m_pElement->m_ulSimpleDuration;
                                double t  = (double) ulTime;
                                cRet.Interp(m_pElement->m_ppValue[0], t1, &cSum, t2, t, pDepend);
                            }
                        }
                        else
                        {
                            // Divide the interval into m_ulNumValues-1 segments
                            UINT32 ulIndex = m_pElement->m_ulNumValues - 1;
                            if (m_pElement->m_ulSimpleDuration)
                            {
                                ulIndex = (m_pElement->m_ulNumValues - 1) * ulTime / m_pElement->m_ulSimpleDuration;
                            }
                            // Now interpolate between this value and the next
                            if (ulIndex + 1 < m_pElement->m_ulNumValues &&
                                m_pElement->m_ppValue[ulIndex] &&
                                m_pElement->m_ppValue[ulIndex + 1])
                            {
                                // Do the interpolation
                                double t1 = ulIndex * m_pElement->m_ulSimpleDuration /
                                            (m_pElement->m_ulNumValues - 1);
                                double t2 = (ulIndex + 1) * m_pElement->m_ulSimpleDuration /
                                            (m_pElement->m_ulNumValues - 1);
                                double t  = ulTime;
                                cRet.Interp(m_pElement->m_ppValue[ulIndex],     t1,
                                            m_pElement->m_ppValue[ulIndex + 1], t2,
                                            t, pDepend);
                            }
                            else
                            {
                                // The only way this can happen is if
                                // ulTime == m_pElement->m_ulSimpleDuration, so if this
                                // if the case, we need to return the un-interpolated
                                // value of m_ppValue[m_pElement->m_ulNumValues - 1]
                                if (m_pElement->m_ppValue[m_pElement->m_ulNumValues - 1])
                                {
                                    cRet = *m_pElement->m_ppValue[m_pElement->m_ulNumValues - 1];
                                }
                            }
                        }
                    }
                    else if (m_pElement->m_ucCalcMode == kCalcModePaced)
                    {
                        // If we need to recompute the paced tables,
                        // then do it now
                        if (m_bRecomputePace ||
                            (!m_bRecomputePace && m_bFirstTime))
                        {
                            RecomputePace(pDepend);
                        }
                        // Find out what index we're at
                        double t = (double) ulTime;
                        UINT32 i = 0;
                        for (i = 0; i < m_pElement->m_ulNumValues - 1; i++)
                        {
                            if (t >= m_pdPacedTime[i] && t <= m_pdPacedTime[i + 1])
                            {
                                break;
                            }
                        }
                        if (i + 1 < m_pElement->m_ulNumValues)
                        {
                            // Do the interpolation
                            cRet.Interp(m_pElement->m_ppValue[i],     m_pdPacedTime[i],
                                        m_pElement->m_ppValue[i + 1], m_pdPacedTime[i + 1],
                                        (double) ulTime, pDepend);
                        }
                    }
                }
                // Now we can clear the first time flag
                m_bFirstTime = FALSE;
            }
        }
    }

    return cRet;
}

CAttr CAnimationSandwichLayer::CumulativeAnimationFunction(UINT32 ulTime,
                                                           CAttr* pUnder,
                                                           CAttr* pDepend)
{
    CAttr cRet;

    if (pUnder)
    {
        // Initialize to the underlying value
        cRet = *pUnder;
        // Are we cumulative or not?
        if (m_pElement->m_ucAccumulate == kAccumulateSum)
        {
            // We are a cumulative animation
            //
            // Modulo the time by the simple duration
            UINT32 ulModTime = 0;
            if (m_pElement->m_ulSimpleDuration)
            {
                ulModTime = ulTime % m_pElement->m_ulSimpleDuration;
            }
            // Evaluate the simple animation function at this time
            CAttr cMod = SimpleAnimationFunction(ulModTime, pUnder, pDepend);
            // Find the iteration
            UINT32 ulIter = 0;
            if (m_pElement->m_ulSimpleDuration)
            {
                ulIter = ulTime / m_pElement->m_ulSimpleDuration;
            }
            if (ulIter)
            {
                // Evaluate the animation at the simple duration
                cRet = SimpleAnimationFunction(m_pElement->m_ulSimpleDuration,
                                               pUnder, pDepend);
                // Multiply by iteration value
                cRet.Mult((double) ulIter);
                // Add the simple animation result
                cRet.Add(&cMod, pDepend);
            }
            else
            {
                // We're on the first iteration
                cRet = cMod;
            }
        }
        else
        {
            // We are a non-cumulative animation
            //
            // Modulo the time by the simple duration
            UINT32 ulModTime = 0;
            if (m_pElement->m_ulSimpleDuration)
            {
                ulModTime = ulTime % m_pElement->m_ulSimpleDuration;
            }
            // Evaluate the simple animation function at this time
            cRet = SimpleAnimationFunction(ulModTime, pUnder, pDepend);
        }
    }

    return cRet;
}

void CAnimationSandwichLayer::RecomputePace(CAttr* pDepend)
{
    if (!m_pdPacedDist)
    {
        m_pdPacedDist = new double [m_pElement->m_ulNumValues];
        if (m_pdPacedDist)
        {
            memset((void*) m_pdPacedDist, 0, sizeof(double) *
                   m_pElement->m_ulNumValues);
        }
    }
    if (!m_pdPacedTime)
    {
        m_pdPacedTime = new double [m_pElement->m_ulNumValues];
        if (m_pdPacedTime)
        {
            memset((void*) m_pdPacedTime, 0, sizeof(double) *
                   m_pElement->m_ulNumValues);
        }
    }
    if (m_pdPacedDist && m_pdPacedTime)
    {
        UINT32 i = 0;
        // Compute D()
        m_pdPacedDist[0] = 0.0;
        for (i = 1; i < m_pElement->m_ulNumValues; i++)
        {
            m_pdPacedDist[i] = m_pdPacedDist[i - 1] +
                               CAttr::Dist(m_pElement->m_ppValue[i - 1],
                                           m_pElement->m_ppValue[i],
                                           pDepend);
        }
        // Compute T()
        double dTotal = m_pdPacedDist[m_pElement->m_ulNumValues - 1];
        for (i = 0; i < m_pElement->m_ulNumValues; i++)
        {
            m_pdPacedTime[i] = m_pdPacedDist[i] * m_pElement->m_ulSimpleDuration / dTotal;
        }
    }
}

UINT32 CAnimationSandwichLayer::ComputeFilteredSimpleTime(UINT32 ulUnfilteredActiveTime)
{
    UINT32 ulRet = ulUnfilteredActiveTime;

    if (m_pElement &&
        (m_pElement->m_bAutoReverse       ||
         m_pElement->m_dSpeed      != 1.0 ||
         m_pElement->m_dAccelerate != 0.0 ||
         m_pElement->m_dDecelerate != 0.0))
    {
        // Compute the duration of the acceleration phase
        double dacc = ((double) m_pElement->m_ulSimpleDuration) *
                      m_pElement->m_dAccelerate;
        // Compute the duration of the deceleration phase
        double ddec = ((double) m_pElement->m_ulSimpleDuration) *
                      m_pElement->m_dDecelerate;
        // Compute the active filtered time (which takes
        // into account the speed attribute)
        double dActiveFilteredTime = 0.0;
        if (m_pElement->m_dSpeed > 0.0)
        {
            dActiveFilteredTime = ((double) ulUnfilteredActiveTime) *
                                  m_pElement->m_dSpeed;
        }
        else
        {
            dActiveFilteredTime = ((double) m_pElement->m_ulADNoSpeed) -
                                  (((double) ulUnfilteredActiveTime) *
                                   fabs(m_pElement->m_dSpeed));
        }
        // Compute the unfiltered simple time
        double dUnfilteredSimpleTime = dActiveFilteredTime;
        double dDur                  = (double) m_pElement->m_ulSimpleDuration;
        double dDurPrime             = dDur;
        if (m_pElement->m_bAutoReverse)
        {
            dDurPrime *= 2.0;
        }
        // Is there any repeating behavior?
        if (m_pElement->m_dRepeatCount == 1.0 &&
            (m_pElement->m_ulRepeatDur  == ((UINT32) -1) ||
             m_pElement->m_bRepeatDurIsIndefinite))
        {
            // No repeating behavior here, so unfiltered simple
            // time is equal to active filtered time
            dUnfilteredSimpleTime = dActiveFilteredTime;
        }
        else
        {
            // This animation repeats, so calcuate the
            // remainder
            if (dDurPrime != 0.0)
            {
                dUnfilteredSimpleTime =
                    dActiveFilteredTime -
                    dDurPrime * floor(dActiveFilteredTime / dDurPrime);
            }
        }
        // Account for autoReverse behavior
        double dUnfilteredSimpleTimePrime = dUnfilteredSimpleTime;
        if (m_pElement->m_bAutoReverse &&
            dUnfilteredSimpleTime >= dDur)
        {
            dUnfilteredSimpleTimePrime = (2.0 * dDur) - dUnfilteredSimpleTime;
        }
        // Compute the decel begin time
        double dDecelBegin = dDur - ddec;
        // Compute the filtered simple time
        double dFilteredSimpleTime = (double) dUnfilteredSimpleTimePrime;
        double dRDenom             = 1.0 -
                                     (m_pElement->m_dAccelerate / 2.0) - 
                                     (m_pElement->m_dDecelerate / 2.0);
        double dR = 1.0;
        if (dRDenom != 0.0)
        {
            dR = 1.0 / dRDenom;
        }
        if (dUnfilteredSimpleTimePrime >= 0.0 &&
            dUnfilteredSimpleTimePrime < dacc)
        {
            dFilteredSimpleTime = dUnfilteredSimpleTimePrime *
                                  ComputeRunRate(dUnfilteredSimpleTimePrime,
                                                 m_pElement->m_dAccelerate,
                                                 m_pElement->m_dDecelerate,
                                                 dDur) / 2.0;
        }
        else if (dUnfilteredSimpleTimePrime >= dacc &&
                 dUnfilteredSimpleTimePrime <= dDecelBegin)
        {
            dFilteredSimpleTime = dR * (dUnfilteredSimpleTimePrime - (dacc / 2.0));

        }
        else if (dUnfilteredSimpleTimePrime > dDecelBegin &&
                 dUnfilteredSimpleTimePrime <= dDur)
        {
            double tdec = dUnfilteredSimpleTime - dDecelBegin;
            double pd   = 1.0;
            if (ddec != 0.0)
            {
                pd = tdec / ddec;
            }
            dFilteredSimpleTime = dR * (dDur - (dacc / 2.0) - ddec +
                                        (tdec * (2.0 - pd) / 2.0));
            // Make sure we don't ever round up to dDur if
            // our input is less than dDur
            if (dUnfilteredSimpleTimePrime < dDur &&
                dFilteredSimpleTime >= dDur - 0.5)
            {
                dFilteredSimpleTime = dDur - 0.50000000001;
            }
        }
        // Now round to nearest integer
        ulRet = (UINT32) floor(dFilteredSimpleTime + 0.5);
    }

    return ulRet;
}

double CAnimationSandwichLayer::ComputeRunRate(double dT, double dAccel,
                                               double dDecel, double dDur)
{
    double dRet = 1.0;

    double dacc    = dDur * dAccel;
    double ddec    = dDur * dDecel;
    double dRDenom = 1.0 - (dAccel / 2.0) - (dDecel / 2.0);
    if (dRDenom != 0.0)
    {
        double dR = 1.0 / dRDenom;
        // Compute the decel begin time
        double dDecBegin = dDur - ddec;
        // Now compute the run rate
        if (dT >= 0.0 && dT < dacc)
        {
            if (dacc != 0.0)
            {
                dRet = dR * dT / dacc;
            }
        }
        else if (dT >= dacc && dT <= dDecBegin)
        {
            dRet = dR;
        }
        else if (dT > dDecBegin && dT <= dDur)
        {
            if (ddec != 0.0)
            {
                dRet = dR * (dDur - dT) / ddec;
            }
        }
    }

    return dRet;
}

#if defined(XXXMEH_SPLINE_ANIMATION)

double CAnimationSandwichLayer::ComputeSpline(double t,  double t1, double t2,
                                              double v1, double v2, double x1,
                                              double y1, double x2, double y2)
{
    double dRet = 0.0;

    if (x1 >= 0.0 && x1 <= 1.0 &&
        y1 >= 0.0 && y1 <= 1.0 &&
        x2 >= 0.0 && x2 <= 1.0 &&
        y2 >= 0.0 && y2 <= 1.0)
    {
        if (t2 >= t1)
        {
            if (t >= t1)
            {
                if (t <= t2)
                {
                    if (t2 > t1)
                    {
                        // We compute the spline interpolation
                        // in the following steps:
                        // a) Scale the time down to unit time
                        // b) Get the output bezier time (still in unit time)
                        // c) Use this output time as the 
                        //    input time for linear interpolation.
                        //
                        // Scale the time to unit time
                        double dInUnitT = (t - t1) / (t2 - t1);
                        // Get the output bezier time
                        double dOutUnitT = ComputeBezierOutputTime(dUnitT, x1, y1, x2, y2);
                        // Do a sanity check on the output time
                        if (dOutUnitT < 0.0)
                        {
                            dOutUnitT = 0.0;
                            HX_ASSERT(FALSE && "Bad value returned from ComputeBezierOutputTime()");
                        }
                        else if (dOutUnitT > 1.0)
                        {
                            dOutUnitT = 1.0;
                            HX_ASSERT(FALSE && "Bad value returned from ComputeBezierOutputTime()");
                        }
                        // Use this time to linearly interpolate
                        dRet = v1 + (v2 - v1) * dOutUnitT;
                    }
                    else
                    {
                        // t1 is equal to t2. So this is a discontinuous
                        // interval, so by endpoint-exclusive logic [a,b),
                        // we choose v2 for the output value. This is not
                        // an input error, so we don't assert.
                        dRet = v2;
                    }
                }
                else
                {
                    dRet = v2;
                    HX_ASSERT(FALSE && "ComputeSpline(): t is greater than t2 - v2 assigned");
                }
            }
            else
            {
                dRet = v1;
                HX_ASSERT(FALSE && "ComputeSpline(): t is less than t1 - v1 assigned");
            }
        }
        else
        {
            HX_ASSERT(FALSE && "ComputeSpline(): t2 cannot be less than t1");
        }
    }
    else
    {
        HX_ASSERT(FALSE && "Illegal bezier control parameters in ComputeSpline()");
    }

    return dRet;
}

double CAnimationSandwichLayer::ComputeBezierOutputTime(double t,
                                                        double x1, double y1,
                                                        double x2, double y2)
{
    double dRet = 0.0;

    if (t  >= 0.0 && t  <= 1.0 &&
        x1 >= 0.0 && x1 <= 1.0 &&
        y1 >= 0.0 && y1 <= 1.0 &&
        x2 >= 0.0 && x2 <= 1.0 &&
        y2 >= 0.0 && y2 <= 1.0)
    {
        // We get the output bezier-curve-warped time
        // by doing the following:
        //
        // a) Find q where x(q) = t
        // b) Compute y(q)
        //
        // Find the q where x(q) = t
        double dQ = SolveForBezierT(t, x1, x2, BEZIER_TOLERANCE);
        // Do a sanity check on this value
        if (dQ < 0.0)
        {
            dQ = 0.0;
            HX_ASSERT(FALSE && "Bad value returned from SolveForBezierT()");
        }
        else if (dQ > 1.0)
        {
            dQ = 1.0;
            HX_ASSERT(FALSE && "Bad value returned from SolveForBezierT()");
        }
        // Compute y(q)
        dRet = ComputeBezier(dQ, y1, y2);
    }
    else
    {
        HX_ASSERT(FALSE && "Illegal input parameters in ComputeBezierOutputTime()");
    }

    return dRet;
}

double CAnimationSandwichLayer::SolveForBezierT(double x,  double x1,
                                                double x2, double tol)
{
    double dRet = 0.0;

    if (x   >= 0.0 && x   <= 1.0 &&
        x1  >= 0.0 && x1  <= 1.0 &&
        x2  >= 0.0 && x2  <= 1.0 &&
        tol >  0.0 && tol <= 1.0)
    {
        double tL = 0.0;
        double xL = 0.0;
        double tR = 1.0;
        double xR = 1.0;
        double tM = 0.5;
        double xM = ComputeBezier(tM, x1, x2);
        UINT32 i  = 0;
        while (fabs(xM - x) > tol &&
               i < BEZIER_MAX_ITERATIONS)
        {
            // Is our computed mid-point smaller or
            // larger than our desired value?
            if (xM > x)
            {
                // Take the left-hand interval
                tR = tM;
            }
            else
            {
                // Take the right-hand interval
                tL = tM;
            }
            // Compute new mid-point
            tM = (tL + tR) / 2.0;
            // Compute new value
            xM = ComputeBezier(tM, x1, x2);
            // Increment the iteration count
            ++i;
        }
        // Set the return value
        dRet = tM;
        // Check if we blew our max iteration count
        if (i >= BEZIER_MAX_ITERATIONS)
        {
            HX_ASSERT(FALSE && "SolveForBezierT(): Could not get within tolerance in max iterations");
        }
    }
    else
    {
        HX_ASSERT(FALSE && "Illegal input parameters in SolveForBezierT()");
    }

    return dRet;
}

double CAnimationSandwichLayer::ComputeBezier(double t, double a1, double a2)
{
    double dRet = 0.0;

    if (t  >= 0.0 && t  <= 1.0 &&
        a1 >= 0.0 && a1 <= 1.0 &&
        a2 >= 0.0 && a2 <= 1.0)
    {
        double t2  = t * t;
        double t3  = t2 * t;
        double ti  = 1.0 - t;
        double ti2 = ti * ti;
        dRet       = 3.0 * t  * ti2 * a1 +
                     3.0 * t2 * ti  * a2 +
                     t3;
    }
    else
    {
        HX_ASSERT(FALSE && "Illegal input parameters in ComputeBezier()");
    }

    return dRet;
}

#endif // #if defined(XXXMEH_SPLINE_ANIMATION)

CAnimationSandwich::CAnimationSandwich(const char* pszTargetElementID,
                                       SMILNodeTag eTargetElementTag,
                                       UINT32      ulAttrName)
{
    MLOG_LEAKCHECK("CON CAnimationSandwich this=0x%08x\n", this);
    m_pTargetElementID  = new CHXString(pszTargetElementID);
    m_eTargetElementTag = eTargetElementTag;
    m_ulAttrName        = ulAttrName;
    m_pLayerList        = NULL;
}

CAnimationSandwich::~CAnimationSandwich()
{
    MLOG_LEAKCHECK("DES CAnimationSandwich this=0x%08x\n", this);
    ClearAllLayers();
    HX_DELETE(m_pTargetElementID);
    HX_DELETE(m_pLayerList);
}

void CAnimationSandwich::ClearAllLayers()
{
    if (m_pLayerList)
    {
        LISTPOSITION pos = m_pLayerList->GetHeadPosition();
        while (pos)
        {
            CAnimationSandwichLayer* pLayer = (CAnimationSandwichLayer*) m_pLayerList->GetNext(pos);
            HX_DELETE(pLayer);
        }
        m_pLayerList->RemoveAll();
    }
}

const char* CAnimationSandwich::GetTargetElementID() const
{
    const char* pRet = NULL;

    if (m_pTargetElementID)
    {
        pRet = (const char*) *m_pTargetElementID;
    }

    return pRet;
}

HX_RESULT CAnimationSandwich::AddLayer(CAnimationSandwichLayer* pLayer)
{
    HX_RESULT retVal = HXR_OK;

    if (pLayer)
    {
        if (!m_pLayerList)
        {
            m_pLayerList = new CHXSimpleList();
        }
        if (m_pLayerList)
        {
            BOOL         bAdded = FALSE;
            LISTPOSITION pos    = m_pLayerList->GetHeadPosition();
            while (pos)
            {
                CAnimationSandwichLayer* pListLayer = (CAnimationSandwichLayer*) m_pLayerList->GetAt(pos);
                if (pListLayer->HigherPriority(pLayer))
                {
                    // The current layer in the list is higher priority
                    // than the layer we want to add, so we must insert
                    // ourselves BEFORE this layer
                    m_pLayerList->InsertBefore(pos, (void*) pLayer);
                    // Set the flag
                    bAdded = TRUE;
                    // Now jump out of the loop
                    break;
                }
                m_pLayerList->GetNext(pos);
            }
            // If we didn't add ourselves, then that
            // means we are the highest priority, so 
            // add ourselves to the tail of the list
            if (!bAdded)
            {
                m_pLayerList->AddTail((void*) pLayer);
            }
        }
        else
        {
            retVal = HXR_OUTOFMEMORY;
        }
    }

    return retVal;
}

void CAnimationSandwich::RemoveLayer(const char* pszAnimID)
{
    if (pszAnimID && m_pLayerList)
    {
        LISTPOSITION pos = m_pLayerList->GetHeadPosition();
        while (pos)
        {
            CAnimationSandwichLayer* pLayer =
                (CAnimationSandwichLayer*) m_pLayerList->GetAt(pos);
            if (pLayer &&
                pLayer->GetAnimationElementID() &&
                !strcmp(pszAnimID, pLayer->GetAnimationElementID()))
            {
                // We need to remove this layer
                //
                // First remove it from the list
                pos = m_pLayerList->RemoveAt(pos);
                // Next delete the object itself
                HX_DELETE(pLayer);
            }
            else
            {
                m_pLayerList->GetNext(pos);
            }
        }
    }
}

void CAnimationSandwich::FreezeLayers(const char* pszAnimID, UINT32 ulCurTime)
{
    if (pszAnimID && m_pLayerList)
    {
        LISTPOSITION pos = m_pLayerList->GetHeadPosition();
        while (pos)
        {
            CAnimationSandwichLayer* pLayer =
                (CAnimationSandwichLayer*) m_pLayerList->GetNext(pos);
            if (pLayer &&
                pLayer->GetAnimationElementID() &&
                !strcmp(pszAnimID, pLayer->GetAnimationElementID()) &&
                pLayer->IsActive(ulCurTime))
            {
                // We need to freeze this layer. We do this
                // by adjusting the layer's m_ulActiveDuration
                // such that pLayer->m_ulDelay + pLayer->m_ulActiveDuration
                // is equal to the current time.
                pLayer->AdjustActiveDuration(ulCurTime);
            }
        }
    }
}

CAttr CAnimationSandwich::GetValue(UINT32 ulTime, CAttr* pUnder, CAttr* pDepend)
{
    CAttr cRet;

    if (pUnder)
    {
        // Initialize the running value
        CAttr cUnder(*pUnder);
        // Now run through the layers
        if (m_pLayerList)
        {
            LISTPOSITION pos = m_pLayerList->GetHeadPosition();
            while (pos)
            {
                CAnimationSandwichLayer* pLayer =
                    (CAnimationSandwichLayer*) m_pLayerList->GetNext(pos);
                if (pLayer &&
                    (pLayer->IsActive(ulTime) ||
                     pLayer->IsFrozen(ulTime)))
                {
                    // Evaluate this layer
                    CAttr cRes = pLayer->AnimationEffectFunction(ulTime, &cUnder, pDepend);
                    // Now we either have to replace OR add
                    if (pLayer->IsAdditive() &&
                        !pLayer->IsToAnimation())
                    {
                        cUnder.Add(&cRes, pDepend);
                    }
                    else
                    {
                        cUnder = cRes;
                    }
                }
            }
        }
        // Now set the return value
        cRet = cUnder;
    }

    return cRet;
}

UINT32 CAnimationSandwich::GetNumLayers() const
{
    UINT32 ulRet = 0;

    if (m_pLayerList)
    {
        ulRet = m_pLayerList->GetCount();
    }

    return ulRet;
}

void CAnimationSandwich::AdjustLayers(UINT32 ulTime)
{
    if (m_pLayerList)
    {
        LISTPOSITION pos = m_pLayerList->GetHeadPosition();
        while (pos)
        {
            CAnimationSandwichLayer* pLayer = (CAnimationSandwichLayer*) m_pLayerList->GetAt(pos);
            if (!pLayer->IsActive(ulTime) &&
                !pLayer->IsFrozen(ulTime))
            {
                // We need to remove this layer
                //
                // First remove it from the list
                pos = m_pLayerList->RemoveAt(pos);
                // Next delete the object itself
                HX_DELETE(pLayer);
            }
            else
            {
                m_pLayerList->GetNext(pos);
            }
        }
    }
}

BOOL CAnimationSandwich::MatchingSandwich(CSmilAnimateElement* pAnim)
{
    BOOL bRet = FALSE;

    if (pAnim &&
        pAnim->m_pTargetElementID &&
        m_pTargetElementID &&
        *pAnim->m_pTargetElementID == *m_pTargetElementID)
    {
        // We know that this animate element has the
        // same target element ID as we do. Now we 
        // need to check if it has the same attributeName.
        // If the animate element is an <animateMotion>, then
        // our sandwich could be either left or top. If the
        // animate element is not <animateMotion>, then the
        // attributeName has to match exactly.
        if (pAnim->m_pNode &&
            pAnim->m_pNode->m_tag == SMILAnimateMotion)
        {
            if (m_ulAttrName == kAttrNameLeft ||
                m_ulAttrName == kAttrNameTop)
            {
                bRet = TRUE;
            }
        }
        else
        {
            UINT32 ulAttrName = pAnim->m_ucAttributeName;
            if (ulAttrName == m_ulAttrName)
            {
                bRet = TRUE;
            }
        }
    }

    return bRet;
}

BOOL CAnimationSandwich::AtLeastOneActiveLayer(UINT32 ulTime)
{
    BOOL bRet = FALSE;

    if (m_pLayerList &&
        m_pLayerList->GetCount() > 0)
    {
        LISTPOSITION pos = m_pLayerList->GetHeadPosition();
        while (pos)
        {
            CAnimationSandwichLayer* pLayer =
                (CAnimationSandwichLayer*) m_pLayerList->GetNext(pos);
            if (pLayer && pLayer->IsActive(ulTime))
            {
                bRet = TRUE;
                break;
            }
        }
    }

    return bRet;
}

CSmilAnimateInfo::CSmilAnimateInfo()
{
    m_pSandwich = NULL;
    m_pUnder    = NULL;
    m_pDepend   = NULL;
}

CSmilAnimateInfo::~CSmilAnimateInfo()
{
    HX_DELETE(m_pSandwich);
    HX_DELETE(m_pUnder);
    HX_DELETE(m_pDepend);
}

