/*----------------------------------------------------------------------*
 *                                                                      *
 * Module  :                                                            *
 *                                                                      *
 * Purpose :                                                            *
 *                                                                      *
 * FX      :                                                            *
 *----------------------------------------------------------------------*/

/**
 * \defgroup rpmatfxgcn RpMatFx on GameCube
 * \ingroup rpmatfx
 * 
 * Material Effects on GameCube
 */

/** 
 * \ingroup rpmatfxgcn
 *
 * \page rpmatfxgcnoverview Overview of RpMatFX on GameCube
 * 
 * RpMatFX on GameCube is provided for PS2 compatibility. It is in no way a
 * GameCube optimized pipeline.
 * No rework of art resources is needed to run RpMatFX on GameCube.
 *
 * \par Environment Mapping
 *
 * Environment mapping is done in two passes. The first pass renders the
 * geometry normally. The second pass renders the environment map using
 * Hardware generated texture coordinates.
 *
 * \par Bump Mapping
 *
 * Bump Mapping is currently not implemented on GameCube. This will be fixed
 * shortly in a forthcoming release.
 *
 * \par Bump Environment Mapping
 *
 * Bump Environment Mapping is currently not implemented on GameCube. This will
 * be fixed shortly in a forthcoming release.
 *
 * \par Dual Pass
 *
 * Dual Pass on GameCube uses a two pass approach.
 *
 */

/*----------------------------------------------------------------------*
 *-   Includes                                                         -*
 *----------------------------------------------------------------------*/

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

/*==== GC libs includes =====*/
#include"dolphin.h"

/*==== RW libs includes =====*/
#include "rpplugin.h"
#include <rpdbgerr.h>
#include <rwcore.h>
#include <rpmatfx.h>

#include "effectpipes.h"


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

/*----------------------------------------------------------------------*
 *-   Defines                                                          -*
 *----------------------------------------------------------------------*/

#define MATFXGCNENVMAPGETDATA(material)                                 \
  &((*((rpMatFXMaterialData **)                                         \
       (((RwUInt8 *)material) +                                         \
        MatFXMaterialDataOffset)))->data[rpSECONDPASS].data.envMap)

#define MATFXGCNBUMPMAPGETDATA(material)                                \
  &((*((rpMatFXMaterialData **)                                         \
       (((RwUInt8 *)material) +                                         \
        MatFXMaterialDataOffset)))->data[rpSECONDPASS].data.bumpMap)

#define MATFXGCNDUALPASSGETDATA(material)                               \
  &((*((rpMatFXMaterialData **)                                         \
       (((RwUInt8 *)material) +                                         \
        MatFXMaterialDataOffset)))->data[rpSECONDPASS].data.dual)

#define MATFXGCNBUMPENVMAPGETDATA(material)                             \
  &((*((rpMatFXMaterialData **)                                         \
       (((RwUInt8 *)material) +                                         \
        MatFXMaterialDataOffset)))->data[rpTHIRDPASS].data.envMap)

/*----------------------------------------------------------------------*
 *-   Local/static Globals                                             -*
 *----------------------------------------------------------------------*/
static RxPipeline   *_RpMatFXAtomicPipe = NULL;
static RxPipeline   *_RpMatFXWorldSectorPipe = NULL;

static RpGeometry   _RwOwnerGeometry;

/*----------------------------------------------------------------------*
 *-   Globals across program                                           -*
 *----------------------------------------------------------------------*/

/*----------------------------------------------------------------------*
 *-   Functions                                                        -*
 *----------------------------------------------------------------------*/

