
/****************************************************************************
 *
 * 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.
 *
 */

/****************************************************************************
 *
 * clmpsview.c
 *
 * Copyright (C) 2001 Criterion Technologies.
 *
 * Original author: Alexandre Hadjadj
 * Reviewed by:
 *
 * Purpose: Clump view basics functions
 *
 ****************************************************************************/

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

#include "rtcharse.h"

#include "skeleton.h"
#include "menu.h"

#include "clmprndr.h"
#include "clmpmorf.h"
#include "clmpdmrf.h"
#include "clmpanim.h"
#include "clmpskin.h"
#include "clmphanm.h"
#include "clmppick.h"
#include "clmpcntl.h"

#include "main.h"
#include "scene.h"

#include "clmpview.h"

#define ANIMSAMPLESPACING (0.1f)
#define MAXANIMSAMPLES (1000)
#define CLUMPTIMEBETWEENSCREENENDS (10.0f)

static RwV3d Zero = {0.0f, 0.0f, 0.0f};

static RwChar ClumpPathFile[256];

static RwChar TexDictionaryName[256];

RpAtomic *LodRoot;
RwInt32 numLods;

RpClump *Clump = (RpClump *)NULL;
RwSphere ClumpSphere;

RwBool ClumpLoaded = FALSE;
RwBool ClumpIsPreLit = FALSE;

ClumpStatistics ClumpStats;
ClumpStatistics LODClumpStats[RPLODATOMICMAXLOD];

RwBool lodChanged = FALSE;

RwFrame *ManipFrame;

RwBool ClumpHasTextures = FALSE;

extern RwInt32 NumMipLevels;
RpMaterial * MaterialGetNumMipLevels(RpMaterial *material, void *data);
RpAtomic * AtomicGetNumMipLevels(RpAtomic *atomic, void *data);
RwInt32 TextureGetNumMipLevels(RpClump *clump);
extern RwReal MipmapKValue;
RpAtomic * AtomicSetMipmapK(RpAtomic *atomic, void *data);

#if (defined(DEBUGSTATION))
extern char debugStationFilePrefix[];
extern char debugStationFilePrefixToo[];
#endif

/*
 *****************************************************************************
 */
static RpAtomic *
AtomicAddBSphereCentre(RpAtomic *atomic, void *data)
{
    RpGeometry *geometry;

    geometry = RpAtomicGetGeometry(atomic);

    if( geometry )
    {
        RwV3d center;
        RwMatrix *LTM;
        RpMorphTarget *morphTarget;
        RwInt32 i, numMorphTargets;
        RwV3d atomicCentre;
        RwSphere *clumpSphere;

        clumpSphere = (RwSphere *)data;

        /*
         * Establish the average centre of this atomic over all morph targets
         */
        atomicCentre = Zero;

        numMorphTargets = RpGeometryGetNumMorphTargets (geometry);

        for( i = 0; i < numMorphTargets; i++ )
        {
            morphTarget = RpGeometryGetMorphTarget(geometry, i);
            center = RpMorphTargetGetBoundingSphere(morphTarget)->center;
            RwV3dAdd(&atomicCentre, &atomicCentre, &center);
        }

        RwV3dScale(&atomicCentre, &atomicCentre, 1.0f / numMorphTargets);

        /*
         * Tranform the average centre of the atomic to world space
         */
        LTM = RwFrameGetLTM(RpAtomicGetFrame(atomic));
        RwV3dTransformPoints(&atomicCentre, &atomicCentre, 1, LTM);

        /*
         * Add the average centre of the atomic up in order to calculate the centre of the clump
         */
        RwV3dAdd(&clumpSphere->center, &clumpSphere->center, &atomicCentre);
    }

    return atomic;
}


/*
 *****************************************************************************
 */
static RpAtomic *
AtomicCompareBSphere(RpAtomic *atomic, void *data)
{
    RpGeometry *geometry;

    geometry = RpAtomicGetGeometry(atomic);

    if( geometry )
    {
        RwSphere *sphere, morphTargetSphere;
        RwV3d tempVec;
        RpMorphTarget *morphTarget;
        RwReal dist;
        RwMatrix *LTM;
        RwInt32 i, numMorphTargets;

        sphere = (RwSphere *)data;

        LTM = RwFrameGetLTM(RpAtomicGetFrame(atomic));

        numMorphTargets = RpGeometryGetNumMorphTargets(geometry);

        for( i = 0; i < numMorphTargets; i++ )
        {
            morphTarget = RpGeometryGetMorphTarget(geometry, i);
            morphTargetSphere = *RpMorphTargetGetBoundingSphere(morphTarget);

            RwV3dTransformPoints(&morphTargetSphere.center,
                &morphTargetSphere.center, 1, LTM);

            RwV3dSub(&tempVec, &morphTargetSphere.center, &sphere->center);

            dist = RwV3dLength(&tempVec) + morphTargetSphere.radius;
            if( dist > sphere->radius )
            {
                sphere->radius = dist;
            }
        }
    }

    return atomic;
}


