/*
 * Animation plug-in
 */

/****************************************************************************
 *
 * Proposed new API for hierarchical animation controller
 * Copyright (C) 1999 Criterion Technologies
 *
 * Author  : Damian Scallan
 *
 * Module  : rpanim.c
 *
 * Purpose : Functions for creating and controlling sequences of articulated
 *           hierarchical keyframe animation.
 *
 * Notes   : For details on interpolating rotation with
 *           Quaternions, see p360
 *           Advanced Animation and Rendering Techniques
 *           Alan Watt and Mark Watt
 *           Addison-Wesley 1993,
 *           ISBN 0-201-54412-1
 *
 ****************************************************************************/

/**
 * \ingroup rpanim
 * \page rpanimoverview RpAnim Plugin Overview
 *
 * The RpAnim Plugin provides standard keyframed hierarchic animation sequence 
 * management facilities for the developer. The groups of functions and datatypes 
 * provided give different levels of control over animations and sequences. With 
 * this plugin, you only need a few lines of code to have models animating in your 
 * application.
 * 
 * The RpAnim object provides a couple of utility functions for extracting quaternions 
 * from RwMatrix objects and performing interpolations using SLERPs.
 *
 * The RpAnimDatabase object allows you to store animation sequences independently of 
 * the models. Animations can be stored, extracted, copied and applied to RpClumps or 
 * RpFrames as desired.
 *
 * The RpAnimClump object exposes functions to perform animation sequence manipulation 
 * on RpClump objects. Individual named sequences can be activated and deactivated 
 * selectively within the clump.
 *
 * Similarly, the RpAnimFrame object lets you adjust animation sequences and apply them 
 * to individual RpFrame objects.
 *
 * Finally, there’s also the \ref RpAnimSequence object itself. This object also 
 * exposes functions to manipulate the sequence it contains. Facilities include access to 
 * the sequence’s various keyframes, interpolators and the sequence name.
 *
 * Requires: Core Library; RtSlerp Toolkit and RpWorld Plugin.
 *
 */

/****************************************************************************
 Includes
 */

#include <stdlib.h>
#include <string.h>
#include <math.h>

#include "rpplugin.h"
#include "rpdbgerr.h"
#include "rpanim.h"

#if (!defined(DOXYGEN_SHOULD_SKIP_THIS))
static const char   rcsid[] __RWUNUSED__ =
    "@@(#)$Id: rpanim.c,v 1.154 2001/08/22 19:17:40 Markj Exp $";
#endif /* (!defined(DOXYGEN_SHOULD_SKIP_THIS)) */

#if defined (__MWERKS__)
#if (defined(RWVERBOSE))
#pragma message (__FILE__ "/" _SKY_EXPAND(__LINE__) ": __MWERKS__ == " _SKY_EXPAND(__MWERKS__))
#endif /* (defined (__MWERKS__)) */
#if (__option (global_optimizer))
#pragma always_inline off
#endif /* (__option (global_optimizer)) */
#endif /*  defined (__MWERKS__) */

/****************************************************************************
 Local Defines
 */

#define DEFAULTTAGID    -1

#define RPANIMGLOBAL(var)                               \
    (RWPLUGINOFFSET(rpAnimGlobals,                      \
                    RwEngineInstance,                   \
                    AnimStatic.animModule.globalsOffset)->var)

#define RWFRAMEGETDATA(frame) \
    ((rpAnimSeqFrameData *)(((RwUInt8 *)frame) + \
                            AnimStatic.frameDataOffset))

#define RWFRAMEGETCONSTDATA(frame) \
    ((const rpAnimSeqFrameData *)(((const RwUInt8 *)frame) + \
                                  AnimStatic.frameDataOffset))

#define RWFRAMEGETANIM(frame) \
    (RWFRAMEGETDATA(frame)->animFrame)
#define RWFRAMEGETCONSTANIM(frame) \
    (RWFRAMEGETCONSTDATA(frame)->animFrame)

#define RWFRAMESETANIM(frame, frameAnim) \
    (RWFRAMEGETDATA(frame)->animFrame = (frameAnim))

#define RWFRAMEGETPRIORITY(frame) \
    (RWFRAMEGETDATA(frame)->priority)
#define RWFRAMEGETCONSTPRIORITY(frame) \
    (RWFRAMEGETCONSTDATA(frame)->priority)

#define RWFRAMESETPRIORITY(frame, p) \
    (RWFRAMEGETDATA(frame)->priority = (p))

#define RWFRAMEGETTAG(frame) \
    (RWFRAMEGETDATA(frame)->tag)
#define RWFRAMEGETCONSTTAG(frame) \
    (RWFRAMEGETCONSTDATA(frame)->tag)

#define RWFRAMESETTAG(frame, t) \
    (RWFRAMEGETDATA(frame)->tag = (t))

#define RWCLUMPGETANIM(clump) \
    ((rpAnimClump *)(((RwUInt8 *)clump) + AnimStatic.clumpAnimOffset))
#define RWCLUMPGETCONSTANIM(clump) \
    ((const rpAnimClump *) \
     (((const RwUInt8 *)clump) + AnimStatic.clumpAnimOffset))

#define RwCosec(_x) \
   ( ((RwReal)1) / RwSin((_x)) )

#if (0 && defined(RWDEBUG) && defined(RWVERBOSE))
#define ANIMMESSAGE(_x)     RWMESSAGE(_x)
#endif /* (defined(RWDEBUG) && defined(RWVERBOSE)) */

#if (!defined(ANIMMESSAGE))
#define ANIMMESSAGE(_x)        /* do nothing */
#endif /* (!defined(ANIMMESSAGE)) */

#define INTERPOLATE_QUATERNION(_startKey, _endKey,                      \
                               _matrix, _along, _transformState,        \
                               _result)                                 \
MACRO_START                                                             \
{                                                                       \
    RtQSlerp(&(_result),                                                \
                  (_startKey), (_endKey), (_along),                     \
                 &(_transformState)->quatSlerpCache);                   \
    RtQuatUnitConvertToMatrix(&(_result), (_matrix));                   \
}                                                                       \
MACRO_STOP

#define INTERPOLATE_MATRIX(_startKey, _endKey,                          \
                               _matrix, _along, _transformState,        \
                               _result)                                 \
MACRO_START                                                             \
{                                                                       \
    static RwMatrix *s = NULL;                                          \
    static RwMatrix *e = NULL;                                          \
                                                                        \
    if (!s)                                                             \
    {                                                                   \
        s = RwMatrixCreate();                                           \
        e = RwMatrixCreate();                                           \
    }                                                                   \
                                                                        \
    RtQuatUnitConvertToMatrix((_startKey), s);                          \
    RtQuatUnitConvertToMatrix((_endKey), e);                            \
                                                                        \
    {                                                                   \
                                                                        \
        RtSlerp *slerp = RtSlerpCreate(rtSLERPREFALL);                  \
        RtSlerpInitialize(slerp, s, e);                                 \
        RtSlerpGetMatrix(slerp, (_matrix), (_along));                   \
        RtSlerpDestroy(slerp);                                          \
    }                                                                   \
}                                                                       \
MACRO_STOP

#define INTERPOLATE_METHOD(_startKey, _endKey,                          \
                           _matrix, _along, _transformState,            \
                           _result)                                     \
    INTERPOLATE_QUATERNION(_startKey, _endKey,                          \
                           _matrix, _along, _transformState,            \
                           _result)

/****************************************************************************
 Public Globals
 */

#if (defined(RWDEBUG))
long                rpAnimStackDepth = 0;
#endif /* (defined(RWDEBUG)) */

typedef struct rpAnimStatic rpAnimStatic;
struct rpAnimStatic
{
    RwModuleInfo        animModule;
    RwInt32             clumpAnimOffset;
    RwInt32             frameDataOffset;
    RwFreeList         *FrameFreeList;
    RwFreeList         *StateFreeList;
    RwFreeList         *FrameListFreeList;
    RwFreeList         *ClumpSequenceFreeList;
    RwFreeList         *SequenceFreeList;
    RwFreeList         *DatabaseFreeList;
    RwFreeList         *DatabaseEntryFreeList;
    RwFreeList         *NameListFreeList;
};

static rpAnimStatic AnimStatic = {
    {
     (RwInt32) 0,              /* globalsOffset */
     (RwInt32) 0               /* numInstances */
     },                        /* ModuleInfo */
    (RwInt32) 0,               /* clumpAnimOffset */
    (RwInt32) 0,               /* frameDataOffset */
    (RwFreeList *) NULL,       /* FrameFreeList */
    (RwFreeList *) NULL,       /* StateFreeList */
    (RwFreeList *) NULL,       /* FrameListFreeList */
    (RwFreeList *) NULL,       /* ClumpSequenceFreeList */
    (RwFreeList *) NULL,       /* SequenceFreeList */
    (RwFreeList *) NULL,       /* DatabaseFreeList */
    (RwFreeList *) NULL,       /* DatabaseEntryFreeList */
    (RwFreeList *) NULL        /* NameListFreeList */
};

/****************************************************************************
 Local (Static) Prototypes
 */

/****************************************************************************
 Local Structures
 */

typedef struct rpAnimSeqFrameData rpAnimSeqFrameData;
struct rpAnimSeqFrameData
{
    rpAnimFrame        *animFrame;
    RwInt32             priority;
    RwInt32             tag;
};

typedef struct rpAnimFrameSeqInfo rpAnimFrameSeqInfo;
struct rpAnimFrameSeqInfo
{
    rpAnimFrameList    *frameAnimList;
    const RwChar       *seqName;
};

typedef struct rpAnimClumpFindStruct rpAnimClumpFindStruct;
struct rpAnimClumpFindStruct
{
    RpAnimClumpSequence *seq;
    const RwChar       *name;
};

typedef struct rpAnimInterpInfo rpAnimInterpInfo;
struct rpAnimInterpInfo
{
    RwInt32             newIndex;
    RwInt32             refCntr;
    RwBool              optimized;
    RwBool              loopStart;
    RwBool              cut;
    RwInt32             visitedBy;
    rpAnimInterpolator *interp;
    rpAnimInterpInfo   *next;
};

typedef struct rpAnimKeyInfo rpAnimKeyInfo;
struct rpAnimKeyInfo
{
    RwBool              used;
    RwInt16             newIndex;
};

typedef struct rpAnimCountSequenceStruct rpAnimCountSequenceStruct;
struct rpAnimCountSequenceStruct
{
    RwChar             *name;
    RwInt32             count;
};

typedef struct rpAnimAddSequenceToDBStruct rpAnimAddSequenceToDBStruct;
struct rpAnimAddSequenceToDBStruct
{
    RpAnimDatabaseEntry *dbEntry;
    RwInt32             currentIndex;
    RwChar             *name;
};

typedef struct rpAnimTagData rpAnimTagData;
struct rpAnimTagData
{
    RwFrame            *frame;
    RwInt32             tag;
};

typedef struct rpAnimNameList rpAnimNameList;
struct rpAnimNameList
{
    RwChar             *name;
    rpAnimNameList     *next;
};

typedef             RwBool
    (*rpAnimInterpInfoSpanTestCallBack) (rpAnimInterpInfo **
                                         interpSpanStart,
                                         RwInt32 interval, void *keys,
                                         RwReal delta);

/****************************************************************************
 Local Defines
 */

/* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

   RpRotate methods

   !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! */

static RpRotateKey *
RotateKeysWrite(RpRotateKey * keys, RwInt32 numKeys,
                RwStream * stream, RwInt32 * bytesWritten)
{
    RwInt32             size;

    RWFUNCTION(RWSTRING("RotateKeysWrite"));

    RWASSERT(keys);
    RWASSERT(stream);
    RWASSERT(bytesWritten);

    *bytesWritten = 0;

    size = numKeys * sizeof(RpRotateKey);
    if (!RwStreamWriteReal(stream, (RwReal *) keys, size))
    {
        RWRETURN(FALSE);
    }
    *bytesWritten += size;

    RWRETURN(keys);
}

static RpRotateKey *
RotateKeysRead(RpRotateKey * keys, RwInt32 numKeys, RwStream * stream)
{
    RWFUNCTION(RWSTRING("RotateKeysRead"));

    RWASSERT(keys);
    RWASSERT(stream);

    if (!RwStreamReadReal(stream, (RwReal *) keys, numKeys *
                          sizeof(RpRotateKey)))
    {
        RWRETURN(FALSE);
    }

    RWRETURN(keys);
}

/* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

   RpTranslateKey methods

   !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! */

static RpTranslateKey *
TranslateKeyBlend(RpTranslateKey * blendKey,
                  RpTranslateKey * startKey, RpTranslateKey * endKey,
                  RwReal along)
{
    RwV3d              *start, *end, *blend;

    RWFUNCTION(RWSTRING("TranslateKeyBlend"));

    RWASSERT(blendKey);
    RWASSERT(startKey);
    RWASSERT(endKey);

    start = (RwV3d *) startKey;
    end = (RwV3d *) endKey;
    blend = (RwV3d *) blendKey;

    RwV3dSub(blend, end, start);
    RwV3dScale(blend, blend, along);
    RwV3dAdd(blend, blend, start);

    RWRETURN(blendKey);
}

static RpTranslateKey *
TranslateKeysWrite(RpTranslateKey * keys, RwInt32 numKeys,
                   RwStream * stream, RwInt32 * bytesWritten)
{
    RwInt32             size;

    RWFUNCTION(RWSTRING("TranslateKeysWrite"));

    RWASSERT(keys);
    RWASSERT(stream);

    *bytesWritten = 0;

    size = numKeys * sizeof(RpTranslateKey);
    if (!RwStreamWriteReal(stream, (RwReal *) keys, size))
    {
        RWRETURN(FALSE);
    }
    *bytesWritten += size;

    RWRETURN(keys);
}

static RpTranslateKey *
TranslateKeysRead(RpTranslateKey * keys, RwInt32 numKeys,
                  RwStream * stream)
{
    RWFUNCTION(RWSTRING("TranslateKeysRead"));

    RWASSERT(keys);
    RWASSERT(stream);

    if (!RwStreamReadReal(stream, (RwReal *) keys, numKeys *
                          sizeof(RpTranslateKey)))
    {
        RwFree(keys);

        RWRETURN(FALSE);
    }

    RWRETURN(keys);
}

/* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

   rpAnimInterpolator methods

   !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! */

static rpAnimInterpolator *
AnimInterpolatorsWrite(rpAnimInterpolator * interps,
                       RwInt32 numInterps,
                       RwStream * stream, RwInt32 * bytesWritten)
{
    RwInt32             interpNum;
    RwInt32             size;

    RWFUNCTION(RWSTRING("AnimInterpolatorsWrite"));

    RWASSERT(interps);
    RWASSERT(stream);

    *bytesWritten = 0;

    for (interpNum = 0; interpNum < numInterps; interpNum++)
    {
        RwInt32             keyFrame;
        RwReal              time;

        keyFrame = interps->startKeyFrame;
        size = sizeof(RwInt32);
        if (!RwStreamWriteInt(stream, &keyFrame, size))
        {
            /* Failed to write to stream, ooops */
            RWRETURN((rpAnimInterpolator *) NULL);
        }
        *bytesWritten += size;

        keyFrame = interps->endKeyFrame;
        size = sizeof(RwInt32);
        if (!RwStreamWriteInt(stream, &keyFrame, sizeof(keyFrame)))
        {
            /* Failed to write to stream, ooops */
            RWRETURN((rpAnimInterpolator *) NULL);
        }
        *bytesWritten += size;

        time = interps->time;
        size = sizeof(RwReal);
        if (!RwStreamWriteReal(stream, &time, sizeof(time)))
        {
            /* Failed to write to stream, ooops */
            RWRETURN((rpAnimInterpolator *) NULL);
        }
        *bytesWritten += size;

        /* onto the next */
        interps++;
    }

    RWRETURN(interps);
}

static rpAnimInterpolator *
AnimInterpolatorsRead(rpAnimInterpolator * interps,
                      RwInt32 numInterps, RwStream * stream)
{
    rpAnimInterpolator *currentInterp;
    RwInt32             interpNum;

    RWFUNCTION(RWSTRING("AnimInterpolatorsRead"));

    RWASSERT(interps);
    RWASSERT(stream);

    currentInterp = interps;

    for (interpNum = 0; interpNum < numInterps; interpNum++)
    {
        RwInt32             keyFrame;
        RwReal              time;

        if (!RwStreamReadInt(stream, &keyFrame, sizeof(keyFrame)))
        {
            /* Failed to write to stream, ooops */
            RwFree(interps);

            RWRETURN((rpAnimInterpolator *) NULL);
        }
        currentInterp->startKeyFrame = keyFrame;

        if (!RwStreamReadInt(stream, &keyFrame, sizeof(keyFrame)))
        {
            /* Failed to write to stream, ooops */
            RwFree(interps);

            RWRETURN((rpAnimInterpolator *) NULL);
        }
        currentInterp->endKeyFrame = keyFrame;

        if (!RwStreamReadReal(stream, &time, sizeof(time)))
        {
            /* Failed to write to stream, ooops */
            RwFree(interps);

            RWRETURN((rpAnimInterpolator *) NULL);
        }
        currentInterp->time = time;

        /* onto the next */
        currentInterp++;
    }

    RWRETURN(interps);
}

/* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

   rpAnimTransformState methods

   !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! */

#define FrameUpdateTranslateMacro(frame, currentInterp, keys, interpPos) \
MACRO_START                                                                 \
{                                                                           \
    RpTranslateKey * const startKey =                                       \
        &((RpTranslateKey *)(keys))[(currentInterp)->startKeyFrame];        \
    RpTranslateKey * const endKey =                                         \
       &((RpTranslateKey *)(keys))[(currentInterp)->endKeyFrame];           \
    const RwReal        along = ((interpPos)) / ((currentInterp)->time);    \
    RwMatrix    * const matrix = RwFrameGetMatrix((frame));                 \
    RpTranslateKey      blendKey;                                           \
                                                                            \
    TranslateKeyBlend(&blendKey, startKey, endKey, along);               \
                                                                            \
    matrix->pos.x = matrix->pos.y = matrix->pos.z = ((RwReal) 0);           \
                                                                            \
    RwFrameTranslate((frame), &blendKey, rwCOMBINEPOSTCONCAT);              \
}                                                                           \
MACRO_STOP

#define FrameUpdateRotateMacro(frame, tState, currentInterp, keys, pos) \
MACRO_START                                                                \
{                                                                          \
    RpRotateKey * startKey;                                                \
    RpRotateKey * endKey;                                                  \
    RwReal        along;                                                   \
    RwMatrix    * matrix;                                                  \
    RwV3d         camPos;                                                  \
    RtQuat        result;                                                  \
                                                                           \
    startKey =                                                             \
       &((RpRotateKey *)(keys))[(currentInterp)->startKeyFrame];           \
    endKey =                                                               \
       &((RpRotateKey *)(keys))[(currentInterp)->endKeyFrame];             \
    along = (pos) / ((currentInterp)->time);                               \
    matrix = RwFrameGetMatrix((frame));                                    \
    camPos = *RwMatrixGetPos(matrix);                                      \
                                                                           \
    if ((tState)->flags & rpNEWINTERP)                                     \
    {                                                                      \
        RtQSetupSlerpCache(startKey, endKey,                               \
                           &(tState)->quatSlerpCache);                     \
                                                                           \
        /* ANIMMESSAGE(("%p == (tState)")); */                             \
                                                                           \
        (tState)->flags &= ~rpNEWINTERP;                                   \
    }                                                                      \
                                                                           \
    INTERPOLATE_METHOD(startKey, endKey,                                   \
                       matrix, along, (tState), result);                   \
                                                                           \
                                                                           \
    RwFrameTranslate((frame), &camPos, rwCOMBINEPOSTCONCAT);               \
}                                                                          \
MACRO_STOP

#if (! ( defined(RWDEBUG) || defined(RWSUPPRESSINLINE) ))

#define FrameUpdateTranslate(frame, currentInterp, keys, pos)           \
   FrameUpdateTranslateMacro(frame, currentInterp, keys, pos)

#define FrameUpdateRotate(frame, tState, currentInterp, keys, pos)      \
   FrameUpdateRotateMacro(frame, tState, currentInterp, keys, pos)

#else /* (! ( defined(RWDEBUG) || defined(RWSUPPRESSINLINE) )) */

static void
FrameUpdateTranslate(RwFrame * frame,
                     rpAnimInterpolator * currentInterp,
                     RpTranslateKey * keys, RwReal pos)
{
    RWFUNCTION(RWSTRING("FrameUpdateTranslate"));

    RWASSERT(frame);
    RWASSERT(currentInterp);
    RWASSERT(keys);

    FrameUpdateTranslateMacro(frame, currentInterp, keys, pos);

    RWRETURNVOID();
}

static void
FrameUpdateRotate(RwFrame * frame,
                  rpAnimTransformState * transformState,
                  rpAnimInterpolator * currentInterp,
                  RpRotateKey * keys, RwReal pos)
{

    RWFUNCTION(RWSTRING("FrameUpdateRotate"));

    RWASSERT(frame);
    RWASSERT(currentInterp);
    RWASSERT(keys);
    RWASSERT(transformState);

    FrameUpdateRotateMacro(frame, transformState, currentInterp, keys,
                           pos);

    RWRETURNVOID();
}

#endif /* (! ( defined(RWDEBUG) || defined(RWSUPPRESSINLINE) )) */

/* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

   RpAnimSequence methods

   !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! */

static RpAnimSequence *
AnimSequenceDeinit(RpAnimSequence * animSequence)
{
    RWFUNCTION(RWSTRING("AnimSequenceDeinit"));

    RWASSERT(animSequence);

    if (animSequence->name)
    {
        RwFree(animSequence->name);
        animSequence->name = (char *) NULL;
    }

    if (animSequence->keys)
    {
        RwFree(animSequence->keys);
        animSequence->keys = NULL;
    }
    animSequence->numKeys = 0;

    if (animSequence->interps)
    {
        RwFree(animSequence->interps);
        animSequence->interps = (rpAnimInterpolator *) NULL;
    }
    animSequence->numInterpolators = 0;

    RWRETURN(animSequence);
}

static              RwReal
AnimSequenceGetDuration(RpAnimSequence * animSeq)
{
    RwReal              duration = (RwReal) (0);
    RwInt32             i;

    RWFUNCTION(RWSTRING("AnimSequenceGetDuration"));

    RWASSERT(animSeq);

    for (i = 0; i < animSeq->numInterpolators; i++)
    {
        rpAnimInterpolator *interp;

        interp = &animSeq->interps[i];
        duration += interp->time;
    }

    RWRETURN(duration);
}

static RpAnimSequence *
AnimSequenceWrite(RpAnimSequence * animSeq, RwStream * stream,
                  RwInt32 * bytesWritten)
{

    RwChar             *name;
    RwInt32             len;
    RwInt32             type;
    RwInt32             numKeys;
    RwInt32             numInterps;
    RwInt32             size;

    RWFUNCTION(RWSTRING("AnimSequenceWrite"));

    RWASSERT(animSeq);
    RWASSERT(stream);

    *bytesWritten = 0;

    /* write the name */
    name = animSeq->name;
    len = (rwstrlen(name) + 1) * sizeof(RwChar);
    size = sizeof(RwInt32);
    if (!RwStreamWriteInt(stream, &len, size))
    {
        /* Failed to write to stream, ooops */
        RWRETURN((RpAnimSequence *) NULL);
    }
    *bytesWritten += size;

    size = len;
    if (!RwStreamWrite(stream, name, size))
    {
        /* Failed to write to stream, ooops */
        RWRETURN((RpAnimSequence *) NULL);
    }
    *bytesWritten += size;

    /* write the type */
    type = animSeq->type;
    size = sizeof(RwInt32);
    if (!RwStreamWriteInt(stream, &type, size))
    {
        /* Failed to write to stream, ooops */
        RWRETURN((RpAnimSequence *) NULL);
    }
    *bytesWritten += size;

    /* write the num keys */
    numKeys = animSeq->numKeys;
    size = sizeof(RwInt32);
    if (!RwStreamWriteInt(stream, &numKeys, size))
    {
        /* Failed to write to stream, ooops */
        RWRETURN((RpAnimSequence *) NULL);
    }
    *bytesWritten += size;

    /* write the num iterps */
    numInterps = animSeq->numInterpolators;
    size = sizeof(RwInt32);
    if (!RwStreamWriteInt(stream, &numInterps, size))
    {
        /* Failed to write to stream, ooops */
        RWRETURN((RpAnimSequence *) NULL);
    }
    *bytesWritten += size;

    /* write the keys */
    switch (type)
    {
        case rpTRANSLATE:
            {
                RpTranslateKey     *keys;

                keys = (RpTranslateKey *) animSeq->keys;
                if (!TranslateKeysWrite(keys, numKeys, stream, &size))
                {
                    /* Failed to write to stream, ooops */
                    RWRETURN((RpAnimSequence *) NULL);
                }
                *bytesWritten += size;

                break;
            }
        case rpROTATE:
            {
                RpRotateKey        *keys;

                keys = (RpRotateKey *) animSeq->keys;
                if (!RotateKeysWrite(keys, numKeys, stream, &size))
                {
                    /* Failed to write to stream, ooops */
                    RWRETURN((RpAnimSequence *) NULL);
                }
                *bytesWritten += size;

                break;
            }
    }

    /* write the interps */
    if (!AnimInterpolatorsWrite(animSeq->interps, numInterps,
                                stream, &size))
    {
        /* Failed to write to stream, ooops */
        RWRETURN((RpAnimSequence *) NULL);
    }
    *bytesWritten += size;

    RWRETURN(animSeq);
}

