
/**
 * \ingroup rpmesh
 * \page rpmeshoverview RpMesh Overview
 *
 * Meshes are a cacheing system designed to speed up rendering.
 *
 * To make efficient use of hardware acceleration, RenderWare Graphics groups your
 * model geometry into Meshes when the Geometry object is loaded and/or unlocked.
 * Meshes are generated by sorting the model geometry by Material to reduce repeated
 * uploads of the same texture data and Tristripping is also performed at the this level.
 *
 * The RpMesh API concerns itself primarily with the tristripping process. It is possible
 * to attach your own callback function to use your own tristripping algorithms if required.
 *
 * \note Manipulating geometry data at run-time can affect overall performance of your application
 * as the Meshes need to be regenerated when such changes are made. For small numbers of changes,
 * the effect should be negligible, but this depends heavily on the underlying architecture
 * of the hardware.
 *
 * Further information is available in the \e Dynamic \e Models chapter of the User Guide.
*
*/

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

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

#include <rwcore.h>

#include "bamatlst.h"
#include "baworld.h"
#include "bamesh.h"
#include "bameshop.h"

#if (!defined(DOXYGEN))
static const char   rcsid[] __RWUNUSED__ =
    "@@(#)$Id: bamesh.c,v 1.102 2001/04/03 10:17:38 katherinet Exp $";
#endif /* (!defined(DOXYGEN)) */

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

typedef struct binMeshHeader binMeshHeader;
struct binMeshHeader
{
    RwUInt32            flags;
    RwUInt32            numMeshes;
    RwUInt32            totalIndicesInMesh;
};

typedef struct binMesh binMesh;
struct binMesh
{
    RwUInt32            numIndices;
    RwInt32             matIndex;
};

typedef struct RpMeshStatic RpMeshStatic;
struct RpMeshStatic
{
    RwFreeList         *BuildMeshFreeList;
};

#define RWINDEXBUFFERSIZE 256
typedef RwUInt32    RpIndexBuffer[RWINDEXBUFFERSIZE];

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

RwModuleInfo        meshModule;

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

static RpMeshStatic MeshStatic = {
    (RwFreeList *) NULL         /* BuildMeshFreeList */
};

static void
MeshFreeListsDestroy(void)
{
    RWFUNCTION(RWSTRING("MeshFreeListsDestroy"));

    if (NULL != MeshStatic.BuildMeshFreeList)
    {
        RwFreeListDestroy(MeshStatic.BuildMeshFreeList);
        MeshStatic.BuildMeshFreeList = (RwFreeList *) NULL;
    }

    RWRETURNVOID();
}

static              RwBool
MeshFreeListsCreate(void)
{
    RwBool              result;

    RWFUNCTION(RWSTRING("MeshFreeListsCreate"));

    MeshStatic.BuildMeshFreeList =
        RwFreeListCreate(sizeof(RpBuildMesh), 50, 0);
    result = (NULL != MeshStatic.BuildMeshFreeList);

    RWRETURN(result);
}

static              RwBool
MeshesRead(RwStream * stream,
           const RpMaterialList * matList,
           RwUInt32 numMeshes,
           RpMesh * mesh, RxVertexIndex * meshIndices)
{
    RwBool              status = TRUE;

    RWFUNCTION(RWSTRING("MeshesRead"));
    RWASSERT(stream);
    RWASSERT(matList);
    RWASSERT(mesh);
    RWASSERT(meshIndices);

    /* Then for each mesh... */

    while (numMeshes-- > 0)
    {
        binMesh             bm;
        RwUInt32            remainingIndices;

        /* ... do another header... */
        status =
            (NULL !=
             RwStreamReadInt(stream, (RwInt32 *) & bm, sizeof(bm)));

        if (!status)
        {
            /* Failure */
            break;
        }

        mesh->numIndices = bm.numIndices;
        mesh->material =
            _rpMaterialListGetMaterial(matList, bm.matIndex);
        mesh->indices = meshIndices;

        /* ...and all the indices */
        remainingIndices = mesh->numIndices;

        while (remainingIndices > 0)
        {
            RpIndexBuffer       IndexBuffer;
            RwInt32            *source = (RwInt32 *) & IndexBuffer[0];
            RwUInt32            readIndices =
                (remainingIndices < RWINDEXBUFFERSIZE) ?
                (remainingIndices) : (RWINDEXBUFFERSIZE);

            status =
                (NULL !=
                 RwStreamReadInt(stream,
                                 source,
                                 sizeof(*source) * readIndices));

            if (!status)
            {
                /* Failure */
                break;
            }

            remainingIndices -= readIndices;

            while (readIndices-- > 0)
            {
                *meshIndices++ = (RxVertexIndex) (*source++);
            }
        }

        mesh++;
    }

    RWRETURN(status);
}

