/*===========================================================================*
 *--- Include files ---------------------------------------------------------*
 *===========================================================================*/
#include <rwcore.h>
#include <rpworld.h>

#include "rpplugin.h"
#include "rpdbgerr.h"

#include "rppatch310.h"

#include "rpplugin.h"
#include "rppatch310.h"
#include "patchvar.h"
#include "patchgen.h"
#include "patchexp.h"

#include "noded3d8patchatomicinstance.h"

#define RWOBJSPACE3D_FVF  (D3DFVF_XYZ | D3DFVF_NORMAL | D3DFVF_DIFFUSE | D3DFVF_TEX2)

#define FLOATASINT(f) (*((const RwInt32 *)&(f)))

/*===========================================================================*
 *--- Private Types ---------------------------------------------------------*
 *===========================================================================*/

/*===========================================================================*
 *--- Private Global Variables ----------------------------------------------*
 *===========================================================================*/

/*===========================================================================*
 *--- Private Defines -------------------------------------------------------*
 *===========================================================================*/

/*===========================================================================*
 *--- Local Types -----------------------------------------------------------*
 *===========================================================================*/

/*===========================================================================*
 *--- Local Global Variables ------------------------------------------------*
 *===========================================================================*/
#if (!defined(DXOYGEN))
static const char rcsid[] __RWUNUSED__ =
    "@@@@(#)$Id: nodeD3D8PatchAtomicInstance.c,v 1.6 2001/09/25 10:28:09 markf Exp $";
#endif /* (!defined(DXOYGEN)) */

/*===========================================================================*
 *--- Local Defines ---------------------------------------------------------*
 *===========================================================================*/
#define NUMCLUSTERSOFINTEREST 0
#define NUMOUTPUTS 0

#define PATCHPASSTHROUGH 1

/*===========================================================================*
 *--- Local functions -------------------------------------------------------*
 *===========================================================================*/

