/*
 * HAnimCnv plugin
 */

/**********************************************************************
 *
 * File : rthanimcnv.c
 *
 * Abstract : Conversion functions to move from RpAnim and RpSkin to
 *            RpHAnim
 *
 **********************************************************************
 *
 * 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 rthanimcnv
 * \page rthanmcvoverview RtHAnimCnv Toolkit Overview
 *
 * Rthanmcv is a set of tools allowing the conversion of RpAnim and RpSkin
 * data into RpHAnim data. It provides functions to extract an HAnim hierarchy 
 * from an RpClumps frame hierarchy or from an RpSkin skinned atomic, and also
 * functions to extract RpHAnimAnimations from RpAnim clumps or conversion of
 * RpSkinAnim object into RpHAnimAnimation objects.
 * In order to function properly the plugin requires that both the RpAnim and 
 * RpSkin plugins are attached as well as the RpHAnim plugin. Due to this
 * requirement the original RpAnim and RpSkin data will still be present if
 * conversion is applied and clumps streamed out. For this reason it is
 * recommended that for final data assets are re-exported where possible.
 */



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

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

#include "rwcore.h"
#include "rpworld.h"
#include "rtslerp.h"
#include "rtquat.h"
#include "rpanim.h"
#include "rpbone.h"
#include "rpcriter.h"
#include "rpdbgerr.h"
#include "rpplugin.h"

#include "archive/rpskin310.h"
#include "rpanim.h"
#include "rphanim.h"
#include "rthanmcv.h"

#if (!defined(DOXYGEN))
static const char rcsid[] __RWUNUSED__ =
    "@@@@(#)$Id: rthanmcv.c,v 1.16 2001/09/24 15:17:09 johns Exp $";
#endif /* (!defined(DOXYGEN)) */

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

/* Defines from rpbone.c */


#define DEFAULTTAG  -1

/* Maximum number of bones we can handle in a single model */
#define MAXBONES 256

#define _EPSILON          ((RwReal)(0.001))

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

typedef struct RtHAnimCnvState RtHAnimCnvState;
struct RtHAnimCnvState
{
    int                 CurrentLevel /* = 0 */ ;
    int                 LastLevel /* = 0 */ ;
    RwBool              bChildren;
    RpHAnimNodeInfo   *pCurrentFrame;
    RpHAnimNodeInfo   *pLastFrame;
    RwInt32             ExtractSequenceFrameNumber;
    RwChar             *pExtractSequenceName /* = NULL */ ;
    RpAnimSequence     *pTranslateSequences[MAXBONES];
    RpAnimSequence     *pRotateSequences[MAXBONES];
    RwFrame            *pDefaultFrames[MAXBONES];
};

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

static RtHAnimCnvState *HAnimCnv;

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

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

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

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

    if (HAnimCnv)
    {
        RwFree(HAnimCnv);
        HAnimCnv = (RtHAnimCnvState *)NULL;
    }

    RWASSERT(NULL == HAnimCnv);

    RWRETURNVOID();
}

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

    if (NULL == HAnimCnv)
    {
        HAnimCnv = (RtHAnimCnvState *) RwMalloc(sizeof(RtHAnimCnvState));
        memset(HAnimCnv, 0, sizeof(RtHAnimCnvState));
    }

    RWRETURNVOID();
}


/****************************************************************************
HierarchyGenerateCountFramesCallback
 
 Callback to perform first part of skin generation - counting the bones.

 Inputs :   RwFrame *       A pointer to a frame in the skeletal hierarchy
            void *          A pointer to a counter
                                 
 Outputs:   RwObject *      The frame pointer passed in
 */

static RwFrame     *
HierarchyGenerateCountFramesCallback(RwFrame * pFrame, void *pData)
{
    RwInt32 *pCount = (RwInt32 *) pData;

    RWFUNCTION(RWSTRING("HierarchyGenerateCountFramesCallback"));
    RWASSERT(pFrame);
    RWASSERT(pData);

    /* First, count the number of bones. */

    (*pCount)++;

    RwFrameForAllChildren(pFrame, HierarchyGenerateCountFramesCallback,
                          pData);

    RWRETURN(pFrame);

}

/****************************************************************************
HAnimGenerateHierarchyCallback
 
 Callback to generate the bones heirarchy - we use an array of animation 
 frames with PUSH and POP flags to infer the heirarchy, rather than explicit
 pointers.

 Inputs :   RwFrame *       A pointer to a frame in the skeletal hierarchy
            void *          A pointer to the skin we're generating
                                 
 Outputs:   RwObject *      The frame pointer passed in
 */

static RwFrame     *
HAnimGenerateHierarchyCallback(RwFrame * pFrame, void *pData)
{
    RpHAnimNodeInfo     *pNodeInfo;

    RWFUNCTION(RWSTRING("HAnimGenerateHierarchyCallback"));
    RWASSERT(pFrame);
    RWASSERT(pData);

    pNodeInfo = HAnimCnv->pCurrentFrame++;
    pNodeInfo->nodeID = _rpAnimSeqGetFrameTag(pFrame);

    if (HAnimCnv->CurrentLevel > HAnimCnv->LastLevel)
    {
        /* Found a child - need to push parent matrix */
        pNodeInfo->flags |= rpHANIMPUSHPARENTMATRIX;
    }

    HAnimCnv->LastLevel = HAnimCnv->CurrentLevel;

    HAnimCnv->CurrentLevel++;

    HAnimCnv->bChildren = FALSE;

    RwFrameForAllChildren(pFrame, HAnimGenerateHierarchyCallback, pData);

    HAnimCnv->CurrentLevel--;

    HAnimCnv->LastLevel = HAnimCnv->CurrentLevel - 1;

    if (HAnimCnv->bChildren == FALSE)
    {
        /* No children - must pop matrix */
        pNodeInfo->flags |= rpHANIMPOPPARENTMATRIX;
    }
    else
    {
        /* Last child does not push the matrix */

        HAnimCnv->pLastFrame->flags &= ~rpHANIMPUSHPARENTMATRIX;
    }

    HAnimCnv->pLastFrame = pNodeInfo;

    HAnimCnv->bChildren = TRUE;

    RWRETURN(pFrame);

}


/**
 * \ingroup rthanimcnv
 * \ref RtHAnimCnvCreateHAnimHierarchy 
 * creates an HAnim hierarchy structure from an RwFrame hierarchy.
 *
 * \param pFrame  A pointer to the root frame of the hierarchy
 *
 * \return  pointer to the newly created hierarchy on success
 *
 * \see RpSkinExtractAnimFromClumpSequence 
 */
