
/****************************************************************************
 *
 * 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. and Canon Inc. will not, under any
 * circumstances, be liable for any lost revenue or other damages
 * arising from the use of this file.
 *
 * Copyright (c) 2001 Criterion Software Ltd.
 * All Rights Reserved.
 *
 */

/****************************************************************************
 *
 * main.c
 *
 * Copyright (C) 2001 Criterion Technologies.
 *
 * Original author: Alexandre Hadjadj
 * Reviewed by:
 *
 * Purpose: A viewer capable of displaying clumps - including bones, skin, 
 *          and animation support.
 *
 ****************************************************************************/

#include <stdio.h>
#include <stdlib.h>
#include <ctype.h> /* isprint */

#include "rwcore.h"
#include "rpworld.h"

#ifdef RWLOGO
#include "rplogo.h"
#endif

#include "rpanim.h"
#include "rpbone.h"
#include "rpcollis.h"
#include "rplodatm.h"
#include "rpmorph.h"
#include "rpskin310.h"
#include "rphanim.h"
#include "rpdmorph.h"
#include "rprandom.h"

#if !defined(_XBOX)
#include "rpmatfx.h"
#endif

#include "rtcharse.h"
#include "rtworld.h"

#include "skeleton.h"
#include "menu.h"
#include "events.h"
#include "camera.h"

#ifdef RWMETRICS
#include "metrics.h"
#endif

#include "main.h"
#include "scene.h"
#include "clmpview.h"
#include "clmpskin.h"
#include "clmphanm.h"
#include "clmppick.h"
#include "clmpcntl.h"

#include "c:\dev\rw310\rwsdk\driver\common\palquant.h"


#define DEFAULT_SCREEN_WIDTH (640)
#define DEFAULT_SCREEN_HEIGHT (480)

static RwBool FPSOn = TRUE;
static RwBool OnScreenInfoOn = TRUE;

static RwCullMode FaceCullMode;
static RwInt32 FaceCullIndex;
static RwBool NewFaceCullMode = FALSE;

static RwInt32 FrameCounter = 0;
static RwInt32 FramesPerSecond = 0;

static RwRGBA ForegroundColor = {200, 200, 200, 255};
static RwRGBA BackgroundColor = { 64,  64,  64,   0};

RtCharset *Charset = (RwRaster *)NULL;

RwReal NormalsScaleFactor = 1.0f;
RwBool NormalsOn = FALSE;

RwBool SpinOn = FALSE;

RwInt32 RenderMode = RENDERSOLID;
RwInt32 NumTriStripAngles = 1;

// mipmapping stuff

RwInt32 NumMipLevels;
RwReal MipmapKValue = -7.0f;


// ---------------------------------
// more mipmapping stuff
// ---------------------------------
RwTexture* textureDictionaryCallback( RwTexture* texture, void *pData );	// function prototype

RwTexture* textureDictionaryCallback( RwTexture* texture, void *pData )
{

	RwTextureFilterMode mode = RwTextureGetFilterMode( texture );
	
//	rwFILTERNEAREST - Point sampled. 
//	rwFILTERLINEAR - Bilinear interpolation. 
//	rwFILTERMIPNEAREST - Point sampled per pixel mipmap. 
//	rwFILTERMIPLINEAR - Bilinear interpolation per pixel mipmap. 
//	rwFILTERLINEARMIPNEAREST - Mipmap interpolated, point sampled. 
//	rwFILTERLINEARMIPLINEAR - Trilinear interpolation

	RwTextureSetFilterMode( texture, rwFILTERLINEARMIPLINEAR );
    RpSkyTextureSetMipmapK( texture, (RwReal)(-7.0f));

	return texture;
}

void setFilterAndK()
{
	RwTexDictionary * currentTextDict;

	currentTextDict = RwTexDictionaryGetCurrent(); 

	RwTexDictionaryForAllTextures( currentTextDict, textureDictionaryCallback, NULL );
}

RpMaterial * MaterialGetNumMipLevels(RpMaterial *material, void *data)
{
    RwTexture *texture;

    texture = RpMaterialGetTexture(material);

    *(RwInt32 *)data = RwRasterGetNumLevels(RwTextureGetRaster(texture));

    return NULL;
}

RpAtomic * AtomicGetNumMipLevels(RpAtomic *atomic, void *data)
{
    RpGeometryForAllMaterials(RpAtomicGetGeometry(atomic),
        MaterialGetNumMipLevels, data);

    return NULL;
}

RwInt32 TextureGetNumMipLevels(RpClump *clump)
{
    RwInt32 numLevels = 0;

    RpClumpForAllAtomics(clump, AtomicGetNumMipLevels, &numLevels);

    return numLevels;
}

RpMaterial * MaterialSetMipmapK(RpMaterial *material, void *data)
{
    RpSkyTextureSetMipmapK(RpMaterialGetTexture(material), *(RwReal *)data);

    return material;
}

RpAtomic * AtomicSetMipmapK(RpAtomic *atomic, void *data)
{
    RpGeometryForAllMaterials(RpAtomicGetGeometry(atomic), MaterialSetMipmapK, data);

    return atomic;
}

/*
 *****************************************************************************
 */
static void
ParseColor(RwChar * buffer)
{
    RwInt32 r, g, b;
    RwChar dummy[8];

    rwsscanf(buffer, RWSTRING("%s %d %d %d"), dummy, &r, &g, &b);

    switch( buffer[2] )
    {
        case '1':
        {
            TopColor.red = (RwUInt8)r;
            TopColor.green = (RwUInt8)g;
            TopColor.blue = (RwUInt8)b;

            break;
        }

        case '2':
        {
            BottomColor.red = (RwUInt8)r;
            BottomColor.green = (RwUInt8)g;
            BottomColor.blue = (RwUInt8)b;

            break;
        }

        default:
        {
            BottomColor.red = TopColor.red = (RwUInt8)r;
            BottomColor.green = TopColor.green = (RwUInt8)g;
            BottomColor.blue = TopColor.blue = (RwUInt8)b;

            break;
        }
    }

    return;
}