/****************************************************************************
 _rxD3D8PatchDefaultRenderCallback

 Purpose:

 On entry:

 On exit :
*/
static void
_rxD3D8PatchDefaultRenderCallback(RwResEntry *repEntry,
                           RpAtomic *atomic,
                           RwUInt32 flags)
{
    RpPatchD3D8Atomic       *patchAtomic;
    RxD3D8InstanceData      *instancedData;
    RwCamera                *cam;
    RwInt32                 numMeshes;
    RwBool                  lighting;
    RwBool                  vertexAlphaBlend;
    RwBool                  forceBlack;
    RwUInt32                ditherEnable;
    RwUInt32                shadeMode;
    void                    *lastVertexBuffer;

    RWFUNCTION(RWSTRING("_rxD3D8PatchDefaultRenderCallback"));

    /* Get lighting state */
    RwD3D8GetRenderState(D3DRS_LIGHTING, &lighting);

    forceBlack = FALSE;

    if (lighting)
    {
        if (flags & rxGEOMETRY_PRELIT)
        {
            /* Emmisive color from the vertex colors */
            RwD3D8SetRenderState(D3DRS_COLORVERTEX, TRUE);
            RwD3D8SetRenderState(D3DRS_EMISSIVEMATERIALSOURCE, D3DMCS_COLOR1);
        }
        else
        {
            /* Emmisive color from material, set to black in the submit node */
            RwD3D8SetRenderState(D3DRS_COLORVERTEX, FALSE);
            RwD3D8SetRenderState(D3DRS_EMISSIVEMATERIALSOURCE, D3DMCS_MATERIAL);
        }
    }
    else
    {
        if ((flags & rxGEOMETRY_PRELIT) == 0)
        {
            forceBlack = TRUE;

            RwD3D8GetRenderState(D3DRS_DITHERENABLE, &ditherEnable);
            RwD3D8GetRenderState(D3DRS_SHADEMODE, &shadeMode);

            RwD3D8SetRenderState(D3DRS_TEXTUREFACTOR, 0xff000000);
            RwD3D8SetRenderState(D3DRS_DITHERENABLE, FALSE);
            RwD3D8SetRenderState(D3DRS_SHADEMODE, D3DSHADE_FLAT);
        }
    }

    /* Enable clipping */
    cam = RwCameraGetCurrentCamera();
    RWASSERT(cam);

    if (RwD3D8CameraIsSphereFullyInsideFrustum(cam, RpAtomicGetWorldBoundingSphere(atomic)))
    {
        RwD3D8SetRenderState(D3DRS_CLIPPING, FALSE);
    }
    else
    {
        RwD3D8SetRenderState(D3DRS_CLIPPING, TRUE);
    }

    /* Set texture to NULL if hasn't any texture flags */
    if ( (flags & (rxGEOMETRY_TEXTURED | rpGEOMETRYTEXTURED2)) == 0)
    {
        RwD3D8SetTexture(NULL, 0);

        if (forceBlack)
        {
            RwD3D8SetTextureStageState(0, D3DTSS_COLOROP,   D3DTOP_SELECTARG2);
            RwD3D8SetTextureStageState(0, D3DTSS_COLORARG2, D3DTA_TFACTOR);

            RwD3D8SetTextureStageState(0, D3DTSS_ALPHAOP,   D3DTOP_DISABLE);
        }
    }

    /* Get vertex alpha Blend state */
    RwRenderStateGet(rwRENDERSTATEVERTEXALPHAENABLE, (void *)&vertexAlphaBlend);

    /* Set Last vertex buffer to force the call */
    lastVertexBuffer = (void *)0xffffffff;

    /* Get the instanced data */
    patchAtomic = (RpPatchD3D8Atomic *)(repEntry + 1);
    instancedData = (RxD3D8InstanceData *)(patchAtomic + 1);

    /*
     * Data shared between meshes
     */

    /*
     * Set the Default Pixel shader
     */
    RwD3D8SetPixelShader(0);

    /*
     * Vertex shader
     */
    RwD3D8SetVertexShader(instancedData->vertexShader);

    /* Get the number of meshes */
    numMeshes = patchAtomic->numMeshes;
    while (numMeshes--)
    {
        if (instancedData->vertexAlpha ||
            (0xFF != instancedData->material->color.alpha))
        {
            if (!vertexAlphaBlend)
            {
                vertexAlphaBlend = TRUE;

                RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void *)TRUE);
            }
        }
        else
        {
            if (vertexAlphaBlend)
            {
                vertexAlphaBlend = FALSE;

                RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void *)FALSE);
            }
        }

        if ( (flags & (rxGEOMETRY_TEXTURED | rpGEOMETRYTEXTURED2)) )
        {
            RwD3D8SetTexture(instancedData->material->texture, 0);

            if (forceBlack)
            {
                /* Only change the colorop, we need to use the texture alpha channel */
                RwD3D8SetTextureStageState(0, D3DTSS_COLOROP,   D3DTOP_SELECTARG2);
                RwD3D8SetTextureStageState(0, D3DTSS_COLORARG2, D3DTA_TFACTOR);
            }
        }

        if (lighting)
        {
            RwD3D8SetSurfaceProperties(&instancedData->material->color,
                                        &instancedData->material->surfaceProps,
                                        (flags & rxGEOMETRY_MODULATE));
        }

        /*
         * Render
         */

        /* Set the stream source */
        if (lastVertexBuffer != instancedData->vertexBuffer)
        {
            RwD3D8SetStreamSource(0, instancedData->vertexBuffer, instancedData->stride);

            lastVertexBuffer = instancedData->vertexBuffer;
        }

        /* Set the Index buffer */
        RwD3D8SetIndices(instancedData->indexBuffer, instancedData->baseIndex);

        /* Draw the indexed primitive */
        RwD3D8DrawIndexedPrimitive((D3DPRIMITIVETYPE)instancedData->primType,
                                   0, instancedData->numVertices,
                                   0, instancedData->numIndices);

        /* Move onto the next instancedData */
        instancedData++;
    }

    if (forceBlack)
    {
        RwD3D8SetRenderState(D3DRS_DITHERENABLE, ditherEnable);
        RwD3D8SetRenderState(D3DRS_SHADEMODE, shadeMode);

        RwD3D8SetTextureStageState(0, D3DTSS_COLOROP,   D3DTOP_SELECTARG2);
        RwD3D8SetTextureStageState(0, D3DTSS_COLORARG2, D3DTA_DIFFUSE);
    }

    RWRETURNVOID();
}

/*****************************************************************************
 _rxD3D8PatchPipelineNodeInitFn

 Initializes the private data (refinement ON by default)

 Inputs :
 Outputs :
 */
