/****************************************************************************
 *                                                                          *
 * module : nodeD3D7SubmitParticles.c                                       *
 *                                                                          *
 * purpose:                                                                 *
 *                                                                          *
 ****************************************************************************/

/****************************************************************************
 includes
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "rpplugin.h"
#include <rpdbgerr.h>
#include <rwcore.h>
#include <rpworld.h>

#include "nodeD3D7SubmitParticles.h"

#include "prvprtcl.h"

#include "windows.h"
#include "d3d.h"

/* Required variables from the core... */
extern D3DDEVICEDESC7 d3dGHardwareCaps;
extern LPDIRECT3D7  lpD3D7;

/**
 * \defgroup rpprtsysd3d7 D3D7
 * \ingroup rpprtsys
 *
 * Submitting object space particles to D3D7 in custom pipelines 
 * 
 * Copyright (c) Criterion Software Limited
 */

static const char   rcsid[] __RWUNUSED__ =
    "@@(#)$Id: nodeD3D7SubmitParticles.c,v 1.17 2001/09/19 12:15:13 johns Exp $";

/****************************************************************************
 local defines
 */

/* Define this to render each particle as an individual trifan */
#define USETRIFANx
#define USEVERTEXBUFFERx       /* Define this to submit from a vertex buffer */

/****************************************************************************
 local (static) globals
 */

typedef struct _RpParticleInfo RpParticleInfo;
struct _RpParticleInfo
{
    RwV3d               position;
    RwV3d               velocity;
    RwReal              startTime;
    RwV2d               size;
};

typedef struct _RpParticlesGeomData RpParticlesGeomData;
struct _RpParticlesGeomData
{
    RpParticleInfo     *particles;
};

#define PARTICLESGEOMGETDATA(geometry)                  \
    ((RpParticlesGeomData **)(((RwUInt8 *)geometry) +   \
      GParticlesGeomDataOffset))


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

   functions

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

#define BUFFER_MAX_VERTS    10000

#ifdef USEVERTEXBUFFER
static LPDIRECT3DVERTEXBUFFER7 VertexBuffer = NULL; /* Vertex buffer */
#endif /*  USEVERTEXBUFFER */

static D3DLVERTEX          PartVerts[10000];
static RwImVertexIndex     Indices[15000]; /* 6 indices per 4 vertex particles */

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

   functions

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

static              RwBool
InitParticleSubmitNode(RxNodeDefinition * self __RWUNUSED__)
{
    RwInt32             i;

#ifdef USEVERTEXBUFFER
    D3DVERTEXBUFFERDESC vbDesc;
    static const DWORD  fvf = D3DFVF_LVERTEX;
#endif
    RWFUNCTION(RWSTRING("InitParticleSubmitNode"));
#ifdef USEVERTEXBUFFER

    /* Set up a vertex buffer for our vertices */
    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;
    }

    /* Set up the format of our immediate mode vertices */

    vbDesc.dwFVF = fvf;
    /* According to NVidia, this is pretty optimal */
    vbDesc.dwNumVertices = BUFFER_MAX_VERTS;
    if (ERR_WRAP(IDirect3D7_CreateVertexBuffer(lpD3D7, &vbDesc,
                                               &VertexBuffer,
                                               0)) != D3D_OK)
    {
        /* Ooops */
        MESSAGE(RWSTRING
                ("Unable to create vertex buffer for immediate mode"));
        VertexBuffer = NULL;
    }
#endif

    /* Initialise particles indices */
    for (i = 0; i < BUFFER_MAX_VERTS / 4; i++)
    {
        Indices[i * 6] = i * 4;
        Indices[i * 6 + 1] = i * 4 + 1;
        Indices[i * 6 + 2] = i * 4 + 2;
        Indices[i * 6 + 3] = i * 4 + 2;
        Indices[i * 6 + 4] = i * 4 + 3;
        Indices[i * 6 + 5] = i * 4;

        PartVerts[i * 4].tu = 0;
        PartVerts[i * 4].tv = 0;

        PartVerts[i * 4 + 1].tu = 0;
        PartVerts[i * 4 + 1].tv = 1;

        PartVerts[i * 4 + 2].tu = 1;
        PartVerts[i * 4 + 2].tv = 1;

        PartVerts[i * 4 + 3].tu = 1;
        PartVerts[i * 4 + 3].tv = 0;
    }

    RWRETURN(TRUE);
}

