/****************************************************************************
 *                                                                          *
 *  Module  :   nodeOpenGLPatchAtomicInstance.c                             *
 *                                                                          *
 *  Purpose :   OpenGL Node for a custom power pipe to render patches.      *
 *                                                                          *
 ****************************************************************************/

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

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

/* RW includes */
#include "rpplugin.h"
#include <rpdbgerr.h>
#include <rwcore.h>
#include <rpworld.h>

#include <rtbezpat.h>

#include "rpplugin.h"
#include "rppatch.h"

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

#include "nodeOpenGLPatchAtomicInstance.h"

#ifdef WIN32
#include "windows.h"
#endif
#include "GL/gl.h"

#if (!defined(DOXYGEN))
static const char rcsid[] __RWUNUSED__ =
    "@@@@(#)$Id: nodeopenglpatchatomicinstance.c,v 1.3 2001/10/03 08:58:41 markf Exp $";
#endif /* (!defined(DOXYGEN)) */

/****************************************************************************
 globals (across program)
 */

/****************************************************************************
 Local defines
 */

#define NUMCLUSTERSOFINTEREST 5
#define NUMOUTPUTS            1

#define MESSAGE(_string)                                     \
    RwDebugSendMessage(rwDEBUGMESSAGE, "PatchAtomicCSL", _string)

#define PATCHOUT         0
#define PATCHPASSTHROUGH 1

/*****************************************************************************
 * Vertex buffer is destroyed.
 */
static void
PatchOpenGLDestroyVertexBuffer(RwResEntry * repEntry)
{
    RpPatchOpenGLAtomic *glAtomic =
        (RpPatchOpenGLAtomic *) (repEntry + 1);

    RWFUNCTION(RWSTRING("PatchOpenGLDestroyVertexBuffer"));

    RWASSERT(repEntry);

    if (glAtomic->vidMemVerts)
    {
        _rwOpenGLVertexHeapFree(glAtomic->vidMemVerts);
    }

    RWRETURNVOID();
}

/*****************************************************************************
 PatchAtomicOpenGLInstanceNode

 Grab stuff out of an Atomic and wang it into a packet - sort out caching and per-poly stuff later.

 on entry: Input  - info for accessing the Clusters and Channels in which this
                    Node is interested, plus the widths of all the Clusters.
           Memory - the MemoryArena from which this Node should get any memory
                    it needs
           Data   - Oi!! This is/may be temporary....
 on exit : TRUE on success, FALSE on failure
*/

