//-----------------------------------------------------------------------------
// Torque Game Engine 
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------

#include "max2dtsExporter/SceneEnum.h"
#include "max2dtsExporter/Sequence.h"
#include "platform/platformAssert.h"
#include "core/filestream.h"
#include "max2dtsExporter/exporter.h"
#include "ts/TSShapeInstance.h"
#include <time.h>
#include "max2dtsExporter/maxUtil.h"

#pragma pack(push,8)
#include <ISTDPLUG.H>
#pragma pack(pop)

U32 dumpMask;

U32 allowEmptySubtrees;
U32 allowCrossedDetails;
U32 allowUnusedMeshes;
U32 transformCollapse;
U32 viconNeeded;
U32 enableSequences;
U32 allowOldSequences;
U32 enableTwoSidedMaterials;
F32 animationDelta;
F32 maxFrameRate;
S32 weightsPerVertex;
F32 weightThreshhold;
char baseTexturePath[256];
S32 t2AutoDetail;

void setBaseTexturePath(const char * str)
{
   dStrcpy(baseTexturePath,str);
   // if first character is '.', then chop everything else off
   if (*baseTexturePath=='.')
   {
      baseTexturePath[1]='\0';
      return;
   }
   // make sure path ends in '/'
   S32 len = dStrlen(baseTexturePath);
   if (baseTexturePath[len-1]!='\\')
   {
      baseTexturePath[len]   = '\\';
      baseTexturePath[len+1] = '\0';
   }
}

FileStream SceneEnumProc::file;

Vector<char *> SceneEnumProc::alwaysExport;
Vector<char *> SceneEnumProc::neverExport;
Vector<char *> SceneEnumProc::neverAnimate;

char SceneEnumProc::exportType = ' ';

//--------------------------------------------------------------

bool SceneEnumProc::isDummy(INode *pNode)
{
   return !dStrnicmp(pNode->GetName(), "dummy", 5);
}

bool SceneEnumProc::isBounds(INode *pNode)
{
   return !dStrnicmp(pNode->GetName(), "bounds", 6);
}

bool SceneEnumProc::isCamera(INode *pNode)
{
    return !dStrnicmp(pNode->GetName(), "camera", 6);
}

bool SceneEnumProc::isVICON(INode *pNode)
{
   return !dStrncmp(pNode->GetName(), "VICON", 5) || !dStricmp(pNode->GetName(), "Bip01");
}

bool SceneEnumProc::isSubtree(INode *pNode)
{
    return pNode->GetParentNode()->IsRootNode() && !hasMesh(pNode);
}

bool SceneEnumProc::isBillboard(INode * pNode)
{
   return !dStrncmp(pNode->GetName(),"BB::",4);
}

bool SceneEnumProc::isBillboardZAxis(INode * pNode)
{
   return !dStrncmp(pNode->GetName(),"BBZ::",5);
}

bool SceneEnumProc::isSortedObject(INode * pNode)
{
   return !dStrnicmp(pNode->GetName(),"SORT::",6);
}

bool SceneEnumProc::isDecal(INode * pNode)
{
   return !dStrnicmp(pNode->GetName(),"Decal::",7);
}

void SceneEnumProc::tweakName(const char ** name)
{
   if (!dStrncmp(*name,"BB::",4))
      *name += 4;
   else if (!dStrncmp(*name,"BBZ::",5))
      *name += 5;
   else if (!dStrncmp(*name,"SORT::",6))
      *name += 6;
}

//--------------------------------------------------------------

void SceneEnumProc::printDump(U32 mask, const char * buffer)
{
    if (mask & dumpMask && file.getStatus()==Stream::Ok)
        file.write(strlen(buffer),buffer);
}

//--------------------------------------------------------------

bool SceneEnumProc::isEmpty()
{
    return subTrees.empty() && !viconNode;
}

//--------------------------------------------------------------

SceneEnumProc::SceneEnumProc() 
{
    // initialize variables
    pShape     = NULL;
    boundsNode = NULL;
    viconNode  = NULL;
    exportError = false;
    exportErrorStr = NULL;
}

//--------------------------------------------------------------