static void
TermParticleSubmitNode(RxNodeDefinition * self __RWUNUSED__)
{
    RWFUNCTION(RWSTRING("TermParticleSubmitNode"));

#ifdef USEVERTEXBUFFER
    if (VertexBuffer)
    {
        IDirect3DVertexBuffer7_Release(VertexBuffer);
        VertexBuffer = NULL;
    }
#endif

    RWRETURNVOID();
}

/****************************************************************************
 SubmitNode

 on entry: -
 on exit : -
*/

static              RwBool
SubmitNode(RxPipelineNodeInstance * self,
           const RxPipelineNodeParam * params)
{
    RxPacket           *packet;
    RxCluster          *objVerts;
    RxCluster          *meshState;
    RxCluster          *renderState;
    RxMeshStateVector  *meshData = NULL;
    RxRenderStateVector *rsvp;

    RWFUNCTION(RWSTRING("SubmitNode"));

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

    objVerts = RxClusterLockRead(packet, 0);
    meshState = RxClusterLockRead(packet, 1);
    renderState = RxClusterLockRead(packet, 2);

    RWASSERT((NULL != objVerts) && (objVerts->numUsed > 0));
    RWASSERT(NULL != meshState);
    meshData = RxClusterGetCursorData(meshState, RxMeshStateVector);
    RWASSERT(NULL != meshData);

    if ((NULL != renderState) && (renderState->numUsed > 0))
    {
        rsvp = RxClusterGetCursorData(renderState, RxRenderStateVector);
        RWASSERT(rsvp != NULL);
    }
    else
    {
        rsvp = &RXPIPELINEGLOBAL(defaultRenderState);
    }

    /* Select texture if needed */
    if ( /*(meshData->Flags & rxGEOMETRY_TEXTURED) && */ rsvp->
        TextureRaster)
    {
        RwRenderStateSet(rwRENDERSTATETEXTURERASTER,
                         (void *) rsvp->TextureRaster);
        RwRenderStateSet(rwRENDERSTATETEXTUREFILTER,
                         (void *) rsvp->FilterMode);

        if (rsvp->AddressModeU == rsvp->AddressModeV)
        {
            RwRenderStateSet(rwRENDERSTATETEXTUREADDRESS,
                             (void *) rsvp->AddressModeU);
        }
        else
        {
            RwRenderStateSet(rwRENDERSTATETEXTUREADDRESSU,
                             (void *) rsvp->AddressModeU);
            RwRenderStateSet(rwRENDERSTATETEXTUREADDRESSV,
                             (void *) rsvp->AddressModeV);
        }
    }
    else
    {
        RwRenderStateSet(rwRENDERSTATETEXTURERASTER, (void *) NULL);
    }

    /* Set blend state */
    if (rxRENDERSTATEFLAG_VERTEXALPHAENABLE & rsvp->Flags)
    {
        RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void *) TRUE);
    }
    else
    {
        RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE,
                         (void *) FALSE);
    }

    /* Set up the D3D engine with the correct matrix though */
    if (!rwMatrixTestFlags
        (&meshData->Obj2World, rwMATRIXINTERNALIDENTITY))
    {
        _rwD3DSetWorldMatrix(&meshData->Obj2World);
    }

    {
        RwIm3DVertex       *verts =
            RxClusterGetCursorData(objVerts, RwIm3DVertex);
        LPDIRECT3DDEVICE7   currDevice =
            (LPDIRECT3DDEVICE7) RwD3DGetCurrentD3DDevice();

        /* Variables to transform particles */
        RwCamera           *cam;
        RwFrame            *camFrame;
        RwMatrix           *camLTM, *particleMatrix;
        RwV3d               up, right;
        RwV3d               scaledUp, scaledRight;

        RpAtomic           *atomic;
        RpGeometry         *geom;
        RpParticlesGeomData *particlesGeomData;
        RpParticleInfo     *particleInfo;

        RwInt32             numVertices = meshData->NumVertices;

        cam = RwCameraGetCurrentCamera();
        RWASSERT(cam);

        camFrame = RwCameraGetFrame(cam);
        RWASSERT(camFrame);

        camLTM = RwFrameGetLTM(camFrame);
        RWASSERT(camLTM);

        up = *RwMatrixGetUp(camLTM);
        right = *RwMatrixGetRight(camLTM);

        particleMatrix = RwMatrixCreate();
        RwMatrixInvert(particleMatrix, &meshData->Obj2World);

        RwV3dTransformVectors(&up, &up, 1, particleMatrix);
        RwV3dTransformVectors(&right, &right, 1, particleMatrix);

        RwMatrixDestroy(particleMatrix);

        RwV3dNormalize(&up, &up);
        RwV3dNormalize(&right, &right);

        RwV3dScale(&up, &up, ((RwReal) 0.5));
        RwV3dScale(&right, &right, ((RwReal) 0.5));

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

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

        particlesGeomData = *PARTICLESGEOMGETDATA(geom);
        RWASSERT(NULL != particlesGeomData);

        particleInfo = particlesGeomData->particles;
        RWASSERT(NULL != particleInfo);

        do
        {
            RwInt32             i;

            for (i = 0; (i < numVertices) && (i * 4 < BUFFER_MAX_VERTS);
                 i++)
            {
                RwV3dScale(&scaledUp, &up, particleInfo->size.x);
                RwV3dScale(&scaledRight, &right, particleInfo->size.y);

                PartVerts[i * 4].x =
                    verts->objVertex.x + scaledRight.x + scaledUp.x;
                PartVerts[i * 4].y =
                    verts->objVertex.y + scaledRight.y + scaledUp.y;
                PartVerts[i * 4].z =
                    verts->objVertex.z + scaledRight.z + scaledUp.z;
                PartVerts[i * 4].color = verts->color;

                PartVerts[i * 4 + 1].x =
                    verts->objVertex.x + scaledRight.x - scaledUp.x;
                PartVerts[i * 4 + 1].y =
                    verts->objVertex.y + scaledRight.y - scaledUp.y;
                PartVerts[i * 4 + 1].z =
                    verts->objVertex.z + scaledRight.z - scaledUp.z;
                /* IF FLAT SHADED - NOT USED */
                PartVerts[i * 4 + 1].color = verts->color;

                PartVerts[i * 4 + 2].x =
                    verts->objVertex.x - scaledRight.x - scaledUp.x;
                PartVerts[i * 4 + 2].y =
                    verts->objVertex.y - scaledRight.y - scaledUp.y;
                PartVerts[i * 4 + 2].z =
                    verts->objVertex.z - scaledRight.z - scaledUp.z;
                PartVerts[i * 4 + 2].color = verts->color;

                PartVerts[i * 4 + 3].x =
                    verts->objVertex.x - scaledRight.x + scaledUp.x;
                PartVerts[i * 4 + 3].y =
                    verts->objVertex.y - scaledRight.y + scaledUp.y;
                PartVerts[i * 4 + 3].z =
                    verts->objVertex.z - scaledRight.z + scaledUp.z;
                /* IF FLAT SHADED - NOT USED */
                PartVerts[i * 4 + 3].color = verts->color;

#ifdef USETRIFAN
                ERR_WRAP(IDirect3DDevice7_DrawPrimitive(currDevice,
                                                        D3DPT_TRIANGLEFAN,
                                                        D3DFVF_LVERTEX,
                                                        &(PartVerts
                                                          [i * 4]), 4,
                                                        0));
#endif
                verts++;       /* Next particle */
                particleInfo++; /* Next particle descriptor */
            }

#ifndef USETRIFAN
#ifdef USEVERTEXBUFFER         /* Define this to submit from a vertex buffer */
            {
                D3DLVERTEX         *bufferMem;

                ERR_WRAP(IDirect3DVertexBuffer7_Lock(VertexBuffer,
                                                     DDLOCK_WRITEONLY |
                                                     DDLOCK_WAIT |
                                                     DDLOCK_SURFACEMEMORYPTR
                                                     |
                                                     DDLOCK_DISCARDCONTENTS,
                                                     (LPVOID *) &
                                                     bufferMem, NULL));

                memcpy(bufferMem, PartVerts,
                       sizeof(RpParticleD3D7Vertex) * i * 4);

                IDirect3DVertexBuffer7_Unlock(VertexBuffer);

                ERR_WRAP(IDirect3DDevice7_DrawIndexedPrimitiveVB
                         (currDevice, D3DPT_TRIANGLELIST, VertexBuffer,
                          0, i * 4, Indices, i * 6, 0));
            }
#else /* Render particles as a single trilist - default */
            ERR_WRAP(IDirect3DDevice7_DrawIndexedPrimitive(currDevice,
                                                           D3DPT_TRIANGLELIST,
                                                           D3DFVF_LVERTEX,
                                                           PartVerts,
                                                           i * 4,
                                                           Indices,
                                                           i * 6, 0));
#endif
#endif
            numVertices -= i;
        }
        while (numVertices > 0); /* While we still have particles to process */
    }

