/* *INDENT-OFF* */

/*
 * nodeps2manager
 * A PS2-specific node to render complete objects in one go
 *
 * Copyright (c) Criterion Software Limited
 */

/****************************************************************************
 *                                                                          *
 * module : nodeps2manager.c                                                *
 *                                                                          *
 * purpose: yawn...                                                         *
 *                                                                          *
 ****************************************************************************/

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

#include "rwcore.h"
#include "matputil.h"

#include "nodeps2objallinone.h"
#include "nodeps2matinstance.h"
#include "nodeps2matbridge.h"
#include "nodeps2manager.h"

#if (!defined(DOXYGEN))
static const char rcsid[] __RWUNUSED__ = 
    "@@@@(#)$Id: nodeps2manager.c,v 1.90 2001/09/12 16:01:38 iestynb Exp $";
#endif /* (!defined(DOXYGEN)) */



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

#define PRIVATEDATATYPE rwPS2ManagerPvtData

#define MESSAGE(_string) \
    RwDebugSendMessage(rwDEBUGMESSAGE, "PS2Manager.csl", (_string))

#define PRIMTYPEMAX \
    (rwPRIMTYPELINELIST | rwPRIMTYPEPOLYLINE | rwPRIMTYPETRILIST | \
     rwPRIMTYPETRISTRIP | rwPRIMTYPETRIFAN   | rwPRIMTYPEPOINTLIST)

/* Rearrange privatedata for a following function call
 * (allows private data to be cast to RxPipelineNodePS2xxxxData) */
#define REARRANGE(_self, _data, _origData)                      \
MACRO_START                                                     \
{                                                               \
    _origData = (rwPS2ManagerPvtData *)((_self)->privateData);  \
    (_self)->privateData = &(_data);                            \
    (_data).matBridgeData   = (_origData)->matBridgeData;       \
    (_data).matInstanceData = (_origData)->matInstanceData;     \
    (_data).objAllInOneData = (_origData)->objAllInOneData;     \
    (_data).instanceCB      = (_origData)->instanceCB;          \
    (_data).postObjectCB    = (_origData)->postObjectCB;        \
    (_data).primLUT         = (_origData)->primLUT;             \
}                                                               \
MACRO_STOP

/* Undo above rearrangement after a function call */
#define UNARRANGE(_self, _data, _origData)                  \
MACRO_START                                                 \
{                                                           \
    (_origData)->matBridgeData   = (_data).matBridgeData;   \
    (_origData)->matInstanceData = (_data).matInstanceData; \
    (_origData)->objAllInOneData = (_data).objAllInOneData; \
    (_origData)->instanceCB      = (_data).instanceCB;      \
    (_origData)->postObjectCB    = (_data).postObjectCB;    \
    (_origData)->primLUT         = (_data).primLUT;         \
    (_self)->privateData = (_origData);                     \
}                                                           \
MACRO_STOP


/****************************************************************************
local types
*/

typedef struct PrimTypeLUT PrimTypeLUT;
struct PrimTypeLUT
{
    RwUInt8 vertToIndRatio[rwPRIMTYPEOR];
    RwUInt8 vertToIndOffset[rwPRIMTYPEOR];
};

typedef struct rwPS2ManagerPvtData rwPS2ManagerPvtData;
struct rwPS2ManagerPvtData
{
    RxPipelineNodePS2ObjAllInOneData            objAllInOneData;
    rwPS2MatInstancePvtData                     matInstanceData;
    rwPS2MatBridgePvtData                       matBridgeData;
    RxPipelineNodePS2ManagerInstanceCallBack    instanceCB;
    RxPipelineNodePS2ManagerPostObjectCallBack  postObjectCB;
    PrimTypeLUT                                 primLUT;
};

/* Blehh :)
 * To be able to reuse all the API/init functions of
 * objAllInOne, matInstance and matBridge, we need to temporarily
 * rearrange private data so it can be cast to the appropriate
 * type - these structs make that easy */

typedef struct rwPS2ManagerPvtDataInstFirst rwPS2ManagerPvtDataInstFirst;
struct rwPS2ManagerPvtDataInstFirst
{
    rwPS2MatInstancePvtData                     matInstanceData;
    RxPipelineNodePS2ObjAllInOneData            objAllInOneData;
    rwPS2MatBridgePvtData                       matBridgeData;
    RxPipelineNodePS2ManagerInstanceCallBack    instanceCB;
    RxPipelineNodePS2ManagerPostObjectCallBack  postObjectCB;
    PrimTypeLUT                                 primLUT;
};

typedef struct rwPS2ManagerPvtDataBridgeFirst rwPS2ManagerPvtDataBridgeFirst;
struct rwPS2ManagerPvtDataBridgeFirst
{
    rwPS2MatBridgePvtData                       matBridgeData;
    RxPipelineNodePS2ObjAllInOneData            objAllInOneData;
    rwPS2MatInstancePvtData                     matInstanceData;
    RxPipelineNodePS2ManagerInstanceCallBack    instanceCB;
    RxPipelineNodePS2ManagerPostObjectCallBack  postObjectCB;
    PrimTypeLUT                                 primLUT;
};

/* For completeness */
typedef struct RxPipelineNodePS2ManagerData RxPipelineNodePS2ManagerDataObjAllInOneFirst;

/* Luckily we don't have to fiddle about with the initialisation
 * data since only matInstance uses it! */
typedef rwPS2MatInstanceInitData RxPipelineNodePS2ManagerInitData;


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

   Functions

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


/****************************************************************************
 _rxPS2ManagerPipelineNodeInit
 */

static RwBool
PS2ManagerPipelineNodeInit(RxPipelineNode *self)
{
    rwPS2ManagerPvtDataInstFirst   matInstanceData;
    rwPS2ManagerPvtDataBridgeFirst matBridgeData;
    rwPS2ManagerPvtData *data;
    RwBool result;

    RWFUNCTION(RWSTRING("PS2ManagerPipelineNodeInit"));

    result = _rwPS2ObjAllInOnePipelineNodeInit(self);

    if (result == FALSE) RWRETURN(result);

    REARRANGE(self, matInstanceData, data);
    result = _rwPS2MatInstancePipelineNodeInit(self);
    UNARRANGE(self, matInstanceData, data);

    if (result == FALSE) RWRETURN(result);

    REARRANGE(self, matBridgeData, data);
    result = _rwPS2MatBridgePipelineNodeInit(self);
    UNARRANGE(self, matBridgeData, data);

    data->instanceCB = (RxPipelineNodePS2ManagerInstanceCallBack)NULL;
    data->postObjectCB = (RxPipelineNodePS2ManagerPostObjectCallBack)NULL;

    /* Set up LUTs to deal with the different primtypes */
    data->primLUT.vertToIndRatio[rwPRIMTYPELINELIST ] = 1;
    data->primLUT.vertToIndRatio[rwPRIMTYPEPOLYLINE ] = 2;
    data->primLUT.vertToIndRatio[rwPRIMTYPETRILIST  ] = 1;
    data->primLUT.vertToIndRatio[rwPRIMTYPETRISTRIP ] = 1;
    data->primLUT.vertToIndRatio[rwPRIMTYPETRIFAN   ] = 3;
    data->primLUT.vertToIndRatio[rwPRIMTYPEPOINTLIST] = 1;

    data->primLUT.vertToIndOffset[rwPRIMTYPELINELIST ] = 0;
    data->primLUT.vertToIndOffset[rwPRIMTYPEPOLYLINE ] = 1;
    data->primLUT.vertToIndOffset[rwPRIMTYPETRILIST  ] = 0;
    data->primLUT.vertToIndOffset[rwPRIMTYPETRISTRIP ] = 2;
    data->primLUT.vertToIndOffset[rwPRIMTYPETRIFAN   ] = 2;
    data->primLUT.vertToIndOffset[rwPRIMTYPEPOINTLIST] = 0;

    RWRETURN(result);
}



/****************************************************************************
 PS2ManagerIm3DNodeBody
 */
