/***************************************************************************
 *                                                                         *
 * Module  : bamatren.c                                                    *
 *                                                                         *
 * Purpose : Default pipe material rendering code                          *
 *                                                                         *
 **************************************************************************/

/****************************************************************************
 Includes
 */

#include <string.h>

#include <rwcore.h>

#include "bamesh.h"

/* This gets us flags, which are the same for worlds and geometries */
#include "bageomet.h"
#include "baworld.h"

#include "bamatren.h"

static const char rcsid[] __RWUNUSED__ = "@@(#)$Id: bamatren.c,v 1.74 2000/11/22 13:05:22 iestynb Exp $";


/****************************************************************************
 Global prototypes
 */

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

typedef void    (*RpMeshVertexCallBackFn)(RwImVertexIndex vertexIndex, void *pData);

/****************************************************************************
 Local (Static) Prototypes
 */

/****************************************************************************
 Local Defines
 */

/****************************************************************************
 Globals (across program)
 */

/****************************************************************************
 Local (static) Globals
 */

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

                                Render

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

/****************************************************************************
 _rpRenderInstancedTriangles

 Renders 'n' triangles

 On entry   : instanced data, raster, filtering mode, addressing mode,
            : border color, instanced polygons, number of polys to render
 On exit    :
 */

static void
_rpRenderInstancedTriangles(RwResEntry * repEntry,
                            RwRaster * raster,
                            RwTextureFilterMode filtering,
                            RwTextureAddressMode addressing,
                            const RwRGBA * color,
                            RWPOLYGONINSTANCE * instancedPolygon,
                            RwInt32 numTriangles
)
{
    RWFUNCTION(RWSTRING("_rpRenderInstancedTriangles"));
    RWASSERT(instancedPolygon);

    _rwPipeState.fpResetIndexBuffer();

    if ((_rwPipeState.currentContext->baseVertexIndex +
         _rwPipeState.currentContext->clippedVertexIndex +
         (numTriangles << 1)) > _rwPipeState.currentPipeSize)
    {
        _rwResizePipe(_rwPipeState.currentContext->clippedVertexIndex
                      + (numTriangles << 1) + 20);
    }

    RwRenderStateSet(rwRENDERSTATETEXTURERASTER, raster);
    if (raster)
    {
        RwRenderStateSet(rwRENDERSTATETEXTUREFILTER, (void *) filtering);
        RwRenderStateSet(rwRENDERSTATETEXTUREADDRESS, (void *) addressing);
        if (addressing == rwTEXTUREADDRESSBORDER)
        {
            RwRenderStateSet(rwRENDERSTATEBORDERCOLOR, (void *)
                             ((((RwUInt32) color->alpha) << 24) |
                              (((RwUInt32) color->red) << 16) |
                              (((RwUInt32) color->green) << 8) |
                              (((RwUInt32) color->blue))));
        }
    }

    if (_rwPipeState.currentContext->clipFlagsOr == 0)
    {
        /* They are all fully on screen, don't bother with the clipper */

        while (numTriangles--)
        {
            RwInt32         vert1 = RWPOLYGONINSTANCEGetVertex1(instancedPolygon);
            RwInt32         vert2 = RWPOLYGONINSTANCEGetVertex2(instancedPolygon);
            RwInt32         vert3 = RWPOLYGONINSTANCEGetVertex3(instancedPolygon);

#ifdef RW4
            _rwPipeState.instPoly = instancedPolygon;
#endif
            /* We can cull in screen space, 'cos we know it's been projected */
            if (_rwPipeState.fpCull2DTriangle(vert1, vert2, vert3) == rwNOTCULLEDNOTDRAWN)
            {
                /* And dispatch it */
                _rwPipeState.fpBufferTriangle(vert1, vert2, vert3);
            }

            /* Onto the next entry */
            instancedPolygon = RWPOLYGONINSTANCEGetNext(repEntry, instancedPolygon);
        }
    }
    else
    {
        /* May be partially off screen, do bother with the clipper */
        while (numTriangles--)
        {
            RwInt32         vert1 = RWPOLYGONINSTANCEGetVertex1(instancedPolygon);
            RwInt32         vert2 = RWPOLYGONINSTANCEGetVertex2(instancedPolygon);
            RwInt32         vert3 = RWPOLYGONINSTANCEGetVertex3(instancedPolygon);
            RwCameraVertex *camVert0 = RwCameraVertexGet(vert1);
            RwCameraVertex *camVert1 = RwCameraVertexGet(vert2);
            RwCameraVertex *camVert2 = RwCameraVertexGet(vert3);

            RwUInt8         c1 = RwCameraVertexGetClipFlags(camVert0);
            RwUInt8         c2 = RwCameraVertexGetClipFlags(camVert1);
            RwUInt8         c3 = RwCameraVertexGetClipFlags(camVert2);

#ifdef RW4
            _rwPipeState.instPoly = instancedPolygon;
#endif
            /* Completely off screen? */
            if (!(c1 & c2 & c3))
            {
                /* Need to clip? */
                if (c1 | c2 | c3)
                {
                    /* Need to cull in camera space - it's partially off screen */
                    if (_rwPipeState.fpCull3DTriangle(vert1, vert2,
                                                      vert3) == rwNOTCULLEDNOTDRAWN)
                    {
                        if
                            ((_rwPipeState.currentContext->baseVertexIndex +
                              _rwPipeState.currentContext->clippedVertexIndex
                              + 20) > _rwPipeState.currentPipeSize)
                        {
                            _rwResizePipe(_rwPipeState.currentContext->clippedVertexIndex
                                          + 50);
                        }

                        _rwPipeState.fpClipTriangle(vert1, vert2, vert3);
                    }
                }
                else
                {
                    /* It's been projected, so cull it in screen space */
                    if (_rwPipeState.fpCull2DTriangle(vert1, vert2,
                                                      vert3) == rwNOTCULLEDNOTDRAWN)
                    {
                        _rwPipeState.fpBufferTriangle(vert1, vert2, vert3);
                    }
                }
            }

            /* Onto the next entry */
            instancedPolygon = RWPOLYGONINSTANCEGetNext(repEntry, instancedPolygon);
        }
    }

    /* Draw all the polygons we have submitted */
    _rwPipeState.fpDrawIndexBuffer();

    RWRETURNVOID();
}

