/*===========================================================================*
 *-                                                                         -*
 *-  Module  :   SKY2_SkinGenericNode.c                                     -*
 *-                                                                         -*
 *-  Purpose :   Hybrid SkinGeneric PipeLine for Playstation II.            -*
 *-              PS2 Manager PowerPipe version.                             -*
 *-                                                                         -*
 *===========================================================================*/

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

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

#include "rpskin.h"

#include "skinskycommon.h"
#include "skinsky.h"
#include "skin.h"

#include "SKY2_SkinGeneric/SKY2_SkinGenericNode.h"
#include "SKY2_SkinGeneric/SKY2_SkinGenericData.h"

#include "../../driver/sky2/baasm.h"

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

/*===========================================================================*
 *--- Private Global Variables ----------------------------------------------*
 *===========================================================================*/
SkinSkyTransforms _rpSkinSkySkinGenericTransforms;

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

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

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

/*===========================================================================*
 *--- Local Defines ---------------------------------------------------------*
 *===========================================================================*/
#define VIFCMD_UNPACK (0x6cl << 24)
#define VIFCMD_CYCLE  (0x01l << 24)
#define VIFCMD_DIRECT (0x50l << 24)
#define VIFCMD_NOP    (0x00l << 24)

#define SKINGETFIRSTMESH(meshHeader)                                    \
    ((RpMesh *)((RwUInt8 *)(meshHeader + 1) + meshHeader->firstMeshOffset))

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

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

/*****************************************************************************
 _rxPipelineNodePS2SkinGenericPS2ManagerInstanceCallBack

 Ps2 manager instance call back for rendering skin meshes.

 Inputs :   clusterData - Clusters
            numClusters - Number of cluster
 Outputs:   RwBool - True on success.
 */
RwBool
_rxPipelineNodePS2SkinGenericPS2ManagerInstanceCallBack(
    void **clusterData,
    RwUInt32 numClusters __RWUNUSEDRELEASE__ )
{
    /* Cluster expansion. */
    RxPS2Mesh *mesh;
    RxPS2DMASessionRecord *dmaSessionRec;
    RpAtomic *atomic;
    RpGeometry *geometry;
    RpSkin *skin;

    /* Skin extension. */
    RwUInt32 numMatrices;
    RwReal *destMBI;
    RwMatrix *mba;
    RwUInt32 serialNum;

    RWFUNCTION(RWSTRING("_rxPipelineNodePS2SkinGenericPS2ManagerInstanceCallBack"));
    RWASSERT(NULL != clusterData);
    RWASSERT(3 == numClusters);
    RWASSERT(NULL != clusterData[0]);
    RWASSERT(NULL != clusterData[1]);
    RWASSERT(NULL != clusterData[2]);

    /* Collect the mesh and atomic data. */
    mesh = (RxPS2Mesh *)clusterData[0];
    RWASSERT(NULL != mesh);
    dmaSessionRec = (RxPS2DMASessionRecord *)clusterData[1];
    RWASSERT(NULL != dmaSessionRec);
    destMBI = (RwReal *)(clusterData[2]);
    RWASSERT(NULL != destMBI);
    atomic = dmaSessionRec->sourceObject.atomic;
    RWASSERT(NULL != atomic);
    geometry = atomic->geometry;
    RWASSERT(NULL != geometry);
    skin = _rpSkinSkyGeometryGetSkin(geometry);
    RWASSERT(NULL != skin);
    numMatrices = _rpSkinSkySkinGetNumMatrices(skin);
    serialNum = ((RwUInt32 *)(*(mesh->cacheEntryRef) + 1))[3];

    /* Skin weights and indices instancing. */
    if(dmaSessionRec->serialNum != serialNum)
    {
        _rpSkinWeightsInstancing(mesh->mesh, skin, destMBI);
    }

    if(0 == mesh->meshNum)
    {
        RwUInt128 ltmp;
        RwUInt64 tmp;
        RwUInt64 tmp1;
        RwUInt32 i;

        /* Bones matrices uploading. */
        mba = _rpSkinMatrixUpdating(atomic, skin);
        RWASSERT(NULL != mba);

        /*---------------- DMA packet -----------------------*/
        sweFinaliseOpenLocalPkt( SWE_LPS_NOFIXUP |
                                 SWE_PKT_DMA_MODE_CHAIN_TTE |
                                 SWE_PKT_LOCAL | SWE_PKT_VU1 |
                                 SWE_PKT_CIRCALLOC,
                                 -(2 + (4 * numMatrices)) );
        RWASSERT(NULL != sweLocalPacket);
        /*---------------------------------------------------*/

        SWE_LOCAL_BLOCK_BEGIN();

        /*------- Transfer Information ----------------------*/
        tmp = ((1l << 28) | (numMatrices * 4)); /*-- 4m QW --*/
        tmp1 = ((((0x6cl << 24) |
                ((numMatrices * 4) << 16) | /*-- 4m QW --*/
                ((long) (pipeASymbStaticDataStart))) << 32) |
                ((1l << 24) | (4 << 8) | (4)));
        MAKE128(ltmp, tmp1, tmp);
        SWEADDCONTFAST(ltmp);
        /*---------------------------------------------------*/

        /*-------- Upload Skin matrices ---------------------*/
        for (i = 0; i < numMatrices; i++, mba++)
        {
            ltmp = *((RwUInt128 *) & mba->right.x);
            SWEADDCONTFAST(ltmp);
            ltmp = *((RwUInt128 *) & mba->up.x);
            SWEADDCONTFAST(ltmp);
            ltmp = *((RwUInt128 *) & mba->at.x);
            SWEADDCONTFAST(ltmp);
            ltmp = *((RwUInt128 *) & mba->pos.x);
            SWEADDCONTFAST(ltmp);
        }
        /*---------------------------------------------------*/

        /*------- Terminate the DMA with an interrupt -------*/
        tmp = (0xfl << 28);
        MAKE128(ltmp, 0l, tmp);
        SWEADDCONTFAST(ltmp);
        /*---------------------------------------------------*/

        SWE_LOCAL_BLOCK_END();

        sweFinaliseOpenLocalPkt(SWE_LPS_CONT, 0);
    }

    RWRETURN(TRUE);
}

