/*
 * Refinement plugin
 */

/**
 * \ingroup rprefine
 * \page rprefineoverview RpRefine Plugin Overview
 *
 * RpRefine adds curved surface support to RenderWare.  In this release, 
 * the PowerPipe node that implements this feature is a software version 
 * and is therefore slower than an our intended solution.  It is provided 
 * to allow developers to begin learning this technology.
 */

/**********************************************************************
 *
 * File :     RpRefine.c
 *
 * Abstract : Handle refinemment 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.
 *
 ************************************************************************/

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

#include "rpplugin.h"
#include "time.h"
#include <rpdbgerr.h>
#include <rwcore.h>
#include <rpworld.h>

#include "rprefine.h"
#include "refinevars.h"

#if (!defined(DOXYGEN_SHOULD_SKIP_THIS))
static const char   rcsid[] __RWUNUSED__ =
    "@@(#)$Id: rprefine.c,v 1.26 2001/02/02 16:04:26 johns Exp $";
#endif /* (!defined(DOXYGEN_SHOULD_SKIP_THIS)) */

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

/****************************************************************************
 * Local Globals.
 */

RpRefineGlobalVars  rpRefineGlobals;

/****************************************************************************
 Global Types
 */

/************************************************************************
 * At start up we create a table telling us the ordering of vertices for each
 * depth.
 */
static void
RefineVertIndexArrayCreate(void)
{
    RwInt32             depth, i, j;
    RwInt32             start, end;
    RwInt32            *v;

    RWFUNCTION(RWSTRING("RefineVertIndexArrayCreate"));

    /* space for the array of triangle vertex indices and offsets into this
     * array */
    rpRefineGlobals.vertIndexArray = (RwInt32 *)
        malloc(sizeof(RwInt32) * 3 *
               (4 + 9 + 16 + 25 + 36 + 49 + 64 + 81 + 100));
    rpRefineGlobals.vertIndexStarts = (RwInt32 **)
        malloc(sizeof(RwInt32 *) * 12);
    v = rpRefineGlobals.vertIndexArray;

    rpRefineGlobals.vertIndexStarts[0] = (RwInt32 *)NULL;
    rpRefineGlobals.vertIndexStarts[1] = (RwInt32 *)NULL; /* depths start at 2 */

    for (depth = 2; depth < 11; depth++)
    {
        rpRefineGlobals.vertIndexStarts[depth] = v;
        start = -1;
        end = depth - 1;
        for (i = 0; i < depth; i++)
        {
            for (j = 0; j < depth - 1 - i; j++)
            {
                *v = start + j + 1;
                v++;
                *v = start + j;
                v++;
                *v = end + j;
                v++;
                *v = start + j + 1;
                v++;
                *v = end + j;
                v++;
                *v = end + j + 1;
                v++;
            }

            *v = start + j + 1;
            v++;
            *v = start + j;
            v++;
            *v = end + j;
            v++;

            start = end;
            end += (depth - i);
        }
    }

    RWRETURNVOID();
}

static void
RefineVertIndexArrayDestroy(void)
{
    RWFUNCTION(RWSTRING("RefineVertIndexArrayDestroy"));

    if (rpRefineGlobals.vertIndexArray)
    {
        free(rpRefineGlobals.vertIndexArray);
        rpRefineGlobals.vertIndexArray = (RwInt32 *)NULL;
    }

    if (rpRefineGlobals.vertIndexStarts)
    {
        free(rpRefineGlobals.vertIndexStarts);
        rpRefineGlobals.vertIndexStarts = (RwInt32 **)NULL;
    }

    RWRETURNVOID();
}

/************************************************************************
 */