static RwBool
_rxD3D8PatchPipelineNodeInitFn( RxPipelineNode *self )
{
    RWFUNCTION(RWSTRING("_rxD3D8PatchPipelineNodeInitFn"));

    if (self)
    {
        RpNodePatchData *data = ((RpNodePatchData *) self->privateData);

        data->patchOn = TRUE;
        data->numExtraUVs = 0;
        data->numExtraRGBAs = 0;

        RWRETURN(TRUE);
    }

    RWRETURN(FALSE);
}

/*****************************************************************************
 _rxD3D8PatchDestroyVertexBuffer

 Destroy all the buffer memory

 Inputs :
 Outputs :
 */
static void
_rxD3D8PatchDestroyVertexBuffer( RwResEntry *repEntry )
{
    RpPatchD3D8Atomic       *patchAtomic;
    RxD3D8InstanceData      *instancedData;
    LPDIRECT3DVERTEXBUFFER8 vertexBuffer;
    RwUInt32                numMeshes;
    RwUInt32                fvf;
    RwUInt32                stride;
    RwUInt32                numVertices;
    RwUInt32                baseIndex;

    RWFUNCTION(RWSTRING("_rxD3D8PatchDestroyVertexBuffer"));

    patchAtomic = (RpPatchD3D8Atomic *)(repEntry + 1);

    /* Get the instanced data structures */
    instancedData = (RxD3D8InstanceData *)(patchAtomic + 1);

    /* Save shared data */
    vertexBuffer = instancedData->vertexBuffer;
    fvf = instancedData->vertexShader;
    stride = instancedData->stride;
    baseIndex = instancedData->baseIndex;

    /* Get the number of meshes */
    numMeshes = patchAtomic->numMeshes;
    numVertices = 0;

    while (numMeshes--)
    {
        /* Destroy the index buffer */
        if (instancedData->indexBuffer)
        {
            IDirect3DIndexBuffer8_Release((LPDIRECT3DINDEXBUFFER8)instancedData->indexBuffer);
            instancedData->indexBuffer = NULL;
        }

        instancedData->vertexBuffer = NULL;

        numVertices += instancedData->numVertices;

        /* On to the next RxD3D8InstanceData */
        instancedData++;
    }

    /* Destroy the only one vertex buffer that we have */
    if (vertexBuffer)
    {
        _rxD3D8VertexBufferManagerDestroy(fvf, stride * numVertices,
                                            vertexBuffer, baseIndex);
    }

    RWRETURNVOID();
}

/*****************************************************************************
 _rxD3D8PatchAtomicAllInOneNodeNode

 _refineNode

 Inputs :
 Outputs :
 */
