R E T I R E D






/*
 * @doc nodePS2ObjOpen
 * @topic A PS2-specific node to begin an object pipeline |
 * @index | nodePS2ObjOpen
 * @normal Copyright (c) Criterion Software Limited
 */
/****************************************************************************
 *                                                                          *
 * module : nodePS2ObjOpen.c                                                *
 *                                                                          *
 * purpose: yawn...                                                         *
 *                                                                          *
 ****************************************************************************/

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

#include <math.h>
#include <eekernel.h> /* SyncDCache(), etc. */
#include "rwcore.h"
#include "baworld.h"
#include "p2stdclsw.h"
#include "skyisms.h"
#include "nodePS2ObjOpen.h"

static const char rcsid[] __RWUNUSED__ = "@@(#)$Id: nodePS2ObjOpen.c,v 1.40 2000/10/02 18:43:12 iestynb Exp $";


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

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

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

/****************************************************************************
 local (static) prototypes
 */

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

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

/****************************************************************************
 local (static) globals
 */

static RwReal *lightFillPos;
static RwUInt32 qWordsWritten;
#ifndef USENULLLIGHTING
#define LIGHTBLOCKQWORDS 32
static u_long128 lightBuffer[LIGHTBLOCKQWORDS];
#endif /* !USENULLLIGHTING */

/* *INDENT-OFF* */

/****************************************************************************
 functions
 */

/****************************************************************************
 apply light function from skywrins.c
 */

static void 
_rwSkyWorldApplyLight(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("_rwSkyWorldApplyLight"));

#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 (qWordsWritten < (LIGHTBLOCKQWORDS-4))
    {
        /* Capture the matrix and lighting coefficients on the first light */
        if (!qWordsWritten)
        {
            if (inverseMat)
            {
                /* Use the inverse matrix given */
                *lightFillPos++ = inverseMat->right.x;
                *lightFillPos++ = inverseMat->right.y;
                *lightFillPos++ = inverseMat->right.z;
                *lightFillPos++ = invScale;
                *lightFillPos++ = inverseMat->up.x;
                *lightFillPos++ = inverseMat->up.y;
                *lightFillPos++ = inverseMat->up.z;
                *lightFillPos++ = invScale;
                *lightFillPos++ = inverseMat->at.x;
                *lightFillPos++ = inverseMat->at.y;
                *lightFillPos++ = inverseMat->at.z;
                *lightFillPos++ = invScale;
                *lightFillPos++ = inverseMat->pos.x;
                *lightFillPos++ = inverseMat->pos.y;
                *lightFillPos++ = inverseMat->pos.z;
                *lightFillPos++ = invScale;
            }
            else
            {
                /* Else use identity matrix */
                *lightFillPos++ = (RwReal)(1.0f);
                *lightFillPos++ = (RwReal)(0.0f);
                *lightFillPos++ = (RwReal)(0.0f);
                *lightFillPos++ = invScale;
                *lightFillPos++ = (RwReal)(0.0f);
                *lightFillPos++ = (RwReal)(1.0f);
                *lightFillPos++ = (RwReal)(0.0f);
                *lightFillPos++ = invScale;
                *lightFillPos++ = (RwReal)(0.0f);
                *lightFillPos++ = (RwReal)(0.0f);
                *lightFillPos++ = (RwReal)(1.0f);
                *lightFillPos++ = invScale;
                *lightFillPos++ = (RwReal)(0.0f);
                *lightFillPos++ = (RwReal)(0.0f);
                *lightFillPos++ = (RwReal)(0.0f);
                *lightFillPos++ = invScale;
            }
            qWordsWritten += 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);

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

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

        /* Then the light specific bit */
        switch (type)
        {
            case (rpLIGHTDIRECTIONAL):
            {
                lightFillPos += 4;
                *lightFillPos++ = at->x;
                *lightFillPos++ = at->y;
                *lightFillPos++ = at->z;
                *lightFillPos++ = (RwReal)(0.0f);
                qWordsWritten += 2;
                break;
            }
            case (rpLIGHTAMBIENT):
            {
                lightFillPos += 4;
                qWordsWritten++;
                break;
            }
            case (rpLIGHTPOINT):
            {
                lightFillPos += 4;
                *lightFillPos++ = pos->x;
                *lightFillPos++ = pos->y;
                *lightFillPos++ = pos->z;
                *lightFillPos++ = light->radius;
                qWordsWritten += 2;
                break;
            }
            case (rpLIGHTSPOT):
            case (rpLIGHTSPOTSOFT):
            {
                lightFillPos += 4;
                *lightFillPos++ = pos->x;
                *lightFillPos++ = pos->y;
                *lightFillPos++ = pos->z;
                *lightFillPos++ = light->radius;
                *lightFillPos++ = at->x;
                *lightFillPos++ = at->y;
                *lightFillPos++ = at->z;
                *lightFillPos++ = light->open;
                qWordsWritten += 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);

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

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

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

        light->lightFrame = RWSRCGLOBAL(lightFrame);
    }

    RWRETURN(light);
}

