/*
 * Data structures for skin plugin
 */

 /**
 * \ingroup rpskinp2
 * \page rpskinp2overview RpSkin Plugin Overview
 *
 * The RpSkin plugin performs skinned animation (PS2 specific)
 *
 * 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
 *
 * Copyright (c) Criterion Software Limited
 *
 */

/*
 * 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.
 *
 ************************************************************************/

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

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

#include <rpcriter.h>
#include "rpplugin.h"
#include <rpdbgerr.h>

#include <rwcore.h>
#include <rpworld.h>
#include <rpskbone.h>
#include <rpskin.h>

#include "skinpriv.h"
#include "skinlink.h"
#include "genmatbl.h"

#if (!defined(DOXYGEN_SHOULD_SKIP_THIS))
static const char   rcsid[] __RWUNUSED__ =
    "@@(#)$Id: rpskin.c,v 1.13 2001/08/17 14:58:40 johns Exp $";
#endif /* (!defined(DOXYGEN_SHOULD_SKIP_THIS)) */

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

/* From driver/sky/skywrins.c */
#define SKYPOLYHEADER(repentry)                                   \
    ((RwInstDataPolyHeader *)((RwInt8*)((repentry)+1)+            \
                              sizeof(SkyInstDataExt)))

#define RPSKINENGINEGETDATA(engineInstance)                              \
     ((_rpSkinLinkCallBack *)(((RwUInt8 *)engineInstance) +              \
                  RpSkinAtomicGlobals.engineOffset))

#define RPSKINENGINEGETCONSTDATA(engineInstance)                         \
     ((const _rpSkinLinkCallBack *)(((const RwUInt8 *)engineInstance) +  \
                  RpSkinAtomicGlobals.engineOffset))

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

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

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

#define RPSKINMATRIXSTACKDEPTH 32
typedef RwMatrix RpSkinMatrixStack[RPSKINMATRIXSTACKDEPTH];

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

#define _EPSILON          ((RwReal)1) / ((RwReal)(1<<6))
#define _TOL_COS_ZERO     (((RwReal)1) - _EPSILON)

#define RwCosecMinusPiToPiMacro(result, x)      \
MACRO_START                                     \
{                                               \
    RwSinMinusPiToPiMacro(result, x);           \
    result = ((RwReal)1) / (result);            \
}                                               \
MACRO_STOP

#define ROUNDUP16(x)      (((RwUInt32)(x) + 16 - 1) & ~(16 - 1))

#define SkinFrameAddTogether(_pOut, _pIn1, _pIn2)               \
MACRO_START                                                     \
{                                                               \
    RtQuatMultiply(&(_pOut)->q, &(_pIn1)->q, &(_pIn2)->q);      \
    RwV3dAdd(&(_pOut)->t, &(_pIn1)->t, &(_pIn2)->t);            \
}                                                               \
MACRO_STOP

#define rtQuatIsUnitary(q)                                      \
  ( ((((RwReal)1)-_EPSILON) <= RtQuatModulusSquaredMacro(q)) && \
    (RtQuatModulusSquaredMacro(q) <= (((RwReal)1)+_EPSILON)) )

#define SkinQuatNormlize(dest, source)                          \
MACRO_START                                                     \
{                                                               \
                                                                \
    RwReal             scale = RtQuatModulusSquared(source);    \
    if (scale >0)                                               \
    {                                                           \
        scale = (RwReal)sqrt(((RwReal)1)/scale);                \
    }                                                           \
    RtQuatScale(dest, source, scale);                           \
}                                                               \
MACRO_STOP

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

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

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

RpSkinAtomicGlobalVars RpSkinAtomicGlobals;

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

extern RwBool       _rpSkinMBInitAtomic(RpAtomic * atomic);

extern RwBool       _rpSkinMBPluginAttach(void);

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


/**
 * \ingroup rpskinp2
 * \ref RpSkinCreate creates a skin for an atomic, and attaches
 * that skin data to the atomic.  A skin maps an atomic's vertices to a set
 * of bones that can be animated.  This mapping can assign up to four
 * "weights" per vertex to allow subtle blending between bones.
 *
 * Skins use RwMatrix structures to define the initial bone
 * transformations.  The hierarchy of the bones is defined by a series of
 * flags that are read as the bones are processed.
 *
 *
 * \param pAtomic  Pointer to an atomic to which the skin is to be attached
 * \param numBones  The number of bones in the skin (maximum is 64)
 * \param pVertexWeights  An array of weights, one per vertex.
 * The weights for each vertex should sum to 1.0
 * \param pVertexIndices  A pointer to an array of bone indices,
 * one per vertex.  4 8-bit indices are packed into each RwUInt32 value,
 * with the least significant mapping to w0 in the vertex weight array.
 * \param pInverseMatrices  A pointer to an array of 4x4 matrices,
 * one per bone, that conatin the transformation from bone-space to
 * object-space for that bone.
 * \param pFlagArray  A pointer to an array of flags, one per bone.
 * rpSKINPUSHPARENTMATRIX - before processing this bone, it's parent matrix
 * should be pushed onto the matrix stack.
 * rpSKINPOPPARENTMATRIX - after processing this bone, the parent matrix
 * should be popped from the matrix stack.
 *
 * \return Returns a pointer to the skin that is generated for the atomic if
 * successful, or NULL if there is an error.
 *
 */

RpSkin             *
RpSkinCreate(RpAtomic * pAtomic, RwInt32 numBones,
             RwMatrixWeights * pVertexWeights,
             RwUInt32 * pVertexIndices, RwMatrix * pInverseMatrices,
             RwUInt32 * pFlagArray)
{
    RpSkin             *pSkin;
    RwInt32             i;
    RwUInt32            bytes;

    RWAPIFUNCTION(RWSTRING("RpSkinCreate"));
    RWASSERT(numBones > 0);

    pSkin = (RpSkin *) RwFreeListAlloc(RpSkinAtomicGlobals.SkinFreeList);
    memset(pSkin, 0, sizeof(RpSkin));

    pSkin->numBones = numBones;

    bytes = sizeof(RpSkinBoneInfo) * numBones + 15;
    pSkin->pBoneInfoUnaligned = RwMalloc(bytes);
    memset(pSkin->pBoneInfoUnaligned, 0, bytes);

    pSkin->pBoneInfo = (RpSkinBoneInfo *)
        ROUNDUP16(pSkin->pBoneInfoUnaligned);

    if (pAtomic)
    {
        pSkin->pGeometry = RpAtomicGetGeometry(pAtomic);
        pSkin->totalVertices =
            RpGeometryGetNumVertices(pSkin->pGeometry);
    }
    else
    {
        pSkin->pGeometry = (RpGeometry *) NULL;
        pSkin->totalVertices = 0;
    }

    /* No skeleton initially */
    pSkin->pCurrentSkeleton = (RpSkinSkeleton *) NULL;
    /* And no hierarchy */
    pSkin->pCurrentHierarchy = (RpHAnimHierarchy *) NULL;

    /* Set up initial boneToSkin matrices */

    for (i = 0; i < numBones; i++)
    {
        if (pInverseMatrices)
        {
            /* RWASSERT(rwMatrixValidFlags(&pInverseMatrices[i], _EPSILON)); */

            pSkin->pBoneInfo[i].boneToSkinMat = pInverseMatrices[i];
        }

        if (pFlagArray)
        {
            pSkin->pBoneInfo[i].flags = pFlagArray[i];
        }

        pSkin->pBoneInfo[i].boneIndex = i;
        pSkin->pBoneInfo[i].boneTag = i;
        pSkin->pBoneInfo[i].pFrame = (RwFrame *) NULL;
    }

    /* Set up vertex weight and index maps */
    if (pSkin->totalVertices > 0)

    {
        bytes = sizeof(RwUInt32) * pSkin->totalVertices;
        pSkin->pMatrixIndexMap = (RwUInt32 *) RwMalloc(bytes);
        memcpy(pSkin->pMatrixIndexMap, pVertexIndices, bytes);

        bytes = sizeof(RwMatrixWeights) * pSkin->totalVertices;
        pSkin->pMatrixWeightsMap = (RwMatrixWeights *) RwMalloc(bytes);
        memcpy(pSkin->pMatrixWeightsMap, pVertexWeights, bytes);
    }

    /* Set skin data */
    if (pAtomic)
    {
        *RPSKINATOMICGETDATA(pAtomic) = pSkin;

        _rpSkinMBInitAtomic(pAtomic);
    }

    RWRETURN(pSkin);
}

/**
 * \ingroup rpskinp2
 * \ref RpSkinDestroy destroys a skin.
 *
 * \param pSkin  The skin.
 *
 * \return NULL
 *
 */

RpSkin             *
RpSkinDestroy(RpSkin * pSkin)
{
    RWAPIFUNCTION(RWSTRING("RpSkinDestroy"));

    if (pSkin)
    {
        if (pSkin->pGeometry && (pSkin->totalVertices > 0))
        {
            MEMORYMESSAGE(("%d == pSkin[%p]->pGeometry[%p]->refCount\n",
                           pSkin->pGeometry->refCount, pSkin,
                           pSkin->pGeometry));

            RWASSERT(pSkin->pGeometry->refCount > 0);

            if (pSkin->pGeometry->refCount <= 1)
            {
                /* This was the last one - destroy the geometry */
                if (pSkin->pMatrixIndexMap)
                {
                    RwFree(pSkin->pMatrixIndexMap);
                    pSkin->pMatrixIndexMap = (RwUInt32 *) NULL;
                }

                if (pSkin->pMatrixWeightsMap)
                {
                    RwFree(pSkin->pMatrixWeightsMap);
                    pSkin->pMatrixWeightsMap = (RwMatrixWeights *) NULL;
                }

                _rpSkinMBDestroyAtomic(pSkin);
            }
        }

        /* Bone Info must be unique per instance because of pFrame pointers */
        if (pSkin->pBoneInfo)
        {
            RwFree(pSkin->pBoneInfoUnaligned);
            pSkin->pBoneInfoUnaligned = NULL;
            pSkin->pBoneInfo = (RpSkinBoneInfo *) NULL;
        }

        RwFreeListFree(RpSkinAtomicGlobals.SkinFreeList, pSkin);
        pSkin = (RpSkin *) NULL;
    }

    RWRETURN(pSkin);
}

/**
 * \ingroup rpskinp2
 * \ref RpSkeletonCreate creates a skeleton, which is used to play
 * back an instance of an animation.
 *
 * \param numBones  The number of bones required in the skeleton.
 *
 * \return A pointer to the new skeleton on success, NULL if there is an error.
 *
 */

RpSkinSkeleton     *
RpSkeletonCreate(RwInt32 numBones)
{
    void               *ptr;
    RpSkinSkeleton     *pSkeleton;
    RwUInt32            bytes;

    RWAPIFUNCTION(RWSTRING("RpSkeletonCreate"));
    RWASSERT(numBones >= 1);

    if (!(numBones >= 1))
    {
        RWRETURN((RpSkinSkeleton *) NULL);
    }

    bytes =
        sizeof(RpSkinSkeleton) + (numBones * sizeof(RpSkinFrame) * 3);
    ptr = RwMalloc(bytes);
    memset(ptr, 0, bytes);

    pSkeleton = (RpSkinSkeleton *) ptr;
    pSkeleton->numBones = numBones;
    pSkeleton->pCurrentAnim = (RpSkinAnim *) NULL;
    pSkeleton->pNextFrame = (RpSkinFrame *) NULL;
    pSkeleton->currentTime = 0.0f;

    pSkeleton->pAnimCallBack = (RpSkinSkeletonCallBack) NULL;
    pSkeleton->animCallBackTime = (RwReal) - 1.0;
    pSkeleton->pAnimCallBackData = NULL;
    pSkeleton->pAnimLoopCallBack = (RpSkinSkeletonCallBack) NULL;
    pSkeleton->pAnimLoopCallBackData = NULL;

    bytes = sizeof(RwMatrix) * numBones + 15;
    ptr = RwMalloc(bytes);
    memset(ptr, 0, bytes);

    pSkeleton->pMatrixArray = (RwMatrix *) ROUNDUP16(ptr);
    pSkeleton->pMatrixArrayUnaligned = ptr;

    RWRETURN(pSkeleton);
}

