/*===========================================================================*
 *--- Include files ---------------------------------------------------------*
 *===========================================================================*/
#include <d3d8.h>

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

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

#include "rpskin.h"

#include "skin.h"

#if !defined( NOASM )

#include "x86matbl.h"

#endif /* !defined( NOASM ) */


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

/*===========================================================================*
 *--- Private Global Variables ----------------------------------------------*
 *===========================================================================*/
static RxPipeline   *SkinAtomicPipe = NULL;

/*===========================================================================*
 *--- Private Defines -------------------------------------------------------*
 *===========================================================================*/
#define NUMCLUSTERSOFINTEREST   0
#define NUMOUTPUTS              0

#define FLOATASINT(f) (*((const RwInt32 *)&(f)))

#define MATRIXPROCESSX(m, o, w)                                     \
    ( ( (m->right.x * o.x) +                                        \
        (m->up.x    * o.y) +                                        \
        (m->at.x    * o.z) +                                        \
        (m->pos.x) ) * w )

#define MATRIXPROCESSY(m, o, w)                                     \
    ( ( (m->right.y * o.x) +                                        \
        (m->up.y    * o.y) +                                        \
        (m->at.y    * o.z) +                                        \
        (m->pos.y) ) * w )

#define MATRIXPROCESSZ(m, o, w)                                     \
    ( ( (m->right.z * o.x) +                                        \
        (m->up.z    * o.y) +                                        \
        (m->at.z    * o.z) +                                        \
        (m->pos.z) ) * w )

#define MATRIXSKIN(v, m, o, w)                                      \
MACRO_START                                                         \
{                                                                   \
    v->x = MATRIXPROCESSX( m, o, w );                                \
    v->y = MATRIXPROCESSY( m, o, w );                                \
    v->z = MATRIXPROCESSZ( m, o, w );                                \
}                                                                   \
MACRO_STOP

#define MATRIXPLUSSKIN(v, m, o, w)                                  \
MACRO_START                                                         \
{                                                                   \
    v->x += MATRIXPROCESSX( m, o, w );                               \
    v->y += MATRIXPROCESSY( m, o, w );                               \
    v->z += MATRIXPROCESSZ( m, o, w );                               \
}                                                                   \
MACRO_STOP

#define MATRIXNORMALPROCESSX(m, o, w)                               \
    ( ( (m->right.x * o.x) +                                        \
        (m->up.x    * o.y) +                                        \
        (m->at.x    * o.z) ) * w )

#define MATRIXNORMALPROCESSY(m, o, w)                               \
    ( ( (m->right.y * o.x) +                                        \
        (m->up.y    * o.y) +                                        \
        (m->at.y    * o.z) ) * w )

#define MATRIXNORMALPROCESSZ(m, o, w)                               \
    ( ( (m->right.z * o.x) +                                        \
        (m->up.z    * o.y) +                                        \
        (m->at.z    * o.z) ) * w )

#define MATRIXNORMALSKIN(n, m, o, w)                                \
MACRO_START                                                         \
{                                                                   \
    n->x = MATRIXNORMALPROCESSX( m, o, w );                          \
    n->y = MATRIXNORMALPROCESSY( m, o, w );                          \
    n->z = MATRIXNORMALPROCESSZ( m, o, w );                          \
}                                                                   \
MACRO_STOP

#define MATRIXNORMALPLUSSKIN(n, m, o, w)                            \
MACRO_START                                                         \
{                                                                   \
    n->x += MATRIXNORMALPROCESSX( m, o, w );                         \
    n->y += MATRIXNORMALPROCESSY( m, o, w );                         \
    n->z += MATRIXNORMALPROCESSZ( m, o, w );                         \
}                                                                   \
MACRO_STOP


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

/*===========================================================================*
 *--- Local Global Variables ------------------------------------------------*
 *===========================================================================*/
static const char rcsid[] __RWUNUSED__ = "";

SkinGlobals _rpSkinGlobals =
{
    0,
    0,
    0,
    { (RwMatrix *)NULL, NULL },
    (RwFreeList *)NULL,
    { 0, 0 },
    {                                 /* SkinGlobalPlatform  platform    */
        0
    }
};

/*===========================================================================*
 *--- Local Defines ---------------------------------------------------------*
 *===========================================================================*/

/*===========================================================================*
 *--- Local functions -------------------------------------------------------*
 *===========================================================================*/

/****************************************************************************
 - SkinMatBlendAtomicRender

 - Matrix blending Atomic render function - performs weighted transform of
   an atomic's vertices according to the attached RpSkin data.

 - If NOASM=0 then an Intel x86 assembler function is used for matrix blending.
   Otherwise...

 - Inputs :   RpAtomic *    A pointer to the atomic.
 - Outputs:   RwBool        TRUE on success
 */
