/*
 * Potentially Visible Set plug-in
 */

/**********************************************************************
 *
 * file :     rppvs.c
 *
 * abstract : handle culling of wsector in renderware
 *
 **********************************************************************
 *
 * This file is a product of Criterion Software Ltd.
 *
 * This file is provided as is with no warranties of any kind and is
 * provided without any obligation on Criterion Software Ltd. or
 * Canon Inc. to assist in its use or modification.
 *
 * Criterion Software Ltd. will not, under any
 * circumstances, be liable for any lost revenue or other damages arising
 * from the use of this file.
 *
 * Copyright (c) 1998 Criterion Software Ltd.
 * All Rights Reserved.
 *
 * RenderWare is a trademark of Canon Inc.
 *
 ************************************************************************/

/**
 * \ingroup rppvs310
 * \page rppvsoverview RpPVS Plugin Overview
 *
 * The RpPVS plugin extends the RenderWare Graphics API to support optimization of
 * BSP world rendering using a custom Potentially Visible Sets algorithm. Under
 * the right conditions, this algorithm can dramatically reduce the time taken
 * to render a large 3D model, such as a level for a game.
 *
 * This plugin supports both ends of the PVS process: you can use it to create
 * PVS data as well as to accelerate rendering using the results.
 *
 * Potentially Visible Sets processing is usually applied to large 3D models, such
 * as one of a large building, where only a small portion is ever expected to be
 * visible at any one time. In use, the application developer tells the plugin to
 * check a number of points within an RpWorld and determine which RpWorldSector
 * objects are visible from each location. When this process is completed, the
 * rendering engine can easily determine which RpWorldSector objects are to be
 * rendered at any location.
 *
 * PVS data generation is most often performed in a 3D modeler exporter plugin.
 */

/*--- Include files ---*/

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

#include "rpplugin.h"

#include <rwcore.h>
#include <rpworld.h>
#include <rpcollis.h>
#include <rtworld.h>
#include <rtray.h>

#include <rpdbgerr.h>
#include <rppvs310.h>
#include "rppvsdef.h"

#include  "nodePVSWorldSector.h"

static const char   rcsid[] __RWUNUSED__ =
    "@@(#)$Id: rppvs310.c,v 1.152 2001/10/04 21:10:50 adamj Exp $";

/****************************************************************************
 Global Vars
 */

#if (defined(RWDEBUG))
long                rpPVSStackDepth = 0;
#endif /* (defined(RWDEBUG)) */

RpPVSGlobalVars     rpPVSGlobals;

/****************************************************************************
 Local (Static) Prototypes
 */

/****************************************************************************
 Local Defines
 */

RwInt32             PVSWorldOffset;
RwInt32             PVSWorldSectorOffset;

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

   Functions

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

/****************************************************************************/

/*
 * Select the correct vismap from the world sector with ver 2 PVS data.
 * Sub-sector vismap are arranged differently.
 */

static RpPVSVisMap *
PVSSelectVismapVer2(RpWorldSector * worldSector, RwV3d * point)
{
    RpPVS              *ppCur;
    RwInt32             i, j, k;
    RwV3d               offset;
    RwV3d               scale;
    const RpPVSCache   *pvsCache;
    const RwBBox       *sectorBBox;
    RwChar             *vismap;

    RWFUNCTION(RWSTRING("PVSSelectVismapVer2"));

    RWASSERT(worldSector);
    RWASSERT(point);
    RWASSERT(rpPVSGlobals.world);

    pvsCache = PVSCACHEFROMCONSTWORLD(rpPVSGlobals.world);
    ppCur = PVSFROMWORLDSECTOR(worldSector);

    vismap = (RwChar *) ppCur->vismap;

    sectorBBox = RpWorldSectorGetBBox(worldSector);

    RwV3dSub(&offset, point, &sectorBBox->inf);

    RwV3dSub(&scale, &sectorBBox->sup, &sectorBBox->inf);
    scale.x = ((RwReal) ppCur->x_segs) / scale.x;
    scale.y = ((RwReal) ppCur->y_segs) / scale.y;
    scale.z = ((RwReal) ppCur->z_segs) / scale.z;

    i = (RwInt32) (offset.x * scale.x);
    j = (RwInt32) (offset.y * scale.y);
    k = (RwInt32) (offset.z * scale.z);

    /* Check for -ve. Can happen if the point is outside. */

    i = (i > 0) ? i : 0;
    j = (j > 0) ? j : 0;
    k = (k > 0) ? k : 0;

    RWRETURN((RpPVSVisMap *)
             (vismap +
              (rpPVSGlobals.sectorCacheVer2.length *
               ((k * ppCur->x_segs * ppCur->y_segs) +
                (j * ppCur->x_segs) + i))));
}

static RpPVSVisMap *
PVSSelectVismap(RpWorldSector * worldSector, RwV3d * point)
{
    const RpPVSCache   *pvsCache;
    RpPVS              *ppCur;
    RwInt32             vindex;

    RWFUNCTION(RWSTRING("PVSSelectVismap"));

    RWASSERT(worldSector);
    RWASSERT(point);
    RWASSERT(rpPVSGlobals.world);

    ppCur = PVSFROMWORLDSECTOR(worldSector);
    pvsCache = PVSCACHEFROMCONSTWORLD(rpPVSGlobals.world);

    vindex = 0;
    if (worldSector->numPolygons > 0)
    {
        const RwBBox       *sectorBBox;
        RwV3d               offset;

        sectorBBox = RpWorldSectorGetBBox(worldSector);

        RwV3dSub(&offset, point, &sectorBBox->inf);
        RwV3dScale(&offset, &offset, pvsCache->sectorCache.oomaxdim);

        RWASSERT((RwInt32) offset.x < ppCur->x_segs);
        RWASSERT((RwInt32) offset.y < ppCur->y_segs);
        RWASSERT((RwInt32) offset.z < ppCur->z_segs);

        /* which vismap? */
        vindex = (RwInt32) offset.z * (ppCur->y_segs * ppCur->x_segs) +
            (RwInt32) offset.y * (ppCur->x_segs) + (RwInt32) offset.x;
    }

    RWRETURN(ppCur->vismap + pvsCache->sectorCache.vislen * vindex);
}

