/*----------------------------------------------------------------------*
 *                                                                      *
 * Module  :                                                            *
 *                                                                      *
 * Purpose : Triangle Perspective Rendering    ATL                      *
 *                                                                      *
 * FX        :                                                          *
 *----------------------------------------------------------------------*
 *----------------------------------------------------------------------*
 *-                             Includes                               -*
 *----------------------------------------------------------------------*/

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

 /* ==== RW libs includes ===== */

#include "rpplugin.h"
#include <rpdbgerr.h>
#include <rwcore.h>
#include <rpmatfx.h>

#if (defined(WIN32))
#include <windows.h>
#endif  /* (defined(WIN32)) */

#if (defined(OPENGL_DRVMODEL_H))
#include <GL/gl.h>
extern RwGlExt      gl;
static RxOpenGLSubmitCallBack OldSubmitCallBack = NULL;
#else /* (defined(OPENGL_DRVMODEL_H)) */
#include <d3d.h>
static LPDIRECT3DDEVICE7      CurrDevice        = NULL;
static RxDX7SubmitCallBack    OldSubmitCallBack = NULL;

static RwInt32 PrimConv[rwPRIMTYPELINELIST |
                        rwPRIMTYPEPOLYLINE |
                        rwPRIMTYPETRILIST  |
                        rwPRIMTYPETRISTRIP |
                        rwPRIMTYPETRIFAN];
#endif /* (defined(OPENGL_DRVMODEL_H)) */

#include "effectPipes.h"

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

static RxPipeline *MatFXMaterialPipe       = NULL;

#if (0)
static RxPipeline *MatFXAtomicInstancePipe = NULL;
static RxPipeline *MatFXSectorInstancePipe = NULL;
#endif /* (0) */


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