/****************************************************************************
 _rpModulateWithMaterial

 Modulates the material with light values in vertex

 On entry   : Vertex index, Pointer to material colour
 On exit    :
 */

static void
_rpModulateWithMaterial(RwImVertexIndex vertexIndex, void *pData)
{
    RwRGBAReal     *matCol = (RwRGBAReal *) pData;
    RwCameraVertex *camVert;
    RwIm2DVertex   *devVert;
    RwRGBAReal     *camVertCol;

    RWFUNCTION(RWSTRING("_rpModulateWithMaterial"));

    RWASSERT(matCol);

    camVert = RwCameraVertexGet(vertexIndex);
    devVert = RwIm2DVertexGet(vertexIndex);
    camVertCol = RwCameraVertexGetRGBA(camVert);

    RwIm2DVertexSetRealRGBA(devVert,
                            ((camVertCol->red) * (matCol->red)),
                            ((camVertCol->green) * (matCol->green)),
                            ((camVertCol->blue) * (matCol->blue)),
                            matCol->alpha);

    RWRETURNVOID();
}

/****************************************************************************
 _rpReplaceLightValue

 Puts the raw lighting value back into the device vertices

 On entry   : Vertex index
 On exit    :
 */

static void
_rpReplaceLightValue(RwImVertexIndex vertexIndex, void *pData __RWUNUSED__)
{
    RwCameraVertex *camVert;
    RwIm2DVertex   *devVert;
    RwRGBAReal     *col;

    RWFUNCTION(RWSTRING("_rpReplaceLightValue"));

    camVert = RwCameraVertexGet(vertexIndex);
    devVert = RwIm2DVertexGet(vertexIndex);
    col = RwCameraVertexGetRGBA(camVert);

    RwIm2DVertexSetRealRGBA(devVert, col->red, col->green, col->blue, col->alpha);

    RWRETURNVOID();
}

/****************************************************************************
 _rpForAllVerticesInMesh

 Call callback for each vertex in the mesh.  Doesn't call twice with same vertex.
 Sets bits in the tag table

 On entry   : Mesh, Triangle CB, Vertex CB, User data
 On exit    :
 */

static void
_rpForAllVerticesInMesh(RpMesh * mesh, RpMeshVertexCallBackFn fpVertCB,
                        void *pData)
{
    RwImVertexIndex *index;
    RwInt32         numInds;

    RWFUNCTION(RWSTRING("_rpForAllVerticesInMesh"));

    RWASSERT(mesh);
    RWASSERT(fpVertCB);

    /* Reset the tag table */
    memset(_rwPipeState.vertexTagTable, 0, (_rwPipeState.vertexTagTableSize + 31) >> 3);

    index = mesh->indices;
    numInds = mesh->numIndices;

    while (numInds--)
    {
        RwImVertexIndex     testIndex = *index;
        RwInt32             longNum;
        RwUInt32            testBit;

        longNum = testIndex >> 5;
        testBit = 1 << (testIndex & 31);
        if (!(_rwPipeState.vertexTagTable[longNum] & testBit))
        {
            /* Not touched this one yet */
            _rwPipeState.vertexTagTable[longNum] |= testBit;
            fpVertCB(testIndex, pData);
        }

        /* Next index */
        index++;
    }

    RWRETURNVOID();
}

/****************************************************************************
 _rpPipeRender

 On entry   : Instanced data
 On exit    :
 */