static              RwInt32
RefineAtomicCallBackDepth(RpAtomic * atomic)
{
    RwInt32             result;
    RwInt32             offset, depth;
    RwV3d               vAt, atomicPos, cameraPos, cameraAt;
    RwReal              dot;
    RwCamera           *camera;

    RWFUNCTION(RWSTRING("RefineAtomicCallBackDepth"));

    result = -1;
    offset = rpRefineGlobals.atmExtOffset;
    camera = RwCameraGetCurrentCamera();

    RWASSERT(offset >= 0);
    RWASSERT(atomic != NULL);
    RWASSERT(camera != NULL);

    {
        RpRefineAtomicExtension *extension;

        extension = RPREFINEOFFSET(atomic, offset);

        if (extension != NULL)
        {
            depth = extension->curDepth;

            /* Calculate how far the atomic is away from the viewer. */
            /* atomicPos = RwFrameGetLTM(RpAtomicGetFrame(atomic))->pos; */
            atomicPos = atomic->worldBoundingSphere.center;

            cameraPos = RwFrameGetLTM(RwCameraGetFrame(camera))->pos;
            cameraAt = RwFrameGetLTM(RwCameraGetFrame(camera))->at;

            RwV3dSub(&vAt, &atomicPos, &cameraPos);

            dot = RwV3dDotProduct(&vAt, &cameraAt);

            result = (RwInt32) (dot * extension->invFarRange);

            result = (result < 0) ? 0 :
                (result >= depth) ? depth : result;

            result = depth - result;

        }
    }

    RWRETURN(result);
}

/************************************************************************
 */
static RpRefineAtomicExtension *
RefineAtomicInitExtension(RpRefineAtomicExtension * extension)
{
    RWFUNCTION(RWSTRING("RefineAtomicInitExtension"));

    RWASSERT(extension != NULL);

    {
        extension->selectDepth = RefineAtomicCallBackDepth;

        extension->farRange = -1;
        extension->invFarRange = -1;

        extension->curDepth = 0;
    }

    RWRETURN(extension);
}

/************************************************************************
 */
static RpRefineAtomicExtension *
RefineAtomicNewExtension(void)
{
    RpRefineAtomicExtension *extension;

    RWFUNCTION(RWSTRING("RefineAtomicNewExtension"));

    extension = (RpRefineAtomicExtension *)
        RwFreeListAlloc(rpRefineGlobals.atmExtFreeList);

    if (extension != NULL)
        RefineAtomicInitExtension(extension);

    RWRETURN(extension);
}

/************************************************************************
 */
static RpRefineAtomicExtension *
RefineAtomicCopyExtensino(RpRefineAtomicExtension * rpDstExt,
                          RpRefineAtomicExtension * rpSrcExt)
{
    RWFUNCTION(RWSTRING("RefineAtomicCopyExtensino"));

    RWASSERT(rpDstExt != NULL);
    RWASSERT(rpSrcExt != NULL);

    {
        rpDstExt->selectDepth = rpSrcExt->selectDepth;

        rpDstExt->farRange = rpSrcExt->farRange;

        rpDstExt->invFarRange = rpSrcExt->invFarRange;

        rpDstExt->curDepth = rpSrcExt->curDepth;
    }

    RWRETURN(rpSrcExt);
}

/************************************************************************
 */
static void
RefineAtomicDestroyExtension(RpRefineAtomicExtension * extension)
{

    RWFUNCTION(RWSTRING("RefineAtomicDestroyExtension"));

    if (extension != NULL)
    {
        RefineAtomicInitExtension(extension);

        RwFreeListFree(rpRefineGlobals.atmExtFreeList, extension);
    }

    RWRETURNVOID();
}

/************************************************************************
 */
