/**********************************************************************
 *
 * File : rphanim.c
 *
 * Abstract : A plugin that performs hiearchical 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
 *
 * See Also : rwsdk/plugin/anim
 *
 **********************************************************************
 *
 * This file is a product of Criterion Software Ltd.
 *
 * This file is provided as is with no warranties of any kind and is
 * provided without any obligation on Criterion Software Ltd. or
 * Canon Inc. to assist in its use or modification.
 *
 * Criterion Software Ltd. will not, under any
 * circumstances, be liable for any lost revenue or other damages arising
 * from the use of this file.
 *
 * Copyright (c) 1999 Criterion Software Ltd.
 * All Rights Reserved.
 *
 * RenderWare is a trademark of Canon Inc.
 *
 ************************************************************************/

/**
 * \ingroup rphanim
 * \page rphanimoverview RpHAnim Plugin Overview
 *
 * The RpHAnim plugin is designed to offer hierarchical animation that can 
 * drive RenderWare \ref RwFrame hierarchies and RenderWare skinned (\ref 
 * RpSkin) atomics.
 * 
 * The main objects managed and used by RpHAnim are the \ref RpHAnimHierarchy
 * and the \ref RpHAnimAnimation. The \ref RpHAnimHierarchy data structure 
 * describes the order of the joints, the shoulder, elbow and so on, that 
 * control the model's animation. Its topology is a tree structure, or 
 * hierarchy of nodes that represent the model's animated joints. The \ref 
 * RpHAnimHierarchy also holds the state of the animation as it is being run.
 * So it stores the current position of each joint as it is interpolated 
 * between key frames at the current point in the animation.
 *
 * A sub-hierarchy can be defined by specifying a node in the main hierarchy.
 * All nodes that are descendants of it (children, grandchildren etc) become 
 * part of this sub-hierarchy. This is useful for running more than one 
 * animation at a time on a single model. The main hierarchy normally has an 
 * animation that can animate the joints of the whole model (or at least the 
 * part of the model not animated by a sub-hierarchy). Any sub-hierarchies can
 * have an animation, and this animation replaces the main hierarchy animation
 * for the sub-hierarchy (i.e. that branch of the main hierarchy) to which it
 * is applied. It does this by overwriting the joint matrices held in the main 
 * hierarchy data structure directly.
 *
 * \ref RpHAnimAnimation's are a block of keyframe data representing an 
 * animation for a particular hierarchy. For each node (i.e. joint) in the 
 * hierarchy (or sub-hierarchy) that the animation is applied to the ANM file 
 * contains a start keyframe, an end keyframe, and any number of intermediate 
 * keyframes as necessary. Each keyframe consists of a quaternion (the 
 * positional information for the joint) and a time, at which the joint is at 
 * exactly that position. This is normally created by loading an ANM file. 
 * They can be run on multiple hierarchies at once as long as all the 
 * hierarchies were created with the same topology. When the joint is at a 
 * time where no keyframe is applied the joint is positioned by linear 
 * interpolation between the two adjacent keyframes. It is possible to use 
 * non-linear interpolations, see overloadable interpolation schemes.
 *
 * \ref RpHAnimHierarchy's can be attached and detached from a standard \ref 
 * RwFrame hierarchy and when attached flags can be set so that the \ref 
 * RwFrame's modeling matrices and/or LTMs are updated when the 
 * animation is being run. This allows the animation system to provide 
 * animation to atomics attached to the \ref RwFrame hierarchy. The \ref 
 * RpHAnimHierarchy also (optionally) maintains an array of matrices held as 
 * either world space or local hierarchy space LTMs. These are used to drive
 * \ref RpSkin atomics as skinned models. A Hierarchy that is attached to \ref
 * RwFrame, updating \ref RwFrame and attached to a skin will provide animation
 * to both atomic hierarchies and skinned atomics whilst only needing to 
 * update a single animation. 
 *
 * RpHAnim also supports the concept of overloadable interpolation 
 * schemes. This requires specifying a custom keyframe type (identified by a 
 * unique ID) and a set of callbacks for manipulating the keyframes. The 
 * callback functions will implement tasks like interpolation and converting 
 * to matrices. Animations are associated with a particular scheme using the
 * ID, and the relevant callbacks will automatically be used whenever an 
 * animation is updated by the standard RpHAnim API interface. To support this,
 * the keyframe types must share a common header giving a pointer to the 
 * previous keyframe, and the time of the keyframe. See \ref 
 * RpHAnimRegisterInterpolationScheme in the API Reference Guide for more 
 * information.
 *
 * Alternatively, the \ref RpHAnimAnimation system may be completely 
 * overridden for animating skinned atomics by accessing the array of 
 * matrices in the \ref RpHAnimHierarchy directly. See \ref 
 * RpHAnimHierarchyGetMatrixArray in the API Reference Guide.
 *
 * \par Quick how-to:
 * -# Export the animated model with HAnim data to create a dff and an ANM
 * file. Do this more than once for more complex animations in which case 
 * there will be more than one dff (you only need one main one) and more than
 * one ANM (animations to play back each hierarchy or subhierarchy).
 * -# Load the dff to create a skinned character with the hierarchy in it.
 * -# If necessary, create any sub-hierarchies, by marking the nodes of the 
 * main hierarchy to be the roots of the sub-hierarchies. This not required 
 * for simple cases.
 * -# Create animation(s) from ANM file(s).
 * -# Attach the animation to the hierarchy or sub-hierarchy it is intended 
 * to animate.
 * -# Start rolling time forward for the hierarchy...
 * -# ...whilst rendering the thing on the screen of course.
 * -# When an animation is finished, load (or use from memory) the next one 
 * and blend from the old one to the new one before starting to play the new 
 * one in earnest. This will involve creating a blend position from the old 
 * and new animations and applying it to the hierarchy.
 */

/**
 * \ingroup rphanimchanges
 * \page rphanimframestonodes Frames Renamed to Nodes
 *
 * In order to make RpHAnim terms clearer and easier to understand the concept
 * of frames has been renamed to nodes. Previously there could be confusion
 * between frames in an \ref RpHAnimHierarchy, \ref RwFrame's which these frames could update
 * and keyframe data for the animation.
 * 
 * An \ref RpHAnimHierarchy is now defined to be a hierarchy of nodes that can be
 * animated for use in skinned and rigid body animation. The documentation,
 * function prototypes and structure names have been updated to reference RwFrames
 * by the explicit name \ref RwFrame and not frame, and to reference nodes as nodes.
 * The changes in structures may cause old applications to fail to link. The
 * changes are as follows.
 *
 * \li \ref RpHAnimHierarchy member numFrames becomes numNodes.
 * \li \ref RpHAnimHierarchy member pFrameInfo becomes pNodeInfo.
 * \li RpHAnimFrameInfo structure (pFrameInfo above) becomes \ref RpHAnimNodeInfo.
 * \li Within \ref RpHAnimNodeInfo frameID becomes nodeID & frameIndex becomes nodeIndex.
 */


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

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

#include "rpplugin.h"
#include <rpdbgerr.h>
#include <rwcore.h>
#include <rpworld.h>

#include <rtquat.h>
#include <rphanim.h>

#if (!defined(DOXYGEN))
static const char rcsid[] __RWUNUSED__ =
    "@@@@(#)$Id: rphanim.c,v 1.78 2001/09/14 15:26:13 rburg Exp $";
#endif /* (!defined(DOXYGEN)) */

#define RPHANIMFRAMEGETDATA(frame)                      \
    ( (void *)(((RwUInt8 *)frame) +                     \
               RpHAnimAtomicGlobals.engineOffset) )

#define RPHANIMFRAMEGETCONSTDATA(frame)                         \
    ( (const void *)(((const RwUInt8 *)frame) +                 \
                     RpHAnimAtomicGlobals.engineOffset) )

/****************************************************************************
 Defines
 */

#if (0 && defined (RWDEBUG) && !defined(MEMORYMESSAGE))
#define MEMORYMESSAGE(_x) RWMESSAGE(_x)
#endif /* (0 && defined (RWDEBUG)) */

#if (!defined(MEMORYMESSAGE))
#define MEMORYMESSAGE(_x)      /* No op */
#endif /* (!defined(MEMORYMESSAGE)) */

/****************************************************************************
 Local types
 */

typedef struct FrameLinkToHierarchyData FrameLinkToHierarchyData;
struct FrameLinkToHierarchyData
{
        RpHAnimHierarchy *pHierarchy;
        RwInt32 id;
};

#define HANIMMATRIXSTACKHEIGHT 32
typedef RwMatrix HAnimMatrixStack[HANIMMATRIXSTACKHEIGHT];

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

#define _EPSILON          ((RwReal)(0.001))
#define _TOL_COS_ZERO     (((RwReal)1) - _EPSILON)
#define RwCosec(_x) \
   ( ((RwReal)1) / RwSin((_x)) )
#define ROUNDUP16(x)      (((RwUInt32)(x) + 16 - 1) & ~(16 - 1))

#if (defined(__R5900__))

/*
 * c.f. rwsdk/driver/sky2/drvmodel.h:RwMatrixMultiplyVUMacro()
 */

#if (!defined(USEINTVAR))
#if (defined(__MWERKS__))
/* Stop CW complaining about variables being unused if they are used, but only in inline asm */
#define USEINTVAR(_var) ((0)?((_var) = 0):(0))
#else /* (defined(__MWERKS__)) */
#define USEINTVAR(_var) /* No op */
#endif /* (defined(__MWERKS__)) */
#endif /* (!defined(USEINTVAR)) */

#define HAnimMatrixMultiplyMacro(_matrix, _matrixIn1, _matrixIn2)       \
MACRO_START                                                             \
{                                                                       \
    int _flags1, _flags2;                                               \
    USEINTVAR(_flags1);                                                 \
    USEINTVAR(_flags2);                                                 \
                                                                        \
    RWASSERT(rwMatrixValidFlags(_matrixIn1, RW_TOL_ORTHONORMAL));       \
    RWASSERT(rwMatrixValidFlags(_matrixIn2, RW_TOL_ORTHONORMAL));       \
                                                                        \
    asm __volatile__ (".set noreorder                                 \n\
        lqc2        vf1,  0x00(%2)                                    \n\
        lqc2        vf2,  0x10(%2)                                    \n\
        lqc2        vf3,  0x20(%2)                                    \n\
        lqc2        vf4,  0x30(%2)                                    \n\
        lwu         %0,   0x0C(%2)                                    \n\
        lqc2        vf5,  0x00(%3)                                    \n\
        lqc2        vf6,  0x10(%3)                                    \n\
        lqc2        vf7,  0x20(%3)                                    \n\
        lqc2        vf8,  0x30(%3)                                    \n\
        lwu         %1,   0x0C(%3)                                    \n\
        vmulax.xyz  ACC,  vf5, vf1                                    \n\
        vmadday.xyz ACC,  vf6, vf1                                    \n\
        vmaddz.xyz  vf9,  vf7, vf1                                    \n\
        vmulax.xyz  ACC,  vf5, vf2                                    \n\
        vmadday.xyz ACC,  vf6, vf2                                    \n\
        vmaddz.xyz  vf10, vf7, vf2                                    \n\
        vmulax.xyz  ACC,  vf5, vf3                                    \n\
        vmadday.xyz ACC,  vf6, vf3                                    \n\
        vmaddz.xyz  vf11, vf7, vf3                                    \n\
        vmulax.xyz  ACC,  vf5, vf4                                    \n\
        vmadday.xyz ACC,  vf6, vf4                                    \n\
        vmaddaz.xyz ACC,  vf7, vf4                                    \n\
        vmaddw.xyz  vf12, vf8, vf0                                    \n\
        and         %1, %1, %0                                        \n\
        sqc2           vf9,  0x00(%4)                                \n\
        sqc2           vf10, 0x10(%4)                                \n\
        sqc2           vf11, 0x20(%4)                                \n\
        sqc2           vf12, 0x30(%4)                                \n\
        sw          %1, 0x0C(%4)                                      \n\
        .set reorder "                                                  \
         : "=r&" (_flags1), "=r&" (_flags2) : "r" (_matrixIn1),         \
           "r" (_matrixIn2), "r" (_matrix) : "cc",  "memory");          \
                                                                        \
    RWASSERT(rwMatrixValidFlags(_matrix, RW_TOL_ORTHONORMAL));          \
                                                                        \
}                                                                       \
MACRO_STOP

#endif /* (defined(__R5900__)) */

#if (!defined(HAnimMatrixMultiplyMacro))

#define HAnimMatrixMultiplyMacro(_matrix, _matrixIn1, _matrixIn2)       \
MACRO_START                                                             \
{                                                                       \
    RWASSERT(rwMatrixValidFlags(_matrixIn1, RW_TOL_ORTHONORMAL));       \
    RWASSERT(rwMatrixValidFlags(_matrixIn2, RW_TOL_ORTHONORMAL));       \
                                                                        \
    RwMatrixMultiply(_matrix, _matrixIn1, _matrixIn2);                  \
                                                                        \
    RWASSERT(rwMatrixValidFlags(_matrix, RW_TOL_ORTHONORMAL));          \
}                                                                       \
MACRO_STOP

#endif /* (!defined(HAnimMatrixMultiplyMacro)) */

/****************************************************************************
 Local (static) globals
 */

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

#define RPHANIMINTERPOLATORINFOBLOCKMAXENTRIES 16
static RwInt32 RpHAnimInterpolatorInfoBlockNumEntries = 0;
static RpHAnimInterpolatorInfo
    RpHAnimInterpolatorInfoBlock[RPHANIMINTERPOLATORINFOBLOCKMAXENTRIES];

/****************************************************************************
 Globals
 */

RpHAnimAtomicGlobalVars RpHAnimAtomicGlobals;

/****************************************************************************
 Local Function Prototypes
 */

/****************************************************************************
 Functions
 */

static RwFrame *
HAnimFrameLinkToHierarchy(RwFrame *rootFrame, void *pData)
{
    FrameLinkToHierarchyData *fdata = (FrameLinkToHierarchyData *)pData;
    RpHAnimHierarchy *pHierarchy = fdata->pHierarchy;
    RwInt32 i;

    RWFUNCTION(RWSTRING("HAnimFrameLinkToHierarchy"));
    RWASSERT(rootFrame);
    RWASSERT(pData);

    if (RpHAnimFrameGetID(rootFrame) == fdata->id)
    {
        for (i=0; i<pHierarchy->numNodes; i++)
        {
            if (pHierarchy->pNodeInfo[i].nodeID == fdata->id)
            {
                pHierarchy->pNodeInfo[i].pFrame = rootFrame;
            }
        }
    }

    RwFrameUpdateObjects(rootFrame);
    RwFrameForAllChildren(rootFrame, HAnimFrameLinkToHierarchy, pData);

    RWRETURN(rootFrame);
}

static RwFrame *
HAnimFramesLinkToHierarchy(RwFrame *rootFrame, void *pData)
{
    RpHAnimHierarchy *pHierarchy = (RpHAnimHierarchy *)pData;
    int i;
    RWFUNCTION(RWSTRING("HAnimFramesLinkToHierarchy"));
    RWASSERT(rootFrame);
    RWASSERT(pData);

    for (i=0; i<pHierarchy->numNodes; i++)
    {
        if (RpHAnimFrameGetID(rootFrame) == pHierarchy->pNodeInfo[i].nodeID)
        {
            pHierarchy->pNodeInfo[i].pFrame = rootFrame;
        }
    }

    RwFrameUpdateObjects(rootFrame);
    RwFrameForAllChildren(rootFrame, HAnimFramesLinkToHierarchy, pData);

    RWRETURN(rootFrame);
}

/*----------------------------------------------------------------------*/

/*                              - ENGINE -                              */

/*----------------------------------------------------------------------*/

static void        *
HAnimOpen(void *instance,
          RwInt32 __RWUNUSED__ offset,
          RwInt32 __RWUNUSED__ size)
{
    RpHAnimInterpolatorInfo     interpInfo;

    RWFUNCTION(RWSTRING("HAnimOpen"));
    RWASSERT(instance);

    RpHAnimAtomicGlobals.HAnimFreeList = (RwFreeList *)NULL;

    {
        RpHAnimAtomicGlobals.HAnimAnimationFreeList =
            RwFreeListCreate(sizeof(RpHAnimAnimation), 20, 0);

        if (NULL == RpHAnimAtomicGlobals.HAnimAnimationFreeList)
        {
            instance = NULL;
        }
    }

    /* setup our default interpolation scheme */
    interpInfo.typeID = rpHANIMSTDKEYFRAMETYPEID;
    interpInfo.keyFrameSize = sizeof(RpHAnimStdKeyFrame);
    interpInfo.keyFrameToMatrixCB = RpHAnimStdKeyFrameToMatrix;
    interpInfo.keyFrameBlendCB = RpHAnimStdKeyFrameBlend;
    interpInfo.keyFrameInterpolateCB = RpHAnimStdKeyFrameInterpolate;
    interpInfo.keyFrameAddCB = RpHAnimStdKeyFrameAdd;
    interpInfo.keyFrameMulRecipCB = RpHAnimStdKeyFrameMulRecip;
    interpInfo.keyFrameStreamReadCB = RpHAnimStdKeyFrameStreamRead;
    interpInfo.keyFrameStreamWriteCB = RpHAnimStdKeyFrameStreamWrite;
    interpInfo.keyFrameStreamGetSizeCB = RpHAnimStdKeyFrameStreamGetSize;

    RpHAnimRegisterInterpolationScheme(&interpInfo);

    RWRETURN(instance);

}

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

    /* "Deregister" all the interpolation schemes */
    RpHAnimInterpolatorInfoBlockNumEntries = 0;

    if (NULL != RpHAnimAtomicGlobals.HAnimAnimationFreeList)
    {
        RwFreeListDestroy(RpHAnimAtomicGlobals.HAnimAnimationFreeList);
        RpHAnimAtomicGlobals.HAnimAnimationFreeList = (RwFreeList *)NULL;
    }

    if (NULL != RpHAnimAtomicGlobals.HAnimFreeList)
    {
        RwFreeListDestroy(RpHAnimAtomicGlobals.HAnimFreeList);
        RpHAnimAtomicGlobals.HAnimFreeList = (RwFreeList *)NULL;
    }

    RWRETURN(instance);
}

