/* *INDENT-OFF* */

/*
 * nodeps2objallinone
 * A PS2-specific object-pipeline-in-a-node
 *
 * Copyright (c) Criterion Software Limited
 */
/****************************************************************************
 *                                                                          *
 * module : nodeps2objallinone.c                                            *
 *                                                                          *
 * purpose: yawn...                                                         *
 *                                                                          *
 ****************************************************************************/

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

#include <eekernel.h> /* SyncDCache(), etc. */
#include <rwcore.h>
#include "baworld.h"
#include "bapipew.h"
#include "p2stdclsw.h"
#include "skyisms.h"
#include "matputil.h"
#include "fastim3d.h"
#include "nodeps2objallinone.h"

#if (!defined(DOXYGEN))
static const char rcsid[] __RWUNUSED__ =
   "@@(#)$Id: nodeps2objallinone.c,v 1.102 2001/09/25 12:56:57 iestynb Exp $";
#endif /* (!defined(DOXYGEN)) */

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

/* This define should die */
#define TSATOMICx
#define USENULLLIGHTINGx

#define PRIVATEDATATYPE RxPipelineNodePS2ObjAllInOneData

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

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

typedef struct rpWorldSectorLightData rpWorldSectorLightData;
struct rpWorldSectorLightData
{
    RwSurfaceProperties *surface;
};

typedef struct rpAtomicLightData rpAtomicLightData;
struct rpAtomicLightData
{
    RwSurfaceProperties *surface;
    RwMatrix        invMat;
    RwReal          invScale;
};

/****************************************************************************
 Global variables
 */

/* AlexF: 12/3/01: Changed the following 2 variables to be non static. This
   lets users write their own light functions should they want to, rather
   than being restricted to the default SkyWorldApplyLight.

   Their names have been changed to reduce potential clashes with user code,
   from
       lightFillPos
       qWordsWritten
   to
       _skyLightFillPos
       _skyLightQWordsWritten
*/
RwReal * _skyLightFillPos;
RwUInt32 _skyLightQWordsWritten;

#ifndef USENULLLIGHTING
RwUInt128 lightBuffer[LIGHTBLOCKQWORDS];
#endif /* !USENULLLIGHTING */

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

   Functions

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

/****************************************************************************
 _rwInitMeshPacket()
 */

__inline RxPacket *
_rwInitMeshPacket(RxPacket *packet,
                  RxPipelineNodeInstance *self,
                  RxPS2DMASessionRecord *DMASessionRecord,
                  RxPS2Mesh *PS2Mesh,
                  const RpMesh *mesh,
                  RwResEntry **cacheEntryRef)
{

    RWFUNCTION(RWSTRING("_rwInitMeshPacket"));

    RWASSERT(packet != NULL);

    _rwInitMeshPacketMacro(packet, self, DMASessionRecord,
                           PS2Mesh, mesh, cacheEntryRef) ;

    RWRETURN(packet);
}

/****************************************************************************
 _rwPS2ObjAllInOnePipelineNodeInit

 Sets the node to not group meshes by default (so they all get sent to the
 pipeline specified in their associated material).
 */
RwBool
_rwPS2ObjAllInOnePipelineNodeInit(RxPipelineNode *self)
{
    RxPipelineNodePS2ObjAllInOneData *data ;
    RWFUNCTION(RWSTRING("_rwPS2ObjAllInOnePipelineNodeInit"));

    RWASSERT(self != NULL);

    data = (RxPipelineNodePS2ObjAllInOneData *) self->privateData;

    /* Assert pipeline node private data pointer != NULL  */
    RWASSERT(data != NULL);

    data->groupMeshes      = FALSE;
    data->lightingCallback = _rxWorldLightingDefaultCallback;
    data->genericVU1Index  = FALSE;

    RWRETURN(TRUE);
}

/****************************************************************************
 SkyWorldApplyLight

 apply light function from skywrins.c
 */

static void
SkyWorldApplyLight(const void *voidLight, const RwMatrix *inverseMat,
                      RwReal invScale, const RwSurfaceProperties *surfaceProps)
{
    const RpLight       *light = (const RpLight *)voidLight;
    RpLightType         type = RpLightGetType(light);
    const RwRGBAReal    *color = RpLightGetColor(light);
    RwFrame             *frame = RpLightGetFrame(light);
    const RwV3d         *pos, *at;

    RWFUNCTION(RWSTRING("SkyWorldApplyLight"));

#ifndef USENULLLIGHTING

    /* Only if we have a lightblock, and have sufficient space left for the
     * light and a termination word (4 qwords)
     */

    /* We quietly ignore the fact the the matrix is 4 qwords, since this
     * happens on the first light only, and we know the buffer is big
     * enough for this and a light and a termination word
     */
    if (_skyLightQWordsWritten < (LIGHTBLOCKQWORDS-4))
    {
        /* Capture the matrix and lighting coefficients on the first light */
        if (!_skyLightQWordsWritten)
        {
            if (inverseMat)
            {
                RWASSERT(RWMATRIXALIGNMENT(inverseMat));

                /* Use the inverse matrix given */
                *_skyLightFillPos++ = inverseMat->right.x;
                *_skyLightFillPos++ = inverseMat->right.y;
                *_skyLightFillPos++ = inverseMat->right.z;
                *_skyLightFillPos++ = invScale;
                *_skyLightFillPos++ = inverseMat->up.x;
                *_skyLightFillPos++ = inverseMat->up.y;
                *_skyLightFillPos++ = inverseMat->up.z;
                *_skyLightFillPos++ = invScale;
                *_skyLightFillPos++ = inverseMat->at.x;
                *_skyLightFillPos++ = inverseMat->at.y;
                *_skyLightFillPos++ = inverseMat->at.z;
                *_skyLightFillPos++ = invScale;
                *_skyLightFillPos++ = inverseMat->pos.x;
                *_skyLightFillPos++ = inverseMat->pos.y;
                *_skyLightFillPos++ = inverseMat->pos.z;
                *_skyLightFillPos++ = invScale;
            }
            else
            {
                /* Else use identity matrix */
                *_skyLightFillPos++ = (RwReal)(1.0f);
                *_skyLightFillPos++ = (RwReal)(0.0f);
                *_skyLightFillPos++ = (RwReal)(0.0f);
                *_skyLightFillPos++ = invScale;
                *_skyLightFillPos++ = (RwReal)(0.0f);
                *_skyLightFillPos++ = (RwReal)(1.0f);
                *_skyLightFillPos++ = (RwReal)(0.0f);
                *_skyLightFillPos++ = invScale;
                *_skyLightFillPos++ = (RwReal)(0.0f);
                *_skyLightFillPos++ = (RwReal)(0.0f);
                *_skyLightFillPos++ = (RwReal)(1.0f);
                *_skyLightFillPos++ = invScale;
                *_skyLightFillPos++ = (RwReal)(0.0f);
                *_skyLightFillPos++ = (RwReal)(0.0f);
                *_skyLightFillPos++ = (RwReal)(0.0f);
                *_skyLightFillPos++ = invScale;
            }
            _skyLightQWordsWritten += 4;

            /* Capture lighting coefficients too */
            ((RwReal *)&surfLightCoeffs)[0] =
                surfaceProps->ambient * (RwReal)(255.0f);
            ((RwReal *)&surfLightCoeffs)[1] =
                surfaceProps->specular * (RwReal)(255.0f);
            ((RwReal *)&surfLightCoeffs)[2] =
                surfaceProps->diffuse * (RwReal)(255.0f);
        }

        if (frame)
        {
            RwMatrix *matrix = RwFrameGetLTM(frame);

            RWASSERT(RWMATRIXALIGNMENT(matrix));

            at = RwMatrixGetAt(matrix);
            pos = RwMatrixGetPos(matrix);
        }
        else
        {
            at = pos = NULL;
        }

        /* Add the light to the light buffer (first the generic bit) */
        _skyLightFillPos[0] = color->red;
        _skyLightFillPos[1] = color->green;
        _skyLightFillPos[2] = color->blue;
        ((RwUInt32 *)(&_skyLightFillPos[3]))[0] = (RwUInt32)type;

        /* Then the light specific bit */
        switch (type)
        {
            case (rpLIGHTDIRECTIONAL):
            {
                _skyLightFillPos += 4;
                *_skyLightFillPos++ = at->x;
                *_skyLightFillPos++ = at->y;
                *_skyLightFillPos++ = at->z;
                *_skyLightFillPos++ = (RwReal)(0.0f);
                _skyLightQWordsWritten += 2;
                break;
            }
            case (rpLIGHTAMBIENT):
            {
                _skyLightFillPos += 4;
                _skyLightQWordsWritten++;
                break;
            }
            case (rpLIGHTPOINT):
            {
                _skyLightFillPos += 4;
                *_skyLightFillPos++ = pos->x;
                *_skyLightFillPos++ = pos->y;
                *_skyLightFillPos++ = pos->z;
                *_skyLightFillPos++ = light->radius;
                _skyLightQWordsWritten += 2;
                break;
            }
            case (rpLIGHTSPOT):
            case (rpLIGHTSPOTSOFT):
            {
                _skyLightFillPos += 4;
                *_skyLightFillPos++ = pos->x;
                *_skyLightFillPos++ = pos->y;
                *_skyLightFillPos++ = pos->z;
                *_skyLightFillPos++ = light->radius;
                *_skyLightFillPos++ = at->x;
                *_skyLightFillPos++ = at->y;
                *_skyLightFillPos++ = at->z;
                *_skyLightFillPos++ = light->minusCosAngle;
                _skyLightQWordsWritten += 3;
                break;
            }
            case (rpNALIGHTTYPE):
            default:
            {
                break;
            }
        }
    }

#endif /* USENULLLIGHTING */

    RWRETURNVOID();
}

