/* @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 "bamatren.h"
#include "baclump.h"
#include "baworld.h"

#include "atompipe.h"

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


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

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

static RwResEntry *_rpAtomicInstance(void *object);

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

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

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

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

/* LUT for figuring out what is overloaded by different vertex types */
static RwUInt8  atomicOverLoads[ATOMICRENDERTYPEMASK + 1];
static RwUInt8  atomicVertSizes[ATOMICRENDERTYPEMASK + 1];

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

                         Opening and closing

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

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

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

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

    if (pipe)
    {
        RwUInt32        i;

        pipe->fpInstance = _rpAtomicInstance;
        pipe->fpPreLight = _rpPipePreLight;
        pipe->fpApplyLight = _rpPipeApplyLight;
        pipe->fpPostLight = _rpPipePostLight;
        pipe->fpRender = _rpPipeRender;

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

        /* Initialise the LUT entries for vertex size/overload flags */
        for (i = 0; i <= ATOMICRENDERTYPEMASK; i++)
        {
            RwInt32         overLoad;
            RwInt32         vertSize;

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

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

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

        RWRETURN(TRUE);
    }

    RWRETURN(FALSE);
}

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

                                Atomic Instancing

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

/****************************************************************************
 _rpAtomicInstanceAnimation

 On entry   : Atomic
 On exit    :
 */

static void
_rpAtomicInstanceAnimation(RpAtomic * apAtom)
{
    RwResEntry     *repEntry = apAtom->repEntry;
    RpGeometry     *geom = apAtom->geometry;
    RwInt32         i;
    RpInterpolator *interpolator = &apAtom->interpolator;
    RwInt32         numVertices = geom->numVertices;

    RWFUNCTION(RWSTRING("_rpAtomicInstanceAnimation"));

    if ((interpolator->startMorphTarget == interpolator->endMorphTarget) ||
        (interpolator->startMorphTarget >= geom->numMorphTargets) ||
        (interpolator->endMorphTarget >= geom->numMorphTargets))
    {
        /* Expand a single morph target */
        RWVERTEXINSTANCE *instancedVertex =
        RWVERTEXINSTANCEGet(repEntry, 0);
        RwV3d          *vertices;
        RpMorphTarget  *morphTarget;

        if ((interpolator->startMorphTarget >= geom->numMorphTargets) ||
            (interpolator->endMorphTarget >= geom->numMorphTargets))
        {
            /* One of the morph target indices is invalid, we can't interpolate,
             * so just grab key frame zero, which is bound to exist.
             */
            morphTarget = &geom->morphTarget[0];
        }
        else
        {
            /* Both morph targets must be the same, so grab one or the other */
            morphTarget = &geom->morphTarget[interpolator->startMorphTarget];
        }

        vertices = morphTarget->verts;

        if (rwObjectTestFlags(geom, rpGEOMETRYNORMALS))
        {
            /* vertices & normals */
            RwV3d          *normals = morphTarget->normals;

            i = numVertices;
            while (i--)
            {
                instancedVertex->objVertex = *vertices;
                instancedVertex->objNormal = *normals;

                /* Next */
                instancedVertex = RWVERTEXINSTANCEGetNext(repEntry, instancedVertex);
                normals++;
                vertices++;
            }
        }
        else
        {
            /* vertices */
            i = numVertices;
            while (i--)
            {
                instancedVertex->objVertex = *vertices;

                /* Next */
                instancedVertex = RWVERTEXINSTANCEGetNext(repEntry, instancedVertex);
                vertices++;
            }
        }
    }
    else
    {
        RpMorphTarget   *startMorphTarget = &geom->morphTarget[interpolator->startMorphTarget];
        RpMorphTarget   *endMorphTarget = &geom->morphTarget[interpolator->endMorphTarget];
        RWVERTEXINSTANCE *instancedVertex =
        RWVERTEXINSTANCEGet(repEntry, 0);
        RwV3d          *startFrameVerts = startMorphTarget->verts;
        RwV3d          *endFrameVerts = endMorphTarget->verts;
        RwReal          scale = ((interpolator->recipTime) * (interpolator->position));

        /* Have to interpolate to get values */
        if (startMorphTarget->normals)
        {
            RwV3d          *startFrameNormals = startMorphTarget->normals;
            RwV3d          *endFrameNormals = endMorphTarget->normals;

            /* Interpolate normals */

            i = numVertices;
            while (i--)
            {
                RwV3dSub(&instancedVertex->objVertex, endFrameVerts, startFrameVerts);
                RwV3dScale(&instancedVertex->objVertex,
                           &instancedVertex->objVertex, scale);
                RwV3dAdd(&instancedVertex->objVertex,
                         &instancedVertex->objVertex, startFrameVerts);

                RwV3dSub(&instancedVertex->objNormal, endFrameNormals, startFrameNormals);
                RwV3dScale(&instancedVertex->objNormal,
                           &instancedVertex->objNormal, scale);
                RwV3dAdd(&instancedVertex->objNormal,
                         &instancedVertex->objNormal, startFrameNormals);

                instancedVertex = RWVERTEXINSTANCEGetNext(repEntry, instancedVertex);
                startFrameVerts++;
                endFrameVerts++;
                startFrameNormals++;
                endFrameNormals++;
            }
        }
        else
        {
            i = numVertices;
            while (i--)
            {
                RwV3dSub(&instancedVertex->objVertex, endFrameVerts, startFrameVerts);
                RwV3dScale(&instancedVertex->objVertex,
                           &instancedVertex->objVertex, scale);
                RwV3dAdd(&instancedVertex->objVertex,
                         &instancedVertex->objVertex, startFrameVerts);

                instancedVertex = RWVERTEXINSTANCEGetNext(repEntry, instancedVertex);
                startFrameVerts++;
                endFrameVerts++;
            }
        }
    }

    RWRETURNVOID();
}