void SceneEnumProc::startDump(const TCHAR * filename, const TCHAR * maxFile)
{
    if (!dumpMask)
        return;

    // add dump file to same directory as shape
    char dumpFilename[256];
    dStrcpy(dumpFilename,filename);
    char * p  = dStrrchr(dumpFilename,'\\');
    char * p2 = dStrrchr(dumpFilename,':');
    if (p && *p=='\\')
       dStrcpy(p+1,"dump.dmp");
    else if (p2 && *p2==':')
       dStrcpy(p2+1,"dump.dmp");
    else
       dStrcpy(dumpFilename,"dump.dmp");

    // get rid of old dump file -- file stream's don't deal with this well
    File zap;
    zap.open(dumpFilename,File::Write);
    zap.close();

    // open debugging file:
    file.open(dumpFilename,FileStream::Write);
    if (file.getStatus() != File::Ok)
    {
       setExportError("Unable to open dump file.");
       return;
    }

    // add info to dump file so we know what file it is for
    printDump(PDAlways,avar("Max file %s exported to %s\r\n",maxFile,filename));

    // add time and date to dump file
    time_t timer;
    struct tm *tblock;
    timer = time(NULL);
    tblock = localtime(&timer);
    printDump(PDAlways,avar("Exported on %s\r\n",asctime(tblock)));
}

//--------------------------------------------------------------

void SceneEnumProc::enumScene(IScene *scene)
{
    printDump(PDPass1,"First pass: collect useful nodes...\r\n\r\n");

    scene->EnumTree(this);

    // if already had an error, exit now
    if (isExportError())
        return;

    // Make sure they have a bounding box
    if (!boundsNode)
    {
        setExportError("No bounding box found");
        return;
    }
    if (boundsNode->NumberOfChildren())
    {
        setExportError("Error:  Bounds node must not have children.");
        return;
    }

    // Make sure they have a vicon node if required
    if (viconNeeded && !viconNode)
    {
        setExportError("No vicon node found");
        return;
    }
}

//--------------------------------------------------------------

SceneEnumProc::~SceneEnumProc() 
{
    // close debugging file:
    file.close();

    shapeMimic.destroyShape(pShape);
    pShape = NULL;
    
    dFree(exportErrorStr);

    clearExportConfig();
}

//--------------------------------------------------------------

// only used for testing ...
TSShape * SceneEnumProc::regenShape()
{
    delete pShape;
    pShape = NULL;

    // generate the shape
    pShape = shapeMimic.generateShape();

    // be sure to propagate any errors    
    if (shapeMimic.isError())
        setExportError(shapeMimic.getError());

    return pShape;
}

//--------------------------------------------------------------
// callback for EnumTree:

S32 SceneEnumProc::callback(INode *pNode) 
{
    // At this stage we do not need to collect all the nodes
    // because the tree structure will still be there when we
    // build the shape.  What we need to do right now is grab
    // the top of all the subtrees, the vicon node if it exists,
    // any meshes hanging on the root level (these will be lower
    // detail levels, we don't need to grab meshes on the sub-trees
    // because they will be found when we recurse into the sub-tree),
    // the bounds node, always nodes, and any sequences.
    
    // NOTE:  excessive return statements here -- the idea is to make
    //        it easier to follow flow...hopefully is doesn't backfire.

    const char *name  = pNode->GetName();
    const char *pname = pNode->GetParentNode()->GetName();

    printDump(PDPass1,avar("Processing Node %s with parent %s\r\n", name, pname));

    ObjectState os = pNode->EvalWorldState(0);

    // If it is a sequence, get the sequence
    if (os.obj->ClassID() == SEQUENCE_CLASS_ID || (allowOldSequences && os.obj->ClassID() == OLD_SEQUENCE_CLASS_ID))
        return getSequence(pNode);

    if (isVICON(pNode))
    {
        // We are the VICON...coo-coo ka-choo
        printDump(PDPass1,avar("Found VICON node (%s)\r\n",name));
        viconNode = pNode;
        return TREE_CONTINUE;
    }
    
    if (isCamera(pNode))
        // just ignore it
        return TREE_CONTINUE;

    if (isSubtree(pNode))
    {
        // Add this node to the subtree list...
        printDump(PDPass1,avar("Found subtree starting at Node \"%s\"\r\n",name));
        subTrees.push_back(pNode);
        return TREE_CONTINUE;
    }

    // See if it is a bounding box.  If so, save it as THE bounding
    // box for the scene
    if (isBounds(pNode))
    {
        if (boundsNode)
        {
            setExportError("More than one bounds node found.");
            return TREE_ABORT;
        }
        printDump(PDPass1,"Bounding box found\r\n");
        boundsNode = pNode;
    }
    else if (hasSkin(pNode))
    {
        if (!pNode->GetParentNode()->IsRootNode())
        {
            setExportError(avar("Skin found on linked node \"%s\" -- skin must be unlinked.",name));
            return TREE_ABORT;
        }
        // skin...
        printDump(PDPass1,avar("Skin Node \"%s\" with parent \"%s\" added to entry list\r\n",name,pname));
        skinNodes.push_back(pNode);
    }
    else if (hasMesh(pNode) && pNode->GetParentNode()->IsRootNode())
    {
        // A node with a mesh hanging on the root...
        // it's probably a lower detail version of a mesh in the shape
        printDump(PDPass1,avar("Mesh Node \"%s\" with parent \"%s\" added to entry list\r\n",name,pname));
        meshNodes.push_back(pNode);
    }

    return TREE_CONTINUE;
}

