/*
 * Bones plug-in
 */

/** 
 * \ingroup rpbone
 * \page rpboneoverview RpBone Plugin Overview
 *
 * The RpBone plugin extends the RenderWare Graphics Core API and allows 
 * it to support hull-based skinned animations.
 * 
 * To create such an animation, the artist should create an exo-skeleton 
 * built as a series of ‘hulls’ around the model. Each hull is linked to a 
 * part of the model, as described in the Artists’ Guide, and the model data 
 * exported using the supplied RenderWare exporter plug-in for your chosen 3D modelling package.
 *
 * The application developer uses the RpBone plugin’s functionality to tie 
 * a model in RpClump form to its exo-skeleton. The plugin itself will ensure 
 * that animation of the skeleton will result in animation of the linked 3D model.
 *
 * This plugin does not support weighted skins. 
 * 
 * Note: The RpBone Plugin provides basic support for skinned animation. The 
 * Developers may prefer to use the newer RpSkin Plug-in which provides improved 
 * support for this kind of animation.
 */

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

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

#include "rpbone.h"
#include "rpanim.h"

#if (!defined(DOXYGEN_SHOULD_SKIP_THIS))
static const char   rcsid[] __RWUNUSED__ =
    "@@(#)$Id: rpbone.c,v 1.73 2001/03/15 16:48:46 katherinet Exp $";
#endif /* (!defined(DOXYGEN_SHOULD_SKIP_THIS)) */

/* Bone flags */
#define rpBONEFLAGNONE              0x00
#define rpBONEFLAGSKINDIRTY         0x01
#define rpBONEFLAGSKINWASDIRTY      0x02
#define rpBONEFLAGANIMATE           0x04

#define RPBONEFRAMEGETDATA(frame)                                       \
  ((rpBone **)(((RwUInt8 *)frame) + BoneStatic.FrameDataOffset))

#define RPBONEFRAMEGETCONSTDATA(frame)                                  \
  ((const rpBone **)(((const RwUInt8 *)frame) + BoneStatic.FrameDataOffset))

#define RPBONEGEOMGETDATA(geom)                                         \
  ((rpBoneGeomData **)(((RwUInt8 *)geom) + RpBoneGeomBoneListOffset))

#define RPBONEGEOMGETCONSTDATA(geom)                                    \
  ((const rpBoneGeomData **)(((const RwUInt8 *)geom) +                  \
                             RpBoneGeomBoneListOffset))

#define rwBONEALIGNMENT rwMATRIXALIGNMENT
#define DEFAULTTAG  -1
#if (defined(RWDEBUG))
long                rpBoneStackDepth = 0;
#endif /* (defined(RWDEBUG)) */

/* Offset into geometry - not static as it needs to be accessed by RpSkin */
RwInt32             RpBoneGeomBoneListOffset = 0;

typedef struct _RpBoneStatic RpBoneStatic;
struct _RpBoneStatic
{
    RwModuleInfo        ModuleInfo;
    RwInt32             FrameDataOffset; /* Offset into frame */
    RwInt32             GeomBoneListStreamOffset; /* Offset into geometry stream */
    RwFreeList         *BoneFreeList;
    RwFreeList         *BoneGeomDataFreeList;
};

static RpBoneStatic BoneStatic = {
    {
     (RwInt32) 0,              /* globalsOffset */
     (RwInt32) 0               /* numInstances */
     },                        /* ModuleInfo */
    (RwInt32) 0,               /* FrameDataOffset */
    (RwInt32) 0,               /* GeomBoneListStreamOffset */
    (RwFreeList *) NULL,       /* FreeList */
    (RwFreeList *) NULL        /* BoneGeomDataFreeList */
};

typedef struct rpBone rpBone;
struct rpBone
{
    RwFrame            *frame;  /* The frame we are attached to */
    RwMatrix            skinToBone; /* Skin->Bone space matrix */
    RwMatrix            skinMatrix; /* Matrix to update skin vertices */
    RwInt32             flags;  /* Bone flags (eg skin dirty) */
};

#if (!defined(rpBoneAssign))
#define rpBoneAssign(_target, _source)            \
    ( *(_target) = *(_source) )
#endif /* (!defined(rpBoneAssign)) */

typedef struct rpBoneList rpBoneList;
struct rpBoneList
{
    RwUInt32            start, length;
    RwInt32             boneTag;
    rpBone             *bone;
};

typedef struct rpBoneGeomData rpBoneGeomData;
struct rpBoneGeomData
{
    rpBoneList         *boneList; /* Bone list */
    RwV3d              *vertices; /* Original vertices */
    RwV3d              *normals; /* Original normals */
};

typedef struct rpBonePlane rpBonePlane;
struct rpBonePlane
{
    RwBool              eliminated;
    RwBool              calculated;
    RwV3d               vertex;
    RwV3d               normal;
};

typedef struct _linkData LinkData;
struct _linkData
{
    RwMatrix           *invSkin;
    RpGeometry         *skinGeom;
    RwFrame            *enclosingFrame;
    RwInt32             vertIndex;
    RwReal              closest;

    rpBonePlane       **bpppPlanes;
    RwInt32             nBone;
};

typedef struct _TagData TagData;
struct _TagData
{
    RwInt32             tag;
    RwFrame            *frame;
};

typedef struct _InitData InitData;
struct _InitData
{
    rpBone             *bone;
    RwInt32             tag;
};

typedef struct _RpBoneUpdateData RpBoneUpdateData;
struct _RpBoneUpdateData
{
    rpBone             *bone;

    RwMatrix           *inverseSkinMatrix;
    RwInt32             parentFlags;
};

static RwFrame     *
FindTaggedFrame(RwFrame * frame, void *data)
{
    TagData            *tagData = (TagData *) data;

    RWFUNCTION(RWSTRING("FindTaggedFrame"));
    RWASSERT(frame);
    RWASSERT(tagData);

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

    RwFrameForAllChildren(frame, FindTaggedFrame, data);

    RWRETURN(frame);
}

/************************************************************************
 *                                                                      *
 * Function:   _FrameGetAtomicCallBack                                  *
 * Purpose:    Callback for all frame objects to find first atomic      *
 * On entry:   Frame object, user data to hold resulting atomic         *
 * On exit:    Frame object if not an atomic, NULL if it is             *
 *                                                                      *
 ************************************************************************/

static RwObject    *
FrameGetAtomicCallBack(RwObject * object, void *data)
{
    RWFUNCTION(RWSTRING("FrameGetAtomicCallBack"));
    RWASSERT(object);
    RWASSERT(data);

    if (RwObjectGetType(object) == rpATOMIC)
    {
        *(RpAtomic **) data = (RpAtomic *) object;

        RWRETURN((RwObject *)NULL);
    }

    RWRETURN(object);
}

/************************************************************************
 *                                                                      *
 * Function:   FrameGetAtomic                                           *
 * Purpose:    Get atomic attached to this frame                        *
 * On entry:   Frame                                                    *
 * On exit:    Atomic                                                   *
 *                                                                      *
 ************************************************************************/

static RpAtomic    *
FrameGetAtomic(RwFrame * frame)
{
    RpAtomic           *atomic = (RpAtomic *)NULL;

    RWFUNCTION(RWSTRING("FrameGetAtomic"));
    RWASSERT(frame);

    RwFrameForAllObjects(frame, FrameGetAtomicCallBack,
                         (void *) &atomic);

    RWRETURN(atomic);
}

/************************************************************************
 *                                                                      *
 * Function:   _FrameGetSkinAtomicCallBack                              *
 * Purpose:    Callback for all frame objects to find first atomic      *
 *             with geomdata                                            *
 * On entry:   Frame object, user data to hold resulting atomic         *
 * On exit:    Frame object if not an atomic, NULL if it is             *
 *                                                                      *
 ************************************************************************/

static RwObject    *
FrameGetSkinAtomicCallBack(RwObject * object, void *data)
{
    RWFUNCTION(RWSTRING("FrameGetSkinAtomicCallBack"));
    RWASSERT(object);
    RWASSERT(data);

    if (RwObjectGetType(object) == rpATOMIC)
    {
        RpGeometry         *geom =
            RpAtomicGetGeometry((RpAtomic *) object);

        if (*RPBONEGEOMGETDATA(geom))
        {
            *(RpAtomic **) data = (RpAtomic *) object;

            RWRETURN((RwObject *)NULL);
        }
    }

    RWRETURN(object);
}

/************************************************************************
 *                                                                      *
 * Function:   FrameGetSkinAtomic                                       *
 * Purpose:    Get atomic attached to this frame with geomdata          *
 * On entry:   Frame                                                    *
 * On exit:    Atomic                                                   *
 *                                                                      *
 ************************************************************************/
static RpAtomic    *
FrameGetSkinAtomic(RwFrame * frame)
{
    RpAtomic           *atomic = (RpAtomic *)NULL;

    RWFUNCTION(RWSTRING("FrameGetSkinAtomic"));
    RWASSERT(frame);

    RwFrameForAllObjects(frame, FrameGetSkinAtomicCallBack,
                         (void *) &atomic);

    RWRETURN(atomic);
}

static RwFrame     *
BoneFrameSetBone(RwFrame * frame, rpBone * bone)
{
    RWFUNCTION(RWSTRING("BoneFrameSetBone"));

    if (frame)
    {
        *RPBONEFRAMEGETDATA(frame) = bone;

        RWRETURN(frame);
    }

    RWRETURN((RwFrame *)NULL);
}

static rpBone      *
BoneFrameGetBone(RwFrame * frame)
{
    RWFUNCTION(RWSTRING("BoneFrameGetBone"));

    if (frame)
    {
        RWRETURN((rpBone *) (*RPBONEFRAMEGETDATA(frame)));
    }

    RWRETURN((rpBone *)NULL);
}

static RwFrame     *
BoneFindTaggedFrame(RwFrame * frame, void *data)
{
    InitData           *initData = (InitData *) data;

    RWFUNCTION(RWSTRING("BoneFindTaggedFrame"));
    RWASSERT(frame);
    RWASSERT(initData);

    if (initData)
    {
        if (_rpAnimSeqGetFrameTag(frame) == initData->tag)
        {
            initData->bone = BoneFrameGetBone(frame);

            RWRETURN((RwFrame *)NULL);
        }
    }

    RwFrameForAllChildren(frame, BoneFindTaggedFrame, data);

    RWRETURN(frame);
}

