/*
 * Particle System plug-in
 */

/****************************************************************************
 *                                                                          *
 *  Module  :   _ppprtclswin.c                                              *
 *                                                                          *
 *  Purpose :                                                               *
 *                                                                          *
 ****************************************************************************/

/****************************************************************************
 Includes
 */

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

/* RW includes */
#include "rpplugin.h"
#include <rpdbgerr.h>
#include <rwcore.h>
#include <rpworld.h>
#include <rprandom.h>

#include "prvprtcl.h"
#include <rpprtsys.h>
#include "ppprtclswin.h"

#if (defined(OPENGL_DRVMODEL_H))
#include "nodeOpenGLSubmitParticles.h"
#elif (defined(D3D7_DRVMODEL_H))
#include "nodeD3D7SubmitParticles.h"
#elif (defined(D3D8_DRVMODEL_H))
#include "nodeD3D8SubmitParticles.h"
#endif

#if (!defined(DOXYGEN))
static const char rcsid[] __RWUNUSED__ = 
    "@@@@(#)$Id: ppprtclswin.c,v 1.49 2001/10/03 09:24:18 markf Exp $";
#endif /* (!defined(DOXYGEN)) */

/****************************************************************************
 Local Types
 */

typedef struct InstancedParticleSystem InstancedParticleSystem;
struct InstancedParticleSystem
{
    RwUInt32            serialNum; /* Serial # - combination of
                                    * elements contributing to
                                    * instanced data.  Enables us to
                                    * detect when a re-instance is
                                    * necessary.
                                    */
    RwUInt32            numParticles;
    RwUInt32            vertSize;
    RxObjSpace3DVertex *vertices;
    RpMaterial         *material;
};

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

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

/****************************************************************************
 Local (Static) Prototypes
 */

/****************************************************************************
 Local Defines
 */

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

#define PARTICLESGEOMGETCONSTDATA(geometry)                             \
    ((const RpParticlesGeomData **)(((const RwUInt8 *)geometry) +       \
    GParticlesGeomDataOffset))

/****************************************************************************
 Local (static) Globals
 */

RwInt32             GParticlesGeomDataOffset;

/****************************************************************************
 Functions
 */

static              RwBool
ParticlesGeomDataDestroy(RpParticlesGeomData * particlesGeomData)
{
    RWFUNCTION(RWSTRING("ParticlesGeomDataDestroy"));

    if (particlesGeomData)
    {
        RpParticleInfo     *particles;

        particles = particlesGeomData->particles;

        if (particles)
        {
            RwFree(particles);
        }

        RwFree(particlesGeomData);

        RWRETURN(TRUE);
    }

    RWRETURN(FALSE);
}

static RpParticlesGeomData *
ParticlesGeomDataCreate(RwInt32 numParticles)
{
    void               *ptr;
    RwUInt32            bytes;
    RpParticlesGeomData *particlesGeomData;

    RWFUNCTION(RWSTRING("ParticlesGeomDataCreate"));

    bytes = sizeof(RpParticlesGeomData);
    ptr = (void *) RwMalloc(bytes);

    if (!ptr)
    {
        RWRETURN(NULL);
    }

    /* memset(ptr, 0, bytes); */
    particlesGeomData = (RpParticlesGeomData *) ptr;

    bytes = sizeof(RpParticleInfo) * numParticles;
    ptr = RwMalloc(bytes);

    if (!ptr)
    {
        ParticlesGeomDataDestroy(particlesGeomData);

        RWRETURN(NULL);
    }

    /* memset(ptr, 0, bytes); */
    particlesGeomData->particles = (RpParticleInfo *) ptr;

    RWRETURN(particlesGeomData);
}

static void        *
ParticleGeomConstructor(void *object,
                        RwInt32 __RWUNUSED__ offset,
                        RwInt32 __RWUNUSED__ size)
{
    RWFUNCTION(RWSTRING("ParticleGeomConstructor"));

    RWASSERT(object);

    *PARTICLESGEOMGETDATA(object) = NULL;

    RWRETURN(object);
}

static void        *
ParticleGeomDestructor(void *object,
                       RwInt32 __RWUNUSED__ offset,
                       RwInt32 __RWUNUSED__ size)
{

    RpParticlesGeomData *pData;

    RWFUNCTION(RWSTRING("ParticleGeomDestructor"));

    RWASSERT(object);

    pData = *PARTICLESGEOMGETDATA(object);

    if (pData)
    {
        *PARTICLESGEOMGETDATA(object) = NULL;

        ParticlesGeomDataDestroy(pData);
    }

    RWRETURN(object);
}

static void        *
ParticleGeomCopy(void *dstObject,
                 const void *srcObject,
                 RwInt32 __RWUNUSED__ offset, RwInt32 __RWUNUSED__ size)
{
    RpGeometry         *dstGeometry;
    RpParticlesGeomData *dstParticlesGeomData;
    RwInt32             numParticles;
    const RpGeometry   *srcGeometry;
    const RpParticlesGeomData *srcParticlesGeomData;

    RWFUNCTION(RWSTRING("ParticleGeomCopy"));

    srcGeometry = (const RpGeometry *) srcObject;
    srcParticlesGeomData = *PARTICLESGEOMGETCONSTDATA(srcGeometry);
    if (!srcParticlesGeomData)
    {
        RWRETURN(NULL);
    }

    numParticles = RpGeometryGetNumVertices(srcGeometry);

    dstGeometry = (RpGeometry *) dstObject;
    dstParticlesGeomData = *PARTICLESGEOMGETDATA(dstGeometry);
    if (!dstParticlesGeomData)
    {
        dstParticlesGeomData = ParticlesGeomDataCreate(numParticles);
        if (!dstParticlesGeomData)
        {
            RWRETURN(NULL);
        }

        *PARTICLESGEOMGETDATA(dstGeometry) = dstParticlesGeomData;
    }

    /* TODO: properly */
    *dstParticlesGeomData = *srcParticlesGeomData;

    RWRETURN(dstObject);
}

RpAtomic           *
_rpParticleAddGeomData(RpAtomic * atomic, RwInt32 numParticles)
{

    RpParticlesGeomData *particlesGeomData;
    RpGeometry         *geometry;

    RWFUNCTION(RWSTRING("_rpParticleAddGeomData"));

    geometry = RpAtomicGetGeometry(atomic);
    if (!geometry)
    {
        RWRETURN(NULL);
    }

    particlesGeomData = ParticlesGeomDataCreate(numParticles);
    if (!particlesGeomData)
    {
        RWRETURN(NULL);
    }
    *PARTICLESGEOMGETDATA(geometry) = particlesGeomData;

    RWRETURN(atomic);
}

