/*
 * nodeSubmitWith2DCull
 * Culling back-facing triangles based on a cross-product test of 2-D projected edges and then submitting the non-culled triangles to the rasteriser |
 * 
 * Copyright (c) Criterion Software Limited
 */
/****************************************************************************
 *                                                                          *
 * module : nodeSubmitWith2DCull.c                                          *
 *                                                                          *
 * purpose: This node culls back-facing triangles based on a cross-product  *
 *          test of 2D projected edges and then submits the non-culled      *
 *          triangles to the rasteriser                                     *
 *                                                                          *
 ****************************************************************************/

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

/* #include <assert.h> */

#include "rwcore.h"
#include "baworld.h"

#include "p2stdclsw.h"

#include "nodeSubmitWith2DCull.h"

#if (!defined(DOXYGEN))
static const char rcsid[] __RWUNUSED__ = "@@(#)$Id: nodeSubmitWith2DCull.c,v 1.58 2001/07/10 11:45:05 johns Exp $";
#endif /* (!defined(DOXYGEN)) */


/* Also TODO - get this to support submission of indexed/unindexed triangles
   (have one for tris and one for lines unless they can be efficiently combined)
   of any primitive type - so default trilists for atomics/worldsectors */

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

#define MESSAGE(string)                                               \
    RwDebugSendMessage(rwDEBUGMESSAGE, "SubmitWith2DCull.csl", string)


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

   functions

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

/****************************************************************************
 _SubmitWith2DCullNode

 TODO: we should be able to pass in an already-constructed set of indices to
       this node, in a RxVertexIndex cluster - useful for multipass FX where
       indices need only be generated once (and triangles backfaced once).
       We require the indices cluster as CLREQOPTIONAL and test
       (indices->flags & CLFLAGSVALID) to see if it had been previously
       generated. If you want Submit to actually have different requirements
       depending on position in the pipeline (ignore incoming indices
       (CLREQDONTCARE), Vs optionally take pre-generated indices
       (CLREQOPTIONAL)), you need two NodeDefinitions (though they can refer
       to the same code): SubmitFirst &SubmitSubsequent or some such.

 TODO: we should also predicate on a flag as to whether or not we actually
       edit the Triangles cluster - we could just leave it as it is and merely
       choose what indices to submit on the basis of backface culling (if we
       submit the triangles array as our indices (Triangles->stride ==
       3*sizeof(RxVertexIndex)) then we do have to edit it).

 on entry: -
 on exit : -
*/