static  RwBool
PS2ManagerIm3DNodeBody(RxPipelineNodeInstance * self,
                       const RxPipelineNodeParam * params)
{
    rwPS2ManagerPvtData *pvtData = (rwPS2ManagerPvtData *)(self->privateData);
    RxPS2DMASessionRecord DMASessionRecord;
    RxPS2Mesh           PS2Mesh;
    _rwIm3DPoolStash   *immData;
    RxCluster           fakeClusters[16];
    void               *data;
    RxHeap             *heap;
    RwBool              success;
    RwPrimitiveType     primType;

    RWFUNCTION(RWSTRING("PS2ManagerIm3DNodeBody"));

    /* OBJALLINONE */

#if (defined(GSB))
    if (skyGCVTValue % skyNumSeqFrms != skyRenderSlot)
    {
        /* Don't render if it's not our turn */
        RWRETURN(TRUE);
    }
#endif /* (defined(GSB)) */

    RWASSERT(params != NULL);
    data = RxPipelineNodeParamGetData(params);
    RWASSERT(NULL != data);

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

    DMASessionRecord.objType = rxOBJTYPE_IM3D;
    DMASessionRecord.sourceObject.agnostic = data;

    immData = (_rwIm3DPoolStash *) data;

    primType = immData->primType;


#if (defined(RWMETRICS))
    /* numVertices updated in PS2Im3DFastTransform */

    /* We don't count lines/points */
    if ((primType == rwPRIMTYPETRIFAN) ||
        (primType == rwPRIMTYPETRISTRIP))
    {
        RWSRCGLOBAL(metrics)->numTriangles += immData->numIndices - 2;
    }
    else if (primType == rwPRIMTYPETRILIST)
    {
        RWSRCGLOBAL(metrics)->numTriangles += immData->numIndices / 3;
    }
#endif /* (defined(RWMETRICS)) */

    success = _rwIm3DObjAllInOneCode(&DMASessionRecord,
                                     &(pvtData->objAllInOneData));
    RWASSERT(FALSE != success);

    {
        RwUInt32            numBatches;
        RwUInt32            batchSize;
        RwUInt32            batchByteSize;
        RwUInt32            headerSize;
        RwUInt32            skipSize;
        RwUInt32            maxBatchesPerBlock;
        RwUInt32            numVerts;
        RwUInt32            sumClusterWidth;
        RwUInt32            numClusters;
        RwUInt32            numOpaqueClusters;
        RwUInt32            numNonOpaqueClusters;
        RwUInt32            indOffset;
        RwUInt32            indPerBlock;
        RwUInt32            i;
        RxObjSpace3DVertex  firstTriFanVertex;
        RxObjSpace3DVertex  swappedTriFanVertex;
        RxObjSpace3DVertex *tempObjVerts;
        RwMeshCache         meshCache;
        RpMeshHeader        meshHeader[2];
        RwUInt32            dmaTagSize = 16;
        RwUInt32            alignSize = 16;
        RwImVertexIndex     firstTriFanIndex = (RwImVertexIndex)0;
        RwImVertexIndex     swappedTriFanIndex = (RwImVertexIndex)0;
        RpMesh             *mesh = (RpMesh *) &(meshHeader[1]);
        RwUInt8            *vertToIndRatio = &(pvtData->primLUT.vertToIndRatio[0]);
        RwUInt8            *vertToIndOffset = &(pvtData->primLUT.vertToIndOffset[0]);
        rwPS2MatInstancePvtData *matInstanceData = &(pvtData->matInstanceData);
        const RwBool        triFan = (primType == rwPRIMTYPETRIFAN);
        const RwBool        triStrip = (primType == rwPRIMTYPETRISTRIP) ;

        meshHeader->flags = 0; /* Initialise the mem */
        RpMeshHeaderSetPrimType(&(meshHeader[0]), primType);

        meshHeader->numMeshes = 1;
        meshHeader->serialNum = 0; /* RPMESHGLOBAL(nextSerialNum)++ ?
                                    * Probably not important (it's fake) */
        meshHeader->totalIndicesInMesh = immData->numIndices;
        meshHeader->firstMeshOffset = 0; /* Cheekily whacked on the end
                                          * there :) */

        /* Build your own meshcache... */
        meshCache.lengthOfMeshesArray = 1;
        meshCache.meshes[0] = (RwResEntry *)NULL; /* Ensure DMA chain gets built */

        PS2Mesh.mesh = mesh;
        PS2Mesh.cacheEntryRef = rwMeshCacheGetEntryRef(&meshCache, 0);
        DMASessionRecord.serialNum = meshHeader->serialNum;

        if (NULL == immData->indices)
        {
            /* PS2MatInstance can now instance from unindexed meshes */
            meshHeader->flags |= rpMESHHEADERUNINDEXED;
        }

        /* We split the primitive into batches if it's too big
         * (we use the circular allocator which can only allocate
         * blocks up to 64k in size) */

        numVerts = immData->numIndices;
        if (FALSE != triFan)
        {
            numVerts = (numVerts - 2) * 3;
        }
        else if (primType == rwPRIMTYPEPOLYLINE)
        {
            numVerts = (numVerts - 1) * 2;
        }

        if (FALSE != triStrip)
        {
            batchSize = matInstanceData->triStrip.batchSize;
            numBatches = ((numVerts - 2) + ((batchSize - 2) - 1)) /
                (batchSize - 2);
        }
        else
        {
            batchSize = matInstanceData->triList.batchSize;
            numBatches = (numVerts + (batchSize - 1)) / batchSize;
        }

        /* TriFans require extra work - we have to shift the original
         * first vertex/index up to the beginning of each batch. */
        if (FALSE != triFan)
        {
            if (meshHeader->flags & rpMESHHEADERUNINDEXED)
            {
                firstTriFanVertex = *(immData->objVerts);
                swappedTriFanVertex = firstTriFanVertex;
            }
            else
            {
                firstTriFanIndex = *(immData->indices);
                swappedTriFanIndex = firstTriFanIndex;
            }
        }

        /* For unindexed objects, we will increment the vertex
         * array pointer (PURPOSEFULLY no predication here) */
        tempObjVerts = immData->objVerts;

        numClusters = 0;
        numOpaqueClusters = 0;
        /* "skip" (in quadwords) is the size of one batch of all the opaque
         * clusters plus all the tags inserted inbetween. If there are no
         * opaque clusters this should be zero. It should be the same for
         * all opaque clusters. */
        skipSize = 0;
        /* Group sizes of non-opaque clusters for simpler calcs later */
        sumClusterWidth = 0;
        for (i = 0; i < CL_MAXCL; i++)
        {
            if (matInstanceData->clinfo[i].attrib & CL_ATTRIB_REQUIRED)
            {
                numClusters++;
                if (matInstanceData->clinfo[i].attrib & CL_ATTRIB_OPAQUE)
                {
                    if (FALSE != triStrip)
                    {
                        skipSize =
                            (RwUInt32) matInstanceData->triStrip.fieldRec[i].skip
                            << 4;
                    }
                    else
                    {
                        skipSize =
                            matInstanceData->triList.fieldRec[i].skip
                            << 4;
                    }
                    numOpaqueClusters++;
                }
                else
                {
                    /* Non-opaque stride is in WORDs */
                    sumClusterWidth += matInstanceData->clinfo[i].stride << 2;
                }
            }
        }
        numNonOpaqueClusters = numClusters - numOpaqueClusters;

        if (numOpaqueClusters == 0)
        {
            /* In this case, skip isn't set up because there's no opaque data.
             * It *should* be 2 DMA tags per (non-opaque) cluster plus another
             * two (see line ~3286 of nodeps2matinstance.c, "Where it would be
             * if there was no opaque data"): */
            skipSize = 2*dmaTagSize*(numNonOpaqueClusters + 1);
        }

        /* I don't think we support something with no instanced clusters atm */
        RWASSERT(numClusters > 0);

        /* We assume at least some clusters by reference so we
         * overestimate the contribution of tags to size. Also,
         * people can overload the Im3D pipe and it still has to
         * work coming through here - to avoid excessive work here,
         * we add a conservative alignment padding for all opaque
         * clusters. */
        batchByteSize =
            skipSize +                   /* Size of one opaque batch - all opaque data and the tags
                                          * inbetween (including those for non-opaque clusters) */
            batchSize * sumClusterWidth; /* Size of one batch of non-opaque clusters (data only) */

        /* Round down, don't bother trying to squeeze partial batches in.
         * We want to get this done some time today... */
        headerSize =
            sizeof(RwResEntry) +          /* This is factored into the allocated block */
            sizeof(rwPS2ResEntryHeader) + /* This too */
            dmaTagSize * 2 + /* Maybe there're extra tags at the start/end */
            128 +            /* The allocated block is padded by a cache line size */
            numNonOpaqueClusters * alignSize; /* Each broken-out cluster's data is
                                               * padded to QW size */

        maxBatchesPerBlock =
            (sweCircAllocBlockSize - headerSize) / batchByteSize;

        if (maxBatchesPerBlock < numBatches)
        {
            indOffset = maxBatchesPerBlock *
                        (batchSize / vertToIndRatio[primType]);
            if (primType == rwPRIMTYPETRISTRIP)
                indOffset -= 2 * maxBatchesPerBlock;
            indPerBlock = indOffset + vertToIndOffset[primType];
        }
        else
        {
            indOffset = meshHeader->totalIndicesInMesh;
            indPerBlock = indOffset;
        }

        /* The mesh has no particular material (-> use current renderstate) */
        mesh->material = (RpMaterial *)NULL;

        /* MATINSTANCE */

        /* Now lockwrite() breakout clusters... effectively anyway.
         * It's probably not bulletproof but as a proof of concept
         * it'll do. Shame we use clusters so much in matInstance,
         * it'd be faster without them.
         * The proper nodePS2Manager will have no need for
         * them but how we get them out of nodeMatInstance
         * whilst sharing code I have NO idea...
         */
        for (i = 0; i < matInstanceData->numBreakOuts; i++)
        {
            RwUInt32            index = matInstanceData->cliIndex[i];
            RwUInt32            slot = self->inputToClusterSlot[index];
            RxCluster          *cluster = &(fakeClusters[slot]);
            RxPipelineCluster  *pipelineCluster = self->slotClusterRefs[slot];

            /*printf("Im3D breakout!\n"); */
            cluster->clusterRef = pipelineCluster;
            cluster->attributes = pipelineCluster->creationAttributes;
            cluster->data = NULL;
            cluster->flags = rxCLFLAGS_NULL;

            matInstanceData->clusters[i] = cluster;
        }

        /* Run through the indices in several batches if necessary */
        mesh->indices = immData->indices;
        mesh->numIndices = indPerBlock;

        while (meshHeader->totalIndicesInMesh > 0)
        {
            /* Ensure DMA chain gets built EVERY time round */
            meshCache.meshes[0] = (RwResEntry *)NULL;

            success =
                _rwRabinsMatInstanceCode(&DMASessionRecord, &PS2Mesh,
                                         matInstanceData);
            RWASSERT(FALSE != success);

            /* Allow instancing and misc uploads for stuff like skinning */
            if (NULL != pvtData->instanceCB)
            {
                void               *dataPtrs[CL_MAXCL + 2];
                RwUInt32            numBreakOuts;
                RwResEntry         *repEntry;
                rwPS2ResEntryHeader *ps2ResHeader;
                RwUInt32            i;

                numBreakOuts = matInstanceData->numBreakOuts;
                repEntry = *(PS2Mesh.cacheEntryRef);
                RWASSERT(NULL != repEntry);
                ps2ResHeader = (rwPS2ResEntryHeader *) (repEntry + 1);

                dataPtrs[0] = &PS2Mesh;
                dataPtrs[1] = &DMASessionRecord;
                /* Note: we still execute even if there are no visible
                 * clusters - a plugin may want to upload only static data. */
                for (i = 0; i < numBreakOuts; i++)
                {
                    dataPtrs[i + 2] = ps2ResHeader->clquickinfo[i].data;
                }

                success = pvtData->instanceCB(dataPtrs, 2 + numBreakOuts);
            }

            /* MATBRIDGE */

            /* The instance CB can cause the mesh not to be rendered by
             * returning FALSE */
            if (FALSE != success)
            {
                success =
                    _rwRabinsBridgeNodeCode(&DMASessionRecord, &PS2Mesh,
                                            &(pvtData->matBridgeData));
                RWASSERT(FALSE != success);

#if (defined(RWMETRICS))
                /* We don't count lines */
                if (!(DMASessionRecord.transType & TRANSLINE))
                {
                    if (DMASessionRecord.transType & TRANSLIST)
                    {
                        RWSRCGLOBAL(metrics)->numProcTriangles +=
                            PS2Mesh.mesh->numIndices / 3;
                    }
                    else
                    {
                        RWSRCGLOBAL(metrics)->numProcTriangles +=
                            PS2Mesh.mesh->numIndices - 2;
                    }
                }
#endif /* (defined(RWMETRICS)) */
            }

            /* Undo prior vertex/index swaps for TriFans */
            if (FALSE != triFan)
            {
                if (NULL != mesh->indices)
                {
                    *(mesh->indices) = swappedTriFanIndex;
                }
                else
                {
                    *(immData->objVerts) = swappedTriFanVertex;
                }
            }

            /* On to the next block of verts if there is one */
            if (indOffset >= meshHeader->totalIndicesInMesh)
            {
                /* We're finished, yay! */
                meshHeader->totalIndicesInMesh = 0;
            }
            else
            {
                meshHeader->totalIndicesInMesh -= indOffset;
                if (meshHeader->totalIndicesInMesh <= indPerBlock)
                {
                    /* Last block */
                    mesh->numIndices = meshHeader->totalIndicesInMesh;
                }

                /* Increment the index pointer to the next block (if we're
                 * unindexed we increment the vertex pointer cos the indices
                 * are implicit in the vertex array). For trifans, we have
                 * to swap the first index/vert each time round... yawn */
                if (mesh->indices)
                {
                    mesh->indices += indOffset;
                    if (FALSE != triFan)
                    {
                        swappedTriFanIndex = *(mesh->indices);
                        *(mesh->indices) = firstTriFanIndex;
                    }
                }
                else
                {
                    immData->objVerts += indOffset;
                    if (FALSE != triFan)
                    {
                        swappedTriFanVertex = *(immData->objVerts);
                        *(immData->objVerts) = firstTriFanVertex;
                    }
                }
            }
        }
        /* Undo vertex increment for unindexed prims
         * (DELIBERATE lack of predication here) */
        immData->objVerts = tempObjVerts;
    }

    /* Allow post-object cleanup without interfering with the render callback */
    if (NULL != pvtData->postObjectCB)
    {
        success = pvtData->postObjectCB(&DMASessionRecord);
        RWASSERT(FALSE != success);
    }

    RWRETURN(TRUE);
}

