/*===========================================================================*
 *--- Include files ---------------------------------------------------------*
 *===========================================================================*/
#include "rwcore.h"
#include "rpworld.h"

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

#include "rphanim.h"
#include "rpskin.h"

#include "skin.h"

/*===========================================================================*
 *--- Private Types ---------------------------------------------------------*
 *===========================================================================*/

/*===========================================================================*
 *--- Private Global Variables ----------------------------------------------*
 *===========================================================================*/

/*===========================================================================*
 *--- Private Defines -------------------------------------------------------*
 *===========================================================================*/

/*===========================================================================*
 *--- Local Types -----------------------------------------------------------*
 *===========================================================================*/

/*===========================================================================*
 *--- Local Global Variables ------------------------------------------------*
 *===========================================================================*/
#if (defined(RWDEBUG))
long _rpSkinStackDepth = 0;
#endif /* (defined(RWDEBUG)) */

#if (!defined(DXOYGEN))
static const char rcsid[] __RWUNUSED__ =
    "@@@@(#)$Id: ";
#endif /* (!defined(DXOYGEN)) */

/*===========================================================================*
 *--- Local Defines ---------------------------------------------------------*
 *===========================================================================*/
#define rpSKINENTRIESPERBLOCK 20
#define rpSKINALIGNMENT 0

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

#define CHECKSTREAMANDRETURN(success) \
MACRO_START                           \
{                                     \
    if(NULL == (success))             \
    {                                 \
        RWRETURN((RwStream *)NULL);   \
    }                                 \
}                                     \
MACRO_STOP

/*===========================================================================*
 *--- Local functions -------------------------------------------------------*
 *===========================================================================*/
static RpSkin *
SkinCreateSkinData( RpSkin *skin, RwUInt32 numBones, RwUInt32 numVertices )
{
    RwUInt32 size;

    RWFUNCTION(RWSTRING("SkinCreateSkinData"));
    RWASSERT(NULL != skin);
    RWASSERT(0 < numBones);

    /* Get some memory. */
    size = (numVertices * (sizeof(RwUInt32) + sizeof(RwMatrixWeights))) +
           (numBones * sizeof(SkinBone)) + 15;
    skin->boneData.unaligned = RwMalloc(size);
    RWASSERT(NULL != skin->boneData.unaligned);

    /* Clean the memory. */
    memset(skin->boneData.unaligned, 0, size);
    skin->boneData.aligned = (SkinBone *)ROUNDUP16(skin->boneData.unaligned);

    /* Store the number of bones. */
    skin->boneData.numBones = numBones;

    skin->boneData.invBoneToSkinMat = (RwMatrix *)RwMalloc(sizeof(RwMatrix) * numBones);

    /* Setup the vertex maps. */
    skin->vertexMaps.matrixIndices =
        (RwUInt32 *)(skin->boneData.aligned + numBones);
    skin->vertexMaps.matrixWeights =
        (RwMatrixWeights *)(skin->vertexMaps.matrixIndices + numVertices);

    RWRETURN(skin);
}

static RpSkin *
SkinDestorySkinData( RpSkin *skin )
{
    RWFUNCTION(RWSTRING("SkinDestorySkinData"));
    RWASSERT(NULL != skin);

    if (skin->boneData.invBoneToSkinMat)
    {
        RwFree( skin->boneData.invBoneToSkinMat );
    }
    
    skin->boneData.invBoneToSkinMat = NULL;
 
    if(skin->boneData.unaligned)
    {
        RwFree(skin->boneData.unaligned);
    }
 
    skin->boneData.unaligned = NULL;
    skin->boneData.aligned = (SkinBone *)NULL;
    skin->boneData.numBones = 0;

    skin->vertexMaps.matrixIndices = (RwUInt32 *)NULL;
    skin->vertexMaps.matrixWeights = (RwMatrixWeights *)NULL;

    RWRETURN(skin);
}