/*----------------------------------------------------------------------*/

/*                             - ATOMIC -                               */

/*----------------------------------------------------------------------*/

static void        *
HAnimConstructor(void *object,
                 RwInt32 __RWUNUSED__ offset,
                 RwInt32 __RWUNUSED__ size)
{
    RpHAnimFrameExtension *frameExt;
    RWFUNCTION(RWSTRING("HAnimConstructor"));
    RWASSERT(object);

    frameExt = (RpHAnimFrameExtension *)RPHANIMFRAMEGETDATA(object);
    frameExt->hierarchy = (RpHAnimHierarchy *)NULL;
    frameExt->id = -1;

    RWRETURN(object);
}

static void        *
HAnimDestructor(void *object,
                RwInt32 __RWUNUSED__ offset,
                RwInt32 __RWUNUSED__ size)
{
    RpHAnimFrameExtension *frameExt;

    RWFUNCTION(RWSTRING("HAnimDestructor"));
    RWASSERT(object);

    frameExt = (RpHAnimFrameExtension *)RPHANIMFRAMEGETDATA(object);
    if (frameExt->hierarchy)
    {
        RpHAnimHierarchy *pHierarchy = frameExt->hierarchy;
        RwInt32 frameNum;
        /* run through all the frames this hierarchy
           points at since they also point back at the
           hierarchy. Set the pointers to NULL and then
           destroy the hierarchy */
        for (frameNum = 0; frameNum < pHierarchy->numNodes; frameNum++)
        {
            if (pHierarchy->pNodeInfo[frameNum].pFrame)
            {
                /* TODO:
                   In here we ultimately check if this pointer is a sub-hierarchy
                   root, if so destroy the sub-hierarchy */
            }

            pHierarchy->pNodeInfo[frameNum].pFrame = (RwFrame *)NULL;
        }

        if (pHierarchy->parentFrame == (RwFrame *)object)
        {
            RpHAnimHierarchyDestroy(pHierarchy);
        }

        frameExt->hierarchy = (RpHAnimHierarchy *)NULL;
    }
    frameExt->id = -1;

    RWRETURN(object);
}

static void        *
HAnimCopy(void *dstObject,
          const void * srcObject,
          RwInt32 __RWUNUSED__ offset,
          RwInt32 __RWUNUSED__ size)
{
    const RpHAnimFrameExtension *srcFrameExt;
    RpHAnimFrameExtension *dstFrameExt;

    RWFUNCTION(RWSTRING("HAnimCopy"));
    RWASSERT(srcObject);
    RWASSERT(dstObject);

    srcFrameExt = (const RpHAnimFrameExtension *)
        RPHANIMFRAMEGETCONSTDATA(srcObject);
    dstFrameExt = (RpHAnimFrameExtension *)
        RPHANIMFRAMEGETDATA(dstObject);

    dstFrameExt->id = srcFrameExt->id;
    if (srcFrameExt->hierarchy)
    {
        RpHAnimHierarchy *srcHierarchy = srcFrameExt->hierarchy;
        if (!(srcHierarchy->flags & rpHANIMHIERARCHYSUBHIERARCHY))
        {
            RpHAnimHierarchy *dstHierarchy;
            RwInt32 i;

            dstHierarchy =
                RpHAnimHierarchyCreate(srcHierarchy->numNodes,
                                       (RwUInt32 *)NULL,
                                       (RwInt32 *)NULL,
                                       (RpHAnimHierarchyFlag)srcHierarchy->flags,
                                       srcHierarchy->maxKeyFrameSize);

            for(i=0; i<dstHierarchy->numNodes; i++)
            {
                dstHierarchy->pNodeInfo[i].pFrame = (RwFrame *)NULL;
                dstHierarchy->pNodeInfo[i].flags = srcHierarchy->pNodeInfo[i].flags;
                dstHierarchy->pNodeInfo[i].nodeIndex = srcHierarchy->pNodeInfo[i].nodeIndex;
                dstHierarchy->pNodeInfo[i].nodeID = srcHierarchy->pNodeInfo[i].nodeID;
            }

            dstFrameExt->hierarchy = dstHierarchy;
            dstHierarchy->parentFrame = (RwFrame *)dstObject;
        }
    }

    RWRETURN(dstObject);
}

/****************************************************************************
 HAnimWrite

 Writes animation hierarchy data to a stream

 Inputs :   RwStream *  Stream to write to
            RwInt32     Size of meshed when serialised (in bytes) (not used)
            void *      Object (atomic)
            RwInt32     Plugin data offset (not used)
            RwInt32     Plugin data size (not used)
 Outputs:   RwStream *  Stream pointer on success
 */

