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

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

#include "rppatch.h"

#include "patchatomic.h"
#include "patch.h"

#include "nodexboxpatchatomicinstance.h"

#define RWOBJSPACE3D_FVF  (D3DFVF_XYZ | D3DFVF_NORMAL | D3DFVF_DIFFUSE | D3DFVF_TEX1)

#define NUMCLUSTERSOFINTEREST   0
#define NUMOUTPUTS              0

#define IGNORED_XBOX            0

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

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

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

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

/*===========================================================================*
 *--- Local Global Variables ------------------------------------------------*
 *===========================================================================*/
#if (!defined(DXOYGEN))
static const char rcsid[] __RWUNUSED__ =
    "@@@@(#)$Id: nodexboxpatchatomicinstance.c,v 1.6 2001/10/04 15:01:50 davidg Exp $";
#endif /* (!defined(DXOYGEN)) */

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

#define PATCHPASSTHROUGH 1

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

/*****************************************************************************
 _rxXBoxPatchDestroyVertexBuffer

 Destroy all the buffer memory

 Inputs :
 Outputs :
 */
static void
_rxXBoxPatchDestroyVertexBuffer( RwResEntry *repEntry )
{
    RxXboxResEntryHeader    *resEntryHeader;

    RWFUNCTION(RWSTRING("_rxXBoxPatchDestroyVertexBuffer"));

    resEntryHeader = (RxXboxResEntryHeader *)(repEntry + 1);

    /* Destroy the only one vertex buffer that we have */
    if (resEntryHeader->vertexBuffer)
    {
       D3DResource_Release((D3DResource *)resEntryHeader->vertexBuffer);
       resEntryHeader->vertexBuffer = NULL;
    }

    RWRETURNVOID();
}

/*****************************************************************************
 _rxXBoxPatchAtomicAllInOneNodeNode

 _refineNode

 Inputs :
 Outputs :
 */
