/*
 * A particle system plugin 
 * 
 * Copyright (c) Criterion Software Limited
 */

/**
 * \ingroup rpprtcls
 * \page rpprtclsoverview RpPrtcls Plugin Overview
 *
 * RpPrtcls is a RenderWare Graphics Plugin which adds a new PowerPipe Node to the 
 * standard PowerPipe set. This PowerPipe Node allows you to add particle system 
 * effects in your applications with ease.
 *
 * Copyright © 2000, Criterion Software Ltd.
 */

/***************************************************************************
 *                                                                         *
 * Module  : rpprtcls.c                                                    *
 *                                                                         *
 * Purpose : A RpGeometry Plugin to allow RpGeometrys to be treated as     *
 *           particle systems                                              *
 *                                                                         *
 **************************************************************************/

/****************************************************************************
 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: rpprtcls.c,v 1.30 2001/03/15 16:52:17 katherinet Exp $";
#endif /* (!defined(DOXYGEN_SHOULD_SKIP_THIS)) */

/****************************************************************************
 Globals across the program
 */

RwInt32             rpDemoExtensionOffset;

/* A cluster for Particles passing down pipelines */
static RwChar       RpParticles_csl[] = RWSTRING("RpParticles.csl");

RxClusterDefinition clusterRpParticles = {
    /* Uses the RpParticle type - see rpprtcls.h */
    RpParticles_csl,
    sizeof(RpParticle), 
    0, 
    (const RwChar *)NULL
};

/* A cluster for RpParticleSystemType-specific data passing down pipelines */
static RwChar       RpParticleSystemData_csl[] =
RWSTRING("RpParticleSystemData.csl");

RxClusterDefinition clusterRpParticleSystemData = { /* Uses whatever type is desired by a particular particle system type */
    RpParticleSystemData_csl,
    0, 
    0, 
    (const RwChar *)NULL
};

/****************************************************************************
 Local (static) Global
 */

/* A freelist of RpParticleSystems structs */
static RwFreeList  *gPluginData;
static RwBool       PluginInit = FALSE;
static RpParticleTimeCB gTimeCB = (RpParticleTimeCB)NULL;

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

                           Functions

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

static void        *
ParticlesStartup(void *dummy0,
                 RwInt32 __RWUNUSED__ dummy1,
                 RwInt32 __RWUNUSED__ dummy2)
{
    RWFUNCTION(RWSTRING("ParticlesStartup"));

    /* create a freelist for plugin data structs */
    if ((gPluginData =
         RwFreeListCreate(sizeof(RpParticleSystem), 25, 0)) != NULL)
    {
        PluginInit = TRUE;

        RWRETURN((dummy0));
    }
    RWRETURN((NULL));
}

static void        *
ParticelsShutdown(void *dummy0,
                  RwInt32 __RWUNUSED__ dummy1,
                  RwInt32 __RWUNUSED__ dummy2)
{
    RWFUNCTION(RWSTRING("ParticelsShutdown"));

    RwFreeListDestroy(gPluginData);
    gPluginData = (RwFreeList *)NULL;
    PluginInit = FALSE;

    RWRETURN((dummy0));
}

static void        *
ParticlesConstructor(void *geom, RwInt32 __RWUNUSED__ offset,
                     RwInt32 __RWUNUSED__ size)
{
    RpParticleSystem   *data;

    RWFUNCTION(RWSTRING("ParticlesConstructor"));

    RWASSERT(PluginInit == TRUE);
    RWASSERT(geom != NULL);

    data = (RpParticleSystem *) RwFreeListAlloc(gPluginData);
    RPEXTFROMGEOMETRY(geom) = data;

    if (data == NULL)
        RWRETURN((NULL));

    data->numParticles = 0;
    data->particles = (RpParticle *)NULL;
    data->type = (RpParticleSystemType *)NULL;
    data->typeData = NULL;

    RWRETURN((geom));
}