static RwBool
PatchAtomicOpenGLInstanceNode(RxPipelineNodeInstance * self,
                              const RxPipelineNodeParam * params)
{
    RpAtomic           *atomic;
    RpGeometry         *geom;
    RwResEntry         *repEntry;
    RwResEntry        **repEntryOwner;
    void               *owner;

    RpPatchOpenGLAtomic *glAtomic;

    PatchAtomicData    *atomicData;
    RpMeshHeader       *meshHeader;
    RwInt32             res, numMeshes, numVerts, numIndices, numTris;
    RpPatchInstanceAtomic *instAtomic;
    RpPatchInstanceMesh *instMesh;
    RpInterpolator     *interpolator;

    RWFUNCTION(RWSTRING("PatchAtomicOpenGLInstanceNode"));

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

#ifdef CHO_DUMP

    rpPatchDumpAtomic(atomic);

#endif /* CHO_DUMP */

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

    interpolator = &atomic->interpolator;

    /* 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 */
        glAtomic = (RpPatchOpenGLAtomic *) (repEntry + 1);
        instAtomic = &glAtomic->instAtomic;
        if ((instAtomic->serialNum != meshHeader->serialNum) ||
            (instAtomic->res != res))
        {
            /* Things have changed, destroy resources to force reinstance */
            RwResourcesFreeResEntry(repEntry);
            repEntry = NULL;
        }
    }

    if (NULL == repEntry)
    {
        PatchMesh          *patchMesh;
        const RpMesh       *mesh;
        RxVertexIndex      *idxs;
        RwUInt32            size;
        RwUInt32            numQuadPatches;
        RwUInt32            numTriPatches;

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

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

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

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

        size = sizeof(RpPatchOpenGLAtomic) +
            (numVerts * sizeof(RxObjSpace3DVertex)) +
            (numMeshes * sizeof(RpPatchInstanceMesh)) +
            (numIndices * sizeof(RxVertexIndex));

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

        /* Extra header info */
        glAtomic = (RpPatchOpenGLAtomic *) (repEntry + 1);
        instAtomic = &glAtomic->instAtomic;
        instAtomic->serialNum = meshHeader->serialNum;
        instAtomic->res = res;

        /* Set the vertex pointer */
        mesh = (const RpMesh *)
            ((const RwUInt8 *) (meshHeader + 1) +
             meshHeader->firstMeshOffset);

        idxs = (RxVertexIndex *) & instAtomic->meshes[numMeshes];

        instAtomic->indices = idxs;
        instAtomic->vertices =
            (RxObjSpace3DVertex *) (idxs + numIndices);
        instAtomic->numMeshes = numMeshes;
        instAtomic->totalIndices = numIndices;
        instAtomic->totalVerts = numVerts;

        /* Fill the vertex buffer (interpolates) */
        if (FALSE == _rpPatchInstanceAtomic(instAtomic, atomic, res))
        {
            RWRETURN(FALSE);
        }

        if (gl.VertexArrayRangeNV)
        {
            glAtomic->vidMemVerts =
                _rwOpenGLVertexHeapMalloc(numVerts *
                                          sizeof(RxObjSpace3DVertex));

            if (glAtomic->vidMemVerts)
            {
                memcpy(glAtomic->vidMemVerts,
                       instAtomic->vertices,
                       numVerts * sizeof(RxObjSpace3DVertex));

                _rwOpenGLVertexHeapCopyComplete();
            }
            else
            {
                glAtomic->vidMemVerts = NULL;
            }
        }
        else
        {
            glAtomic->vidMemVerts = NULL;
        }

        interpolator->flags &= ~rpINTERPOLATORDIRTYINSTANCE;
    }
    else
    {
        RwResourcesUseResEntry(repEntry);
    }

    /* Now build reference clusters into the repEntry */
    instMesh = instAtomic->meshes;
    while (numMeshes--)
    {
        RxPacket           *packet;
        RxCluster          *indices, *meshState, *renderState,
            *objVerts, *extensionData;
        RxMeshStateVector  *meshData;
        RxRenderStateVector *rsvp;
        RxOpenGLExtensionData *extData;

#if (0)
        RwUInt8             primTypeRatio[7] = { 0, 2, 1, 3, 1, 1, 1 };
        RwUInt8             primTypeOffset[7] = { 0, 0, 1, 0, 1, 1, 0 };
#endif /* (0) */

        packet = RxPacketCreate(self);
        RWASSERT(NULL != packet);

        objVerts = RxClusterLockWrite(packet, 0, self);
        RWASSERT(NULL != objVerts);

        /* HACK: clear the MODIFIED flag */
        objVerts->flags = 0;

        meshState = RxClusterLockWrite(packet, 2, self);
        RWASSERT(NULL != meshState);
        renderState = RxClusterLockWrite(packet, 3, self);
        RWASSERT(NULL != renderState);
        extensionData = RxClusterLockWrite(packet, 4, self);
        RWASSERT(NULL != extensionData);

        meshState =
            RxClusterInitializeData(meshState, 1,
                                    sizeof(RxMeshStateVector));
        RWASSERT(NULL != meshState);
        renderState =
            RxClusterInitializeData(renderState, 1,
                                    sizeof(RxRenderStateVector));
        RWASSERT(NULL != renderState);
        extensionData =
            RxClusterInitializeData(extensionData, 1,
                                    sizeof(RxOpenGLExtensionData));
        RWASSERT(NULL != extensionData);

        objVerts = RxClusterSetExternalData(objVerts,
                                            instAtomic->vertices +
                                            instMesh->minVert,
                                            sizeof(RxObjSpace3DVertex),
                                            instMesh->numVerts);
        RWASSERT(NULL != objVerts);

        /* Set up MeshState data for this packet */
        meshData = RxClusterGetCursorData(meshState, RxMeshStateVector);
        RWASSERT(NULL != meshData);
        meshData->SourceObject = (void *) instMesh->material;
        meshData->DataObject =
            (void *) RxPipelineNodeParamGetData(params);
        meshData->PrimType = (RwPrimitiveType) rwPRIMTYPETRISTRIP;
        meshData->NumElements = instMesh->numIndices;
        meshData->NumVertices = instMesh->numVerts;

        /* Set up the Local to Camera matrix for this Atomic */
        RwMatrixSetIdentity(&meshData->Obj2Cam);
        meshData->Obj2World = *RwFrameGetLTM(RpAtomicGetFrame(atomic));
        meshData->Flags = RpGeometryGetFlags(geom);

        meshData->SurfaceProperties =
            *RpMaterialGetSurfaceProperties(instMesh->material);
        meshData->Texture = RpMaterialGetTexture(instMesh->material);
        meshData->MatCol = *RpMaterialGetColor(instMesh->material);

        RpMaterialGetRenderPipeline(instMesh->material,
                                    &meshData->Pipeline);

        meshData->ClipFlagsAnd = 0;
        meshData->ClipFlagsOr = 0;
        meshData->SourceMesh = instMesh->mesh;

        meshState->numUsed++;

        /* Set up RenderState data for this packet */
        rsvp = RxClusterGetCursorData(renderState, RxRenderStateVector);
        *rsvp = RXPIPELINEGLOBAL(defaultRenderState);
        if ((meshData->Flags & rpGEOMETRYTEXTURED) &&
            (NULL != meshData->Texture))
        {
            rsvp->TextureRaster = RwTextureGetRaster(meshData->Texture);
            rsvp->AddressModeU =
                RwTextureGetAddressingU(meshData->Texture);
            rsvp->AddressModeV =
                RwTextureGetAddressingV(meshData->Texture);
            rsvp->FilterMode =
                RwTextureGetFilterMode(meshData->Texture);
        }

        /* we like transparent objects too */
        if (meshData->MatCol.alpha != 255)
        {
            rsvp->Flags |= rxRENDERSTATEFLAG_VERTEXALPHAENABLE;
        }
        else
        {
            rsvp->Flags &= ~rxRENDERSTATEFLAG_VERTEXALPHAENABLE;
        }

        renderState->numUsed++;

        /* Set up extension data */
        extData =
            RxClusterGetCursorData(extensionData,
                                   RxOpenGLExtensionData);
        if (glAtomic->vidMemVerts == NULL)
        {
            extData->vidMemVerts = NULL;
        }
        else
        {
            extData->vidMemVerts =
                glAtomic->vidMemVerts + instMesh->minVert;
        }
        extData->minVert = instMesh->minVert;
        extData->numVerts = instMesh->numVerts;

        extensionData->numUsed++;

        indices = RxClusterLockWrite(packet, 1, self);
        RWASSERT(NULL != indices);
        indices = RxClusterSetExternalData(indices,
                                           instMesh->indices,
                                           sizeof(RxVertexIndex),
                                           instMesh->numIndices);
        RWASSERT(NULL != indices);

        RxPacketDispatch(packet, 0, self);

        /* Next mesh */
        instMesh++;
    }