/*
 *****************************************************************************
 */
static void
ClumpGetBoundingSphere(RpClump *clump, RwSphere *clumpSphere)
{
    RwInt32 numAnimSamples;

    AnimRestart(clump);

    /*
     * First find the mean of all the atomics' bounding sphere centers.
     * All morph targets of all atomics and all frame animations are taken into account.
     * The result is the clump's bounding sphere center...
     */
    clumpSphere->center = Zero;
    numAnimSamples = 0;
    FinishedLastSequence = FALSE;

    do
    {
        RwSphere curClumpSphere;

        /*
         * average over morph targets and atomics
         */
        curClumpSphere.center = Zero;
        RpClumpForAllAtomics(clump, AtomicAddBSphereCentre, &curClumpSphere);

        RwV3dScale(&curClumpSphere.center, 
            &curClumpSphere.center, 1.0f / RpClumpGetNumAtomics (clump));

        /*
         * Sum up the above average in order to calculate the overall
         * average over the frame animation...
         */
        RwV3dAdd(&clumpSphere->center,
            &clumpSphere->center, &curClumpSphere.center);

        numAnimSamples++;

        if( ClumpHasFrameAnimation )
        {
            /*
             * advance the frame animation
             */
            ClumpIncrementFrameAnimationSeq(clump, ANIMSAMPLESPACING);
            RwFrameUpdateObjects(RpClumpGetFrame(clump));
        }
    }
    while( !FinishedLastSequence && 
            numAnimSamples < MAXANIMSAMPLES &&
            ClumpHasFrameAnimation );

    RwV3dScale(&clumpSphere->center, &clumpSphere->center, 1.0f / numAnimSamples);

    AnimRestart(clump);

    /*
     * Now, given the clump's bounding sphere center, determine the radius
     * by finding the greatest distance from the center that encloses all
     * the atomics' bounding spheres.  All morph targets, atomics and animations
     * are taken into account
     */
    clumpSphere->radius = -RwRealMAXVAL;
    numAnimSamples = 0;
    FinishedLastSequence = FALSE;

    do
    {
        RpClumpForAllAtomics(clump, AtomicCompareBSphere, clumpSphere);

        numAnimSamples++;

        if( ClumpHasFrameAnimation )
        {
            /*
             * advance the frame animation
             */
            ClumpIncrementFrameAnimationSeq(clump, ANIMSAMPLESPACING);
            RwFrameUpdateObjects(RpClumpGetFrame (clump));
        }
    }
    while( !FinishedLastSequence && 
            numAnimSamples < MAXANIMSAMPLES &&
            ClumpHasFrameAnimation );

    AnimRestart(clump);

    return;
}


/*
 *****************************************************************************
 */
static RpAtomic *
AtomicTestGeometry(RpAtomic *atomic, void *data)
{
    RpGeometry *geometry;

    geometry = RpAtomicGetGeometry(atomic);

    if( !geometry )
    {
        RsWarningMessage(RWSTRING("Some atomics have no geometry."));

        return (RpAtomic *)NULL;
    }

    if( !(RpGeometryGetFlags (geometry) & rpGEOMETRYNORMALS) )
    {
        RsWarningMessage(RWSTRING("Some atomics have no vertex normals."));
        return (RpAtomic *)NULL;
    }

    return atomic;
}


/*
 *****************************************************************************
 */
static RpAtomic *
AtomicGetStatistics(RpAtomic *atomic, void *data)
{
    RpGeometry *geometry;
    RwFrame *frame;
    RwInt32 frameNum;

    geometry = RpAtomicGetGeometry(atomic);

    if( geometry )
    {
        ClumpStatistics *clumpStats;

        clumpStats = (ClumpStatistics *) data;

        clumpStats->totalAtomics++;

        clumpStats->totalTriangles += RpGeometryGetNumTriangles(geometry);
        clumpStats->totalVertices += RpGeometryGetNumVertices(geometry);

        frame = RwFrameGetRoot(RpAtomicGetFrame(atomic));
        frameNum = RwFrameCount(frame);
        if( frameNum > clumpStats->maxFramePerAtomic )
        {
            clumpStats->maxFramePerAtomic = frameNum;
        }
        
        ClumpIsPreLit = ClumpIsPreLit || 
            ( (RpGeometryGetFlags (geometry) & rpGEOMETRYPRELIT) ? TRUE  : FALSE );
             
    }

    return atomic;
}


/*
 *****************************************************************************
 */