/**
 * \ingroup rpskinp2
 * \ref RpSkeletonDestroy destroys a skeleton
 *
 * \param pSkeleton  A pointer to the skeleton to be destroyed.
 *
 * \return NULL
 *
 */

RpSkinSkeleton     *
RpSkeletonDestroy(RpSkinSkeleton * pSkeleton)
{
    RWAPIFUNCTION(RWSTRING("RpSkeletonDestroy"));

    RWASSERT(NULL != pSkeleton);

    if (pSkeleton)
    {
        RwMatrix           *pMatrixArrayUnaligned;

        pMatrixArrayUnaligned = (RwMatrix *)
            pSkeleton->pMatrixArrayUnaligned;

        MEMORYMESSAGE(("RwFree(%p) / * == %p->MatrixArrayUnaligned */",
                       pMatrixArrayUnaligned, pSkeleton));

        RwFree(pMatrixArrayUnaligned);
        pSkeleton->pMatrixArrayUnaligned = NULL;

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

        RwFree(pSkeleton);
        pSkeleton = (RpSkinSkeleton *) NULL;
    }

    RWRETURN(pSkeleton);
}

/**
 * \ingroup rpskinp2
 * \ref RpSkinSkeletonCreate creates a skeleton, which is used to play
 * back an instance of an animation on a skin.
 *
 * \param pSkin  A pointer to the skin to which the skeleton will be attached.
 *
 * \return A pointer to the new skeleton on success, NULL if there is an error.
 *
 */

RpSkinSkeleton     *
RpSkinSkeletonCreate(RpSkin * pSkin)
{
    RpSkinSkeleton     *pSkeleton;

    RWAPIFUNCTION(RWSTRING("RpSkinSkeletonCreate"));
    RWASSERT(pSkin);

    if (!pSkin)
    {
        RWRETURN((RpSkinSkeleton *) NULL);
    }

    /* Create skeleton with correct number of bones for this skin */
    pSkeleton = RpSkeletonCreate(pSkin->numBones);

    RWRETURN(pSkeleton);

}

/**
 * \ingroup rpskinp2
 * \ref RpSkinSkeletonDestroy destroys a skeleton
 *
 * \param pSkin  A pointer to the skin for which
 *               the skeleton will be destroyed.
 *
 * \return NULL
 *
 */

RpSkinSkeleton     *
RpSkinSkeletonDestroy(RpSkin * pSkin)
{
    RpSkinSkeleton     *pSkeleton;

    RWAPIFUNCTION(RWSTRING("RpSkinSkeletonDestroy"));
    RWASSERT(NULL != pSkin);

    if (!pSkin)
    {
        RWRETURN((RpSkinSkeleton *) NULL);
    }

    /* Destroy skin's skeleton */
    pSkeleton = RpSkeletonDestroy(RpSkinGetSkeleton(pSkin));

    RWRETURN(pSkeleton);
}

/**
 * \ingroup rpskinp2
 * \ref RpSkinSetSkeleton sets the skeleton used to play back an
 * instance of an animation on a skin.
 *
 * \param pSkin  A pointer to the skin for which the skeleton is to be used.
 * \param pSkeleton  A pointer to the skeleton to use for animating the skin
 *
 * \return TRUE on success, FALSE if an error occurs.
 *
 * \see RpSkinGetSkeleton
 */

RwBool
RpSkinSetSkeleton(RpSkin * pSkin, RpSkinSkeleton * pSkeleton)
{
    RWAPIFUNCTION(RWSTRING("RpSkinSetSkeleton"));
    RWASSERT(pSkin);
    RWASSERT(pSkeleton);
    RWASSERT(pSkin->pCurrentHierarchy == NULL);

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

    pSkin->pCurrentSkeleton = pSkeleton;

    RpSkinUpdateMatrices(pSkin);

    RWRETURN(TRUE);
}

/**
 * \ingroup rpskinp2
 * \ref RpSkinGetSkeleton gets the skeleton used to play back an
 * instance of an animation on a skin.
 *
 * \param pSkin  A pointer to the skin for which the skeleton is to be got.
 *
 * \return A pointer to the skeleton for animating the skin
 *
 * \see RpSkinSetSkeleton
 */

RpSkinSkeleton     *
RpSkinGetSkeleton(RpSkin * pSkin)
{
    RpSkinSkeleton     *result = (RpSkinSkeleton *) NULL;

    RWAPIFUNCTION(RWSTRING("RpSkinGetSkeleton"));
    RWASSERT(pSkin);

    if (pSkin)
    {
        result = pSkin->pCurrentSkeleton;
    }

    RWRETURN(result);
}

/**
 * \ingroup rpskinp2
 * \ref RpSkinSetHAnimHierarchy sets the HAnimHierarchy used to play back an
 * instance of an animation on a skin. The HAnimHierarchy will overide
 * a skeleton if both are attached.
 *
 * \param pSkin  A pointer to the skin for which the skeleton is to be used.
 * \param pHierarchy A pointer to the HAnimHierarchy use for animating
 *                   the skin
 *
 * \return TRUE on success, FALSE if an error occurs.
 *
 * \see RpSkinGetHAnimHierarchy
 */
RwBool
RpSkinSetHAnimHierarchy(RpSkin * pSkin, RpHAnimHierarchy * pHierarchy)
{
    RWAPIFUNCTION(RWSTRING("RpSkinSetHAnimHierarchy"));
    RWASSERT(pSkin);
    RWASSERT(pHierarchy);
    RWASSERT(pSkin->pCurrentSkeleton == NULL);

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

    pSkin->pCurrentHierarchy = pHierarchy;

    RWRETURN(TRUE);
}

/**
 * \ingroup rpskinp2
 * \ref RpSkinGetHAnimHierarchy gets the HAnimHierarchy used to play back an
 * instance of an animation on a skin. The HAnimHierarchy will overide
 *  a skeleton if both are attached.
 *
 * \param pSkin  A pointer to the skin for which the skeleton is to be used.
 *
 * \return A pointer to the HAnimHierarchy for animating the skin
 *
 * \see RpSkinSetHAnimHierarchy
 */
RpHAnimHierarchy   *
RpSkinGetHAnimHierarchy(RpSkin * pSkin)
{
    RWAPIFUNCTION(RWSTRING("RpSkinGetHAnimHierarchy"));
    RWASSERT(pSkin);

    if (!pSkin)
    {
        RWRETURN((RpHAnimHierarchy *) NULL);
    }

    RWRETURN(pSkin->pCurrentHierarchy);
}

/**
 * \ingroup rpskinp2
 * \ref RpSkinAtomicGetSkin returns the skin attached to an atomic.
 *
 *  \param pAtomic  A pointer to the atomic from which to get the skin.
 *
 *  \return A pointer to the attached skin.
 */

RpSkin             *
RpSkinAtomicGetSkin(RpAtomic * pAtomic)
{
    RpSkin             *pSkin;

    RWAPIFUNCTION(RWSTRING("RpSkinAtomicGetSkin"));
    RWASSERT(pAtomic);

    /* Find the skin */

    pSkin = *RPSKINATOMICGETDATA(pAtomic);

    RWRETURN(pSkin);
}

/**
 * \ingroup rpskinp2
 * \ref RpSkinSetCurrentAnim sets the animation that will play back on
 * a skin.  The skin must have a skeleton attached to it with RpSkinSetSkeleton
 * before an animation can be set.
 *
 * \param pSkin  A pointer to the skin.
 * \param pAnim  A pointer to the animation.
 *
 * \return TRUE on success, FALSE if an error occurs.
 *
 * \see RpSkinSetSkeleton
 */

RwBool
RpSkinSetCurrentAnim(RpSkin * pSkin, RpSkinAnim * pAnim)
{
    RpSkinFrame        *pRpSkinFrame;
    RwInt32             i;

    RWAPIFUNCTION(RWSTRING("RpSkinSetCurrentAnim"));
    RWASSERT(pSkin);
    RWASSERT(pAnim);

    if (!pSkin || !pSkin->pCurrentSkeleton)
    {
        RWRETURN(FALSE);
    }

    pSkin->pCurrentSkeleton->pCurrentAnim = pAnim;
    pSkin->pCurrentSkeleton->currentTime = 0.0f;

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

    pRpSkinFrame = pAnim->pFrames;

    for (i = 0; i < pSkin->numBones; i++)
    {
        RpSkinFrame        *dest;

        dest = &rpSKINSKELETONGETINTERPFRAME(pSkin->pCurrentSkeleton)[i];
        *dest = pRpSkinFrame[i];
        SkinQuatNormlize(&dest->q, &dest->q);

        dest = &rpSKINSKELETONGETINTERPFRAME1(pSkin->pCurrentSkeleton)[i];
        *dest =  pRpSkinFrame[i];
        SkinQuatNormlize(&dest->q, &dest->q);

        dest = &rpSKINSKELETONGETINTERPFRAME2(pSkin->pCurrentSkeleton)[i];
        *dest = pRpSkinFrame[i + pSkin->numBones];
        SkinQuatNormlize(&dest->q, &dest->q);
    }

    pSkin->pCurrentSkeleton->pNextFrame =
        &pRpSkinFrame[pSkin->numBones * 2];

    RpSkinUpdateMatrices(pSkin);

    RWRETURN(TRUE);
}

#if (!defined(RpSkinFrameInterpolateMacro))

#define RpSkinFrameInterpolateMacro(_pOut, _pIn1, _pIn2, _time)          \
MACRO_START                                                              \
{                                                                        \
    /* Compute dot product                                               \
     * (equal to cosine of the angle between quaternions)                \
     */                                                                  \
    RwReal              fCosTheta = (RwV3dDotProduct(&(_pIn1)->q.imag,   \
                                                     &(_pIn2)->q.imag) + \
                                     (_pIn1)->q.real * (_pIn2)->q.real); \
    RwReal              fAlpha = (((_time) - (_pIn1)->time) /            \
                                  ((_pIn2)->time - (_pIn1)->time));      \
    RwReal              fBeta;                                           \
    RwBool              bObtuseTheta;                                    \
    RwBool              bNearlyZeroTheta;                                \
                                                                         \
    RWASSERT((_pOut));                                                   \
    RWASSERT((_pIn1));                                                   \
    RWASSERT((_pIn2));                                                   \
    RWASSERT((_pIn1)->time <= (_time));                                  \
    RWASSERT((_pIn2)->time >= (_time));                                  \
                                                                         \
    /* Linearly interpolate positions */                                 \
    RpV3dInterpolate(&(_pOut)->t, &(_pIn1)->t, fAlpha, &(_pIn2)->t);     \
    (_pOut)->time = (_time);                                             \
                                                                         \
    /* Check angle to see if quaternions are in opposite hemispheres */  \
    bObtuseTheta = (fCosTheta < ((RwReal) 0));                           \
                                                                         \
    if (bObtuseTheta)                                                    \
    {                                                                    \
        /* If so, flip one of the quaterions */                          \
        fCosTheta = -fCosTheta;                                          \
        RwV3dNegate(&(_pIn2)->q.imag, &(_pIn2)->q.imag);                 \
        (_pIn2)->q.real = -(_pIn2)->q.real;                              \
    }                                                                    \
                                                                         \
    /* Set factors to do linear interpolation,                           \
     * as a special case where the quaternions are close together. */    \
    fBeta = ((RwReal) 1) - fAlpha;                                       \
                                                                         \
    /* If the quaternions aren't close,                                  \
     * proceed with spherical interpolation */                           \
    bNearlyZeroTheta = (fCosTheta >= _TOL_COS_ZERO);                     \
                                                                         \
    if (!bNearlyZeroTheta)                                               \
    {                                                                    \
        RwReal        fTheta;                                            \
        RwReal        fCosecTheta;                                       \
                                                                         \
        RwIEEEACosfMacro(fTheta, fCosTheta);                             \
        RwCosecMinusPiToPiMacro(fCosecTheta, fTheta);                     \
                                                                         \
        fBeta *=  fTheta;                                                \
        RwSinMinusPiToPiMacro(fBeta, fBeta);                              \
        fBeta *=  fCosecTheta;                                           \
                                                                         \
        fAlpha *=  fTheta;                                               \
        RwSinMinusPiToPiMacro(fAlpha, fAlpha);                            \
        fAlpha *=  fCosecTheta;                                          \
    }                                                                    \
                                                                         \
    /* Do the interpolation */                                           \
    (_pOut)->q.imag.x =                                                  \
        fBeta * (_pIn1)->q.imag.x + fAlpha * (_pIn2)->q.imag.x;          \
    (_pOut)->q.imag.y =                                                  \
        fBeta * (_pIn1)->q.imag.y + fAlpha * (_pIn2)->q.imag.y;          \
    (_pOut)->q.imag.z =                                                  \
        fBeta * (_pIn1)->q.imag.z + fAlpha * (_pIn2)->q.imag.z;          \
    (_pOut)->q.real =                                                    \
        fBeta * (_pIn1)->q.real + fAlpha * (_pIn2)->q.real;              \
                                                                         \
                                                                         \
}                                                                        \
MACRO_STOP

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