static RwBool
CalculatePerturbedUVs(RxHeap *heap,
                  RxCluster *vertices,
                  RxCluster *indices,
                  RxMeshStateVector *meshState,
                  RwTexCoords *bumpUVs)
{
    RwUInt32 numVerts;
    RwUInt8 *processedFlags;
    RpMaterial *material;
    RxVertexIndex *inds;

    RWFUNCTION(RWSTRING("CalculatePerturbedUVs"));

    numVerts = meshState->NumVertices;
    material = meshState->SourceObject;

    processedFlags = RxHeapAlloc(heap, vertices->numUsed);
    memset(processedFlags, 0, vertices->numUsed);

    inds           = RxClusterGetCursorData(indices, RxVertexIndex);

    if (inds && processedFlags)
    {
        RwInt32 i, maxIndex;
        MatFXBumpMapData *bumpMapData = MATFXOPENGLBUMPMAPGETDATA(material, rpSECONDPASS);
        RwReal coef         = bumpMapData->coef;
        RwReal invBumpWidth = bumpMapData->invBumpWidth;
        RwFrame *bumpFrame  = bumpMapData->frame;

        RwReal factor = coef * invBumpWidth;
        RwV3d lightPosObj, *lightPos;

        RwMatrix worldToObj;

        if (bumpFrame == NULL)
        {
            bumpFrame = RwCameraGetFrame(RwCameraGetCurrentCamera());
            RWASSERT(bumpFrame);
        }

        lightPos = RwMatrixGetPos(RwFrameGetLTM(bumpFrame));

        RwMatrixInvert(&worldToObj, &meshState->Obj2World);
        RwV3dTransformPoints(&lightPosObj, lightPos, 1, &worldToObj);

        if (meshState->PrimType == rwPRIMTYPETRILIST)
        {
            maxIndex = meshState->NumElements * 3;
        }
        else
        {
            maxIndex = meshState->NumElements + 2;
        }

        for (i = 0; i < maxIndex; i++)
        {
            if (processedFlags[inds[i]] == FALSE)
            {
                RxObjSpace3DVertex *vert1 = (RxObjSpace3DVertex *)NULL;
                RxObjSpace3DVertex *vert2 = (RxObjSpace3DVertex *)NULL;
                RxObjSpace3DVertex *vert3 = (RxObjSpace3DVertex *)NULL;
                RwV3d pos1, pos2, pos3;
                RwV3d b, t, n, temp1, temp2, l;

                if (meshState->PrimType == rwPRIMTYPETRILIST)
                {
                    vert1 = RxClusterGetIndexedData(vertices, RxObjSpace3DVertex, inds[i]);
                    vert2 = RxClusterGetIndexedData(vertices, RxObjSpace3DVertex, inds[((i/3)*3) + (i+1)%3]);
                    vert3 = RxClusterGetIndexedData(vertices, RxObjSpace3DVertex, inds[((i/3)*3) + (i+2)%3]);
                }
                else if (meshState->PrimType == rwPRIMTYPETRISTRIP)
                {
                    RwInt32 i1, i2, i3;
                    i1 = i;
                    if (i < 2)
                    {
                        if (i%2)
                        {
                            i2 = i + 2;
                            i3 = i + 1;
                        }
                        else
                        {
                            i2 = i + 1;
                            i3 = i + 2;
                        }
                    }
                    else
                    {
                        if (i%2)
                        {
                            i2 = i - 1;
                            i3 = i - 2;
                        }
                        else
                        {
                            i2 = i - 2;
                            i3 = i - 1;
                        }
                    }
                    vert1 = RxClusterGetIndexedData(vertices, RxObjSpace3DVertex, inds[i1]);
                    vert2 = RxClusterGetIndexedData(vertices, RxObjSpace3DVertex, inds[i2]);
                    vert3 = RxClusterGetIndexedData(vertices, RxObjSpace3DVertex, inds[i3]);
                }

                RxObjSpace3DVertexGetPos(vert1, &pos1);
                RxObjSpace3DVertexGetNormal(vert1, &n);

                RwV3dSub(&l, &lightPosObj, &pos1);
                RwV3dNormalize(&l, &l);

                /* Check to see whether the light is behind the triangle */
                if (1) /* RwV3dDotProduct(&l, &n) > 0.0f) */
                {
                    RwV2d shift;
                    RwReal unused;

                    RxObjSpace3DVertexGetPos(vert2, &pos2);
                    RxObjSpace3DVertexGetPos(vert3, &pos3);

                    /* A nice little algorithm to find the tangent vector */
                    RwV3dSub(&temp1, &pos2, &pos1);
                    RwV3dSub(&temp2, &pos3, &pos1);

                    RwV3dScale(&temp1, &temp1, vert3->v - vert1->v);
                    RwV3dScale(&temp2, &temp2, vert2->v - vert1->v);

                    RwV3dSub(&t, &temp1, &temp2);
                    _rwV3dNormalizeMacro(unused, &t, &t);
                    RwV3dCrossProduct(&b, &t, &n);

                    /*
                     * So now that we have b, t and n, we have the tangent
                     * space coordinate system axes.
                     */
                    shift.x = RwV3dDotProduct(&t, &l);
                    shift.y = RwV3dDotProduct(&b, &l);

                    _rwV2dNormalizeMacro(unused, &shift, &shift);

                    bumpUVs[inds[i]].u = vert1->u - (shift.x * factor);
                    bumpUVs[inds[i]].v = vert1->v - (shift.y * factor);
                }

                processedFlags[inds[i]] = TRUE;
            }
        }
    }

    RxHeapFree(heap, processedFlags);

    RWRETURN(TRUE);
}

