/*
 *
 * Collision plugin for Renderware. 
 */

/**
 * \ingroup rpcollis
 * \page rpcollisoverview RpCollision Plugin Overview
 *
 * This plugin provides facilities for detecting collisions with atomics or 
 * the static geometry of a world.
 *
 * Some functions are really just tools that operate on base object
 * data. Others depend on plugin extension data which, if present,
 * significantly speeds up the intersection tests.
 *
 * \par Intersection primitives
 *
 * Intersection tests require an intersection primitive (\ref
 * RpIntersection) to be set up. This specifies one of several simple
 * geometric objects to be used for the test: a point,
 * line, sphere, or box.  It is also possible to use an atomic as the
 * primitive, in which case its bounding sphere is used for precise
 * tests with triangles in a world or geometry.
 *
 * \par World intersections
 *
 * These functions are available:-
 *
 * \li \ref RpWorldForAllWorldSectorIntersections finds intersections
 *       with world sectors in a world, based on their bounding boxes.
 * \li \ref RpWorldForAllAtomicIntersections finds intersections with
 *       atomics in a world, based on their bounding spheres.
 * \li \ref RpCollisionWorldForAllIntersections finds intersections
 *       with the triangles of the static geometry of a world.
 *
 * The last function is prefixed with RpCollisionWorld since it uses
 * plugin extension data within world sectors to speed up the testing
 * process. This data would normally be created when the world is
 * exported from an art package, but may also be generated in offline
 * tools using the functions
 *
 * \li \ref RpCollisionWorldSectorBuildData
 * \li \ref RpCollisionWorldBuildData
 *
 * \par Atomic and Geometry intersections
 *
 * These are provided for precise tests with the triangles of an
 * \ref RpGeometry.
 *
 * \li \ref RpCollisionGeometryForAllIntersections
 * \li \ref RpAtomicForAllIntersections
 *
 * The first function may be used to test for intersections with
 * geometries. The intersection primitive must be specified in the
 * coordinate space of the geometry and the geometry must have only a
 * single \ref RpMorphTarget. Although not obligatory, performance will
 * be considerably improved if collision data is generated with \ref
 * RpCollisionGeometryBuildData. This is particularly useful for
 * modelling moving parts of a scene which objects must interact with,
 * or for building custom collision worlds.
 *
 * The second function is a more generic test at the atomic level. The
 * intersection primitive is specified in world coordinates (which
 * Renderware transforms to local coordinates). If the atomic's
 * geometry has more than one \ref RpMorphTarget, the test will be based
 * on the current interpolation. If the atomic is based on a geometry
 * with collision data, then this will be used to perform a fast test.
 * 
 * \par Other information
 *
 * For simple, low level intersection tests between geometric
 * primitives, use \ref RtIntersectionLineTriangle, \ref
 * RtIntersectionSphereTriangle, and \ref RtIntersectionBBoxTriangle.
 *
 * Collision data is based on an axis aligned BSP tree, operating in
 * a similar way to the world sector tree but on a finer scale.
 *
 */

/*
 *  File : rpcollis.c - internal documentation
 *
 *  BSP system
 *  ==========
 *
 *  This replaces the older world collision BSP system (RW3.03):-
 *
 *  1) It is not restricted to 256 vertices per sector as the old system
 *     was.
 *  2) It uses overlapping sectors (based on world sectors) which eliminates
 *     the need to separately test polygons lying on dividing planes. In
 *     some cases this gave rise to an unnecessarily high number of polygon
 *     intersection tests.
 *  3) The separation from the world into a plugin simplifies the role of
 *     the world plugin, allows backward compatibility with 3.03 BSPs and
 *     provides a neater way of switching in alternative collision systems.
 *
 *  As with the world sector generation, the space is divided up using axis
 *  aligned planes (see rwsdk/tool/import/rtimport.c for additional info).
 *  Each division results in a left and right sector which overlap so that
 *  they each fully contain a subset of polygons. Triangles are never chopped.
 *
 *  The BSP generation and traversal is factored into a sub-module
 *  (see ctbuild and ctbsp). The data is built simply from a list of
 *  vertices and triangles which makes it versatile, and potentially a useful
 *  exposed type.
 *
 *  World intersections are dealt with in ctworld, and atomic/geometry 
 *  intersections in ctgeom.
 *
 *  The functions in this file deal with management of the collision plugin
 *  data (see ctdef.h). This is unified for both world sectors and
 *  geometries.
 */