RpHAnimHierarchy *
RtHAnimCnvCreateHAnimHierarchy(RwFrame * pFrame)
{
    RpHAnimHierarchy   *pHierarchy;
    RwInt32             frameCount;

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

    /* Initialize the skbone state block */
    HAnimCnvStateConstructor();
    
    frameCount = 0;

    /* Count the number of bones in the model */
    HierarchyGenerateCountFramesCallback(pFrame, (void *) &frameCount);

    /* Allocate the HAnimHierarchy */
    pHierarchy = RpHAnimHierarchyCreate(frameCount,
                                        (RwUInt32 *)NULL,
                                        (RwInt32 *)NULL,
                                        (RpHAnimHierarchyFlag)0,
                                        rpHANIMSTDKEYFRAMESIZE);    

    /* Now we need to fill in the frame infos */

    HAnimCnv->CurrentLevel = 0;
    HAnimCnv->LastLevel = 0;
    HAnimCnv->pCurrentFrame = pHierarchy->pNodeInfo;

    HAnimGenerateHierarchyCallback(pFrame, (void *) pHierarchy);

    {
        RpHAnimNodeInfo     *pNodeInfo;
        RwInt32             i;


        pNodeInfo = pHierarchy->pNodeInfo;

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

    }

    RpHAnimFrameSetHierarchy(pFrame, pHierarchy);

    /* destroy the skbone state block */
    HAnimCnvStateDestructor();

    RWRETURN(pHierarchy);
}

/**
 * \ingroup rthanimcnv
 * \ref RtHAnimCnvCreateHAnimHierarchyFromSkin 
 * creates an HAnim hierarchy from a skinned atomic.
 *
 * \param pSkinAtomic  A pointer to the atomic with the skin to convert    
 *
 * \return  pointer to the newly created HAnim hierarchy
 *
 */
RpHAnimHierarchy *
RtHAnimCnvCreateHAnimHierarchyFromSkin(RpAtomic * pSkinAtomic)
{
    RpHAnimHierarchy   *pHierarchy = (RpHAnimHierarchy *)NULL;
    RpSkin             *pSkin;
    RwFrame            *pFrame;

    RWAPIFUNCTION(RWSTRING("RtHAnimCnvCreateHAnimHierarchyFromSkin"));
    RWASSERT(pSkinAtomic);

    pSkin = RpSkinAtomicGetSkin(pSkinAtomic);

    if (pSkin)
    {   
        RwInt32 i;
        /* Allocate the HAnimHierarchy */
        pHierarchy = 
            RpHAnimHierarchyCreate(pSkin->numBones, 
                                   (RwUInt32 *)NULL, 
                                   (RwInt32 *)NULL, 
                                   rpHANIMHIERARCHYLOCALSPACEMATRICES, 
                                   rpHANIMSTDKEYFRAMESIZE);

        pFrame = RpAtomicGetFrame(pSkinAtomic);

        /* Now we need to fill in the frame infos */        
        for (i = 0; i < pHierarchy->numNodes; i++)
        {
            pHierarchy->pNodeInfo[i].flags = pSkin->pBoneInfo[i].flags;
            pHierarchy->pNodeInfo[i].nodeIndex = i;
            pHierarchy->pNodeInfo[i].pFrame = (RwFrame *)NULL;
            pHierarchy->pNodeInfo[i].nodeID = -1;
        }

        RpHAnimFrameSetHierarchy(pFrame, pHierarchy);
    }

    RWRETURN(pHierarchy);
}

/**
 * \ingroup rthanimcnv
 * \ref RtHAnimCnvSkinAnimToHAnimAnimation 
 * creates an HAnim animation from a skin animation.
 *
 * \param pSkinAnim  A pointer to the Skin Animation to convert    
 *
 * \return  pointer to the newly created HAnim animation
 *
 */
RpHAnimAnimation *
RtHAnimCnvSkinAnimToHAnimAnimation(RpSkinAnim *pSkinAnim)
{
    RpHAnimAnimation *pHAnimation;
    RwInt32 nodeIndex;

    RWAPIFUNCTION(RWSTRING("RtHAnimCnvSkinAnimToHAnimAnimation"));
    RWASSERT(pSkinAnim);

    pHAnimation = RpHAnimAnimationCreate(0x1, pSkinAnim->numFrames, pSkinAnim->flags, pSkinAnim->duration);
    for (nodeIndex = 0; nodeIndex < pSkinAnim->numFrames; nodeIndex++)
    {
        RtQuatConjugate(&((RpHAnimStdKeyFrame *)pHAnimation->pFrames)[nodeIndex].q, &pSkinAnim->pFrames[nodeIndex].q);
        ((RpHAnimStdKeyFrame *)pHAnimation->pFrames)[nodeIndex].t = pSkinAnim->pFrames[nodeIndex].t;
        ((RpHAnimStdKeyFrame *)pHAnimation->pFrames)[nodeIndex].time = pSkinAnim->pFrames[nodeIndex].time;
        ((RpHAnimStdKeyFrame *)pHAnimation->pFrames)[nodeIndex].prevFrame = 
            (RpHAnimStdKeyFrame *)pHAnimation->pFrames + (pSkinAnim->pFrames[nodeIndex].prevFrame - pSkinAnim->pFrames);
    }

    pHAnimation->interpInfo = RpHAnimGetInterpolatorInfo(rpHANIMSTDKEYFRAMETYPEID);

    RWRETURN(pHAnimation);
}

/****************************************************************************
 HAnimMakeTransKey
 
 Makes a new translation key frame for an rpanim sequence.
 
 Inputs :   RwV3d *         A pointer to the newly interpolated translation data
            RpAnimSequnce * A pointer to the animation sequence
            RwReal          The time for the new key frame
                                 
 Outputs:   None
 */