/****************************************************************************
 PS2ManagerWorldSectorNodeBody
 */
static RwBool
PS2ManagerWorldSectorNodeBody(RxPipelineNodeInstance *self,
                              const RxPipelineNodeParam * params)
{
    rwPS2ManagerPvtData  *pvtData = (rwPS2ManagerPvtData *)(self->privateData);
    RxPS2DMASessionRecord DMASessionRecord;
    RxPS2Mesh             PS2Mesh;
    RxCluster             fakeClusters[16];
    void                 *data;
    RwBool                success;

    RWFUNCTION(RWSTRING("PS2ManagerWorldSectorNodeBody"));


    /* OBJALLINONE */

#if (defined(GSB))
    if (skyGCVTValue % skyNumSeqFrms != skyRenderSlot)
    {
        /* Don't render if it's not our turn */
        RWRETURN(TRUE);
    }
#endif /* (defined(GSB)) */

    RWASSERT( params != NULL );
    data = RxPipelineNodeParamGetData(params);

    DMASessionRecord.objType               = rxOBJTYPE_WORLDSECTOR;
    DMASessionRecord.sourceObject.agnostic = data;

    /* Check ASAP for an empty sector */
    if (DMASessionRecord.sourceObject.worldSector->numVertices <= 0)
    {
        /* Don't execute the rest of the pipeline */
        RWRETURN(TRUE);
    }

#if (defined(RWMETRICS))
    /* Update our metrics statistics */
    RWSRCGLOBAL(metrics)->numVertices  +=
        RpWorldSectorGetNumVertices(DMASessionRecord.sourceObject.worldSector);
    RWSRCGLOBAL(metrics)->numTriangles +=
        RpWorldSectorGetNumPolygons(DMASessionRecord.sourceObject.worldSector);
#endif /* (defined(RWMETRICS)) */

    success = _rwRabinsObjAllInOneCode(
                  &DMASessionRecord, &(pvtData->objAllInOneData));
    RWASSERT(FALSE != success);

    {
        RpMeshHeader *meshHeader = (RpMeshHeader *)NULL;
        RwMeshCache  *meshCache  = (RwMeshCache *)NULL;
        const RpMesh *mesh;
        RwUInt32      n;

        RWASSERT(((RwUInt8)rwSECTORATOMIC)
                 == RwObjectGetType((RwObject *)data));

        meshHeader = ((RpWorldSector *) data)->mesh;
        meshCache = rpWorldSectorGetMeshCache((RpWorldSector *) data,
                                              meshHeader->numMeshes);
        mesh = (const RpMesh *) (meshHeader + 1);

        for (n = 0; n < meshHeader->numMeshes; n++)
        {
            rwPS2MatInstancePvtData *matInstanceData;
            RwTexture *texture;
            RwUInt32   i;

            PS2Mesh.mesh = mesh;
            PS2Mesh.cacheEntryRef = rwMeshCacheGetEntryRef(meshCache, n);
            PS2Mesh.meshNum = n;


            /* Let the texture cache get an early-as-poss lookin' so that
             * it can do asynchronous texture upload overlapped with the
             * last lot of VIF uploads. */
            texture = mesh->material->texture;
            if ((FALSE == pvtData->matBridgeData.noTexture) &&
                (NULL != texture) &&
                (texture->raster != skyTextureRaster))
            {
                RpSkyTexCacheAccessSpeculate(texture->raster);
            }


            /* MATINSTANCE */

            matInstanceData = &(pvtData->matInstanceData);

            /* Now lockwrite() breakout clusters... effectively anyway.
             * It's probably not bulletproof but as a proof of concept
             * it'll do. Shame we use clusters so much in matInstance,
             * it'd be faster without them.
             * The proper nodePS2Manager will have no need for
             * them but how we get them out of nodeMatInstance
             * whilst sharing code I have NO idea...
             */
            for (i = 0;i < matInstanceData->numBreakOuts;i++)
            {
                RwUInt32   index   = matInstanceData->cliIndex[i];
                RwUInt32   slot    = self->inputToClusterSlot[index];
                RxCluster *cluster = &(fakeClusters[slot]);
                RxPipelineCluster *pipelineCluster =
                    self->slotClusterRefs[slot];

                cluster->clusterRef = pipelineCluster;
                cluster->attributes = pipelineCluster->creationAttributes;
                RxClusterResetCursor(cluster);
                cluster->data = NULL;
                cluster->flags = rxCLFLAGS_NULL;

                matInstanceData->clusters[i] = cluster;
            }

            success = _rwRabinsMatInstanceCode(
                          &DMASessionRecord, &PS2Mesh, matInstanceData);
            RWASSERT(FALSE != success);

            /* Allow instancing and misc uploads for stuff like skinning */
            if (NULL != pvtData->instanceCB)
            {
                void *dataPtrs[CL_MAXCL + 2];
                RwUInt32 numBreakOuts;
                RwResEntry *repEntry;
                rwPS2ResEntryHeader *ps2ResHeader;

                numBreakOuts = pvtData->matInstanceData.numBreakOuts;
                repEntry = *(PS2Mesh.cacheEntryRef);
                RWASSERT(NULL != repEntry);
                ps2ResHeader = (rwPS2ResEntryHeader *)(repEntry + 1);

                dataPtrs[0] = &PS2Mesh;
                dataPtrs[1] = &DMASessionRecord;
                /* Note: we still execute even if there are no visible
                 * clusters - a plugin may want to upload only static data. */
                for (i = 0;i < numBreakOuts;i++)
                {
                    dataPtrs[i + 2] = ps2ResHeader->clquickinfo[i].data;
                }

                success = pvtData->instanceCB(dataPtrs, 2 + numBreakOuts);
            }

            /* MATBRIDGE */

            /* The instance CB can cause the mesh not to be rendered by
             * returning FALSE */
            if (FALSE != success)
            {
                success =
                    _rwRabinsBridgeNodeCode(
                        &DMASessionRecord, &PS2Mesh, &(pvtData->matBridgeData));
                RWASSERT(FALSE != success);

#if (defined(RWMETRICS))
                /* We don't count lines */
                if (!(DMASessionRecord.transType & TRANSLINE))
                {
                    if (DMASessionRecord.transType & TRANSLIST)
                    {
                        RWSRCGLOBAL(metrics)->numProcTriangles +=
                            PS2Mesh.mesh->numIndices / 3;
                    }
                    else
                    {
                        RWSRCGLOBAL(metrics)->numProcTriangles +=
                            PS2Mesh.mesh->numIndices - 2;
                    }
                }
#endif /* (defined(RWMETRICS)) */
            }

            mesh++;
        }
    }

    /* Allow post-object cleanup without interfering with the render callback */
    if (NULL != pvtData->postObjectCB)
    {
        success = pvtData->postObjectCB(&DMASessionRecord);
        RWASSERT(FALSE != success);
    }

    RWRETURN(TRUE);
}

/****************************************************************************
 PS2ManagerAtomicNodeBody
 */