static RwBool
SkinMatBlendAtomicRender(RpAtomic *atomic)
{
    RpGeometry *geometry;
    RpSkin *skin;

    RWFUNCTION(RWSTRING("SkinMatBlendAtomicRender"));
    RWASSERT(NULL != atomic);

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

    skin = *RPSKINGEOMETRYGETDATA(geometry);

    if(NULL != skin)
    {
        RpMeshHeader        *meshHeader;
        RpMesh              *mesh;
        RwUInt32            numMeshes;

        RwResEntry          *resEntry;
        RxD3D8ResEntryHeader *resEntryHeader;
        RxD3D8InstanceData  *instancedData;

        RwUInt32            numTextureCoords;

        const RpMorphTarget *morphTarget;

        /* Get mesh info */
        meshHeader = geometry->mesh;

        /* Get number of texture coordinates */
        numTextureCoords = RpGeometryGetNumTexCoordSets(geometry);

        /* Get morph target */
        morphTarget = &(geometry->morphTarget[0]);
        RWASSERT(NULL != morphTarget);

        /* Get instanced data */
        resEntry = geometry->repEntry;
        resEntryHeader = (RxD3D8ResEntryHeader *)(resEntry + 1);
        instancedData = (RxD3D8InstanceData *)(resEntryHeader + 1);

        mesh = (RpMesh *)(meshHeader + 1);
        numMeshes = meshHeader->numMeshes;

        while (numMeshes--)
        {
            RwUInt8 *vertexData;

            /* Lock the vertex buffer */
            if (SUCCEEDED(IDirect3DVertexBuffer8_Lock((LPDIRECT3DVERTEXBUFFER8)instancedData->vertexBuffer,
                         instancedData->baseIndex * instancedData->stride,
                         instancedData->numVertices * instancedData->stride, &vertexData,
                         D3DLOCK_NOSYSLOCK)))
            {
                const RwV3d *originalVertices;
                const RwV3d *originalNormals;

                RpHAnimHierarchy    *hierarchy;
                const RwMatrix *skinToBone;
                RwMatrix *matrixArray = NULL;

                RwUInt8 *vertices;
                RwUInt8 *normals;

                RwInt32 i;

                /* Get original vertex information */
                originalVertices = (const RwV3d *)(&morphTarget->verts[instancedData->minVert]);

                if (rwObjectTestFlags(geometry, rpGEOMETRYNORMALS))
                {
                    originalNormals = (const RwV3d *)(&morphTarget->normals[instancedData->minVert]);
                }
                else
                {
                    originalNormals = NULL;
                }

                /* Find Destination offset */
                vertices = vertexData;

                if (originalNormals != NULL)
                {
                    normals = vertexData + sizeof(RwV3d);
                }
                else
                {
                    normals = NULL;
                }

                /* Get the bone information. */
                skinToBone = RpSkinGetSkinToBoneMatrices(skin);
                RWASSERT(NULL != skinToBone);

                /* Perform matrix blending */
                hierarchy = RPSKINATOMICGETDATA(atomic)->hierarchy;

                if(NULL != hierarchy)
                {
                    matrixArray = _rpSkinGlobals.matrixCache.aligned;

                    if(hierarchy->flags & rpHANIMHIERARCHYNOMATRICES)
                    {
                        RwFrame *frame;
                        RwMatrix *ltm;

                        RwMatrix inverseAtomicLTM;
                        RwMatrix temmatrix;
                        RwInt32 i;

                        frame = RpAtomicGetFrame(atomic);
                        RWASSERT(NULL != frame);
                        ltm = RwFrameGetLTM(frame);
                        RWASSERT(NULL != ltm);

                        RwMatrixInvert(&inverseAtomicLTM, ltm);

                        for( i = 0; i < hierarchy->numNodes; i++ )
                        {
                            RwFrame *node;
                            RwMatrix *ltm;

                            node = hierarchy->pNodeInfo[i].pFrame;
                            RWASSERT(NULL != node);
                            ltm = RwFrameGetLTM(node);
                            RWASSERT(NULL != ltm);

                            RwMatrixMultiply( &temmatrix,
                                              &skinToBone[i],
                                              ltm );
                            RwMatrixMultiply( &matrixArray[i],
                                              &temmatrix,
                                              &inverseAtomicLTM );
                        }
                    }
                    else if(hierarchy->flags & rpHANIMHIERARCHYLOCALSPACEMATRICES)
                    {
                        for( i = 0; i < hierarchy->numNodes; i++ )
                        {
                            RwMatrixMultiply( &matrixArray[i],
                                              &skinToBone[i],
                                              &(hierarchy->pMatrixArray[i]) );
                        }
                    }
                    else
                    {
                        RwMatrix inverseAtomicLTM;
                        RwMatrix temmatrix;

                        RwMatrixInvert(&inverseAtomicLTM,
                                       RwFrameGetLTM(RpAtomicGetFrame
                                                     (atomic)));

                        for( i = 0; i < hierarchy->numNodes; i++)
                        {
                            RwMatrixMultiply( &temmatrix,
                                              &skinToBone[i],
                                              &(hierarchy->pMatrixArray[i]) );
                            RwMatrixMultiply( &matrixArray[i],
                                              &temmatrix,
                                              &inverseAtomicLTM );
                        }
                    }
                }

                /* Vertex blending */
#if !defined( NOASM )
        _rpSkinIntelx86MatrixBlend( instancedData->numVertices,
                                    skin->vertexMaps.matrixWeights + instancedData->minVert,
                                    skin->vertexMaps.matrixIndices + instancedData->minVert,
                                    matrixArray,
                                    (RwV3d *)vertices,
                                    originalVertices,
                                    (RwV3d *)normals,
                                    originalNormals,
                                    instancedData->stride);

#else /* !defined( NOASM ) */
                for ( i = 0;
                    i < instancedData->numVertices;
                    i++,
                    vertices += instancedData->stride,
                    normals += instancedData->stride )
                {
                    const RwUInt32 matrixIndices = skin->vertexMaps.matrixIndices[i + instancedData->minVert];
                    const RwMatrixWeights *matrixWeights = &(skin->vertexMaps.matrixWeights[i + instancedData->minVert]);
                    const RwMatrix *matrix;

                    /* Hideously slow matrix operations follow... */
                    if (FLOATASINT(matrixWeights->w0) > 0)
                    {
                        matrix = &matrixArray[matrixIndices & 0xFF];

                        MATRIXSKIN( ((RwV3d *)vertices),
                                    matrix,
                                    originalVertices[i],
                                    matrixWeights->w0 );

                        if(NULL != originalNormals)
                        {
                            MATRIXNORMALSKIN( ((RwV3d *)normals),
                                              matrix,
                                              originalNormals[i],
                                              matrixWeights->w0 );
                        }
                    }
                    else
                    {
                        continue;
                    }

                    if (FLOATASINT(matrixWeights->w1) > 0)
                    {
                        matrix = &matrixArray[(matrixIndices >> 8) & 0xFF];

                        MATRIXPLUSSKIN( ((RwV3d *)vertices),
                                        matrix,
                                        originalVertices[i],
                                        matrixWeights->w1 );

                        if(NULL != originalNormals)
                        {
                            MATRIXNORMALPLUSSKIN( ((RwV3d *)normals),
                                                  matrix,
                                                  originalNormals[i],
                                                  matrixWeights->w1 );
                        }
                    }
                    else
                    {
                        continue;
                    }

                    if (FLOATASINT(matrixWeights->w2) > 0)
                    {
                        matrix = &matrixArray[(matrixIndices >> 16) & 0xFF];

                        MATRIXPLUSSKIN( ((RwV3d *)vertices),
                                        matrix,
                                        originalVertices[i],
                                        matrixWeights->w2 );

                        if(NULL != originalNormals)
                        {
                            MATRIXNORMALPLUSSKIN( ((RwV3d *)normals),
                                                  matrix,
                                                  originalNormals[i],
                                                  matrixWeights->w2 );
                        }
                    }
                    else
                    {
                        continue;
                    }

                    if (FLOATASINT(matrixWeights->w3) > 0)
                    {
                        matrix = &matrixArray[(matrixIndices >> 24) & 0xFF];

                        MATRIXPLUSSKIN( ((RwV3d *)vertices),
                                        matrix,
                                        originalVertices[i],
                                        matrixWeights->w3 );

                        if(NULL != originalNormals)
                        {
                            MATRIXNORMALPLUSSKIN( ((RwV3d *)normals),
                                                  matrix,
                                                  originalNormals[i],
                                                  matrixWeights->w3 );
                        }
                    }
                    else
                    {
                        continue;
                    }
                }
#endif /* !defined( NOASM ) */

                IDirect3DVertexBuffer8_Unlock((LPDIRECT3DVERTEXBUFFER8)instancedData->vertexBuffer);
            }

            instancedData++;
            mesh++;
        }
    }

    RWRETURN(TRUE);
}