static RwStream    *
RefineAtomicCallBackReadChunk(RwStream * stream,
                              RwInt32 __RWUNUSED__ len,
                              RpAtomic * atomic,
                              RwInt32 offset, RwInt32 __RWUNUSED__ size)
{
    RwStream           *result;
    RwUInt32            structSize, version;

    RWFUNCTION(RWSTRING("RefineAtomicCallBackReadChunk"));

    result = (RwStream *)NULL;

    if (RwStreamFindChunk(stream, rwID_STRUCT, &structSize, &version))
    {
        RpRefineAtomicExtension *extension;

        if ((version >= rwLIBRARYBASEVERSION) &&
            (version <= rwLIBRARYCURRENTVERSION))
        {
            result = stream;

            extension = RefineAtomicNewExtension();

            /* Read the atomic cache from the stream. */
            RWASSERT(structSize <= sizeof(RpRefineAtomicExtension));

            RwStreamRead(stream, extension, structSize);

            RwMemNative(extension, sizeof(RpRefineAtomicExtension));

            /* Reset the select depth to the default.
             */
            extension->selectDepth = RefineAtomicCallBackDepth;

            RPREFINEOFFSET(atomic, offset) = extension;
        }
        else
        {
            RPREFINEOFFSET(atomic, offset) = (RpRefineAtomicExtension *)NULL;
            RWERROR((E_RW_BADVERSION));
        }
    }
    else
    {
        RPREFINEOFFSET(atomic, offset) = (RpRefineAtomicExtension *)NULL;
    }

    RWRETURN(result);
}

/************************************************************************
 */
static RwStream    *
RefineAtomicCallBackWriteChunk(RwStream * stream,
                               RwInt32 __RWUNUSED__ len,
                               RpAtomic * atomic,
                               RwInt32 offset,
                               RwInt32 __RWUNUSED__ size)
{
    RpRefineAtomicExtension *extension;

    RWFUNCTION(RWSTRING("RefineAtomicCallBackWriteChunk"));

    extension = ((RpRefineAtomicExtension *)
                 RPREFINEOFFSET(atomic, offset));

    if (extension != NULL)
    {
        RpRefineAtomicExtension tmpExtension;

        RwStreamWriteChunkHeader(stream, rwID_STRUCT,
                                 sizeof(RpRefineAtomicExtension));

        /* Write out the cache. */
        tmpExtension = *extension;
        RwMemLittleEndian(&extension, sizeof(RpRefineAtomicExtension));

        RwStreamWrite(stream, &tmpExtension,
                      sizeof(RpRefineAtomicExtension));
    }

    RWRETURN(stream);
}

/************************************************************************
 */
static              RwInt32
RefineAtomicCallBackGetChunkSize(RpAtomic * atomic,
                                 RwInt32 offset, RwInt32 size)
{
    RwInt32             totalsize;
    RpRefineAtomicExtension *extension;

    RWFUNCTION(RWSTRING("RefineAtomicCallBackGetChunkSize"));

    size = 0;
    totalsize = 0;

    extension = RPREFINEOFFSET(atomic, offset);

    if (extension != NULL)
    {
        totalsize = sizeof(RpRefineAtomicExtension) + rwCHUNKHEADERSIZE;
    }

    RWRETURN(totalsize);
}

/************************************************************************
 */
static void        *
RefineAtomicCallBackConstructor(RpAtomic * atomic,
                                RwInt32 offset,
                                RwInt32 __RWUNUSED__ size)
{
    RWFUNCTION(RWSTRING("RefineAtomicCallBackConstructor"));

    RPREFINEOFFSET(atomic, offset) = (RpRefineAtomicExtension *)NULL;

    RWRETURN(atomic);
}

/************************************************************************
 */
static void        *
RefineAtomicCallBackCopy(RpAtomic * dstAtomic,
                         RpAtomic * srcAtomic,
                         RwInt32 offset, RwInt32 __RWUNUSED__ size)
{
    RpRefineAtomicExtension *srcExtension, *dstExtension;

    RWFUNCTION(RWSTRING("RefineAtomicCallBackCopy"));

    srcExtension = RPREFINEOFFSET(srcAtomic, offset);

    /* Check if the src is empty. */
    if (srcExtension != NULL)
    {
        /* Check if we need to create a new cache for the destination. */
        dstExtension = RPREFINEOFFSET(dstAtomic, offset);

        if (dstExtension == NULL)
            dstExtension = RefineAtomicNewExtension();

        if (dstExtension != NULL)
            RefineAtomicCopyExtensino(dstExtension, srcExtension);

        RPREFINEOFFSET(dstAtomic, offset) = dstExtension;
    }

    RWRETURN(srcAtomic);
}