/****************************************************************************/
/* Mesh header create/destroy functions */

void  
_rpMeshHeaderDestroy(RpMeshHeader * meshHeader)
{
    RWFUNCTION(RWSTRING("_rpMeshHeaderDestroy"));
    
    RWASSERT((RpMeshHeader *)NULL != meshHeader);
    
    RwFree(meshHeader);

    meshHeader = (RpMeshHeader *)NULL;

    RWRETURNVOID();
}

RpMeshHeader * 
_rpMeshHeaderCreate(RwUInt32 size)
{
    RpMeshHeader * meshHeader;
    
    RWFUNCTION(RWSTRING("_rpMeshHeaderCreate"));

    meshHeader = (RpMeshHeader *) RwMalloc(size);

    RWRETURN(meshHeader);
}

/****************************************************************************
 _rpMeshClose

 On entry   : instance, offset, size
 On exit    : instance pointer on success
 */

void *
_rpMeshClose(void *instance,
             RwInt32 __RWUNUSED__ offset, RwInt32 __RWUNUSED__ size)
{
    RWFUNCTION(RWSTRING("_rpMeshClose"));

    /* One less module instance */
    meshModule.numInstances--;

    if (0 == meshModule.numInstances)
    {
        MeshFreeListsDestroy();
    }

    /* Success !! */
    RWRETURN(instance);
}

/****************************************************************************
 _rpMeshOpen

 On entry   : instance, offset, size
 On exit    : instance pointer on success
 */

void *
_rpMeshOpen(void *instance,
            RwInt32 __RWUNUSED__ offset, RwInt32 __RWUNUSED__ size)
{
    RWFUNCTION(RWSTRING("_rpMeshOpen"));

    /* Cache the global data offset (same for all instances) */
    meshModule.globalsOffset = offset;

    if (0 == meshModule.numInstances)
    {
        if (!MeshFreeListsCreate())
        {
            MeshFreeListsDestroy();
            instance = NULL;
            RWRETURN(instance);
        }
    }

    /* Do the setup for this instance */
    RPMESHGLOBAL(nextSerialNum) = 1;

    /* One more module instance */
    meshModule.numInstances++;

    /* Set up LUTs for: RwPrimitiveType <--> rpMESHHEADER[primtype] */
    RPMESHGLOBAL(meshFlagsToPrimType)[0 /*rpMESHHEADERTRILIST */ ] =
        rwPRIMTYPETRILIST;
    RPMESHGLOBAL(meshFlagsToPrimType)[rpMESHHEADERTRISTRIP] =
        rwPRIMTYPETRISTRIP;
    RPMESHGLOBAL(meshFlagsToPrimType)[rpMESHHEADERTRIFAN] =
        rwPRIMTYPETRIFAN;
    RPMESHGLOBAL(meshFlagsToPrimType)[rpMESHHEADERLINELIST] =
        rwPRIMTYPELINELIST;
    RPMESHGLOBAL(meshFlagsToPrimType)[rpMESHHEADERPOLYLINE] =
        rwPRIMTYPEPOLYLINE;
    RPMESHGLOBAL(meshFlagsToPrimType)[rpMESHHEADERPOINTLIST] =
        rwPRIMTYPEPOINTLIST;
    RPMESHGLOBAL(primTypeToMeshFlags)[rwPRIMTYPELINELIST] =
        rpMESHHEADERLINELIST;
    RPMESHGLOBAL(primTypeToMeshFlags)[rwPRIMTYPEPOLYLINE] =
        rpMESHHEADERPOLYLINE;
    RPMESHGLOBAL(primTypeToMeshFlags)[rwPRIMTYPETRILIST] =
        0 /*rpMESHHEADERTRILIST */ ;
    RPMESHGLOBAL(primTypeToMeshFlags)[rwPRIMTYPETRISTRIP] =
        rpMESHHEADERTRISTRIP;
    RPMESHGLOBAL(primTypeToMeshFlags)[rwPRIMTYPETRIFAN] =
        rpMESHHEADERTRIFAN;
    RPMESHGLOBAL(primTypeToMeshFlags)[rwPRIMTYPEPOINTLIST] =
        rpMESHHEADERPOINTLIST;

    /* Success */
    RWRETURN(instance);
}