static RpSkin *
SkinCreate( RwUInt32 numVertices,
            RwUInt32 numBones,
            RwUInt32 *boneTags,
            RwMatrixWeights *vertexWeights,
            RwUInt32 *vertexIndices,
            RwMatrix *inverseMatrices )
{
    RpSkin *skin;

    RWFUNCTION(RWSTRING("SkinCreate"));
    RWASSERT(0 < numVertices);
    RWASSERT(0 < numBones);

    /* Get a new skin. */
    skin = (RpSkin *)RwFreeListAlloc(_rpSkinGlobals.freeList);
    RWASSERT(NULL != skin);

    /* Clean the skin. */
    memset(skin, 0, sizeof(RpSkin));

    /* Setup the skin's bones. */
    SkinCreateSkinData(skin, numBones, numVertices);
    RWASSERT(NULL != skin->boneData.aligned);
    RWASSERT(NULL != skin->boneData.invBoneToSkinMat);
    RWASSERT(NULL != skin->vertexMaps.matrixIndices);
    RWASSERT(NULL != skin->vertexMaps.matrixWeights);

    /* Set up initial boneToSkin matrices */
    if(NULL != inverseMatrices)
    {
        RwUInt32 iBone;

        for( iBone = 0; iBone < skin->boneData.numBones; iBone++ )
        {
            SkinBone *skinBone;

            skinBone = &(skin->boneData.aligned[iBone]);

            skinBone->boneTag = iBone;
            
            RwMatrixCopy( &(skin->boneData.invBoneToSkinMat[iBone]),
                          &(inverseMatrices[iBone]) );            
        }
    }

    /* Setup the skin's vertex maps. */
    if(NULL != vertexIndices)
    {
        RwUInt32 size;

        size = sizeof(RwUInt32) * numVertices;

        RWASSERT(NULL != skin->vertexMaps.matrixIndices);
        memcpy(skin->vertexMaps.matrixIndices, vertexIndices, size);
    }

    if(NULL != vertexWeights)
    {
        RwUInt32 size;

        size = sizeof(RwMatrixWeights) * numVertices;

        RWASSERT(NULL != skin->vertexMaps.matrixWeights);
        memcpy(skin->vertexMaps.matrixWeights, vertexWeights, size);
    }

    /* Set up initial bone tags */
    if(NULL != boneTags)
    {
        RwUInt32 iBone;

        for( iBone = 0; iBone < skin->boneData.numBones; iBone++ )
        {
            SkinBone *skinBone;

            skinBone = &(skin->boneData.aligned[iBone]);

            skinBone->boneTag = iBone;
        }
    }

    RWRETURN(skin);
}

/*===========================================================================*
 *--- Private Functions -----------------------------------------------------*
 *===========================================================================*/

/*===========================================================================*
 *--- Plugin Engine Functions -----------------------------------------------*
 *===========================================================================*/
static void *
SkinOpen( void *instance,
          RwInt32 offset __RWUNUSED__,
          RwInt32 size   __RWUNUSED__ )
{
    RWFUNCTION(RWSTRING("SkinOpen"));
    RWASSERT(NULL != instance);

    /* Create the skinning pipelines, only once. */
    if(_rpSkinGlobals.module.numInstances == 0)
    {
        RwUInt32 pipes;
        RwBool success;
        RwUInt32 size;

        /* Assume we always want the generic pipe. */
        pipes = rpSKINPIPELINESKINGENERIC;

        /* Is the matfx plugin attached? */
        offset = RwEngineGetPluginOffset(rwID_MATERIALEFFECTSPLUGIN);
        if(-1 != offset)
        {
            pipes |= rpSKINPIPELINESKINMATFX;
        }

        /* Set up the pipeline. */
        success = _rpSkinPipelinesCreate(pipes);
        RWASSERT(FALSE != success);

        /* Create a RpSkin freelist. */
        _rpSkinGlobals.freeList = RwFreeListCreate( sizeof(RpSkin),
                                                    rpSKINENTRIESPERBLOCK,
                                                    rpSKINALIGNMENT );
        RWASSERT(NULL != _rpSkinGlobals.freeList);

        /* Create the skinning matrix cache. */
        size = (sizeof(RwMatrix) * rpSKINMAXNUMBEROFMATRICES) + 15;

        _rpSkinGlobals.matrixCache.unaligned = RwMalloc(size);
        RWASSERT(NULL != _rpSkinGlobals.matrixCache.unaligned);

        /* Clean the memory. */
        memset(_rpSkinGlobals.matrixCache.unaligned, 0, size);

        _rpSkinGlobals.matrixCache.aligned = (RwMatrix *)
            ROUNDUP16(_rpSkinGlobals.matrixCache.unaligned);
    }

    /* Another instance. */
    _rpSkinGlobals.module.numInstances++;

    RWRETURN(instance);
}


static void *
SkinClose( void *instance,
           RwInt32 offset __RWUNUSED__,
           RwInt32 size   __RWUNUSED__ )
{
    RWFUNCTION(RWSTRING("SkinClose"));
    RWASSERT(NULL != instance);
    RWASSERT(0 < _rpSkinGlobals.module.numInstances);

    /* One less module instance. */
    _rpSkinGlobals.module.numInstances--;

    if(0 == _rpSkinGlobals.module.numInstances)
    {
        RwBool success;

        /* Destroy the skinning pipelines. */
        success = _rpSkinPipelinesDestroy();
        RWASSERT(FALSE != success);

        /* Destory the skin free list. */
        RWASSERT(NULL != _rpSkinGlobals.freeList);
        RwFreeListDestroy(_rpSkinGlobals.freeList);
        _rpSkinGlobals.freeList = (RwFreeList *)NULL;

        /* Destory the matrix cache. */
        RWASSERT(NULL != _rpSkinGlobals.matrixCache.unaligned);
        RwFree(_rpSkinGlobals.matrixCache.unaligned);
        _rpSkinGlobals.matrixCache.unaligned = NULL;
    }

    RWRETURN(instance);
}