/****************************************************************************
 _rwRabinsObjOpenCode()
 */

static RwBool /* success? */
_rwRabinsObjOpenCode(RxPS2DMASessionRecord *DMASessionRecord)
{
    RwBool result = FALSE;
    RpMeshHeader *meshHeader;
    RwInt32      numMeshes;
    RwMatrix     xForm, *pxForm;

    RWFUNCTION(RWSTRING("_rwRabinsObjOpenCode"));

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

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

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

                meshHeader = worldSector->mesh;

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

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

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

                    sphere = rpAtomicCalcWorldBoundingSphere(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 *mpLocalToWorld;

                    pxForm = (RwMatrix *)&xForm;
                    mpLocalToWorld
                          = RwFrameGetLTM((RwFrame *)rwObjectGetParent(apAtom));
                    RwMatrixMultiply(pxForm, mpLocalToWorld,
                           &(((RwCamera *)RWSRCGLOBAL(curCamera))->viewMatrix));
                }
            }
            break;
        default:
            RWRETURN(FALSE);
    }
    numMeshes = meshHeader->numMeshes;

    if (numMeshes)
    {
        RwUInt32  primCode;

        /* 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 */
            /* Prim is tri fan */
            primCode = 5;
            DMASessionRecord->transType = skyTransType | TRANSLIST | TRANSCLIP | TRANSTRI;
        }
#else /* TSATOMIC */
        /* Select code, skipmesh fn and primcode based on mesh type... */
        if (meshHeader->flags & rpMESHHEADERTRISTRIP)
        {
            /* Prim is tri strip */
            primCode = 4;
            if (DMASessionRecord->inFrustum == rwSPHEREINSIDE)
            {
                /* We don't need to cull  :-) */
                DMASessionRecord->transType = skyTransType | TRANSSTRIP | TRANSNCL | TRANSTRI;
            }
            else
            {
                /* We need to cull  :-( */
                DMASessionRecord->transType = skyTransType | TRANSSTRIP | TRANSCLIP | TRANSTRI;
            }
        }
        else
        {
            if (DMASessionRecord->inFrustum == rwSPHEREINSIDE)
            {
                /* Actually tri list */
                primCode = 3;
                /* We don't need to clip/cull  :-) */
                DMASessionRecord->transType = skyTransType | TRANSLIST | TRANSNCL | TRANSTRI;
            }
            else
            {
                /* Prim is tri fan */
                primCode = 5;
                /* We need to clip/cull  :-( */
                DMASessionRecord->transType = skyTransType | TRANSLIST | TRANSCLIP | TRANSTRI;
            }
        }