static void
BoneListInit(rpBoneList * boneList, RwFrame * rootFrame)
{
    InitData            data;

    RWFUNCTION(RWSTRING("BoneListInit"));
    RWASSERT(boneList);
    RWASSERT(rootFrame);

    if (boneList && rootFrame)
    {
        while (boneList->length)
        {
            data.bone = (rpBone *)NULL;
            data.tag = boneList->boneTag;

            RwFrameForAllChildren(rootFrame,
                                  BoneFindTaggedFrame, (void *) &data);

            boneList->bone = data.bone;
            boneList++;
        }
    }

    RWRETURNVOID();
}

static void
BoneDestroy(rpBone * bone)
{
    RWFUNCTION(RWSTRING("BoneDestroy"));

    if (bone)
    {
        RwFrame            *frame = bone->frame;

        /* Free bone structure */
        RwFreeListFree(BoneStatic.BoneFreeList, bone);
        bone = (rpBone *)NULL;

        /* Detach from atomic */
        if (frame)
        {
            /* Set the frames bone pointer to NULL */
            BoneFrameSetBone(frame, (rpBone * )NULL);
        }
    }

    RWRETURNVOID();
}

static rpBone      *
BoneCreate(void)
{
    rpBone             *bone;

    RWFUNCTION(RWSTRING("BoneCreate"));

    bone = (rpBone *) RwFreeListAlloc(BoneStatic.BoneFreeList);

    if (!bone)
    {
        RWRETURN((rpBone *)NULL);
    }

    /* Initialise structure */
    bone->frame = (RwFrame *)NULL;

    RWASSERT(RWMATRIXALIGNMENT(&bone->skinToBone));
    RwMatrixSetIdentity(&bone->skinToBone);
    RWASSERT(RWMATRIXALIGNMENT(&bone->skinMatrix));
    RwMatrixSetIdentity(&bone->skinMatrix);
    bone->flags = rpBONEFLAGSKINDIRTY | rpBONEFLAGANIMATE;

    RWRETURN(bone);
}

static RwFrame     *
BonesCreateCallback(RwFrame * frame, void *data)
{
    rpBone             *bone;
    RwFrame            *skinFrame;

    RWFUNCTION(RWSTRING("BonesCreateCallback"));
    RWASSERT(frame);
    RWASSERT(data);

    if (frame && data)
    {
        skinFrame = (RwFrame *) data;

        /* If this atomic doesn't already have a bone then create one */
        bone = (rpBone *) BoneFrameGetBone(frame);
        if (!bone && _rpAnimSeqGetFrameTag(frame) != DEFAULTTAG)
        {
            bone = BoneCreate();
            if (bone)
            {
                bone->frame = frame;

                /* Attach to frame */
                BoneFrameSetBone(frame, bone);
            }

            /* Compute skin->bone matrix? */
            if (skinFrame)
            {
                RwMatrixInvert(&bone->skinToBone, RwFrameGetLTM(frame));
                RwMatrixTransform(&bone->skinToBone,
                                  RwFrameGetMatrix(skinFrame),
                                  rwCOMBINEPRECONCAT);
            }
        }

        RwFrameForAllChildren(frame, BonesCreateCallback, data);
    }

    RWRETURN(frame);
}

/* Nasty hack for now... */
static rpBone     **boneMap;

static int
#ifdef _WINDOWS
                    __cdecl
#endif                          /* _WINDOWS */
sortBones(const void *pA, const void *pB)
{
    rpBone             *bone1, *bone2;

    RWFUNCTION(RWSTRING("sortBones"));
    RWASSERT(pA);
    RWASSERT(pB);

    bone1 = boneMap[*(const RwUInt32 *) pA];
    bone2 = boneMap[*(const RwUInt32 *) pB];

    /* Compare the bone pointers on this vertex to determine order */
    if (bone1 > bone2)
    {
        RWRETURN(1);
    }
    else if (bone1 < bone2)
    {
        RWRETURN(-1);
    }
    else
    {
        RWRETURN(0);
    }
}

static rpBoneList  *
BoneBuildBoneList(RpGeometry * geom, rpBone ** vertexBoneMap)
{
    RwUInt32            numVerts;
    RwUInt32           *mapBack, *mapForwards;
    rpBoneList         *boneList;
    RwUInt32            numBones;
    rpBone             *bone;
    RwInt32             i;
    void               *tempWorkSpace;
    RwTexCoords        *texCoords;
    RwRGBA             *preLitLum;

    RWFUNCTION(RWSTRING("BoneBuildBoneList"));
    RWASSERT(geom);
    RWASSERT(vertexBoneMap);

    numVerts = RpGeometryGetNumVertices(geom);

    /* Allocate workspace */
    mapBack = (RwUInt32 *) RwMalloc(sizeof(RwUInt32) * numVerts * 2);
    if (!mapBack)
    {
        RWERROR((E_RW_NOMEM, sizeof(RwUInt32) * numVerts * 2));
        RWRETURN((rpBoneList *)NULL);
    }
    mapForwards = mapBack + numVerts;

    tempWorkSpace = (RwV3d *) RwMalloc(sizeof(RwV3d) * numVerts);
    if (!tempWorkSpace)
    {
        RwFree(mapBack);
        RWERROR((E_RW_NOMEM, sizeof(RwV3d) * numVerts));
        RWRETURN((rpBoneList *)NULL);
    }

    /* mapBack - given an output index, where does the vertex come from */
    /* mapForwards - given an input index, where did the vertex end up? */

    /* First sort the vertices based on bone matrix - we need to sort several
     * arrays here: triangle indices, vpOriginalSkin and vpOriginalNormals in
     * the body structure.
     */

    /* Starting place for the backwards map */
    for (i = 0; i < (RwInt32) numVerts; i++)
    {
        mapBack[i] = i;
    }

    /* Now sort 'em */
    boneMap = vertexBoneMap;
    qsort(mapBack, numVerts, sizeof(RwUInt32), sortBones);

    /* Now generate a forwards map */
    for (i = 0; i < (RwInt32) numVerts; i++)
    {
        mapForwards[mapBack[i]] = i;
    }

    /* Now just need to remap them */

    /* Do the vertex indices */
    if (RpGeometryLock(geom, rpGEOMETRYLOCKPOLYGONS))
    {
        RpTriangle         *triangle = RpGeometryGetTriangles(geom);
        RwInt32             numTriangles =
            RpGeometryGetNumTriangles(geom);

        while (numTriangles--)
        {
            /* Process this triangle */
            triangle->vertIndex[0] = (RwUInt16)
                mapForwards[triangle->vertIndex[0]];
            triangle->vertIndex[1] = (RwUInt16)
                mapForwards[triangle->vertIndex[1]];
            triangle->vertIndex[2] = (RwUInt16)
                mapForwards[triangle->vertIndex[2]];

            /* Next triangle */
            triangle++;
        }

        RpGeometryUnlock(geom);
    }

    /* Put em back in the geometry in the right order so initial render is good... */
    if (RpGeometryLock
        (geom, rpGEOMETRYLOCKVERTICES | rpGEOMETRYLOCKNORMALS))
    {
        RpMorphTarget      *morphTarget =
            RpGeometryGetMorphTarget(geom, 0);
        RwV3d              *tempVerts = (RwV3d *) tempWorkSpace;
        RwV3d              *verts, *normals;
        rpBoneGeomData     *geomData;

        geomData = *RPBONEGEOMGETDATA(geom);

        /* Redo the vertices */
        if (geomData->vertices)
        {
            memcpy(tempVerts, geomData->vertices,
                   numVerts * sizeof(RwV3d));
            for (i = 0; i < (RwInt32) numVerts; i++)
            {
                geomData->vertices[i] = tempVerts[mapBack[i]];
            }

            verts = RpMorphTargetGetVertices(morphTarget);
            if (verts)
            {
                memcpy(verts, geomData->vertices,
                       numVerts * sizeof(RwV3d));
            }
        }

        /* And redo the normals */
        if (geomData->normals)
        {
            memcpy(tempVerts, geomData->normals,
                   numVerts * sizeof(RwV3d));
            for (i = 0; i < (RwInt32) numVerts; i++)
            {
                geomData->normals[i] = tempVerts[mapBack[i]];
            }

            normals = RpMorphTargetGetVertexNormals(morphTarget);
            if (normals)
            {
                memcpy(normals, geomData->normals,
                       numVerts * sizeof(RwV3d));
            }
        }

        RpGeometryUnlock(geom);
    }

    /* And the texture coordinates */
    texCoords = RpGeometryGetVertexTexCoords(geom, rwTEXTURECOORDINATEINDEX0);
    if (texCoords)
    {
        if (RpGeometryLock(geom, rpGEOMETRYLOCKTEXCOORDS))
        {
            RwTexCoords        *tempTexCoords;

            tempTexCoords = (RwTexCoords *) tempWorkSpace;
            if (tempTexCoords)
            {
                memcpy(tempTexCoords, texCoords,
                       sizeof(RwTexCoords) * numVerts);
                for (i = 0; i < (RwInt32) numVerts; i++)
                {
                    texCoords[i] = tempTexCoords[mapBack[i]];
                }
            }
        }
    }

    /* And the prelight colors */
    preLitLum = RpGeometryGetPreLightColors(geom);
    if (preLitLum)
    {
        if (RpGeometryLock(geom, rpGEOMETRYLOCKPRELIGHT))
        {
            RwRGBA             *tempPreLight;

            tempPreLight = (RwRGBA *) tempWorkSpace;
            if (tempPreLight)
            {
                memcpy(tempPreLight, preLitLum,
                       sizeof(RwRGBA) * numVerts);
                for (i = 0; i < (RwInt32) numVerts; i++)
                {
                    preLitLum[i] = tempPreLight[mapBack[i]];
                }
            }
        }
    }

    /* And the bone map table */
    {
        rpBone            **tempBones = (rpBone **) tempWorkSpace;

        memcpy(tempBones, vertexBoneMap, sizeof(rpBone *) * numVerts);
        for (i = 0; i < (RwInt32) numVerts; i++)
        {
            vertexBoneMap[i] = tempBones[mapBack[i]];
        }
    }

    /***********************************************************************/
    /* Now build a list of contiguous vertices so we can transform quickly */

    /***********************************************************************/

    /* First find out how long our list will be */
    bone = vertexBoneMap[0];
    numBones = 1;
    for (i = 1; i < (RwInt32) numVerts; i++)
    {
        if (vertexBoneMap[i] != bone)
        {
            numBones++;
            bone = vertexBoneMap[i];
        }
    }

    /* Now we can allocate a bonelist structure, 
     * and fill it in (don't forget terminator) */
    boneList =
        (rpBoneList *) RwMalloc(sizeof(rpBoneList) * (numBones + 1));
    if (boneList)
    {
        rpBoneList         *curBoneList;

        /* Now fill it in */
        curBoneList = boneList;

        /* First one (we kind of assume that there will be at least one vertex)... */
        curBoneList->start = 0;
        curBoneList->length = 1;
        curBoneList->bone = vertexBoneMap[0];
        curBoneList->boneTag =
            _rpAnimSeqGetFrameTag(curBoneList->bone->frame);

        for (i = 1; i < (RwInt32) numVerts; i++)
        {
            if (vertexBoneMap[i] != curBoneList->bone)
            {
                /* Time for a new element */
                curBoneList++;
                curBoneList->start = i;
                curBoneList->length = 1;
                curBoneList->bone = vertexBoneMap[i];
                curBoneList->boneTag =
                    _rpAnimSeqGetFrameTag(curBoneList->bone->frame);
            }
            else
            {
                /* One more on this element */
                curBoneList->length++;
            }
        }

        /* Terminate it */
        curBoneList++;
        curBoneList->start = 0;
        curBoneList->length = 0;
        curBoneList->bone = (rpBone *)NULL;
        curBoneList->boneTag = 0;
    }

    /* And free up the workspace */
    RwFree(tempWorkSpace);
    RwFree(mapBack);

    RWRETURN(boneList);
}