static void
_rpGCMatFxCalculatePerturbedUVs(RwTexCoords *bumpUVs,
                                RwObject *object,
                                MatFXBumpMapData *bumpMapData,
                                RpMesh *mesh,
                                RwV3d *vertices,
                                RwV3d *normals,
                                RwTexCoords *texCoords,
                                RwInt32 numVertices,
                                RwInt32 flags)
{
    RwUInt8 *processedFlags;

    RWFUNCTION(RWSTRING("_rpGCMatFxCalculatePerturbedUVs"));

    processedFlags = RwMalloc(numVertices);
    memset(processedFlags, 0, numVertices);

    if (processedFlags)
    {
        RwInt32         i;
        RwReal          factor;
        RwV3d           lightPosObj;
        RwFrame         *bumpFrame;
        RwV3d           *lightPos;
        RxVertexIndex   *indices;

        indices = mesh->indices;

        bumpFrame   = bumpMapData->frame;
        factor      = bumpMapData->coef * bumpMapData->invBumpWidth;

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

        /*
         * Calc light pos in object space
         */
        lightPos = RwMatrixGetPos(RwFrameGetLTM(bumpFrame));

        if (rpATOMIC == RwObjectGetType(object))
        {
            RwMatrix    worldToObj;

            RwMatrixInvert(&worldToObj,
                RwFrameGetLTM(RpAtomicGetFrame((RpAtomic *)object)));
            RwV3dTransformPoints(&lightPosObj, lightPos, 1, &worldToObj);
        }
        else
        {
            lightPosObj = *lightPos;
        }

        for (i = 0; i < mesh->numIndices; i++)
        {
            if (processedFlags[indices[i]] == FALSE)
            {
                RwV3d       *vert1, *vert2, *vert3;
                RwV3d       *norm1;
                RwTexCoords *uv1, *uv2, *uv3;
                RwV3d       l;

                if (rwPRIMTYPETRISTRIP & flags)
                {
                    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 = &vertices[indices[i1]];
                    vert2 = &vertices[indices[i2]];
                    vert3 = &vertices[indices[i3]];

                    norm1 = &normals[indices[i1]];

                    uv1 = &texCoords[indices[i1]];
                    uv2 = &texCoords[indices[i2]];
                    uv3 = &texCoords[indices[i3]];
                }
                else
                {
                    vert1 = &vertices[indices[i]];
                    vert2 = &vertices[indices[((i / 3) * 3) + (i + 1) % 3]];
                    vert3 = &vertices[indices[((i / 3) * 3) + (i + 2) % 3]];

                    norm1 = &normals[indices[i]];

                    uv1 = &texCoords[indices[i]];
                    uv2 = &texCoords[indices[((i / 3) * 3) + (i + 1) % 3]];
                    uv3 = &texCoords[indices[((i / 3) * 3) + (i + 2) % 3]];
                }

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

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

                    /* A nice little algorithm to find the tangent vector */
                    RwV3dSub(&temp1, vert2, vert1);
                    RwV3dSub(&temp2, vert3, vert1);

                    RwV3dScale(&temp1, &temp1, uv3->v - uv1->v);
                    RwV3dScale(&temp2, &temp2, uv2->v - uv1->v);

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

                    /*
                     * 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[indices[i]].u = uv1->u - (shift.x * factor);
                    bumpUVs[indices[i]].v = uv1->v - (shift.y * factor);
                }

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

    RwFree(processedFlags);

    RWRETURNVOID();
}

/****************************************************************************
 _rpGCMatFxEnvMatrixSetup
 
 Purpose:   Set vertex descriptor and transform matrix.
 
 On entry:  object - The object to be rendered.
            frame - Additional frame to transform by.
                
 On exit:
 */
static void
_rpGCMatFxEnvMatrixSetup(RwObject *object, RwFrame *frame)
{
    RwMatrix    tmpMatrix;
    RwMatrix    *matrix;
    Mtx         texMtx;

    RWFUNCTION(RWSTRING("_rpGCMatFxTEVTexSetup"));

    tmpMatrix.flags = rwMATRIXINTERNALIDENTITY | rwMATRIXTYPEORTHONORMAL;
    matrix = &tmpMatrix;

    /*
     * Calculate the transform matrix.
     */
    if (rpATOMIC == RwObjectGetType(object))
    {
        if (frame)
        {
            RwMatrix    invFrameLTM;

            invFrameLTM.flags = rwMATRIXINTERNALIDENTITY | rwMATRIXTYPEORTHONORMAL;
            RwMatrixInvert(&invFrameLTM, RwFrameGetLTM(frame));

            /* Calc normals transform matrix, world space */
            RwMatrixMultiply(&tmpMatrix, &invFrameLTM,
                             RwFrameGetLTM(RpAtomicGetFrame((RpAtomic *)object)));
        }
        else
        {
            /* Calc normals transform matrix, world space */
            RwMatrixMultiply(&tmpMatrix, &_RwDlInvCamLTM,
                             RwFrameGetLTM(RpAtomicGetFrame((RpAtomic *)object)));
        }
    }
    else
    {
        if (frame)
        {
            RwMatrixInvert(&tmpMatrix, RwFrameGetLTM(frame));
        }
        else
        {
            matrix = &_RwDlInvCamLTM;
        }
    }

    texMtx[0][0] = -matrix->right.x * -0.5f;
    texMtx[0][1] = -matrix->up.x * -0.5f;
    texMtx[0][2] = -matrix->at.x * -0.5f;
    texMtx[0][3] = -0.5f;

    texMtx[1][0] =  matrix->right.y * -0.5f;
    texMtx[1][1] =  matrix->up.y * -0.5f;
    texMtx[1][2] =  matrix->at.y * -0.5f;
    texMtx[1][3] =  0.5f;

    texMtx[2][0] =  0.0f;
    texMtx[2][1] =  0.0f;
    texMtx[2][2] =  0.0f;
    texMtx[2][3] =  1.0f;

    GXLoadTexMtxImm(texMtx, GX_TEXMTX0, GX_MTX3x4);

    RWRETURNVOID();
}

/****************************************************************************
 _rpGCMatFxRenderSetup
 
 Purpose:   Set vertex descriptor and transform matrix.
 
 On entry:  object - The object to be rendered.
            pipeData - Pipeline data for the object.
                
 On exit:
 */
static void
_rpGCMatFxRenderSetup(void *object, RxGameCubePipeData *pipeData)
{
    RxGameCubeResEntryHeader    *resEntryHeader;
    RxGameCubeInstanceData      *instancedData;
    RwMatrix                    tmpMtx;
    RwMatrix                    *transMtx;
    Mtx                         mtx;

    RWFUNCTION(RWSTRING("_rpGCMatFxRenderSetup"));

    resEntryHeader = (RxGameCubeResEntryHeader *)(pipeData->resEntry + 1);
    RWASSERT(resEntryHeader);

    instancedData = (RxGameCubeInstanceData *)(resEntryHeader + 1);
    RWASSERT(instancedData);

    /*
     * Stamp the instanced data as being used this frame.
     */
    resEntryHeader->token = _rwDlTokenGetCurrent();

    /*
     * Calculate the transform matrix.
     */
    if (rpATOMIC == RwObjectGetType(object))
    {
        /* Calculate the transform matrix */
        tmpMtx.flags = rwMATRIXINTERNALIDENTITY | rwMATRIXTYPEORTHONORMAL;
        RwMatrixMultiply(&tmpMtx,
                         RwFrameGetLTM(RpAtomicGetFrame((RpAtomic *)object)),
                         &_RwDlInvCamLTM);
        transMtx = &tmpMtx;
    }
    else
    {
        transMtx = &_RwDlInvCamLTM;
    }

    /*
	 * Set the transform matrix
	 */

    mtx[0][0] = -transMtx->right.x;
    mtx[0][1] = -transMtx->up.x;
    mtx[0][2] = -transMtx->at.x;
    mtx[0][3] = -transMtx->pos.x;

    mtx[1][0] =  transMtx->right.y;
    mtx[1][1] =  transMtx->up.y;
    mtx[1][2] =  transMtx->at.y;
    mtx[1][3] =  transMtx->pos.y;

    mtx[2][0] = -transMtx->right.z;
    mtx[2][1] = -transMtx->up.z;
    mtx[2][2] = -transMtx->at.z;
    mtx[2][3] = -transMtx->pos.z;

    GXLoadPosMtxImm(mtx, GX_PNMTX0);

    if (rxGEOMETRY_NORMALS & pipeData->flags)
	{
        GXLoadNrmMtxImm(mtx, GX_PNMTX0);
	}

    /*
     * Vertex setup
     */

    /* Set vertex descriptor to GX_NONE */
    GXClearVtxDesc();

    /* Position */
    GXSetVtxDesc(GX_VA_POS, (GXAttrType)resEntryHeader->indexType);
    GXSetVtxAttrFmt(GX_VTXFMT0,
                    GX_VA_POS,
                    GX_POS_XYZ,
                    GX_F32,
                    0);
    RWASSERT(NULL != resEntryHeader->positions);
    GXSetArray(GX_VA_POS, resEntryHeader->positions, sizeof(RwV3d));

    /* Normals */
    if (rxGEOMETRY_NORMALS & pipeData->flags)
    {
        GXSetVtxDesc(GX_VA_NRM, (GXAttrType)resEntryHeader->indexType);
        GXSetVtxAttrFmt(GX_VTXFMT0,
                        GX_VA_NRM,
                        GX_NRM_XYZ,
                        GX_F32,
                        0);
        RWASSERT(NULL != resEntryHeader->normals);
        GXSetArray(GX_VA_NRM, resEntryHeader->normals, sizeof(RwV3d));
    }

    /* Pre-lighting */
    if (rxGEOMETRY_PRELIT & pipeData->flags)
    {
        /* Color */
        GXSetVtxDesc(GX_VA_CLR0, (GXAttrType)resEntryHeader->indexType);
        GXSetVtxAttrFmt(GX_VTXFMT0,
                        GX_VA_CLR0,
                        GX_CLR_RGBA,
                        GX_RGBA8,
                        0);
        RWASSERT(NULL != resEntryHeader->preLight);
        GXSetArray(GX_VA_CLR0, resEntryHeader->preLight, sizeof(RwRGBA));
    }

    /* Texture coordinates */
    if (rxGEOMETRY_TEXTURED & pipeData->flags)
    {
        /* UVs */
        GXSetVtxDesc(GX_VA_TEX0, (GXAttrType)resEntryHeader->indexType);
        GXSetVtxAttrFmt(GX_VTXFMT0,
                        GX_VA_TEX0,
                        GX_TEX_ST,
                        GX_F32,
                        0);
        RWASSERT(NULL != resEntryHeader->texCoords[0]);
        GXSetArray(GX_VA_TEX0, resEntryHeader->texCoords[0], sizeof(RwTexCoords));
    }
    else if (rxGEOMETRY_TEXTURED2 & pipeData->flags)
    {
        /* UV1s */
        GXSetVtxDesc(GX_VA_TEX0, (GXAttrType)resEntryHeader->indexType);
        GXSetVtxAttrFmt(GX_VTXFMT0,
                        GX_VA_TEX0,
                        GX_TEX_ST,
                        GX_F32,
                        0);
        RWASSERT(NULL != resEntryHeader->texCoords[0]);
        GXSetArray(GX_VA_TEX0, resEntryHeader->texCoords[0], sizeof(RwTexCoords));

        /* UV2s */
        GXSetVtxDesc(GX_VA_TEX1, (GXAttrType)resEntryHeader->indexType);
        GXSetVtxAttrFmt(GX_VTXFMT0,
                        GX_VA_TEX1,
                        GX_TEX_ST,
                        GX_F32,
                        0);
        RWASSERT(NULL != resEntryHeader->texCoords[1]);
        GXSetArray(GX_VA_TEX1, resEntryHeader->texCoords[1], sizeof(RwTexCoords));
    }

    RWRETURNVOID();
}

/****************************************************************************
 _rpGCMatFXRenderCallback
 
 Purpose:   Renders a matFX object.
 
 On entry:  object - The object to be rendered.
            pipeData - Pipeline data for the object.
                
 On exit:
 */
static void *
_rpGCMatFXRenderCallback(void *object, RxGameCubePipeData *pipeData)
{
    RxGameCubeResEntryHeader    *resEntryHeader;
    RxGameCubeInstanceData      *instancedData;
    RpMeshHeader                *meshHeader;
    RpMesh                      *mesh;
    RwResEntry                  *resEntry = NULL;
    RwTexCoords                 *bumpTexCoords = NULL;
    RwInt32                     numMeshes, numVertices, flags;

	RWFUNCTION(RWSTRING("_rpGCMatFXRenderCallback"));

    resEntryHeader = (RxGameCubeResEntryHeader *)(pipeData->resEntry + 1);
    RWASSERT(resEntryHeader);

    instancedData = (RxGameCubeInstanceData *)(resEntryHeader + 1);
    RWASSERT(instancedData);

    /* Exposed from gctevsetup.h into rpworld.h */
    _rxGCTEVSetup(pipeData);

    _rpGCMatFxRenderSetup(object, pipeData);

    if (rpATOMIC == RwObjectGetType(object))
    {
        RpGeometry  *geom;

        geom = RpAtomicGetGeometry((RpAtomic *)object);
        numVertices = RpGeometryGetNumVertices(geom);
        flags = RpGeometryGetFlags(geom);

        meshHeader = (RpAtomicGetGeometry((RpAtomic *)object))->mesh;
    }
    else
    {
        numVertices = RpWorldSectorGetNumVertices((RpWorldSector *)object);
        flags = RpWorldGetFlags((RpWorld *)RWSRCGLOBAL(curWorld));

        meshHeader = ((RpWorldSector *)object)->mesh;
    }

    mesh = (RpMesh *)(meshHeader + 1);
    numMeshes = resEntryHeader->numMeshes;
    while (numMeshes--)
    {
        rpMatFXMaterialData *matFXData;

        matFXData = *MATFXMATERIALGETDATA(instancedData->material);
        if (matFXData)
        {
            /*
             * Channel setup
             */
            if (rxGEOMETRY_MODULATE & pipeData->flags)
            {
                if (pipeData->numLights > 0)
                {
                    /*
                     * Set material color in GXSetChanCtrl() to RpMaterial color.
                     */
                    GXSetChanMatColor(GX_COLOR0A0,
                                      *(GXColor *)&instancedData->material->color);

                    if (pipeData->dirLightMask && pipeData->spotLightMask)
                    {
                        GXSetChanMatColor(GX_COLOR1A1,
                                          *(GXColor *)&instancedData->material->color);
                    }
                }
                else if (TRUE == pipeData->ambientLight)
                {
                    GXColor     color;
                    RwReal      ambientCoef;
                    RwRGBA      *matColor;
                    RwRGBAReal  *ambColor;

                    /*
                     * Modulate ambient color with material color per material and set
                     * as material color in GXSetChanCtrl().
                     */
                    ambientCoef = instancedData->material->surfaceProps.ambient;
                    matColor = &instancedData->material->color;
                    ambColor = &pipeData->ambientLightColor;

                    color.r = (RwUInt8)((RwReal)matColor->red * ambColor->red * ambientCoef);
                    color.g = (RwUInt8)((RwReal)matColor->green * ambColor->green * ambientCoef);
                    color.b = (RwUInt8)((RwReal)matColor->blue * ambColor->blue * ambientCoef);
                    color.a = matColor->alpha;

                    GXSetChanMatColor(GX_COLOR0A0, color);
                }
            }

            if ((pipeData->numLights > 0) && (TRUE == pipeData->ambientLight))
            {
                GXColor     color;
                RwReal      ambientCoef;
                RwRGBAReal  *ambColor;

                /*
                 * Set ambient color in GXSetChanCtrl() to accumulated ambient light.
                 */
                ambientCoef = instancedData->material->surfaceProps.ambient;
                ambColor = &pipeData->ambientLightColor;

                color.r = (RwUInt8)(ambColor->red * ambientCoef * 255.0f);
                color.g = (RwUInt8)(ambColor->green * ambientCoef * 255.0f);
                color.b = (RwUInt8)(ambColor->blue * ambientCoef * 255.0f);
                color.a = 0xFF;

                GXSetChanAmbColor(GX_COLOR0A0, color);
            }

            /*
             * Effects, setup & submit.
             */
            switch (matFXData->flags)
            {
            case rpMATFXEFFECTENVMAP:
                {
                    MatFXEnvMapData *envMapData;
                    RwBlendFunction srcBlend, dstBlend;
                    static GXColor  shiney = {0xFF, 0xFF, 0xFF, 0xFF};

                    envMapData = MATFXGCNENVMAPGETDATA(instancedData->material);

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

                    /*
                     * First pass
                     */
                    _rwDlRenderStateTexture(instancedData->material->texture);
                    GXCallDisplayList(instancedData->displayList, instancedData->size);

                    /*
                     * Second pass
                     */
                    RwRenderStateSet(rwRENDERSTATESRCBLEND, (void *)rwBLENDSRCALPHA);
                    RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void *)rwBLENDONE);

                    GXSetChanCtrl(GX_COLOR0A0,
                                  GX_FALSE,
                                  GX_SRC_REG,   /* Ambient */
                                  GX_SRC_REG,   /* Material */
                                  0,
                                  GX_DF_CLAMP,
                                  GX_AF_NONE);

                    GXSetChanCtrl(GX_COLOR1A1,
                                  GX_FALSE,
                                  GX_SRC_REG,   /* Ambient */
                                  GX_SRC_REG,   /* Material */
                                  0,
                                  GX_DF_CLAMP,
                                  GX_AF_NONE);

                    shiney.a = (RwUInt8)(envMapData->coef * 255.0f);

                    GXSetChanMatColor(GX_COLOR0A0, shiney);

                    GXSetNumTexGens(1);
                    GXSetTexCoordGen(GX_TEXCOORD0, GX_TG_MTX3x4, GX_TG_NRM, GX_TEXMTX0);

                    /* GXSetTevOp is a convenience function, see docs */
                    GXSetTevOp(GX_TEVSTAGE0, GX_MODULATE);
                    GXSetTevOrder(GX_TEVSTAGE0, GX_TEXCOORD0, GX_TEXMAP0, GX_COLOR0A0);

                    _rwDlRenderStateTexture(envMapData->texture);
                    _rpGCMatFxEnvMatrixSetup(object, envMapData->frame);
                    GXCallDisplayList(instancedData->displayList, instancedData->size);

                    RwRenderStateSet(rwRENDERSTATESRCBLEND, (void *)srcBlend);
                    RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void *)dstBlend);

                    /*
                     * Need to do a full reset of the TEV now.
                     */
                    /* Exposed from gctevsetup.h into rpworld.h */
                    _rxGCTEVSetup(pipeData);
                }
                break;

            case rpMATFXEFFECTDUAL:
                {
                    MatFXDualData   *dualData;
                    RwBlendFunction srcBlend, dstBlend;

                    dualData = MATFXGCNDUALPASSGETDATA(instancedData->material);

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

                    /*
                     * First pass
                     */
                    _rwDlRenderStateTexture(instancedData->material->texture);
                    GXCallDisplayList(instancedData->displayList, instancedData->size);

                    /*
                     * Second pass
                     */
                    RwRenderStateSet(rwRENDERSTATESRCBLEND, (void *)dualData->srcBlendMode);
                    RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void *)dualData->dstBlendMode);

                    /* Reset the UV array for the second pass */
                    if (rxGEOMETRY_TEXTURED2 & pipeData->flags)
                    {
                        GXSetArray(GX_VA_TEX0, resEntryHeader->texCoords[1], sizeof(RwTexCoords));
                    }

                    _rwDlRenderStateTexture(dualData->texture);
                    GXCallDisplayList(instancedData->displayList, instancedData->size);

                    RwRenderStateSet(rwRENDERSTATESRCBLEND, (void *)srcBlend);
                    RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void *)dstBlend);
                }
                break;

            case rpMATFXEFFECTBUMPMAP:
                {
                    MatFXBumpMapData    *bumpMapData;
                    RwBlendFunction     srcBlend, dstBlend;

                    bumpMapData = MATFXGCNBUMPMAPGETDATA(instancedData->material);

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

                    /*
                     * First pass
                     */
                    RwRenderStateSet(rwRENDERSTATESRCBLEND, (void *)rwBLENDINVSRCALPHA);
                    RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void *)rwBLENDZERO);

                    _rwDlRenderStateTexture(bumpMapData->texture);
                    GXSetAlphaCompare(GX_ALWAYS, 0, GX_AOP_AND, GX_ALWAYS, 0);

                    /* Z Buffer before texturing */
                    GXSetZCompLoc(GX_TRUE);

                    GXCallDisplayList(instancedData->displayList, instancedData->size);

                    /*
                     * Second pass
                     */
                    RwRenderStateSet(rwRENDERSTATESRCBLEND, (void *)rwBLENDSRCALPHA);
                    RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void *)rwBLENDONE);

                    if (NULL == resEntry)
                    {
                        RwInt32 size;

                        size = sizeof(RwResEntry);
                        size = ((size + 0x1F) & ~0x1F) - size;
                        size += sizeof(RwTexCoords) * numVertices;

                        resEntry = RwResourcesAllocateResEntry(&_RwOwnerGeometry,
                                                               NULL,
                                                               size,
                                                               _rxGCResEntryWaitDone);

                        bumpTexCoords = (RwTexCoords *)((((RwUInt32)(resEntry + 1)) + 0x1F) & ~0x1F);
                    }

                    /* Calculate perturbed UV's for all vertices not just this materials */
                    _rpGCMatFxCalculatePerturbedUVs(bumpTexCoords,
                                                    object,
                                                    bumpMapData,
                                                    mesh,
                                                    resEntryHeader->positions,
                                                    resEntryHeader->normals,
                                                    resEntryHeader->texCoords[0],
                                                    numVertices,
                                                    flags);

                    /* Flush the memory range from d-cache */
                    DCFlushRange(bumpTexCoords, sizeof(RwTexCoords) * numVertices);

                    /* Swap in the perturded UV array */
                    GXSetArray(GX_VA_TEX0, bumpTexCoords, sizeof(RwTexCoords));
                    GXCallDisplayList(instancedData->displayList, instancedData->size);
                    
                    /* Swap in the old UV array */
                    GXSetArray(GX_VA_TEX0, resEntryHeader->texCoords[0], sizeof(RwTexCoords));

                    GXSetAlphaCompare(GX_GREATER, 0, GX_AOP_AND, GX_GREATER, 0);   

                    /* Z Buffer after texturing */
                    GXSetZCompLoc(GX_FALSE);

                    RwRenderStateSet(rwRENDERSTATESRCBLEND, (void *)srcBlend);
                    RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void *)dstBlend);
                }
                break;

            case rpMATFXEFFECTBUMPENVMAP:
                {
                    MatFXBumpMapData    *bumpMapData;
                    MatFXEnvMapData     *envMapData;
                    RwBlendFunction     srcBlend, dstBlend;
                    static GXColor      shiney = {0xFF, 0xFF, 0xFF, 0xFF};

                    bumpMapData = MATFXGCNBUMPMAPGETDATA(instancedData->material);
                    envMapData = MATFXGCNBUMPENVMAPGETDATA(instancedData->material);

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

                    /*
                     * First pass
                     */
                    RwRenderStateSet(rwRENDERSTATESRCBLEND, (void *)rwBLENDINVSRCALPHA);
                    RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void *)rwBLENDZERO);

                    _rwDlRenderStateTexture(bumpMapData->texture);
                    GXSetAlphaCompare(GX_ALWAYS, 0, GX_AOP_AND, GX_ALWAYS, 0);

                    /* Z Buffer before texturing */
                    GXSetZCompLoc(GX_TRUE);

                    GXCallDisplayList(instancedData->displayList, instancedData->size);

                    /*
                     * Second pass
                     */
                    RwRenderStateSet(rwRENDERSTATESRCBLEND, (void *)rwBLENDSRCALPHA);
                    RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void *)rwBLENDONE);

                    if (NULL == resEntry)
                    {
                        RwInt32 size;

                        size = sizeof(RwResEntry);
                        size = ((size + 0x1F) & ~0x1F) - size;
                        size += sizeof(RwTexCoords) * numVertices;

                        resEntry = RwResourcesAllocateResEntry(&_RwOwnerGeometry,
                                                               NULL,
                                                               size,
                                                               _rxGCResEntryWaitDone);

                        bumpTexCoords = (RwTexCoords *)((((RwUInt32)(resEntry + 1)) + 0x1F) & ~0x1F);
                    }

                    /* Calcualte perturbed UV's for all vertices not just this materials */
                    _rpGCMatFxCalculatePerturbedUVs(bumpTexCoords,
                                                    object,
                                                    bumpMapData,
                                                    mesh,
                                                    resEntryHeader->positions,
                                                    resEntryHeader->normals,
                                                    resEntryHeader->texCoords[0],
                                                    numVertices,
                                                    flags);

                    /* Flush the memory range from d-cache */
                    DCFlushRange(bumpTexCoords, sizeof(RwTexCoords) * numVertices);

                    /* Swap in the perturded UV array */
                    GXSetArray(GX_VA_TEX0, bumpTexCoords, sizeof(RwTexCoords));
                    GXCallDisplayList(instancedData->displayList, instancedData->size);

                    /* Swap in the old UV array */
                    GXSetArray(GX_VA_TEX0, resEntryHeader->texCoords[0], sizeof(RwTexCoords));

                    GXSetAlphaCompare(GX_GREATER, 0, GX_AOP_AND, GX_GREATER, 0);   

                    /* Z Buffer after texturing */
                    GXSetZCompLoc(GX_FALSE);

                    /*
                     * Third pass
                     */
                    RwRenderStateSet(rwRENDERSTATESRCBLEND, (void *)rwBLENDSRCALPHA);
                    RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void *)rwBLENDONE);

                    GXSetChanCtrl(GX_COLOR0A0,
                                  GX_FALSE,
                                  GX_SRC_REG,   /* Ambient */
                                  GX_SRC_REG,   /* Material */
                                  0,
                                  GX_DF_CLAMP,
                                  GX_AF_NONE);

                    GXSetChanCtrl(GX_COLOR1A1,
                                  GX_FALSE,
                                  GX_SRC_REG,   /* Ambient */
                                  GX_SRC_REG,   /* Material */
                                  0,
                                  GX_DF_CLAMP,
                                  GX_AF_NONE);

                    shiney.a = (RwUInt8)(envMapData->coef * 255.0f);

                    GXSetChanMatColor(GX_COLOR0A0, shiney);

                    GXSetNumTexGens(1);
                    GXSetTexCoordGen(GX_TEXCOORD0, GX_TG_MTX3x4, GX_TG_NRM, GX_TEXMTX0);

                    /* GXSetTevOp is a convenience function, see docs */
                    GXSetTevOp(GX_TEVSTAGE0, GX_MODULATE);
                    GXSetTevOrder(GX_TEVSTAGE0, GX_TEXCOORD0, GX_TEXMAP0, GX_COLOR0A0);

                    _rwDlRenderStateTexture(envMapData->texture);
                    _rpGCMatFxEnvMatrixSetup(object, envMapData->frame);
                    GXCallDisplayList(instancedData->displayList, instancedData->size);

                    RwRenderStateSet(rwRENDERSTATESRCBLEND, (void *)srcBlend);
                    RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void *)dstBlend);

                    /*
                     * Need to do a full reset of the TEV now.
                     */
                    /* Exposed from gctevsetup.h into rpworld.h */
                    _rxGCTEVSetup(pipeData);
                }
                break;

            default:
                {
                    _rwDlRenderStateTexture(instancedData->material->texture);
                    GXCallDisplayList(instancedData->displayList, instancedData->size);
                }
                break;
            }
        }
        else
        {
            _rwDlRenderStateTexture(instancedData->material->texture);
            GXCallDisplayList(instancedData->displayList, instancedData->size);
        }

        instancedData++;
        mesh++;
    }

	RWRETURN(object);
}