static RpWorldSector *
PVSSelectWorldSector(RpIntersection * is, RpWorldSector * spSect,
                     void *pData)
{
    RpPVSCache         *pvsCache = (RpPVSCache *) pData;
    RwBool              outside;
    RwV3d              *pt;
    const RwBBox       *bbox;

    RWFUNCTION(RWSTRING("PVSSelectWorldSector"));

    pvsCache->spCOP = spSect;

    /* Double check we are actually inside this sector's bound. ? */
    pt = &is->t.point;
    bbox = RpWorldSectorGetBBox(spSect);
    outside = ((pt->x < bbox->inf.x) || (pt->x > bbox->sup.x) ||
               (pt->y < bbox->inf.y) || (pt->y > bbox->sup.y) ||
               (pt->z < bbox->inf.z) || (pt->z > bbox->sup.z));

    if (outside)
        RWRETURN(spSect);

    if (pvsCache->sectorCache.version == PVSVERSION)
    {
        pvsCache->sectorVismap = PVSSelectVismap(spSect, &is->t.point);
    }
    else
    {
        pvsCache->sectorVismap =
            PVSSelectVismapVer2(spSect, &is->t.point);
    }

    /* could expand an RLE version here */
    RWRETURN((RpWorldSector *)NULL);
}

/**
 * \ingroup rppvs310
 * \ref RpPVSSetViewPosition
 * is used to set the viewing position for subsequent PVS culling. It selects the
 * appropriate visibility map for PVS culling. This function must be called before
 * any render function otherwise incorrect culling will occur.
 *
 * RpPVSSetViewPosition is typically used immediately prior to a frame render by
 * setting the view position equal to that of the current camera.
 *
 * The PVS plugin must be attached before using this function.
 *
 * The include file rppvs310.h and the library file rppvs310.lib are
 * required to use this function. The library file rpworld.lib is also
 * required.
 *
 * \param wpWorld  A pointer to the RpWorld containing the PVS data.
 * \param pos  A pointer to a RwV3d which specifies the current viewing position.
 *
 * \return RpWorld if sucessful, NULL otherwise.
 *
 * \see RpPVSAtomicVisible
 * \see RpPVSDestroy
 * \see RpPVSGetProgressCallBack
 * \see RpPVSHook
 * \see RpPVSPluginAttach
 * \see RpPVSQuery
 * \see RpPVSSetProgressCallBack
 * \see RpPVSStatisticsGet
 * \see RpPVSUnhook
 * \see RpPVSWorldSectorVisible
 */
RpWorld            *
RpPVSSetViewPosition(RpWorld * wpWorld, RwV3d * pos)
{
    RpPVSCache         *pvsCache;
    RpIntersection      isPoint;

    RWAPIFUNCTION(RWSTRING("RpPVSSetViewPosition"));

    RWASSERT(wpWorld);
    RWASSERT(pos);

    rpPVSGlobals.world = wpWorld;

    pvsCache = PVSCACHEFROMWORLD(wpWorld);
    if (pvsCache->sectorBlocks)
    {
        isPoint.type = rpINTERSECTPOINT;
        isPoint.t.point = *pos;
        RpWorldForAllWorldSectorIntersections(wpWorld, &isPoint,
                                              PVSSelectWorldSector,
                                              pvsCache);
    }

    RWRETURN(wpWorld);
}

/**
 * \ingroup rppvs310
 * \ref RpPVSSetProgressCallBack is used to define a PVS creation progress
 * callback function for the specified world.  The callback is called from
 * \ref RpPVSGeneric every time it has processed a single world sector, enabling
 * an application to monitor how the generation of PVS data is progressing and,
 * possibly, to provide feedback to the user.
 *
 * The format of the callback function is:
 * \verbatim
   RwBool (*RpPVSProgressCallBack) (RwInt32 message, RwReal value);

   where message is one of the following:

   rpPVSPROGRESSSTART
   The PVS creation process is about to start. The argument value is equal to 0.0.

   rpPVSPROGRESSUPDATE
   The PVS creation process has finished processing a subsection of the world.
   The argument value is equal to the percentage of the world processed up to this point.

   rpPVSPROGRESSEND
   The PVS creation process has ended.  All world sectors have been processed.
   The argument value is equal to 100.0.

   The progress callback may return FALSE to indicate that the generation of
   PVS data should terminate.  Otherwise, return TRUE to continue.

   The PVS plugin must be attached before using this function.
   \endverbatim
 *
 * \param world  A pointer to a RpWorld.
 * \param callback  A pointer to the RpPVSProgressCallBack function.
 *
 * \return RpWorld if sucessful, NULL otherwise.
 *
 * \see RpPVSAtomicVisible
 * \see RpPVSDestroy
 * \see RpPVSGetProgressCallBack
 * \see RpPVSHook
 * \see RpPVSPluginAttach
 * \see RpPVSQuery
 * \see RpPVSSetViewPosition
 * \see RpPVSStatisticsGet
 * \see RpPVSUnhook
 * \see RpPVSWorldSectorVisible
 */
RpWorld            *
RpPVSSetProgressCallBack(RpWorld * world,
                         RpPVSProgressCallBack callback)
{

    RWAPIFUNCTION(RWSTRING("RpPVSSetProgressCallBack"));
    RWASSERT(world);

    if (world)
    {
        RpPVSCache         *pvsCache = PVSCACHEFROMWORLD(world);

        if (pvsCache)
        {
            pvsCache->progressCallBack = callback;
            RWRETURN(world);
        }

        RWERROR((E_RW_NULLP));
        RWRETURN((RpWorld *)NULL);
    }

    RWERROR((E_RW_NULLP));
    RWRETURN((RpWorld *)NULL);
}