/************************************************************************
 *                                                                      *
 * Function:   BoneCalculatePlane                                    *
 * Purpose:    Calculate plane equation for a bone hull triangle        *
 * On entry:   bone plane, bone matrix, hull vertices, vertex indices   *
 * On exit:    void                                                     *
 *                                                                      *
 ************************************************************************/

static void
BoneCalculatePlane(rpBonePlane * bonePlane,
                   RwMatrix * boneMatrix,
                   RwV3d * vertices, RwUInt16 * vertexIndex)
{
    RWFUNCTION(RWSTRING("BoneCalculatePlane"));
    RWASSERT(bonePlane);
    RWASSERT(boneMatrix);
    RWASSERT(vertices);
    RWASSERT(vertexIndex);

    if (!bonePlane->calculated)
    {
        RwV3d               side1, side2;

        bonePlane->calculated = TRUE;

        /* calculate normal */
        RwV3dSub(&side1, &vertices[vertexIndex[1]],
                 &vertices[vertexIndex[0]]);
        RwV3dSub(&side2, &vertices[vertexIndex[2]],
                 &vertices[vertexIndex[0]]);
        RwV3dCrossProduct(&bonePlane->normal, &side2, &side1);

        /* put the plane in bone space */
        RwV3dTransformVectors(&bonePlane->normal, &bonePlane->normal,
                              1, boneMatrix);
        RwV3dTransformPoints(&bonePlane->vertex,
                             &vertices[vertexIndex[0]], 1, boneMatrix);

        /* normalize after transform for more accuracy */
        _rwV3dNormalize(&bonePlane->normal, &bonePlane->normal);
    }

    RWRETURNVOID();
}

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

    if (_rpAnimSeqGetFrameTag(frame) != DEFAULTTAG)
    {
        (*(RwInt32 *) data)++;
    }

    RwFrameForAllChildren(frame, BoneCountCallback, data);

    RWRETURN(frame);
}

static RwFrame     *
BodyLinkBonesCalcPlaneCallback(RwFrame * frame, void *data)
{
    RpAtomic           *apBone;
    RpGeometry         *gpBone;
    RwV3d              *vpVert;
    RpTriangle         *tpTri;
    RwMatrix            mBone;
    RwInt32             nNumTri;
    RwInt32             nI, nJ, nShared, nV;
    RwUInt16           *npV1, *npV2;
    rpBone             *bpBone;
    rpBonePlane        *bppPlanes;
    LinkData           *linkData;

    RWFUNCTION(RWSTRING("BodyLinkBonesCalcPlaneCallback"));
    RWASSERT(frame);
    RWASSERT(data);

    linkData = (LinkData *) data;

    apBone = FrameGetAtomic(frame);

    bpBone = BoneFrameGetBone(frame);

    if (bpBone && apBone
        && (_rpAnimSeqGetFrameTag(frame) != DEFAULTTAG))
    {
        RpMorphTarget      *morphTarget;

        /* Transform bone into skin space */
        gpBone = RpAtomicGetGeometry(apBone);
        RwMatrixCopy(&mBone, RwFrameGetLTM(RpAtomicGetFrame(apBone)));
        RwMatrixTransform(&mBone, linkData->invSkin,
                          rwCOMBINEPOSTCONCAT);

        morphTarget = RpGeometryGetMorphTarget(gpBone, 0);
        vpVert = RpMorphTargetGetVertices(morphTarget);
        tpTri = RpGeometryGetTriangles(gpBone);
        nNumTri = RpGeometryGetNumTriangles(gpBone);

        bppPlanes =
            linkData->bpppPlanes[linkData->nBone] = (rpBonePlane *)
            RwMalloc(nNumTri * sizeof(rpBonePlane));
        if (!bppPlanes)
        {
            RWERROR((E_RW_NOMEM, nNumTri * sizeof(rpBonePlane)));
            RWRETURN((RwFrame *)NULL);
        }

        /* set up planes */
        for (nI = 0; nI < nNumTri; nI++)
        {
            bppPlanes[nI].eliminated = FALSE;
            bppPlanes[nI].calculated = FALSE;
            npV1 = tpTri[nI].vertIndex;
            BoneCalculatePlane(&bppPlanes[nI], &mBone, vpVert, npV1);
        }

        /* eliminate coplanar hull triangles */
        for (nI = 0; nI < nNumTri - 1; nI++)
        {
            if (bppPlanes[nI].eliminated)
            {
                continue;
            }

            npV1 = tpTri[nI].vertIndex;

            for (nJ = nI + 1; nJ < nNumTri; nJ++)
            {
                if (bppPlanes[nJ].eliminated)
                {
                    continue;
                }

                npV2 = tpTri[nJ].vertIndex;
                nShared = 0;
                /* count the number of shared vertices */
                for (nV = 0; nV < 9; nV++)
                {
                    nShared +=
                        (npV1[(nV / 3) % 3] == npV2[nV % 3]) ? 1 : 0;
                }
                if (nShared > 1)
                {
                    RwReal              rDot;

                    /* shares at least two vertices, so compare normals */
                    rDot = RwV3dDotProduct(&bppPlanes[nI].normal,
                                           &bppPlanes[nJ].normal);

                    if (rDot > (RwReal) (0.98))
                    {
                        /* identical, so eliminate one */
                        bppPlanes[nJ].eliminated = TRUE;
                    }
                }
            }
        }
        linkData->nBone++;
    }

    /* Go recursive */
    RwFrameForAllChildren(frame, BodyLinkBonesCalcPlaneCallback, data);

    RWRETURN(frame);
}

static RwFrame     *
LinkVertices(RwFrame * frame, void *data)
{

    RpGeometry         *gpBone;
    RwInt32             nNumTri, nJ;
    RwV3d               vSkin;
    RwV3d              *vpSkin;
    LinkData           *linkData;
    rpBone             *bpBone;
    rpBonePlane        *bppPlanes;
    RwReal              rDot;
    RwBool              bBoneContainsVertex;

    RWFUNCTION(RWSTRING("LinkVertices"));
    RWASSERT(data);

    linkData = (LinkData *) data;

    bpBone = BoneFrameGetBone(frame);

    if (bpBone && (_rpAnimSeqGetFrameTag(frame) != DEFAULTTAG))
    {
        vpSkin =
            RpMorphTargetGetVertices(RpGeometryGetMorphTarget
                                     (linkData->skinGeom, 0));

        /* try this vertex against this bone */
        bBoneContainsVertex = TRUE;
        gpBone = RpAtomicGetGeometry(FrameGetAtomic(frame));
        nNumTri = gpBone ? RpGeometryGetNumTriangles(gpBone) : 0;
        bppPlanes = linkData->bpppPlanes[linkData->nBone];
        for (nJ = 0; nJ < nNumTri; nJ++)
        {
            if (bppPlanes[nJ].eliminated)
            {
                continue;
            }

            RwV3dSub(&vSkin, &vpSkin[linkData->vertIndex],
                     &bppPlanes[nJ].vertex);

            rDot = RwV3dDotProduct(&vSkin, &bppPlanes[nJ].normal);
            if (rDot < (RwReal) (0))
            {
                /* Skin vertex is outside this plane - abort this bone */
                bBoneContainsVertex = FALSE;
                break;
            }
        }
        /* Does this bone's geometry contain the skin vertex? */
        if (bBoneContainsVertex)
        {
            linkData->enclosingFrame = frame;
        }

        linkData->nBone++;
    }

    RwFrameForAllChildren(frame, LinkVertices, data);

    RWRETURN(frame);
}

static RwFrame     *
LinkByProximity(RwFrame * frame, void *data)
{
    RwMatrix            boneMatrix;
    RwV3d               vSkin;
    rpBone             *bone;
    LinkData           *linkData;

    RWFUNCTION(RWSTRING("LinkByProximity"));
    RWASSERT(frame);
    RWASSERT(data);

    linkData = (LinkData *) data;

    bone = (rpBone *) BoneFrameGetBone(frame);

    if (bone && (_rpAnimSeqGetFrameTag(frame) != DEFAULTTAG))
    {
        RwV3d              *skinVerts;
        RwReal              dot;

        skinVerts =
            RpMorphTargetGetVertices(RpGeometryGetMorphTarget
                                     (linkData->skinGeom, 0));

        RwMatrixSetIdentity(&boneMatrix);
        RwMatrixCopy(&boneMatrix, RwFrameGetLTM(frame));
        RwMatrixTransform(&boneMatrix, linkData->invSkin,
                          rwCOMBINEPOSTCONCAT);

        /* How close is the bone origin? */
        RwV3dSub(&vSkin, &boneMatrix.pos,
                 &skinVerts[linkData->vertIndex]);
        dot = RwV3dDotProduct(&vSkin, &vSkin);

        /* Closer? */
        if (!linkData->enclosingFrame || dot < linkData->closest)
        {
            linkData->closest = dot;
            linkData->enclosingFrame = frame;
        }
    }

    RwFrameForAllChildren(frame, LinkByProximity, data);

    RWRETURN(frame);
}