static RwBool
_rxD3D8PatchAtomicAllInOneNodeNode( RxPipelineNodeInstance *self,
                            const RxPipelineNodeParam *params )
{
    RpNodePatchData     *patchData;
    RpAtomic            *atomic;
    RpPatchAtomicExt    *atomicExt;
    RpGeometry          *geom;
    RpMeshHeader        *meshHeader;
    RwInt32 res;
    RwInt32 numVerts;
    RwInt32 numMeshes;
    RwInt32 numIndices;
    RwInt32 numTris;

    RwResEntry *repEntry;
    RwResEntry **repEntryOwner;

    void *owner;

    RpInterpolator          *interpolator;
    RpPatchD3D8Atomic       *d3d8Atomic;   

    RwMatrix    *matrix;
    RwUInt32    lighting;

    RWFUNCTION(RWSTRING("_rxD3D8PatchAtomicAllInOneNodeNode"));

    RWASSERT(NULL != self);
    RWASSERT(NULL != params);

    patchData = (RpNodePatchData *) self->privateData;
    RWASSERT(NULL != patchData);

    /* Cheap early out if this node's toggled off */
    if (0 && (patchData->patchOn == FALSE))
    {
        RxPacketDispatch(NULL, PATCHPASSTHROUGH, self);
        RWRETURN(TRUE);
    }

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

    atomicExt = (RpPatchAtomicExt *)
        RPPATCHPROP(atomic, rpPatchGlobals.atomicExtOffset);
    RWASSERT(atomicExt->flag & RPPATCHATOMICFLAGPATCH);

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

    numVerts = geom->numVertices;
    /* If there ain't vertices, we cain't make packets... */
    if (numVerts <= 0)
    {
        /* Don't execute the rest of the pipeline */
        RWRETURN(TRUE);
    }

    meshHeader = geom->mesh;
    numMeshes = meshHeader->numMeshes;
    /* Early out if no meshes */
    if (numMeshes <= 0)
    {
        /* If the app wants to use plugin data to make packets, it
         * should use its own instancing function. If we have verts
         * here, we need meshes too in order to build a packet. */
        RWRETURN(TRUE);
    }

    /* Query for the LOD. */
    res = (rpPatchGlobals.atomicLODCallBack) (atomic);

    interpolator = &atomic->interpolator;

    /* 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)
    {
        owner = (void *) atomic;
        repEntryOwner = &atomic->repEntry;
        repEntry = atomic->repEntry;
    }
    else
    {
        owner = (void *) geom;
        repEntryOwner = &geom->repEntry;
        repEntry = geom->repEntry;
    }

    if (NULL != repEntry)
    {
        /* If anything has changed, we should re-instance */
        d3d8Atomic = (RpPatchD3D8Atomic *) (repEntry + 1);

        if ((d3d8Atomic->res != res) ||
            (d3d8Atomic->serialNumber != meshHeader->serialNum))
        {
            /* Things have changed, destroy resources to force reinstance */
            RwResourcesFreeResEntry(repEntry);
            repEntry = NULL;
        }
    }

    /* We need to re-generate the mesh due to some changes. */
    if (repEntry == NULL)
    {
        RwUInt32                size;
        RpPatchInstanceAtomic   *instAtomic;
        RxVertexIndex           *idxs;
        RpPatchInstanceMesh     *instMesh;
        RxD3D8InstanceData      *instancedData;
        LPDIRECT3DVERTEXBUFFER8 vertexBuffer;
        RwUInt32                baseIndex;

        /* Total number of indices and verts. */
        numVerts =
            (atomicExt->numQuadPatch * RPPATCHQUADNUMVERT(res + 1)) +
            (atomicExt->numTriPatch * RPPATCHTRINUMVERT(res + 1));

        numIndices =
            (atomicExt->numQuadPatch * RPPATCHQUADNUMINDEX(res + 1)) +
            (atomicExt->numTriPatch * RPPATCHTRINUMINDEX(res + 1));

        numTris =
            (atomicExt->numQuadPatch * RPPATCHQUADNUMTRI(res + 1)) +
            (atomicExt->numTriPatch * RPPATCHTRINUMTRI(res + 1));

        /* Add extra indices for connecting patches together. */
        numIndices +=
            (atomicExt->numQuadPatch + atomicExt->numTriPatch - 1) * 2;

        /* Add extra for odd number of tri patches to preserve winding order. */
        if (res & 0x01)
            numIndices += (atomicExt->numTriPatch);

        /* RpPatchInstanceAtomic, stores serialNumber & numMeshes */
        size = sizeof(RpPatchInstanceAtomic);

        /* RxD3D8InstanceData structures, one for each mesh */
        size += sizeof(RxD3D8InstanceData) * numMeshes;

        repEntry = RwResourcesAllocateResEntry(owner, repEntryOwner,
                                               size,
                                               _rxD3D8PatchDestroyVertexBuffer);
        RWASSERT(NULL != repEntry);

        /* Blank the RpPatchInstanceAtomic & RxD3D8InstanceData's to '0' */
        memset((repEntry + 1), 0, size);

        /* Create Temporary atomic info */
        d3d8Atomic = (RpPatchD3D8Atomic *) (repEntry + 1);
        d3d8Atomic->res = res;
        d3d8Atomic->serialNumber = meshHeader->serialNum;
        d3d8Atomic->numMeshes = (RwUInt16)numMeshes;

        /* Create temporary atomic info */
        size = sizeof(RpPatchInstanceAtomic) +
            (numVerts * sizeof(RxObjSpace3DVertex)) +
            (numMeshes * sizeof(RpPatchInstanceMesh)) +
            (numIndices * sizeof(RxVertexIndex));

        instAtomic = (RpPatchInstanceAtomic *)RwMalloc(size);

        idxs = (RxVertexIndex *) & instAtomic->meshes[numMeshes];

        instAtomic->indices = idxs;
        instAtomic->vertices = (RxObjSpace3DVertex *) (idxs + numIndices);
        instAtomic->numMeshes = numMeshes;
        instAtomic->totalIndices = numIndices;
        instAtomic->totalVerts = numVerts;

        /* Fill the vertex buffer (interpolates) */
        if (FALSE == _rpPatchInstanceAtomic(instAtomic, atomic, res))
        {
            RwFree(instAtomic);

            RWRETURN(FALSE);
        }

        /* Get the first RxD3D8InstanceData pointer */
        instancedData = (RxD3D8InstanceData *)(d3d8Atomic + 1);

        /* Create only one vertex buffer stored in the first instance data */
        if (FALSE == _rxD3D8VertexBufferManagerCreate(RWOBJSPACE3D_FVF,
                                                    instAtomic->totalVerts * sizeof(RxObjSpace3DVertex),
                                                    (void **)&vertexBuffer,
                                                    &baseIndex))
        {
            /* Ooops */
            RwFree(instAtomic);

            RWRETURN(FALSE);
        }
        else
        {
            RxObjSpace3DVertex *vbVerts;

            if (SUCCEEDED(IDirect3DVertexBuffer8_Lock(vertexBuffer,
                             baseIndex * sizeof(RxObjSpace3DVertex),
                             instAtomic->totalVerts * sizeof(RxObjSpace3DVertex),
                             (RwUInt8 **)&vbVerts, 0)))
            {
                memcpy(vbVerts, instAtomic->vertices, instAtomic->totalVerts * sizeof(RxObjSpace3DVertex));

                IDirect3DVertexBuffer8_Unlock(vertexBuffer);
            }
        }

        /* Fill instance data and indices */
        instMesh = instAtomic->meshes;
        numMeshes = instAtomic->numMeshes;
        while (numMeshes--)
        {
            /*
             * Min vertex index,
             * (needed for instancing & reinstancing)
             */
            instancedData->minVert = instMesh->minVert,

            /* Number of vertices */
            instancedData->numVertices = instMesh->numVerts,

            /* Primitive type */
            instancedData->primType = D3DPT_TRIANGLESTRIP;

            /* The number of indices */
            instancedData->numIndices = instMesh->numIndices;

            /* Material */
            instancedData->material = instMesh->material;

            /* Vertex shader */
            instancedData->vertexShader = RWOBJSPACE3D_FVF;

            /* The vertex format stride */
            instancedData->stride = sizeof(RxObjSpace3DVertex);

            /* Initialize the vertex buffers pointers */
            instancedData->vertexBuffer = vertexBuffer;

            /* Initialize vertex buffer managed to FALSE */
            instancedData->baseIndex = baseIndex + instancedData->minVert;

            instancedData->managed = TRUE;

            /* Initialize vertex alpha to FALSE */
            instancedData->vertexAlpha = FALSE;

            /*
             * Set the index buffer
             */

            /* Initialize the index buffers pointers */
            if (RwD3D8IndexBufferCreate(instMesh->numIndices, &(instancedData->indexBuffer)))
            {
                RxVertexIndex   *indexBuffer;

                if (D3D_OK == IDirect3DIndexBuffer8_Lock((LPDIRECT3DINDEXBUFFER8)instancedData->indexBuffer,
                                                    0, 0, (RwUInt8 **)&indexBuffer, 0))
                {
                    memcpy(indexBuffer, instMesh->indices, sizeof(RxVertexIndex) * instMesh->numIndices);

                    IDirect3DIndexBuffer8_Unlock((LPDIRECT3DINDEXBUFFER8)instancedData->indexBuffer);
                }
            }

            instancedData++;
            instMesh++;
        }

        RwFree(instAtomic);
    }
    else
    {
        RwResourcesUseResEntry(repEntry);
    }

    /*
     * Set up lights
     */
    _rwD3D8AtomicDefaultLightingCallback((void *)atomic);

    /* 
     * Set the world transform
     */
    matrix = RwFrameGetLTM(RpAtomicGetFrame(atomic));

    RwD3D8SetTransformWorld(matrix);

    RwD3D8GetRenderState(D3DRS_LIGHTING, &lighting);

    if (lighting &&
        !rwMatrixTestFlags(matrix, (rwMATRIXTYPENORMAL | rwMATRIXINTERNALIDENTITY)))
    {
        const RwReal minlimit = 0.9f;
        const RwReal maxlimit = 1.1f;
        RwReal length;
        
        length = RwV3dDotProduct(&(matrix->right), &(matrix->right));

        if ( (FLOATASINT(length) > FLOATASINT(maxlimit)) || (FLOATASINT(length) < FLOATASINT(minlimit)) )
        {
            RwD3D8SetRenderState(D3DRS_NORMALIZENORMALS, TRUE);
        }
        else
        {
            length = RwV3dDotProduct(&(matrix->up), &(matrix->up));

            if ( (FLOATASINT(length) > FLOATASINT(maxlimit)) || (FLOATASINT(length) < FLOATASINT(minlimit)) )
            {
                RwD3D8SetRenderState(D3DRS_NORMALIZENORMALS, TRUE);
            }
            else
            {
                length = RwV3dDotProduct(&(matrix->at), &(matrix->at));

                if ( (FLOATASINT(length) > FLOATASINT(maxlimit)) || (FLOATASINT(length) < FLOATASINT(minlimit)) )
                {
                    RwD3D8SetRenderState(D3DRS_NORMALIZENORMALS, TRUE);
                }
                else
                {
                    RwD3D8SetRenderState(D3DRS_NORMALIZENORMALS, FALSE);
                }
            }
        }
    }
    else
    {
        RwD3D8SetRenderState(D3DRS_NORMALIZENORMALS, FALSE);
    }

    /*
     * Render
     */
    _rxD3D8PatchDefaultRenderCallback(repEntry, atomic, RpGeometryGetFlags(geom));

    RWRETURN(TRUE);
}