/*****************************************************************************
 _rpSkinSkySkinGenericSetupTransforms

 Initalises the _rpSkinSkySkinGenericTransforms transforms.

 Inputs :   none.
 Outputs:   none.
 */
void
_rpSkinSkySkinGenericSetupTransforms()
{
    RwUInt32 code;

    RWFUNCTION(RWSTRING("_rpSkinSkySkinGenericSetupTransforms"));

    /* Empty the vu code array. */
    for( code = 0; code < VU1CODEARRAYSIZE; code++ )
    {
        _rpSkinSkySkinGenericTransforms[code] = &vu1nullTrans;
    }

    _rpSkinSkySkinGenericTransforms[SKINTRANSTRI  | SKINTRANSPER | SKINTRANSNCL ] = &SKY2_SkinGenericPRS;
    _rpSkinSkySkinGenericTransforms[SKINTRANSTRI  | SKINTRANSPER | SKINTRANSCULL] = &SKY2_SkinBFacePRS;
    _rpSkinSkySkinGenericTransforms[SKINTRANSTRI  | SKINTRANSISO | SKINTRANSNCL ] = &SKY2_SkinGenericPRL;
    _rpSkinSkySkinGenericTransforms[SKINTRANSTRI  | SKINTRANSISO | SKINTRANSCULL] = &vu1nullTrans;
    _rpSkinSkySkinGenericTransforms[SKINTRANSLINE | SKINTRANSPER | SKINTRANSNCL ] = &SKY2_SkinSegmentsPRS;
    _rpSkinSkySkinGenericTransforms[SKINTRANSLINE | SKINTRANSPER | SKINTRANSCULL] = &vu1nullTrans;
    _rpSkinSkySkinGenericTransforms[SKINTRANSLINE | SKINTRANSISO | SKINTRANSNCL ] = &SKY2_SkinSegmentsPRL;
    _rpSkinSkySkinGenericTransforms[SKINTRANSLINE | SKINTRANSISO | SKINTRANSCULL] = &vu1nullTrans;

    RWRETURNVOID();
}


/****************************************************************************
 _rxPipelineNodePS2SkinPS2AllObjectSetupCallBack

 PS2All Object Setup Call-Back

 TODO DOC

 Inputs :
 Outputs:
 */