RwBool
_rpParticleExtendGeom(void)
{
    RWFUNCTION(RWSTRING("_rpParticleExtendGeom"));

    /* Extend geometry to hold userdata */
    GParticlesGeomDataOffset =
        RpGeometryRegisterPlugin(sizeof(RpParticlesGeomData *),
                                 rwID_PARTICLESPLUGIN,
                                 ParticleGeomConstructor,
                                 ParticleGeomDestructor,
                                 ParticleGeomCopy);

    RWRETURN(TRUE);
}

/*************************************************************************
 * Instance functions
 */

static void
instanceVerts(RxObjSpace3DVertex * instVerts,
              RpAtomic * atomic __RWUNUSED__, RpGeometry * geom,
              RwUInt32 vertSize)
{

    const RpMorphTarget *morphTarget = &geom->morphTarget[0];
    const RwV3d        *pos = morphTarget->verts;
    const RwRGBA       *preLitLum = geom->preLitLum;
    RwInt32             numVerts;

    RWFUNCTION(RWSTRING("instanceVerts"));

    numVerts = geom->numVertices;

    while (numVerts--)
    {
        RxObjSpace3DVertexSetPos(instVerts, pos);
        pos++;
        preLitLum++;
        instVerts = ((RxObjSpace3DVertex *)
                     ((RwUInt8 *) instVerts + vertSize));
    }

    RWRETURNVOID();
}

static RpAtomic    *
ParticlesSetState(RpAtomic * atomic)
{
    RpGeometry         *geometry;
    RpParticlesGeomData *geomData;
    RpParticlesData    *particlesData;
    RpParticleInfo     *particleInfo;
    RwInt32             numParticles, particleNum;
    RwReal              width, length;
    RwReal              speedInit, speedVar;
    RwReal              angle;
    RwReal              duration;
    RwReal              dampening;

    RWFUNCTION(RWSTRING("ParticlesSetState"));

    particlesData = *PARTICLESATOMICGETDATA(atomic);
    if (!particlesData)
    {
        RWRETURN(NULL);
    }

    geometry = RpAtomicGetGeometry(atomic);
    if (!geometry)
    {
        RWRETURN(NULL);
    }

    geomData = *PARTICLESGEOMGETDATA(geometry);
    if (!geomData)
    {
        RWRETURN(NULL);
    }

    particleInfo = geomData->particles;
    if (!particleInfo)
    {
        RWRETURN(NULL);
    }

    numParticles = particlesData->numParticles;

    /* set the initial positions in YX plane */
    width = particlesData->emitterSize.x;
    length = particlesData->emitterSize.y;
    for (particleNum = 0; particleNum < numParticles; particleNum++)
    {
        RwV3d              *pos;
        RwInt32             dice;

        pos = &particleInfo[particleNum].position;

        dice = RpRandom();
        pos->x = ((((RwReal) (dice) * width) / (RwReal) (RPRANDMAX)) -
                  (width * ((RwReal) 0.5)));

        dice = RpRandom();
        pos->y = ((((RwReal) (dice) * length) / (RwReal) (RPRANDMAX)) -
                  (length * ((RwReal) 0.5)));

        pos->z = ((RwReal) 0);
    }

    /* set the initial velocities */
    angle = particlesData->angle;
    speedInit = particlesData->speed;
    speedVar = particlesData->speedVariation;
    dampening = particlesData->dampening;

    for (particleNum = 0; particleNum < numParticles; particleNum++)
    {
        RwV3d              *vel;
        RwReal              randX, randY, randZ;
        RwReal              angleRangeX, angleRangeY;
        RwReal              speed;
        RwInt32             dice;

        vel = &particleInfo[particleNum].velocity;

        dice = RpRandom();
        randX = (((RwReal) (dice)) / ((RwReal) (RPRANDMAX)));
        dice = RpRandom();
        randY = (((RwReal) (dice)) / ((RwReal) (RPRANDMAX)));
        dice = RpRandom();
        randZ = (((RwReal) (dice)) / ((RwReal) (RPRANDMAX)));

        angleRangeX = ((randX * ((RwReal) 2)) - ((RwReal) 1));
        angleRangeY = ((randY * ((RwReal) 2)) - ((RwReal) 1));

        vel->x = angle * angleRangeX;
        vel->y = angle * angleRangeY;
        vel->z = ((RwReal) 1) - ((vel->x * vel->x) + (vel->y * vel->y));

        speed = ((speedInit - (speedVar / ((RwReal) 2))) +
                 (speedVar * randZ));
        speed *= (((RwReal) 1) -
                  (RwRealMin2((angleRangeX * angleRangeX) +
                              (angleRangeY * angleRangeY),
                              ((RwReal) 1)) * dampening * ((RwReal) -
                                                           1)));

        RwV3dScale(vel, vel, speed);
    }

    /* set the initial start times */
    duration = particlesData->duration;
    for (particleNum = 0; particleNum < numParticles; particleNum++)
    {
        RwReal             *startTime;
        RwInt32             dice;

        startTime = &particleInfo[particleNum].startTime;
        dice = RpRandom();
        *startTime = (((RwReal) dice * duration) / (RwReal) RPRANDMAX);
    }

    RWRETURN(atomic);
}

/*****************************************************************************
 ParticleSystemInstanceNode

 Creates a ResEntry for a ParticleSystem. For parametrically
 animated particles, this acts just like an atomic's ResEntry,
 but for incrementally animated particles, this ResEntry can be
 edited during the pipeline to update it from frame to frame.
 To facilitate this, the clusters referencing the data are
 flagged rxCLFLAGS_EXTERNALMODIFIABLE which means the array's
 contents may be changed but it cannot be freed or resized

*/