static RpAnimSequence *
AnimSequenceRead(RwStream * stream)
{

    RpAnimSequence     *animSeq;
    RwInt8              tmpName[64];
    RwChar              name[64];
    RwInt32             len, i;
    RwInt32             type;
    RwInt32             numKeys;
    RwInt32             numInterps;
    rpAnimInterpolator *interps;

    RWFUNCTION(RWSTRING("AnimSequenceRead"));

    RWASSERT(stream);

    /* read the name */
    if (!RwStreamReadInt(stream, &len, sizeof(len)))
    {
        /* Failed to read from stream, ooops */
        RWRETURN((RpAnimSequence *) NULL);
    }

    if (!RwStreamRead(stream, tmpName, len))
    {
        /* Failed to read from stream, ooops */
        RWRETURN((RpAnimSequence *) NULL);
    }

    /* the name is stored in single byte format
     * so if we're a UNICODE build, convert it */
    for (i = 0; i < len; i++)
    {
        name[i] = (RwChar) tmpName[i];
    }
    name[len] = '\0';

    /* read the type */
    if (!RwStreamReadInt(stream, &(type), sizeof(type)))
    {
        /* Failed to write to stream, ooops */
        RWRETURN((RpAnimSequence *) NULL);
    }

    /* read the num keys */
    if (!RwStreamReadInt(stream, &numKeys, sizeof(numKeys)))
    {
        /* Failed to write to stream, ooops */
        RWRETURN((RpAnimSequence *) NULL);
    }

    /* read the num iterps */
    if (!RwStreamReadInt(stream, &numInterps, sizeof(numInterps)))
    {
        /* Failed to write to stream, ooops */
        RWRETURN((RpAnimSequence *) NULL);
    }

    /* create the animSequence */
    animSeq = RpAnimSequenceCreate(name, (RpAnimType) type,
                                   numKeys, numInterps);
    if (!animSeq)
    {
        RWRETURN((RpAnimSequence *) NULL);
    }

    /* read the keys */
    switch (type)
    {
        case rpTRANSLATE:
            {
                RpTranslateKey     *keys;

                keys = (RpTranslateKey *) animSeq->keys;
                if (!TranslateKeysRead(keys, numKeys, stream))
                {
                    /* Failed to write to stream, ooops */
                    RWRETURN((RpAnimSequence *) NULL);
                }

                break;
            }
        case rpROTATE:
            {
                RpRotateKey        *keys;

                keys = (RpRotateKey *) animSeq->keys;
                if (!RotateKeysRead(keys, numKeys, stream))
                {
                    /* Failed to write to stream, ooops */
                    RWRETURN((RpAnimSequence *) NULL);
                }

                break;
            }
    }

    /* read the interps */
    interps = animSeq->interps;
    if (!AnimInterpolatorsRead(interps, numInterps, stream))
    {
        /* Failed to write to stream, ooops */
        RWRETURN((RpAnimSequence *) NULL);
    }

    RWRETURN(animSeq);
}

static              RwInt32
AnimSequenceSize(RpAnimSequence * animSeq)
{

    RwChar             *name;
    RwInt32             len;
    RwInt32             type;
    RwInt32             numKeys;
    RwInt32             numInterps;
    RwInt32             size = 0;

    RWFUNCTION(RWSTRING("AnimSequenceSize"));

    RWASSERT(animSeq);

    /* name length */
    size += sizeof(RwInt32);

    /* name */
    name = animSeq->name;
    len = (rwstrlen(name) + 1) * sizeof(RwChar);
    size += len;

    /* type */
    type = animSeq->type;
    size += sizeof(RwInt32);

    /* num keys */
    numKeys = animSeq->numKeys;
    size += sizeof(RwInt32);

    /* num interps */
    numInterps = animSeq->numInterpolators;
    size += sizeof(RwInt32);

    /* keys */
    switch (type)
    {
        case rpTRANSLATE:
            {
                size += sizeof(RpTranslateKey) * numKeys;
                break;
            }
        case rpROTATE:
            {
                size += sizeof(RpRotateKey) * numKeys;
                break;
            }
    }

    /* interps */
    size += sizeof(rpAnimInterpolator) * numInterps;

    RWRETURN(size);
}

/* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

   rpAnimFrame methods

   !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! */

static              RwBool
FrameAnimDestroy(rpAnimFrame * frameAnim)
{
    rpAnimSequenceList *tranSeqList;
    rpAnimSequenceList *rotSeqList;

    RWFUNCTION(RWSTRING("FrameAnimDestroy"));
    RWASSERT(frameAnim);

    rotSeqList = frameAnim->rotateSequences;
    while (rotSeqList)
    {
        rpAnimSequenceList *const rotSeqListNext = rotSeqList->next;
        RpAnimSequence     *const rotSeq = rotSeqList->sequence;

        RpAnimSequenceDestroy(rotSeq);
        ANIMMESSAGE(("%p rotSeq->name %s rotSeq->refCnt %d",
                     rotSeq, rotSeq->name, rotSeq->refCnt));
        RwFreeListFree(RPANIMGLOBAL(animListFreeList), rotSeqList);

        rotSeqList = rotSeqListNext;
    }

    tranSeqList = frameAnim->translateSequences;
    while (tranSeqList)
    {
        rpAnimSequenceList *const tranSeqListNext = tranSeqList->next;
        RpAnimSequence     *const tranSeq = tranSeqList->sequence;

        RpAnimSequenceDestroy(tranSeq);
        ANIMMESSAGE(("%p tranSeq->name %s tranSeq->refCnt %d",
                     tranSeq, tranSeq->name, tranSeq->refCnt));
        RwFreeListFree(RPANIMGLOBAL(animListFreeList), tranSeqList);

        tranSeqList = tranSeqListNext;
    }

    RwFreeListFree(AnimStatic.FrameFreeList, frameAnim);
    frameAnim = (rpAnimFrame *) NULL;

    RWRETURN(TRUE);
}

static rpAnimFrame *
FrameAnimCreate(void /*RwChar *name */ )
{
    rpAnimFrame        *frameAnim;

    RWFUNCTION(RWSTRING("FrameAnimCreate"));

    frameAnim =
        (rpAnimFrame *) RwFreeListAlloc(AnimStatic.FrameFreeList);

    if (!frameAnim)
    {
        RWRETURN((rpAnimFrame *) NULL);
    }

    frameAnim->numTranslateSequences = 0;
    frameAnim->translateSequences = (rpAnimSequenceList *) NULL;
    frameAnim->numRotateSequences = 0;
    frameAnim->rotateSequences = (rpAnimSequenceList *) NULL;

    RWRETURN(frameAnim);
}

static void        *
AnimFrameDtor(void *object,
              RwInt32 __RWUNUSED__ offsetInObject,
              RwInt32 __RWUNUSED__ sizeInObject)
{
    RwFrame            *const frame = (RwFrame *) object;
    rpAnimFrame        *frameAnim;

    RWFUNCTION(RWSTRING("AnimFrameDtor"));

    RWASSERT(frame);
    RWASSERT(((RwObject *) (frame))->type == (rwFRAME));

    frameAnim = RWFRAMEGETANIM(frame);

#if (0)
    RtDBStackDump("Destructor\n", RtDBStackCurrent());
    ANIMMESSAGE(("frameAnim %p", frameAnim));
#endif /* (0) */

    if (frameAnim)
    {
        rpAnimSeqFrameData *data;

        data = RWFRAMEGETDATA(frame);

        /* destoy the frameAnim */
        FrameAnimDestroy(frameAnim);

        data->animFrame = (rpAnimFrame *) NULL;
        data->priority = 0;
        data->tag = DEFAULTTAGID;

        RWFRAMESETANIM(frame, (rpAnimFrame *) NULL);

    }

    RWRETURN(frame);
}

static void        *
AnimFrameCtor(void *object,
              RwInt32 __RWUNUSED__ offsetInObject,
              RwInt32 __RWUNUSED__ sizeInObject)
{
    RwFrame            *const frame = (RwFrame *) object;
    rpAnimSeqFrameData *data;

    RWFUNCTION(RWSTRING("AnimFrameCtor"));

    RWASSERT(frame);

    RWASSERT(((RwObject *) (frame))->type == (rwFRAME));

    data = RWFRAMEGETDATA(frame);

    data->animFrame = (rpAnimFrame *) NULL;
    data->priority = 0;
    data->tag = DEFAULTTAGID;

    RWRETURN(frame);
}

static void        *
AnimFrameCopy(void *dstObject,
              const void *srcObject,
              RwInt32 __RWUNUSED__ offsetInObject,
              RwInt32 __RWUNUSED__ sizeInObject)
{
    const RwFrame      *const srcFrame = (const RwFrame *) srcObject;
    RwFrame            *const dstFrame = (RwFrame *) dstObject;
    rpAnimFrame        *srcFrameAnim;

    RWFUNCTION(RWSTRING("AnimFrameCopy"));

    RWASSERT(srcFrame);
    RWASSERT(((const RwObject *) (srcFrame))->type == (rwFRAME));
    RWASSERT(dstFrame);
    RWASSERT(((RwObject *) (dstFrame))->type == (rwFRAME));

    srcFrameAnim = RWFRAMEGETCONSTANIM(srcFrame);
    if (srcFrameAnim)
    {
        rpAnimFrame        *frameAnim;
        RwInt32             tag;

        /* create the new frame anim */
        frameAnim = FrameAnimCreate( /*NULL */ );
        if (!frameAnim)
        {
            RWRETURN(NULL);
        }

        RWFRAMESETANIM(dstFrame, frameAnim);

        if (srcFrameAnim->numRotateSequences > 0)
        {
            /* copy rotate sequences */
            rpAnimSequenceList *seqList;

            seqList = srcFrameAnim->rotateSequences;
            while (seqList)
            {
                RpAnimFrameAddSequence(dstFrame, seqList->sequence);
                seqList = seqList->next;
            }
        }
        if (srcFrameAnim->numTranslateSequences > 0)
        {
            /* copy rotate sequences */
            rpAnimSequenceList *seqList;

            seqList = srcFrameAnim->translateSequences;
            while (seqList)
            {
                RpAnimFrameAddSequence(dstFrame, seqList->sequence);
                seqList = seqList->next;
            }
        }

        /* copy the frame tag */
        tag = RWFRAMEGETCONSTTAG(srcFrame);
        RWFRAMESETTAG(dstFrame, tag);
    }

    RWRETURN(dstFrame);
}

/* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

   rpAnimState methods

   !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! */

static rpAnimState *
AnimStateCreate(void)
{
    rpAnimState        *animState;

    RWFUNCTION(RWSTRING("AnimStateCreate"));

    animState = (rpAnimState *)
        RwFreeListAlloc(AnimStatic.StateFreeList);

    if (!animState)
    {
        RWRETURN((rpAnimState *) NULL);
    }

    animState->sequence = (RpAnimSequence *) NULL;
    animState->currentInterp = (rpAnimInterpolator *) NULL;
    animState->position = (RwReal) (0);

    RWRETURN(animState);
}

static              RwBool
AnimStateDestroy(rpAnimState * animState)
{
    RWFUNCTION(RWSTRING("AnimStateDestroy"));

    RWASSERT(animState);

    RwFreeListFree(AnimStatic.StateFreeList, animState);
    animState = (rpAnimState *) NULL;

    RWRETURN(TRUE);
}

static rpAnimState *
AnimStateReset(rpAnimState * animState, RwBool forward, RwReal time)
{
    RpAnimSequence     *animSeq;

    RWFUNCTION(RWSTRING("AnimStateReset"));

    RWASSERT(animState);

    animState->position = time;
    animSeq = animState->sequence;

    if (forward)
    {
        animState->currentInterp = animSeq->interps;
    }
    else
    {
        RwInt32             offset;

        offset = animSeq->numInterpolators - 1;
        animState->currentInterp = animSeq->interps + offset;
    }

    RWRETURN(animState);
}

static rpAnimState *
AnimTranslateStateAddTime(rpAnimState * animState,
                          rpAnimTransformState * transformState,
                          RwFrame * frame, RwBool forward, RwReal time)
{

    RwInt32             interpInd;
    RwInt32             numInterps;
    RpAnimSequence     *animSeq;
    rpAnimInterpolator *interps;
    rpAnimInterpolator *currentInterp;
    RwReal              interpPos;
    void               *keys;

    RWFUNCTION(RWSTRING("AnimTranslateStateAddTime"));

    RWASSERT(animState);
    RWASSERT(transformState);

    transformState->flags |= rpDIRTY;

    animSeq = animState->sequence;
    currentInterp = animState->currentInterp;
    numInterps = animSeq->numInterpolators;
    keys = animSeq->keys;

    interps = animState->sequence->interps;
    interpInd = animState->currentInterp - interps;

    /* work out which interpolator we're in */
    animState->position += time;
    if (forward)
    {
        while (animState->position >= currentInterp->time)
        {
            if (interpInd < (numInterps - 1))
            {
                animState->position -= currentInterp->time;
                interpInd++;
                transformState->flags |= rpNEWINTERP;
                animState->currentInterp = ++currentInterp;
            }
            else
            {
                /* clamp it */
                animState->position = currentInterp->time;
                break;
            }
        }

        interpPos = animState->position;
    }
    else
    {
        while (animState->position >= currentInterp->time)
        {
            if (interpInd > 0)
            {
                animState->position -= currentInterp->time;
                interpInd--;
                transformState->flags |= rpNEWINTERP;
                animState->currentInterp = --currentInterp;
            }
            else
            {
                /* clamp it */
                animState->position = currentInterp->time;
                break;
            }
        }

        interpPos = currentInterp->time - animState->position;
    }

    FrameUpdateTranslate(frame, currentInterp, 
                          (RpTranslateKey *) keys, interpPos);

    RWRETURN(animState);
}

static rpAnimState *
AnimRotateStateAddTime(rpAnimState * animState,
                       rpAnimTransformState * transformState,
                       RwFrame * frame, RwBool forward, RwReal time)
{

    RwInt32             interpInd;
    RwInt32             numInterps;
    RpAnimSequence     *animSeq;
    rpAnimInterpolator *interps;
    rpAnimInterpolator *currentInterp;
    RwReal              interpPos;
    void               *keys;

    RWFUNCTION(RWSTRING("AnimRotateStateAddTime"));

    RWASSERT(animState);
    RWASSERT(transformState);

    transformState->flags |= rpDIRTY;

    animSeq = animState->sequence;
    currentInterp = animState->currentInterp;
    numInterps = animSeq->numInterpolators;
    keys = animSeq->keys;

    interps = animState->sequence->interps;
    interpInd = animState->currentInterp - interps;

    /* work out which interpolator we're in */
    animState->position += time;
    if (forward)
    {
        while (animState->position >= currentInterp->time)
        {
            if (interpInd < (numInterps - 1))
            {
                animState->position -= currentInterp->time;
                interpInd++;
                transformState->flags |= rpNEWINTERP;
                animState->currentInterp = ++currentInterp;
            }
            else
            {
                /* clamp it */
                animState->position = currentInterp->time;
                break;
            }
        }

        interpPos = animState->position;
    }
    else
    {
        while (animState->position >= currentInterp->time)
        {
            if (interpInd > 0)
            {
                animState->position -= currentInterp->time;
                interpInd--;
                transformState->flags |= rpNEWINTERP;
                animState->currentInterp = --currentInterp;
            }
            else
            {
                /* clamp it */
                animState->position = currentInterp->time;
                break;
            }
        }

        interpPos = currentInterp->time - animState->position;
    }

    FrameUpdateRotate(frame, transformState, currentInterp, 
                      (RpRotateKey *)keys, interpPos);

    RWRETURN(animState);
}

/* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

   rpAnimFrameState methods

   !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! */

static rpAnimFrameState *
AnimFrameStateInit(rpAnimFrameState * animFrameState)
{
    RWFUNCTION(RWSTRING("AnimFrameStateInit"));

    RWASSERT(animFrameState);

    animFrameState->transformState.flags = 0;
    animFrameState->translateState = (rpAnimState *) NULL;
    animFrameState->rotateState = (rpAnimState *) NULL;

    RWRETURN(animFrameState);
}

static rpAnimFrameState *
AnimFrameStateDeinit(rpAnimFrameState * animFrameState)
{
    RWFUNCTION(RWSTRING("AnimFrameStateDeinit"));

    RWASSERT(animFrameState);

    if (animFrameState->translateState)
    {
        AnimStateDestroy(animFrameState->translateState);
        animFrameState->translateState = (rpAnimState *) NULL;
    }

    if (animFrameState->rotateState)
    {
        AnimStateDestroy(animFrameState->rotateState);
        animFrameState->rotateState = (rpAnimState *) NULL;
    }

    RWRETURN(animFrameState);
}

static rpAnimFrameState *
AnimFrameStateReset(rpAnimFrameState * animFrameState, RwBool
                    forward, RwReal time)
{
    RWFUNCTION(RWSTRING("AnimFrameStateReset"));

    RWASSERT(animFrameState);

    animFrameState->transformState.flags = rpNEWINTERP;

    if (animFrameState->translateState)
    {
        AnimStateReset(animFrameState->translateState, forward, time);
    }

    if (animFrameState->rotateState)
    {
        AnimStateReset(animFrameState->rotateState, forward, time);
    }

    RWRETURN(animFrameState);
}

static rpAnimFrameState *
AnimFrameStateAddTime(rpAnimFrameState * animFrameState,
                      RwFrame * frame, RwBool forward, RwReal time)
{
    RWFUNCTION(RWSTRING("AnimFrameStateAddTime"));

    RWASSERT(animFrameState);

    /* Rotations must be applied first */
    if (animFrameState->rotateState)
    {
        AnimRotateStateAddTime(animFrameState->rotateState,
                               &animFrameState->transformState, frame,
                               forward, time);
    }

    if (animFrameState->translateState)
    {
        AnimTranslateStateAddTime(animFrameState->translateState,
                                  &animFrameState->transformState,
                                  frame, forward, time);
    }

    RWRETURN(animFrameState);
}

/* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

   rpAnimFrameList methods

   !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! */

static rpAnimFrameList *
AnimFrameListCreate(void)
{
    rpAnimFrameList    *animFrameList;

    RWFUNCTION(RWSTRING("AnimFrameListCreate"));

    animFrameList = (rpAnimFrameList *)
        RwFreeListAlloc(AnimStatic.FrameListFreeList);
    if (!animFrameList)
    {
        RWRETURN((rpAnimFrameList *) NULL);
    }

    animFrameList->frame = (RwFrame *) NULL;
    AnimFrameStateInit(&animFrameList->animFrameState);
    animFrameList->next = (rpAnimFrameList *) NULL;

    RWRETURN(animFrameList);
}

static              RwBool
AnimFrameListDestroy(rpAnimFrameList * animFrameList)
{
    RWFUNCTION(RWSTRING("AnimFrameListDestroy"));

    RWASSERT(animFrameList);

    AnimFrameStateDeinit(&animFrameList->animFrameState);

    RwFreeListFree(AnimStatic.FrameListFreeList, animFrameList);
    animFrameList = (rpAnimFrameList *) NULL;

    RWRETURN(TRUE);
}

static rpAnimFrameList *
AnimFrameListAddTime(rpAnimFrameList * frameList, RwBool forward,
                     RwInt32 priority, RwReal time)
{
    RWFUNCTION(RWSTRING("AnimFrameListAddTime"));

    RWASSERT(frameList);

    /* you will obey my priority */
    if (priority >= RWFRAMEGETPRIORITY(frameList->frame))
    {
        RWFRAMESETPRIORITY(frameList->frame, priority);

        AnimFrameStateAddTime(&frameList->animFrameState,
                              frameList->frame, forward, time);
    }

    RWRETURN(frameList);
}

static              RwReal
AnimFrameListGetSequenceDuration(rpAnimFrameList * frameAnimList,
                                 const RwChar * seqName)
{
    RwReal              maxDuration = (RwReal) (0);

    RWFUNCTION(RWSTRING("AnimFrameListGetSequenceDuration"));

    RWASSERT(frameAnimList);
    RWASSERT(seqName);

    while (frameAnimList)
    {
        RwFrame            *frame = frameAnimList->frame;
        rpAnimFrame        *frameAnim;
        RwReal              duration = (RwReal) (0);
        rpAnimSequenceList *animSeq;

        frameAnim = RWFRAMEGETANIM(frame);

        animSeq = frameAnim->translateSequences;
        while (animSeq)
        {
            if (rwstrcmp(animSeq->sequence->name, seqName) == 0)
            {
                duration = AnimSequenceGetDuration(animSeq->sequence);
                if (duration > maxDuration)
                {
                    maxDuration = duration;
                }
            }

            animSeq = animSeq->next;
        }

        animSeq = frameAnim->rotateSequences;
        while (animSeq)
        {
            if (rwstrcmp(animSeq->sequence->name, seqName) == 0)
            {
                duration = AnimSequenceGetDuration(animSeq->sequence);
                if (duration > maxDuration)
                {
                    maxDuration = duration;
                }
            }

            animSeq = animSeq->next;
        }

        /* onto the next */
        frameAnimList = frameAnimList->next;
    }

    RWRETURN(maxDuration);
}

static rpAnimFrameList *
AnimFrameListReset(rpAnimFrameList * frameAnimList, RwBool forward,
                   RwReal time)
{
    RWFUNCTION(RWSTRING("AnimFrameListReset"));

    RWASSERT(frameAnimList);

    while (frameAnimList)
    {
        AnimFrameStateReset(&frameAnimList->animFrameState, forward,
                            time);
        frameAnimList = frameAnimList->next;
    }

    RWRETURN(frameAnimList);
}

/* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

   RpAnimClumpSequence methods

   !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! */

static              RwBool
AnimClumpSequenceDestroy(RpAnimClumpSequence * clumpAnimSeq)
{
    RWFUNCTION(RWSTRING("AnimClumpSequenceDestroy"));

    RWASSERT(clumpAnimSeq);

    if (clumpAnimSeq->name)
    {
        RwFree(clumpAnimSeq->name);
        clumpAnimSeq->name = (char *) NULL;
    }

    if (clumpAnimSeq->frameList)
    {
        rpAnimFrameList    *animFrameList;

        animFrameList = clumpAnimSeq->frameList;
        while (animFrameList)
        {
            rpAnimFrameList    *const animFrameListNext =
                animFrameList->next;

            AnimFrameListDestroy(animFrameList);
            animFrameList = animFrameListNext;
        }
        clumpAnimSeq->frameList = (rpAnimFrameList *) NULL;
    }

    RwFreeListFree(AnimStatic.ClumpSequenceFreeList, clumpAnimSeq);
    clumpAnimSeq = (RpAnimClumpSequence *) NULL;

    RWRETURN(TRUE);
}