/****************************************************************************
 _sectorDoApplyLight

 Applies a light to an atomic sector

 On entry   : light, lighting data (package of data required)
 On exit    : light pointer on success
 */

static RpLight*
_sectorDoApplyLight(RpLight *light, void *pData)
{
    RWFUNCTION(RWSTRING("_sectorDoApplyLight"));
    RWASSERT(light);
    RWASSERTISTYPE(light, rpLIGHT);

    if (rwObjectTestFlags(light, rpLIGHTLIGHTWORLD))
    {
        rpWorldSectorLightData *lightingData =
            (rpWorldSectorLightData*) pData;

        RWASSERT(lightingData);

        SkyWorldApplyLight(light, NULL, 1.0f, lightingData->surface);
    }

    RWRETURN(light);
}

/****************************************************************************
 _atomicDoApplyLight

 Applies a light to an atomic

 On entry   : light, lighting data (package of data required)
 On exit    : light pointer on success
 */

static RpLight *
_atomicDoApplyLight(RpLight *light, void *pData)
{
    RWFUNCTION(RWSTRING("_atomicDoApplyLight"));
    RWASSERT(light);
    RWASSERTISTYPE(light, rpLIGHT);

    if (rwObjectTestFlags(light, rpLIGHTLIGHTATOMICS))
    {
        rpAtomicLightData *lightingData = (rpAtomicLightData *) pData;

        RWASSERT(lightingData);

        SkyWorldApplyLight(light,
                          &lightingData->invMat,
                           lightingData->invScale,
                           lightingData->surface);
    }

    RWRETURN(light);
}
/****************************************************************************
 _atomicDoApplyLightFrame

 Applies a light to an atomic (but use the frame counter mechanism to
 apply only once)

 On entry   : light, lighting data (package of data required)
 On exit    : light pointer on success
 */

static RpLight*
_atomicDoApplyLightFrame(RpLight *light, void *pData)
{
    RWFUNCTION(RWSTRING("_atomicDoApplyLightFrame"));
    RWASSERT(light);
    RWASSERTISTYPE(light, rpLIGHT);

    if (light->lightFrame != RWSRCGLOBAL(lightFrame))
    {
        if (rwObjectTestFlags(light, rpLIGHTLIGHTATOMICS))
        {
            rpAtomicLightData *lightingData = (rpAtomicLightData *)pData;

            RWASSERT(lightingData);

            SkyWorldApplyLight(light, &lightingData->invMat,
                                  lightingData->invScale,
                                  lightingData->surface);
        }

        light->lightFrame = RWSRCGLOBAL(lightFrame);
    }

    RWRETURN(light);
}

/****************************************************************************
 _rwRabinsObjAllInOneCode()
 */