//--------------------------------------------------------------

S32 SceneEnumProc::getSequence(INode *pNode)    
{
    // We could just push this onto the sequences list, but
    // we'll do some validity testing first.

    // Sequences must have "root" as parent:
    if (!pNode->GetParentNode()->IsRootNode())
    {
        setExportError("Sequence defined off the \"root\" node.");
        return TREE_ABORT;
    }

    Object *ob = pNode->GetObjectRef();

    // the following should never happen...
    if (ob->ClassID() != SEQUENCE_CLASS_ID && ob->ClassID() != OLD_SEQUENCE_CLASS_ID)
    {
       setExportError("Error in getSequencee: report to programmer");
       return TREE_ABORT;
    }

    // Do extra check to make sure sequence is valid
    IParamBlock *pblock = (IParamBlock *)ob->GetReference(0);
    Control *control = pblock->GetController(PB_SEQ_BEGIN_END);
    if (!control)
    {
        setExportError(avar("Sequence \"%s\" is missing keyframes to mark start and end",pNode->GetName()));
        return TREE_ABORT;
    }

    sequences.push_back(pNode);
    printDump(PDPass1,avar("Sequence %s found.\r\n",pNode->GetName()));
   
    return TREE_CONTINUE;
}

void SceneEnumProc::getPolyCountSubtree(INode * node, S32 & polyCount, F32 & minVolPerPoly, F32 & maxVolPerPoly)
{
   F32 vol;
   S32 pc;
   vol = findVolume(node,pc);
   if (pc>0)
   {
      F32 volPerPoly = vol/(F32)pc;
      if (volPerPoly<minVolPerPoly)
         minVolPerPoly = volPerPoly;
      else if (volPerPoly>maxVolPerPoly)
         maxVolPerPoly = volPerPoly;
   }
   polyCount += pc;
   for (S32 i=0; i<node->NumberOfChildren(); i++)
      getPolyCountSubtree(node->GetChildNode(i),polyCount,minVolPerPoly,maxVolPerPoly);
}

void SceneEnumProc::getPolyCount(S32 & polyCount, F32 & minVolPerPoly, F32 & maxVolPerPoly)
{
   polyCount = 0;
   minVolPerPoly = 10E30f;
   maxVolPerPoly = -10E30F;
   if (viconNode)
      getPolyCountSubtree(viconNode,polyCount,minVolPerPoly,maxVolPerPoly);

   for (S32 i=0; i<subTrees.size(); i++)
      getPolyCountSubtree(subTrees[i],polyCount,minVolPerPoly,maxVolPerPoly);
}

//--------------------------------------------------------------
// We've now scanned in the nodes (saving off what we need) via
// Max repeatedly calling callback(INode *) method...
// Now put the shape together.
void SceneEnumProc::processScene()
{
    S32 i;

    printDump(PDPass2,"\r\nSecond pass:  put shape structure together...\r\n\r\n");

    // get poly count -- this will be used by t2AutoDetail method
    S32 polyCount;
    F32 minVolPerPoly,maxVolPerPoly;
    getPolyCount(polyCount,minVolPerPoly,maxVolPerPoly);

    // eventually we should get a better way of doing this...    
    shapeMimic.setGlobals(boundsNode,polyCount,minVolPerPoly,maxVolPerPoly);

    // set up bounds node
    shapeMimic.addBounds(boundsNode);

    // add the VICON subtree
    if (viconNode)
        shapeMimic.addSubtree(viconNode);

    // add other subtrees
    for (i=0; i<subTrees.size(); i++)
        shapeMimic.addSubtree(subTrees[i]);

    // add meshes
    for (i=0; i<meshNodes.size(); i++)
        shapeMimic.addMesh(meshNodes[i]);
        
    // add skin
    for (i=0; i<skinNodes.size(); i++)
        shapeMimic.addSkin(skinNodes[i]);

    // add sequences
    for (i=0; i<sequences.size(); i++)
        shapeMimic.addSequence(sequences[i]);

    shapeMimic.collapseTransforms();

    // generate the shape
    pShape = shapeMimic.generateShape();

    // clear lists for next time around
    shapeMimic.clearCollapseTransforms();
    
    // be sure to propagate any errors    
    if (shapeMimic.isError())
        setExportError(shapeMimic.getError());
}