/*
 *****************************************************************************
 */
static void
ProcessSetupFile(RwChar *filename)
{
    RwFileFunctions *fileFuncs;

    fileFuncs = RwOsGetFileInterface();
    if( fileFuncs )
    {
        void *fp;
        RwChar *path;
        RwChar buffer[256];

        path = RsPathnameCreate(filename);
        fp = fileFuncs->rwfopen(path, RWSTRING("rt"));
        RsPathnameDestroy(path);

        if( fp )
        {
            while( fileFuncs->rwfgets(buffer, 256, fp) )
            {
                /*
                 * Ignore commented lines...
                 */
                if( buffer[0] != '#' )
                {
                    RwChar *src, *dst;

                    src = dst = buffer;
                    while( *src != '\0' )
                    {
                        /*
                         * Is character displayable?
                         */
                        if( isprint(*src) )
                        {
                            *dst++ = *src;
                        }
                        src++;
                    }

                    /*
                     * File names are zero terminated....
                     */
                    *dst = '\0';

                    if( buffer[0] == '@' )
                    {
                        /*
                         * it's a color...
                         */
                        ParseColor(buffer);
                    }
                    else
                    {
                        /*
                         * it's a file...
                         */
                        HandleFileLoad(buffer);
                    }
                }
            }

            fileFuncs->rwfclose(fp);
        }
    }

    return;
}


/*
 *****************************************************************************
 */
static RwBool
Initialize (void)
{
    if( RsInitialize() )
    {
        if( !RsGlobal.maximumWidth )
        {
            RsGlobal.maximumWidth = DEFAULT_SCREEN_WIDTH;
        }

        if( !RsGlobal.maximumHeight )
        {
            RsGlobal.maximumHeight = DEFAULT_SCREEN_HEIGHT;
        }

        RsGlobal.appName = RWSTRING("RW3 Clump Viewer");

        RsGlobal.maxFPS = 120;

        return TRUE;
    }

    return FALSE;
}


/*
 *****************************************************************************
 */
static RwBool
RenderModeCB(RwBool justCheck)
{
    if( justCheck )
    {
        if( ClumpHasSkinAnimation )
        {
            RenderMode = RENDERSOLID;

            return FALSE;
        }

        return TRUE;
    }

    if( ClumpHasHAnimAnimation )
    {
        switch( RenderMode )
        {
            case RENDERWIRE:
            {
                RenderMode = RENDERSKEL;

                break;
            }

            case RENDERWIRESKEL:
            case RENDERWIRESOLID:
            {
                RenderMode = RENDERSOLIDSKEL;

                break;

            }

            case RENDERALL:
            {
                RenderMode = RENDERSOLID;

                break;
            }
        }
    }

    if( ClumpStats.maxFramePerAtomic <= 1 )
    {
        switch( RenderMode )
        {
            case RENDERSKEL:
            case RENDERWIRESKEL:
            {
                RenderMode = RENDERWIRESOLID;

                break;
            }

            case RENDERSOLIDSKEL:
            case RENDERALL:
            {
                RenderMode = RENDERSOLID;

                break;
            }
        }
    }

    return TRUE;
}


static RwBool 
FaceCullCallback(RwBool justCheck)
{
    if( justCheck )
    {
        return FaceCullIndex != -1;
    }

    switch( FaceCullIndex )
    {
        case 0:
        {
            FaceCullMode = rwCULLMODECULLNONE;

            break;
        }

        case 1:
        {
            FaceCullMode = rwCULLMODECULLBACK;

            break;
        }

        case 2:
        {
            FaceCullMode = rwCULLMODECULLFRONT;

            break;
        }
    }

    NewFaceCullMode = TRUE;

    return TRUE;
}


/*
 *****************************************************************************
 */
static RwBool
AmbientLightOnCB(RwBool justCheck)
{
    if( justCheck )
    {
        return TRUE;
    }

    if( AmbientLightOn )
    {
        RpWorldAddLight(World, AmbientLight);
    }
    else
    {
        RpWorldRemoveLight(World, AmbientLight);
    }

    return TRUE;
}


/*
 *****************************************************************************
 */
static RwBool
MainLightOnCB(RwBool justCheck)
{
    if( justCheck )
    {
        return TRUE;
    }

    if( MainLightOn )
    {
        RpWorldAddLight(World, MainLight);
    }
    else
    {
        RpWorldRemoveLight(World, MainLight);
    }

    return TRUE;
}


/*
 *****************************************************************************
 */
static RwBool
NormalsOnCB(RwBool justCheck)
{
    if( justCheck )
    {
        return ClumpLoaded && (ClumpHasSkinAnimation || ClumpHasHAnimAnimation);
    }

    return TRUE;
}


/*
 *****************************************************************************
 */
static RwBool
ResetClumpCB(RwBool justCheck)
{
    if( justCheck )
    {
        return ClumpLoaded;
    }

    ClumpReset(Clump);

    ClumpControlReset();

    SpinOn = FALSE;

    return TRUE;
}


static RwBool
SpinOnCB(RwBool justCheck)
{
    if( justCheck )
    {
        return ClumpLoaded;
    }
    
    return TRUE;
}

static RwBool 
DumpClumpCB(RwBool justCheck)
{
    if( justCheck )
    {
        return ClumpLoaded;
    }

    SaveDFF();

    return TRUE;
}


static RwBool 
DumpTexDictCB(RwBool justCheck)
{
    if( justCheck )
    {
        return ClumpLoaded && ClumpHasTextures;
    }

    SaveTextureDictionary();

    return TRUE;
}


/*
 *****************************************************************************
 */