/****************************************************************************
 _rxD3D8SkinAtomicInstance

 Purpose:   Just create the vertex buffer

 On entry:  

 On exit :
*/
static RwBool
_rxD3D8SkinAtomicInstance(const RpAtomic *atomic, RxD3D8InstanceData *instancedData)
{
    const RpGeometry    *geometry;
    RpGeometryFlag      flags;
    RwUInt32            vbSize;
    RwUInt32            fvfFlags;
    RwUInt32            numTextureCoords;
    RwUInt8             *vertexData;

    RWFUNCTION(RWSTRING("_rxD3D8SkinAtomicInstance"));

    geometry = (const RpGeometry *)RpAtomicGetGeometry(atomic);
    flags = RpGeometryGetFlags(geometry);

    /* Get number of texture coordinates */
    numTextureCoords = RpGeometryGetNumTexCoordSets(geometry);

    /*
     * Calculate the stride of the vertex if set to the default value of -1
     */
    /* Positions */
    instancedData->stride = sizeof(RwV3d);
    fvfFlags = D3DFVF_XYZ;

    /* Normals */
    if (flags & rxGEOMETRY_NORMALS)
    {
        instancedData->stride += sizeof(RwV3d);
        fvfFlags |= D3DFVF_NORMAL;
    }

    /* Pre-lighting */
    if (flags & rxGEOMETRY_PRELIT)
    {
        instancedData->stride += sizeof(RwRGBA);
        fvfFlags |= D3DFVF_DIFFUSE;
    }

    /* Texture coordinates */
    switch(numTextureCoords)
    {
        case 8:
            instancedData->stride += sizeof(RwTexCoords);
            fvfFlags |= (D3DFVF_TEX8 | D3DFVF_TEXCOORDSIZE2(7));
        case 7:
            instancedData->stride += sizeof(RwTexCoords);
            fvfFlags |= (D3DFVF_TEX7 | D3DFVF_TEXCOORDSIZE2(6));
        case 6:
            instancedData->stride += sizeof(RwTexCoords);
            fvfFlags |= (D3DFVF_TEX6 | D3DFVF_TEXCOORDSIZE2(5));
        case 5:
            instancedData->stride += sizeof(RwTexCoords);
            fvfFlags |= (D3DFVF_TEX5 | D3DFVF_TEXCOORDSIZE2(4));
        case 4:
            instancedData->stride += sizeof(RwTexCoords);
            fvfFlags |= (D3DFVF_TEX4 | D3DFVF_TEXCOORDSIZE2(3));
        case 3:
            instancedData->stride += sizeof(RwTexCoords);
            fvfFlags |= (D3DFVF_TEX3 | D3DFVF_TEXCOORDSIZE2(2));
        case 2:
            instancedData->stride += sizeof(RwTexCoords);
            fvfFlags |= (D3DFVF_TEX2 | D3DFVF_TEXCOORDSIZE2(1));
        case 1:
            instancedData->stride += sizeof(RwTexCoords);
            fvfFlags |= (D3DFVF_TEX1 | D3DFVF_TEXCOORDSIZE2(0));
    }

    /*
     * Create the vertex buffer
     */
    vbSize = instancedData->stride * instancedData->numVertices;

    if(instancedData->vertexBuffer != NULL)
    {
        _rxD3D8VertexBufferManagerDestroy(fvfFlags, vbSize,
                                        instancedData->vertexBuffer,
                                        instancedData->baseIndex);

        instancedData->vertexBuffer = NULL;
    }

    instancedData->managed = TRUE;

    if (FALSE == _rxD3D8VertexBufferManagerCreate(fvfFlags, vbSize,
                                                &(instancedData->vertexBuffer),
                                                &(instancedData->baseIndex)))
    {
        RWRETURN(FALSE);
    }

    /*
     * Set the vertex shader flags
     */
    instancedData->vertexShader = fvfFlags;

    /*
     * Fill static information of the vertex buffer
     */
    if (SUCCEEDED(IDirect3DVertexBuffer8_Lock((LPDIRECT3DVERTEXBUFFER8)instancedData->vertexBuffer,
                 instancedData->baseIndex * instancedData->stride,
                 instancedData->numVertices * instancedData->stride, &vertexData,
                 D3DLOCK_NOSYSLOCK)))
    {
        if (rwObjectTestFlags(geometry, rpGEOMETRYLOCKPRELIGHT))
        {
            RwInt32         offset;
            const RwRGBA    *color;
            const RwRGBA    *matColor;
            RwUInt8         *vertexBuffer;
            RwInt32         alpha;
            RwUInt32        numVertices;

            offset = sizeof(RwV3d);

            if(rwObjectTestFlags(geometry, rpGEOMETRYNORMALS))
            {
                offset += sizeof(RwV3d);
            }

            color = (const RwRGBA *)(((const RwUInt8 *)(&geometry->preLitLum[instancedData->minVert])));
            matColor = RpMaterialGetColor(instancedData->material);

            vertexBuffer = vertexData + offset;
            numVertices = instancedData->numVertices;

            alpha = 0xff;

            if (rwObjectTestFlags(geometry, rpGEOMETRYMODULATEMATERIALCOLOR) &&
                (*((const RwUInt32 *)matColor) != 0xffffffff))
            {
                while (numVertices--)
                {
                    *((RwUInt32 *)vertexBuffer) =
                        (((color->alpha * (matColor->alpha + 1)) & 0xff00) << (24-8)) |
                        (((color->red * (matColor->red + 1)) & 0xff00) << (16-8)) |
                        (((color->green * (matColor->green + 1)) & 0xff00) << (8-8)) |
                        ((color->blue * (matColor->blue + 1)) >> 8);

                    /* Does the pre-light contain alpha */
                    alpha &= color->alpha;

                    vertexBuffer += instancedData->stride;
                    color++;
                }
            }
            else
            {
                while (numVertices--)
                {
                    /* Does the pre-light contain alpha */
                    alpha &= color->alpha;

                    *((RwUInt32 *)vertexBuffer) = ((color->alpha << 24) |
                                                   (color->red << 16) |
                                                   (color->green << 8) |
                                                   (color->blue));

                    vertexBuffer += instancedData->stride;
                    color++;
                }
            }

            instancedData->vertexAlpha = (alpha != 0xff);
        }
        else
        {
            instancedData->vertexAlpha = FALSE;
        }

        /* Texture coordinates */
        if (numTextureCoords)
        {
            RwUInt32    offset;
            const RwTexCoords *texCoord;
            RwUInt32    n;
            RwUInt8     *vertexBuffer;
            RwUInt32    numVertices;

            offset = sizeof(RwV3d);

            if(rwObjectTestFlags(geometry, rpGEOMETRYNORMALS))
            {
                offset += sizeof(RwV3d);
            }

            if(rwObjectTestFlags(geometry, rpGEOMETRYPRELIT))
            {
                offset += sizeof(RwRGBA);
            }

            for (n = 0; n < numTextureCoords; n++)
            {
                texCoord = (const RwTexCoords *)(((const RwUInt8 *)(&geometry->texCoords[n][instancedData->minVert])));

                vertexBuffer = vertexData + offset;
                numVertices = instancedData->numVertices;
                while (numVertices--)
                {
                    *((RwTexCoords *)vertexBuffer) = *texCoord;
                    vertexBuffer += instancedData->stride;
                    texCoord++;
                }

                offset += sizeof(RwTexCoords);
            }
        }

        IDirect3DVertexBuffer8_Unlock((LPDIRECT3DVERTEXBUFFER8)instancedData->vertexBuffer);
    }

    RWRETURN(TRUE);
}