/*****************************************************************************
 *  Include files
 */
#include <stdlib.h>

#include <rwcore.h>
#include <rpworld.h>

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

#include "ctbsp.h"
#include "ctbuild.h"
#include "ctdef.h"

#if (!defined(DOXYGEN))
static const char rcsid[] __RWUNUSED__ =
    "@@@@(#)$Id: rpcollis.c,v 1.32 2001/04/02 13:06:25 johns Exp $";
#endif /* (!defined(DOXYGEN)) */


/*****************************************************************************
 *  Global Variables
 */
#if (defined(RWDEBUG))
long                rpCollisionStackDepth = 0;
#endif /* (defined(RWDEBUG)) */

RwInt32             _rpCollisionNumInstances = 0; /* Num of module instances */
RwInt32             _rpCollisionGlobalsOffset = 0; /* Instance globals offset */
RwInt32             _rpCollisionAtomicDataOffset = 0; /* Offset in RpAtomic      */
RwInt32             _rpCollisionGeometryDataOffset = 0; /* Offset in RpGeometry    */
RwInt32             _rpCollisionWorldSectorDataOffset = 0; /* Offset in RpWorldSector */

/*
 * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
 *
 *           Open/Close
 *
 * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
 */

/******************************************************************************
 *
 *  Open call back for the plugin (argument to RwEngineRegisterPlugin).
 */
static void        *
CollisionOpen(void *instance,
              RwInt32 __RWUNUSED__ offset, RwInt32 __RWUNUSED__ size)
{
    RWFUNCTION(RWSTRING("CollisionOpen"));
    RWASSERT(instance);

    _rpCollisionNumInstances++;

    /* Init atomic collision reference counter */
    RPCOLLISIONGLOBAL(collisionRef) = 0;

    RWRETURN(instance);
}

/************************************************************************
 *
 *  Close call back for plugin (argument to RwEngineRegisterPlugin).
 */
static void        *
CollisionClose(void *instance,
               RwInt32 __RWUNUSED__ offset, RwInt32 __RWUNUSED__ size)
{
    RWFUNCTION(RWSTRING("CollisionClose"));
    RWASSERT(instance);

    _rpCollisionNumInstances--;

    RWRETURN(instance);
}

/*
 * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
 *
 *  Creation/Destruction of collision data
 *
 *  (For either worldsector or geometry)
 *
 * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
 */

/******************************************************************************
 *
 *  Destroy collision data.
 */
void
_rpCollisionDataDestroy(RpCollisionData * data)
{
    RWFUNCTION(RWSTRING("_rpCollisionDataDestroy"));

    if (data)
    {
        if (!(data->flags & rpCOLLISIONDATASINGLEMALLOC))
        {
            /* BSP tree creation allocated it's own memory */
            _rpCollBSPTreeDestroy(data->tree);
        }

        RwFree(data);
    }

    RWRETURNVOID();
}

/******************************************************************************
 *
 *  Build collision data from list of triangles.
 */