static RwBool
InitializeMenu(void)
{
    static RwChar ambientLightOnLabel[] = RWSTRING("Ambient light");
    static RwChar ambientLightLabel[]   = RWSTRING("Ambient intensity");
    static RwChar mainLightOnLabel[]    = RWSTRING("Main light ");
    static RwChar mainLightLabel[]      = RWSTRING("Main light intensity");

    static RwChar renderModeLabel[] = RWSTRING("Render mode_R");
    static const RwChar *renderModeEnumStrings[NUMRENDERMODES] =
    {
        RWSTRING("Solid"),
        RWSTRING("Wireframe"),
        RWSTRING("Skeleton"),
        RWSTRING("Wire & skeleton"),
        RWSTRING("Wire & solid"),
        RWSTRING("Solid & skeleton"),
        RWSTRING("TriStrip lengths"),
        RWSTRING("TriStrips"),
        RWSTRING("Meshes"),
        RWSTRING("All")
    };

    static RwChar normalsLabel[]            = RWSTRING("Normals_N");
    static RwChar normalsScaleFactorLabel[] = RWSTRING("Normals length");
    
    static RwChar faceCullLabel[] = RWSTRING("Face culling");
    static const RwChar *faceCullStrings[] = 
    {
        RWSTRING("none"),
        RWSTRING("back"),
        RWSTRING("front")
    };
    static RwChar triStripAnglesLabel[] = RWSTRING("TriStrip length");

    static RwChar fieldOfViewLabel[] = RWSTRING("Field of view");
    static RwChar farClipLabel[]     = RWSTRING("Far clip plane");
    static RwChar nearClipLabel[]    = RWSTRING("Near clip plane");

    static RwChar resetClumpLabel[]  = RWSTRING("Reset clump_C");
    static RwChar spinOnLabel[]      = RWSTRING("Spin clump_S");
    static RwChar dumpClumpLabel[]   = RWSTRING("Dump clump");
    static RwChar dumpTexDictLabel[] = RWSTRING("Dump tex dict");

    static RwChar onScreenInfoLabel[] = RWSTRING("On screen info_O");
    static RwChar fpsLabel[] = RWSTRING("FPS_F");

    if( MenuOpen(TRUE, &ForegroundColor, &BackgroundColor) )
    {
        /*
         * Lights control...
         */
        MenuAddEntryBool(ambientLightOnLabel, &AmbientLightOn, AmbientLightOnCB);
        
        MenuAddEntryReal(ambientLightLabel, &AmbientIntensity, 
                         (MenuTriggerCallBack)NULL, 0.0f, 1.0f, 0.1f);

        MenuAddEntryBool(mainLightOnLabel, &MainLightOn, MainLightOnCB);
        
        MenuAddEntryReal(mainLightLabel, &MainIntensity, 
                         (MenuTriggerCallBack)NULL, 0.0f, 1.0f, 0.1f);

        MenuAddSeparator();

        /*
         * Render modes...
         */
        MenuAddEntryInt(renderModeLabel, &RenderMode, 
            RenderModeCB, 0, NUMRENDERMODES - 1, 1, renderModeEnumStrings);
        
        MenuAddEntryBool(normalsLabel, &NormalsOn, NormalsOnCB);
        
        MenuAddEntryReal(normalsScaleFactorLabel,
            &NormalsScaleFactor, NormalsOnCB, 0.1f, 10.0f, 0.1f);

        MenuAddEntryInt(faceCullLabel, &FaceCullIndex, 
            FaceCullCallback, 0, 2, 1, faceCullStrings);

        MenuAddEntryInt(triStripAnglesLabel, &NumTriStripAngles,
            NULL, 1, 100, 1, NULL);

        MenuAddSeparator();

        /*
         * Camera frustum control...
         */
        MenuAddEntryReal(fieldOfViewLabel, &FieldOfView, 
                         (MenuTriggerCallBack)NULL, 0.1f, 179.9f, 1.0f);
        
        MenuAddEntryReal(farClipLabel, &FarClip, 
                         (MenuTriggerCallBack)NULL, 
                         NearClip + MINNEARTOFARCLIP, FARMAXCLIP, 0.1f);

        MenuAddEntryReal(nearClipLabel, &NearClip, 
                         (MenuTriggerCallBack)NULL, 
                         NEARMINCLIP, FarClip - MINNEARTOFARCLIP, 1.0f);

        MenuAddSeparator();

        /*
         * Clump control...
         */
        MenuAddEntryTrigger(resetClumpLabel, ResetClumpCB);

        MenuAddEntryBool(spinOnLabel, &SpinOn, SpinOnCB);

        MenuAddEntryTrigger(dumpClumpLabel, DumpClumpCB);

        MenuAddEntryTrigger(dumpTexDictLabel, DumpTexDictCB);

        MenuAddSeparator();

        MenuAddEntryBool(onScreenInfoLabel, &OnScreenInfoOn, 
                         (MenuTriggerCallBack)NULL);
        MenuAddEntryBool(fpsLabel, &FPSOn, 
                         (MenuTriggerCallBack)NULL);

        MenuSetStatus(MENUOFF);

        return TRUE;
    }

    return FALSE;
}


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

// ------------------------------------------------------------------------
// ------------------------------------------------------------------------
// ------------------------------------------------------------------------
// Texture Read Overrides
// ------------------------------------------------------------------------
// ------------------------------------------------------------------------
// ------------------------------------------------------------------------

#define RWSTRNSTORE(_dst, _src)                               \
MACRO_START                                                   \
{                                                             \
    const size_t n = sizeof((_dst))/sizeof((_dst)[0]);        \
                                                              \
    rwstrncpy((_dst), (_src), n);                               \
                                                              \
    if ( n <= rwstrlen((_src)))                               \
    {                                                         \
        const size_t trunc = n-1;                             \
                                                              \
        (_dst)[trunc] = ((RwChar)0);                          \
    }                                                         \
}                                                             \
MACRO_STOP

//