static RwBool
CalculateSphereMapUVs(RxHeap * __RWUNUSED__ heap,
                      RxCluster *vertices,
                      RxCluster *  __RWUNUSED__  indices,
                      RxMeshStateVector *meshState,
                      RwTexCoords *uvs,
                      MatFXPass pass)
{
    RwUInt32 i;
    RxObjSpace3DVertex *verts;
    RwMatrix *invFrame, *envTransform;
    RpMaterial *material;
    RwFrame *envFrame;
    MatFXEnvMapData   *envMapData;

    RWFUNCTION(RWSTRING("CalculateSphereMapUVs"));

    verts = RxClusterGetCursorData(vertices, RxObjSpace3DVertex);
    RWASSERT(verts);

    material = meshState->SourceObject;
    RWASSERT(material);

    envMapData   = MATFXOPENGLENVMAPGETDATA(material, pass);
    envFrame     = envMapData->frame;

    invFrame     = RwMatrixCreate();
    envTransform = RwMatrixCreate();

    if (envFrame)
    {
        RwMatrixInvert(invFrame, RwFrameGetLTM(envFrame));
    }
    else
    {
        RwMatrixInvert(invFrame,
            RwFrameGetLTM(RwCameraGetFrame(RwCameraGetCurrentCamera())));
    }
    RwMatrixMultiply(envTransform, &meshState->Obj2World, invFrame);

    for (i = 0; i < meshState->NumVertices; i++)
    {
        RwV3d normal;
        RxObjSpace3DVertexGetNormal(verts, &normal);

        RwV3dTransformVectors(&normal, &normal, 1, envTransform);

        uvs->u = 0.5f * (1.0f + normal.x);
        uvs->v = -0.5f * (1.0f + normal.y);

        verts++;
        uvs++;
    }

    RwMatrixDestroy(invFrame);
    RwMatrixDestroy(envTransform);

    RWRETURN(TRUE);
}


static RwBool
DoEnvMapPass(RxPacket *packet,
             RxHeap *heap,
             RxCluster *vertices,
             RxCluster *indices,
             RxMeshStateVector *meshState,
             MatFXPass pass)
{
    RpMaterial  *material;
    RwRaster    *raster;
    RwTextureFilterMode filterMode;
    RwTexCoords *envUVs;
    RwTexture   *envMap;
    RwReal shininess;
    MatFXEnvMapData   *envMapData;
#ifdef D3D7_DRVMODEL_H
    RwUInt32 col = 0xFFFFFFFF;
    D3DDRAWPRIMITIVESTRIDEDDATA stridedVerts;
#endif

    RWFUNCTION(RWSTRING("DoEnvMapPass"));

    material   = (RpMaterial*)meshState->SourceObject;

    envMapData = MATFXOPENGLENVMAPGETDATA(material, pass);

    envMap = envMapData->texture;
    RWASSERT(envMap != NULL);

    /* Don't continue if we have no sphere map texture */
    if (envMap == NULL)
    {
        RWRETURN(FALSE);
    }

    raster = RwTextureGetRaster(envMap);
    RwRenderStateSet(rwRENDERSTATETEXTURERASTER, (void *)raster);
    filterMode = RwTextureGetFilterMode(envMap);
    RwRenderStateSet(rwRENDERSTATETEXTUREFILTER, (void *)filterMode);

    /* Save sphere mapping blending modes */
    RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void *)TRUE);
    RwRenderStateSet(rwRENDERSTATESRCBLEND,  (void *)rwBLENDSRCALPHA);
    RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void *)rwBLENDONE);

    shininess = envMapData->coef;

    envUVs = (RwTexCoords*)
        RxHeapAlloc(heap, vertices->numUsed * sizeof(RwTexCoords));
    CalculateSphereMapUVs(heap, vertices, indices, meshState, envUVs, pass);

#ifdef OPENGL_DRVMODEL_H
    /* Override uvs set from the retained mode pipe */
    glTexCoordPointer(2, GL_FLOAT, 0, envUVs);

    glDisableClientState(GL_COLOR_ARRAY);
    glColor4f(1, 1, 1, shininess);

    /* No lighting on gloss pass -
     * flat shaded with alpha blended additively */
    glDisable(GL_LIGHTING);

    if (gl.VertexArrayRangeNV)
    {
        glDisableClientState(GL_VERTEX_ARRAY_RANGE_NV);
    }

    /* Second pass - sphere mapped with alpha */
    RWASSERT(NULL != OldSubmitCallBack);
    OldSubmitCallBack(packet, heap, vertices,
                      indices, meshState);

    if (gl.VertexArrayRangeNV)
    {
        glEnableClientState(GL_VERTEX_ARRAY_RANGE_NV);
    }