static void
HAnimMakeTransKey(RwV3d * pV, RpAnimSequence * pAnimSeq, RwReal time)
{
    RwInt32             nInt;
    RwReal              rITime = (RwReal) 0.0;
    RwReal              alpha;
    RwReal              beta;
    RwV3d              *pKey1;
    RwV3d              *pKey2;

    RWFUNCTION(RWSTRING("HAnimMakeTransKey"));
    RWASSERT(pV);
    RWASSERT(pAnimSeq);

    /* Find the keyframes to interpolate between */
    nInt = 0;

    for (nInt = 0; nInt < pAnimSeq->numInterpolators; nInt++)
    {
        rITime += pAnimSeq->interps[nInt].time;
        if (rITime > time)
        {
            /* Found it */
            break;
        }
    }

    if (nInt == pAnimSeq->numInterpolators)
    {
        /* Just need the last key */
        *pV = ((RwV3d *) pAnimSeq->keys)[pAnimSeq->numKeys - 1];
        RWRETURNVOID();
    }

    /* nInt is the interpolator we're after... */

    time -= (rITime - pAnimSeq->interps[nInt].time);
    beta = time / pAnimSeq->interps[nInt].time;
    alpha = (RwReal) 1.0 - beta;

    pKey1 =
        &((RwV3d *) pAnimSeq->keys)[pAnimSeq->
                                    interps[nInt].startKeyFrame];
    pKey2 =
        &((RwV3d *) pAnimSeq->keys)[pAnimSeq->
                                    interps[nInt].endKeyFrame];

    pV->x = (alpha * pKey1->x) + (beta * pKey2->x);
    pV->y = (alpha * pKey1->y) + (beta * pKey2->y);
    pV->z = (alpha * pKey1->z) + (beta * pKey2->z);

    RWRETURNVOID();
}

/****************************************************************************
 HAnimMakeRotKey
 
 Makes a new rotation key frame for an rpanim sequence.
 
 Inputs :   RtQuat *        A pointer to the newly interpolated rotation data
            RpAnimSequnce * A pointer to the animation sequence
            RwReal          The time for the new key frame
                                 
 Outputs:   None
 */

static void
HAnimMakeRotKey(RtQuat * pQuat, RpAnimSequence * pAnimSeq, RwReal time)
{
    RpSkinFrame         f1;
    RpSkinFrame         f2;
    RpSkinFrame         fr;
    RwInt32             nInt;
    RwInt32             startKeyFrame;
    RwInt32             endKeyFrame;
    RwReal              rITime = (RwReal) 0.0;

    RWFUNCTION(RWSTRING("HAnimMakeRotKey"));
    RWASSERT(pQuat);
    RWASSERT(pAnimSeq);

    /* Find the keyframes to interpolate between */
    nInt = 0;

    for (nInt = 0; nInt < pAnimSeq->numInterpolators; nInt++)
    {
        rITime += pAnimSeq->interps[nInt].time;
        if (rITime > time)
        {
            /* Found it */
            break;
        }
    }

    if (nInt == pAnimSeq->numInterpolators)
    {
        /* Just need the last key */
        *pQuat = ((RtQuat *) pAnimSeq->keys)[pAnimSeq->numKeys - 1];
        RWRETURNVOID();
    }

    /* nInt is the interpolator we're after - let's use
     * the RpSkinFrame interpolation
     * routine, as we don't care about speed right now */

    startKeyFrame = pAnimSeq->interps[nInt].startKeyFrame;
    f1.q.imag.x = ((RtQuat *) pAnimSeq->keys)[startKeyFrame].imag.x;
    f1.q.imag.y = ((RtQuat *) pAnimSeq->keys)[startKeyFrame].imag.y;
    f1.q.imag.z = ((RtQuat *) pAnimSeq->keys)[startKeyFrame].imag.z;
    f1.q.real = ((RtQuat *) pAnimSeq->keys)[startKeyFrame].real;
    f1.time = rITime - pAnimSeq->interps[nInt].time;

    endKeyFrame = pAnimSeq->interps[nInt].endKeyFrame;
    f2.q.imag.x = ((RtQuat *) pAnimSeq->keys)[endKeyFrame].imag.x;
    f2.q.imag.y = ((RtQuat *) pAnimSeq->keys)[endKeyFrame].imag.y;
    f2.q.imag.z = ((RtQuat *) pAnimSeq->keys)[endKeyFrame].imag.z;
    f2.q.real = ((RtQuat *) pAnimSeq->keys)[endKeyFrame].real;
    f2.time = rITime;

    RpSkinFrameInterpolate(&fr, &f1, &f2, time);

    pQuat->imag.x = fr.q.imag.x;
    pQuat->imag.y = fr.q.imag.y;
    pQuat->imag.z = fr.q.imag.z;
    pQuat->real = fr.q.real;

    RWRETURNVOID();
}

/****************************************************************************
 HAnimCnvExtractSequence
 
 Retrieve an anim sequence for a frame, making sure that it has the same number
 of rotation and translation key frames and that they all match up in time
 
 Inputs :   RwFrame *       A pointer to the frame we're interested in
            void *          A pointer to the skin this animation will play back on
                                 
 Outputs:   None
 */