static RwBool
PalettizeImage(RwImage **image, RwInt32 depth)
{
    RwRGBA      palette[256];
    RwImage     *palImage;
    rwPalQuant  palQuant;

//    RWFUNCTION(RWSTRING("PalettizeImage"));
//    RWASSERT(image);
//    RWASSERT((depth == 4) | (depth == 8));

    if (!rwPalQuantInit(&palQuant))
    {
        return(FALSE);
    }

    /* add all pixels from the image */
    rwPalQuantAddImage(&palQuant, *image, (RwReal)(1));
    rwPalQuantResolvePalette(palette, (1UL << depth), &palQuant);

    /* Create the new image */
    palImage = RwImageCreate(RwImageGetWidth(*image),
                             RwImageGetHeight(*image),
                             depth);
    if (palImage)
    {
        /* Allocate the pixels & palette */
        RwImageAllocatePixels(palImage);

        /* match images */
        rwPalQuantMatchImage(RwImageGetPixels(palImage),
                             RwImageGetStride(palImage),
                             RwImageGetDepth(palImage),
                             FALSE,
                             &palQuant,
                             *image);

        /* Copy the palette */
        memcpy(RwImageGetPalette(palImage), palette,
               sizeof(RwRGBA) * (1UL << depth));

        /* Destroy the old image */
        RwImageDestroy(*image);

        /* Set the new one */
        *image = palImage;
    }
    else
    {
        return(FALSE);
    }

    rwPalQuantTerm(&palQuant);

    return(TRUE);
}

//

static RwBool
PalettizeMipmaps(RwRGBA *MipPalette, RwImage *sourceImage,
                 RwImage **mipArray, RwInt32 totalMipLevels,
                 RwInt32 logpalsize)
{
    RwInt32     mipLevel, i;
    rwPalQuant  palQuant;

//    RWFUNCTION(RWSTRING("PalettizeMipmaps"));
//    RWASSERT(mipArray);
//    RWASSERT(totalMipLevels > 0);
//    RWASSERT((logpalsize == 4) | (logpalsize == 8));

    /* first quick check they don't all have the same palette */
    if (RwImageGetPalette(mipArray[0]))
    {
        mipLevel = 1;
        while (mipLevel < totalMipLevels)
        {
            RwUInt32    *pal1;
            RwUInt32    *pal2;

            pal1 = ((RwUInt32 *)RwImageGetPalette(mipArray[0]));
            pal2 = ((RwUInt32 *)RwImageGetPalette(mipArray[mipLevel]));

            /* if any don't have palettes we need to match */
            if (!pal1 || !pal2)
            {
                mipLevel = 64;
                break;
            }
            for (i = 0; i < 1 << logpalsize; i++)
            {
                /* or the palettes are different we need to match */
                if (pal1[i] != pal2[i])
                {
                    mipLevel = 64;
                    break;
                }
            }

            mipLevel++;
        }

        /* they actually have identical palettes - no matching needed */
        if (mipLevel == totalMipLevels)
        {
            memcpy(MipPalette, RwImageGetPalette(mipArray[0]),
                   sizeof(RwRGBA) * (1UL << RwImageGetDepth(mipArray[0])));
            return(TRUE);
        }
    }

    if (!rwPalQuantInit(&palQuant))
    {
        return(FALSE);
    }

    /* add all pixels from all mips */
    mipLevel = 0;
    while (mipLevel < totalMipLevels)
    {
        rwPalQuantAddImage(
            &palQuant, mipArray[mipLevel], (RwReal)(1 << (mipLevel << 1)));
        mipLevel++;
    }
    rwPalQuantResolvePalette(MipPalette, 1 << logpalsize, &palQuant);

    /* match images */
    mipLevel = 0;
    while (mipLevel < totalMipLevels)
    {
        RwImage            *dcimg = mipArray[mipLevel];
        RwImage            *palImage;

        palImage = RwImageCreate(RwImageGetWidth(dcimg),
                                 RwImageGetHeight(dcimg), logpalsize);
        if (palImage)
        {
            RwImageAllocatePixels(palImage);
            rwPalQuantMatchImage(RwImageGetPixels(palImage),
                                 RwImageGetStride(palImage),
                                 RwImageGetDepth(palImage),
                                 FALSE,
                                 &palQuant,
                                 dcimg);
            RwImageSetPalette(palImage, MipPalette);
            mipArray[mipLevel] = palImage;
            if (dcimg != sourceImage)
            {
                RwImageDestroy(dcimg);
            }
        }
        else
        {
            return(FALSE);
        }

        mipLevel++;
    }
    rwPalQuantTerm(&palQuant);

    return(TRUE);
}

//