/************************************************************************
 *                                                                      *
 * Function:   _rpBodySkinUpdateCallback                                *
 * Purpose:    Traverse bone hierarchy doing the following:             *
 *             1) Create the old skin -> new skin transform             *
 *             2) Perculating the skindirty status down to child bones. *
 * On entry:   The body, bone atomic, inverse skin matrix,parent's flags*
 * On exit:    Success?                                                 *
 *                                                                      *
 ************************************************************************/

static RwFrame     *
BoneClumpSkinUpdateCallback(RwFrame * frame, void *data)
{
    RpBoneUpdateData    parentData;
    RpBoneUpdateData   *boneData;
    rpBone             *bone;

    RWFUNCTION(RWSTRING("BoneClumpSkinUpdateCallback"));
    RWASSERT(frame);
    RWASSERT(data);

    boneData = (RpBoneUpdateData *) data;
    bone = BoneFrameGetBone(frame);

    if (bone)
    {
        /* Do necessary so that bone can be applied to skin */
        /*if((bone->flags & rpBONEFLAGSKINDIRTY) ||
         * (boneData->parentFlags & rpBONEFLAGSKINWASDIRTY)) */
        if (RwFrameDirty(frame) &&
            (_rpAnimSeqGetFrameTag(frame) != DEFAULTTAG))
        {
            /* Create original (org skin->org bone)->new bone->new skin matrix */
            RwMatrixCopy(&bone->skinMatrix, &bone->skinToBone);
            RwMatrixTransform(&bone->skinMatrix,
                              RwFrameGetLTM(frame),
                              rwCOMBINEPOSTCONCAT);
            RwMatrixTransform(&bone->skinMatrix,
                              boneData->inverseSkinMatrix,
                              rwCOMBINEPOSTCONCAT);

            RwFrameUpdateObjects(frame);

            bone->flags &= ~rpBONEFLAGSKINDIRTY;
            bone->flags |= rpBONEFLAGSKINWASDIRTY;
        }
        else
        {
            bone->flags &= ~rpBONEFLAGSKINDIRTY;
            bone->flags &= ~rpBONEFLAGSKINWASDIRTY;
        }

        /* Do children, passing them our skin-dirty mask */
        parentData.inverseSkinMatrix = boneData->inverseSkinMatrix;
        parentData.parentFlags = bone->flags;

        RwFrameForAllChildren(frame,
                              BoneClumpSkinUpdateCallback,
                              (void *) &parentData);
    }
    else
    {
        RwFrameForAllChildren(frame, BoneClumpSkinUpdateCallback, data);
    }

    RWRETURN(frame);
}

/************************************************************************/

/*                                                                      */

/*                           - PLUGIN STUFF -                           */

/*                                                                      */

/************************************************************************/

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

/*                              - ENGINE -                              */

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

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

    RWASSERT(RWMATRIXALIGNMENT(offsetof(rpBone, skinToBone)));
    RWASSERT(RWMATRIXALIGNMENT(offsetof(rpBone, skinMatrix)));

#if (0)
    RWMESSAGE(("%d == offsetof(rpBone, frame)",
               offsetof(rpBone, frame)));
    RWMESSAGE(("%d == offsetof(rpBone, skinToBone) %d == rwMATRIXALIGNMENT", offsetof(rpBone, skinToBone), rwMATRIXALIGNMENT));
    RWMESSAGE(("%d == offsetof(rpBone, skinMatrix) %d == rwMATRIXALIGNMENT", offsetof(rpBone, skinMatrix), rwMATRIXALIGNMENT));
    RWMESSAGE(("%d == offsetof(rpBone, flags)",
               offsetof(rpBone, flags)));
#endif /* (0) */

    if (0 == BoneStatic.ModuleInfo.numInstances)
    {
        BoneStatic.BoneFreeList =
            RwFreeListCreate(sizeof(rpBone), 20, rwBONEALIGNMENT);

        if (!BoneStatic.BoneFreeList)
        {
            /* Failure */
            RWRETURN(NULL);
        }

        BoneStatic.BoneGeomDataFreeList =
            RwFreeListCreate(sizeof(rpBoneGeomData), 20, 0);

        if (!BoneStatic.BoneGeomDataFreeList)
        {
            RwFreeListDestroy(BoneStatic.BoneFreeList);
            BoneStatic.BoneFreeList = (RwFreeList *)NULL;
            /* Failure */
            RWRETURN(NULL);
        }
    }

    /* Grab the global data offset (same for all instances) */
    BoneStatic.ModuleInfo.globalsOffset = offset;

    /* One more module instance */
    BoneStatic.ModuleInfo.numInstances++;

    RWRETURN(instance);
}

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

    /* One less module instance */
    BoneStatic.ModuleInfo.numInstances--;

    if (0 == BoneStatic.ModuleInfo.numInstances)
    {
        if (NULL != BoneStatic.BoneGeomDataFreeList)
        {
            RwFreeListDestroy(BoneStatic.BoneGeomDataFreeList);
            BoneStatic.BoneGeomDataFreeList = (RwFreeList *)NULL;
        }

        if (NULL != BoneStatic.BoneFreeList)
        {
            RwFreeListDestroy(BoneStatic.BoneFreeList);
            BoneStatic.BoneFreeList = (RwFreeList *)NULL;
        }
    }

    RWRETURN(instance);
}

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

/*                             - GEOMETRY -                             */

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

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

    *RPBONEGEOMGETDATA(object) = (rpBoneGeomData *)NULL;

    RWRETURN(object);
}

static void        *
BoneGeomDestructor(void *object,
                   RwInt32 __RWUNUSED__ offset,
                   RwInt32 __RWUNUSED__ size)
{
    rpBoneGeomData     *geomData;

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

    geomData = *RPBONEGEOMGETDATA(object);

    if (geomData)
    {
        /* Free bone list */
        if (geomData->boneList)
        {
            RwFree(geomData->boneList);
            geomData->boneList = (rpBoneList *)NULL;
        }

        /* Free vertices */
        if (geomData->vertices)
        {
            RwFree(geomData->vertices);
            geomData->vertices = (RwV3d *)NULL;
        }

        /* Free normals */
        if (geomData->normals)
        {
            RwFree(geomData->normals);
            geomData->normals = (RwV3d *)NULL;
        }

        *RPBONEGEOMGETDATA(object) = (rpBoneGeomData *)NULL;

        RwFreeListFree(BoneStatic.BoneGeomDataFreeList, geomData);
        geomData = (rpBoneGeomData *)NULL;
    }

    RWRETURN(object);
}

static void        *
BoneGeomCopy(void *dstObject,
             const void *__RWUNUSED__ srcObject,
             RwInt32 __RWUNUSED__ offset, RwInt32 __RWUNUSED__ size)
{
    RWFUNCTION(RWSTRING("BoneGeomCopy"));
    RWASSERT(dstObject);

    /* can't realistically do this since we need to get
     * back to the frames from the geometry to copy the
     * data */

    RWRETURN(dstObject);
}

static RwStream    *
BoneGeomWrite(RwStream * stream,
              RwInt32 __RWUNUSED__ binaryLength,
              const void *object,
              RwInt32 __RWUNUSED__ offsetInObject,
              RwInt32 __RWUNUSED__ sizeInObject)
{
    const rpBoneGeomData *geomData;
    rpBoneList         *boneList;

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

    geomData = *RPBONEGEOMGETCONSTDATA(object);

    boneList = geomData->boneList;
    if (boneList)
    {
        while (boneList->length)
        {
            if (!RwStreamWriteInt(stream, (RwInt32 *) boneList, sizeof
                                  (RwInt32) * 3))
            {
                RWRETURN((RwStream *)NULL);
            }

            boneList++;
        }

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

    RWRETURN(stream);
}

static RwStream    *
BoneGeomRead(RwStream * stream,
             RwInt32 binaryLength,
             void *object,
             RwInt32 __RWUNUSED__ offsetInObject,
             RwInt32 __RWUNUSED__ sizeInObject)
{
    rpBoneGeomData     *geomData;
    rpBoneList         *boneList;
    rpBoneList         *curBoneList;
    RwInt32             bytesRead = 0;

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

    boneList = (rpBoneList *) RwMalloc((binaryLength / 3) * 4);
    if (!boneList)
    {
        RWERROR((E_RW_NOMEM, (binaryLength / 3) * 4));
        RWRETURN((RwStream *)NULL);
    }

    curBoneList = boneList;

    while (bytesRead < binaryLength)
    {
        /*
         * The first 3 elements of the rpBoneList structure are RwUInt32s so
         * it should be safe to do this.
         */
        if (!RwStreamReadInt(stream, (RwInt32 *) curBoneList, sizeof
                             (RwInt32) * 3))
        {
            RWRETURN((RwStream *)NULL);
        }

        curBoneList->bone = (rpBone *)NULL;

        bytesRead += (sizeof(RwInt32) * 3);
        curBoneList++;
    }

    geomData = (rpBoneGeomData *)
        RwFreeListAlloc(BoneStatic.BoneGeomDataFreeList);

    if (geomData)
    {
        (*RPBONEGEOMGETDATA(object)) = geomData;
        geomData->boneList = boneList;
        geomData->normals = (RwV3d *)NULL;
        geomData->vertices = (RwV3d *)NULL;
    }
    else
    {
        /* Tidy up */
        RwFree(boneList);
        RWERROR((E_RW_NOMEM, sizeof(rpBoneGeomData)));
        RWRETURN((RwStream *)NULL);
    }

    RWRETURN(stream);
}

static              RwInt32
BoneGeomGetSize(const void *object,
                RwInt32 __RWUNUSED__ offsetInObject,
                RwInt32 __RWUNUSED__ sizeInObject)
{
    const rpBoneGeomData *geomData;
    rpBoneList         *boneList;
    RwInt32             numBones = 0;
    RwInt32             size = 0;

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

    geomData = *RPBONEGEOMGETCONSTDATA(object);

    if (geomData)
    {
        if (geomData->boneList)
        {
            boneList = geomData->boneList;
            while (boneList->length)
            {
                boneList++;
                numBones++;
            }

            /* Number of bones plus a terminator */
            size = (sizeof(RwUInt32) * 3) * (numBones + 1);
        }
    }

    RWRETURN(size);
}

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

/*                            - FRAME DATA -                            */

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

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

    *RPBONEFRAMEGETDATA(object) = (rpBone *)NULL;

    RWRETURN(object);
}

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

    if (*RPBONEFRAMEGETDATA(object))
    {
        BoneDestroy((rpBone *) (*RPBONEFRAMEGETDATA(object)));
    }

    RWRETURN(object);
}