static void        *
ParticlesDestructor(void *geom, RwInt32 __RWUNUSED__ offset,
                    RwInt32 __RWUNUSED__ size)
{
    RpParticleSystem   *data;

    RWFUNCTION(RWSTRING("ParticlesDestructor"));

    RWASSERT(PluginInit == TRUE);
    RWASSERT(geom != NULL);

    data = RPEXTFROMGEOMETRY(geom);

    if (data == NULL)
    {
        RwDebugSendMessage(rwDEBUGMESSAGE, "RpParticleSystem",
                           "Badly constructed geometry; missing RpParticleSystem pointer");
        RWRETURN((NULL));
    }

    if (RpParticleGeometryValidate((RpGeometry *) geom))
    {
        RpParticleGeometryDestroy((RpGeometry *) geom);
    }

    RwFreeListFree(gPluginData, data);
    RPEXTFROMGEOMETRY(geom) = (RpParticleSystem *)NULL;

    /* success */
    RWRETURN((geom));
}

/**
 * \ingroup rpprtcls
 * \ref RpParticleSystemTimer is the function through which
 * \ref RpParticleSystemUpdateCB callbacks access the \ref RpParticleTimeCB
 * callback passed to \ref RpParticleGeometryPluginAttach.
 *
 * \return A \ref RwUInt32 value representing time in milliseconds.
 */
RwUInt32
RpParticleSystemTimer(void)
{
    RWAPIFUNCTION(RWSTRING("RpParticleSystemTimer"));

    RWASSERT(PluginInit == TRUE);

    RWRETURN((gTimeCB()));
}

/**
 * \ingroup rpprtcls
 * \ref RpParticleGeometryGetParticleSystem retrieves the
 * \ref RpParticleSystem plugin data attached to an \ref RpGeometry
 *
 * \param geom  A pointer to an \ref RpGeometry
 *
 * \return A pointer to the \ref RpParticleSystem plugin data attached to
 * the \ref RpGeometry
 */
RpParticleSystem   *
RpParticleGeometryGetParticleSystem(RpGeometry * geom)
{
    RWAPIFUNCTION(RWSTRING("RpParticleGeometryGetParticleSystem"));

    RWASSERT(PluginInit == TRUE);
    RWASSERT(geom);

    RWRETURN(((RpParticleSystem *) RPEXTFROMGEOMETRY(geom)));
}

/**
 * \ingroup rpprtcls
 * \ref RpParticleGeometryCreate initializes the \ref RpParticleSystem
 * plugin data attached to an \ref RpGeometry, given a specific
 * \ref RpParticleSystemType.
 *
 * This includes allocating particle-system-type-specific data via
 * the \ref RpParticleSystem's \ref RpParticleSystemType's
 * \ref RpParticleSystemDataAllocCB callback.
 *
 * \param geom  A pointer to an \ref RpGeometry
 * \param type  A pointer to an \ref RpParticleSystemType
 *
 * \return A pointer to the input \ref RpGeometry on success, NULL otherwise.
 */
RpGeometry         *
RpParticleGeometryCreate(RpGeometry * geom, RpParticleSystemType * type)
{
    RpParticleSystem   *system;

    RWAPIFUNCTION(RWSTRING("RpParticleGeometryCreate"));

    RWASSERT(PluginInit == TRUE);
    RWASSERT(geom != NULL);

    /* Set up the particles part of a geometry */
    system = RpParticleGeometryGetParticleSystem(geom);

    if (system == NULL)
        RWRETURN(((RpGeometry *)NULL));

    /* Set up initial state */
    if (type->allocCB(&system->typeData) == FALSE)
    {
        RwDebugSendMessage(rwDEBUGMESSAGE,
                           "RpParticleGeometryCreate()",
                           "Particle system type failed to initialise its data");
        RWRETURN(((RpGeometry *)NULL));
    }
    system->type = type;

    if (RpParticleGeometryValidate(geom) != FALSE)
    {
        /* It's already set up with data */
        RWRETURN((geom));
    }

    system->numParticles =
        RpGeometryGetNumVertices((RpGeometry *) geom);
    system->particles =
        (RpParticle *) RwMalloc(system->numParticles *
                                sizeof(RpParticle));
    if (system->particles == NULL)
        RWRETURN((FALSE));

    RWRETURN((geom));
}