/****************************************************************************/

/**
 * \ingroup rppvs310
 * \ref RpPVSGetProgressCallBack is used to retrieve PVS creation progress callback
 * function of the specified world.  The callback is called from \ref RpPVSGeneric
 * every time it has processed a single world sector, enabling an application to
 * monitor how the generation of PVS data is progressing and, possibly, to provide
 * feedback to the user.
 *
 * The format of the callback function is:
 *
 * \verbatim
   RwBool (*RpPVSProgressCallBack) (RwInt32 message, RwReal value);

   where message is one of the following:

   rpPVSPROGRESSSTART
   The PVS creation process is about to start.  The argument value is equal to 0.0.

   rpPVSPROGRESSUPDATE
   The PVS creation process has finished processing a subsection of the world.
   The argument value is equal to the percentage of the world processed up to this point.

   rpPVSPROGRESSEND
   The PVS creation process has ended.  All world sectors have been processed.
   The argument value is equal to 100.0.

   The progress callback may return FALSE to indicate that the generation of PVS data
   should terminate. Otherwise, return TRUE to continue.

   The PVS plugin must be attached before using this function.
   \endverbatim
 *
 * \param world  A pointer to a RpWorld.
 *
 * \return The RpPVSProgressCallBack function if sucessful, NULL otherwise.
 *
 * \see RpPVSAtomicVisible
 * \see RpPVSDestroy
 * \see RpPVSHook
 * \see RpPVSPluginAttach
 * \see RpPVSQuery
 * \see RpPVSSetProgressCallBack
 * \see RpPVSSetViewPosition
 * \see RpPVSStatisticsGet
 * \see RpPVSUnhook
 * \see RpPVSWorldSectorVisible
 */
RpPVSProgressCallBack
RpPVSGetProgressCallBack(RpWorld * world)
{
    RWAPIFUNCTION(RWSTRING("RpPVSGetProgressCallBack"));
    RWASSERT(world);

    if (world)
    {
        RpPVSCache         *pvsCache = PVSCACHEFROMWORLD(world);

        if (pvsCache)
        {
            RWRETURN(pvsCache->progressCallBack);
        }

        RWERROR((E_RW_NULLP));
        RWRETURN((RpPVSProgressCallBack)NULL);
    }

    RWERROR((E_RW_NULLP));
    RWRETURN((RpPVSProgressCallBack)NULL);
}

/****************************************************************************/

/**
 * \ingroup rppvs310
 * \ref RpPVSHook is used to enable rendering of the specified world using
 * the world's PVS data.  Typically used prior to a frame render (and followed
 * afterwards by \ref RpPVSUnhook) but may be permanently enabled if PVS rendering
 * is always required.  If RpPVSHook is not used all world sectors in the current
 * camera's view frustum are rendered.
 *
 * Note that this function overrides the world sector render callback which is
 * only returned to its original form when \ref RpPVSUnhook is called.
 *
 * The PVS plugin must be attached before using this function.
 *
 * The include file rppvs310.h and the library file rppvs310.lib are
 * required to use this function. The library file rpworld.lib is also
 * required.
 *
 * \param wpWorld  A pointer to a RpWorld with PVS data.
 *
 * \return RpWorld if sucessful, NULL otherwise.
 *
 * \see RpPVSAtomicVisible
 * \see RpPVSDestroy
 * \see RpPVSGetProgressCallBack
 * \see RpPVSPluginAttach
 * \see RpPVSQuery
 * \see RpPVSSetProgressCallBack
 * \see RpPVSSetViewPosition
 * \see RpPVSStatisticsGet
 * \see RpPVSUnhook
 * \see RpPVSWorldSectorVisible
 */
RpWorld *
RpPVSHook(RpWorld *wpWorld)
{
    RpPVSCache  *pvsCache;

    RWAPIFUNCTION(RWSTRING("RpPVSHook"));
    RWASSERT(wpWorld);

    pvsCache = PVSCACHEFROMWORLD(wpWorld);
    if (pvsCache->sectorBlocks)
    {
        if (!pvsCache->hooked)
        {
            pvsCache->hooked = TRUE;
            rpPVSGlobals.world = wpWorld;
        }
    }

    RWRETURN(wpWorld);
}

/****************************************************************************/

/**
 * \ingroup rppvs310
 * \ref RpPVSUnhook is used to disable rendering of the specified world
 * using the world's PVS data.  Typically used just after to a frame render
 * (which followed a call to \ref RpPVSHook) but may be permanently disabled
 * if PVS rendering is not required.
 *
 * Note that the function \ref RpPVSHook overrides the world sector render
 * callback which is only returned to its original form when RpPVSUnhook is called.
 * The PVS plugin must be attached before using this function.
 *
 * The include file rppvs310.h and the library file rppvs310.lib are
 * required to use this function. The library file rpworld.lib is also
 * required.
 *
 * \param wpWorld  A pointer to a RpWorld containing PVS data.
 *
 * \return RpWorld if sucessful, NULL otherwise.
 *
 * \see RpPVSAtomicVisible
 * \see RpPVSDestroy
 * \see RpPVSGetProgressCallBack
 * \see RpPVSHook
 * \see RpPVSPluginAttach
 * \see RpPVSQuery
 * \see RpPVSSetProgressCallBack
 * \see RpPVSSetViewPosition
 * \see RpPVSStatisticsGet
 * \see RpPVSWorldSectorVisible
 */
RpWorld *
RpPVSUnhook(RpWorld *wpWorld)
{
    RpPVSCache  *pvsCache;

    RWAPIFUNCTION(RWSTRING("RpPVSUnhook"));
    RWASSERT(wpWorld);

    pvsCache = PVSCACHEFROMWORLD(wpWorld);
    if (pvsCache->hooked)
    {
        pvsCache->hooked = FALSE;
        rpPVSGlobals.world = (RpWorld *)NULL;
    }

    RWRETURN(wpWorld);
}