static              RwBool
ParticleSystemInstanceNode(RxPipelineNodeInstance * self,
                           const RxPipelineNodeParam * params)
{
    InstancedParticleSystem *instancedParticleSystem;
    RpAtomic           *atomic;
    RpGeometry         *geom;
    RpMaterial         *mat;
    RpParticlesData    *particlesData;
    RpParticlesGeomData *particlesGeomData;
    RwMatrix           *Obj2Cam;
    RwResEntry         *repEntry;
    RwUInt32            geomFlags;
    RwUInt32            numParticles;
    RwUInt32            output;
    RwUInt32            particleSystemDataSize;
    RwUInt32            vertSize;
    RxCluster          *meshState;
    RxCluster          *objVerts;
    RxCluster          *renderState;
    RxMeshStateVector  *meshData;
    RxPacket           *packet;
    RxRenderStateVector *defaultRsvp;
    RxRenderStateVector *rsvp;
    const RwRGBA       *MatCol;
    const RwSurfaceProperties *SurfaceProperties;
    RwBool              zWriteEnable = TRUE;

    RWFUNCTION(RWSTRING("ParticleSystemInstanceNode"));

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

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

    particlesData = *PARTICLESATOMICGETDATA(atomic);
    RWASSERT(NULL != particlesData);

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

    repEntry = atomic->repEntry;
    instancedParticleSystem =
        (InstancedParticleSystem *) (repEntry + 1);

    geomFlags = RpGeometryGetFlags(geom);

    if (NULL != repEntry)
    {
        /* If the mesh has been rebuilt, we should re-instance
         * [NOTE: particle systems objects should only have one mesh
         * and should have no triangles] */
        if (instancedParticleSystem->serialNum != geom->mesh->serialNum)
        {
            /* Things have changed, destroy resources to force reinstance */
            RwResourcesFreeResEntry(repEntry);
            repEntry = NULL;
        }
    }

    if (NULL == repEntry)
    {
        RxObjSpace3DVertex *dstVerts;
        RwUInt32            numParticles, vertSize, size;

        numParticles = particlesData->numParticles;

        /* Particles need UVs so... */
        vertSize = RxObjSpace3DVertexFullSize;

        size = (sizeof(InstancedParticleSystem) +
                (numParticles * vertSize));

        repEntry = RwResourcesAllocateResEntry(atomic,
                                               &atomic->repEntry,
                                               size, NULL);
        RWASSERT(NULL != repEntry);

        /* Extra header info */
        instancedParticleSystem =
            (InstancedParticleSystem *) (repEntry + 1);
        instancedParticleSystem->serialNum = geom->mesh->serialNum;

        dstVerts = (RxObjSpace3DVertex *) (instancedParticleSystem + 1);

        /* Instance the vertices */
        instanceVerts(dstVerts, atomic, geom, vertSize);

        instancedParticleSystem->numParticles = numParticles;
        instancedParticleSystem->vertices = dstVerts;
        instancedParticleSystem->vertSize = vertSize;
        /* Jesus how messy is this.... is it worth it?!?!? */
        instancedParticleSystem->material =
            ((RpMesh *) (((RwUInt8 *) (geom->mesh + 1)) +
                         geom->mesh->firstMeshOffset))->material;

        /* All clean now */
        geom->lockedSinceLastInst = 0;
    }
    else
    {
        RwUInt32            dirtyFlags = geom->lockedSinceLastInst;
        RxObjSpace3DVertex *dstVerts;
        RwUInt32            vertSize;

        RwResourcesUseResEntry(repEntry);
        instancedParticleSystem =
            (InstancedParticleSystem *) (repEntry + 1);
        dstVerts = instancedParticleSystem->vertices;
        vertSize = instancedParticleSystem->vertSize;

        /* If verts (positions) are only thing to change, 
         * reinstance just those 
         * [modified normals/UVs don't matter for particles, 
         * but prelighting does] */
        switch (dirtyFlags)
        {
            case (rpGEOMETRYLOCKPRELIGHT):
            case (rpGEOMETRYLOCKVERTICES):
                {
                    instanceVerts(dstVerts, atomic, geom, vertSize);
                    break;
                }
            default:
                break;
        }

        geom->lockedSinceLastInst = 0;
    }

    if (particlesData->flags & rpPARTICLESDIRTY)
    {
        particlesData->flags = (RpParticlesFlag)
            (~rpPARTICLESDIRTY & particlesData->flags);
        ParticlesSetState(atomic);
    }

    /* Now build reference clusters into the RepEntry */
    packet = RxPacketCreate(self);
    RWASSERT(NULL != packet);

    numParticles = instancedParticleSystem->numParticles;
    vertSize = instancedParticleSystem->vertSize;
    particleSystemDataSize = sizeof(RpParticlesData);

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

    meshState =
        RxClusterInitializeData(meshState, 1,
                                sizeof(RxMeshStateVector));
    RWASSERT(NULL != meshState);

    renderState =
        RxClusterInitializeData(renderState, 1,
                                sizeof(RxRenderStateVector));
    RWASSERT(NULL != renderState);

    objVerts =
        RxClusterSetExternalData(objVerts,
                                 instancedParticleSystem->vertices,
                                 vertSize, numParticles);
    RWASSERT(NULL != objVerts);

    mat = instancedParticleSystem->material;

    /* Set up MeshState data for this packet */
    meshData = RxClusterGetCursorData(meshState, RxMeshStateVector);
    meshData->SourceObject = (void *) mat;
    /* The only thing that makes sense for particles */
    meshData->PrimType = rwPRIMTYPETRILIST;
    meshData->NumVertices = numParticles;
    meshData->NumElements = 0; /* No triangles for particules yet */
    /* Set up the Local to Camera matrix for this Atomic */
    Obj2Cam = RwFrameGetLTM(RpAtomicGetFrame(atomic));
    RwMatrixCopy(&meshData->Obj2Cam, Obj2Cam);
    RwMatrixTransform(&meshData->Obj2Cam,
                      &(((RwCamera *)
                         RWSRCGLOBAL(curCamera))->viewMatrix),
                      rwCOMBINEPOSTCONCAT);
    meshData->Obj2World = *RwFrameGetLTM(RpAtomicGetFrame(atomic));

    SurfaceProperties = RpMaterialGetSurfaceProperties(mat);

    RwSurfacePropertiesAssign(&meshData->SurfaceProperties,
                              SurfaceProperties);

    meshData->Flags = geomFlags;
    meshData->Texture = RpMaterialGetTexture(mat);
    MatCol = RpMaterialGetColor(mat);
    RwRGBAAssign(&meshData->MatCol, MatCol);
    meshData->Pipeline = mat->pipeline;
    meshData->ClipFlagsAnd = 0;
    meshData->ClipFlagsOr = 0;

    meshState->numUsed++;

    /* Set up RenderState data for this packet */
    rsvp = RxClusterGetCursorData(renderState, RxRenderStateVector);
    RWASSERT(NULL != rsvp);

    defaultRsvp = &RXPIPELINEGLOBAL(defaultRenderState);
    RxRenderStateVectorAssign(rsvp, defaultRsvp);

    /*if (geomFlags & rpGEOMETRYTEXTURED) */
    {
        RwTexture          *texture = meshData->Texture;

        if (texture)
        {
            rsvp->TextureRaster = RwTextureGetRaster(texture);
            rsvp->AddressModeU = RwTextureGetAddressingU(texture);
            rsvp->AddressModeV = RwTextureGetAddressingV(texture);
            rsvp->FilterMode = RwTextureGetFilterMode(texture);
        }
    }


    renderState->numUsed++;

    /* Particle systems consist of one mesh only, so we're done! */
    output = 1;

    RwRenderStateGet(rwRENDERSTATEZWRITEENABLE, (void *)&zWriteEnable);
    RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, (void *)FALSE);

    RxPacketDispatch(packet, output, self);

    RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, (void *)zWriteEnable);

    RWRETURN(TRUE);
}