static RpAnimClumpSequence *
AnimClumpSequenceCreate(const RwChar * name)
{
    RpAnimClumpSequence *clumpAnimSeq;
    RwChar             *seqName = (RwChar *)NULL;

    RWFUNCTION(RWSTRING("AnimClumpSequenceCreate"));

    RWASSERT(name);

    clumpAnimSeq = (RpAnimClumpSequence *)
        RwFreeListAlloc(AnimStatic.ClumpSequenceFreeList);
    if (!clumpAnimSeq)
    {
        RWRETURN((RpAnimClumpSequence *) NULL);
    }

    rwstrdup(seqName, name);

    if (!seqName)
    {
        RWRETURN((RpAnimClumpSequence *) NULL);
    }

    clumpAnimSeq->name = seqName;
    clumpAnimSeq->duration = (RwReal) (0);
    clumpAnimSeq->position = (RwReal) (0);
    clumpAnimSeq->forward = TRUE;
    clumpAnimSeq->priority = 0;
    clumpAnimSeq->frameList = (rpAnimFrameList *) NULL;
    clumpAnimSeq->fpAnimClumpSeqCB = (RpAnimClumpSequenceCallBack)NULL;
    clumpAnimSeq->callBackData = NULL;
    clumpAnimSeq->next = (RpAnimClumpSequence *) NULL;

    RWRETURN(clumpAnimSeq);
}

static RpAnimClumpSequence *
AnimClumpRemoveSequence(RpAnimClumpSequence * dummy,
                        const RwChar * sequenceName)
{
    RpAnimClumpSequence *animClumpSequence;
    RpAnimClumpSequence *animClumpSequencePrev;

    RWFUNCTION(RWSTRING("AnimClumpRemoveSequence"));

    RWASSERT(dummy);
    RWASSERT(sequenceName);

    animClumpSequencePrev = dummy;
    animClumpSequence = animClumpSequencePrev->next;
    while (animClumpSequence)
    {
        if (rwstrcmp(animClumpSequence->name, sequenceName) == 0)
        {
            /* remove from sequence */

            animClumpSequencePrev->next = animClumpSequence->next;
            animClumpSequence->next = (RpAnimClumpSequence *) NULL;

            RWRETURN(animClumpSequence);
        }

        animClumpSequencePrev = animClumpSequence;
        animClumpSequence = animClumpSequence->next;
    }

    RWRETURN((RpAnimClumpSequence *) NULL);
}

static RpAnimClumpSequence *
AnimClumpSequenceAddTime(RpAnimClumpSequence * animSeq,
                         RpClump * clump, RwReal time)
{
    rpAnimFrameList    *frameList;

    RWFUNCTION(RWSTRING("AnimClumpSequenceAddTime"));

    RWASSERT(animSeq);
    RWASSERT(clump);

    animSeq->position += time;
    if (animSeq->position > animSeq->duration)
    {
        rpAnimClump        *clumpAnim;
        RpAnimClumpSequence *animClumpSequence;

        /* remove from the active list */
        clumpAnim = RWCLUMPGETANIM(clump);
        animClumpSequence =
            AnimClumpRemoveSequence
            (clumpAnim->activeClumpSequenceList, animSeq->name);

        if (animClumpSequence)
        {
            RpAnimClumpSequence *inactiveDummySeq;

            /* add it to the inactive list */
            inactiveDummySeq = clumpAnim->clumpSequenceList;
            animClumpSequence->next = inactiveDummySeq->next;
            inactiveDummySeq->next = animClumpSequence;

            while (animSeq->position > animSeq->duration)
            {
                animSeq->position =
                    (animSeq->position) - (animSeq->duration);
            }

            /* call the sequence end callback */
            animSeq->fpAnimClumpSeqCB(clump, animSeq,
                                      animSeq->position,
                                      animSeq->callBackData);
        }

        /* no longer same animSeq so get outa town */
        RWRETURN(animSeq);
    }

    frameList = animSeq->frameList;
    while (frameList)
    {
        AnimFrameListAddTime(frameList, animSeq->forward,
                             animSeq->priority, time);
        frameList = frameList->next;
    }

    RWRETURN(animSeq);
}

static RpClump     *
AnimClumpSequenceDefaultCallBack(RpClump * clump,
                                 RpAnimClumpSequence * animClumpSeq,
                                 RwReal pos, void *__RWUNUSED__ data)
{
    /* test function */
    RwReal              duration;

    RWFUNCTION(RWSTRING("AnimClumpSequenceDefaultCallBack"));

    RWASSERT(clump);
    RWASSERT(animClumpSeq);

    duration = animClumpSeq->duration;
    while (pos > duration)
    {
        pos = (pos) - (duration);
    }

    RpAnimClumpStartSequence(clump, animClumpSeq->name,
                             animClumpSeq->forward,
                             animClumpSeq->priority, pos);

    RWRETURN(clump);
}

/* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

   rpAnimFrameSeqInfo methods

   !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! */

static RwFrame     *
AnimSetClumpFrameList(RwFrame * frame, void *data)
{
    rpAnimFrame        *frameAnim;

    RWFUNCTION(RWSTRING("AnimSetClumpFrameList"));

    RWASSERT(frame);
    RWASSERT(data);

    frameAnim = RWFRAMEGETANIM(frame);
    if (frameAnim)
    {
        rpAnimFrameSeqInfo *frameAnimSeqInfo;
        const RwChar       *name;
        rpAnimSequenceList *animSeq;
        RpAnimSequence     *translateSequence = (RpAnimSequence *) NULL;
        RpAnimSequence     *rotateSequence = (RpAnimSequence *) NULL;

        frameAnimSeqInfo = (rpAnimFrameSeqInfo *) data;
        name = frameAnimSeqInfo->seqName;

        /* look through the translations */
        animSeq = frameAnim->translateSequences;
        while (animSeq)
        {
            if (rwstrcmp(animSeq->sequence->name, name) == 0)
            {
                translateSequence = animSeq->sequence;
                break;
            }

            animSeq = animSeq->next;
        }

        /* look through the rotations */
        animSeq = frameAnim->rotateSequences;
        while (animSeq)
        {
            if (rwstrcmp(animSeq->sequence->name, name) == 0)
            {
                rotateSequence = animSeq->sequence;
                break;
            }

            animSeq = animSeq->next;
        }

        /* do we have anything to add to the animFrameList */
        if (translateSequence || rotateSequence)
        {
            rpAnimFrameList    *animFrameList;

            animFrameList = AnimFrameListCreate();
            if (!animFrameList)
            {
                RWRETURN((RwFrame *) NULL);
            }

            /* set up the frame */
            animFrameList->frame = frame;

            /* set the translate sequence */
            if (translateSequence)
            {
                rpAnimState        *animState;

                animState = AnimStateCreate();
                if (!animState)
                {
                    AnimFrameListDestroy(animFrameList);

                    RWRETURN((RwFrame *) NULL);
                }

                animState->sequence = translateSequence;
                animState->currentInterp = translateSequence->interps;
                animState->position = (RwReal) (0);

                animFrameList->animFrameState.translateState =
                    animState;
            }

            /* set the rotate sequence */
            if (rotateSequence)
            {
                rpAnimState        *animState;

                animState = AnimStateCreate();
                if (!animState)
                {
                    AnimFrameListDestroy(animFrameList);

                    RWRETURN((RwFrame *) NULL);
                }

                animState->sequence = rotateSequence;
                animState->currentInterp = rotateSequence->interps;
                animState->position = (RwReal) (0);

                animFrameList->animFrameState.rotateState = animState;
                animFrameList->animFrameState.transformState.flags =
                    rpNEWINTERP;
            }

            /* HACK */
            {
                rpAnimFrame        *frameAnim;
                rpAnimTransformState *transformState;

                transformState =
                    &animFrameList->animFrameState.transformState;

                frameAnim = RWFRAMEGETANIM(frame);

                transformState->flags = rpDIRTY | rpNEWINTERP;
            }

            /* patch it in */
            animFrameList->next = frameAnimSeqInfo->frameAnimList;
            frameAnimSeqInfo->frameAnimList = animFrameList;
            /* set the frame */
            frameAnimSeqInfo->frameAnimList->frame = frame;
        }
    }

    /* now do any children */
    RwFrameForAllChildren(frame, AnimSetClumpFrameList, data);

    RWRETURN(frame);
}

/* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

   rpAnimClump methods

   !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! */

static rpAnimClump *
AnimClumpDeinit(rpAnimClump * clumpAnim)
{

    RpAnimClumpSequence *clumpAnimSeq;
    RwInt32             numAnimClumpSequences;

    RWFUNCTION(RWSTRING("AnimClumpDeinit"));

    RWASSERT(clumpAnim);

    numAnimClumpSequences = clumpAnim->numAnimClumpSequences;

    /* Deinit active */
    clumpAnimSeq = clumpAnim->activeClumpSequenceList;
    while (clumpAnimSeq)
    {
        RpAnimClumpSequence *const clumpAnimSeqNext =
            clumpAnimSeq->next;

        AnimClumpSequenceDestroy(clumpAnimSeq);
        numAnimClumpSequences--;
        clumpAnimSeq = clumpAnimSeqNext;
    }
    clumpAnim->activeClumpSequenceList = (RpAnimClumpSequence *) NULL;

    /* Deinit list */
    clumpAnimSeq = clumpAnim->clumpSequenceList;
    while (clumpAnimSeq)
    {
        RpAnimClumpSequence *const clumpAnimSeqNext =
            clumpAnimSeq->next;

        AnimClumpSequenceDestroy(clumpAnimSeq);
        numAnimClumpSequences--;
        clumpAnimSeq = clumpAnimSeqNext;
    }
    clumpAnim->clumpSequenceList = (RpAnimClumpSequence *) NULL;

    RWASSERT(numAnimClumpSequences == -2); /*FIX */
    clumpAnim->numAnimClumpSequences = 0;

    rwFrameListDeinitialize(&clumpAnim->frameList);

    RWRETURN(clumpAnim);
}

static rpAnimClump *
AnimClumpInit(rpAnimClump * clumpAnim)
{
    RpAnimClumpSequence *clumpSequenceList =
        (RpAnimClumpSequence *) NULL;
    RpAnimClumpSequence *activeClumpSequenceList =
        (RpAnimClumpSequence *) NULL;

    RWFUNCTION(RWSTRING("AnimClumpInit"));

    RWASSERT(clumpAnim);

    clumpAnim->numAnimClumpSequences = 0;
    clumpAnim->frameList.frames = (RwFrame **) NULL;
    clumpAnim->frameList.numFrames = 0;

    clumpAnim->clumpSequenceList = (RpAnimClumpSequence *) NULL;
    clumpAnim->activeClumpSequenceList = (RpAnimClumpSequence *) NULL;

    /* Init list */
    clumpSequenceList = AnimClumpSequenceCreate(RWSTRING("_dummy_"));

    if (!clumpSequenceList)
    {
        RWRETURN((rpAnimClump *) NULL);
    }

    /* Init active */
    activeClumpSequenceList =
        AnimClumpSequenceCreate(RWSTRING("_dummy_"));

    if (!activeClumpSequenceList)
    {
        AnimClumpSequenceDestroy(clumpSequenceList);

        RWRETURN((rpAnimClump *) NULL);
    }

    clumpAnim->clumpSequenceList = clumpSequenceList;
    clumpAnim->activeClumpSequenceList = activeClumpSequenceList;

    RWRETURN(clumpAnim);
}

static void        *
AnimClumpDtor(void *object,
              RwInt32 __RWUNUSED__ offsetInObject,
              RwInt32 __RWUNUSED__ sizeInObject)
{
    rpAnimClump        *clumpAnim;

    RWFUNCTION(RWSTRING("AnimClumpDtor"));

    RWASSERT(object);
    RWASSERT(((RwObject *) (object))->type == (rpCLUMP));

    clumpAnim = RWCLUMPGETANIM(object);
    AnimClumpDeinit(clumpAnim);

    RWRETURN(object);
}

static void        *
AnimClumpCtor(void *object,
              RwInt32 __RWUNUSED__ offsetInObject,
              RwInt32 __RWUNUSED__ sizeInObject)
{
    RpClump            *clump;
    rpAnimClump        *clumpAnim;

    RWFUNCTION(RWSTRING("AnimClumpCtor"));

    RWASSERT(object);
    RWASSERT(((RwObject *) (object))->type == (rpCLUMP));

    clump = (RpClump *) object;
    clumpAnim = RWCLUMPGETANIM(clump);

    AnimClumpInit(clumpAnim);

    RWRETURN(object);
}

static void        *
AnimClumpCopy(void *dstObject,
              const void *srcObject,
              RwInt32 __RWUNUSED__ offsetInObject,
              RwInt32 __RWUNUSED__ sizeInObject)
{
    RpAnimClumpSequence *seq;
    rpAnimClump        *dstClumpAnim;
    const rpAnimClump  *srcClumpAnim;

    RWFUNCTION(RWSTRING("AnimClumpCopy"));

    RWASSERT(srcObject);
    RWASSERT(((const RwObject *) (srcObject))->type == (rpCLUMP));
    RWASSERT(dstObject);
    RWASSERT(((RwObject *) (dstObject))->type == (rpCLUMP));

    dstClumpAnim = RWCLUMPGETANIM(dstObject);
    srcClumpAnim = RWCLUMPGETCONSTANIM(srcObject);

    /*  add all the anims that were on the clump we're cloning.
     * Note the active status is not maintained. */
    seq = srcClumpAnim->clumpSequenceList->next;
    while (seq)
    {
        RpAnimClumpAddSequence((RpClump *) dstObject,
                               (const RwChar *) seq->name,
                               seq->fpAnimClumpSeqCB,
                               seq->callBackData);
        seq = seq->next;
    }
    seq = srcClumpAnim->activeClumpSequenceList->next;
    while (seq)
    {
        RpAnimClumpAddSequence((RpClump *) dstObject,
                               (const RwChar *) seq->name,
                               seq->fpAnimClumpSeqCB,
                               seq->callBackData);

        seq = seq->next;
    }

    RWRETURN(dstObject);
}

/* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

   RpAnimSequence methods

   !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! */

static RpAnimSequence *
AnimSequenceInit(RpAnimSequence * animSequence,
                 const RwChar * name,
                 RpAnimType type,
                 RwInt32 numKeys, RwInt32 numInterpolators)
{
    RWFUNCTION(RWSTRING("AnimSequenceInit"));

    RWASSERT(AnimStatic.animModule.numInstances);
    RWASSERT(animSequence);
    RWASSERT(name);

    /* Set the ref count */
    animSequence->refCnt = 1;

    /* set the name */
    rwstrdup(animSequence->name, name);

    if (!animSequence->name)
    {
        RWRETURN((RpAnimSequence *) NULL);
    }

    /* set the keys */
    switch (type)
    {
        case rpTRANSLATE:
            {
                animSequence->keys =
                    RwMalloc(sizeof(RpTranslateKey) * numKeys);
                break;
            }
        case rpROTATE:
            {
                animSequence->keys =
                    RwMalloc(sizeof(RpRotateKey) * numKeys);
                break;
            }
        case rpERR:
            {
                break;
            }
        case rpANIMTYPEFORCEENUMSIZEINT:
            break;

    }
    if (!animSequence->keys)
    {
        AnimSequenceDeinit(animSequence);

        RWRETURN((RpAnimSequence *) NULL);
    }
    animSequence->type = type;
    animSequence->numKeys = numKeys;

    /* set the interpolators */
    animSequence->interps = (rpAnimInterpolator *)
        RwMalloc(sizeof(rpAnimInterpolator) * numInterpolators);

    if (!animSequence->interps)
    {
        AnimSequenceDeinit(animSequence);

        RWRETURN((RpAnimSequence *) NULL);
    }
    animSequence->numInterpolators = numInterpolators;

    RWRETURN(animSequence);
}

static void
AnimFreeListsDestroy(void)
{
    RWFUNCTION(RWSTRING("AnimFreeListsDestroy"));

    if (NULL != AnimStatic.NameListFreeList)
    {
        RwFreeListDestroy(AnimStatic.NameListFreeList);
        AnimStatic.NameListFreeList = (RwFreeList *) NULL;
    }

    if (NULL != AnimStatic.DatabaseEntryFreeList)
    {
        RwFreeListDestroy(AnimStatic.DatabaseEntryFreeList);
        AnimStatic.DatabaseEntryFreeList = (RwFreeList *) NULL;
    }

    if (NULL != AnimStatic.DatabaseFreeList)
    {
        RwFreeListDestroy(AnimStatic.DatabaseFreeList);
        AnimStatic.DatabaseFreeList = (RwFreeList *) NULL;
    }

    if (NULL != AnimStatic.SequenceFreeList)
    {
        RwFreeListDestroy(AnimStatic.SequenceFreeList);
        AnimStatic.SequenceFreeList = (RwFreeList *) NULL;
    }

    if (NULL != AnimStatic.ClumpSequenceFreeList)
    {
        RwFreeListDestroy(AnimStatic.ClumpSequenceFreeList);
        AnimStatic.ClumpSequenceFreeList = (RwFreeList *) NULL;
    }

    if (NULL != AnimStatic.FrameListFreeList)
    {
        RwFreeListDestroy(AnimStatic.FrameListFreeList);
        AnimStatic.FrameListFreeList = (RwFreeList *) NULL;
    }

    if (NULL != AnimStatic.StateFreeList)
    {
        RwFreeListDestroy(AnimStatic.StateFreeList);
        AnimStatic.StateFreeList = (RwFreeList *) NULL;
    }

    if (NULL != AnimStatic.FrameFreeList)
    {
        RwFreeListDestroy(AnimStatic.FrameFreeList);
        AnimStatic.FrameFreeList = (RwFreeList *) NULL;
    }

    if (NULL != RPANIMGLOBAL(animListFreeList))
    {
        RwFreeListDestroy(RPANIMGLOBAL(animListFreeList));
        RPANIMGLOBAL(animListFreeList) = (RwFreeList *) NULL;
    }

    RWRETURNVOID();
}

static void        *
AnimClose(void *instance,
          RwInt32 __RWUNUSED__ offset, RwInt32 __RWUNUSED__ size)
{
    RWFUNCTION(RWSTRING("AnimClose"));

    RWASSERT(instance);

    /* one less module instance */
    AnimStatic.animModule.numInstances--;

    if (0 == AnimStatic.animModule.numInstances)
    {
        AnimFreeListsDestroy();
    }

    RWRETURN(instance);
}

static              RwBool
AnimFreeListsCreate(void)
{
    RwBool              result;

    RWFUNCTION(RWSTRING("AnimFreeListsCreate"));

    /* RWASSERT(NULL == RPANIMGLOBAL(animListFreeList)); */

    RPANIMGLOBAL(animListFreeList) =
        RwFreeListCreate(sizeof(rpAnimSequenceList), 50, 0);

    result = (NULL != RPANIMGLOBAL(animListFreeList));

    if (result)
    {
        AnimStatic.FrameFreeList =
            RwFreeListCreate(sizeof(rpAnimFrame), 50, 0);
        result = (NULL != AnimStatic.FrameFreeList);
    }

    if (result)
    {
        AnimStatic.StateFreeList =
            RwFreeListCreate(sizeof(rpAnimState), 50, 0);
        result = (NULL != AnimStatic.StateFreeList);
    }

    if (result)
    {
        AnimStatic.FrameListFreeList =
            RwFreeListCreate(sizeof(rpAnimFrameList), 50, 0);
        result = (NULL != AnimStatic.FrameListFreeList);
    }

    if (result)
    {
        AnimStatic.ClumpSequenceFreeList =
            RwFreeListCreate(sizeof(RpAnimClumpSequence), 50, 0);
        result = (NULL != AnimStatic.ClumpSequenceFreeList);
    }

    if (result)
    {
        AnimStatic.SequenceFreeList =
            RwFreeListCreate(sizeof(RpAnimSequence), 50, 0);
        result = (NULL != AnimStatic.SequenceFreeList);
    }

    if (result)
    {
        AnimStatic.DatabaseFreeList =
            RwFreeListCreate(sizeof(RpAnimDatabase), 50, 0);
        result = (NULL != AnimStatic.DatabaseFreeList);
    }

    if (result)
    {
        AnimStatic.DatabaseEntryFreeList =
            RwFreeListCreate(sizeof(RpAnimDatabaseEntry), 50, 0);
        result = (NULL != AnimStatic.DatabaseEntryFreeList);
    }

    if (result)
    {
        AnimStatic.NameListFreeList =
            RwFreeListCreate(sizeof(rpAnimNameList), 50, 0);
        result = (NULL != AnimStatic.NameListFreeList);
    }

    RWRETURN(result);
}

static void        *
AnimOpen(void *instance,
         RwInt32 __RWUNUSED__ offset, RwInt32 __RWUNUSED__ size)
{
    RWFUNCTION(RWSTRING("AnimOpen"));

    RWASSERT(instance);

    /* setup the offset */
    AnimStatic.animModule.globalsOffset = offset;

    if (0 == AnimStatic.animModule.numInstances)
    {
        if (!AnimFreeListsCreate())
        {
            AnimFreeListsDestroy();
            instance = NULL;
            RWRETURN(instance);
        }
    }

    /* one more module instance */
    AnimStatic.animModule.numInstances++;

    RWRETURN(instance);
}

static RwStream    *
AnimFrameReadStream(RwStream * stream,
                    RwInt32 __RWUNUSED__ binaryLength,
                    void *object,
                    RwInt32 __RWUNUSED__ offsetInObject,
                    RwInt32 __RWUNUSED__ sizeInObject)
{
    RwFrame            *frame;
    RwInt32             animDefined;
    RwInt32             seqNum;
    RwInt32             numTranSeqs, numRotateSeqs;
    rpAnimSeqFrameData *frameData;

    RWFUNCTION(RWSTRING("AnimFrameReadStream"));

    RWASSERT(stream);
    RWASSERT(object);

    /* setup the frameAnim */
    frame = (RwFrame *) object;

    frameData = RWFRAMEGETDATA(frame);

    /* Frame Tag */
    if (!RwStreamReadInt(stream, &frameData->tag, sizeof(RwInt32)))
    {
        RWRETURN((RwStream *) NULL);
    }

    if (!RwStreamReadInt(stream, &animDefined, sizeof(animDefined)))
    {
        RWRETURN((RwStream *) NULL);
    }

    if (!animDefined)
    {
        /* nothing more to read */
        RWRETURN(stream);
    }

    /* Priority value */
    if (!RwStreamReadInt(stream, &frameData->priority, sizeof(RwInt32)))
    {
        RWRETURN((RwStream *) NULL);
    }

    /* read num translations */
    if (!RwStreamReadInt(stream, &numTranSeqs, sizeof(numTranSeqs)))
    {
        RWRETURN((RwStream *) NULL);
    }

    /* read num rotations */
    if (!RwStreamReadInt(stream, &numRotateSeqs, sizeof(numTranSeqs)))
    {
        RWRETURN((RwStream *) NULL);
    }

    /* read translations */
    for (seqNum = 0; seqNum < numTranSeqs; seqNum++)
    {
        RpAnimSequence     *const tranSeq = AnimSequenceRead(stream);

        if (!tranSeq)
        {
            RWRETURN((RwStream *) NULL);
        }

        RpAnimFrameAddSequence(frame, tranSeq);
        ANIMMESSAGE(("%p tranSeq->name %s tranSeq->refCnt %d",
                     tranSeq, tranSeq->name, tranSeq->refCnt));

        /* Decrement the ref count so the sequence gets destroyed later */
        /* N.B. causes negative reference counts from app/demo/ppskin! */
        RpAnimSequenceDestroy(tranSeq);
    }

    /* read rotations */
    for (seqNum = 0; seqNum < numRotateSeqs; seqNum++)
    {
        RpAnimSequence     *const rotSeq = AnimSequenceRead(stream);

        if (!rotSeq)
        {
            RWRETURN((RwStream *) NULL);
        }

        RpAnimFrameAddSequence(frame, rotSeq);
        ANIMMESSAGE(("%p rotSeq->name %s rotSeq->refCnt %d",
                     rotSeq, rotSeq->name, rotSeq->refCnt));

        /* Decrement the ref count so the sequence gets destroyed later */
        /* N.B. causes negative reference counts from app/demo/ppskin! */
        RpAnimSequenceDestroy(rotSeq);
    }

    RWRETURN(stream);
}

