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

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

#include "rppatch.h"

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

#include "noded3d7patchatomicinstance.h"

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

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

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

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

/*===========================================================================*
 *--- Local Global Variables ------------------------------------------------*
 *===========================================================================*/
#if (!defined(DXOYGEN))
static const char rcsid[] __RWUNUSED__ =
    "@@@@(#)$Id: noded3d7patchatomicinstance.c,v 1.6 2001/09/19 12:40:11 johns Exp $";
#endif /* (!defined(DXOYGEN)) */

/*===========================================================================*
 *--- Local Defines ---------------------------------------------------------*
 *===========================================================================*/
#define NUMCLUSTERSOFINTEREST 5
#define NUMOUTPUTS 1

#define PATCHPASSTHROUGH 1

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

/*****************************************************************************
 PatchDX7PipelineNodeInitFn

 Initializes the private data (refinement ON by default)

 Inputs :
 Outputs :
 */
static RwBool
PatchDX7PipelineNodeInitFn( RxPipelineNode *self )
{
    RWFUNCTION(RWSTRING("PatchDX7PipelineNodeInitFn"));

    if (self)
    {
        RpNodePatchData     data;

        data.patchOn = TRUE;
        data.numExtraUVs = 0;
        data.numExtraRGBAs = 0;

        *((RpNodePatchData *) self->privateData) = data;
        RWRETURN(TRUE);
    }
    RWRETURN(FALSE);
}

static void
PatchDestroyDX7VertexBuffer( RwResEntry *repEntry )
{
    RpPatchDX7Atomic   *dx7Atomic = (RpPatchDX7Atomic *) (repEntry + 1);

    RWFUNCTION(RWSTRING("PatchDestroyDX7VertexBuffer"));
    RWASSERT(repEntry);

    if (dx7Atomic->vertexBuffer)
    {
        IDirect3DVertexBuffer7_Release(dx7Atomic->vertexBuffer);
        dx7Atomic->vertexBuffer = NULL;
    }

    RWRETURNVOID();
}

/*****************************************************************************
 PatchAtomicDX7InstanceNode

 _refineNode

 Inputs :
 Outputs :
 */