/****************************************************************************/

static RpWorldSector *
PVSnull(RpWorldSector * spSect, void *__RWUNUSED__ pData)
{
    RpPVS              *pvs = PVSFROMWORLDSECTOR(spSect);

    RWFUNCTION(RWSTRING("PVSnull"));

    pvs->vismap = (RwUInt32 *)NULL;

    RWRETURN(spSect);
}

/**
 * \ingroup rppvs310
 * \ref RpPVSDestroy
 * is used to destroy the PVS for the given world.
 *
 * The include file rppvs310.h and the library file rppvs310.lib are
 * required to use this function. The library file rpworld.lib is also
 * required.
 *
 * \param wpWorld  A pointer to a RpWorld containing the PVS data.
 *
 * \return RpWorld if sucessful, NULL otherwise.
 *
 * \see RpPVSAtomicVisible
 * \see RpPVSGetProgressCallBack
 * \see RpPVSHook
 * \see RpPVSPluginAttach
 * \see RpPVSQuery
 * \see RpPVSSetProgressCallBack
 * \see RpPVSSetViewPosition
 * \see RpPVSStatisticsGet
 * \see RpPVSUnhook
 * \see RpPVSWorldSectorVisible
 */
RpWorld            *
RpPVSDestroy(RpWorld * wpWorld)
{
    RpPVSCache         *pvsCache;

    RWAPIFUNCTION(RWSTRING("RpPVSDestroy"));

    RWASSERT(wpWorld);

    RpPVSUnhook(wpWorld);

    pvsCache = PVSCACHEFROMWORLD(wpWorld);
    if (pvsCache->sectorBlocks)
    {
        RpWorldForAllWorldSectors(wpWorld, PVSnull, NULL);
        RwFree(pvsCache->sectorBlocks);
        pvsCache->sectorBlocks = (char *)NULL;
    }

    rpPVSGlobals.world = (RpWorld *)NULL;

    RWRETURN(wpWorld);
}

/****************************************************************************/

/**
 * \ingroup rppvs310
 * \ref RpPVSWorldSectorVisible
 * is used to determine if the sector is visible using the current visibility map.
 *
 * The include file rppvs310.h and the library file rppvs310.lib are
 * required to use this function. The library file rpworld.lib is also
 * required.
 *
 * \param sector  A pointer to a RpWorldSector to be determined.
 *
 * \return TRUE if visible, FALSE otherwise.
 *
 * \see RpPVSAtomicVisible
 * \see RpPVSDestroy
 * \see RpPVSGetProgressCallBack
 * \see RpPVSHook
 * \see RpPVSPluginAttach
 * \see RpPVSQuery
 * \see RpPVSSetProgressCallBack
 * \see RpPVSSetViewPosition
 * \see RpPVSStatisticsGet
 * \see RpPVSUnhook
 */
RwBool
RpPVSWorldSectorVisible(RpWorldSector * sector)
{
    const RpPVSCache   *pvsCache;

    RWAPIFUNCTION(RWSTRING("RpPVSWorldSectorVisible"));

    RWASSERT(sector);
    RWASSERT(rpPVSGlobals.world);

    /* POV has been set */
    pvsCache = PVSCACHEFROMCONSTWORLD(rpPVSGlobals.world);
    if (pvsCache->sectorVismap)
    {
        RpPVS              *pvs = PVSFROMWORLDSECTOR(sector);

        /* Reject on PVS */
        if (pvsCache->sectorCache.version == PVSVERSION)
        {
            if (PVSVISMAPGETSECTOR
                (pvsCache->sectorVismap, pvs->sectorID))
            {
                RWRETURN(TRUE);
            }
            else
            {
                RWRETURN(FALSE);
            }
        }
        else
        {
            RwChar             *sectorVismap;

            sectorVismap = (RwChar *) pvsCache->sectorVismap;

            if (PVSVISMAPGETSECTORVER2(sectorVismap, pvs->sectorID))
            {
                RWRETURN(TRUE);
            }
            else
            {
                RWRETURN(FALSE);
            }

        }
    }

    /* in the absence of PVS we assume its visible */
    RWRETURN(TRUE);
}

/****************************************************************************/

static RpWorldSector *
PVSSectorVis(RpWorldSector * sector, void *data)
{
    RwBool             *isvisible = (RwBool *) data;

    RWFUNCTION(RWSTRING("PVSSectorVis"));

    if (RpPVSWorldSectorVisible(sector))
    {
        (*isvisible) = TRUE;
    }

    RWRETURN(sector);
}

/**
 * \ingroup rppvs310
 * \ref RpPVSAtomicVisible
 * is used to determine if the atomic is visible from the current visibility
 * map.
 *
 * The include file rppvs310.h and the library file rppvs310.lib are
 * required to use this function. The library file rpworld.lib is also
 * required.
 *
 * \param atom  A pointer to a RpAtomic.
 *
 * \return TRUE if visible, FALSE otherwise.
 *
 * \see RpPVSDestroy
 * \see RpPVSGetProgressCallBack
 * \see RpPVSHook
 * \see RpPVSPluginAttach
 * \see RpPVSQuery
 * \see RpPVSSetProgressCallBack
 * \see RpPVSSetViewPosition
 * \see RpPVSStatisticsGet
 * \see RpPVSUnhook
 * \see RpPVSWorldSectorVisible
 */
RwBool
RpPVSAtomicVisible(RpAtomic * atom)
{
    RwBool              isvisible;

    RWAPIFUNCTION(RWSTRING("RpPVSAtomicVisible"));

    isvisible = FALSE;
    RpAtomicForAllWorldSectors(atom, PVSSectorVis, &isvisible);
    RWRETURN(isvisible);
}

