/*
 * Particle System plug-in
 */

/****************************************************************************
 *                                                                          *
 * module : nodeParticleSystemInstance.c                                    *
 *                                                                          *
 * purpose: creates a ResEntry for a ParticleSystem. For parametrically     *
 *          animated particles, this acts just like an atomic's ResEntry,   *
 *          but for incrementally animated particles, this ResEntry can be  *
 *          edited during the pipeline to update it from frame to frame.    *
 *          To facilitate this, the clusters referencing the data are       *
 *          flagged rxCLFLAGS_EXTERNALMODIFIABLE which means the array's    *
 *          contents may be changed but it cannot be freed or resized       *
 *                                                                          *
 ****************************************************************************/

/****************************************************************************
 includes
 */

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

#include "rpplugin.h"
#include <rpdbgerr.h>
#include "rpprtcls.h"

#if (!defined(DOXYGEN_SHOULD_SKIP_THIS))
static const char   rcsid[] __RWUNUSED__ =
    "@@(#)$Id: nodeParticleSystemInstance.c,v 1.33 2001/02/02 14:50:40 johns Exp $";
#endif /* (!defined(DOXYGEN_SHOULD_SKIP_THIS)) */

/****************************************************************************
 local defines
 */

#define PRIVATEDATATYPE RpParticleSystemInstanceData

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

/****************************************************************************
 local types
 */

typedef struct InstancedParticleSystem InstancedParticleSystem;
struct InstancedParticleSystem
{
    RwUInt32            serialNum; /* Serial # - combination of
                                    * elements contributing to
                                    * instanced data.  Enables us to
                                    * detect when a re-instance is
                                    * necessary.
                                    */
    RwUInt32            numParticles;
    RwUInt32            vertSize;
    RxObjSpace3DVertex *vertices;
    RpMaterial         *material;
};

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

                           Functions

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

static void
instanceVerts(RxObjSpace3DVertex * instVerts,
              RpAtomic * __RWUNUSED__ atomic, RpGeometry * geom,
              RwUInt32 vertSize)
{
    const RpMorphTarget *morphTarget = &geom->morphTarget[0];
    const RwV3d        *pos = morphTarget->verts;
    const RwRGBA       *preLitLum = geom->preLitLum;
    RwInt32             numVerts;

    RWFUNCTION(RWSTRING("instanceVerts"));

    numVerts = geom->numVertices;

    while (numVerts--)
    {
        RxObjSpace3DVertexSetPos(instVerts, pos);
        RxObjSpace3DVertexSetPreLitColor(instVerts, preLitLum);
        pos++;
        preLitLum++;
        instVerts =
            (RxObjSpace3DVertex *) ((RwUInt8 *) instVerts + vertSize);
    }

    RWRETURNVOID();
}

/*****************************************************************************
 _ParticleSystemInstanceNodePipelineNodeInitFn

 Initialises the private data 
*/
static              RwBool
_NodeParticleSystemInstancePipelineNodeInitFn(RxPipelineNode * Self)
{
    RWFUNCTION(RWSTRING("_NodeParticleSystemInstancePipelineNodeInitFn"));

    if (Self)
    {
        PRIVATEDATATYPE    *data =
            (PRIVATEDATATYPE *) Self->privateData;

        data->parametric = TRUE;

        RWRETURN((TRUE));
    }
    RWRETURN((FALSE));
}

/*****************************************************************************
 ParticleSystemInstanceNode

 Creates a ResEntry for a ParticleSystem. For parametrically
 animated particles, this acts just like an atomic's ResEntry,
 but for incrementally animated particles, this ResEntry can be
 edited during the pipeline to update it from frame to frame.
 To facilitate this, the clusters referencing the data are
 flagged rxCLFLAGS_EXTERNALMODIFIABLE which means the array's
 contents may be changed but it cannot be freed or resized

*/