RwBool /* success? */
_rwRabinsObjAllInOneCode(RxPS2DMASessionRecord *DMASessionRecord,
                         RxPipelineNodePS2ObjAllInOneData *pvtData)
{
    RpMeshHeader *meshHeader;
    RwInt32       numMeshes;
    RwMatrix      xForm;
    RwMatrix     *pxForm = NULL;
    RwBool        success = TRUE;
    RwUInt32      primCode;

    RWFUNCTION(RWSTRING("_rwRabinsObjAllInOneCode"));

    RWASSERT(RWMATRIXALIGNMENT(&xForm));

    /* rabin... open local packet & write object data (matrix & lightblock) */

    /* if you need additional data to flow down the pipe, amend the definition
     * of RxPS2DMASessionRecord (sky2\wrldpipe.h) */

    /* Based on the object type, get the meshes */
    switch ( DMASessionRecord->objType )
    {
        case rxOBJTYPE_WORLDSECTOR:
            {
                RpWorldSector *worldSector = DMASessionRecord->sourceObject.worldSector;

                meshHeader = worldSector->mesh;

                /* Early out if no meshes */
                if(meshHeader->numMeshes <= 0)
                {
                    /* Damn... we have geometry with vertices but no triangles
                     * We have to test because with plugin data to compensate
                     * (e.g bezier control points), it might be valid... :o/ */
                    RWRETURN(TRUE);
                }

                /* We need to do a frustum test here */
                {
                    RwFrustumPlane *frustPlane;
                    RwUInt32       numPlanes;

                    /* Assume innocent until proven guilty */
                    DMASessionRecord->inFrustum = rwSPHEREINSIDE;

                    frustPlane =
                        CAMERAEXTFROMCAMERA(RWSRCGLOBAL(curCamera))
                        ->largeFrustumPlanes;

                    numPlanes = 6;
                    while (numPlanes--)
                    {
                        RwV3d  vCorner;
                        RwReal dot;

                        /* Check against plane */
                        vCorner.x =
                            ((RwV3d *)&worldSector->boundingBox)
                            [1-frustPlane->closestX].x;
                        vCorner.y =
                            ((RwV3d *)&worldSector->boundingBox)
                            [1-frustPlane->closestY].y;
                        vCorner.z =
                            ((RwV3d *)&worldSector->boundingBox)
                            [1-frustPlane->closestZ].z;

                        dot = RwV3dDotProduct(&vCorner,
                                              &frustPlane->plane.normal);
                        dot -= frustPlane->plane.distance;
                        /* We only need to detect boundary case, we should
                         * never get a totally outside. */
                        if (dot > 0)
                        {
                            /* Its outside the plane */
                            DMASessionRecord->inFrustum = rwSPHEREBOUNDARY;
                            break;
                        }
                        frustPlane++;
                    }
                }
                /* We just cache the camera matrix */
                pxForm = &(((RwCamera *)RWSRCGLOBAL(curCamera))->viewMatrix);
            }
            break;

        case rxOBJTYPE_ATOMIC:
            {
                RpAtomic *apAtom = DMASessionRecord->sourceObject.atomic;
                RpGeometry *geom = RpAtomicGetGeometry(apAtom);

                RWASSERT( geom != NULL );

                /* Not sure if the above is an assert or should clean exit */
                if (geom == NULL)
                    RWRETURN(FALSE);

                meshHeader = geom->mesh;

                /* Early out if no meshes */
                if(meshHeader->numMeshes <= 0)
                {
                    /* Damn... we have geometry with vertices but no triangles
                     * We have to test because with plugin data to compensate
                     * (e.g bezier control points), it might be valid... :o/ */
                    RWRETURN(TRUE);
                }

                /* We need to to a frustum test here */
                {
                    RwFrustumPlane     *frustPlane;
                    const RwSphere     *sphere;
                    RwUInt32           numPlanes;

                    /* Assume innocent until proven guilty */
                    DMASessionRecord->inFrustum = rwSPHEREINSIDE;

                    sphere = RpAtomicGetWorldBoundingSphere(apAtom);
                    RWASSERT(sphere);

                    frustPlane =
                        CAMERAEXTFROMCAMERA(RWSRCGLOBAL(curCamera))
                        ->largeFrustumPlanes;
                    numPlanes = 6;
                    while (numPlanes--)
                    {
                        RwReal dot;
                        dot = RwV3dDotProduct(&sphere->center,
                                              &frustPlane->plane.normal);
                        dot -= frustPlane->plane.distance;

                        /* We only need to detect boundary case, we should
                         * never get a totally outside.
                         */
                        if (dot > -sphere->radius)
                        {
                            DMASessionRecord->inFrustum = rwSPHEREBOUNDARY;
                            break;
                        }
                        frustPlane++;
                    }
                }
                /* We need to cache the transform */
                {
                    RwMatrix * const mpLocalToWorld =
                        RwFrameGetLTM((RwFrame *)rwObjectGetParent(apAtom));
                    RwMatrix * const viewMatrix =
                        &(((RwCamera *)RWSRCGLOBAL(curCamera))->viewMatrix);

                    pxForm = (RwMatrix *)&xForm;

                    RWASSERT(RWMATRIXALIGNMENT(pxForm));
                    RWASSERT(RWMATRIXALIGNMENT(mpLocalToWorld));
                    RWASSERT(RWMATRIXALIGNMENT(viewMatrix));

                    RwMatrixMultiply(pxForm, mpLocalToWorld, viewMatrix);
                }
            }
            break;
        default:
            RWRETURN(FALSE);
    }

    numMeshes = meshHeader->numMeshes;
    RWASSERT(numMeshes > 0);

    /* meshHeader->flags tells us if we need to dispatch as tri strip */
    /* DMASessionRecord->inFrustum tells us if it fits inside the */
    /* large frustum, ie no clipping needed */

    /* TRANS{,N}FOG and TRANS{PER,ISO} from skyTransType */
    /* We save the type in the session record */
#ifdef TSATOMIC
    if (rwRenderPipelineGetCurrent() == RpAtomicGetRenderPipeline())
    {
        /* We are rendering an atomic */
        /* Prim is tri strip */
        primCode = 4;
        DMASessionRecord->inFrustum = rwSPHEREINSIDE; /* Force */
        DMASessionRecord->transType =
            skyTransType | TRANSSTRIP | TRANSNCL | TRANSTRI;
    }
    else
    {
        /* We are rendering a world */
        /* GS manual p113/114 - triList for clipped tristrips */
        primCode = 3;
        DMASessionRecord->transType = skyTransType | TRANSLIST | TRANSCLIP | TRANSTRI;
    }
#else /* TSATOMIC */
    /* Select code, skipmesh fn and primcode based on mesh type...
     * [no in-VU fans/polylines] */
    if ((meshHeader->flags & rpMESHHEADERTRISTRIP) == rpMESHHEADERTRISTRIP)
    {
        /* GS manual p113/114 - triStrip */
        primCode = 4;
        if (DMASessionRecord->inFrustum == rwSPHEREINSIDE)
        {
            /* We don't need to cull  :-) */
            DMASessionRecord->transType = (RxSkyTransTypeFlags)
                (skyTransType | TRANSSTRIP | TRANSNCL | TRANSTRI);
        }
        else
        {
            /* We need to cull  :-( */
            DMASessionRecord->transType = (RxSkyTransTypeFlags)
                (skyTransType | TRANSSTRIP | TRANSCLIP | TRANSTRI);
            /* The true TS clipper fixes up the primCode itself on VU1
             * The culling clipper still submits strips */
        }
    }
    else if (((meshHeader->flags & rpMESHHEADERTRIFAN  ) == rpMESHHEADERTRIFAN) ||
             ((meshHeader->flags & rpMESHHEADERPRIMMASK) == 0) /* trilist */      )
    {
        /* TriFans get instanced into triLists
         * GS manual p113/114 - triList */
        primCode = 3;
        if (DMASessionRecord->inFrustum == rwSPHEREINSIDE)
        {
            DMASessionRecord->transType = (RxSkyTransTypeFlags)
                (skyTransType | TRANSLIST | TRANSNCL | TRANSTRI);
        }
        else
        {
            DMASessionRecord->transType = (RxSkyTransTypeFlags)
                (skyTransType | TRANSLIST | TRANSCLIP | TRANSTRI);
        }
    }
    else if ((meshHeader->flags & rpMESHHEADERPOINTLIST) == rpMESHHEADERPOINTLIST)
    {
        /* We can't guess what primitive type you'll submit,
         * so we choose something arbitrarily */
        if (DMASessionRecord->inFrustum == rwSPHEREINSIDE)
        {
            /* Assume tri strip */
            primCode = 4;
            /* We don't need to clip/cull  :-) */
            DMASessionRecord->transType = (RxSkyTransTypeFlags)
                (skyTransType | TRANSSTRIP | TRANSNCL | TRANSTRI);
        }
        else
        {
            /* Assume ADC-culled tri strips */
            primCode = 4;
            /* We need to clip/cull  :-( */
            DMASessionRecord->transType = (RxSkyTransTypeFlags)
                (skyTransType | TRANSSTRIP | TRANSCLIP | TRANSTRI);
        }
    }
    else /* (((meshHeader->flags & rpMESHHEADERLINELIST) == rpMESHHEADERLINELIST) ||
             ((meshHeader->flags & rpMESHHEADERPOLYLINE) == rpMESHHEADERPOLYLINE)   ) */
    {
        /* PolyLines get instanced into LineLists */

        /* GS manual p113/114 - lineList (lineStrip is 2) */
        primCode = 1;

        /* For now the line transform always culls and never fogs */
        if (DMASessionRecord->inFrustum == rwSPHEREINSIDE)
        {
            /* We don't need to clip/cull  :-) */
            DMASessionRecord->transType = (RxSkyTransTypeFlags)
                (skyTransType | TRANSLIST | TRANSNCL | TRANSLINE);
        }
        else
        {
            /* We need to clip/cull  :-( */
            DMASessionRecord->transType = (RxSkyTransTypeFlags)
                (skyTransType | TRANSLIST | TRANSCLIP | TRANSLINE);
        }
    }
#endif /* TSATOMIC */

    if (FALSE == pvtData->genericVU1Index)
    {
        /* For legacy we keep the index equal to the transType flags.
         * PS2Manager instance callbacks will have to override this. */
        DMASessionRecord->vu1CodeIndex =
            (RwUInt8)(DMASessionRecord->transType & ~TRANSCULL);
    }
    else
    {
        /* For our new default pipes, we use a smaller array of
         * multi-purpose transforms. This kludge will go away for PS2All. */
        DMASessionRecord->vu1CodeIndex =
            ((TRANSLINE & DMASessionRecord->transType) >> 2) |
            ((TRANSISO  & DMASessionRecord->transType) >> 2) |
            ((TRANSCULL & DMASessionRecord->transType) >> 5);
        /* Our order is:
         *  TRI  | PERSP | NCULL
         *  TRI  | PERSP | CULL
         *  TRI  | ISO   | NCULL
         *  TRI  | ISO   | CULL
         *  LINE | PERSP | NCULL
         *  LINE | PERSP | CULL
         *  LINE | ISO   | NCULL
         *  LINE | ISO   | CULL
         * [TRANSLINE = 16, TRANSISO = 8, TRANSCULL = 32] */
    }

#ifndef USENULLLIGHTING
    /* We now need to capture some lights */
    _skyLightFillPos = (RwReal *)lightBuffer;

    /* Make space for the matrix*/
    _skyLightFillPos += 4;
    _skyLightQWordsWritten = 0;

    /* Apply lights */
    if (DMASessionRecord->objType == rxOBJTYPE_WORLDSECTOR)
    {
        RpWorldSector *worldSector =
            DMASessionRecord->sourceObject.worldSector;
        RpWorld *world = (RpWorld *) RWSRCGLOBAL(curWorld);

        /* This one test buys us a speed boost */
        if(rwObjectTestFlags(world, rpWORLDLIGHT))
        {
            pvtData->lightingCallback(rxOBJTYPE_WORLDSECTOR,
                                      worldSector,
                                      &world->surfaceProps,
                                      SkyWorldApplyLight);
        }
    }
    else
    {
        RpAtomic *apAtom = DMASessionRecord->sourceObject.atomic;

        if (rwObjectTestFlags((apAtom->geometry), rpGEOMETRYLIGHT))
        {
            /*lightingData.surface = &apAtom->geometry->surfaceProps;*/
            /* temporary hack to get the surface properties from
             * the first mesh */
            RwSurfaceProperties *surfaceProps =
                &((RpMesh*)(((RwUInt8*)(meshHeader + 1)) +
                            meshHeader->firstMeshOffset))->material->surfaceProps;

            /* Increase the marker ! */
            RWSRCGLOBAL(lightFrame)++;

            pvtData->lightingCallback(rxOBJTYPE_ATOMIC, apAtom,
                                      surfaceProps,
                                      SkyWorldApplyLight);
        }
    }

    /* Finalise */
    if (_skyLightQWordsWritten)
    {
        long tmp, tmp1;
        u_long128 ltmp = 0;

        /* Terminate and finalise the light buffer */
        ((u_long128 *)_skyLightFillPos)[0] = nullLightBlock[1];
        _skyLightQWordsWritten++;

        _skyLightFillPos = (RwReal *)lightBuffer;

        tmp = (1l<<28) | (_skyLightQWordsWritten);
        tmp1 = (((0x6cl<<24)|((RwUInt64)_skyLightQWordsWritten<<16)
               | (VU1LIGHTOFFSET))
               << 32) |
               ((1l<<24)|(4<<8)|(4));
        MAKE128(ltmp, tmp1, tmp);
        _skyLightQWordsWritten++;

        /* Header for return (with VIF TAG to upload to output buffer) */
        ((u_long128 *)_skyLightFillPos)[0] = ltmp;

    }
    else
    {
        /* This is what we will upload */
        _skyLightFillPos = (RwReal *)nullLightBlock;
        _skyLightQWordsWritten = sizeof(nullLightBlock)/sizeof(u_long128);
    }
#else /* !USENULLLIGHTING */
    _skyLightFillPos = nullLightBlock;
    _skyLightQWordsWritten = sizeof(nullLightBlock)/sizeof(u_long128);
#endif /* !USENULLLIGHTING */

    RWASSERT(RWMATRIXALIGNMENT(pxForm));

    /* Upload matrix, GIFTag (redone in matBridge - FIX) and light data */
    success = openVU1SetupPkt(primCode, NULL,
                              (const RwMatrix *)pxForm,
                              (const u_long128 *)_skyLightFillPos, _skyLightQWordsWritten);
    RWASSERT(FALSE != success);

    RWRETURN(success);
}