static RwStream    *
HAnimWrite(RwStream * stream,
           RwInt32 __RWUNUSED__ binaryLength,
           const void *object,
           RwInt32 __RWUNUSED__ offsetInObject,
           RwInt32 __RWUNUSED__  sizeInObject)
{
    RwInt32             i;
    const RpHAnimFrameExtension *frameExt;
    RpHAnimNodeInfo     *pNodeInfo;
    RpHAnimHierarchy    *animHierarchy;
    RwInt32              streamVersion = rpHANIMSTREAMCURRENTVERSION;

    RWFUNCTION(RWSTRING("HAnimWrite"));
    RWASSERT(stream);
    RWASSERT(object);

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

    frameExt = (const RpHAnimFrameExtension *)RPHANIMFRAMEGETCONSTDATA(object);

    if (!RwStreamWriteInt
        (stream, (const RwInt32 *) &frameExt->id, sizeof(RwInt32)))
    {
        RWRETURN((RwStream *)NULL);
    }

    animHierarchy = frameExt->hierarchy;

    /* is this a valid root hierarchy */
    if (animHierarchy &&
        !(animHierarchy->flags & rpHANIMHIERARCHYSUBHIERARCHY))
    {
        /* Output hierarchy paramters - numNodes, totalVertices, vertex weights, vertex matrix indices */
        if (!RwStreamWriteInt
            (stream, (const RwInt32 *) &animHierarchy->numNodes,
             sizeof(RwInt32)))
        {
            RWRETURN((RwStream *)NULL);
        }

        /* write out the flags */
        if (!RwStreamWriteInt
            (stream, (const RwInt32 *) &animHierarchy->flags,
             sizeof(RwInt32)))
        {
            RWRETURN((RwStream *)NULL);
        }

        /* write out the maxKeyFrameSize */
        if (!RwStreamWriteInt
            (stream, (const RwInt32 *) &animHierarchy->maxKeyFrameSize,
             sizeof(RwInt32)))
        {
            RWRETURN((RwStream *)NULL);
        }

        /* Write the bone info */

        pNodeInfo = animHierarchy->pNodeInfo;

        for (i = 0; i < animHierarchy->numNodes; i++)
        {
            if (!RwStreamWriteInt
                (stream, (RwInt32 *) & pNodeInfo->nodeID,
                 sizeof(RwInt32)))
            {
                RWRETURN((RwStream *)NULL);
            }

            if (!RwStreamWriteInt
                (stream, (RwInt32 *) & pNodeInfo->nodeIndex,
                 sizeof(RwInt32)))
            {
                RWRETURN((RwStream *)NULL);
            }

            if (!RwStreamWriteInt
                (stream, (RwInt32 *) & pNodeInfo->flags,
                 sizeof(RwInt32)))
            {
                RWRETURN((RwStream *)NULL);
            }

            pNodeInfo++;
        }

    }
    else
    {
        RwInt32 numNodes = 0;

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

    RWRETURN(stream);
}

/****************************************************************************
 HAnimRead

 Reads a frames animation hierarchy data from a stream

 Inputs :   RwStream *  Stream to read from
            RwInt32     Size of animation hierarchy data (in bytes)
            void *      Object (atomic)
            RwInt32     Plugin data offset (not used)
            RwInt32     Plugin data size (not used)
 Outputs:   RwStream *  Stream pointer on success
 */

static RwStream    *
HAnimRead(RwStream * stream,
          RwInt32 __RWUNUSED__ binaryLength,
          void *object,
          RwInt32 __RWUNUSED__ offsetInObject,
          RwInt32 __RWUNUSED__ sizeInObject)
{
    RwInt32             i;
    RwInt32             numNodes;
    RwInt32             streamVersion;
    RpHAnimNodeInfo     *pNodeInfo;
    RpHAnimHierarchy      *pHierarchy;
    RpHAnimFrameExtension *frameExt = (RpHAnimFrameExtension *)RPHANIMFRAMEGETDATA(object);

    RWFUNCTION(RWSTRING("HAnimRead"));
    RWASSERT(stream);
    RWASSERT(object);

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

    if (streamVersion != 0x100)
    {
        RWRETURN((RwStream *)NULL);
    }

    if (!RwStreamReadInt
        (stream, (RwInt32 *) &frameExt->id, sizeof(RwInt32)))
    {
        RWRETURN((RwStream *)NULL);
    }

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

    if (numNodes > 0)
    {
        RpHAnimHierarchyFlag flags;
        RwInt32 maxKeyFrameSize;

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

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

        /* we know the maxKeyFrameSize now so allocate a structure of appropriate
           size */
        pHierarchy = (RpHAnimHierarchy *) RwMalloc(sizeof(RpHAnimHierarchy) +
                                                   (numNodes * maxKeyFrameSize * 3));
        memset(pHierarchy, 0, sizeof(RpHAnimHierarchy) +
               (numNodes * maxKeyFrameSize * 3));
        RWASSERT(pHierarchy);

        if (pHierarchy)
        {
            void *ptr = NULL;

            pHierarchy->flags = flags;
            pHierarchy->parentFrame = (RwFrame *)object;
            /* Input hierarchy paramters - numNodes, totalVertices, vertex weights, vertex matrix indices */
            pHierarchy->numNodes = numNodes;

            /* We'll always default the current key size to the max size */
            pHierarchy->currentKeyFrameSize = maxKeyFrameSize;
            pHierarchy->maxKeyFrameSize = maxKeyFrameSize;

            /* And root hierarchies point to themselves, we don't
               yet support streaming sub-hierarachies so this must be a root */
            pHierarchy->parentHierarchy = pHierarchy;
            pHierarchy->offsetInParent = 0;

            /* Read the bone info */

            if (pHierarchy->flags & rpHANIMHIERARCHYNOMATRICES)
            {
                pHierarchy->pMatrixArray = (RwMatrix *) NULL;
                pHierarchy->pMatrixArrayUnaligned = NULL;
            }
            else
            {
                ptr = RwMalloc(sizeof(RwMatrix) * numNodes + 15);
                pHierarchy->pMatrixArray = (RwMatrix *) ROUNDUP16(ptr);
                pHierarchy->pMatrixArrayUnaligned = ptr;
            }

            pHierarchy->pNodeInfo = (RpHAnimNodeInfo *) RwMalloc(sizeof(RpHAnimNodeInfo) * numNodes);

            pNodeInfo = pHierarchy->pNodeInfo;

            for (i = 0; i < pHierarchy->numNodes; i++)
            {
                if (!RwStreamReadInt
                    (stream, (RwInt32 *) & pNodeInfo->nodeID,
                     sizeof(RwInt32)))
                {
                    RWRETURN((RwStream *)NULL);
                }

                if (!RwStreamReadInt
                    (stream, (RwInt32 *) & pNodeInfo->nodeIndex,
                     sizeof(RwInt32)))
                {
                    RWRETURN((RwStream *)NULL);
                }

                if (!RwStreamReadInt
                    (stream, (RwInt32 *) & pNodeInfo->flags,
                     sizeof(RwInt32)))
                {
                    RWRETURN((RwStream *)NULL);
                }

                pNodeInfo->pFrame = (RwFrame *)NULL;

                pNodeInfo++;
            }

        }
        frameExt->hierarchy = pHierarchy;
    }

    RWRETURN(stream);
}

/****************************************************************************
 HAnimSize

 Gets the size of a frames animation hierarchy (when serialized)

 Inputs :   void *      Object (atomic)
            RwInt32     Plugin data offset (not used)
            RwInt32     Plugin data size (not used)
 Outputs:   RwInt32     Size of mesh when serialised (in bytes)
 */

static              RwInt32
HAnimSize(const void *object,
          RwInt32 __RWUNUSED__ offsetInObject,
          RwInt32 __RWUNUSED__ sizeInObject)
{
    RwInt32             size = 0;
    const RpHAnimFrameExtension *frameExt = (const RpHAnimFrameExtension *)
        RPHANIMFRAMEGETCONSTDATA(object);

    RWFUNCTION(RWSTRING("HAnimSize"));

    size = sizeof(RwInt32);        /* Stream version no */
    size += sizeof(RwInt32);       /* frame ID */
    size += sizeof(RwInt32);       /* Num frames in hierarchy (0 no hierarchy) */

    if (frameExt->hierarchy &&
        !(frameExt->hierarchy->flags & rpHANIMHIERARCHYSUBHIERARCHY))
    {
        size += sizeof(RwInt32);   /* Hierarchy flags for recreation */
        size += sizeof(RwInt32);   /* Hierarchy max keyframe size */
        size +=
            frameExt->hierarchy->numNodes * (sizeof(RwInt32) * 3);
    }

    RWRETURN(size);
}

#if (defined(RWDEBUG))

/**
 * \ingroup rphanim
 * \ref RpHAnimHierarchySetFlags
 * allows you to set flags defining how the \ref RpHAnimHierarchy was created
 * and how it should be updated. Only the flags defining update modes should
 * be changed after creation.
 *
 * \param pHierarchy A pointer to the hierarchy to set the flags on
 * \param flags Flags to set on the hierarchy
 *
 * \return A pointer to the hierarchy on success
 *
 * \see RpHAnimHierarchyGetFlags
 */
RpHAnimHierarchy *
RpHAnimHierarchySetFlags(RpHAnimHierarchy *pHierarchy,
                         RpHAnimHierarchyFlag flags)
{
    RWAPIFUNCTION(RWSTRING("RpHAnimHierarchySetFlags"));
    RWASSERT(pHierarchy);

    RpHAnimHierarchySetFlagsMacro(pHierarchy, flags);

    RWRETURN(pHierarchy);
}

/**
 * \ingroup rphanim
 * \ref RpHAnimHierarchyGetFlags
 * allows you flags defining how the \ref RpHAnimHierarchy was created and how
 * it should be updated. Only the flags defining update modes should be changed
 * after creation.
 *
 * \param pHierarchy
 *
 * \return the flags set on the hierarchy
 *
 * \see RpHAnimHierarchySetFlags
 */
RpHAnimHierarchyFlag
RpHAnimHierarchyGetFlags(RpHAnimHierarchy *pHierarchy)
{
    RpHAnimHierarchyFlag result;

    RWAPIFUNCTION(RWSTRING("RpHAnimHierarchyGetFlags"));
    RWASSERT(pHierarchy);

    result = (RpHAnimHierarchyFlag)
        RpHAnimHierarchyGetFlagsMacro(pHierarchy);

    RWRETURN(result);
}

/**
 * \ingroup rphanim
 * \ref RpHAnimFrameInterpolate
 *  interpolates between two keyframes
 * and returns the result.
 *
 * \param pHierarchy A pointer to the hierarchy in use
 * \param pOut A pointer to the output keyframe
 * \param pIn1 A pointer to the first input keyframe
 * \param pIn2 A pointer to the second input keyframe
 * \param time The time to which to interpolate
 *
 * \return None
 */

void
RpHAnimFrameInterpolate(RpHAnimHierarchy *pHierarchy,
                        void * pOut, void * pIn1,
                        void * pIn2, RwReal time)
{
    RWAPIFUNCTION(RWSTRING("RpHAnimFrameInterpolate"));
    RWASSERT(pHierarchy);

    RWASSERT(pHierarchy->keyFrameInterpolateCB);
    pHierarchy->keyFrameInterpolateCB(pOut, pIn1, pIn2, time);

    RWRETURNVOID();
}

/**
 * \ingroup rphanim
 * \ref RpHAnimFrameBlend
 *  Blends between two keyframes using a given
 * blend factor.
 *
 * \param pHierarchy A pointer to the hierarchy in use
 * \param pOut A pointer to the output keyframe.
 * \param pIn1 A pointer to the first input keyframe.
 * \param pIn2 A pointer to the second input keyframe.
 * \param fAlpha The blending factor.
 *
 * \return None
 */
void
RpHAnimFrameBlend(RpHAnimHierarchy *pHierarchy,
                  void * pOut,
                  void * pIn1,
                  void * pIn2,
                  RwReal fAlpha)
{
    RWAPIFUNCTION(RWSTRING("RpHAnimFrameBlend"));
    RWASSERT(pHierarchy);

    RWASSERT(pHierarchy->keyFrameBlendCB);
    pHierarchy->keyFrameBlendCB(pOut, pIn1, pIn2, fAlpha);

    RWRETURNVOID();
}

/**
 * \ingroup rphanim
 * \ref RpHAnimFrameToMatrix
 *  Converts a keyframe structure into an RwMatrix
 *
 * \param pHierarchy A pointer to the hierarchy in use
 * \param pMatrix A pointer to the output pMatrix.
 * \param pIFrame A pointer to the input keyframe.
 *
 * \return None
 */
void
RpHAnimFrameToMatrix(RpHAnimHierarchy *pHierarchy,
                     RwMatrix * pMatrix, void * pIFrame)
{
    RWAPIFUNCTION(RWSTRING("RpHAnimFrameToMatrix"));
    RWASSERT(pHierarchy);
    RWASSERT(pMatrix);
    RWASSERT(pIFrame);

    RWASSERT(pHierarchy->keyFrameToMatrixCB);
    pHierarchy->keyFrameToMatrixCB(pMatrix, pIFrame);

    RWRETURNVOID();
}

/**
 * \ingroup rphanim
 * \ref RpHAnimFrameAddTogether
 *  Adds together two keyframes
 *
 * \param pHierarchy A pointer to the hierarchy in use
 * \param pOut A pointer to the output summed keyframe.
 * \param pIn1 A pointer to the first input keyframe.
 * \param pIn2 A pointer to the second input keyframe.
 *
 * \return None
 */
void
RpHAnimFrameAddTogether(RpHAnimHierarchy *pHierarchy,
                        void * pOut, void *pIn1, void *pIn2)
{
    RWAPIFUNCTION(RWSTRING("RpHAnimFrameAddTogether"));
    RWASSERT(pHierarchy);
    RWASSERT(pOut);
    RWASSERT(pIn1);
    RWASSERT(pIn2);

    RWASSERT(pHierarchy->keyFrameAddCB);
    pHierarchy->keyFrameAddCB(pOut, pIn1, pIn2);

    RWRETURNVOID();
}


/**
 * \ingroup rphanim
 * \ref RpHAnimAnimationGetTypeID
 *
 *
 * Returns the typeID of an animation
 *
 * \param pHAnimation A pointer to the animation.
 *
 * \return TypeID of the animation passed in.
 *
 */
RwInt32
RpHAnimAnimationGetTypeID(RpHAnimAnimation *pHAnimation)
{
    RwInt32 typeID;

    RWAPIFUNCTION(RWSTRING("RpHAnimAnimationGetTypeID"));
    RWASSERT(pHAnimation);

    typeID = pHAnimation->interpInfo->typeID;

    RWRETURN(typeID);
}

#endif /* (defined(RWDEBUG)) */

/**
 * \ingroup rphanim
 * \ref RpHAnimRegisterInterpolationScheme
 * registers a keyframe interpolation scheme. This may be used by animations
 * in place of the standard keyframe system. The function must be called before
 * any animations using the scheme are created or read from a stream.
 *
 * An \ref RpHAnimInterpolatorInfo structure should be filled with the type ID
 * of the scheme, the size of the keyframe structure and a list of
 * callbacks for processing keyframes, before being passed to this function.
 *
 * The type ID must be unique and it is recommended that values in the
 * range 1 to 255 are reserved for internal use by RenderWare. It is suggested
 * that developers construct unique IDs using MAKECHUNKID(vendorID, typeID).
 * An animation using the scheme may be created by passing in the type ID.
 *
 * The keyframe structure should always begin with a standard header
 * (\ref RpHAnimKeyFrameHeader), ie the first 4 bytes hold a pointer to the
 * previous keyframe for the particular hierarchy node, and the second 4
 * bytes hold the time of the keyframe. This allows the keyframe type to
 * function with \ref RpHAnimHierarchyAddAnimTime etc. The header size should
 * be included in the keyFrameSize entry.
 *
 * See \ref RpHAnimInterpolatorInfo for information about the callbacks.
 *
 * \param interpInfo    Pointer to a data structure containing the
 *                      interpolator information. This information will be
 *                      copied to an internal data block.
 *
 * \return TRUE if successful, or FALSE if an error occurred.
 *
 * \see RpHAnimGetInterpolatorInfo
 */
RwBool
RpHAnimRegisterInterpolationScheme(RpHAnimInterpolatorInfo *interpInfo)
{
    RWAPIFUNCTION(RWSTRING("RpHAnimRegisterInterpolationScheme"));
    RWASSERT(interpInfo);
    RWASSERT(interpInfo->keyFrameSize >= 0);

    if (RpHAnimInterpolatorInfoBlockNumEntries <
            RPHANIMINTERPOLATORINFOBLOCKMAXENTRIES)
    {
        RwInt32    i;

        /* Check that the ID isn't already in use */
        for (i=0; i<RpHAnimInterpolatorInfoBlockNumEntries; i++)
        {
            if (RpHAnimInterpolatorInfoBlock[i].typeID == interpInfo->typeID)
            {
                RWERROR((E_RP_HANIM_INTERP_IDINUSE));
                RWRETURN(FALSE);
            }
        }

        RpHAnimInterpolatorInfoBlock[RpHAnimInterpolatorInfoBlockNumEntries]
            = *interpInfo;
        RpHAnimInterpolatorInfoBlockNumEntries++;

        RWRETURN(TRUE);
    }

    RWERROR((E_RP_HANIM_INTERP_BLOCKFULL));
    RWRETURN(FALSE);
}

/**
 * \ingroup rphanim
 * \ref RpHAnimGetInterpolatorInfo
 * retrieves the information for the interpolation scheme of the given
 * type ID. The scheme must first have been registered using
 * RpHAnimRegisterInterpolationScheme, unless it is provided as standard.
 * The standard keyframe system has type rpHANIMSTDKEYFRAMETYPEID.
 *
 * \param typeID    The type ID of the interpolation scheme.
 *
 * \return A pointer to the interpolator information, or NULL if no
 *         scheme is registered under the given ID.
 *
 * \see RpHAnimRegisterInterpolationScheme
 */
RpHAnimInterpolatorInfo *
RpHAnimGetInterpolatorInfo(RwInt32 typeID)
{
    RwInt32     i;

    RWAPIFUNCTION(RWSTRING("RpHAnimGetInterpolatorInfo"));

    /* Lookup the typeID in the InterpolatorInfoBlock */
    for (i=0; i<RpHAnimInterpolatorInfoBlockNumEntries; i++)
    {
        if (RpHAnimInterpolatorInfoBlock[i].typeID == typeID)
        {
            RWRETURN(&RpHAnimInterpolatorInfoBlock[i]);
        }
    }

    /* Unregistered animation type */
    RWERROR((E_RP_HANIM_INTERP_IDUNKNOWN));
    RWRETURN((RpHAnimInterpolatorInfo *)NULL);
}

/**
 * \ingroup rphanim
 * \ref RpHAnimHierarchyCreate
 * creates and returns a standard RpHAnimHierarchy . This takes the number of
 * nodes in the hierarchy and arrays of push/pop node flags and node IDs. It also
 * takes hierarchy flags and a maximum keyframe size.
 *
 * The flags are used to define certain creation time aspects and update aspects
 * of a hierarchy (see below for description)
 *
 * \param numNodes The number of nodes required in the hierarchy.
 * \param nodeFlags The push/pop flags should be setup such that any node with
 * no children has a POP flag and all but the last sibling of a level of nodes
 * has a push flag.
 * \param nodeIDs The nodeIDs are one per node and should match up to the ids
 * set on RwFrames with \ref RpHAnimFrameSetID, if these are setup matching
 * \ref RpHAnimHierarchyAttach is able to attach all the RwFrames to allow their update.
 * \param flags
 * \param maxKeyFrameSize Defines the size of the biggest type of keyframe you
 * wish to use on this hierarchy, currently there is only one keyframe type whose
 * size is rpHANIMSTDKEYFRAMESIZE. Overloaded interpolation schemes will be available
 * which may use larger more complex schemes or support compressed data.
 *
 * The flags include:
 *
 * Creation time flags:
 *
 * \li rpHANIMHIERARCHYSUBHIERARCHY This defines that this hierarchy is a sub-hierarchy,
 * meaning that it represents a branch of a full hierarchy. The sub-hierarchy is linked
 * to the main hierarchy by the matrix array. This means the sub-hierarchy holds it's own
 * arrays of interpolated keys and only updates the main hierarchy when you update the
 * sub-hierarchy matrix array.
 * \li rpHANIMHIERARCHYNOMATRICES this flag specifies that the hierarchy should be created
 * with no local matrix array. This type of hierarchy will directly update an RwFrame hierarchy
 * but not store extra matrices. Skinning will then extract the matrices it needs from RwFrame,
 * this is slightly less efficient but will save memory.
 *
 * Update flags
 *
 * \li rpHANIMHIERARCHYUPDATEMODELLINGMATRICES
 * \li rpHANIMHIERARCHYUPDATELTMS
 * These two flags specify whether a hierarchy should update the modeling matrices and/or
 * LTM's in an RwFrame hierarchy, for the RwFrames to be updated you must first attach the hierarchy
 * using \ref RpHAnimHierarchyAttach. The choice of which matrices in RwFrames to update is down to the
 * applications use of the results. If  you are likely to modify the RwFrames yourself forcing
 * hierarchies to be resynched it's important to update the modeling matrices since otherwise
 * the resynched hierarchies will contain the wrong LTM's. If you are going to modify lots of
 * the hierarchy or root nodes it's probably sensible just to update the modeling matrices.
 * If you plan on only updating a few lower down matrices (procedural IK on arms/legs etc)
 * you can update both sets of matrices this way only those you procedurally modify will be
 * resynched. If you can guarantee no updates between animation and usage of the RwFrames in
 * rendering you can update only the LTMs.
 * \li  rpHANIMHIERARCHYLOCALSPACEMATRICES  this flag causes the local copy of
 * the matrices to be updated as LTM's relative to the \ref RwFrame the
 * hierarchy is attached to. This will provide the most optimal method
 * for animating skinned objects with \ref RpSkin but will add slightly more
 * complication to any procedural animation you may wish to apply.
 *
 *
 * \return A pointer to the new hierarchy on success, NULL if there is an error.
 *
 */

RpHAnimHierarchy     *
RpHAnimHierarchyCreate(RwInt32 numNodes, RwUInt32 *nodeFlags, RwInt32 *nodeIDs,
                       RpHAnimHierarchyFlag flags, RwInt32 maxKeyFrameSize)
{
    void               *ptr;
    RpHAnimHierarchy    *pHierarchy;
    int                 node;

    RWAPIFUNCTION(RWSTRING("RpHAnimHierarchyCreate"));
    RWASSERT(numNodes >= 1);

    if (!(numNodes >= 1))
    {
        RWRETURN((RpHAnimHierarchy *)NULL);
    }

    ptr = RwMalloc(sizeof(RpHAnimHierarchy) +
                   (numNodes * maxKeyFrameSize * 3));

    pHierarchy = (RpHAnimHierarchy *) ptr;
    pHierarchy->flags = flags;
    pHierarchy->numNodes = numNodes;
    pHierarchy->pCurrentAnim = (RpHAnimAnimation *)NULL;
    pHierarchy->pNextFrame = NULL;
    pHierarchy->currentTime = 0.0f;
    pHierarchy->parentFrame = (RwFrame *)NULL;

    pHierarchy->pAnimCallBack = (RpHAnimHierarchy * (*)(RpHAnimHierarchy *, void *))NULL;
    pHierarchy->animCallBackTime = (RwReal) - 1.0;
    pHierarchy->pAnimCallBackData = NULL;
    pHierarchy->pAnimLoopCallBack = (RpHAnimHierarchy * (*)(RpHAnimHierarchy *, void *))NULL;
    pHierarchy->pAnimLoopCallBackData = NULL;

    if (!(flags & rpHANIMHIERARCHYNOMATRICES))
    {
        ptr = RwMalloc(sizeof(RwMatrix) * numNodes + 15);
        pHierarchy->pMatrixArray = (RwMatrix *) ROUNDUP16(ptr);
        pHierarchy->pMatrixArrayUnaligned = ptr;
    }

    pHierarchy->pNodeInfo = (RpHAnimNodeInfo *)  RwMalloc(sizeof(RpHAnimNodeInfo) * numNodes);

    for (node = 0; node < numNodes; node++)
    {
        pHierarchy->pNodeInfo[node].pFrame = (RwFrame *)NULL;
        if (nodeIDs)
        {
            pHierarchy->pNodeInfo[node].nodeID = nodeIDs[node];
        }
        pHierarchy->pNodeInfo[node].nodeIndex = node;
        if (nodeFlags)
        {
            pHierarchy->pNodeInfo[node].flags = nodeFlags[node];
        }
    }

    pHierarchy->currentKeyFrameSize = maxKeyFrameSize;
    pHierarchy->maxKeyFrameSize = maxKeyFrameSize;
    pHierarchy->keyFrameToMatrixCB = (RpHAnimKeyFrameToMatrixCallBack)NULL;
    pHierarchy->keyFrameInterpolateCB = (RpHAnimKeyFrameInterpolateCallBack)NULL;
    pHierarchy->keyFrameBlendCB = (RpHAnimKeyFrameBlendCallBack)NULL;
    pHierarchy->keyFrameAddCB = (RpHAnimKeyFrameAddCallBack)NULL;

    /* root/main hierarchies point to themselves with a zero offset */
    pHierarchy->parentHierarchy = pHierarchy;
    pHierarchy->offsetInParent = 0;

    RWRETURN(pHierarchy);
}

/**
 * \ingroup rphanim
 * \ref RpHAnimHierarchyDestroy
 *  destroys a hierarchy
 *
 * \param pHierarchy A pointer to the hierarchy to be destroyed.
 *
 * \return NULL
 *
 */

RpHAnimHierarchy     *
RpHAnimHierarchyDestroy(RpHAnimHierarchy * pHierarchy)
{
    RWAPIFUNCTION(RWSTRING("RpHAnimHierarchyDestroy"));

    RWASSERT(NULL != pHierarchy);

    if (pHierarchy)
    {
        RwFrame *parentFrame = pHierarchy->parentFrame;
        if (!(pHierarchy->flags & rpHANIMHIERARCHYSUBHIERARCHY))
        {
            if (!(pHierarchy->flags & rpHANIMHIERARCHYNOMATRICES))
            {
                RwFree(pHierarchy->pMatrixArrayUnaligned);
            }
            RwFree(pHierarchy->pNodeInfo);
        }

        pHierarchy->pMatrixArrayUnaligned = NULL;
        pHierarchy->pMatrixArray = (RwMatrix *)NULL;
        pHierarchy->pNodeInfo = (RpHAnimNodeInfo *)NULL;

        MEMORYMESSAGE(("RwFree(%p)", pHierarchy));

        RwFree(pHierarchy);
        pHierarchy = (RpHAnimHierarchy *)NULL;

        if (parentFrame)
        {
            RpHAnimFrameExtension *frameExt =
                (RpHAnimFrameExtension *)RPHANIMFRAMEGETDATA(parentFrame);
            frameExt->hierarchy = (RpHAnimHierarchy *)NULL;
        }
    }

    RWRETURN(pHierarchy);
}

/**
 * \ingroup rphanim
 * \ref RpHAnimHierarchyCreateSubHierarchy
 * This function creates a sub-hierarchy of a given main hierarchy. The
 * parent-hierarchy and the offset into the hierarchy are given along with
 * flags defining the sub-hierarchies settings. You can specify a different
 * maxkeyframesize for the sub-hierarchy and use different types of keyframe
 * on sub-hierarchies and main hierarchies, specifying -1 will adopt the
 * parent hierarchies size settings.
 *
 * The sub-hierarchy can then be used to run a different animation from the
 * base hierarchy. The general loop process would be as follows.
 *
 * \li \ref RpHAnimHierarchyAddAnimTime (mainHierarchy, time);
 * \li \ref RpHAnimHierarchyUpdateMatrices (mainHierarchy);
 * \li \ref RpHAnimHierarchyAddAnimTime (subHierarchy, time);
 * \li \ref RpHAnimHierarchyUpdateMatrices (subHierarchy);
 *
 * Here we update the main hierarchy interpolated keyframe array
 * and then convert the keyframes into the matrix array. We then update the
 * sub-hierarchies keyframes locally to the sub-hierarchy and the update
 * matrices overwrites the appropriate matrices in the main hierarchy, because
 * of this you cannot update LTM's of a sub-hierarchy if the main hierarchy LTM's
 * were not updated.
 *
 * \param pParentHierarchy the parent hierarchy to be used for this sub-hierarchy
 * \param startNode the index of the node representing the root of the new
 *        sub-hierarchy
 * \param flags the flags to be used in the new sub-hierarchy, these can be copied
 *        from the source hierarchy.
 * \param maxKeyFrameSize the maximum keyframe size to allow in the sub-hierarchy,
 *        passing -1 will copy the parent hierarchies settings.
 *
 * \return A pointer to the new sub-hierarchy on success
 */
RpHAnimHierarchy     *
RpHAnimHierarchyCreateSubHierarchy(RpHAnimHierarchy *pParentHierarchy,
                                   RwInt32 startNode,
                                   RpHAnimHierarchyFlag flags,
                                   RwInt32 maxKeyFrameSize)
{
    void               *ptr;
    RpHAnimHierarchy    *pHierarchy;
    RwInt32             numNodes;
    RwInt32             framesOnStack;
    RpHAnimNodeInfo    *pNodeInfo;
    RwInt32             currentKeyFrameSize;

    RWAPIFUNCTION(RWSTRING("RpHAnimHierarchyCreateSubHierarchy"));
    RWASSERT(pParentHierarchy);
    RWASSERT(pParentHierarchy->numNodes > startNode);

    /* calculate the size of the sub hierarchy, by running through the
       push pop flags in the parent hierarchy */
    numNodes = 1;
    framesOnStack = 0;
    pNodeInfo = &pParentHierarchy->pNodeInfo[startNode];
    if (!(pNodeInfo->flags & rpHANIMPOPPARENTMATRIX))
    {
        framesOnStack = 1;
        pNodeInfo++;
    }
    while (framesOnStack)
    {
        if (pNodeInfo->flags & rpHANIMPUSHPARENTMATRIX)
        {
            framesOnStack++;
        }
        if (pNodeInfo->flags & rpHANIMPOPPARENTMATRIX)
        {
            framesOnStack--;
        }
        numNodes++;
        pNodeInfo++;
    }

    if (maxKeyFrameSize == -1)
    {
        maxKeyFrameSize = pParentHierarchy->maxKeyFrameSize;
        currentKeyFrameSize = pParentHierarchy->currentKeyFrameSize;
    }
    else
    {
        currentKeyFrameSize = maxKeyFrameSize;
    }

    ptr = RwMalloc(sizeof(RpHAnimHierarchy) +
                   (numNodes * maxKeyFrameSize * 3));

    pHierarchy = (RpHAnimHierarchy *) ptr;
    pHierarchy->flags = flags | rpHANIMHIERARCHYSUBHIERARCHY;
    pHierarchy->numNodes = numNodes;
    pHierarchy->pCurrentAnim = (RpHAnimAnimation *)NULL;
    pHierarchy->pNextFrame = NULL;
    pHierarchy->currentTime = 0.0f;
    /* TODO: Should this find the correct frame? If the parent hierarchy is
       unattached */
    pHierarchy->parentFrame = pParentHierarchy->pNodeInfo[startNode].pFrame;

    pHierarchy->pAnimCallBack = (RpHAnimHierarchy * (*)(RpHAnimHierarchy *, void *))NULL;
    pHierarchy->animCallBackTime = (RwReal) - 1.0;
    pHierarchy->pAnimCallBackData = NULL;
    pHierarchy->pAnimLoopCallBack = (RpHAnimHierarchy * (*)(RpHAnimHierarchy *, void *))NULL;
    pHierarchy->pAnimLoopCallBackData = NULL;

    pHierarchy->pMatrixArray = &pParentHierarchy->pMatrixArray[startNode];
    pHierarchy->pMatrixArrayUnaligned = NULL;

    pHierarchy->pNodeInfo = &pParentHierarchy->pNodeInfo[startNode];

    pHierarchy->currentKeyFrameSize = currentKeyFrameSize;
    pHierarchy->maxKeyFrameSize = maxKeyFrameSize;

    pHierarchy->parentHierarchy = pParentHierarchy;
    pHierarchy->offsetInParent = startNode;

    RWRETURN(pHierarchy);
}

/**
 * \ingroup rphanim
 * \ref RpHAnimHierarchyCreateFromHierarchy
 *  creates a hierarchy with the same structure as the source hierarchy.
 *  This can be used to create multiple secondary hierarchies for blending.
 *
 * \param pHierarchy The source hierarchy to copy info from
 * \param flags Flags for the new hierarchy, if cloning a hierarchy simply
 *              pass in the source hierarchy flags.
 * \param maxKeyFrameSize specifies the maximum size of keyframes which
 *        will be usable on the new hierarchy
 *
 * \return A pointer to the new hierarchy on success,
 * NULL if there is an error.
 *
 */
RpHAnimHierarchy *
RpHAnimHierarchyCreateFromHierarchy(RpHAnimHierarchy *pHierarchy,
                                    RpHAnimHierarchyFlag flags,
                                    RwInt32 maxKeyFrameSize)
{
    RpHAnimHierarchy    *pNewHierarchy;
    RwInt32             numNodes;
    RwInt32             i;

    RWAPIFUNCTION(RWSTRING("RpHAnimHierarchyCreateFromHierarchy"));
    RWASSERT(pHierarchy);

    numNodes = pHierarchy->numNodes;
    pNewHierarchy = RpHAnimHierarchyCreate(numNodes,
                                           (RwUInt32 *)NULL,
                                           (RwInt32 *)NULL,
                                           flags,
                                           maxKeyFrameSize);

    /* now copy over the pNodeInfo data */
    for(i=0; i<pNewHierarchy->numNodes; i++)
    {
        pNewHierarchy->pNodeInfo[i].pFrame = (RwFrame *)NULL;
        pNewHierarchy->pNodeInfo[i].flags = pHierarchy->pNodeInfo[i].flags;
        pNewHierarchy->pNodeInfo[i].nodeIndex = pHierarchy->pNodeInfo[i].nodeIndex;
        pNewHierarchy->pNodeInfo[i].nodeID = pHierarchy->pNodeInfo[i].nodeID;
    }

    RWRETURN(pNewHierarchy);
}

/**
 * \ingroup rphanim
 * \ref RpHAnimHierarchyAttach
 * attaches hierarchies from RwFrame. When attached the \ref RpHAnimHierarchy's can
 * update RwFrame directly through animation updates. The attachment is done by linking
 * ID's set on the RwFrame with \ref RpHAnimFrameSetID and the ID's for the nodes passed
 * into \ref RpHAnimHierarchyCreate.
 *
 * \param pHierarchy The hierarchy which you want to attach to an RwFrame hierarchy.
 *
 * \return A pointer to the hierarchy on success
 * \see RpHAnimHierarchyDetach
 */
RpHAnimHierarchy *
RpHAnimHierarchyAttach(RpHAnimHierarchy *pHierarchy)
{
    RWAPIFUNCTION(RWSTRING("RpHAnimHierarchyAttach"));
    RWASSERT(pHierarchy);

    HAnimFramesLinkToHierarchy(pHierarchy->parentFrame, pHierarchy);

    RWRETURN(pHierarchy);
}

/**
 * \ingroup rphanim
 * \ref RpHAnimHierarchyDetach
 * detaches hierarchies from RwFrame. When attached the \ref RpHAnimHierarchy's
 * can update \ref RwFrame directly through anim updates. Hierarchies are automatically
 * detached when they are destroyed, however currently if you destroy an RwFrame which
 * is referenced by an AnimHierarchy but not at the root of a hierarchy things may
 * break. Since in general RwFrame hierarchies are destroyed in one go rather than
 * one at a time this shouldn't be a huge issue.
 *
 * \param pHierarchy
 *
 * \return A pointer to the hierarchy on success.
 *
 * \see RpHAnimHierarchyAttach
 */
RpHAnimHierarchy *
RpHAnimHierarchyDetach(RpHAnimHierarchy *pHierarchy)
{
    RwInt32 i;

    RWAPIFUNCTION(RWSTRING("RpHAnimHierarchyDetach"));
    RWASSERT(pHierarchy);

    for (i=0; i<pHierarchy->numNodes; i++)
    {
        pHierarchy->pNodeInfo[i].pFrame = (RwFrame *)NULL;
    }

    RWRETURN(pHierarchy);
}

/**
 * \ingroup rphanim
 * \ref RpHAnimHierarchyAttachFrameIndex attaches a single node within
 * the hierarchy to an RwFrame. This allows you to setup RwFrame updates
 * on only a few necessary RwFrames rather than the whole RwFrame hierarchy.
 *
 * \param pHierarchy The hierarchy in which you want to attach to an RwFrame
 * \param frameIndex The index of the node you wish to be attached
 *
 * \see RpHAnimHierarchyAttach
 * \see RpHAnimIDGetIndex
 */
RpHAnimHierarchy *
RpHAnimHierarchyAttachFrameIndex(RpHAnimHierarchy *pHierarchy,
                                 RwInt32 nodeIndex)
{
    FrameLinkToHierarchyData linkInfo;

    RWAPIFUNCTION(RWSTRING("RpHAnimHierarchyAttachFrameIndex"));
    RWASSERT(pHierarchy);

    linkInfo.id = pHierarchy->pNodeInfo[nodeIndex].nodeID;
    linkInfo.pHierarchy = pHierarchy;
    HAnimFrameLinkToHierarchy(pHierarchy->parentFrame, &linkInfo);

    RWRETURN(pHierarchy);
}

/**
 * \ingroup rphanim
 * \ref RpHAnimHierarchyDetachFrameIndex detaches a single node within
 * the hierarchy from an RwFrame.
 *
 * \param pHierarchy The hierarchy in which you want to detach a RwFrame
 * \param nodeIndex The index of the node you wish to be detached
 *
 * \see RpHAnimHierarchyDetach
 * \see RpHAnimIDGetIndex
 */
RpHAnimHierarchy *
RpHAnimHierarchyDetachFrameIndex(RpHAnimHierarchy *pHierarchy, RwInt32 nodeIndex)
{
    RWAPIFUNCTION(RWSTRING("RpHAnimHierarchyDetachFrameIndex"));
    RWASSERT(pHierarchy);

    pHierarchy->pNodeInfo[nodeIndex].pFrame = (RwFrame *)NULL;

    RWRETURN(pHierarchy);
}

/**
 * \ingroup rphanim
 * \ref RpHAnimHierarchyGetMatrixArray
 *
 * Function call access to the pMatrixArray in a hierarchy.
 *
 * \param pHierarchy pointer to the hierarchy to retrieve the
 *        matrix array pointer.
 *
 * \return A pointer to the hierarchies matrix array on success
 *
 */
RwMatrix               *
RpHAnimHierarchyGetMatrixArray(RpHAnimHierarchy * pHierarchy)
{
    RwMatrix           *result = (RwMatrix *)NULL;

    RWAPIFUNCTION(RWSTRING("RpHAnimHierarchyGetMatrixArray"));
    RWASSERT(pHierarchy);

    if (pHierarchy)
    {
        result = pHierarchy->pMatrixArray;
    }

    RWRETURN(result);
}

/**
 * \ingroup rphanim
 * \ref RpHAnimFrameSetHierarchy
 *  sets the RwFrame to point to a hierarchy allowing it to be
 *  streamed out
 *
 * \param pFrame A pointer to the root RwFrame for which
 *        the hierarchy is to be used.
 * \param pHierarchy A pointer to the hierarchy to use
 *        for animating the RwFrame hierarchy
 *
 * \return TRUE on success, FALSE if an error occurs.
 *
 * \see RpHAnimFrameGetHierarchy
 */

RwBool
RpHAnimFrameSetHierarchy(RwFrame * pFrame, RpHAnimHierarchy * pHierarchy)
{
    RpHAnimFrameExtension *frameExt;
    RWAPIFUNCTION(RWSTRING("RpHAnimFrameSetHierarchy"));
    RWASSERT(pFrame);

    if (!pFrame)
    {
        RWRETURN(FALSE);
    }

    frameExt = (RpHAnimFrameExtension *)RPHANIMFRAMEGETDATA(pFrame);

    /* Disconnect any existing hierarchy */
    if (frameExt->hierarchy)
    {
        frameExt->hierarchy->parentFrame = (RwFrame *) NULL;
    }

    frameExt->hierarchy = pHierarchy;
    if (pHierarchy)
    {
        pHierarchy->parentFrame = pFrame;
    }

    RWRETURN(TRUE);
}

/**
 * \ingroup rphanim
 * \ref RpHAnimFrameGetHierarchy
 *  gets the hierarchy pointed to by this RwFrame.
 *
 * \param pFrame A pointer to the RwFrame for which the hierarchy is to be got.
 *
 * \return A pointer to the hierarchy attached to the RwFrame
 *
 * \see RpHAnimFrameSetHierarchy
 */

RpHAnimHierarchy     *
RpHAnimFrameGetHierarchy(RwFrame *pFrame)
{
    RpHAnimHierarchy     *result = (RpHAnimHierarchy *)NULL;

    RWAPIFUNCTION(RWSTRING("RpHAnimFrameGetHierarchy"));
    RWASSERT(pFrame);

    if (pFrame)
    {
        RpHAnimFrameExtension *frameExt;
        frameExt = (RpHAnimFrameExtension *)RPHANIMFRAMEGETDATA(pFrame);
        result = frameExt->hierarchy;
    }

    RWRETURN(result);
}

/**
 * \ingroup rphanim
 * \ref RpHAnimFrameSetID
 *  sets the ID stored with the RwFrame, these are used to attach hierarchies
 *  to RwFrames.
 *
 * \param pFrame A pointer to the RwFrame to set the ID on.
 * \param id The ID to set.
 *
 * \return TRUE on success, FALSE if an error occurs.
 *
 * \see RpHAnimFrameGetHierarchy
 */

RwBool
RpHAnimFrameSetID(RwFrame * pFrame, RwInt32 id)
{
    RpHAnimFrameExtension *frameExt;
    RWAPIFUNCTION(RWSTRING("RpHAnimFrameSetID"));
    RWASSERT(pFrame);

    if (!pFrame)
    {
        RWRETURN(FALSE);
    }

    frameExt = (RpHAnimFrameExtension *)RPHANIMFRAMEGETDATA(pFrame);
    frameExt->id = id;

    RWRETURN(TRUE);
}

/**
 * \ingroup rphanim
 * \ref RpHAnimFrameGetID
 *  gets the ID stored with the RwFrame, these are used to attach hierarchies
 *  to RwFrames.
 *
 * \param pFrame pointer to the RwFrame from which the ID is to be retrieved.
 *
 * \return The ID stored on the RwFrame.
 *
 * \see RpHAnimFrameSetHierarchy
 */

RwInt32
RpHAnimFrameGetID(RwFrame *pFrame)
{
    RwInt32 result = -1;

    RWAPIFUNCTION(RWSTRING("RpHAnimFrameGetID"));
    RWASSERT(pFrame);

    if (pFrame)
    {
        RpHAnimFrameExtension *frameExt;
        frameExt = (RpHAnimFrameExtension *)RPHANIMFRAMEGETDATA(pFrame);
        result = frameExt->id;
    }

    RWRETURN(result);
}

/**
 * \ingroup rphanim
 * \ref RpHAnimHierarchySetKeyFrameCallBacks
 * Sets up the callbacks to be used by the hierarchy. These callbacks
 * perform keyframe blending, interpolation and conversion to matrices.
 * The callback setup is implicitly performed when setting the current
 * animation on a hierarchy, however if you wish to fully procedurally
 * drive the keyframe data this function allows the callbacks to be
 * setup so that \ref RpHAnimHierarchyUpdateMatrices performs correctly.
 *
 *
 * \param pHierarchy A pointer to the hierarchy
 * \param keyframeTypeID ID of the keyframe type to retrieve the callbacks for
 *
 * \return TRUE on success, FALSE if an error occurs.
 *
 */
RwBool
RpHAnimHierarchySetKeyFrameCallBacks(RpHAnimHierarchy * pHierarchy,
                                     RwInt32 keyFrameTypeID)
{
    RpHAnimInterpolatorInfo     *interpInfo;

    RWAPIFUNCTION(RWSTRING("RpHAnimHierarchySetKeyFrameCallBacks"));
    RWASSERT(pHierarchy);

    interpInfo = RpHAnimGetInterpolatorInfo(keyFrameTypeID);
    if (!interpInfo)
    {
        /* Unregistered animation type */
        RWRETURN(FALSE);
    }

    pHierarchy->keyFrameToMatrixCB = interpInfo->keyFrameToMatrixCB;
    pHierarchy->keyFrameBlendCB = interpInfo->keyFrameBlendCB;
    pHierarchy->keyFrameInterpolateCB = interpInfo->keyFrameInterpolateCB;
    pHierarchy->keyFrameAddCB = interpInfo->keyFrameAddCB;

    RWRETURN(TRUE);
}

/**
 * \ingroup rphanim
 * \ref RpHAnimHierarchySetCurrentAnim
 * Sets the current animation on the hierarchy. It is assumed that the
 * animation is designed for the hierarchy it is being set on and no
 * hierarchy structure comparisons are made. The animation defines the
 * interpolation schemes used and they will be setup on the hierarchy
 * at this call. The max keyframe size of the hierarchy must be sufficient
 * to support the keyframe size required by the interpolation scheme.
 * The animation is initialized to time zero at this call.
 *
 * If the hierarchy is attached to a skin, the skin will not update until
 * \ref RpHAnimHierarchyUpdateMatrices is called.
 *
 * \param pHierarchy A pointer to the hierarchy
 * \param pAnim A pointer to the animation
 *
 * \return TRUE on success, FALSE if an error occurs.
 *
 */

RwBool
RpHAnimHierarchySetCurrentAnim(RpHAnimHierarchy * pHierarchy,
                               RpHAnimAnimation * pAnim)
{
    RpHAnimInterpolatorInfo     *interpInfo;

    RWAPIFUNCTION(RWSTRING("RpHAnimHierarchySetCurrentAnim"));
    RWASSERT(pHierarchy);
    RWASSERT(pAnim);

    pHierarchy->pCurrentAnim = pAnim;
    pHierarchy->currentTime = 0.0f;

    /* Get interpolator functions */
    interpInfo = pAnim->interpInfo;
    pHierarchy->currentKeyFrameSize = interpInfo->keyFrameSize;
    RWASSERT(pHierarchy->currentKeyFrameSize <= pHierarchy->maxKeyFrameSize);

    pHierarchy->keyFrameToMatrixCB = interpInfo->keyFrameToMatrixCB;
    pHierarchy->keyFrameBlendCB = interpInfo->keyFrameBlendCB;
    pHierarchy->keyFrameInterpolateCB = interpInfo->keyFrameInterpolateCB;
    pHierarchy->keyFrameAddCB = interpInfo->keyFrameAddCB;

    /* Set up initial interpolation frames for time = 0 */

    memcpy(rpHANIMHIERARCHYGETINTERPFRAME(pHierarchy, 0),
           (RwUInt8 *)pAnim->pFrames,
           pHierarchy->numNodes * pHierarchy->currentKeyFrameSize);
    memcpy(rpHANIMHIERARCHYGETINTERPFRAME1(pHierarchy, 0),
           (RwUInt8 *)pAnim->pFrames,
           pHierarchy->numNodes * pHierarchy->currentKeyFrameSize);
    memcpy(rpHANIMHIERARCHYGETINTERPFRAME2(pHierarchy, 0),
           (RwUInt8 *)pAnim->pFrames + (pHierarchy->numNodes * pHierarchy->currentKeyFrameSize),
           pHierarchy->numNodes * pHierarchy->currentKeyFrameSize);

    pHierarchy->pNextFrame = (RwUInt8 *)pAnim->pFrames + (pHierarchy->numNodes * pHierarchy->currentKeyFrameSize * 2);

    RWRETURN(TRUE);
}

/**
 * \ingroup rphanim
 * \ref RpHAnimHierarchySubAnimTime
 * subtract anim time will step through the keyframes to find the current
 * pair for each node to interpolate. These will then be interpolated by
 * calling the appropriate overloaded interpolation scheme functions. If the
 * animation loops or passes the time set in \ref RpHAnimHierarchySetAnimCallBack
 * then the appropriate callback will be called. By default the animation will
 * simply loop continuously.
 *
 * If the hierarchy is attached to a skin, the skin will not update until
 * \ref RpHAnimHierarchyUpdateMatrices is called.
 *
 * \param pHierarchy A pointer to a hierarchy
 * \param time The amount of time to subtract from the animation
 *
 * \return TRUE on success
 *
 * \see RpHAnimHierarchyUpdateMatrices
 * \see RpHAnimHierarchyAddAnimTime
 * \see RpHAnimHierarchySetCurrentAnimTime
 */

RwBool
RpHAnimHierarchySubAnimTime(RpHAnimHierarchy * pHierarchy, RwReal time)
{
    RpHAnimAnimation      *pCurrentAnim;
    RpHAnimKeyFrameHeader *pCurrentBoneFrame1;
    RpHAnimKeyFrameHeader *pCurrentBoneFrame2;
    RpHAnimKeyFrameHeader *pCurrentBoneFrame;
    RwBool              bCallBack = FALSE;
    RwBool              bLoopCallBack = FALSE;
    RwInt32             i;
    RwInt32             numNodes;
    RwReal              duration;

    RWAPIFUNCTION(RWSTRING("RpHAnimHierarchySubAnimTime"));
    RWASSERT(pHierarchy);
    RWASSERT(time >= 0.0f);

    bCallBack |=
        ((pHierarchy->currentTime > pHierarchy->animCallBackTime) &&
         (pHierarchy->currentTime - time <=
          pHierarchy->animCallBackTime));

    pHierarchy->currentTime -= time;
    pCurrentAnim = pHierarchy->pCurrentAnim;
    duration = pCurrentAnim->duration;

    if (pHierarchy->currentTime < 0.0f)
    {

        while (pHierarchy->currentTime < 0.0f)
        {
            pHierarchy->currentTime += duration;
        }

        time = pHierarchy->currentTime;
        RpHAnimHierarchySetCurrentAnim(pHierarchy, pCurrentAnim);
        RpHAnimHierarchyAddAnimTime(pHierarchy, time);

        bCallBack |=
            (pHierarchy->currentTime < pHierarchy->animCallBackTime);

        bLoopCallBack = TRUE;
    }

    pCurrentBoneFrame = (RpHAnimKeyFrameHeader *)
        rpHANIMHIERARCHYGETINTERPFRAME(pHierarchy, 0);
    pCurrentBoneFrame1 = (RpHAnimKeyFrameHeader *)
        rpHANIMHIERARCHYGETINTERPFRAME1(pHierarchy, 0);
    pCurrentBoneFrame2 = (RpHAnimKeyFrameHeader *)
        rpHANIMHIERARCHYGETINTERPFRAME2(pHierarchy, 0);

    numNodes = pHierarchy->numNodes;

    for (i = 0; i < numNodes; i++)
    {
        while (pHierarchy->currentTime < pCurrentBoneFrame1->time)
        {
            memcpy(pCurrentBoneFrame2, pCurrentBoneFrame1, pHierarchy->currentKeyFrameSize);
            memcpy(pCurrentBoneFrame1, pCurrentBoneFrame1->prevFrame, pHierarchy->currentKeyFrameSize);
        }

        if (pCurrentBoneFrame->time != pHierarchy->currentTime)
        {
            /* Interpolate to new time */
            RpHAnimFrameInterpolate(pHierarchy,
                                    pCurrentBoneFrame,
                                    pCurrentBoneFrame1,
                                    pCurrentBoneFrame2,
                                    pHierarchy->currentTime);
        }

        pCurrentBoneFrame = (RpHAnimKeyFrameHeader *)((RwUInt8 *)pCurrentBoneFrame + pHierarchy->currentKeyFrameSize);
        pCurrentBoneFrame1 = (RpHAnimKeyFrameHeader *)((RwUInt8 *)pCurrentBoneFrame1 + pHierarchy->currentKeyFrameSize);
        pCurrentBoneFrame2 = (RpHAnimKeyFrameHeader *)((RwUInt8 *)pCurrentBoneFrame2 + pHierarchy->currentKeyFrameSize);
    }

    /* Invalidate the "next frame" pointer
     * to force a recalculation when playing forwards */

    pHierarchy->pNextFrame = NULL;

    if (bCallBack && pHierarchy->pAnimCallBack)
    {
        if (!(pHierarchy->pAnimCallBack)
            (pHierarchy, pHierarchy->pAnimCallBackData))
        {
            /* If the callback returns false, we don't want to call it again */
            pHierarchy->pAnimCallBack = (RpHAnimHierarchy * (*)(RpHAnimHierarchy *, void *))NULL;
        }
    }

    if (bLoopCallBack && pHierarchy->pAnimLoopCallBack)
    {
        if (!(pHierarchy->pAnimLoopCallBack) (pHierarchy,
                                              pHierarchy->pAnimLoopCallBackData))
        {
            /* If the callback returns false, we don't want to call it again */
            pHierarchy->pAnimLoopCallBack = (RpHAnimHierarchy * (*)(RpHAnimHierarchy *, void *))NULL;
        }

    }

    RWRETURN(TRUE);

}

/**
 * \ingroup rphanim
 * \ref RpHAnimHierarchyStdKeyFrameAddAnimTime
 * add anim time will step through the standard keyframes to find the current
 * pair for each node to interpolate.
 * These will then be interpolated by
 * calling the appropriate overloaded interpolation scheme functions. If the
 * animation loops or passes the time set in \ref RpHAnimHierarchySetAnimCallBack
 * then the appropriate callback will be called. By default the animation will
 * simply loop continuously.
 *
 * If you have been playing the animation backwards and now wish to play it
 * forwards you must first call \ref RpHAnimHierarchySetCurrentAnim followed by
 * \ref RpHAnimHierarchySetCurrentAnimTime to reset the animation so that it's
 * playing forwards from the correct time.
 *
 * The hierarchy's matrices and attached RwFrames will not update until
 * \ref RpHAnimHierarchyUpdateMatrices is called.
 *
 * \param pHierarchy A pointer to a hierarchy.
 * \param time The amount of time to add to the animation.
 *
 * \return TRUE on success
 *
 * \warning
 * This function is optimized for standard keyframes,
 * and should only be called for heirachies with standard keyframes.
 *
 *
 * \see RpHAnimHierarchyAddAnimTime
 * \see RpHAnimHierarchySetCurrentAnim
 * \see RpHAnimHierarchySetCurrentAnimTime
 * \see RpHAnimHierarchyStdKeyFrameAddAnimTime
 * \see RpHAnimHierarchySubAnimTime
 * \see RpHAnimHierarchyUpdateMatrices
 *
 */
RwBool
RpHAnimHierarchyStdKeyFrameAddAnimTime(RpHAnimHierarchy * pHierarchy, RwReal time)
{
    RwBool result;
    RWAPIFUNCTION(RWSTRING("RpHAnimHierarchyStdKeyFrameAddAnimTime"));
    RWASSERT(pHierarchy);
    RWASSERT(pHierarchy->pNextFrame);

    RWASSERT( rpHANIMSTDKEYFRAMESIZE == (pHierarchy)->currentKeyFrameSize);
    RWASSERT(time >= 0.0f);

    result = TRUE;  /* if time == 0 Do nothing */

    if (time > 0.0f)
    {
        RwBool              bCallBack = FALSE;
        RwBool              bLoopCallBack = FALSE;
        RwBool              bRepeat;
        RwInt32             i;
        RwInt32             minBone;
        RwInt32             numNodes;
        RwReal              duration;
        RwReal              minTime;
        RpHAnimAnimation      *pCurrentAnim;
        RpHAnimKeyFrameHeader *pCurrentBoneFrame1;
        RpHAnimKeyFrameHeader *pCurrentBoneFrame2;
        RpHAnimKeyFrameHeader *pCurrentBoneFrame;
        RpHAnimHierarchyCallBack pAnimCallBack;
        RpHAnimHierarchyCallBack pAnimLoopCallBack;

        pCurrentAnim = pHierarchy->pCurrentAnim;

        bCallBack |= ( ((pHierarchy->currentTime <
                         pHierarchy->animCallBackTime) &&
                        (pHierarchy->currentTime + time >=
                         pHierarchy->animCallBackTime)) );
        pHierarchy->currentTime += time;
        duration = pCurrentAnim->duration;

        if (pHierarchy->currentTime > duration)
        {
            RwReal stacked;
            /* Loop! */
            while (pHierarchy->currentTime > duration)
            {
                pHierarchy->currentTime -= duration;

                bCallBack |=  (pHierarchy->currentTime >=
                               pHierarchy->animCallBackTime);

                bLoopCallBack = TRUE;
            }

            stacked = pHierarchy->currentTime;
            RpHAnimHierarchySetCurrentAnim(pHierarchy, pCurrentAnim);
            pHierarchy->currentTime = stacked;
        }

        pCurrentBoneFrame = (RpHAnimKeyFrameHeader *)
            rpHANIMHIERARCHYGETINTERPFRAME(pHierarchy, 0);
        pCurrentBoneFrame1 =  (RpHAnimKeyFrameHeader *)
            rpHANIMHIERARCHYGETINTERPFRAME1(pHierarchy, 0);
        pCurrentBoneFrame2 =  (RpHAnimKeyFrameHeader *)
            rpHANIMHIERARCHYGETINTERPFRAME2(pHierarchy, 0);

        numNodes = pHierarchy->numNodes;

        minBone = 0;

        do
        {
            bRepeat = FALSE;
            minTime = pHierarchy->currentTime;

            for (i = 0; i < numNodes; i++)
            {
                if (pHierarchy->currentTime >=
                    pCurrentBoneFrame2->time)
                {
                    if (pCurrentBoneFrame2->time < minTime)
                    {
                        minTime = pCurrentBoneFrame2->time;
                        minBone = i;
                        bRepeat = TRUE;
                    }
                }
                pCurrentBoneFrame = (RpHAnimKeyFrameHeader *)
                        &((RpHAnimStdKeyFrame *)
                          (pCurrentBoneFrame))[1];
                pCurrentBoneFrame1 = (RpHAnimKeyFrameHeader *)
                        &((RpHAnimStdKeyFrame *)
                          (pCurrentBoneFrame1))[1];
                pCurrentBoneFrame2 = (RpHAnimKeyFrameHeader *)
                        &((RpHAnimStdKeyFrame *)
                          (pCurrentBoneFrame2))[1];
            }

            if (bRepeat)
            {
                *((RpHAnimStdKeyFrame *)
                  (rpHANIMHIERARCHYGETINTERPFRAME1(pHierarchy,
                                                   minBone)) ) =
                *((RpHAnimStdKeyFrame *)
                  (rpHANIMHIERARCHYGETINTERPFRAME2(pHierarchy,
                                                   minBone)) );

                *((RpHAnimStdKeyFrame *)
                  (rpHANIMHIERARCHYGETINTERPFRAME2(pHierarchy,
                                                   minBone)) ) =
                *((RpHAnimStdKeyFrame *)
                  (pHierarchy->pNextFrame));

                pHierarchy->pNextFrame =
                     &((RpHAnimStdKeyFrame *)(pHierarchy->pNextFrame))[1];
            }

            pCurrentBoneFrame =  (RpHAnimKeyFrameHeader *)
                rpHANIMHIERARCHYGETINTERPFRAME(pHierarchy, 0);
            pCurrentBoneFrame1 = (RpHAnimKeyFrameHeader *)
                rpHANIMHIERARCHYGETINTERPFRAME1(pHierarchy, 0);
            pCurrentBoneFrame2 = (RpHAnimKeyFrameHeader *)
                rpHANIMHIERARCHYGETINTERPFRAME2(pHierarchy, 0);

        }
        while (bRepeat);

        for (i = 0; i < numNodes; i++)
        {
            if (pCurrentBoneFrame->time !=
                pHierarchy->currentTime)
            {
                /* Interpolate to new time */
                RpHAnimFrameInterpolate(pHierarchy,
                                        pCurrentBoneFrame,
                                        pCurrentBoneFrame1,
                                        pCurrentBoneFrame2,
                                        pHierarchy->currentTime);
            }

            pCurrentBoneFrame = (RpHAnimKeyFrameHeader *)
                &((RpHAnimStdKeyFrame *)pCurrentBoneFrame)[1];
            pCurrentBoneFrame1 = (RpHAnimKeyFrameHeader *)
                &((RpHAnimStdKeyFrame *)pCurrentBoneFrame1)[1];
            pCurrentBoneFrame2 = (RpHAnimKeyFrameHeader *)
                &((RpHAnimStdKeyFrame *)pCurrentBoneFrame2)[1];
        }

        if (bCallBack &&
            (pAnimCallBack = pHierarchy->pAnimCallBack) )
        {
            if (!pAnimCallBack(pHierarchy,
                               pHierarchy->pAnimCallBackData))
            {
                /* If the callback returns false,
                 * we don't want to call it again */
                pHierarchy->pAnimCallBack =
                    (RpHAnimHierarchyCallBack) NULL;
            }
        }

        if (bLoopCallBack &&
            (pAnimLoopCallBack = pHierarchy->pAnimLoopCallBack) )
        {
            if (!pAnimLoopCallBack(pHierarchy,
                                   pHierarchy->pAnimLoopCallBackData))
            {
                /* If the callback returns false,
                   we don't want to call it again */
                pHierarchy->pAnimLoopCallBack =
                    (RpHAnimHierarchyCallBack) NULL;
            }
        }
    }

    RWRETURN(result);
}

/**
 * \ingroup rphanim
 * \ref RpHAnimHierarchyAddAnimTime
 * add anim time will step through the keyframes to find the current
 * pair for each node to interpolate. These will then be interpolated by
 * calling the appropriate overloaded interpolation scheme functions. If the
 * animation loops or passes the time set in \ref RpHAnimHierarchySetAnimCallBack
 * then the appropriate callback will be called. By default the animation will
 * simply loop continuously.
 *
 * If you have been playing the animation backwards and now wish to play it
 * forwards you must first call \ref RpHAnimHierarchySetCurrentAnim followed by
 * \ref RpHAnimHierarchySetCurrentAnimTime to reset the animation so that it's
 * playing forwards from the correct time.
 *
 * The hierarchy's matrices and attached RwFrames will not update until
 * \ref RpHAnimHierarchyUpdateMatrices is called.
 *
 * \param pHierarchy A pointer to a hierarchy.
 * \param time The amount of time to add to the animation.
 *
 * \return TRUE on success
 *
 * \see RpHAnimHierarchyAddAnimTime
 * \see RpHAnimHierarchySetCurrentAnim
 * \see RpHAnimHierarchySetCurrentAnimTime
 * \see RpHAnimHierarchyStdKeyFrameAddAnimTime
 * \see RpHAnimHierarchySubAnimTime
 * \see RpHAnimHierarchyUpdateMatrices
 */
RwBool
RpHAnimHierarchyAddAnimTime(RpHAnimHierarchy * pHierarchy, RwReal time)
{
    RwBool              result;
    RWAPIFUNCTION(RWSTRING("RpHAnimHierarchyAddAnimTime"));
    RWASSERT(pHierarchy);
    RWASSERT(time >= 0.0f);

    result = TRUE;  /* if time == 0 Do nothing */

    if (time > 0.0f)
    {
        RwBool              bCallBack = FALSE;
        RwBool              bLoopCallBack = FALSE;
        RwBool              bRepeat;
        RwInt32             i;
        RwInt32             minBone;
        RwInt32             numNodes;
        RwReal              duration;
        RwReal              minTime;
        RpHAnimAnimation      *pCurrentAnim;
        RpHAnimKeyFrameHeader *pCurrentBoneFrame1;
        RpHAnimKeyFrameHeader *pCurrentBoneFrame2;
        RpHAnimKeyFrameHeader *pCurrentBoneFrame;
        RpHAnimHierarchyCallBack pAnimCallBack;
        RpHAnimHierarchyCallBack pAnimLoopCallBack;

        pCurrentAnim = pHierarchy->pCurrentAnim;

        bCallBack |= ( ((pHierarchy->currentTime <
                         pHierarchy->animCallBackTime) &&
                        (pHierarchy->currentTime + time >=
                         pHierarchy->animCallBackTime)) );
        pHierarchy->currentTime += time;
        duration = pCurrentAnim->duration;

        if (pHierarchy->currentTime > duration)
        {
            RwReal stacked;
            /* Loop! */
            while (pHierarchy->currentTime > duration)
            {
                pHierarchy->currentTime -= duration;

                bCallBack |=  (pHierarchy->currentTime >=
                               pHierarchy->animCallBackTime);

                bLoopCallBack = TRUE;
            }

            stacked = pHierarchy->currentTime;
            RpHAnimHierarchySetCurrentAnim(pHierarchy,
                                           pCurrentAnim);
            pHierarchy->currentTime = stacked;
        }

        pCurrentBoneFrame = (RpHAnimKeyFrameHeader *)
            rpHANIMHIERARCHYGETINTERPFRAME(pHierarchy, 0);
        pCurrentBoneFrame1 =  (RpHAnimKeyFrameHeader *)
            rpHANIMHIERARCHYGETINTERPFRAME1(pHierarchy, 0);
        pCurrentBoneFrame2 =  (RpHAnimKeyFrameHeader *)
            rpHANIMHIERARCHYGETINTERPFRAME2(pHierarchy, 0);

        numNodes = pHierarchy->numNodes;

        minBone = 0;

        /* Setup the next frame pointer if required (due to flipping between forward and backward playing) */
        if( pHierarchy->pNextFrame == NULL )
        {
            RwReal fTime;
            /*
             * This should only be called in rare instances
             * where the direction of the animation has
             * changed and pNextFrame still needs to be
             * set as above condition was not satisfied
             */
            fTime = pHierarchy->currentTime;
            RpHAnimHierarchySetCurrentAnim(pHierarchy,
                                           pCurrentAnim);
            RpHAnimHierarchySetCurrentAnimTime( pHierarchy, fTime );
            /*
             * Moved assert here as pNextFrame is set to NULL in subtract anim time as a flag to reevaluate
             * Make sure that it is not NULL at this point
             */
            RWASSERT( pHierarchy->pNextFrame );
        }


        do
        {
            bRepeat = FALSE;
            minTime = pHierarchy->currentTime;

            for (i = 0; i < numNodes; i++)
            {
                if (pHierarchy->currentTime >=
                    pCurrentBoneFrame2->time)
                {
                    if (pCurrentBoneFrame2->time < minTime)
                    {
                        minTime = pCurrentBoneFrame2->time;
                        minBone = i;
                        bRepeat = TRUE;
                    }
                }

                pCurrentBoneFrame = (RpHAnimKeyFrameHeader *)
                    ((RwUInt8 *)pCurrentBoneFrame +
                     pHierarchy->currentKeyFrameSize);
                pCurrentBoneFrame1 = (RpHAnimKeyFrameHeader *)
                    ((RwUInt8 *)pCurrentBoneFrame1 +
                     pHierarchy->currentKeyFrameSize);
                pCurrentBoneFrame2 = (RpHAnimKeyFrameHeader *)
                    ((RwUInt8 *)pCurrentBoneFrame2 +
                     pHierarchy->currentKeyFrameSize);
            }

            if (bRepeat)
            {

                memcpy(rpHANIMHIERARCHYGETINTERPFRAME1(pHierarchy,
                                                       minBone),
                       rpHANIMHIERARCHYGETINTERPFRAME2(pHierarchy,
                                                       minBone),
                       pHierarchy->currentKeyFrameSize);
                memcpy(rpHANIMHIERARCHYGETINTERPFRAME2(pHierarchy,
                                                       minBone),
                       pHierarchy->pNextFrame,
                       pHierarchy->currentKeyFrameSize);
                pHierarchy->pNextFrame = (RwUInt8 *)
                    pHierarchy->pNextFrame +
                    pHierarchy->currentKeyFrameSize;
            }

            pCurrentBoneFrame =  (RpHAnimKeyFrameHeader *)
                rpHANIMHIERARCHYGETINTERPFRAME(pHierarchy, 0);
            pCurrentBoneFrame1 = (RpHAnimKeyFrameHeader *)
                rpHANIMHIERARCHYGETINTERPFRAME1(pHierarchy, 0);
            pCurrentBoneFrame2 = (RpHAnimKeyFrameHeader *)
                rpHANIMHIERARCHYGETINTERPFRAME2(pHierarchy, 0);

        }
        while (bRepeat);

        for (i = 0; i < numNodes; i++)
        {
            if (pCurrentBoneFrame->time !=
                pHierarchy->currentTime)
            {
                /* Interpolate to new time */
                RpHAnimFrameInterpolate(pHierarchy,
                                        pCurrentBoneFrame,
                                        pCurrentBoneFrame1,
                                        pCurrentBoneFrame2,
                                        pHierarchy->currentTime);
            }

            pCurrentBoneFrame = (RpHAnimKeyFrameHeader *)
                ((RwUInt8 *)pCurrentBoneFrame +
                 pHierarchy->currentKeyFrameSize);
            pCurrentBoneFrame1 = (RpHAnimKeyFrameHeader *)
                ((RwUInt8 *)pCurrentBoneFrame1 +
                 pHierarchy->currentKeyFrameSize);
            pCurrentBoneFrame2 = (RpHAnimKeyFrameHeader *)
                ((RwUInt8 *)pCurrentBoneFrame2 +
                 pHierarchy->currentKeyFrameSize);

        }

        if (bCallBack &&
            (pAnimCallBack = pHierarchy->pAnimCallBack) )
        {
            if (!pAnimCallBack(pHierarchy,
                               pHierarchy->pAnimCallBackData))
            {
                /* If the callback returns false,
                 * we don't want to call it again */
                pHierarchy->pAnimCallBack =
                    (RpHAnimHierarchyCallBack) NULL;
            }
        }

        if (bLoopCallBack &&
            (pAnimLoopCallBack = pHierarchy->pAnimLoopCallBack) )
        {
            if (!pAnimLoopCallBack(pHierarchy,
                                   pHierarchy->pAnimLoopCallBackData))
            {
                /* If the callback returns false,
                   we don't want to call it again */
                pHierarchy->pAnimLoopCallBack =
                    (RpHAnimHierarchyCallBack) NULL;
            }
        }
    }

    RWRETURN(result);
}

/**
 * \ingroup rphanim
 *  \ref RpHAnimHierarchySetCurrentAnimTime
 *  sets the current animation time
 *  of a hierarchy.
 *
 * If the hierarchy is attached to a skin, the skin will not update until
 * \ref RpHAnimHierarchyUpdateMatrices is called.
 *
 *  \param pHierarchy A pointer to a hierarchy.
 *  \param time The time to which to set the current animation.
 *
 *  \return TRUE on success, FALSE otherwise.
 *
 * \see RpHAnimHierarchyUpdateMatrices
 * \see RpHAnimHierarchyAddAnimTime
 * \see RpHAnimHierarchySubAnimTime
 * \see RpHAnimHierarchySetCurrentAnimTime
 */

RwBool
RpHAnimHierarchySetCurrentAnimTime(RpHAnimHierarchy * pHierarchy,
                                   RwReal time)
{
    RwReal offset;
    RWAPIFUNCTION(RWSTRING("RpHAnimHierarchySetCurrentAnimTime"));
    RWASSERT(pHierarchy);

    offset = time - pHierarchy->currentTime;

    if (offset < 0.0f)
    {
        RpHAnimHierarchySubAnimTime(pHierarchy, -offset);
    }
    else
    {
        if (!pHierarchy->pNextFrame)
        {
            /* if things have been playing backwards the nextframe
               pointer will be invalid so reset the animation and
               play forwards to where we want */
            RpHAnimHierarchySetCurrentAnim(pHierarchy,
                                           pHierarchy->pCurrentAnim);
            RpHAnimHierarchyAddAnimTime(pHierarchy, time);
        }
        else
        {
            RpHAnimHierarchyAddAnimTime(pHierarchy, offset);
        }
    }

    RWRETURN(TRUE);
}

/**
 * \ingroup rphanim
 * \ref RpHAnimHierarchySetAnimCallBack
 *  is used to set a callback function
 * that will be called when a hierarchy reaches a specified time.  The
 * callback function should return a pointer to the hierarchy if the function
 * should continue to be called, or NULL if it should never be called again.
 *
 * \param pHierarchy A pointer to a hierarchy.
 * \param pCallBack A pointer to a callback function.
 * \param time The time at which the callback function should be called.
 * \param pData A pointer to a block of data to be passed into the callback function
 *
 * \return A pointer to the hierarchy on success, NULL if an error occurs.
 *
 */

RpHAnimHierarchy     *
RpHAnimHierarchySetAnimCallBack(RpHAnimHierarchy * pHierarchy,
                                RpHAnimHierarchyCallBack pCallBack,
                                RwReal time, void *pData)
{
    RWAPIFUNCTION(RWSTRING("RpHAnimHierarchySetAnimCallBack"));

    RWASSERT(pHierarchy);

    pHierarchy->pAnimCallBack = pCallBack;
    pHierarchy->animCallBackTime = time;
    pHierarchy->pAnimCallBackData = pData;

    RWRETURN(pHierarchy);
}

/**
 * \ingroup rphanim
 * \ref RpHAnimHierarchySetAnimLoopCallBack
 *  is used to set a callback function
 * that will be called when a hierarchy's animation loops.  The
 * callback function should return a pointer to the hierarchy if the function
 * should continue to be called, or NULL if it should never be called again.
 *
 * \param pHierarchy A pointer to a hierarchy.
 * \param pCallBack A pointer to a callback function.
 * \param pData A pointer to a block of data to be passed into the callback function
 *
 * \return A pointer to the hierarchy on success, NULL if an error occurs.
 *
 */

RpHAnimHierarchy     *
RpHAnimHierarchySetAnimLoopCallBack(RpHAnimHierarchy * pHierarchy,
                                    RpHAnimHierarchyCallBack pCallBack,
                                    void *pData)
{
    RWAPIFUNCTION(RWSTRING("RpHAnimHierarchySetAnimLoopCallBack"));

    RWASSERT(pHierarchy);

    pHierarchy->pAnimLoopCallBack = pCallBack;
    pHierarchy->pAnimLoopCallBackData = pData;

    RWRETURN(pHierarchy);
}

/**
 * \ingroup rphanim
 * \ref RpHAnimHierarchyBlend
 *  interpolates between two hierarchies.  Each
 * hierarchy should have an animation attached, and the result will be
 * interpolated between the current state of the two input hierarchies
 *
 * The most common use of this function will probably be to blend from the
 * end of one animation to the start of the next. This would be done
 * with 3 hierarchies, anim1skel, hanimskel and tempskel.

 * Initially anim1 would be  running on anim1skel which is attached to the skin.
 * When the blend is required hanim is attached to hanimskel and tempskel
 * is now attached to the skin. For each update during the blend duration
 * RpHAnimHierarchyBlend is used to blend between anim1skel and hanimskel, storing
 * the results in tempskel. At the end of the blend hanimskel is attached to
 * the skin.

 * \param pOutHierarchy A pointer to a hierarchy in which the result is returned.
 * \param pInHierarchy1 A pointer to the first input hierarchy
 * \param pInHierarchy2 A pointer to the second input hierarchy
 * \param alpha The blending parameter
 * \li 0.0 returns pInHierarchy1
 * \li 1.0 returns pInHierarchy2
 *
 * \return TRUE on success, FALSE otherwise.
 *
 */

RwBool
RpHAnimHierarchyBlend(RpHAnimHierarchy * pOutHierarchy,
                      RpHAnimHierarchy * pInHierarchy1,
                      RpHAnimHierarchy * pInHierarchy2,
                      RwReal alpha)
{
    RwInt32             i;

    RWAPIFUNCTION(RWSTRING("RpHAnimHierarchyBlend"));
    RWASSERT(pOutHierarchy);
    RWASSERT(pInHierarchy1);
    RWASSERT(pInHierarchy2);
    RWASSERT((alpha >= (RwReal) 0.0) && (alpha <= (RwReal) 1.0));
    RWASSERT(pOutHierarchy->numNodes == pInHierarchy1->numNodes);
    RWASSERT(pOutHierarchy->numNodes == pInHierarchy2->numNodes);

    for (i = 0; i < pOutHierarchy->numNodes; i++)
    {
        RpHAnimFrameBlend(pOutHierarchy,
                          rpHANIMHIERARCHYGETINTERPFRAME(pOutHierarchy, i),
                          rpHANIMHIERARCHYGETINTERPFRAME(pInHierarchy1, i),
                          rpHANIMHIERARCHYGETINTERPFRAME(pInHierarchy2, i),
                          alpha);
    }

    RWRETURN(TRUE);
}

/**
 * \ingroup rphanim
 * \ref RpHAnimHierarchyAddTogether
 *  adds together two hierarchies.  Each
 * hierarchy should have an animation attached, and the result will be
 * the addition of the two input hierarchies.  This is mainly useful where
 * the second hierarchy's pose is a delta from a set pose to be added to the
 * pose held in the first hierarchy.  For example, a walk animation could have
 * a crouch pose added to it as a delta from a standing pose.
 * \note pOutHierarchy, pInHierarchy1 and pInHierarchy2 MUST be different.
 *
 * \param pOutHierarchy A pointer to a hierarchy in which the result is returned.
 * \param pInHierarchy1 A pointer to the first input hierarchy
 * \param pInHierarchy2 A pointer to the second input hierarchy
 *
 * \return TRUE on success, FALSE otherwise.
 *
 * \see RpHAnimAnimationMakeDelta
 *
 */

RwBool
RpHAnimHierarchyAddTogether(RpHAnimHierarchy * pOutHierarchy,
                            RpHAnimHierarchy * pInHierarchy1,
                            RpHAnimHierarchy * pInHierarchy2)
{
    RwInt32             i;

    RWAPIFUNCTION(RWSTRING("RpHAnimHierarchyAddTogether"));
    RWASSERT(pOutHierarchy);
    RWASSERT(pInHierarchy1);
    RWASSERT(pInHierarchy2);
    RWASSERT(pOutHierarchy->numNodes == pInHierarchy1->numNodes);
    RWASSERT(pOutHierarchy->numNodes == pInHierarchy2->numNodes);

    for (i = 0; i < pOutHierarchy->numNodes; i++)
    {

        RpHAnimFrameAddTogether(pOutHierarchy,
                                rpHANIMHIERARCHYGETINTERPFRAME(pOutHierarchy, i),
                                rpHANIMHIERARCHYGETINTERPFRAME(pInHierarchy1, i),
                                rpHANIMHIERARCHYGETINTERPFRAME(pInHierarchy2, i));
    }

    RWRETURN(TRUE);
}

/**
 * \ingroup rphanim
 * \ref RpHAnimHierarchyBlendSubHierarchy
 *  interpolates the nodes in a sub-hierarchy with it parent hierarchy.  Each
 * hierarchy should have an animation attached, and the result will be
 * interpolated between the current state of the two input hierarchies. Either
 * source hierarchy can be the sub-hierarchy, the blend is performed from
 * pInHierarchy1 to pInHierarchy2. The output hierarchy must be indentical in
 * structure to either the main or sub-hierarchy.
 *
 * \param pOutHierarchy A pointer to a hierarchy in which the result is returned.
 * \param pInHierarchy1 A pointer to the first source hierarchy
 * \param pInHierarchy2 A pointer to the second source hierarchy
 * \param alpha The blending parameter
 * \li 0.0 returns pInHierarchy1
 * \li 1.0 returns pInHierarchy2
 *
 * \return TRUE on success, FALSE otherwise.
 *
 */
RwBool
RpHAnimHierarchyBlendSubHierarchy(RpHAnimHierarchy * pOutHierarchy,
                                  RpHAnimHierarchy * pInHierarchy1,
                                  RpHAnimHierarchy * pInHierarchy2,
                                  RwReal alpha)
{
    RwInt32             i;

    RWAPIFUNCTION(RWSTRING("RpHAnimHierarchyBlendSubHierarchy"));
    RWASSERT(pOutHierarchy);
    RWASSERT(pInHierarchy1);
    RWASSERT(pInHierarchy2);
    RWASSERT((alpha >= (RwReal) 0.0) && (alpha <= (RwReal) 1.0));
    RWASSERT(pOutHierarchy->numNodes == pInHierarchy1->numNodes ||
             pOutHierarchy->numNodes == pInHierarchy2->numNodes);

    if (pOutHierarchy->flags & rpHANIMHIERARCHYSUBHIERARCHY)
    {
        RwInt32 outOffset = pOutHierarchy->offsetInParent;
        RwInt32 in1Offset = pInHierarchy1->offsetInParent;
        RwInt32 in2Offset = pInHierarchy2->offsetInParent;

        for (i = pOutHierarchy->offsetInParent;
             i < pOutHierarchy->numNodes+pOutHierarchy->offsetInParent;
             i++)
        {
            /* blend keyframes */
            RpHAnimFrameBlend(pOutHierarchy,
                              rpHANIMHIERARCHYGETINTERPFRAME(pOutHierarchy, i - outOffset),
                              rpHANIMHIERARCHYGETINTERPFRAME(pInHierarchy1, i - in1Offset),
                              rpHANIMHIERARCHYGETINTERPFRAME(pInHierarchy2, i - in2Offset),
                              alpha);
        }
    }
    else /* output hierarchy is a main hierarchy */
    {
        if (pInHierarchy1->flags & rpHANIMHIERARCHYSUBHIERARCHY)
        {
            /* memcpy keys up to the parent offset */
            memcpy(rpHANIMHIERARCHYGETINTERPFRAME(pOutHierarchy, 0),
                   rpHANIMHIERARCHYGETINTERPFRAME(pInHierarchy2, 0),
                   pOutHierarchy->currentKeyFrameSize * pInHierarchy1->offsetInParent);

            for (i = pInHierarchy1->offsetInParent;
                 i < pInHierarchy1->offsetInParent+pInHierarchy1->numNodes;
                 i++)
            {
                /* blend keyframes in the subhierarchy range */
                RpHAnimFrameBlend(pOutHierarchy,
                                  rpHANIMHIERARCHYGETINTERPFRAME(pOutHierarchy, i),
                                  rpHANIMHIERARCHYGETINTERPFRAME(pInHierarchy1, i - pInHierarchy1->offsetInParent),
                                  rpHANIMHIERARCHYGETINTERPFRAME(pInHierarchy2, i),
                                  alpha);
            }

            /* memcpy keys past sub hierarchy */
            memcpy(rpHANIMHIERARCHYGETINTERPFRAME(pOutHierarchy,
                                                  pInHierarchy1->offsetInParent+pInHierarchy1->numNodes),
                   rpHANIMHIERARCHYGETINTERPFRAME(pInHierarchy2,
                                                  pInHierarchy1->offsetInParent+pInHierarchy1->numNodes),
                   pOutHierarchy->currentKeyFrameSize *
                   (pOutHierarchy->numNodes-(pInHierarchy1->offsetInParent+pInHierarchy1->numNodes)));
        }
        else
        {
            /* memcpy keys up to the parent offset */
            memcpy(rpHANIMHIERARCHYGETINTERPFRAME(pOutHierarchy, 0),
                   rpHANIMHIERARCHYGETINTERPFRAME(pInHierarchy1, 0),
                   pOutHierarchy->currentKeyFrameSize * pInHierarchy2->offsetInParent);

            for (i = pInHierarchy2->offsetInParent; i < pInHierarchy2->offsetInParent+pInHierarchy2->numNodes; i++)
            {
                /* blend keyframes in the subhierarchy range */
                RpHAnimFrameBlend(pOutHierarchy,
                                  rpHANIMHIERARCHYGETINTERPFRAME(pOutHierarchy, i),
                                  rpHANIMHIERARCHYGETINTERPFRAME(pInHierarchy1, i),
                                  rpHANIMHIERARCHYGETINTERPFRAME(pInHierarchy2, i - pInHierarchy2->offsetInParent),
                                  alpha);
            }

            /* memcpy keys past sub hierarchy */
            memcpy(rpHANIMHIERARCHYGETINTERPFRAME(pOutHierarchy, pInHierarchy2->offsetInParent+pInHierarchy2->numNodes),
                   rpHANIMHIERARCHYGETINTERPFRAME(pInHierarchy1, pInHierarchy2->offsetInParent+pInHierarchy2->numNodes),
                   pOutHierarchy->currentKeyFrameSize * (pOutHierarchy->numNodes-(pInHierarchy2->offsetInParent+pInHierarchy2->numNodes)));
        }
    }

    RWRETURN(TRUE);
}

/**
 * \ingroup rphanim
 * \ref RpHAnimHierarchyAddSubHierarchy
 *  adds together two hierarchies.  Each
 * hierarchy should have an animation attached, and the result will be
 * the addition of the two input hierarchies. The output hierarchy must
 * be identical in structure to either the main or sub-hierarchy.
 *
 * \param pOutHierarchy A pointer to a hierarchy in which the result is returned.
 * \param pMainHierarchy A pointer to the parent hierarchy
 * \param pSubHierarchy A pointer to the sub hierarchy
 * \note pOutHierarchy, pMainHierarchy and pSubHierarchy MUST be different.
 *
 * \return TRUE on success, FALSE otherwise.
 *
 * \see RpHAnimAnimationMakeDelta
 *
 */
RwBool
RpHAnimHierarchyAddSubHierarchy(RpHAnimHierarchy * pOutHierarchy,
                                RpHAnimHierarchy * pMainHierarchy,
                                RpHAnimHierarchy * pSubHierarchy)
{
    RwInt32             i;
    RwInt32             outOffset = 0;

    RWAPIFUNCTION(RWSTRING("RpHAnimHierarchyAddSubHierarchy"));
    RWASSERT(pOutHierarchy);
    RWASSERT(pMainHierarchy);
    RWASSERT(pSubHierarchy);
    RWASSERT(pOutHierarchy->numNodes == pMainHierarchy->numNodes ||
             pOutHierarchy->numNodes == pSubHierarchy->numNodes);

    if (!(pOutHierarchy->flags & rpHANIMHIERARCHYSUBHIERARCHY))
    {
        outOffset = pSubHierarchy->offsetInParent;
    }

    for (i = 0; i < pOutHierarchy->numNodes; i++)
    {

        RpHAnimFrameAddTogether(pOutHierarchy,
                                rpHANIMHIERARCHYGETINTERPFRAME(pOutHierarchy,
                                                               i + outOffset),
                                rpHANIMHIERARCHYGETINTERPFRAME(pMainHierarchy, i + pSubHierarchy->offsetInParent),
                                rpHANIMHIERARCHYGETINTERPFRAME(pSubHierarchy, i));
    }

    RWRETURN(TRUE);
}

/**
 * \ingroup rphanim
 * \ref RpHAnimHierarchyCopy
 *  copies state of bones in the in-hierarchy into
 *                              the out-hierarchy.
 *
 * \param pOutHierarchy A pointer to a hierarchy in which the result is returned.
 * \param pInHierarchy A pointer to the input hierarchy
 *
 * \return TRUE on success, FALSE otherwise.
 *
 */

RwBool
RpHAnimHierarchyCopy(RpHAnimHierarchy * pOutHierarchy,
                     RpHAnimHierarchy * pInHierarchy)
{
    RWAPIFUNCTION(RWSTRING("RpHAnimHierarchyCopy"));
    RWASSERT(pOutHierarchy);
    RWASSERT(pInHierarchy);
    RWASSERT(pOutHierarchy->numNodes == pInHierarchy->numNodes);

    memcpy(rpHANIMHIERARCHYGETINTERPFRAME(pOutHierarchy, 0),
           rpHANIMHIERARCHYGETINTERPFRAME(pInHierarchy, 0),
           pOutHierarchy->currentKeyFrameSize * pOutHierarchy->numNodes);

    RWRETURN(TRUE);
}

/**
 * \ingroup rphanim
 * \ref RpHAnimHierarchyUpdateMatrices
 * iterates over all the nodes in the hierarchy updating a combination
 * of three members those being the local array of matrices, the modeling
 * matrices in \ref RwFrame's and the LTM's in RwFrame. If this hierarchy
 * is a sub-hierarchy then the matrices updated will be a sub range of the
 * parent hierarchies matrices.
 *
 * \param pHierarchy A pointer to a hierarchy
 *
 * \return TRUE on success, FALSE if an error occurs.
 */
RwBool
RpHAnimHierarchyUpdateMatrices(RpHAnimHierarchy * pHierarchy)
{
    RwBool              result;

    RWAPIFUNCTION(RWSTRING("RpHAnimHierarchyUpdateMatrices"));

    result = ( (RpHAnimHierarchy *)NULL !=  pHierarchy);

    if (result)
    {
        RpHAnimKeyFrameHeader *pCurrentInterpFrame = (RpHAnimKeyFrameHeader *)
            rpHANIMHIERARCHYGETINTERPFRAME(pHierarchy, 0);
        HAnimMatrixStack    matrixStack;
        RwMatrix           *pMatrixStackTop = &matrixStack[0];
        RpHAnimNodeInfo   *pCurrentFrame;
        RwInt32             pCurrentFrameFlags;
        RwBool              noMatrices, updateLTMs;
        RwBool              updateModellingMatrices;
        RwInt32             currentKeyFrameSize;
        RwInt32             i, numNodes;
        RwMatrix            frameMatrix;
        RwMatrix            parentMatrix;
        RwMatrix            tempMatrix;
        RwMatrix           *pCurrentMatrix, *pMatrixArray;

        if (RwFrameGetParent(pHierarchy->parentFrame) &&
            !(pHierarchy->flags & rpHANIMHIERARCHYLOCALSPACEMATRICES) )
        {
            RwFrame *parentFrame = RwFrameGetParent(pHierarchy->parentFrame);
            /* this anim hierarchy is not set to the parent of
             * the frame hierarchy so work out the
             * anim hierarchy's parent LTM without
             * calling RwFrameGetLTM since
             * we don't want to sync everything */

            RwMatrixCopy(&parentMatrix, RwFrameGetMatrix(parentFrame));
            RWASSERT(rwMatrixValidFlags(&parentMatrix, RW_TOL_ORTHONORMAL));

            parentFrame = RwFrameGetParent(parentFrame);

            while (parentFrame)
            {
                RwMatrixCopy(&tempMatrix, &parentMatrix);
                RWASSERT(rwMatrixValidFlags(&tempMatrix, RW_TOL_ORTHONORMAL));

                RwMatrixMultiply(&parentMatrix, &tempMatrix,
                                 RwFrameGetMatrix(parentFrame));
                parentFrame = RwFrameGetParent(parentFrame);
            }
        }
        else
        {
            RwMatrixSetIdentity(&parentMatrix);
        }

        /* push the parent matrix onto the stack to avoid reading outside the
           stack memory on the last loop of the update */
        RwMatrixCopy(pMatrixStackTop, &parentMatrix);
        pMatrixStackTop++;

        /* RWASSERT(rwMatrixValidFlags(&parentMatrix, _EPSILON)); */
        /* RWASSERT(rwMatrixValidFlags(pCurrentMatrix, _EPSILON)); */

        numNodes = pHierarchy->numNodes;
        updateModellingMatrices = ( pHierarchy->flags &
                                    rpHANIMHIERARCHYUPDATEMODELLINGMATRICES );
        updateLTMs = (pHierarchy->flags & rpHANIMHIERARCHYUPDATELTMS);
        noMatrices = (pHierarchy->flags & rpHANIMHIERARCHYNOMATRICES);
        currentKeyFrameSize = pHierarchy->currentKeyFrameSize;
        pCurrentFrame = pHierarchy->pNodeInfo;
        pMatrixArray = pHierarchy->pMatrixArray;
        pCurrentMatrix = pMatrixArray;

        if (updateLTMs)
        {
            /* we need to make sure these frames get sync'd to the world without
               recalculating LTMs */
            if (!(rwObjectGetPrivateFlags(pHierarchy->parentFrame->root) &
                (rwFRAMEPRIVATEHIERARCHYSYNCLTM | rwFRAMEPRIVATEHIERARCHYSYNCOBJ)))
            {
                /* Not in the dirty list - add it */
                rwLinkListAddLLLink(&RWSRCGLOBAL(dirtyFrameList),
                                    &pHierarchy->parentFrame->root->inDirtyListLink);

                rwObjectSetPrivateFlags(pHierarchy->parentFrame->root,
                        rwObjectGetPrivateFlags(pHierarchy->parentFrame->root) |
                        rwFRAMEPRIVATEHIERARCHYSYNCOBJ);
            }
        }

        for (i = 0; i < numNodes; i++)
        {
            RwMatrix   *ptrMatrix;

            RWASSERT(&matrixStack[0] <= pMatrixStackTop);
            RWASSERT(pMatrixStackTop < &matrixStack[HANIMMATRIXSTACKHEIGHT]);

            pCurrentFrameFlags = pCurrentFrame->flags;

            if (rpHANIMPUSHPARENTMATRIX & pCurrentFrameFlags)
            {
                ptrMatrix = pMatrixStackTop++;
                RwMatrixCopy(ptrMatrix, &parentMatrix);
                RWASSERT(rwMatrixValidFlags(ptrMatrix, RW_TOL_ORTHONORMAL));
            }

            RpHAnimFrameToMatrix(pHierarchy, &frameMatrix,
                                 pCurrentInterpFrame);

            if ( (pCurrentFrame->pFrame != NULL) &&
                 updateModellingMatrices )
            {
                RwMatrix * const pMatrix =
                    RwFrameGetMatrix(pCurrentFrame->pFrame);

                RwMatrixCopy(pMatrix, &frameMatrix);
                RWASSERT(rwMatrixValidFlags(pMatrix, RW_TOL_ORTHONORMAL));

                if (!updateLTMs)
                {
                    RwFrameUpdateObjects(pCurrentFrame->pFrame);
                }
            }

            /* RWASSERT(rwMatrixValidFlags(&frameMatrix, _EPSILON)); */
            /* RWASSERT(rwMatrixValidFlags(&parentMatrix, _EPSILON)); */

            HAnimMatrixMultiplyMacro(&tempMatrix, &frameMatrix, &parentMatrix);

            /* RWASSERT(rwMatrixValidFlags(&tempMatrix, _EPSILON)); */

            if ( (pCurrentFrame->pFrame) != NULL &&  updateLTMs)
            {
                RwMatrix * const pMatrix = &pCurrentFrame->pFrame->ltm;

                RwMatrixCopy(pMatrix, &tempMatrix);
                RWASSERT(rwMatrixValidFlags(pMatrix, RW_TOL_ORTHONORMAL));

                rwObjectSetPrivateFlags(pCurrentFrame->pFrame,
                            rwObjectGetPrivateFlags(pCurrentFrame->pFrame) |
                            rwFRAMEPRIVATESUBTREESYNCOBJ);

            }

            /* RWASSERT(rwMatrixValidFlags(&tempMatrix, _EPSILON)); */
            if (!noMatrices)
            {
                pMatrixArray[i] = tempMatrix;
            }

            /* RWASSERT(rwMatrixValidFlags(pCurrentMatrix, _EPSILON)); */

            ptrMatrix = ( (rpHANIMPOPPARENTMATRIX & pCurrentFrameFlags) ?
                          --pMatrixStackTop:
                          &tempMatrix );

            RWASSERT(&matrixStack[0] <= pMatrixStackTop);
            RWASSERT(pMatrixStackTop < &matrixStack[HANIMMATRIXSTACKHEIGHT]);

            RwMatrixCopy(&parentMatrix, ptrMatrix);
            RWASSERT(rwMatrixValidFlags(&parentMatrix, RW_TOL_ORTHONORMAL));

            /* RWASSERT(rwMatrixValidFlags(&parentMatrix, _EPSILON)); */

            pCurrentInterpFrame = ( (RpHAnimKeyFrameHeader *)
                                    ((RwUInt8 *)pCurrentInterpFrame +
                                     currentKeyFrameSize) );
            pCurrentMatrix++;
            pCurrentFrame++;
        }

        /* RWASSERT(&matrixStack[0] == pMatrixStackTop); */
    }

    RWRETURN(result);
}

/**
 * \ingroup rphanim
 * \ref RpHAnimIDGetIndex
 * returns the index of a node in the node arrays based on
 * a given node ID. This is useful for procedurally animating nodes
 * since you need the index to access the matrices/keyframes. This is
 * also very useful for creating subhierarchies. Given the ID on the root
 * of the subhierarchy this gives you the index to pass into
 * \ref RpHAnimHierarchyCreateSubHierarchy
 *
 * \param pHierarchy Hierarchy to find the node index within
 * \param ID  ID of the node for which you want the index
 */
RwInt32
RpHAnimIDGetIndex(RpHAnimHierarchy *pHierarchy, RwInt32 ID)
{
    RwInt32             result = -1;
    RwInt32             i;
    RpHAnimNodeInfo   *pNodeInfo;

    RWAPIFUNCTION(RWSTRING("RpHAnimIDGetIndex"));
    RWASSERT(pHierarchy);

    pNodeInfo = pHierarchy->pNodeInfo;

    for (i = 0; i < pHierarchy->numNodes; i++)
    {
        if (ID == pNodeInfo[i].nodeID)
        {
            result = i;
            break;
        }
    }

    RWRETURN(result);
}

/**
 * \ingroup rphanim
 * \ref RpHAnimAnimationCreate
 * creates an animation, and allocates space for the number of keyframes
 * required. This keyframe block should be filled with keyframe sequences
 * for controlling each node in a hierarchy. See \ref RpHAnimAnimation for
 * details of the required layout of the keyframes.
 *
 * \param typeID    The ID number specifying the interpolation scheme to be used
                    with this animation, also determines the keyframe size.
 * \param numFrames The number of keyframes in the animation.
 * \param flags     Reserved for future use - should be 0.
 * \param duration  The total length of the animation.
 *
 * \return A pointer to the created animation, or NULL if an error occurs.
 */

RpHAnimAnimation         *
RpHAnimAnimationCreate(RwInt32 typeID,
                       RwInt32 numFrames,
                       RwInt32 flags,
                       RwReal duration)
{
    RpHAnimAnimation           *pAnimation;
    RpHAnimInterpolatorInfo    *interpInfo;

    RWAPIFUNCTION(RWSTRING("RpHAnimAnimationCreate"));
    RWASSERT(numFrames > 0);

    interpInfo = RpHAnimGetInterpolatorInfo(typeID);
    if (!interpInfo)
    {
        /* Unregistered animation type */
        RWRETURN((RpHAnimAnimation *)NULL);
    }

    pAnimation = (RpHAnimAnimation *) RwAnimAnimationMalloc();
    pAnimation->numFrames = numFrames;
    pAnimation->duration = duration;
    pAnimation->flags = flags;
    pAnimation->interpInfo = interpInfo;
    pAnimation->pFrames =
        (RpHAnimStdKeyFrame *) RwMalloc(numFrames * interpInfo->keyFrameSize);

    RWRETURN(pAnimation);
}

/**
 * \ingroup rphanim
 * \ref RpHAnimAnimationDestroy
 * destroys an animation, performs no checking as to whether any
 * hierarchies are using this animation.
 *
 * \param pAnimation Pointer to the animation to be destroyed
 *
 * \return NULL if an error occurs.
 */

RpHAnimAnimation         *
RpHAnimAnimationDestroy(RpHAnimAnimation * pAnimation)
{
    RWAPIFUNCTION(RWSTRING("RpHAnimAnimationDestroy"));

    if (NULL != pAnimation)
    {
        RpHAnimAnimation *animPtr = pAnimation;

        if (NULL != pAnimation->pFrames)
        {
            RwFree(pAnimation->pFrames);
            pAnimation->pFrames = NULL;

        }
        RwAnimAnimationFree(pAnimation);
        pAnimation = (RpHAnimAnimation *)NULL;
        RWRETURN(animPtr);
    }

    RWRETURN((RpHAnimAnimation *)NULL);
}

/**
 * \ingroup rphanim
 * \ref RpHAnimAnimationRead is a wrapper around RpHAnimAnimationStreamRead.
 * Read opens the stream and searches for the appropriate chunk header and
 * then calls stream read.
 *
 * Reads a .ANM animation file from disk.
 *
 * \param pFilename A pointer to the name of the file to be read.
 *
 * \return A pointer to the animation, or NULL if an error occurs
 *
 */

RpHAnimAnimation         *
RpHAnimAnimationRead(const RwChar * pFilename)
{
    RpHAnimAnimation         *pAnimation;
    RwStream           *pStream;

    RWAPIFUNCTION(RWSTRING("RpHAnimAnimationRead"));
    RWASSERT(pFilename);

    pStream = RwStreamOpen(rwSTREAMFILENAME, rwSTREAMREAD, pFilename);

    if (!pStream)
    {
        RWRETURN((RpHAnimAnimation *)NULL);
    }

    if (!RwStreamFindChunk(pStream, rwID_HANIMANIMATION,
                           (RwUInt32 *)NULL, (RwUInt32 *)NULL))
    {
        RWRETURN((RpHAnimAnimation *)NULL);
    }

    pAnimation = RpHAnimAnimationStreamRead(pStream);

    RwStreamClose(pStream, NULL);

    RWRETURN(pAnimation);
}

/**
 * \ingroup rphanim
 * \ref RpHAnimAnimationWrite is basically a wrapper around \ref RpHAnimAnimationStreamWrite.
 * Write will open a stream and call stream write.
 *
 * Writes a .ANM animation file to disk.
 *
 * \param pAnimation A pointer to the animation to be written.
 * \param pFilename A pointer to the name of the file to be written to.
 *
 * \return TRUE on success, or FALSE if an error occurs.
 *
 */

RwBool
RpHAnimAnimationWrite(RpHAnimAnimation * pAnimation, const RwChar * pFilename)
{
    RwStream           *pStream;

    RWAPIFUNCTION(RWSTRING("RpHAnimAnimationWrite"));
    RWASSERT(pAnimation);
    RWASSERT(pFilename);

    pStream = RwStreamOpen(rwSTREAMFILENAME, rwSTREAMWRITE, pFilename);

    if (!pStream)
    {
        RWRETURN(FALSE);
    }

    RpHAnimAnimationStreamWrite(pAnimation, pStream);

    RwStreamClose(pStream, NULL);

    RWRETURN(TRUE);
}

/**
 *
 * \ingroup rphanim
 * \ref RpHAnimAnimationStreamRead creates and reads in animation data. Upon reading it will
 * lookup pointers to interpolation functions based on the interpolation
 * scheme ID which is written out to the file. If the interpolation scheme
 * is not available (i.e. it was saved from an application with a custom
 * overloaded type) then the animationstreamread will fail.
 *
 * Calls the overloaded
 * \ref RpHAnimKeyFrameStreamReadCallBack and \ref RpHAnimKeyFrameStreamWriteCallBack
 * functions for the particular keyframe type allowing keyframes to be of
 * varying sizes/data types etc. This not only allows interpolation schemes to
 * perform higher order interpolation but allows them to attach extra data to achieve
 * it or compress data in order to save memory space.
 *
 * \param stream A pointer to the stream to be read from.
 *
 * \return A pointer to the animation, or NULL if an error occurs
 *
 */

RpHAnimAnimation         *
RpHAnimAnimationStreamRead(RwStream * stream)
{
    RwInt32                     streamVersion;
    RwInt32                     typeID;
    RpHAnimInterpolatorInfo    *interpInfo;
    RpHAnimAnimation           *pAnimation;

    RWAPIFUNCTION(RWSTRING("RpHAnimAnimationStreamRead"));
    RWASSERT(stream);

    if (!RwStreamReadInt
        (stream, (RwInt32 *) & (streamVersion), sizeof(RwInt32)))
    {
        RWRETURN((RpHAnimAnimation *)NULL);
    }

    if (streamVersion != 0x100)
    {
        RWRETURN((RpHAnimAnimation *)NULL);
    }

    if (!RwStreamReadInt
        (stream, (RwInt32 *) & (typeID), sizeof(RwInt32)))
    {
        RWRETURN((RpHAnimAnimation *)NULL);
    }

    interpInfo = RpHAnimGetInterpolatorInfo(typeID);
    if (!interpInfo)
    {
        RWRETURN((RpHAnimAnimation *)NULL);
    }

    /* allocate the animation */
    pAnimation = (RpHAnimAnimation *) RwAnimAnimationMalloc();

    if (!pAnimation)
    {
        RWRETURN((RpHAnimAnimation *)NULL);
    }

    pAnimation->interpInfo = interpInfo;

    if (!RwStreamReadInt
        (stream, (RwInt32 *) & (pAnimation->numFrames), sizeof(RwInt32)))
    {
        RWRETURN((RpHAnimAnimation *)NULL);
    }

    if (!RwStreamReadInt
        (stream, (RwInt32 *) & (pAnimation->flags), sizeof(RwInt32)))
    {
        RWRETURN((RpHAnimAnimation *)NULL);
    }

    if (!RwStreamReadReal
        (stream, (RwReal *) & (pAnimation->duration), sizeof(RwReal)))
    {
        RWRETURN((RpHAnimAnimation *)NULL);
    }

    RWASSERT(interpInfo->keyFrameStreamReadCB);
    interpInfo->keyFrameStreamReadCB(stream, pAnimation);

    RWRETURN(pAnimation);
}

/**
 * \ingroup rphanim
 * \ref RpHAnimAnimationStreamWrite writes a hierarchical animation to a stream.  Calls the overloaded
 * \ref RpHAnimKeyFrameStreamReadCallBack and \ref RpHAnimKeyFrameStreamWriteCallBack
 * functions for the particular keyframe type allowing keyframes to be of
 * varying sizes/data types etc. This not only allows interpolation schemes to
 * perform higher order interpolation but allows them to attach extra data to achieve
 * it or compress data in order to save memory space.
 *
 * \param pAnimation A pointer to the animation to be written.
 * \param stream A pointer to the stream to be written to.
 *
 * \return TRUE on success, or FALSE if an error occurs.
 *
 */

RwBool
RpHAnimAnimationStreamWrite(RpHAnimAnimation * pAnimation,
                            RwStream * stream)
{
    RwInt32 streamVersion = rpHANIMSTREAMCURRENTVERSION;

    RWAPIFUNCTION(RWSTRING("RpHAnimAnimationStreamWrite"));
    RWASSERT(pAnimation);
    RWASSERT(stream);

    RwStreamWriteChunkHeader(stream, rwID_HANIMANIMATION,
                             RpHAnimAnimationStreamGetSize(pAnimation));

    if (!RwStreamWriteInt
        (stream, (RwInt32 *) & (streamVersion), sizeof(RwInt32)))
    {
        RWRETURN(FALSE);
    }

    if (!RwStreamWriteInt
        (stream, (RwInt32 *) & (pAnimation->interpInfo->typeID), sizeof(RwInt32)))
    {
        RWRETURN(FALSE);
    }

    if (!RwStreamWriteInt
        (stream, (RwInt32 *) & (pAnimation->numFrames), sizeof(RwInt32)))
    {
        RWRETURN(FALSE);
    }

    if (!RwStreamWriteInt
        (stream, (RwInt32 *) & (pAnimation->flags), sizeof(RwInt32)))
    {
        RWRETURN(FALSE);
    }

    if (!RwStreamWriteReal
        (stream, (RwReal *) & (pAnimation->duration), sizeof(RwReal)))
    {
        RWRETURN(FALSE);
    }

    RWASSERT(pAnimation->interpInfo->keyFrameStreamWriteCB);
    pAnimation->interpInfo->keyFrameStreamWriteCB(pAnimation, stream);

    RWRETURN(TRUE);
}

/**
 * \ingroup rphanim
 * \ref RpHAnimAnimationStreamGetSize is used in stream writing and will call the overloaded
 * interpolator \ref RpHAnimKeyFrameStreamGetSizeCallBack function in order to
 * acquire the size of the unknown keyframe data.
 *
 * \param pAnimation A pointer to the hierarchical animation.
 *
 * \return Size of the hierarchical animation in bytes.
 *
 */

RwInt32
RpHAnimAnimationStreamGetSize(RpHAnimAnimation * pAnimation)
{
    RwInt32             size = 0;

    RWAPIFUNCTION(RWSTRING("RpHAnimAnimationStreamGetSize"));

    RWASSERT(pAnimation);

    size += sizeof(RwInt32);    /* stream version */
    size += sizeof(RwInt32);    /* animation type id */
    size += sizeof(RwInt32);    /* numframes */
    size += sizeof(RwInt32);    /* flags */
    size += sizeof(RwReal);     /* duration */

    /* and add size of the keyframes */
    RWASSERT(pAnimation->interpInfo->keyFrameStreamGetSizeCB);
    size += pAnimation->interpInfo->keyFrameStreamGetSizeCB(pAnimation);

    RWRETURN(size);
}

/**
 * \ingroup rphanim
 * \ref RpHAnimAnimationMakeDelta
 * makes a delta animation from a given RpHAnimAnimation, it requires
 * the number of nodes to decode the animation and a time from which to
 * make the delta.  This will give you the animation which can be added on to
 * another animation, e.g. characters leaning whilst running.  This calls the
 * overloaded interpolation scheme
 * \ref RpHAnimKeyFrameMulRecipCallBack
 * which multiplies the incoming keyframe by the reciprocal of the start
 * keyframe given.
 *
 * \param pAnimation A pointer to the animation.
 * \param numNodes Number of node to decode the animation.
 * \param time The time in the animation to delta from.
 *
 * \return TRUE on success, or FALSE if an error occurs.
 *
 */

RwBool
RpHAnimAnimationMakeDelta(RpHAnimAnimation * pAnimation,
                          RwInt32 numNodes,
                          RwReal time)
{
    RpHAnimHierarchy      *pHierarchy;
    RwInt32                i;
    RwInt32                keyFrameSize;
    RpHAnimKeyFrameHeader *pHierarchyBoneFrame;
    RpHAnimKeyFrameHeader *pAnimationBoneFrame;
    RpHAnimKeyFrameHeader *pNextAnimFrame;
    RpHAnimKeyFrameHeader *pSearchFrame;

    RWAPIFUNCTION(RWSTRING("RpHAnimAnimationMakeDelta"));
    RWASSERT(pAnimation);

    if (!pAnimation)
    {
        RWRETURN(FALSE);
    }

    /* Create a hierarchy to get the animation at desired delta time */

    keyFrameSize = pAnimation->interpInfo->keyFrameSize;
    pHierarchy = RpHAnimHierarchyCreate(numNodes,
                                        (RwUInt32 *)NULL,
                                        (RwInt32 *)NULL,
                                        (RpHAnimHierarchyFlag)0,
                                        keyFrameSize);
    if (!pHierarchy)
    {
        RWRETURN(FALSE);
    }

    RpHAnimHierarchySetCurrentAnim(pHierarchy, pAnimation);
    RpHAnimHierarchySetCurrentAnimTime(pHierarchy, time);

    /* Start of frame list has a frame for each bone (time 0) */
    pHierarchyBoneFrame = (RpHAnimKeyFrameHeader *)
        rpHANIMHIERARCHYGETINTERPFRAME(pHierarchy, 0);
    pAnimationBoneFrame = (RpHAnimKeyFrameHeader *)
        pAnimation->pFrames;

    /* Track each bone in hierarchy with the frame-sequence
     * for that bone in the animation */
    for (i = 0; i < numNodes; i++)
    {
        /* Now change this bones frames in the animation */
        pNextAnimFrame = pAnimationBoneFrame;
        while (1)
        {
            /* Transform animation frame by inverse of hierarchy frame */
            RWASSERT(pAnimation->interpInfo->keyFrameMulRecipCB);
            pAnimation->interpInfo->keyFrameMulRecipCB(pNextAnimFrame, pHierarchyBoneFrame);

            /* End of animation for this bone? */
            if (pNextAnimFrame->time == pAnimation->duration)
            {
                break;
            }

            /* Now find the next frame for this bone (frame that points back to this one) */
            pSearchFrame = pNextAnimFrame;
            while (pSearchFrame->prevFrame != pNextAnimFrame)
            {
                pSearchFrame = (RpHAnimKeyFrameHeader *)((RwUInt8 *)pSearchFrame + pHierarchy->currentKeyFrameSize);
            }

            pNextAnimFrame = pSearchFrame;
        }

        /* Next bone */
        pHierarchyBoneFrame = (RpHAnimKeyFrameHeader *)((RwUInt8 *)pHierarchyBoneFrame + pHierarchy->currentKeyFrameSize);
        pAnimationBoneFrame = (RpHAnimKeyFrameHeader *)((RwUInt8 *)pAnimationBoneFrame + pHierarchy->currentKeyFrameSize);

    }

    /* Done */
    RpHAnimHierarchyDestroy(pHierarchy);

    RWRETURN(TRUE);
}

/**
 * \ingroup rphanim
 * \ref RpHAnimPluginAttach
 *  is used to attach the Animation plugin to the
 * RenderWare system to enable the manipulation of
 * animating node hierarchies.
 * The plugin must be attached between initializing the system
 * with \ref RwEngineInit
 * and opening it with \ref RwEngineOpen.
 *
 * \return TRUE if successful, FALSE if an error occurs.
 *
 */

RwBool
RpHAnimPluginAttach(void)
{
    RwInt32             success = 0;
    RWAPIFUNCTION(RWSTRING("RpHAnimPluginAttach"));

    /* Register the plugIn */

    if (RwEngineRegisterPlugin(0,
                               rwID_HANIMPLUGIN,
                               HAnimOpen, HAnimClose) < 0)
    {
        RWRETURN(FALSE);
    }

    /* Extend RwFrames to hold ID and hierarchy ptr */
    RpHAnimAtomicGlobals.engineOffset =
        RwFrameRegisterPlugin(sizeof(RpHAnimFrameExtension),
                              rwID_HANIMPLUGIN,
                              HAnimConstructor, HAnimDestructor,
                              HAnimCopy);

    /* Attach the stream handling functions */
    success =
        RwFrameRegisterPluginStream(rwID_HANIMPLUGIN,
                                    HAnimRead, HAnimWrite, HAnimSize);

    RWRETURN((success >= 0) && (RpHAnimAtomicGlobals.engineOffset >= 0));
}