/**
 * \ingroup rpprtcls
 * \ref RpParticleGeometryDestroy destroys the \ref RpParticleSystem
 * plugin data attached to an \ref RpGeometry.
 *
 * This includes deallocating particle-system-type-specific data via
 * the \ref RpParticleSystem's \ref RpParticleSystemType's
 * \ref RpParticleSystemDataFreeCB callback.
 *
 * \param geom  A pointer to an \ref RpGeometry
 */
void
RpParticleGeometryDestroy(RpGeometry * geom)
{
    RWAPIFUNCTION(RWSTRING("RpParticleGeometryDestroy"));

    RWASSERT(PluginInit == TRUE);
    RWASSERT(geom != NULL);

    if (!RpParticleGeometryValidate(geom))
    {
        RwDebugSendMessage(rwDEBUGMESSAGE,
                           "RpParticleGeometryDestroy()",
                           "Particle geometry not yet initialised");
    }
    else
    {
        /* Destroy up the particles part of a geometry */
        RpParticleSystem   *data =
            (RpParticleSystem *) RPEXTFROMGEOMETRY(geom);

        /* Free particle-system-specific data */
        if (data->typeData)
        {
            data->type->freeCB(data->typeData);
            data->typeData = NULL;
        }
        data->type = (RpParticleSystemType *)NULL;

        /* Free the particles */
        if (data->particles != NULL)
        {
            RwFree(data->particles);
            data->particles = (RpParticle *)NULL;
        }
        data->numParticles = 0;
    }
    RWRETURNVOID();
}

/**
 * \ingroup rpprtcls
 * \ref RpParticleGeometryValidate checks that an \ref RpGeometry has
 * valid \ref RpParticleSystem plugin data attached.
 *
 * \param geom  A pointer to an \ref RpGeometry
 *
 * \return TRUE on success, FALSE on failure.
 */
RwBool
RpParticleGeometryValidate(RpGeometry * geom)
{
    RpParticleSystem   *data;

    RWAPIFUNCTION(RWSTRING("RpParticleGeometryValidate"));

    RWASSERT(PluginInit == TRUE);
    RWASSERT(geom != NULL);

    /* Find out if the particles part of this geometry is set up correctly */
    data = (RpParticleSystem *) RPEXTFROMGEOMETRY(geom);

    if (data == NULL)
    {
        RwDebugSendMessage(rwDEBUGMESSAGE,
                           "RpParticleGeometryValidate()",
                           "Badly constructed geometry; missing RpParticleSystem pointer");
        RWRETURN((FALSE));
    }
    if (data->particles == NULL)
        RWRETURN((FALSE));
    if (data->numParticles != (RwUInt32) RpGeometryGetNumVertices(geom))
        RWRETURN((FALSE));
    if (data->type == NULL)
        RWRETURN((FALSE));

    RWRETURN((TRUE));
}

/**
 * \ingroup rpprtcls
 * \ref RpParticleGeometryGetType retrieves the type of a particle
 * system.
 *
 * \param geom  A pointer to the \ref RpGeometry containing the \ref RpParticleSystem
 *
 * \return A pointer to the \ref RpParticleSystemType on success,
 * otherwise NULL.
 */
RpParticleSystemType *
RpParticleGeometryGetType(RpGeometry * geom)
{
    RWAPIFUNCTION(RWSTRING("RpParticleGeometryGetType"));

    RWASSERT(PluginInit == TRUE);
    RWASSERT(geom != NULL);

    if (!RpParticleGeometryValidate(geom))
    {
        RwDebugSendMessage(rwDEBUGMESSAGE,
                           "RpParticleGeometryGetType()",
                           "Particle geometry not yet correctly initialised");
        RWRETURN(((RpParticleSystemType *)NULL));
    }
    else
    {
        RpParticleSystem   *system =
            RpParticleGeometryGetParticleSystem(geom);

        RWRETURN((system->type));
    }

    RWRETURN(((RpParticleSystemType *)NULL));
}

