/****************************************************************************
 *                                                                          *
 * module : nodeOpenGLSubmitParticles.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 "nodeOpenGLSubmitParticles.h"

#include "prvprtcl.h"

#ifdef WIN32
#include "windows.h"
#endif
#include "GL/gl.h"

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

static const char   rcsid[] __RWUNUSED__ =
    "@@(#)$Id: nodeOpenGLSubmitParticles.c,v 1.12 2001/08/30 09:11:12 johns Exp $";

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

/****************************************************************************
 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

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

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

    /* Apply the correct transformation here */
    if (!rwMatrixTestFlags
        (&meshData->Obj2World, rwMATRIXINTERNALIDENTITY))
    {
        glPushMatrix();
        _rwOpenGLApplyRwMatrix(&meshData->Obj2World);
    }

    {
        RwIm3DVertex       *verts =
            RxClusterGetCursorData(objVerts, RwIm3DVertex);

        /* 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();
        RWASSERT(particleMatrix);

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

        glBegin(GL_QUADS);
        if ( /*(meshData->Flags & rxGEOMETRY_TEXTURED) && */ rsvp->
            TextureRaster)
        {
            while (numVertices--)
            {
                RwV3dScale(&scaledUp, &up, particleInfo->size.x);
                RwV3dScale(&scaledRight, &right, particleInfo->size.y);

                glColor4ubv((GLubyte *) & verts->color);
                glTexCoord2f(0, 0);
                glVertex3f((GLfloat) verts->objVertex.x +
                           scaledRight.x + scaledUp.x,
                           (GLfloat) verts->objVertex.y +
                           scaledRight.y + scaledUp.y,
                           (GLfloat) verts->objVertex.z +
                           scaledRight.z + scaledUp.z);

                glTexCoord2f(0, 1);
                glVertex3f((GLfloat) verts->objVertex.x +
                           scaledRight.x - scaledUp.x,
                           (GLfloat) verts->objVertex.y +
                           scaledRight.y - scaledUp.y,
                           (GLfloat) verts->objVertex.z +
                           scaledRight.z - scaledUp.z);

                glTexCoord2f(1, 1);
                glVertex3f((GLfloat) verts->objVertex.x -
                           scaledRight.x - scaledUp.x,
                           (GLfloat) verts->objVertex.y -
                           scaledRight.y - scaledUp.y,
                           (GLfloat) verts->objVertex.z -
                           scaledRight.z - scaledUp.z);

                glTexCoord2f(1, 0);
                glVertex3f((GLfloat) verts->objVertex.x -
                           scaledRight.x + scaledUp.x,
                           (GLfloat) verts->objVertex.y -
                           scaledRight.y + scaledUp.y,
                           (GLfloat) verts->objVertex.z -
                           scaledRight.z + scaledUp.z);
                verts++;
                particleInfo++;
            }
        }
        else
        {
            while (numVertices--)
            {
                RwV3dScale(&scaledUp, &up, particleInfo->size.x);
                RwV3dScale(&scaledRight, &right, particleInfo->size.y);

                glColor4ubv((GLubyte *) & verts->color);
                glVertex3f((GLfloat) verts->objVertex.x +
                           scaledRight.x + scaledUp.x,
                           (GLfloat) verts->objVertex.y +
                           scaledRight.y + scaledUp.y,
                           (GLfloat) verts->objVertex.z +
                           scaledRight.z + scaledUp.z);
                glVertex3f((GLfloat) verts->objVertex.x +
                           scaledRight.x - scaledUp.x,
                           (GLfloat) verts->objVertex.y +
                           scaledRight.y - scaledUp.y,
                           (GLfloat) verts->objVertex.z +
                           scaledRight.z - scaledUp.z);
                glVertex3f((GLfloat) verts->objVertex.x -
                           scaledRight.x - scaledUp.x,
                           (GLfloat) verts->objVertex.y -
                           scaledRight.y - scaledUp.y,
                           (GLfloat) verts->objVertex.z -
                           scaledRight.z - scaledUp.z);
                glVertex3f((GLfloat) verts->objVertex.x -
                           scaledRight.x + scaledUp.x,
                           (GLfloat) verts->objVertex.y -
                           scaledRight.y + scaledUp.y,
                           (GLfloat) verts->objVertex.z -
                           scaledRight.z + scaledUp.z);
                verts++;
                particleInfo++;
            }
        }
        glEnd();
    }

#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))
    {
        glPopMatrix();
    }

    /* 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 rpprtsysopenglp2
 * \ref RxNodeDefinitionGetOpenGLSubmitParticles returns a pointer to a node 
 * to dispatch particles as quads using OpenGL 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   *
RxNodeDefinitionGetOpenGLSubmitParticles(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[] = "SubmitParticles.csl";

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

    RWAPIFUNCTION(RWSTRING("RxNodeDefinitionGetOpenGLSubmitParticles"));

    RWRETURN(&nodeOpenGLSubmitParticles);
}