/*!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!*
 *                                                                           *
 *                     - Create and destory pipelines -                      *
 *                                                                           *
 *!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!*/

/****************************************************************************
 _rpAtomicMatFxPipelineCreate
 
 Purpose:
 
 On entry:
                
 On exit:
 */
static RxPipeline *
_rpAtomicMatFxPipelineCreate(void)
{
    RxPipeline  *pipe;

    RWFUNCTION(RWSTRING("_rpAtomicMatFxPipelineCreate"));

    pipe = RxPipelineCreate();
    if (pipe)
    {
        RxLockedPipe    *lpipe;

        lpipe = RxPipelineLock(pipe);
        if (NULL != lpipe)
        {
            RxNodeDefinition    *instanceNode;

            /*
             * Get the instance node definition
             */
            instanceNode = RxNodeDefinitionGetGameCubeAtomicAllInOne();

            /*
             * Add the node to the pipeline
             */
            lpipe = RxLockedPipeAddFragment(lpipe, NULL, instanceNode, NULL);

            /*
             * Unlock the pipeline
             */
            lpipe = RxLockedPipeUnlock(lpipe);

            RWRETURN(pipe);
        }

        RxPipelineDestroy(pipe);
    }

    RWRETURN(NULL);
}

/****************************************************************************
 _rpWorldSectorMatFxPipelineCreate
 
 Purpose:
 
 On entry:
                
 On exit:
 */