#else
    ENSURE_STATE(CurrDevice, D3DRENDERSTATE_LIGHTING, FALSE);

    stridedVerts.position.lpvData  = RxClusterGetCursorData(vertices, void);
    stridedVerts.position.dwStride = sizeof(RxObjSpace3DVertex);

    col = 0xFFFFFF | (((RwUInt8)(shininess * 255.0f)) << 24);
    stridedVerts.diffuse.lpvData  = &col;
    stridedVerts.diffuse.dwStride = 0;

    stridedVerts.textureCoords[0].lpvData  = envUVs;
    stridedVerts.textureCoords[0].dwStride = sizeof(RwTexCoords);

    if ((NULL != indices) && (indices->numUsed > 0))
    {
        ERR_WRAP(IDirect3DDevice7_DrawIndexedPrimitiveStrided(CurrDevice,
                                  PrimConv[meshState->PrimType],
                                  D3DFVF_XYZ | D3DFVF_DIFFUSE | D3DFVF_TEX1,
                                  &stridedVerts,
                                  meshState->NumVertices,
                                  RxClusterGetCursorData(indices, WORD),
                                  indices->numUsed,
                                  0));
    }
    else
    {
        ERR_WRAP(IDirect3DDevice7_DrawPrimitiveStrided(CurrDevice,
                                  PrimConv[meshState->PrimType],
                                  D3DFVF_XYZ | D3DFVF_DIFFUSE | D3DFVF_TEX1,
                                  &stridedVerts,
                                  meshState->NumVertices,
                                  0));
    }
#endif
    RxHeapFree(heap, envUVs);

    RWRETURN(TRUE);
}