#ifdef RWMETRICS
    /* Now update our metrics statistics */
    RWSRCGLOBAL(metrics)->numVertices += numVerts;
    RWSRCGLOBAL(metrics)->numTriangles += numTris;
#endif

    RWRETURN(TRUE);
}

/**
 * \ingroup rppatchopengl
 * \ref RxNodeDefinitionGetOpenGLPatchAtomicInstance
 * returns a pointer to a node to instance a patch atomic in a form suitable for
 * rendering by an OpenGL 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 RxClOpenGLExtensionData  - don't want
 *
 * The characteristics of this node's output:
 *      \li RxClObjSpace3DVertices   - valid
 *      \li RxClIndices              - valid
 *      \li RxClMeshState            - valid
 *      \li RxClRenderState          - valid
 *      \li RxClOpenGLExtensionData  - valid
 *
 * \return pointer to a node to instance an atomic
 *
 * \see RxNodeDefinitionGetAtomicEnumerateLights
 * \see RxNodeDefinitionGetMaterialScatter
 */

RxNodeDefinition *
RxNodeDefinitionGetOpenGLPatchAtomicInstance(void)
{
    static RxClusterRef N1clofinterest[] = { /* */
        {&RxClObjSpace3DVertices, rxCLALLOWABSENT, rxCLRESERVED},
        {&RxClIndices, rxCLALLOWABSENT, rxCLRESERVED},
        {&RxClMeshState, rxCLALLOWABSENT, rxCLRESERVED},
        {&RxClRenderState, rxCLALLOWABSENT, rxCLRESERVED},
        {&RxClOpenGLExtensionData, rxCLALLOWABSENT, rxCLRESERVED}
    };

    static RxClusterValidityReq N1inputreqs[NUMCLUSTERSOFINTEREST] = { /* */
        rxCLREQ_DONTWANT,
        rxCLREQ_DONTWANT,
        rxCLREQ_DONTWANT,
        rxCLREQ_DONTWANT,
        rxCLREQ_DONTWANT
    };

    static RwChar       _PatchAtomicOut[] = "PatchOut";
    static RxClusterValid N1outcl1[NUMCLUSTERSOFINTEREST] = { /* */
        rxCLVALID_VALID,
        rxCLVALID_VALID,
        rxCLVALID_VALID,
        rxCLVALID_VALID,
        rxCLVALID_VALID
    };

    static RxOutputSpec N1outputs[] = { /* */
        {_PatchAtomicOut,
         N1outcl1,
         rxCLVALID_NOCHANGE}
    };

    static RwChar       _PatchAtomic_csl[] = "PatchAtomic.csl";

    static RxNodeDefinition nodeOpenGLPatchAtomicInstanceCSL = { /* */
        _PatchAtomic_csl,       /* Name */
        {                      /* nodemethods */
         PatchAtomicOpenGLInstanceNode, /* +-- nodebody */
         NULL,                 /* +-- nodeinit */
         NULL,                 /* +-- nodeterm */
         NULL,
         NULL,
         NULL,
         NULL},
        {                      /* Io */
         NUMCLUSTERSOFINTEREST, /* +-- NumClustersOfInterest */
         N1clofinterest,       /* +-- ClustersOfInterest */
         N1inputreqs,          /* +-- InputRequirements */
         NUMOUTPUTS,           /* +-- NumOutputs */
         N1outputs             /* +-- Outputs */
         },
        0,
        FALSE,
        0
    };

    RWAPIFUNCTION(RWSTRING("RxNodeDefinitionGetOpenGLPatchAtomicInstance"));

    RWRETURN(&nodeOpenGLPatchAtomicInstanceCSL);
}