static RxPipeline *
_rpWorldSectorMatFxPipelineCreate(void)
{
    RxPipeline  *pipe;

    RWFUNCTION(RWSTRING("_rpWorldSectorMatFxPipelineCreate"));

    pipe = RxPipelineCreate();
    if (pipe)
    {
        RxLockedPipe    *lpipe;

        lpipe = RxPipelineLock(pipe);
        if (NULL != lpipe)
        {
            RxNodeDefinition    *instanceNode;

            /*
             * Get the instance node definition
             */
            instanceNode = RxNodeDefinitionGetGameCubeWorldSectorAllInOne();

            /*
             * Add the node to the pipeline
             */
            lpipe = RxLockedPipeAddFragment(lpipe, NULL, instanceNode, NULL);

            /*
             * Unlock the pipeline
             */
            lpipe = RxLockedPipeUnlock(lpipe);

            RWRETURN(pipe);
        }

        RxPipelineDestroy(pipe);
    }

    RWRETURN(NULL);
}

RwBool
_rpMatFXPipelinesCreate(void)
{
    RxNodeDefinition    *instanceNode;
    RxPipelineNode      *node;

    RWFUNCTION(RWSTRING("_rpMatFXPipelinesCreate"));

    /*
     * Atomic
     */
    _RpMatFXAtomicPipe = _rpAtomicMatFxPipelineCreate();
    RWASSERT(NULL != _RpMatFXAtomicPipe);

    /* Get the node definition */
    instanceNode = RxNodeDefinitionGetGameCubeAtomicAllInOne();
    RWASSERT(NULL != instanceNode);

    /* Set the pipeline specific data */
    node = RxPipelineFindNodeByName(_RpMatFXAtomicPipe, instanceNode->name, NULL, NULL);
    RWASSERT(NULL != node);

    /* Set the MatFX render callback */
    RxGameCubeAllInOneSetRenderCallBack(node, _rpGCMatFXRenderCallback);

    /*
     * World sector
     */
    _RpMatFXWorldSectorPipe = _rpWorldSectorMatFxPipelineCreate();

    RWASSERT(NULL != _RpMatFXWorldSectorPipe);

    /* Get the node definition */
    instanceNode = RxNodeDefinitionGetGameCubeWorldSectorAllInOne();
    RWASSERT(NULL != instanceNode);

    /* Set the pipeline specific data */
    node = RxPipelineFindNodeByName(_RpMatFXWorldSectorPipe, instanceNode->name, NULL, NULL);
    RWASSERT(NULL != node);

    /* Set the MatFX render callback */
    RxGameCubeAllInOneSetRenderCallBack(node, _rpGCMatFXRenderCallback);

    RWRETURN(TRUE);
}

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

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

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

    RWRETURN(TRUE);
}