RpCollisionData    *
_rpCollisionDataBuild(RwInt32 numVertices,
                      RwV3d * vertices,
                      RwInt32 numTriangles,
                      RpCollBSPTriangle * triangles)
{
    RpCollisionData    *data;
    RwInt32             memSize;

    RWFUNCTION(RWSTRING("_rpCollisionDataBuild"));

    /* Allocate memory for collision data (does not include BSP) */
    memSize = sizeof(RpCollisionData) + numTriangles * sizeof(RwUInt16);
    data = (RpCollisionData *) RwMalloc(memSize);
    if (!data)
    {
        RWERROR((E_RW_NOMEM, memSize));
        RWRETURN((RpCollisionData *)NULL);
    }

    /* Initialize data */
    data->flags = 0;
    data->numTriangles = numTriangles;
    data->triangleMap = (RwUInt16 *) (data + 1);

    /* Now build the BSP tree */
    data->tree = _rpCollBSPTreeBuild(numVertices, vertices,
                                     numTriangles, triangles,
                                     data->triangleMap);

    if (!data->tree)
    {
        RwFree(data);
        RWRETURN((RpCollisionData *)NULL);
    }

    RWRETURN(data);
}

/******************************************************************************
 *
 *  Callback for destruction of collision data
 *  (argument to Rp___RegisterPlugin)
 */
static void        *
CollisionDataDestroy(void *object,
                     RwInt32 offset, RwInt32 __RWUNUSED__ size)
{
    RpCollisionData   **extData;

    RWFUNCTION(RWSTRING("CollisionDataDestroy"));
    RWASSERT(object);

    extData = RWPLUGINOFFSET(RpCollisionData *, object, offset);
    if (*extData)
    {
        _rpCollisionDataDestroy(*extData);
        *extData = (RpCollisionData *)NULL;
    }

    RWRETURN(object);
}

/******************************************************************************
 *
 *  Callback for creation of collision data
 *  (argument to Rp___RegisterPlugin).
 */
static void        *
CollisionDataCreate(void *object,
                    RwInt32 offset, RwInt32 __RWUNUSED__ size)
{
    RWFUNCTION(RWSTRING("CollisionDataCreate"));
    RWASSERT(object);

    /* Initialize to null state */
    *RWPLUGINOFFSET(RpCollisionData *, object, offset) = (RpCollisionData *)NULL;

    /* Memory is reserved for the structure during generation
     * or stream read
     */

    RWRETURN(object);
}

/******************************************************************************
 *
 *  Collision atomic extensions
 *  
 *  - these just hold a collision reference which is
 *  updated when an atomic is intersected so that it can be eliminated if
 *  later found in the same intersection test (due to attachment to multiple
 *  world sectors).
 */
static void        *
CollisionAtomicInit(void *object,
                    RwInt32 __RWUNUSED__ offset,
                    RwInt32 __RWUNUSED__ size)
{
    RWFUNCTION(RWSTRING("CollisionAtomicInit"));

    /* Initialize reference count */
    RPATOMICCOLLISIONREF(object) = 0;

    RWRETURN(object);
}

/*
 * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
 *
 *  Stream Functions for worldsector/geometry collision data
 *
 * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
 */

/****************************************************************************
 *
 *  Serialise collision data
 *  (argument to Rp___RegisterPluginStream)
 */
static RwStream    *
CollisionDataStreamWrite(RwStream * stream,
                         RwInt32 __RWUNUSED__ binaryLength,
                         const void *object,
                         RwInt32 offset, RwInt32 __RWUNUSED__ size)
{
    const RpCollisionData *data;

    RWFUNCTION(RWSTRING("CollisionDataStreamWrite"));
    RWASSERT(stream);
    RWASSERT(object);

    data = *RWPLUGINOFFSETCONST(RpCollisionData *, object, offset);

    /* Exit if no data */
    if (!data)
    {
        RWRETURN(stream);
    }

    /* Write size info */
    if (!RwStreamWriteInt(stream,
                          (const RwInt32 *) &data->tree->numLeafNodes,
                          sizeof(RwInt32))
        || !RwStreamWriteInt(stream,
                             (const RwInt32 *) &data->numTriangles,
                             sizeof(RwInt32)))
    {
        RWRETURN((RwStream *)NULL);
    }

    /* The BSP tree nodes */
    if (!_rpCollBSPTreeStreamWrite(data->tree, stream))
    {
        RWRETURN((RwStream *)NULL);
    }

    /* Triangle map */
    {
        RwInt32             numTri = data->numTriangles;
        RwUInt16           *triIndex = data->triangleMap;

        while (numTri--)
        {
            RwInt32             i;

            i = *(triIndex++);
            if (!RwStreamWriteInt
                (stream, (const RwInt32 *) &i, sizeof(i)))
            {
                RWRETURN((RwStream *)NULL);
            }
        }
    }

    RWRETURN(stream);
}