static void        *
BoneFrameDataCopy(void *dstObject,
                  const void *srcObject,
                  RwInt32 __RWUNUSED__ offset,
                  RwInt32 __RWUNUSED__ size)
{
    rpBone             *dst;
    const rpBone       *src;

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

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

    dst = *RPBONEFRAMEGETDATA(dstObject);
    src = *RPBONEFRAMEGETCONSTDATA(srcObject);
    if (dst && src)
    {

        rpBoneAssign(dst, src);
        dst->frame = (RwFrame *) dstObject;
    }

    RWRETURN(dstObject);
}

/************************************************************************
 *                                                                      *
 * Function:   _rpBodyLinkBonesByHull                                   *
 * Purpose:    Link each vertex to bone whose hull geometry contains it.*
 * On entry:   The body                                                 *
 * On exit:    Success?                                                 *
 *                                                                      *
 ************************************************************************/

RpClump            *
_rpBodyLinkBonesByHull(RpClump * clump)
{

    RwMatrix            mInverseSkin, mBone;
    RwFrame            *clumpFrame;
    RpAtomic           *skinAtomic;
    RpGeometry         *gpSkin;
    RwInt32             nI, nBone, nNumBones;
    RwInt32             numVertices;
    rpBonePlane       **bpppPlanes = (rpBonePlane **)NULL;
    LinkData            linkData;
    rpBone            **vertexBoneMap;
    rpBoneGeomData     *geomData;

    RWFUNCTION(RWSTRING("_rpBodyLinkBonesByHull"));
    RWASSERT(BoneStatic.ModuleInfo.numInstances);
    RWASSERT(clump);

    /* Get skin atomic, geometry and vertices */
    clumpFrame = RpClumpGetFrame(clump);
    skinAtomic = FrameGetAtomic(clumpFrame);
    gpSkin = skinAtomic ? RpAtomicGetGeometry(skinAtomic) : (RpGeometry *)NULL;
    if (!gpSkin)
    {
        RWRETURN((RpClump *)NULL);
    }

    /* count the number of bones */
    nNumBones = 0;
    RwFrameForAllChildren(RpClumpGetFrame(clump),
                          BoneCountCallback, (void *) &nNumBones);

    if (nNumBones == 0)
    {
        /* we insist on there being at least 1 bone */
        RWRETURN((RpClump *)NULL);
    }

    /* Create array large enough to map all vertices in geometry */
    numVertices = RpGeometryGetNumVertices(gpSkin);
    vertexBoneMap =
        (rpBone **) RwMalloc(sizeof(rpBone *) * numVertices);
    if (!vertexBoneMap)
    {
        RWRETURN((RpClump *)NULL);
    }

    memset(vertexBoneMap, 0, sizeof(rpBone *) * numVertices);

    /* Get inverse skin clump matrix */
    RwMatrixSetIdentity(&mInverseSkin);
    RwMatrixInvert(&mInverseSkin,
                   RwFrameGetMatrix(RpAtomicGetFrame(skinAtomic)));

    /* Matrix to hold bone position in skin space */
    RwMatrixSetIdentity(&mBone);

    bpppPlanes =
        (rpBonePlane **) RwMalloc(nNumBones * sizeof(rpBonePlane *));
    memset(bpppPlanes, 0, nNumBones * sizeof(rpBonePlane *));

    /* work out simple planes for all the bones */
    linkData.nBone = 0;
    linkData.invSkin = &mInverseSkin;
    linkData.bpppPlanes = bpppPlanes;
    RwFrameForAllChildren(RpClumpGetFrame(clump),
                          BodyLinkBonesCalcPlaneCallback,
                          (void *) &linkData);

    /* run through all the vertices */
    for (nI = 0; nI < RpGeometryGetNumVertices(gpSkin); nI++)
    {
        linkData.nBone = 0;
        linkData.invSkin = &mInverseSkin;
        linkData.bpppPlanes = bpppPlanes;
        linkData.skinGeom =
            RpAtomicGetGeometry(FrameGetAtomic(RpClumpGetFrame(clump)));
        linkData.enclosingFrame = (RwFrame *)NULL;
        linkData.vertIndex = nI;
        RwFrameForAllChildren(RpClumpGetFrame(clump),
                              LinkVertices, (void *) &linkData);

        if (!linkData.enclosingFrame)
        {
            linkData.closest = RwRealMAXVAL;

            /* link vertex to bone by proximity instead */
            RwFrameForAllChildren(RpClumpGetFrame(clump),
                                  LinkByProximity, (void *) &linkData);
        }

        /* Link enclosing bone to this vertex */
        vertexBoneMap[nI] = BoneFrameGetBone(linkData.enclosingFrame);
    }

    /* clear up */
    if (bpppPlanes)
    {
        for (nBone = 0; nBone < nNumBones; nBone++)
        {
            if (bpppPlanes[nBone])
            {
                RwFree(bpppPlanes[nBone]);
            }
        }
        RwFree(bpppPlanes);
    }

    geomData = (*RPBONEGEOMGETDATA(gpSkin));
    if (geomData)
    {
        if (!geomData->boneList)
        {
            geomData->boneList =
                BoneBuildBoneList(gpSkin, vertexBoneMap);
        }
    }

    RwFree(vertexBoneMap);

    RWRETURN(clump);
}

/************************************************************************
 *                                                                      *
 * Function:   rpBonesClumpLinkVerticesProximity                       *
 * Purpose:    Link each vertex to its closest bone                     *
 * On entry:   The body, maximum proximity to link bone - a value of    *
 *             tkBONESINFINITEPROXIMITY will always link closest bone.  *
 * On exit:    Success?                                                 *
 *                                                                      *
 ************************************************************************/

RpClump            *
_rpBodyLinkBonesByProximity(RpClump * clump)
{
    RpAtomic           *skinAtomic;
    RpGeometry         *skinGeometry;
    RwFrame            *rootFrame;
    RwMatrix            invSkinMatrix;
    RwInt32             numVertices;
    rpBoneGeomData     *geomData;
    RwInt32             i;
    rpBone            **vertexBoneMap; /* The map from each vertex
                                        * in the skin to their bone */

    RWFUNCTION(RWSTRING("_rpBodyLinkBonesByProximity"));
    RWASSERT(BoneStatic.ModuleInfo.numInstances);
    RWASSERT(clump);

    /* Get the skins geometry */
    rootFrame = RpClumpGetFrame(clump);
    skinAtomic = FrameGetAtomic(RpClumpGetFrame(clump));
    skinGeometry = skinAtomic ? RpAtomicGetGeometry(skinAtomic) : (RpGeometry *)NULL;
    if (!skinGeometry)
    {
        RWRETURN((RpClump *)NULL);
    }

    /* Create array large enough to map all vertices in geometry */
    numVertices = RpGeometryGetNumVertices(skinGeometry);
    vertexBoneMap =
        (rpBone **) RwMalloc(sizeof(rpBone *) * numVertices);
    if (!vertexBoneMap)
    {
        RWRETURN((RpClump *)NULL);
    }

    memset(vertexBoneMap, 0, sizeof(rpBone *) * numVertices);

    /* Get inverse skin clump matrix */
    RwMatrixSetIdentity(&invSkinMatrix);
    RwMatrixInvert(&invSkinMatrix, RwFrameGetMatrix(rootFrame));

    for (i = 0; i < numVertices; i++)
    {
        LinkData            linkData;

        linkData.invSkin = &invSkinMatrix;
        linkData.skinGeom = skinGeometry;
        linkData.enclosingFrame = (RwFrame *)NULL;
        linkData.vertIndex = i;
        linkData.closest = RwRealMAXVAL;

        RwFrameForAllChildren(rootFrame,
                              LinkByProximity, (void *) &linkData);

        /* Link enclosing bone to this vertex */
        vertexBoneMap[i] = BoneFrameGetBone(linkData.enclosingFrame);
    }

    geomData = (*RPBONEGEOMGETDATA(skinGeometry));
    if (geomData)
    {
        if (!geomData->boneList)
        {
            geomData->boneList =
                BoneBuildBoneList(skinGeometry, vertexBoneMap);
        }
    }

    RwFree(vertexBoneMap);

    RWRETURN(clump);
}

/************************************************************************
 *                                                                      *
 * Function:   _rpBodyLinkBonesByMap                                    *
 * Purpose:    Setup the vertex bone links given a pre-generated map    *
 * On entry:   The clump, vertexTagMap                                  *
 * On exit:    Success?                                                 *
 *                                                                      *
 ************************************************************************/