/************************************************************************
 */
static void        *
RefineAtomicCallBackDestructor(RpAtomic * atomic,
                               RwInt32 offset,
                               RwInt32 __RWUNUSED__ size)
{
    RpRefineAtomicExtension *extension;

    RWFUNCTION(RWSTRING("RefineAtomicCallBackDestructor"));

    extension = RPREFINEOFFSET(atomic, offset);

    if (extension != NULL)
    {
        RefineAtomicDestroyExtension(extension);

        RPREFINEOFFSET(atomic, offset) = (RpRefineAtomicExtension *)NULL;
    }

    RWRETURN(atomic);
}

/************************************************************************
 */
static void        *
RefineAtomicCallBackOpen(void *ptr,
                         RwInt32 __RWUNUSED__ offset,
                         RwInt32 __RWUNUSED__ size)
{
    void               *result;
    RwUInt32            num;

    RWFUNCTION(RWSTRING("RefineAtomicCallBackOpen"));

    result = ptr;
    /*
     * if (RpRefineAtomicGlobals == NULL)
     * result = NULL;
     * else
     */
    {
        num = 4096 / sizeof(RpRefineAtomicExtension);

        rpRefineGlobals.atmExtFreeList =
            RwFreeListCreate(sizeof(RpRefineAtomicExtension), num, 0);

        if (rpRefineGlobals.atmExtFreeList == NULL)
            result = NULL;

        RefineVertIndexArrayCreate();

        if (rpRefineGlobals.vertIndexArray == NULL)
            result = NULL;
    }

    RWRETURN(result);
}

/************************************************************************
 */
static void        *
RefineAtomicCallBackClose(void *ptr,
                          RwInt32 __RWUNUSED__ offset,
                          RwInt32 __RWUNUSED__ size)
{
    RWFUNCTION(RWSTRING("RefineAtomicCallBackClose"));
    /*
     * if (RpRefineAtomicGlobals != NULL)
     */
    {
        if (rpRefineGlobals.atmExtFreeList != NULL)
            RwFreeListDestroy(rpRefineGlobals.atmExtFreeList);

        rpRefineGlobals.atmExtFreeList = (RwFreeList *)NULL;

        RefineVertIndexArrayDestroy();

        /*
         * RwFree(RpRefineAtomicGlobals);
         * 
         * RpRefineAtomicGlobals = NULL;
         */
    }

    RWRETURN(ptr);
}

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

RwInt32            *
_rpRefineGetVertIndexArray(RwInt32 depth)
{
    RWFUNCTION(RWSTRING("_rpRefineGetVertIndexArray"));

    RWRETURN(rpRefineGlobals.vertIndexStarts[depth]);
}

/**
 * \ingroup rprefine
 * \ref RpRefineAtomicSetRange
 * is called to set the maximum range for the refinement.
 *
 * The atomic will not be refined when beyond this distance.
 *
 * \param atomic  the refinement's atomic.
 * \param farRange  maximum range for the refinement.
 *
 * \return The atomic if successful, NULL otherwise.
 *
 * \see RpRefineAtomicGetRange
 * \see RpRefinePluginAttach
 */
RpAtomic           *
RpRefineAtomicSetRange(RpAtomic * atomic, RwReal farRange)
{
    RpAtomic           *result;
    RwInt32             offset;

    RWAPIFUNCTION(RWSTRING("RpRefineAtomicSetRange"));

    result = (RpAtomic *)NULL;
    offset = rpRefineGlobals.atmExtOffset;

    RWASSERT(offset >= 0);
    RWASSERT(atomic != NULL);
    RWASSERT(farRange > (RwReal) 0.0);

    {
        RpRefineAtomicExtension *extension;

        extension = RPREFINEOFFSET(atomic, offset);

        if (extension == NULL)
        {
            extension = RefineAtomicNewExtension();

            RPREFINEOFFSET(atomic, offset) = extension;
        }

        if (extension != NULL)
        {
            extension->farRange = farRange;
            extension->invFarRange =
                (RwReal) RPREFINEMAXDEPTH / farRange;

            result = atomic;
        }
    }

    RWRETURN(result);
}