static RpAtomic *
AtomicSetRenderFlags(RpAtomic *atomic, void *data)
{
    RpAtomicSetFlags(atomic, rpATOMICRENDER | rpATOMICCOLLISIONTEST);

    return atomic;
}


/*
 *****************************************************************************
 */
void
ClumpReset(RpClump *clump)
{
    RwFrame *clumpFrame;
    clumpFrame = RpClumpGetFrame(clump);

    RwFrameTranslate(clumpFrame, &Zero, rwCOMBINEREPLACE);

    return;
}


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

// mipmapping stuff

static RpMaterial *
MaterialSetTextureFilterMode(RpMaterial *material, void *data)
{
    RwTextureSetFilterMode(RpMaterialGetTexture(material), *(RwTextureFilterMode *)data);
    
    return material;
}


static RpAtomic *
AtomicSetTextureFilterMode(RpAtomic *atomic, void *data)
{
    RpGeometryForAllMaterials(RpAtomicGetGeometry(atomic), MaterialSetTextureFilterMode, data);

    return atomic;
}

//

RwTextureFilterMode filterFlag;

static void
ClumpInitialize(RpClump *clump)
{
    RwFrame *clumpFrame;
	RwBool autoMip;

    /*
     * Destroy any previously existing clump...
     */
    if( ClumpLoaded )
    {
        RpWorldRemoveClump(World, Clump);
        
        HAnimDestroy();

        SkinDestroy();

        DMorphDestroy();

        RpClumpDestroy(Clump);
    }

    /*
     * Turns the lights Off...
     */
    if( AmbientLightOn )
    {
        AmbientLightOn = FALSE;
        RpWorldRemoveLight(World, AmbientLight);
    }

    if( MainLightOn )
    {
        MainLightOn = FALSE;
        RpWorldRemoveLight(World, MainLight);
    }

    Clump = clump;

    /*
     * Move the clump to the world's origin...
     */
    clumpFrame = RpClumpGetFrame(Clump);
    RwFrameTranslate(clumpFrame, &Zero, rwCOMBINEREPLACE);

    RpWorldAddClump(World, Clump);

    SkinClumpInitialize(Clump, ClumpPathFile);

    HAnimClumpInitialize(Clump, ClumpPathFile);

    AnimClumpInitialize(Clump);

    MorphClumpInitialize(Clump);

    DMorphClumpInitialize(Clump, ClumpPathFile);

    if( !LodRoot )
    {
        RpClumpForAllAtomics(Clump, AtomicSetRenderFlags, NULL);
    }

    /*
     * Warn the user if some or all the atomics comprising the the clump
     * have no geometry or the geometry is without vertex normals...
     */
    RpClumpForAllAtomics (Clump, AtomicTestGeometry, NULL);

    /*
     * Gather some statistics about the new clump...
     */
    ClumpStats.totalAtomics = 0;
    ClumpStats.totalTriangles = 0;
    ClumpStats.totalVertices = 0;
    ClumpStats.maxFramePerAtomic = 0;
    
    ClumpIsPreLit = FALSE;

	// mipmapping stuff here

//xxx	NumMipLevels = TextureGetNumMipLevels(Clump);

	filterFlag = rwFILTERLINEARMIPLINEAR;

	autoMip = RwTextureGetAutoMipmapping();  

//xxx    RpClumpForAllAtomics(clump, AtomicSetTextureFilterMode, (void *)&filterFlag);
//xxx
//xxx    RpClumpForAllAtomics(Clump, AtomicSetMipmapK, (void *)&MipmapKValue);

	//

    RpClumpForAllAtomics (Clump, AtomicGetStatistics, &ClumpStats);

    /*
     * Resize the immediate mode vertex array to accommodate the new clump...
     */
    ResizeIm3DVertexArray();

    /*
     * Turns the lights back on if necessary...
     */
    if( ClumpIsPreLit )   
    {
        AmbientLightOn = FALSE; 
        MainLightOn = FALSE;
    }
    else
    {
        AmbientLightOn = TRUE; 
        RpWorldAddLight(World, AmbientLight);

		// let's turn on the main light as well, ms

        MainLightOn = TRUE;
        RpWorldAddLight(World, MainLight);
    }
    
    AmbientIntensity = 0.15f;
    MainIntensity = 1.0f;

    RenderMode = RENDERSOLID;
    ClumpLoaded = TRUE;
    AtomicSelected = (RpAtomic *)NULL;

    /*
     * Determine the clump's overall bounding sphere
     * (for all morph targets and for all animations)...
     */
    ClumpGetBoundingSphere(Clump, &ClumpSphere);

    NormalsScaleFactor = 0.15f * ClumpSphere.radius;

    return;
}


/*
 *****************************************************************************
 */
