/****************************************************************************
 *                                                                          *
 *  Module  :   winmatbl.c                                                  *
 *                                                                          *
 *  Purpose :   PC specific matrix-blending pipeline                        *
 *                                                                          *
 ****************************************************************************/

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

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

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

#include <rwcore.h>
#include <rpworld.h>
#include <rprandom.h>
#include <rpspline.h>
#include <rpskin310.h>

#include "skinpriv.h"
#include "winmatbl.h"

#if !defined( NOASM )
#   include "x86matbl.h"
#endif /* !defined( NOASM ) */

#if (!defined(DOXYGEN_SHOULD_SKIP_THIS))
static const char   rcsid[] __RWUNUSED__ =
    "@@(#)$Id: winmatbl.c,v 1.2 2001/09/28 15:38:00 markf Exp $";
#endif /* (!defined(DOXYGEN_SHOULD_SKIP_THIS)) */

/****************************************************************************
 Local Types
 */

/****************************************************************************
 Local (Static) Prototypes
 */


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

#define _EPSILON          ((RwReal)(0.001))

/****************************************************************************
 Globals (across program)
 */

RpAtomicCallBackRender pDefRenderCallback = NULL;

/****************************************************************************
 Local (static) Globals
 */

/* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

   Functions

   !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! */

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

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

void
_rpSkinMBPipelineDestroy(void)
{
    RWFUNCTION(RWSTRING("_rpSkinMBPipelineDestroy"));

    RWRETURNVOID();
}

/****************************************************************************
 _rpSkinMBPluginAttach

 Initialise the matrix blending pipeline.

 Inputs :   None

 Outputs:   RwBool              TRUE on success
 */

RwBool
_rpSkinMBPluginAttach(void)
{
    RWFUNCTION(RWSTRING("_rpSkinMBPluginAttach"));

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

    _rpSkinIntelx86ConfirmConstants();

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

    RWRETURN(TRUE);
}

/****************************************************************************
 SkinMatBlendAtomicRender

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

 If NOASM=0 then fast x86 assembler is used for matrix blending.
 Otherwise...
 THIS IS A VERY SLOW, GENERIC "C" IMPLEMENTATION AND SHOULD BE OVERLOADED
 WITH SOMETHING FAST, SEXY AND PLATFORM-SPECIFIC.

 Inputs :   RpAtomic *    A pointer to the atomic.

 Outputs:   RwBool        TRUE on success
 */