/****************************************************************************/

/**
 * \ingroup rppvs310
 * \ref RpPVSQuery is used to determine whether the specified world
 * contains PVS data, that is, if it has already been processed using
 * \ref RpPVSCreate.
 *
 * The PVS plugin must be attached before using this function.
 *
 * The include file rppvs310.h and the library file rppvs310.lib are
 * required to use this function. The library file rpworld.lib is also
 * required.
 *
 * \param wpWorld  A pointer to a RpWorld to be queried.
 *
 * \return TRUE if PVS data is present, FALSE otherwise.
 *
 * \see RpPVSAtomicVisible
 * \see RpPVSDestroy
 * \see RpPVSGetProgressCallBack
 * \see RpPVSHook
 * \see RpPVSPluginAttach
 * \see RpPVSSetProgressCallBack
 * \see RpPVSSetViewPosition
 * \see RpPVSStatisticsGet
 * \see RpPVSUnhook
 * \see RpPVSWorldSectorVisible
 */
RwBool
RpPVSQuery(RpWorld * wpWorld)
{
    RpPVSCache         *pvsCache;

    RWAPIFUNCTION(RWSTRING("RpPVSQuery"));

    RWASSERT(wpWorld);

    rpPVSGlobals.world = wpWorld;

    pvsCache = PVSCACHEFROMWORLD(wpWorld);
    RWRETURN(pvsCache->sectorBlocks ? TRUE : FALSE);
}

/****************************************************************************/

/**
 * \ingroup rppvs310
 * \ref RpPVSStatisticsGet is used to retrieve statistics relating to the
 * rendering perfomance of the PVS data in the specified world.  The figures
 * obtained from this function are:
 * (a) the total number of triangles that would have been rendered if PVS data
 *     was not used, and
 * (b) the total number of triangles that have been rendered using the PVS data.
 * Typically used immediately after a frame render.
 *
 * The PVS plugin must be attached before using this function.
 *
 * \param wpWorld  A pointer to a RpWorld with PVS data.
 * \param ptotal  A pointer to a RwInt32 to return number of polygons before PVS culling.
 * \param paccept  A pointer to a RwInt32 to return number of polygons after PVS culling.
 *
 * \return RpWorld if sucessful, NULL otherwise.
 *
 * \see RpPVSAtomicVisible
 * \see RpPVSDestroy
 * \see RpPVSGetProgressCallBack
 * \see RpPVSHook
 * \see RpPVSPluginAttach
 * \see RpPVSQuery
 * \see RpPVSSetProgressCallBack
 * \see RpPVSSetViewPosition
 * \see RpPVSUnhook
 * \see RpPVSWorldSectorVisible
 */
RpWorld            *
RpPVSStatisticsGet(RpWorld * wpWorld, RwInt32 * ptotal,
                   RwInt32 * paccept)
{
    RpPVSCache         *pvsCache;

    RWAPIFUNCTION(RWSTRING("RpPVSStatisticsGet"));

    RWASSERT(wpWorld);
    RWASSERT(ptotal);
    RWASSERT(paccept);

    rpPVSGlobals.world = wpWorld;

    pvsCache = PVSCACHEFROMWORLD(wpWorld);

    /* read tally.. */
    *ptotal = pvsCache->ptotal;
    *paccept = pvsCache->paccept;

    /* and zero them */
    pvsCache->ptotal = 0;
    pvsCache->paccept = 0;

    RWRETURN(wpWorld);
}

/****************************************************************************/

/****************************************************************************/

/****************************************************************************/
static void        *
PVSSectorConstructor(RpWorldSector * spSect,
                     RwInt32 __RWUNUSED__ offset,
                     RwInt32 __RWUNUSED__ size)
{
    RpPVS              *ppCur = PVSFROMWORLDSECTOR(spSect);

    RWFUNCTION(RWSTRING("PVSSectorConstructor"));

    ppCur->sectorID = ~0;
    ppCur->x_segs = 1;
    ppCur->y_segs = 1;
    ppCur->z_segs = 1;
    ppCur->vismap = (RwUInt32 *)NULL;
    RWRETURN(spSect);
}

static void        *
PVSSectorDestructor(RpWorldSector * spSect,
                    RwInt32 __RWUNUSED__ offset,
                    RwInt32 __RWUNUSED__ size)
{
    RpPVS              *ppCur = PVSFROMWORLDSECTOR(spSect);

    RWFUNCTION(RWSTRING("PVSSectorDestructor"));

    ppCur->sectorID = ~0;
    ppCur->vismap = (RwUInt32 *)NULL;
    RWRETURN(spSect);
}

/****************************************************************************/
static              RwInt32
PVSSectorGetChunkSize(const RpWorldSector * spSect,
                      RwInt32 __RWUNUSED__ offset,
                      RwInt32 __RWUNUSED__ size)
{
    const RpWorld      *world = RpWorldSectorGetWorld(spSect);
    const RpPVSCache   *pvsCache = PVSCACHEFROMCONSTWORLD(world);

    RWFUNCTION(RWSTRING("PVSSectorGetChunkSize"));

    if ((pvsCache->sectorBlocks) &&
        (pvsCache->sectorCache.version == PVSVERSION))
    {
        RWRETURN(sizeof(RpPVS));
    }

    RWRETURN(0);
}