static RpClump *
LoadDff(RwChar *path)
{
    RwStream *stream;
    RpClump *clump = (RpClump *)NULL;

    /*
     * Open a stream connected to the disk file...
     */
    stream = RwStreamOpen(rwSTREAMFILENAME, rwSTREAMREAD, path);
    if( stream )
    {
        /*
         * Find a clump chunk in the stream...
         */
        if( !RwStreamFindChunk(stream, rwID_CLUMP, 
                               (RwUInt32 *)NULL, (RwUInt32 *)NULL) )
        {
            RwStreamClose(stream, NULL);
            return (RpClump *)NULL;
        }

        /*
         * Read the clump chunk...
         */
        clump = RpClumpStreamRead(stream);

        RwStreamClose(stream, NULL);
    }

    return clump;
}


/*
 *****************************************************************************
 */
RwInt32 
ClumpChooseLevelOfDetail(RpAtomic * lodRoot)
{
    RwInt32 result;
    RwV3d displacement, *atomicPos, *cameraPos, *cameraAt;
    RwReal dot, max, min;

    result = -1;

    if( lodRoot && Camera )
    {
        /*
         * Calculate how far the atomic is from the viewer.
         */
        atomicPos =
            RwMatrixGetPos(RwFrameGetLTM(RpAtomicGetFrame(lodRoot)));
        cameraPos =
            RwMatrixGetPos(RwFrameGetLTM(RwCameraGetFrame(Camera)));
        cameraAt =
            RwMatrixGetAt(RwFrameGetLTM(RwCameraGetFrame(Camera)));

        RwV3dSub(&displacement, atomicPos, cameraPos);

        dot = RwV3dDotProduct (&displacement, cameraAt);
        min = NearClip + ClumpSphere.radius * 2.0f;
        max = FarClip - ClumpSphere.radius;
        if( dot <= min || max <= min )
        {
            result = 0;
        }
        else if (dot >= max)
        {
            result = numLods;
        }
        else
        {
            result =
                (RwInt32) (sqrt ((dot - min) / (max - min)) *
                           (numLods + 1));
        }

        if (result != RpLODAtomicGetCurrentLOD (lodRoot))
        {
            lodChanged = TRUE;
        }
    }

    return result;
}


/*
 *****************************************************************************
 */
static RpAtomic *
CountAtomic(RpAtomic *atomic, void *count)
{
    (*(RwUInt32 *) count)++;
    return atomic;
}


/*
 *****************************************************************************
 */
static RwUInt32
ClumpCountAtomics(RpClump *clump)
{
    RwUInt32 count = 0;

    RpClumpForAllAtomics(clump, CountAtomic, (void *)&count);
    return count;
}


/*
 *****************************************************************************
 */
static RpAtomic *
GetFirstAtomic(RpAtomic *atomic, void *firstAtomic)
{
    *(RpAtomic **)firstAtomic = atomic;
    return (RpAtomic *)NULL;
}


/*
 *****************************************************************************
 */
RpAtomic *
ClumpGetFirstAtomic(RpClump * clump)
{
    RpAtomic *atomic = (RpAtomic *)NULL;

    RpClumpForAllAtomics(clump, GetFirstAtomic, (void *)&atomic);
    return atomic;
}


/*
 *****************************************************************************
 */