static RwBool
_rxXBoxPatchAtomicAllInOneNodeNode( RxPipelineNodeInstance *self,
                            const RxPipelineNodeParam *params )
{
    RpNodePatchData *patchData;

    RpAtomic        *atomic;
    RpGeometry      *geom;

    PatchAtomicData *atomicData;
    RpMeshHeader    *meshHeader;
    RwInt32         res;
    RwInt32         numMeshes;

    RwResEntry      *repEntry;
    RwResEntry      **repEntryOwner;

    PatchMesh       *patchMesh;
    RwUInt32        numQuadPatches;
    RwUInt32        numTriPatches;
    RwUInt32         numVerts;

    void            *owner;

    RxXboxResEntryHeader    *resEntryHeader;

    RWFUNCTION(RWSTRING("_rxXBoxPatchAtomicAllInOneNodeNode"));

    RWASSERT(NULL != self);
    RWASSERT(NULL != params);

    patchData = (RpNodePatchData *) self->privateData;
    RWASSERT(NULL != patchData);

    /* Cheap early out if this node's toggled off */
    if (0 && (patchData->patchOn == FALSE))
    {
        RxPacketDispatch(NULL, PATCHPASSTHROUGH, self);
        RWRETURN(TRUE);
    }

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

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

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

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

    meshHeader = geom->mesh;
    numMeshes = meshHeader->numMeshes;
    /* Early out if no meshes */
    if (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);
    }

    /* Get the patch mesh. info */
    patchMesh = atomicData->patchMesh;
    RWASSERT(NULL != patchMesh);

    numQuadPatches = _rpPatchMeshGetNumQuadPatches(patchMesh);
    numTriPatches = _rpPatchMeshGetNumTriPatches(patchMesh);

    /* Query for the LOD. */
    res = (atomicData->lod.callback)(atomic, atomicData->lod.userData);

    /* Total number of verts. */
    numVerts = (numQuadPatches * PATCHQUADNUMVERT(res + 1)) +
               (numTriPatches * PATCHTRINUMVERT(res + 1));


    /* If the geometry has more than one morph target the resEntry in the
     * atomic is used else the resEntry in the geometry */
    if (RpGeometryGetNumMorphTargets(geom) != 1)
    {
        owner = (void *) atomic;
        repEntryOwner = &atomic->repEntry;
        repEntry = atomic->repEntry;
    }
    else
    {
        owner = (void *) geom;
        repEntryOwner = &geom->repEntry;
        repEntry = geom->repEntry;
    }

    if (NULL != repEntry)
    {
        /* If anything has changed, we should re-instance */
        resEntryHeader = (RxXboxResEntryHeader *) (repEntry + 1);

        if ( (resEntryHeader->serialNumber != meshHeader->serialNum) ||
             (numVerts != resEntryHeader->numVertices) )
        {
            /* Things have changed, destroy resources to force reinstance */
            RwResourcesFreeResEntry(repEntry);
            repEntry = NULL;
        }
    }

    /* We need to re-generate the mesh due to some changes. */
    if (repEntry == NULL)
    {
        RwInt32                 numIndices;
        RwInt32                 numTris;
        RwUInt32                size;
        D3DVertexBuffer         *vertexBuffer;
        RpPatchInstanceAtomic   *instAtomic;
        RxVertexIndex           *idxs;
        RpPatchInstanceMesh     *instMesh;
        RxXboxInstanceData      *instancedData;
        RxVertexIndex           *curIndexBuffer;

        /* Total number of indices and tris. */
        numIndices = (numQuadPatches * PATCHQUADNUMINDEX(res + 1)) +
                     (numTriPatches * PATCHTRINUMINDEX(res + 1));

        numTris = (numQuadPatches * PATCHQUADNUMTRI(res + 1)) +
                  (numTriPatches * PATCHTRINUMTRI(res + 1));

        /* Add extra indices for connecting patches together. */
        numIndices += (numQuadPatches + numTriPatches - 1) * 2;

        /* Add extra for odd number of tri patches to preserve winding order. */
        if (res & 0x01)
            numIndices += (numTriPatches);

        /* Create only one vertex buffer */
        if (D3D_OK != D3DDevice_CreateVertexBuffer(
                            numVerts * sizeof(RxObjSpace3DVertex),
                            IGNORED_XBOX, IGNORED_XBOX, IGNORED_XBOX,
                            (D3DVertexBuffer **)&vertexBuffer))
        {
            RWRETURN(FALSE);
        }

        /* Create temporary atomic info */
        size = sizeof(RpPatchInstanceAtomic) +
            /*
             * (numVerts * sizeof(RxObjSpace3DVertex)) +
             */
            (numMeshes * sizeof(RpPatchInstanceMesh)) +
            (numIndices * sizeof(RxVertexIndex));

        instAtomic = (RpPatchInstanceAtomic *)RwMalloc(size);

        idxs = (RxVertexIndex *) & instAtomic->meshes[numMeshes];

        instAtomic->indices = idxs;
        instAtomic->numMeshes = numMeshes;
        instAtomic->totalIndices = numIndices;
        instAtomic->totalVerts = numVerts;

        /* Tesselate directly to the vertex buffer */
        D3DVertexBuffer_Lock(vertexBuffer, 0, 0, (RwUInt8 **)&(instAtomic->vertices), 0);

        /* Fill the vertex buffer (interpolates) */
        if (FALSE == _rpPatchInstanceAtomic(instAtomic, atomic, res))
        {
            RwFree(instAtomic);

            D3DResource_Release((D3DResource *)vertexBuffer);

            RWRETURN(FALSE);
        }       

        D3DVertexBuffer_Unlock((D3DVertexBuffer *)vertexBuffer);

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

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

        /* Index buffers must be 16byte aligned */
        size = (size + 15) & ~0xF;

        /* Index buffers */
        instMesh = instAtomic->meshes;
        numMeshes = instAtomic->numMeshes;
        while (numMeshes--)
        {
            /* Index buffers should be 16byte aligned */
            size += ((sizeof(RxVertexIndex) * instMesh->numIndices) + 15) & ~0xF;
            instMesh++;
        }

        repEntry = RwResourcesAllocateResEntry(owner, repEntryOwner,
                                               size,
                                               _rxXBoxPatchDestroyVertexBuffer);
        RWASSERT(NULL != repEntry);

        /* Blank the RpPatchInstanceAtomic & RxXboxInstanceData's to '0' */
        memset((repEntry + 1), 0, size);

        /* Create Temporary atomic info */
        resEntryHeader = (RxXboxResEntryHeader *) (repEntry + 1);
        resEntryHeader->serialNumber = meshHeader->serialNum;
        resEntryHeader->numMeshes = (RwUInt16)(instAtomic->numMeshes);

        /* Save pointer to vertex buffer */
        resEntryHeader->vertexBuffer = vertexBuffer;

        /* Primitive type */
        resEntryHeader->primType = D3DPT_TRIANGLESTRIP;

        /* Number of vertices */
        resEntryHeader->numVertices = numVerts;

        /* The vertex format stride */
        resEntryHeader->stride = sizeof(RxObjSpace3DVertex);

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

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

        /* First instance */
        resEntryHeader->begin = instancedData;

        /* Find the base index buffer */
        curIndexBuffer = (RxVertexIndex *)(instancedData + meshHeader->numMeshes);
        curIndexBuffer = (RxVertexIndex *)((((RwUInt32)(curIndexBuffer)) + 15) & ~0xF);

        /* Fill instance data and indices */
        instMesh = instAtomic->meshes;
        numMeshes = instAtomic->numMeshes;
        while (numMeshes--)
        {
            /*
             * Min vertex index,
             * (needed for instancing & reinstancing)
             */
            instancedData->minVert = instMesh->minVert;

            /* Number of vertices */
            instancedData->numVertices = instMesh->numVerts;

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

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

            /* Pixel shader */
            instancedData->pixelShader = 0;

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

            /* Initialize the index buffers pointers */
            instancedData->indexBuffer = curIndexBuffer;

            /*
             * Set the index buffer
             */
            if (instancedData->minVert)
            {
                RxVertexIndex   *indexBuffer;
                RxVertexIndex   *index;
                RwUInt32        numIndices;

                indexBuffer = instancedData->indexBuffer;
                index = instMesh->indices;
                numIndices = instMesh->numIndices;
                while (numIndices--)
                {
                    *indexBuffer = (RxVertexIndex)((*index) + instancedData->minVert);

                    indexBuffer++;
                    index++;
                }
            }
            else
            {
                memcpy(instancedData->indexBuffer, instMesh->indices,
                        sizeof(RxVertexIndex) * instMesh->numIndices);
            }

            /* Jump to the next index buffer */
            curIndexBuffer += instMesh->numIndices;
            curIndexBuffer = (RxVertexIndex *)((((RwUInt32)(curIndexBuffer)) + 15) & ~0xF);

            instancedData++;
            instMesh++;
        }

        /* One-past-last instance */
        resEntryHeader->end = instancedData;

        RwFree(instAtomic);
    }
    else
    {
        RwResourcesUseResEntry(repEntry);
    }

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

    /* 
     * Set the world transform
     */
    {
        D3DMATRIX   matrix;
        RwMatrix    *atomicLTM;

        atomicLTM = RwFrameGetLTM(RpAtomicGetFrame(atomic));

        matrix.m[0][0] = atomicLTM->right.x;
        matrix.m[0][1] = atomicLTM->right.y;
        matrix.m[0][2] = atomicLTM->right.z;
        matrix.m[0][3] = 0.0f;

        matrix.m[1][0] = atomicLTM->up.x;
        matrix.m[1][1] = atomicLTM->up.y;
        matrix.m[1][2] = atomicLTM->up.z;
        matrix.m[1][3] = 0.0f;

        matrix.m[2][0] = atomicLTM->at.x;
        matrix.m[2][1] = atomicLTM->at.y;
        matrix.m[2][2] = atomicLTM->at.z;
        matrix.m[2][3] = 0.0f;

        matrix.m[3][0] = atomicLTM->pos.x;
        matrix.m[3][1] = atomicLTM->pos.y;
        matrix.m[3][2] = atomicLTM->pos.z;
        matrix.m[3][3] = 1.0f;

        D3DDevice_SetTransform(D3DTS_WORLD, &matrix);
    }

    /*
     * Render
     */
    if (patchData->renderCallback != NULL)
    {
        patchData->renderCallback(resEntryHeader, atomic, rpATOMIC, RpGeometryGetFlags(geom));
    }

    RWRETURN(TRUE);
}

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

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

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