#ifdef RWMETRICS
    RWSRCGLOBAL(metrics)->numProcTriangles += 2 * meshData->NumVertices;
    RWSRCGLOBAL(metrics)->numTriangles += 2 * meshData->NumVertices;
    RWSRCGLOBAL(metrics)->numVertices += 4 * meshData->NumVertices;
#endif

    if (!rwMatrixTestFlags
        (&meshData->Obj2World, rwMATRIXINTERNALIDENTITY))
    {
        _rwD3DSetWorldMatrix(NULL);
    }

    /* Output the packet to the first (and only) output of this Node.
     * >X< (something like multi-pass rendering 
     * could be done by subsequent Nodes)
     */
    RxPacketDispatch(packet, 0, self);

    RWRETURN(TRUE);
}

/**
 * \ingroup rpprtsysd3d7
 * \ref RxNodeDefinitionGetD3D7SubmitParticles returns a pointer to a node 
 * to dispatch particles as quads using D3D7 transform facilities.
 *
 * If the render state cluster is not present (or contains no data) then the
 * node assumes that the packet's render state is the default render state,
 * obtained through RxRenderStateVectorGetDefaultRenderStateVector(). This is
 * used in this case to set render states prior to rasterisation.
 *
 * The node has one output.  The purpose of this is to allow packets to be
 * modified and submitted again later on in the pipeline to perform multipass
 * rendering.
 *
 * \verbatim
   The node has a single output, through which packets passes unchanged.
  
   The input requirements of this node:
  
   RxClObjSpace3DVertices   - required
   RxClMeshState            - required
   RxClRenderState          - optional
  
   The characteristics of this node's first output:
   
   RxClObjSpace3DVertices   - no-change
   RxClMeshState            - no-change
   RxClRenderState          - no-change
   \endverbatim
 *
 * \return pointer to a node to submit triangles to the rasteriser on success,
 * NULL otherwise
 */