static RwFrame            *
HAnimCnvExtractSequence(RwFrame * pFrame, void *pData)
{
    RpHAnimHierarchy   *pHierarchy = (RpHAnimHierarchy *) pData;
    RpAnimSequence     *pAnimSeq;
    RpAnimSequence     *pRotateSeq;
    RpAnimSequence     *pTranslateSeq;
    RwInt32             i;

    RWFUNCTION(RWSTRING("HAnimCnvExtractSequence"));
    RWASSERT(pFrame);
    RWASSERT(pData);

    HAnimCnv->pDefaultFrames[HAnimCnv->ExtractSequenceFrameNumber] = pFrame;
    HAnimCnv->pTranslateSequences[HAnimCnv->ExtractSequenceFrameNumber] =
        RpAnimFrameGetTranslationSequence(pFrame,
                                          HAnimCnv->pExtractSequenceName);
    HAnimCnv->pRotateSequences[HAnimCnv->ExtractSequenceFrameNumber] =
        RpAnimFrameGetRotationSequence(pFrame,
                                       HAnimCnv->pExtractSequenceName);

    /* Make sure the two sequences have the same number of keyframes */

    pRotateSeq =
        HAnimCnv->pRotateSequences[HAnimCnv->ExtractSequenceFrameNumber];

    if (!pRotateSeq)
    {
        pTranslateSeq =
            HAnimCnv->
            pTranslateSequences[HAnimCnv->ExtractSequenceFrameNumber];

        if (pTranslateSeq)
        {
            /* No rotations, assume everything to be identity and fill in from the translation keys */
            RtQuat              rotKey = { {0.0f, 0.0f, 0.0f}
            ,
            1.0f
            };

            pAnimSeq =
                RpAnimSequenceCreate(HAnimCnv->pExtractSequenceName,
                                     rpROTATE, pTranslateSeq->numKeys,
                                     pTranslateSeq->numInterpolators);

            for (i = 0; i < pTranslateSeq->numKeys; i++)
            {
                RpAnimSequenceSetRotateKey(pAnimSeq, i, &rotKey);
            }

            for (i = 0; i < pTranslateSeq->numInterpolators; i++)
            {
                RpAnimSequenceSetInterpolator(pAnimSeq, i, i, i + 1,
                                              pTranslateSeq->
                                              interps[i].time);
            }

            HAnimCnv->pRotateSequences[HAnimCnv->
                                     ExtractSequenceFrameNumber] =
                pAnimSeq;
            RpAnimFrameAddSequence(pFrame,
                                   HAnimCnv->pRotateSequences[HAnimCnv->
                                                            ExtractSequenceFrameNumber]);
        }
        else
        {
            /* Haven't got any translations or rotations here - let's just make them up then */
            RtQuat              rotKey = { {0.0f, 0.0f, 0.0f}
            ,
            1.0f
            };

            pAnimSeq =
                RpAnimSequenceCreate(HAnimCnv->pExtractSequenceName,
                                     rpROTATE, 2, 1);
            RpAnimSequenceSetRotateKey(pAnimSeq, 0, &rotKey);
            RpAnimSequenceSetRotateKey(pAnimSeq, 1, &rotKey);
            RpAnimSequenceSetInterpolator(pAnimSeq, 0, 0, 1,
                                          (RwReal) 0.00001);

            HAnimCnv->pRotateSequences[HAnimCnv->
                                     ExtractSequenceFrameNumber] =
                pAnimSeq;
            RpAnimFrameAddSequence(pFrame,
                                   HAnimCnv->pRotateSequences[HAnimCnv->
                                                            ExtractSequenceFrameNumber]);

            pAnimSeq =
                RpAnimSequenceCreate(HAnimCnv->pExtractSequenceName,
                                     rpTRANSLATE, 2, 1);
            RpAnimSequenceSetTranslateKey(pAnimSeq, 0,
                                          &RwFrameGetMatrix
                                          (pFrame)->pos);
            RpAnimSequenceSetTranslateKey(pAnimSeq, 1,
                                          &RwFrameGetMatrix
                                          (pFrame)->pos);
            RpAnimSequenceSetInterpolator(pAnimSeq, 0, 0, 1,
                                          (RwReal) 0.00001);

            HAnimCnv->
                pTranslateSequences[HAnimCnv->
                                    ExtractSequenceFrameNumber] =
                pAnimSeq;
            RpAnimFrameAddSequence(pFrame,
                                   HAnimCnv->pTranslateSequences[HAnimCnv->
                                                               ExtractSequenceFrameNumber]);
        }
    }
    else
    {

        pTranslateSeq =
            HAnimCnv->
            pTranslateSequences[HAnimCnv->ExtractSequenceFrameNumber];

        if (!pTranslateSeq)
        {
            /* No translations, get them from the frame and fill in from the rotation keys */

            pAnimSeq =
                RpAnimSequenceCreate(HAnimCnv->pExtractSequenceName,
                                     rpTRANSLATE,
                                     pRotateSeq->numKeys,
                                     pRotateSeq->numInterpolators);

            for (i = 0; i < pRotateSeq->numKeys; i++)
            {
                RpAnimSequenceSetTranslateKey(pAnimSeq, i,
                                              &RwFrameGetMatrix
                                              (pFrame)->pos);
            }

            for (i = 0; i < pRotateSeq->numInterpolators; i++)
            {
                RpAnimSequenceSetInterpolator(pAnimSeq, i, i, i + 1,
                                              pRotateSeq->
                                              interps[i].time);
            }

            HAnimCnv->
                pTranslateSequences[HAnimCnv->
                                    ExtractSequenceFrameNumber] =
                pAnimSeq;
            RpAnimFrameAddSequence(pFrame,
                                   HAnimCnv->pTranslateSequences[HAnimCnv->
                                                               ExtractSequenceFrameNumber]);
        }
        else
        {
        }
    }

    /* Make sure the two sequences match up in time... */
    {
        RwInt32             currentRotateInterp = 0;
        RwInt32             currentRotateKey = 0;
        RwInt32             currentTranslateInterp = 0;
        RwInt32             currentTranslateKey = 0;
        RtQuat             *pRotateKeys;
        RwInt32             numRotateKeys = 0;
        RwV3d              *pTranslateKeys;
        RwInt32             numTranslateKeys = 0;
        rpAnimInterpolator *pInterpolators;
        RwInt32             numInterpolators = 0;
        RwInt32             numTempKeys;
        RwReal              nextRotation = (RwReal) 0.0;
        RwReal              nextTranslation = (RwReal) 0.0;
        RwReal              lastTime = (RwReal) 0.0;

        /* Allocate some temporary lists of keys - we'll generate new sequences in here */

        if (HAnimCnv->
            pRotateSequences[HAnimCnv->
                             ExtractSequenceFrameNumber]->numKeys >
            HAnimCnv->
            pTranslateSequences
            [HAnimCnv->ExtractSequenceFrameNumber]->numKeys)
        {
            numTempKeys =
                HAnimCnv->
                pRotateSequences
                [HAnimCnv->ExtractSequenceFrameNumber]->numKeys * 2;
        }
        else
        {
            numTempKeys =
                HAnimCnv->pTranslateSequences
                [HAnimCnv->ExtractSequenceFrameNumber]->numKeys * 2;
        }

        pRotateKeys = (RtQuat *) RwMalloc(numTempKeys * sizeof(RtQuat));
        pTranslateKeys =
            (RwV3d *) RwMalloc(numTempKeys * sizeof(RwV3d));
        pInterpolators =
            (rpAnimInterpolator *) RwMalloc(numTempKeys *
                                            sizeof(rpAnimInterpolator));

        /* Copy first key frames */

        pRotateSeq =
            HAnimCnv->pRotateSequences[HAnimCnv->
                                     ExtractSequenceFrameNumber];
        pTranslateSeq =
            HAnimCnv->pTranslateSequences[HAnimCnv->
                                        ExtractSequenceFrameNumber];
        pRotateKeys[numRotateKeys++] =
            ((RtQuat *) (pRotateSeq->keys))[0];
        pTranslateKeys[numTranslateKeys++] =
            ((RwV3d *) (pTranslateSeq->keys))[0];

        /* Get next keys */
        RWASSERT(0 <= currentRotateInterp);
        RWASSERT(currentRotateInterp < pRotateSeq->numInterpolators);
        currentRotateKey =
            pRotateSeq->interps[currentRotateInterp].endKeyFrame;
        RWASSERT(0 <= currentRotateKey);

        RWASSERT(0 <= currentTranslateInterp);
        RWASSERT(currentTranslateInterp <
                 pTranslateSeq->numInterpolators);
        currentTranslateKey =
            pTranslateSeq->interps[currentTranslateInterp].endKeyFrame;
        RWASSERT(0 <= currentTranslateKey);

        /* Get times of next keys */
        RWASSERT(0 <= currentRotateInterp);
        nextRotation += pRotateSeq->interps[currentRotateInterp].time;

        RWASSERT(0 <= currentRotateInterp);
        nextTranslation +=
            pTranslateSeq->interps[currentRotateInterp].time;

        /* While there are interpolators that we haven't looked at... */
        while ((currentRotateInterp <
                HAnimCnv->
                pRotateSequences
                [HAnimCnv->ExtractSequenceFrameNumber]->numInterpolators)
               || (currentTranslateInterp <
                   HAnimCnv->
                   pTranslateSequences
                   [HAnimCnv->ExtractSequenceFrameNumber]->
                   numInterpolators))
        {
            /* See if the next rotation and translation keys match up */
            RWASSERT(0 <= currentRotateInterp);

            if (nextRotation < nextTranslation)
            {
                /* Rotation comes before translation, 
                 * so we'll need another translation key to match it */
                HAnimMakeTransKey(&pTranslateKeys[numTranslateKeys++],
                                 HAnimCnv->pTranslateSequences
                                 [HAnimCnv->ExtractSequenceFrameNumber],
                                 nextRotation);

                RWASSERT(0 <= currentRotateKey);
                pRotateKeys[numRotateKeys++] =
                    ((RtQuat
                      *) (HAnimCnv->pRotateSequences
                          [HAnimCnv->ExtractSequenceFrameNumber]->keys))
                    [currentRotateKey];

                pInterpolators[numInterpolators].startKeyFrame =
                    numTranslateKeys - 2;
                pInterpolators[numInterpolators].endKeyFrame =
                    numTranslateKeys - 1;
                pInterpolators[numInterpolators++].time =
                    nextRotation - lastTime;
                lastTime = nextRotation;

                currentRotateInterp++;

                if (currentRotateInterp <
                    HAnimCnv->
                    pRotateSequences
                    [HAnimCnv->ExtractSequenceFrameNumber]->
                    numInterpolators)
                {

                    RWASSERT(0 <= currentRotateInterp);
                    RWASSERT(currentRotateInterp <
                             HAnimCnv->
                             pRotateSequences
                             [HAnimCnv->ExtractSequenceFrameNumber]->
                             numInterpolators);
                    currentRotateKey =
                        HAnimCnv->pRotateSequences[HAnimCnv->
                                                 ExtractSequenceFrameNumber]->
                        interps[currentRotateInterp].endKeyFrame;

                    RWASSERT(0 <= currentRotateKey);

                    nextRotation +=
                        HAnimCnv->pRotateSequences
                        [HAnimCnv->ExtractSequenceFrameNumber]->interps
                        [currentRotateInterp].time;

                }
                else
                {
                    nextRotation = nextTranslation;
                }

                continue;
            }

            if (nextTranslation < nextRotation)
            {
                /* Rotation comes after translation, 
                 * so we'll need another rotation key to match it */
                HAnimMakeRotKey(&pRotateKeys[numRotateKeys++],
                               HAnimCnv->pRotateSequences
                               [HAnimCnv->ExtractSequenceFrameNumber],
                               nextTranslation);

                RWASSERT(0 <= currentTranslateKey);
                pTranslateKeys[numTranslateKeys++] =
                    ((RwV3d
                      *) (HAnimCnv->pTranslateSequences
                          [HAnimCnv->ExtractSequenceFrameNumber]->keys))
                    [currentTranslateKey];

                pInterpolators[numInterpolators].startKeyFrame =
                    numRotateKeys - 2;
                pInterpolators[numInterpolators].endKeyFrame =
                    numRotateKeys - 1;
                pInterpolators[numInterpolators++].time =
                    nextTranslation - lastTime;
                lastTime = nextTranslation;

                currentTranslateInterp++;

                if (currentTranslateInterp <
                    HAnimCnv->pTranslateSequences
                    [HAnimCnv->
                     ExtractSequenceFrameNumber]->numInterpolators)
                {

                    RWASSERT(0 <= currentTranslateInterp);
                    RWASSERT(currentTranslateInterp <
                             HAnimCnv->
                             pTranslateSequences
                             [HAnimCnv->ExtractSequenceFrameNumber]->
                             numInterpolators);
                    currentTranslateKey =
                        HAnimCnv->pTranslateSequences[HAnimCnv->
                                                    ExtractSequenceFrameNumber]->
                        interps[currentTranslateInterp].endKeyFrame;

                    RWASSERT(0 <= currentTranslateKey);

                    nextTranslation +=
                        HAnimCnv->pTranslateSequences
                        [HAnimCnv->ExtractSequenceFrameNumber]->interps
                        [currentTranslateInterp].time;
                }
                else
                {
                    nextTranslation = nextRotation;
                }

                continue;
            }

            if (nextRotation == nextTranslation)
            {
                /* Rotation and translation at the same time - just copy them */
                RWASSERT(0 <= currentTranslateKey);
                pTranslateKeys[numTranslateKeys++] =
                    ((RwV3d
                      *) (HAnimCnv->pTranslateSequences
                          [HAnimCnv->ExtractSequenceFrameNumber]->keys))
                    [currentTranslateKey];

                RWASSERT(0 <= currentRotateKey);
                pRotateKeys[numRotateKeys++] =
                    ((RtQuat
                      *) (HAnimCnv->pRotateSequences
                          [HAnimCnv->ExtractSequenceFrameNumber]->keys))
                    [currentRotateKey];

                pInterpolators[numInterpolators].startKeyFrame =
                    numTranslateKeys - 2;
                pInterpolators[numInterpolators].endKeyFrame =
                    numTranslateKeys - 1;
                pInterpolators[numInterpolators++].time =
                    nextRotation - lastTime;
                lastTime = nextRotation;

                currentRotateInterp++;

                if (currentRotateInterp <
                    HAnimCnv->pRotateSequences
                    [HAnimCnv->
                     ExtractSequenceFrameNumber]->numInterpolators)
                {

                    RWASSERT(0 <= currentRotateInterp);
                    RWASSERT(currentRotateInterp <
                             HAnimCnv->
                             pRotateSequences
                             [HAnimCnv->ExtractSequenceFrameNumber]->
                             numInterpolators);
                    currentRotateKey =
                        HAnimCnv->pRotateSequences[HAnimCnv->
                                                 ExtractSequenceFrameNumber]->
                        interps[currentRotateInterp].endKeyFrame;

                    RWASSERT(0 <= currentRotateKey);

                    nextRotation +=
                        HAnimCnv->pRotateSequences
                        [HAnimCnv->ExtractSequenceFrameNumber]->interps
                        [currentRotateInterp].time;
                }
                else
                {
                    currentTranslateInterp++;

                    if (currentTranslateInterp <
                        HAnimCnv->pTranslateSequences
                        [HAnimCnv->
                         ExtractSequenceFrameNumber]->numInterpolators)
                    {

                        RWASSERT(0 <= currentTranslateInterp);
                        RWASSERT(currentTranslateInterp <
                                 HAnimCnv->
                                 pTranslateSequences
                                 [HAnimCnv->ExtractSequenceFrameNumber]->
                                 numInterpolators);
                        currentTranslateKey =
                            HAnimCnv->pTranslateSequences[HAnimCnv->
                                                        ExtractSequenceFrameNumber]->
                            interps[currentTranslateInterp].endKeyFrame;

                        RWASSERT(0 <= currentTranslateKey);

                        nextTranslation +=
                            HAnimCnv->pTranslateSequences
                            [HAnimCnv->ExtractSequenceFrameNumber]->
                            interps[currentTranslateInterp].time;

                        nextRotation = nextTranslation;
                        continue;
                    }
                }

                currentTranslateInterp++;

                if (currentTranslateInterp <
                    HAnimCnv->pTranslateSequences
                    [HAnimCnv->
                     ExtractSequenceFrameNumber]->numInterpolators)
                {

                    RWASSERT(0 <= currentTranslateInterp);
                    RWASSERT(currentTranslateInterp <
                             HAnimCnv->
                             pTranslateSequences
                             [HAnimCnv->ExtractSequenceFrameNumber]->
                             numInterpolators);
                    currentTranslateKey =
                        HAnimCnv->pTranslateSequences[HAnimCnv->
                                                    ExtractSequenceFrameNumber]->
                        interps[currentTranslateInterp].endKeyFrame;

                    RWASSERT(0 <= currentTranslateKey);

                    nextTranslation +=
                        HAnimCnv->pTranslateSequences
                        [HAnimCnv->ExtractSequenceFrameNumber]->interps
                        [currentTranslateInterp].time;

                }
                else
                {
                    nextTranslation = nextRotation;
                }
            }

        }

        /* Copy new sequences into place */

        RWASSERT(numTranslateKeys == numRotateKeys);

        pAnimSeq =
            RpAnimSequenceCreate(HAnimCnv->pExtractSequenceName,
                                 rpTRANSLATE, numTranslateKeys,
                                 numInterpolators);

        /* Fill in the new sequence */

        for (i = 0; i < numTranslateKeys; i++)
        {
            RpAnimSequenceSetTranslateKey(pAnimSeq, i,
                                          &pTranslateKeys[i]);
        }

        for (i = 0; i < numInterpolators; i++)
        {
            RpAnimSequenceSetInterpolator(pAnimSeq, i,
                                          pInterpolators
                                          [i].startKeyFrame,
                                          pInterpolators[i].endKeyFrame,
                                          pInterpolators[i].time);
        }

        RpAnimFrameRemoveSequence(pFrame,
                                  HAnimCnv->pTranslateSequences
                                  [HAnimCnv->ExtractSequenceFrameNumber]);
        RpAnimSequenceDestroy(HAnimCnv->pTranslateSequences
                              [HAnimCnv->ExtractSequenceFrameNumber]);
        HAnimCnv->pTranslateSequences[HAnimCnv->
                                    ExtractSequenceFrameNumber] =
            pAnimSeq;
        RpAnimFrameAddSequence(pFrame,
                               HAnimCnv->pTranslateSequences[HAnimCnv->
                                                           ExtractSequenceFrameNumber]);

        pAnimSeq =
            RpAnimSequenceCreate(HAnimCnv->pExtractSequenceName, rpROTATE,
                                 numRotateKeys, numInterpolators);

        /* Fill in the new sequence */

        for (i = 0; i < numRotateKeys; i++)
        {
            RpAnimSequenceSetRotateKey(pAnimSeq, i, &pRotateKeys[i]);
        }

        for (i = 0; i < numInterpolators; i++)
        {
            RpAnimSequenceSetInterpolator(pAnimSeq, i,
                                          pInterpolators
                                          [i].startKeyFrame,
                                          pInterpolators[i].endKeyFrame,
                                          pInterpolators[i].time);
        }

        RpAnimFrameRemoveSequence(pFrame,
                                  HAnimCnv->pRotateSequences
                                  [HAnimCnv->ExtractSequenceFrameNumber]);
        RpAnimSequenceDestroy(HAnimCnv->pRotateSequences
                              [HAnimCnv->ExtractSequenceFrameNumber]);
        HAnimCnv->pRotateSequences[HAnimCnv->ExtractSequenceFrameNumber] =
            pAnimSeq;
        RpAnimFrameAddSequence(pFrame,
                               HAnimCnv->
                               pRotateSequences
                               [HAnimCnv->ExtractSequenceFrameNumber]);

        RwFree(pRotateKeys);
        RwFree(pTranslateKeys);
        RwFree(pInterpolators);

    }

    HAnimCnv->ExtractSequenceFrameNumber++;

    RwFrameForAllChildren(pFrame, HAnimCnvExtractSequence,
                          (void *) pHierarchy);

    RWRETURN(pFrame);
}