static RwBool
DoBumpMapPass(RxPacket *packet,
              RxHeap *heap,
              RxCluster *vertices,
              RxCluster *indices,
              RxMeshStateVector *meshState)
{
    RpMaterial  *material;
    RwTexCoords *bumpUVs;
    RwTexture   *bumpMap = NULL;
    MatFXBumpMapData  *bumpMapData;
#ifdef D3D7_DRVMODEL_H
    RwBool alphaEnable;
    RwBlendFunction srcBlend, destBlend;
    RxObjSpace3DVertex *vertsBase;
    RwV3d *normalsBase;
    RwUInt32 *colsBase;
    DWORD alphaTest;
    D3DDRAWPRIMITIVESTRIDEDDATA stridedVerts;
#endif

    RWFUNCTION(RWSTRING("MultiPassCallBack"));

    material   = (RpMaterial*)meshState->SourceObject;

    bumpMapData = MATFXOPENGLBUMPMAPGETDATA(material, rpSECONDPASS);

    if (bumpMapData->texture != NULL)
    {
        RwRaster *raster;
        RwTextureFilterMode filterMode;
        RwTextureAddressMode addressModeU, addressModeV;

        bumpMap = bumpMapData->texture;

        raster = RwTextureGetRaster(bumpMap);
        RwRenderStateSet(rwRENDERSTATETEXTURERASTER,
                         (void *)raster);
        filterMode = RwTextureGetFilterMode(bumpMap);
        RwRenderStateSet(rwRENDERSTATETEXTUREFILTER,
                         (void *)filterMode);
        addressModeU = RwTextureGetAddressingU(bumpMap);
        RwRenderStateSet(rwRENDERSTATETEXTUREADDRESSU,
                         (void *)addressModeU);
        addressModeV = RwTextureGetAddressingV(bumpMap);
        RwRenderStateSet(rwRENDERSTATETEXTUREADDRESSV,
                         (void *)addressModeV);
    }
    else
    {
        bumpMap = RpMaterialGetTexture(material);
    }

    RWASSERT(NULL != bumpMap);
    if (bumpMap == NULL)
    {
        /* Ordinary submit */
        RWASSERT(NULL != OldSubmitCallBack);
        OldSubmitCallBack(packet, heap, vertices, indices,
                          meshState);
    }

#ifdef OPENGL_DRVMODEL_H
    glPushAttrib(GL_COLOR_BUFFER_BIT);

    glEnable(GL_BLEND);
    glBlendFunc(GL_ONE_MINUS_SRC_ALPHA, GL_ZERO);
    glDisable(GL_ALPHA_TEST);

    RWASSERT(NULL != OldSubmitCallBack);
    OldSubmitCallBack(packet, heap, vertices, indices,
                      meshState);

    glBlendFunc(GL_SRC_ALPHA, GL_ONE);

    /* Offset texture coordinates */
    glPushClientAttrib(GL_CLIENT_VERTEX_ARRAY_BIT);

    /* We can use the RxHeap here because
     * we are inside a pipeline execution */
    bumpUVs = (RwTexCoords*)
        RxHeapAlloc(heap,
                    vertices->numUsed * sizeof(RwTexCoords));

    CalculatePerturbedUVs(heap, vertices, indices,
                          meshState, bumpUVs);

    /* Override uvs set from the retained mode pipe */
    glTexCoordPointer(2, GL_FLOAT, 0, bumpUVs);

    if (gl.VertexArrayRangeNV)
    {
        glDisableClientState(GL_VERTEX_ARRAY_RANGE_NV);
    }

    RWASSERT(NULL != OldSubmitCallBack);
    OldSubmitCallBack(packet, heap, vertices, indices,
                      meshState);

    if (gl.VertexArrayRangeNV)
    {
        glEnableClientState(GL_VERTEX_ARRAY_RANGE_NV);
    }

    RxHeapFree(heap, bumpUVs);

    glPopClientAttrib();    /* Restore vertex array state */
    glPopAttrib();          /* Restore blending */
#else
    RwRenderStateGet(rwRENDERSTATEVERTEXALPHAENABLE, (void *)&alphaEnable);
    RwRenderStateGet(rwRENDERSTATESRCBLEND, (void *)&srcBlend);
    RwRenderStateGet(rwRENDERSTATEDESTBLEND, (void *)&destBlend);

    RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void *)TRUE);
    RwRenderStateSet(rwRENDERSTATESRCBLEND,
                     (void *)rwBLENDINVSRCALPHA);
    RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void *)rwBLENDZERO);

    ERR_WRAP(IDirect3DDevice7_GetRenderState(CurrDevice,
                                             D3DRENDERSTATE_ALPHATESTENABLE,
                                             &alphaTest));
    ERR_WRAP(IDirect3DDevice7_SetRenderState(CurrDevice,
                                             D3DRENDERSTATE_ALPHATESTENABLE,
                                             FALSE));

    RWASSERT(NULL != OldSubmitCallBack);
    OldSubmitCallBack(packet, heap, vertices, indices,
                      meshState);

    RwRenderStateSet(rwRENDERSTATESRCBLEND,
                     (void *)rwBLENDSRCALPHA);
    RwRenderStateSet(rwRENDERSTATEDESTBLEND,
                     (void *)rwBLENDONE);

    /* We can use the RxHeap here because
     * we are inside a pipeline execution */
    bumpUVs = (RwTexCoords*)
        RxHeapAlloc(heap,
                    vertices->numUsed * sizeof(RwTexCoords));

    CalculatePerturbedUVs(heap, vertices, indices,
                          meshState, bumpUVs);

    memset(&stridedVerts, 0, sizeof(D3DDRAWPRIMITIVESTRIDEDDATA));

    vertsBase = RxClusterGetCursorData(vertices,
                                       RxObjSpace3DVertex);
    stridedVerts.position.lpvData  = vertsBase;
    stridedVerts.position.dwStride = sizeof(RxObjSpace3DVertex);

    normalsBase = &vertsBase->objNormal;
    stridedVerts.normal.lpvData  = normalsBase;
    stridedVerts.normal.dwStride = sizeof(RxObjSpace3DVertex);

    colsBase = &vertsBase->color;
    stridedVerts.diffuse.lpvData  = colsBase;
    stridedVerts.diffuse.dwStride = sizeof(RxObjSpace3DVertex);

    stridedVerts.textureCoords[0].lpvData  = bumpUVs;
    stridedVerts.textureCoords[0].dwStride = sizeof(RwTexCoords);

    if ((NULL != indices) && (indices->numUsed > 0))
    {
        ERR_WRAP(IDirect3DDevice7_DrawIndexedPrimitiveStrided(CurrDevice,
                   PrimConv[meshState->PrimType],
                   D3DFVF_XYZ | D3DFVF_NORMAL | D3DFVF_DIFFUSE | D3DFVF_TEX1,
                   &stridedVerts,
                   meshState->NumVertices,
                   RxClusterGetCursorData(indices, WORD),
                   indices->numUsed,
                   0));
    }
    else
    {
        ERR_WRAP(IDirect3DDevice7_DrawPrimitiveStrided(CurrDevice,
                   PrimConv[meshState->PrimType],
                   D3DFVF_XYZ | D3DFVF_NORMAL | D3DFVF_DIFFUSE | D3DFVF_TEX1,
                   &stridedVerts,
                   meshState->NumVertices,
                   0));
    }

    RxHeapFree(heap, bumpUVs);

    if (alphaTest)
    {
        ERR_WRAP(IDirect3DDevice7_SetRenderState(CurrDevice,
                                                 D3DRENDERSTATE_ALPHATESTENABLE,
                                                 TRUE));
    }

    RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE,
                     (void *)alphaEnable);
    RwRenderStateSet(rwRENDERSTATESRCBLEND, (void *)srcBlend);
    RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void *)destBlend);