/****************************************************************************
 _rxD3D8SkinInstance

 Purpose:   To instance.

 On entry:  

 On exit :
*/
RwResEntry *
_rxD3D8SkinInstance(const RpAtomic *atomic,
              void *owner,
              RwResEntry **resEntryPointer,
              RpMeshHeader *meshHeader,
              _rxD3D8SkinInstanceNodeData *privateData,
              RwUInt32 flags __RWUNUSED__,
              RwBool reinstance __RWUNUSED__)
{
    RxD3D8ResEntryHeader    *resEntryHeader;
    RxD3D8InstanceData      *instancedData;
    RwResEntry              *resEntry;
    RpMesh                  *mesh;
    RwInt16                 numMeshes;
    RwUInt32                size;

    RWFUNCTION(RWSTRING("_rxD3D8SkinInstance"));

    /*
     * Calculate the amount of memory to allocate
     */

    /* RxD3D8ResEntryHeader, stores serialNumber & numMeshes */
    size = sizeof(RxD3D8ResEntryHeader);

    /* RxD3D8InstanceData structures, one for each mesh */
    size += sizeof(RxD3D8InstanceData) * meshHeader->numMeshes;

    /*
     * Allocate the resource entry
     */
    resEntry = RwResourcesAllocateResEntry(owner,
                                           resEntryPointer,
                                           size,
                                           _rwD3D8ResourceEntryInstanceDataDestroy);
    RWASSERT(NULL != resEntry);

    /* Blank the RxD3D8ResEntryHeader & RxD3D8InstanceData's to '0' */
    memset((resEntry + 1), 0, size);

    /*
     * Initialize the RxD3D8ResEntryHeader
     */
    resEntryHeader = (RxD3D8ResEntryHeader *)(resEntry + 1);

    /* Set the serial number */
    resEntryHeader->serialNumber = meshHeader->serialNum;

    /* Set the number of meshes */
    resEntryHeader->numMeshes = meshHeader->numMeshes;

    /* Get the first RxD3D8InstanceData pointer */
    instancedData = (RxD3D8InstanceData *)(resEntryHeader + 1);

    mesh = (RpMesh *)(meshHeader + 1);
    numMeshes = meshHeader->numMeshes;
    while (numMeshes--)
    {
        RwUInt32 numIndices = mesh->numIndices;

        /*
         * Number of vertices and Min vertex index,
         * (needed for instancing & reinstancing)
         */
        _rwD3D8MeshGetNumVerticesMinIndex(mesh->indices, numIndices,
                                &instancedData->numVertices,
                                &instancedData->minVert);

        /* Primitive type */
        instancedData->primType = (meshHeader->flags & rpMESHHEADERTRISTRIP) ?
                                  D3DPT_TRIANGLESTRIP : D3DPT_TRIANGLELIST;

        /* The number of indices */
        instancedData->numIndices = numIndices;

        /* Material */
        instancedData->material = mesh->material;

        /* Vertex shader */
        instancedData->vertexShader = 0;

        /* The vertex format stride */
        instancedData->stride = 0;

        /* Initialize the vertex buffers pointers */
        instancedData->vertexBuffer = NULL;

        /* Initialize vertex buffer managed to FALSE */
        instancedData->baseIndex = 0;

        instancedData->managed = FALSE;

        /* Initialize vertex alpha to FALSE */
        instancedData->vertexAlpha = FALSE;

        /*
         * Set the index buffer
         */

        /* Initialize the index buffers pointers */
        if (RwD3D8IndexBufferCreate(numIndices, &(instancedData->indexBuffer)))
        {
            RxVertexIndex   *indexBuffer;

             if (D3D_OK == IDirect3DIndexBuffer8_Lock((LPDIRECT3DINDEXBUFFER8)instancedData->indexBuffer,
                                                    0, 0, (RwUInt8 **)&indexBuffer, 0))
             {
                if(instancedData->minVert)
                {
                    RxVertexIndex   *indexSrc;

                    indexSrc = mesh->indices;

                    while (numIndices--)
                    {
                        *indexBuffer = (RxVertexIndex)((*indexSrc) - (RxVertexIndex)instancedData->minVert);

                        indexBuffer++;
                        indexSrc++;
                    }
                }
                else
                {
                    memcpy(indexBuffer, mesh->indices, sizeof(RxVertexIndex) * numIndices);
                }

                 IDirect3DIndexBuffer8_Unlock((LPDIRECT3DINDEXBUFFER8)instancedData->indexBuffer);
             }
        }

        /*
         * Call the instance callback
         */
        _rxD3D8SkinAtomicInstance(atomic, instancedData);

        instancedData++;
        mesh++;
    }

    RWRETURN(resEntry);
}