static RwBool
PS2ManagerAtomicNodeBody(RxPipelineNodeInstance *self,
                         const RxPipelineNodeParam * params)
{
    rwPS2ManagerPvtData  *pvtData = (rwPS2ManagerPvtData *)(self->privateData);
    RxPS2DMASessionRecord DMASessionRecord;
    RxPS2Mesh             PS2Mesh;
    RxCluster             fakeClusters[16];
    void                 *data;
    RwBool                success;

    RWFUNCTION(RWSTRING("PS2ManagerAtomicNodeBody"));

    /* OBJALLINONE */

#if (defined(GSB))
    if (skyGCVTValue % skyNumSeqFrms != skyRenderSlot)
    {
        /* Don't render if it's not our turn */
        RWRETURN(TRUE);
    }
#endif /* (defined(GSB)) */

    RWASSERT( params != NULL );
    data = RxPipelineNodeParamGetData(params);

    DMASessionRecord.objType               = rxOBJTYPE_ATOMIC;
    DMASessionRecord.sourceObject.agnostic = data;

#if (defined(RWMETRICS))
    /* Update our metrics statistics */
    {
        RpGeometry *geom =
            RpAtomicGetGeometry(DMASessionRecord.sourceObject.atomic);

        RWSRCGLOBAL(metrics)->numVertices  +=
            RpGeometryGetNumVertices(geom);
        RWSRCGLOBAL(metrics)->numTriangles +=
            RpGeometryGetNumTriangles(geom);
    }
#endif /* (defined(RWMETRICS)) */

    success = _rwRabinsObjAllInOneCode(
                  &DMASessionRecord, &(pvtData->objAllInOneData));
    RWASSERT(FALSE != success);

    {
        RpMeshHeader *meshHeader = (RpMeshHeader *)NULL;
        RwMeshCache  *meshCache  = (RwMeshCache *)NULL;
        const RpMesh *mesh;
        RpGeometry   *geom;
        RwUInt32      n;

        RWASSERT(((RwUInt8)rpATOMIC) == RwObjectGetType((RwObject *)data));

        geom = RpAtomicGetGeometry((RpAtomic *) data);
        RWASSERT( geom != NULL );

        meshHeader = geom->mesh;

        /* If the geometry has more than one morph target the resEntry in the
         * atomic is used else the resEntry in the geometry */
        if (RpGeometryGetNumMorphTargets(geom) != 1)
        {
            meshCache = rpAtomicGetMeshCache((RpAtomic *)data,
                                             meshHeader->numMeshes);
        }
        else
        {
            meshCache = rpGeometryGetMeshCache(geom, meshHeader->numMeshes);
        }

        mesh = (const RpMesh *) (meshHeader + 1);

        for (n = 0; n < meshHeader->numMeshes; n++)
        {
            RwTexture *texture;
            RwUInt32 i;

            PS2Mesh.mesh = mesh;
            PS2Mesh.cacheEntryRef = rwMeshCacheGetEntryRef(meshCache, n);
            PS2Mesh.meshNum = n;


            /* Let the texture cache get an early-as-poss lookin' so that
             * it can do asynchronous texture upload overlapped with the
             * last lot of VIF uploads. */
            texture = mesh->material->texture;
            if ((FALSE == pvtData->matBridgeData.noTexture) &&
                (NULL != texture) &&
                (texture->raster != skyTextureRaster))
            {
                RpSkyTexCacheAccessSpeculate(texture->raster);
            }


            /* MATINSTANCE */

            if (!(geom->instanceFlags & rpGEOMETRYPERSISTENT))
            {
                /* Now lockwrite() breakout clusters... effectively anyway.
                 * It's probably not bulletproof but as a proof of concept
                 * it'll do. Shame we use clusters so much in matInstance,
                 * it'd be faster without them.
                 * The proper nodePS2Manager will have no need for
                 * them but how we get them out of nodeMatInstance
                 * whilst sharing code I have NO idea...
                 */
                rwPS2MatInstancePvtData *matInstanceData =
                    &(pvtData->matInstanceData);
                for (i = 0;i < matInstanceData->numBreakOuts;i++)
                {
                    RwUInt32   index   = matInstanceData->cliIndex[i];
                    RwUInt32   slot    = self->inputToClusterSlot[index];
                    RxCluster *cluster = &(fakeClusters[slot]);
                    RxPipelineCluster *pipelineCluster =
                        self->slotClusterRefs[slot];

                    cluster->clusterRef = pipelineCluster;
                    cluster->attributes = pipelineCluster->creationAttributes;
                    RxClusterResetCursor(cluster);
                    cluster->data = NULL;
                    cluster->flags = rxCLFLAGS_NULL;

                    matInstanceData->clusters[i] = cluster;
                }

                success = _rwRabinsMatInstanceCode(
                              &DMASessionRecord, &PS2Mesh, matInstanceData);
                RWASSERT(FALSE != success);

                /* Allow instancing and misc uploads for stuff like skinning */
                if (NULL != pvtData->instanceCB)
                {
#if (!defined(FASTMORPH))
                    void *dataPtrs[CL_MAXCL + 2];
#else /* (!defined(FASTMORPH)) */
                    void *dataPtrs[CL_MAXCL + 4];
#endif /* (!defined(FASTMORPH)) */
                    RwUInt32 numBreakOuts;
                    RwResEntry *repEntry;
                    rwPS2ResEntryHeader *ps2ResHeader;

                    numBreakOuts = pvtData->matInstanceData.numBreakOuts;
                    repEntry = *(PS2Mesh.cacheEntryRef);
                    RWASSERT(NULL != repEntry);
                    ps2ResHeader = (rwPS2ResEntryHeader *)(repEntry + 1);

                    dataPtrs[0] = &PS2Mesh;
                    dataPtrs[1] = &DMASessionRecord;
                    /* Note: we still execute even if there are no visible
                     * clusters - a plugin may want to upload only static data. */
                    for (i = 0;i < numBreakOuts;i++)
                    {
                        dataPtrs[i + 2] = ps2ResHeader->clquickinfo[i].data;
                    }

                    success = pvtData->instanceCB(dataPtrs, 2 + numBreakOuts);
                }
            }
            else
            {
                /* Allow misc static data uploads for stuff like skinning
                 * (no instancing because the instance data is PERSISTENT) */
                if (NULL != pvtData->instanceCB)
                {
                    void *dataPtrs[2];

                    dataPtrs[0] = &PS2Mesh;
                    dataPtrs[1] = &DMASessionRecord;

                    success = pvtData->instanceCB(dataPtrs, 2);
                }
            }


            /* MATBRIDGE */

            /* The instance CB can cause the mesh not to be rendered by
             * returning FALSE */
            if (FALSE != success)
            {
                success =
                    _rwRabinsBridgeNodeCode(
                        &DMASessionRecord, &PS2Mesh, &(pvtData->matBridgeData));
                RWASSERT(FALSE != success);

#if (defined(RWMETRICS))
                /* We don't count lines */
                if (!(DMASessionRecord.transType & TRANSLINE))
                {
                    if (DMASessionRecord.transType & TRANSLIST)
                    {
                        RWSRCGLOBAL(metrics)->numProcTriangles +=
                            PS2Mesh.mesh->numIndices / 3;
                    }
                    else
                    {
                        RWSRCGLOBAL(metrics)->numProcTriangles +=
                            PS2Mesh.mesh->numIndices - 2;
                    }
                }
#endif /* (defined(RWMETRICS)) */
            }

            mesh++;
        }
    }

    {
        /* Some flags are cleared once per atomic not per mesh,
         * given that meshes are instanced/reinstanced separately */
        RpGeometry *geom = RpAtomicGetGeometry((RpAtomic *) data);
        RpInterpolator *interpolator =
            RpAtomicGetInterpolator((RpAtomic*) data);

        geom->lockedSinceLastInst = 0;
        interpolator->flags &= ~rpINTERPOLATORDIRTYINSTANCE;
    }

    /* Allow post-object cleanup without interfering with the render callback */
    if (NULL != pvtData->postObjectCB)
    {
        success = pvtData->postObjectCB(&DMASessionRecord);
        RWASSERT(FALSE != success);
    }

    RWRETURN(TRUE);
}

/**
 * \ingroup rpworldp2sky2
 * \ref RxPipelineNodePS2ManagerSetInstanceCallBack is used to
 * register a callback to instance data or set up additional DMA data.
 *
 * The registered callback will be called after standard instancing has
 * occurred - standard instancing being that done by PS2Manager. User
 * cluster or clusters with CL_ATTRIB_DONT_FILL or CL_ATTRIB_PLACEHOLDER
 * attributes will not be instanced by PS2Manager. The instance callback
 * will receive an array of pointers to cluster data for all clusters
 * which have CL_ATTRIB_READ or CL_ATTRIB_WRITE specified (the clusters
 * that can be filled in by user CPU-side code). The first pointer will
 * always be to the \ref RxPS2Mesh cluster and the second to the
 * \ref RxPS2DMASessionRecord cluster. There need not be any further
 * (CL_ATTRIB_READ or CL_ATTRIB_WRITE) clusters for the callback to be
 * called (a plug-in may, for example, only want to upload a texture or
 * some static data).
 *
 * The default instancing code (called for all standard, non-read-or-write
 * clusters) can instance from meshes containing any \ref RwPrimitiveType,
 * indexed or unindexed. User-supplied callbacks, however, need only
 * handle meshes of the types required by the user.
 *
 * This function should be called after unlocking the pipeline containing
 * this node.
 *
 * \param  self   A pointer to a PS2Manager.csl pipeline node
 * \param  callBack   A pointer to a callback (NULL disables a prior callback)
 *
 * \return a pointer to the input pipeline node on success, otherwise NULL.
 *
 * \see RxPipelineNodePS2ManagerSetPostObjectCallBack
 * \see RxPipelineNodePS2ManagerGenerateCluster
 * \see RxPipelineNodePS2ManagerSetVUBufferSizes
 * \see RxPipelineNodePS2ManagerSetPointListVUBufferSize
 * \see RxPipelineNodePS2ManagerGetVUBatchSize
 * \see RxPipelineNodePS2ManagerSetLighting
 * \see RxPipelineNodePS2ManagerSetVU1CodeArray
 * \see RxPipelineNodePS2ManagerGetVU1CodeArray
 * \see RxPipelineNodePS2ManagerSetVIFOffset
 * \see RxNodeDefinitionGetPS2Manager
 * \see RxNodeDefinitionGetPS2MatInstance
 * \see RxNodeDefinitionGetPS2MatBridge
 * \see RxNodeDefinitionGetPS2ObjAllInOne
 */