/****************************************************************************
 _rwIm3DObjAllInOneCode()
 */

RwBool
_rwIm3DObjAllInOneCode(RxPS2DMASessionRecord *DMASessionRecord,
                       RxPipelineNodePS2ObjAllInOneData * __RWUNUSED__ pvtData)
{
    RwMatrix          xForm;
    RwMatrix         *pxForm = NULL;
    _rwIm3DPoolStash *stash;
    RwUInt32          primCode;
    RwBool            success;

    RWFUNCTION(RWSTRING("_rwIm3DObjAllInOneCode"));

    RWASSERT(DMASessionRecord);
    RWASSERT(RWMATRIXALIGNMENT(&xForm));

    stash = (_rwIm3DPoolStash *)(DMASessionRecord->sourceObject.agnostic);

    /* rabin... open local packet & write object data (matrix & lightblock) */

    /* if you need additional data to flow down the pipe, amend the definition
     * of RxPS2DMASessionRecord (sky2\wrldpipe.h) */

    /* We need to to a frustum test here, but it's Im3D so
     * it's too much hassle for now - assume the worst!
     * (later will let app. (or wrapper func) work it out and tell us) */
    DMASessionRecord->inFrustum = rwSPHEREBOUNDARY;

    /* We need to cache the transform */
    {
        RwMatrix * const mpLocalToWorld =
            (RwMatrix *)(stash->ltm);
        RwMatrix * const viewMatrix =
            &(((RwCamera *)RWSRCGLOBAL(curCamera))->viewMatrix);

        RWASSERT(RWMATRIXALIGNMENT(mpLocalToWorld));
        RWASSERT(RWMATRIXALIGNMENT(viewMatrix));

        if (mpLocalToWorld != NULL)
        {
            pxForm = (RwMatrix *)&xForm;
            RWASSERT(RWMATRIXALIGNMENT(pxForm));
            RwMatrixMultiply(pxForm, mpLocalToWorld, viewMatrix);
        }
        else
        {
            pxForm = viewMatrix;
        }
    }

    /* Upload per-render stuff (matrix, VU code) */
    /* TRANS{,N}FOG and TRANS{PER,ISO} from skyTransType */
    /* We save the type in the session record */

    /* No in-VU fans/polylines */
    if (stash->primType == rwPRIMTYPETRISTRIP)
    {
        /* GS manual p113/114 - triStrip */
        primCode = 4;
        DMASessionRecord->transType = (RxSkyTransTypeFlags)
            ( (stash->flags & rwIM3D_NOCLIP) ?
              ( skyTransType | TRANSSTRIP | TRANSNCL | TRANSTRI) :
              ( skyTransType | TRANSSTRIP | TRANSCLIP | TRANSTRI) );
            /* The true TS clipper fixes up the primCode itself on VU */
    }
    else if ((stash->primType == rwPRIMTYPETRILIST) ||
             (stash->primType == rwPRIMTYPETRIFAN )   )
    {
        /* TriFans will be instanced into triLists
         * GS manual p113/114 - triList */
        primCode = 3;
        if (stash->flags & rwIM3D_NOCLIP)
        {
            DMASessionRecord->transType = (RxSkyTransTypeFlags)
                ( skyTransType | TRANSLIST | TRANSNCL | TRANSTRI);
        }
        else
        {
            DMASessionRecord->transType =(RxSkyTransTypeFlags)
                ( skyTransType | TRANSLIST | TRANSCLIP | TRANSTRI);
        }
    }
    else if (rwPRIMTYPEPOINTLIST == stash->primType)
    {
        /* We can't guess what primitive type you'll submit,
         * so we choose something arbitrarily */
        if (stash->flags & rwIM3D_NOCLIP)
        {
            /* Assume tri strip */
            primCode = 4;
            /* We don't need to clip/cull  :-) */
            DMASessionRecord->transType = (RxSkyTransTypeFlags)
                (skyTransType | TRANSSTRIP | TRANSNCL | TRANSTRI);
        }
        else
        {
            /* Assume ADC-culled tri strips */
            primCode = 4;
            /* We need to clip/cull  :-( */
            DMASessionRecord->transType = (RxSkyTransTypeFlags)
                (skyTransType | TRANSSTRIP | TRANSCLIP | TRANSTRI);
        }
    }
    else /* (stash->primType == rwPRIMTYPELINELIST) ||
            (stash->primType == rwPRIMTYPEPOLYLINE) */
    {
        /* PolyLines will be instanced into linelists */

        /* GS manual p113/114 - lineList (lineStrip is 2) */
        primCode = 1;

        /* For now the line transform always culls and never fogs */
        DMASessionRecord->transType = (RxSkyTransTypeFlags)
            ( (stash->flags & rwIM3D_NOCLIP) ?
              ( skyTransType | TRANSLIST | TRANSNCL | TRANSLINE) :
              ( skyTransType | TRANSLIST | TRANSCLIP | TRANSLINE) );
    }

    /* Copied from _rwRabinsObjAllInOne */
    if (FALSE == pvtData->genericVU1Index)
    {
        /* For legacy we keep the index equal to the transType flags.
         * PS2Manager instance callbacks will have to override this. */
        DMASessionRecord->vu1CodeIndex =
            (RwUInt8)(DMASessionRecord->transType & ~TRANSCULL);
    }
    else
    {
        /* For our new default pipes, we use a smaller array of
         * multi-purpose transforms. This kludge will go away for PS2All. */
        DMASessionRecord->vu1CodeIndex =
            ((TRANSLINE & DMASessionRecord->transType) >> 2) |
            ((TRANSISO  & DMASessionRecord->transType) >> 2) |
            ((TRANSCULL & DMASessionRecord->transType) >> 5);
        /* Our order is:
         *  TRI  | PERSP | NCULL
         *  TRI  | PERSP | CULL
         *  TRI  | ISO   | NCULL
         *  TRI  | ISO   | CULL
         *  LINE | PERSP | NCULL
         *  LINE | PERSP | CULL
         *  LINE | ISO   | NCULL
         *  LINE | ISO   | CULL
         * [TRANSLINE = 16, TRANSISO = 8, TRANSCULL = 32] */
    }
    /* End copied from _rwRabinsObjAllInOne */

#ifndef USENULLLIGHTING
    /* We now need to capture some lights */
    _skyLightFillPos = (RwReal *)lightBuffer;

    /* Make space for the matrix*/
    _skyLightFillPos += 4;
    _skyLightQWordsWritten = 0;

    /* Apply lights - nothing to do in Im3D */

    /* Finalise - this is what we will upload */
    _skyLightFillPos = (RwReal *)nullLightBlock;
    _skyLightQWordsWritten = sizeof(nullLightBlock)/sizeof(u_long128);

#else /* !USENULLLIGHTING */
    _skyLightFillPos = nullLightBlock;
    _skyLightQWordsWritten = sizeof(nullLightBlock)/sizeof(u_long128);
#endif /* !USENULLLIGHTING */

    RWASSERT(RWMATRIXALIGNMENT(pxForm));

    /* This actually checks and uploads VU code. !!! FIX !!! */
    success = openVU1SetupPkt(primCode, NULL,
                              (const RwMatrix *)pxForm,
                              (const u_long128 *)_skyLightFillPos, _skyLightQWordsWritten);
    RWASSERT(FALSE != success);

    RWRETURN(success);
}