/****************************************************************************
 _rxD3D8AtomicAllInOneNode
 */
static RwBool
_rxD3D8SkinAtomicAllInOneNode(RxPipelineNodeInstance *self,
                        const RxPipelineNodeParam *params)
{
    _rxD3D8SkinInstanceNodeData *privateData;
    RpAtomic                *atomic;
    RpGeometry              *geometry;
    RwResEntry              *resEntry;
    RpMeshHeader            *meshHeader;
    RwUInt32                geomFlags;
    RwMatrix                *matrix;
    RwUInt32                lighting;

    RWFUNCTION(RWSTRING("_rxD3D8SkinAtomicAllInOneNode"));
    RWASSERT(NULL != self);
    RWASSERT(NULL != params);

    privateData = (_rxD3D8SkinInstanceNodeData *)self->privateData;

    atomic = (RpAtomic *)RxPipelineNodeParamGetData(params);
    RWASSERT(NULL != atomic);

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

    /* If there ain't vertices, we cain't make packets... */
    if (geometry->numVertices <= 0)
    {
        /* Don't execute the rest of the pipeline */
        RWRETURN(TRUE);
    }

    meshHeader = geometry->mesh;

    /* Early out if no meshes */
    if (meshHeader->numMeshes <= 0)
    {
        /* If the app wants to use plugin data to make packets, it
         * should use its own instancing function. If we have verts
         * here, we need meshes too in order to build a packet. */
        RWRETURN(TRUE);
    }

    /* If the geometry has more than one morph target the resEntry in the
     * atomic is used else the resEntry in the geometry */
    resEntry = geometry->repEntry;

    /* If the meshes have changed we should re-instance */
    if (resEntry)
    {
        RxD3D8ResEntryHeader    *resEntryHeader;

        resEntryHeader = (RxD3D8ResEntryHeader *)(resEntry + 1);
        if (resEntryHeader->serialNumber != meshHeader->serialNum)
        {
            /* Destroy resources to force reinstance */
            RwResourcesFreeResEntry(resEntry);
            resEntry = NULL;
        }
    }

    geomFlags = RpGeometryGetFlags(geometry);

    /* Check to see if a resource entry already exists */
    if (!resEntry)
    {
        RwResEntry  **resEntryPointer;
        void        *owner;

        meshHeader = geometry->mesh;

        if (RpGeometryGetNumMorphTargets(geometry) != 1)
        {
            owner = (void *)atomic;
            resEntryPointer = &atomic->repEntry;
        }
        else
        {
            owner = (void *)geometry;
            resEntryPointer = &geometry->repEntry;
        }

        /*
         * Create vertex buffers and instance
         */
        resEntry = _rxD3D8SkinInstance(atomic, owner, resEntryPointer,
                                 meshHeader, privateData, geomFlags, FALSE);
        if (!resEntry)
        {
            RWRETURN(FALSE);
        }

        /* The geometry is up to date */
        geometry->lockedSinceLastInst = 0;
    }
    else
    {
        /* We have a resEntry so use it */
        RwResourcesUseResEntry(resEntry);
    }

    /* Update skin vertices */
    SkinMatBlendAtomicRender(atomic);

    /*
     * Set up lights
     */
    _rwD3D8AtomicDefaultLightingCallback((void *)atomic);

    /* 
     * Set the world transform
     */
    matrix = RwFrameGetLTM(RpAtomicGetFrame(atomic));

    RwD3D8SetTransformWorld(matrix);

    RwD3D8GetRenderState(D3DRS_LIGHTING, &lighting);

    if (lighting &&
        !rwMatrixTestFlags(matrix, (rwMATRIXTYPENORMAL | rwMATRIXINTERNALIDENTITY)))
    {
        const RwReal minlimit = 0.9f;
        const RwReal maxlimit = 1.1f;
        RwReal length;
        
        length = RwV3dDotProduct(&(matrix->right), &(matrix->right));

        if ( (FLOATASINT(length) > FLOATASINT(maxlimit)) || (FLOATASINT(length) < FLOATASINT(minlimit)) )
        {
            RwD3D8SetRenderState(D3DRS_NORMALIZENORMALS, TRUE);
        }
        else
        {
            length = RwV3dDotProduct(&(matrix->up), &(matrix->up));

            if ( (FLOATASINT(length) > FLOATASINT(maxlimit)) || (FLOATASINT(length) < FLOATASINT(minlimit)) )
            {
                RwD3D8SetRenderState(D3DRS_NORMALIZENORMALS, TRUE);
            }
            else
            {
                length = RwV3dDotProduct(&(matrix->at), &(matrix->at));

                if ( (FLOATASINT(length) > FLOATASINT(maxlimit)) || (FLOATASINT(length) < FLOATASINT(minlimit)) )
                {
                    RwD3D8SetRenderState(D3DRS_NORMALIZENORMALS, TRUE);
                }
                else
                {
                    RwD3D8SetRenderState(D3DRS_NORMALIZENORMALS, FALSE);
                }
            }
        }
    }
    else
    {
        RwD3D8SetRenderState(D3DRS_NORMALIZENORMALS, FALSE);
    }

    /*
     * Render
     */
    if (privateData->renderCallback)
    {
        privateData->renderCallback(resEntry, (void *)atomic, rpATOMIC, geomFlags);
    }

    RWRETURN(TRUE);
}