/****************************************************************************
 HAnimCnvDestroySequence
 
 Destroy an anim sequence and remove it from its frame.
   
 Inputs :   RwFrame *       A pointer to the frame we're interested in
            void *          A pointer to the skin this animation will play back on
                                 
 Outputs:   None
 */

static RwFrame            *
HAnimCnvDestroySequence(RwFrame * pFrame, void *pData)
{
    RpAnimSequence     *RotSeq;
    RpAnimSequence     *TrnSeq;

    RWFUNCTION(RWSTRING("HAnimCnvDestroySequence"));
    RWASSERT(pFrame);

#if (0)
    RWMESSAGE(("HAnimCnv->ExtractSequenceFrameNumber %d",
               (int) HAnimCnv->ExtractSequenceFrameNumber));
#endif /* (0) */

    RotSeq =
        HAnimCnv->pRotateSequences[HAnimCnv->ExtractSequenceFrameNumber];
    TrnSeq =
        HAnimCnv->pTranslateSequences[HAnimCnv->ExtractSequenceFrameNumber];

    RpAnimFrameRemoveSequence(pFrame, TrnSeq);
    RpAnimSequenceDestroy(TrnSeq);

    RpAnimFrameRemoveSequence(pFrame, RotSeq);
    RpAnimSequenceDestroy(RotSeq);

    HAnimCnv->ExtractSequenceFrameNumber++;

    RwFrameForAllChildren(pFrame, HAnimCnvDestroySequence,
                          (void *) pData);

    RWRETURN(pFrame);

}