RwBool
_rxPipelineNodePS2SkinGenericPS2AllObjectSetupCallBack(
    RxPS2AllPipeData *ps2AllPipeData,
    RwMatrix **transform,
    RxWorldApplyLightFunc lightingFunc )
{
    RpAtomic *atomic;
    RwFrustumTestResult inFrustum;

    RWFUNCTION(RWSTRING("_rxPipelineNodePS2SkinGenericPS2AllObjectSetupCallBack"));

    atomic = (RpAtomic *)(ps2AllPipeData->sourceObject);
    RWASSERT(NULL != atomic);

    /* Get the RwMeshCache from the atomic/geometry;
     * If the geometry has more than one morph target the resEntry
     * in the atomic is used else the resEntry in the geometry */
    RpAtomicPS2AllGetMeshHeaderMeshCache(atomic, ps2AllPipeData);

    /* Early out if no meshes */
    if(ps2AllPipeData->meshHeader->numMeshes <= 0)
    {
        /* Oy... we have geometry with vertices but no triangles
         * We have to test because with plugin data to compensate
         * (e.g bezier control points, or procedural vector code
         * ala particles), it might be valid... :o/ */
        RWRETURN(FALSE);
    }

    /* Gather metrics */
    RpAtomicPS2AllGatherObjMetrics(atomic);

    /* Set up numMorphTargets and spExtra */
    RpAtomicPS2AllMorphSetup(atomic, ps2AllPipeData);

    /* Decide whether this geometry needs instancing or not */
    RpAtomicPS2AllObjInstanceTest(atomic, ps2AllPipeData);

    /* We need to cache the transform */
    RpAtomicPS2AllTransformSetup(atomic, transform);

    /* We need to to a frustum test here */
    RpAtomicPS2AllFrustumTest(atomic, &inFrustum);

    /* Set up primType and transtype */
    RpAtomicPS2AllPrimTypeTransTypeSetup(ps2AllPipeData, inFrustum);

    /* Do we modulate with material colours for this geometry? */
    RpAtomicPS2AllMatModulateSetup(atomic, ps2AllPipeData);

    /* Do default lighting unless overloaded (same function
     * overloads this and PS2Manager pipelines) */
    if(NULL != _rpSkinPS2AllLightFunc())
    {
        _rpSkinPS2AllLightFunc()( rxOBJTYPE_ATOMIC,
                                  ps2AllPipeData->sourceObject,
                                  ps2AllPipeData->surfProps,
                                  lightingFunc );
    }
    else
    {
        RpAtomicPS2AllLightingSetup(ps2AllPipeData, lightingFunc);
    }

    RWRETURN(TRUE);
}


/****************************************************************************
 _rxPipelineNodePS2SkinGenericPS2AllInstanceCallBack

 PS2All Instance Call-Back

 TODO DOC

 Inputs :
 Outputs:
 */
RwBool
_rxPipelineNodePS2SkinGenericPS2AllInstanceCallBack(
    RxPS2AllPipeData *ps2AllPipeData,
    void **clusterData,
    RwUInt32 numClusters __RWUNUSEDRELEASE__ )
{
    RpAtomic *atomic;
    RpGeometry *geometry;
    RpSkin *skin;

    RwReal *destMBI;

    RWFUNCTION(RWSTRING("_rxPipelineNodePS2SkinGenericPS2AllInstanceCallBack"));

    /* Grab the atomic. */
    atomic = (RpAtomic *)ps2AllPipeData->sourceObject;
    RWASSERT(NULL != atomic);

    /* Then it's geometry. */
    geometry = RpAtomicGetGeometry(atomic);
    RWASSERT(NULL != geometry);

    /* Finally the skin. */
    skin = *RPSKINGEOMETRYGETDATA(geometry);
    RWASSERT(NULL != skin);

    RWASSERT(1 == numClusters);

    /*------------ Standard data instancing -----------*/
    RpAtomicPS2AllInstance(ps2AllPipeData);

    /*------------ Skin weights and indices instancing -----------*/
    destMBI = (RwReal *)(clusterData[0]);
    RWASSERT(NULL != destMBI);

    /* Call the weights instancing. */
    _rpSkinWeightsInstancing(ps2AllPipeData->mesh, skin, destMBI);

    RWRETURN(TRUE);
}


/****************************************************************************
 _rxPipelineNodePS2SkinGenericPS2AllMatBridgeCallBack

 PS2All Bridge Call-Back

 TODO DOC

 Inputs :
 Outputs:
 */