static RwStream    *
AnimFrameWriteStream(RwStream * stream,
                     RwInt32 __RWUNUSED__ binaryLength,
                     const void *object,
                     RwInt32 __RWUNUSED__ offsetInObject,
                     RwInt32 __RWUNUSED__ sizeInObject)
{

    const RwFrame      *frame;
    rpAnimFrame        *frameAnim;
    rpAnimSequenceList *animSeq;
    RwInt32             animDefined = FALSE;
    RwInt32             numTranSeqs, numRotateSeqs;
    RwInt32             size;
    RwInt32             sizeTotal = 0;
    const rpAnimSeqFrameData *frameData;

    RWFUNCTION(RWSTRING("AnimFrameWriteStream"));

    RWASSERT(stream);
    RWASSERT(object);

    frame = (const RwFrame *) object;
    frameAnim = RWFRAMEGETCONSTANIM(frame);
    frameData = RWFRAMEGETCONSTDATA(frame);

    /* Frame Tag */
    size = sizeof(RwInt32);
    if (!RwStreamWriteInt(stream, &frameData->tag, size))
    {
        RWRETURN((RwStream *) NULL);
    }
    sizeTotal += size;

    animDefined = frameAnim ? TRUE : FALSE;
    size = sizeof(RwInt32);
    if (!RwStreamWriteInt(stream, &animDefined, size))
    {
        RWRETURN((RwStream *) NULL);
    }
    sizeTotal += size;

    if (!animDefined)
    {
        RWASSERT(binaryLength == sizeTotal);

        /* nothing more to write */
        RWRETURN(stream);
    }

    /* Priority value */
    size = sizeof(RwInt32);
    if (!RwStreamWriteInt(stream, &frameData->priority, size))
    {
        RWRETURN((RwStream *) NULL);
    }
    sizeTotal += size;

    /* write num translations */
    numTranSeqs = frameAnim->numTranslateSequences;
    size = sizeof(RwInt32);
    if (!RwStreamWriteInt(stream, &numTranSeqs, size))
    {
        RWRETURN((RwStream *) NULL);
    }
    sizeTotal += size;

    /* write num rotations */
    numRotateSeqs = frameAnim->numRotateSequences;
    size = sizeof(RwInt32);
    if (!RwStreamWriteInt(stream, &numRotateSeqs, size))
    {
        RWRETURN((RwStream *) NULL);
    }
    sizeTotal += size;

    /* write translations */
    animSeq = frameAnim->translateSequences;
    while (animSeq)
    {
        if (!AnimSequenceWrite(animSeq->sequence, stream, &size))
        {
            RWRETURN((RwStream *) NULL);
        }

        animSeq = animSeq->next;
        sizeTotal += size;
    }

    /* write rotations */
    animSeq = frameAnim->rotateSequences;
    while (animSeq)
    {
        if (!AnimSequenceWrite(animSeq->sequence, stream, &size))
        {
            RWRETURN((RwStream *) NULL);
        }

        animSeq = animSeq->next;
        sizeTotal += size;
    }

    RWASSERT(sizeTotal == binaryLength);

    RWRETURN(stream);
}

static              RwInt32
AnimFrameSizeStream(const void *object,
                    RwInt32 __RWUNUSED__ offsetInObject,
                    RwInt32 __RWUNUSED__ sizeInObject)
{
    const RwFrame      *frame;
    rpAnimFrame        *frameAnim;
    rpAnimSequenceList *animSeq;
    RwInt32             numTranSeqs, numRotateSeqs;
    RwInt32             size = 0;

    RWFUNCTION(RWSTRING("AnimFrameSizeStream"));

    RWASSERT(object);

    frame = (const RwFrame *) object;
    frameAnim = RWFRAMEGETCONSTANIM(frame);

    /* Frame Tag */
    size += sizeof(RwInt32);

    /* animation present flag */
    size += sizeof(RwInt32);

    if (!frameAnim)
    {
        RWRETURN(size);
    }

    /* Priority value */
    size += sizeof(RwInt32);

    /* num translations */
    numTranSeqs = frameAnim->numTranslateSequences;
    size += sizeof(RwInt32);

    /* num rotations */
    numRotateSeqs = frameAnim->numRotateSequences;
    size += sizeof(RwInt32);

    /* translations */
    animSeq = frameAnim->translateSequences;
    while (animSeq)
    {
        size += AnimSequenceSize(animSeq->sequence);

        animSeq = animSeq->next;
    }

    /* rotations */
    animSeq = frameAnim->rotateSequences;
    while (animSeq)
    {
        size += AnimSequenceSize(animSeq->sequence);

        animSeq = animSeq->next;
    }

    RWRETURN(size);
}

static RpAnimSequence *
AnimClumpAddSequenceCallback(RpAnimSequence * animSequence, void *data)
{
    RWFUNCTION(RWSTRING("AnimClumpAddSequenceCallback"));

    RpAnimClumpAddSequence((RpClump *) data,
                           RpAnimSequenceGetName(animSequence), 
                           (RpAnimClumpSequenceCallBack)NULL,
                           NULL);

    RWRETURN(animSequence);
}

static RwFrame     *
AnimClumpAddAllSequencesCallBack(RwFrame * frame, void *data)
{
    RWFUNCTION(RWSTRING("AnimClumpAddAllSequencesCallBack"));

    RpAnimFrameForAllSequences(frame, AnimClumpAddSequenceCallback,
                               data);

    RwFrameForAllChildren(frame, AnimClumpAddAllSequencesCallBack,
                          data);

    RWRETURN(frame);
}

static RpClump     *
AnimClumpForAllFramesAddSequences(RpClump * clump)
{
    RWFUNCTION(RWSTRING("AnimClumpForAllFramesAddSequences"));

    if (clump)
    {
        RwFrame            *clumpFrame = RpClumpGetFrame(clump);

        if (clumpFrame)
        {
            AnimClumpAddAllSequencesCallBack(clumpFrame,
                                             (void *) clump);
        }

        RWRETURN(clump);
    }

    RWRETURN((RpClump *) NULL);
}

static RwStream    *
AnimClumpReadStream(RwStream * stream,
                    RwInt32 __RWUNUSED__ binaryLength,
                    void *object,
                    RwInt32 __RWUNUSED__ offsetInObject,
                    RwInt32 __RWUNUSED__ sizeInObject)
{

    RwInt32             animation;

    RWFUNCTION(RWSTRING("AnimClumpReadStream"));

    RWASSERT(stream);
    RWASSERT(object);

    if (!RwStreamReadInt(stream, &animation, sizeof(RwInt32)))
    {
        RWRETURN((RwStream *) NULL);
    }
    if (animation)
    {
        AnimClumpForAllFramesAddSequences((RpClump *) object);
    }

    RWRETURN(stream);
}

static RwStream    *
AnimClumpWriteStream(RwStream * stream,
                     RwInt32 __RWUNUSED__ binaryLength,
                     const void *object,
                     RwInt32 __RWUNUSED__ offsetInObject,
                     RwInt32 __RWUNUSED__ sizeInObject)
{
    RwInt32             animation = FALSE;
    const rpAnimClump  *clumpAnim;

    RWFUNCTION(RWSTRING("AnimClumpWriteStream"));

    RWASSERT(stream);
    RWASSERT(object);

    clumpAnim = RWCLUMPGETCONSTANIM((const RpClump *) object);
    if (clumpAnim && clumpAnim->numAnimClumpSequences > 0)
    {
        animation = TRUE;
    }

    if (!RwStreamWriteInt(stream, &animation, sizeof(RwInt32)))
    {
        RWRETURN((RwStream *) NULL);
    }
    RWRETURN(stream);
}

static              RwInt32
AnimClumpSizeStream(const void *object,
                    RwInt32 __RWUNUSED__ offsetInObject,
                    RwInt32 __RWUNUSED__ sizeInObject)
{
    const rpAnimClump  *clumpAnim;

    RWFUNCTION(RWSTRING("AnimClumpSizeStream"));

    RWASSERT(object);

    clumpAnim = RWCLUMPGETCONSTANIM((const RpClump *) object);
    if (clumpAnim && clumpAnim->numAnimClumpSequences > 0)
    {
        RWRETURN(sizeof(RwInt32));
    }
    else
    {
        RWRETURN(-1);
    }
}

static rpAnimInterpInfo **
InterpInfoGetCycle(rpAnimInterpInfo * interpInfo, rpAnimInterpInfo **
                   interpInfoCycle, RwInt32 cycleID)
{
    rpAnimInterpInfo  **cycle = interpInfoCycle;

    RWFUNCTION(RWSTRING("InterpInfoGetCycle"));

    RWASSERT(interpInfo);
    RWASSERT(interpInfoCycle);

    while (interpInfo->visitedBy != cycleID)
    {
        interpInfo->visitedBy = cycleID;

        *cycle = interpInfo;
        cycle++;
        interpInfo = interpInfo->next;
    }

    /* mark the end of the cycle */
    *cycle = (rpAnimInterpInfo *) NULL;

    RWRETURN(interpInfoCycle);
}

static              RwBool
TranslateKeyCompare(RpTranslateKey * keyA, RpTranslateKey * keyB,
                    RwReal delta)
{
    RWFUNCTION(RWSTRING("TranslateKeyCompare"));

    RWASSERT(keyA);
    RWASSERT(keyB);

    if (RwRealAbs((keyA->x) - (keyB->x)) > delta)
    {
        RWRETURN(FALSE);
    }

    if (RwRealAbs((keyA->y) - (keyB->y)) > delta)
    {
        RWRETURN(FALSE);
    }

    if (RwRealAbs((keyA->z) - (keyB->z)) > delta)
    {
        RWRETURN(FALSE);
    }

    RWRETURN(TRUE);
}

static              RwBool
InterpInfoPosSpanTest(rpAnimInterpInfo ** interpSpanStart, RwInt32
                      interval, RpTranslateKey * keys, RwReal delta)
{

    RwInt32             keyIndex, i;
    RpTranslateKey      blendKey, *startKey, *endKey;
    RwReal              along;

    RWFUNCTION(RWSTRING("InterpInfoPosSpanTest"));

    RWASSERT(interpSpanStart);
    RWASSERT(keys);

    keyIndex = interpSpanStart[0]->interp->startKeyFrame;
    startKey = &keys[keyIndex];

    keyIndex = interpSpanStart[interval]->interp->endKeyFrame;
    endKey = &keys[keyIndex];

    along = (RwReal) (((RwReal) 0));

    for (i = 0; i < interval; i++)
    {
        RpTranslateKey     *realKey;

        /* get the blend key */
        along += interpSpanStart[i]->interp->time;
        TranslateKeyBlend(&blendKey, startKey, endKey, along);

        /* get the real key */
        keyIndex = interpSpanStart[i]->interp->endKeyFrame;
        realKey = &keys[keyIndex];

        /* compare real and blend */
        if (!TranslateKeyCompare(realKey, &blendKey, delta))
        {
            RWRETURN(FALSE);
        }
    }

    RWRETURN(TRUE);
}

static              RwBool
RotationKeyCompare(RpRotateKey * keyA, RpRotateKey * keyB, RwReal delta)
{
    RwReal              diff;

    RWFUNCTION(RWSTRING("RotationKeyCompare"));

    RWASSERT(keyA);
    RWASSERT(keyB);

    diff = keyA->imag.x - keyB->imag.x;
    if (RwRealAbs(diff) > delta)
    {
        RWRETURN(FALSE);
    }

    diff = keyA->imag.y - keyB->imag.y;
    if (RwRealAbs(diff) > delta)
    {
        RWRETURN(FALSE);
    }

    diff = keyA->imag.z - keyB->imag.z;
    if (RwRealAbs(diff) > delta)
    {
        RWRETURN(FALSE);
    }

    diff = keyA->real - keyB->real;
    if (RwRealAbs(diff) > delta)
    {
        RWRETURN(FALSE);
    }

    RWRETURN(TRUE);
}

static              RwBool
InterpInfoOrientationSpanTest(rpAnimInterpInfo ** interpSpanStart,
                              RwInt32 interval, RpRotateKey * keys,
                              RwReal delta)
{

    RwMatrix           *rotate = RwMatrixCreate();
    RwBool              useSlerp;
    RwInt32             keyIndex, i;
    RpRotateKey         blendKey, *startKey, *endKey;
    RwReal              along;
    RtQSlerpCache       quatSlerpCache;

    RWFUNCTION(RWSTRING("InterpInfoOrientationSpanTest"));

    RWASSERT(interpSpanStart);
    RWASSERT(keys);

    keyIndex = interpSpanStart[0]->interp->startKeyFrame;
    startKey = &keys[keyIndex];

    keyIndex = interpSpanStart[interval]->interp->endKeyFrame;
    endKey = &keys[keyIndex];

    /* setup the slerp */

    /*
     * this horrible bit of code is to get around a
     * bug in the slerp code
     */

    useSlerp = ((startKey->imag.x != endKey->imag.x) ||
                (startKey->imag.y != endKey->imag.y) ||
                (startKey->imag.z != endKey->imag.z) |
                (startKey->real != endKey->real));

    if (useSlerp)
    {
        RtQSetupSlerpCache(startKey, endKey, &quatSlerpCache);
    }
    else
    {
        blendKey = *startKey;
    }

    along = (RwReal) (((RwReal) 0));

    for (i = 0; i < interval; i++)
    {
        RpRotateKey        *realKey;

        /* get the blend key */
        along += interpSpanStart[i]->interp->time;
        if (useSlerp)
        {
            /* this is extremly inefficient and needs to be optimized */
            RtQSlerp(&blendKey, startKey, endKey, along,
                     &quatSlerpCache);
        }

        /* get the real key */
        keyIndex = interpSpanStart[i]->interp->endKeyFrame;
        realKey = &keys[keyIndex];

        /* compare real and blend */
        if (!RotationKeyCompare(realKey, &blendKey, delta))
        {
            RwMatrixDestroy(rotate);
            RWRETURN(FALSE);
        }
    }

    RwMatrixDestroy(rotate);
    RWRETURN(TRUE);
}

static              RwInt32
InterpInfoFindSpan(rpAnimInterpInfo ** interpSpanStart, RwInt32 posA,
                   RwInt32 posB,
                   void *keys, RwReal delta,
                   rpAnimInterpInfoSpanTestCallBack spanCB)
{
    RwInt32             mid;

    RWFUNCTION(RWSTRING("InterpInfoFindSpan"));

    RWASSERT(interpSpanStart);

    /* takes nlog(n) time */

    /* have we homed in on the soloution */
    if (posA == posB)
    {
        RWRETURN(posA);
    }

    if ((posB - posA) == 1)
    {
        if (spanCB(interpSpanStart, posB, keys, delta))
        {
            RWRETURN(posB);
        }
        else
        {
            RWRETURN(posA);
        }
    }

    /* find the mid point */
    mid = posA + ((posB - posA) / 2);

    /* is the soloution in the upper bound */
    if (spanCB(interpSpanStart, mid, keys, delta))
    {
        RWRETURN(InterpInfoFindSpan(interpSpanStart, mid, posB,
                                    keys, delta, spanCB));
    }

    /* else must be in the lower bound */
    RWRETURN(InterpInfoFindSpan(interpSpanStart, posA, mid, keys,
                                delta, spanCB));
}

static rpAnimInterpolator *
InterpInfoCreateNewInterpolator(rpAnimInterpInfo ** interpInfoSpan,
                                RwSList * interpList, RwInt32 span)
{
    rpAnimInterpolator *interp;
    rpAnimInterpolator *first, *last;
    RwInt32             i;
    RwReal              time = (RwReal) (((RwReal) 0));

    RWFUNCTION(RWSTRING("InterpInfoCreateNewInterpolator"));

    RWASSERT(interpInfoSpan);
    RWASSERT(interpList);

    interp = (rpAnimInterpolator *) rwSListGetNewEntry(interpList);
    if (!interp)
    {
        RWRETURN((rpAnimInterpolator *) NULL);
    }

    first = interpInfoSpan[0]->interp;
    last = interpInfoSpan[span]->interp;

    interp->startKeyFrame = first->startKeyFrame;
    interp->endKeyFrame = last->endKeyFrame;

    for (i = 0; i <= span; i++)
    {
        time += interpInfoSpan[i]->interp->time;
    }

    interp->time = time;

    RWRETURN(interp);
}

static rpAnimState *
AnimStateOptimizeCycle(rpAnimState * animState, rpAnimInterpInfo **
                       interpInfoSpan, void *keys,
                       RwReal delta,
                       rpAnimInterpInfoSpanTestCallBack spanCB)
{
    RwSList            *interpList;
    RwInt32             numNewInterps;
    RwInt32             start, end, interval, along;
    RwInt32             maxSpan = 0;
    rpAnimInterpolator *newInterps;

    RWFUNCTION(RWSTRING("AnimStateOptimizeCycle"));

    RWASSERT(animState);
    RWASSERT(interpInfoSpan);
    RWASSERT(keys);

    interpList = rwSListCreate(sizeof(rpAnimInterpolator));
    if (!interpList)
    {
        RWRETURN((rpAnimState *) NULL);
    }

    start = end = interval = along = 0;
    while (interpInfoSpan[interval])
    {
        interval++;
    }

    end = interval - 1;

    while (along < interval)
    {
        RwInt32             newSpan;

        newSpan = InterpInfoFindSpan(&interpInfoSpan[along], 0,
                                     end - along, keys, delta, spanCB);

        if (!InterpInfoCreateNewInterpolator
            (&interpInfoSpan[along], interpList, newSpan))
        {
            rwSListDestroy(interpList);

            RWRETURN((rpAnimState *) NULL);
        }

        if (newSpan > maxSpan)
        {
            maxSpan = newSpan;
        }

        along += newSpan + 1;
    }

    numNewInterps = rwSListGetNumEntries(interpList);
    {
        rpAnimInterpolator *tempInterps;
        RwInt32             size;

        tempInterps =
            (rpAnimInterpolator *) rwSListGetArray(interpList);
        size = sizeof(rpAnimInterpolator) * numNewInterps;

        newInterps = (rpAnimInterpolator *) RwMalloc(size);
        if (!newInterps)
        {
            RWRETURN((rpAnimState *) NULL);
        }
        memcpy(newInterps, tempInterps, size);
        rwSListDestroy(interpList);
    }

    RwFree(animState->sequence->interps);
    animState->sequence->interps = newInterps;
    animState->currentInterp = newInterps;
    animState->sequence->numInterpolators = numNewInterps;
    animState->position = (RwReal) (((RwReal) 0));

    RWRETURN(animState);
}

static rpAnimState *
AnimStateOptimize(rpAnimState * animState, void *keys,
                  RwReal delta, rpAnimInterpInfoSpanTestCallBack spanCB)
{

    rpAnimInterpInfo   *interpInfoArray;
    rpAnimInterpInfo  **interpInfoCycle;
    rpAnimInterpolator *interpolators;
    RwInt32             i, numInterpolators;

    RWFUNCTION(RWSTRING("AnimStateOptimize"));

    RWASSERT(animState);
    RWASSERT(keys);

    numInterpolators = animState->sequence->numInterpolators;
    interpInfoArray = (rpAnimInterpInfo *)
        RwMalloc(sizeof(rpAnimInterpInfo) * numInterpolators);
    if (!interpInfoArray)
    {
        RWRETURN((rpAnimState *) NULL);
    }

    interpInfoCycle = (rpAnimInterpInfo **)
        RwMalloc(sizeof(rpAnimInterpInfo *) * (numInterpolators + 1));

    if (!interpInfoCycle)
    {
        RwFree(interpInfoArray);

        RWRETURN((rpAnimState *) NULL);
    }

    interpolators = animState->sequence->interps;

    /* setup the interpInfoArray */
    for (i = 0; i < numInterpolators; i++)
    {
        interpInfoArray[i].newIndex = -1;
        interpInfoArray[i].refCntr = 0;
        interpInfoArray[i].optimized = FALSE;
        interpInfoArray[i].loopStart = FALSE;
        interpInfoArray[i].cut = FALSE;
        interpInfoArray[i].visitedBy = -1;
        interpInfoArray[i].interp = &interpolators[i];
        interpInfoArray[i].next = (rpAnimInterpInfo *) NULL;
    }

    /* compute the ref counts */
    for (i = 0; i < numInterpolators; i++)
    {
        rpAnimInterpolator *currentInterp, *nextInterp;

        currentInterp = interpInfoArray[i].interp;
        if (i == numInterpolators - 1)
        {
            nextInterp = interpInfoArray[0].interp;
        }
        else
        {
            nextInterp = interpInfoArray[i + 1].interp;
        }

        if (nextInterp)
        {
            RwInt32             interpInd;

            interpInd = nextInterp - interpolators;

            interpInfoArray[interpInd].refCntr++;

            interpInfoArray[i].next = &interpInfoArray[interpInd];

            if (currentInterp->endKeyFrame != nextInterp->startKeyFrame)
            {
                interpInfoArray[interpInd].cut = TRUE;
            }
        }
    }

    /* get each animation cycle and optimize it */
    /* for now assume the animation forms one big loop */
    /* TODO: update to cope with multiple animation cycles */

    /* check we have a single loop */
    for (i = 0; i < numInterpolators; i++)
    {
        if (interpInfoArray[i].refCntr != 1)
        {
            RwFree(interpInfoArray);
            RwFree(interpInfoCycle);

            RWRETURN((rpAnimState *) NULL);
        }
    }

#if 0
    /* find any run-in's */
    for (i = 0; i < numInterpolators; i++)
    {
        if ((interpInfoArray[i].visitedBy == -1) &&
            (interpInfoArray[i].refCntr == 0))
        {
            InterpInfoGetCycle(&interpInfoCycle[i], i,
                               numInterpolators);
        }
    }
#endif /* 0 */

    InterpInfoGetCycle(interpInfoArray, interpInfoCycle, 1);

    AnimStateOptimizeCycle(animState, interpInfoCycle, keys,
                           delta, spanCB);

    RwFree(interpInfoArray);
    RwFree(interpInfoCycle);

    RWRETURN(animState);
}

static rpAnimKeyInfo *
KeyInfoArrayCreate(rpAnimState * animState, RwInt32 numKeys,
                   RwInt32 * numNewKeys)
{

    rpAnimInterpolator *interpolators;
    rpAnimKeyInfo      *keyInfo;
    RwInt32             i, numInterpolators;
    RwInt16             newKeyIndex;

    RWFUNCTION(RWSTRING("KeyInfoArrayCreate"));

    RWASSERT(animState);
    RWASSERT(numNewKeys);

    /* create the keyInfo array */
    keyInfo =
        (rpAnimKeyInfo *) RwMalloc(sizeof(rpAnimKeyInfo) * numKeys);

    if (!keyInfo)
    {
        RWRETURN((rpAnimKeyInfo *) NULL);
    }

    /* intialize te keyInfo array */
    for (i = 0; i < numKeys; i++)
    {
        keyInfo[i].used = FALSE;
        keyInfo[i].newIndex = -1;
    }

    /* find out which keys are used */
    numInterpolators = animState->sequence->numInterpolators;
    interpolators = animState->sequence->interps;
    for (i = 0; i < numInterpolators; i++)
    {
        RwInt32             startKey, endKey;

        startKey = interpolators[i].startKeyFrame;
        endKey = interpolators[i].endKeyFrame;

        keyInfo[startKey].used = TRUE;
        keyInfo[endKey].used = TRUE;
    }

    /* reindex the used keys */
    newKeyIndex = 0;
    for (i = 0; i < numKeys; i++)
    {
        if (keyInfo[i].used)
        {
            keyInfo[i].newIndex = newKeyIndex;
            newKeyIndex++;
        }
    }

    *numNewKeys = newKeyIndex;

    RWRETURN(keyInfo);
}

static              RwBool
KeyInfoArrayDestroy(rpAnimKeyInfo * keyInfo)
{
    RWFUNCTION(RWSTRING("KeyInfoArrayDestroy"));

    RWASSERT(keyInfo);

    RwFree(keyInfo);

    RWRETURN(TRUE);
}

static rpAnimInterpolator *
AnimInterpolatorsRemap(rpAnimInterpolator * interps, RwInt32
                       numInterpolators, rpAnimKeyInfo * keyInfo)
{
    RwInt32             i;
    rpAnimInterpolator *interpolators;

    RWFUNCTION(RWSTRING("AnimInterpolatorsRemap"));

    RWASSERT(interps);
    RWASSERT(keyInfo);

    interpolators = interps;

    /* remap the interpolators */
    for (i = 0; i < numInterpolators; i++)
    {
        RwInt32             startKey, endKey;
        RwInt32             newStartKey, newEndKey;

        startKey = interps->startKeyFrame;
        endKey = interps->endKeyFrame;

        newStartKey = keyInfo[startKey].newIndex;
        newEndKey = keyInfo[endKey].newIndex;

        interps->startKeyFrame = newStartKey;
        interps->endKeyFrame = newEndKey;

        interps++;
    }

    RWRETURN(interpolators);
}

