/*===========================================================================*
 *--- Include files ---------------------------------------------------------*
 *===========================================================================*/
#include <rwcore.h>
#include <rpworld.h>

#include "rpplugin.h"
#include "rpdbgerr.h"

#include "rppatch.h"

#include "patchatomic.h"
#include "patch.h"

#include "noded3d8patchatomicinstance.h"

#define RWOBJSPACE3D_FVF  (D3DFVF_XYZ | D3DFVF_NORMAL | D3DFVF_DIFFUSE | D3DFVF_TEX1)

#define FLOATASINT(f) (*((const RwInt32 *)&(f)))

/*===========================================================================*
 *--- Private Types ---------------------------------------------------------*
 *===========================================================================*/

/*===========================================================================*
 *--- Private Global Variables ----------------------------------------------*
 *===========================================================================*/

/*===========================================================================*
 *--- Private Defines -------------------------------------------------------*
 *===========================================================================*/

/*===========================================================================*
 *--- Local Types -----------------------------------------------------------*
 *===========================================================================*/

/*===========================================================================*
 *--- Local Global Variables ------------------------------------------------*
 *===========================================================================*/
#if (!defined(DXOYGEN))
static const char rcsid[] __RWUNUSED__ =
    "@@@@(#)$Id: noded3d8patchatomicinstance.c,v 1.3 2001/10/04 14:17:40 davidg Exp $";
#endif /* (!defined(DXOYGEN)) */

/*===========================================================================*
 *--- Local Defines ---------------------------------------------------------*
 *===========================================================================*/
#define NUMCLUSTERSOFINTEREST 0
#define NUMOUTPUTS 0

#define PATCHPASSTHROUGH 1

/*===========================================================================*
 *--- Local functions -------------------------------------------------------*
 *===========================================================================*/

 /****************************************************************************
 _rxD3D8PatchGetNumTotalVertices

 Purpose:

 On entry:

 On exit :
*/
static RwUInt32
_rxD3D8PatchGetNumTotalVertices(const RwResEntry *repEntry)
{
    const RxD3D8ResEntryHeader  *resEntryHeader;
    const RxD3D8InstanceData    *instancedData;
    RwInt32                     numMeshes;
    RwUInt32                    numVerts;

    RWFUNCTION(RWSTRING("_rxD3D8PatchGetNumTotalVertices"));

    /* Get the instanced data */
    resEntryHeader = (const RxD3D8ResEntryHeader *)(repEntry + 1);
    instancedData = (const RxD3D8InstanceData *)(resEntryHeader + 1);

    /* Get the number of meshes */
    numMeshes = resEntryHeader->numMeshes;

    numVerts = 0;
    while (numMeshes--)
    {
        numVerts += instancedData->numVertices;

        instancedData++;
    }

    RWRETURN(numVerts);
}

/*****************************************************************************
 _rxD3D8PatchDestroyVertexBuffer

 Destroy all the buffer memory

 Inputs :
 Outputs :
 */
static void
_rxD3D8PatchDestroyVertexBuffer( RwResEntry *repEntry )
{
    RxD3D8ResEntryHeader    *resEntryHeader;
    RxD3D8InstanceData      *instancedData;
    LPDIRECT3DVERTEXBUFFER8 vertexBuffer;
    RwUInt32                numMeshes;
    RwUInt32                fvf;
    RwUInt32                stride;
    RwUInt32                numVertices;
    RwUInt32                baseIndex;

    RWFUNCTION(RWSTRING("_rxD3D8PatchDestroyVertexBuffer"));

    resEntryHeader = (RxD3D8ResEntryHeader *)(repEntry + 1);

    /* Get the instanced data structures */
    instancedData = (RxD3D8InstanceData *)(resEntryHeader + 1);

    /* Save shared data */
    vertexBuffer = instancedData->vertexBuffer;
    fvf = instancedData->vertexShader;
    stride = instancedData->stride;
    baseIndex = instancedData->baseIndex;

    /* Get the number of meshes */
    numMeshes = resEntryHeader->numMeshes;
    numVertices = 0;

    while (numMeshes--)
    {
        /* Destroy the index buffer */
        if (instancedData->indexBuffer)
        {
            IDirect3DIndexBuffer8_Release((LPDIRECT3DINDEXBUFFER8)instancedData->indexBuffer);
            instancedData->indexBuffer = NULL;
        }

        instancedData->vertexBuffer = NULL;

        numVertices += instancedData->numVertices;

        /* On to the next RxD3D8InstanceData */
        instancedData++;
    }

    /* Destroy the only one vertex buffer that we have */
    if (vertexBuffer)
    {
        _rxD3D8VertexBufferManagerDestroy(fvf, stride * numVertices,
                                            vertexBuffer, baseIndex);
    }

    RWRETURNVOID();
}