static void *
SkinGeometryConstructor( void *object,
                         RwInt32 offset __RWUNUSED__,
                         RwInt32 size   __RWUNUSED__ )
{
    RWFUNCTION(RWSTRING("SkinGeometryConstructor"));
    RWASSERT(NULL != object);

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

    RWRETURN(object);
}


static void *
SkinGeometryDestructor( void *object,
                        RwInt32 offset __RWUNUSED__,
                        RwInt32 size   __RWUNUSED__ )
{
    RpGeometry *geometry;
    RpSkin *skin;

    RWFUNCTION(RWSTRING("SkinGeometryDestructor"));
    RWASSERT(NULL != object);

    /* Get the skin data from the geometry extension . */
    geometry = (RpGeometry *)object;
    skin = *RPSKINGEOMETRYGETDATA(geometry);

    if(NULL != skin)
    {
        /* Give the platforms a chance to destory the skin. */
        _rpSkinDeinitialize(geometry);

        /* Destory the skin, and remove the extension. */
        *RPSKINGEOMETRYGETDATA(geometry) = RpSkinDestroy(skin);
    }

    RWASSERT(NULL == *RPSKINGEOMETRYGETDATA(object));
    RWRETURN(object);
}


static void *
SkinGeometryCopy( void *dstObject,
                  const void *srcObject,
                  RwInt32 offset __RWUNUSED__,
                  RwInt32 size   __RWUNUSED__ )
{
    const RpGeometry *srcGeometry;
    const RpSkin *srcSkin;

    RpGeometry *dstGeometry;
    RpSkin *dstSkin;

    RWFUNCTION(RWSTRING("SkinGeometryCopy"));
    RWASSERT(NULL != srcObject);
    RWASSERT(NULL != dstObject);

    /* Get the geometry from the objects. */
    srcGeometry = (const RpGeometry *)srcObject;
    RWASSERT(NULL != srcGeometry);
    dstGeometry = (RpGeometry *)dstObject;
    RWASSERT(NULL != dstGeometry);

    /* Get and setup the skins on the atomics. */
    srcSkin = *RPSKINGEOMETRYGETCONSTDATA(srcGeometry);

    if(NULL != srcSkin)
    {
        RwUInt32 iBone;
        RwUInt32 numVertices;

        numVertices = RpGeometryGetNumVertices(srcGeometry);
        RWASSERT(0 < numVertices);

        dstSkin = RpSkinCreate( numVertices,
                                srcSkin->boneData.numBones,
                                (RwUInt32 *)NULL,
                                srcSkin->vertexMaps.matrixWeights,
                                srcSkin->vertexMaps.matrixIndices,
                                (RwMatrix *)NULL );
        RWASSERT(NULL != dstSkin);
        RWASSERT(NULL != dstSkin->boneData.aligned);
        RWASSERT(NULL != dstSkin->boneData.invBoneToSkinMat);
        RWASSERT(NULL != dstSkin->vertexMaps.matrixWeights);
        RWASSERT(NULL != dstSkin->vertexMaps.matrixIndices);
        RWASSERT(dstSkin->boneData.numBones == srcSkin->boneData.numBones);

        for(iBone = 0; iBone < dstSkin->boneData.numBones; iBone++)
        {
            SkinBone *srcBone;
            SkinBone *dstBone;

            /* Grab the source and destination bones. */
            srcBone = &(srcSkin->boneData.aligned[iBone]);
            dstBone = &(dstSkin->boneData.aligned[iBone]);

            /* Copy them. */
            dstBone->boneTag = srcBone->boneTag;
            
            RwMatrixCopy( &(dstSkin->boneData.invBoneToSkinMat[iBone]),
                          &(srcSkin->boneData.invBoneToSkinMat[iBone]) );
        }

        dstGeometry = RpSkinGeometrySetSkin(dstGeometry, dstSkin);
        RWASSERT(NULL != dstGeometry);
    }

    RWRETURN(dstObject);
}


static void *
SkinAtomicConstructor( void *object,
                       RwInt32 offset __RWUNUSED__,
                       RwInt32 size   __RWUNUSED__ )
{
    RpAtomic *atomic;
    SkinAtomicData *atomicData;

    RWFUNCTION(RWSTRING("SkinAtomicConstructor"));
    RWASSERT(NULL != object);

    /* Get the atomic and it's extension data. */
    atomic = (RpAtomic *)object;
    atomicData = RPSKINATOMICGETDATA(atomic);

    atomicData->hierarchy = (RpHAnimHierarchy *)NULL;

    RWRETURN(object);
}


static void *
SkinAtomicDestructor( void *object,
                      RwInt32 offset __RWUNUSED__,
                      RwInt32 size   __RWUNUSED__ )
{
    RpAtomic *atomic;
    SkinAtomicData *atomicData;

    RWFUNCTION(RWSTRING("SkinAtomicDestructor"));
    RWASSERT(NULL != object);

    /* Get the atomic and it's extension data. */
    atomic = (RpAtomic *)object;
    atomicData = RPSKINATOMICGETDATA(atomic);

    if(NULL != atomicData->hierarchy)
    {
        atomicData->hierarchy = (RpHAnimHierarchy *)NULL;
    }

    RWRETURN(object);
}