/*===========================================================================*
 *--- Private functions -----------------------------------------------------*
 *===========================================================================*/

/*
 * _rxNodeDefinitionGetD3D8SkinAtomicAllInOne returns a
 * pointer to the \ref RxNodeDefinition associated with
 * the Skin Atomic version of PowerPipe.
 */
static RxNodeDefinition *
_rxNodeDefinitionGetD3D8SkinAtomicAllInOne(void)
{   
    static RwChar _SkinAtomicInstance_csl[] = "nodeD3D8SkinAtomicAllInOne.csl";

    static RxNodeDefinition nodeD3D8SkinAtomicAllInOneCSL = { /* */
        _SkinAtomicInstance_csl,                    /* Name */
        {                                           /* Nodemethods */
            _rxD3D8SkinAtomicAllInOneNode,              /* +-- nodebody */
            NULL,                                   /* +-- nodeinit */
            NULL,                                   /* +-- nodeterm */
            _rwD3D8SkinAtomicAllInOnePipelineInit,  /* +-- pipelinenodeinit */
            NULL,                                   /* +-- pipelineNodeTerm */
            NULL,                                   /* +-- pipelineNodeConfig */
            NULL,                                   /* +-- configMsgHandler */
        },
        {                                           /* Io */
            NUMCLUSTERSOFINTEREST,                  /* +-- NumClustersOfInterest */
            NULL,                                   /* +-- ClustersOfInterest */
            NULL,                                   /* +-- InputRequirements */
            NUMOUTPUTS,                             /* +-- NumOutputs */
            NULL                                    /* +-- Outputs */
        },
        (RwUInt32)sizeof(_rxD3D8SkinInstanceNodeData),/* PipelineNodePrivateDataSize */
        FALSE,                                      /* editable */
        0                                           /* inPipes */
    };

    RWFUNCTION(RWSTRING("_rxNodeDefinitionGetD3D8SkinAtomicAllInOne"));

    RWRETURN(&nodeD3D8SkinAtomicAllInOneCSL);
}