static RwStream    *
PVSSectorReadChunk(RwStream * s,
                   RwInt32 len,
                   RpWorldSector * spSect,
                   RwInt32 __RWUNUSED__ offset,
                   RwInt32 __RWUNUSED__ size)
{
    RpPVS              *ppCur = PVSFROMWORLDSECTOR(spSect);
    RpPVSVer2          *ppCurVer2;

    RWFUNCTION(RWSTRING("PVSSectorReadChunk"));

    if (len == sizeof(RpPVS))
    {
        RwStreamRead(s, ppCur, len);
        RwMemNative(ppCur, len);
    }
    else
    {
        /* Pre ver 3. Assume ver 2 */

        ppCurVer2 = (RpPVSVer2 *) RwMalloc(len);
        RwStreamRead(s, ppCurVer2, len);

        /*
         * Only convert the first four bytes since it is an int.
         * The rest are chars.
         */
        RwMemNative(ppCurVer2, sizeof(int));

        ppCur->sectorID = ppCurVer2->sectorID;
        ppCur->x_segs = ppCurVer2->x_cut;
        ppCur->y_segs = ppCurVer2->y_cut;
        ppCur->z_segs = ppCurVer2->z_cut;

        ppCur->vismap = (RpPVSVisMap *) ppCurVer2;
    }

    RWRETURN(s);
}

static RwStream    *
PVSSectorWriteChunk(RwStream * s,
                    RwInt32 len,
                    const RpWorldSector * spSect,
                    RwInt32 __RWUNUSED__ offset,
                    RwInt32 __RWUNUSED__ size)
{
    const RpPVS        *ppCur = PVSFROMCONSTWORLDSECTOR(spSect);
    RpPVS               pvs;

    RWFUNCTION(RWSTRING("PVSSectorWriteChunk"));

    pvs = *ppCur;
    RwMemLittleEndian(&pvs, sizeof(pvs));
    RwStreamWrite(s, &pvs, len);
    RWRETURN(s);
}

/****************************************************************************/
static void        *
PVSWorldConstructor(RpWorld * wpWorld,
                    RwInt32 __RWUNUSED__ offset,
                    RwInt32 __RWUNUSED__ size)
{
    RpPVSCache         *pvsCache = PVSCACHEFROMWORLD(wpWorld);

    RWFUNCTION(RWSTRING("PVSWorldConstructor"));

    /* vismap allocator */
    pvsCache->sectorCache.version = PVSVERSION;
    pvsCache->sectorCache.viscount = 0;
    pvsCache->sectorCache.vislen = 0;
    pvsCache->sectorCache.oomaxdim = 1.0f;
    pvsCache->sectorBlocks = (char *)NULL;

    /* selected vismap */
    pvsCache->spCOP = (RpWorldSector *)NULL;
    pvsCache->sectorVismap = (RwUInt32 *)NULL;

    /* pipeline hooking */
    pvsCache->fpRender = (RpWorldSectorCallBackRender)NULL;
    pvsCache->hooked = FALSE;

    /* stats */
    pvsCache->ptotal = 0;
    pvsCache->paccept = 0;

    /* generation stuff */
    pvsCache->maxNumVert = 0;
    pvsCache->maxNumPoly = 0;
    pvsCache->im3DVert = (RwIm3DVertex *)NULL;
    pvsCache->imVertIndex = (RwImVertexIndex *)NULL;
    pvsCache->nextID = 0;
    pvsCache->ppNext = (char *)NULL;
    pvsCache->progressCallBack = (RpPVSProgressCallBack)NULL;

    RWRETURN(wpWorld);
}

static void        *
PVSWorldDestructor(RpWorld * wpWorld,
                   RwInt32 __RWUNUSED__ offset,
                   RwInt32 __RWUNUSED__ size)
{
    RpPVSCache         *pvsCache = PVSCACHEFROMWORLD(wpWorld);

    RWFUNCTION(RWSTRING("PVSWorldDestructor"));

    RpPVSUnhook(wpWorld);

    if (pvsCache->sectorBlocks)
    {
        RwFree(pvsCache->sectorBlocks);
        pvsCache->sectorBlocks = (char *)NULL;
    }

    rpPVSGlobals.world = (RpWorld *)NULL;

    RWRETURN(wpWorld);
}

static              RwInt32
PVSWorldGetChunkSize(const RpWorld * wpWorld,
                     RwInt32 __RWUNUSED__ offset,
                     RwInt32 __RWUNUSED__ size)
{
    const RpPVSCache   *pvsCache = PVSCACHEFROMCONSTWORLD(wpWorld);

    RWFUNCTION(RWSTRING("PVSWorldGetChunkSize"));

    if ((pvsCache->sectorBlocks) &&
        (pvsCache->sectorCache.version == PVSVERSION))
    {
        RwInt32             len;

        len = sizeof(RpPVSCacheSize);
        len +=
            pvsCache->sectorCache.viscount *
            pvsCache->sectorCache.vislen * sizeof(RpPVSVisMap);
        RWRETURN(len);
    }

    RWRETURN(0);
}

/****************************************************************************/
RpWorldSector      *
_rpPVSAlloc(RpWorldSector * spSect, void *pData)
{
    RpPVSCache         *pvsCache = (RpPVSCache *) pData;
    RpPVS              *ppCur = PVSFROMWORLDSECTOR(spSect);
    RwInt32             len, num;

    RWFUNCTION(RWSTRING("_rpPVSAlloc"));

    RWASSERT(ppCur->sectorID == pvsCache->nextID);

    /* find the largest number of verts in a sector. */
    num = RpWorldSectorGetNumVertices(spSect);
    pvsCache->maxNumVert =
        (num > pvsCache->maxNumVert) ? num : pvsCache->maxNumVert;
    num = RpWorldSectorGetNumPolygons(spSect);
    pvsCache->maxNumPoly =
        (num > pvsCache->maxNumPoly) ? num : pvsCache->maxNumPoly;

    /* assign vismaps */
    ppCur->vismap = (RpPVSVisMap *) pvsCache->ppNext;

    /* bump to next set of vismaps */
    len =
        ppCur->z_segs * ppCur->y_segs * ppCur->x_segs *
        pvsCache->sectorCache.vislen;
    pvsCache->ppNext += len * sizeof(RpPVSVisMap);
    pvsCache->nextID++;

    RWRETURN(spSect);
}

/****************************************************************************/