/**
 * \ingroup rpprtsys
 * \ref RxNodeDefinitionGetPrtclSysInstanceCSL returns a
 * pointer to a node to instance an \ref RpParticleSystem.
 *
 * This node creates a \ref RwResEntry per \ref RpParticleSystem.
 * For parametrically animated particle systems, this acts just
 * like an atomic's \ref RwResEntry, but for incrementally animated
 * particles, this \ref RwResEntry can be edited during the pipeline
 * to update it from frame to frame. To facilitate this, the clusters
 * referencing the data are flagged rxCLFLAGS_EXTERNALMODIFIABLE,
 * which means that their data array's contents may be modified but
 * the array cannot be freed or resized.
 *
 * If a \ref RpParticleSystem's \ref RpParticleSystemType includes any
 * private data then this is put into a cluster which is also set to
 * rxCLFLAGS_EXTERNALMODIFIABLE so that particle-processing nodes
 * can feed back information to the \ref RpParticleSystem. If this
 * cluster is created, then packets will be sent to the node's
 * first output otherwise they will go to its second.
 *
 * \verbatim
   The node has two outputs
   The input requirements of this node:
  
   RxClObjSpace3DVertices      - don't want
   RxClMeshState               - don't want
   RxClRenderState             - don't want
   clusterRpParticles          - don't want
   clusterRpParticleSystemData - don't want
  
   The characteristics of the first of this node's outputs:
  
   RxClObjSpace3DVertices      - valid
   RxClMeshState               - valid
   RxClRenderState             - valid
   clusterRpParticles          - valid
   clusterRpParticleSystemData - valid
  
   The characteristics of the second of this node's outputs:
  
   RxClObjSpace3DVertices      - valid
   RxClMeshState               - valid
   RxClRenderState             - valid
   clusterRpParticles          - valid
   clusterRpParticleSystemData - invalid
   \endverbatim
 * 
 * \return pointer to a node to instance an \ref RpParticleSystem.
 *
 * \see RxNodeDefinitionGetPrtclExpandCSL
 */
RxNodeDefinition   *
RxNodeDefinitionGetPrtclSysInstanceCSL(void)
{
    static RxClusterRef N1clofinterest[] = /* */
    {
        {&RxClObjSpace3DVertices, rxCLALLOWABSENT, 0},
        {&RxClMeshState, rxCLALLOWABSENT, 0},
        {&RxClRenderState, rxCLALLOWABSENT, 0}
    };

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

    static RxClusterValidityReq N1inputreqs[NUMCLUSTERSOFINTERESTPRTCLSYSINSTANCE] = { /* */
        rxCLREQ_DONTWANT,
        rxCLREQ_DONTWANT,
        rxCLREQ_DONTWANT
    };

    static RxClusterValid N1outclWithPSData[NUMCLUSTERSOFINTERESTPRTCLSYSINSTANCE] = { /* */
        rxCLVALID_VALID,
        rxCLVALID_VALID,
        rxCLVALID_VALID
    };

    static RxClusterValid N1outclWithoutPSData[NUMCLUSTERSOFINTERESTPRTCLSYSINSTANCE] = { /* */
        rxCLVALID_VALID,
        rxCLVALID_VALID,
        rxCLVALID_VALID
    };

    static RwChar       _InstanceOutWithPSData[] =
        RWSTRING("ParticleSystemInstanceOutWithPSData");
    static RwChar       _InstanceOutWithoutPSData[] =
        RWSTRING("ParticleSystemInstanceOutWithoutPSData");

    static RxOutputSpec N1outputs[] = /* */
    {
        {_InstanceOutWithPSData, N1outclWithPSData, rxCLVALID_NOCHANGE},

        {_InstanceOutWithoutPSData, N1outclWithoutPSData,
         rxCLVALID_NOCHANGE}
    };

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

    static RwChar       _ParticleSystemInstance_csl[] =
        RWSTRING("ParticleSystemInstance.csl");

    static RxNodeDefinition nodeParticleSystemInstanceCSL = /* */
    {
        _ParticleSystemInstance_csl,
        {
         ParticleSystemInstanceNode,
         NULL,
         NULL,
         NULL,
         NULL,
         NULL,
         NULL},
        {
         NUMCLUSTERSOFINTERESTPRTCLSYSINSTANCE,
         N1clofinterest,
         N1inputreqs,
         NUMOUTPUTS,
         N1outputs},
        0,
        (RxNodeDefEditable) FALSE,
        0
    };

    RWAPIFUNCTION(RWSTRING("RxNodeDefinitionGetPrtclSysInstanceCSL"));

    RWRETURN(&nodeParticleSystemInstanceCSL);
}

/***************************************************************************
 * Particle expand stuff
 */

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

   Functions

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

 /*****************************************************************************
 _ParticleExpandNode

 Takes projected vertices and expands them into screenspace quads, so that
 particles are really cheap.
*/

#define RWSTRIDEDPTRINC(_ptr, _stride) \
  (  (((RwUInt8 *)_ptr) + _stride) )