static              RwBool
ParticleSystemInstanceNode(RxPipelineNodeInstance * self,
                           const RxPipelineNodeParam * params)
{
    InstancedParticleSystem *instancedParticleSystem;
    PRIVATEDATATYPE    *privateData;
    RpAtomic           *atomic;
    RpGeometry         *geom;
    RpMaterial         *mat;
    RpParticleSystem   *particleSystem;
    RwMatrix           *Obj2Cam;
    RwResEntry         *repEntry;
    RwUInt32            geomFlags;
    RwUInt32            numParticles;
    RwUInt32            output;
    RwUInt32            particleSystemDataSize;
    RwUInt32            vertSize;
    RxCluster          *meshState;
    RxCluster          *objVerts;
    RxCluster          *particles;
    RxCluster          *renderState;
    RxCluster          *systemData;
    RxMeshStateVector  *meshData;
    RxPacket           *packet;
    RxRenderStateVector *defaultRsvp;
    RxRenderStateVector *rsvp;
    const RwRGBA       *MatCol;
    const RwSurfaceProperties *SurfaceProperties;

    RWFUNCTION(RWSTRING("ParticleSystemInstanceNode"));

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

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

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

    particleSystem = RpParticleGeometryGetParticleSystem(geom);
    RWASSERT(NULL != particleSystem);

    repEntry = atomic->repEntry;
    instancedParticleSystem =
        (InstancedParticleSystem *) (repEntry + 1);
    geomFlags = RpGeometryGetFlags(geom);

    if (NULL != repEntry)
    {
        /* If the mesh has been rebuilt, we should re-instance
         * [NOTE: particle systems objects should only have one mesh
         * and should have no triangles] */
        if (instancedParticleSystem->serialNum != geom->mesh->serialNum)
        {
            /* Things have changed, destroy resources to force reinstance */
            RwResourcesFreeResEntry(repEntry);
            repEntry = (RwResEntry *)NULL;
        }
    }

    if (NULL == repEntry)
    {
        RxObjSpace3DVertex *dstVerts;
        RwUInt32            numParticles, vertSize, size;

        numParticles = geom->numVertices;

        /* Particles need UVs so... */
        vertSize = RxObjSpace3DVertexFullSize;

        size = sizeof(InstancedParticleSystem) +
            numParticles * (vertSize + sizeof(RpParticle));

        repEntry =
            RwResourcesAllocateResEntry(atomic, 
                                        &atomic->repEntry,
                                        size,
                                        (RwResEntryDestroyNotify)NULL);
        RWASSERT(NULL != repEntry);

        /* Extra header info */
        instancedParticleSystem =
            (InstancedParticleSystem *) (repEntry + 1);
        instancedParticleSystem->serialNum = geom->mesh->serialNum;

        dstVerts = (RxObjSpace3DVertex *) (instancedParticleSystem + 1);

        /* Instance the vertices */
        instanceVerts(dstVerts, atomic, geom, vertSize);

        instancedParticleSystem->numParticles = numParticles;
        instancedParticleSystem->vertices = dstVerts;
        instancedParticleSystem->vertSize = vertSize;
        /* Jesus how messy is this.... is it worth it?!?!? */
        instancedParticleSystem->material =
            ((RpMesh *) (((RwUInt8 *) (geom->mesh + 1)) +
                         geom->mesh->firstMeshOffset))->material;

        /* All clean now */
        geom->lockedSinceLastInst = 0;
    }
    else
    {
        RwUInt32            dirtyFlags = geom->lockedSinceLastInst;
        RxObjSpace3DVertex *dstVerts;
        RwUInt32            vertSize;

        RwResourcesUseResEntry(repEntry);
        instancedParticleSystem =
            (InstancedParticleSystem *) (repEntry + 1);
        dstVerts = instancedParticleSystem->vertices;
        vertSize = instancedParticleSystem->vertSize;

        /* If verts (positions) are only thing to change, 
         * reinstance just those 
         * [modified normals/UVs don't matter for particles, 
         * but prelighting does] */
        switch (dirtyFlags)
        {
            case (rpGEOMETRYLOCKPRELIGHT):
            case (rpGEOMETRYLOCKVERTICES):
                {
                    instanceVerts(dstVerts, atomic, geom, vertSize);
                    break;
                }
            default:
                break;
        }

        geom->lockedSinceLastInst = 0;
    }

    /* Now build reference clusters into the RepEntry */
    packet = RxPacketCreate(self);
    RWASSERT(NULL != packet);

    numParticles = instancedParticleSystem->numParticles;
    vertSize = instancedParticleSystem->vertSize;
    particleSystemDataSize = particleSystem->type->dataSize;

    objVerts = RxClusterLockWrite(packet, 0, self);
    meshState = RxClusterLockWrite(packet, 1, self);
    renderState = RxClusterLockWrite(packet, 2, self);
    particles = RxClusterLockWrite(packet, 3, self);

    systemData = (RxCluster *)NULL;
    if (particleSystemDataSize != 0)
    {
        systemData = RxClusterLockWrite(packet, 4, self);
    }

    meshState =
        RxClusterInitializeData(meshState, 1,
                                sizeof(RxMeshStateVector));
    RWASSERT(NULL != meshState);
    renderState =
        RxClusterInitializeData(renderState, 1,
                                sizeof(RxRenderStateVector));
    RWASSERT(NULL != renderState);
    particles =
        RxClusterInitializeData(particles, numParticles,
                                sizeof(RpParticle));
    RWASSERT(NULL != particles);

    objVerts =
        RxClusterSetExternalData(objVerts,
                                 instancedParticleSystem->vertices,
                                 vertSize, numParticles);
    RWASSERT(NULL != objVerts);

    particles =
        RxClusterSetExternalData(particles, particleSystem->particles,
                                 sizeof(RpParticle), numParticles);
    RWASSERT(NULL != particles);

    if (privateData->parametric == FALSE)
    {
        /* ObjVerts and particles data may be updated each frame 
         * by the animation node(s) */
        /* TODO: these and further flag sets are weird. 
         * & Why are they done like this? */
        objVerts->flags &= ~rxCLFLAGS_EXTERNAL;
        objVerts->flags |= rxCLFLAGS_EXTERNALMODIFIABLE;
        particles->flags &= ~rxCLFLAGS_EXTERNAL;
        particles->flags |= rxCLFLAGS_EXTERNALMODIFIABLE;
    }

    if (systemData != NULL)
    {
        systemData =
            RxClusterSetExternalData(systemData,
                                     particleSystem->typeData,
                                     particleSystemDataSize, 1);
        RWASSERT(NULL != systemData);

        /* The particle system's data is always modifiable 
         * & (probably useful, not harmful) */
        systemData->flags &= ~rxCLFLAGS_EXTERNAL;
        systemData->flags |= rxCLFLAGS_EXTERNALMODIFIABLE;
    }

    mat = instancedParticleSystem->material;

    /* Set up MeshState data for this packet */
    meshData = RxClusterGetCursorData(meshState, RxMeshStateVector);
    meshData->SourceObject = (void *) mat;
    /* The only thing that makes sense for particles */
    meshData->PrimType = rwPRIMTYPETRILIST;
    meshData->NumVertices = numParticles;
    meshData->NumElements = 0; /* No triangles for particules yet */
    /* Set up the Local to Camera matrix for this Atomic */
    Obj2Cam = RwFrameGetLTM(RpAtomicGetFrame(atomic));
    RwMatrixCopy(&meshData->Obj2Cam, Obj2Cam);
    RwMatrixTransform(&meshData->Obj2Cam,
                      &(((RwCamera *) RWSRCGLOBAL(curCamera))->
                        viewMatrix), rwCOMBINEPOSTCONCAT);
    meshData->Obj2World = *RwFrameGetLTM(RpAtomicGetFrame(atomic));
    /*meshData->SurfaceProperties = *RpGeometryGetSurfaceProperties(geom); */

    SurfaceProperties = RpMaterialGetSurfaceProperties(mat);

    RwSurfacePropertiesAssign(&meshData->SurfaceProperties,
                              SurfaceProperties);

    meshData->Flags = geomFlags;
    meshData->Texture = RpMaterialGetTexture(mat);
    MatCol = RpMaterialGetColor(mat);
    RwRGBAAssign(&meshData->MatCol, MatCol);
    meshData->Pipeline = mat->pipeline;
    meshData->ClipFlagsAnd = 0;
    meshData->ClipFlagsOr = 0;

    meshState->numUsed++;

    /* Set up RenderState data for this packet */
    rsvp = RxClusterGetCursorData(renderState, RxRenderStateVector);
    RWASSERT(NULL != rsvp);

    defaultRsvp = &RXPIPELINEGLOBAL(defaultRenderState);
    RxRenderStateVectorAssign(rsvp, defaultRsvp);

    if (geomFlags & rpGEOMETRYTEXTURED)
    {
        RwTexture          *texture = meshData->Texture;

        if (texture)
        {
            rsvp->TextureRaster = RwTextureGetRaster(texture);
            rsvp->AddressModeU = RwTextureGetAddressingU(texture);
            rsvp->AddressModeV = RwTextureGetAddressingV(texture);
            rsvp->FilterMode = RwTextureGetFilterMode(texture);
        }
    }

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

    renderState->numUsed++;

    /* Particle systems consist of one mesh only, so we're done! */
    output = (systemData == NULL) ? 1 : 0;

    RxPacketDispatch(packet, output, self);

    RWRETURN((TRUE));
}