/**
 * \ingroup rpskinp2
 * \ref RpSkinFrameInterpolate interpolates between two skin frames
 * and returns the result.
 *
 * \param pOut  A pointer to the output frame
 * \param pIn1  A pointer to the first input frame
 * \param pIn2  A pointer to the second input frame
 * \param time  The time to which to interpolate
 *
 * \return None
 */

void
RpSkinFrameInterpolate(RpSkinFrame * pOut, RpSkinFrame * pIn1,
                       RpSkinFrame * pIn2, RwReal time)
{
    RWAPIFUNCTION(RWSTRING("RpSkinFrameInterpolate"));

    RpSkinFrameInterpolateMacro(pOut, pIn1, pIn2, time);

    RWRETURNVOID();
}

/**
 * \ingroup rpskinp2
 * \ref RpSkinSkeletonSetCurrentAnim sets the animation that is
 * assigned to a skeleton.
 *
 * If the skeleton is attached to a skin, the skin will not update until
 * \ref RpSkinUpdateMatrices is called.
 *
 * \param pSkeleton  A pointer to the skeleton
 * \param pAnim  A pointer to the animation
 *
 * \return TRUE on success, FALSE if an error occurs.
 *
 */

RwBool
RpSkinSkeletonSetCurrentAnim(RpSkinSkeleton * pSkeleton,
                             RpSkinAnim * pAnim)
{
    RpSkinFrame        *pSkinFrame;
    RwInt32             i;

    RWAPIFUNCTION(RWSTRING("RpSkinSkeletonSetCurrentAnim"));
    RWASSERT(pSkeleton);

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

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

    pSkinFrame = pAnim->pFrames;

    for (i = 0; i < pSkeleton->numBones; i++)
    {
        RpSkinFrame        *dest;

        dest = &rpSKINSKELETONGETINTERPFRAME(pSkeleton)[i] ;
        *dest = pSkinFrame[i];
        SkinQuatNormlize(&dest->q, &dest->q);

        dest = &rpSKINSKELETONGETINTERPFRAME1(pSkeleton)[i] ;
        *dest = pSkinFrame[i];
        SkinQuatNormlize(&dest->q, &dest->q);

        dest = &rpSKINSKELETONGETINTERPFRAME2(pSkeleton)[i] ;
        *dest = pSkinFrame[i + pSkeleton->numBones];
        SkinQuatNormlize(&dest->q, &dest->q);
    }

    pSkeleton->pNextFrame = &pSkinFrame[pSkeleton->numBones * 2];

    RWRETURN(TRUE);
}

/**
 * \ingroup rpskinp2
 * \ref RpSkinSkeletonAddAnimTime adds the specified amount of time to
 * the current animation on a skeleton.
 *
 * If the skeleton is attached to a skin, the skin will not update until
 * \ref RpSkinUpdateMatrices is called.
 *
 * \param pSkeleton  A pointer to a skeleton.
 * \param time  The amount of time to add to the animation.
 *
 * \return TRUE on success
 *
 * \see RpSkinUpdateMatrices
 * \see RpSkinSkeletonSubAnimTime
 * \see RpSkinSkeletonSetCurrentAnimTime
 * \see RpSkinAddAnimTime
 * \see RpSkinSetCurrentAnimTime
 *
 */

RwBool
RpSkinSkeletonAddAnimTime(RpSkinSkeleton * pSkeleton, RwReal time)
{
    RwBool              bCallBack = FALSE;
    RwBool              bLoopCallBack = FALSE;
    RwBool              bRepeat;
    RwInt32             i;
    RwInt32             minBone;
    RwInt32             numBones;
    RwReal              duration;
    RwReal              minTime;
    RpSkinAnim         *pCurrentAnim;
    RpSkinFrame        *pCurrentBoneFrame1;
    RpSkinFrame        *pCurrentBoneFrame2;
    RpSkinFrame        *pCurrentBoneFrame;

    RWAPIFUNCTION(RWSTRING("RpSkinSkeletonAddAnimTime"));
    RWASSERT(pSkeleton);

    if (time < 0.0f)
    {
        RWRETURN(RpSkinSkeletonSubAnimTime(pSkeleton, -time));
    }

    if (time == 0.0f)
    {
        /* Do nothing */
        RWRETURN(TRUE);
    }

    pCurrentAnim = pSkeleton->pCurrentAnim;

    if (pSkeleton->pNextFrame == NULL)
    {
        /* Invalid next frame
         * - we've probably been playing backwards for a while */

        time = pSkeleton->currentTime + time;

        /* Reset anim */
        RpSkinSkeletonSetCurrentAnim(pSkeleton, pCurrentAnim);
        RWRETURN(RpSkinSkeletonSetCurrentAnimTime(pSkeleton, time));
    }

    pCurrentBoneFrame = rpSKINSKELETONGETINTERPFRAME(pSkeleton);
    pCurrentBoneFrame1 = rpSKINSKELETONGETINTERPFRAME1(pSkeleton);
    pCurrentBoneFrame2 = rpSKINSKELETONGETINTERPFRAME2(pSkeleton);

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

    pSkeleton->currentTime += time;

    duration = pCurrentAnim->duration;

    if (pSkeleton->currentTime > duration)
    {
        /* Loop! */
        while (pSkeleton->currentTime > duration)
        {
            pSkeleton->currentTime -= duration;

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

            bLoopCallBack = TRUE;
        }

        time = pSkeleton->currentTime;

        RpSkinSkeletonSetCurrentAnim(pSkeleton, pCurrentAnim);
        pSkeleton->currentTime = time;
    }

    pCurrentBoneFrame = rpSKINSKELETONGETINTERPFRAME(pSkeleton);
    pCurrentBoneFrame1 = rpSKINSKELETONGETINTERPFRAME1(pSkeleton);
    pCurrentBoneFrame2 = rpSKINSKELETONGETINTERPFRAME2(pSkeleton);

    numBones = pSkeleton->numBones;

    minBone = 0;

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

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

            pCurrentBoneFrame++;
            pCurrentBoneFrame1++;
            pCurrentBoneFrame2++;
        }

        if (bRepeat)
        {
            rpSKINSKELETONGETINTERPFRAME1(pSkeleton)[minBone] =
                rpSKINSKELETONGETINTERPFRAME2(pSkeleton)[minBone];
            rpSKINSKELETONGETINTERPFRAME2(pSkeleton)[minBone] =
                *pSkeleton->pNextFrame;
            pSkeleton->pNextFrame++;
        }

        pCurrentBoneFrame = rpSKINSKELETONGETINTERPFRAME(pSkeleton);
        pCurrentBoneFrame1 = rpSKINSKELETONGETINTERPFRAME1(pSkeleton);
        pCurrentBoneFrame2 = rpSKINSKELETONGETINTERPFRAME2(pSkeleton);

    }
    while (bRepeat);

    for (i = 0; i < numBones; i++)
    {
        if (pCurrentBoneFrame->time != pSkeleton->currentTime)
        {
            /* Interpolate to new time */
            const RwReal        currentTime = pSkeleton->currentTime;

            RpSkinFrameInterpolateMacro(pCurrentBoneFrame,
                                        pCurrentBoneFrame1,
                                        pCurrentBoneFrame2,
                                        currentTime);
        }

        pCurrentBoneFrame++;
        pCurrentBoneFrame1++;
        pCurrentBoneFrame2++;

    }

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

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

    }

    RWRETURN(TRUE);
}

/**
 * \ingroup rpskinp2
 * \ref RpSkinSkeletonSubAnimTime subtracts the given amount of time
 * from the current animation on a skin.
 *
 * If the skeleton is attached to a skin, the skin will not update until
 * \ref RpSkinUpdateMatrices is called.
 *
 * \param pSkeleton  A pointer to a skeleton
 * \param time  The amount of time to subtract from the animation
 *
 * \return TRUE on success
 *
 * \see RpSkinUpdateMatrices
 * \see RpSkinSkeletonAddAnimTime
 * \see RpSkinSkeletonSetCurrentAnimTime
 * \see RpSkinAddAnimTime
 * \see RpSkinSetCurrentAnimTime
 */

RwBool
RpSkinSkeletonSubAnimTime(RpSkinSkeleton * pSkeleton, RwReal time)
{
    RpSkinAnim         *pCurrentAnim;
    RpSkinFrame        *pCurrentBoneFrame1;
    RpSkinFrame        *pCurrentBoneFrame2;
    RpSkinFrame        *pCurrentBoneFrame;
    RwBool              bCallBack = FALSE;
    RwBool              bLoopCallBack = FALSE;
    RwInt32             i;
    RwInt32             numBones;
    RwReal              duration;

    RWAPIFUNCTION(RWSTRING("RpSkinSkeletonSubAnimTime"));
    RWASSERT(pSkeleton);

    if (time < 0.0f)
    {
        RWRETURN(RpSkinSkeletonAddAnimTime(pSkeleton, -time));
    }

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

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

    if (pSkeleton->currentTime < 0.0f)
    {

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

        time = pSkeleton->currentTime;
        RpSkinSkeletonSetCurrentAnim(pSkeleton, pCurrentAnim);
        RpSkinSkeletonAddAnimTime(pSkeleton, time);

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

        bLoopCallBack = TRUE;
    }

    pCurrentBoneFrame = rpSKINSKELETONGETINTERPFRAME(pSkeleton);
    pCurrentBoneFrame1 = rpSKINSKELETONGETINTERPFRAME1(pSkeleton);
    pCurrentBoneFrame2 = rpSKINSKELETONGETINTERPFRAME2(pSkeleton);

    numBones = pSkeleton->numBones;

    for (i = 0; i < numBones; i++)
    {
        while (pSkeleton->currentTime < pCurrentBoneFrame1->time)
        {
            *pCurrentBoneFrame2 = *pCurrentBoneFrame1;
            *pCurrentBoneFrame1 = *(pCurrentBoneFrame1->prevFrame);
        }

        if (pCurrentBoneFrame->time != pSkeleton->currentTime)
        {
            /* Interpolate to new time */
            const RwReal        currentTime = pSkeleton->currentTime;

            RpSkinFrameInterpolateMacro(pCurrentBoneFrame,
                                        pCurrentBoneFrame1,
                                        pCurrentBoneFrame2,
                                        currentTime);
        }

        pCurrentBoneFrame++;
        pCurrentBoneFrame1++;
        pCurrentBoneFrame2++;
    }

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

    pSkeleton->pNextFrame = (RpSkinFrame *) NULL;

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

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

    }

    RWRETURN(TRUE);

}