/*****************************************************************************
 _rxD3D8PatchAtomicAllInOneNodeNode

 _refineNode

 Inputs :
 Outputs :
 */
static RwBool
_rxD3D8PatchAtomicAllInOneNodeNode( RxPipelineNodeInstance *self,
                            const RxPipelineNodeParam *params )
{
    RpNodePatchData *patchData;

    RpAtomic        *atomic;
    RpGeometry      *geom;

    PatchAtomicData *atomicData;
    RpMeshHeader    *meshHeader;
    RwInt32         res;
    RwInt32         numMeshes;

    RwResEntry      *repEntry;
    RwResEntry      **repEntryOwner;

    PatchMesh       *patchMesh;
    RwUInt32        numQuadPatches;
    RwUInt32        numTriPatches;
    RwUInt32         numVerts;

    void            *owner;

    RxD3D8ResEntryHeader    *resEntryHeader;

    RwMatrix            *matrix;
    RwUInt32            lighting;

    RWFUNCTION(RWSTRING("_rxD3D8PatchAtomicAllInOneNodeNode"));

    RWASSERT(NULL != self);
    RWASSERT(NULL != params);

    patchData = (RpNodePatchData *) self->privateData;
    RWASSERT(NULL != patchData);

    /* Cheap early out if this node's toggled off */
    if (0 && (patchData->patchOn == FALSE))
    {
        RxPacketDispatch(NULL, PATCHPASSTHROUGH, self);
        RWRETURN(TRUE);
    }

    atomic = (RpAtomic *) RxPipelineNodeParamGetData(params);
    RWASSERT(NULL != atomic);

    atomicData = PATCHATOMICGETDATA(atomic);
    RWASSERT(NULL != atomicData);

    geom = RpAtomicGetGeometry(atomic);
    RWASSERT(NULL != geom);

    numVerts = geom->numVertices;
    /* If there ain't vertices, we cain't make packets... */
    if (numVerts <= 0)
    {
        /* Don't execute the rest of the pipeline */
        RWRETURN(TRUE);
    }

    meshHeader = geom->mesh;
    numMeshes = meshHeader->numMeshes;
    /* Early out if no meshes */
    if (numMeshes <= 0)
    {
        /* If the app wants to use plugin data to make packets, it
         * should use its own instancing function. If we have verts
         * here, we need meshes too in order to build a packet. */
        RWRETURN(TRUE);
    }

    /* Get the patch mesh. info */
    patchMesh = atomicData->patchMesh;
    RWASSERT(NULL != patchMesh);

    numQuadPatches = _rpPatchMeshGetNumQuadPatches(patchMesh);
    numTriPatches = _rpPatchMeshGetNumTriPatches(patchMesh);

    /* Query for the LOD. */
    res = (atomicData->lod.callback)(atomic, atomicData->lod.userData);

    /* Total number of verts. */
    numVerts = (numQuadPatches * PATCHQUADNUMVERT(res + 1)) +
               (numTriPatches * PATCHTRINUMVERT(res + 1));


    /* If the geometry has more than one morph target the resEntry in the
     * atomic is used else the resEntry in the geometry */
    if (RpGeometryGetNumMorphTargets(geom) != 1)
    {
        owner = (void *) atomic;
        repEntryOwner = &atomic->repEntry;
        repEntry = atomic->repEntry;
    }
    else
    {
        owner = (void *) geom;
        repEntryOwner = &geom->repEntry;
        repEntry = geom->repEntry;
    }

    if (NULL != repEntry)
    {
        /* If anything has changed, we should re-instance */
        resEntryHeader = (RxD3D8ResEntryHeader *) (repEntry + 1);

        if ( (resEntryHeader->serialNumber != meshHeader->serialNum) ||
             (numVerts != _rxD3D8PatchGetNumTotalVertices(repEntry)) )
        {
            /* Things have changed, destroy resources to force reinstance */
            RwResourcesFreeResEntry(repEntry);
            repEntry = NULL;
        }
    }

    /* We need to re-generate the mesh due to some changes. */
    if (repEntry == NULL)
    {
        RwInt32                 numIndices;
        RwInt32                 numTris;
        RwUInt32                size;
        RpPatchInstanceAtomic   *instAtomic;
        RpPatchInstanceMesh     *instMesh;
        RxD3D8InstanceData      *instancedData;
        LPDIRECT3DVERTEXBUFFER8 vertexBuffer;
        RwUInt32                baseIndex;

        /* Total number of indices and tris. */
        numIndices = (numQuadPatches * PATCHQUADNUMINDEX(res + 1)) +
                     (numTriPatches * PATCHTRINUMINDEX(res + 1));

        numTris = (numQuadPatches * PATCHQUADNUMTRI(res + 1)) +
                  (numTriPatches * PATCHTRINUMTRI(res + 1));

        /* Add extra indices for connecting patches together. */
        numIndices += (numQuadPatches + numTriPatches - 1) * 2;

        /* Add extra for odd number of tri patches to preserve winding order. */
        if (res & 0x01)
            numIndices += (numTriPatches);

        /* RpPatchInstanceAtomic, stores serialNumber & numMeshes */
        size = sizeof(RxD3D8ResEntryHeader);

        /* RxD3D8InstanceData structures, one for each mesh */
        size += sizeof(RxD3D8InstanceData) * numMeshes;

        repEntry = RwResourcesAllocateResEntry(owner, repEntryOwner,
                                               size,
                                               _rxD3D8PatchDestroyVertexBuffer);
        RWASSERT(NULL != repEntry);

        /* Blank the RpPatchInstanceAtomic & RxD3D8InstanceData's to '0' */
        memset((repEntry + 1), 0, size);

        /* Set the header info */
        resEntryHeader = (RxD3D8ResEntryHeader *) (repEntry + 1);
        resEntryHeader->serialNumber = meshHeader->serialNum;
        resEntryHeader->numMeshes = (RwUInt16)numMeshes;

        /* Create temporary atomic info */
        size = sizeof(RpPatchInstanceAtomic) +
            /*
            (numVerts * sizeof(RxObjSpace3DVertex)) +
            */
            (numMeshes * sizeof(RpPatchInstanceMesh)) +
            (numIndices * sizeof(RxVertexIndex));

        instAtomic = (RpPatchInstanceAtomic *)RwMalloc(size);

        instAtomic->indices = (RxVertexIndex *) & instAtomic->meshes[numMeshes];
        instAtomic->numMeshes = numMeshes;
        instAtomic->totalIndices = numIndices;
        instAtomic->totalVerts = numVerts;

        /* Create only one vertex buffer stored in the first instance data */
        if (FALSE == _rxD3D8VertexBufferManagerCreate(RWOBJSPACE3D_FVF,
                                                    numVerts * sizeof(RxObjSpace3DVertex),
                                                    (void **)&vertexBuffer,
                                                    &baseIndex))
        {
            RwFree(instAtomic);

            RWRETURN(FALSE);
        }

        /*
         *  Tesselate the patch directly over the vertex buffer
         */
        if (SUCCEEDED(IDirect3DVertexBuffer8_Lock(vertexBuffer,
                         baseIndex * sizeof(RxObjSpace3DVertex),
                         numVerts * sizeof(RxObjSpace3DVertex),
                         (RwUInt8 **)&(instAtomic->vertices), 0)))
        {
            /* Interpolate */
            if (FALSE == _rpPatchInstanceAtomic(instAtomic, atomic, res))
            {
                _rxD3D8VertexBufferManagerDestroy(RWOBJSPACE3D_FVF,
                                                numVerts * sizeof(RxObjSpace3DVertex),
                                                (void *)vertexBuffer, baseIndex);

                RwFree(instAtomic);

                RWRETURN(FALSE);
            }

            IDirect3DVertexBuffer8_Unlock(vertexBuffer);
        }

        /* Get the first RxD3D8InstanceData pointer */
        instancedData = (RxD3D8InstanceData *)(resEntryHeader + 1);

        /* Fill instance data and indices */
        instMesh = instAtomic->meshes;
        numMeshes = instAtomic->numMeshes;
        while (numMeshes--)
        {
            /*
             * Min vertex index,
             * (needed for instancing & reinstancing)
             */
            instancedData->minVert = instMesh->minVert;

            /* Number of vertices */
            instancedData->numVertices = instMesh->numVerts;

            /* Primitive type */
            instancedData->primType = D3DPT_TRIANGLESTRIP;

            /* The number of indices */
            instancedData->numIndices = instMesh->numIndices;

            /* Material */
            instancedData->material = instMesh->material;

            /* Vertex shader */
            instancedData->vertexShader = RWOBJSPACE3D_FVF;

            /* The vertex format stride */
            instancedData->stride = sizeof(RxObjSpace3DVertex);

            /* Initialize the vertex buffers pointers */
            instancedData->vertexBuffer = vertexBuffer;

            /* Initialize vertex buffer managed to FALSE */
            instancedData->baseIndex = baseIndex + instancedData->minVert;

            instancedData->managed = TRUE;

            /* Initialize vertex alpha to FALSE */
            instancedData->vertexAlpha = FALSE;

            /*
             * Set the index buffer
             */

            /* Initialize the index buffers pointers */
            if (RwD3D8IndexBufferCreate(instMesh->numIndices, &(instancedData->indexBuffer)))
            {
                RxVertexIndex   *indexBuffer;

                if (D3D_OK == IDirect3DIndexBuffer8_Lock((LPDIRECT3DINDEXBUFFER8)instancedData->indexBuffer,
                                                    0, 0, (RwUInt8 **)&indexBuffer, 0))
                {
                    memcpy(indexBuffer, instMesh->indices, sizeof(RxVertexIndex) * instMesh->numIndices);

                    IDirect3DIndexBuffer8_Unlock((LPDIRECT3DINDEXBUFFER8)instancedData->indexBuffer);
                }
            }

            instancedData++;
            instMesh++;
        }

        RwFree(instAtomic);
    }
    else
    {
        RwResourcesUseResEntry(repEntry);
    }

    /*
     * Set up lights
     */
    _rwD3D8AtomicDefaultLightingCallback((void *)atomic);

    /* 
     * Set the world transform
     */
    matrix = RwFrameGetLTM(RpAtomicGetFrame(atomic));

    RwD3D8SetTransformWorld(matrix);

    RwD3D8GetRenderState(D3DRS_LIGHTING, &lighting);

    if (lighting &&
        !rwMatrixTestFlags(matrix, (rwMATRIXTYPENORMAL | rwMATRIXINTERNALIDENTITY)))
    {
        const RwReal minlimit = 0.9f;
        const RwReal maxlimit = 1.1f;
        RwReal length;
        
        length = RwV3dDotProduct(&(matrix->right), &(matrix->right));

        if ( (FLOATASINT(length) > FLOATASINT(maxlimit)) || (FLOATASINT(length) < FLOATASINT(minlimit)) )
        {
            RwD3D8SetRenderState(D3DRS_NORMALIZENORMALS, TRUE);
        }
        else
        {
            length = RwV3dDotProduct(&(matrix->up), &(matrix->up));

            if ( (FLOATASINT(length) > FLOATASINT(maxlimit)) || (FLOATASINT(length) < FLOATASINT(minlimit)) )
            {
                RwD3D8SetRenderState(D3DRS_NORMALIZENORMALS, TRUE);
            }
            else
            {
                length = RwV3dDotProduct(&(matrix->at), &(matrix->at));

                if ( (FLOATASINT(length) > FLOATASINT(maxlimit)) || (FLOATASINT(length) < FLOATASINT(minlimit)) )
                {
                    RwD3D8SetRenderState(D3DRS_NORMALIZENORMALS, TRUE);
                }
                else
                {
                    RwD3D8SetRenderState(D3DRS_NORMALIZENORMALS, FALSE);
                }
            }
        }
    }
    else
    {
        RwD3D8SetRenderState(D3DRS_NORMALIZENORMALS, FALSE);
    }

    /*
     * Render
     */
    if (patchData->renderCallback != NULL)
    {
        patchData->renderCallback(repEntry, atomic, rpATOMIC, RpGeometryGetFlags(geom));
    }

    RWRETURN(TRUE);
}