/**
 * \ingroup rprefine
 * \ref RpRefineAtomicGetRange
 * is called to get the current range for the refinement.
 *
 * The atomic will not be refined when beyond this distance.
 *
 * \param atomic  the refinement's atomic.
 *
 * \return The far range if successful, -1 otherwise.
 *
 * \see RpRefineAtomicSetRange
 * \see RpRefinePluginAttach
 */
RwReal
RpRefineAtomicGetRange(RpAtomic * atomic)
{
    RwReal              result;
    RwInt32             offset;

    RWAPIFUNCTION(RWSTRING("RpRefineAtomicGetRange"));

    result = (RwReal) - 1.0;
    offset = rpRefineGlobals.atmExtOffset;

    RWASSERT(offset >= 0);
    RWASSERT(atomic != NULL);

    {
        RpRefineAtomicExtension *extension;

        extension = RPREFINEOFFSET(atomic, offset);

        if (extension != NULL)
            result = extension->farRange;
    }

    RWRETURN(result);
}

/**
 * \ingroup rprefine
 * \ref RpRefineAtomicSetDepthCallBack
 * is called to set call back for determining the depth of refinement.
 *
 * This is called when an atomic is refined, thereby allowing the
 * refinement depth to set dynamically -- for example depending on
 * camera distance.
 *
 * The format for the RpRefineAtomicDepthCallback is
 *
 * RwInt32 (*RpAtomicDepthCallback) (RpAtomic *atomic)
 *
 * \param atomic  the refinement atomic
 * \param callback  the callback function.
 *
 * \return the atomic if successful. NULL otherwise.
 *
 * \see RpRefineAtomicGetDepth
 * \see RpRefineAtomicSetDepth
 * \see RpRefinePluginAttach
 */
RpAtomic           *
RpRefineAtomicSetDepthCallBack(RpAtomic * atomic,
                               RpRefineAtomicDepthCallBack callback)
{
    RpAtomic           *result;
    RwInt32             offset;

    RWAPIFUNCTION(RWSTRING("RpRefineAtomicSetDepthCallBack"));

    result = (RpAtomic *)NULL;
    offset = rpRefineGlobals.atmExtOffset;

    RWASSERT(offset >= 0);
    RWASSERT(atomic != NULL);
    RWASSERT(callback != NULL);

    {
        RpRefineAtomicExtension *extension;

        extension = RPREFINEOFFSET(atomic, offset);

        if (extension == NULL)
        {
            extension = RefineAtomicNewExtension();

            RPREFINEOFFSET(atomic, offset) = extension;
        }

        if (extension != NULL)
        {
            extension->selectDepth = callback;

            result = atomic;
        }
    }

    RWRETURN(result);
}

/**
 * \ingroup rprefine
 * \ref RpRefineAtomicSetDepth
 * is called to set the depth for the refinement.
 *
 * Each sided of each triangle will be split
 * into this many segments by refinement.
 *
 * \param atomic  the refinement's atomic.
 * \param depth  maximum depth of the refinements.
 *
 * \return The atomic if successful, NULL otherwise.
 *
 * \see RpRefineAtomicGetDepth
 * \see RpRefineAtomicSetDepthCallBack
 * \see RpRefinePluginAttach
 */
RpAtomic           *
RpRefineAtomicSetDepth(RpAtomic * atomic, RwInt32 depth)
{
    RpAtomic           *result;
    RwInt32             offset;

    RWAPIFUNCTION(RWSTRING("RpRefineAtomicSetDepth"));

    result = (RpAtomic *)NULL;
    offset = rpRefineGlobals.atmExtOffset;

    RWASSERT(offset >= 0);
    RWASSERT(atomic != NULL);
    RWASSERT(depth > 0);

    {
        RpRefineAtomicExtension *extension;

        extension = RPREFINEOFFSET(atomic, offset);

        if (extension == NULL)
        {
            extension = RefineAtomicNewExtension();

            RPREFINEOFFSET(atomic, offset) = extension;
        }

        if (extension != NULL)
        {
            extension->curDepth = (depth < RPREFINEMAXDEPTH) ?
                ((depth > RPREFINEMINDEPTH) ?
                 depth : RPREFINEMINDEPTH) : RPREFINEMAXDEPTH;

            result = atomic;
        }
    }

    RWRETURN(result);
}