/**
 * \ingroup rpskinp2
 * \ref RpSkinSkeletonSetCurrentAnimTime sets the current animation time
 *  of a skeleton.
 *
 * If the skeleton is attached to a skin, the skin will not update until
 * \ref RpSkinUpdateMatrices is called.
 *
 *  \param pSkeleton  A pointer to a skeleton.
 *  \param time  The time to which to set the current animation.
 *
 *  \return TRUE on success, FALSE otherwise.
 *
 * \see RpSkinUpdateMatrices
 * \see RpSkinSkeletonAddAnimTime
 * \see RpSkinSkeletonSubAnimTime
 * \see RpSkinAddAnimTime
 * \see RpSkinSetCurrentAnimTime
 */

RwBool
RpSkinSkeletonSetCurrentAnimTime(RpSkinSkeleton * pSkeleton,
                                 RwReal time)
{
    RWAPIFUNCTION(RWSTRING("RpSkinSkeletonSetCurrentAnimTime"));
    RWASSERT(pSkeleton);

    RpSkinSkeletonAddAnimTime(pSkeleton, time - pSkeleton->currentTime);

    RWRETURN(TRUE);
}

/**
 * \ingroup rpskinp2
 * \ref RpSkinSkeletonSetAnimCallBack is used to set a callback function
 * that will be called when a skeleton reaches a specified time.  The
 * callback function should return a pointer to the skeleton if the function
 * should continue to be called, or NULL if it should never be called again.
 *
 * \param pSkeleton  A pointer to a skeleton.
 * \param pCallBack  A pointer to a callback function.
 * \param time  The time at which the callback function should be called.
 * \param pData Data which is passed to the callback
 *
 * \return A pointer to the skeleton on success, NULL if an error occurs.
 *
 */

RpSkinSkeleton     *
RpSkinSkeletonSetAnimCallBack(RpSkinSkeleton * pSkeleton,
                              RpSkinSkeletonCallBack pCallBack,
                              RwReal time, void *pData)
{
    RWAPIFUNCTION(RWSTRING("RpSkinSkeletonSetAnimCallBack"));

    RWASSERT(pSkeleton);

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

    RWRETURN(pSkeleton);
}

/**
 * \ingroup rpskinp2
 * \ref RpSkinSkeletonSetAnimLoopCallBack is used to set a callback function
 * that will be called when a skeleton's animation loops.  The
 * callback function should return a pointer to the skeleton if the function
 * should continue to be called, or NULL if it should never be called again.
 *
 * \param pSkeleton  A pointer to a skeleton.
 * \param pCallBack  A pointer to a callback function.
 * \param pData Data which is passed to the callback
 * \return A pointer to the skeleton on success, NULL if an error occurs.
 *
 */

RpSkinSkeleton     *
RpSkinSkeletonSetAnimLoopCallBack(RpSkinSkeleton * pSkeleton,
                                  RpSkinSkeletonCallBack pCallBack,
                                  void *pData)
{
    RWAPIFUNCTION(RWSTRING("RpSkinSkeletonSetAnimLoopCallBack"));

    RWASSERT(pSkeleton);

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

    RWRETURN(pSkeleton);
}

/**
 * \ingroup rpskinp2
 * \ref RpSkinFrameBlend Blends between two RpSkinFrames using a given
 * blend factor.
 *
 * \param pOut  A pointer to the output frame.
 * \param pIn1  A pointer to the first input frame.
 * \param pIn2  A pointer to the second input frame.
 * \param fAlpha  The blending factor.
 *
 * \return None
 */
void
RpSkinFrameBlend(RpSkinFrame * pOut, RpSkinFrame * pIn1,
                 RpSkinFrame * pIn2, RwReal fAlpha)
{
    RwReal              fBeta, fTheta;

    /* Compute dot product
     * (equal to cosine of the angle between quaternions)
     */
    RwReal              fCosTheta =
        RwV3dDotProduct(&pIn1->q.imag, &pIn2->q.imag) +
        pIn1->q.real * pIn2->q.real;
    RwBool              bObtuseTheta;
    RwBool              bNearlyZeroTheta;

    RWAPIFUNCTION(RWSTRING("RpSkinFrameBlend"));
    RWASSERT(pOut);
    RWASSERT(pIn1);
    RWASSERT(pIn2);

    /* Linearly interpolate positions */
    RpV3dInterpolate(&pOut->t, &pIn1->t, fAlpha, &pIn2->t);

    /* Check angle to see if quaternions are in opposite hemispheres */
    bObtuseTheta = (fCosTheta < ((RwReal) 0));

    if (bObtuseTheta)
    {
        /* If so, flip one of the quaterions */
        fCosTheta = -fCosTheta;
        RwV3dNegate(&pIn2->q.imag, &pIn2->q.imag);
        pIn2->q.real = -pIn2->q.real;
    }

    /* Set factors to do linear interpolation, as a special case where the */
    /* quaternions are close together. */
    fBeta = 1.0f - fAlpha;

    /* If the quaternions aren't close, proceed with spherical interpolation */
    bNearlyZeroTheta = (fCosTheta >= _TOL_COS_ZERO);

    if (!bNearlyZeroTheta)
    {
        RwReal              fCosecTheta;

        RwIEEEACosfMacro(fTheta, fCosTheta);
        RwCosecMinusPiToPiMacro(fCosecTheta, fTheta);

        fBeta *= fTheta;
        RwSinMinusPiToPiMacro(fBeta, fBeta);
        fBeta *= fCosecTheta;

        fAlpha *= fTheta;
        RwSinMinusPiToPiMacro(fAlpha, fAlpha);
        fAlpha *= fCosecTheta;
    }

    /* Do the interpolation */
    pOut->q.imag.x = fBeta * pIn1->q.imag.x + fAlpha * pIn2->q.imag.x;
    pOut->q.imag.y = fBeta * pIn1->q.imag.y + fAlpha * pIn2->q.imag.y;
    pOut->q.imag.z = fBeta * pIn1->q.imag.z + fAlpha * pIn2->q.imag.z;
    pOut->q.real = fBeta * pIn1->q.real + fAlpha * pIn2->q.real;

    RWRETURNVOID();
}

/**
 * \ingroup rpskinp2
 * \ref RpSkinSkeletonBlend interpolates between two skeletons.  Each
 * skeleton should have an animation attached, and the result will be
 * interpolated between the current state of the two input skeletons
 *
 * 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 skeletons, anim1skel, anim2skel and tempskel.
 *
 * Initially anim1 would be running on anim1skel which is attached to the
 * skin.  When the blend is required anim2 is attached to anim2skel and
 * tempskel is now attached to the skin. For each frame of the blend
 * duration RpSkinSkeletonBlend is used to blend between anim1skel and
 * anim2skel, storing the results in tempskel. At the end of the blend
 * anim2skel is attached to the skin.
 *
 * \param pOutSkeleton  A pointer to a skeleton in which
 *                      the result is returned.
 * \param pInSkeleton1  A pointer to the first input skeleton
 * \param pInSkeleton2  A pointer to the second input skeleton
 * \param alpha  The blending parameter - 0.0 returns pInSkeleton1, 1.0 returns
 * pInSkeleton2.
 *
 * \return TRUE on success, FALSE otherwise.
 *
 */

RwBool
RpSkinSkeletonBlend(RpSkinSkeleton * pOutSkeleton,
                    RpSkinSkeleton * pInSkeleton1,
                    RpSkinSkeleton * pInSkeleton2, RwReal alpha)
{
    RwInt32             i;

    RWAPIFUNCTION(RWSTRING("RpSkinSkeletonBlend"));
    RWASSERT(pOutSkeleton);
    RWASSERT(pInSkeleton1);
    RWASSERT(pInSkeleton2);
    RWASSERT((alpha >= (RwReal) 0.0) && (alpha <= (RwReal) 1.0));
    RWASSERT(pOutSkeleton->numBones == pInSkeleton1->numBones);
    RWASSERT(pOutSkeleton->numBones == pInSkeleton2->numBones);

    for (i = 0; i < pOutSkeleton->numBones; i++)
    {
        RpSkinFrameBlend(&rpSKINSKELETONGETINTERPFRAME(pOutSkeleton)[i],
                         &rpSKINSKELETONGETINTERPFRAME(pInSkeleton1)[i],
                         &rpSKINSKELETONGETINTERPFRAME(pInSkeleton2)[i],
                         alpha);
    }

    RWRETURN(TRUE);
}

/**
 * \ingroup rpskinp2
 * \ref RpSkinSkeletonAddTogether adds together two skeletons.  Each
 * skeleton should have an animation attached, and the result will be
 * the addition of the two input skeletons.  This is mainly useful where
 * the second skeleton's pose is a delta from a set pose to be added to the
 * pose held in the first skeleton.  For example, a walk animation could have
 * a crouch pose added to it as a delta from a standing pose.
 * \note pOutSkeleton, pInSkeleton1 and pInSkeleton2 MUST be different.
 *
 * So used together, if a head is moved round in a circle in an animation
 * which is converted to a delta animation; and this is added to a walk
 * animation; the result is a walking whatever, with it's head going
 * round and round.
 *
 * \param pOutSkeleton  A pointer to a skeleton in which
 *                      the result is returned.
 * \param pInSkeleton1  A pointer to the first input skeleton
 * \param pInSkeleton2  A pointer to the second input skeleton
 *
 * \return TRUE on success, FALSE otherwise.
 *
 * \see RpSkinAnimMakeDelta
 *
 */

RwBool
RpSkinSkeletonAddTogether(RpSkinSkeleton * pOutSkeleton,
                          RpSkinSkeleton * pInSkeleton1,
                          RpSkinSkeleton * pInSkeleton2)
{
    RwInt32             i;

    RWAPIFUNCTION(RWSTRING("RpSkinSkeletonAddTogether"));
    RWASSERT(pOutSkeleton);
    RWASSERT(pInSkeleton1);
    RWASSERT(pInSkeleton2);
    RWASSERT(pOutSkeleton->numBones == pInSkeleton1->numBones);
    RWASSERT(pOutSkeleton->numBones == pInSkeleton2->numBones);

    for (i = 0; i < pOutSkeleton->numBones; i++)
    {
        SkinFrameAddTogether(&rpSKINSKELETONGETINTERPFRAME(pOutSkeleton)
                             [i],
                             &rpSKINSKELETONGETINTERPFRAME(pInSkeleton1)
                             [i],
                             &rpSKINSKELETONGETINTERPFRAME(pInSkeleton2)
                             [i]);
    }

    RWRETURN(TRUE);
}

/**
 * \ingroup rpskinp2
 * \ref RpSkinSkeletonCopy copies state of bones in the in-skeleton into
 *                              the out-skeleton.
 *
 * \param pOutSkeleton  A pointer to a skeleton in which
 *                      the result is returned.
 * \param pInSkeleton  A pointer to the input skeleton
 *
 * \return TRUE on success, FALSE otherwise.
 *
 */

RwBool
RpSkinSkeletonCopy(RpSkinSkeleton * pOutSkeleton,
                   RpSkinSkeleton * pInSkeleton)
{
    RWAPIFUNCTION(RWSTRING("RpSkinSkeletonCopy"));
    RWASSERT(pOutSkeleton);
    RWASSERT(pInSkeleton);
    RWASSERT(pOutSkeleton->numBones == pInSkeleton->numBones);

    memcpy(rpSKINSKELETONGETINTERPFRAME(pOutSkeleton),
           rpSKINSKELETONGETINTERPFRAME(pInSkeleton),
           sizeof(RpSkinFrame) * pOutSkeleton->numBones);

    RWRETURN(TRUE);
}

/**
 * \ingroup rpskinp2
 * \ref RpSkinSetCurrentAnimTime sets the animation time of the
 * skeleton currently attached to a skin and updates the skin's bone matrices
 * to reflect the result.
 *
 * \param pSkin  A pointer to a skin
 * \param time  The absolute time to which to set the current animation
 *
 * \return TRUE on success, FALSE if an error occurs.
 */