/*
 * Function to support pre Ver 3 PVS data.
 *
 * Destroy Ver 1 PVS data since we cannot handle them.
 *
 */
static RpWorldSector *
PVSSectorDestroyVismapVer1(RpWorldSector * spSect,
                           void * __RWUNUSED__ pData)
{
    RpPVS              *ppCur;
#if (0)
    RpPVSCache         *pvsCache = (RpPVSCache *) pData;
#endif /* (0) */

    RWFUNCTION(RWSTRING("PVSSectorDestroyVismapVer1"));

    ppCur = PVSFROMWORLDSECTOR(spSect);

    if (ppCur->vismap != NULL)
        RwFree(ppCur->vismap);

    ppCur->vismap = (RwUInt32 *)NULL;

    RWRETURN(spSect);
}

/*
 * Function to support pre Ver 3 PVS data.
 *
 * Pre Ver 3 PVS data was stored with the sector rather than the world, so
 * it needs to be recombined after into the world after loading.
 */
static RpWorldSector *
PVSunify(RpWorldSector * spSect, void *pData)
{
    RpPVSCache         *pvsCache = (RpPVSCache *) pData;
    RpPVS              *ppCur;

    RpPVSVer2          *ppCurVer2;
    RwInt32             num;

    RWFUNCTION(RWSTRING("PVSunify"));

    /* find the largest number of verts in a sector. */
    num = RpWorldSectorGetNumVertices(spSect);
    pvsCache->maxNumVert =
        (num > pvsCache->maxNumVert) ? num : pvsCache->maxNumVert;

    num = RpWorldSectorGetNumPolygons(spSect);
    pvsCache->maxNumPoly =
        (num > pvsCache->maxNumPoly) ? num : pvsCache->maxNumPoly;

    /* swap pvs over to single chunk */
    ppCur = PVSFROMWORLDSECTOR(spSect);
    ppCurVer2 = (RpPVSVer2 *) pvsCache->ppNext;

    memcpy((RwChar *) ppCurVer2, (RwChar *) ppCur->vismap,
           rpPVSGlobals.sectorCacheVer2.size);

    RwFree(ppCur->vismap);

    ppCur->vismap = (RpPVSVisMap *) ppCurVer2->vismap;

    /* onto next one */
    pvsCache->ppNext =
        (((RwChar *) ppCurVer2) + rpPVSGlobals.sectorCacheVer2.size);
    pvsCache->nextID++;

    RWRETURN(spSect);
}

static RwStream    *
PVSWorldReadChunk(RwStream * s,
                  RwInt32 len,
                  RpWorld * wpWorld,
                  RwInt32 __RWUNUSED__ offset,
                  RwInt32 __RWUNUSED__ size)
{
    RpPVSCache         *pvsCache = PVSCACHEFROMWORLD(wpWorld);
    RwInt32             length, version;

    RWFUNCTION(RWSTRING("PVSWorldReadChunk"));

    /* set current world */
    rpPVSGlobals.world = wpWorld;

    /* Version number. */
    RwStreamRead(s, &version, sizeof(RwUInt32));
    RwMemNative(&version, sizeof(RwUInt32));

    /* Check the version before reading the remaining data. */
    if (version == PVSVERSION2)
    {
        /* Old ver 2 PVS data. */
        RWMESSAGE(("Warning : Version 2 PVS Data found."));

        RwStreamRead(s,
                     &rpPVSGlobals.sectorCacheVer2.count,
                     (sizeof(RpPVSCacheSize) - sizeof(RwUInt32)));
        RwMemNative(&rpPVSGlobals.sectorCacheVer2.count,
                    (sizeof(RpPVSCacheSize) - sizeof(RwUInt32)));

        /* allocate space */
        pvsCache->sectorBlocks = (char *)
            RwMalloc(rpPVSGlobals.sectorCacheVer2.size *
                     rpPVSGlobals.sectorCacheVer2.count);

        /* swap over PVS for each sector */
        pvsCache->ppNext = pvsCache->sectorBlocks;
        RpWorldForAllWorldSectors(wpWorld, PVSunify, pvsCache);

        rpPVSGlobals.sectorCacheVer2.version = PVSVERSION2;
        pvsCache->sectorCache.version = PVSVERSION2;
    }
    else if (version == PVSVERSION)
    {
        /* assign and read the rest */
        RwStreamRead(s,
                     &pvsCache->sectorCache.viscount,
                     (sizeof(RpPVSCacheSize) - sizeof(RwUInt32)));
        RwMemNative(&pvsCache->sectorCache.viscount,
                    (sizeof(RpPVSCacheSize) - sizeof(RwUInt32)));

        /* allocate space and read */
        length =
            pvsCache->sectorCache.viscount *
            pvsCache->sectorCache.vislen;
        pvsCache->sectorBlocks = (char *)
            RwMalloc(length * sizeof(RpPVSVisMap));
        RwStreamRead(s, pvsCache->sectorBlocks,
                     length * sizeof(RpPVSVisMap));

        /* point each sector into vismap array, find largest sector */
        pvsCache->nextID = 0;
        pvsCache->ppNext = pvsCache->sectorBlocks;
        RpWorldForAllWorldSectors(wpWorld, _rpPVSAlloc, pvsCache);

        rpPVSGlobals.sectorCacheVer2.version = PVSVERSION;
        pvsCache->sectorCache.version = PVSVERSION;
    }
    else
    {
        /* its not the right version! */

        /* Destroy the data read in by the sector. */
        RpWorldForAllWorldSectors(wpWorld,
                                  PVSSectorDestroyVismapVer1, pvsCache);

        /* Old ver 1 PVS data. */
        RWMESSAGE(("ERROR : Version 1 PVS Data found. Cannot load."));

        rpPVSGlobals.sectorCacheVer2.version = PVSVERSION1;
        pvsCache->sectorCache.version = PVSVERSION1;

        RwStreamSkip(s, len - sizeof(RwUInt32));
        RWRETURN(s);
    }

    RWRETURN(s);
}