static void *
SkinAtomicCopy( void *dstObject,
                const void *srcObject,
                RwInt32 offset __RWUNUSED__,
                RwInt32 size   __RWUNUSED__ )
{
    const RpAtomic *srcAtomic;
    const SkinAtomicData *srcAtomicData;

    RpAtomic *dstAtomic;
    SkinAtomicData *dstAtomicData;

    RWFUNCTION(RWSTRING("SkinAtomicCopy"));
    RWASSERT(NULL != srcObject);
    RWASSERT(NULL != dstObject);

    /* Get the atomic from the objects. */
    srcAtomic = (const RpAtomic *)srcObject;
    RWASSERT(NULL != srcAtomic);
    dstAtomic = (RpAtomic *)dstObject;
    RWASSERT(NULL != dstAtomic);

    /* Get and setup the atomic extension. */
    srcAtomicData = RPSKINATOMICGETCONSTDATA(srcAtomic);
    RWASSERT(NULL != srcAtomicData);
    dstAtomicData = RPSKINATOMICGETDATA(dstAtomic);
    RWASSERT(NULL != dstAtomicData);


    /* Copy the present hierarchy. */
    dstAtomicData->hierarchy = srcAtomicData->hierarchy;

    /*
     * Note: no need to set the correct pipeline as this should be
     * done for us (as it's a property of the atomic not the plugin
     * extension.
     */

    RWRETURN(dstObject);
}

static RwBool
SkinAtomicAlways( void *object,
                  RwInt32 offset __RWUNUSED__,
                  RwInt32 size   __RWUNUSED__ )
{
    RpAtomic *atomic;
    RpGeometry *geometry;

    RWFUNCTION(RWSTRING("SkinAtomicAlways"));
    RWASSERT(NULL != object);

    /* Grab the atomic, geometry and skin. */
    atomic = (RpAtomic *)object;
    RWASSERT(NULL != atomic);
    geometry = RpAtomicGetGeometry(atomic);

    if(NULL != geometry)
    {
        RpSkin *skin;

        skin = RpSkinGeometryGetSkin(geometry);

        if(NULL != skin)
        {
            RpAtomic *success;

            success = _rpSkinPipelinesAttach(atomic);
            RWASSERT(NULL != success);
        }
    }

    RWRETURN(TRUE);
}


static RwInt32
SkinGeometrySize( const void *object,
                  RwInt32 offset __RWUNUSED__,
                  RwInt32 bytes  __RWUNUSED__ )
{
    const RpGeometry *geometry;
    const RpSkin *skin;

    RwInt32 size;

    RWFUNCTION(RWSTRING("SkinGeometrySize"));
    RWASSERT(NULL != object);

    /* Get the geometry and the skin. */
    geometry = (const RpGeometry *)object;
    RWASSERT(NULL != geometry);
    skin = *RPSKINGEOMETRYGETCONSTDATA(geometry);

    /* Reset size. */
    size = 0;

    if(NULL != skin)
    {
        RwUInt32 totalVertices;

        /* Num of vertices. */
        totalVertices = RpGeometryGetNumVertices(geometry);

        /* Num bones. */
        size = sizeof(RwUInt32);

        /* Matrix Indices. */
        size += totalVertices * sizeof(RwUInt32);

        /* Matrix Weights. */
        size += totalVertices * sizeof(RwMatrixWeights);

        /* SkinBone data. */
        size += skin->boneData.numBones * sizeof(SkinBone);
    }

    RWRETURN(size);
}


static RwStream *
SkinGeometryWrite( RwStream *stream,
                   RwInt32 binaryLength   __RWUNUSED__,
                   const void *object,
                   RwInt32 offsetInObject __RWUNUSED__,
                   RwInt32 sizeInObject   __RWUNUSED__ )
{
    const RpGeometry *geometry;
    const RpSkin *skin;

    RWFUNCTION(RWSTRING("SkinGeometryWrite"));
    RWASSERT(NULL != stream);
    RWASSERT(NULL != object);

    /* Get the geometry and skin. */
    geometry = (const RpGeometry *)object;
    RWASSERT(NULL != geometry);
    skin = *RPSKINGEOMETRYGETCONSTDATA(geometry);

    if(NULL != skin)
    {
        RwUInt32 totalVertices;
        RwStream *success;
        RwUInt32 iBone;

        /* Output skin paramters. */

        /* Num of vertices. */
        totalVertices = RpGeometryGetNumVertices(geometry);

        /* Num bones. */
        success = RwStreamWriteInt32( stream,
                      (const RwInt32 *)&(skin->boneData.numBones),
                      sizeof(RwUInt32) );
        CHECKSTREAMANDRETURN(success);

        /* Vertex indices. */
        success = RwStreamWriteInt32( stream,
                      (const RwInt32 *)(skin->vertexMaps.matrixIndices),
                      sizeof(RwUInt32) * totalVertices );
        CHECKSTREAMANDRETURN(success);

        /* Vertex weights. */
        success = RwStreamWriteReal( stream,
                      (const RwReal *)(skin->vertexMaps.matrixWeights),
                      sizeof(RwMatrixWeights) * totalVertices );
        CHECKSTREAMANDRETURN(success);

        /* Bone info. */
        for( iBone = 0; iBone < skin->boneData.numBones; iBone++ )
        {
            SkinBone *bone;

            bone = &(skin->boneData.aligned[iBone]);

            success = RwStreamWriteInt32( stream,
                          (const RwInt32 *)&(bone->boneTag),
                          sizeof(RwUInt32) );
            CHECKSTREAMANDRETURN(success);

            success = RwStreamWriteReal( stream,
                          (const RwReal *)&(skin->boneData.invBoneToSkinMat[iBone]),
                          sizeof(RwMatrix) );
            CHECKSTREAMANDRETURN(success);
        }
    }

    RWRETURN(stream);
}