/****************************************************************************
 _rpSkinPipelinesCreate
 Create the skinning pipelines.
 Inputs :   None
 Outputs:   RwBool - on success.
 */
RwBool
_rpSkinPipelinesCreate(RwUInt32 pipes __RWUNUSED__)
{
    RWFUNCTION(RWSTRING("_rpSkinPipelinesCreate"));

#if !defined( NOASM ) && defined( RWDEBUG )

    _rpSkinIntelx86ConfirmConstants();

#endif /* !defined( NOASM ) && defined( RWDEBUG ) */

    SkinAtomicPipe = RxPipelineCreate();
    if (SkinAtomicPipe)
    {
        RxLockedPipe    *lpipe;

        SkinAtomicPipe->pluginId = rwID_SKINPLUGIN;

        if (NULL != (lpipe = RxPipelineLock(SkinAtomicPipe)))
        {
            lpipe = RxLockedPipeAddFragment(lpipe,
                NULL,
                _rxNodeDefinitionGetD3D8SkinAtomicAllInOne(),
                NULL);

            lpipe = RxLockedPipeUnlock(lpipe);

            RWASSERT(SkinAtomicPipe == (RxPipeline *)lpipe);

            RWRETURN(TRUE);
        }

        RxPipelineDestroy(SkinAtomicPipe);
        SkinAtomicPipe = NULL;
    }

    RWRETURN(FALSE);
}