//--------------------------------------------------------------

bool checkDuplicateNodes(TSShape * pShape, S32 & dup1, S32 & dup2)
{
   S32 i,j;
   i = (dup1<0) ? 0 : dup1+1;
   for (; i < ((S32)pShape->nodes.size())-1; i++)
   {
      j = (dup2<0) ? i+1 : dup2+1;
      dup2=-1;
      for (; j<pShape->nodes.size(); j++)
      {
         if (pShape->nodes[i].nameIndex == pShape->nodes[j].nameIndex)
         {
            dup1 = i;
            dup2 = j;
            return true;
         }
      }
   }
   return false;
}

bool checkDuplicateObjects(TSShape * pShape, S32 & dup1, S32 & dup2)
{
   S32 i,j;
   i = (dup1<0) ? 0 : dup1+1;
   for (; i < ((S32)pShape->objects.size())-1; i++)
   {
      j = (dup2<0) ? i+1 : dup2+1;
      dup2=-1;
      for (; j<pShape->objects.size(); j++)
      {
         if (pShape->objects[i].nameIndex == pShape->objects[j].nameIndex)
         {
            dup1 = i;
            dup2 = j;
            return true;
         }
      }
   }
   return false;
}

//--------------------------------------------------------------

#define NumBoolParams 31

char * BoolParamNames[NumBoolParams] =
{
    "Dump::NodeCollection",
    "Dump::ShapeConstruction",
    "Dump::NodeCulling",
    "Dump::NodeStates",
    "Dump::NodeStateDetails",
    "Dump::ObjectStates",
    "Dump::ObjectStateDetails",
    "Dump::ObjectOffsets",
    "Dump::SequenceDetails",
    "Dump::ShapeHierarchy",
    "Error::AllowEmptySubtrees",
    "Error::AllowCrossedDetails",
    "Error::AllowUnusedMeshes",
    "Error::AllowOldSequences",
    "Error::RequireViconNode",
    "Param::EnableTwoSidedMaterials",
    "Param::CollapseTransforms",
    "Param::SequenceExport",
    "Sequence::defaultCyclic",
    "Sequence::defaultBlend",
    "Sequence::defaultFirstLastFrameSame",
    "Sequence::defaultUseFrameRate",
    "Sequence::defaultIgnoreGroundTransform",
    "Sequence::defaultUseGroundFrameRate",
    "Sequence::defaultEnableMorphAnimation",
    "Sequence::defaultEnableVisAnimation",
    "Sequence::defaultEnableTransformAnimation",
    "Sequence::defaultForceMorphAnimation",
    "Sequence::defaultForceVisAnimation",
    "Sequence::defaultForceTransformAnimation",
    "Sequence::defaultOverrideDuration"
};

U32 * BoolParams[NumBoolParams] =
{
    &dumpMask,
    &dumpMask,
    &dumpMask,
    &dumpMask,
    &dumpMask,
    &dumpMask,
    &dumpMask,
    &dumpMask,
    &dumpMask,
    &dumpMask,
    (U32*)&allowEmptySubtrees,
    (U32*)&allowCrossedDetails,
    (U32*)&allowUnusedMeshes,
    (U32*)&allowOldSequences,
    (U32*)&viconNeeded,
    (U32*)&enableTwoSidedMaterials,
    (U32*)&transformCollapse,
    (U32*)&enableSequences,
    (U32*)&SequenceObject::defaultCyclic,
    (U32*)&SequenceObject::defaultBlend,
    (U32*)&SequenceObject::defaultFirstLastFrameSame,
    (U32*)&SequenceObject::defaultUseFrameRate,
    (U32*)&SequenceObject::defaultIgnoreGroundTransform,
    (U32*)&SequenceObject::defaultUseGroundFrameRate,
    (U32*)&SequenceObject::defaultEnableMorphAnimation,
    (U32*)&SequenceObject::defaultEnableVisAnimation,
    (U32*)&SequenceObject::defaultEnableTransformAnimation,
    (U32*)&SequenceObject::defaultForceMorphAnimation,
    (U32*)&SequenceObject::defaultForceVisAnimation,
    (U32*)&SequenceObject::defaultForceTransformAnimation,
    (U32*)&SequenceObject::defaultOverrideDuration
};