static RwStream *
SkinGeometryRead( RwStream *stream,
                  RwInt32 binaryLength   __RWUNUSED__,
                  void *object,
                  RwInt32 offsetInObject __RWUNUSED__,
                  RwInt32 sizeInObject   __RWUNUSED__ )
{
    RpGeometry *geometry;
    RpSkin *skin;
    RwUInt32 numBones;
    RwUInt32 totalVertices;
    RwUInt32 iBone;
    RwStream *success;

    RWFUNCTION(RWSTRING("SkinGeometryRead"));
    RWASSERT(NULL != stream);
    RWASSERT(NULL != object);

    /* Get the geometry. */
    geometry = (RpGeometry *)object;
    RWASSERT(NULL != geometry);

    /* Lets read the number of bones. */
    success = RwStreamReadInt32( stream,
                                 (RwInt32 *)&numBones,
                                 sizeof(RwInt32));
    CHECKSTREAMANDRETURN(success);

    /* Get the number of vertices. */
    totalVertices = RpGeometryGetNumVertices(geometry);

    skin = SkinCreate( totalVertices,
                       numBones,
                       (RwUInt32 *)NULL,
                       (RwMatrixWeights *)NULL,
                       (RwUInt32 *)NULL,
                       (RwMatrix *)NULL );
    RWASSERT(NULL != skin);

    /* Vertex indices. */
    success = RwStreamReadInt32( stream,
                                 (RwInt32 *)skin->vertexMaps.matrixIndices,
                                 sizeof(RwUInt32) * totalVertices );
    CHECKSTREAMANDRETURN(success);

    /* Vertex weights. */
    success = RwStreamReadReal( stream,
                                (RwReal *)skin->vertexMaps.matrixWeights,
                                sizeof(RwMatrixWeights) * totalVertices );
    CHECKSTREAMANDRETURN(success);

    /* Bone info. */
    for( iBone = 0; iBone < skin->boneData.numBones; iBone++ )
    {
        SkinBone *bone;

        bone = &(skin->boneData.aligned[iBone]);

        success = RwStreamReadInt32( stream,
                                     (RwInt32 *)&(bone->boneTag),
                                     sizeof(RwUInt32) );
        CHECKSTREAMANDRETURN(success);

        success = RwStreamReadReal( stream,
                                    (RwReal *)&(skin->boneData.invBoneToSkinMat[iBone]),
                                    sizeof(RwMatrix) );
        CHECKSTREAMANDRETURN(success);
    }

    geometry = RpSkinGeometrySetSkin(geometry, skin);
    RWASSERT(NULL != geometry);

    RWRETURN(stream);
}


