/*****************************************************************************
 *
 * File :     rpanisot.c
 *
 * Abstract : Xbox Anisotropic Texture Sampling Plugin for 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) 2000 Criterion Software Ltd.
 * All Rights Reserved.
 *
 * RenderWare is a trademark of Canon Inc.
 *
 *****************************************************************************/

#include <string.h>

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

#include "rpplugin.h"
#include <rpdbgerr.h>
#include "rpanisot.h"
#include "anispriv.h"

#if (!defined(DOXYGEN))
static const char rcsid[] __RWUNUSED__ = 
    "@@@@(#)$Id: rpanisot.c,v 1.4 2001/08/30 08:52:30 johns Exp $";
#endif /* (!defined(DOXYGEN)) */

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

#define TEXTUREGETANISOT(tex) \
    ((RwUInt8*)(((RwUInt8*)tex) + anisotTextureOffset))

#define CONSTTEXTUREGETANISOT(tex) \
    ((const RwUInt8*)(((const RwUInt8*)tex) + anisotTextureOffset))

/*--- Global Variables ---*/

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

static RwModuleInfo anisotModule;

static RwInt32      anisotTextureOffset = 0;
static RwInt32      anisotTextureStreamOffset = 0;
static RwInt8       anisotDefault = 1;  /* 1 does just isotropic sampling */

/*--- Plugin Engine Functions ---*/

static void        *
anisotOpen(void *instance, RwInt32 __RWUNUSED__ offset,
             RwInt32 __RWUNUSED__ size)
{
    RWFUNCTION(RWSTRING("anisotOpen"));
    RWASSERT(instance);

    anisotModule.numInstances++;

    RWRETURN(instance);
}

static void        *
anisotClose(void *instance, RwInt32 __RWUNUSED__ offset,
              RwInt32 __RWUNUSED__ size)
{
    RWFUNCTION(RWSTRING("anisotClose"));
    RWASSERT(instance);

    anisotModule.numInstances--;

    RWRETURN(instance);
}

/*--- Plugin Texture Functions ---*/

static void        *
anisotConstruct(void *object, RwInt32 __RWUNUSED__ offset,
                  RwInt32 __RWUNUSED__ size)
{
    RwTexture          *tex;
    RwUInt8           *anisot;

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

    tex = (RwTexture *) object;
    anisot = TEXTUREGETANISOT(tex);
    *anisot = (RwUInt8)anisotDefault;

    RWRETURN(object);
}

static void        *
anisotDestruct(void *object, RwInt32 __RWUNUSED__ offset,
                 RwInt32 __RWUNUSED__ size)
{
    RWFUNCTION(RWSTRING("anisotDestruct"));
    RWASSERT(object);

    RWRETURN(object);
}

static void        *
anisotCopy(void *destinationObject, const void *sourceObject,
             RwInt32 __RWUNUSED__ offset, RwInt32 __RWUNUSED__ size)
{
    RwTexture          *destinationTexture;
    const RwTexture    *sourceTexture;
    RwUInt8           *destinationanisot;
    const RwUInt8     *sourceanisot;

    RWFUNCTION(RWSTRING("anisotCopy"));
    RWASSERT(destinationObject);
    RWASSERT(sourceObject);

    destinationTexture = (RwTexture *) destinationObject;
    sourceTexture = (const RwTexture *) sourceObject;
    destinationanisot = TEXTUREGETANISOT(destinationTexture);
    sourceanisot = CONSTTEXTUREGETANISOT(sourceTexture);

    *destinationanisot = *sourceanisot;

    RWRETURN(destinationObject);
}

/*--- Plugin Texture Stream Functions ---*/

static RwStream    *
anisotRead(RwStream * stream, RwInt32 __RWUNUSED__ binaryLength,
             void *object, RwInt32 __RWUNUSED__ offset,
             RwInt32 __RWUNUSED__ size)
{
    RwTexture          *tex = (RwTexture *) object;
    RwInt32             val;

    RWFUNCTION(RWSTRING("anisotRead"));

    if ((binaryLength == 4) && (tex))
    {
        if (RwStreamReadInt(stream, &val, sizeof(RwInt32)))
        {
            RpAnisotTextureSetMaxAnisotropy(tex, (RwUInt8)val);
            RWRETURN(stream);
        }
        RWRETURN((RwStream *)NULL);
    }
    RWRETURN((RwStream *)NULL);
}

static RwStream    *
anisotWrite(RwStream * stream, RwInt32 __RWUNUSED__ binaryLength,
              const void *object, RwInt32 __RWUNUSED__ offset,
              RwInt32 __RWUNUSED__ size)
{
    const RwTexture    *tex = (const RwTexture *) object;
    RwInt32             val;

    RWFUNCTION(RWSTRING("anisotWrite"));

    if ((tex) && (tex->raster))
    {
        val = (RwInt32) (*CONSTTEXTUREGETANISOT(tex));
        if (RwStreamWriteInt(stream, &val, sizeof(RwInt32)))
        {
            RWRETURN(stream);
        }
        RWRETURN((RwStream *)NULL);
    }
    RWRETURN((RwStream *)NULL);
}

static RwInt32
anisotGetSize(const void * __RWUNUSED__  object,
              RwInt32 __RWUNUSED__ offsetInObject,
              RwInt32 __RWUNUSED__ sizeInObject)
{
    RWFUNCTION(RWSTRING("anisotGetSize"));

    RWRETURN(4);
}