RxPipelineNode *
RxPipelineNodePS2ManagerSetInstanceCallBack(
    RxPipelineNode *self,
    RxPipelineNodePS2ManagerInstanceCallBack callBack)
{
    PRIVATEDATATYPE *data;

    RWAPIFUNCTION(RWSTRING("RxPipelineNodePS2ManagerSetInstanceCallBack"));

    if (NULL == self)
    {
        RwDebugSendMessage(rwDEBUGMESSAGE,
                           "RxPipelineNodePS2ManagerSetInstanceCallBack",
                           "NULL node passed");
        RWRETURN((RxPipelineNode *)NULL);
    }

    if (NULL == self->privateData)
    {
        RwDebugSendMessage(rwDEBUGMESSAGE,
                           "RxPipelineNodePS2ManagerSetInstanceCallBack",
                           "Node's private data pointer is NULL - only call this function after unlocking the containing pipeline");
        RWRETURN((RxPipelineNode *)NULL);
    }

    data = (PRIVATEDATATYPE *)self->privateData;
    data->instanceCB = callBack;

    RWRETURN(self);
}

/**
 * \ingroup rpworldp2sky2
 * \ref RxPipelineNodePS2ManagerSetPostObjectCallBack is used to
 * register a callback to perform post-object cleanup.
 *
 * The registered callback will be called, for the owning PS2Manager
 * pipeline node, after all meshes in an object have been processed
 * (remember that PS2Manager is used in object pipelines, not material
 * pipelines), such that cleanup may be performed at the end of the
 * object's pipeline execution (without having to interfere with the
 * object's render callback).
 *
 * This callback will receive a pointer to the data of the
 * \ref RxPS2DMASessionRecord cluster.
 *
 * This function should be called after unlocking the pipeline containing
 * this node.
 *
 * \param  self   A pointer to a PS2Manager.csl pipeline node
 * \param  callBack   A pointer to a callback (NULL disables a prior callback)
 *
 * \return a pointer to the input pipeline node on success, otherwise NULL.
 *
 * \see RxPipelineNodePS2ManagerSetInstanceCallBack
 * \see RxPipelineNodePS2ManagerGenerateCluster
 * \see RxPipelineNodePS2ManagerSetVUBufferSizes
 * \see RxPipelineNodePS2ManagerSetPointListVUBufferSize
 * \see RxPipelineNodePS2ManagerGetVUBatchSize
 * \see RxPipelineNodePS2ManagerSetLighting
 * \see RxPipelineNodePS2ManagerSetVU1CodeArray
 * \see RxPipelineNodePS2ManagerGetVU1CodeArray
 * \see RxPipelineNodePS2ManagerSetVIFOffset
 * \see RxNodeDefinitionGetPS2Manager
 * \see RxNodeDefinitionGetPS2MatInstance
 * \see RxNodeDefinitionGetPS2MatBridge
 * \see RxNodeDefinitionGetPS2ObjAllInOne
 */
RxPipelineNode *
RxPipelineNodePS2ManagerSetPostObjectCallBack(
    RxPipelineNode *self,
    RxPipelineNodePS2ManagerPostObjectCallBack callBack)
{
    PRIVATEDATATYPE *data;

    RWAPIFUNCTION(RWSTRING("RxPipelineNodePS2ManagerSetPostObjectCallBack"));

    if (NULL == self)
    {
        RwDebugSendMessage(rwDEBUGMESSAGE,
                           "RxPipelineNodePS2ManagerSetPostObjectCallBack",
                           "NULL node passed");
        RWRETURN((RxPipelineNode *)NULL);
    }

    if (NULL == self->privateData)
    {
        RwDebugSendMessage(rwDEBUGMESSAGE,
                           "RxPipelineNodePS2ManagerSetPostObjectCallBack",
                           "Node's private data pointer is NULL - only call this function after unlocking the containing pipeline");
        RWRETURN((RxPipelineNode *)NULL);
    }

    data = (PRIVATEDATATYPE *)self->privateData;
    data->postObjectCB = callBack;

    RWRETURN(self);
}

/**
 * \ingroup rpworldp2sky2
 * \ref RxPipelineNodePS2ManagerGenerateCluster is used to
 * force this node to generate clusters. It should be called with a
 * pointer to this node and each of the RxClPS2* clusters required.
 * This function must be called before the pipeline containing this
 * node is unlocked because that is when cluster attributes are
 * finalized.
 *
 * These are the PS2-specific clusters you can request for this node
 * (see also \ref RxPS2ClusterType):
 *      \li RxClPS2xyz - Object-space position of vertices
 *      \li RxClPS2uv - Base texture coordinates of vertices
 *      \li RxClPS2uv2 - Two texture coordinate sets
 *      \li RxClPS2rgba - Prelight colors of vertices
 *      \li RxClPS2normal - Object-space normals at each vertex
 *      \li RxClPS2user1 - User-defined cluster 1
 *      \li RxClPS2user2 - User-defined cluster 2
 *      \li RxClPS2user3 - User-defined cluster 3
 *      \li RxClPS2user4 - User-defined cluster 4
 *
 * PS2-specific attributes (in the RxPS2AttributeSet namespace, which
 * has the identifier string "PS2") are used to define two properties
 * of the above PS2-specific clusters. Firstly, the
 * \ref RxPS2ClusterAttrib flags define how the data's instancing into
 * DMA chains is handled by the instancing portion of PS2manager.csl
 * (see the docs for these flags for details). Secondly, the
 * \ref RxPS2ClusterFormatAttrib flags define what format the data
 * will be in as interpreted by the DMA engine and VIF when the data
 * is uploaded to VU1.
 *
 * Currently, PS2Manager.csl is in a nascent form but in its final
 * incarnation, PS2Manager.csl will be all that you will need on PS2
 * and will allow instancing of custom data and customizable per-object,
 * per-mesh and per-vertex data. The recently-added
 * \ref RxPipelineNodePS2ManagerSetInstanceCallBack should provide as much
 * flexibility as with pipelines constructed from the separated
 * PS2ObjAllInOne.csl, PS2MatInstance.csl and PS2MatBridge.csl nodes with
 * additional inserted user nodes. The exception is that PS2Manager.csl
 * ignores material pipelines so that all meshes within an object are
 * rendered in the same style (that defined by the object pipeline).
 *
 * You must request at least one cluster for the pipeline to function.
 * This function should be called before unlocking the pipeline containing
 * this node.
 *
 * \param  self   A pointer to a PS2Manager.csl pipeline node
 * \param  cluster2generate   A pointer to a PS2-specific cluster definition
 * \param  type   PS2-specific cluster type specifier
 *
 * \return a pointer to the input pipeline node on success, otherwise NULL.
 *
 * \see RxPipelineNodePS2MatInstanceGenerateCluster
 * \see RxPipelineNodePS2ManagerSetInstanceCallBack
 * \see RxPipelineNodePS2ManagerSetPostObjectCallBack
 * \see RxPipelineNodePS2ManagerSetVUBufferSizes
 * \see RxPipelineNodePS2ManagerSetPointListVUBufferSize
 * \see RxPipelineNodePS2ManagerGetVUBatchSize
 * \see RxPipelineNodePS2ManagerSetLighting
 * \see RxPipelineNodePS2ManagerSetVU1CodeArray
 * \see RxPipelineNodePS2ManagerGetVU1CodeArray
 * \see RxPipelineNodePS2ManagerSetVIFOffset
 * \see RxClusterGetAttributes
 * \see RxClusterSetAttributes
 * \see RxPipelineClusterGetCreationAttributes
 * \see RxPipelineClusterSetCreationAttributes
 * \see RxNodeDefinitionGetPS2Manager
 * \see RxNodeDefinitionGetPS2MatInstance
 * \see RxNodeDefinitionGetPS2MatBridge
 * \see RxNodeDefinitionGetPS2ObjAllInOne
 */
RxPipelineNode *
RxPipelineNodePS2ManagerGenerateCluster(
    RxPipelineNode      *self,
    RxClusterDefinition *cluster2generate,
    RwUInt32             type)
{
    RxPipelineNode *result;

    RWAPIFUNCTION(RWSTRING("RxPipelineNodePS2ManagerGenerateCluster"));

    /* This uses initializationData, not privateData, so no need to rearrange */
    result = RxPipelineNodePS2MatInstanceGenerateCluster(self, cluster2generate, type);

    if (NULL != result)
    {
        /* For non-opaque clusters, need to force 'em present cos there are no
         * subsequent nodes to require them in the case of PS2Manager! */
        if (cluster2generate->defaultAttributes & CL_ATTRIB_READWRITE)
        {
            RwUInt32 numCl = self->nodeDef->io.numClustersOfInterest;
            self->nodeDef->io.clustersOfInterest[numCl - 1].forcePresent =
                rxCLFORCEPRESENT;
        }
    }

    RWRETURN(result);
}

/**
 * \ingroup rpworldp2sky2
 * \ref RxPipelineNodePS2ManagerSetVUBufferSizes is used to set the
 * maximum number of triangles (for trilists) and vertices (for tristrips)
 * that may be passed to the VU code section of the pipe containing this
 * node. The stride (in quad words) of the input buffer in vector memory
 * is also set by this function. If this function is not called the defaults
 * required by the standard transforms will be used. For pipelines feeding
 * user supplied VU code, these vaules may be obtained from the pipeline
 * specific header file created by processing stddata.i
 *
 * This function should be called before unlocking the pipeline containing
 * this node.
 *
 * \param  self                   A pointer to the current \ref RxPipelineNode
 * \param  strideOfInputCluster   stride of cluster in VU memory
 * \param  vuTSVertexMaxCount     maximum number of vertices per TriStrip batch
 * \param  vuTLTriMaxCount        maximum number of triangles per TriList batch
 *
 * \return a pointer to this node on success, otherwise NULL
 *
 * \see RxPipelineNodePS2ManagerSetPointListVUBufferSize
 * \see RxPipelineNodePS2ManagerGetVUBatchSize
 * \see RxPipelineNodePS2ManagerSetInstanceCallBack
 * \see RxPipelineNodePS2ManagerSetPostObjectCallBack
 * \see RxPipelineNodePS2ManagerGenerateCluster
 * \see RxPipelineNodePS2ManagerSetLighting
 * \see RxPipelineNodePS2ManagerSetVU1CodeArray
 * \see RxPipelineNodePS2ManagerGetVU1CodeArray
 * \see RxPipelineNodePS2ManagerSetVIFOffset
 * \see RxNodeDefinitionGetPS2Manager
 * \see RxNodeDefinitionGetPS2MatInstance
 * \see RxNodeDefinitionGetPS2MatBridge
 * \see RxNodeDefinitionGetPS2ObjAllInOne
 */