RxNodeDefinition   *
RxNodeDefinitionGetD3D7SubmitParticles(void)
{
    static RxClusterRef N1clofinterest[] = { /* */
        {&RxClObjSpace3DVertices, rxCLALLOWABSENT, rxCLRESERVED},
        {&RxClMeshState, rxCLALLOWABSENT, rxCLRESERVED},
        {&RxClRenderState, rxCLALLOWABSENT, rxCLRESERVED}
    };

#define NUMCLUSTERSOFINTEREST \
        ((sizeof(N1clofinterest))/(sizeof(N1clofinterest[0])))

    static RxClusterValidityReq N1inputreqs[NUMCLUSTERSOFINTEREST] = { /* */
        rxCLREQ_REQUIRED,
        rxCLREQ_REQUIRED,
        rxCLREQ_OPTIONAL
    };

    static RxClusterValid N1outcl1[NUMCLUSTERSOFINTEREST] = { /* */
        rxCLVALID_NOCHANGE,
        rxCLVALID_NOCHANGE,
        rxCLVALID_NOCHANGE
    };

    static RwChar       _SubmitOut[] = "SubmitOut";

    static RxOutputSpec N1outputs[] = { /* */
        {_SubmitOut,
         N1outcl1,
         rxCLVALID_NOCHANGE}
    };

#define NUMOUTPUTS \
        ((sizeof(N1outputs))/(sizeof(N1outputs[0])))

    static RwChar       _SubmitParticles_csl[] =
        "D3D7SubmitParticles.csl";

    static RxNodeDefinition nodeD3D7SubmitParticles = { /* */
        _SubmitParticles_csl,
        {SubmitNode, InitParticleSubmitNode, TermParticleSubmitNode,
         NULL, NULL, NULL, NULL},
        {NUMCLUSTERSOFINTEREST, N1clofinterest, N1inputreqs,
         NUMOUTPUTS, N1outputs},
        0, FALSE, 0
    };

    RWAPIFUNCTION(RWSTRING("RxNodeDefinitionGetD3D7SubmitParticles"));

    RWRETURN(&nodeD3D7SubmitParticles);
}