/****************************************************************************
 _rpBuildMeshCreate

 On entry   : A hint as to how many triangles should be.  Prevents reallocs later.
 On exit    : Pointer to new mesh on success
 */

RpBuildMesh *
_rpBuildMeshCreate(RwUInt32 bufferSize)
{
    RpBuildMesh        *mesh;

    RWFUNCTION(RWSTRING("_rpBuildMeshCreate"));

    mesh =
        (RpBuildMesh *) RwFreeListAlloc(MeshStatic.BuildMeshFreeList);

    if (mesh)
    {
        mesh->numTriangles = 0; /* Mark the end of the list */

        /* Provided hint as to how big? */
        if (bufferSize)
        {
            RwUInt32            size = (bufferSize *
                                        sizeof(RpBuildMeshTriangle));
            mesh->meshTriangles =
                (RpBuildMeshTriangle *) RwMalloc(size);

            if (!mesh->meshTriangles)
            {
                /* Can't allocate the number of triangles we wanted,
                 * so fail to call */
                RwFreeListFree(MeshStatic.BuildMeshFreeList, mesh);
                RWERROR((E_RW_NOMEM, size));
                RWRETURN((RpBuildMesh *) NULL);
            }
            mesh->triangleBufferSize = bufferSize;
        }
        else
        {
            mesh->meshTriangles = (RpBuildMeshTriangle *) NULL;
            mesh->triangleBufferSize = 0;
        }

        RWRETURN(mesh);
    }

    RWERROR((E_RW_NOMEM, sizeof(RpBuildMesh)));
    RWRETURN((RpBuildMesh *) NULL);
}

/****************************************************************************
 _rpBuildMeshDestroy

 On entry   : Mesh to detroy
 On exit    : TRUE on success
 */

RwBool
_rpBuildMeshDestroy(RpBuildMesh * mesh)
{
    RWFUNCTION(RWSTRING("_rpBuildMeshDestroy"));
    RWASSERT(mesh);

    if (mesh->meshTriangles)
    {
        RwFree(mesh->meshTriangles);
        mesh->meshTriangles = (RpBuildMeshTriangle *) NULL;
    }

    RwFreeListFree(MeshStatic.BuildMeshFreeList, mesh);
    mesh = (RpBuildMesh *) NULL;

    RWRETURN(TRUE);
}

/****************************************************************************
 _rpMeshDestroy

 On entry   : Mesh to detroy
 On exit    : TRUE on success
 */

RwBool
_rpMeshDestroy(RpMeshHeader * mesh)
{
    RWFUNCTION(RWSTRING("_rpMeshDestroy"));
    RWASSERT(mesh);

    /* are we looking at a null mesh   */
    /* header if none of these are set */
    if (mesh->flags ||
        mesh->numMeshes ||
        mesh->serialNum ||
        mesh->totalIndicesInMesh || mesh->firstMeshOffset)
    {
        _rpMeshHeaderDestroy(mesh);
    }
    RWRETURN(TRUE);
}