/*!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!*
 *                                                                           *
 *                           - Attach pipelines -                            *
 *                                                                           *
 *!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!*/

RpAtomic *
_rpMatFXPipelineAtomicSetup(RpAtomic *atomic)
{
    RWFUNCTION(RWSTRING("_rpMatFXPipelineAtomicSetup"));
    RWASSERT(atomic);

    RpAtomicSetInstancePipeline(atomic, _RpMatFXAtomicPipe);

    RWRETURN(atomic);
}

RpWorldSector *
_rpMatFXPipelineWorldSectorSetup(RpWorldSector *worldSector)
{
    RWFUNCTION(RWSTRING("_rpMatFXPipelineWorldSectorSetup"));
    RWASSERT(worldSector);

    RpWorldSectorSetInstancePipeline(worldSector, _RpMatFXWorldSectorPipe);

    RWRETURN(worldSector);
}

/*!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!*
 *                                                                           *
 *                            - Enable effects -                             *
 *                                                                           *
 *!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!*/

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

    RWRETURN(material);
}

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

    RWRETURN(material);
}

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

    RWRETURN(material);
}

/*!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!*
 *                                                                           *
 *                         - Device data fucntions -                         *
 *                                                                           *
 *!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!*/

RwBool
_rpMatFXSetupDualRenderState(MatFXDualData *dualData __RWUNUSED__,
                             RwRenderState state __RWUNUSED__)
{
    RWFUNCTION(RWSTRING("_rpMatFXSetupDualRenderState"));

    RWRETURN(TRUE);
}

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

    RWFUNCTION(RWSTRING("_rpMatFXSetupBumpMapTexture"));

    texture = _rpMatFXTextureMaskCreate(baseTexture, effectTexture);

    RWRETURN(texture);
}