/*===========================================================================*
 *--- Private functions -----------------------------------------------------*
 *===========================================================================*/

/*===========================================================================*
 *--- Plugin Engine Functions -----------------------------------------------*
 *===========================================================================*/

/*===========================================================================*
 *--- Plugin API Functions --------------------------------------------------*
 *===========================================================================*/

/**
 * \ingroup rprefine
 * \ref RxNodeDefinitionGetD3D8PatchAtomicInstance
 * returns a pointer to a node to instance a patch atomic in a form suitable for
 * rendering by a DirectX 7 pipeline.
 *
 * The node has one output. Successful generation of meshes from patches are passed
 * via this output.
 *
 * The input requirements of this node:
 *     \li RxClObjSpace3DVertices  - don't want
 *     \li RxClIndices             - don't want
 *     \li RxClMeshState           - don't want
 *     \li RxClRenderState         - don't want
 *     \li RxClD3D8VertexBufferInfo - don't want
 *
 * The characteristics of this node's outputs:
 *     \li RxClObjSpace3DVertices  - valid
 *     \li RxClIndices             - valid
 *     \li RxClMeshState           - valid
 *     \li RxClRenderState         - valid
 *     \li RxClD3D8VertexBufferInfo - valid
 *
 * \return pointer to node for patch faceting custom pipelines on success,
 * NULL otherwise
 */