U32 BoolParamBit[NumBoolParams] =
{
    PDPass1,
    PDPass2,
    PDPass3,
    PDNodeStates,
    PDNodeStateDetails,
    PDObjectStates,
    PDObjectStateDetails,
    PDObjectOffsets,
    PDSequences,
    PDShapeHierarchy,
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    0
};

#define NumFloatParams 6

char * FloatParamNames[NumFloatParams] =
{
    "Params::AnimationDelta",
    "Params::MaxFrameRate",
    "Sequence::defaultFrameRate",
    "Sequence::defaultGroundFrameRate",
    "Params::SkinWeightThreshhold",
    "Sequence::defaultDuration"
};

F32 * FloatParams[NumFloatParams] =
{
    &animationDelta,
    &maxFrameRate,
    &SequenceObject::defaultFrameRate,
    &SequenceObject::defaultGroundFrameRate,
    &weightThreshhold,
    &SequenceObject::defaultDuration
};

#define NumIntParams 5

char * IntParamNames[NumIntParams] =
{
    "SequenceObject::defaultNumFrames",
    "SequenceObject::defaultNumGroundFrames",
    "SequenceObject::defaultDefaultSequencePriority",
    "Params::weightsPerVertex",
    "Params::T2AutoDetail"
};

S32 * IntParams[NumIntParams] =
{
    &SequenceObject::defaultNumFrames,
    &SequenceObject::defaultNumGroundFrames,
    &SequenceObject::defaultDefaultSequencePriority,
    &weightsPerVertex,
    &t2AutoDetail
};

#define NumStringParams 2

char testString[15] ="xxxxxxxxxxxxxx";

char * StringParamNames[NumStringParams] =
{
    "Test::test",
    "Params::baseTexturePath"
};

char * StringParams[NumStringParams] =
{
    testString,
    baseTexturePath
};

S32 StringParamMaxLen[NumStringParams] =
{
    5,
    200 // 256 really, but this should be enough
};

S32 getParamEntry(const char * name, char ** nameTable, S32 size)
{
    for (S32 i=0; i<size; i++)
    {
        if (!dStrcmp(name,nameTable[i]))
            return i;
    }
    return -1;
}

extern void findFiles(const char *, Vector<char*> &);
extern void clearFindFiles(Vector<char*>);