RxPipelineNode *
RxPipelineNodePS2ManagerSetVUBufferSizes(RxPipelineNode *self,
                                         RwInt32 strideOfInputCluster,
                                         RwInt32 vuTSVertexMaxCount,
                                         RwInt32 vuTLTriMaxCount)
{
    RxPipelineNode *result;

    RWAPIFUNCTION(RWSTRING("RxPipelineNodePS2ManagerSetVUBufferSizes"));

    /* This uses initializationData, not privateData,
     * so no need to rearrange */
    result = RxPipelineNodePS2MatInstanceNodeSetVUBufferSizes(
                 self,
                 strideOfInputCluster,
                 vuTSVertexMaxCount,
                 vuTLTriMaxCount);

    RWRETURN(result);
}

/**
 * \ingroup rpworldp2sky2
 * \ref RxPipelineNodePS2ManagerSetPointListVUBufferSize is used to set
 * the maximum number of vertices for pointlists that may be passed to the VU
 * code section of the pipe containing this node. The stride (in quad words)
 * of the input buffer in vector memory is also set by this function.
 *
 * This function should be called instead of
 * \ref RxPipelineNodePS2ManagerSetVUBufferSizes, they should not both be
 * called. Pipelines set up with this function will only be able to handle
 * pointlist objects, not triangle- or line-based objects. For pipelines
 * feeding user supplied VU code, the values to pass to this function may
 * be obtained from the pipeline specific header file created by processing
 * stddata.i. Note that for a pointlist pipeline, the first half of the VU1
 * code array should be used - i.e the TRANSTRI slots are reused.
 * (see \ref RxPipelineNodePS2ManagerSetVU1CodeArray)
 *
 * This function should be called before unlocking the pipeline containing
 * this node.
 *
 * \param  self                   A pointer to the current \ref RxPipelineNode
 * \param  strideOfInputCluster   stride of cluster in VU memory
 * \param  vuPLVertexMaxCount     maximum number of vertices per PointList batch
 *
 * \return a pointer to this node on success, otherwise NULL
 *
 * \see RxPipelineNodePS2ManagerSetVUBufferSizes
 * \see RxPipelineNodePS2ManagerGetVUBatchSize
 * \see RxPipelineNodePS2ManagerSetInstanceCallBack
 * \see RxPipelineNodePS2ManagerSetPostObjectCallBack
 * \see RxPipelineNodePS2ManagerGenerateCluster
 * \see RxPipelineNodePS2ManagerSetLighting
 * \see RxPipelineNodePS2ManagerSetVU1CodeArray
 * \see RxPipelineNodePS2ManagerGetVU1CodeArray
 * \see RxPipelineNodePS2ManagerSetVIFOffset
 * \see RxNodeDefinitionGetPS2Manager
 * \see RxNodeDefinitionGetPS2MatInstance
 * \see RxNodeDefinitionGetPS2MatBridge
 * \see RxNodeDefinitionGetPS2ObjAllInOne
 */
RxPipelineNode *
RxPipelineNodePS2ManagerSetPointListVUBufferSize(
    RxPipelineNode *self,
    RwInt32 strideOfInputCluster,
    RwInt32 vuPLVertexMaxCount)
{
    RxPipelineNode *result;

    RWAPIFUNCTION(RWSTRING("RxPipelineNodePS2ManagerSetPointListVUBufferSize"));

    /* This uses initializationData, not privateData,
     * so no need to rearrange */
    result = RxPipelineNodePS2MatInstanceSetPointListVUBufferSize(
                 self,
                 strideOfInputCluster,
                 vuPLVertexMaxCount);

    RWRETURN(result);
}

/**
 * \ingroup rpworldp2sky2
 * \ref RxPipelineNodePS2ManagerGetVUBatchSize 
 * is used to query the batch size used for uploading data to the VU.
 *
 * This function should be called after unlocking the pipeline containing
 * this node.
 *
 * \param  self   A pointer to the current \ref RxPipelineNode
 * \param  flags  \ref RpMeshHeaderFlags specifying what sort of mesh the
 *                returned values should relate to. Valid values are zero or
 *                rpMESHHEADERTRISTRIP - for pointlist rendering, use zero
 *
 * \return the batch size on success, zero otherwise
 *
 * \see RxPipelineNodePS2ManagerSetVUBufferSizes
 * \see RxPipelineNodePS2ManagerSetPointListVUBufferSize
 * \see RxPipelineNodePS2ManagerSetInstanceCallBack
 * \see RxPipelineNodePS2ManagerSetPostObjectCallBack
 * \see RxPipelineNodePS2ManagerGenerateCluster
 * \see RxPipelineNodePS2ManagerSetLighting
 * \see RxPipelineNodePS2ManagerSetVU1CodeArray
 * \see RxPipelineNodePS2ManagerGetVU1CodeArray
 * \see RxPipelineNodePS2ManagerSetVIFOffset
 * \see RxNodeDefinitionGetPS2Manager
 * \see RxNodeDefinitionGetPS2MatInstance
 * \see RxNodeDefinitionGetPS2MatBridge
 * \see RxNodeDefinitionGetPS2ObjAllInOne
 */
RwInt32
RxPipelineNodePS2ManagerGetVUBatchSize(
    RxPipelineNode *self,
    RpMeshHeaderFlags flags)
{
    RwInt32          result;
    PRIVATEDATATYPE *data;

    RWAPIFUNCTION(RWSTRING("RxPipelineNodePS2ManagerGetVUBatchSize"));

    if (NULL == self)
    {
        RwDebugSendMessage(rwDEBUGMESSAGE,
                           "RxPipelineNodePS2ManagerGetVUBatchSize",
                           "NULL node passed");
        RWRETURN(0);
    }

    if (self->privateData == NULL)
    {
        RwDebugSendMessage(rwDEBUGMESSAGE,
                           "RxPipelineNodePS2ManagerGetVUBatchSize",
                           "Call this function only *after* RxLockedPipeUnlock");
        RWRETURN(0);
    }

    data = (PRIVATEDATATYPE *) self->privateData;

    if (flags & rpMESHHEADERTRISTRIP)
    {
        result = data->matInstanceData.triStrip.batchSize;
    }
    else
    {
        result = data->matInstanceData.triList.batchSize;
    }

    RWRETURN(result);
}

/**
 * \ingroup rpworldp2sky2
 * \ref RxPipelineNodePS2ManagerSetLighting allows you to
 * overload PS2Manager.csl with your own lighting system.
 *
 * The Lighting function you pass must have the following format:
 * \verbatim
   void RxWorldLightingCallBack *(RwInt32 objectType, void *object,
                                  RwSurfaceProperties *surface,
                                  RxWorldApplyLightFunc lightingFunc)
   \endverbatim
 * where objectType is the type of object being passed (currently an
 * atomic or a world sector) and object is a pointer to it. You should
 * evaluate which lights affect this atomic and call the supplied
 * lighting func, passing the pointer to the light, the inverse LTM of
 * the object, the matrix scaling factor and the supplied surface
 * properties.
 *
 * This function should be called after unlocking the pipeline containing
 * this node.
 *
 * \param self              A pointer to the current \ref RxPipelineNode
 * \param newLightingFunc   The \ref RxWorldLightingCallBack to use
 *
 * \see RxPipelineNodePS2ManagerSetInstanceCallBack
 * \see RxPipelineNodePS2ManagerSetPostObjectCallBack
 * \see RxPipelineNodePS2ManagerGenerateCluster
 * \see RxPipelineNodePS2ManagerSetVUBufferSizes
 * \see RxPipelineNodePS2ManagerSetPointListVUBufferSize
 * \see RxPipelineNodePS2ManagerGetVUBatchSize
 * \see RxPipelineNodePS2ManagerSetVU1CodeArray
 * \see RxPipelineNodePS2ManagerGetVU1CodeArray
 * \see RxPipelineNodePS2ManagerSetVIFOffset
 * \see RxNodeDefinitionGetPS2Manager
 * \see RxNodeDefinitionGetPS2MatInstance
 * \see RxNodeDefinitionGetPS2MatBridge
 * \see RxNodeDefinitionGetPS2ObjAllInOne
 */
void
RxPipelineNodePS2ManagerSetLighting(RxPipelineNode *self,
                                    RxWorldLightingCallBack newLightingFunc)
{
    RWAPIFUNCTION(RWSTRING("RxPipelineNodePS2ManagerSetLighting"));

    /* Just pass it on through, because:
     * rwPS2ManagerPvtData == RxPipelineNodePS2ManagerDataObjAllInOneFirst */
    RxPipelineNodePS2ObjAllInOneSetLighting(self, newLightingFunc);

    RWRETURNVOID();
}