static RwStream *
SkinAtomicRead( RwStream *stream,
                RwInt32 binaryLength   __RWUNUSED__,
                void *object,
                RwInt32 offsetInObject __RWUNUSED__,
                RwInt32 sizeInObject )
{
    RpAtomic *atomic;
    RpGeometry *geometry;
    RpSkin *skin;
    RwStream *success;

    RWFUNCTION(RWSTRING("SkinAtomicRead"));
    RWASSERT(NULL != stream);
    RWASSERT(NULL != object);

    /*
     * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
     *  This function remains to allow backwards
     *  compatibility with old school skinned atomics.
     *  In time it should be removed.
     * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
     */

    atomic = (RpAtomic *)object;
    RWASSERT(NULL != atomic);

    geometry = RpAtomicGetGeometry(atomic);
    RWASSERT(NULL != geometry);

    skin = RpSkinGeometryGetSkin(geometry);
    if(NULL == skin)
    {
        RwUInt32 numBones;
        RwUInt32 numVertices;
        RwUInt32 iBone;

        /* Need to load the skin. */

        success = RwStreamReadInt( stream,
                                   (RwInt32 *)&numBones,
                                   sizeof(RwInt32));
        CHECKSTREAMANDRETURN(success);

        numVertices = RpGeometryGetNumVertices(geometry);
        RWASSERT(0 < numVertices);

        skin = SkinCreate( numVertices,
                           numBones,
                           (RwUInt32 *)NULL,
                           (RwMatrixWeights *)NULL,
                           (RwUInt32 *)NULL,
                           (RwMatrix *)NULL );
        RWASSERT(NULL != skin);

        success = RwStreamSkip(stream, sizeof(RwInt32));
        CHECKSTREAMANDRETURN(success);


        success = RwStreamReadInt( stream,
                                   (RwInt32 *)(skin->vertexMaps.matrixIndices),
                                   sizeof(RwUInt32) * numVertices);
        CHECKSTREAMANDRETURN(success);

        success = RwStreamReadReal( stream,
                                    (RwReal *)(skin->vertexMaps.matrixWeights),
                                    sizeof(RwMatrixWeights) * numVertices );
        CHECKSTREAMANDRETURN(success);

        /* Read the bone info */
        for( iBone = 0; iBone < skin->boneData.numBones; iBone++ )
        {
            SkinBone *bone;

            bone = &(skin->boneData.aligned[iBone]);

            success = RwStreamReadInt( stream,
                                       (RwInt32 *)&(bone->boneTag),
                                       sizeof(RwInt32) );
            CHECKSTREAMANDRETURN(success);

            success = RwStreamSkip(stream, sizeof(RwInt32) + sizeof(RwInt32));
            CHECKSTREAMANDRETURN(success);

            success = RwStreamReadReal( stream,
                                        (RwReal *)&(skin->boneData.invBoneToSkinMat[iBone]),
                                        sizeof(RwMatrix) );
            CHECKSTREAMANDRETURN(success);
        }

        geometry = RpSkinGeometrySetSkin(geometry, skin);
        RWASSERT(NULL != geometry);
    }
    else
    {
        success = RwStreamSkip(stream, sizeInObject);
        CHECKSTREAMANDRETURN(success);
    }

    RWRETURN(stream);
}

static RwStream *
SkinAtomicWrite( RwStream *stream,
                 RwInt32 binaryLength   __RWUNUSED__,
                 const void *object     __RWUNUSED__,
                 RwInt32 offsetInObject __RWUNUSED__,
                 RwInt32 sizeInObject   __RWUNUSED__ )
{
    RWFUNCTION(RWSTRING("SkinAtomicWrite"));
    RWRETURN(stream);
}

static RwInt32
SkinAtomicGetSize( const void *object     __RWUNUSED__,
                   RwInt32 offsetInObject __RWUNUSED__,
                   RwInt32 sizeInObject   __RWUNUSED__ )
{
    RWFUNCTION(RWSTRING("SkinAtomicGetSize"));
    RWRETURN((RwInt32)0);
}

/*===========================================================================*
 *--- Plugin API Functions --------------------------------------------------*
 *===========================================================================*/

/**
 * \ingroup rpskin
 * \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;

    RWAPIFUNCTION(RWSTRING("RpSkinPluginAttach"));

    /* Register the plugIn */
    _rpSkinGlobals.engineOffset =
        RwEngineRegisterPlugin( 0,
                                rwID_SKINPLUGIN,
                                SkinOpen,
                                SkinClose );
    RWASSERT(0 < _rpSkinGlobals.engineOffset);

    /* Extend atomic to hold skin atomic data. */
    _rpSkinGlobals.atomicOffset =
        RpAtomicRegisterPlugin( sizeof(RpSkin *),
                                rwID_SKINPLUGIN,
                                SkinAtomicConstructor,
                                SkinAtomicDestructor,
                                SkinAtomicCopy );
    RWASSERT(0 < _rpSkinGlobals.atomicOffset);

    /* Attach the stream handling functions. */
    success = RpAtomicRegisterPluginStream( rwID_SKINPLUGIN,
                                            SkinAtomicRead,
                                            SkinAtomicWrite,
                                            SkinAtomicGetSize );
    RWASSERT(0 < success);

    /* Attach an always callback streaming function. */
    success = RpAtomicSetStreamAlwaysCallBack( rwID_SKINPLUGIN,
                                               SkinAtomicAlways );
    RWASSERT(0 < success);

    /* Attach a rights callback streaming function. */
    success = RpAtomicSetStreamRightsCallBack( rwID_SKINPLUGIN,
                                               SkinAtomicAlways );
    RWASSERT(0 < success);

    /* Extend geometry to hold skin geometry data. */
    _rpSkinGlobals.geometryOffset =
        RpGeometryRegisterPlugin( sizeof(RpSkin *),
                                  rwID_SKINPLUGIN,
                                  SkinGeometryConstructor,
                                  SkinGeometryDestructor,
                                  SkinGeometryCopy );
    RWASSERT(0 < _rpSkinGlobals.geometryOffset);

    /* Attach the stream handling functions */
    success = RpGeometryRegisterPluginStream( rwID_SKINPLUGIN,
                                              SkinGeometryRead,
                                              SkinGeometryWrite,
                                              SkinGeometrySize );
    RWASSERT(0 < success);

    RWRETURN(TRUE);
}