/*--- Plugin API Functions ---*/
/**
 * \ingroup rpanisot
 * \ref RpAnisotGetMaxSupportedMaxAnisotropy
 * is used to determine the maximum Anisotropy available on the hardware.
 * For Xbox, this is 4.
 *
 * \return maximum anisotropy supported
 *
 * \see RpAnisotTextureSetMaxAnisotropy
 * \see RpAnisotTextureGetMaxAnisotropy
 */
RwInt8
RpAnisotGetMaxSupportedMaxAnisotropy(void)
{
    RWAPIFUNCTION(RWSTRING("RpAnisotGetMaxSupportedMaxAnisotropy"));

#if (defined(XBOX_DRVMODEL_H) || defined(NULL_DRVMODEL_H))
    RWRETURN(4);
#else   
    RWRETURN(0);    /* Not supported */
#endif
}

/**
 * \ingroup rpanisot
 * \ref RpAnisotTextureSetMaxAnisotropy is used to set the maximum anisotropy
 * value that will be used when a particular texture is drawn. Higher numbers
 * will produce better quality but slower texture sampling when polygons are
 * viewed edge on, so it should be used in moderation.
 * 
 * On Xbox this is done with the D3DTSS_MAXANISOTROPY texture stage state.
 * If the value is > 1, filter modes with magnification and minification filters
 * of type D3DTEXF_ANISOTROPIC are chosen with the same mip mapping filter
 * D3DTSS_MIPFILTER determined by the filter mode of the texture.
 * See also RwTextureFilterMode & RwTextureGetFilterMode.
 *
 * For a complete technical discussion see the white paper
 * "Anisotropic Texture Filtering in OpenGL" on http://www.nvidia.com/developer 
 * \param tex  texture to update.
 * \param val  integer max anisotropy value, must be less than
 * the maximum reported by RpAnisotGetMaxSupportedMaxAnisotropy.
 *
 * \return The texture, otherwise NULL on failure
 *
 * \see RpAnisotTextureGetMaxAnisotropy
 * \see RpAnisotGetMaxSupportedMaxAnisotropy
 */
RwTexture          *
RpAnisotTextureSetMaxAnisotropy(RwTexture * tex, RwInt8 val)
{
    RWAPIFUNCTION(RWSTRING("RpAnisotTextureSetMaxAnisotropy"));

    RWASSERT( val > 0 );

    /* or perhaps just clamp? */
    RWASSERT( val <= RpAnisotGetMaxSupportedMaxAnisotropy() );

    if (tex)
    {
        *TEXTUREGETANISOT(tex) = val;
        #ifdef XBOX_DRVMODEL_H
        _rwXbRasterSetMaxAnisotropy( tex->raster, val );
        #endif
        RWRETURN(tex);
    }

    RWRETURN((RwTexture *)NULL);
}

/**
 * \ingroup rpanisot
 * \ref RpAnisotTextureGetMaxAnisotropy is used to get the maximum anisotropy
 * value that will be used when a particular texture is drawn.
 * 
 * See \ref RpAnisotTextureSetMaxAnisotropy for further information.
 *
 * \param tex  texture being queried
 *
 * \return The texture's maximum anisotropy value
 *
 * \see RpAnisotTextureSetMaxAnisotropy
 * \see RpAnisotGetMaxSupportedMaxAnisotropy
 */
RwInt8
RpAnisotTextureGetMaxAnisotropy(RwTexture * tex)
{
    RWAPIFUNCTION(RWSTRING("RpAnisotTextureGetMaxAnisotropy"));
    if (tex)
    {
        RWRETURN(*TEXTUREGETANISOT(tex));
    }

    RWRETURN(0);
}

/**
 * \ingroup rpanisot
 * \ref RpAnisotPluginAttach is used to attach the anisot plugin to
 * the RenderWare system to enable Xbox anisotropic texturing values
 * to be stored with textures (and set as the D3DTSS_MAXANISOTROPY
 * texture stage state).
 * The plugin must be attached between initializing the system with
 * \ref RwEngineInit and opening it with \ref RwEngineOpen. 
 *
 * Note that the include file rpanisot.h is required and must be included by an
 * application wishing to use this facility.  The anisot library is contained
 * in the file rpanisot.lib.
 *
 * \return Returns TRUE if successful or FALSE if there is an error.
 *
 *
 *
 */
RwBool
RpAnisotPluginAttach(void)
{
    RwInt32             offset;

    RWAPIFUNCTION(RWSTRING("RpAnisotPluginAttach"));

    offset =
        RwEngineRegisterPlugin(0, rwPLUGIN_ID, anisotOpen,
                               anisotClose);
    if (offset < 0)
    {
        RWRETURN(FALSE);
    }

    anisotTextureOffset =
        RwTextureRegisterPlugin(sizeof(RwUInt8), rwPLUGIN_ID,
                                anisotConstruct, anisotDestruct,
                                anisotCopy);
    if (anisotTextureOffset < 0)
    {
        RWRETURN(FALSE);
    }

    anisotTextureStreamOffset =
        RwTextureRegisterPluginStream(rwPLUGIN_ID, anisotRead,
                                      anisotWrite, anisotGetSize);

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

    RWRETURN(TRUE);
}