/****************************************************************************
 *
 *  Read collision data from stream.
 *  (argument to Rp___RegisterPluginStream)
 */
static RwStream    *
CollisionDataStreamRead(RwStream * stream,
                        RwInt32 binaryLength,
                        void *object,
                        RwInt32 offset, RwInt32 __RWUNUSED__ size)
{
    RWFUNCTION(RWSTRING("CollisionDataStreamRead"));
    RWASSERT(stream);
    RWASSERT(object);

    if (binaryLength)
    {
        RpCollisionData    *data;
        RwInt32             numLeaves;
        RwInt32             numTris;
        RwInt32             treeSize, memSize;

        /* Read size info */
        if (!RwStreamReadInt
            (stream, (RwInt32 *) & numLeaves, sizeof(RwInt32))
            || !RwStreamReadInt(stream, (RwInt32 *) & numTris,
                                sizeof(RwInt32)))
        {
            RWRETURN((RwStream *)NULL);
        }

        RWASSERT(numTris > 0);
        RWASSERT(numLeaves > 0);

        /* Allocate single block of memory for all data */
        treeSize = _rpCollBSPTreeMemGetSize(numLeaves);
        memSize =
            sizeof(RpCollisionData) + treeSize +
            numTris * sizeof(RwUInt16);
        data = (RpCollisionData *) RwMalloc(memSize);
        if (!data)
        {
            RWERROR((E_RW_NOMEM, memSize));
            RWRETURN((RwStream *)NULL);
        }

        /* Initialize the collision data */
        data->flags = rpCOLLISIONDATASINGLEMALLOC;
        data->tree = (RpCollBSPTree *) (data + 1);
        data->numTriangles = numTris;
        data->triangleMap =
            (RwUInt16 *) ((RwUInt8 *) data->tree + treeSize);

        /* Initialize BSP tree and read data */
        _rpCollBSPTreeInit(data->tree, numLeaves);
        if (!_rpCollBSPTreeStreamRead(data->tree, stream))
        {
            RwFree(data);
            RWRETURN((RwStream *)NULL);
        }

        /* Read triangle map */
        {
            RwInt32             numTri = data->numTriangles;
            RwUInt16           *triIndex = data->triangleMap;

            while (numTri--)
            {
                RwInt32             i;

                if (!RwStreamReadInt(stream, &i, sizeof(i)))
                {
                    RwFree(data);
                    RWRETURN((RwStream *)NULL);
                }
                *(triIndex++) = (RwUInt16) i;
            }
        }

        /* Attach collision data to the object */
        *RWPLUGINOFFSET(RpCollisionData *, object, offset) = data;
    }

    /* All done */
    RWRETURN(stream);
}

/****************************************************************************
 *
 *  Get size of binary stream collision data.
 *  (argument to Rp___RegisterPluginStream)
 */
static              RwInt32
CollisionDataStreamGetSize(const void *object,
                           RwInt32 offsetInObject,
                           RwInt32 __RWUNUSED__ sizeInObject)
{
    const RpCollisionData *data;
    RwUInt32            binarySize;

    RWFUNCTION(RWSTRING("CollisionDataStreamGetSize"));
    RWASSERT(object);

    data =
        *RWPLUGINOFFSETCONST(RpCollisionData *, object, offsetInObject);

    if (data)
    {
        /* Header - numLeafNodes, numTriangles */
        binarySize = 2 * sizeof(RwInt32);

        /* BSP tree nodes */
        binarySize += _rpCollBSPTreeStreamGetSize(data->tree);

        /* Triangle map */
        binarySize += data->numTriangles * sizeof(RwInt32);
    }
    else
    {
        binarySize = 0;
    }

    RWRETURN(binarySize);
}