/****************************************************************************
 _rpSkinPipelinesDestroy
 Destroy the skinning pipelines.
 Inputs :   None
 Outputs:   RwBool - on success.
 */
RwBool
_rpSkinPipelinesDestroy(void)
{
    RWFUNCTION(RWSTRING("_rpSkinPipelinesDestroy"));

    if (SkinAtomicPipe)
    {
        RxPipelineDestroy(SkinAtomicPipe);
        SkinAtomicPipe = NULL;
    }

    RWRETURN(TRUE);
}


/****************************************************************************
 _rpSkinPipelinesAttach
 Attach the generic skinning pipeline to an atomic.
 Inputs :   atomic *   - Pointer to the atomic.
 Outputs:   RpAtomic * - Pointer to the atomic on success.
 */
RpAtomic *
_rpSkinPipelinesAttach(RpAtomic *atomic)
{
    RWFUNCTION(RWSTRING("_rpSkinPipelinesAttach"));

    RpAtomicSetPipeline(atomic, SkinAtomicPipe);

    RWRETURN(atomic);
}


/****************************************************************************
 _rpSkinInitialize

 Initialise an atomic's matrix-blending skin data.

 Inputs :   RpGeometry * - Pointer to a skinned geometry.
 Outputs:   RpGeometry * - Pointer to the skinned geometry on success.
 */
RpGeometry *
_rpSkinInitialize(RpGeometry *geometry)
{
    RpSkin *skin;

    RWFUNCTION(RWSTRING("_rpSkinInitialize"));
    RWASSERT(NULL != geometry);

    skin = *RPSKINGEOMETRYGETDATA(geometry);

    if(skin != NULL)
    {
    }

    RWRETURN(geometry);
}


/****************************************************************************
 _rpSkinDeinitialize
 Platform specific deinitialize function for skinned geometry's.
 Inputs :  *geometry    - Pointer to the skinned geometry.
 Outputs:  RpGeometry * - The geometry which has been deinitialized.
 */
RpGeometry *
_rpSkinDeinitialize(RpGeometry *geometry)
{
    RpSkin *skin;

    RWFUNCTION(RWSTRING("_rpSkinDeinitialize"));
    RWASSERT(NULL != geometry);

    skin = *RPSKINGEOMETRYGETDATA(geometry);

    if(NULL != skin)
    {
    }

    RWRETURN(geometry);
}


/*===========================================================================*
 *--- Plugin Engine Functions -----------------------------------------------*
 *===========================================================================*/

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