/**
 * \ingroup rpskin
 * \ref RpSkinAtomicSetHAnimHierarchy sets the HAnimHierarchy used to play back
 * an instance of an animation on a skinned atomic.
 *
 * \param atomic    A pointer to the skinned atomic for which the hierarchy
 *                  is to be used.
 * \param hierarchy A pointer to the HAnimHierarchy use for animating the
 *                  skinned atomic.
 *
 * \return the atomic on success, NULL if an error occurs.
 *
 * \see RpSkinAtomicGetHAnimHierarchy
 */
RpAtomic *
RpSkinAtomicSetHAnimHierarchy( RpAtomic *atomic,
                               RpHAnimHierarchy *hierarchy )
{
    SkinAtomicData *atomicData;

    RWAPIFUNCTION(RWSTRING("RpSkinAtomicSetHAnimHierarchy"));
    RWASSERT(0 < _rpSkinGlobals.module.numInstances);
    RWASSERT(NULL != atomic);
    RWASSERT(hierarchy->numNodes <= rpSKINMAXNUMBEROFMATRICES);

    atomicData = RPSKINATOMICGETDATA(atomic);
    RWASSERT(NULL != atomicData);

    atomicData->hierarchy = hierarchy;

    /* TODO REMOVE - Hack until we have a better way. */
    atomic = _rpSkinPipelinesAttach(atomic);
    RWASSERT(NULL != atomic);

    RWRETURN(atomic);
}

/**
 * \ingroup rpskin
 * \ref RpSkinAtomicGetHAnimHierarchy gets the HAnimHierarchy used to play
 * back an instance of an animation on a skinned atomic.
 *
 * \param atomic A pointer to the skinned atomic for the hierarchy.
 *
 * \return A pointer to the HAnimHierarchy for animating the skinned atomic.
 *
 * \see RpSkinAtomicSetHAnimHierarchy
 */
RpHAnimHierarchy *
RpSkinAtomicGetHAnimHierarchy(const RpAtomic *atomic)
{
    const SkinAtomicData *atomicData;

    RWAPIFUNCTION(RWSTRING("RpSkinAtomicGetHAnimHierarchy"));
    RWASSERT(0 < _rpSkinGlobals.module.numInstances);
    RWASSERT(NULL != atomic);

    atomicData = RPSKINATOMICGETCONSTDATA(atomic);
    RWASSERT(NULL != atomicData);

    RWRETURN(atomicData->hierarchy);
}

/**
 * \ingroup rpskin
 * \ref RpSkinGeometryGetSkin returns the skin attached to a goemetry.
 *
 * \param geometry A pointer to the geometry from which to get the skin.
 *
 * \return A pointer to the attached skin.
 */
RpSkin *
RpSkinGeometryGetSkin(RpGeometry *geometry)
{
    RpSkin *skin;

    RWAPIFUNCTION(RWSTRING("RpSkinGeometryGetSkin"));
    RWASSERT(0 < _rpSkinGlobals.module.numInstances);
    RWASSERT(NULL != geometry);

    /* Grab the skin. */
    skin = *RPSKINGEOMETRYGETDATA(geometry);

    RWRETURN(skin);
}

/**
 * \ingroup rpskin
 * \ref RpSkinGeometrySetSkin attaches a skin to a geometry.
 *
 * \param geometry A pointer to the geometry where to set the skin.
 * \param skin     A pointer to the skin to attach.
 *
 * \return A pointer to the geometry.
 */
RpGeometry *
RpSkinGeometrySetSkin( RpGeometry *geometry, RpSkin *skin )
{
    RpSkin *oldSkin;

    RWAPIFUNCTION(RWSTRING("RpSkinGeometrySetSkin"));
    RWASSERT(0 < _rpSkinGlobals.module.numInstances);
    RWASSERT(NULL != geometry);

    oldSkin = *RPSKINGEOMETRYGETDATA(geometry);
    if( skin != oldSkin )
    {
        /* Skins are different hence we need to change something. */
        if( NULL != oldSkin )
        {
            /* Give the platform a chance to tidy up. */
            _rpSkinDeinitialize(geometry);
        }

        /* Set the new skin. */
        *RPSKINGEOMETRYGETDATA(geometry) = skin;

        /* Setup the new skin. */
        if( NULL != skin )
        {
            /* Give the platforms a chance to create the skin. */
            _rpSkinInitialize(geometry);
        }
    }

    RWRETURN(geometry);
}