static RpClump *
LoadLodDffs(RwChar *lodPath, RwChar *lodPathEnding)
{
    RpClump *clump[RPLODATOMICMAXLOD];
    RwInt32 i;

    /*
     * Remove end of clump name and extension 
     * so we can append lod numbers and ext.
     */
    lodPathEnding[0] = '\0';

    /*
     * Load it as a LOD object, building new names
     */
    for( i = 0; i < RPLODATOMICMAXLOD; i++ )
    {
        rwsprintf(lodPathEnding, "%d.dff", (int) i);

        clump[i] = LoadDff(lodPath);
        if( clump[i] )
        {
            numLods = i; 
        }
    }

    /*
     * Level 0 LOD supplies the atomics and frame hierarchy
     */
    if( clump[0] )
    {
        RpGeometry *geom;
        RwUInt32 numAtomics;

        /*
         * All clump must have the same number of atomics
         */
        numAtomics = ClumpCountAtomics(clump[0]);
        for( i = 1; i < RPLODATOMICMAXLOD; i++ )
        {
            if( clump[i] && (ClumpCountAtomics(clump[i]) != numAtomics) )
            {
                /*
                 * Wrong number of atomics - tidy up and fail
                 */
                for( i = 0; i < RPLODATOMICMAXLOD; i++ )
                {
                    if( clump[i] )
                    {
                        RpClumpDestroy(clump[i]);
                        clump[i] = (RpClump *)NULL;
                    }
                }
                return (RpClump *)NULL;
            }
        }

        /*
         * Now set up the LODs - first level 0 - always got this
         */
        LodRoot = ClumpGetFirstAtomic(clump[0]);
        AtomicSetRenderFlags(LodRoot, NULL);
        geom = RpAtomicGetGeometry(LodRoot);

        RpLODAtomicSetGeometry(LodRoot, 0, geom);

        RpLODAtomicSetLODCallBack(LodRoot, ClumpChooseLevelOfDetail);

        /*
         * Now the other levels - for missing ones, use next available
         */
        for( i = 1; i < RPLODATOMICMAXLOD; i++ )
        {
            if (clump[i])
            {
                geom = 
                    RpAtomicGetGeometry(ClumpGetFirstAtomic(clump[i]));
            }

            RpLODAtomicSetGeometry(LodRoot, i, geom);
        }

        /*
         * Destroy all clumps except first one.  Geometries will
         * remain, because of reference counting.
         */
        for( i = 1; i < RPLODATOMICMAXLOD; i++ )
        {
            if( clump[i] )
            {
                LODClumpStats[i].totalAtomics = 0;
                LODClumpStats[i].totalTriangles = 0;
                LODClumpStats[i].totalVertices = 0;
                LODClumpStats[i].maxFramePerAtomic = 0;

                RpClumpForAllAtomics(clump[i],
                    AtomicGetStatistics, &LODClumpStats[i]);

                RpClumpDestroy(clump[i]);
                clump[i] = (RpClump *)NULL;
            }
        }
    }
    else
    {
        /*
         * No level zero atomic, tidy up and fail...
         */
        for( i = 0; i < RPLODATOMICMAXLOD; i++ )
        {
            if( clump[i] )
            {
                RpClumpDestroy(clump[i]);
                clump[i] = (RpClump *)NULL;
            }
        }

        return (RpClump *)NULL;
    }

    RpLODAtomicHookRender(LodRoot);

    return clump[0];
}


/*
 *****************************************************************************
 */
RwBool
SaveTextureDictionary(void)
{
    RwStream *stream = (RwStream *)NULL;
    RwChar *path = (RwChar *)NULL;
    RwBool success = FALSE;

    path = RsPathnameCreate(TexDictionaryName);
    stream = RwStreamOpen(rwSTREAMFILENAME, rwSTREAMWRITE, path);
    RsPathnameCreate(path);

    if( stream )
    {
        RwTexDictionary *texDict;

        texDict = RwTexDictionaryGetCurrent();

        if( RwTexDictionaryStreamWrite(texDict, stream) )
        {
            success = TRUE;
        }

        RwStreamClose(stream, NULL);
    }
    
    return success;
}


/*
 *****************************************************************************
 */
static RwTexDictionary *
LoadTextureDictionary(RwChar *path)
{
    RwTexDictionary *texDict = (RwTexDictionary *)NULL;
    RwStream *stream = (RwStream *)NULL;

    if( RwOsGetFileInterface()->rwfexist(path) )
    {
        stream = RwStreamOpen(rwSTREAMFILENAME, rwSTREAMREAD, path);
        if( stream )
        {
            if( RwStreamFindChunk(stream, rwID_TEXDICTIONARY, 
                                  (RwUInt32 *)NULL, (RwUInt32 *)NULL) )
            {
                texDict = RwTexDictionaryStreamRead(stream);
            }

            RwStreamClose(stream, NULL);
        }
    }

    return texDict;
}


/*
 *****************************************************************************
 */
static void 
TexDictionaryGetName(RwChar *texDictionaryName, RwChar *dffPath)
{
    /*
     * Creates a path for the texture dictionary which has the same name 
     * as the BSP and resides in the same directory as the DFF.
     * Texture dictionaries are platform-dependent...
     */
    RwInt32 i;

#if (defined(D3D7_DRVMODEL_H))
    const RwChar ext[] = RWSTRING("_d3d7.txd");
#elif (defined(OPENGL_DRVMODEL_H))
    const RwChar ext[] = RWSTRING("_ogl.txd");
#elif (defined(SKY))
    const RwChar ext[] = RWSTRING("_ps2.txd");
#elif (defined(_XBOX))
    const RwChar ext[] = RWSTRING("_xbox.txd");
#else
    /* #error Unsupported platform */
#endif

    rwstrcpy(texDictionaryName, dffPath);

    i = rwstrlen(texDictionaryName)-1;

    while( i >= 0 )
    {
        if( texDictionaryName[i] == '.' )
        {
            texDictionaryName[i] = '\0';

            break;
        }

        i--;
    }

    rwstrcat(texDictionaryName, ext);

    return;
}


/*
 *****************************************************************************
 */
static RwTexture *
TextureTest(RwTexture *texture, void *data)
{
    /*
     * The first time this function is called, we immediately know
     * that this texture dictionary has at least one texture. This is 
     * all we need to know, so return NULL to stop looking...
     */
    *(RwBool *)data = TRUE;

    return (RwTexture *)NULL;
}