RpClump            *
_rpBodyLinkBonesByMap(RpClump * clump, RwInt32 * vertexTagMap)
{
    RwMatrix            mInverseSkin, mBone;
    RwFrame            *clumpFrame;
    RpAtomic           *skinAtomic;
    RpGeometry         *gpSkin;
    RwInt32             nI;
    RwInt32             numVertices;
    rpBone            **vertexBoneMap;
    rpBoneGeomData     *geomData;
    LinkData            linkData;

    RWFUNCTION(RWSTRING("_rpBodyLinkBonesByMap"));
    RWASSERT(BoneStatic.ModuleInfo.numInstances);
    RWASSERT(clump);
    RWASSERT(vertexTagMap);

    /* Get skin atomic, geometry and vertices */
    clumpFrame = RpClumpGetFrame(clump);
    skinAtomic = FrameGetAtomic(clumpFrame);
    gpSkin = skinAtomic ? RpAtomicGetGeometry(skinAtomic) : (RpGeometry *)NULL;
    if (!gpSkin)
    {
        RWRETURN((RpClump *)NULL);
    }

    /* Create array large enough to map all vertices in geometry */
    numVertices = RpGeometryGetNumVertices(gpSkin);
    vertexBoneMap =
        (rpBone **) RwMalloc(sizeof(rpBone *) * numVertices);
    if (!vertexBoneMap)
    {
        RWRETURN((RpClump *)NULL);
    }

    memset(vertexBoneMap, 0, sizeof(rpBone *) * numVertices);

    /* Get inverse skin clump matrix */
    RwMatrixSetIdentity(&mInverseSkin);
    RwMatrixInvert(&mInverseSkin,
                   RwFrameGetMatrix(RpAtomicGetFrame(skinAtomic)));

    /* Matrix to hold bone position in skin space */
    RwMatrixSetIdentity(&mBone);

    /* run through all the vertices */
    for (nI = 0; nI < RpGeometryGetNumVertices(gpSkin); nI++)
    {
        RwFrame            *frame;

        frame = RpClumpGetFrame(clump);
        if (frame)
        {
            TagData             tagData;

            tagData.tag = vertexTagMap[nI];
            tagData.frame = (RwFrame *)NULL;

            RwFrameForAllChildren(frame,
                                  FindTaggedFrame, (void *) &tagData);

            if (!tagData.frame)
            {
                linkData.closest = RwRealMAXVAL;
                linkData.invSkin = &mInverseSkin;
                linkData.skinGeom =
                    RpAtomicGetGeometry(FrameGetAtomic
                                        (RpClumpGetFrame(clump)));
                linkData.enclosingFrame = (RwFrame *)NULL;
                linkData.vertIndex = nI;

                /* link vertex to bone by proximity instead */
                RwFrameForAllChildren(RpClumpGetFrame(clump),
                                      LinkByProximity,
                                      (void *) &linkData);
                tagData.frame = linkData.enclosingFrame;
            }

            /* Link enclosing bone to this vertex */
            vertexBoneMap[nI] = BoneFrameGetBone(tagData.frame);
        }
    }

    geomData = (*RPBONEGEOMGETDATA(gpSkin));
    if (geomData)
    {
        if (!geomData->boneList)
        {
            geomData->boneList =
                BoneBuildBoneList(gpSkin, vertexBoneMap);
        }
    }

    RwFree(vertexBoneMap);

    RWRETURN(clump);
}

/**
 * \ingroup rpbone
 * \ref RpBoneClumpInitialize is used to setup the bones data for
 * the specified clump so that it can be manipulated as a skinned object.
 * The clump will usually consist of a single atomic with a geometry that
 * represents the `skin', together with a hierarchy of frames. A bone is
 * associated with each frame in the clump's frame hierarchy, which when
 * manipulated by a sequence of frame movements can be transferred to the
 * skin with a call to \ref RpBoneClumpSkinUpdate.  This function must be
 * called before attempting to animate a skinned clump.  The bones
 * plug-in must be attached before using this function.
 *
 * \param clump  pointer to the clump.
 *
 * \return a pointer to the clump if successful or NULL if there is an error.
 *
 * \see RpBoneClumpSkinUpdate
 * \see RpBoneGenerateBodyParts
 * \see RpBonePluginAttach
 */
RpClump            *
RpBoneClumpInitialize(RpClump * clump)
{
    RwFrame            *clumpFrame;
    RpAtomic           *skinAtomic;
    RpGeometry         *skinGeometry;
    RpMorphTarget      *morphTarget;
    RwV3d              *skinVertices;
    RwV3d              *skinNormals;
    RwInt32             numVertices;
    rpBoneGeomData     *geomData;

    RWAPIFUNCTION(RWSTRING("RpBoneClumpInitialize"));
    RWASSERT(BoneStatic.ModuleInfo.numInstances);
    RWASSERT(clump);

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

    /* Create a bone for every frame in the hierarchy */
    RwFrameForAllChildren(clumpFrame,
                          BonesCreateCallback, (void *) clumpFrame);

    /* Get the skins geometry */
    skinAtomic = FrameGetAtomic(clumpFrame);
    skinGeometry = skinAtomic ? RpAtomicGetGeometry(skinAtomic) : (RpGeometry *)NULL;

    if (!skinGeometry)
    {
        RWRETURN((RpClump *)NULL);
    }

    morphTarget = RpGeometryGetMorphTarget(skinGeometry, 0);
    skinVertices = RpMorphTargetGetVertices(morphTarget);
    skinNormals = RpMorphTargetGetVertexNormals(morphTarget);

    if (!skinVertices)
    {
        RWRETURN((RpClump *)NULL);
    }

    numVertices = RpGeometryGetNumVertices(skinGeometry);

    geomData = *RPBONEGEOMGETDATA(skinGeometry);
    if (!geomData)
    {
        geomData = (rpBoneGeomData *)
            RwFreeListAlloc(BoneStatic.BoneGeomDataFreeList);
        *RPBONEGEOMGETDATA(skinGeometry) = geomData;
        geomData->boneList = (rpBoneList *)NULL;
        geomData->vertices = (RwV3d *)NULL;
        geomData->normals = (RwV3d *)NULL;
    }
    else
    {
        /* Initialise the bone pointers in the bonelist */
        BoneListInit(geomData->boneList, clumpFrame);
    }

    if (skinVertices)
    {
        /* Create copy of original skin vertices */
        geomData->vertices =
            (RwV3d *) RwMalloc(sizeof(RwV3d) * numVertices);
        if (!geomData->vertices)
        {
            RWRETURN((RpClump *)NULL);
        }

        memcpy(geomData->vertices, skinVertices,
               sizeof(RwV3d) * numVertices);
    }

    /* Copy over vertex normals */
    if (skinNormals)
    {
        geomData->normals =
            (RwV3d *) RwMalloc(sizeof(RwV3d) * numVertices);
        if (geomData->normals)
        {
            memcpy(geomData->normals, skinNormals,
                   sizeof(RwV3d) * numVertices);
        }
    }

    RWRETURN(clump);
}

/************************************************************************
 *                                                                      *
 * Function:   RpBodySkinUpdate                                         *
 * Purpose:    Apply bones to skin                                      *
 * On entry:   The body, optionally return bounding-box                 *
 * On exit:    Success?                                                 *
 *                                                                      *
 ************************************************************************/

/**
 * \ingroup rpbone
 * \ref RpBoneClumpSkinUpdate is used to recalculate the
 * specified clump's skin geometry. Typically used to reflect the new
 * positions and orientations of the clump's bones following an animation
 * update.  The bones plug-in must be attached before using this
 * function.
 *
 * \param clump  pointer to the clump.
 *
 * \return TRUE if successful or FALSE if there is an error.
 *
 * \see RpBoneClumpInitialize
 * \see RpBoneGenerateBodyParts
 * \see RpBonePluginAttach
 */
RwBool
RpBoneClumpSkinUpdate(RpClump * clump)
{
    RwFrame            *clumpFrame;
    RpAtomic           *skinAtomic;
    RwMatrix            mInverseSkinMatrix;
    RpGeometry         *gpSkinGeometry;
    RpMorphTarget      *morphTarget;
    RwSphere            boundingSphere;
    RwV3d              *vpSkinVertices, *vpSkinNormals;
    RpBoneUpdateData    parentBoneData;

    RWAPIFUNCTION(RWSTRING("RpBoneClumpSkinUpdate"));
    RWASSERT(BoneStatic.ModuleInfo.numInstances);
    RWASSERT(clump);

    /* Get skin atomic */
    clumpFrame = RpClumpGetFrame(clump);
    if (!clumpFrame)
    {
        RWRETURN(FALSE);
    }

    skinAtomic = FrameGetSkinAtomic(clumpFrame);
    if (!skinAtomic)
    {
        RWRETURN(FALSE);
    }

    /* Get skin inverse matrix */
    RwMatrixSetIdentity(&mInverseSkinMatrix);
    /* DONT use RwFrameGetMatrix here as we dont want RwFrameUpdateObjects called */
    RwMatrixInvert(&mInverseSkinMatrix,
                   &RpAtomicGetFrame(skinAtomic)->modelling);

    /* Get skin vertices */
    gpSkinGeometry = RpAtomicGetGeometry(skinAtomic);
    morphTarget = RpGeometryGetMorphTarget(gpSkinGeometry, 0);
    vpSkinVertices = RpMorphTargetGetVertices(morphTarget);
    vpSkinNormals = RpMorphTargetGetVertexNormals(morphTarget);
    if (!vpSkinVertices)
    {
        RWRETURN(FALSE);
    }

    /* Compute matrices to apply to original skin vertices for each bone */
    /* Also perculate skin-dirty status to child bones */
    /* Skip root atomic as this is not a bone */

    parentBoneData.inverseSkinMatrix = &mInverseSkinMatrix;
    parentBoneData.parentFlags = rpBONEFLAGNONE;

    RwFrameForAllChildren(RpClumpGetFrame(clump),
                          BoneClumpSkinUpdateCallback,
                          (void *) &parentBoneData);

    /* Lock geometry */
    if (RpGeometryLock
        (gpSkinGeometry,
         rpGEOMETRYLOCKVERTICES | rpGEOMETRYLOCKNORMALS))
    {
        RwBBox              bbBBox;
        rpBoneList         *boneList;
        RwV3d              *srceVerts;
        RwV3d              *srceNorms;
        rpBoneGeomData     *geomData;

        geomData = *RPBONEGEOMGETDATA(gpSkinGeometry);

        boneList = geomData->boneList;
        srceVerts = geomData->vertices;
        srceNorms = geomData->normals;

        /* We already built a list telling us how to do this, so do it...! */
        /* Apply bones to skin (move vertices linked to bone using its skin matrix) */
        if (vpSkinNormals && srceNorms && boneList)
        {
            while (boneList->length)
            {
                if (boneList->bone
                    && (boneList->bone->flags & rpBONEFLAGSKINWASDIRTY))
                {
                    RwMatrix           *boneMatrix =
                        &boneList->bone->skinMatrix;
                    RwInt32             length = boneList->length;

                    RwV3dTransformPoints(vpSkinVertices +
                                         boneList->start,
                                         srceVerts + boneList->start,
                                         length, boneMatrix);
                    RwV3dTransformVectors(vpSkinNormals +
                                          boneList->start,
                                          srceNorms + boneList->start,
                                          length, boneMatrix);
                }
                boneList++;
            }
        }
        else
        {
            if (boneList)
            {
                while (boneList->length)
                {
                    if (boneList->bone->flags & rpBONEFLAGSKINWASDIRTY)
                    {
                        RwMatrix           *boneMatrix =
                            &boneList->bone->skinMatrix;
                        RwInt32             length = boneList->length;

                        RwV3dTransformPoints(vpSkinVertices +
                                             boneList->start,
                                             srceVerts +
                                             boneList->start, length,
                                             boneMatrix);
                    }
                    boneList++;
                }
            }
        }

        /* Find the bounding box, so we can figure out a bounding sphere */
        RwBBoxCalculate(&bbBBox, vpSkinVertices,
                        RpGeometryGetNumVertices(gpSkinGeometry));

        /* Calc new bounding sphere for morph target and atomic
         * (encloses corners of bbox) */
        RwV3dSub(&boundingSphere.center, &bbBBox.sup, &bbBBox.inf);
        RwV3dScale(&boundingSphere.center, &boundingSphere.center,
                   (RwReal) (0.5));
        boundingSphere.radius = RwV3dLength(&boundingSphere.center);
        RwV3dAdd(&boundingSphere.center, &boundingSphere.center,
                 &bbBBox.inf);

        RpMorphTargetSetBoundingSphere(morphTarget, &boundingSphere);
        skinAtomic->boundingSphere = boundingSphere;
        rwObjectSetPrivateFlags(skinAtomic,
                                rwObjectGetPrivateFlags(skinAtomic) |
                                rpATOMICPRIVATEWORLDBOUNDDIRTY);

        /* Unlock geometry */
        RpGeometryUnlock(gpSkinGeometry);

        RWRETURN(TRUE);
    }

    RWRETURN(FALSE);
}