/****************************************************************************
 PS2ObjAllInOneNodeBody()

 essentially the same node is used by worldsectors and atomics; a nodebody
 unique to the object type then invokes the generic
 _rwPS2ObjAllInOneNodeBody(), passing through an objType identifier
 */

static RwBool
PS2ObjAllInOneNodeBody(RxPipelineNodeInstance *self,
                       void *data,
                       RwInt32 objType)
{
    RxPipelineNodePS2ObjAllInOneData *pvtData = ( (RxPipelineNodePS2ObjAllInOneData *)
                                          (self->privateData) );
    RxPS2DMASessionRecord DMASessionRecord;
    RxPS2Mesh             PS2Mesh;
    RxPipeline           *defaultPipeline;
    RwBool                result = FALSE;
    RpInterpolator       *interpolator = NULL;
    RpGeometry           *geom = NULL;
    RwBool                success;
    RwInt32               numVerts;

    RWFUNCTION(RWSTRING("PS2ObjAllInOneNodeBody"));

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

    RWASSERT( data != NULL );

    DMASessionRecord.objType               = (RxPS2ObjectType)objType;
    DMASessionRecord.sourceObject.agnostic = data;

    /* Check ASAP for an empty sector/atomic (atomic might have no
     * verts but plugin data for Bezier control points, for example) */
    if (objType == rxOBJTYPE_WORLDSECTOR)
    {
        numVerts = DMASessionRecord.sourceObject.worldSector->numVertices;
#ifdef RWMETRICS
        RWSRCGLOBAL(metrics)->numVertices += numVerts;
        RWSRCGLOBAL(metrics)->numTriangles += DMASessionRecord.sourceObject.worldSector->numPolygons;
#endif /* RWMETRICS */
    }
    else /* rxOBJTYPE_ATOMIC */
    {
        geom = RpAtomicGetGeometry(DMASessionRecord.sourceObject.atomic);
        RWASSERT( geom != NULL );

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

    if (numVerts <= 0)
    {
        /* Don't execute the rest of the pipeline */
        RWRETURN(TRUE);
    }

    defaultPipeline = RpMaterialGetDefaultPipeline();

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

    {
        RpMeshHeader *meshHeader = NULL;
        RwMeshCache *meshCache = NULL;
        RwResEntry **cacheEntryRef;
        const RpMesh *mesh;
        RxPacket   *pk;
        RwUInt32 n;

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

        switch ( objType )
        {
            case rxOBJTYPE_WORLDSECTOR:
                {
                    meshHeader = ((RpWorldSector *) data)->mesh;

                    meshCache =
                        rpWorldSectorGetMeshCache((RpWorldSector *) data,
                                                  meshHeader->numMeshes);
                }
                break;

            case rxOBJTYPE_ATOMIC:
                {
                    interpolator = RpAtomicGetInterpolator((RpAtomic *)data);

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

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

        /* TODO: second stage of optimisation - group meshes by pipeline, do
         * that in line with the "materials are frozen at instance time, lock
         * and unlock your geom to update 'em" paradigm, so you can instance
         * relevant info here per Atomic/WS.
         * Third stage, inline matInstance functionality in this node if that
         * turns out to be a significant win.
         */
        if (pvtData->groupMeshes != FALSE)
        {
            for (n = 0; n < meshHeader->numMeshes; n++)
            {
                pk = RxPacketCreate(self);

                /* Assert creation of packet */

                RWASSERT(pk != NULL);

                goto initmeshpacket;
            donegroupedinitmeshpacket:

                RxPacketDispatch(pk, 0, self);

                mesh++;
            }
        }
        else
        {
            for (n = 0; n < meshHeader->numMeshes; n++)
            {
                RxPipeline *destPipeline = defaultPipeline;

                if (mesh->material->pipeline != NULL)
                {
                    destPipeline = mesh->material->pipeline;
                }

                pk = RxPacketCreate(self);

                /* Assert creation of packet */
                RWASSERT(pk != NULL);

                goto initmeshpacket;
            doneUNgroupedinitmeshpacket:

                RxPacketDispatchToPipeline(pk, destPipeline, self);

                mesh++;
            }
        }

        if (0)
        {
        initmeshpacket:

            /* Put here so it can be inlined but not duplicated */

            cacheEntryRef =  rwMeshCacheGetEntryRef(meshCache, n);
            _rwInitMeshPacketMacro(pk, self, &DMASessionRecord,
                                   &PS2Mesh, mesh, cacheEntryRef);

            /* Assert initialisation of packet */
            RWASSERT(pk != NULL);

            if (pvtData->groupMeshes != FALSE)
            {
                goto donegroupedinitmeshpacket;
            }
            else
            {
                goto doneUNgroupedinitmeshpacket;
            }
        }
    }

    if ( objType == rxOBJTYPE_ATOMIC )
    {
        /* Some flags are cleared once per atomic not per mesh,
         * given that meshes are instanced/reinstanced separately */
        geom->lockedSinceLastInst = 0;
        interpolator->flags &= ~rpINTERPOLATORDIRTYINSTANCE;
    }

    result = TRUE;

    RWRETURN(result);
}

/****************************************************************************
 PS2ObjAllInOneWorldSectorNodeBody()
 */

static RwBool
PS2ObjAllInOneWorldSectorNodeBody(RxPipelineNodeInstance *self,
                                  const RxPipelineNodeParam * params)
{
    RwBool result;
    void  *data;

    RWFUNCTION(RWSTRING("PS2ObjAllInOneWorldSectorNodeBody"));

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

    result = PS2ObjAllInOneNodeBody(self, data, rxOBJTYPE_WORLDSECTOR);

    RWRETURN(result);
}

/****************************************************************************
 PS2ObjAllInOneAtomicNodeBody()
 */

static RwBool
PS2ObjAllInOneAtomicNodeBody(RxPipelineNodeInstance *self,
                             const RxPipelineNodeParam * params)
{
    RwBool result;
    void  *data;

    RWFUNCTION(RWSTRING("PS2ObjAllInOneAtomicNodeBody"));

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

    result = PS2ObjAllInOneNodeBody(self, data, rxOBJTYPE_ATOMIC);

    RWRETURN(result);
}

/****************************************************************************
 _rxWorldLightingDefaultCallback()
 */

void
_rxWorldLightingDefaultCallback(RwInt32 objectType, void *object,
                                RwSurfaceProperties *surface,
                                RxWorldApplyLightFunc __RWUNUSED__ lightingFunc)
{
    RpWorld  *world;

    RWFUNCTION(RWSTRING("_rxWorldLightingDefaultCallback"));

    world = (RpWorld *) RWSRCGLOBAL(curWorld);

    /* Apply lights */
    if (objectType == rxOBJTYPE_WORLDSECTOR)
    {
        RpWorldSector *worldSector = (RpWorldSector *)object;
        rpWorldSectorLightData lightingData;

        /* Ideally I'd like to use the code in baworld.c but the function
         * _rpWorldSectorLight but it isn't exposed */

        /* By definition, if we are rendering atomic
         * sectors we must  have a world!!! */
        lightingData.surface = surface;

        rpWorldForAllGlobalLights(_sectorDoApplyLight,
                                  &lightingData);
        rpWorldSectorForAllLocalLights(worldSector,
                                       _sectorDoApplyLight,
                                       &lightingData);
    }
    else
    {
        RpAtomic *apAtom = (RpAtomic *)object;
        RwLLLink *cur, *end;

        if (NULL != world)
        {
            rpAtomicLightData lightingData;
            RwMatrix *frameMat =
                RwFrameGetLTM((RwFrame *)rwObjectGetParent(apAtom));

            RWASSERT(RWMATRIXALIGNMENT(&lightingData.invMat));
            RWASSERT(RWMATRIXALIGNMENT(frameMat));

            /* Setup the lighting data block */
            RwMatrixInvert(&lightingData.invMat, frameMat);

            if ((rwMatrixGetFlags(frameMat) & rwMATRIXTYPEMASK)
                 == rwMATRIXTYPEORTHONORMAL)
            {
                lightingData.invScale = (RwReal) 1.0;
            }
            else
            {
                lightingData.invScale =
                    RwV3dDotProduct(&lightingData.invMat.at,
                                    &lightingData.invMat.at);

                rwSqrtMacro(lightingData.invScale,
                            lightingData.invScale);
            }

            lightingData.surface = surface;

            /* Directional light it */
            rpWorldForAllGlobalLights(_atomicDoApplyLight, &lightingData);

            /* For all sectors that this atomic lies in, apply all */
            /* lights within */
            cur = rwLinkListGetFirstLLLink(&apAtom->llWorldSectorsInAtomic);
            end = rwLinkListGetTerminator(&apAtom->llWorldSectorsInAtomic);
            while (cur != end)
            {
                RpTie *tpTie = rwLLLinkGetData(cur, RpTie,
                                               lWorldSectorInAtomic);

                /* Now apply all the lights (but this time we do the */
                /* frame thing) */
                rpWorldSectorForAllLocalLights(tpTie->worldSector,
                                               _atomicDoApplyLightFrame,
                                               &lightingData);

                /* Next one */
                cur = rwLLLinkGetNext(cur);
            }
        }
    }

    RWRETURNVOID();
}

/**
 * \ingroup rpworldp2sky2
 * \ref RxPipelineNodePS2ObjAllInOneSetGrouping allows you to specify
 * whether this instance of the PS2ObjAllInOne.csl node will send meshes
 * in packets to pipelines specified by their associated materials, or
 * whether it will send them to the (hopefully connected!) first output
 * of this node.
 *
 * For objects in which all meshes use the same pipeline, it can be more
 * efficient to have those meshes processed by just one pipeline, since
 * passing packets between pipelines can incur a significant cost. If you
 * tell this node to group its meshes, it will dispatch them all to the
 * first output of this node, where the application should arrange that
 * the nodes of a suitable material pipeline are connected.
 *
 * Note that one application of this process might be as a form of
 * LOD - when objects are close up, you may apply localized special effects
 * by giving different meshes within the object different pipelines, but
 * when the object gets far away it becomes less worthy of processor time,
 * so you switch to uniform rendering of the object where all meshes pass
 * down this "atomic rendering pipeline" (which differs from the usual
 * atomic pipeline by containing 'inlined' material rendering nodes).
 *
 * This function can be called when the pipeline node's containing pipeline
 * is unlocked.
 *
 * \param  node   The pipeline node to set up
 * \param  groupMeshes   A boolean specifying whether the pipeline
 * node should group meshes or not (default is not - FALSE)
 *
 * \return pointer to the pipeline node on success, NULL otherwise
 *
 * \see RxNodeDefinitionGetPS2ObjAllInOne
 */
RxPipelineNode *
RxPipelineNodePS2ObjAllInOneSetGrouping(RxPipelineNode *node,
                                        RwBool groupMeshes)
{
    RxPipelineNodePS2ObjAllInOneData *data;
    RWAPIFUNCTION(RWSTRING("RxPipelineNodePS2ObjAllInOneSetGrouping"));

    RWASSERT(node != NULL);

    /* Assert pipeline node private data pointer is non-NULL
       -- i.e. the containing pipeline is unlocked */

    data = (RxPipelineNodePS2ObjAllInOneData *)node->privateData;
    RWASSERT(data != NULL);

    data->groupMeshes = groupMeshes;

    RWRETURN(node);
}

/**
 * \ingroup rpworldp2sky2
 * \ref RxPipelineNodePS2ObjAllInOneSetLighting allows you to
 * overload PS2ObjAllInOne.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  node   The pipeline node to set up
 * \param  newLightingFunc   The \ref RxWorldLightingCallBack to use
 *
 * \see RxNodeDefinitionGetPS2ObjAllInOne
 */
void
RxPipelineNodePS2ObjAllInOneSetLighting(RxPipelineNode *node,
                                        RxWorldLightingCallBack newLightingFunc)
{
    RxPipelineNodePS2ObjAllInOneData *data;
    RWAPIFUNCTION(RWSTRING("RxPipelineNodePS2ObjAllInOneSetLighting"));

    RWASSERT(node != NULL);

    /* Assert pipeline node private data pointer is non-NULL
       -- i.e. the containing pipeline is unlocked */

    data = (RxPipelineNodePS2ObjAllInOneData *)node->privateData;
    RWASSERT(data != NULL);

    data->lightingCallback = newLightingFunc;

    RWRETURNVOID();
}

/**
 * \ingroup rpworldp2sky2
 * \ref RxNodeDefinitionGetPS2ObjAllInOne returns a pointer to the
 * "PS2ObjWorldSectorAllInOne.csl" or "PS2ObjAtomicAllInOne.csl" node
 * definition - these being to generate a packet for each mesh in,
 * respectively, world sectors or atomics.
 *
 * \ref RxNodeDefinitionGetPS2ObjAllInOne has one parameter which you
 * use to choose whether you want a node to handle world sectors or
 * atomics.
 *
 * On the PS2, instancing of data is performed in the material pipeline
 * on a per-mesh basis. This is because different material pipelines
 * can require data to be instanced into a different format, depending
 * on what the VU1 code (associated with the pipeline) requires. This
 * means that PS2*ObjAllInOne.csl is not the equivalent of
 * AtomicInstance.csl or WorldSectorInstance.csl which form the default
 * generic atomic and world sector object pipelines. Instead, it does
 * some per-object setup and then generates a packet per mesh and
 * dispatches them to their associated material pipelines.
 *
 * PS2*ObjAllInOne.csl determines if the object's bounding volume
 * crosses the frustum (this is the large PS2 overdraw frustum) and
 * selects a clipping transform if so or a non-clipping transform if
 * not. It also checks to see if the object is made of trilists or
 * tristrips and selects a trilist transform or a tristrip transform
 * respectively. The selected transform is specified in a
 * PS2DMASessionRecord structure and an inFrustum state is stored there
 * also.
 *
 * The node then calculates the object-to-camera matrix and finds all
 * the lights which affect the object. These pieces of information
 * are added to a VU1 packet and uploaded (just once since they are
 * per-object not per-mesh).
 *
 * Next, a packet is generated for every mesh, containing a reference
 * to the \ref RxPS2DMASessionRecord structure in a RxClPS2DMASessionRecord
 * cluster. An RxClPS2Mesh cluster containing a \ref RxPS2Mesh structure is
 * also created which contains a reference to the \ref RpMesh and also
 * the \ref RwResEntry used for caching the instanced data for each
 * mesh - plugin data attached to the world sector or atomic contains
 * a list of references to these \ref RwResEntry.
 *
 * The node will output each packet to the pipeline referenced in
 * its mesh's associated material. There is an alternative, potentially
 * more efficient mode of execution (where all meshes get passed down
 * this pipeline to 'inlined' material rendering nodes), which is described
 * in detail in the docs for \ref RxPipelineNodePS2ObjAllInOneSetGrouping.
 *
 * \param  objType   An \ref RxPS2ObjectType specifying the object type
 *                   the returned node shoudl handle (rxOBJTYPE_IM3D,
 *                   rxOBJTYPE_WORLDSECTOR or rxOBJTYPE_ATOMIC are valid)
 *
 * The node has one output (used in the alternative execution mode),
 * but by default all packets are sent to material pipelines
 * The input requirements of this node:
 *      \li RxClPS2DMASessionRecord        - don't want
 *      \li RxClPS2Mesh                    - don't want
 *
 * The characteristics of this node's first output:
 *      \li RxClPS2DMASessionRecord        - valid
 *      \li RxClPS2Mesh                    - valid
 *
 * \return pointer to a node to generate a packet for each mesh in
 * world sectors or atomics.
 *
 * \see RxPipelineNodePS2ObjAllInOneSetGrouping
 * \see RxNodeDefinitionGetPS2MatBridge
 * \see RxNodeDefinitionGetPS2MatInstance
 */

/****************************************************************************
 RxNodeDefinitionGetPS2ObjAllInOne()
 */

RxNodeDefinition *
RxNodeDefinitionGetPS2ObjAllInOne(RwInt32 objType)
{
    /*****************************************/
    /**                                     **/
    /**  PS2OBJOPEN.CSL NODE SPECIFICATION  **/
    /**                                     **/
    /*****************************************/

    static RxClusterRef nodeClusters[] =
    {   { &RxClPS2DMASessionRecord, rxCLFORCEPRESENT, rxCLRESERVED },
        { &RxClPS2Mesh,             rxCLFORCEPRESENT, rxCLRESERVED }   };

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

    /* input requirements (this array parallel to ClusterRefs) */
    static RxClusterValidityReq nodeReqs[NUMCLUSTERSOFINTEREST] =
    {   rxCLREQ_DONTWANT,
        rxCLREQ_DONTWANT   };

    /* output spec (this array parallel to ClusterRefs) */
    static RxClusterValid N1outcl1[NUMCLUSTERSOFINTEREST] =
    {   rxCLVALID_VALID,
        rxCLVALID_VALID    };

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

    RxNodeDefinition *result = NULL;

    RWAPIFUNCTION(RWSTRING("RxNodeDefinitionGetPS2ObjAllInOne"));

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

    switch (objType)
    {
        case rxOBJTYPE_WORLDSECTOR:
        {
            static RwChar _PS2ObjWorldSectorAllInOneOut[] =
                RWSTRING("PS2ObjWorldSectorAllInOneOut");

            static RxOutputSpec N1OutputsWorldSector[] = { /* */
                {_PS2ObjWorldSectorAllInOneOut,
                 N1outcl1,
                 rxCLVALID_NOCHANGE} };

            static RwChar _NodeName[] = RWSTRING("PS2ObjWorldSectorAllInOne.csl");

            static RxNodeDefinition WorldNodeDefinition =
            {
                _NodeName,        /* Name */
                {                                      /* nodemethods */
                    PS2ObjAllInOneWorldSectorNodeBody,
                    /* +-- nodebody */
                    (RxNodeInitFn) NULL,               /* +-- nodeinit */
                    (RxNodeTermFn) NULL,               /* +-- nodeterm */
                    _rwPS2ObjAllInOnePipelineNodeInit, /* +-- pipelinenodeinit */
                    (RxPipelineNodeTermFn) NULL,       /* +-- pipelineNodeTerm */
                    (RxPipelineNodeConfigFn) NULL,     /* +--  pipelineNodeConfig */
                    (RxConfigMsgHandlerFn) NULL        /* +--  configMsgHandler */
                },
                {                                      /* Io */
                    NUMCLUSTERSOFINTEREST,             /* +-- NumClustersOfInterest */
                    nodeClusters,                      /* +-- ClustersOfInterest */
                    nodeReqs,                          /* +-- InputRequirements */
                    1,                                 /* +-- NumOutputs */
                    N1OutputsWorldSector               /* +-- Outputs */
                },
                (RwUInt32) sizeof(PRIVATEDATATYPE),    /* PipelineNodePrivateDataSize */
                (RxNodeDefEditable)FALSE,              /* editable */
                (RwInt32) 0                            /* inPipes */
            };

            result = &WorldNodeDefinition;
        }

        break;

        case rxOBJTYPE_ATOMIC:
        {
            static RwChar _PS2ObjAtomicAllInOneOut[] =
                RWSTRING("PS2ObjAtomicAllInOneOut");

            static RxOutputSpec N1OutputsAtomic[] = { /* */
                {_PS2ObjAtomicAllInOneOut,
                 N1outcl1,
                 rxCLVALID_NOCHANGE} };

            static RwChar _PS2ObjAtomicAllInOne_csl[] =
                RWSTRING("PS2ObjAtomicAllInOne.csl");

            static RxNodeDefinition AtomicNodeDefiniton =
            {
                _PS2ObjAtomicAllInOne_csl,             /* Name */
                {                                      /* nodemethods */
                    PS2ObjAllInOneAtomicNodeBody,   /* +-- nodebody */
                    (RxNodeInitFn) NULL,               /* +-- nodeinit */
                    (RxNodeTermFn) NULL,               /* +-- nodeterm */
                    _rwPS2ObjAllInOnePipelineNodeInit, /* +-- pipelinenodeinit */
                    (RxPipelineNodeTermFn) NULL,       /* +-- pipelineNodeTerm */
                    (RxPipelineNodeConfigFn) NULL,     /* +--  pipelineNodeConfig */
                    (RxConfigMsgHandlerFn) NULL        /* +--  configMsgHandler */
                },
                {                                      /* Io */
                    NUMCLUSTERSOFINTEREST,             /* +-- NumClustersOfInterest */
                    nodeClusters,                      /* +-- ClustersOfInterest */
                    nodeReqs,                          /* +-- InputRequirements */
                    1,                                 /* +-- NumOutputs */
                    N1OutputsAtomic                    /* +-- Outputs */
                },
                (RwUInt32) sizeof(PRIVATEDATATYPE),    /* PipelineNodePrivateDataSize */
                (RxNodeDefEditable) FALSE,             /* editable */
                (RwInt32) 0                            /* inPipes */
            };

            result = &AtomicNodeDefiniton;
        }
        break;
    }

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

    RWRETURN(result);
}