/**
 * \ingroup rpworldp2sky2
 * \ref RxPipelineNodePS2ManagerSetVU1CodeArray is a node API
 * function which permits setting of the VU code fragments to be used
 * by this pipeline.
 *
 * The bridge node connects its main CPU based pipeline to up to 16
 * different VU1 based pipelines. These are provided as an array of
 * 16 pointers to callable DMA chain mode packets that will upload
 * code to location 0 onward on VU1. The array is indexed using the
 * TRANS* bit values stored in the RxClPS2DMASessionRecord cluster
 * (there are 4 such toggles, hence 16 combinations - note that user
 * created code fragments may be used and TRANS* values set by a user
 * node if the default clip/list/iso/fog values set by
 * PS2WorldSectorObjAllInOne.csl/
 * PS2AtomicObjAllInOne.csl are not appropriate).
 *
 *      \li TRANSFOG:  This transform fogs.
 *      \li TRANSCLIP: This transform can cope with geometry which will
 * project out side the overdraw area.
 *      \li TRANSLIST: This is a trilist transform.
 *      \li TRANSISO:  This transform is used for isometric projection.
 *      \li TRANSLINE: Draw linelists/linestrips, not trilists/tristrips.
 *
 * TRANSNFOG, TRANSNCL, TRANSSTRIP, TRANSPER and TRANSTRI (all value
 * zero) are provided for convience. If this function is not called,
 * or is called with NULL, the default VU1 based code fragments will
 * be used. If an array element is NULL, the default VU1 code fragment
 * for that combination of types will be used (a useful debugging
 * procedure is to fill unused slots with some simple VU1 code that
 * ignores input data and merely serves to alert you that that code
 * slot is being used when it shouldn't - a big red triangle drawn
 * over the screen for instance, something immediately recognisable)..
 *
 * NOTE: For a pointlist pipeline, the first half of the code array
 * should be used - i.e the TRANSTRI slots are reused.
 * (see \ref RxPipelineNodePS2ManagerSetPointListVUBufferSize)
 *
 * This function must be called after unlocking the pipeline
 * containing the PS2Manager.csl node.
 *
 * \param  self         A pointer to the current \ref RxPipelineNode
 * \param VU1CodeArray  An array of 16 pointers to a callable dma
 * chain which will upload code and return. This is used by reference.
 *
 * \return a pointer to this node on success, otherwise NULL.
 *
 * \see RxPipelineNodePS2ManagerSetInstanceCallBack
 * \see RxPipelineNodePS2ManagerSetPostObjectCallBack
 * \see RxPipelineNodePS2ManagerGenerateCluster
 * \see RxPipelineNodePS2ManagerSetVUBufferSizes
 * \see RxPipelineNodePS2ManagerSetPointListVUBufferSize
 * \see RxPipelineNodePS2ManagerGetVUBatchSize
 * \see RxPipelineNodePS2ManagerSetLighting
 * \see RxPipelineNodePS2ManagerGetVU1CodeArray
 * \see RxPipelineNodePS2ManagerSetVIFOffset
 * \see RxNodeDefinitionGetPS2Manager
 * \see RxNodeDefinitionGetPS2MatInstance
 * \see RxNodeDefinitionGetPS2MatBridge
 * \see RxNodeDefinitionGetPS2ObjAllInOne
 */
RxPipelineNode *
RxPipelineNodePS2ManagerSetVU1CodeArray(RxPipelineNode *self,
                                        void **VU1CodeArray)
{
    rwPS2ManagerPvtDataBridgeFirst data;
    rwPS2ManagerPvtData *origData;
    RxPipelineNode *result;

    RWAPIFUNCTION(RWSTRING("RxPipelineNodePS2ManagerSetVU1CodeArray"));

    if (NULL == self)
    {
        RwDebugSendMessage(rwDEBUGMESSAGE,
                           "RxPipelineNodePS2ManagerSetVU1CodeArray",
                           "NULL node passed");
        RWRETURN((RxPipelineNode *)NULL);
    }

    if (self->privateData == NULL)
    {
        RwDebugSendMessage(rwDEBUGMESSAGE,
                           "RxPipelineNodePS2ManagerSetVU1CodeArray",
                           "Call this function only *after* RxLockedPipeUnlock");
        RWRETURN((RxPipelineNode *)NULL);
    }

    REARRANGE(self, data, origData);
    result = RxPipelineNodePS2MatBridgeSetVU1CodeArray(self, VU1CodeArray);
    UNARRANGE(self, data, origData);

    RWRETURN(result);
}

/**
 * \ingroup rpworldp2sky2
 * \ref RxPipelineNodePS2ManagerGetVU1CodeArray is a node API
 * function which retrieves an array of pointers to the VU code
 * fragments currently to be used by this pipeline. It mirrors
 * \ref RxPipelineNodePS2ManagerSetVU1CodeArray
 *
 * This function must be called after unlocking the pipeline
 * containing the PS2Manager.csl node.
 *
 * \param  self     A pointer to the current \ref RxPipelineNode
 *
 * \return a pointer to the current VU1 code array on success,
 * otherwise NULL.
 *
 * \see RxPipelineNodePS2ManagerSetInstanceCallBack
 * \see RxPipelineNodePS2ManagerSetPostObjectCallBack
 * \see RxPipelineNodePS2ManagerGenerateCluster
 * \see RxPipelineNodePS2ManagerSetVUBufferSizes
 * \see RxPipelineNodePS2ManagerSetPointListVUBufferSize
 * \see RxPipelineNodePS2ManagerGetVUBatchSize
 * \see RxPipelineNodePS2ManagerSetLighting
 * \see RxPipelineNodePS2ManagerSetVU1CodeArray
 * \see RxPipelineNodePS2ManagerSetVIFOffset
 * \see RxNodeDefinitionGetPS2Manager
 * \see RxNodeDefinitionGetPS2MatInstance
 * \see RxNodeDefinitionGetPS2MatBridge
 * \see RxNodeDefinitionGetPS2ObjAllInOne
 */
const void **
RxPipelineNodePS2ManagerGetVU1CodeArray(RxPipelineNode *self)
{
    rwPS2ManagerPvtDataBridgeFirst data;
    rwPS2ManagerPvtData *origData;
    const void **result;

    RWAPIFUNCTION(RWSTRING("RxPipelineNodePS2ManagerGetVU1CodeArray"));

    if (NULL == self)
    {
        RwDebugSendMessage(rwDEBUGMESSAGE,
                           "RxPipelineNodePS2ManagerGetVU1CodeArray",
                           "NULL node passed");
        RWRETURN((const void **)NULL);
    }

    if (self->privateData == NULL)
    {
        RwDebugSendMessage(rwDEBUGMESSAGE,
                           "RxPipelineNodePS2ManagerGetVU1CodeArray",
                           "Call this function only *after* RxLockedPipeUnlock");
        RWRETURN((const void **)NULL);
    }

    REARRANGE(self, data, origData);
    result = RxPipelineNodePS2MatBridgeGetVU1CodeArray(self);
    UNARRANGE(self, data, origData);

    RWRETURN(result);
}

/**
 * \ingroup rpworldp2sky2
 * \ref RxPipelineNodePS2ManagerSetVIFOffset is used to set the VIF
 * offset that is used when processing multiple batches of data. If this
 * function is not called, a value suitable for the default code fragments
 * and static data allocations will be used. For user provided VU1 code,
 * this value can be obtained from the pipeline specific headerfile generated
 * by processing stddata.i
 *
 * This function must be called after unlocking the pipeline
 * containing the PS2Manager.csl node.
 *
 * \param  self      A pointer to the current \ref RxPipelineNode
 * \param vifOffset  Value of VIF double buffer offset to be used
 * by this pipe
 *
 * \return a pointer to this node on success, otherwise NULL.
 *
 * \see RxPipelineNodePS2ManagerSetInstanceCallBack
 * \see RxPipelineNodePS2ManagerSetPostObjectCallBack
 * \see RxPipelineNodePS2ManagerGenerateCluster
 * \see RxPipelineNodePS2ManagerSetVUBufferSizes
 * \see RxPipelineNodePS2ManagerSetPointListVUBufferSize
 * \see RxPipelineNodePS2ManagerGetVUBatchSize
 * \see RxPipelineNodePS2ManagerSetLighting
 * \see RxPipelineNodePS2ManagerSetVU1CodeArray
 * \see RxPipelineNodePS2ManagerGetVU1CodeArray
 * \see RxNodeDefinitionGetPS2Manager
 * \see RxNodeDefinitionGetPS2MatInstance
 * \see RxNodeDefinitionGetPS2MatBridge
 * \see RxNodeDefinitionGetPS2ObjAllInOne
 */
RxPipelineNode *
RxPipelineNodePS2ManagerSetVIFOffset(RxPipelineNode *self, int vifOffset)
{
    rwPS2ManagerPvtDataBridgeFirst data;
    rwPS2ManagerPvtData *origData;
    RxPipelineNode *result;

    RWAPIFUNCTION(RWSTRING("RxPipelineNodePS2ManagerSetVIFOffset"));

    if (NULL == self)
    {
        RwDebugSendMessage(rwDEBUGMESSAGE,
                           "RxPipelineNodePS2ManagerSetVIFOffset",
                           "NULL node passed");
        RWRETURN((RxPipelineNode *)NULL);
    }

    if (self->privateData == NULL)
    {
        RwDebugSendMessage(rwDEBUGMESSAGE,
                           "RxPipelineNodePS2ManagerSetVIFOffset",
                           "Call this function only *after* RxLockedPipeUnlock");
        RWRETURN((RxPipelineNode *)NULL);
    }

    REARRANGE(self, data, origData);
    result = RxPipelineNodePS2MatBridgeSetVIFOffset(self, vifOffset);
    UNARRANGE(self, data, origData);

    RWRETURN(result);
}

/**
 * \ingroup rpworldp2sky2
 * \ref RxPipelineNodePS2ManagerNoTexture is a post construction
 * time API which permits preventing the manager node from
 * uploading any texture information.
 *
 * By default, the manager node will upload the raster for each mesh's
 * texture to video memory, just before the geometry of that mesh is
 * processed by VU1. It will also set up the appropriate filtering
 * and addressing modes for the texture. This API function can be used
 * to prevent this from happening. This is useful if a user-supplied
 * instance callback (see \ref RxPipelineNodePS2ManagerSetInstanceCallBack)
 * is able to set up textures itself more efficiently than can the
 * manager node (given that it is written based on knowledge of the
 * current application), if the instance callback needs to choose the
 * texture to upload from somewhere other than the object's materials,
 * etc.
 *
 * This function must be called after unlocking the pipeline
 * containing the PS2Manager.csl node.
 *
 * \param  self      A pointer to the current \ref RxPipelineNode
 * \param noTexture  TRUE to prevent the manager node from
 * uploading texture information, FALSE (default) to allow it
 *
 * \return a pointer to this node on success, otherwise NULL.
 *
 * \see RxPipelineNodePS2ManagerSetInstanceCallBack
 * \see RxPipelineNodePS2ManagerSetPostObjectCallBack
 * \see RxPipelineNodePS2ManagerGenerateCluster
 * \see RxPipelineNodePS2ManagerSetVUBufferSizes
 * \see RxPipelineNodePS2ManagerSetPointListVUBufferSize
 * \see RxPipelineNodePS2ManagerGetVUBatchSize
 * \see RxPipelineNodePS2ManagerSetLighting
 * \see RxPipelineNodePS2ManagerSetVU1CodeArray
 * \see RxPipelineNodePS2ManagerGetVU1CodeArray
 * \see RxNodeDefinitionGetPS2Manager
 * \see RxNodeDefinitionGetPS2MatInstance
 * \see RxNodeDefinitionGetPS2MatBridge
 * \see RxNodeDefinitionGetPS2ObjAllInOne
 */