static              RwBool
_SubmitWith2DCullNode(RxPipelineNodeInstance * self,
                      const RxPipelineNodeParam * __RWUNUSED__ params)
{
    RxPacket            *packet;
    RxCluster           *devVerts, *indices, *meshState, *renderState;
    RxScrSpace2DVertex  *devVert, *dv0, *dv1, *dv2;
    RxMeshStateVector   *meshData;
    RxRenderStateVector *rsvp;
    RwUInt32             i;
    RxVertexIndex       *curIndices;
    RxVertexIndex       *lastIndices;

    RWFUNCTION(RWSTRING("_SubmitWith2DCullNode"));

    RWASSERT(NULL != self);

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

    devVerts    = RxClusterLockRead( packet, 0);
    RWASSERT((devVerts  != NULL) && (devVerts->numUsed > 0));
    indices     = RxClusterLockWrite(packet, 1, self);
    meshState   = RxClusterLockWrite(packet, 2, self);
    RWASSERT( meshState != NULL);
    renderState = RxClusterLockRead( packet, 3);

    meshData = RxClusterGetCursorData(meshState, RxMeshStateVector);
    RWASSERT(NULL != meshData);

    /* TODO: Submit can only handle TriLists as of yet */
    RWASSERT(meshData->PrimType == rwPRIMTYPETRILIST);

    if (meshData->ClipFlagsOr == 0)
    {
        rsvp = (RxRenderStateVector *)NULL;
        if (renderState != NULL)
        {
            rsvp = RxClusterGetCursorData(renderState, RxRenderStateVector);
        }

        /* Uninitialised renderstate -> default
         * TODO: Should this instead let current state persist ala Im3D? */
        if (rsvp == NULL)
        {
            rsvp = &RXPIPELINEGLOBAL(defaultRenderState);
        }

        /* Set the appropriate texture */
        if ((meshData->Flags & rxGEOMETRY_TEXTURED) &&
            (NULL != 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 shading modes */
        RwRenderStateSet(rwRENDERSTATESHADEMODE,
                         (void *)rsvp->ShadeMode);

        /* Set blending modes */
        RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE,
                         (rxRENDERSTATEFLAG_VERTEXALPHAENABLE &
                          rsvp->Flags) ? (void *)TRUE :
                         (void *)FALSE);
        RwRenderStateSet(rwRENDERSTATESRCBLEND,
                         (void *)rsvp->SrcBlend);
        RwRenderStateSet(rwRENDERSTATEDESTBLEND,
                         (void *)rsvp->DestBlend);

        /* Set ZBuffering modes */
        RwRenderStateSet(rwRENDERSTATEZTESTENABLE,
                         (rxRENDERSTATEFLAG_ZTESTENABLE &
                          rsvp->Flags) ? (void *)TRUE :
                         (void *)FALSE);
        RwRenderStateSet(rwRENDERSTATEZWRITEENABLE,
                         (rxRENDERSTATEFLAG_ZWRITEENABLE &
                          rsvp->Flags) ? (void *)TRUE :
                         (void *)FALSE);

        if ((indices != NULL) && (indices->numUsed > 0))
        {
            RWASSERT(indices->stride == sizeof(RxVertexIndex));

            curIndices = RxClusterGetIndexedData(indices, RxVertexIndex, 0);
            lastIndices =
                RxClusterGetIndexedData(indices,
                                        RxVertexIndex,
                                        (meshData->NumElements - 1) * 3);
            devVert = RxClusterGetCursorData(devVerts, RxScrSpace2DVertex);
            RWASSERT(NULL != devVert);

            for (i = 0; i < meshData->NumElements; i++)
            {
                dv0 = &devVert[curIndices[0]];
                dv1 = &devVert[curIndices[1]];
                dv2 = &devVert[curIndices[2]];

                if (((RwIm2DVertexGetScreenX(dv0) -
                      RwIm2DVertexGetScreenX(dv1)) *
                     (RwIm2DVertexGetScreenY(dv2) -
                      RwIm2DVertexGetScreenY(dv1))) <=
                    ((RwIm2DVertexGetScreenY(dv0) -
                      RwIm2DVertexGetScreenY(dv1)) *
                     (RwIm2DVertexGetScreenX(dv2) -
                      RwIm2DVertexGetScreenX(dv1))))
                {
                    /* Overwrite this culled tri with one from the end of the array */
                    curIndices[0] = lastIndices[0];
                    curIndices[1] = lastIndices[1];
                    curIndices[2] = lastIndices[2];

                    lastIndices -= 3;
                    i--;
                    meshData->NumElements--;
                }
                else
                {
                    curIndices += 3;
                }
            }

            /* Account for deleted triangles */
            indices->numUsed = meshData->NumElements * 3;
            RxClusterResetCursor(indices);

            if (meshData->NumElements > 0)
            {

                RwIm2DVertex *vertices;
                
                vertices = (RwIm2DVertex *) 
                    RxClusterGetCursorData(devVerts, void);
                
                /* Metrics updated inside here */
                RwIm2DRenderIndexedPrimitive
                    (rwPRIMTYPETRILIST,
                     vertices,
                     meshData->NumVertices,
                     RxClusterGetCursorData(indices, RxVertexIndex),
                     3*meshData->NumElements);
                /* Output the packet to the first (and only) output of this
                 * Node. (something like multi-pass rendering could be done
                 * by subsequent Nodes) */
                RxPacketDispatch(packet, 0, self);
            }
            else
            {
                /* The second output is (usually) left unconnected, so
                   the now empty packet will die */
                RxPacketDispatch(packet, 1, self);
            }
        }
        else
        {
            RxScrSpace2DVertex *curVerts =
                RxClusterGetIndexedData(devVerts, RxScrSpace2DVertex, 0);
            RxScrSpace2DVertex *lastVerts =
                RxClusterGetIndexedData(devVerts,
                                        RxScrSpace2DVertex,
                                        (meshData->NumElements*3) - 1);

            for (i = 0; i < meshData->NumElements; i++)
            {
                dv0 = curVerts;
                dv1 = curVerts + 1;
                dv2 = curVerts + 2;

                if (((RwIm2DVertexGetScreenX(dv0) -
                      RwIm2DVertexGetScreenX(dv1)) *
                     (RwIm2DVertexGetScreenY(dv2) -
                      RwIm2DVertexGetScreenY(dv1))) <=
                    ((RwIm2DVertexGetScreenY(dv0) -
                      RwIm2DVertexGetScreenY(dv1)) *
                     (RwIm2DVertexGetScreenX(dv2) -
                      RwIm2DVertexGetScreenX(dv1))))
                {
                    /* Overwrite this culled tri with one from the end of the array */
                    *dv2 = *lastVerts;
                    lastVerts--;
                    *dv1 = *lastVerts;
                    lastVerts--;
                    *dv0 = *lastVerts;
                    lastVerts--;

                    i--;
                    meshData->NumElements--;
                }
                else
                {
                    curVerts += 3;
                }
            }

            /* Account for deleted triangles */
            devVerts->numUsed = meshData->NumElements * 3;
            RxClusterResetCursor(devVerts);

            if (meshData->NumElements > 0)
            {

                RwIm2DVertex *vertices;
                
                vertices = (RwIm2DVertex *) 
                    RxClusterGetCursorData(devVerts, void);

                /* Metrics updated inside here */
                RwIm2DRenderPrimitive(rwPRIMTYPETRILIST,
                                      vertices,
                                      meshData->NumVertices);
                /* Output the packet to the first (and only) output of this
                 * Node. (something like multi-pass rendering could be done
                 * by subsequent Nodes) */
                RxPacketDispatch(packet, 0, self);
            }
            else
            {
                /* The second output is (usually) left unconnected, so
                   the now empty packet will die */
                RxPacketDispatch(packet, 1, self);
            }
        }
    }

    RWRETURN(TRUE);
}

/**
 * \ingroup rpworldp2generic
 * \ref RxNodeDefinitionGetSubmitWith2DCull returns a pointer to a node to
 * cull back-facing triangles based on a cross-product test of 2D projected
 * edges and then submit the non-culled triangles to the rasteriser
 *
 * This node is usually used when ClipFlagsOr in a packet's mesh state is
 * known to be zero and hence all triangles in the packet are fully onscreen.
 * This means that we can perform 2D back-face culling and do not need to
 * access camera vertices at all when doing this. Back-face culled triangles
 * are deleted from the triangles cluster (this disrupts the order of the
 * triangles, which is assumed to be unimportant).
 *
 * The node as it is currently only supports submission of triangle lists
 * (indexed or unindexed). Currently, triangle strips and fans are assumed
 * to be expanded into triangle lists earlier in the pipeline (this occurs
 * in the standard atomic and world sector instancing nodes and also in the
 * Im3D node ImmMangleTriangleIndices.csl). Direct support for triangle strips
 * (and possibly fans) will be added later.
 *
 * 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 two outputs and normally packets pass back-face culled through
 * the first output. If all the triangles in a packet are culled then the
 * packet is sent through the node's second output (which is usually left
 * disconnected, resulting in the packet's destruction). The purpose of having
 * outputs from this node is to allow packets to be modified and submitted
 * again later on in the pipeline, to perform multipass rendering.
 *
 * The node has two outputs.
 * The input requirements of this node:
 *      \li RxClScrSpace2DVertices - required
 *      \li RxClIndices            - required
 *      \li RxClMeshState          - required
 *      \li RxClRenderState        - optional
 *
 * The characteristics of this node's first output:
 *      \li RxClScrSpace2DVertices - no change
 *      \li RxClIndices            - valid
 *      \li RxClMeshState          - valid
 *      \li RxClRenderState        - no change
 *
 * The characteristics of this node's second output:
 *      \li RxClScrSpace2DVertices - no change
 *      \li RxClIndices            - invalid
 *      \li RxClMeshState          - valid
 *      \li RxClRenderState        - no change
 *
 * \return pointer to a node to cull back-facing triangles based on a
 * cross-product test of 2D projected edges and then submit the non-culled
 * triangles to the rasteriser
 *
 * \see RxNodeDefinitionGetAtomicEnumerateLights
 * \see RxNodeDefinitionGetAtomicInstance
 * \see RxNodeDefinitionGetFastPathSplitter
 * \see RxNodeDefinitionGetLight
 * \see RxNodeDefinitionGetMaterialScatter
 * \see RxNodeDefinitionGetPostLight
 * \see RxNodeDefinitionGetPreLight
 * \see RxNodeDefinitionGetWorldSectorEnumerateLights
 * \see RxNodeDefinitionGetWorldSectorInstance
 */
RxNodeDefinition *
RxNodeDefinitionGetSubmitWith2DCull(void)
{
    static RxClusterRef N1clofinterest[] = { /* */
     {&RxClScrSpace2DVertices, rxCLALLOWABSENT, rxCLRESERVED},
     {&RxClIndices, rxCLALLOWABSENT, rxCLRESERVED},
     {&RxClMeshState, rxCLALLOWABSENT, rxCLRESERVED},
     {&RxClRenderState, rxCLALLOWABSENT, rxCLRESERVED} };

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

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

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

    static RxClusterValid N1outcl2[NUMCLUSTERSOFINTEREST] = { /* */
        rxCLVALID_NOCHANGE,
        rxCLVALID_INVALID,
        rxCLVALID_VALID,
        rxCLVALID_NOCHANGE };

    static RwChar _SubmitOut[]       = RWSTRING("SubmitOut");
    static RwChar _SubmitAllCulled[] = RWSTRING("SubmitAllCulled");

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

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

    static RwChar _SubmitWith2DCull_csl[] = RWSTRING("SubmitWith2DCull.csl");

    static RxNodeDefinition nodeSubmitWith2DCullCSL = { /* */
        _SubmitWith2DCull_csl,
        {
            _SubmitWith2DCullNode,
            (RxNodeInitFn)NULL,
            (RxNodeTermFn)NULL,
            (RxPipelineNodeInitFn)NULL,
            (RxPipelineNodeTermFn)NULL,
            (RxPipelineNodeConfigFn)NULL,
            (RxConfigMsgHandlerFn)NULL
        },
        {
            NUMCLUSTERSOFINTEREST,
            N1clofinterest,
            N1inputreqs,
            NUMOUTPUTS,
            N1outputs
        },
        0,
        (RxNodeDefEditable)FALSE,
        0
    };

    RWAPIFUNCTION(RWSTRING("RxNodeDefinitionGetSubmitWith2DCull"));

    /*RWMESSAGE((RWSTRING("Pipeline II node")));*/

    RWRETURN(&nodeSubmitWith2DCullCSL);
}