#endif /* TSATOMIC */

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

        /* Make space for the matrix*/
        lightFillPos += 4;
        qWordsWritten = 0;

        /* Apply lights */
        if (DMASessionRecord->objType == rxOBJTYPE_WORLDSECTOR)
        {
            RpWorldSector *worldSector = DMASessionRecord
                                                     ->sourceObject.worldSector;
            /* Ideally I'd like to use the code in baworld.c but the function */
            /* _rpWorldSectorLight but it isn't exposed */
            if (rwObjectTestFlags(worldSector, rpWORLDLIGHT))
            {
                /* By definition, if we are rendering atomic sectors we must */
                /* have a world!!! */
                RpWorld *world = (RpWorld *) RWSRCGLOBAL(curWorld);
                rpWorldSectorLightData lightingData;

                lightingData.surface = &world->surfaceProps;

                rpWorldForAllGlobalLights(_sectorDoApplyLight, &lightingData);
                rpWorldSectorForAllLocalLights(worldSector,
                                            _sectorDoApplyLight, &lightingData);
            }

        }
        else
        {
            RpAtomic *apAtom = DMASessionRecord->sourceObject.atomic;
            RpWorld  *world = (RpWorld *) RWSRCGLOBAL(curWorld);
            RwLLLink *cur, *end;

            if (world && rwObjectTestFlags((apAtom->geometry), rpGEOMETRYLIGHT))
            {
                rpAtomicLightData lightingData;

                /* Setup the lighting data block */
                RwMatrixInvert(&lightingData.invMat,
                               RwFrameGetLTM(
                                         (RwFrame *)rwObjectGetParent(apAtom)));
                lightingData.invScale = RwV3dDotProduct(&lightingData.invMat.at,
                                                       &lightingData.invMat.at);
                lightingData.invScale = _rwRSqrt(&lightingData.invScale);
                /*lightingData.surface = &apAtom->geometry->surfaceProps;*/
                /*hack to compile*/
                lightingData.surface = &((RpMesh*)(((RwUInt8*)(meshHeader + 1)) + meshHeader->firstMeshOffset))->material->surfaceProps;

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

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

        /* Finalise */
        if (qWordsWritten)
        {
            long tmp, tmp1;
            u_long128 ltmp;

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

            lightFillPos = (RwReal *)lightBuffer;

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

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

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

        /* Always gouraud opaque for retained mode */
        skyPrim_State |= 0x8l;

        /* This acutally checks and uploads VU code. !!! FIX !!! */
        if (!openVU1SetupPkt(primCode, NULL,
                             (const RwMatrix *)pxForm,
                             (const u_long128 *)lightFillPos, qWordsWritten))
        {
            result = FALSE;
        }
    }

    result = TRUE; /* success */

    RWRETURN(result);
}

/****************************************************************************
 _rwPS2ObjOpenNodeBody()

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

static RwBool
_rwPS2ObjOpenNodeBody(RxPipelineNodeInstance *self,
                      void *data, RwInt32 objType)
{
    RwBool result = FALSE;
    RxPacket *pk;

    RWFUNCTION(RWSTRING("_rwPS2ObjOpenNodeBody"));

    RWASSERT( data != NULL );

    if ( (pk = RxPacketCreate(self)) != NULL )
    { /* packet created okay? */
        RxCluster *clDMASessionRecord;

        if ( (clDMASessionRecord = RxClusterLockWrite(pk, 0 /* RxClPS2DMASessionRecord */, self)) != NULL )
        { /* cluster locked for writing okay? */
            if ( RxClusterInitialiseData(clDMASessionRecord, 1, sizeof(RxPS2DMASessionRecord)) )
            { /* cluster data allocated okay? */
                RxPS2DMASessionRecord *DMASessionRecord;

                DMASessionRecord = RxClusterGetCursorData(clDMASessionRecord, RxPS2DMASessionRecord);

                RWASSERT( DMASessionRecord != NULL );

                DMASessionRecord->objType               = objType;
                DMASessionRecord->sourceObject.agnostic = data;

                result = _rwRabinsObjOpenCode(DMASessionRecord); /* success? */
            }

            RxClusterUnlock(clDMASessionRecord);

            if ( result )
            { /* all things being equal, we've got a packet, so... dispatch it */
                RxPacketDispatch(pk, 0 /* DefaultOutput */, self);
            }
        }
    }

    /* note that it isn't necessary to free clusters or packets in the
       event of failure; RwPipelineExecute() will clean up */

    /* note (00-02-23): above isn't strictly true; if you fetch, or create, a packet and
       don't dispatch, or destroy, it, it is in limbo (live, but neither in an input or an
       output queue) and will remain there beyond even the lifetime of the pipeline */

    RWRETURN(result);
}