static RwBool
PatchAtomicDX7InstanceNode( RxPipelineNodeInstance *self,
                            const RxPipelineNodeParam *params )
{
    RwInt32 output;
    RpNodePatchData *patchData;
    RxHeap *heap;

    RpAtomic *atomic;
    RpGeometry *geom;

    PatchAtomicData *atomicData;
    RpMeshHeader *meshHeader;
    RwInt32 res;
    RwInt32 numVerts;
    RwInt32 numMeshes;
    RwInt32 numIndices;
    RwInt32 numTris;

    RwResEntry *repEntry;
    RwResEntry **repEntryOwner;

    void *owner;

    RpPatchDX7Atomic *dx7Atomic;
    RpInterpolator *interpolator;
    RpPatchInstanceAtomic *instAtomic;
    RpPatchInstanceMesh *instMesh;

    RxObjSpace3DVertex *vbVerts;
    D3DVERTEXBUFFERDESC vbDesc;

    RWFUNCTION(RWSTRING("PatchAtomicDX7InstanceNode"));

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

    heap = RxPipelineNodeParamGetHeap(params);
    RWASSERT(NULL != heap);

    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);
    }

    /* Default output is 0, pass-through. */
    output = PATCHPASSTHROUGH;

    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);
    }

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

    interpolator = &atomic->interpolator;

    /* 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 */
        dx7Atomic = (RpPatchDX7Atomic *) (repEntry + 1);
        instAtomic = &dx7Atomic->instAtomic;
        if ((instAtomic->serialNum != meshHeader->serialNum) ||
            (instAtomic->res != res))
        {
            /* 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)
    {
        PatchMesh *patchMesh;
        RwUInt32 numQuadPatches;
        RwUInt32 numTriPatches;

        const RpMesh       *mesh;
        RxVertexIndex      *idxs;
        RwUInt32            size;

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

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

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

        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);

        size = sizeof(RpPatchDX7Atomic) +
            (numVerts * sizeof(RxObjSpace3DVertex)) +
            (numMeshes * sizeof(RpPatchInstanceMesh)) +
            (numIndices * sizeof(RxVertexIndex));

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

        /* Extra header info */
        dx7Atomic = (RpPatchDX7Atomic *) (repEntry + 1);
        instAtomic = &dx7Atomic->instAtomic;
        instAtomic->serialNum = meshHeader->serialNum;
        instAtomic->res = res;

        /* Set the vertex pointer */
        mesh = (const RpMesh *)((const RwUInt8 *)(meshHeader + 1) +
                                meshHeader->firstMeshOffset);

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

        instAtomic->indices = idxs;
        instAtomic->vertices =
            (RxObjSpace3DVertex *) (idxs + numIndices);
        instAtomic->numMeshes = numMeshes;
        instAtomic->totalIndices = numIndices;
        instAtomic->totalVerts = numVerts;

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

        /* Allocate the vertex buffer */
        memset(&vbDesc, 0, sizeof(vbDesc));
        vbDesc.dwSize = sizeof(vbDesc);
        if (d3dGHardwareCaps.dwDevCaps & D3DDEVCAPS_TLVERTEXVIDEOMEMORY)
        {
            vbDesc.dwCaps = D3DVBCAPS_WRITEONLY;
        }
        else
        {
            vbDesc.dwCaps =
                D3DVBCAPS_WRITEONLY | D3DVBCAPS_SYSTEMMEMORY;
        }
        vbDesc.dwFVF = RWOBJSPACE3D_FVF;
        vbDesc.dwNumVertices = numVerts;
        if (ERR_WRAP(IDirect3D7_CreateVertexBuffer(lpD3D7, &vbDesc,
                                                   &dx7Atomic->
                                                   vertexBuffer,
                                                   0)) != D3D_OK)
        {
            /* Ooops */
            dx7Atomic->vertexBuffer = NULL;
            RWRETURN(FALSE);
        }

        if (ERR_WRAP(IDirect3DVertexBuffer7_Lock(dx7Atomic->vertexBuffer, DDLOCK_WRITEONLY | DDLOCK_WAIT | DDLOCK_SURFACEMEMORYPTR | DDLOCK_DISCARDCONTENTS, /* Woohoo - we can do this */
                                                 (LPVOID *) & vbVerts,
                                                 NULL)) == D3D_OK)

            memcpy(vbVerts, instAtomic->vertices,
                   numVerts * sizeof(RxObjSpace3DVertex));

        IDirect3DVertexBuffer7_Unlock(dx7Atomic->vertexBuffer);

        dx7Atomic->optimized = (geom->numMorphTargets < 2);

        /* If we have less than 2 morph targets, we can optimize the buffer */
        if (dx7Atomic->optimized)
        {
            ERR_WRAP(IDirect3DVertexBuffer7_Optimize
                     (dx7Atomic->vertexBuffer,
                      (LPDIRECT3DDEVICE7) RwD3DGetCurrentD3DDevice(),
                      0));
        }
    }
    else
    {
        RwResourcesUseResEntry(repEntry);
    }

    /* Now build reference clusters into the repEntry */

    instMesh = instAtomic->meshes;
    while (numMeshes--)
    {
        RxPacket           *packet;
        RxCluster          *objVerts, *indices, *meshState,
            *renderState, *vbInfoCluster;
        RxMeshStateVector  *meshData;
        RxRenderStateVector *rsvp;
        RxDX7VertexBufferInfo *vbInfo;
        RpMaterial         *mat;
#if (0)
        RwUInt8             primTypeRatio[7] = { 0, 2, 1, 3, 1, 1, 1 };
        RwUInt8             primTypeOffset[7] = { 0, 0, 1, 0, 1, 1, 0 };
#endif /* (0) */

        packet = RxPacketCreate(self);
        RWASSERT(NULL != packet);

        objVerts = RxClusterLockWrite(packet, 0, self);
        RWASSERT(NULL != objVerts);

        /* HACK: clear the MODIFIED flag */
        objVerts->flags = 0;

        meshState = RxClusterLockWrite(packet, 2, self);
        RWASSERT(NULL != meshState);
        renderState = RxClusterLockWrite(packet, 3, self);
        RWASSERT(NULL != renderState);
        vbInfoCluster = RxClusterLockWrite(packet, 4, self);
        RWASSERT(NULL != vbInfoCluster);

        meshState =
            RxClusterInitializeData(meshState, 1,
                                    sizeof(RxMeshStateVector));
        RWASSERT(meshState != NULL);
        renderState =
            RxClusterInitializeData(renderState, 1,
                                    sizeof(RxRenderStateVector));
        RWASSERT(renderState != NULL);
        vbInfoCluster =
            RxClusterInitializeData(vbInfoCluster, 1,
                                    sizeof(RxDX7VertexBufferInfo));
        RWASSERT(vbInfoCluster != NULL);

        objVerts = RxClusterSetExternalData(objVerts,
                                            instAtomic->vertices +
                                            instMesh->minVert,
                                            sizeof(RxObjSpace3DVertex),
                                            instMesh->numVerts);
        RWASSERT(NULL != objVerts);

        /* Set up MeshState data for this packet */
        mat = instMesh->material;
        meshData = RxClusterGetCursorData(meshState, RxMeshStateVector);
        RWASSERT(NULL != meshData);
        meshData->SourceObject = (void *) mat;
        meshData->DataObject =
            (void *) RxPipelineNodeParamGetData(params);
        meshData->PrimType = (RwPrimitiveType) rwPRIMTYPETRISTRIP;
        /* meshData->PrimType = (RwPrimitiveType) rwPRIMTYPEPOLYLINE; */
        meshData->NumElements = instMesh->numIndices;
        meshData->NumVertices = instMesh->numVerts;

        /* Set up the Local to Camera matrix for this Atomic */
        RwMatrixSetIdentity(&meshData->Obj2Cam);
        meshData->Obj2World = *RwFrameGetLTM(RpAtomicGetFrame(atomic));
        meshData->SurfaceProperties =
            *RpMaterialGetSurfaceProperties(mat);
        meshData->Flags = RpGeometryGetFlags(geom);
        meshData->Texture = RpMaterialGetTexture(mat);
        meshData->MatCol = *RpMaterialGetColor(mat);
        meshData->Pipeline = mat->pipeline;
        meshData->ClipFlagsAnd = 0;
        meshData->ClipFlagsOr = 0;
        meshData->SourceMesh = instMesh->mesh;

        meshState->numUsed++;

        /* Set up RenderState data for this packet */
        rsvp = RxClusterGetCursorData(renderState, RxRenderStateVector);
        *rsvp = RXPIPELINEGLOBAL(defaultRenderState);
        if (meshData->Flags & rpGEOMETRYTEXTURED)
        {
            if (NULL != meshData->Texture)
            {
                rsvp->TextureRaster =
                    RwTextureGetRaster(meshData->Texture);
                rsvp->AddressModeU =
                    RwTextureGetAddressingU(meshData->Texture);
                rsvp->AddressModeV =
                    RwTextureGetAddressingV(meshData->Texture);
                rsvp->FilterMode =
                    RwTextureGetFilterMode(meshData->Texture);
            }
        }

        /* we like transparent objects too */
        if (meshData->MatCol.alpha != 255)
        {
            rsvp->Flags |= rxRENDERSTATEFLAG_VERTEXALPHAENABLE;
        }

        /* Set up vbInfo */
        vbInfo =
            RxClusterGetCursorData(vbInfoCluster,
                                   RxDX7VertexBufferInfo);
        vbInfo->vertexBuffer = (void *) dx7Atomic->vertexBuffer;
        vbInfo->minVert = instMesh->minVert;
        vbInfo->numVerts = instMesh->numVerts;

        /* Ready for next mesh */
        indices = RxClusterLockWrite(packet, 1, self);
        RWASSERT(NULL != indices);
        indices = RxClusterSetExternalData(indices,
                                           instMesh->indices,
                                           sizeof(RxVertexIndex),
                                           instMesh->numIndices);
        RWASSERT(NULL != indices);

        RxPacketDispatch(packet, 0, self);

        /* Next mesh */
        instMesh++;
    }