static RpAnimSequence *
AnimTranslateOptimizeKeys(RpAnimSequence * animTrans,
                          rpAnimState * animState)
{

    RwInt32             numKeys, numNewKeys;
    rpAnimKeyInfo      *keyInfo;

    RWFUNCTION(RWSTRING("AnimTranslateOptimizeKeys"));

    RWASSERT(animTrans);

    numKeys = RpAnimSequenceGetNumKeys(animTrans);

    keyInfo = KeyInfoArrayCreate(animState, numKeys, &numNewKeys);
    if (!keyInfo)
    {
        RWRETURN((RpAnimSequence *) NULL);
    }

    /* create new key array */
    if (numNewKeys < numKeys)
    {
        rpAnimInterpolator *interpolators;
        RwInt32             i, numInterpolators;
        RpTranslateKey     *keys, *newKeys;

        numInterpolators = animTrans->numInterpolators;
        interpolators = animTrans->interps;

        /* create the new keys */
        keys = (RpTranslateKey *) (animTrans->keys);
        newKeys = (RpTranslateKey *)
            RwMalloc(sizeof(RpTranslateKey) * numNewKeys);

        if (!newKeys)
        {
            KeyInfoArrayDestroy(keyInfo);

            RWRETURN((RpAnimSequence *) NULL);
        }

        for (i = 0; i < numKeys; i++)
        {
            if (keyInfo[i].used)
            {
                RwInt16             newKeyIndex;

                newKeyIndex = keyInfo[i].newIndex;
                newKeys[newKeyIndex] = keys[i];
            }
        }

        /* remap the interpolators */
        AnimInterpolatorsRemap(interpolators, numInterpolators,
                               keyInfo);

        /* assign the new keys */
        animTrans->keys = newKeys;
        animTrans->numKeys = numNewKeys;

        /* free the old keys */
        RwFree(keys);
    }

    KeyInfoArrayDestroy(keyInfo);

    RWRETURN(animTrans);
}

static RpAnimSequence *
AnimRotateOptimizeKeys(RpAnimSequence * animRotate,
                       rpAnimState * animState)
{
    RwInt32             numKeys, numNewKeys;
    rpAnimKeyInfo      *keyInfo;

    RWFUNCTION(RWSTRING("AnimRotateOptimizeKeys"));

    RWASSERT(animRotate);

    numKeys = RpAnimSequenceGetNumKeys(animRotate);

    keyInfo = KeyInfoArrayCreate(animState, numKeys, &numNewKeys);
    if (!keyInfo)
    {
        RWRETURN((RpAnimSequence *) NULL);
    }

    /* create new key array */
    if (numNewKeys < numKeys)
    {
        rpAnimInterpolator *interpolators;
        RwInt32             i, numInterpolators;
        RpRotateKey        *keys, *newKeys;

        numInterpolators = animRotate->numInterpolators;
        interpolators = animRotate->interps;

        /* create the new keys */
        keys = (RpRotateKey *) (animRotate->keys);
        newKeys =
            (RpRotateKey *) RwMalloc(sizeof(RpRotateKey) * numNewKeys);

        if (!newKeys)
        {
            KeyInfoArrayDestroy(keyInfo);

            RWRETURN((RpAnimSequence *) NULL);
        }

        for (i = 0; i < numKeys; i++)
        {
            if (keyInfo[i].used)
            {
                RwInt16             newKeyIndex;

                newKeyIndex = keyInfo[i].newIndex;
                newKeys[newKeyIndex] = keys[i];
            }
        }

        /* remap the interpolators */
        AnimInterpolatorsRemap(interpolators, numInterpolators,
                               keyInfo);

        /* assign the new keys */
        animRotate->keys = newKeys;
        animRotate->numKeys = numNewKeys;

        /* free the old keys */
        RwFree(keys);
    }

    KeyInfoArrayDestroy(keyInfo);

    RWRETURN(animRotate);
}

static RwFrame     *
FrameAnimOptimize(RwFrame * frame, rpAnimFrameState * frameState,
                  RwChar * name, RwReal delta)
{
    RpAnimSequence     *animTrans = (RpAnimSequence *) NULL;
    RpAnimSequence     *animRotate = (RpAnimSequence *) NULL;

    RWFUNCTION(RWSTRING("FrameAnimOptimize"));

    RWASSERT(AnimStatic.animModule.numInstances);
    RWASSERT(frame);

    /* translation */
    animTrans = RpAnimFrameGetTranslationSequence(frame, name);
    if (animTrans)
    {
        rpAnimState        *animState;
        RpTranslateKey     *keys;
        rpAnimInterpInfoSpanTestCallBack spanCB;

        animState = frameState->translateState;
        keys = (RpTranslateKey *) (animTrans->keys);
        spanCB =
            (rpAnimInterpInfoSpanTestCallBack) InterpInfoPosSpanTest;
        AnimStateOptimize(animState, (void *) keys, delta, spanCB);
        AnimTranslateOptimizeKeys(animTrans, animState);
    }

    /* rotation */
    animRotate = RpAnimFrameGetRotationSequence(frame, name);
    if (animRotate)
    {
        rpAnimState        *animState;
        RpRotateKey        *keys;
        rpAnimInterpInfoSpanTestCallBack spanCB;

        animState = frameState->rotateState;
        keys = (RpRotateKey *) (animRotate->keys);
        spanCB =
            (rpAnimInterpInfoSpanTestCallBack)
            InterpInfoOrientationSpanTest;
        AnimStateOptimize(animState, (void *) keys, delta, spanCB);
        AnimRotateOptimizeKeys(animRotate, animState);
    }

    RWRETURN(frame);
}

static RpAnimClumpSequence *
AnimClumpFindSeqCallBack(RpAnimClumpSequence * animClumpSequence,
                         RwBool __RWUNUSED__ active, void *data)
{
    rpAnimClumpFindStruct *findStruct;

    RWFUNCTION(RWSTRING("AnimClumpFindSeqCallBack"));

    RWASSERT(animClumpSequence);
    RWASSERT(data);

    findStruct = (rpAnimClumpFindStruct *) data;
    if (rwstrcmp
        (RpAnimClumpSequenceGetName(animClumpSequence),
         findStruct->name) == 0)
    {
        findStruct->seq = animClumpSequence;
        RWRETURN((RpAnimClumpSequence *) NULL);
    }

    RWRETURN(animClumpSequence);
}

static RwFrame     *
AnimCountFramesWithAnimCallback(RwFrame * frame, void *pData)
{
    rpAnimCountSequenceStruct *info =
        (rpAnimCountSequenceStruct *) pData;
    rpAnimFrame        *frameAnim;

    RWFUNCTION(RWSTRING("AnimCountFramesWithAnimCallback"));

    RWASSERT(frame);

    {
        frameAnim = RWFRAMEGETANIM(frame);
        if (frameAnim)
        {
            rpAnimSequenceList *curSequence;

            /* Rotation sequences */
            curSequence = frameAnim->rotateSequences;
            while (curSequence)
            {
                if (strcmp(curSequence->sequence->name, info->name) ==
                    0)
                {
                    info->count++;
                    break;
                }

                curSequence = curSequence->next;
            }

            /* Translation sequences */
            curSequence = frameAnim->translateSequences;
            while (curSequence)
            {
                if (strcmp(curSequence->sequence->name, info->name) ==
                    0)
                {
                    info->count++;
                    break;
                }

                curSequence = curSequence->next;
            }
        }

        RwFrameForAllChildren(frame, AnimCountFramesWithAnimCallback,
                              pData);
        RWRETURN(frame);
    }

    RWRETURN((RwFrame *) NULL);
}

static              RwInt32
AnimFrameCountAnimSequencesByName(RwFrame * rootFrame, RwChar * name)
{
    rpAnimCountSequenceStruct info;

    RWFUNCTION(RWSTRING("AnimFrameCountAnimSequencesByName"));

    info.count = 0;
    info.name = name;

    AnimCountFramesWithAnimCallback(rootFrame, &info);
    RWRETURN(info.count);
}

static RwFrame     *
AnimAddSequencesFromFrames(RwFrame * frame, void *pData)
{
    rpAnimAddSequenceToDBStruct *addSeq =
        (rpAnimAddSequenceToDBStruct *) pData;
    rpAnimFrame        *frameAnim;

    RWFUNCTION(RWSTRING("AnimAddSequencesFromFrames"));

    RWASSERT(frame);
    RWASSERT(addSeq);

    {
        frameAnim = RWFRAMEGETANIM(frame);
        if (frameAnim)
        {
            rpAnimSequenceList *curSequence;

            /* Rotation sequences */
            curSequence = frameAnim->rotateSequences;
            while (curSequence)
            {
                if (strcmp(curSequence->sequence->name, addSeq->name) ==
                    0)
                {
                    addSeq->dbEntry->animSequences[addSeq->
                                                   currentIndex] =
                        curSequence->sequence;
                    addSeq->dbEntry->tagArray[addSeq->currentIndex] =
                        _rpAnimSeqGetFrameTag(frame);
                    curSequence->sequence->refCnt++;
                    addSeq->currentIndex++;
                    break;
                }

                curSequence = curSequence->next;
            }

            /* Translation sequences */
            curSequence = frameAnim->translateSequences;
            while (curSequence)
            {
                if (strcmp(curSequence->sequence->name, addSeq->name) ==
                    0)
                {
                    addSeq->dbEntry->animSequences[addSeq->
                                                   currentIndex] =
                        curSequence->sequence;
                    addSeq->dbEntry->tagArray[addSeq->currentIndex] =
                        _rpAnimSeqGetFrameTag(frame);
                    curSequence->sequence->refCnt++;
                    addSeq->currentIndex++;
                    break;
                }

                curSequence = curSequence->next;
            }
        }

        RwFrameForAllChildren(frame, AnimAddSequencesFromFrames, pData);

        RWRETURN(frame);
    }

    RWRETURN((RwFrame *) NULL);
}

static RwFrame     *
AnimFrameBuildSequenceNameList(RwFrame * frame, void *data)
{
    rpAnimNameList     *nameList = (rpAnimNameList *) data;

    RWFUNCTION(RWSTRING("AnimFrameBuildSequenceNameList"));

    RWASSERT(frame);
    RWASSERT(nameList);

    {
        rpAnimFrame        *frameAnim;

        frameAnim = RWFRAMEGETANIM(frame);
        if (frameAnim)
        {
            rpAnimSequenceList *curSequence;
            rpAnimNameList     *nameList_next;

            /* Rotation sequences */
            curSequence = frameAnim->rotateSequences;
            while (curSequence)
            {
                RwBool              found = FALSE;

                nameList_next = nameList->next;
                while (nameList_next && !found)
                {
                    if (strcmp(nameList_next->name,
                               curSequence->sequence->name) == 0)
                    {
                        found = TRUE;
                    }
                    nameList_next = nameList_next->next;
                }

                if (!found)
                {
                    rpAnimNameList     *newEntry;

                    newEntry = (rpAnimNameList *)
                        RwFreeListAlloc(AnimStatic.NameListFreeList);

                    if (newEntry)
                    {
                        newEntry->next = nameList->next;
                        newEntry->name = curSequence->sequence->name;
                        nameList->next = newEntry;
                    }
                    else
                    {
                        RWERROR((E_RW_NOMEM, sizeof(rpAnimNameList)));
                    }
                }

                curSequence = curSequence->next;
            }

            /* Translation sequences */
            curSequence = frameAnim->translateSequences;
            while (curSequence)
            {
                RwBool              found = FALSE;

                nameList_next = nameList->next;
                while (nameList_next && !found)
                {
                    if (strcmp(nameList_next->name,
                               curSequence->sequence->name) == 0)
                    {
                        found = TRUE;
                    }
                    nameList_next = nameList_next->next;
                }

                if (!found)
                {
                    rpAnimNameList     *newEntry;

                    newEntry = (rpAnimNameList *)
                        RwFreeListAlloc(AnimStatic.NameListFreeList);

                    if (newEntry)
                    {
                        newEntry->next = nameList->next;
                        newEntry->name = curSequence->sequence->name;
                        nameList->next = newEntry;
                    }
                    else
                    {
                        RWERROR((E_RW_NOMEM, sizeof(rpAnimNameList)));
                    }
                }

                curSequence = curSequence->next;
            }
        }

        RwFrameForAllChildren(frame, AnimFrameBuildSequenceNameList,
                              data);

        RWRETURN(frame);
    }

    RWRETURN((RwFrame *) NULL);
}

static RwFrame     *
_findTaggedFrame(RwFrame * frame, void *data)
{
    rpAnimTagData      *tagData = (rpAnimTagData *) data;

    RWFUNCTION(RWSTRING("_findTaggedFrame"));

    RWASSERT(frame);
    RWASSERT(tagData);

    {

        if (_rpAnimSeqGetFrameTag(frame) == tagData->tag)
        {
            tagData->frame = frame;
            RWRETURN((RwFrame *) NULL);
        }

        RwFrameForAllChildren(frame, _findTaggedFrame, data);

        RWRETURN(frame);
    }

    RWRETURN((RwFrame *) NULL);
}

static RwFrame     *
AnimDatabaseCopyDBEntryToFrameHierarchy(RwFrame * rootFrame,
                                        void *pData)
{
    RpAnimDatabaseEntry *dbEntry = (RpAnimDatabaseEntry *) pData;
    RwInt32             i;
    RwInt32             tag;

    RWFUNCTION(RWSTRING("AnimDatabaseCopyDBEntryToFrameHierarchy"));

    RWASSERT(rootFrame);
    RWASSERT(dbEntry);

    {

        tag = _rpAnimSeqGetFrameTag(rootFrame);
        if (tag != DEFAULTTAGID)
        {
            for (i = 0; i < dbEntry->numAnimSequences; i++)
            {
                if (tag == dbEntry->tagArray[i])
                {
                    RpAnimFrameAddSequence(rootFrame,
                                           dbEntry->animSequences[i]);
                }
            }
        }

        RwFrameForAllChildren(rootFrame,
                              AnimDatabaseCopyDBEntryToFrameHierarchy,
                              pData);

        RWRETURN(rootFrame);
    }

    RWRETURN((RwFrame *) NULL);
}

static void
AnimDatabaseCopyEntry(RpAnimDatabase * database,
                      RpAnimDatabaseEntry * entry)
{
    RpAnimDatabaseEntry *newEntry;

    RWFUNCTION(RWSTRING("AnimDatabaseCopyEntry"));

    RWASSERT(database);
    RWASSERT(entry);

    {
        newEntry = _rpAnimDatabaseEntryCreate(entry->numAnimSequences);
        if (newEntry)
        {
            RwInt32             i;

            rwstrdup(newEntry->name, entry->name);
            newEntry->next = database->entries;
            database->entries = newEntry;
            database->numEntries++;
            for (i = 0; i < newEntry->numAnimSequences; i++)
            {
                newEntry->animSequences[i] = entry->animSequences[i];
                newEntry->animSequences[i]->refCnt++;
                newEntry->tagArray[i] = entry->tagArray[i];
            }
            RWRETURNVOID();
        }
        else
        {
            RWRETURNVOID();
        }
    }
    RWRETURNVOID();
}

/*
 * RWAPIFUNCTION()s
 */

/**
 * \ingroup rpanim
 * \ref RpAnimClumpSequenceOptimize is used to try and optimize
 * the named sequence. It achieves this by determining which keyframes can 
 * be eliminated. A key is determined to be unnecessary if the difference be it 
 * and its neighbour is less than the error threshold delta.
 *
 * \param clump  pointer to the clump.
 * \param name  pointer to a string containing the name of the animation sequence.
 * \param delta  error threshold.
 *
 * \return a pointer to the clump if successful, or NULL if there is an error.
 *
 * \see RpAnimClumpRemoveSequence
 * \see RpAnimClumpStartSequence
 * \see RpAnimClumpStopSequence
 * \see RpAnimClumpSequenceGetName
 * \see RpAnimClumpAddTime
 * \see RpAnimClumpForAllSequences
 * \see RpAnimPluginAttach
 */
RpClump            *
RpAnimClumpSequenceOptimize(RpClump * clump, RwChar * name,
                            RwReal delta)
{
    rpAnimFrameList    *frameList;
    RpAnimClumpSequence *seq;
    rpAnimClumpFindStruct findStruct;

    RWAPIFUNCTION(RWSTRING("RpAnimClumpSequenceOptimize"));

    RWASSERT(AnimStatic.animModule.numInstances);
    RWASSERT(clump);
    RWASSERT(name);

    findStruct.seq = (RpAnimClumpSequence *) NULL;
    findStruct.name = name;
    RpAnimClumpForAllSequences(clump,
                               AnimClumpFindSeqCallBack, &findStruct);

    if (findStruct.seq)
    {

        seq = findStruct.seq;

        frameList = seq->frameList;

        while (frameList)
        {
            FrameAnimOptimize(frameList->frame,
                              &frameList->animFrameState, name, delta);
            frameList = frameList->next;
        }
    }
    else
    {
        clump = (RpClump *) NULL;
    }

    RWRETURN(clump);
}

/**
 * \ingroup rpanim
 * \ref RpAnimClumpAddSequence is used to register the specified
 * animation sequence with the given clump. The specified callback
 * function is executed when the animation
 * time exceeds its duration and may be used, for example,
 * to setup the next animated sequence, if required.
 * If NULL is specified for the callback, a default function is
 * called which simply plays the sequence again. Note that a
 * registered sequence is made active by the API function
 * \ref RpAnimClumpStartSequence. When the callback is executed,
 * however, the given animated sequence will have been stopped
 * and made inactive.
 * The animation plug-in must be attached before using this function.
 * 
 * The format of the callback is 
 * \verbatim
   RpClump *
   (*RpAnimClumpSequenceCallBack)(RpClump *clump,
                                  RpAnimClumpSequence *sequence,
                                  RwReal pos,
                                   void *data);
  \endverbatim
 * where pos is the amount the animation has over-run its duration 
 * and data is a user-supplied data pointer. 
 *
 * \param clump  pointer to the clump.
 * \param sequenceName  pointer to a string containing the name of the sequence.
 * \param fpAnimClumpSeqCB  pointer to the callback function.
 * \param sequenceCallBackData  pointer to user-supplied data to pass to callback function.
 * \return a pointer to the clump if successful, or NULL if there is an error.
 *
 * \see RpAnimClumpRemoveSequence
 * \see RpAnimClumpStartSequence
 * \see RpAnimClumpStopSequence
 * \see RpAnimClumpSequenceGetName
 * \see RpAnimClumpAddTime
 * \see RpAnimClumpForAllSequences
 * \see RpAnimPluginAttach
 */
RpClump            *
RpAnimClumpAddSequence(RpClump * clump,
                       const RwChar * sequenceName,
                       RpAnimClumpSequenceCallBack fpAnimClumpSeqCB,
                       void *sequenceCallBackData)
{

    rpAnimClump        *clumpAnim;
    RwFrame            *rootFrame;
    rpAnimFrameSeqInfo  frameAnimSeqInfo;
    RpAnimClumpSequence *animClumpSeq;

    RWAPIFUNCTION(RWSTRING("RpAnimClumpAddSequence"));

    RWASSERT(AnimStatic.animModule.numInstances);
    RWASSERT(clump);
    RWASSERT(sequenceName);

    clumpAnim = RWCLUMPGETANIM(clump);

    /* Check a sequence by the same name has not
     * been already added to the clump */
    animClumpSeq = clumpAnim->activeClumpSequenceList;
    while (animClumpSeq)
    {
        if (rwstrcmp(animClumpSeq->name, sequenceName) == 0)
        {
            /* Should return something a little more informative than this */
            RWRETURN((RpClump *) NULL);
        }

        animClumpSeq = animClumpSeq->next;
    }

    animClumpSeq = clumpAnim->clumpSequenceList;
    while (animClumpSeq)
    {
        if (rwstrcmp(animClumpSeq->name, sequenceName) == 0)
        {
            /* Should return something a little more informative than this */
            RWRETURN((RpClump *) NULL);
        }

        animClumpSeq = animClumpSeq->next;
    }

    rootFrame = RpClumpGetFrame(clump);
    if (!rootFrame)
    {
        RWRETURN((RpClump *) NULL);
    }

    /* set up the clump frame list: cheesy but nowhere else to put it */
    if (!clumpAnim->frameList.frames)
    {
        if (!rwFrameListInitialize(&clumpAnim->frameList, rootFrame))
        {
            RWRETURN((RpClump *) NULL);
        }
    }

    frameAnimSeqInfo.frameAnimList = (rpAnimFrameList *) NULL;
    frameAnimSeqInfo.seqName = sequenceName;
    AnimSetClumpFrameList(rootFrame, &frameAnimSeqInfo);

    /* did we find the sequence? */
    if (frameAnimSeqInfo.frameAnimList)
    {
        animClumpSeq = AnimClumpSequenceCreate(sequenceName);
        if (!animClumpSeq)
        {
            AnimFrameListDestroy(frameAnimSeqInfo.frameAnimList);

            RWRETURN((RpClump *) NULL);
        }

        /* set the frame list */
        animClumpSeq->frameList = frameAnimSeqInfo.frameAnimList;

        /* set the call back */
        if (fpAnimClumpSeqCB)
        {
            animClumpSeq->fpAnimClumpSeqCB = fpAnimClumpSeqCB;
        }
        else
        {
            animClumpSeq->fpAnimClumpSeqCB =
                AnimClumpSequenceDefaultCallBack;
        }
        animClumpSeq->callBackData = sequenceCallBackData;

        /* patch it in to the list */
        clumpAnim->numAnimClumpSequences++;
        animClumpSeq->next = clumpAnim->clumpSequenceList->next;
        clumpAnim->clumpSequenceList->next = animClumpSeq;

        /* set the duration */
        animClumpSeq->duration =
            AnimFrameListGetSequenceDuration
            (frameAnimSeqInfo.frameAnimList, sequenceName);

        RWRETURN(clump);
    }

    RWRETURN((RpClump *) NULL);
}

/**
 * \ingroup rpanim
 * \ref RpAnimClumpRemoveSequence is used to un-register the
 * specified animation  sequence with the given clump.
 * The animation plug-in must be attached before using this function.
 *
 * \param clump  pointer to the clump.
 * \param sequenceName  pointer to a string containing the name of the sequence.
 *
 * \return pointer to the clump if successful or NULL if there is an error.
 *
 * \see RpAnimClumpAddSequence
 * \see RpAnimClumpStartSequence
 * \see RpAnimClumpStopSequence
 * \see RpAnimClumpSequenceGetName
 * \see RpAnimClumpAddTime
 * \see RpAnimClumpForAllSequences
 * \see RpAnimPluginAttach
 */
RpClump            *
RpAnimClumpRemoveSequence(RpClump * clump, RwChar * sequenceName)
{
    rpAnimClump        *clumpAnim;
    RpAnimClumpSequence *animClumpSequence;

    RWAPIFUNCTION(RWSTRING("RpAnimClumpRemoveSequence"));

    RWASSERT(AnimStatic.animModule.numInstances);
    RWASSERT(clump);
    RWASSERT(sequenceName);

    clumpAnim = RWCLUMPGETANIM(clump);

    /* get the sequence from the inactive list */
    animClumpSequence =
        AnimClumpRemoveSequence(clumpAnim->clumpSequenceList,
                                sequenceName);
    if (!animClumpSequence)
    {
        RWRETURN((RpClump *) NULL);
    }

    AnimClumpSequenceDestroy(animClumpSequence);

    clumpAnim->numAnimClumpSequences--;

    RWRETURN(clump);
}

/**
 * \ingroup rpanim
 * \ref RpAnimClumpAddTime is used to increment the position of
 * all active animated sequences in the specified clump by the given amount.
 * A sequence is active only if it has been started with
 * \ref RpAnimClumpStartSequence.
 * The animation plug-in must be attached before using this function.
 *
 * \param clump  pointer to the clump.
 * \param time  RwReal value equal to the time increment.
 *
 * \return a pointer to the clump if successful or NULL if there is an error.
 *
 * \see RpAnimClumpAddSequence
 * \see RpAnimClumpRemoveSequence
 * \see RpAnimClumpStartSequence
 * \see RpAnimClumpStopSequence
 * \see RpAnimClumpSequenceGetName
 * \see RpAnimClumpForAllSequences
 * \see RpAnimPluginAttach
 */