/****************************************************************************
 _rwPS2ObjOpenWorldSectorNodeBody()
 */

static RwBool
_rwPS2ObjOpenWorldSectorNodeBody(RxPipelineNodeInstance *self,
                                 RxMemoryArena *arena __RWUNUSED__,
                                 void *data)
{
    RwBool result;

    RWFUNCTION(RWSTRING("_rwPS2ObjOpenWorldSectorNodeBody"));

    result = _rwPS2ObjOpenNodeBody(self, data, rxOBJTYPE_WORLDSECTOR);

    RWRETURN(result);
}

/****************************************************************************
 _rwPS2ObjOpenAtomicNodeBody()
 */

static RwBool
_rwPS2ObjOpenAtomicNodeBody(RxPipelineNodeInstance *self,
                            RxMemoryArena *arena __RWUNUSED__,
                            void *data)
{
    RwBool result;

    RWFUNCTION(RWSTRING("_rwPS2ObjOpenAtomicNodeBody"));

    result = _rwPS2ObjOpenNodeBody(self, data, rxOBJTYPE_ATOMIC);

    RWRETURN(result);
}

/**********************************************************************
 * @func <f RxNodeDefinitionGetPS2ObjOpen> returns a pointer to the
 * "PS2ObjWorldSectorOpen.csl" or "PS2ObjAtomicOpen.csl" node definition.
 *
 * A basic PS2 pipeline organisation is as follows:
 *
 * WorldSectorInstancingPipeline - "PS2ObjWorldSectorOpen.csl", "PS2ObjEnumMeshes.csl", "PS2ObjClose.csl"
 *
 * AtomicInstancingPipeline - "PS2ObjAtomicOpen.csl", "PS2ObjEnumMeshes.csl", "PS2ObjClose.csl"
 *
 * MaterialPipeline - "PS2MatInstance.csl", "PS2MatBridge.csl"
 *
 * Heading up their respective instancing pipelines, the PS2ObjXXXOpen nodes receive a reference to the object via
 * their data pointer and create a packet for the object containing an RxPS2DMASessionRecord. The nodes open
 * a DMA session, and add data to the DMA chain for the object-scoped state (matrix, lights, ...).
 *
 * PS2ObjEnumMeshes receives the object packet output by PS2ObjXXXOpen and generates a new packet for each mesh
 * within the object, each packet containing a reference to the object's RxPS2DMASessionRecord and a unique RxPS2Mesh
 * record, which conveys a pointer to the mesh. Mesh packets are then vectored to material pipelines, either
 * the pipeline assigned to the mesh's material, or in the absence of a specific assignment, to the default material
 * pipeline. Within the material pipeline, the mesh packets receive processing from PS2MatInstance and PS2MatBridge.
 *
 * Once all material pipelines have been executed, flow of control returns to the instancing pipe, which executes
 * node PS2ObjClose to complete object processing.
 *
 * Note the difference between PS2 and "generic" pipelines: PS2 pipelines defer instancing
 * (conversion of compressed atomic or worldsector data into a format suitable for rendering) to the material pipeline,
 * as against generic pipelines which instance at the object level (in the instancing pipeline). This enables PS2
 * pipelines to select instanced data format at mesh granularity, dependent on rendering requirements.
 *
 * @xref <f RxNodeDefinitionGetPS2MatBridge>
 * @xref <f RxNodeDefinitionGetPS2MatInstance>
 * @xref <f RxNodeDefinitionGetPS2ObjClose>
 * @xref <f RxNodeDefinitionGetPS2ObjEnumMeshes>
 */

/****************************************************************************
 RxNodeDefinitionGetPS2ObjOpen()
 */