static              RwBool
_ParticleExpandNode(RxPipelineNodeInstance * self,
                    const RxPipelineNodeParam * params)
{

    RxPacket           *packet;
    RwCamera           *camera;
    RwInt32             width, height;
    RwReal              xRes, yRes, yResScaled, xResScaled;
    RwReal              minScreenX, minScreenY;
    RwReal              maxScreenX, maxScreenY;
    RxCluster          *DevVerts;
    RxCluster          *CamVerts;
    RxCluster          *MeshState;
    RxCluster          *RenderState;
    RxCluster          *Indices;
    RxMeshStateVector  *meshData;
    RwInt32             numVerts, vertNum, numDrawnParticles;
    RxScrSpace2DVertex *srcVert, *dstVert, *devVert;
    RwV2d               vertCoords[4] =
        { {-1, -1}, {-1, 1}, {1, 1}, {1, -1} };
    RwV2d               texCoords[4] =
        { {((RwReal) 0), ((RwReal) 0)}, {((RwReal) 0), ((RwReal) 1)},
    {((RwReal) 1), ((RwReal) 1)}, {((RwReal) 1), ((RwReal) 0)}
    };
    RwInt32             devVertStride;
    RpAtomic           *atomic;
    RpGeometry         *geom;
    RpParticlesData    *particlesData;
    RpParticlesGeomData *particlesGeomData;
    RpParticleInfo     *particleInfo;

    RWFUNCTION(RWSTRING("_ParticleExpandNode"));

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

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

    particlesData = *PARTICLESATOMICGETDATA(atomic);
    RWASSERT(NULL != particlesData);

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

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

    packet = RxPacketFetch(self);
    if (!packet)
    {
        RWRETURN(FALSE);
    }

    DevVerts = RxClusterLockWrite(packet, 0, self);
    CamVerts = RxClusterLockWrite(packet, 1, self);
    MeshState = RxClusterLockWrite(packet, 2, self);
    RenderState = RxClusterLockWrite(packet, 3, self);
    Indices = RxClusterLockWrite(packet, 4, self);

    if ((!DevVerts) || (!CamVerts) || (!MeshState) ||
        (!RenderState) || (!Indices))
    {
        RWRETURN(FALSE);
    }

    if ((DevVerts->numUsed <= 0) ||
        (CamVerts->numUsed <= 0) || (MeshState->numUsed <= 0))
    {
        RWRETURN(FALSE);
    }

    devVertStride = DevVerts->stride;

    camera = RwCameraGetCurrentCamera();
    if (!camera)
    {
        RWRETURN(FALSE);
    }

    width = RwRasterGetWidth(RwCameraGetRaster(camera));
    xRes = (RwReal) width;
    height = RwRasterGetHeight(RwCameraGetRaster(camera));
    yRes = (RwReal) height;
    xResScaled =
        (((RwReal) 0.5) * xRes) / RwCameraGetViewWindow(camera)->x;
    yResScaled =
        (((RwReal) 0.5) * yRes) / RwCameraGetViewWindow(camera)->y;
    minScreenX = ((RwReal) 0);
    minScreenY = ((RwReal) 0);
    maxScreenX = xRes;
    maxScreenY = yRes;

    meshData = RxClusterGetCursorData(MeshState, RxMeshStateVector);
    numVerts = (RxVertexIndex) meshData->NumVertices;
    numDrawnParticles = 0;

    DevVerts = RxClusterResizeData(DevVerts, 4 * numVerts);
    RWASSERT(NULL != DevVerts);
    CamVerts = RxClusterResizeData(CamVerts, 4 * numVerts);
    RWASSERT(NULL != CamVerts);

    /* Alloc enough extra triangles */
    {
        RwUInt32            newSize = numVerts * 6;

        if (Indices->numAlloced < newSize)
        {
            Indices =
                RxClusterInitializeData(Indices, newSize,
                                        sizeof(RxVertexIndex));
            RWASSERT(NULL != Indices);
        }
    }

    /* Do stuff! Groovy expantzhion bebbeh. */
    RxClusterResetCursor(DevVerts);
    RxClusterResetCursor(CamVerts);

    srcVert = RxClusterGetCursorData(DevVerts, RxScrSpace2DVertex);
    dstVert =
        RxClusterGetIndexedData(DevVerts, RxScrSpace2DVertex, numVerts);

    for (vertNum = 0; vertNum < numVerts; vertNum++)
    {
        RwReal              recipZ, xSize, ySize, x, y, z;
        RwUInt32            red, green, blue, alpha;
        RwInt32             i;

        recipZ = RwIm2DVertexGetRecipCameraZ(srcVert);
        xSize =
            ((RwReal) 0.4) * (particleInfo->size.x * xResScaled *
                              recipZ);
        ySize =
            ((RwReal) 0.4) * (particleInfo->size.y * yResScaled *
                              recipZ);
        x = RwIm2DVertexGetScreenX(srcVert);
        y = RwIm2DVertexGetScreenY(srcVert);
        z = RwIm2DVertexGetScreenZ(srcVert);

        red = RwIm2DVertexGetRed(srcVert);
        green = RwIm2DVertexGetGreen(srcVert);
        blue = RwIm2DVertexGetBlue(srcVert);
        alpha = RwIm2DVertexGetAlpha(srcVert);

        for (i = 0; i < 4; i++)
        {
            if (i == 0)
            {
                devVert = srcVert;
                srcVert = (RxScrSpace2DVertex *)
                    RWSTRIDEDPTRINC(srcVert, devVertStride);
            }
            else
            {
                devVert = dstVert;
                dstVert = (RxScrSpace2DVertex *)
                    RWSTRIDEDPTRINC(dstVert, devVertStride);
            }

            RwIm2DVertexSetIntRGBA(devVert, red, green, blue, alpha);
            RwIm2DVertexSetScreenX(devVert,
                                   x + (vertCoords[i].x * xSize));
            RwIm2DVertexSetScreenY(devVert,
                                   y + (vertCoords[i].y * ySize));
            RwIm2DVertexSetScreenZ(devVert, z);
            RwIm2DVertexSetU(devVert, texCoords[i].x, recipZ);
            RwIm2DVertexSetV(devVert, texCoords[i].y, recipZ);
            RwIm2DVertexSetRecipCameraZ(devVert, recipZ);
        }

        particleInfo++;
    }

    meshData->NumElements = numVerts * 2;
    meshData->NumVertices = numVerts * 4;

    DevVerts->numUsed = meshData->NumVertices;
    CamVerts->numUsed = meshData->NumVertices;
    Indices->numUsed = meshData->NumElements * 3;

    /* setup the triangles */
    {
        RwUInt32            i, s, e;
        RxVertexIndex      *idxPtr;

        RxClusterResetCursor(Indices);
        idxPtr = RxClusterGetCursorData(Indices, RxVertexIndex);

        s = 0;
        e = numVerts;

        for (i = 0; i < meshData->NumElements / 2; i++)
        {
            idxPtr[0] = (RxVertexIndex) (s);
            idxPtr[1] = (RxVertexIndex) (e);
            idxPtr[2] = (RxVertexIndex) (e + 1);
            idxPtr[3] = (RxVertexIndex) (s);
            idxPtr[4] = (RxVertexIndex) (e + 1);
            idxPtr[5] = (RxVertexIndex) (e + 2);

            idxPtr += 6;
            s += 1;
            e += 3;
        }
    }

    /* Kill off camverts (if present), they no longer correspond to the mesh */
    RxClusterDestroyData(CamVerts);

    /* Send the particles down the second
     * ("ParticlesOutWithTriangles") output */
    RxPacketDispatch(packet, 1, self);

    RWRETURN(TRUE);
}