static RwImage     *
TextureImageReadAndSize(const RwChar * name,
                        const RwChar * maskName,
                        RwInt32 format,
                        RwInt32 * rasterWidth,
                        RwInt32 * rasterHeight,
                        RwInt32 * rasterDepth, RwInt32 * rasterFlags)
{
    RwChar              mipName[256];
    RwChar              mipmaskName[256];
    const RwChar       *filenameExt;
    const RwChar       *masknameExt;
    RwImage            *image;

//    RWFUNCTION(RWSTRING("TextureImageReadAndSize"));
//    RWASSERT(name);

    RWSTRNSTORE(mipName, name);

    filenameExt = RwImageFindFileType(name);
    if (filenameExt)
    {
        rwstrcat(mipName, filenameExt);
    }

    mipmaskName[0] = ((RwChar) 0);

    if (maskName && maskName[0])
    {
        RWSTRNSTORE(mipmaskName, maskName);
        masknameExt = RwImageFindFileType(maskName);
        if (masknameExt)
        {
            rwstrcat(mipmaskName, masknameExt);
        }
    }

    image = RwImageReadMaskedImage(mipName, mipmaskName);
    if (!image)
    {
        return((RwImage *)NULL);
    }

    /* do we need to fill in raster format info? */
    if ((*rasterWidth == 0) || (*rasterHeight == 0))
    {
        if (!RwImageFindRasterFormat(image, format,
                                     rasterWidth, rasterHeight,
                                     rasterDepth, rasterFlags))
        {
            RwImageDestroy(image);
//            RWERROR((E_RW_INVIMAGEFORMAT));
            return((RwImage *)NULL);
        }
    }

    /* do we need to resample? */
    if ((RwImageGetWidth(image) != *rasterWidth)
        || (RwImageGetHeight(image) != *rasterHeight))
    {
        RwImage            *resampledImage;
        RwImage            *origImage;
        RwInt32             originalDepth;

        /* and if we do, we'll need to expand up to 32bpp first */
        originalDepth = RwImageGetDepth(image);
        if (originalDepth != 32)
        {
            /* we'll reuse this later */
            origImage = image;

            image = RwImageCreate(RwImageGetWidth(origImage),
                                  RwImageGetHeight(origImage), 32);
            if (!image)
            {
                RwImageDestroy(origImage);
                return((RwImage *)NULL);
            }
            if (!RwImageAllocatePixels(image))
            {
                RwImageDestroy(image);
                RwImageDestroy(origImage);
                return((RwImage *)NULL);
            }
            RwImageCopy(image, origImage);
			RwImageDestroy(origImage);
        }

        resampledImage = RwImageCreate(*rasterWidth, *rasterHeight, 32);
        if (!resampledImage)
        {
            RwImageDestroy(image);
            return((RwImage *)NULL);
        }
        if (!RwImageAllocatePixels(resampledImage))
        {
            RwImageDestroy(resampledImage);
            RwImageDestroy(image);
            return((RwImage *)NULL);
        }

        RwImageResample(resampledImage, image);
        RwImageDestroy(image);
        image = resampledImage;

        /* If original image depth was 4 or 8, RE-PALLETIZE */
        if (4 == originalDepth)
        {
            PalettizeImage(&image, originalDepth);
        }
        else if (8 == originalDepth)
        {
            PalettizeImage(&image, originalDepth);
        }
    }

    return(image);
}

/* Read just Mip Level #0 */
static RwTexture   *
TextureDefaultNormalRead(const RwChar * name, const RwChar * maskName)
{
    RwTexture          *texture;
    RwImage            *image;
    RwRaster           *raster;
    RwRGBA              MipPalette[256];
    RwChar              mipName[256];
    RwChar              mipmaskName[256];
    RwInt32             rasterWidth;
    RwInt32             rasterHeight;
    RwInt32             rasterDepth;
    RwInt32             rasterFlags;

//    RWFUNCTION(RWSTRING("TextureDefaultNormalRead"));
//    RWASSERT(name);

    /* Construct the filename */
    /* RWASSERT(rwstrlen(name) <
     * rwTEXTUREBASENAMELENGTH + rwTEXTUREMIPMAPNAMECHARS); */

    RWSTRNSTORE(mipName, name);

    /* And the maskname */
    mipmaskName[0] = ((RwChar) 0);
    if (maskName && maskName[0])
    {
        /* RWASSERT(rwstrlen(maskName) <
         * rwTEXTUREBASENAMELENGTH + rwTEXTUREMIPMAPNAMECHARS); */
        RWSTRNSTORE(mipmaskName, maskName);
    }

    RwTextureGenerateMipmapName(mipName, mipmaskName, 0,
                                rwRASTERTYPETEXTURE);

    rasterWidth = 0;
    rasterHeight = 0;
    image =
        TextureImageReadAndSize(mipName, mipmaskName,
                                rwRASTERTYPETEXTURE, &rasterWidth,
                                &rasterHeight, &rasterDepth,
                                &rasterFlags);
    if (!image)
    {
        return((RwTexture *)NULL);
    }

    /* Create a raster */
    raster =
        RwRasterCreate(rasterWidth, rasterHeight, rasterDepth,
                       rasterFlags);
    if (!raster)
    {
        RwImageDestroy(image);
        return((RwTexture *)NULL);
    }

    /* create a palette */
    if (RwRasterGetFormat(raster) &
        (rwRASTERFORMATPAL4 | rwRASTERFORMATPAL8))
    {
        if (RwRasterGetFormat(raster) & rwRASTERFORMATPAL4)
        {
            PalettizeMipmaps((RwRGBA *)MipPalette, (RwImage *)NULL, &image, 1, 4);
        }
        else
        {
            PalettizeMipmaps((RwRGBA *)MipPalette, (RwImage *)NULL, &image, 1, 8);
        }
        RwImageSetPalette(image, MipPalette);
    }

    RwImageGammaCorrect(image);

    /* Convert the image into the raster */
    if (!RwRasterSetFromImage(raster, image))
    {
        RwRasterDestroy(raster);
        RwImageDestroy(image);
        return((RwTexture *)NULL);
    }

    RwImageDestroy(image);

    /* Create a texture */
    texture = RwTextureCreate(raster);
    if (!texture)
    {
        RwRasterDestroy(raster);
        return((RwTexture *)NULL);
    }

    /* The name [and maskname] */
    RwTextureSetName(texture, name);
    if (maskName)
    {
        RwTextureSetMaskName(texture, maskName);
    }
    else
    {
        RwTextureSetMaskName(texture, RWSTRING(""));
    }

//    RWASSERT(texture);
//    RWASSERT(0 < (texture->refCount));

    return(texture);
}

//