RwBool
_rxPipelineNodePS2SkinGenericPS2AllMatBridgeCallBack(
    RxPS2AllPipeData *ps2AllPipeData )
{
    RpAtomic *atomic;
    RpGeometry *geometry;
    RpSkin *skin;

    RwUInt32 numMatrices = 0;
    RwUInt32 skinNumQW = 0;

    RwUInt32 numInitialQW;
    RwUInt32 numExtraQW;

    RWFUNCTION(RWSTRING("_rxPipelineNodePS2SkinGenericPS2AllMatBridgeCallBack"));

    /* Get atomic. */
    atomic = (RpAtomic *)ps2AllPipeData->sourceObject;
    RWASSERT(NULL != atomic);

    /* And it's geometry. */
    geometry = RpAtomicGetGeometry(atomic);
    RWASSERT(NULL != geometry);

    /* Finally the skin. */
    skin = *RPSKINGEOMETRYGETDATA(geometry);
    RWASSERT(NULL != skin);

    /* Are we uploading bones? */
    if(ps2AllPipeData->mesh == SKINGETFIRSTMESH(ps2AllPipeData->meshHeader))
    {
        numMatrices = skin->boneData.numBones;
        skinNumQW = 1 + 4 * numMatrices;
    }

    /* If you exit here, the mesh won't be rendered */

    /* Asynchronously upload the texture if nec/poss */
    RpMeshPS2AllAsyncTextureUpload(ps2AllPipeData);

    /* If changed, does skyTexCacheAccessRaster and
     * updates global renderstate */
    RpMeshPS2AllSyncTextureUpload(ps2AllPipeData);

    /* Open a VIF transfer packet, with a header DMA tag */
    numInitialQW = rpMESHPS2ALLGIFTAGNUMINITIALQW +
                   rpMESHPS2ALLMATCOLNUMINITIALQW +
                   rpMESHPS2ALLSURFPROPSNUMINITIALQW +
                   rpMESHPS2ALLCLIPINFONUMINITIALQW +
                   rpMESHPS2ALLTEXTURESTATENUMINITIALQW +
                   skinNumQW;
    numExtraQW = rpMESHPS2ALLVU1CODEUPLOADNUMEXTRAQW;
    RpMeshPS2AllStartVIFUploads(numInitialQW, numExtraQW);

    /* Skinning VIF uploads here... VIF tag(s) necessary.
     * Would have called _rxPS2AllStartVIFUploads with (numInitialQW = 0)
     * if needed DMA tags in the user uploads (would have had to fix up
     * with a terminal DMA tag so the following standard transfers work). */
    if (0 != skinNumQW)
    {
        RwMatrix *mba;
        /*----------------- Bones matrices uploading --------*/
        RwUInt128 ltmp = 0;
        RwUInt64 tmp;
        RwUInt64 tmp1;
        RwUInt32 i;

        mba = _rpSkinMatrixUpdating(atomic, skin);
        RWASSERT(NULL != mba);

        /* VIF tag to upload our matrices */
        tmp1 = ((((0x6CL << 24) |
                  ((numMatrices * 4) << 16) |  /*-- 4m QW --*/
                  ((long) (pipeASymbStaticDataStart))) << 32) |
                ((1L << 24) | (4 << 8) | (4)));
        tmp = 0x0l;
        MAKE128(ltmp, tmp1, tmp);
        SWEADDCONTFAST(ltmp);
        /*---------------------------------------------------*/

        /*-------- Upload Skin matrices ---------------------*/
        for (i = 0; i < numMatrices; i++, mba++)
        {
            ltmp = *((RwUInt128 *) & mba->right.x);
            SWEADDCONTFAST(ltmp);
            ltmp = *((RwUInt128 *) & mba->up.x);
            SWEADDCONTFAST(ltmp);
            ltmp = *((RwUInt128 *) & mba->at.x);
            SWEADDCONTFAST(ltmp);
            ltmp = *((RwUInt128 *) & mba->pos.x);
            SWEADDCONTFAST(ltmp);
        /*---------------------------------------------------*/
        }
    }

    /* Here follow the standard VIF uploads */

    /* Upload a GIF tag for the code to submit geom to the GS under */
    RpMeshPS2AllGIFTagUpload(ps2AllPipeData);

    /* Upload material colour, dependent on whether there's a texture
     * (also does some renderstate setup based on alpha - that's why
     * this needs to be before the texture state setup func) */
    RpMeshPS2AllMatColUpload(ps2AllPipeData);

    /* Upload surface properties, including the 'extra' float in W */
    RpMeshPS2AllSurfPropsUpload(ps2AllPipeData);

    /* Sets up clipping info and J-C's switch QWs (NOTE: uses "transType&7") */
    RpMeshPS2AllClipInfoUpload(ps2AllPipeData);

    /* Upload texture renderstate to the GS (this sends stuff thru VIF
     * only, ergo can be combined with other VIF stuff! :) ) */
    RpMeshPS2AllTextureStateUpload(ps2AllPipeData);

    /* Set up vu1CodeIndex */
    RpMeshPS2AllVU1CodeIndexSetup(ps2AllPipeData);

    /* Upload the VU1 code (if it changed) last, since
     * it uses a DMA tag (ref transfer of the code) */
    RpMeshPS2AllVU1CodeUpload(ps2AllPipeData);

    /* Kicks off geometry transfer, sets up refCnt/clrCnt and
     * closes the packet (so it can be extended later I think) */
    RpMeshPS2AllEndVIFUploads(ps2AllPipeData);

    RWRETURN(TRUE);
}

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

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