static RwBool
CollisionAtomicPluginAttach(void)
{
    RwBool              result;
    RwInt32             offset;

    RWFUNCTION(RWSTRING("CollisionAtomicPluginAttach"));

    /* Atomic plugin - for storing collision reference ID */
    _rpCollisionAtomicDataOffset =
        RpAtomicRegisterPlugin(sizeof(RwInt32),
                               rwID_COLLISPLUGIN,
                               CollisionAtomicInit,
                               (RwPluginObjectDestructor)NULL,
                               (RwPluginObjectCopy)NULL);

    result = (_rpCollisionAtomicDataOffset >= 0);
    
    if (result)
    {

        /* Geometry plugin */
        _rpCollisionGeometryDataOffset =
            RpGeometryRegisterPlugin(sizeof(RpCollisionData *),
                                     rwID_COLLISPLUGIN,
                                     CollisionDataCreate,
                                     CollisionDataDestroy,
                                     (RwPluginObjectCopy)NULL);

        result = (_rpCollisionGeometryDataOffset >= 0);
        if (result)
        {

            offset = 
                RpGeometryRegisterPluginStream(rwID_COLLISPLUGIN,
                                               CollisionDataStreamRead,
                                               CollisionDataStreamWrite,
                                               CollisionDataStreamGetSize);

            result = (offset >=0);
                
        }
    }

    RWRETURN(result);
}

static RwBool
CollisionWorldPluginAttach(void)
{
    RwBool              result;
    RwInt32             offset;

    RWFUNCTION(RWSTRING("CollisionWorldPluginAttach"));

    /* Global instance data */

    _rpCollisionGlobalsOffset =
        RwEngineRegisterPlugin(sizeof(RpCollisionGlobals),
                               rwID_COLLISPLUGIN,
                               CollisionOpen, CollisionClose);

    result = (_rpCollisionGlobalsOffset >= 0);

    if (result)
    {
        /* World sector plugin */
        _rpCollisionWorldSectorDataOffset =
            RpWorldSectorRegisterPlugin(sizeof(RpCollisionData *),
                                        rwID_COLLISPLUGIN,
                                        CollisionDataCreate,
                                        CollisionDataDestroy,
                                        (RwPluginObjectCopy)NULL);

        result = (_rpCollisionWorldSectorDataOffset >= 0);
    
        if (result)
        {

            offset = RpWorldSectorRegisterPluginStream(rwID_COLLISPLUGIN,
                                                       CollisionDataStreamRead,
                                                       CollisionDataStreamWrite,
                                                       CollisionDataStreamGetSize);

            result = (offset >=0);
    
        }
    }

    RWRETURN(result);
}

/*
 * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
 *
 *           API Interface
 *
 * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
 */

/**
 * \ingroup rpcollis
 * \ref RpCollisionPluginAttach is called by the application to indicate
 * that the collision plugin should be used. The call
 * to this function should be placed between \ref RwEngineInit and
 * \ref RwEngineOpen and the world plugin must already be attached.
 *
 * The library rpcollis and the header file rpcollis.h are required.
 *
 * \return True on success, false otherwise
 *
 * \see RpWorldPluginAttach
 * \see RpWorldForAllWorldSectorIntersections
 * \see RpWorldForAllAtomicIntersections
 * \see RpCollisionWorldForAllIntersections
 * \see RpCollisionWorldSectorBuildData
 * \see RpCollisionWorldBuildData
 * \see RpAtomicForAllIntersections
 * \see RpCollisionGeometryForAllIntersections
 * \see RpCollisionGeometryBuildData
 */
RwBool
RpCollisionPluginAttach(void)
{
    RwBool              result;

    RWAPIFUNCTION(RWSTRING("RpCollisionPluginAttach"));

    result = CollisionWorldPluginAttach();
    
    if (result)
    {
        result = CollisionAtomicPluginAttach();
    }

    RWRETURN(result);
}