RpClump            *
RpAnimClumpAddTime(RpClump * clump, RwReal time)
{
    rpAnimClump        *clumpAnim;
    RpAnimClumpSequence *animSeq;
    RwInt32             frameNum, numFrames;
    RwFrame           **frameArray;

    RWAPIFUNCTION(RWSTRING("RpAnimClumpAddTime"));

    RWASSERT(AnimStatic.animModule.numInstances);
    RWASSERT(clump);

    clumpAnim = RWCLUMPGETANIM(clump);

    /* reset the frame priority values */
    frameArray = clumpAnim->frameList.frames;
    numFrames = clumpAnim->frameList.numFrames;
    for (frameNum = 0; frameNum < numFrames; frameNum++)
    {
        RWFRAMESETPRIORITY(*frameArray, 0);
        frameArray++;
    }

    animSeq = clumpAnim->activeClumpSequenceList->next;
    while (animSeq)
    {
        RpAnimClumpSequence *const animSeqNext = animSeq->next;

        AnimClumpSequenceAddTime(animSeq, clump, time);

        /* onto the next */
        animSeq = animSeqNext;
    }

    RWRETURN(clump);
}

/**
 * \ingroup rpanim
 * \ref RpAnimClumpStartSequence is used to make the specified
 * animation sequence active. When \ref RpAnimClumpAddTime is
 * called, active sequences are moved forward in time if the argument
 * forward is set to TRUE or backwards if set to FALSE. The priority
 * indicates the precedence of the sequence relative to all other
 * sequences registered within the same clump.
 * The sequence must have been registered with the specified clump for
 * this function to work correctly.
 * The animation plug-in must be attached before using this function.
 *
 * \param clump  pointer to the clump.
 * \param sequenceName  pointer to a string containing the name of the sequence.
 * \param forward  A boolean value equal to TRUE if the sequence is to be animated
 *       forward in time or FALSE if backwards.
 * \param priority  A RwInt32 value equal to the priority of the sequence (lowest
 *       priority is zero)
 * \param time  A RwReal value equal to the time when the sequence starts, relative
 *       to a time zero at the sequence's beginning (must be greater than or
 *       equal to zero).
 *
 * \return a pointer to the clump if successful or NULL if there is an error.
 *
 * \see RpAnimClumpStopSequence
 * \see RpAnimClumpAddSequence
 * \see RpAnimClumpRemoveSequence
 * \see RpAnimClumpSequenceGetName
 * \see RpAnimClumpAddTime
 * \see RpAnimClumpForAllSequences
 * \see RpAnimPluginAttach
 */
RpClump            *
RpAnimClumpStartSequence(RpClump * clump,
                         const RwChar * sequenceName,
                         RwBool forward, RwInt32 priority, RwReal time)
{

    rpAnimClump        *clumpAnim;
    RpAnimClumpSequence *animClumpSequence;
    RpAnimClumpSequence *activeDummySeq;

    RWAPIFUNCTION(RWSTRING("RpAnimClumpStartSequence"));

    RWASSERT(AnimStatic.animModule.numInstances);
    RWASSERT(clump);

    clumpAnim = RWCLUMPGETANIM(clump);

    /* get the sequence from the inactive list */
    animClumpSequence =
        AnimClumpRemoveSequence(clumpAnim->clumpSequenceList,
                                sequenceName);
    if (!animClumpSequence)
    {
        RWRETURN((RpClump *) NULL);
    }

    /* set its play direction */
    animClumpSequence->forward = forward;

    /* set its priority */
    animClumpSequence->priority = priority;

    /* add it to the active list */
    activeDummySeq = clumpAnim->activeClumpSequenceList;
    animClumpSequence->next = activeDummySeq->next;
    activeDummySeq->next = animClumpSequence;

    /* reset the sequence */
    /* still need to do this ? */
    animClumpSequence->position = time;
    AnimFrameListReset(animClumpSequence->frameList, forward, time);

    AnimClumpSequenceAddTime(animClumpSequence, clump, (RwReal) (0));

    RWRETURN(clump);
}

/**
 * \ingroup rpanim
 * \ref RpAnimClumpStopSequence is used to make the specified
 * animation sequence inactive. Inactive sequences are not animated when
 * \ref RpAnimClumpAddTime is called.
 * The animation plug-in must be attached before using this function.
 *
 * \param clump  pointer to the clump.
 * \param sequenceName  pointer to a string containing the name of the sequence.
 *
 * \return a pointer to the clump if successful or NULL if there is an error.
 *
 * \see RpAnimClumpStartSequence
 * \see RpAnimClumpAddSequence
 * \see RpAnimClumpRemoveSequence
 * \see RpAnimClumpSequenceGetName
 * \see RpAnimClumpAddTime
 * \see RpAnimClumpForAllSequences
 * \see RpAnimPluginAttach
 * \see RpAnimDatabase
 */
RpClump            *
RpAnimClumpStopSequence(RpClump * clump, RwChar * sequenceName)
{

    rpAnimClump        *clumpAnim;
    RpAnimClumpSequence *animClumpSequence;
    RpAnimClumpSequence *inactiveDummySeq;

    RWAPIFUNCTION(RWSTRING("RpAnimClumpStopSequence"));

    RWASSERT(AnimStatic.animModule.numInstances);
    RWASSERT(clump);
    RWASSERT(sequenceName);

    clumpAnim = RWCLUMPGETANIM(clump);

    /* get the sequence from the inactive list */
    animClumpSequence =
        AnimClumpRemoveSequence
        (clumpAnim->activeClumpSequenceList, sequenceName);
    if (!animClumpSequence)
    {
        RWRETURN((RpClump *) NULL);
    }

    /* add it to the inactive list */
    inactiveDummySeq = clumpAnim->clumpSequenceList;
    animClumpSequence->next = inactiveDummySeq->next;
    inactiveDummySeq->next = animClumpSequence;

    RWRETURN(clump);
}

/**
 * \ingroup rpanim
 * \ref RpAnimClumpForAllSequences is used to apply the given
 * callback function to all sequences registered with the specified
 * clump.
 * Note that callbacks are executed for all active sequences first.
 * Note that if any invocation of the callback function returns a
 * failure status (NULL) the iteration is terminated.
 * However, RpAnimClumpForAllSequences will still return successfully.
 * The animation plug-in must be attached before using this function.
 *
 * The format of the callback function is: 
 * \verbatim
   RpAnimClumpSequence *
   (*RpAnimClumpForAllSequencesCallBack)(RpAnimClumpSequence *sequence,
                                         RwBool active,
                                         void *data);
   \endverbatim
 * where active indicates whether the sequence is active and 
 * data is a user-supplied data pointer to pass to the callback function. 
 *
 * \param clump  pointer to the clump.
 * \param fpAnimClumpSeqCB  pointer to the callback function.
 * \param sequenceCallBackData  pointer to user-supplied data to pass to callback function.
 *
 * \return a pointer to the clump if successful or NULL if there is an error.
 *
 * \see RpAnimClumpAddSequence
 * \see RpAnimClumpRemoveSequence
 * \see RpAnimClumpStartSequence
 * \see RpAnimClumpStopSequence
 * \see RpAnimClumpSequenceGetName
 * \see RpAnimClumpAddTime
 * \see RpAnimPluginAttach
 */
RpClump            *
RpAnimClumpForAllSequences(RpClump * clump,
                           RpAnimClumpForAllSequencesCallBack
                           fpAnimClumpSeqCB, void *sequenceCallBackData)
{
    rpAnimClump        *clumpAnim;
    RpAnimClumpSequence *animSeq;

    RWAPIFUNCTION(RWSTRING("RpAnimClumpForAllSequences"));

    RWASSERT(clump);
    RWASSERT(AnimStatic.animModule.numInstances);

    clumpAnim = RWCLUMPGETANIM(clump);

    /* go through the active list */
    animSeq = clumpAnim->activeClumpSequenceList->next;
    while (animSeq)
    {
        RpAnimClumpSequence *const animSeqNext = animSeq->next;

        if (!fpAnimClumpSeqCB(animSeq, TRUE, sequenceCallBackData))
        {
            /* early out */
            RWRETURN(clump);
        }

        /* onto the next */
        animSeq = animSeqNext;
    }

    /* go through the inactive list */
    animSeq = clumpAnim->clumpSequenceList->next;
    while (animSeq)
    {
        RpAnimClumpSequence *const animSeqNext = animSeq->next;

        if (!fpAnimClumpSeqCB(animSeq, FALSE, sequenceCallBackData))
        {
            /* early out */
            RWRETURN(clump);
        }

        /* onto the next */
        animSeq = animSeqNext;
    }

    RWRETURN(clump);
}

/**
 * \ingroup rpanim
 * \ref RpAnimClumpSetSequenceCallBack is used to set the
 * clumpSequenceCallBack of a named sequence. The sequence is,
 * by definition, registered with a clump.
 * The animation plug-in must be attached before using this function.
 *
 * The format of the callback function is: 
   \verbatim
   RpAnimClumpSequence *
   (*RpAnimClumpForAllSequencesCallBack)(RpAnimClumpSequence *sequence,
                                         RwBool active,
                                         void *data);
   \endverbatim
 * \param clump  pointer to the clump.
 * \param sequenceName  pointer to a string containing the name of the sequence.
 * \param fpAnimClumpSeqCB  pointer to the callback function.
 * \param sequenceCallBackData  pointer to user-supplied data to pass to callback function.
 *
 * \return a pointer to the clump if successful,
 * or NULL if there is an error.
 *
 * \see RpAnimClumpAddSequence
 * \see RpAnimClumpRemoveSequence
 * \see RpAnimClumpStartSequence
 * \see RpAnimClumpStopSequence
 * \see RpAnimClumpAddTime
 * \see RpAnimClumpForAllSequences
 * \see RpAnimPluginAttach
 */

RpClump            *
RpAnimClumpSetSequenceCallBack(RpClump * clump, RwChar * sequenceName,
                               RpAnimClumpSequenceCallBack
                               fpAnimClumpSeqCB,
                               void *sequenceCallBackData)
{
    RpAnimClumpSequence *animClumpSeq;
    rpAnimClumpFindStruct findStruct;

    RWAPIFUNCTION(RWSTRING("RpAnimClumpSetSequenceCallBack"));

    RWASSERT(AnimStatic.animModule.numInstances);
    RWASSERT(clump);
    RWASSERT(sequenceName);

    findStruct.seq = (RpAnimClumpSequence *) NULL;
    findStruct.name = sequenceName;
    RpAnimClumpForAllSequences(clump, AnimClumpFindSeqCallBack,
                               &findStruct);

    animClumpSeq = findStruct.seq;
    if (!animClumpSeq)
    {
        RWRETURN((RpClump *) NULL);
    }

    /* set the new sequence callback */
    animClumpSeq->fpAnimClumpSeqCB = fpAnimClumpSeqCB;
    animClumpSeq->callBackData = sequenceCallBackData;

    RWRETURN(clump);
}

/**
 * \ingroup rpanim
 * \ref RpAnimClumpSequenceGetName is used to retrieve the name
 * associated with the specified animation sequence. The sequence is,
 * by definition, registered with a clump.
 * The animation plug-in must be attached before using this function.
 *
 * \param animSequence  pointer to the animation sequence data.
 *
 * \return a pointer to the sequence name if successful,
 * or NULL if there is an error.
 *
 * \see RpAnimClumpAddSequence
 * \see RpAnimClumpRemoveSequence
 * \see RpAnimClumpStartSequence
 * \see RpAnimClumpStopSequence
 * \see RpAnimClumpAddTime
 * \see RpAnimClumpForAllSequences
 * \see RpAnimPluginAttach
 */
RwChar             *
RpAnimClumpSequenceGetName(RpAnimClumpSequence * animSequence)
{
    RWAPIFUNCTION(RWSTRING("RpAnimClumpSequenceGetName"));

    RWASSERT(animSequence);
    RWASSERT(AnimStatic.animModule.numInstances);

    RWRETURN(animSequence->name);
}

/* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

   rpAnimFrame methods

   !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! */

/**
 * \ingroup rpanim
 * \ref RpAnimFrameAddSequence is used to attach the specified
 * animation sequence to the given frame.
 * The animation plug-in must be attached before using this function.
 *
 * \param frame  pointer to the frame.
 * \param sequence  pointer to a string containing the name of the sequence.
 *
 * \return pointer to the frame if successful or NULL if there is an error.
 *
 * \see RpAnimFrameRemoveSequence
 * \see RpAnimFrameForAllSequences
 * \see RpAnimSequenceCreate
 * \see RpAnimPluginAttach
 */
RwFrame            *
RpAnimFrameAddSequence(RwFrame * frame, RpAnimSequence * sequence)
{
    rpAnimFrame        *frameAnim;
    rpAnimSequenceList *seq;

    RWAPIFUNCTION(RWSTRING("RpAnimFrameAddSequence"));

    RWASSERT(frame);
    RWASSERT(sequence);
    RWASSERT(AnimStatic.animModule.numInstances);

    frameAnim = RWFRAMEGETANIM(frame);
    if (!frameAnim)
    {
        /* create the new frame anim */
        frameAnim = FrameAnimCreate( /*NULL */ );
        if (!frameAnim)
        {
            RWRETURN((RwFrame *) NULL);
        }

        RWFRAMESETANIM(frame, frameAnim);
    }

    switch (sequence->type)
    {
        case rpTRANSLATE:
            {
                /* Check the frame does not already have
                 * a sequence by the same name */
                seq = frameAnim->translateSequences;
                while (seq)
                {
                    if (rwstrcmp(seq->sequence->name, sequence->name) ==
                        0)
                    {
                        RWRETURN(frame);
                    }

                    seq = seq->next;
                }

                seq = (rpAnimSequenceList *)
                    RwFreeListAlloc(RPANIMGLOBAL(animListFreeList));
                seq->sequence = sequence;
                seq->next = frameAnim->translateSequences;
                frameAnim->translateSequences = seq;
                frameAnim->numTranslateSequences++;

                /* Increment the reference count */
                sequence->refCnt++;

                RWRETURN(frame);
            }
        case rpROTATE:
            {
                /* Check the frame does not already have
                 * a sequence by the same name */
                seq = frameAnim->rotateSequences;
                while (seq)
                {
                    if (rwstrcmp(seq->sequence->name, sequence->name) ==
                        0)
                    {
                        RWRETURN(frame);
                    }

                    seq = seq->next;
                }

                seq = (rpAnimSequenceList *)
                    RwFreeListAlloc(RPANIMGLOBAL(animListFreeList));
                seq->sequence = sequence;
                seq->next = frameAnim->rotateSequences;
                frameAnim->rotateSequences = seq;
                frameAnim->numRotateSequences++;

                /* Increment the reference count */
                sequence->refCnt++;

                RWRETURN(frame);
            }
        case rpERR:
            {
                break;
            }
        case rpANIMTYPEFORCEENUMSIZEINT:
            break;
    }

    RWRETURN((RwFrame *) NULL);
}

/**
 * \ingroup rpanim
 * \ref RpAnimFrameRemoveSequence is used to disassociate the
 * specified animation sequence from the given frame.
 * The sequence is not deleted by this function.
 * The animation plug-in must be attached before using this function.
 *
 * \param frame  pointer to the frame.
 * \param sequence  pointer to the animation sequence.
 *
 * \return pointer to the frame if successful or NULL if there is an error,
 * or if the sequence cannot be found.
 *
 * \see RpAnimFrameAddSequence
 * \see RpAnimFrameForAllSequences
 * \see RpAnimPluginAttach
 * \see RpAnimSequence
 */
RwFrame            *
RpAnimFrameRemoveSequence(RwFrame * frame, RpAnimSequence * sequence)
{

    rpAnimFrame        *frameAnim;
    rpAnimSequenceList *prevSeq;
    rpAnimSequenceList *curSeq;

    RWAPIFUNCTION(RWSTRING("RpAnimFrameRemoveSequence"));

    RWASSERT(AnimStatic.animModule.numInstances);
    RWASSERT(frame);
    RWASSERT(sequence);

    frameAnim = RWFRAMEGETANIM(frame);
    if (frameAnim)
    {
        switch (sequence->type)
        {
            case rpTRANSLATE:
                {
                    curSeq = frameAnim->translateSequences;

                    /* First in list */
                    if (curSeq->sequence == sequence)
                    {
                        frameAnim->translateSequences = curSeq->next;
                        RwFreeListFree(RPANIMGLOBAL(animListFreeList),
                                       curSeq);

                        frameAnim->numTranslateSequences--;

                        /* Decrement the sequences reference count */
                        sequence->refCnt--;
                        /* RWASSERT(0 < sequence->refCnt); */
                        RWASSERT(0 <= sequence->refCnt);

                        RWRETURN(frame);
                    }

                    prevSeq = curSeq;
                    curSeq = prevSeq->next;

                    /* Find the sequence in the list and remove it */
                    while (curSeq)
                    {
                        if (curSeq->sequence == sequence)
                        {
                            prevSeq->next = curSeq->next;
                            RwFreeListFree(RPANIMGLOBAL
                                           (animListFreeList), curSeq);

                            frameAnim->numTranslateSequences--;

                            /* Decrement the sequences reference count */
                            sequence->refCnt--;
                            /* RWASSERT(0 < sequence->refCnt); */
                            RWASSERT(0 <= sequence->refCnt);

                            RWRETURN(frame);
                        }

                        prevSeq = curSeq;
                        curSeq = prevSeq->next;
                    }

                    RWRETURN((RwFrame *) NULL);
                }
            case rpROTATE:
                {
                    curSeq = frameAnim->rotateSequences;

                    /* First in list */
                    if (curSeq->sequence == sequence)
                    {
                        frameAnim->rotateSequences = curSeq->next;
                        RwFreeListFree(RPANIMGLOBAL(animListFreeList),
                                       curSeq);

                        frameAnim->numRotateSequences--;

                        /* Decrement the sequences reference count */
                        sequence->refCnt--;
                        /* RWASSERT(0 < sequence->refCnt); */
                        RWASSERT(0 <= sequence->refCnt);

                        RWRETURN(frame);
                    }

                    prevSeq = curSeq;
                    curSeq = prevSeq->next;

                    /* Find the sequence in the list and remove it */
                    while (curSeq)
                    {
                        if (curSeq->sequence == sequence)
                        {
                            prevSeq->next = curSeq->next;
                            RwFreeListFree(RPANIMGLOBAL
                                           (animListFreeList), curSeq);

                            frameAnim->numRotateSequences--;

                            /* Decrement the sequences reference count */
                            sequence->refCnt--;
                            /* RWASSERT(0 < sequence->refCnt); */
                            RWASSERT(0 <= sequence->refCnt);

                            RWRETURN(frame);
                        }

                        prevSeq = curSeq;
                        curSeq = prevSeq->next;
                    }

                    RWRETURN((RwFrame *) NULL);
                }
            case rpERR:
                {
                    break;
                }
            case rpANIMTYPEFORCEENUMSIZEINT:
                break;
        }
    }

    RWRETURN((RwFrame *) NULL);
}

/**
 * \ingroup rpanim
 * \ref RpAnimFrameGetNumTranslationSequences is used to determine
 * the number of animation sequences associated with the specified frame
 * that are of type rpTRANSLATE.
 * The animation plug-in must be attached before using this function.
 *
 * \param frame  pointer to the frame.
 *
 * \return a RwInt32 value equal to the number of sequences,
 * or -1 if there is an error.
 *
 * \see RpAnimFrameGetNumRotationSequences
 * \see RpAnimFrameGetRotationSequence
 * \see RpAnimFrameGetTranslationSequence
 * \see RpAnimFrameForAllSequences
 * \see RpAnimPluginAttach
 */
RwInt32
RpAnimFrameGetNumTranslationSequences(RwFrame * frame)
{
    rpAnimFrame        *frameAnim;

    RWAPIFUNCTION(RWSTRING("RpAnimFrameGetNumTranslationSequences"));

    RWASSERT(AnimStatic.animModule.numInstances);
    RWASSERT(frame);

    frameAnim = RWFRAMEGETANIM(frame);
    if (frameAnim)
    {
        RWRETURN(frameAnim->numTranslateSequences);
    }

    RWRETURN(-1);
}

/**
 * \ingroup rpanim
 * \ref RpAnimFrameGetNumRotationSequences is used to determine
 * the number of animation sequences associated with the specified frame
 * that are of type rpROTATE.
 * The animation plug-in must be attached before using this function.
 *
 * \param frame  pointer to the frame.
 *
 * \return a RwInt32 value equal to the number of sequences,
 * or -1 if there is an error.
 *
 * \see RpAnimFrameGetNumTranslationSequences
 * \see RpAnimFrameGetRotationSequence
 * \see RpAnimFrameGetTranslationSequence
 * \see RpAnimFrameForAllSequences
 * \see RpAnimPluginAttach
 */
RwInt32
RpAnimFrameGetNumRotationSequences(RwFrame * frame)
{
    rpAnimFrame        *frameAnim;

    RWAPIFUNCTION(RWSTRING("RpAnimFrameGetNumRotationSequences"));

    RWASSERT(AnimStatic.animModule.numInstances);
    RWASSERT(frame);

    frameAnim = RWFRAMEGETANIM(frame);
    if (frameAnim)
    {
        RWRETURN(frameAnim->numRotateSequences);
    }

    RWRETURN(-1);
}

/**
 * \ingroup rpanim
 * \ref RpAnimFrameGetTranslationSequence is used to
 * retrieve a translation animation sequence from the specified
 * frame with the given name.
 * The animation plug-in must be attached before using this function.
 *
 * \param frame  pointer to the frame.
 * \param sequenceName  pointer to a string containing the name of the sequence.
 *
 * \return a pointer to the animation sequence if successful,
 * or NULL if there is an error, or if a translation sequence
 * with the supplied name does not exist.
 *
 * \see RpAnimFrameGetRotationSequence
 * \see RpAnimFrameGetNumRotationSequences
 * \see RpAnimFrameGetNumTranslationSequences
 * \see RpAnimFrameForAllSequences
 * \see RpAnimSequenceCreate
 * \see RpAnimPluginAttach
 */
RpAnimSequence     *
RpAnimFrameGetTranslationSequence(RwFrame * frame,
                                  RwChar * sequenceName)
{
    rpAnimFrame        *frameAnim;

    RWAPIFUNCTION(RWSTRING("RpAnimFrameGetTranslationSequence"));

    RWASSERT(AnimStatic.animModule.numInstances);
    RWASSERT(frame);
    RWASSERT(sequenceName);

    frameAnim = RWFRAMEGETANIM(frame);
    if (frameAnim)
    {
        rpAnimSequenceList *animSeq;

        animSeq = frameAnim->translateSequences;
        while (animSeq)
        {
            if (rwstrcmp(animSeq->sequence->name, sequenceName) == 0)
            {
                RWRETURN(animSeq->sequence);
            }

            animSeq = animSeq->next;
        }
    }

    RWRETURN((RpAnimSequence *) NULL);
}

/**
 * \ingroup rpanim
 * \ref RpAnimFrameGetRotationSequence is used to retrieve a
 * rotation animation sequence from the specified frame with the given name.
 * The animation plug-in must be attached before using this function.
 *
 * \param frame  pointer to the frame.
 * \param sequenceName  pointer to a string containing the name of the sequence.
 *
 * \return a pointer to the animation sequence if successful,
 * or NULL if there is an error, or if a rotation sequence with
 * the supplied name does not exist.
 *
 * \see RpAnimFrameGetTranslationSequence
 * \see RpAnimFrameGetNumRotationSequences
 * \see RpAnimFrameGetNumTranslationSequences
 * \see RpAnimFrameForAllSequences
 * \see RpAnimSequenceCreate
 * \see RpAnimPluginAttach
 */