RxNodeDefinition   *
RxNodeDefinitionGetD3D8PatchAtomicInstance(void)
{
    static RwChar _PatchAtomic_csl[] = "PatchAtomic.csl";
    
    static RxNodeDefinition nodeD3D8PatchAtomic = { /* */
        _PatchAtomic_csl,            /* Name */
        {                                           /* Nodemethods */
            _rxD3D8PatchAtomicAllInOneNodeNode,         /* +-- nodebody */
            NULL,                                   /* +-- nodeinit */
            NULL,                                   /* +-- nodeterm */
            _rwD3D8PatchPipelineNodeInitFn,    /* +-- pipelinenodeinit */
            NULL,                                   /* +-- pipelineNodeTerm */
            NULL,                                   /* +-- pipelineNodeConfig */
            NULL,                                   /* +-- configMsgHandler */
        },
        {                                           /* Io */
            NUMCLUSTERSOFINTEREST,                  /* +-- NumClustersOfInterest */
            NULL,                                   /* +-- ClustersOfInterest */
            NULL,                                   /* +-- InputRequirements */
            NUMOUTPUTS,                             /* +-- NumOutputs */
            NULL                                    /* +-- Outputs */
        },
        (RwUInt32)sizeof(RpNodePatchData),  /* PipelineNodePrivateDataSize */
        FALSE,                                      /* editable */
        0                                           /* inPipes */
    };

    RWAPIFUNCTION(RWSTRING("RxNodeDefinitionGetD3D8PatchAtomicInstance"));

    RWRETURN(&nodeD3D8PatchAtomic);
}