/****************************************************************************
 HAnimMakeStdKeyFrame
 
 Build an animation from for the given time and bone
 
 Inputs :   RpSkinFrame *   A pointer to the skin frame we're interested in
            RwInt32         frame index
            RwInt32         key index
            RwReal          time
                                 
 Outputs:   None
 */

static void
HAnimMakeStdKeyFrame(RpHAnimStdKeyFrame * pFrame, RwInt32 frame,
                  RwInt32 key, RwReal time)
{
    RwMatrix           *pMatrix;

    RWFUNCTION(RWSTRING("HAnimMakeStdKeyFrame"));
    RWASSERT(pFrame);

    if (HAnimCnv->pRotateSequences[frame])
    {
        pFrame->q.imag.x =
            (((RpRotateKey
               *) HAnimCnv->pRotateSequences[frame]->keys)[key]).imag.x;
        pFrame->q.imag.y =
            (((RpRotateKey
               *) HAnimCnv->pRotateSequences[frame]->keys)[key]).imag.y;
        pFrame->q.imag.z =
            (((RpRotateKey
               *) HAnimCnv->pRotateSequences[frame]->keys)[key]).imag.z;
        pFrame->q.real =
            -(((RpRotateKey *) HAnimCnv->pRotateSequences[frame]->keys)
              [key]).real;
    }
    else
    {
        pFrame->q.imag.x = (RwReal) 0.0;
        pFrame->q.imag.y = (RwReal) 0.0;
        pFrame->q.imag.z = (RwReal) 0.0;
        pFrame->q.real = (RwReal) 1.0;
        pMatrix = RwFrameGetMatrix(HAnimCnv->pDefaultFrames[frame]);

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

        RtQuatConvertFromMatrix(&pFrame->q, pMatrix);
    }

    if (HAnimCnv->pTranslateSequences[frame])
    {
        pFrame->t.x =
            (((RpTranslateKey
               *) HAnimCnv->pTranslateSequences[frame]->keys)[key]).x;
        pFrame->t.y =
            (((RpTranslateKey
               *) HAnimCnv->pTranslateSequences[frame]->keys)[key]).y;
        pFrame->t.z =
            (((RpTranslateKey
               *) HAnimCnv->pTranslateSequences[frame]->keys)[key]).z;
    }
    else
    {
        pMatrix = RwFrameGetMatrix(HAnimCnv->pDefaultFrames[frame]);
        /* RWASSERT(rwMatrixValidFlags(pMatrix, _EPSILON)); */

        pFrame->t.x = pMatrix->pos.x;
        pFrame->t.y = pMatrix->pos.y;
        pFrame->t.z = pMatrix->pos.z;
    }

    pFrame->time = time;

    RWRETURNVOID();
}