static RwTexture   *
TextureDefaultMipmapRead(const RwChar * name, const RwChar * maskName)
{
    RwTexture          *texture;
    RwImage            *mipArray[16];
    RwRaster           *raster;
    RwRGBA              MipPalette[256];
    RwInt32             format;
    RwChar              mipName[256];
    RwChar              mipmaskName[256];
    RwInt32             rasterWidth;
    RwInt32             rasterHeight;
    RwInt32             rasterDepth;
    RwInt32             rasterFlags;

//    RWFUNCTION(RWSTRING("TextureDefaultMipmapRead"));
//    RWASSERT(name);

    /* Construct the filename */
    /* RWASSERT(rwstrlen(name) <
     * rwTEXTUREBASENAMELENGTH + rwTEXTUREMIPMAPNAMECHARS); */

    RWSTRNSTORE(mipName, name);

    /* And the maskname */
    mipmaskName[0] = ((RwChar) 0);
    if (maskName && maskName[0])
    {
        /* RWASSERT(rwstrlen(maskName) <
         * rwTEXTUREBASENAMELENGTH + rwTEXTUREMIPMAPNAMECHARS); */
        RWSTRNSTORE(mipmaskName, maskName);
    }

    format = rwRASTERTYPETEXTURE;

//    if (RWTEXTUREGLOBAL(haveTexMipmaps))
    if (1)	// see haveTexMipmaps above
    {
        format |= (RwInt32) rwRASTERFORMATMIPMAP;
//        if (RWTEXTUREGLOBAL(generateTexMipmaps))
        if (1)
        {
            format |= (RwInt32) rwRASTERFORMATAUTOMIPMAP;
        }
    }

    RwTextureGenerateMipmapName(mipName, mipmaskName, 0, format);

    rasterWidth = 0;
    rasterHeight = 0;
    mipArray[0] = TextureImageReadAndSize(mipName, mipmaskName, format,
                                          &rasterWidth, &rasterHeight,
                                          &rasterDepth, &rasterFlags);

	// override the rasterflags, these should have been set in maya
	// but what a pain.  The next line of code is all the reason for
	// all this texture read override code.

	rasterFlags |= rwRASTERFORMATMIPMAP | rwRASTERFORMATAUTOMIPMAP;

	//

    /* have we a valid mip#0? */
    if (!mipArray[0])
    {
        return((RwTexture *)NULL);
    }

    /* Create a raster */
    raster = RwRasterCreate(rasterWidth, rasterHeight, rasterDepth, rasterFlags);
    if (!raster)
    {
        RwImageDestroy(mipArray[0]);
        return((RwTexture *)NULL);
    }

    if (rasterFlags & rwRASTERFORMATMIPMAP)
    {
        /* and now the mipmaps if device can support them */
        if (rasterFlags & rwRASTERFORMATAUTOMIPMAP)
        {
            /* Convert the image into the raster [auto mipmap and gamma] */
            if (!RwRasterSetFromImage(raster, mipArray[0]))
            {
                RwRasterDestroy(raster);
                RwImageDestroy(mipArray[0]);
                return((RwTexture *)NULL);
            }
            RwImageDestroy(mipArray[0]);
        }
        else
        {
            /* if they weren't automatically generated; we'll read them */
            RwInt32             mipLevel, totalMipLevels;

            /* generate mip array */
            totalMipLevels = RwRasterGetNumLevels(raster);
            mipLevel = 1;
            while (mipLevel < totalMipLevels)
            {
                RWSTRNSTORE(mipName, name);

                /* And the maskname */
                mipmaskName[0] = ((RwChar) 0);
                if (maskName && maskName[0])
                {
                    RWSTRNSTORE(mipmaskName, maskName);
                }

                RwTextureGenerateMipmapName(mipName, mipmaskName,
                                            (RwUInt8) mipLevel, format);

                RwRasterLock(raster, (RwUInt8) mipLevel,
                             (RwRasterLockMode) ( rwRASTERLOCKWRITE | 
                                                  rwRASTERLOCKNOFETCH) );
                rasterWidth = RwRasterGetWidth(raster);
                rasterHeight = RwRasterGetHeight(raster);
                rasterDepth = RwRasterGetDepth(raster);
                rasterFlags = RwRasterGetFormat(raster) | raster->cType;
                RwRasterUnlock(raster);

                mipArray[mipLevel] =
                    TextureImageReadAndSize(mipName, mipmaskName,
                                            format, &rasterWidth,
                                            &rasterHeight, &rasterDepth,
                                            &rasterFlags);
                if (!mipArray[mipLevel])
                {
                    while (--mipLevel >= 0)
                    {
                        RwImageDestroy(mipArray[mipLevel]);
                    }
                    RwRasterDestroy(raster);
                    return((RwTexture *)NULL);
                }

                mipLevel++;
            }

            /* create a single palette */
            if (RwRasterGetFormat(raster) &
                (rwRASTERFORMATPAL4 | rwRASTERFORMATPAL8))
            {
                if (RwRasterGetFormat(raster) & rwRASTERFORMATPAL4)
                {
                    PalettizeMipmaps((RwRGBA *)MipPalette, (RwImage *)NULL, 
                                     mipArray, totalMipLevels, 4);
                }
                else
                {
                    PalettizeMipmaps((RwRGBA *)MipPalette, (RwImage *)NULL,
                                     mipArray, totalMipLevels, 8);
                }

                /* once only on mip0 palette */
                RwImageGammaCorrect(mipArray[0]);
            }
            else
            {
                mipLevel = 0;
                while (mipLevel < totalMipLevels)
                {
                    RwImageGammaCorrect(mipArray[mipLevel]);
                    mipLevel++;
                }
            }

            /* assign to each mipmap */
            mipLevel = 0;
            while (mipLevel < totalMipLevels)
            {
                if (RwRasterLock (raster, 
                                  (RwUInt8) mipLevel,
                                  (RwRasterLockMode) ( rwRASTERLOCKWRITE | 
                                                       rwRASTERLOCKNOFETCH)) )
                {
                    if (!RwRasterSetFromImage
                        (raster, mipArray[mipLevel]))
                    {
                        while (mipLevel < totalMipLevels)
                        {
                            RwImageDestroy(mipArray[mipLevel]);
                            mipLevel++;
                        }
                        RwRasterDestroy(raster);
                        return((RwTexture *)NULL);
                    }
                    RwRasterUnlock(raster);
                }

                RwImageDestroy(mipArray[mipLevel]);
                mipLevel++;
            }
        }
    }
    else
    {
        RwImageGammaCorrect(mipArray[0]);

        /* Convert the image into the raster */
        if (!RwRasterSetFromImage(raster, mipArray[0]))
        {
            RwRasterDestroy(raster);
            RwImageDestroy(mipArray[0]);
            return((RwTexture *)NULL);
        }
        RwImageDestroy(mipArray[0]);
    }

    /* Create a texture */
    texture = RwTextureCreate(raster);
    if (!texture)
    {
        RwRasterDestroy(raster);
        return((RwTexture *)NULL);
    }

    /* The name [and maskname] */
    RwTextureSetName(texture, name);
    if (maskName)
    {
        RwTextureSetMaskName(texture, maskName);
    }
    else
    {
        RwTextureSetMaskName(texture, RWSTRING(""));
    }

//    RWASSERT(texture);
//    RWASSERT(0 < (texture->refCount));

    return(texture);
}