/****************************************************************************
 rpMeshAddTriangle

 On entry   : Mesh to add to
            : Material that triangle uses
            : Vertex indices
            : Original triangle index
 On exit    : New mesh list on success, or NULL on failure
 */

RpBuildMesh *
_rpBuildMeshAddTriangle(RpBuildMesh * mesh, RpMaterial * material,
                        RwInt32 vert1, RwInt32 vert2, RwInt32 vert3)
{
    RWFUNCTION(RWSTRING("_rpBuildMeshAddTriangle"));
    RWASSERT(mesh);

    /* Run out of space? */
    if (mesh->numTriangles >= mesh->triangleBufferSize)
    {
        RpBuildMeshTriangle *newMeshTriangles;
        RwUInt32            size = sizeof(RpBuildMeshTriangle) *
            (mesh->numTriangles + 1);

        if (mesh->numTriangles)
        {
            /* Realloc */
            newMeshTriangles = (RpBuildMeshTriangle *)
                RwRealloc(mesh->meshTriangles, size);
        }
        else
        {
            /* Malloc */
            newMeshTriangles = (RpBuildMeshTriangle *) RwMalloc(size);
        }

        if (!newMeshTriangles)
        {
            RWERROR((E_RW_NOMEM, size));
            RWRETURN((RpBuildMesh *) NULL);
        }

        mesh->meshTriangles = newMeshTriangles;
        mesh->triangleBufferSize = mesh->numTriangles + 1;
    }

    /* Chuck the new triangle in the list */
    mesh->meshTriangles[mesh->numTriangles].material = material;
    mesh->meshTriangles[mesh->numTriangles].vertIndex[0] =
        (RwInt16) vert1;
    mesh->meshTriangles[mesh->numTriangles].vertIndex[1] =
        (RwInt16) vert2;
    mesh->meshTriangles[mesh->numTriangles].vertIndex[2] =
        (RwInt16) vert3;
    mesh->numTriangles++;

    RWRETURN(mesh);
}


/**
 * \ingroup rpmesh
 * \ref RpMeshHeaderGetPrimType is used to extract the primitive type
 * from a mesh header's flags.
 *
 * Note that this function is used for debug purposes only and, for
 * efficiency, is available as a macro for final release versions of an
 * application.
 *
 * The world plugin must be attached before using this function.
 *
 * \param meshHeader  Pointer to the mesh header.
 *
 * \return Returns the \ref RwPrimitiveType of the mesh header.
 *
 * \see RpMeshHeaderSetPrimType
 *
 */
RwPrimitiveType
RpMeshHeaderGetPrimType(RpMeshHeader * meshHeader)
{
    RwPrimitiveType     result;
    RwUInt32            type;

    RWAPIFUNCTION(RWSTRING("RpMeshHeaderGetPrimType"));

    RWASSERT(NULL != meshHeader);
    /* Check the prim types still fit in a byte (if not then
     * we need to change our global LUT to non-RwUInt8) */
    RWASSERT(((RwUInt32) rwPRIMTYPEOR) <= 255);

    type = meshHeader->flags & rpMESHHEADERPRIMMASK;
    RWASSERT(type <= rpMESHHEADERPOINTLIST);

    result =
        (RwPrimitiveType) (RPMESHGLOBAL(meshFlagsToPrimType)[type]);

    RWRETURN(result);
}

/**
 * \ingroup rpmesh
 * \ref RpMeshHeaderSetPrimType is used to change the flags of a mesh
 * header to determine its primitive type.
 *
 * Note that this function is used for debug purposes only and, for
 * efficiency, is available as a macro for final release versions of an
 * application.
 *
 * The world plugin must be attached before using this function.
 *
 * \param meshHeader  Pointer to the mesh header.
 * \param primType    \ref RwPrimitiveType value to set.
 *
 * \return Returns a pointer to the mesh header on success, otherwise NULL
 *
 * \see RpMeshHeaderGetPrimType
 */