/**
 * \ingroup rpprtsys
 * \ref RxNodeDefinitionGetPrtclExpandCSL returns a pointer to
 * a node to expand vertices into sprites.
 *
 * This node takes each input screen-space vertex and explodes it into
 * either three (if the node is drawing particles as triangles) or four
 * (if quads) vertices. Performing this operation right at the end of the
 * pipeline, beyond transformation, is very efficient.
 *
 * The node is capable of treating a specified particle texture as a
 * series of embedded frames and doing animation by modifying UV
 * coordinates over time and on a per-particle basis. The flags for
 * choosing animation mode are described at \ref RxPipelineNodePtExAnimFlag.
 * This flag, the texture to use, whether to draw tris or quads,
 * world-space particle size, blend mode, ZWrite enabling, an override
 * colour and speed of animation are all specifiable in the node's
 * \ref RxPipelineNodeParticleExpandData private data.
 *
 * This node has four outputs. The first is used when particle expansion
 * is turned off in the pipeline node's private data (packets are passed
 * straight to this output without modification). The second is used when
 * particles are output as quads and must this have indices included with
 * them. The third is used when particles are output as triangles which
 * can be rendered as an unindexed priimitive and so do not need indices
 * with them. The fourth output is used when all the particles are clipped
 * from the screen (note that particles just need to touch the clipping
 * planes to be clipped - this is not noticeable on most modern hardware
 * which has a guard band clipping region).
 *
 * \verbatim
   The node has four outputs
   The input requirements of this node:
  
   RxClScrSpace2DVertices  - required
   RxClCamSpace3DVertices  - required
   RxClMeshState           - required
   RxClRenderState         - optional
   RxClIndices             - don't want
   clusterRpParticles      - optional
  
   The characteristics of the first of this node's outputs:
  
   RxClScrSpace2DVertices  - no change
   RxClCamSpace3DVertices  - no change
   RxClMeshState           - no change
   RxClRenderState         - no change
   RxClIndices             - no change
   clusterRpParticles      - no change
  
   The characteristics of the second of this node's outputs:
  
   RxClScrSpace2DVertices  - valid
   RxClCamSpace3DVertices  - invalid
   RxClMeshState           - no change
   RxClRenderState         - valid
   RxClIndices             - valid
   clusterRpParticles      - no change
  
   The characteristics of the third of this node's outputs:
  
   RxClScrSpace2DVertices  - valid
   RxClCamSpace3DVertices  - invalid
   RxClMeshState           - no change
   RxClRenderState         - valid
   RxClIndices             - invalid
   clusterRpParticles      - no change
  
   The characteristics of the fourth of this node's outputs:
  
   RxClScrSpace2DVertices  - invalid
   RxClCamSpace3DVertices  - invalid
   RxClMeshState           - no change
   RxClRenderState         - valid
   RxClIndices             - invalid
   clusterRpParticles      - no change
   \endverbatim
 *  
 * \return pointer to a node to expand vertices into sprites.
 *
 * \see RxPipelineNodeParticleExpandUVMalloc
 * \see RxNodeDefinitionGetPrtclSysInstanceCSL
 */
RxNodeDefinition   *
RxNodeDefinitionGetPrtclExpandCSL(void)
{

    static RxClusterRef N1clofinterest[] = /* */
    {
        {&RxClScrSpace2DVertices, rxCLALLOWABSENT, 0},
        {&RxClCamSpace3DVertices, rxCLALLOWABSENT, 0},
        {&RxClMeshState, rxCLALLOWABSENT, 0},
        {&RxClRenderState, rxCLALLOWABSENT, 0},
        {&RxClIndices, rxCLALLOWABSENT, 0}
    };

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

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

    static RxClusterValid N1outclNoParticles[NUMCLUSTERSOFINTERESTPRTCLEXPAND] = /* */
    {
        rxCLVALID_NOCHANGE,
        rxCLVALID_NOCHANGE,
        rxCLVALID_NOCHANGE,
        rxCLVALID_NOCHANGE,
        rxCLVALID_NOCHANGE
    };

    static RxClusterValid N1outclParticlesOutTri[NUMCLUSTERSOFINTERESTPRTCLEXPAND] = /* */
    {
        rxCLVALID_VALID,
        rxCLVALID_INVALID,
        rxCLVALID_NOCHANGE,
        rxCLVALID_VALID,
        rxCLVALID_VALID
    };

    static RxClusterValid N1outclParticlesOutNoTri[NUMCLUSTERSOFINTERESTPRTCLEXPAND] = /* */
    {
        rxCLVALID_VALID,
        rxCLVALID_INVALID,
        rxCLVALID_NOCHANGE,
        rxCLVALID_VALID,
        rxCLVALID_INVALID
    };

    static RxClusterValid N1outclAllParticlesClipped[NUMCLUSTERSOFINTERESTPRTCLEXPAND] = /* */
    {
        rxCLVALID_INVALID,
        rxCLVALID_INVALID,
        rxCLVALID_NOCHANGE,
        rxCLVALID_VALID,
        rxCLVALID_INVALID
    };

    static RwChar       _NoParticles[] = RWSTRING("NoParticles");
    static RwChar       _ParticlesOutTri[] =
        RWSTRING("ParticlesOutWithTriangles");
    static RwChar       _ParticlesOutNoTri[] =
        RWSTRING("ParticlesOutWithoutTriangles");
    static RwChar       _AllParticlesCulled[] =
        RWSTRING("AllParticlesCulled");

    static RxOutputSpec N1outputs[] = /* */
    {
        {_NoParticles, N1outclNoParticles, rxCLVALID_NOCHANGE},
        {_ParticlesOutTri, N1outclParticlesOutTri, rxCLVALID_NOCHANGE},

        {_ParticlesOutNoTri, N1outclParticlesOutNoTri,
         rxCLVALID_NOCHANGE},
        {_AllParticlesCulled, N1outclAllParticlesClipped,
         rxCLVALID_NOCHANGE}
    };

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

    static RwChar       _ParticleExpand_csl[] =
        RWSTRING("ParticleExpand.csl");

    static RxNodeDefinition nodeParticleExpandCSL = /* */
    {
        _ParticleExpand_csl,
        {
         _ParticleExpandNode,
         NULL,
         NULL,
         NULL,
         NULL,
         NULL,
         NULL},

        {
         NUMCLUSTERSOFINTERESTPRTCLEXPAND,
         N1clofinterest,
         N1inputreqs,
         NUMOUTPUTS,
         N1outputs},
        0,
        (RxNodeDefEditable) FALSE,
        0
    };

    RWAPIFUNCTION(RWSTRING("RxNodeDefinitionGetPrtclExpandCSL"));

    RWRETURN(&nodeParticleExpandCSL);
}