/*
 *****************************************************************************
 */
RwBool 
ClumpLoadDFF(RwChar *dffPath)
{
    RpClump *clump = (RpClump *)NULL;
    RwChar *path = (char *)NULL;
    RwChar lodPath[256], *lodPathEnding = (char *)NULL;
    RwTexDictionary *prevTexDict = (RwTexDictionary *)NULL;
    RwTexDictionary *texDict = (RwTexDictionary *)NULL;

    path = RsPathnameCreate(dffPath);

    /*
     * Remember the current dictionary so that it can be 
     * reinstated if the DFF load fails...
     */
    prevTexDict = RwTexDictionaryGetCurrent();  

    /*
     * Attempt to load a texture dictionary...
     */
    TexDictionaryGetName(TexDictionaryName, path);

    texDict = LoadTextureDictionary(TexDictionaryName);
    if( texDict )
    {
        /*
         * A texture dictionary is available, so make it the current
         * one before the loading the DFF...
         */
        RwTexDictionarySetCurrent(texDict);
    }
    else
    {
        /*
         * No texture dictionary available, so create a new empty
         * dictionary and make it the current one. This dictionary
         * will be populated with textures (if any) when the DFF is 
         * loaded. If textures have been loaded along with the DFF, 
         * we can save this dictionary, so it may be loaded directly
         * next time round...
         */
        RwTexDictionarySetCurrent(RwTexDictionaryCreate());

        RsSetModelTexturePath(path);
    }

    rwstrcpy(lodPath, path);
    lodPathEnding = rwstrstr(lodPath, RWSTRING("0.dff"));
    if( !lodPathEnding )
    {
        lodPathEnding = rwstrstr(lodPath, RWSTRING("0.DFF"));
    }

    if( !lodPathEnding )
    {
        clump = LoadDff(path);

        LodRoot = (RpAtomic *)NULL;
    }
    else
    {
        clump = LoadLodDffs(lodPath, lodPathEnding);
    }

    if( clump )
    {
        if( texDict )
        {
            /*
             * Pretend there's no textures, so we can disable the menu
             * item that saves the texture dictionary - we don't need to
             * resave it...
             */
            ClumpHasTextures = FALSE;
        }
        else
        {
            ClumpHasTextures = FALSE;

            RwTexDictionaryForAllTextures(RwTexDictionaryGetCurrent(), 
                TextureTest, (void *)&ClumpHasTextures);
        }

        rwstrcpy(ClumpPathFile, path);
        RsPathnameDestroy(path);

        /*
         * Setup the clump for viewing...
         */
        ClumpInitialize(clump);

        RwTexDictionaryDestroy(prevTexDict);

        return TRUE;
    }

    RsPathnameDestroy(path);

    /*
     * The DFF failed to load so reinstate the original texture dictionary...
     */
    RwTexDictionaryDestroy(RwTexDictionaryGetCurrent());
    RwTexDictionarySetCurrent(prevTexDict);

    return FALSE;
}


/*
 *****************************************************************************
 */
void
ClumpDeltaUpdate(RwReal delta)
{
    AnimUpdateClump(delta);
    HAnimClumpUpdate(delta);
//  SkinClumpUpdate(delta);
    MorphClumpUpdate(delta);
    DMorphClumpUpdate(delta);
	SkinClumpUpdate(delta);		// ms, moved after dmorph for pause, etc.

    if( AtomicSelected &&
        (MorphOn || SkinOn || HAnimOn ||
         (FrameAnimOn && ClumpIsBoneAndSkinSystem) || lodChanged) )
    {
        AtomicGetBoundingBox(AtomicSelected, &CurrentAtomicBBox);
        UpdateSelectedStats();

        lodChanged = FALSE;
    }

    return;
}


/*
 *****************************************************************************
 */
RwBool 
ClumpViewInit(void)
{
    ClumpPathFile[0] = '\0';

    return TRUE;
}


/*
 *****************************************************************************
 */
void
ClumpViewTerminate(void)
{
    if( Im3DVertices )
    {
        RwMemoryFunctions *memFuncs;

        memFuncs = RwOsGetMemoryInterface();

        memFuncs->rwfree(Im3DVertices);
    }

    HAnimDestroy();

    SkinDestroy();

    DMorphDestroy();

    if( Clump )
    {
        RpWorldRemoveClump(World, Clump);

        RpClumpDestroy(Clump);
    }
    
    return;
}


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

//added by Mike S.
extern unsigned int clumpFrame;