//RwTexture *(*RwTextureCallBackRead)(const RwChar *name, const RwChar *maskName)

// override texture read
// based on TextureDefaultRead in batextur.c

RwTexture * RwTextureReadMonsters(const RwChar *name, const RwChar *maskName)
{

    RwTexture * texture = (RwTexture *)NULL;
//    RwTexture * texture1 = (RwTexture *)NULL;
//    RwTexture * texture2 = (RwTexture *)NULL;

//    RWFUNCTION(RWSTRING("TextureDefaultRead"));

//		haveTexMipmaps is a RW global set in RwTextureSetMipmapping
//		we're calling this function so TextureDefaultMipmapRead should be
//		called.

//    if (RWTEXTUREGLOBAL(haveTexMipmaps))
//    {
        texture = (TextureDefaultMipmapRead(name, maskName));

		// set the texture filter and K values here

//	    texture1 = RwTextureSetFilterMode(texture, rwFILTERLINEARMIPLINEAR );
//		
//	    texture2 = RpSkyTextureSetMipmapK(texture, (RwReal)(-7.0f));

//    }
//    else
//    {
//        texture = (TextureDefaultNormalRead(name, maskName));
//    }

    return(texture);
}

static RwBool
Initialize3D(void *param)
{
    if( !RsRwInitialize(param) )
    {
        RsErrorMessage(RWSTRING("Error initializing RenderWare."));

        return FALSE;
    }

    Charset = RtCharsetCreate(&ForegroundColor, &BackgroundColor);
    if( Charset == NULL )
    {
        RsErrorMessage(RWSTRING("Cannot create raster charset."));

        return FALSE;
    }

    if( !SceneInit() )
    {
        RsErrorMessage(RWSTRING("Error initializing the scene."));

        return FALSE;
    }

    if( !InitializeMenu() )
    {
        RsErrorMessage(RWSTRING("Error initializing menu."));

        return FALSE;
    }

    if( !ClumpViewInit() )
    {
        RsErrorMessage(RWSTRING("Error initializing clumpview."));

        return FALSE;
    }

	// initialize mipmapping parameters before opening clump
	// this is after the engine has been started

    RpSkyTextureSetDefaultMipmapK(MipmapKValue);
	RwTextureSetMipmapping( TRUE );
	RwTextureSetAutoMipmapping( TRUE );
//	RwTextureSetReadCallBack(RwTextureReadMonsters);
    /*
     * Load Setup File
     */
    {
        RwChar iniFile[] = RWSTRING("./clmpview.ini");
        ProcessSetupFile(iniFile);
    }

#ifdef RWMETRICS
    RsMetricsOpen (Camera);
#endif

    RwResourcesSetArenaSize(1024 * 1024 * 2);

    return TRUE;
}


/*
 *****************************************************************************
 */
static void
Terminate3D (void)
{

#ifdef RWMETRICS
    RsMetricsClose();
#endif

    MenuClose();

    SceneDestroy();

    if( Charset )
    {
        RwRasterDestroy(Charset);
    }

    RsRwTerminate();

    return;
}


/*
 *****************************************************************************
 */
static RwBool
AttachPlugins(void)
{
    if( !RpWorldPluginAttach() )
    {
        return FALSE;
    }

#ifdef RWLOGO
    if( !RpLogoPluginAttach() )
    {
        return FALSE;
    }
#endif

    if( !RpMorphPluginAttach() )
    {
        RsErrorMessage(RWSTRING("Morph plugin attach failed."));

        return FALSE;
    }

    if( !RpSkinPluginAttach() )
    {
        RsErrorMessage(RWSTRING("Skin plugin attach failed."));

        return FALSE;
    }

    if( !RpDMorphPluginAttach() )
    {
        RsErrorMessage(RWSTRING("DMorph plugin attach failed."));

        return FALSE;
    }

    if( !RpLODAtomicPluginAttach() )
    {
        RsErrorMessage(RWSTRING("LOD Atomic plugin attach failed."));

        return FALSE;
    }

    if( !RpCollisionPluginAttach() )
    {
        RsErrorMessage(RWSTRING("Collision plugin attach failed."));

        return FALSE;
    }

    if( !RpAnimPluginAttach() )
    {
        RsErrorMessage(RWSTRING("Frame animation plugin attach failed."));

        return FALSE;
    }

    if( !RpBonePluginAttach() )
    {
        RsErrorMessage(RWSTRING("Bone plugin attach failed."));

        return FALSE;
    }

    if( !RpHAnimPluginAttach() )
    {
        RsErrorMessage(RWSTRING("H animation plugin attach failed."));

        return FALSE;
    }

    if( !RpRandomPluginAttach() )
    {
        RsErrorMessage(RWSTRING("Random plugin attach failed."));

        return FALSE;
    }

#if !defined(_XBOX)
    if( !RpMatFXPluginAttach() )
    {
        RsErrorMessage(RWSTRING("Material effects plugin attach failed."));

        return FALSE;
    }
#endif

    return TRUE;
}