static RpAtomic    *
SkinMatBlendAtomicRender(RpAtomic * atomic)
{
    RpSkin              *pSkin;
    RwV3d               *pVertices;
    RwV3d               *pNormals;
    const RwV3d         *pOriginalVertices;
    const RwV3d         *pOriginalNormals;
    RwMatrix            *pMatrixArray = NULL;

#if defined( NOASM )
    const RwMatrix      *pMatrix;
    RwInt32             i;
#endif /* defined( NOASM ) */

    RWFUNCTION(RWSTRING("SkinMatBlendAtomicRender"));
    RWASSERT(atomic);

    if (atomic)
    {
        pSkin = *RPSKINATOMICGETDATA(atomic);

        if (pSkin)
        {
            /* Perform matrix blending */

            RpGeometryLock(pSkin->pGeometry,
                           rpGEOMETRYLOCKVERTICES |
                           rpGEOMETRYLOCKNORMALS);

            pVertices =
                RpMorphTargetGetVertices(RpGeometryGetMorphTarget
                                         (pSkin->pGeometry, 0));
            pNormals =
                RpMorphTargetGetVertexNormals(RpGeometryGetMorphTarget
                                              (pSkin->pGeometry, 0));
            pOriginalVertices = (const RwV3d *) pSkin->pPlatformData;
            if (pNormals)
            {
                pOriginalNormals = (const RwV3d *)
                    &(pOriginalVertices[pSkin->totalVertices]);
            }
            else
            {
                pOriginalNormals = NULL;
            }

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

            if (pSkin->pCurrentHierarchy)
            {
                const RwInt32   numBones =
                                    pSkin->pCurrentHierarchy->numNodes;
                const RwMatrix  *atomicLTM =
                                    RwFrameGetLTM(RpAtomicGetFrame(atomic));
                RwInt32         i;

                pMatrixArray = RpSkinAtomicGlobals.SkinMatrixCache;

                if (pSkin->pCurrentHierarchy->
                    flags & rpHANIMHIERARCHYNOMATRICES)
                {
                    if (atomicLTM->flags & rwMATRIXINTERNALIDENTITY)
                    {
                        for (i = 0; i < numBones; i++)
                        {
                            RwFrame *pFrame =
                                pSkin->pCurrentHierarchy->pNodeInfo[i].
                                pFrame;
                            RwMatrixMultiply(&pMatrixArray[i],
                                             &pSkin->pBoneInfo[i].
                                             boneToSkinMat,
                                             RwFrameGetLTM(pFrame));
                        }
                    }
                    else
                    {
                        RwMatrix            inverseAtomicLTM;
                        RwMatrix            tempMatrix;

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

                        for (i = 0; i < numBones; i++)
                        {
                            RwFrame *pFrame =
                                pSkin->pCurrentHierarchy->pNodeInfo[i].
                                pFrame;
                            RwMatrixMultiply(&tempMatrix,
                                             &pSkin->pBoneInfo[i].
                                             boneToSkinMat,
                                             RwFrameGetLTM(pFrame));
                            RwMatrixMultiply(&pMatrixArray[i], &tempMatrix,
                                             &inverseAtomicLTM);
                        }
                    }
                }
                else
                {

                    if ( pSkin->pCurrentHierarchy->
                            flags & rpHANIMHIERARCHYLOCALSPACEMATRICES ||
                        atomicLTM->flags & rwMATRIXINTERNALIDENTITY )
                    {
                        for (i = 0; i < numBones; i++)
                        {
                            RwMatrixMultiply(&pMatrixArray[i],
                                             &pSkin->pBoneInfo[i].
                                             boneToSkinMat,
                                             &pSkin->pCurrentHierarchy->
                                             pMatrixArray[i]);
                        }
                    }
                    else
                    {
                        RwMatrix    inverseAtomicLTM;
                        RwMatrix    tempMatrix;

                        RwMatrixInvert(&inverseAtomicLTM, atomicLTM);

                        for (i = 0; i < numBones; i++)
                        {
                            RwMatrixMultiply(&tempMatrix,
                                             &pSkin->pBoneInfo[i].
                                             boneToSkinMat,
                                             &pSkin->pCurrentHierarchy->
                                             pMatrixArray[i]);
                            RwMatrixMultiply(&pMatrixArray[i],
                                             &tempMatrix,
                                             &inverseAtomicLTM);
                        }
                    }
                }
            }

#if !defined( NOASM )

            _rpSkinIntelx86MatrixBlend( pSkin->totalVertices,
                                        pSkin->pMatrixWeightsMap,
                                        pSkin->pMatrixIndexMap,
                                        pMatrixArray,
                                        pVertices,
                                        pOriginalVertices,
                                        pNormals,
                                        pOriginalNormals );

#else /* !defined( NOASM ) */

            for (i = 0; i < pSkin->totalVertices; i++)
            {
                /* Hideously slow matrix operations follow... */
                if (pSkin->pMatrixWeightsMap[i].w0 > (RwReal) 0.0)
                {
                    pMatrix =
                        &pMatrixArray[pSkin->pMatrixIndexMap[i] & 0xFF];
                    /* RWASSERT(rwMatrixValidFlags(pMatrix, _EPSILON)); */

                    if (pSkin->pMatrixWeightsMap[i].w0 == (RwReal) 1.0)
                    {
                        pVertices[i].x =
                            ((pMatrix->right.x * pOriginalVertices[i].x) +
                             (pMatrix->up.x * pOriginalVertices[i].y) +
                             (pMatrix->at.x * pOriginalVertices[i].z) +
                             (pMatrix->pos.x));

                        pVertices[i].y =
                            ((pMatrix->right.y * pOriginalVertices[i].x) +
                             (pMatrix->up.y * pOriginalVertices[i].y) +
                             (pMatrix->at.y * pOriginalVertices[i].z) +
                             (pMatrix->pos.y));

                        pVertices[i].z =
                            ((pMatrix->right.z * pOriginalVertices[i].x) +
                             (pMatrix->up.z * pOriginalVertices[i].y) +
                             (pMatrix->at.z * pOriginalVertices[i].z) +
                             (pMatrix->pos.z));

                        if (pNormals)
                        {
                            pNormals[i].x =
                                ((pMatrix->right.x *
                                  pOriginalNormals[i].x) +
                                 (pMatrix->up.x * pOriginalNormals[i].y) +
                                 (pMatrix->at.x * pOriginalNormals[i].z));

                            pNormals[i].y =
                                ((pMatrix->right.y *
                                  pOriginalNormals[i].x) +
                                 (pMatrix->up.y * pOriginalNormals[i].y) +
                                 (pMatrix->at.y * pOriginalNormals[i].z));

                            pNormals[i].z =
                                ((pMatrix->right.z *
                                  pOriginalNormals[i].x) +
                                 (pMatrix->up.z * pOriginalNormals[i].y) +
                                 (pMatrix->at.z * pOriginalNormals[i].z));
                        }

                        continue;
                    }
                    else
                    {
                        pVertices[i].x =
                            ((pMatrix->right.x * pOriginalVertices[i].x) +
                             (pMatrix->up.x * pOriginalVertices[i].y) +
                             (pMatrix->at.x * pOriginalVertices[i].z) +
                             (pMatrix->pos.x)) *
                            pSkin->pMatrixWeightsMap[i].w0;

                        pVertices[i].y =
                            ((pMatrix->right.y * pOriginalVertices[i].x) +
                             (pMatrix->up.y * pOriginalVertices[i].y) +
                             (pMatrix->at.y * pOriginalVertices[i].z) +
                             (pMatrix->pos.y)) *
                            pSkin->pMatrixWeightsMap[i].w0;

                        pVertices[i].z =
                            ((pMatrix->right.z * pOriginalVertices[i].x) +
                             (pMatrix->up.z * pOriginalVertices[i].y) +
                             (pMatrix->at.z * pOriginalVertices[i].z) +
                             (pMatrix->pos.z)) *
                            pSkin->pMatrixWeightsMap[i].w0;

                        if (pNormals)
                        {
                            pNormals[i].x =
                                ((pMatrix->right.x *
                                  pOriginalNormals[i].x) +
                                 (pMatrix->up.x * pOriginalNormals[i].y) +
                                 (pMatrix->at.x * pOriginalNormals[i].z)) *
                                pSkin->pMatrixWeightsMap[i].w0;

                            pNormals[i].y =
                                ((pMatrix->right.y *
                                  pOriginalNormals[i].x) +
                                 (pMatrix->up.y * pOriginalNormals[i].y) +
                                 (pMatrix->at.y * pOriginalNormals[i].z)) *
                                pSkin->pMatrixWeightsMap[i].w0;

                            pNormals[i].z =
                                ((pMatrix->right.z *
                                  pOriginalNormals[i].x) +
                                 (pMatrix->up.z * pOriginalNormals[i].y) +
                                 (pMatrix->at.z * pOriginalNormals[i].z)) *
                                pSkin->pMatrixWeightsMap[i].w0;
                        }
                    }
                }
                else
                {
                    continue;
                }

                if (pSkin->pMatrixWeightsMap[i].w1 > (RwReal) 0.0)
                {
                    pMatrix =
                        &pMatrixArray[(pSkin->
                                       pMatrixIndexMap[i] >> 8) & 0xFF];
                    /* RWASSERT(rwMatrixValidFlags(pMatrix, _EPSILON)); */

                    pVertices[i].x +=
                        ((pMatrix->right.x * pOriginalVertices[i].x) +
                         (pMatrix->up.x * pOriginalVertices[i].y) +
                         (pMatrix->at.x * pOriginalVertices[i].z) +
                         (pMatrix->pos.x)) *
                        pSkin->pMatrixWeightsMap[i].w1;

                    pVertices[i].y +=
                        ((pMatrix->right.y * pOriginalVertices[i].x) +
                         (pMatrix->up.y * pOriginalVertices[i].y) +
                         (pMatrix->at.y * pOriginalVertices[i].z) +
                         (pMatrix->pos.y)) *
                        pSkin->pMatrixWeightsMap[i].w1;

                    pVertices[i].z +=
                        ((pMatrix->right.z * pOriginalVertices[i].x) +
                         (pMatrix->up.z * pOriginalVertices[i].y) +
                         (pMatrix->at.z * pOriginalVertices[i].z) +
                         (pMatrix->pos.z)) *
                        pSkin->pMatrixWeightsMap[i].w1;

                    if (pNormals)
                    {
                        pNormals[i].x +=
                            ((pMatrix->right.x *
                              pOriginalNormals[i].x) +
                             (pMatrix->up.x * pOriginalNormals[i].y) +
                             (pMatrix->at.x * pOriginalNormals[i].z)) *
                            pSkin->pMatrixWeightsMap[i].w1;

                        pNormals[i].y +=
                            ((pMatrix->right.y *
                              pOriginalNormals[i].x) +
                             (pMatrix->up.y * pOriginalNormals[i].y) +
                             (pMatrix->at.y * pOriginalNormals[i].z)) *
                            pSkin->pMatrixWeightsMap[i].w1;

                        pNormals[i].z +=
                            ((pMatrix->right.z *
                              pOriginalNormals[i].x) +
                             (pMatrix->up.z * pOriginalNormals[i].y) +
                             (pMatrix->at.z * pOriginalNormals[i].z)) *
                            pSkin->pMatrixWeightsMap[i].w1;
                    }

                }
                else
                {
                    continue;
                }

                if (pSkin->pMatrixWeightsMap[i].w2 > (RwReal) 0.0)
                {
                    pMatrix =
                        &pMatrixArray[(pSkin->
                                       pMatrixIndexMap[i] >> 16) &
                                      0xFF];
                    /* RWASSERT(rwMatrixValidFlags(pMatrix, _EPSILON)); */

                    pVertices[i].x +=
                        ((pMatrix->right.x * pOriginalVertices[i].x) +
                         (pMatrix->up.x * pOriginalVertices[i].y) +
                         (pMatrix->at.x * pOriginalVertices[i].z) +
                         (pMatrix->pos.x)) *
                        pSkin->pMatrixWeightsMap[i].w2;

                    pVertices[i].y +=
                        ((pMatrix->right.y * pOriginalVertices[i].x) +
                         (pMatrix->up.y * pOriginalVertices[i].y) +
                         (pMatrix->at.y * pOriginalVertices[i].z) +
                         (pMatrix->pos.y)) *
                        pSkin->pMatrixWeightsMap[i].w2;

                    pVertices[i].z +=
                        ((pMatrix->right.z * pOriginalVertices[i].x) +
                         (pMatrix->up.z * pOriginalVertices[i].y) +
                         (pMatrix->at.z * pOriginalVertices[i].z) +
                         (pMatrix->pos.z)) *
                        pSkin->pMatrixWeightsMap[i].w2;

                    if (pNormals)
                    {
                        pNormals[i].x +=
                            ((pMatrix->right.x *
                              pOriginalNormals[i].x) +
                             (pMatrix->up.x * pOriginalNormals[i].y) +
                             (pMatrix->at.x * pOriginalNormals[i].z)) *
                            pSkin->pMatrixWeightsMap[i].w2;

                        pNormals[i].y +=
                            ((pMatrix->right.y *
                              pOriginalNormals[i].x) +
                             (pMatrix->up.y * pOriginalNormals[i].y) +
                             (pMatrix->at.y * pOriginalNormals[i].z)) *
                            pSkin->pMatrixWeightsMap[i].w2;

                        pNormals[i].z +=
                            ((pMatrix->right.z *
                              pOriginalNormals[i].x) +
                             (pMatrix->up.z * pOriginalNormals[i].y) +
                             (pMatrix->at.z * pOriginalNormals[i].z)) *
                            pSkin->pMatrixWeightsMap[i].w2;
                    }

                }
                else
                {
                    continue;
                }

                if (pSkin->pMatrixWeightsMap[i].w3 > (RwReal) 0.0)
                {
                    pMatrix =
                        &pMatrixArray[(pSkin->
                                       pMatrixIndexMap[i] >> 24) &
                                      0xFF];
                    /* RWASSERT(rwMatrixValidFlags(pMatrix, _EPSILON)); */

                    pVertices[i].x +=
                        ((pMatrix->right.x * pOriginalVertices[i].x) +
                         (pMatrix->up.x * pOriginalVertices[i].y) +
                         (pMatrix->at.x * pOriginalVertices[i].z) +
                         (pMatrix->pos.x)) *
                        pSkin->pMatrixWeightsMap[i].w3;

                    pVertices[i].y +=
                        ((pMatrix->right.y * pOriginalVertices[i].x) +
                         (pMatrix->up.y * pOriginalVertices[i].y) +
                         (pMatrix->at.y * pOriginalVertices[i].z) +
                         (pMatrix->pos.y)) *
                        pSkin->pMatrixWeightsMap[i].w3;

                    pVertices[i].z +=
                        ((pMatrix->right.z * pOriginalVertices[i].x) +
                         (pMatrix->up.z * pOriginalVertices[i].y) +
                         (pMatrix->at.z * pOriginalVertices[i].z) +
                         (pMatrix->pos.z)) *
                        pSkin->pMatrixWeightsMap[i].w3;

                    if (pNormals)
                    {
                        pNormals[i].x +=
                            ((pMatrix->right.x *
                              pOriginalNormals[i].x) +
                             (pMatrix->up.x * pOriginalNormals[i].y) +
                             (pMatrix->at.x * pOriginalNormals[i].z)) *
                            pSkin->pMatrixWeightsMap[i].w3;

                        pNormals[i].y +=
                            ((pMatrix->right.y *
                              pOriginalNormals[i].x) +
                             (pMatrix->up.y * pOriginalNormals[i].y) +
                             (pMatrix->at.y * pOriginalNormals[i].z)) *
                            pSkin->pMatrixWeightsMap[i].w3;

                        pNormals[i].z +=
                            ((pMatrix->right.z *
                              pOriginalNormals[i].x) +
                             (pMatrix->up.z * pOriginalNormals[i].y) +
                             (pMatrix->at.z * pOriginalNormals[i].z)) *
                            pSkin->pMatrixWeightsMap[i].w3;
                    }

                }
                else
                {
                    continue;
                }
            }

#endif /* !defined( NOASM ) */

            RpGeometryUnlock(pSkin->pGeometry);

            (*pDefRenderCallback) (atomic);
        }
    }

    RWRETURN(atomic);
}

