/* @doc 
 *
 * Atomic pipeline components
 *
 * Copyright (c) 1998 Criterion Software Ltd.
*/

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

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

#include <rwcore.h>

#include "bamateri.h"
#include "bageomet.h"
#include "litepipe.h"
#include "baworld.h"
#include "bamatren.h"
#include "baclump.h"

#include "sectpipe.h"

static const char rcsid[] __RWUNUSED__ = "@@(#)$Id: sectpipe.c,v 1.12 2000/11/22 14:52:28 iestynb Exp $";


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

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

static RwResEntry *_rpWorldSectorInstance(void *object);

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

/* Mask for figuring out what style of rendering to use */
#define WORLDRENDERTYPEMASK (rpWORLDTEXTURED)

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

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

/* LUT for figuring out what is overloaded by different vertex types */
static RwUInt8  worldOverLoads[WORLDRENDERTYPEMASK + 1];
static RwUInt8  worldVertSizes[WORLDRENDERTYPEMASK + 1];

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

                         Opening and closing

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

/****************************************************************************
 initGenericAtomPipe

 On entry   : Pipeline to init
 On exit    : TRUE on success
 */

RwBool 
initGenericSectPipe(RwRenderPipeline *pipe)
{
    RWFUNCTION(RWSTRING("initGenericSectPipe"));
    RWASSERT(pipe);

    if (pipe)
    {
        RwUInt32        i;

        /* Overload some bits of the pipe with specific-to-sector bits */
        pipe->fpInstance = _rpWorldSectorInstance;
        pipe->fpPreLight = _rpPipePreLight;
        pipe->fpApplyLight = _rpPipeApplyLight;
        pipe->fpPostLight = _rpPipePostLight;
        pipe->fpRender = _rpPipeRender;

        /* Actual light enumeration function is in baworld.c */

        /* Set up the overload types */
        for (i = 0; i <= WORLDRENDERTYPEMASK; i++)
        {
            RwInt32         overLoad;
            RwInt32         vertSize;

            /* Defaults: */
            overLoad = 0;
            vertSize = offsetof(RWVERTEXINSTANCE, u);

            if (i & rpWORLDTEXTURED)
            {
                overLoad |= rwVERTEXSUPPLIESUV;
                vertSize = sizeof(RWVERTEXINSTANCE);
            }

            /* Chuck it in the LUT */
            worldOverLoads[i] = (RwUInt8) overLoad;
            worldVertSizes[i] = (RwUInt8) vertSize;
        }

        RWRETURN(TRUE);
    }

    RWRETURN(FALSE);
}

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

                          Instance Atomic sectors

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

/****************************************************************************
 _rpWorldSectorInstance

 On entry   : Atomic sector
 On exit    : TRUE on success
 */