/************************************************************************
 *                                                                      *
 * Function:   RpBoneGenerateBodyParts                                  *
 * Purpose:    Split skin into smaller skin and limb atomics            *
 * On entry:   The skin clump                                           *
 * On exit:    Success?                                                 *
 *                                                                      *
 ************************************************************************/

/**
 * \ingroup rpbone
 * \ref RpBoneGenerateBodyParts is used to split the `skin'
 * geometry defined in the specified clump into a number of smaller skins
 * and limb atomics.  The bones plug-in must be attached before using
 * this function.
 *
 * \param clump  pinter to the clump.
 *
 * \return TRUE if successful or FALSE if there is an error.
 *
 * \see RpBoneClumpInitialize
 * \see RpBoneClumpSkinUpdate
 * \see RpBonePluginAttach
 */
RwBool
RpBoneGenerateBodyParts(RpClump * clump)
{
    RpAtomic           *apSkinAtomic, *newAtomic;
    RpGeometry         *gpSkinGeom, *newGeom;
    RpTriangle         *tpSkinTris, *newTris;
    rpBoneGeomData     *geomData;
    rpBoneList         *boneList, *newBoneList;
    RwInt32             currBoneStart = 0, currBoneEnd = 0;
    RwInt32             newNumTris, i, j, newNumVerts;
    RwBool             *trisDone, *boneUsed;
    RwInt32            *vertMap, vertMapIndex, newTriIndex;
    RwInt32             numBones, boneUsedCount, boneIndex;
    RwInt32             mtIndex, oldNumSkinVerts;

    RWAPIFUNCTION(RWSTRING("RpBoneGenerateBodyParts"));
    RWASSERT(BoneStatic.ModuleInfo.numInstances);
    RWASSERT(clump);

    /* Get skin atomic */
    apSkinAtomic = FrameGetAtomic(RpClumpGetFrame(clump));
    gpSkinGeom = RpAtomicGetGeometry(apSkinAtomic);
    tpSkinTris = RpGeometryGetTriangles(gpSkinGeom);

    /* We're going to need the bone list and orig verts + normals */
    geomData = *RPBONEGEOMGETDATA(gpSkinGeom);
    /* sanity check the bonelist, bones and frames */
    boneList = geomData->boneList;
    do
    {
        if (!boneList->bone)
        {
            RWERROR((E_RW_NULLP));
            RWRETURN(FALSE);
        }
        boneList++;
    }
    while (boneList->length != 0);
    boneList = geomData->boneList;

    /* setup arrays to decide undealt with stuff (joins) */
    trisDone =
        (RwBool *) RwMalloc(sizeof(RwBool) * gpSkinGeom->numTriangles);
    for (i = 0; i < gpSkinGeom->numTriangles; i++)
    {
        trisDone[i] = FALSE;
    }
    vertMap =
        (RwInt32 *) RwMalloc(sizeof(RwInt32) * gpSkinGeom->numVertices);

    RwFrameUpdateObjects(RpClumpGetFrame(clump));
    numBones = 0;

    do
    {
        numBones++;
        currBoneStart = boneList->start;
        currBoneEnd = boneList->start + boneList->length - 1;
        for (i = 0; i < gpSkinGeom->numVertices; i++)
        {
            vertMap[i] = -1;
        }
        newNumVerts = 0;
        newNumTris = 0;
        for (i = 0; i < gpSkinGeom->numTriangles; i++)
        {
            if (tpSkinTris[i].vertIndex[0] >= currBoneStart &&
                tpSkinTris[i].vertIndex[0] <= currBoneEnd &&
                tpSkinTris[i].vertIndex[1] >= currBoneStart &&
                tpSkinTris[i].vertIndex[1] <= currBoneEnd &&
                tpSkinTris[i].vertIndex[2] >= currBoneStart &&
                tpSkinTris[i].vertIndex[2] <= currBoneEnd)
            {
                RwUInt16            j;

                newNumTris++;
                /* mark the tri as done, and tag it as this bone */
                trisDone[i] = numBones;

                /* mark these 3 verts as used */
                for (j = 0; j < 3; j++)
                {
                    if (vertMap[tpSkinTris[i].vertIndex[j]] == -1)
                    {
                        vertMap[tpSkinTris[i].vertIndex[j]] =
                            newNumVerts;
                        newNumVerts++;
                    }
                }
            }
        }

        if (newNumTris > 0)
        {
            newAtomic = RpAtomicCreate();
            newGeom = RpGeometryCreate(newNumVerts, newNumTris,
                                       RpGeometryGetFlags(gpSkinGeom));
            newTris = RpGeometryGetTriangles(newGeom);

            for (i = 0; i < gpSkinGeom->numVertices; i++)
            {
                if (vertMap[i] != -1)
                {
                    if (RpGeometryGetFlags(gpSkinGeom) &
                        rpGEOMETRYTEXTURED)
                    {
                        newGeom->texCoords[0][vertMap[i]] =
                            gpSkinGeom->texCoords[0][i];
                    }
                    if (RpGeometryGetFlags(gpSkinGeom) &
                        rpGEOMETRYPRELIT)
                    {
                        newGeom->preLitLum[vertMap[i]] =
                            gpSkinGeom->preLitLum[i];
                    }
                }
            }

            for (mtIndex = 0;
                 mtIndex < RpGeometryGetNumMorphTargets(gpSkinGeom);
                 mtIndex++)
            {
                RpMorphTarget      *oldMorphTarget, *newMorphTarget;
                RwV3d              *srcVerts, *dstVerts;
                RwV3d              *srcNorms, *dstNorms;
                RwSphere            newBoundingSphere;

                if (mtIndex > 0)
                {
                    RpGeometryAddMorphTarget(newGeom);
                }

                newMorphTarget =
                    RpGeometryGetMorphTarget(newGeom, mtIndex);
                oldMorphTarget =
                    RpGeometryGetMorphTarget(gpSkinGeom, mtIndex);

                if (mtIndex == 0)
                {
                    srcVerts = geomData->vertices;
                    srcNorms = geomData->normals;
                }
                else
                {
                    srcVerts = oldMorphTarget->verts;
                    srcNorms = oldMorphTarget->normals;
                }
                dstVerts = newMorphTarget->verts;
                dstNorms = newMorphTarget->normals;

                for (i = 0; i < gpSkinGeom->numVertices; i++)
                {
                    if (vertMap[i] != -1)
                    {
                        RwV3dTransformPoints(&dstVerts[vertMap[i]],
                                             &srcVerts[i],
                                             1,
                                             &boneList->
                                             bone->skinToBone);
                        if (geomData->normals)
                        {
                            RwV3dTransformVectors(&dstNorms
                                                  [vertMap[i]],
                                                  &srcNorms[i], 1,
                                                  &boneList->
                                                  bone->skinToBone);
                        }
                    }
                }

                RpMorphTargetCalcBoundingSphere(newMorphTarget,
                                                &newBoundingSphere);
                RpMorphTargetSetBoundingSphere(newMorphTarget,
                                               &newBoundingSphere);
            }

            j = 0;
            for (i = 0; i < gpSkinGeom->numTriangles; i++)
            {
                if (trisDone[i] == numBones)
                {
                    newTris[j].vertIndex[0] =
                        (RwUInt16) vertMap[tpSkinTris[i].vertIndex[0]];
                    newTris[j].vertIndex[1] =
                        (RwUInt16) vertMap[tpSkinTris[i].vertIndex[1]];
                    newTris[j].vertIndex[2] =
                        (RwUInt16) vertMap[tpSkinTris[i].vertIndex[2]];

                    RpGeometryTriangleSetMaterial(newGeom,
                                                  &newTris[j],
                                                  RpGeometryTriangleGetMaterial
                                                  (gpSkinGeom,
                                                   &tpSkinTris[i]));
                    j++;
                }
            }
            RpGeometryUnlock(newGeom);
            RpAtomicSetGeometry(newAtomic, newGeom, 0);
            RpAtomicSetFrame(newAtomic, boneList->bone->frame);
            RpClumpAddAtomic(clump, newAtomic);
        }
        boneList++;
    }
    while (boneList->length != 0);

    /* sort out joining tris here */
    for (i = 0; i < gpSkinGeom->numVertices; i++)
    {
        vertMap[i] = -2;
    }

    /* firstly just mark which verts are needed */
    newNumTris = 0;
    for (i = 0; i < gpSkinGeom->numTriangles; i++)
    {
        if (!trisDone[i])
        {
            newNumTris++;
            vertMap[tpSkinTris[i].vertIndex[0]] = -1;
            vertMap[tpSkinTris[i].vertIndex[1]] = -1;
            vertMap[tpSkinTris[i].vertIndex[2]] = -1;
        }
    }
    /* now setup the new vert array and calculate mappings and build a new bonelist */
    boneUsed = (RwBool *) RwMalloc(sizeof(RwBool) * numBones);
    for (i = 0; i < numBones; i++)
    {
        boneUsed[i] = FALSE;
    }
    boneList = geomData->boneList;
    boneUsedCount = 0;
    for (i = 0; i < numBones; i++)
    {
        for (j = 0;
             j < ((RwInt32) boneList[i].length) && !boneUsed[i]; j++)
        {
            if (vertMap[boneList[i].start + j] == -1)
            {
                boneUsed[i] = TRUE;
                boneUsedCount++;
            }
        }
    }
    /* Allocate the new bonelist */
    newBoneList =
        (rpBoneList *) RwMalloc(sizeof(rpBoneList) *
                                (boneUsedCount + 1));
    vertMapIndex = 0;
    boneIndex = 0;
    for (i = 0; i < numBones; i++)
    {
        if (boneUsed[i])
        {
            newBoneList[boneIndex].start = vertMapIndex;
            newBoneList[boneIndex].bone = boneList[i].bone;
            newBoneList[boneIndex].boneTag = boneList[i].boneTag;
            for (j = 0; j < ((RwInt32) boneList[i].length); j++)
            {
                if (vertMap[boneList[i].start + j] == -1)
                {
                    vertMap[boneList[i].start + j] = vertMapIndex;
                    vertMapIndex++;
                }
            }
            newBoneList[boneIndex].length = vertMapIndex -
                newBoneList[boneIndex].start;
            boneIndex++;
        }
    }
    newBoneList[boneIndex].length = 0;
    RwFree(geomData->boneList);
    geomData->boneList = newBoneList;

    /* now copy the appropriate triangles and remap the vert indices */
    newTris = (RpTriangle *) RwMalloc(sizeof(RpTriangle) * newNumTris);
    newTriIndex = 0;
    for (i = 0; i < gpSkinGeom->numTriangles; i++)
    {
        if (!trisDone[i])
        {
            newTris[newTriIndex] = tpSkinTris[i];
            newTris[newTriIndex].vertIndex[0] = (RwUInt16)
                vertMap[newTris[newTriIndex].vertIndex[0]];
            newTris[newTriIndex].vertIndex[1] = (RwUInt16)
                vertMap[newTris[newTriIndex].vertIndex[1]];
            newTris[newTriIndex].vertIndex[2] = (RwUInt16)
                vertMap[newTris[newTriIndex].vertIndex[2]];
            newTriIndex++;
        }
    }

    RpGeometryLock(gpSkinGeom, rpGEOMETRYLOCKALL);
    RwFree(gpSkinGeom->triangles);
    gpSkinGeom->triangles = (RpTriangle *) newTris;
    gpSkinGeom->numTriangles = newNumTris;

    {

        RwTexCoords        *newVertTexCoords = (RwTexCoords *)NULL;
        RwRGBA             *newPreLitLum = (RwRGBA *)NULL;

        if (RpGeometryGetFlags(gpSkinGeom) & rpGEOMETRYTEXTURED)
        {
            newVertTexCoords = (RwTexCoords *)
                RwMalloc(sizeof(RwTexCoords) * vertMapIndex);
        }
        if (RpGeometryGetFlags(gpSkinGeom) & rpGEOMETRYPRELIT)
        {
            newPreLitLum =
                (RwRGBA *) RwMalloc(sizeof(RwRGBA) * vertMapIndex);
        }

        for (i = 0; i < gpSkinGeom->numVertices; i++)
        {
            if (vertMap[i] >= 0)
            {
                if (RpGeometryGetFlags(gpSkinGeom) & rpGEOMETRYTEXTURED)
                {
                    newVertTexCoords[vertMap[i]] =
                        gpSkinGeom->texCoords[0][i];
                }
                if (RpGeometryGetFlags(gpSkinGeom) & rpGEOMETRYPRELIT)
                {
                    newPreLitLum[vertMap[i]] = gpSkinGeom->preLitLum[i];
                }
            }
        }

        if (RpGeometryGetFlags(gpSkinGeom) & rpGEOMETRYTEXTURED)
        {
            RwFree(gpSkinGeom->texCoords[0]);
            gpSkinGeom->texCoords[0] = newVertTexCoords;
        }
        if (RpGeometryGetFlags(gpSkinGeom) & rpGEOMETRYPRELIT)
        {
            RwFree(gpSkinGeom->preLitLum);
            gpSkinGeom->preLitLum = newPreLitLum;
        }
    }

    oldNumSkinVerts = gpSkinGeom->numVertices;
    gpSkinGeom->numVertices = vertMapIndex;
    for (mtIndex = 0;
         mtIndex < RpGeometryGetNumMorphTargets(gpSkinGeom); mtIndex++)
    {
        RwV3d              *newVerts = (RwV3d *)NULL;
        RwV3d              *newNormals = (RwV3d *)NULL;
        RpMorphTarget      *morphTarget =
            RpGeometryGetMorphTarget(gpSkinGeom, mtIndex);
        RwSphere            newBoundingSphere;

        if (morphTarget->normals)
        {
            newVerts =
                (RwV3d *) RwMalloc(sizeof(RwV3d) * vertMapIndex * 2);
            newNormals =
                ((RwV3d *) (((RwUInt8 *) newVerts) +
                            (sizeof(RwV3d) * vertMapIndex)));
        }
        else
        {
            newVerts = (RwV3d *) RwMalloc(sizeof(RwV3d) * vertMapIndex);
            newNormals = (RwV3d *)NULL;
        }

        for (i = 0; i < oldNumSkinVerts; i++)
        {
            if (vertMap[i] >= 0)
            {
                newVerts[vertMap[i]] = morphTarget->verts[i];
                if (morphTarget->normals)
                {
                    newNormals[vertMap[i]] = morphTarget->normals[i];
                }
            }
        }
        RwFree(morphTarget->verts);
        morphTarget->verts = newVerts;
        morphTarget->normals = newNormals;
        RpMorphTargetCalcBoundingSphere(morphTarget,
                                        &newBoundingSphere);
        RpMorphTargetSetBoundingSphere(morphTarget, &newBoundingSphere);
    }

    RwFree(geomData->vertices);
    geomData->vertices = (RwV3d *)
        RwMalloc(sizeof(RwV3d) * gpSkinGeom->numVertices);
    memcpy(geomData->vertices,
           gpSkinGeom->morphTarget->verts,
           sizeof(RwV3d) * gpSkinGeom->numVertices);
    if (geomData->normals)
    {
        RwFree(geomData->normals);
        geomData->normals = (RwV3d *)
            RwMalloc(sizeof(RwV3d) * gpSkinGeom->numVertices);
        memcpy(geomData->normals,
               gpSkinGeom->morphTarget->normals,
               sizeof(RwV3d) * gpSkinGeom->numVertices);
    }

    RpGeometryUnlock(gpSkinGeom);
    RwFree(trisDone);
    RwFree(vertMap);
    RwFree(boneUsed);
    RWRETURN(TRUE);
}