RwBool
RpSkinSetCurrentAnimTime(RpSkin * pSkin, RwReal time)
{
    RWAPIFUNCTION(RWSTRING("RpSkinSetCurrentAnimTime"));
    RWASSERT(pSkin);
    RWASSERT(pSkin->pCurrentSkeleton);

    RpSkinAddAnimTime(pSkin,
                      time - pSkin->pCurrentSkeleton->currentTime);
    RWRETURN(TRUE);
}

/**
 * \ingroup rpskinp2
 * \ref RpSkinAddAnimTime adds the given amount of time to the current
 * animation on a skin and updates the skin's bone matrices to the result.
 *
 * \param pSkin  A pointer to a skin.
 * \param time  The amount of time to add to the animation.
 *
 * \return TRUE on success, FALSE if an error occurs.
 *
 * \see RpSkinSubAnimTime
 */
RwBool
RpSkinAddAnimTime(RpSkin * pSkin, RwReal time)
{
    RwBool              result;

    RWAPIFUNCTION(RWSTRING("RpSkinAddAnimTime"));
    RWASSERT(pSkin);
    RWASSERT(pSkin->pCurrentSkeleton);

    result = RpSkinSkeletonAddAnimTime(pSkin->pCurrentSkeleton, time);

    if (result)
    {
        RpSkinUpdateMatrices(pSkin);
    }

    RWRETURN(result);
}

/****************************************************************************
 RpSkinSubAnimTime

 Subtracts the given amount of time from the current animation on a skin

 Inputs :   RpSkin *    A pointer to a skin
            RwReal      The amount of time to subtract from the animation

 Outputs:   TRUE on success
 */

/**
 * \ingroup rpskinp2
 * \ref RpSkinSubAnimTime subtracts the given amount of time from
 * the current animation on a skin and updates the skin's bone matrices
 * to the result.
 *
 * \param pSkin  A pointer to a skin.
 * \param time  The amount of time to add to the animation.
 *
 * \return TRUE on success, FALSE if an error occurs.
 *
 * \see RpSkinAddAnimTime
 */
RwBool
RpSkinSubAnimTime(RpSkin * pSkin, RwReal time)
{
    RwBool              result;

    RWAPIFUNCTION(RWSTRING("RpSkinSubAnimTime"));
    RWASSERT(pSkin);
    RWASSERT(pSkin->pCurrentSkeleton);

    result = RpSkinSkeletonSubAnimTime(pSkin->pCurrentSkeleton, time);

    if (result)
    {
        RpSkinUpdateMatrices(pSkin);
    }

    RWRETURN(result);
}

#if (0)

/****************************************************************************
 SkinInterpolateFromIdentity

 Interpolates from the identity quaternion to the passed in quaternion by
 the specified amount.

 Inputs :   RpSkinFrame *   Output frame pointer
                        RpSkinFrame *   Input frame pointer
                        RwReal                  Interpolation factor

 Outputs:   None
 */

static void
SkinInterpolateFromIdentity(RpSkinFrame * pOut, RpSkinFrame * pIn,
                            RwReal alpha)
{
    RwReal              fBeta, fTheta;

    /* Compute dot product
     * (equal to cosine of the angle between quaternions)
     */
    RwReal              fCosTheta = pIn->q.real;
    RwBool              bObtuseTheta;
    RwBool              bNearlyZeroTheta;

    RWFUNCTION(RWSTRING("SkinInterpolateFromIdentity"));
    RWASSERT(pOut);
    RWASSERT(pIn);
    RWASSERT(alpha <= (RwReal) 1.0);
    RWASSERT(alpha >= (RwReal) 0.0);

    /* Don't interpolate positions */
    pOut->t = pIn->t;

    /* Check angle to see if quaternions are in opposite hemispheres */
    bObtuseTheta = (fCosTheta < ((RwReal) 0));

    if (bObtuseTheta)
    {
        /* If so, flip the quaterion */
        fCosTheta = -fCosTheta;
        RwV3dNegate(&pIn->q.imag, &pIn->q.imag);
        pIn->q.real = -pIn->q.real;
    }

    /* Set factors to do linear interpolation, as a special case where the */
    /* quaternions are close together. */
    fBeta = 1.0f - alpha;

    /* If the quaternions aren't close, proceed with spherical interpolation */
    bNearlyZeroTheta = (fCosTheta >= _TOL_COS_ZERO);

    if (!bNearlyZeroTheta)
    {
        RwReal              fCosecTheta;

        RwIEEEACosfMacro(fTheta, fCosTheta);
        RwCosecMinusPiToPiMacro(fCosecTheta, fTheta);

        fBeta *= fTheta;
        RwSinMinusPiToPiMacro(fBeta, fBeta);
        fBeta *= fCosecTheta;

        fAlpha *= fTheta;
        RwSinMinusPiToPiMacro(fAlpha, fAlpha);
        fAlpha *= fCosecTheta;
    }

    /* Do the interpolation */
    pOut->q.imag.x = alpha * pIn->q.imag.x;
    pOut->q.imag.y = alpha * pIn->q.imag.y;
    pOut->q.imag.z = alpha * pIn->q.imag.z;
    pOut->q.real = fBeta + alpha * pIn->q.real;

    RWRETURNVOID();

}

#endif /* (0) */

/****************************************************************************
 SkinAnimFrameToMatrix

 Converts a skin frame to a 4x4 matrix

 Inputs :   RwMatrix * A pointer to the result matrix
            RpSkinFrame *  A pointer to the skin frame

 Outputs:   None
 */

static void
SkinAnimFrameToMatrix(RwMatrix * pMatrix, RpSkinFrame * pIFrame)
{
    RwReal             scale;
    RtQuat             q;
    RWFUNCTION(RWSTRING("SkinAnimFrameToMatrix"));
    RWASSERT(pMatrix);
    RWASSERT(pIFrame);

    /* Convert to matrix */

    /*
     * Warning !!
     * rpanim and rtquat evolved with Watt-and-Watt style Quaternions
     * - see p360, Advanced Animation and Rendering Techniques
     *             Alan Watt and Mark Watt, Addison-Wesley 1993,
     *             ISBN 0-201-54412-1
     * rpskin evolved with DX7-style Quaternions,
     * - see mssdk/samples/Multimedia/D3DIM/src/D3DFrame/d3dmath.cpp
     * These rotate in mutally opposite senses
     * - one right handed, the other left-handed.
     * Take conjugate to allow for flipping.
     */

    scale = RtQuatModulusSquared(&pIFrame->q);
    rwInvSqrtMacro(scale, scale);
    q.real = pIFrame->q.real * scale;
    scale = -scale;
    RwV3dScale(&q.imag, &pIFrame->q.imag, scale);

    RtQuatUnitConvertToMatrix(&q, pMatrix);

#if (0)
    {
        RwReal              x = pIFrame->q.imag.x;
        RwReal              y = pIFrame->q.imag.y;
        RwReal              z = pIFrame->q.imag.z;
        RwReal              w = pIFrame->q.real;
        RwV3d               square;
        RwV3d               cross;
        RwV3d               wimag;

        square.x = x * x;
        square.y = y * y;
        square.z = z * z;

        cross.x = y * z;
        cross.y = z * x;
        cross.z = x * y;

        wimag.x = w * x;
        wimag.y = w * y;
        wimag.z = w * z;

        pMatrix->right.x = 1 - 2 * (square.y + square.z);
        pMatrix->right.y = 2 * (cross.z - wimag.z);
        pMatrix->right.z = 2 * (cross.y + wimag.y);

        pMatrix->up.x = 2 * (cross.z + wimag.z);
        pMatrix->up.y = 1 - 2 * (square.x + square.z);
        pMatrix->up.z = 2 * (cross.x - wimag.x);

        pMatrix->at.x = 2 * (cross.y - wimag.y);
        pMatrix->at.y = 2 * (cross.x + wimag.x);
        pMatrix->at.z = (1 - 2 * (square.x + square.y));

    }
#endif /* (0) */

    pMatrix->pos.x = pIFrame->t.x;
    pMatrix->pos.y = pIFrame->t.y;
    pMatrix->pos.z = pIFrame->t.z;

#if (0)
    pMatrix->rw = 0.0f;
    pMatrix->uw = 0.0f;
    pMatrix->aw = 0.0f;
    pMatrix->pw = 1.0f;
#endif /* (0) */

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

    RWRETURNVOID();
}

/****************************************************************************
 SkinMatrixSetIdentity

 Sets an RwMatrix to identity

 Inputs :   RwMatrix *  A pointer to the matrix

 Outputs:   None
 */

static void
SkinMatrixSetIdentity(RwMatrix * pMatrix)
{
    RWFUNCTION(RWSTRING("SkinMatrixSetIdentity"));
    RWASSERT(pMatrix);

    RwMatrixSetIdentity(pMatrix);

#if (0)

    pMatrix->right.x = 1.0f;
    pMatrix->right.y = 0.0f;
    pMatrix->right.z = 0.0f;

    pMatrix->up.x = 0.0f;
    pMatrix->up.y = 1.0f;
    pMatrix->up.z = 0.0f;

    pMatrix->at.x = 0.0f;
    pMatrix->at.y = 0.0f;
    pMatrix->at.z = 1.0f;

    pMatrix->pos.x = 0.0f;
    pMatrix->pos.y = 0.0f;
    pMatrix->pos.z = 0.0f;

#if (0)
    pMatrix->rw = 0.0f;
    pMatrix->uw = 0.0f;
    pMatrix->aw = 0.0f;
    pMatrix->pw = 1.0f;
#endif /* (0) */

#endif /* (0) */

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

    RWRETURNVOID();
}

/****************************************************************************
 SkinMatrixMultiply

 Multiplies 2 matrices.  Hopefully quite quickly.

 Inputs :   RwMatrix *  A pointer to the output matrix
            RwMatrix *  A pointer to the first input matrix
            RwMatrix *  A pointer to the second input matrix

 Outputs:   None
 */

#ifdef _R5900_

static void
SkinMatrixMultiply(RwMatrix * pOutMatrix, RwMatrix * pMat1,
                   RwMatrix * pMat2)
{

    RWFUNCTION(RWSTRING("SkinMatrixMultiply"));
    RWASSERT(pOutMatrix);
    RWASSERT(pMat1);
    RWASSERT(pMat2);

    SkinMatrixMultiplyMacro5900(pOutMatrix, pMat1, pMat2);

    RWRETURNVOID();
}

#else