#ifdef RWMETRICS
    /* Now update our metrics statistics */
    RWSRCGLOBAL(metrics)->numVertices += numVerts;
    RWSRCGLOBAL(metrics)->numTriangles += numTris;
#endif

    RWRETURN(TRUE);
}

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

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

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

/**
 * \ingroup rprefine
 * \ref RxNodeDefinitionGetDX7PatchAtomicInstance
 * 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 RxClDX7VertexBufferInfo - don't want
 *
 * The characteristics of this node's outputs:
 *     \li RxClObjSpace3DVertices  - valid
 *     \li RxClIndices             - valid
 *     \li RxClMeshState           - valid
 *     \li RxClRenderState         - valid
 *     \li RxClDX7VertexBufferInfo - valid
 *
 * \return pointer to node for patch faceting custom pipelines on success,
 * NULL otherwise
 */
RxNodeDefinition   *
RxNodeDefinitionGetDX7PatchAtomicInstance(void)
{
    static RxClusterRef gNodeClusters[NUMCLUSTERSOFINTEREST] = /* */
    {
        {&RxClObjSpace3DVertices, rxCLALLOWABSENT, rxCLRESERVED},
        {&RxClIndices, rxCLALLOWABSENT, rxCLRESERVED},
        {&RxClMeshState, rxCLALLOWABSENT, rxCLRESERVED},
        {&RxClRenderState, rxCLALLOWABSENT, rxCLRESERVED},
        {&RxClDX7VertexBufferInfo, rxCLALLOWABSENT, rxCLRESERVED}
    };

    static RxClusterValidityReq gNodeReqs[NUMCLUSTERSOFINTEREST] = /* */
        /* parallel to ClusterRefs */
    {
        rxCLREQ_DONTWANT,
        rxCLREQ_DONTWANT,
        rxCLREQ_DONTWANT,
        rxCLREQ_DONTWANT,
        rxCLREQ_DONTWANT
    };

    static RxClusterValid gNodePatchOut[NUMCLUSTERSOFINTEREST] = /* */
        /* parallel to ClusterRefs */
    {
        rxCLVALID_VALID,
        rxCLVALID_VALID,
        rxCLVALID_VALID,
        rxCLVALID_VALID,
        rxCLVALID_VALID,
    };

    static RwChar       _PatchOut[] = RWSTRING("PatchOut");

    static RxOutputSpec gNodeOuts[NUMOUTPUTS] = /* */
    {
        {
         _PatchOut,            /* Name */
         gNodePatchOut,        /* OutputClusters */
         rxCLVALID_NOCHANGE}
    };                         /* AllOtherClusters */

    static RwChar       _PatchAtomic_csl[] =
        RWSTRING("PatchAtomic.csl");

    static RxNodeDefinition nodeDX7PatchAtomic = /* */
    {
        _PatchAtomic_csl,       /* Name */
        {PatchAtomicDX7InstanceNode, /* +-- nodebody */
         (RxNodeInitFn) NULL,
         (RxNodeTermFn) NULL,
         PatchDX7PipelineNodeInitFn, /* +-- pipelinenodeinit */
         (RxPipelineNodeTermFn) NULL,
         (RxPipelineNodeConfigFn) NULL,
         (RxConfigMsgHandlerFn) NULL},
        {                      /* Io */
         NUMCLUSTERSOFINTEREST, /* +-- NumClustersOfInterest */
         gNodeClusters,        /* +-- ClustersOfInterest */
         gNodeReqs,            /* +-- InputRequirements */
         NUMOUTPUTS,           /* +-- NumOutputs */
         gNodeOuts             /* +-- Outputs */
         }
        , sizeof(RpNodePatchData), /* no private data needed for pipeline nodes made from this */
        (RxNodeDefEditable) FALSE, /* node definition not editable (it's a global) */
        0
    };                         /* how many pipeline nodes have been made from this definition? */

    RxNodeDefinition   *result = &nodeDX7PatchAtomic;

    RWAPIFUNCTION(RWSTRING("RxNodeDefinitionGetDX7PatchAtomicInstance"));

    RWRETURN(result);
}