/**
 * \ingroup rprefine
 * \ref RxNodeDefinitionGetXBoxPatchAtomicInstance
 * returns a pointer to a node to instance a patch atomic in a form suitable for
 * rendering by a DirectX 7 pipeline.
 *
 * The node has one output. Successful generation of meshes from patches are passed
 * via this output.
 *
 * The input requirements of this node:
 *     \li RxClObjSpace3DVertices  - don't want
 *     \li RxClIndices             - don't want
 *     \li RxClMeshState           - don't want
 *     \li RxClRenderState         - don't want
 *     \li RxClXBoxVertexBufferInfo - don't want
 *
 * The characteristics of this node's outputs:
 *     \li RxClObjSpace3DVertices  - valid
 *     \li RxClIndices             - valid
 *     \li RxClMeshState           - valid
 *     \li RxClRenderState         - valid
 *     \li RxClXBoxVertexBufferInfo - valid
 *
 * \return pointer to node for patch faceting custom pipelines on success,
 * NULL otherwise
 */
RxNodeDefinition   *
RxNodeDefinitionGetXBoxPatchAtomicInstance(void)
{
    static RwChar _PatchAtomic_csl[] = "PatchAtomic.csl";
    
    static RxNodeDefinition nodeXBoxPatchAtomic = { /* */
        _PatchAtomic_csl,            /* Name */
        {                                           /* Nodemethods */
            _rxXBoxPatchAtomicAllInOneNodeNode,         /* +-- nodebody */
            NULL,                                   /* +-- nodeinit */
            NULL,                                   /* +-- nodeterm */
            _rwXBoxPatchPipelineNodeInitFn,    /* +-- pipelinenodeinit */
            NULL,                                   /* +-- pipelineNodeTerm */
            NULL,                                   /* +-- pipelineNodeConfig */
            NULL,                                   /* +-- configMsgHandler */
        },
        {                                           /* Io */
            NUMCLUSTERSOFINTEREST,                  /* +-- NumClustersOfInterest */
            NULL,                                   /* +-- ClustersOfInterest */
            NULL,                                   /* +-- InputRequirements */
            NUMOUTPUTS,                             /* +-- NumOutputs */
            NULL                                    /* +-- Outputs */
        },
        (RwUInt32)sizeof(RpNodePatchData),  /* PipelineNodePrivateDataSize */
        FALSE,                                      /* editable */
        0                                           /* inPipes */
    };

    RWAPIFUNCTION(RWSTRING("RxNodeDefinitionGetXBoxPatchAtomicInstance"));

    RWRETURN(&nodeXBoxPatchAtomic);
}