const char * SceneEnumProc::readConfigFile(const char * filename)
{
    clearExportConfig();

    Vector<char*> fnames;
    findFiles(filename,fnames);
    if (fnames.size()>1)
      // could potentially happen when not exporting, but act as if we are...
      return "Multiple config (*.cfg) files found in shape directory.";

    if (fnames.empty())
    {
        printDump(PDAlways,"\r\nConfig file not found.\r\n");
        return NULL;
    }

    // Build new file name out of passed file name and found file...
    char fileBuffer[256];
    dStrcpy(fileBuffer,filename);
    char * s = dStrrchr(fileBuffer,'\\');
    dStrcpy(s+1,fnames[0]);

    FileStream fs;
    fs.open(fileBuffer,FileStream::Read);
    if (fs.getStatus() != Stream::Ok)
    {
        printDump(PDAlways,avar("\r\nConfig file \"%s\" not found.\r\n",fileBuffer));
        return NULL;
    }

    printDump(PDAlways,avar("\r\nBegin reading config file \"%s\".\r\n",fileBuffer));

    S32 len = fs.getStreamSize();

    // read one line at a time
    char * buffer = new char[len+1];
    fs.read(len,buffer);
    fs.close();

    // make sure we're properly terminated
    buffer[len] = '\n';

    S32 mode = 0;
    char * pos = buffer;
    do
    {
        char * next = dStrchr(pos,'\n');
        *next='\0';

        // chop off trailing \r's
        char * ch = next-1;
        while (ch>pos && *ch=='\r')
        {
            *ch = '\0';
            ch--;
        }
        if (ch!=pos)
        {
            if (!dStricmp(pos,"AlwaysExport:"))
                mode = 0;
            else if (!dStricmp(pos,"NeverExport:"))
                mode = 1;
            else if (!dStricmp(pos,"NeverAnimate:"))
                mode = 2;
            else if (*pos=='+' || *pos=='-')
            {
                bool newVal = *pos=='+';
                S32 idx = getParamEntry(pos+1,BoolParamNames,NumBoolParams);
                if (idx>=0)
                {
                    if (BoolParamBit[idx])
                    {
                        if (newVal)
                            *BoolParams[idx] |= BoolParamBit[idx];
                        else
                            *BoolParams[idx] &= ~BoolParamBit[idx];
                    }
                    else
                        *BoolParams[idx] = newVal;
                    printDump(PDAlways,avar("%s %s.\r\n",BoolParamNames[idx],newVal ? "enabled" : "disabled"));
                }
                else
                    printDump(PDAlways,avar("Unknown bool parameter \"%s\"\r\n",pos+1));
            }
            else if (*pos=='=')
            {
                char * endName = strchr(pos+1,' ');
                if (endName)
                {
                    *endName = '\0';
                    S32 idx1 = getParamEntry(pos+1,FloatParamNames,NumFloatParams);
                    S32 idx2 = getParamEntry(pos+1,StringParamNames,NumStringParams);
                    S32 idx3 = getParamEntry(pos+1,IntParamNames,NumIntParams);
                    if (idx1>=0)
                    {
                        // Float
                        *FloatParams[idx1] = atof(endName+1);
                        printDump(PDAlways,avar("%s = %f\r\n",FloatParamNames[idx1],*FloatParams[idx1]));
                    }
                    if (idx2>=0)
                    {
                        // string
                        S32 maxLen = StringParamMaxLen[idx2];
                        dStrncpy(StringParams[idx2],endName+1,maxLen-1);
                        StringParams[idx2][maxLen]='\0';
                        printDump(PDAlways,avar("%s = \"%s\"\r\n",StringParamNames[idx2],StringParams[idx2]));
                    }
                    if (idx3>=0)
                    {
                        // S32
                        *IntParams[idx3] = atoi(endName+1);
                        printDump(PDAlways,avar("%s = %i\r\n",IntParamNames[idx3],*IntParams[idx3]));
                    }
                    if (idx1<0 && idx2<0 && idx3<0)
                        printDump(PDAlways,avar("Unknown parameter \"%\"\r\n",pos+1));
                }
            }
            else if (*pos!='\\' && *pos!='/' && *pos!=';')
            {
                if (mode == 0)
                {
                    alwaysExport.push_back(dStrdup(pos));
                    printDump(PDAlways,avar("Always export node: \"%s\"\r\n",pos));
                }
                else if (mode == 1)
                {
                    neverExport.push_back(dStrdup(pos));
                    printDump(PDAlways,avar("Never export node: \"%s\"\r\n",pos));
                }
                else if (mode == 2)
                {
                    neverAnimate.push_back(dStrdup(pos));
                    printDump(PDAlways,avar("Never animate transform on node: \"%s\"\r\n",pos));
                }
            }
        }

        pos = next+1;
    }
    while (pos < &buffer[len]);

    delete [] buffer;

    printDump(PDAlways,"End reading config file.\r\n");
    return NULL;
}

void SceneEnumProc::writeConfigFile(const char * filename)
{
    // make sure to get rid of old version first...
    // otherwise we might have a "collision"
    File zap;
    zap.open(filename,File::Write);
    zap.close();

    FileStream fs;
    fs.open(filename,FileStream::Write);
    if (fs.getStatus() != Stream::Ok)
        return;

    // just write the parameters...none of the config lists

    S32 i;
    char buffer[1024];

    for (i=0; i<NumBoolParams; i++)
    {
        bool enabled = BoolParamBit[i] ? BoolParamBit[i] & *BoolParams[i] : *BoolParams[i];
        dStrcpy(buffer,avar("%s%s\r\n",enabled ? "+" : "-",BoolParamNames[i]));
        fs.write(dStrlen(buffer),buffer);
    }

    for (i=0; i<NumFloatParams; i++)
    {
        dStrcpy(buffer,avar("=%s %f\r\n",FloatParamNames[i],*FloatParams[i]));
        fs.write(dStrlen(buffer),buffer);
    }

    for (i=0; i<NumIntParams; i++)
    {
        dStrcpy(buffer,avar("=%s %i\r\n",IntParamNames[i],*IntParams[i]));
        fs.write(dStrlen(buffer),buffer);
    }

    for (i=0; i<NumStringParams; i++)
    {
        dStrcpy(buffer,avar("=%s %s\r\n",StringParamNames[i],StringParams[i]));
        fs.write(dStrlen(buffer),buffer);
    }

    fs.close();
}