/**
 * \ingroup rpprtcls
 * \ref RxNodeDefinitionGetParticleSystemInstanceCSL returns a
 * pointer to a node to instance an \ref RpParticleSystem.
 *
 * This node creates a \ref RwResEntry per \ref RpParticleSystem.
 * For parametrically animated particle systems, this acts just
 * like an atomic's \ref RwResEntry, but for incrementally animated
 * particles, this \ref RwResEntry can be edited during the pipeline
 * to update it from frame to frame. To facilitate this, the clusters
 * referencing the data are flagged rxCLFLAGS_EXTERNALMODIFIABLE,
 * which means that their data array's contents may be modified but
 * the array cannot be freed or resized.
 *
 * If a \ref RpParticleSystem's \ref RpParticleSystemType includes any
 * private data then this is put into a cluster which is also set to
 * rxCLFLAGS_EXTERNALMODIFIABLE so that particle-processing nodes
 * can feed back information to the \ref RpParticleSystem. If this
 * cluster is created, then packets will be sent to the node's
 * first output otherwise they will go to its second.
 *
 * \verbatim 
   The node has two outputs
   The input requirements of this node:
  
   RxClObjSpace3DVertices      - don't want
   RxClMeshState               - don't want
   RxClRenderState             - don't want
   clusterRpParticles          - don't want
   clusterRpParticleSystemData - don't want
  
   The characteristics of the first of this node's outputs:
  
   RxClObjSpace3DVertices      - valid
   RxClMeshState               - valid
   RxClRenderState             - valid
   clusterRpParticles          - valid
   clusterRpParticleSystemData - valid
  
   The characteristics of the second of this node's outputs:
  
   RxClObjSpace3DVertices      - valid
   RxClMeshState               - valid
   RxClRenderState             - valid
   clusterRpParticles          - valid
   clusterRpParticleSystemData - invalid
 
   \endverbatim
 * \return pointer to a node to instance an \ref RpParticleSystem.
 *
 * \see RxNodeDefinitionGetParticleExpandCSL
 */