#endif

    RWRETURN(TRUE);
}


static RwBool
MultiPassCallBack(RxPacket *packet,
                  RxHeap *heap,
                  RxCluster *vertices,
                  RxCluster *indices,
                  RxMeshStateVector *meshState)
{
    RpMaterial *material;
    rpMatFXMaterialData *matEffectsData;
    RpMatFXMaterialFlags effectType;

    RWFUNCTION(RWSTRING("MultiPassCallBack"));

#ifdef D3D7_DRVMODEL_H
    CurrDevice = (LPDIRECT3DDEVICE7) RwD3DGetCurrentD3DDevice();
#endif

    material       = (RpMaterial*)meshState->SourceObject;
    matEffectsData = *MATFXMATERIALGETDATA(material);
    if (NULL == matEffectsData)
    {
        /* This material hasn't been set up for MatFX so we
         * treat it as is it were set to rpMATFXEFFECTNULL */
        effectType = rpMATFXEFFECTNULL;
    }
    else
    {
        effectType = matEffectsData->flags;
    }

    switch (effectType)
    {
        case rpMATFXEFFECTENVMAP:
        {
            RwBlendFunction srcBlend, destBlend;

            /* First pass is done 'as is' */
            RWASSERT(NULL != OldSubmitCallBack);
            OldSubmitCallBack(packet, heap, vertices, indices, meshState);

            /* Save any state we want to preserve */
            RwRenderStateGet(rwRENDERSTATESRCBLEND,  (void *)&srcBlend);
            RwRenderStateGet(rwRENDERSTATEDESTBLEND, (void *)&destBlend);

            DoEnvMapPass(packet, heap, vertices, indices, meshState, rpSECONDPASS);

            /* Restore our old blending modes */
            RwRenderStateSet(rwRENDERSTATESRCBLEND, (void *)srcBlend);
            RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void *)destBlend);
            break;
        }
        case rpMATFXEFFECTBUMPMAP:
        {
            RwBlendFunction srcBlend, destBlend;

            /* Save any state we want to preserve */
            RwRenderStateGet(rwRENDERSTATESRCBLEND,  (void *)&srcBlend);
            RwRenderStateGet(rwRENDERSTATEDESTBLEND, (void *)&destBlend);

            DoBumpMapPass(packet, heap, vertices, indices, meshState);

            /* Restore our old blending modes */
            RwRenderStateSet(rwRENDERSTATESRCBLEND, (void *)srcBlend);
            RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void *)destBlend);
            break;
        }
        case rpMATFXEFFECTBUMPENVMAP:
        {
            RwBlendFunction srcBlend, destBlend;

            /* Save any state we want to preserve */
            RwRenderStateGet(rwRENDERSTATESRCBLEND,  (void *)&srcBlend);
            RwRenderStateGet(rwRENDERSTATEDESTBLEND, (void *)&destBlend);

            DoBumpMapPass(packet, heap, vertices, indices, meshState);
            DoEnvMapPass(packet, heap, vertices, indices, meshState, rpTHIRDPASS);

            /* Restore our old blending modes */
            RwRenderStateSet(rwRENDERSTATESRCBLEND, (void *)srcBlend);
            RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void *)destBlend);
            break;
        }
        case rpMATFXEFFECTDUAL:
        {
            RwTexture *dualTex;
            RwBlendFunction oldSrcBlend, oldDstBlend, srcBlend, dstBlend;
            MatFXDualData     *dualData =
                MATFXOPENGLDUALGETDATA(material, rpSECONDPASS);

            RWASSERT(NULL != OldSubmitCallBack);
            OldSubmitCallBack(packet, heap, vertices, indices,
                              meshState);

            dualTex  = dualData->texture;
            srcBlend = dualData->srcBlendMode;
            dstBlend = dualData->dstBlendMode;

            /* Set second pass texture and blend modes */
            if (dualTex)
            {
                RwRaster    *raster;
                RwTextureFilterMode filterMode;
                RwTextureAddressMode addressingU;
                RwTextureAddressMode addressingV;

                raster = RwTextureGetRaster(dualTex);
                RwRenderStateSet(rwRENDERSTATETEXTURERASTER,
                                 (void *)raster);
                filterMode = RwTextureGetFilterMode(dualTex);
                RwRenderStateSet(rwRENDERSTATETEXTUREFILTER,
                                 (void *)filterMode);

                addressingU = RwTextureGetAddressingU(dualTex);
                RwRenderStateSet(rwRENDERSTATETEXTUREADDRESSU,
                                 (void *)addressingU);
                addressingV = RwTextureGetAddressingV(dualTex);
                RwRenderStateSet(rwRENDERSTATETEXTUREADDRESSV,
                                 (void *)addressingV);
            }

            RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void *)TRUE);

            RwRenderStateGet(rwRENDERSTATESRCBLEND, (void *)&oldSrcBlend);
            RwRenderStateGet(rwRENDERSTATEDESTBLEND, (void *)&oldDstBlend);
            RwRenderStateSet(rwRENDERSTATESRCBLEND, (void *)srcBlend);
            RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void *)dstBlend);