RpMeshHeader *
RpMeshHeaderSetPrimType(RpMeshHeader * meshHeader,
                        RwPrimitiveType primType)
{
    RWAPIFUNCTION(RWSTRING("RpMeshHeaderSetPrimType"));

    RWASSERT(NULL != meshHeader);
    /* Check the mesh header prim types still fit in a byte
     * (if not then we need to change rpMESHHEADERPRIMMASK
     * and the type of our global LUT) */
    RWASSERT(((RwUInt32) rpMESHHEADERPRIMTYPEOR) <= 255);

    meshHeader->flags =
        (meshHeader->flags & ~rpMESHHEADERPRIMMASK) |
        (rpMESHHEADERPRIMMASK &
         RPMESHGLOBAL(primTypeToMeshFlags)[primType &
                                           rpMESHHEADERPRIMMASK]);

    RWRETURN(meshHeader);
}


/****************************************************************************
 _rpMeshHeaderForAllMeshes

 On entry   : Mesh to enumerate, callback function, user data
 On exit    : Mehs pointer on succes, NULL on failure
 */

RpMeshHeader *
_rpMeshHeaderForAllMeshes(RpMeshHeader * meshHeader,
                          RpMeshCallBack fpCallBack, void *pData)
{
    RwInt32             numMeshes;
    RpMesh             *mesh;

    RWFUNCTION(RWSTRING("_rpMeshHeaderForAllMeshes"));
    RWASSERT(meshModule.numInstances);
    RWASSERT(meshHeader);
    RWASSERT(fpCallBack);

    numMeshes = meshHeader->numMeshes;
    mesh = ((RpMesh *) ((RwUInt8 *) (meshHeader + 1) +
                        meshHeader->firstMeshOffset));

    while (numMeshes--)
    {
        if (!fpCallBack(mesh, meshHeader, pData))
        {
            /* Early out */
            RWRETURN(meshHeader);
        }

        /* Set up for next mesh */
        mesh++;
    }

    RWRETURN(meshHeader);
}

/****************************************************************************
 _rpMeshWrite

 Writes a mesh to the given stream

 On entry   : Mesh to write
            : Object (geometry or world sector)
            : Stream to write the mesh to
            : Material list meshes materials are in
 On exit    : Stream pointer on success
 */

RwStream *
_rpMeshWrite(const RpMeshHeader * meshHeader,
             const void *__RWUNUSEDRELEASE__ object,
             RwStream * stream, const RpMaterialList * matList)
{
    binMeshHeader       bmh;
    RwUInt32            numMeshes;
    const RpMesh       *mesh;

    RWFUNCTION(RWSTRING("_rpMeshWrite"));
    RWASSERT(meshHeader);
    RWASSERT(stream);
    RWASSERT(object);

    /* Write out a header */
    bmh.flags = meshHeader->flags;
    bmh.numMeshes = (RwUInt32) meshHeader->numMeshes;
    bmh.totalIndicesInMesh = meshHeader->totalIndicesInMesh;
    if (!RwStreamWriteInt(stream, (const RwInt32 *) &bmh, sizeof(bmh)))
    {
        /* Failure */
        RWRETURN((RwStream *) NULL);
    }

    /* Then for each mesh... */
    numMeshes = meshHeader->numMeshes;
    mesh = (const RpMesh *) (meshHeader + 1);
    while (numMeshes--)
    {
        binMesh             bm;
        RwUInt32            numIndices;
        RxVertexIndex      *meshIndices;

        /* ... do another header... */
        bm.numIndices = mesh->numIndices;
        bm.matIndex = _rpMaterialListFindMaterialIndex(matList,
                                                       mesh->material);
        /* Do something reasonably sensible on error conditions */
        if (bm.matIndex < 0)
        {
            bm.matIndex = 0;
        }
        if (!RwStreamWriteInt
            (stream, (const RwInt32 *) &bm, sizeof(bm)))
        {
            /* Failure */
            RWRETURN((RwStream *) NULL);
        }

        /* ...and all the triangles */
        numIndices = mesh->numIndices;
        meshIndices = mesh->indices;

        while (numIndices > 0)
        {
            RpIndexBuffer       IndexBuffer;
            RwUInt32            i;
            RwBool              status;
            RwUInt32            writeIndices =
                (numIndices < RWINDEXBUFFERSIZE) ?
                (numIndices) : (RWINDEXBUFFERSIZE);

            for (i = 0; i < writeIndices; i++)
            {
                IndexBuffer[i] = (RwUInt32) * meshIndices++;
            }

            status =
                (NULL !=
                 RwStreamWriteInt(stream,
                                  (const RwInt32 *) &IndexBuffer[0],
                                  sizeof(IndexBuffer[0]) *
                                  writeIndices));

            if (!status)
            {
                /* Failure */
                RWRETURN((RwStream *) NULL);
            }

            numIndices -= writeIndices;
        }

        mesh++;
    }

    /* All done */
    RWRETURN(stream);
}