/**
 * \ingroup rpprtcls
 * \ref RpParticleGeometrySetType sets the type of a particle system.
 *
 * The particle system should already have been initialized. The old
 * type-specific data will be de-initialisezed and data for the new
 * type initialized, via the callbacks of the two \ref RpParticleSystemType's.
 *
 * \param geom  A pointer to the parent \ref RpGeometry
 * \param type  A pointer to the new \ref RpParticleSystemType
 *
 * \return A pointer to the input geometry on success, otherwise NULL.
 */
RpGeometry         *
RpParticleGeometrySetType(RpGeometry * geom,
                          RpParticleSystemType * type)
{
    RWAPIFUNCTION(RWSTRING("RpParticleGeometrySetType"));

    RWASSERT(PluginInit == TRUE);
    RWASSERT(geom != NULL);

    if (!RpParticleGeometryValidate(geom))
    {
        RwDebugSendMessage(rwDEBUGMESSAGE,
                           "RpParticleGeometrySetType()",
                           "Particle geometry not yet correctly initialised");
        RWRETURN(((RpGeometry *)NULL));
    }
    else
    {
        RpParticleSystem   *system =
            RpParticleGeometryGetParticleSystem(geom);
        void               *allocResult;

        if (type->allocCB(&allocResult) == FALSE)
        {
            RwDebugSendMessage(rwDEBUGMESSAGE,
                               "RpParticleGeometrySetType()",
                               "New particle system type failed to initialise its data");
            RWRETURN(((RpGeometry *)NULL));
        }

        /* Free any data created by the old type */
        system->type->freeCB(system->typeData);

        system->type = type;
        system->typeData = allocResult;

        RWRETURN((geom));
    }

    RWRETURN(((RpGeometry *)NULL));
}

/**
 * \ingroup rpprtcls
 * \ref RpParticleGeometryInitialize calls the \ref RpParticleSystemInitCB
 * of the specified \ref RpParticleSystem.
 *
 * \param geom  A pointer to the parent \ref RpGeometry
 * \param mat  A pointer to the geometry's \ref RpMaterial (which should reference
 * the relevant particle rendering pipeline)
 *
 * \return TRUE on success, FALSE on failure.
 */
RwBool
RpParticleGeometryInitialize(RpGeometry * geom, RpMaterial * mat)
{
    RpParticleSystem   *system;

    RWAPIFUNCTION(RWSTRING("RpParticleGeometryInitialize"));

    RWASSERT(PluginInit == TRUE);
    RWASSERT(geom != NULL);
    RWASSERT(mat != NULL);

    system = RpParticleGeometryGetParticleSystem(geom);

    if (system != NULL)
    {
        RWRETURN((system->type->initCB(system, geom, mat)));
    }

    RwDebugSendMessage(rwDEBUGMESSAGE, "RpParticleSystemInitialise()",
                       "Error: Cannot get particle system from geometry");
    RWRETURN((FALSE));
}

/**
 * \ingroup rpprtcls
 * \ref RpParticleSystemUpdate calls the \ref RpParticleSystemUpdateCB
 * of the specified \ref RpParticleSystem.
 *
 * \param system  A pointer to an \ref RpParticleSystem
 *
 * \return TRUE on success, FALSE on failure.
 */
RwBool
RpParticleSystemUpdate(RpParticleSystem * system)
{
    RWAPIFUNCTION(RWSTRING("RpParticleSystemUpdate"));

    RWASSERT(PluginInit == TRUE);
    RWASSERT(system != NULL);

    RWRETURN((system->type->updateCB(system)));
}