//--------------------------------------------------------------

void SceneEnumProc::clearExportConfig()
{
    S32 i;

    for (i=0; i<alwaysExport.size(); i++)
        dFree(alwaysExport[i]);
    alwaysExport.clear();

    for (i=0; i<neverExport.size(); i++)
        dFree(neverExport[i]);
    neverExport.clear();

    for (i=0; i<neverAnimate.size(); i++)
        dFree(neverAnimate[i]);
    neverAnimate.clear();
}

//--------------------------------------------------------------

#define dumpLine(buffer) { const char * str = buffer; file->write(dStrlen(str),str);}

void SceneEnumProc::exportTextFile(Stream * file)
{
    dumpLine("Nodes:\r\n");

    S32 i;
    for (i=0; i<pShape->nodes.size(); i++)
    {
        if (pShape->nodes[i].nameIndex<0)
        {
            dumpLine("<no name>");
        }
        else
            dumpLine(pShape->getName(pShape->nodes[i].nameIndex));
        dumpLine("\r\n");
    }

    dumpLine("\r\nObjects:\r\n");
    for (i=0; i<pShape->objects.size(); i++)
    {
        if (pShape->objects[i].nameIndex<0)
        {
            dumpLine("<no name>");
        }
        else
            dumpLine(pShape->getName(pShape->objects[i].nameIndex));
        dumpLine("\r\n");
    }
    dumpLine("\r\n");

   TSShapeInstance * pShapeInstance = new TSShapeInstance(pShape);
   pShapeInstance->dump(*file);
   delete pShapeInstance;
}

//--------------------------------------------------------------

bool SceneEnumProc::dumpShape(const char * filename)
{
   // now let's read the shape in, create a shape instance, and dump out its hierrarchy
   FileStream fs;
   fs.open(filename,FileStream::Read);
   if( fs.getStatus() != Stream::Ok )
      return false;

   TSShape * readShape = new TSShape;
   readShape->read(&fs);
   fs.close();

   TSShapeInstance * pShapeInstance = new TSShapeInstance(readShape);
   pShapeInstance->dump(file);

   // check for duplicate nodes
   printDump(PDShapeHierarchy,"\r\nChecking for duplicate nodes...\r\n");
   S32 dup1 = -1;
   S32 dup2 = -1;
   while (checkDuplicateNodes(readShape,dup1,dup2))
   {
      const char * str = readShape->getName(readShape->nodes[dup1].nameIndex);
      printDump(PDShapeHierarchy,avar("Node with name \"%s\" is duplicated.\r\n",str));
   }

   // check for duplicate objects
   printDump(PDShapeHierarchy,"\r\nChecking for duplicate objects...\r\n");
   dup1 = -1;
   dup2 = -1;
   while (checkDuplicateObjects(readShape,dup1,dup2))
   {
      const char * str = readShape->getName(readShape->objects[dup1].nameIndex);
      printDump(PDShapeHierarchy,avar("Object with name \"%s\" is duplicated.\r\n",str));
   }

   delete pShapeInstance;
   delete readShape;

   return true;
}

void SceneEnumProc::setInitialDefaults()
{
    dumpMask = PDPass1 | PDPass2 | PDPass3 |
               PDObjectOffsets             |
               PDNodeStates                |
               PDObjectStates              |
               PDNodeStateDetails          |
               PDObjectStateDetails        |
               PDSequences                 |
               PDShapeHierarchy;

    allowEmptySubtrees = true;
    allowCrossedDetails = false;
    allowUnusedMeshes = true;
    transformCollapse = true;
    viconNeeded = false;
    enableSequences = true;
    allowOldSequences = true;
    enableTwoSidedMaterials = true;
    animationDelta = 0.0001f;
    maxFrameRate = 30.0f;
    weightsPerVertex = 10;
    weightThreshhold = 0.001f;
    dStrcpy(baseTexturePath,".");

    resetSequenceDefaults();
}