/************************************************************************
 * Particle animation stuff
 */

static              RwBool
_FountainParametricNode(RxPipelineNodeInstance * self,
                        const RxPipelineNodeParam * params __RWUNUSED__)
{

    RxPacket           *packet;
    RxCluster          *objVerts, *meshState, *renderState;
    RpAtomic           *atomic;
    RpGeometry         *geom;
    RpParticlesData    *particlesData;
    RpParticlesGeomData *particlesGeomData;
    RpParticleInfo     *particleInfo;
    RwInt32             numParticles, particleNum;
    RwReal              time, flightTime, invFlightTime;
    RwRGBAReal         *startCol, *endCol;
    RwV3d              *accel;
    RwV2d              *prtclSize;
    RwReal              growth;

    RWFUNCTION(RWSTRING("_FountainParametricNode"));

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

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

    particlesData = *PARTICLESATOMICGETDATA(atomic);
    RWASSERT(NULL != particlesData);

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

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

    packet = RxPacketFetch(self);
    if (!packet)
    {
        RWRETURN(FALSE);
    }

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

    if ((!objVerts) || (!meshState) || (!renderState))
    {
        RWRETURN(FALSE);
    }

    if ((objVerts->numUsed <= 0) ||
        (meshState->numUsed <= 0) || (renderState->numUsed <= 0))
    {
        RWRETURN(FALSE);
    }

#if 1
    /* We fade (using alpha) on a per-particle basis, so we need to
     * enable prelighting if it's not already enabled and also enable
     * use of vertex alpha */
    RxClusterGetCursorData(meshState, RxMeshStateVector)->Flags |=
        rxGEOMETRY_PRELIT;
    RxClusterGetCursorData(renderState, RxRenderStateVector)->Flags |=
        rxRENDERSTATEFLAG_VERTEXALPHAENABLE;
#endif /* 0 */

    time = particlesData->time;
    flightTime = particlesData->flightTime;
    invFlightTime = ((RwReal) 1) / flightTime;
    startCol = &particlesData->startCol;
    endCol = &particlesData->endCol;
    accel = &particlesData->force;
    prtclSize = &particlesData->particleSize;
    growth = particlesData->growth;

    numParticles = particlesData->numParticles;
    for (particleNum = 0; particleNum < numParticles; particleNum++)
    {
        RxObjSpace3DVertex *objVert;
        RwReal              prtlcTime, tNorm, t2;
        RwRGBAReal          colS, colE, colReal;
        RwRGBA              colInt;
        RwV3d               posNew, *pos, *vel;
        RwV2d              *pSize;

        /* current particle */
        objVert = RxClusterGetCursorData(objVerts, RxObjSpace3DVertex);

        /* particle time */
        prtlcTime = particleInfo->startTime;
        prtlcTime += time;
        if (prtlcTime > flightTime)
        {
            prtlcTime -= flightTime;
        }
        tNorm = prtlcTime * invFlightTime;

        /* particle colour */
        RwRGBARealScaleMacro(&colS, startCol, ((RwReal) 1) - tNorm);
        RwRGBARealScaleMacro(&colE, endCol, tNorm);
        RwRGBARealAddMacro(&colReal, &colS, &colE);
        colInt.red = (RwUInt8) colReal.red;
        colInt.green = (RwInt8) colReal.green;
        colInt.blue = (RwInt8) colReal.blue;
        colInt.alpha = (RwInt8) colReal.alpha;
        RxObjSpace3DVertexSetPreLitColor(objVert, &colInt);

        /* particle size */
        growth = particlesData->growth;
        growth = ((growth - ((RwReal) 1)) * tNorm) + ((RwReal) 1);
        growth *= ((RwReal) 0.5);
        pSize = &particleInfo->size;
        RwV2dScale(pSize, prtclSize, growth);

        /* particle position */
        /* s = u*t + 0.5*a*(t^2) + p */
        pos = &particleInfo->position;
        vel = &particleInfo->velocity;
        t2 = prtlcTime * prtlcTime;
        posNew.x = (vel->x * prtlcTime) + (accel->x * t2) + pos->x;
        posNew.y = (vel->y * prtlcTime) + (accel->y * t2) + pos->y;
        posNew.z = (vel->z * prtlcTime) + (accel->z * t2) + pos->z;
        RxObjSpace3DVertexSetPos(objVert, &posNew);

        /* onto next particle */
        particleInfo++;
        RxClusterIncCursor(objVerts);
    }

    RxPacketDispatch(packet, 0, self);

    RWRETURN(TRUE);
}

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

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

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

    static RxClusterValid N1outclOutput[NUMCLUSTERSOFINTERESTFOUNTAIN] = /* */
    {
        rxCLVALID_VALID,
        rxCLVALID_NOCHANGE,
        rxCLVALID_VALID
    };

    static RwChar       _Output[] = RWSTRING("Output");

    static RxOutputSpec N1outputs[] = /* */
    {
        {
         _Output,
         N1outclOutput,
         rxCLVALID_NOCHANGE}
    };

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

    static RwChar       _FountainParametric_csl[] =
        RWSTRING("FountainParametric.csl");

    static RxNodeDefinition nodeFountainParametricCSL = /* */
    {
        _FountainParametric_csl,
        {
         _FountainParametricNode,
         NULL,
         NULL,
         NULL,
         NULL,
         NULL,
         NULL},
        {
         NUMCLUSTERSOFINTERESTFOUNTAIN,
         N1clofinterest,
         N1inputreqs,
         NUMOUTPUTS,
         N1outputs},
        0,
        (RxNodeDefEditable) FALSE,
        0
    };

    RWFUNCTION(RWSTRING("_rpGetNodeFountainParametricCSL"));

    RWRETURN(&nodeFountainParametricCSL);
}