RxNodeDefinition *
RxNodeDefinitionGetPS2ObjOpen(
    RwInt32 objType) /* @parm */
{
    /*****************************************/
    /**                                     **/
    /**  PS2OBJOPEN.CSL NODE SPECIFICATION  **/
    /**                                     **/
    /*****************************************/

    static RxClusterRef nodeClusters[] =
    {
        /* 0 */
        { &RxClPS2DMASessionRecord, rxCLALLOWABSENT, rxCLRESERVED }
    };

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

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

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

    static RwChar _DefaultOutput[] = RWSTRING("DefaultOutput");

    static RxOutputSpec nodeOuts[] =
    {
        {
            _DefaultOutput,              // Name
            nodeOut1,                    // OutputClusters
            rxCLVALID_NOCHANGE            // AllOtherClusters
        }
    };

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

    static RwChar _PS2ObjWorldSectorOpen_csl[] = RWSTRING("PS2ObjWorldSectorOpen.csl");

    static RxNodeDefinition nodePS2ObjWorldSectorOpenCSL =
    {
        _PS2ObjWorldSectorOpen_csl,            // Name
        {                                      // nodemethods
            _rwPS2ObjOpenWorldSectorNodeBody,  // +-- nodebody
            (RxNodeInitFn)NULL,                // +-- nodeinit
            (RxNodeTermFn)NULL,                // +-- nodeterm
            (RxPipelineNodeInitFn) NULL,       // +-- pipelinenodeinit
            (RxPipelineNodeTermFn) NULL,       // +-- pipelineNodeTerm
            (RxPipelineNodeConfigFn) NULL,     // +--  pipelineNodeConfig
            (RxConfigMsgHandlerFn) NULL        // +--  configMsgHandler
        },
        {                                      // Io
            NUMCLUSTERSOFINTEREST,             // +-- NumClustersOfInterest
            nodeClusters,                      // +-- ClustersOfInterest
            nodeReqs,                          // +-- InputRequirements
            NUMOUTPUTS,                        // +-- NumOutputs
            nodeOuts                           // +-- Outputs
        },
        (RwUInt32)0,                           // PipelineNodePrivateDataSize
        FALSE,                                 // editable
        (RwInt32)0                             // inPipes
    };

    static RwChar _PS2ObjAtomicOpen_csl[] = RWSTRING("PS2ObjAtomicOpen.csl");

    static RxNodeDefinition nodePS2ObjAtomicOpenCSL =
    {
        _PS2ObjAtomicOpen_csl,                 // Name
        {                                      // nodemethods
            _rwPS2ObjOpenAtomicNodeBody,       // +-- nodebody
            (RxNodeInitFn)NULL,                // +-- nodeinit
            (RxNodeTermFn)NULL,                // +-- nodeterm
            (RxPipelineNodeInitFn) NULL,       // +-- pipelinenodeinit
            (RxPipelineNodeTermFn) NULL,       // +-- pipelineNodeTerm
            (RxPipelineNodeConfigFn) NULL,     // +--  pipelineNodeConfig
            (RxConfigMsgHandlerFn) NULL        // +--  configMsgHandler
        },
        {                                      // Io
            NUMCLUSTERSOFINTEREST,             // +-- NumClustersOfInterest
            nodeClusters,                      // +-- ClustersOfInterest
            nodeReqs,                          // +-- InputRequirements
            NUMOUTPUTS,                        // +-- NumOutputs
            nodeOuts                           // +-- Outputs
        },
        (RwUInt32)0,                           // PipelineNodePrivateDataSize
        FALSE,                                 // editable
        (RwInt32)0                             // inPipes
    };

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

    RxNodeDefinition *result = NULL;

    RWAPIFUNCTION(RWSTRING("RxNodeDefinitionGetPS2ObjOpen"));

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

    switch (objType)
    {
        case rxOBJTYPE_WORLDSECTOR:
            result = &nodePS2ObjWorldSectorOpenCSL;
            break;

        case rxOBJTYPE_ATOMIC:
            result = &nodePS2ObjAtomicOpenCSL;
            break;
    }

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

    RWRETURN(result);
}