RpAnimSequence     *
RpAnimFrameGetRotationSequence(RwFrame * frame, RwChar * sequenceName)
{
    rpAnimFrame        *frameAnim;

    RWAPIFUNCTION(RWSTRING("RpAnimFrameGetRotationSequence"));

    RWASSERT(AnimStatic.animModule.numInstances);
    RWASSERT(frame);
    RWASSERT(sequenceName);

    frameAnim = RWFRAMEGETANIM(frame);
    if (frameAnim)
    {
        rpAnimSequenceList *animSeq;

        animSeq = frameAnim->rotateSequences;
        while (animSeq)
        {
            if (rwstrcmp(animSeq->sequence->name, sequenceName) == 0)
            {
                RWRETURN(animSeq->sequence);
            }

            animSeq = animSeq->next;
        }
    }

    RWRETURN((RpAnimSequence *) NULL);
}

/**
 * \ingroup rpanim
 * \ref RpAnimFrameForAllSequences is used to apply the given
 * callback function to all sequences associated with the specified frame.
 * Note that if any invocation of the callback function returns a failure
 * status (NULL) the iteration is terminated.
 * However, \ref RpAnimFrameForAllSequences  will still return successfully
 * The animation plug-in must be attached before using this function.
 *
 * The format of the callback function is 
   \verbatim
   RpAnimSequence *
   (*RpAnimFrameForAllSequencesCallBack)(RpAnimSequence *sequence,
                                         void *data);
   \endverbatim
 * where data is a user-supplied data pointer to pass to the callback function. 
 *
 * \param frame  pointer to the frame.
 * \param callback  pointer to the callback function.
 * \param data  pointer to user-supplied data to pass to callback function.
 *
 * \return pointer to the frame if successful or NULL if there is an error.
 *
 * \see RpAnimFrameGetRotationSequence
 * \see RpAnimFrameGetTranslationSequence
 * \see RpAnimFrameGetNumRotationSequences
 * \see RpAnimFrameGetNumTranslationSequences
 * \see RpAnimPluginAttach
 */
RwFrame            *
RpAnimFrameForAllSequences(RwFrame * frame,
                           RpAnimFrameForAllSequencesCallBack callback,
                           void *data)
{
    rpAnimFrame        *frameAnim;

    RWAPIFUNCTION(RWSTRING("RpAnimFrameForAllSequences"));

    RWASSERT(AnimStatic.animModule.numInstances);
    RWASSERT(frame);
    RWASSERT(callback);

    frameAnim = RWFRAMEGETANIM(frame);
    if (frameAnim)
    {
        rpAnimSequenceList *curSequence;
        rpAnimSequenceList *nextSequence;

        /* Rotation sequences */
        curSequence = frameAnim->rotateSequences;
        while (curSequence)
        {
            nextSequence = curSequence->next;
            if (!callback(curSequence->sequence, data))
            {
                RWRETURN(frame);
            }

            curSequence = nextSequence;
        }

        /* Translation sequences */
        curSequence = frameAnim->translateSequences;
        while (curSequence)
        {
            nextSequence = curSequence->next;
            if (!callback(curSequence->sequence, data))
            {
                RWRETURN(frame);
            }

            curSequence = nextSequence;
        }
    }

    RWRETURN(frame);
}

/**
 * \ingroup rpanim
 * \ref RpAnimSequenceCreate is used to allocate memory for a new
 * animation sequence with the specified name and type and with the given
 * number of keyframes and interpolators.  Note that, internally, a
 * reference count is maintained for the sequence which takes a value of
 * one when this function is used. This indicates that the sequence must
 * be explicitly destroyed by the application when it is no longer
 * required. This does not apply to clumps (containing animated
 * sequences) that were derived from a binary stream file. In this case,
 * destroying the clump will automatically destroy the animation
 * sequences.  The animation plug-in must be attached before using this
 * function.
 *
 * \param sequenceName  pointer to a string containing the name of the sequence.
 * \param type  the animation type (either rpROTATE or rpTRANSLATE).
 * \param numKeys  a RwInt32 value equal to the number of keyframes.
 * \param numInterps  a RwInt32 value equal to the number of interpolators.
 *
 * \return a pointer to the animation sequence if successful,
 * or NULL if there is an error.
 *
 * \see RpAnimSequenceSetRotateKey
 * \see RpAnimSequenceSetTranslateKey
 * \see RpAnimSequenceSetInterpolator
 * \see RpAnimPluginAttach
 */
RpAnimSequence     *
RpAnimSequenceCreate(const RwChar * sequenceName,
                     RpAnimType type, RwInt32 numKeys,
                     RwInt32 numInterps)
{
    RpAnimSequence     *sequence;

    RWAPIFUNCTION(RWSTRING("RpAnimSequenceCreate"));

    RWASSERT(AnimStatic.animModule.numInstances);
    RWASSERT(sequenceName);

    sequence = (RpAnimSequence *)
        RwFreeListAlloc(AnimStatic.SequenceFreeList);

    if (!sequence)
    {
        RWRETURN((RpAnimSequence *) NULL);
    }

    AnimSequenceInit(sequence, sequenceName, type, numKeys, numInterps);

    RWRETURN(sequence);
}

/**
 * \ingroup rpanim
 * \ref RpAnimSequenceDestroy is used to free the memory
 * allocated to the specified animation sequence.  The animation plug-in
 * must be attached before using this function.
 *
 * \param animSequence  pointer to the animation sequence.
 *
 * \return TRUE if successful or FALSE if there is an error.
 *
 * \see RpAnimSequenceCreate
 * \see RpAnimPluginAttach
 */
RwBool
RpAnimSequenceDestroy(RpAnimSequence * animSequence)
{
    RWAPIFUNCTION(RWSTRING("RpAnimSequenceDestroy"));

    RWASSERT(AnimStatic.animModule.numInstances);
    RWASSERT(animSequence);

    /* RWASSERT(0 < animSequence->refCnt); */
    RWASSERT(0 <= animSequence->refCnt);

    animSequence->refCnt =
        (0 < animSequence->refCnt) ? (animSequence->refCnt - 1) : 0;

    if (0 >= animSequence->refCnt)
    {
        AnimSequenceDeinit(animSequence);
        RwFreeListFree(AnimStatic.SequenceFreeList, animSequence);
        animSequence = (RpAnimSequence *) NULL;
    }

    RWRETURN(TRUE);
}

/**
 * \ingroup rpanim
 * \ref RpAnimSequenceGetName is used to retrieve the name of the
 * specified animation sequence.  The animation plug-in must be attached
 * before using this function.
 *
 * \param animSequence  pointer to the animation sequence.
 *
 * \return a pointer to the name of the animation sequence if successful,
 * or NULL if there is an error.
 *
 * \see RpAnimSequenceGetType
 * \see RpAnimSequenceGetNumKeys
 * \see RpAnimSequenceGetNumInterpolators
 * \see RpAnimSequenceCreate
 * \see RpAnimPluginAttach
 */
RwChar             *
RpAnimSequenceGetName(RpAnimSequence * animSequence)
{
    RWAPIFUNCTION(RWSTRING("RpAnimSequenceGetName"));

    RWASSERT(AnimStatic.animModule.numInstances);
    RWASSERT(animSequence);

    RWRETURN(animSequence->name);
}

/**
 * \ingroup rpanim
 * \ref RpAnimSequenceGetType is used to determine whether the
 * specified animation sequence is of type rpROTATE, indicating it
 * contains a sequence of rotation keyframes, or rpTRANSLATE, indicating
 * the sequence consists of a number of translation keyframes. The type
 * of the sequence is set when it is created and cannot be changed.  The
 * animation plug-in must be attached before using this function.
 *
 * \param animSequence  pointer to the animation sequence.
 *
 * \return a RpAnimType value equal to the animation type if successful,
 * or rpERR if there is an error.
 *
 * \see RpAnimSequenceCreate
 * \see RpAnimSequenceGetName
 * \see RpAnimSequenceGetNumKeys
 * \see RpAnimSequenceGetNumInterpolators
 * \see RpAnimPluginAttach
 *
 */
RpAnimType
RpAnimSequenceGetType(RpAnimSequence * animSequence)
{
    RWAPIFUNCTION(RWSTRING("RpAnimSequenceGetType"));

    RWASSERT(AnimStatic.animModule.numInstances);
    RWASSERT(animSequence);

    RWRETURN(animSequence->type);
}

/**
 * \ingroup rpanim
 * \ref RpAnimSequenceGetNumKeys is used to determine the number
 * of keyframes (of the sequence's type) that constitute the specified
 * animation sequence. This number is set when the sequence is created
 * and cannot be changed.  The animation plug-in must be attached before
 * using this function.
 *
 * \param animSequence  pointer to the animation sequence.
 *
 * \return a RwInt32 value equal to the number of keyframes,
 *  or -1 if there is an error.
 *
 * \see RpAnimSequenceCreate
 * \see RpAnimSequenceSetRotateKey
 * \see RpAnimSequenceSetTranslateKey
 * \see RpAnimSequenceGetNumInterpolators
 * \see RpAnimSequenceGetType
 * \see RpAnimSequenceGetName
 * \see RpAnimPluginAttach
 */
RwInt32
RpAnimSequenceGetNumKeys(RpAnimSequence * animSequence)
{
    RWAPIFUNCTION(RWSTRING("RpAnimSequenceGetNumKeys"));

    RWASSERT(AnimStatic.animModule.numInstances);
    RWASSERT(animSequence);

    RWRETURN(animSequence->numKeys);
}

/**
 * \ingroup rpanim
 * \ref RpAnimSequenceGetNumInterpolators is used to determine
 * the number of interpolators controlling the specified animation
 * sequence. This number is set when the sequence is created and cannot
 * be changed.  The animation plug-in must be attached before using this
 * function.
 *
 * \param animSequence  pointer to the animation sequence.
 *
 * \return a RwInt32 value equal to the number of interpolators,
 * or -1 if there is an error.
 *
 * \see RpAnimSequenceCreate
 * \see RpAnimSequenceSetInterpolator
 * \see RpAnimSequenceGetNumKeys
 * \see RpAnimSequenceGetType
 * \see RpAnimSequenceGetName
 * \see RpAnimPluginAttach
 */
RwInt32
RpAnimSequenceGetNumInterpolators(RpAnimSequence * animSequence)
{
    RWAPIFUNCTION(RWSTRING("RpAnimSequenceGetNumInterpolators"));

    RWASSERT(AnimStatic.animModule.numInstances);
    RWASSERT(animSequence);

    RWRETURN(animSequence->numInterpolators);
}

/**
 * \ingroup rpanim
 * \ref RpAnimSequenceSetTranslateKey is used to define the
 * parameters of a trranslation keyframe in the specified animation
 * sequence with the given index. The sequence must be of type
 * rpTRANSLATE.  The animation plug-in must be attached before using this
 * function.
 *
 * \param animSequence  pointer to the animation sequence.
 * \param keyNum  a RwInt32 value equal to the index of the keyframe (index of the
 * first keyframe is zero).
 * \param trans  a 3D vector describing the extent of the translation.
 *
 * \return Returns a pointer to the animation sequence if successful
 * or NULL if there is an error, or if the sequence is not of type rpTRANSLATE.
 *
 * \see RpAnimSequenceCreate
 * \see RpAnimSequenceSetRotateKey
 * \see RpAnimSequenceGetTranslateKey
 * \see RpAnimSequenceGetRotateKey
 * \see RpAnimPluginAttach
 */
RpAnimSequence     *
RpAnimSequenceSetTranslateKey(RpAnimSequence * animSequence,
                              RwInt32 keyNum, RwV3d * trans)
{
    RWAPIFUNCTION(RWSTRING("RpAnimSequenceSetTranslateKey"));

    RWASSERT(AnimStatic.animModule.numInstances);
    RWASSERT(animSequence);
    RWASSERT(trans);

    if (animSequence->type == rpTRANSLATE)
    {
        RwInt32             numKeys;

        numKeys = animSequence->numKeys;
        if ((keyNum >= 0) && (keyNum < numKeys))
        {
            RpTranslateKey     *transKey;

            transKey =
                &(((RpTranslateKey *) animSequence->keys)[keyNum]);

            *transKey = *trans;

            RWRETURN(animSequence);
        }
    }

    RWRETURN((RpAnimSequence *) NULL);
}

/**
 * \ingroup rpanim
 * \ref RpAnimSequenceSetRotateKey is used to define the
 * parameters of a rotation keyframe in the specified animation sequence
 * with the given index. The sequence must be of type rpROTATE.  The
 * animation plug-in must be attached before using this function.
 *
 * The rotation parameters are specified using a quaterion represented
 * by the RenderWare type RtQuat : 
   \verbatim
       typedef struct
       {
               RwV3d imag;
               RwReal real;
       }
       RtQuat;
   \endverbatim
 * where imag holds the imaginary conponents and real holds the real component of the quaterion. 
 *
 * \param animSequence  pointer to the animation sequence.
 * \param keyNum  a RwInt32 value equal to the index of the keyframe (first keyframe has index zero).
 * \param quat  pointer to a quaterion representing the rotation.
 *
 * \return returns a pointer to the animation sequence if successful,
 * or NULL if there is an error, or if the sequence is not of type rpROTATE.
 *
 * \see RtQuatConvertFromMatrix
 * \see RtQuatConvertToMatrix
 * \see RwMatrixRotate
 * \see RpAnimSequenceCreate
 * \see RpAnimSequenceSetTranslateKey
 * \see RpAnimSequenceGetRotateKey
 * \see RpAnimSequenceGetTranslateKey
 * \see RpAnimPluginAttach
 */
RpAnimSequence     *
RpAnimSequenceSetRotateKey(RpAnimSequence * animSequence,
                           RwInt32 keyNum, RtQuat * quat)
{
    RWAPIFUNCTION(RWSTRING("RpAnimSequenceSetRotateKey"));

    RWASSERT(AnimStatic.animModule.numInstances);
    RWASSERT(animSequence);
    RWASSERT(quat);

    if (animSequence->type == rpROTATE)
    {
        RwInt32             numKeys;

        numKeys = animSequence->numKeys;
        if ((keyNum >= 0) && (keyNum < numKeys))
        {
            RpRotateKey        *rotKey;

            rotKey = &(((RpRotateKey *) animSequence->keys)[keyNum]);
            *rotKey = *quat;

            RWRETURN(animSequence);
        }
    }

    RWRETURN((RpAnimSequence *) NULL);
}

/**
 * \ingroup rpanim
 * \ref RpAnimSequenceGetTranslateKey is used to retrieve a
 * translation keyframe from the specified animation sequence with the
 * given index. This function may be used to reset the components of the
 * translation for this keyframe.  Note that the translation keyframe is
 * an exposed data type and is equivalent to the RwV3d type.  The
 * animation plug-in must be attached before using this function.
 *
 * \param animSequence  pointer to the animation sequence.
 * \param keyNum  a RwInt32 value equal to the index of the keyframe
 *       (first keyframe has index zero).
 *
 * \return a pointer to the translation keyframe if successful,
 *        or NULL if there is an error, or if the index is out of range.
 *
 * \see RpAnimSequenceCreate
 * \see RpAnimSequenceGetRotateKey
 * \see RpAnimSequenceSetTranslateKey
 * \see RpAnimSequenceSetRotateKey
 * \see RpAnimPluginAttach
 */
RpTranslateKey     *
RpAnimSequenceGetTranslateKey(RpAnimSequence * animSequence,
                              RwInt32 keyNum)
{
    RWAPIFUNCTION(RWSTRING("RpAnimSequenceGetTranslateKey"));

    RWASSERT(AnimStatic.animModule.numInstances);
    RWASSERT(animSequence);

    if (animSequence->type == rpTRANSLATE)
    {
        RwInt32             numKeys;

        numKeys = animSequence->numKeys;
        if ((keyNum >= 0) && (keyNum < numKeys))
        {
            RpTranslateKey     *transKey;

            transKey =
                &(((RpTranslateKey *) animSequence->keys)[keyNum]);

            RWRETURN(transKey);
        }
    }

    RWRETURN((RwV3d *) NULL);
}

/**
 * \ingroup rpanim
 * \ref RpAnimSequenceGetRotateKey is used to retrieve a rotation
 * keyframe from the specified animation sequence with the given
 * index. This function may be used to reset the axis of rotation and
 * angle of rotation for this keyframe.  The animation plug-in must be
 * attached before using this function.
 *
 * Note that the rotation keyframe is an exposed data type and is
 * equivalent to the RenderWare quaterion type: 
   \verbatim
       typedef struct
       {
               RwV3d imag;
               RwReal real;
       }
       RtQuat;
   \endverbatim
 * where imag holds the imaginary conponents and real holds the real component of the quaterion. 
 *
 * \param animSequence  ponter to the animation sequence.
 * \param keyNum  a RwInt32 value equal to the index of the keyframe
 *      (first keyframe has index zero).
 *
 * \return returns a pointer to the rotation keyframe if successful,
 *        or NULL if there is an error, or if the index is out of range.
 *
 * \see RpAnimSequenceCreate
 * \see RpAnimSequenceGetTranslateKey
 * \see RpAnimSequenceSetRotateKey
 * \see RpAnimSequenceSetTranslateKey
 * \see RtQuatConvertToMatrix
 * \see RtQuatConvertFromMatrix
 * \see RwMatrixQueryRotate
 * \see RpAnimPluginAttach
 */
RpRotateKey        *
RpAnimSequenceGetRotateKey(RpAnimSequence * animSequence,
                           RwInt32 keyNum)
{
    RWAPIFUNCTION(RWSTRING("RpAnimSequenceGetRotateKey"));

    RWASSERT(AnimStatic.animModule.numInstances);
    RWASSERT(animSequence);

    if (animSequence->type == rpROTATE)
    {
        RwInt32             numKeys;

        numKeys = animSequence->numKeys;
        if ((keyNum >= 0) && (keyNum < numKeys))
        {
            RpRotateKey        *rotKey;

            rotKey = &(((RpRotateKey *) animSequence->keys)[keyNum]);

            RWRETURN(rotKey);
        }
    }

    RWRETURN((RtQuat *) NULL);
}

/**
 * \ingroup rpanim
 * \ref RpAnimSequenceSetInterpolator is used to define the
 * parameters of an interpolator in the specified animation sequence with
 * the given index.  The animation plug-in must be attached before using
 * this function.
 *
 * \param animSequence  pointer to the animation sequence.
 * \param interpNum     a RwInt32 value equal to the index of the interpolator
 *      (index of the first interpolator is zero).
 * \param startKeyFrame  a RwInt32 value equal to the index of the start keyframe
 *      (index of the first keyframe is zero).
 * \param endKeyFrame  a RwInt32 value equal to the index of the end keyframe
 *      (index of the first keyframe is zero).
 * \param duration  a RwReal value equal to the duration of the interpolation.
 *
 * \return returns a pointer to the animation sequence if successful,
 *        or NULL if there is an error.
 *
 * \see RpAnimSequenceCreate
 * \see RpAnimSequenceGetNumInterpolators
 * \see RpAnimPluginAttach
 */
RpAnimSequence     *
RpAnimSequenceSetInterpolator(RpAnimSequence * animSequence,
                              RwInt32 interpNum, RwInt32 startKeyFrame,
                              RwInt32 endKeyFrame, RwReal duration)
{
    RwInt32             numInterps;

    RWAPIFUNCTION(RWSTRING("RpAnimSequenceSetInterpolator"));

    RWASSERT(AnimStatic.animModule.numInstances);
    RWASSERT(animSequence);

    numInterps = animSequence->numInterpolators;
    if ((interpNum >= 0) && (interpNum < numInterps))
    {
        switch (animSequence->type)
        {
            case rpTRANSLATE:
                {
                    RpTranslateKey     *keys;

                    keys = (RpTranslateKey *) (animSequence->keys);
                    animSequence->interps[interpNum].startKeyFrame =
                        startKeyFrame;
                    animSequence->interps[interpNum].endKeyFrame =
                        endKeyFrame;
                    animSequence->interps[interpNum].time = duration;
                }
                break;

            case rpROTATE:
                {
                    RpRotateKey        *keys;

                    keys = (RpRotateKey *) animSequence->keys;
                    animSequence->interps[interpNum].startKeyFrame =
                        startKeyFrame;
                    animSequence->interps[interpNum].endKeyFrame =
                        endKeyFrame;
                    animSequence->interps[interpNum].time = duration;
                }
                break;

            default:
                break;
        }

        RWRETURN(animSequence);
    }

    RWRETURN((RpAnimSequence *) NULL);
}

/* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

   RpAnim methods

   !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! */

RwInt32
_rpAnimSeqGetFrameTag(RwFrame * frame)
{
    RWFUNCTION(RWSTRING("_rpAnimSeqGetFrameTag"));

    RWRETURN(RWFRAMEGETTAG(frame));
}

RwFrame            *
_rpAnimSeqSetFrameTag(RwFrame * frame, RwInt32 tag)
{
    RWFUNCTION(RWSTRING("_rpAnimSeqSetFrameTag"));

    RWFRAMESETTAG(frame, tag);

    RWRETURN(frame);
}

/**
 * \ingroup rpanim
 * \ref RpAnimPluginAttach attaches the animation plug-in to the
 * RenderWare system to enable the generation of hierarchical keyframe
 * and morph target animations.  The animation plug-in must be attached
 * between initializing the system with \ref RwEngineInit and opening it with
 * \ref RwEngineOpen.
 *
 * Note that the animation plug-in requires the world plug-in to be
 * attached. The include file rpanim.h is also required and must be
 * included by an application wishing to generate animations.
 *
 * Note also that when linking the animation plug-in library, rpanim.lib,
 * into an application, make sure the RenderWare SLERP library,
 * rtslerp.lib, is also made available to the linker.
 *
 * \return TRUE if successful or FALSE if there is an error.
 *
 * \see RwEngineInit
 * \see RwEngineOpen
 * \see RwEngineStart
 * \see RpWorldPluginAttach
 */
RwBool
RpAnimPluginAttach(void)
{
    RwInt32             offset;

    RWAPIFUNCTION(RWSTRING("RpAnimPluginAttach"));

    offset =
        RwEngineRegisterPlugin(sizeof(rpAnimGlobals),
                               rwID_ANIMPLUGIN, AnimOpen, AnimClose);
    if (offset < 0)
    {
        RWRETURN(FALSE);
    }

    /* clump animation */
    AnimStatic.clumpAnimOffset =
        RpClumpRegisterPlugin(sizeof(rpAnimClump),
                              rwID_ANIMPLUGIN,
                              AnimClumpCtor,
                              AnimClumpDtor, AnimClumpCopy);
    if (AnimStatic.clumpAnimOffset < 0)
    {
        RWRETURN(FALSE);
    }

    /* frame animation */
    AnimStatic.frameDataOffset =
        RwFrameRegisterPlugin(sizeof(rpAnimSeqFrameData),
                              rwID_ANIMPLUGIN,
                              AnimFrameCtor,
                              AnimFrameDtor, AnimFrameCopy);
    if (AnimStatic.frameDataOffset < 0)
    {
        RWRETURN(FALSE);
    }

    offset =
        RwFrameRegisterPluginStream(rwID_ANIMPLUGIN,
                                    AnimFrameReadStream,
                                    AnimFrameWriteStream,
                                    AnimFrameSizeStream);
    if (offset < 0)
    {
        RWRETURN(FALSE);
    }

    offset =
        RpClumpRegisterPluginStream(rwID_ANIMPLUGIN,
                                    AnimClumpReadStream,
                                    AnimClumpWriteStream,
                                    AnimClumpSizeStream);

    if (offset < 0)
    {
        RWRETURN(FALSE);
    }

    RWRETURN(TRUE);
}

/* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

   rpAnimClump optimization structures & prototypes

   !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! */

/* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

   Interpolator/Keyframe optimization code

   !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! */

/* Animation database code */

/**
 * \ingroup rpanim
 * \ref RpAnimDatabaseCreate is used to create a new animation
 * sequence database. The database is created with no entries.  The
 * animation plug-in must be attached before using this function.
 *
 * \return a pointer to the new animation sequence database if successful,
 *        or NULL if there is an error.
 *
 * \see RpAnimDatabaseCopyClumpSequence
 * \see RpAnimDatabaseCopyAllClumpSequences
 * \see RpAnimDatabaseCopySequenceToClump
 * \see RpAnimDatabaseCopyAllSequencesToClump
 * \see RpAnimDatabaseCopyDatabaseSequence
 * \see RpAnimDatabaseCopyAllDatabaseSequences
 * \see RpAnimPluginAttach
 */