/************************************************************************
 *                                                                      *
 * Function:   RpBonesPluginAttach                                      *
 * Purpose:    Attach atomic bones plugin to Renderware                 *
 * On entry:   void                                                     *
 * On exit:    Success status                                           *
 *                                                                      *
 ************************************************************************/

/**
 * \ingroup rpbone
 * \ref RpBonePluginAttach is used to attach the bones plug-in to
 * the RenderWare system to enable the manipulation of skinned
 * clumps. The plug-in must be attached between initializing the system
 * with \ref RwEngineInit and opening it with \ref RwEngineOpen.  Note that the
 * bones plug-in requires the world plug-in to be attached. The include
 * file rpbone.h is also required and must be included by an application
 * wishing to use this plug-in.
 *
 * \return TRUE if successful or FALSE if there is an error.
 *
 * \see RwEngineInit
 * \see RwEngineOpen
 * \see RwEngineStart
 * \see RpWorldPluginAttach
 */
RwBool
RpBonePluginAttach(void)
{
    RWAPIFUNCTION(RWSTRING("RpBonePluginAttach"));
    if (RwEngineRegisterPlugin(0,
                               rwID_BONEPLUGIN,
                               BoneOpen, BoneClose) < 0)
    {
        RWRETURN(FALSE);
    }

    /* Extend geometry to hold a bonelist */
    RpBoneGeomBoneListOffset =
        RpGeometryRegisterPlugin(sizeof(rpBoneGeomData *),
                                 (RwUInt32) rwID_BONEPLUGIN,
                                 BoneGeomConstructor,
                                 BoneGeomDestructor, BoneGeomCopy);
    if (RpBoneGeomBoneListOffset < 0)
    {
        RWRETURN(FALSE);
    }

    /* Read/Write bone list */
    BoneStatic.GeomBoneListStreamOffset =
        RpGeometryRegisterPluginStream((RwUInt32)
                                       rwID_BONEPLUGIN,
                                       BoneGeomRead,
                                       BoneGeomWrite, BoneGeomGetSize);
    if (BoneStatic.GeomBoneListStreamOffset < 0)
    {
        RWRETURN(FALSE);
    }

    /* Extend frame to hold a bone */
    BoneStatic.FrameDataOffset =
        RwFrameRegisterPlugin(sizeof(rpBone *),
                              (RwUInt32) rwID_BONEPLUGIN,
                              BoneFrameDataConstructor,
                              BoneFrameDataDestructor,
                              BoneFrameDataCopy);
    if (BoneStatic.FrameDataOffset < 0)
    {
        RWRETURN(FALSE);
    }

    /* Hurrah */
    RWRETURN(TRUE);
}