/****************************************************************************
 HAnimCnvLinkPrevFrames
 
 Link animation frames to their the previous frame in an animation for the same bone
 
 Inputs :   RpSkin *        A pointer to the skin the animation is used on
            RpSkinAnim *    A pointer to the anim to process
                                 
 Outputs:   None
 */

static void
HAnimCnvLinkPrevFrames(RpHAnimHierarchy * pHierarchy, RpHAnimAnimation * pAnim)
{

    RwInt32             i, j;
    RpHAnimStdKeyFrame        *pCurrentFrame;
    RpHAnimStdKeyFrame        *pPrevFrame = (RpHAnimStdKeyFrame *)NULL;

    RWFUNCTION(RWSTRING("HAnimCnvLinkPrevFrames"));
    RWASSERT(pAnim);

    if (pHierarchy)
    {
        for (i = 0; i < pHierarchy->numNodes; i++)
        {
            pCurrentFrame = (RpHAnimStdKeyFrame *)pAnim->pFrames;
            pPrevFrame = (RpHAnimStdKeyFrame *)NULL;

            for (j = 0; j < pAnim->numFrames; j++)
            {
                if (pCurrentFrame->prevFrame ==
                    (RpHAnimStdKeyFrame *) (0x30000000 | i))
                {
                    pCurrentFrame->prevFrame = pPrevFrame;
                    pPrevFrame = pCurrentFrame;
                }

                pCurrentFrame++;
            }
        }
    }
    else
    {
        pCurrentFrame = (RpHAnimStdKeyFrame *)pAnim->pFrames;
        pPrevFrame = (RpHAnimStdKeyFrame *)NULL;

        for (j = 0; j < pAnim->numFrames; j++)
        {
            if (pCurrentFrame->prevFrame ==
                (RpHAnimStdKeyFrame *) (0x30000000))
            {
                pCurrentFrame->prevFrame = pPrevFrame;
                pPrevFrame = pCurrentFrame;
            }

            pCurrentFrame++;
        }
    }
    RWRETURNVOID();
}

/**
 * \ingroup rthanimcnv
 * \ref RtHAnimCnvExtractAnimFromClumpSequence 
 * extracts a named animation from an RpAnim clump
 *
 * \param pClump  The clump from which to extract the animation
 * \param pSequenceName  The name of the animation
 *
 * \return pointer to the animation that has been extracted
 *
 */