/**
 * \ingroup rprefine
 * \ref RpRefineAtomicGetDepth
 * is called to get the current depth of the refinement.
 *
 * Each sided of each triangle will be split
 * into this many segments by refinement.
 *
 * \param atomic  the refinement's atomic.
 *
 * \return the refine depth if successful, -1 otherwise.
 *
 * \see RpRefineAtomicSetDepth
 * \see RpRefineAtomicSetDepthCallBack
 * \see RpRefinePluginAttach
 */
RwInt32
RpRefineAtomicGetDepth(RpAtomic * atomic)
{
    RwInt32             result;
    RwInt32             offset;

    RWAPIFUNCTION(RWSTRING("RpRefineAtomicGetDepth"));

    result = -1;
    offset = rpRefineGlobals.atmExtOffset;

    RWASSERT(offset >= 0);
    RWASSERT(atomic != NULL);

    {
        RpRefineAtomicExtension *extension;

        extension = RPREFINEOFFSET(atomic, offset);

        if (extension != NULL)
            result = extension->curDepth;
    }

    RWRETURN(result);
}

/**
 * \ingroup rprefine
 * \ref RpRefinePluginAttach
 * is called by the application to indicate
 * that the refinement plugin should be used.
 *
 * The call to this function should be placed after \ref RwEngineInit.
 *
 * \return True on success, false otherwise
 */
RwBool
RpRefinePluginAttach(void)
{
    RwInt32             offset;
    RwBool              result = FALSE;

    RWAPIFUNCTION(RWSTRING("RpRefinePluginAttach"));

    /* Extend the global data block to include refinement globals */

    /* Need to initialise the global here before engine open to pick up the
     * offsets */
    /*
     * RpRefineAtomicGlobals = (RpRefineAtomicGlobalVars *)
     * RwMalloc(sizeof(RpRefineAtomicGlobalVars));
     * 
     * if (RpRefineAtomicGlobals != NULL)
     */
    {
        rpRefineGlobals.engineOffset =
            RwEngineRegisterPlugin(0,
                                   MAKECHUNKID(rwVENDORID_CSLRD,
                                               rwID_REFINEPLUGIN),
                                   (RwPluginObjectConstructor)
                                   RefineAtomicCallBackOpen,
                                   (RwPluginObjectDestructor)
                                   RefineAtomicCallBackClose);

        rpRefineGlobals.atmExtOffset =
            RpAtomicRegisterPlugin(sizeof(RpRefineAtomicExtension *),
                                   rwID_REFINEPLUGIN,
                                   (RwPluginObjectConstructor)
                                   RefineAtomicCallBackConstructor,
                                   (RwPluginObjectDestructor)
                                   RefineAtomicCallBackDestructor,
                                   (RwPluginObjectCopy)
                                   RefineAtomicCallBackCopy);

        offset =
            RpAtomicRegisterPluginStream(MAKECHUNKID
                                         (rwVENDORID_CSLRD,
                                          rwID_REFINEPLUGIN),
                                         (RwPluginDataChunkReadCallBack)
                                         RefineAtomicCallBackReadChunk,
                                         (RwPluginDataChunkWriteCallBack)
                                         RefineAtomicCallBackWriteChunk,
                                         (RwPluginDataChunkGetSizeCallBack)
                                         RefineAtomicCallBackGetChunkSize);

        rpRefineGlobals.atmExtFreeList = (RwFreeList *)NULL;

        rpRefineGlobals.enabledFlag = 0;

        result = (rpRefineGlobals.atmExtOffset >= 0);
    }

    RWRETURN(result);
}