void
ClumpDisplayOnScreenInfo(RwRaster *cameraRaster)
{
    RtCharsetDesc charsetDesc;
    RwInt32 csh;
    RwInt32 csw;
    RwInt32 crw;
    RwInt32 crh;
    RwInt32 lines;
    RwInt32 lineSpace;
    RwInt32 linesFromBottom = 1;
    RwChar caption[128];
    RwInt32 usedLOD;
    ClumpStatistics *clumpStats;

	// ms, added

#ifdef RWMETRICS
	if (RWMETRICS)
		return;
#endif

    RtCharsetGetDesc(Charset, &charsetDesc);

    csh = charsetDesc.height;
    csw = charsetDesc.width;
    lineSpace = LINESPACE;

    crw = RwRasterGetWidth(cameraRaster);
    crh = RwRasterGetHeight(cameraRaster);

    lines = (crh - TOPMARGIN - BOTTOMMARGIN) /
            (charsetDesc.height + lineSpace) - 2;

    if( LodRoot )
    {
        usedLOD = RpLODAtomicGetCurrentLOD(LodRoot);

        if( usedLOD > 0 )
        {
            clumpStats = &LODClumpStats[usedLOD];
        }
        else
        {
            clumpStats = &ClumpStats;
        }
    }
    else
    {
        clumpStats = &ClumpStats;
    }

    if( !ClumpLoaded )
    {
        /*
         * Print the start-up screen...
         */
        RtCharsetPrint(Charset, RWSTRING("No clump loaded"),
            LEFTMARGIN, TOPMARGIN + csh + LINESPACE);

        return;
    }

    if( AtomicSelected )
    {
        /*
         * Print the atomic statistics...
         */
        rwsprintf(caption, RWSTRING("Atomic %c"), 'A' + currentAtomicNumber);
        RtCharsetPrint (Charset, caption, LEFTMARGIN, TOPMARGIN);

        rwsprintf(caption, RWSTRING("Triangles: %d"), AtomicTotalTriangles);
        RtCharsetPrint (Charset, 
            caption, LEFTMARGIN,TOPMARGIN + csh + lineSpace);

        rwsprintf(caption, RWSTRING("Vertices: %d"), AtomicTotalVertices);
        RtCharsetPrint (Charset, 
            caption, LEFTMARGIN, TOPMARGIN + (csh + lineSpace) * 2);

        if( ClumpHasMorphAnimation )
        {
            rwsprintf(caption,
                RWSTRING("Morph targets: %d"), AtomicTotalMorphTargets);
            RtCharsetPrint (Charset, 
                caption, LEFTMARGIN, TOPMARGIN + (csh + lineSpace) * 3);
        }

        if( ClumpHasFrameAnimation )
        {
            rwsprintf(caption,
                RWSTRING("Rotation Keys: %d"), AtomicTotalRotationKeys);
            RtCharsetPrint(Charset,
                caption, LEFTMARGIN, TOPMARGIN + (csh + lineSpace) * 4);

            rwsprintf(caption,
                RWSTRING("Translate Keys: %d"), AtomicTotalTranslationKeys);
            RtCharsetPrint (Charset, 
                caption, LEFTMARGIN, TOPMARGIN + (csh + lineSpace) * 5);
        }
    }
    else
    {
        /*
         * Print the clump statistics...
         */
        rwsprintf(caption, RWSTRING("Atomics: %d"), clumpStats->totalAtomics);
        RtCharsetPrint(Charset, caption, LEFTMARGIN, TOPMARGIN);

        rwsprintf(caption, RWSTRING("Frame %3d"), clumpFrame);
        RtCharsetPrint (Charset, caption, LEFTMARGIN+(20*csw), TOPMARGIN);


        rwsprintf(caption, RWSTRING("Mip K %5.1f"), MipmapKValue);
        RtCharsetPrint (Charset, caption, LEFTMARGIN+(36*csw), TOPMARGIN);



        rwsprintf(caption, 
            RWSTRING("Triangles: %d"), clumpStats->totalTriangles);
        RtCharsetPrint(Charset,
            caption, LEFTMARGIN, TOPMARGIN + csh + lineSpace);

        rwsprintf(caption, 
            RWSTRING("Vertices: %d"), clumpStats->totalVertices);
        RtCharsetPrint(Charset,
            caption, LEFTMARGIN, TOPMARGIN + (csh + lineSpace) * 2);

        rwsprintf(caption, RWSTRING("Radius: %0.2f"), ClumpSphere.radius);
        RtCharsetPrint(Charset,
            caption, LEFTMARGIN, TOPMARGIN + (csh + lineSpace) * 3);

        if( ClumpHasSkinAnimation )
        {
            rwsprintf(caption, RWSTRING("Bones: %d"), AtomicTotalSkinBones);
            RtCharsetPrint(Charset, 
                caption, LEFTMARGIN, TOPMARGIN + (csh + lineSpace) * 4);

            rwsprintf(caption, 
                RWSTRING("Keyframes: %d"), AtomicTotalAtomicTotalKeyFrame);
            RtCharsetPrint(Charset, 
                caption, LEFTMARGIN, TOPMARGIN + (csh + lineSpace) * 5);
        }
        else if( ClumpHasFrameAnimation )
        {
            rwsprintf(caption, 
                RWSTRING("Rotation keys: %d"), clumpStats->totalRotationKeys);
            RtCharsetPrint(Charset,
                caption, LEFTMARGIN, TOPMARGIN + (csh + lineSpace) * 4);

            rwsprintf(caption, RWSTRING("Translat keys: %d"),
                clumpStats->totalTranslationKeys);
            RtCharsetPrint(Charset,
                caption, LEFTMARGIN, TOPMARGIN + (csh + lineSpace) * 5);
        }

		// print DEBUGSTATION Stuff
		
#if (defined(DEBUGSTATION))
#if 0
		rwsprintf(caption, 
			RWSTRING("IP: %s x"), debugStationFilePrefix);
		RtCharsetPrint(Charset, 
			caption, LEFTMARGIN, TOPMARGIN + (csh + lineSpace) * 6);
#endif
#endif

    }

    linesFromBottom = 1;

    /*
     * Print the animation and animation speed ...
     */
    if( ClumpHasMorphAnimation && MorphOn )
    {
        rwsprintf(caption, RWSTRING("MPS: %.1f"), MorphsPerSecond);
        linesFromBottom++;
    }
    else if( ClumpHasFrameAnimation && FrameAnimOn )
    {
        if( AllAnimsTogether )
        {
            rwsprintf(caption,
                      RWSTRING("All Animations - AnimSpeed: %.2f"),
                      AnimSequenceNames[CurrentAnimSequenceIndex],
                      FrameAnimSpeed);
        }
        else
        {
            rwsprintf(caption,
                      RWSTRING("Anim: \"%s\" - AnimSpeed: %.2f"),
                      AnimSequenceNames[CurrentAnimSequenceIndex],
                      FrameAnimSpeed);
        }
        linesFromBottom++;
    }
    else
    {
        rwsprintf(caption, RWSTRING(""));
    }

    RtCharsetPrint (Charset, caption,
                    crw - csw * rwstrlen(caption) - RIGHTMARGIN,
                    crh - csh * (linesFromBottom) - BOTTOMMARGIN);

    linesFromBottom++;

    /*
     * Print the lod..
     */
    if( LodRoot )
    {
        rwsprintf(caption, RWSTRING("Level Of Detail: %d"),
            RpLODAtomicGetCurrentLOD(LodRoot));
        RtCharsetPrint(Charset, caption,
                       crw - csw * rwstrlen(caption) - RIGHTMARGIN,
                       crh - csh * (linesFromBottom) - BOTTOMMARGIN);
        linesFromBottom++;
    }

    linesFromBottom++;

    /*
     * Print user control messages
     */
    if( ClumpPick )
    {
        rwsprintf(caption, RWSTRING("Pick Atomic"));

        RtCharsetPrint(Charset, caption,
                       crw - csw * rwstrlen(caption) - RIGHTMARGIN,
                       crh - csh * (linesFromBottom) - BOTTOMMARGIN);
        linesFromBottom++;
    }

    if( ClumpRotate || ClumpDirectRotate)
    {
        rwsprintf(caption, RWSTRING("Rotate Atomic"));

        RtCharsetPrint(Charset, caption,
                       crw - csw * rwstrlen(caption) - RIGHTMARGIN,
                       crh - csh * (linesFromBottom) - BOTTOMMARGIN);
        linesFromBottom++;
    }

    if( ClumpTranslate || ClumpDirectTranslate )
    {
        rwsprintf(caption, RWSTRING("Translate Atomic"));

        RtCharsetPrint(Charset, caption,
                       crw - csw * rwstrlen(caption) - RIGHTMARGIN,
                       crh - csh * (linesFromBottom) - BOTTOMMARGIN);
        linesFromBottom++;
    }

    return;
}


/*
 *****************************************************************************
 */
RwBool 
SaveDFF(void)
{
    RwStream *stream = (RwStream *)NULL;
    RwBool success = TRUE;
    RwChar *path;

    path = RsPathnameCreate(RWSTRING("./new.dff"));
    stream = RwStreamOpen(rwSTREAMFILENAME, rwSTREAMWRITE, path);
    RsPathnameDestroy(path);

    if( stream )
    {
        if( !RpClumpStreamWrite(Clump, stream) )
        {   
            RsErrorMessage(RWSTRING("Cannot write DFF file."));

            success = FALSE;
        }

        RwStreamClose(stream, NULL);
    }
    else
    {
        RsErrorMessage(RWSTRING("Cannot open stream to write DFF file."));

        success =  FALSE;
    }

    return success;
}

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