static void        *
ParticlesCopy(void *destGeom,
              const void *srcGeom,
              RwInt32 __RWUNUSED__ offset, RwInt32 __RWUNUSED__ size)
{
    const RpParticleSystem *srcData;
    RpParticleSystem  **destData;
    RpParticleSystem   *target;

    RWFUNCTION(RWSTRING("ParticlesCopy"));

    RWASSERT(PluginInit == TRUE);
    RWASSERT(srcGeom != NULL);
    RWASSERT(destGeom != NULL);
    srcData =
        (const RpParticleSystem *) RPEXTFROMCONSTGEOMETRY(srcGeom);

    if (srcData != NULL)
    {
        destData = &(RPEXTFROMGEOMETRY(destGeom));

        target = *destData;

        target = (RpParticleSystem *)NULL;
        *destData = target;

        target = (RpParticleSystem *) RwFreeListAlloc(gPluginData);
        *destData = target;

        if (target != NULL)
        {
            RpParticleSystemAssign(target, srcData);

            if (srcData->particles != NULL)
            {
                target->particles =
                    (RpParticle *) RwMalloc(srcData->numParticles *
                                            sizeof(RpParticle));
                if (target->particles != NULL)
                {
                    /* Success ! */
                    memcpy(target->particles,
                           srcData->particles,
                           (srcData->numParticles *
                            sizeof(RpParticle)));

                }
                else
                {
                    RwFreeListFree(gPluginData, target);
                    target = (RpParticleSystem *)NULL;
                    *destData = target;
                    destGeom = NULL;
                }
            }
            if (srcData->typeData != NULL)
            {
                target->typeData = RwMalloc(srcData->type->dataSize);
                if (target->typeData != NULL)
                {
                    /* Success ! */
                    memcpy(target->typeData,
                           srcData->typeData, srcData->type->dataSize);
                }
                else
                {
                    if (target->particles != NULL)
                    {
                        RwFree(target->particles);
                    }
                    RwFreeListFree(gPluginData, target);
                    target = (RpParticleSystem *)NULL;
                    *destData = target;
                    destGeom = NULL;
                }
            }
        }
        else
        {
            destGeom = NULL;   /* Error */
        }
    }
    else
    {
        RwDebugSendMessage(rwDEBUGMESSAGE, "RpParticleSystem",
                           "Badly constructed geometry; missing RpParticleSystem pointer");
        destGeom = NULL;
    }

    RWRETURN((destGeom));
}

/**
 * \ingroup rpprtcls
 * \ref RpParticleGeometryPluginAttach sets up the particles
 * plugin, registering plugin data for \ref RpGeometry's.
 *
 * A \ref RpParticleTimeCB timer callback must be supplied.
 *
 * \param timer  A pointer to an \ref RpParticleSystem
 *
 * \return TRUE on success, FALSE on failure.
 *
 * This function should be called 
 * between \ref RwEngineInit
 * and \ref RwEngineOpen.
 */
RwBool
RpParticleGeometryPluginAttach(RpParticleTimeCB timer)
{
    RWAPIFUNCTION(RWSTRING("RpParticleGeometryPluginAttach"));

    if (timer == NULL)
        RWRETURN((FALSE));
    gTimeCB = timer;

    if (!RwEngineRegisterPlugin
        (0, MAKECHUNKID(rwVENDORID_CSLRD, rwID_PARTICLESPLUGIN),
         ParticlesStartup, ParticelsShutdown))
    {
        RWRETURN((FALSE));
    }

    if ((rpDemoExtensionOffset
         = RpGeometryRegisterPlugin(sizeof(RpParticleSystem *),
                                    MAKECHUNKID(rwVENDORID_CSLRD,
                                                rwID_PARTICLESPLUGIN),
                                    ParticlesConstructor,
                                    ParticlesDestructor,
                                    ParticlesCopy)) == -1)
    {
        RWRETURN((FALSE));
    }

    RWRETURN((TRUE));
}