static RwStream    *
PVSWorldWriteChunk(RwStream * s,
                   RwInt32 __RWUNUSED__ len,
                   RpWorld * wpWorld,
                   RwInt32 __RWUNUSED__ offset,
                   RwInt32 __RWUNUSED__ size)
{
    const RpPVSCache   *pvsCache = PVSCACHEFROMCONSTWORLD(wpWorld);
    RpPVSCacheSize      sectorCache;
    RwInt32             length;

    RWFUNCTION(RWSTRING("PVSWorldWriteChunk"));

    /* set current world */
    rpPVSGlobals.world = (RpWorld *) wpWorld;

    /* just the non runtime dependent state */
    sectorCache = pvsCache->sectorCache;
    RwMemLittleEndian(&sectorCache, sizeof(sectorCache));
    RwStreamWrite(s, &sectorCache, sizeof(sectorCache));

    /* and all the vismaps */
    length =
        pvsCache->sectorCache.viscount * pvsCache->sectorCache.vislen;
    RwStreamWrite(s, pvsCache->sectorBlocks,
                  length * sizeof(RpPVSVisMap));

    RWRETURN(s);
}

/**
 * \ingroup rppvs310
 * \ref RpPVSPluginAttach is used to attach the PVS plugin to the
 * RenderWare system to enable the building and use of potentially visible
 * sets.  The PVS plugin must be attached between initializing the system
 * with \ref RwEngineInit and opening it with \ref RwEngineOpen.
 *
 * Note that the PVS plugin requires the world plugin to be attached.
 *
 * The include file rppvs310.h is also required and must be included by an application
 * wishing to use PVS.  Note also that when linking the PVS plugin library,
 * rppvs310.lib, into an application, make sure the following RenderWare libraries
 * are made available to the linker: rtray.lib and rtworld.lib.
 *
 * \return TRUE if successful or FALSE if there is an error.
 *
 * \see RpPVSAtomicVisible
 * \see RpPVSDestroy
 * \see RpPVSGetProgressCallBack
 * \see RpPVSHook
 * \see RpPVSQuery
 * \see RpPVSSetProgressCallBack
 * \see RpPVSSetViewPosition
 * \see RpPVSStatisticsGet
 * \see RpPVSUnhook
 * \see RpPVSWorldSectorVisible
 * \see RpWorldPluginAttach
 * \see RwEngineInit
 * \see RwEngineOpen
 * \see RwEngineStart
 */
RwBool
RpPVSPluginAttach(void)
{
    RwInt32             offset;

    RWAPIFUNCTION(RWSTRING("RpPVSPluginAttach"));

    rpPVSGlobals.world = (RpWorld *)NULL;
    rpPVSGlobals.camera = (RwCamera *)NULL;
    rpPVSGlobals.image = (RwImage *)NULL;

    rpPVSGlobals.rasterW = PVSRASTERWIDTH;
    rpPVSGlobals.rasterH = PVSRASTERHEIGHT;

    rpPVSGlobals.worldOffset =
        RpWorldRegisterPlugin(sizeof(RpPVSCache),
                              rwID_PVSPLUGIN,
                              (RwPluginObjectConstructor)PVSWorldConstructor,
                              (RwPluginObjectDestructor)PVSWorldDestructor,
                              (RwPluginObjectCopy)NULL);
    if (rpPVSGlobals.worldOffset < 0)
    {
        RWRETURN(FALSE);
    }

    offset =
        RpWorldRegisterPluginStream(rwID_PVSPLUGIN,
                                    (RwPluginDataChunkReadCallBack)
                                    PVSWorldReadChunk,
                                    (RwPluginDataChunkWriteCallBack)
                                    PVSWorldWriteChunk,
                                    (RwPluginDataChunkGetSizeCallBack)
                                    PVSWorldGetChunkSize);

    if (offset < 0)
    {
        RWRETURN(FALSE);
    }

    rpPVSGlobals.sectorOffset =
        RpWorldSectorRegisterPlugin(sizeof(RpPVS),
                                    rwID_PVSPLUGIN,
                                    (RwPluginObjectConstructor)PVSSectorConstructor,
                                    (RwPluginObjectDestructor)PVSSectorDestructor,
                                    (RwPluginObjectCopy)NULL);
    if (rpPVSGlobals.sectorOffset < 0)
    {
        RWRETURN(FALSE);
    }

    offset =
        RpWorldSectorRegisterPluginStream(rwID_PVSPLUGIN,
                                          (RwPluginDataChunkReadCallBack)
                                          PVSSectorReadChunk,
                                          (RwPluginDataChunkWriteCallBack)
                                          PVSSectorWriteChunk,
                                          (RwPluginDataChunkGetSizeCallBack)
                                          PVSSectorGetChunkSize);

    if (offset < 0)
    {
        RWRETURN(FALSE);
    }

    RWRETURN(TRUE);
}

/*
 * Set the size of raster to be used during PVS sampling.
 */
RwBool
_rpPVSRasterSetSize(RwInt32 w, RwInt32 h)
{
    RWFUNCTION(RWSTRING("_rpPVSRasterSetSize"));

    RWASSERT(w > 0);
    RWASSERT(h > 0);

    rpPVSGlobals.rasterW = w;
    rpPVSGlobals.rasterH = h;

    RWRETURN(TRUE);
}

/*
 * Get the size of raster be used during PVS sampling.
 */
RwBool
_rpPVSRasterGetSize(RwInt32 *w, RwInt32 *h)
{
    RWFUNCTION(RWSTRING("_rpPVSRasterGetSize"));

    RWASSERT(w != NULL);
    RWASSERT(h != NULL);

    *w = rpPVSGlobals.rasterW;
    *h = rpPVSGlobals.rasterH;

    RWRETURN(TRUE);
}