RxPipelineNode *
RxPipelineNodePS2ManagerNoTexture(RxPipelineNode * self,
                                  RwBool noTexture)
{
    rwPS2ManagerPvtDataBridgeFirst data;
    rwPS2ManagerPvtData *origData;
    RxPipelineNode *result;

    RWAPIFUNCTION(RWSTRING("RxPipelineNodePS2ManagerNoTexture"));

    if (NULL == self)
    {
        RwDebugSendMessage(rwDEBUGMESSAGE,
                           "RxPipelineNodePS2ManagerNoTexture",
                           "NULL node passed");
        RWRETURN((RxPipelineNode *)NULL);
    }

    if (self->privateData == NULL)
    {
        RwDebugSendMessage(rwDEBUGMESSAGE,
                           "RxPipelineNodePS2ManagerNoTexture",
                           "Call this function only *after* RxLockedPipeUnlock");
        RWRETURN((RxPipelineNode *)NULL);
    }

    REARRANGE(self, data, origData);
    result = RxPipelineNodePS2MatBridgeNoTexture(self, noTexture);
    UNARRANGE(self, data, origData);

    RWRETURN(result);
}

/**
 * \ingroup rpworldp2sky2
 * \ref RxNodeDefinitionGetPS2Manager returns a pointer to the
 * "PS2ManagerWorldSector.csl", "PS2ManagerAtomic.csl" or
 * "PS2ManagerIm3D.csl" node definition - these being to render,
 * respectively, world sectors, atomics or the geometry submitted
 * through calls to RwIm3D render functions.
 *
 * This node does everything required to render an object, with minor
 * restrictions. It performs the function of the PS2ObjAllInOne.csl,
 * PS2MatInstance.csl and PS2MatBridge.csl nodes combined. This node
 * will not pass packets on to subsequent nodes in the pipeline nor
 * will accept them from previous nodes - it was designed to be the
 * only node in a pipeline, for maximum speed (the PVSWorldSector.csl
 * node, if placed before it, will still work since it doesn't use
 * packets). PS2Manager.csl will also ignore material pipelines, so
 * that all meshes in objects will be rendered in the same style
 * (that defined by the object pipeline). This node will be made more
 * customizable in future releases so that it will be the only node
 * (bar PVSWorldSector.csl) that you need on PS2.
 *
 * \param  objType   An \ref RxPS2ObjectType specifying the object type
 *                   to be rendered by this node (rxOBJTYPE_IM3D,
 *                   rxOBJTYPE_WORLDSECTOR or rxOBJTYPE_ATOMIC are valid)
 *
 * \return A pointer to the \ref RxNodeDefinition on success, otherwise NULL
 *
 * \see RxPipelineNodePS2ManagerSetInstanceCallBack
 * \see RxPipelineNodePS2ManagerSetPostObjectCallBack
 * \see RxPipelineNodePS2ManagerGenerateCluster
 * \see RxPipelineNodePS2ManagerSetVUBufferSizes
 * \see RxPipelineNodePS2ManagerSetPointListVUBufferSize
 * \see RxPipelineNodePS2ManagerGetVUBatchSize
 * \see RxPipelineNodePS2ManagerSetVIFOffset
 * \see RxPipelineNodePS2ManagerSetVU1CodeArray
 * \see RxPipelineNodePS2ManagerGetVU1CodeArray
 * \see RxPipelineNodePS2ManagerSetLighting
 * \see RxNodeDefinitionGetPS2MatInstance
 * \see RxNodeDefinitionGetPS2MatBridge
 * \see RxNodeDefinitionGetPS2ObjAllInOne
 */
RxNodeDefinition *
RxNodeDefinitionGetPS2Manager(RwInt32 objType)
{
    /*****************************************/
    /**                                     **/
    /**  PS2OBJOPEN.CSL NODE SPECIFICATION  **/
    /**                                     **/
    /*****************************************/

#define NUMCLUSTERSOFINTEREST 0

    static RwChar _PS2ManagerIm3D_csl[] = RWSTRING("PS2ManagerIm3D.csl");

    static RxNodeDefinition nodePS2ManagerIm3DCSL =
    {
        _PS2ManagerIm3D_csl,        /* Name */
        {                                      /* nodemethods */
            PS2ManagerIm3DNodeBody,         /* +-- nodebody */
            (RxNodeInitFn) NULL,               /* +-- nodeinit */
            (RxNodeTermFn) NULL,               /* +-- nodeterm */
            PS2ManagerPipelineNodeInit,        /* +-- pipelinenodeinit */
            (RxPipelineNodeTermFn) NULL,       /* +-- pipelineNodeTerm */
            (RxPipelineNodeConfigFn) NULL,     /* +--  pipelineNodeConfig */
            (RxConfigMsgHandlerFn) NULL        /* +--  configMsgHandler */
        },
        {                                      /* Io */
            NUMCLUSTERSOFINTEREST,             /* +-- NumClustersOfInterest */
            (RxClusterRef *)NULL,                              /* +-- ClustersOfInterest */
            (RxClusterValidityReq *)NULL,                              /* +-- InputRequirements */
            0,                                 /* +-- NumOutputs */
            (RxOutputSpec *)NULL                               /* +-- Outputs */
        },
        (RwUInt32) sizeof(PRIVATEDATATYPE),    /* PipelineNodePrivateDataSize */
        rxNODEDEFCONST,                        /* editable */
        (RwInt32) 0                            /* inPipes */
    };

    static RwChar _PS2ManagerWorldSector_csl[] = RWSTRING("PS2ManagerWorldSector.csl");

    static RxNodeDefinition nodePS2ManagerWorldSectorCSL =
    {
        _PS2ManagerWorldSector_csl,            /* Name */
        {                                      /* nodemethods */
            PS2ManagerWorldSectorNodeBody,  /* +-- nodebody */
            (RxNodeInitFn) NULL,               /* +-- nodeinit */
            (RxNodeTermFn) NULL,               /* +-- nodeterm */
            PS2ManagerPipelineNodeInit,        /* +-- pipelinenodeinit */
            (RxPipelineNodeTermFn) NULL,       /* +-- pipelineNodeTerm */
            (RxPipelineNodeConfigFn) NULL,     /* +--  pipelineNodeConfig */
            (RxConfigMsgHandlerFn) NULL        /* +--  configMsgHandler */
        },
        {                                      /* Io */
            NUMCLUSTERSOFINTEREST,             /* +-- NumClustersOfInterest */
            (RxClusterRef *)NULL,                              /* +-- ClustersOfInterest */
            (RxClusterValidityReq *)NULL,                              /* +-- InputRequirements */
            0,                                 /* +-- NumOutputs */
            (RxOutputSpec *)NULL                               /* +-- Outputs */
        },
        (RwUInt32) sizeof(PRIVATEDATATYPE),    /* PipelineNodePrivateDataSize */
        rxNODEDEFCONST,                        /* editable */
        (RwInt32) 0                            /* inPipes */
    };

    static RwChar _PS2ManagerAtomic_csl[] = RWSTRING("PS2ManagerAtomic.csl");

    static RxNodeDefinition nodePS2ManagerAtomicCSL =
    {
        _PS2ManagerAtomic_csl,                 /* Name */
        {                                      /* nodemethods */
            PS2ManagerAtomicNodeBody,       /* +-- nodebody */
            (RxNodeInitFn) NULL,               /* +-- nodeinit */
            (RxNodeTermFn) NULL,               /* +-- nodeterm */
            PS2ManagerPipelineNodeInit,        /* +-- pipelinenodeinit */
            (RxPipelineNodeTermFn) NULL,       /* +-- pipelineNodeTerm */
            (RxPipelineNodeConfigFn) NULL,     /* +--  pipelineNodeConfig */
            (RxConfigMsgHandlerFn) NULL        /* +--  configMsgHandler */
        },
        {                                      /* Io */
            NUMCLUSTERSOFINTEREST,             /* +-- NumClustersOfInterest */
            (RxClusterRef *)NULL,              /* +-- ClustersOfInterest */
            (RxClusterValidityReq *)NULL,      /* +-- InputRequirements */
            0,                                 /* +-- NumOutputs */
            (RxOutputSpec *)NULL               /* +-- Outputs */
        },
        (RwUInt32) sizeof(PRIVATEDATATYPE),    /* PipelineNodePrivateDataSize */
        rxNODEDEFCONST,                        /* editable */
        (RwInt32) 0                            /* inPipes */
    };

    /***************************************/

    RxNodeDefinition *result = (RxNodeDefinition *)NULL;

    RWAPIFUNCTION(RWSTRING("RxNodeDefinitionGetPS2Manager"));

    RWASSERT( (objType == rxOBJTYPE_WORLDSECTOR) ||
              (objType == rxOBJTYPE_ATOMIC)||
              (objType == rxOBJTYPE_IM3D) );

    switch (objType)
    {
        case rxOBJTYPE_IM3D:
            result = &nodePS2ManagerIm3DCSL;
            break;

        case rxOBJTYPE_WORLDSECTOR:
            result = &nodePS2ManagerWorldSectorCSL;
            break;

        case rxOBJTYPE_ATOMIC:
            result = &nodePS2ManagerAtomicCSL;
            break;
    }

    /*RWMESSAGE((RWSTRING("result %p"), result));*/

    RWRETURN(result);
}