static RwResEntry *
_rpWorldSectorInstance(void *object)
{
    RpWorldSector          *worldSector = (RpWorldSector *) object;
    RpMeshHeader           *meshHeader = worldSector->mesh;
    RpWorld                *world = (RpWorld *) RWSRCGLOBAL(curWorld);
    RwInt32                 numVerts = worldSector->numVertices;
    RwInt32                 numPolys;
    RwUInt32                renderTypeFlags = rwObjectGetFlags(world) & WORLDRENDERTYPEMASK;
    RwInt32                 sizeOfInstVert = (RwInt32) worldVertSizes[renderTypeFlags];
    RwUInt16                overloadFlags = (RwUInt16) worldOverLoads[renderTypeFlags];
    RwInstDataPolyHeader   *polyHeader = RWPOLYHEADER(worldSector->repEntry);

    RWFUNCTION(RWSTRING("_rpWorldSectorInstance"));
    RWASSERT(object);

    /* We instance everything into a trilist so expand strips into lists */
    if (meshHeader->flags & rpMESHHEADERTRISTRIP)
    {
        numPolys = meshHeader->totalIndicesInMesh -
                   (2*meshHeader->numMeshes);
    }
    else
    {
        numPolys = meshHeader->totalIndicesInMesh / 3;
    }

    if (numPolys && worldSector->repEntry)
    {
        /* Do we need to re-instance - 
         * has the app changed the representation */
        if ((polyHeader->serialNum != meshHeader->serialNum) ||
            (polyHeader->overloadFlags != overloadFlags) ||
            (polyHeader->sizeOfVertex != (RwUInt32) sizeOfInstVert))
        {
            /* Release resources to force re-instance */
            RwResourcesFreeResEntry(worldSector->repEntry);
        }
    }

    if (!worldSector->repEntry)
    {
        /* Allocate some resources and initialise the static elements */
        RwInt32         nNumTran;
        RwResEntry     *repEntry;

        if (!numPolys)
        {
#ifdef INSTEMPTYSECTORS
            /* Dummy allocation such that atomics are not destroyed */
            if (!RwResourcesAllocateResEntry(worldSector, &worldSector->repEntry,
                                             sizeof
                                             (RwInstDataPolyHeader), NULL))
            {
                RWRETURN(NULL);
            }

            /* Because this implementation of the pipeline is based one polygons. */
            polyHeader = RWPOLYHEADER(worldSector->repEntry);

            polyHeader->xForm = NULL;

            polyHeader->vertices = NULL;
            polyHeader->numVertices = 0;
            polyHeader->sizeOfVertex = sizeof (RWVERTEXINSTANCE);

            polyHeader->polygons = NULL;
            polyHeader->numPolygons = 0;
            polyHeader->sizeOfPolygon = sizeof (RWPOLYGONINSTANCE);
            polyHeader->firstPolygonOffset = 0;

            polyHeader->overloadFlags = 0;
            polyHeader->mesh = (void *) meshHeader;

            /* Copy serial num so we know if the app changes things */
            polyHeader->serialNum = meshHeader->serialNum;

            /* Make sure we can copy the flags across - need
             * commonality between the flag sets
             */
            RWASSERT(rpGEOMETRYTEXTURED == rpWORLDTEXTURED);
            RWASSERT(rpGEOMETRYPRELIT == rpWORLDPRELIT);
            RWASSERT(rpGEOMETRYNORMALS == rpWORLDNORMALS);
            RWASSERT(rpGEOMETRYLIGHT == rpWORLDLIGHT);
            RWASSERT(rpGEOMETRYMODULATEMATERIALCOLOR == rpWORLDMODULATEMATERIALCOLOR);
            polyHeader->flags = RpWorldGetFlags(world);

            /* Nothing done in the pipe yet */
            polyHeader->pipelineOpFlags = 0;

            RWRETURN(worldSector->repEntry);
#else /* INSTEMPTYSECTORS */
            RWRETURN(NULL);
#endif /* INSTEMPTYSECTORS */
        }

        nNumTran = meshHeader->numMeshes;

        {
            RwInt32         sizeTri = sizeof(RWPOLYGONINSTANCE) * numPolys;
            RwInt32         sizeVert = sizeOfInstVert * numVerts;
            RwInt32         size = sizeof (RwInstDataPolyHeader) +
            sizeTri + sizeVert;

            if (!RwResourcesAllocateResEntry(worldSector,
                                             &worldSector->repEntry,
                                             size, NULL))
            {
                RWRETURN(NULL);
            }

            /* Convenient access */
            repEntry = worldSector->repEntry;

            /* Because this implementation of the pipeline is based one polygons. */
            polyHeader = RWPOLYHEADER(worldSector->repEntry);

            polyHeader->vertices = (void *) (polyHeader + 1);
            polyHeader->numVertices = numVerts;
            polyHeader->sizeOfVertex = sizeOfInstVert;

            polyHeader->polygons = (void *) ((RwUInt8 *)
                                             polyHeader->vertices + sizeVert);
            polyHeader->numPolygons = numPolys;
            polyHeader->sizeOfPolygon = sizeof(RWPOLYGONINSTANCE);
            polyHeader->firstPolygonOffset = 0;

            polyHeader->overloadFlags = overloadFlags;
            polyHeader->mesh = (void *) meshHeader;

            /* Copy serial num so we know if the app changes things */
            polyHeader->serialNum = meshHeader->serialNum;

            /* Make sure we can copy the flags across - need
             * commonality between the flag sets
             */
            RWASSERT(rpGEOMETRYTEXTURED == rpWORLDTEXTURED);
            RWASSERT(rpGEOMETRYPRELIT == rpWORLDPRELIT);
            RWASSERT(rpGEOMETRYNORMALS == rpWORLDNORMALS);
            RWASSERT(rpGEOMETRYLIGHT == rpWORLDLIGHT);
            RWASSERT(rpGEOMETRYMODULATEMATERIALCOLOR == rpWORLDMODULATEMATERIALCOLOR);
            polyHeader->flags = RpWorldGetFlags(world);
        }

        /* Initialize the triangles (and texture coords if appropriate) */
        {
            RWPOLYGONINSTANCE   *instancedPolygon;
            RpMesh              *mesh;
            RwInt32             numMeshes, numTriangles;

            /* Initialise polygons */
            numMeshes = meshHeader->numMeshes;

            instancedPolygon = RWPOLYGONINSTANCEGet(repEntry, 0);
            mesh = (RpMesh *) ((RwUInt8 *) (meshHeader + 1) + meshHeader->firstMeshOffset);

            if (meshHeader->flags & rpMESHHEADERTRISTRIP)
            {
                while (numMeshes--)
                {
                    RxVertexIndex  *index = mesh->indices;
                    RwInt32         i;

                    numTriangles = mesh->numIndices-2;
                    for (i = 0; i < numTriangles; i++)
                    {
                        RwInt32     odd = (i & 1);
                        RwInt32     even = ((~i) & 1);

                        /* Flip winding of alternate triangles */
                        instancedPolygon->vertIndex[0] = index[i + odd];
                        instancedPolygon->vertIndex[1] = index[i + even];
                        instancedPolygon->vertIndex[2] = index[i+2];

                        /* Ready for next mesh triangle */
                        instancedPolygon =
                            RWPOLYGONINSTANCEGetNext(repEntry, instancedPolygon);
                    }

                    /* Ready for next mesh */
                    mesh++;
                }
            }
            else
            {
                while (numMeshes--)
                {
                    RxVertexIndex *index = mesh->indices;

                    numTriangles = mesh->numIndices/3;
                    while (numTriangles--)
                    {
                        instancedPolygon->vertIndex[0] = *index++;
                        instancedPolygon->vertIndex[1] = *index++;
                        instancedPolygon->vertIndex[2] = *index++;

                        /* Ready for next mesh triangle */
                        instancedPolygon =
                            RWPOLYGONINSTANCEGetNext(repEntry, instancedPolygon);
                    }

                    /* Ready for next mesh */
                    mesh++;
                }
            }
        }

        /* Initialise the vertices */
        if (rwObjectTestFlags(world, rpWORLDNORMALS))
        {
            /* Vertices and normals */
            RpVertexNormal *normals = worldSector->normals;
            RwV3d          *vertices = worldSector->vertices;
            RWVERTEXINSTANCE *instancedVertex =
            RWVERTEXINSTANCEGet(repEntry, 0);
            RwInt32         nI = numVerts;

            RWASSERT(normals);
            RWASSERT(vertices);
            while (nI--)
            {
                /* Set up obj normal and vertex */
                RPV3DFROMVERTEXNORMAL(instancedVertex->objNormal, *normals);
                instancedVertex->objVertex = *vertices;

                instancedVertex = RWVERTEXINSTANCEGetNext(repEntry, instancedVertex);
                vertices++;
                normals++;
            }
        }
        else
        {
            /* Just vertices */
            RwV3d          *vertices = worldSector->vertices;
            RWVERTEXINSTANCE *instancedVertex =
            RWVERTEXINSTANCEGet(repEntry, 0);
            RwInt32         nI = numVerts;

            RWASSERT(vertices);
            while (nI--)
            {
                /* Set up vertex */
                instancedVertex->objVertex = *vertices;

                instancedVertex = RWVERTEXINSTANCEGetNext(repEntry, instancedVertex);
                vertices++;
            }
        }

        /* If we have vertex texture coordinates, set them up too */
        if (rwObjectTestFlags(world, rpWORLDTEXTURED))
        {
            RWVERTEXINSTANCE *instancedVertex =
                RWVERTEXINSTANCEGet(repEntry, 0);

            RwTexCoords *texCoords = worldSector->vertexTexCoords;
            RwInt32     nI = numVerts;

            RWASSERT(texCoords);

            while (nI--)
            {
                instancedVertex->u = texCoords->u;
                instancedVertex->v = texCoords->v;
                texCoords++;

                instancedVertex = RWVERTEXINSTANCEGetNext(repEntry,
                                                          instancedVertex);
            }
        }

        /* Initialise preLitLum */
        if (rwObjectTestFlags(world, rpWORLDPRELIT))
        {
            RwRGBA         *preLitLum = worldSector->preLitLum;
            RWVERTEXINSTANCE *instancedVertex =
            RWVERTEXINSTANCEGet(repEntry, 0);
            RwInt32         nI = numVerts;

            RWASSERT(preLitLum);

            while (nI--)
            {
                /* Set up prelight value */
                instancedVertex->c.preLitColor = *preLitLum;
                instancedVertex = RWVERTEXINSTANCEGetNext(repEntry, instancedVertex);
                preLitLum++;
            }
        }
        else
        {
            /* Initialise pre lit lums to black */
            RWVERTEXINSTANCE *instancedVertex =
            RWVERTEXINSTANCEGet(repEntry, 0);
            RwInt32         nI = numVerts;

            /* Set up prelight value to black */
            while (nI--)
            {
                static RwRGBA   opaqueBlack =
                {0, 0, 0, 255};

                instancedVertex->c.preLitColor = opaqueBlack;
                instancedVertex = RWVERTEXINSTANCEGetNext(repEntry, instancedVertex);
            }
        }
    }
    else
    {
        RwResourcesUseResEntry(worldSector->repEntry);
    }

    /* Set up the pointers to the actual data ! */
    polyHeader->xForm = (void *) &(((RwCamera *)
                                    RWSRCGLOBAL(curCamera))->viewMatrix);

    /* Configure pipeline for where geometry elements are coming from */
    _rwSetPolygonOverload(overloadFlags);

    _rwPipeState.currentContext->firstFreeVertexIndex = numVerts;

    if ((_rwPipeState.currentContext->baseVertexIndex + numVerts +
         (numVerts >> 2)) > _rwPipeState.currentPipeSize)
    {
        /* Resize the array and allow space for 25% clipped vertices */
        _rwResizePipe(numVerts + (numVerts >> 2));
    }

    /* Nothing done in the pipe yet */
    polyHeader->pipelineOpFlags = 0;

    RWRETURN(worldSector->repEntry);
}