/****************************************************************************
 _rpMeshRead

 Reads a mesh from the given stream

 On entry   : Stream to read from
            : Object (geometry or world sector)
            : Material from object
 On exit    : Mesh read from the stream
 */

RpMeshHeader *
_rpMeshRead(RwStream * stream,
            const void *__RWUNUSED__ object,
            const RpMaterialList * matList)
{
    binMeshHeader       bmh;
    RpMeshHeader       *meshHeader;
    RwUInt32            size;

    RWFUNCTION(RWSTRING("_rpMeshRead"));
    RWASSERT(stream);
    RWASSERT(matList);

    /* Read in a header */
    if (!RwStreamReadInt(stream, (RwInt32 *) & bmh, sizeof(bmh)))
    {
        /* Failure */
        RWRETURN((RpMeshHeader *) NULL);
    }

    /* Figure out the size (little bit of slack for alignment) */
    size = (sizeof(RpMeshHeader) +
            ((sizeof(RpMesh) + 4) * bmh.numMeshes));
    size += (sizeof(RxVertexIndex) * bmh.totalIndicesInMesh);

    /* Allocate memory for the mesh */
    meshHeader = _rpMeshHeaderCreate(size);
    if (meshHeader)
    {
        RpMesh             *mesh = (RpMesh *) (meshHeader + 1);

        meshHeader->flags = bmh.flags;
        meshHeader->numMeshes = (RwUInt16) bmh.numMeshes;
        meshHeader->serialNum = RPMESHGLOBAL(nextSerialNum);
        meshHeader->totalIndicesInMesh =
            (RwUInt32) bmh.totalIndicesInMesh;
        meshHeader->firstMeshOffset = 0;
        RPMESHGLOBAL(nextSerialNum)++;

        if (!MeshesRead(stream, matList, meshHeader->numMeshes,
                        mesh, (RxVertexIndex *) (mesh + bmh.numMeshes)))
        {
            meshHeader = (RpMeshHeader *) NULL;
        }
    }

    /* All done */
    RWRETURN(meshHeader);
}

/****************************************************************************
 _rpMeshSize

 Calculates size of mesh (in bytes) when serialised

 On entry   : Mesh to size up
 On exit    : Size of mesh when serialised (in bytes)
 */

RwInt32
_rpMeshSize(const RpMeshHeader * meshHeader)
{
    RwUInt32            size;

    RWFUNCTION(RWSTRING("_rpMeshSize"));
    RWASSERT(meshHeader);

    /* The binary size is pretty easy */
    size = (sizeof(binMeshHeader) +
            (meshHeader->numMeshes * sizeof(binMesh)) +
            (meshHeader->totalIndicesInMesh * sizeof(RwUInt32)));

    RWRETURN(size);
}

RwInt16
_rpMeshGetNextSerialNumber(void)
{
    RwInt16             serialNum;

    RWFUNCTION(RWSTRING("_rpMeshGetNextSerialNumber"));

    serialNum = RPMESHGLOBAL(nextSerialNum);

    RPMESHGLOBAL(nextSerialNum)++;

    RWRETURN(serialNum);
}
