/**
 * \ingroup rtvcat
 * \page rtvcatoverview rtvcat toolkit overview
 *
 * The rtvcat toolkit provides a custom tristripping callback for 
 * generating tristrips that respect a vertex cache of a given size.
 *
 * Copyright Criterion Software Limited
 */

/**********************************************************************
 *
 * File :     rtvcat.cpp
 *
 * Abstract : Generates vertex cache aware tristrips
 *
 **********************************************************************
 *
 * This file is a product of Criterion Software Ltd.
 *
 * This file is provided as is with no warranties of any kind and is
 * provided without any obligation on Criterion Software Ltd. or
 * Canon Inc. to assist in its use or modification.
 *
 * Criterion Software Ltd. will not, under any
 * circumstances, be liable for any lost revenue or other damages arising
 * from the use of this file.
 *
 * Copyright (c) 1998 Criterion Software Ltd.
 * All Rights Reserved.
 *
 * RenderWare is a trademark of Canon Inc.
 *
 ************************************************************************/

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

#include "rwcore.h"
#include "rpdbgerr.h"

#include "rpworld.h"

#ifndef SKY2_DRVMODEL_H

#include "NvTriStrip.h"

/****************************************************************************
 Defines
 */

#if (defined(_WINDOWS))
#define  __RWCDECL   __cdecl
#endif /* (defined(_WINDOWS)) */

#if (!defined(__RWCDECL))
#define  __RWCDECL             /* No op */
#endif /* (!defined(__RWCDECL)) */

#define RasterHasAlpha(_type)                           \
            ( ((_type) == rwRASTERFORMAT1555) ||        \
              ((_type) == rwRASTERFORMAT4444) ||        \
              ((_type) == rwRASTERFORMAT8888) )


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

typedef struct rpMesh16 rpMesh16;
struct rpMesh16
{
    RwUInt16           *indices;    
    RwUInt32            numIndices; 
    RpMaterial         *material;   
};


/****************************************************************************
 Local (static) Functions
 */

static int __RWCDECL
SortPolygons(const void *pA, const void *pB)
{
    const int transBIT = 16, rastBIT = 8, pipeBIT = 4, texBIT = 2, matBIT = 1;

    const RpBuildMeshTriangle **mtpA = (const RpBuildMeshTriangle **) pA;
    const RpBuildMeshTriangle **mtpB = (const RpBuildMeshTriangle **) pB;

    RpMaterial *materialA = (*mtpA)->material;
    RpMaterial *materialB = (*mtpB)->material;

    RwRaster   *rasterA   = (RwRaster *)NULL;
    RwRaster   *rasterB   = (RwRaster *)NULL;
    RxPipeline *pipelineA = (RxPipeline *)NULL;
    RxPipeline *pipelineB = (RxPipeline *)NULL;
    RwTexture  *textureA  = (RwTexture *)NULL;
    RwTexture  *textureB  = (RwTexture *)NULL;

    /* IMO use UInts for bitfields! Sign bits are nothing but trouble... */
    RwInt32     orderA = 0;
    RwInt32     orderB = 0;

    RWFUNCTION(RWSTRING("SortPolygons"));

    /* Easy case first */
    if (materialA == materialB)
    {
        RWRETURN(0);
    }

    /* We sort on:
     *   transparency > raster > pipeline > texture
     *
     * Transparency is required for correct alpha render ordering.
     * Raster upload is the greatest cost.
     * Pipeline swap might be a significant cost - vector code upload, CPU-side code cache miss
     * Texture state changes might also hurt even with the same raster. (?)
     */

    if (materialA)
    {
        /* Place transparent materials after non transparent ones */
        if (materialA->texture)
        {
            textureA = materialA->texture;
            rasterA = RwTextureGetRaster(textureA);

            if (RasterHasAlpha
                (RwRasterGetFormat(rasterA) & (RwInt32)
                 rwRASTERFORMATPIXELFORMATMASK))
            {
                orderA |= transBIT;
            }
        }

        if (materialA->color.alpha != 0xff)
        {
            orderA |= transBIT;
        }

        pipelineA = materialA->pipeline;
    }

    if (materialB)
    {
        /* Place transparent materials after non transparent ones */
        if (materialB->texture)
        {
            textureB = materialB->texture;
            rasterB = RwTextureGetRaster(textureB);

            if (RasterHasAlpha
                (RwRasterGetFormat(rasterB) & (RwInt32)
                 rwRASTERFORMATPIXELFORMATMASK))
            {
                orderB |= transBIT;
            }
        }

        if (materialB->color.alpha != 0xff)
        {
            orderB |= transBIT;
        }

        pipelineB = materialB->pipeline;
    }

    orderA |= ((RwUInt32) rasterA > (RwUInt32) rasterB) ? rastBIT : 0;
    orderB |= ((RwUInt32) rasterA < (RwUInt32) rasterB) ? rastBIT : 0;

    orderA |= ((RwUInt32) pipelineA > (RwUInt32) pipelineB) ? pipeBIT : 0;
    orderB |= ((RwUInt32) pipelineA < (RwUInt32) pipelineB) ? pipeBIT : 0;

    orderA |= ((RwUInt32) textureA > (RwUInt32) textureB) ? texBIT : 0;
    orderB |= ((RwUInt32) textureA < (RwUInt32) textureB) ? texBIT : 0;

    orderA |= ((RwUInt32) materialA > (RwUInt32) materialB) ? matBIT : 0;
    orderB |= ((RwUInt32) materialA < (RwUInt32) materialB) ? matBIT : 0;

    RWRETURN(orderA - orderB);
}