static void
SkinMatrixMultiply(RwMatrix * pOutMatrix,
                   RwMatrix * pMat1, RwMatrix * pMat2)
{

    RWFUNCTION(RWSTRING("SkinMatrixMultiply"));
    RWASSERT(pOutMatrix);
    RWASSERT(pMat1);
    RWASSERT(pMat2);

    /* RWASSERT(rwMatrixValidFlags(pMat1, _EPSILON)); */
    /* RWASSERT(rwMatrixValidFlags(pMat2, _EPSILON)); */

    RwMatrixMultiply(pOutMatrix, pMat1, pMat2);

#if (0)
    /* RWASSERT(rwMatrixValidFlags(pOutMatrix, _EPSILON)); */
#endif /* (0) */

#if (0)
    pOutMatrix->right.x = ((pMat1->right.x * pMat2->right.x) +
                           (pMat1->right.y * pMat2->up.x) +
                           (pMat1->right.z * pMat2->at.x));
    pOutMatrix->right.y = ((pMat1->right.x * pMat2->right.y) +
                           (pMat1->right.y * pMat2->up.y) +
                           (pMat1->right.z * pMat2->at.y));
    pOutMatrix->right.z = ((pMat1->right.x * pMat2->right.z) +
                           (pMat1->right.y * pMat2->up.z) +
                           (pMat1->right.z * pMat2->at.z));

    pOutMatrix->up.x = ((pMat1->up.x * pMat2->right.x) +
                        (pMat1->up.y * pMat2->up.x) +
                        (pMat1->up.z * pMat2->at.x));
    pOutMatrix->up.y = ((pMat1->up.x * pMat2->right.y) +
                        (pMat1->up.y * pMat2->up.y) +
                        (pMat1->up.z * pMat2->at.y));
    pOutMatrix->up.z = ((pMat1->up.x * pMat2->right.z) +
                        (pMat1->up.y * pMat2->up.z) +
                        (pMat1->up.z * pMat2->at.z));

    pOutMatrix->at.x = ((pMat1->at.x * pMat2->right.x) +
                        (pMat1->at.y * pMat2->up.x) +
                        (pMat1->at.z * pMat2->at.x));
    pOutMatrix->at.y = ((pMat1->at.x * pMat2->right.y) +
                        (pMat1->at.y * pMat2->up.y) +
                        (pMat1->at.z * pMat2->at.y));
    pOutMatrix->at.z = ((pMat1->at.x * pMat2->right.z) +
                        (pMat1->at.y * pMat2->up.z) +
                        (pMat1->at.z * pMat2->at.z));

    pOutMatrix->pos.x = ((pMat1->pos.x * pMat2->right.x) +
                         (pMat1->pos.y * pMat2->up.x) +
                         (pMat1->pos.z * pMat2->at.x) + pMat2->pos.x);
    pOutMatrix->pos.y = ((pMat1->pos.x * pMat2->right.y) +
                         (pMat1->pos.y * pMat2->up.y) +
                         (pMat1->pos.z * pMat2->at.y) + pMat2->pos.y);
    pOutMatrix->pos.z = ((pMat1->pos.x * pMat2->right.z) +
                         (pMat1->pos.y * pMat2->up.z) +
                         (pMat1->pos.z * pMat2->at.z) + pMat2->pos.z);

    pOutMatrix->rw = (RwReal) 0.0;
    pOutMatrix->uw = (RwReal) 0.0;
    pOutMatrix->aw = (RwReal) 0.0;
    pOutMatrix->pw = (RwReal) 1.0;

#endif /* (0) */

    RWRETURNVOID();
}

#endif /* _R5900_ */

/**
 * \ingroup rpskinp2
 * \ref RpSkinUpdateMatrices updates the a skin's matrices to reflect
 * the current state of its skeleton.  This should be called after any
 * direct operation on a skeleton or animation that is attached to a skin.
 *
 * \param pSkin  A pointer to a skin
 *
 * \return TRUE on success, FALSE if an error occurs.
 */

RwBool
RpSkinUpdateMatrices(RpSkin * pSkin)
{
    RpSkinBoneInfo     *pCurrentBone;
    RpSkinFrame        *pCurrentBoneFrame;
    RwInt32             i;
    RwMatrix           *pCurrentMatrix;
    RwMatrix            parentMatrix;
    RwMatrix            boneMatrix;
    RwMatrix            tempMatrix;
    RpSkinMatrixStack   matrixStack;
    RwMatrix           *pMatrixStackTop = &matrixStack[0];

    RWAPIFUNCTION(RWSTRING("RpSkinUpdateMatrices"));
    RWASSERT(pSkin);

    /* memset(&matrixStack[0], 0, sizeof(matrixStack)); */
    parentMatrix.flags = 0;
    boneMatrix.flags = 0;
    tempMatrix.flags = 0;
    pCurrentBoneFrame =
        rpSKINSKELETONGETINTERPFRAME(pSkin->pCurrentSkeleton);

    if (!pSkin->pCurrentSkeleton)
    {
        RWRETURN(FALSE);
    }

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

    pCurrentBone = pSkin->pBoneInfo;
    pCurrentMatrix = pSkin->pCurrentSkeleton->pMatrixArray;

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

    for (i = 0; i < pSkin->numBones; i++)
    {

        if (pCurrentBone->flags & rpSKINPUSHPARENTMATRIX)
        {
            *pMatrixStackTop++ = parentMatrix;
        }

        SkinAnimFrameToMatrix(&boneMatrix, pCurrentBoneFrame);

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

        SkinMatrixMultiply(&tempMatrix, &boneMatrix, &parentMatrix);

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

        if (pCurrentBone->pFrame != NULL)
        {
            RwMatrix           *pMatrix;

            pMatrix = RwFrameGetMatrix(pCurrentBone->pFrame);

            pMatrix->right.x = tempMatrix.right.x;
            pMatrix->right.y = tempMatrix.right.y;
            pMatrix->right.z = tempMatrix.right.z;

            pMatrix->up.x = tempMatrix.up.x;
            pMatrix->up.y = tempMatrix.up.y;
            pMatrix->up.z = tempMatrix.up.z;

            pMatrix->at.x = tempMatrix.at.x;
            pMatrix->at.y = tempMatrix.at.y;
            pMatrix->at.z = tempMatrix.at.z;

            pMatrix->pos.x = tempMatrix.pos.x;
            pMatrix->pos.y = tempMatrix.pos.y;
            pMatrix->pos.z = tempMatrix.pos.z;

            /* RwMatrixUpdate(pMatrix); */
            RwMatrixOptimize(pMatrix, RWMATRIXOPTIMIZETOLERANCE);

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

            RwFrameUpdateObjects(pCurrentBone->pFrame);
        }

        /* RWASSERT(rwMatrixValidFlags(&pCurrentBone->boneToSkinMat, _EPSILON)); */
        /* RWASSERT(rwMatrixValidFlags(&tempMatrix, _EPSILON)); */

        SkinMatrixMultiply(pCurrentMatrix,
                           &pCurrentBone->boneToSkinMat, &tempMatrix);

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

        if (pCurrentBone->flags & rpSKINPOPPARENTMATRIX)
        {
            parentMatrix = *--pMatrixStackTop;
        }
        else
        {
            parentMatrix = tempMatrix;
        }

#if (0)
        /* RWASSERT(rwMatrixValidFlags(&parentMatrix, _EPSILON)); */
#endif /* (0) */

        pCurrentBoneFrame++;
        pCurrentMatrix++;
        pCurrentBone++;
    }

    RWRETURN(TRUE);
}

/**
 * \ingroup rpskinp2
 * \ref RpSkinAttachFrameToBone attaches an RwFrame to a skin's bone.
 * The modelling matrix of this frame will be automatically updated with
 * the LTM of the bone as the skin animates.
 *
 * \param pSkin  A pointer to a skin
 * \param boneIndex  The index of the bone to which the frame is to be attached
 * \param pFrame  A pointer to the frame
 *
 * \return TRUE on success, FALSE if an error occurs.
 */

RwBool
RpSkinAttachFrameToBone(RpSkin * pSkin, RwInt32 boneIndex,
                        RwFrame * pFrame)
{
    RWAPIFUNCTION("RpSkinAttachFrameToBone");
    RWASSERT(pSkin);

    if (boneIndex >= pSkin->numBones)
    {
        RWRETURN(FALSE);
    }

    pSkin->pBoneInfo[boneIndex].pFrame = pFrame;

    RWRETURN(TRUE);
}

/**
 * \ingroup rpskinp2
 * \ref RpSkinBoneTagToBoneIndex returns the bone index of a bone with
 * the specified bone tag value.
 *
 * \param pSkin  A pointer to a skin
 * \param boneTag  The tag value of the bone
 *
 * \return The index of the bone, or -1 on error.
 */

RwInt32
RpSkinBoneTagToBoneIndex(RpSkin * pSkin, RwInt32 boneTag)
{
    RwInt32             result = -1;
    RwInt32             i;

    RWAPIFUNCTION("RpSkinBoneTagToBoneIndex");
    RWASSERT(pSkin);

    for (i = 0; i < pSkin->numBones; i++)
    {
        if (pSkin->pBoneInfo[i].boneTag == boneTag)
        {
            result = i;
            break;
        }
    }

    RWRETURN(result);
}

/**
 * \ingroup rpskinp2
 * \ref RpSkinAnimCreate creates an animation.  Animations are arrays
 * of RpAnimFrame stuctures, each of which is a keyframe for a particular
 * bone of a skin.
 *
 * An animation keyframe is a quaternion and a translation, plus a time.
 * An animation is an array of these, presented in the order they are needed
 * to animate forwards through time.  Pointers link all of the frames
 * for a particular bone backwards through time in a list.
 *
 * For example, a 3 boned animation, with keyframes at the following times:
 *
 * Bone 1: 0.0, 1.0, 2.0, 3.0
 * Bone 2: 0.0, 3.0
 * Bone 3: 0.0, 2.0, 2.5, 3.0
 *
 * should be formatted in an RpSkinAnim animation like this:
 *
 * B1,0.0 B2,0.0 B3,0.0 B1,1.0, B2,3.0, B3,2.0, B1,2.0, B1,3.0, B3,2.5 B3,3.0
 *
 * Each bone MUST start at time = 0.0, and each bone must terminate
 * with a frame at time = duration of animation.

 * \param numFrames  The number of frames 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.
 */

RpSkinAnim         *
RpSkinAnimCreate(RwInt32 numFrames, RwInt32 flags, RwReal duration)
{
    RpSkinAnim         *pSkinAnim;
    RwUInt32            bytes;

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

    bytes = sizeof(RpSkinAnim) + (numFrames * sizeof(RpSkinFrame));

    pSkinAnim = (RpSkinAnim *) RwMalloc(bytes);
    memset(pSkinAnim, 0, bytes);

    pSkinAnim->numFrames = numFrames;

    pSkinAnim->duration = duration;

    pSkinAnim->flags = flags;

    pSkinAnim->pFrames = (RpSkinFrame *) ((RwUInt8 *)pSkinAnim + sizeof(RpSkinAnim));

    RWRETURN(pSkinAnim);

}

/**
 * \ingroup rpskinp2
 * \ref RpSkinAnimDestroy destroys an animation.
 *
 * \param pSkinAnim  Pointer to the animation to be destroyed
 *
 * \return NULL if an error occurs.
 */

RpSkinAnim         *
RpSkinAnimDestroy(RpSkinAnim * pSkinAnim)
{
    RWAPIFUNCTION(RWSTRING("RpSkinAnimDestroy"));

    if (NULL != pSkinAnim)
    {
        RwFree(pSkinAnim);
        pSkinAnim = (RpSkinAnim *) NULL;
    }
    RWRETURN(pSkinAnim);
}

/**
 * \ingroup rpskinp2
 * \ref RpSkinAnimRead
 *
 * Reads a .SKA 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
 *
 */

RpSkinAnim         *
RpSkinAnimRead(const RwChar * pFilename)
{
    RpSkinAnim         *pSkinAnim;
    RwStream           *pStream;
    RwInt32             i;
    RwInt32             temp;
    RwUInt32            bytes;
    RwInt32             numFrames;

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

    pStream = RwStreamOpen(rwSTREAMFILENAME, rwSTREAMREAD, pFilename);

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

    if (!RwStreamReadInt
        (pStream, (RwInt32 *) &numFrames,
         sizeof(RwInt32)))
    {
        RWRETURN((RpSkinAnim *) NULL);
    }

    bytes = sizeof(RpSkinAnim) + (sizeof(RpSkinFrame) * numFrames);
    pSkinAnim = (RpSkinAnim *) RwMalloc(bytes);

    if (!pSkinAnim)
    {
        RWRETURN((RpSkinAnim *) NULL);
    }

    memset(pSkinAnim, 0, bytes);
    pSkinAnim->pFrames = (RpSkinFrame *) ((RwUInt8 *)pSkinAnim + sizeof(RpSkinAnim));
    pSkinAnim->numFrames = numFrames;

    if (!RwStreamReadInt
        (pStream, (RwInt32 *) & (pSkinAnim->flags), sizeof(RwInt32)))
    {
        RWRETURN((RpSkinAnim *) NULL);
    }

    if (!RwStreamReadReal
        (pStream, (RwReal *) & (pSkinAnim->duration), sizeof(RwReal)))
    {
        RWRETURN((RpSkinAnim *) NULL);
    }

    for (i = 0; i < pSkinAnim->numFrames; i++)
    {
        if (!RwStreamReadReal
            (pStream, (RwReal *) & (pSkinAnim->pFrames[i]),
             sizeof(RwReal) * 8))
        {
            RWRETURN((RpSkinAnim *) NULL);
        }

        if (!RwStreamReadInt
            (pStream, (RwInt32 *) & temp, sizeof(RwInt32)))
        {
            RWRETURN((RpSkinAnim *) NULL);
        }

        pSkinAnim->pFrames[i].prevFrame =
            &pSkinAnim->pFrames[temp / sizeof(RpSkinFrame)];

        SkinQuatNormlize(&pSkinAnim->pFrames[i].q,
                         &pSkinAnim->pFrames[i].q);

    }

    RwStreamClose(pStream, NULL);

    RWRETURN(pSkinAnim);
}