RpAnimDatabase     *
RpAnimDatabaseCreate(void)
{
    RpAnimDatabase     *db;

    RWAPIFUNCTION(RWSTRING("RpAnimDatabaseCreate"));
    RWASSERT(AnimStatic.animModule.numInstances);

    db = (RpAnimDatabase *)
        RwFreeListAlloc(AnimStatic.DatabaseFreeList);

    if (db)
    {
        db->entries = (RpAnimDatabaseEntry *) NULL;
        db->numEntries = 0;
        RWRETURN(db);
    }
    else
    {
        RWERROR((E_RW_NOMEM, sizeof(RpAnimDatabase)));
        RWRETURN((RpAnimDatabase *) NULL);
    }
}

/**
 * \ingroup rpanim
 * \ref RpAnimDatabaseDestroy is used to destroy the specified
 * animation sequence database, including any sequences it may contain.
 * The animation plug-in must be attached before using this function.
 *
 * \param database  pointer to the sequence database.
 *
 * \return TRUE if successful or FALSE if there is an error.
 *
 * \see RpAnimDatabaseCreate
 * \see RpAnimPluginAttach
 */
RwBool
RpAnimDatabaseDestroy(RpAnimDatabase * database)
{
    RpAnimDatabaseEntry *entry;

    RWAPIFUNCTION(RWSTRING("RpAnimDatabaseDestroy"));

    RWASSERT(AnimStatic.animModule.numInstances);
    RWASSERT(database);

    {
        while (database->entries)
        {
            RwInt32             i;

            entry = database->entries;
            database->entries = entry->next;

            for (i = 0; i < entry->numAnimSequences; i++)
            {
                RpAnimSequenceDestroy(entry->animSequences[i]);
            }
            RwFree(entry->animSequences);
            RwFree(entry->tagArray);
            RwFree(entry->name);

            RwFreeListFree(AnimStatic.DatabaseEntryFreeList, entry);
            entry = (RpAnimDatabaseEntry *) NULL;
        }

        RwFreeListFree(AnimStatic.DatabaseFreeList, database);
        database = (RpAnimDatabase *) NULL;
        RWRETURN(TRUE);
    }

    RWRETURN(FALSE);
}

RpAnimDatabaseEntry *
_rpAnimDatabaseEntryCreate(RwInt32 numSequences)
{
    RpAnimDatabaseEntry *newEntry;

    RWFUNCTION(RWSTRING("_rpAnimDatabaseEntryCreate"));

    newEntry = (RpAnimDatabaseEntry *)
        RwFreeListAlloc(AnimStatic.DatabaseEntryFreeList);

    if (newEntry)
    {
        newEntry->name = (char *) NULL;
        newEntry->next = (RpAnimDatabaseEntry *) NULL;
        newEntry->numAnimSequences = numSequences;
        newEntry->animSequences = (RpAnimSequence **)
            RwMalloc(sizeof(RpAnimSequence *) * numSequences);

        if (newEntry->animSequences)
        {
            newEntry->tagArray = (RwInt32 *)
                RwMalloc(sizeof(RwInt32) * numSequences);

            if (newEntry->tagArray)
            {
                RWRETURN(newEntry);
            }
            else
            {
                RwFree(newEntry->animSequences);
                RwFreeListFree(AnimStatic.DatabaseEntryFreeList,
                               newEntry);
                newEntry = (RpAnimDatabaseEntry *) NULL;

                RWERROR((E_RW_NOMEM,
                         sizeof(RpAnimSequence *) * numSequences));
                RWRETURN((RpAnimDatabaseEntry *) NULL);
            }
        }
        else
        {
            RwFreeListFree(AnimStatic.DatabaseEntryFreeList, newEntry);
            newEntry = (RpAnimDatabaseEntry *) NULL;
            RWERROR((E_RW_NOMEM,
                     sizeof(RpAnimSequence *) * numSequences));
            RWRETURN((RpAnimDatabaseEntry *) NULL);
        }
    }
    else
    {
        RWERROR((E_RW_NOMEM, sizeof(RpAnimDatabaseEntry)));
        RWRETURN((RpAnimDatabaseEntry *) NULL);
    }
}

/**
 * \ingroup rpanim
 * \ref RpAnimDatabaseCopyClumpSequence is used to copy the an
 * animation sequence in the given clump to the specified animation
 * database. The animation sequence to be copied is identified using the
 * given name. The animation plug-in must be attached before using this
 * function.
 *
 * \param database  pointer to the sequence database.
 * \param clump  pointer to the clump.
 * \param name  pointer to a string containing the name of the animation sequence.
 *
 * \return pointer to the database if successful or NULL if there an error.
 *
 * \see RpAnimDatabaseCopyAllClumpSequences
 * \see RpAnimDatabaseCopySequenceToClump
 * \see RpAnimDatabaseCopyAllSequencesToClump
 * \see RpAnimDatabaseCopyDatabaseSequence
 * \see RpAnimDatabaseCopyAllDatabaseSequences
 * \see RpAnimPluginAttach
 */
RpAnimDatabase     *
RpAnimDatabaseCopyClumpSequence(RpAnimDatabase * database,
                                RpClump * clump, RwChar * name)
{
    RpAnimDatabaseEntry *newEntry = (RpAnimDatabaseEntry *) NULL;
    rpAnimAddSequenceToDBStruct addSeq;

    RWAPIFUNCTION(RWSTRING("RpAnimDatabaseCopyClumpSequence"));

    RWASSERT(AnimStatic.animModule.numInstances);
    RWASSERT(database);
    RWASSERT(clump);
    RWASSERT(name);

    {
        if (!RpAnimDatabaseContainsSequence(database, name))
        {
            RwFrame            *rootFrame = RpClumpGetFrame(clump);
            RwInt32             numSequences =
                AnimFrameCountAnimSequencesByName(rootFrame, name);

            if (numSequences > 0)
            {
                newEntry = _rpAnimDatabaseEntryCreate(numSequences);
                if (newEntry)
                {
                    newEntry->next = database->entries;
                    database->entries = newEntry;
                    database->numEntries++;

                    rwstrdup(newEntry->name, name);
                    addSeq.currentIndex = 0;
                    addSeq.dbEntry = newEntry;
                    addSeq.name = name;
                    AnimAddSequencesFromFrames(rootFrame, &addSeq);
                    RWRETURN(database);
                }
                else
                {
                    RWRETURN((RpAnimDatabase *) NULL);
                }
            }
            else
            {
                RWERROR((E_RP_ANIM_NOSUCHSEQUENCE, name));
                RWRETURN((RpAnimDatabase *) NULL);
            }
        }
        else
        {
            RWERROR((E_RP_ANIM_DATABASE_ENTRYALREADYPRESENT, name));
            RWRETURN((RpAnimDatabase *) NULL);
        }
    }

    RWRETURN((RpAnimDatabase *) NULL);
}

/**
 * \ingroup rpanim
 * \ref RpAnimDatabaseCopyAllClumpSequences is used to copy all
 * the animation sequences in the given clump to the specified animation
 * database.  The animation plug-in must be attached before using this
 * function.
 *
 * \param database  pointer to the sequence database.
 * \param clump  pointer to the clump.
 *
 * \return pointer to the database if successful,
 *        or NULL if there is an error.
 *
 * \see RpAnimDatabaseCopyClumpSequence
 * \see RpAnimDatabaseCopySequenceToClump
 * \see RpAnimDatabaseCopyAllSequencesToClump
 * \see RpAnimDatabaseCopyDatabaseSequence
 * \see RpAnimDatabaseCopyAllDatabaseSequences
 * \see RpAnimPluginAttach
 */
RpAnimDatabase     *
RpAnimDatabaseCopyAllClumpSequences(RpAnimDatabase * database,
                                    RpClump * clump)
{

    RWAPIFUNCTION(RWSTRING("RpAnimDatabaseCopyAllClumpSequences"));

    RWASSERT(AnimStatic.animModule.numInstances);
    RWASSERT(database);
    RWASSERT(clump);

    {
        rpAnimNameList      nameList;
        rpAnimNameList     *nameList_next;
        RwFrame            *rootFrame = RpClumpGetFrame(clump);

        nameList.next = (rpAnimNameList *) NULL;
        nameList.name = (RwChar *)NULL;

        AnimFrameBuildSequenceNameList(rootFrame, &nameList);

        for (nameList_next = nameList.next;
             nameList_next; nameList_next = nameList.next)
        {
            RpAnimDatabaseCopyClumpSequence(database, clump,
                                            nameList_next->name);
            nameList.next = nameList_next->next;

            RwFreeListFree(AnimStatic.NameListFreeList, nameList_next);
            nameList_next = (rpAnimNameList *) NULL;
        }
        RWRETURN(database);
    }

    RWRETURN((RpAnimDatabase *) NULL);
}

/**
 * \ingroup rpanim
 * \ref RpAnimDatabaseCopySequenceToClump is used to copy an
 * animation sequence from the specified database to the given clump. The
 * animation sequence to be copied is idenified using the given name.
 * The animation plug-in must be attached before using this function.
 *
 * \param database  pointer to the sequence database.
 * \param clump  pointer to the clump.
 * \param name  pointer to a string containing the name of the animation sequence.
 *
 * \return pointer to the database if successful or NULL if there is an error.
 *
 * \see RpAnimDatabaseCopyClumpSequence
 * \see RpAnimDatabaseCopyAllClumpSequences
 * \see RpAnimDatabaseCopyAllSequencesToClump
 * \see RpAnimDatabaseCopyDatabaseSequence
 * \see RpAnimDatabaseCopyAllDatabaseSequences
 * \see RpAnimPluginAttach
 */
RpAnimDatabase     *
RpAnimDatabaseCopySequenceToClump(RpAnimDatabase * database,
                                  RpClump * clump, RwChar * name)
{
    RpAnimDatabaseEntry *entry;

    RWAPIFUNCTION(RWSTRING("RpAnimDatabaseCopySequenceToClump"));

    RWASSERT(AnimStatic.animModule.numInstances);
    RWASSERT(database);
    RWASSERT(clump);
    RWASSERT(name);

    {
        if (RpAnimDatabaseContainsSequence(database, name))
        {
            entry = database->entries;
            while (entry)
            {
                if (strcmp(entry->name, name) == 0)
                {
                    AnimDatabaseCopyDBEntryToFrameHierarchy
                        (RpClumpGetFrame(clump), entry);
                    RpAnimClumpAddSequence(clump, 
                                           entry->name, 
                                           (RpAnimClumpSequenceCallBack)NULL,
                                           NULL);
                }

                entry = entry->next;
            }
            RWRETURN(database);
        }
        else
        {
            RWERROR((E_RP_ANIM_DATABASE_NOENTRY, name));
            RWRETURN((RpAnimDatabase *) NULL);
        }
    }

    RWRETURN((RpAnimDatabase *) NULL);
}

/**
 * \ingroup rpanim
 * \ref RpAnimDatabaseCopyAllSequencesToClump is used to copy all
 * animation sequences contained in the specified database to the given
 * clump.  The animation plug-in must be attached before using this
 * function.
 *
 * \param database  pointer to the sequence database.
 * \param clump  pointer to the clump.
 *
 * \return pointer to the database if successful,
 *        or NULL if there is an error.
 *
 * \see RpAnimDatabaseCopyClumpSequence
 * \see RpAnimDatabaseCopyAllClumpSequences
 * \see RpAnimDatabaseCopySequenceToClump
 * \see RpAnimDatabaseCopyDatabaseSequence
 * \see RpAnimDatabaseCopyAllDatabaseSequences
 * \see RpAnimPluginAttach
 */
RpAnimDatabase     *
RpAnimDatabaseCopyAllSequencesToClump(RpAnimDatabase * database,
                                      RpClump * clump)
{
    RpAnimDatabaseEntry *entry;

    RWAPIFUNCTION(RWSTRING("RpAnimDatabaseCopyAllSequencesToClump"));

    RWASSERT(AnimStatic.animModule.numInstances);
    RWASSERT(database);
    RWASSERT(clump);

    {
        entry = database->entries;
        while (entry)
        {
            AnimDatabaseCopyDBEntryToFrameHierarchy(RpClumpGetFrame
                                                    (clump), entry);
            RpAnimClumpAddSequence(clump, entry->name, 
                                   (RpAnimClumpSequenceCallBack)NULL,
                                   NULL);
            entry = entry->next;
        }
        RWRETURN(database);
    }

    RWRETURN((RpAnimDatabase *) NULL);
}

/**
 * \ingroup rpanim
 * \ref RpAnimDatabaseStreamGetSize is used to determine the size
 * in bytes of the binary representation of the given animation sequence
 * database. This value is used in the binary chunk header to indicate
 * the size of the chunk. The size does not include the size of the chunk
 * header.  The animation plug-in must be attached before using this
 * function.
 *
 * \param database  Pointer to the sequence database.
 *
 * \return a RwInt32 value equal to the chunk size of the database
 *        in bytes if successful, or zero if there is an error.
 *
 * \see RpAnimDatabaseStreamRead
 * \see RpAnimDatabaseStreamWrite
 * \see RpAnimPluginAttach
 */
RwInt32
RpAnimDatabaseStreamGetSize(RpAnimDatabase * database)
{
    RpAnimDatabaseEntry *entry;
    RwInt32             size = 0;

    RWAPIFUNCTION(RWSTRING("RpAnimDatabaseStreamGetSize"));

    RWASSERT(AnimStatic.animModule.numInstances);
    RWASSERT(database);

    {
        /* numEntries */
        size += sizeof(RwInt32);

        entry = database->entries;

        while (entry)
        {
            RwInt32             i;

            /* name */
            size += rwStringStreamGetSize(entry->name);
            /* numAnimSequences */
            size += sizeof(RwInt32);
            /* tags */
            size += sizeof(RwInt32) * entry->numAnimSequences;
            for (i = 0; i < entry->numAnimSequences; i++)
            {
                size += AnimSequenceSize(entry->animSequences[i]);
            }

            entry = entry->next;
        }

        RWRETURN(size);
    }

    RWRETURN(0);
}

/**
 * \ingroup rpanim
 * \ref RpAnimDatabaseStreamWrite is used to write the specified
 * animation sequence database to the given binary stream. Note that the
 * stream will have been opened prior to this function call.  The
 * animation plug-in must be attached before using this function.
 *
 * \param database  pointer to the sequence database.
 * \param stream  Pointer to the binary stream.
 *
 * \return returns a pointer to the database if successful,
 *        or NULL if there is an error.
 *
 * \see RpAnimDatabaseStreamRead
 * \see RpAnimDatabaseStreamGetSize
 * \see RpAnimPluginAttach
 */
RpAnimDatabase     *
RpAnimDatabaseStreamWrite(RpAnimDatabase * database, RwStream * stream)
{
    RpAnimDatabaseEntry *entry;

    RWAPIFUNCTION(RWSTRING("RpAnimDatabaseStreamWrite"));

    RWASSERT(AnimStatic.animModule.numInstances);
    RWASSERT(database);
    RWASSERT(stream);

    {
        /* This writes a header for the clump without the clump
         * extension data */
        if (!RwStreamWriteChunkHeader
            (stream, rwID_ANIMDATABASE,
             RpAnimDatabaseStreamGetSize(database)))
        {
            RWRETURN((RpAnimDatabase *) NULL);
        }

        if (!RwStreamWriteInt
            (stream, &database->numEntries, sizeof(RwInt32)))
        {
            RWRETURN((RpAnimDatabase *) NULL);
        }

        entry = database->entries;

        while (entry)
        {
            RwInt32             i, bytesWritten;

            rwStringStreamWrite(entry->name, stream);
            RwStreamWriteInt(stream, &entry->numAnimSequences,
                             sizeof(RwInt32));
            RwStreamWriteInt(stream, entry->tagArray,
                             sizeof(RwInt32) * entry->numAnimSequences);
            for (i = 0; i < entry->numAnimSequences; i++)
            {
                AnimSequenceWrite(entry->animSequences[i], stream,
                                  &bytesWritten);
            }

            entry = entry->next;
        }

        RWRETURN(database);
    }

    RWRETURN((RpAnimDatabase *) NULL);
}

/**
 * \ingroup rpanim
 * \ref RpAnimDatabaseStreamRead is used to read an animation
 * sequence database from the specified binary stream. Note that prior to
 * this function call a binary database chunk must be found in the stream
 * using the RwStreamFindChunk API function The animation plug-in must be
 * attached before using this function.
 *
 * \param stream  pointer to the binary stream.
 *
 * \return a pointer to the database if successful or NULL if there is an error.
 *
 * \see RpAnimDatabaseStreamWrite
 * \see RpAnimDatabaseStreamGetSize
 * \see RpAnimPluginAttach
 *
 * The sequence of operations to locate and read an animation database
 * from a binary  stream connected to a disk file is as follows: 
   \verbatim
      RwStream *stream;
      RpAnimDatabase *database;
 
      stream = RwStreamOpen(rwSTREAMFILENAME, rwSTREAMREAD,
                                          "mybinary.xxx");
      if( stream )
      {
          if ( RwStreamFindChunk(stream, rwID_ANIMDATABASE, NULL, NULL) )
          {
                  database = RpAnimDatabaseStreamRead(stream, NULL);
 
                  RwStreamClose(stream, NULL);
          }
      }
    \endverbatim
 */
RpAnimDatabase     *
RpAnimDatabaseStreamRead(RwStream * stream)
{
    RpAnimDatabase     *database;
    RwInt32             entryNum;

    RWAPIFUNCTION(RWSTRING("RpAnimDatabaseStreamRead"));

    RWASSERT(AnimStatic.animModule.numInstances);
    RWASSERT(stream);

    {
        database = RpAnimDatabaseCreate();
        if (database)
        {
            if (!RwStreamReadInt
                (stream, &database->numEntries, sizeof(RwInt32)))
            {
                RWRETURN((RpAnimDatabase *) NULL);
            }

            for (entryNum = 0; entryNum < database->numEntries;
                 entryNum++)
            {
                RwInt32             i, numSequences;
                RwChar             *entryName;
                RpAnimDatabaseEntry *entry;

                entryName = rwStringStreamFindAndRead((RwChar *)NULL, stream);
                RwStreamReadInt(stream, &numSequences, sizeof(RwInt32));

                entry = _rpAnimDatabaseEntryCreate(numSequences);
                if (entry)
                {
                    entry->next = database->entries;
                    database->entries = entry;
                    entry->name = entryName;
                    RwStreamReadInt(stream, entry->tagArray,
                                    sizeof(RwInt32) *
                                    entry->numAnimSequences);
                    for (i = 0; i < entry->numAnimSequences; i++)
                    {
                        entry->animSequences[i] =
                            AnimSequenceRead(stream);
                    }
                }
                else
                {
                    RwFree(entryName);
                }
            }

            RWRETURN(database);
        }
        else
        {
            RWRETURN((RpAnimDatabase *) NULL);
        }
    }

    RWRETURN((RpAnimDatabase *) NULL);
}

/**
 * \ingroup rpanim
 * \ref RpAnimDatabaseCopyDatabaseSequence is used to copy an
 * animation sequence from the given source database to the specified
 * target datatbase. The animation sequence to be copied is identified
 * using the given name.  The animation plug-in must be attached before
 * using this function.
 *
 * \param target  pointer to the target sequence database.
 * \param source  pointer to the source sequence database.
 * \param name  pointer to a string containing the name of the animation sequence.
 *
 * \return  a pointer to the target database if successful,
 *         or NULL if there is an error.
 *
 * \see RpAnimDatabaseCopyClumpSequence
 * \see RpAnimDatabaseCopyAllClumpSequences
 * \see RpAnimDatabaseCopySequenceToClump
 * \see RpAnimDatabaseCopyAllSequencesToClump
 * \see RpAnimDatabaseCopyAllDatabaseSequences
 * \see RpAnimPluginAttach
 */
RpAnimDatabase     *
RpAnimDatabaseCopyDatabaseSequence(RpAnimDatabase * target,
                                   RpAnimDatabase * source,
                                   RwChar * name)
{
    RpAnimDatabaseEntry *entry;

    RWAPIFUNCTION(RWSTRING("RpAnimDatabaseCopyDatabaseSequence"));

    RWASSERT(AnimStatic.animModule.numInstances);
    RWASSERT(target);
    RWASSERT(source);
    RWASSERT(name);

    {

        entry = source->entries;
        while (entry)
        {
            if (rwstrcmp(entry->name, name) == 0 &&
                !RpAnimDatabaseContainsSequence(target, entry->name))
            {
                AnimDatabaseCopyEntry(target, entry);
            }
            else
            {
                RWERROR((E_RP_ANIM_DATABASE_ENTRYALREADYPRESENT, name));
            }

            entry = entry->next;
        }

        RWRETURN(target);
    }

    RWRETURN((RpAnimDatabase *) NULL);
}

/**
 * \ingroup rpanim
 * \ref RpAnimDatabaseCopyAllDatabaseSequences is used to copy
 * all the animation sequences contained in the given source sequence
 * database to the specified target database.  The animation plug-in must
 * be attached before using this function.
 *
 * \param target  pointer to the target sequence database.
 * \param source  pointer to the source sequence database.
 *
 * \return a pointer to the target database if successful,
 *        or NULL if there is an error.
 *
 * \see RpAnimDatabaseCopyClumpSequence
 * \see RpAnimDatabaseCopyAllClumpSequences
 * \see RpAnimDatabaseCopySequenceToClump
 * \see RpAnimDatabaseCopyAllSequencesToClump
 * \see RpAnimDatabaseCopyDatabaseSequence
 * \see RpAnimPluginAttach
 */
RpAnimDatabase     *
RpAnimDatabaseCopyAllDatabaseSequences(RpAnimDatabase * target,
                                       RpAnimDatabase * source)
{
    RpAnimDatabaseEntry *entry;

    RWAPIFUNCTION(RWSTRING("RpAnimDatabaseCopyAllDatabaseSequences"));

    RWASSERT(AnimStatic.animModule.numInstances);
    RWASSERT(target);
    RWASSERT(source);

    {
        entry = source->entries;
        while (entry)
        {
            if (!RpAnimDatabaseContainsSequence(target, entry->name))
            {
                AnimDatabaseCopyEntry(target, entry);
            }
            else
            {
                RWERROR((E_RP_ANIM_DATABASE_ENTRYALREADYPRESENT,
                         entry->name));
            }

            entry = entry->next;
        }

        RWRETURN(target);
    }

    RWRETURN((RpAnimDatabase *) NULL);
}

/**
 * \ingroup rpanim
 * \ref RpAnimDatabaseContainsSequence is used to determine
 * whether the given animation sequence name is present in the specified
 * animation database.  The animation plug-in must be attached before
 * using this function.
 *
 * \param database  pointer to the sequence database.
 * \param name  pointer to a string containing the name of the animation sequence.
 *
 * \return TRUE if database contains sequence, or
 *        FALSE if not or there is an error.
 *
 * \see RpAnimDatabaseCopyClumpSequence
 * \see RpAnimDatabaseCopyAllClumpSequences
 * \see RpAnimDatabaseCopySequenceToClump
 * \see RpAnimDatabaseCopyAllSequencesToClump
 * \see RpAnimDatabaseCopyDatabaseSequence
 * \see RpAnimDatabaseCopyAllDatabaseSequences
 * \see RpAnimPluginAttach
 */
RwBool
RpAnimDatabaseContainsSequence(RpAnimDatabase * database, RwChar * name)
{
    RpAnimDatabaseEntry *entry;

    RWAPIFUNCTION(RWSTRING("RpAnimDatabaseContainsSequence"));

    RWASSERT(AnimStatic.animModule.numInstances);
    RWASSERT(database);
    RWASSERT(name);

    {

        entry = database->entries;

        while (entry)
        {
            if (strcmp(entry->name, name) == 0)
            {
                RWRETURN(TRUE);
            }

            entry = entry->next;
        }
        RWRETURN(FALSE);
    }

    RWRETURN(FALSE);
}

#if defined (__MWERKS__)
#if (defined(RWVERBOSE))
#pragma message (__FILE__ "/" _SKY_EXPAND(__LINE__) ": __MWERKS__ == " _SKY_EXPAND(__MWERKS__))
#endif /* (defined (__MWERKS__)) */
#if (__option (global_optimizer))
#pragma always_inline on
#endif /* (__option (global_optimizer)) */
#endif /*  defined (__MWERKS__) */