/*===========================================================================*
 *--- Private functions -----------------------------------------------------*
 *===========================================================================*/

/*===========================================================================*
 *--- Plugin Engine Functions -----------------------------------------------*
 *===========================================================================*/

/*===========================================================================*
 *--- Plugin API Functions --------------------------------------------------*
 *===========================================================================*/

/**
 * \ingroup rprefine
 * \ref RxNodeDefinitionGetD3D8PatchAtomicInstance
 * returns a pointer to a node to instance a patch atomic in a form suitable for
 * rendering by a DirectX 7 pipeline.
 *
 * The node has one output. Successful generation of meshes from patches are passed
 * via this output.
 *
 * The input requirements of this node:
 *     \li RxClObjSpace3DVertices  - don't want
 *     \li RxClIndices             - don't want
 *     \li RxClMeshState           - don't want
 *     \li RxClRenderState         - don't want
 *     \li RxClD3D8VertexBufferInfo - don't want
 *
 * The characteristics of this node's outputs:
 *     \li RxClObjSpace3DVertices  - valid
 *     \li RxClIndices             - valid
 *     \li RxClMeshState           - valid
 *     \li RxClRenderState         - valid
 *     \li RxClD3D8VertexBufferInfo - valid
 *
 * \return pointer to node for patch faceting custom pipelines on success,
 * NULL otherwise
 */