#ifdef OPENGL_DRVMODEL_H
            {
                RxCluster *extraUVs;
                const GLvoid *uvs;

                extraUVs = RxClusterLockRead(packet, 6);
                RWASSERT(NULL != extraUVs);

                uvs = RxClusterGetCursorData(extraUVs, const GLvoid);
                RWASSERT(NULL != uvs);

                glTexCoordPointer(2, GL_FLOAT, 0, uvs);

                RWASSERT(NULL != OldSubmitCallBack);
                OldSubmitCallBack(packet, heap, vertices, indices,
                              meshState);
            }
#else
            ERR_WRAP(IDirect3DDevice7_SetTextureStageState(CurrDevice,
                                       0,
                                       D3DTSS_TEXCOORDINDEX,
                                       1));

            RWASSERT(NULL != OldSubmitCallBack);
            OldSubmitCallBack(packet, heap, vertices, indices,
                          meshState);

            ERR_WRAP(IDirect3DDevice7_SetTextureStageState(CurrDevice,
                                       0,
                                       D3DTSS_TEXCOORDINDEX,
                                       0));
#endif

            RwRenderStateSet(rwRENDERSTATESRCBLEND, (void *)oldSrcBlend);
            RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void *)oldDstBlend);

            break;
        }
        case rpMATFXEFFECTNULL:
        default:
        {
            /* Ordinary submit */
            RWASSERT(NULL != OldSubmitCallBack);
            OldSubmitCallBack(packet, heap, vertices, indices, meshState);
            break;
        }
    }

    RWRETURN(TRUE);
}

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

    MatFXMaterialPipe = RxPipelineCreate();
    if (MatFXMaterialPipe)
    {
        RxNodeDefinition *nodeDef;
        RxLockedPipe *lpipe;
        RxPipelineNode *submitNode;

        if ((lpipe = RxPipelineLock(MatFXMaterialPipe)) == NULL)
        {
            _rpMatFXPipelinesDestroy();
            RWRETURN(FALSE);
        }

#ifdef OPENGL_DRVMODEL_H
        nodeDef = RxNodeDefinitionGetOpenGLSubmitWithLight();
#else
        nodeDef = RxNodeDefinitionGetDX7SubmitWithLight();
#endif

        lpipe = RxLockedPipeAddFragment(lpipe, NULL, nodeDef, NULL);
        lpipe = RxLockedPipeUnlock(lpipe);

        RWASSERT(MatFXMaterialPipe == (RxPipeline *)lpipe);

        /* Now configure the submit to make it suitable for multiple passes */
        submitNode = RxPipelineFindNodeByName(MatFXMaterialPipe,
                              nodeDef->name, NULL,  NULL);

#ifdef OPENGL_DRVMODEL_H
        OldSubmitCallBack = RxOpenGLSubmitGetCallBack(submitNode);
        RxOpenGLSubmitSetCallBack(submitNode, MultiPassCallBack);
#else
        OldSubmitCallBack = RxDX7SubmitGetCallBack(submitNode);
        RxDX7SubmitSetCallBack(submitNode, MultiPassCallBack);

        /* Finally initialise RW-> D3D primitive conversion */
        memset(PrimConv, 0, sizeof(PrimConv));
        PrimConv[rwPRIMTYPELINELIST] = (RwInt32) D3DPT_LINELIST;
        PrimConv[rwPRIMTYPEPOLYLINE] = (RwInt32) D3DPT_LINESTRIP;
        PrimConv[rwPRIMTYPETRILIST] = (RwInt32) D3DPT_TRIANGLELIST;
        PrimConv[rwPRIMTYPETRISTRIP] = (RwInt32) D3DPT_TRIANGLESTRIP;
        PrimConv[rwPRIMTYPETRIFAN] = (RwInt32) D3DPT_TRIANGLEFAN;
#endif

        RWRETURN(TRUE);
    }

    RWRETURN(FALSE);
}

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

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

    RWRETURN(TRUE);
}