RpHAnimAnimation         *
RtHAnimCnvExtractAnimFromClumpSequence(RpClump * pClump,
                                       RwChar * pSequenceName)
{
    RwInt32             currentKeys[64];
    RwReal              currentTimes[64];
    RwReal              totalTimes[64];
    RwReal              totalSequenceTime;
    RwReal              minTime;
    RwInt32             totalFrames, framesAdded;
    RpHAnimStdKeyFrame *pRpHAnimFrame;
    RpHAnimStdKeyFrame *pCurrentRpHAnimFrame;
    RwInt32             i, j;
    RpHAnimAnimation       *pRpHAnimAnimation;
    RpHAnimHierarchy   *pHierarchy = (RpHAnimHierarchy *)NULL;
    RwInt32             numNodes;

    RWAPIFUNCTION(RWSTRING("RtHAnimCnvExtractAnimFromClumpSequence"));
    RWASSERT(pClump);
    RWASSERT(pSequenceName);

    /* Initialize the skbone state block */
    HAnimCnvStateConstructor();

    HAnimCnv->pExtractSequenceName = pSequenceName;

    HAnimCnv->ExtractSequenceFrameNumber = 0;

    /* Find the hierarchy */

    pHierarchy = RpHAnimFrameGetHierarchy(RpClumpGetFrame(pClump));

    /* Get the rotation and translation sequences for all frames */

    if (pHierarchy)
    {
        numNodes = pHierarchy->numNodes;
        RwFrameForAllChildren(RpClumpGetFrame(pClump),
                              HAnimCnvExtractSequence, (void *) pHierarchy);
    }
    else
    {
        /* No hierarchy, currently we only support extracting animation if
           you've already converted the hierarchy */
        HAnimCnvStateDestructor();
        RWRETURN((RpHAnimAnimation *)NULL);
    }

    /* Sum the length of the sequence -
     * assuming they're all the same, which they should be */

    totalSequenceTime = 0.0f;

    for (j = 0; j < numNodes; j++)
    {
        totalTimes[j] = 0.0f;

        if (HAnimCnv->pRotateSequences[j])
        {
            RWASSERT(HAnimCnv->pTranslateSequences[j]->numInterpolators ==
                     HAnimCnv->pRotateSequences[j]->numInterpolators);

            for (i = 0;
                 i < HAnimCnv->pRotateSequences[j]->numInterpolators; i++)
            {
                totalTimes[j] +=
                    HAnimCnv->pRotateSequences[j]->interps[i].time;
            }

            if (totalTimes[j] > totalSequenceTime)
            {
                totalSequenceTime = totalTimes[j];
            }
        }
        else
        {
            if (HAnimCnv->pTranslateSequences[j])
            {
                for (i = 0;
                     i <
                     HAnimCnv->pTranslateSequences[j]->numInterpolators;
                     i++)
                {
                    totalTimes[j] +=
                        HAnimCnv->pTranslateSequences[j]->interps[i].time;
                }

                if (totalTimes[j] > totalSequenceTime)
                {
                    totalSequenceTime = totalTimes[j];
                }
            }
        }
    }

    /* Find total number of frames */

    totalFrames = 0;

    for (i = 0; i < numNodes; i++)
    {
        if (HAnimCnv->pRotateSequences[i])
        {
            totalFrames += HAnimCnv->pRotateSequences[i]->numKeys;
        }
        else
        {
            if (HAnimCnv->pTranslateSequences[i])
            {
                totalFrames += HAnimCnv->pTranslateSequences[i]->numKeys;
            }
            else
            {
                /* If there's no rot/trans sequences, we'll have frames at the start and end of the sequence */
                totalFrames += 2;
                totalTimes[i] = totalSequenceTime;
            }
        }

        if (totalTimes[i] < totalSequenceTime)
        {
            /* If the sequence for this bone does not last the whole sequence time, add a further terminating frame */
            totalFrames++;
        }
    }

    /* Allocate memory for RpSkinFrame sequence */

    pRpHAnimFrame =
        (RpHAnimStdKeyFrame *) RwMalloc(totalFrames * sizeof(RpHAnimStdKeyFrame));

    /* Add starting frames */

    pCurrentRpHAnimFrame = pRpHAnimFrame;

    for (i = 0; i < numNodes; i++)
    {
        HAnimMakeStdKeyFrame(pCurrentRpHAnimFrame, i, 0, 0.0f);
        /* Temporarily store the bone number so we can link up the previous frame lists */
        pCurrentRpHAnimFrame->prevFrame =
            (RpHAnimStdKeyFrame *) (0x30000000 | i);
        pCurrentRpHAnimFrame++;
    }

    for (i = 0; i < numNodes; i++)
    {

        if (HAnimCnv->pRotateSequences[i])
        {
            currentTimes[i] =
                HAnimCnv->pRotateSequences[i]->interps[0].time;
        }
        else
        {
            if (HAnimCnv->pTranslateSequences[i])
            {
                currentTimes[i] =
                    HAnimCnv->pTranslateSequences[i]->interps[0].time;
            }
            else
            {
                currentTimes[i] = totalSequenceTime;
            }
        }

        HAnimMakeStdKeyFrame(pCurrentRpHAnimFrame, i, 1, currentTimes[i]);
        /* Temporarily store the bone number so we can link up the previous frame lists */
        pCurrentRpHAnimFrame->prevFrame =
            (RpHAnimStdKeyFrame *) (0x30000000 | i);
        pCurrentRpHAnimFrame++;

        currentKeys[i] = 1;
    }

    /* Sort the frames into "JIT" order... */

    framesAdded = numNodes * 2;

    while (framesAdded < totalFrames)
    {
        /* Find the smallest time left for any keyframe */

        minTime = 99999.9f;

        for (i = 0; i < numNodes; i++)
        {
            if (currentTimes[i] < minTime)
            {
                minTime = currentTimes[i];
            }
        }

        /* Found the smallest time - add new frames for all sequences with this time */

        for (i = 0; i < numNodes; i++)
        {
            if (currentTimes[i] == minTime)
            {

                if (currentTimes[i] == totalTimes[i])
                {
                    /* Add last key again */
                    currentTimes[i] = totalSequenceTime;
                    HAnimMakeStdKeyFrame(pCurrentRpHAnimFrame, i,
                                      currentKeys[i], currentTimes[i]);
                    currentKeys[i]++;
                }
                else
                {
                    currentKeys[i]++;
                    if (HAnimCnv->pRotateSequences[i])
                    {
                        currentTimes[i] +=
                            HAnimCnv->
                            pRotateSequences[i]->interps[currentKeys[i]
                                                         - 1].time;
                    }
                    else
                    {
                        currentTimes[i] +=
                            HAnimCnv->pTranslateSequences[i]->interps
                            [currentKeys[i] - 1].time;
                    }
                    HAnimMakeStdKeyFrame(pCurrentRpHAnimFrame, i,
                                      currentKeys[i], currentTimes[i]);
                }

                /* Temporarily store the bone number so we can link up the previous frame lists */
                pCurrentRpHAnimFrame->prevFrame =
                    (RpHAnimStdKeyFrame *) (0x30000000 | i);

                pCurrentRpHAnimFrame++;
                framesAdded++;
            }
        }
    }

    pRpHAnimAnimation = RpHAnimAnimationCreate(0x1, totalFrames, 0, totalSequenceTime);
    
    pRpHAnimAnimation->pFrames = pRpHAnimFrame;

    HAnimCnvLinkPrevFrames(RpHAnimFrameGetHierarchy(RpClumpGetFrame(pClump)), pRpHAnimAnimation);

    /* Destroy the temporary anim sequences */

    HAnimCnv->ExtractSequenceFrameNumber = 0;

    RwFrameForAllChildren(RpClumpGetFrame(pClump),
                          HAnimCnvDestroySequence, (void *) pHierarchy);

    /* destroy the skbone state block */
    HAnimCnvStateDestructor();

    RWRETURN(pRpHAnimAnimation);
}