/****************************************************************************
 _rpAtomicInstance

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

static RwResEntry *
_rpAtomicInstance(void *object)
{
    RpAtomic       *apAtom = (RpAtomic *) object;
    RpGeometry     *geom = apAtom->geometry;

    RWFUNCTION(RWSTRING("_rpAtomicInstance"));
    RWASSERT(geom);

    /* Don't instance anything with no geometry! */
    if (geom)
    {
        RpMeshHeader            *meshHeader = geom->mesh;
        RwInt32                 numVerts = geom->numVertices;
        RwInt32                 numPolys;
        RpInterpolator          *interpolator = &apAtom->interpolator;
        RwInstDataPolyHeader    *polyHeader;
        RwUInt32                renderTypeFlags = rwObjectGetFlags(geom) & ATOMICRENDERTYPEMASK;
        RwUInt16                overloadFlags = (RwUInt16) atomicOverLoads[renderTypeFlags];
        RwInt32                 sizeOfInstVert = (RwInt32) atomicVertSizes[renderTypeFlags];
        RwUInt16                needToReInstance = 0;
        RWPOLYGONINSTANCE       *instancedPolygon;
        RpMesh                  *mesh;
        RwInt32                 numMeshes, numTriangles;
        RwResEntry              *repEntry = apAtom->repEntry;

        /* 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 (repEntry)
        {
            /* Poly header (because this particular implementation uses polygons) */
            polyHeader = RWPOLYHEADER(repEntry);

            /* Do we need to re-instance - has the app changed the mesh or do
             * the vertices have different fields.
             */
            if (((polyHeader->serialNum & 0xFFFF) !=
                 meshHeader->serialNum) ||
                (polyHeader->overloadFlags != overloadFlags) ||
                (polyHeader->sizeOfVertex != (RwUInt32)sizeOfInstVert))
            {
                /* Release resources to force re-instance (this does vertices/normals too) */
                RwResourcesFreeResEntry(repEntry);
                repEntry = NULL;
            }
            else
            {
                /* Force instancing of just what is needed */
                needToReInstance = geom->lockedSinceLastInst;
            }
        }

        if (!repEntry)
        {
            /* Allocate some resources and initialise the static elements */
            RwInt32         sizeVert = sizeOfInstVert * numVerts;
            RwInt32         sizePoly = sizeof(RWPOLYGONINSTANCE) * numPolys;
            RwInt32         size = sizeof (RwInstDataPolyHeader) +
            sizeof (RwMatrix) +
            sizeVert + sizePoly;

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

            /* Convenience access */
            repEntry = apAtom->repEntry;

            /* Poly header (because this particular implementation uses polygons) */
            polyHeader = RWPOLYHEADER(repEntry);

            /* Set up the pointers to the actual data ! */
            polyHeader->xForm = (void *) (polyHeader + 1);

            polyHeader->vertices = (void *) (((RwMatrix *)
                                              polyHeader->xForm) + 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 = (RwUInt16) atomicOverLoads[renderTypeFlags];
            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 = RpGeometryGetFlags(geom);

            /* Need to ReInstance everything (we use the lock flag to see what's changed) */
            needToReInstance = rpGEOMETRYLOCKALL;
        }
        else
        {
            RwResourcesUseResEntry(apAtom->repEntry);
        }

        if ((needToReInstance & rpGEOMETRYLOCKTEXCOORDS) &&
            rwObjectTestFlags(geom, rpGEOMETRYTEXTURED))
        {
            /* Instance vertex texture coordinates */
            RwInt32         i = numVerts;
            RWVERTEXINSTANCE *instancedVertex;
            RwTexCoords      *vertexTexCoords = geom->vertexTexCoords;

            RWASSERT(vertexTexCoords);

            instancedVertex = RWVERTEXINSTANCEGet(repEntry, 0);
            while (i--)
            {
                instancedVertex->u = vertexTexCoords->u;
                instancedVertex->v = vertexTexCoords->v;

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

        if (needToReInstance & rpGEOMETRYLOCKPRELIGHT)
        {
            /* Initialise preLitLum */
            if (rwObjectTestFlags(geom, rpGEOMETRYPRELIT))
            {
                RwInt32         i = numVerts;
                RWVERTEXINSTANCE *instancedVertex;
                RwRGBA         *preLitLum = geom->preLitLum;

                RWASSERT(preLitLum);

                instancedVertex = RWVERTEXINSTANCEGet(repEntry, 0);
                while (i--)
                {
                    instancedVertex->c.preLitColor = *preLitLum;
                    preLitLum++;
                    instancedVertex =
                        RWVERTEXINSTANCEGetNext(repEntry, instancedVertex);
                }
            }
            else
            {
                RwInt32         i = numVerts;
                RWVERTEXINSTANCE *instancedVertex;

                instancedVertex = RWVERTEXINSTANCEGet(repEntry, 0);
                while (i--)
                {
                    static RwRGBA   opaqueBlack =
                    {0, 0, 0, 255};

                    instancedVertex->c.preLitColor = opaqueBlack;
                    instancedVertex =
                        RWVERTEXINSTANCEGetNext(repEntry, instancedVertex);
                }
            }
        }

        if (needToReInstance & rpGEOMETRYLOCKPOLYGONS)
        {
            /* 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++;
                }
            }
        }

        /* Do any animation */
        if ((interpolator->flags & (RwInt32)
             rpINTERPOLATORDIRTYINSTANCE) ||
            (needToReInstance & (rpGEOMETRYLOCKVERTICES | rpGEOMETRYLOCKNORMALS)))
        {
            _rpAtomicInstanceAnimation(apAtom);

            /* Its no longer dirty */
            interpolator->flags &= ~rpINTERPOLATORDIRTYINSTANCE;
        }

        /* All has been dealt with now */
        geom->lockedSinceLastInst = 0;

        /* Initialise local to camera matrix */
        {
            RwMatrix       *localToCamera = (RwMatrix *) polyHeader->xForm;
            RwMatrix       *mpLocalToWorld = RwFrameGetLTM((RwFrame *)
                                                           rwObjectGetParent(apAtom));

            RwMatrixMultiply(localToCamera, mpLocalToWorld,
                             &(((RwCamera
                                 *)
                                RWSRCGLOBAL(curCamera))->viewMatrix));
        }

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

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

        _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));
        }

        RWRETURN(apAtom->repEntry);
    }

    RWRETURN(NULL);
}