/**
 * \ingroup rpskinp2
 * \ref RpSkinAnimWrite
 *
 * Writes a .SKA animation file to disk.
 *
 * \param pSkinAnim  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
RpSkinAnimWrite(RpSkinAnim * pSkinAnim, const RwChar * pFilename)
{
    RwStream           *pStream;
    RwInt32             i;
    RwInt32             temp;

    RWAPIFUNCTION(RWSTRING("RpSkinAnimWrite"));
    RWASSERT(pSkinAnim);
    RWASSERT(pFilename);

    pStream = RwStreamOpen(rwSTREAMFILENAME, rwSTREAMWRITE, pFilename);

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

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

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

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

    for (i = 0; i < pSkinAnim->numFrames; i++)
    {
        if (!RwStreamWriteReal
            (pStream, (RwReal *) & (pSkinAnim->pFrames[i]),
             sizeof(RwReal) * 8))
        {
            RWRETURN(FALSE);
        }

        temp =
            (RwInt32) (pSkinAnim->pFrames[i].prevFrame) -
            (RwInt32) (pSkinAnim->pFrames);

        if (!RwStreamWriteInt
            (pStream, (RwInt32 *) & temp, sizeof(RwInt32)))
        {
            RWRETURN(FALSE);
        }
    }

    RwStreamClose(pStream, NULL);

    RWRETURN(TRUE);
}

/**
 * \ingroup rpskinp2
 * \ref RpSkinAnimStreamRead
 *
 * Reads a skin animation from a stream.
 *
 * \param stream  A pointer to the stream to be read from.
 *
 * \return A pointer to the animation, or NULL if an error occurs
 *
 */

RpSkinAnim         *
RpSkinAnimStreamRead(RwStream * stream)
{
    RpSkinAnim         *pSkinAnim;
    RwInt32             i;
    RwInt32             temp;
    RwUInt32            bytes;
    RwInt32             numFrames;

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

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

    bytes = sizeof(RpSkinAnim) + (sizeof(RpSkinFrame) * numFrames);
    pSkinAnim = (RpSkinAnim *) RwMalloc(bytes);

    if (!pSkinAnim)
    {
        RWRETURN((RpSkinAnim *) NULL);
    }

    memset(pSkinAnim, 0, bytes);
    pSkinAnim->pFrames = (RpSkinFrame *) ((RwUInt8 *)pSkinAnim + sizeof(RpSkinAnim));
    pSkinAnim->numFrames = numFrames;

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

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

    for (i = 0; i < pSkinAnim->numFrames; i++)
    {
        if (!RwStreamReadReal(stream,
                              (RwReal *) & (pSkinAnim->pFrames[i]),
                              sizeof(RwReal) * 8))
        {
            RWRETURN((RpSkinAnim *) NULL);
        }

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

        pSkinAnim->pFrames[i].prevFrame =
            &pSkinAnim->pFrames[temp / sizeof(RpSkinFrame)];
    }

    RWRETURN(pSkinAnim);
}

/**
 * \ingroup rpskinp2
 * \ref RpSkinAnimStreamWrite
 *
 * Writes a skin animation to a stream.
 *
 * \param pSkinAnim  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
RpSkinAnimStreamWrite(RpSkinAnim * pSkinAnim, RwStream * stream)
{
    RwInt32             i;
    RwInt32             temp;

    RWAPIFUNCTION(RWSTRING("RpSkinAnimStreamWrite"));
    RWASSERT(pSkinAnim);
    RWASSERT(stream);

    RwStreamWriteChunkHeader(stream, rwID_SKINANIMATION,
                             RpSkinAnimStreamGetSize(pSkinAnim));

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

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

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

    for (i = 0; i < pSkinAnim->numFrames; i++)
    {
        if (!RwStreamWriteReal
            (stream, (RwReal *) & (pSkinAnim->pFrames[i]),
             sizeof(RwReal) * 8))
        {
            RWRETURN(FALSE);
        }

        temp =
            (RwInt32) (pSkinAnim->pFrames[i].prevFrame) -
            (RwInt32) (pSkinAnim->pFrames);

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

    RWRETURN(TRUE);
}

/**
 * \ingroup rpskinp2
 * \ref RpSkinAnimStreamGetSize
 *
 * Calculates the size of a skin animation when written to a stream.
 *
 * \param pSkinAnim  A pointer to the skin animation.
 *
 * \return Size of the skin animation in bytes.
 *
 */

RwInt32
RpSkinAnimStreamGetSize(RpSkinAnim * pSkinAnim)
{
    RwInt32             i;
    RwInt32             size = 0;

    RWAPIFUNCTION(RWSTRING("RpSkinAnimStreamGetSize"));

    RWASSERT(pSkinAnim);

    size += sizeof(RwInt32);

    size += sizeof(RwInt32);

    size += sizeof(RwReal);

    for (i = 0; i < pSkinAnim->numFrames; i++)
    {
        size += sizeof(RwReal) * 8;

        size += sizeof(RwInt32);
    }

    RWRETURN(size);
}

/**
 * \ingroup rpskinp2
 * \ref RpSkinAnimMakeDelta
 *
 * Uses an animation and determines a base pose of that animation (at the time
 * given as a parameter). The frames either side of that become 'deltas', that
 * is the difference from the base pose is calculated. These are simply used as
 * relative movements, offsets if you like from another skeleton. Any sequence
 * of these delta frames can be added to another existing animation using
 * \ref RpSkinSkeletonAddTogether on the fly, to modify it.
 *
 * \param pSkinAnim  A pointer to the animation
 * \param numBones  The number of bones required in the skeleton.
 * \param time  The time in the animation to delta from.
 *
 * \return TRUE on success, or FALSE if an error occurs.
 *
 */

RwBool
RpSkinAnimMakeDelta(RpSkinAnim * pSkinAnim, RwInt32 numBones,
                    RwReal time)
{
    RpSkinSkeleton     *pSkeleton;
    RwInt32             i;
    RpSkinFrame        *pSkeletonBoneFrame;
    RpSkinFrame        *pSkinAnimBoneFrame;
    RpSkinFrame        *pNextAnimFrame;
    RpSkinFrame        *pSearchFrame;

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

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

    /* Create a skeleton to get the animation at desired delta time */
    pSkeleton = RpSkeletonCreate(numBones);
    if (!pSkeleton)
    {
        RWRETURN(FALSE);
    }

    RpSkinSkeletonSetCurrentAnim(pSkeleton, pSkinAnim);
    RpSkinSkeletonSetCurrentAnimTime(pSkeleton, time);

    /* Start of frame list has a frame for each bone (time 0) */
    pSkeletonBoneFrame = rpSKINSKELETONGETINTERPFRAME(pSkeleton);
    pSkinAnimBoneFrame = pSkinAnim->pFrames;

    /* Track each bone in skeleton with the
     * frame-sequence for that bone in the animation */
    for (i = 0; i < numBones; i++)
    {
        RtQuat              qInverse, qFrame;

        /* Get inverse of skeleton frame */
        RtQuatReciprocal(&qInverse, &pSkeletonBoneFrame->q);

        /* Now change this bones frames in the animation */
        pNextAnimFrame = pSkinAnimBoneFrame;
        while (1)
        {
            /* Transform animation frame by inverse of skeleton frame */
            qFrame = pNextAnimFrame->q;
            RtQuatMultiply(&pNextAnimFrame->q, &qInverse, &qFrame);
            RwV3dSub(&pNextAnimFrame->t, &pNextAnimFrame->t,
                     &pSkeletonBoneFrame->t);

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

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

            pNextAnimFrame = pSearchFrame;
        }

        /* Next bone */
        pSkeletonBoneFrame++;
        pSkinAnimBoneFrame++;
    }

    /* Done */
    RpSkeletonDestroy(pSkeleton);

    RWRETURN(TRUE);
}

/*----------------------------------------------------------------------*
 *                             - ATOMIC -                               *
 *----------------------------------------------------------------------*/

/****************************************************************************
 SkinWrite

 Writes skin 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    *
SkinWrite(RwStream * stream,
          RwInt32 __RWUNUSED__ binaryLength,
          const void *object,
          RwInt32 __RWUNUSED__ offsetInObject,
          RwInt32 __RWUNUSED__ sizeInObject)
{
    const RpSkin       *pSkin;
    RwInt32             i;
    RpSkinBoneInfo     *pBoneInfo;

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

    pSkin = *RPSKINATOMICGETCONSTDATA(object);

    if (pSkin)
    {
        /* Output skin paramters -
         * numBones, totalVertices, vertex weights, vertex matrix indices */
        if (!RwStreamWriteInt
            (stream, (const RwInt32 *) &pSkin->numBones,
             sizeof(RwInt32)))
        {
            RWRETURN((RwStream *) NULL);
        }

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

        if (!RwStreamWriteInt
            (stream, (const RwInt32 *) pSkin->pMatrixIndexMap,
             sizeof(RwUInt32) * pSkin->totalVertices))
        {
            RWRETURN((RwStream *) NULL);
        }

        if (!RwStreamWriteReal
            (stream, (const RwReal *) pSkin->pMatrixWeightsMap,
             sizeof(RwMatrixWeights) * pSkin->totalVertices))
        {
            RWRETURN((RwStream *) NULL);
        }

        /* Write the bone info */

        pBoneInfo = pSkin->pBoneInfo;

        for (i = 0; i < pSkin->numBones; i++)
        {
            if (!RwStreamWriteInt
                (stream, (RwInt32 *) & pBoneInfo->boneTag,
                 sizeof(RwInt32)))
            {
                RWRETURN((RwStream *) NULL);
            }

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

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

            if (!RwStreamWriteReal
                (stream, (RwReal *) & pBoneInfo->boneToSkinMat,
                 sizeof(RwMatrix)))
            {
                RWRETURN((RwStream *) NULL);
            }

            pBoneInfo++;
        }

    }

    RWRETURN(stream);
}