void
_rpPipeRender(RwResEntry * repEntry)
{
    RwInstDataPolyHeader *polyHeader = RWPOLYHEADER(repEntry);
    RpMeshHeader   *meshHeader = (RpMeshHeader *) polyHeader->mesh;
    RwInt32         instancedPolyIndex = 0;
    RwInt32         numMeshes = meshHeader->numMeshes;
    RpMesh         *mesh;
    RwBool          modulate = FALSE;
    RwInt32         i;

    RWFUNCTION(RWSTRING("_rpPipeRender"));
    RWASSERT(repEntry);

    if (_rwPipeState.currentContext->clipFlagsAnd == 0)
    {
        /* Resize vertex tag table if too small */
        if (polyHeader->numVertices > _rwPipeState.vertexTagTableSize)
        {
            _rwResizeVertexTagTable(polyHeader->numVertices);
        }

        RwRenderStateSet(rwRENDERSTATESHADEMODE, (void *) rwSHADEMODEGOURAUD);
        _rwPipeState.clipGouraud = TRUE;

        /* If all the meshes are coloured by an opaque white material, _rpPostLight
         * * has copied lighting values from the camera vertices into the device vertices,
         * * so we can avoid some unnecessary work here.
         */

        if (polyHeader->flags & rpWORLDMODULATEMATERIALCOLOR)
        {
            modulate = TRUE;
        }

        mesh = (RpMesh *) ((RwUInt8 *) (meshHeader + 1) + meshHeader->firstMeshOffset);
        for (i = 0; i < numMeshes; i++)
        {
            RwInt32             numTriangles;
            RWPOLYGONINSTANCE   *polygons =
                        RWPOLYGONINSTANCEGet(repEntry, instancedPolyIndex);
            static const RwRGBA opaqueWhite = {255, 255, 255, 255};

            /* Deal with the material if there is one */
            RpMaterial              *material = mesh->material;
            RwTexture               *texture = NULL;
            const RwRGBA            *color = &opaqueWhite;
            RwRaster                *ras = NULL;
            RwTextureFilterMode     filt = rwFILTERNAFILTERMODE;
            RwTextureAddressMode    addr = rwTEXTUREADDRESSNATEXTUREADDRESS;

            /* Figure out the number of polygons */
            if (meshHeader->flags & rpMESHHEADERTRISTRIP)
            {
                numTriangles = mesh->numIndices - 2;
            }
            else
            {
                numTriangles = mesh->numIndices / 3;
            }

            if (material)
            {
                texture = RpMaterialGetTexture(material);
                color = RpMaterialGetColor(material);

                if (texture)
                {
                    ras = RwTextureGetRaster(texture);
                    filt = RwTextureGetFilterMode(texture);
                    addr = RwTextureGetAddressing(texture);
                }
            }

            /* Make sure polygon buffer is large enough */
            if ((_rwPipeState.triangleIndexBuffer.vertexIndexFillLevel
                 + (3 * numTriangles) + 20) >
                _rwPipeState.triangleIndexBuffer.vertexIndexPipeSize)
            {
                _rwResizePipeIndexBuffer(&_rwPipeState.triangleIndexBuffer,
                                         _rwPipeState.triangleIndexBuffer.vertexIndexFillLevel
                                         + (3 * numTriangles) + 20);
            }

            /* We use the material color */
            if (((material->color.red & material->color.green &
                 material->color.blue & material->color.alpha) != 255) && modulate)
            {
                RwRGBAReal      scaledMatCol;

                /* Do we need alpha blending */
                if (material->color.alpha != 0xff)
                {
                    RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE,
                                     (void *) TRUE);
                }
                else
                {
                    RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE,
                                     (void *) FALSE);
                }

                scaledMatCol.red = (((RwReal)(material->color.red)) *
                                    ((RwReal) (1.0 / 255.0)));
                scaledMatCol.green = (((RwReal)(material->color.green)) *
                                      ((RwReal) (1.0 / 255.0)));
                scaledMatCol.blue = (((RwReal)(material->color.blue)) *
                                     ((RwReal) (1.0 / 255.0)));
                scaledMatCol.alpha = material->color.alpha;

                /* Modulate material colours with vertex lighting values */
                _rpForAllVerticesInMesh(mesh, _rpModulateWithMaterial,
                                        &scaledMatCol);
                _rpRenderInstancedTriangles(repEntry, ras, filt, addr, color,
                                            polygons, numTriangles);
            }
            else
            {
                /* Must be opaque by definition */
                RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE,
                                 (void *) FALSE);

                if (modulate == TRUE)
                {
                    /* Lit vertex colours are just equal to lighting values, so copy them */
                    _rpForAllVerticesInMesh(mesh, _rpReplaceLightValue, NULL);
                }

                /* Relying entirely on the vertex colours */
                _rpRenderInstancedTriangles(repEntry, ras, filt, addr, color,
                                            polygons, numTriangles);
            }

            /* Get ready for next round */
            mesh++;

            instancedPolyIndex += numTriangles;
        }
    }

    RWRETURNVOID();
}