/*****************************************************************
 * Particle pipe create stuff
 */

RxPipeline         *
_rpParticleMaterialPipelineCreate(void)
{
    RxPipeline         *pipe;
    RxPipeline         *lpipe;

    RWFUNCTION(RWSTRING("_rpParticleMaterialPipelineCreate"));

    pipe = RxPipelineCreate();
    if (!pipe)
    {
        RWRETURN(NULL);
    }

    lpipe = RxPipelineLock(pipe);
    if (lpipe)
    {
        RwInt32 firstFrag;
        
#if (defined(OPENGL_DRVMODEL_H))
        /* Let's fast path for OpenGL */
        lpipe =
            RxLockedPipeAddFragment(lpipe,
                                    (RwUInt32 *)&firstFrag,
                                    _rpGetNodeFountainParametricCSL(),
                                    RxNodeDefinitionGetOpenGLSubmitParticles(),
                                    NULL);
#elif (defined(D3D7_DRVMODEL_H))
        lpipe =
            RxLockedPipeAddFragment(lpipe,
                                    (RwUInt32 *)&firstFrag,
                                    _rpGetNodeFountainParametricCSL(),
                                    RxNodeDefinitionGetD3D7SubmitParticles(),
                                    NULL);
#elif (defined(D3D8_DRVMODEL_H))
        lpipe =
            RxLockedPipeAddFragment(lpipe,
                                    (RwUInt32 *)&firstFrag,
                                    _rpGetNodeFountainParametricCSL(),
                                    RxNodeDefinitionGetD3D8SubmitParticles(),
                                    NULL);
#else
        lpipe =
            RxLockedPipeAddFragment(lpipe,
                                    (RwUInt32 *)&firstFrag,
                                    _rpGetNodeFountainParametricCSL(),
                                    RxNodeDefinitionGetTransform(),
                                    RxNodeDefinitionGetPrtclExpandCSL(),
                                    RxNodeDefinitionGetSubmitTriangle(),
                                    NULL);

        {
            RxPipelineNode  *node = NULL; 
            RxPipelineNode  *node2 = NULL;

            /* Link up the particle expand node to the pipeline following it on its
             * second and third outputs as well as the first */
            node =
                RxPipelineFindNodeByName(lpipe, "ParticleExpand.csl", NULL,
                                         NULL);
            node2 =
                RxPipelineFindNodeByName(lpipe, "SubmitTriangle.csl", NULL,
                                         NULL);

            lpipe = RxLockedPipeAddPath(lpipe,
                                        RxPipelineNodeFindOutputByName(node,
                                        "ParticlesOutWithTriangles"),
                                        RxPipelineNodeFindInput(node2));
            lpipe =
                RxLockedPipeAddPath(lpipe,
                                    RxPipelineNodeFindOutputByName(node,
                                    "ParticlesOutWithoutTriangles"),
                                    RxPipelineNodeFindInput(node2));
        }
#endif

        if (!lpipe)
        {
            RWRETURN(NULL);
        }

        lpipe = RxLockedPipeUnlock(lpipe);
        if (lpipe != NULL)
        {
            RWRETURN(pipe);
        }

        RxPipelineDestroy(pipe);
    }

    RWRETURN(NULL);
}

RxPipeline         *
_rpParticleObjectPipelineCreate(void)
{

    RxPipeline         *pipe;
    RxPipeline         *lpipe;

    RWFUNCTION(RWSTRING("_rpParticleObjectPipelineCreate"));

    pipe = RxPipelineCreate();
    if (!pipe)
    {
        RWRETURN(NULL);
    }

    lpipe = RxPipelineLock(pipe);
    if (lpipe)
    {
        RwUInt32            firstFrag;
        RxPipelineNode     *node = NULL;
        RxPipelineNode     *node2 = NULL;
        RxNodeDefinition   *particleInstance;
        RxNodeDefinition   *materialScatter;

        particleInstance = RxNodeDefinitionGetPrtclSysInstanceCSL();
        materialScatter = RxNodeDefinitionGetMaterialScatter();

        lpipe = RxLockedPipeAddFragment(lpipe, &firstFrag,
                                        particleInstance,
                                        materialScatter, NULL);

        /* Both ParticleSystemInstance outputs need to be connected to the
         * scatter node (one outputs with ParticleSystem-specific data and
         * one without) and only the first is connected automatically by
         * RxPipelineAddFragment() */
        node =
            RxPipelineFindNodeByName(lpipe,
                                     "ParticleSystemInstance.csl", NULL,
                                     NULL);
        node2 =
            RxPipelineFindNodeByName(lpipe, "MaterialScatter.csl", NULL,
                                     NULL);
        lpipe =
            RxLockedPipeAddPath(lpipe,
                                RxPipelineNodeFindOutputByName(node,
                                                               "ParticleSystemInstanceOutWithoutPSData"),
                                RxPipelineNodeFindInput(node2));

        if (lpipe != NULL)
        {
            lpipe = RxLockedPipeUnlock(lpipe);
            if (lpipe != NULL)
            {
#if (defined(RWDEBUG))
                if (lpipe != pipe)
                {
                    static char         _message[] =
                        "ObjectPipelineCreate(), after unlock: (lpipe != pipe)";
                    MESSAGE(RWSTRING(_message));
                }
#endif /* (defined(RWDEBUG)) */

                RWRETURN(pipe);
            }
        }
    }

    RWRETURN(NULL);
}

RwBool
_rpParticlesSetupPipes(void)
{
    RWFUNCTION(RWSTRING("_rpParticlesSetupPipes"));

    GParticlesObjectPipe = _rpParticleObjectPipelineCreate();
    if (!GParticlesObjectPipe)
    {
        RWRETURN(FALSE);
    }

    GParticlesMaterialPipe = _rpParticleMaterialPipelineCreate();
    if (!GParticlesMaterialPipe)
    {
        RWRETURN(FALSE);
    }

    RWRETURN(TRUE);
}

RwBool
_rpParticlesDestroyPipes(void)
{
    RWFUNCTION(RWSTRING("_rpParticlesDestroyPipes"));

    if (GParticlesMaterialPipe)
    {
        RxPipelineDestroy(GParticlesMaterialPipe);
        GParticlesMaterialPipe = NULL;
    }

    if (GParticlesObjectPipe)
    {
        RxPipelineDestroy(GParticlesObjectPipe);
        GParticlesObjectPipe = NULL;
    }

    RWRETURN(FALSE);
}