/****************************************************************************
 SkinRead

 Reads an atomic's skin data from a stream

 Inputs :   RwStream *  Stream to read from
            RwInt32     Size of skin 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    *
SkinRead(RwStream * stream,
         RwInt32 __RWUNUSED__ binaryLength,
         void *object,
         RwInt32 __RWUNUSED__ offsetInObject,
         RwInt32 __RWUNUSED__ sizeInObject)
{
    RpSkin             *pSkin;
    RwInt32             i;
    RpSkinBoneInfo     *pBoneInfo;
    RwUInt32            bytes;

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

    pSkin = (RpSkin *) RwFreeListAlloc(RpSkinAtomicGlobals.SkinFreeList);
    if (pSkin)
    {
        memset(pSkin, 0, sizeof(RpSkin));

        /* Input skin paramters -
         * numBones, totalVertices, vertex weights, vertex matrix indices */
        if (!RwStreamReadInt
            (stream, (RwInt32 *) & pSkin->numBones, sizeof(RwInt32)))
        {
            RWRETURN((RwStream *) NULL);
        }

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

        bytes = pSkin->totalVertices * sizeof(RwUInt32);
        pSkin->pMatrixIndexMap = (RwUInt32 *) RwMalloc(bytes);
        memset(pSkin->pMatrixIndexMap, 0, bytes);

        if (!RwStreamReadInt(stream,
                             (RwInt32 *) pSkin->pMatrixIndexMap,
                             sizeof(RwUInt32) * pSkin->totalVertices))
        {
            RWRETURN((RwStream *) NULL);
        }

        bytes = pSkin->totalVertices * sizeof(RwMatrixWeights);
        pSkin->pMatrixWeightsMap = (RwMatrixWeights *) RwMalloc(bytes);
        memset(pSkin->pMatrixWeightsMap, 0, bytes);

        if (!RwStreamReadReal(stream,
                              (RwReal *) pSkin->pMatrixWeightsMap,
                              sizeof(RwMatrixWeights) *
                              pSkin->totalVertices))
        {
            RWRETURN((RwStream *) NULL);
        }

        /* Read the bone info */

        bytes = pSkin->numBones * sizeof(RpSkinBoneInfo) + 15;
        pSkin->pBoneInfoUnaligned = RwMalloc(bytes);
        memset(pSkin->pBoneInfoUnaligned, 0, bytes);

        pBoneInfo = pSkin->pBoneInfo =
            (RpSkinBoneInfo *) ROUNDUP16(pSkin->pBoneInfoUnaligned);

        for (i = 0; i < pSkin->numBones; i++)
        {
            if (!RwStreamReadInt
                (stream, (RwInt32 *) & pBoneInfo->boneTag,
                 sizeof(RwInt32)))
            {
                RWRETURN((RwStream *) NULL);
            }

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

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

            if (!RwStreamReadReal
                (stream, (RwReal *) & pBoneInfo->boneToSkinMat,
                 sizeof(RwMatrix)))
            {
                RWRETURN((RwStream *) NULL);
            }

            pBoneInfo->pFrame = (RwFrame *) NULL;

            pBoneInfo++;
        }

        pSkin->pGeometry = RpAtomicGetGeometry((RpAtomic *) object);
        /* No skeleton initially */
        pSkin->pCurrentSkeleton = (RpSkinSkeleton *) NULL;
        /* And no hierarchy */
        pSkin->pCurrentHierarchy = (RpHAnimHierarchy *) NULL;

    }

    *RPSKINATOMICGETDATA(object) = pSkin;

    _rpSkinMBInitAtomic((RpAtomic *) object);

    RWRETURN(stream);
}

/****************************************************************************
 SkinSize

 Gets the size of an atomic's skin (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
SkinSize(const void *object,
         RwInt32 __RWUNUSED__ offsetInObject,
         RwInt32 __RWUNUSED__ sizeInObject)
{
    RwInt32             size = 0;
    const RpSkin       *pSkin =
        (const RpSkin *) *RPSKINATOMICGETCONSTDATA(object);

    RWFUNCTION(RWSTRING("SkinSize"));

    if (pSkin)
    {
        size = 2 * sizeof(RwInt32);
        size += pSkin->totalVertices * sizeof(RwMatrixWeights);
        size += pSkin->totalVertices * sizeof(RwUInt32);
        size +=
            pSkin->numBones * (sizeof(RwInt32) * 3 + sizeof(RwMatrix));
    }

    RWRETURN(size);
}

/*----------------------------------------------------------------------*
 *                              - ENGINE -                              *
 *----------------------------------------------------------------------*/

static void        *
SkinDestructor(void *object,
               RwInt32 __RWUNUSED__ offset, RwInt32 __RWUNUSED__ size)
{
    RpSkin             *pSkin;

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

    pSkin = *RPSKINATOMICGETDATA(object);

    pSkin = RpSkinDestroy(pSkin);

    *RPSKINATOMICGETDATA(object) = pSkin;

    RWRETURN(object);
}

static void        *
SkinCopy(void *dstObject,
         const void *__RWUNUSED__ srcObject,
         RwInt32 __RWUNUSED__ offset, RwInt32 __RWUNUSED__ size)
{
    const RpAtomic     *pAtomicSrc = (const RpAtomic *) srcObject;
    RpAtomic           *pAtomicDst = (RpAtomic *) dstObject;
    const RpSkin       *pSrcSkin;
    RpSkin             *pDstSkin;
    RxPipeline         *pPipeline;
    RwInt32             i;
    RwUInt32            bytes;

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

    if (*RPSKINATOMICGETCONSTDATA(pAtomicSrc))
    {
        pSrcSkin = *RPSKINATOMICGETCONSTDATA(pAtomicSrc);
        pDstSkin = (RpSkin *) RwFreeListAlloc(RpSkinAtomicGlobals.SkinFreeList);
        memset(pDstSkin, 0, sizeof(RpSkin));
        *RPSKINATOMICGETDATA(pAtomicDst) = pDstSkin;

        RpSkinAssign(pDstSkin, pSrcSkin);

        /* Need to duplicate boneinfo because of pFrame pointers */
        bytes = pDstSkin->numBones * sizeof(RpSkinBoneInfo) + 15;
        pDstSkin->pBoneInfoUnaligned =
            (RpSkinBoneInfo *) RwMalloc(bytes);
        memset(pDstSkin->pBoneInfoUnaligned, 0, bytes);

        pDstSkin->pBoneInfo = (RpSkinBoneInfo *)
            ROUNDUP16(pDstSkin->pBoneInfoUnaligned);

        memcpy(pDstSkin->pBoneInfo, pSrcSkin->pBoneInfo,
               pDstSkin->numBones * sizeof(RpSkinBoneInfo));
        for (i = 0; i < pDstSkin->numBones; i++)
        {
            pDstSkin->pBoneInfo[i].pFrame = (RwFrame *) NULL;
        }

        pDstSkin->pCurrentSkeleton = (RpSkinSkeleton *) NULL;

        RpAtomicGetInstancePipeline(pAtomicSrc, &pPipeline);

        RpAtomicSetInstancePipeline(pAtomicDst, pPipeline);
    }
    else
    {
        *RPSKINATOMICGETDATA(pAtomicDst) = (RpSkin *) NULL;
    }

    RWRETURN(dstObject);
}

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

    *RPSKINATOMICGETDATA(object) = (RpSkin *) NULL;

    RWRETURN(object);
}

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

    _rpSkinMBPipelineDestroy();

    if (RpSkinAtomicGlobals.SkinFreeList)
    {
        RwFreeListDestroy(RpSkinAtomicGlobals.SkinFreeList);
        RpSkinAtomicGlobals.SkinFreeList = (RwFreeList *) NULL;
    }

    if (RpSkinAtomicGlobals.SkinMatrixCacheUnaligned)
    {
        RwFree(RpSkinAtomicGlobals.SkinMatrixCacheUnaligned);
        RpSkinAtomicGlobals.SkinMatrixCacheUnaligned = NULL;
        RpSkinAtomicGlobals.SkinMatrixCache = (RwMatrix *) NULL;
    }

    RWRETURN(instance);
}

static void        *
SkinOpen(void *instance,
         RwInt32 __RWUNUSED__ offset, RwInt32 __RWUNUSED__ size)
{

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

    if (FALSE == _rpSkinMBPluginAttach())
    {
        instance = NULL;
    }
    else
    {
        RwUInt32            bytes;

        RpSkinAtomicGlobals.SkinFreeList =
            RwFreeListCreate(sizeof(RpSkin), 20, 0);

        if (RpSkinAtomicGlobals.SkinFreeList == NULL)
        {
            instance = NULL;
            RWRETURN(instance);
        }

#if (defined(_R5900_))
        /* PS2 has a limit of 64 matrices */
        bytes = (sizeof(RwMatrix) * 64) + 15;
#else
        /* Generic implementation is 256 */
        bytes = (sizeof(RwMatrix) * 256) + 15;
#endif
        RpSkinAtomicGlobals.SkinMatrixCacheUnaligned = RwMalloc(bytes);
        memset(RpSkinAtomicGlobals.SkinMatrixCacheUnaligned, 0, bytes);

        if (RpSkinAtomicGlobals.SkinMatrixCacheUnaligned == NULL)
        {
            RwFreeListDestroy(RpSkinAtomicGlobals.SkinFreeList);
            RpSkinAtomicGlobals.SkinFreeList = (RwFreeList *) NULL;

            instance = NULL;
            RWRETURN(instance);
        }

        RpSkinAtomicGlobals.SkinMatrixCache = (RwMatrix *)
            ROUNDUP16(RpSkinAtomicGlobals.SkinMatrixCacheUnaligned);

        RpSkinInvalidateMatrixCache();
    }

    RWRETURN(instance);
}

/**
 * \ingroup rpskinp2
 * \ref RpSkinPluginAttach is used to attach the skin plugin to the
 * RenderWare system to enable the manipulation of skinned atomics.
 * The plugin must be attached between initializing the system with
 * RwEngineInit and opening it with RwEngineOpen.
 *
 * Note that the skin plugin requires the world plugin to be attached.
 * The include file rpskin.h is also required and must be included by
 * an application wishing to use this plugin.
 *
 * \return TRUE if successful, FALSE if an error occurs.
 *
 */

RwBool
RpSkinPluginAttach(void)
{
    RwInt32 success;
    _rpSkinLinkCallBack *skinLinkCallBack;

    RWAPIFUNCTION(RWSTRING("RpSkinPluginAttach"));

    /* Register the plugIn */
    RpSkinAtomicGlobals.engineOffset =
        RwEngineRegisterPlugin(sizeof(_rpSkinLinkCallBack),
                               rwID_SKINPLUGIN,
                               SkinOpen,
                               SkinClose);
    if(RpSkinAtomicGlobals.engineOffset < 0)
    {
        RWRETURN(FALSE);
    }

    /* Set the Skin plugin link function. */
    skinLinkCallBack = RPSKINENGINEGETDATA(RwEngineInstance);
    RWASSERT(NULL != skinLinkCallBack);
    skinLinkCallBack->skinLinkPopulateCallBack = _rpSkinLinkPopulate;

    /* Extend atomic to hold a skin */
    RpSkinAtomicGlobals.atomicOffset =
        RpAtomicRegisterPlugin(sizeof(RpSkin *),
                               rwID_SKINPLUGIN,
                               SkinConstructor,
                               SkinDestructor,
                               SkinCopy);

    if(RpSkinAtomicGlobals.atomicOffset < 0)
    {
        RWRETURN(FALSE);
    }

    /* Attach the stream handling functions */
    success = RpAtomicRegisterPluginStream(rwID_SKINPLUGIN,
                                           SkinRead,
                                           SkinWrite,
                                           SkinSize);
    if(success < 0)
    {
        RWRETURN(FALSE);
    }

    RWRETURN(TRUE);
}

/*
 * \ingroup rpskinp2
 * \ref RpSkinInitPipeline Initializes the skinning render pipeline.
 * This function must be called after RwEngineOpen() and before the
 * first skinned atomic is to be rendered.
 *
 * \return TRUE if successful, FALSE if an error occurs.
 */
RwBool
RpSkinInitPipeline(void)
{
    RWAPIFUNCTION(RWSTRING("RpSkinInitPipeline"));

    RWERROR((E_RW_REDUNDANT_FUNCTION));

    RWRETURN(TRUE);
}

/**
 * \ingroup rpskinp2
 * \ref RpSkinInvalidateMatrixCache is used to invalidate the cache of
 * skinning matrices maintained by the plugin. In most cases this should
 * not need to be called since the cache is held for a particular RpSkin
 * on a particular render frame. If however you wish to render and skin,
 * followed by either updating it's matrices or rendering other non
 * skinned data and then render it again within the same
 * RwCameraBeginUpdate/RwCameraEndUpdate block you must call this before
 * the 2nd render.
 *
 * \return TRUE if successful, FALSE if an error occurs.
 *
 */

RwBool
RpSkinInvalidateMatrixCache(void)
{
    RWAPIFUNCTION(RWSTRING("RpSkinInvalidateMatrixCache"));

    RpSkinAtomicGlobals.SkinMatrixCacheSkin = (RpSkin *) NULL;
    RpSkinAtomicGlobals.SkinMatrixCacheRenderFrame =
        RWSRCGLOBAL(renderFrame) - 1;

    RWRETURN(TRUE);
}