/*---------------------- ATTACH PIPELINE -------------------------------*/
RpAtomic *
_rpMatFXPipelineAtomicSetup(RpAtomic * atomic)
{
    RWFUNCTION(RWSTRING("_rpMatFXPipelineAtomicSetup"));
    RWRETURN(atomic);
}

RpWorldSector *
_rpMatFXPipelineWorldSectorSetup(RpWorldSector * worldSector)
{
    RWFUNCTION(RWSTRING("_rpMatFXPipelineWorldSectorSetup"));
    RWRETURN(worldSector);
}

RpMaterial *
_rpMatFXEnvMapEnable(RpMaterial *material)
{
    RWFUNCTION(RWSTRING("_rpMatFXEnvMapEnable"));
    RWASSERT(material);

    RpMaterialSetRenderPipeline(material, MatFXMaterialPipe);

    RWRETURN(material);
}

RpMaterial *
_rpMatFXBumpMapEnable(RpMaterial *material)
{
    RWFUNCTION(RWSTRING("_rpMatFXBumpMapEnable"));
    RWASSERT(material);

    RpMaterialSetRenderPipeline(material, MatFXMaterialPipe);

    RWRETURN(material);
}

RpMaterial *
_rpMatFXDualEnable(RpMaterial *material)
{
    RWFUNCTION(RWSTRING("_rpMatFXDualEnable"));
    RWASSERT(material);

    RpMaterialSetRenderPipeline(material, MatFXMaterialPipe);

    RWRETURN(material);
}

RwBool
_rpMatFXSetupDualRenderState(MatFXDualData * __RWUNUSEDRELEASE__ dualData,
                             RwRenderState __RWUNUSED__ nState)
{
    RWFUNCTION(RWSTRING("_rpMatFXSetupDualRenderState"));
    RWASSERT(dualData);
    RWRETURN(TRUE);
}

RwTexture *
_rpMatFXSetupBumpMapTexture(const RwTexture *baseTexture,
                            const RwTexture *effectTexture)
{
    RwTexture *texture;
    RWFUNCTION(RWSTRING("_rpMatFXSetupBumpMapTexture"));

    texture = _rpMatFXTextureMaskCreate(baseTexture, effectTexture);

    RWRETURN(texture);
}