/****************************************************************************
 _rpSkinMBInitAtomic

 Initialise an atomic's matrix-blending skin data.

 Inputs :   RpAtomic *          A pointer to a skin atomic.

 Outputs:   RwBool              TRUE on success
 */

RwBool
_rpSkinMBInitAtomic(RpAtomic * atomic)
{
    RpSkin             *pSkin;

    RwV3d              *pOriginalVertices;
    RwV3d              *pOriginalNormals;
    RwV3d              *pSrcVertices;
    RwV3d              *pSrcNormals;
    RwInt32             i;

    RWFUNCTION(RWSTRING("_rpSkinMBInitAtomic"));

    if (pDefRenderCallback == NULL)
    {
        pDefRenderCallback = RpAtomicGetRenderCallBack(atomic);
    }

    RpAtomicSetRenderCallBack(atomic, SkinMatBlendAtomicRender);

    pSkin = *RPSKINATOMICGETDATA(atomic);

    if (pSkin == NULL)
    {
        RWRETURN(FALSE);
    }

    /* Add a copy of the original vertices and normals to the platform-specific skin data pointer */

    RpGeometryLock(pSkin->pGeometry,
                   rpGEOMETRYLOCKVERTICES | rpGEOMETRYLOCKNORMALS);

    pSrcVertices =
        RpMorphTargetGetVertices(RpGeometryGetMorphTarget
                                 (pSkin->pGeometry, 0));
    pSrcNormals =
        RpMorphTargetGetVertexNormals(RpGeometryGetMorphTarget
                                      (pSkin->pGeometry, 0));
    if (pSrcNormals)
    {
        const RwUInt32 bytes = sizeof(RwV3d) * pSkin->totalVertices * 2;
        pOriginalVertices = (RwV3d *) RwMalloc(bytes);
        memset(pOriginalVertices, 0, bytes);
        
        pOriginalNormals = &(pOriginalVertices[pSkin->totalVertices]);
    }
    else
    {
        const RwUInt32 bytes = sizeof(RwV3d) * pSkin->totalVertices;
        pOriginalVertices = (RwV3d *) RwMalloc(bytes);
        memset(pOriginalVertices, 0, bytes);

        pOriginalNormals = NULL;
    }

    for (i = 0; i < pSkin->totalVertices; i++)
    {
        pOriginalVertices[i] = pSrcVertices[i];
        if (pOriginalNormals)
        {
            pOriginalNormals[i] = pSrcNormals[i];
        }
    }

    RpGeometryUnlock(pSkin->pGeometry);

    pSkin->pPlatformData = pOriginalVertices;

    RWRETURN(TRUE);
}

void
_rpSkinMBDestroyAtomic(RpSkin * pSkin)
{
    RWFUNCTION(RWSTRING("_rpSkinMBDestroyAtomic"));

    if (pSkin && pSkin->pPlatformData)
    {
        RwFree(pSkin->pPlatformData);
        pSkin->pPlatformData = NULL;
    }

    RWRETURNVOID();
}