/*
 *****************************************************************************
 */
static void
DisplayOnScreenInfo(RwCamera * camera)
{
    RtCharsetDesc charsetDesc;
    RwInt32 crw, crh, left, bottom;
    RwV3d pos;
    RwChar caption[128];

    crw = RwRasterGetWidth(RwCameraGetRaster(camera));
    crh = RwRasterGetHeight(RwCameraGetRaster(camera));

    RtCharsetGetDesc(Charset, &charsetDesc);

    if( FPSOn )
    {
        rwsprintf(caption, RWSTRING("FPS: %03d"), FramesPerSecond);

        RtCharsetPrint(Charset, caption,
            crw - charsetDesc.width * rwstrlen(caption) - RIGHTMARGIN, TOPMARGIN);
    }

    if( ClumpLoaded && DisplayOnScreenInfo && MenuGetStatus() == MENUOFF )
    {    
        pos = *RwMatrixGetPos(RwFrameGetLTM(RpClumpGetFrame(Clump)));

        rwsprintf(caption,
            RWSTRING("Clump Pos: [%6.1f, %6.1f, %6.1f]"), pos.x, pos.y, pos.z);

        left = crw - charsetDesc.width * rwstrlen(caption) - RIGHTMARGIN;
        bottom = crh - charsetDesc.height - BOTTOMMARGIN;
        RtCharsetPrint(Charset, caption, left, bottom);

        ClumpDisplayOnScreenInfo(RwCameraGetRaster(camera));
    }

    return;
}


/*
 *****************************************************************************
 */
static void 
QueryDefaultFaceCullMode(void)
{
    RwCullMode cullMode;

    if( !RwRenderStateGet(rwRENDERSTATECULLMODE, (void *)&cullMode) )
    {
        FaceCullIndex = -1;
    }

    switch( cullMode )
    {
        case rwCULLMODECULLNONE:
        {
            FaceCullIndex = 0;

            break;
        }

        case rwCULLMODECULLBACK:
        {
            FaceCullIndex = 1;

            break;
        }

        case rwCULLMODECULLFRONT:
        {
            FaceCullIndex = 2;

            break;
        }

        default:
        {
            FaceCullIndex = -1;

            break;
        }
    }

    return;
}


/*
 *****************************************************************************
 */
static void
Render3D(void)
{
    static RwBool firstCall = TRUE;

    RwCameraClear(Camera, &BackgroundColor,
                  rwCAMERACLEARZ | rwCAMERACLEARIMAGE);

    if( RwCameraBeginUpdate(Camera) )
    {
        if( firstCall )
        {
            QueryDefaultFaceCullMode();

            firstCall = FALSE;
        }

        if( NewFaceCullMode )
        {
            RwRenderStateSet(rwRENDERSTATECULLMODE, (void *)FaceCullMode);

            NewFaceCullMode = FALSE;
        }

        if( MenuGetStatus() != HELPMODE )
        {
            /*
             * Scene rendering here...
             */
            SceneRender();

            DisplayOnScreenInfo(Camera);
        }

        MenuRender(Camera, (RwRaster *)NULL);

#ifdef RWMETRICS
        RsMetricsRender();
#endif

        RwCameraEndUpdate(Camera);
    }

    /*
     * Display camera's raster...
     */
    RsCameraShowRaster(Camera);

    FrameCounter++;

    return;
}


/*
 *****************************************************************************
 */
static void
Idle(void)
{
    RwUInt32 thisTime;
    RwReal delta;

    static RwBool firstCall = TRUE;
    static RwUInt32 LastFrameTime;
    static RwUInt32 LastAnimTime;

    if( firstCall )
    {
        /*
         * Initialize the timers variables that control clump animation and
         * estimating the number of rendered frames per second...
         */
        LastAnimTime = LastFrameTime = RsTimer();

        firstCall = FALSE;
    }

    thisTime = RsTimer();

    delta = (thisTime - LastAnimTime) * 0.001f;
    LastAnimTime = thisTime;

    /*
     * Has a second elapsed since we last updated the FPS...
     */
    if( thisTime > (LastFrameTime + 1000) )
    {
        /*
         * Capture the frame counter...
         */
        FramesPerSecond = FrameCounter;

        /*
         * ...and reset...
         */
        FrameCounter = 0;

        LastFrameTime = thisTime;
    }

    SceneCameraUpdate();

    SceneLightsUpdate();

    ClumpDeltaUpdate(delta);

    ClumpControlUpdate(delta);

    Render3D();

    return;
}


/*
 *****************************************************************************
 */
RsEventStatus 
AppEventHandler(RsEvent event, void *param)
{
    switch( event )
    {
        case rsINITIALIZE:
        {
            return Initialize()? rsEVENTPROCESSED : rsEVENTERROR;
        }

        case rsCAMERASIZE:
        {
            CameraSize(Camera, 
                (RwRect *)param, CurrentViewWindow, DEFAULT_ASPECTRATIO);

            return rsEVENTPROCESSED;
        }

        case rsRWINITIALIZE:
        {
            return Initialize3D(param) ? rsEVENTPROCESSED : rsEVENTERROR;
        }

        case rsRWTERMINATE:
        {
            Terminate3D();

            return rsEVENTPROCESSED;
        }

        case rsPLUGINATTACH:
        {
            return AttachPlugins() ? rsEVENTPROCESSED : rsEVENTERROR;
        }

        case rsINPUTDEVICEATTACH:
        {
            AttachInputDevices();

            return rsEVENTPROCESSED;
        }

        case rsFILELOAD:
        {
            return HandleFileLoad(param) ? rsEVENTPROCESSED : rsEVENTERROR;
        }

        case rsIDLE:
        {
            Idle();

            return rsEVENTPROCESSED;
        }

        default:
        {
            return rsEVENTNOTPROCESSED;
        }
    }
}

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