/**
 * \ingroup rpskin
 * \ref RpSkinCreate creates a skin for a geometry, and attaches
 * that skin data to the geometry.  A skin maps a geometry'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.
 * 
 * Note that RpSkinCreate makes its own copy of all the array arguments.
 *
 * \param numVertices      The number of vertices in the skin.
 * \param numBones         The number of bones in the skin (maximum is platform
 *                         specific).
 * \param boneTags         An array of tags for the bones.
 * \param vertexWeights    An array of weights, one per vertex. The weights for
 *                         each vertex should sum to 1.0
 * \param vertexIndices    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 inverseMatrices  A pointer to an array of 4x4 matrices, one per bone,
 *                         that contain the transformation from bone-space to
 *                         object-space for that bone.
 *
 * \return Returns a pointer to the skin that is generated for the geometry if
 * successful, or NULL if there is an error.
 */
RpSkin *
RpSkinCreate( RwUInt32 numVertices,
              RwUInt32 numBones,
              RwUInt32 *boneTags,
              RwMatrixWeights *vertexWeights,
              RwUInt32 *vertexIndices,
              RwMatrix *inverseMatrices )
{
    RpSkin *skin;

    RWAPIFUNCTION(RWSTRING("RpSkinCreate"));
    RWASSERT(0 < _rpSkinGlobals.module.numInstances);
    RWASSERT(0 < numVertices);
    RWASSERT(NULL != vertexWeights);
    RWASSERT(NULL != vertexIndices);
    RWASSERT(NULL != inverseMatrices);
    RWASSERT(0 < numBones);

    /* Create a new skin. */
    skin = SkinCreate( numVertices,
                       numBones,
                       boneTags,
                       vertexWeights,
                       vertexIndices,
                       inverseMatrices );
    RWASSERT(NULL != skin);

    RWRETURN(skin);
}

/**
 * \ingroup rpskin
 * \ref RpSkinDestroy destroys a skin.
 *
 * \param skin  Pointer to skin to destory.
 *
 * \return NULL
 */
RpSkin *
RpSkinDestroy(RpSkin *skin)
{
    RWAPIFUNCTION(RWSTRING("RpSkinDestroy"));
    RWASSERT(0 < _rpSkinGlobals.module.numInstances);
    RWASSERT(NULL != skin);

    /* Destory the skin extension data. */
    SkinDestorySkinData(skin);

    /* Free the skin. */
    RwFreeListFree(_rpSkinGlobals.freeList, skin);
    skin = (RpSkin *)NULL;

    RWRETURN(skin);
}

/**
 * \ingroup rpskin
 * \ref RpSkinGetNumBones returns the number of bones in the skin.
 *
 * \param skin Pointer to skin to query.
 *
 * \return Number of bones.
 */
RwUInt32
RpSkinGetNumBones(RpSkin *skin)
{
    RWAPIFUNCTION(RWSTRING("RpSkinGetNumBones"));
    RWASSERT(0 < _rpSkinGlobals.module.numInstances);
    RWASSERT(NULL != skin);

    RWRETURN(skin->boneData.numBones);
}

/**
 * \ingroup rpskin
 * \ref RpSkinGetVertexBoneWeights returns read-only access to the skin's
 * per-vertex array of bone weights.
 *
 * \param skin Pointer to skin to query.
 *
 * \return Read-only per-vertex array of bone weights.
 * 
 * \see RpSkinCreate
 */
const RwMatrixWeights *
RpSkinGetVertexBoneWeights( RpSkin *skin )
{
    RWAPIFUNCTION(RWSTRING("RpSkinGetVertexBoneWeights"));
    RWASSERT(0 < _rpSkinGlobals.module.numInstances);
    RWASSERT(NULL != skin);

    RWRETURN(skin->vertexMaps.matrixWeights);
}

/**
 * \ingroup rpskin
 * \ref RpSkinGetVertexBoneIndices returns read-only access to the skin's
 * per-vertex array of bone indices.
 *
 * \param skin Pointer to skin to query.
 *
 * \return Read-only per-vertex array of bone indices.
 * 
 * \see RpSkinCreate
 */
const RwUInt32 *
RpSkinGetVertexBoneIndices( RpSkin *skin )
{
    RWAPIFUNCTION(RWSTRING("RpSkinGetVertexBoneIndices"));
    RWASSERT(0 < _rpSkinGlobals.module.numInstances);
    RWASSERT(NULL != skin);

    RWRETURN(skin->vertexMaps.matrixIndices);
}

/**
 * \ingroup rpskin
 * \ref RpSkinGetSkinToBoneMatrices returns read-only access to the skin's
 * per-bone array of skin to bone space transform matrices.
 *
 * \param skin Pointer to skin to query.
 *
 * \return Read-only per-bone array of skin to bone space transform matrices.
 * 
 * \see RpSkinCreate
 */
const RwMatrix *
RpSkinGetSkinToBoneMatrices( RpSkin *skin )
{
    RWAPIFUNCTION(RWSTRING("RpSkinGetSkinToBoneMatrices"));
    RWASSERT(0 < _rpSkinGlobals.module.numInstances);
    RWASSERT(NULL != skin);

    RWRETURN(skin->boneData.invBoneToSkinMat);
}