RxNodeDefinition   *
RxNodeDefinitionGetParticleSystemInstanceCSL(void)
{

    static RxClusterRef N1clofinterest[] = { /* */
        {&RxClObjSpace3DVertices,
         rxCLALLOWABSENT,
         0},
        {&RxClMeshState,
         rxCLALLOWABSENT,
         0},
        {&RxClRenderState,
         rxCLALLOWABSENT,
         0},
        {&clusterRpParticles,
         rxCLALLOWABSENT,
         0},
        {&clusterRpParticleSystemData,
         rxCLALLOWABSENT,
         0}
    };

#define NUMCLUSTERSOFINTEREST \
        ((sizeof(N1clofinterest))/(sizeof(N1clofinterest[0])))

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

    static RxClusterValid N1outclWithPSData[NUMCLUSTERSOFINTEREST] = { /* */
        rxCLVALID_VALID,
        rxCLVALID_VALID,
        rxCLVALID_VALID,
        rxCLVALID_VALID,
        rxCLVALID_VALID
    };

    static RxClusterValid N1outclWithoutPSData[NUMCLUSTERSOFINTEREST] = { /* */
        rxCLVALID_VALID,
        rxCLVALID_VALID,
        rxCLVALID_VALID,
        rxCLVALID_VALID,
        rxCLVALID_INVALID
    };

    static RwChar       _InstanceOutWithPSData[] =
        RWSTRING("ParticleSystemInstanceOutWithPSData");
    static RwChar       _InstanceOutWithoutPSData[] =
        RWSTRING("ParticleSystemInstanceOutWithoutPSData");

    static RxOutputSpec N1outputs[] = { /* */
        {_InstanceOutWithPSData,
         N1outclWithPSData,
         rxCLVALID_NOCHANGE},
        {_InstanceOutWithoutPSData,
         N1outclWithoutPSData,
         rxCLVALID_NOCHANGE}
    };

#define NUMOUTPUTS \
        ((sizeof(N1outputs))/(sizeof(N1outputs[0])))

    static RwChar       _ParticleSystemInstance_csl[] =
        RWSTRING("ParticleSystemInstance.csl");

    static RxNodeDefinition nodeParticleSystemInstanceCSL = { /* */
        _ParticleSystemInstance_csl,
        {ParticleSystemInstanceNode,
         (RxNodeInitFn)NULL,
         (RxNodeTermFn)NULL,
         _NodeParticleSystemInstancePipelineNodeInitFn,
         (RxPipelineNodeTermFn)NULL,
         (RxPipelineNodeConfigFn)NULL,
         (RxConfigMsgHandlerFn)NULL },
        {NUMCLUSTERSOFINTEREST,
         N1clofinterest,
         N1inputreqs,
         NUMOUTPUTS,
         N1outputs},
        sizeof(PRIVATEDATATYPE),
        (RxNodeDefEditable) FALSE,
        0
    };

    RWAPIFUNCTION(RWSTRING("RxNodeDefinitionGetParticleSystemInstanceCSL"));

    RWRETURN((&nodeParticleSystemInstanceCSL));
}