RxNodeDefinition   *
RxNodeDefinitionGetD3D8PatchAtomicInstance(void)
{
    static RwChar _PatchAtomic_csl[] = "PatchAtomic.csl";
    
    static RxNodeDefinition nodeD3D8PatchAtomic = { /* */
        _PatchAtomic_csl,            /* Name */
        {                                           /* Nodemethods */
            _rxD3D8PatchAtomicAllInOneNodeNode,         /* +-- nodebody */
            NULL,                                   /* +-- nodeinit */
            NULL,                                   /* +-- nodeterm */
            _rxD3D8PatchPipelineNodeInitFn,    /* +-- pipelinenodeinit */
            NULL,                                   /* +-- pipelineNodeTerm */
            NULL,                                   /* +-- pipelineNodeConfig */
            NULL,                                   /* +-- configMsgHandler */
        },
        {                                           /* Io */
            NUMCLUSTERSOFINTEREST,                  /* +-- NumClustersOfInterest */
            NULL,                                   /* +-- ClustersOfInterest */
            NULL,                                   /* +-- InputRequirements */
            NUMOUTPUTS,                             /* +-- NumOutputs */
            NULL                                    /* +-- Outputs */
        },
        (RwUInt32)sizeof(RpNodePatchData),  /* PipelineNodePrivateDataSize */
        FALSE,                                      /* editable */
        0                                           /* inPipes */
    };

    RWAPIFUNCTION(RWSTRING("RxNodeDefinitionGetD3D8PatchAtomicInstance"));

    RWRETURN(&nodeD3D8PatchAtomic);
}