/**
 * \ingroup rtvcat
 * \ref RpBuildMeshGenerateCacheAwareTriStrip generates a vertex cache aware
 * triangle strip.
 *
 * \param buildMesh pointer to the mesh which the triangle strip will be
 * generated from.
 * \param data  pointer to user-supplied data to pass to the callback
 * function.
 *
 * \return a pointer to the constructed mesh header.
 *
 * \see RpMeshGetTriStripMethod
 * \see RpMeshSetTriStripMethod
 * \see RpBuildMeshGenerateDefaultTriStrip
 * \see RpBuildMeshGeneratePreprocessTriStrip
 * \see RpBuildMeshGenerateExhaustiveTriStrip
 * \see RpBuildMeshGenerateTrivialTriStrip
 * \see RpBuildMeshGenerateDefaultIgnoreWindingTriStrip
 * \see RpBuildMeshGeneratePreprocessIgnoreWindingTriStrip
 * \see RpBuildMeshGenerateExhaustiveIgnoreWindingTriStrip
 *
 */
extern "C"
RpMeshHeader *
RpBuildMeshGenerateCacheAwareTriStrip(RpBuildMesh *buildMesh, 
                                      void *data)
{
    RpMeshHeader         *result;
    RpBuildMeshTriangle **triPointers;
    RwUInt32              i;
    RwUInt16              numMeshes;
    RwUInt32              meshSize;
    RxVertexIndex        *stripMeshInds;

    RWAPIFUNCTION(RWSTRING("RpBuildMeshGenerateCacheAwareTriStrip"));
    RWASSERT(buildMesh);

    triPointers = (RpBuildMeshTriangle **)RwMalloc(buildMesh->numTriangles * sizeof(RpBuildMeshTriangle *));
    if (!triPointers)
    {
        RWRETURN((RpMeshHeader *)NULL);
    }

    /* Fill in pointers so that we can sort */
    for (i = 0; i < buildMesh->numTriangles; i++)
    {
        triPointers[i] = &(buildMesh->meshTriangles[i]);
    }

    /* Now sort them */
    qsort(triPointers, buildMesh->numTriangles, sizeof(RpBuildMeshTriangle *), SortPolygons);

    /* Figure out how many meshes there are */
    numMeshes = 1;
    if (buildMesh->numTriangles >= 2)
    {
        RpMaterial *lastMat = triPointers[0]->material;

        for (i = 1; i < buildMesh->numTriangles; i++)
        {
            if (triPointers[i]->material != lastMat)
            {
                /* We found another material */
                lastMat = triPointers[i]->material;
                numMeshes++;
            }
        }
    }


    /* Allocate an mesh array */
    RwUInt16 *inMeshIndices = (RwUInt16 *)RwMalloc(buildMesh->numTriangles * 3 * sizeof(RwUInt16));
    rpMesh16 *inMeshes = (rpMesh16 *)RwMalloc(numMeshes * sizeof(rpMesh16));

    rpMesh16 *tempMesh = inMeshes;
    RwInt32 index = 0;

    RpMaterial *lastMat   = triPointers[0]->material;
    RwInt32 meshIndexCount = 0;

    tempMesh->indices    = inMeshIndices;
    tempMesh->numIndices = 0;
    tempMesh->material   = triPointers[0]->material;

    for (i = 0; i < buildMesh->numTriangles; i++)
    {
        if (triPointers[i]->material != lastMat)
        {
            tempMesh++;

            /* We found another material so store off mesh information */
            tempMesh->indices    = inMeshIndices + index;
            tempMesh->numIndices = 0;
            tempMesh->material   = triPointers[i]->material;

            lastMat = triPointers[i]->material;
        }

        inMeshIndices[index++] = triPointers[i]->vertIndex[0];
        inMeshIndices[index++] = triPointers[i]->vertIndex[1];
        inMeshIndices[index++] = triPointers[i]->vertIndex[2];

        tempMesh->numIndices += 3;
    }

    /* Don't need these any more */
    RwFree(triPointers);
    triPointers = (RpBuildMeshTriangle **)NULL;

    /* Configure the tristripper */
	SetCacheSize(CACHESIZE_GEFORCE3);
	SetStitchStrips(true);
	SetMinStripSize(0);

    if (data == 0)
    {
	    SetListsOnly(false);
    }
    else
    {
	    SetListsOnly(true);
    }

    PrimitiveGroup **primGroups = (PrimitiveGroup **)RwMalloc(numMeshes * sizeof(PrimitiveGroup *));

    for (i = 0; i < numMeshes; i++)
    {
        unsigned short numGroups;

        /* Call into NvTriStrip */
        GenerateStrips(inMeshes[i].indices, inMeshes[i].numIndices, &primGroups[i], &numGroups);
    }

    /* Work out how big the mesh header needs to be */
    RwInt32 numOutIndices = 0;
    for (i = 0; i < numMeshes; i++)
    {
        numOutIndices += primGroups[i]->numIndices;
    }

    /* Allocate the meshes */
    meshSize = sizeof(RpMeshHeader) +
               sizeof(RpMesh) * numMeshes +
               sizeof(RxVertexIndex) * numOutIndices;

    /* Should really use PMESHGLOBAL(nextSerialNum) */
    static RwInt16 nextSerialNum = 0;

    result = _rpMeshHeaderCreate(meshSize);
    if (data == 0)
    {
        result->flags = rpMESHHEADERTRISTRIP;
    }
    else
    {
        result->flags = 0;
    }

    result->numMeshes = numMeshes;
    result->serialNum = nextSerialNum; 
    result->firstMeshOffset = 0;
    result->totalIndicesInMesh = numOutIndices;
    nextSerialNum++;

    /* Fill in destination meshes */
    RpMesh *outMesh;
    outMesh = (RpMesh *)(result + 1);
    stripMeshInds = (RxVertexIndex *) (outMesh + numMeshes);
    for (i = 0; i < numMeshes; i++)
    {
        RwUInt32 j;

        /* Add in the next mesh */
        outMesh->indices    = stripMeshInds;
        outMesh->numIndices = primGroups[i]->numIndices;
        outMesh->material   = inMeshes[i].material;

        /* And the indices */
        for (j = 0; j < primGroups[i]->numIndices; j++)
        {
            *stripMeshInds++ = primGroups[i]->indices[j];
        }

        /* Skip to next */
        outMesh++;
    }

    /* Blow away our NvTriStrip resources */
    for (i = 0; i < numMeshes; i++)
    {
    	delete[] primGroups[i];
    }

    RwFree(primGroups);
    primGroups = NULL;

    RwFree(inMeshIndices);
    inMeshIndices = NULL;
    
    RwFree(inMeshes);
    inMeshes = NULL;

    RWRETURN(result);
}

#endif
