// Filename: soft2Egg.c
// Created by:  masad (26Sep03)
// 
////////////////////////////////////////////////////////////////////
//
// PANDA 3D SOFTWARE
// Copyright (c) 2003, Disney Enterprises, Inc.  All rights reserved
//
// All use of this software is subject to the terms of the Panda 3d
// Software license.  You should have received a copy of this license
// along with this source code; you will also find a current copy of
// the license at http://etc.cmu.edu/panda3d/docs/license/ .
//
// To contact the maintainers of this program write to
// panda3d-general@lists.sourceforge.net .
//
////////////////////////////////////////////////////////////////////

////////////////////////////////////////////////////////////////////
// Includes
////////////////////////////////////////////////////////////////////

#include <SAA.h>

#ifdef __cplusplus
extern "C" {
#endif

#include "pandatoolbase.h"

int init_soft2egg(int, char **);

#if 0
// DWD includes
#include "eggBase.h"
#include <eggParametrics.h>
#include <animTable.h>
#include <linMathOutput.h>

// system includes
#include <fstream.h>
#include <strstream.h>
#include <math.h>
#include <assert.h>
#include <unistd.h>
#include <ieeefp.h>

// Performer includes
#include <Performer/pr/pfLinMath.h>

// SoftImage includes
#include <SAA.h>
#include <SI_macros.h>

static const int    TEX_PER_MAT = 1;
static FILE            *outStream  = stdout;
//static FILE        *outStream  = stderr;

class soft2egg : public EggBase
{
  public:

    soft2egg() : EggBase("r:d:s:m:t:P:b:e:f:T:S:M:A:N:v:o:FhknpaxiucCD")
    {
        rsrc_path = "/ful/ufs/soft371_mips2/3D/rsrc";
        database_name = NULL;
        scene_name = NULL;
        model_name = NULL;
        animFileName = NULL;
        eggFileName = NULL;
        tex_path = NULL;
        eggGroupName = NULL;
        tex_filename = NULL;
        search_prefix = NULL;
        result = SI_SUCCESS;
    
        skeleton = new EggGroup();
        foundRoot = FALSE;
        animRoot = NULL;
        morphRoot = NULL;
        geom_as_joint = 0;
        make_anim = 0;
        make_nurbs = 0;
        make_poly = 0;
        make_soft = 0;
        make_morph = 1;
        make_duv = 1;
        make_dart = TRUE;
        has_morph = 0;
        make_pose = 0;
        animData.is_z_up = FALSE;
        nurbs_step = 1;
        anim_start = -1000;
        anim_end = -1000;
        anim_rate = 24;
        pose_frame = -1;
        verbose = 0;
        flatten = 0;
        shift_textures = 0;
        ignore_tex_offsets = 0;
        use_prefix = 0;
    }

    virtual void Help();
    virtual void Usage();
    virtual void ShowOpts();

    virtual boolean UseOutputSwitch() const {
       return false;
    }

    virtual boolean
    HandleGetopts(char flag, char *optarg, int &optind, int argc, char **argv);

    int   isNum( float );
    char *GetRootName( const char * );
    char *RemovePathName( const char * );
    char *GetSliderName( const char * );
    char *GetFullName( SAA_Scene *, SAA_Elem * );
    char *GetName( SAA_Scene *, SAA_Elem * );
    char *GetModelNoteInfo( SAA_Scene *, SAA_Elem * );
    char *MakeTableName( const char *, int );
    char *DepointellizeName( char * );
    SAA_Elem *FindModelByName( char *, SAA_Scene *, SAA_Elem *, int );
    char *ConvertTexture( SAA_Scene *, SAA_Elem * );
    int  *FindClosestTriVert( EggVertexPool *, SAA_DVector *, int );
    int  *MakeIndexMap( int *, int, int );
    int     findShapeVert( SAA_DVector, SAA_DVector *, int );
    void LoadSoft();
    void MakeEgg( EggGroup *, EggJoint *, AnimGroup *, SAA_Scene *, SAA_Elem * );
    void MakeSurfaceCurve(  SAA_Scene *, SAA_Elem *, EggGroup *,
        EggNurbsSurface *&, int , SAA_SubElem *, bool );

    EggNurbsCurve *MakeUVNurbsCurve( int, long *,  double *, double *, 
        EggGroup *, char * );

    EggNurbsCurve *MakeNurbsCurve( SAA_Scene *, SAA_Elem *, EggGroup *,
        float [4][4], char * );

    void AddKnots( perf_vector<double> &, double *, int, SAA_Boolean, int );
    void MakeJoint( SAA_Scene *, EggJoint *&, AnimGroup *&, SAA_Elem *, char * );
    void MakeSoftSkin( SAA_Scene *, SAA_Elem *, SAA_Elem *, int, char * );
    void CleanUpSoftSkin( SAA_Scene *, SAA_Elem *, char * );
    void MakeAnimTable( SAA_Scene *, SAA_Elem *, char * );
    void MakeVertexOffsets( SAA_Scene *, SAA_Elem *, SAA_ModelType type,
        int, int, SAA_DVector *, float (*)[4], char * );
    void MakeMorphTable( SAA_Scene *, SAA_Elem *, SAA_Elem *, int,  char *, 
        float );
    void MakeLinearMorphTable( SAA_Scene *, SAA_Elem *, int, char *, float );
    void MakeWeightedMorphTable( SAA_Scene *, SAA_Elem *, SAA_Elem *, int,  
        int, char *, float );
    void MakeExpressionMorphTable( SAA_Scene *, SAA_Elem *, SAA_Elem *, int,  
        int, char *, float );
    void MakeTexAnim( SAA_Scene *, SAA_Elem *, char * );

  private:

    char        *rsrc_path;
    char        *database_name;
    char        *scene_name;
    char        *model_name;
    char        *eggFileName;
    char         *animFileName;
    char         *eggGroupName;
    char        *tex_path;
    char        *tex_filename;
    char        *search_prefix;

    SI_Error            result;
    SAA_Scene           scene;
    SAA_Elem            model;
    SAA_Database        database;
    EggGroup           *dart;
    EggGroup           *skeleton;
    AnimGroup          *rootAnim;
    EggJoint           *rootJnt;
    AnimGroup          *animRoot;
    AnimGroup          *morphRoot;
    EggData             animData;

    int                    nurbs_step;
    int                    anim_start;
    int                    anim_end;
    int                    anim_rate;
    int                    pose_frame;
    int                    verbose;
    int                    flatten;
    int                    shift_textures;
    int                    ignore_tex_offsets;
    int                    use_prefix;
 
    bool                foundRoot;
    bool                geom_as_joint;
    bool                make_anim;
    bool                make_nurbs;
    bool                make_poly;
    bool                make_soft;
    bool                make_morph;
    bool                make_duv;
    bool                make_dart;
    bool                has_morph;
    bool                make_pose;

    ofstream    eggFile;
    ofstream    animFile;
    ofstream    texFile;

};


////////////////////////////////////////////////////////////////////
//     Function: Help
//       Access: Public, Virtual
//  Description: Displays the "what is this program" message, along
//               with the usage message.  Should be overridden in base
//               classes to describe the current program.
////////////////////////////////////////////////////////////////////
void soft2egg::
Help() 
{
    cerr <<
    "soft2egg takes a SoftImage scene or model\n"
    "and outputs its contents as an egg file\n";

    Usage();
}

////////////////////////////////////////////////////////////////////
//     Function: Usage
//       Access: Public, Virtual
//  Description: Displays the usage message.
////////////////////////////////////////////////////////////////////
void soft2egg::
Usage() {
  cerr << "\nUsage:\n"
       << _commandName << " [opts] (must specify -m or -s)\n\n"
       << "Options:\n";

  ShowOpts();
  cerr << "\n";
}



////////////////////////////////////////////////////////////////////
//     Function: ShowOpts
//       Access: Public, Virtual
//  Description: Displays the valid options.  Should be extended in
//               base classes to show additional options relevant to
//               the current program.
////////////////////////////////////////////////////////////////////
void soft2egg::
ShowOpts() 
{
    cerr <<
    "  -r <path>  - Used to provide soft with the resource\n" 
    "               Defaults to 'c:/Softimage/SOFT_3.9.2/3D/test'.\n"
      //    "               Defaults to '/ful/ufs/soft371_mips2/3D/rsrc'.\n"
    "  -d <path>  - Database path.\n"
    "  -s <scene> - Indicates that a scene will be converted.\n"
    "  -m <model> - Indicates that a model will be converted.\n"
    "  -t <path>  - Specify path to place converted textures.\n"
    "  -T <name>  - Specify filename for texture map listing.\n"
    "  -S <step>  - Specify step for nurbs surface triangulation.\n"
    "  -M <name>  - Specify model output filename. Defaults to scene name.\n"
    "  -A <name>  - Specify anim output filename. Defaults to scene name.\n"
    "  -N <name>  - Specify egg group name.\n"
    "  -k         - Enable soft assignment for geometry.\n"
    "  -n         - Specify egg NURBS representation instead of poly's.\n"
    "  -p         - Specify egg polygon output for geometry.\n"
    "  -P <frame> - Specify frame number for static pose.\n"
    "  -b <frame> - Specify starting frame for animation (default = first).\n"
    "  -e <frame> - Specify ending frame for animation (default = last).\n"
    "  -f <fps>   - Specify frame rate for animation playback.\n"
    "  -a         - Compile animation tables if animation present.\n"
    "  -F         - Ignore hierarchy and build a completely flat skeleton.\n"
    "  -v <level> - Set debug level.\n"
    "  -x         - Shift NURBS parameters to preserve Alias textures.\n"
    "  -i         - Ignore Soft texture uv offsets.\n"
    "  -u         - Use Soft prefix in model names.\n"
    "  -c         - Cancel morph conversion.\n"
    "  -C         - Cancel duv conversion.\n"
    "  -D         - Don't make the output model a character.\n"
    "  -o <prefix>- Convert only models with given prefix.\n";

      EggBase::ShowOpts();
}


////////////////////////////////////////////////////////////////////
//     Function: HandleGetopts
//       Access: Public, Virtual
//  Description:
////////////////////////////////////////////////////////////////////
boolean soft2egg::
HandleGetopts(char flag, char *optarg, int &optind, int argc, char **argv) 
{
    boolean okflag = true;

    switch (flag) 
    {
      case 'r':       // Set the resource path for soft.
        if ( strcmp( optarg, "" ) )
        {
            // Get the path.
            rsrc_path = optarg;
            fprintf( outStream, "using rsrc path %s\n", rsrc_path );
        }
        break;

    case 'd':       // Set the database path.
        if ( strcmp( optarg, "" ) )
        {
            // Get the path.
            database_name = optarg;
            fprintf( outStream, "using database %s\n", database_name );
        }
        break;

    case 's':     // Check if its a scene.
        if ( strcmp( optarg, "" ) )
        {
            // Get scene name.
            scene_name = optarg;
            fprintf( outStream, "loading scene %s\n", scene_name );
        }
        break;
    
    case 'm':     // Check if its a model.
        if ( strcmp( optarg, "" ) )
        {
            // Get model name.
            model_name = optarg;
            fprintf( outStream, "loading model %s\n", model_name );
        }
        break;
    
    case 't':     // Get converted texture path.
        if ( strcmp( optarg, "" ) )
        {
            // Get tex path name.
            tex_path = optarg;
            fprintf( outStream, "texture path:  %s\n", tex_path );
        }
        break;

    case 'T':      // Specify texture list filename. 
        if ( strcmp( optarg, "") )
        {
            // Get the name.
            tex_filename = optarg;
            fprintf( outStream, "creating texture list file: %s\n", 
                tex_filename );
        }
        break;
    case 'S':     // Set NURBS step.
        if ( strcmp( optarg, "" ) )
        {
            nurbs_step = atoi(optarg);
            fprintf( outStream, "NURBS step:  %d\n", nurbs_step );
        }
        break;
 
    case 'M':     // Set model output file name.
        if ( strcmp( optarg, "" ) )
        {
            eggFileName = optarg;
            fprintf( outStream, "Model output filename:  %s\n", eggFileName );
        }
        break;
 
    case 'A':     // Set anim output file name.
        if ( strcmp( optarg, "" ) )
        {
            animFileName = optarg;
            fprintf( outStream, "Anim output filename:  %s\n", animFileName );
        }
        break;
 
    case 'N':     // Set egg model name.
        if ( strcmp( optarg, "" ) )
        {
            eggGroupName = optarg;
            fprintf( outStream, "Egg group name:  %s\n", eggGroupName );
        }
        break;

    case 'o':     // Set search_prefix.
        if ( strcmp( optarg, "" ) )
        {
            search_prefix = optarg;
            fprintf( outStream, "Only converting models with prefix:  %s\n", 
                search_prefix );
        }
        break;
 
    case 'h':    // print help message
        Help();
        exit(1);
        break;
    
    case 'c':    // Cancel morph animation conversion
        make_morph = FALSE;
        fprintf( outStream, "canceling morph conversion\n" );
        break;

    case 'C':    // Cancel uv animation conversion
        make_duv = FALSE;
        fprintf( outStream, "canceling uv animation conversion\n" );
        break;
    
    case 'D':    // Omit the Dart flag
        make_dart = FALSE;
        fprintf( outStream, "making a non-character model\n" );
        break;
    
    case 'k':    // Enable soft skinning
        //make_soft = TRUE;
        //fprintf( outStream, "enabling soft skinning\n" );
        fprintf( outStream, "-k flag no longer necessary\n" );
        break;
    
    case 'n':    // Generate egg NURBS output
        make_nurbs = TRUE;
        fprintf( outStream, "outputting egg NURBS info\n" );
        break;
    
    case 'p':    // Generate egg polygon output
        make_poly = TRUE;
        fprintf( outStream, "outputting egg polygon info\n" );
        break;
    
    case 'P':    // Generate static pose from given frame
        if ( strcmp( optarg, "" ) )
        {
            make_pose = TRUE;
            pose_frame = atoi(optarg);
            fprintf( outStream, "generating static pose from frame %d\n",
                pose_frame );
        }
        break;
    
    case 'a':     // Compile animation tables.
        make_anim = TRUE;
        fprintf( outStream, "attempting to compile anim tables\n" );
        break;

    case 'F':     // Build a flat skeleton.
        flatten = TRUE;
        fprintf( outStream, "building a flat skeleton!!!\n" );
        break;

    case 'x':     // Shift NURBS parameters to preserve Alias textures.
        shift_textures = TRUE;
        fprintf( outStream, "shifting NURBS parameters...\n" );
        break;

    case 'i':     // Ignore Soft uv texture offsets 
        ignore_tex_offsets = TRUE;
        fprintf( outStream, "ignoring texture offsets...\n" );
        break;

    case 'u':     // Use Soft prefix in model names 
        use_prefix = TRUE;
        fprintf( outStream, "using prefix in model names...\n" );
        break;


    case 'v':     // print debug messages.
        if ( strcmp( optarg, "" ) )
        {
            verbose = atoi(optarg);
            fprintf( outStream, "using debug level %d\n", verbose );
        }
        break;

    case 'b':     // Set animation start frame.
        if ( strcmp( optarg, "" ) )
        {
            anim_start = atoi(optarg);
            fprintf( outStream, "animation starting at frame:  %d\n", 
                anim_start );
        }
        break;

    case 'e':     /// Set animation end frame.
        if ( strcmp( optarg, "" ) )
        {
            anim_end = atoi(optarg);
            fprintf( outStream, "animation ending at frame:  %d\n", anim_end );
        }
        break;
 
    case 'f':     /// Set animation frame rate.
        if ( strcmp( optarg, "" ) )
        {
            anim_rate = atoi(optarg);
            fprintf( outStream, "animation frame rate:  %d\n", anim_rate );
        }
        break;
 
    default:
        okflag = EggBase::HandleGetopts(flag, optarg, optind, argc, argv);
  }

  return (okflag);
}



////////////////////////////////////////////////////////////////////
//     Function: isNum
//       Access: Public, Virtual
//  Description: Take a float and make sure it is of the body. 
////////////////////////////////////////////////////////////////////
int soft2egg::
isNum( float num ) 
{
    return( ( num < HUGE_VAL ) && finite( num ) );
}


////////////////////////////////////////////////////////////////////
//     Function: GetRootName
//       Access: Public
//  Description: Given a string, return a copy of the string up to
//                 the first occurence of '-'. 
////////////////////////////////////////////////////////////////////
char *soft2egg::
GetRootName( const char *name )
{
    char *hyphen;
    char *root;
    int      len;

    hyphen = strchr( name, '-' );
    len = hyphen-name;

    if ( (hyphen != NULL) && len )
    {
        root = (char *)malloc(sizeof(char)*(len+1));
        strncpy( root, name, len );
        root[sizeof(char)*(len)] = '\0';
    }
    else
    {
        root = (char *)malloc( sizeof(char)*(strlen(name)+1));
        strcpy( root, name );
    }

    return( root );
}


////////////////////////////////////////////////////////////////////
//     Function: RemovePathName
//       Access: Public
//  Description: Given a string, return a copy of the string after 
//                 the last occurence of '/ 
////////////////////////////////////////////////////////////////////
char *soft2egg::
RemovePathName( const char *name )
{
    char *slash;
    char *root;

    if ( *name != NULL )
    {
        slash = strrchr( name, '/' );

        root = (char *)malloc( sizeof(char)*(strlen(name)+1));

        if ( slash != NULL )
            strcpy( root, ++slash );
        else
            strcpy( root, name );

        return( root );
    }

    fprintf( stderr, "Error: RemovePathName received NULL string!\n" );
    return ( (char *)name );
}

////////////////////////////////////////////////////////////////////
//     Function: GetSliderName
//       Access: Public
//  Description: Given a string, return that part of the string after 
//               the first occurence of '-' and before the last
//                 occurance of '.' 
////////////////////////////////////////////////////////////////////
char *soft2egg::
GetSliderName( const char *name )
{
    if ( name != NULL )
    {
        strstream newStr;
        char *hyphen;
        char *end;

        hyphen = strchr( name, '-' );

        // pull off stuff before first hyphen
        if (hyphen != NULL)
        {
            newStr << ++hyphen;
            end = newStr.str();
        }

        char *lastPeriod;

        lastPeriod = strrchr( end, '.' );

        // ignore stuff after last period
        if ( lastPeriod != NULL )
        {
            *lastPeriod = '\0';
        }

        if ( verbose >= 1 )
            fprintf( stdout, "slider name: '%s'\n", end );

        return( end );
    }
    
    return( (char *)name );
}

////////////////////////////////////////////////////////////////////
//     Function: GetName
//       Access: Public
//  Description: Given an element, return a copy of the element's 
//                 name WITHOUT prefix. 
////////////////////////////////////////////////////////////////////
char *soft2egg::
GetName( SAA_Scene *scene, SAA_Elem *element )
{
    int    nameLen;
    char *name;

    // get the name
    SAA_elementGetNameLength( scene, element, &nameLen ); 
    name = (char *)malloc(sizeof(char)*++nameLen);
    SAA_elementGetName( scene, element, nameLen, name );
    
    return name;
}

////////////////////////////////////////////////////////////////////
//     Function: GetFullName
//       Access: Public
//  Description: Given an element, return a copy of the element's 
//                 name complete with prefix. 
////////////////////////////////////////////////////////////////////
char *soft2egg::
GetFullName( SAA_Scene *scene, SAA_Elem *element )
{
    int    nameLen;
    char *name;

    // get the name
    SAA_elementGetNameLength( scene, element, &nameLen ); 
    name = (char *)malloc(sizeof(char)*++nameLen);
    SAA_elementGetName( scene, element, nameLen, name );
    
    int prefixLen;
    char *prefix;

    // get the prefix
    SAA_elementGetPrefixLength( scene, element, &prefixLen ); 
    prefix = (char *)malloc(sizeof(char)*++prefixLen);
    SAA_elementGetPrefix( scene, element, prefixLen, prefix );

    strstream fullNameStrm;

    // add 'em together
    fullNameStrm << prefix << "-" << name << ends;

    //free( name );
    //free( prefix );
    
    return fullNameStrm.str();
}

////////////////////////////////////////////////////////////////////
//     Function: GetModelNoteInfo
//       Access: Public
//  Description: Given an element, return a string containing the
//                 contents of its MODEL NOTE entry 
////////////////////////////////////////////////////////////////////
char *soft2egg::
GetModelNoteInfo( SAA_Scene *scene, SAA_Elem *model )
{

    int         size;
    char     *modelNote = NULL;
    SAA_Boolean bigEndian;


    SAA_elementGetUserDataSize( scene, model, "MNOT", &size );

    if ( size != 0 )
    {
        // allocate modelNote string
        modelNote = (char *)malloc(sizeof(char)*(size + 1));

        // get ModelNote data from this model
        SAA_elementGetUserData( scene, model, "MNOT", size, 
            &bigEndian, (void *)modelNote );

        //strip off newline, if present
        char *eol = strchr( modelNote, '\n' );
        if ( eol != NULL)
            *eol = '\0';
        else
            modelNote[size] = '\0';

        if ( verbose >= 1 )
            fprintf( outStream, "\nmodelNote = %s\n", 
                modelNote );    
    }

    return modelNote;
}


////////////////////////////////////////////////////////////////////
//     Function: MakeTableName
//       Access: Public
//  Description: Given a string, and a number, return a new string
//                 consisting of "string.number". 
////////////////////////////////////////////////////////////////////
char *soft2egg::
MakeTableName( const char *name, int number )
{
    strstream namestrm;

    namestrm << name << "." << number << ends;
    return namestrm.str();
}

////////////////////////////////////////////////////////////////////
//     Function: FindModelByName
//       Access: Public
//  Description: Given a string, find the model in the scene 
//                 whose name corresponds to the given string.                  
////////////////////////////////////////////////////////////////////
SAA_Elem *soft2egg::
FindModelByName( char *name, SAA_Scene *scene, SAA_Elem *models, 
    int numModels )
{
    char     *foundName;
    SAA_Elem *foundModel = NULL;
    
    for ( int model = 0; model < numModels; model++ )
    {
        foundName = GetName( scene, &models[model] );
    
        if ( !strcmp( name, foundName ) )
        {
            if ( verbose >= 1 )
                fprintf( outStream, "foundModel: '%s' = '%s'\n",
                    name, foundName );

            foundModel = &models[model];
            return( foundModel );
        }    
    }    

    fprintf( outStream, "findModelByName: failed to find model named: '%s'\n",
        name );

    return ( foundModel );    
}


////////////////////////////////////////////////////////////////////
//     Function: DepointellizeName
//       Access: Public
//  Description: Given a string, return the string up to the first
//                 period. 
////////////////////////////////////////////////////////////////////
char *soft2egg::
DepointellizeName( char *name )
{
    char    *endPtr;
    char    *newName;

    newName = (char *)malloc(sizeof(char)*(strlen(name)+1));
    sprintf( newName, "%s", name );

    endPtr = strchr( newName, '.' );
    if ( endPtr != NULL ) 
      *endPtr = '\0';

    return ( newName );
}


////////////////////////////////////////////////////////////////////
//     Function: ConvertTexture
//       Access: Public
//  Description: Given a string, return a copy of the string without
//               the leading file path, and make an rgb file of the
//                 same name in the tex_path directory. 
////////////////////////////////////////////////////////////////////
char *soft2egg::
ConvertTexture( SAA_Scene *scene, SAA_Elem *texture )
{
  char *fileName = NULL;
  int  fileNameLen = 0;

  // get the texture's name
  SAA_texture2DGetPicNameLength( scene, texture, &fileNameLen);

  if ( fileNameLen )
  {
	fileName = (char *)malloc(sizeof(char)*++fileNameLen);
	SAA_texture2DGetPicName( scene, texture, fileNameLen, fileName );
  }
	
  // make sure we are not being passed a NULL image, an empty image
  // string or the default image created by egg2soft
  if ( (fileName != NULL) && strlen( fileName ) && strcmp( fileName, 
        "/fat/people/gregw/new_test/PICTURES/default") &&
				( strstr( fileName, "noIcon" ) == NULL) )
  {
    char *texName = NULL;
    char *texNamePath = NULL;
    char *tmpName = NULL;
    char *fileNameExt = NULL;

    // strip off path and add .rgb
    tmpName = strrchr( fileName, '/' );

    if ( tmpName == NULL )
        tmpName = fileName;
    else
        tmpName++;

    float transp;

	// check for alpha
	SAA_texture2DGetTransparency( scene, texture, &transp );

	if ( transp != 0.0f ) {
        texName = (char *)malloc(sizeof(char)*(strlen(tmpName)+6));
        sprintf( texName, "%s.rgba", tmpName );
	} else {
        texName = (char *)malloc(sizeof(char)*(strlen(tmpName)+5));
        sprintf( texName, "%s.rgb", tmpName );
	}

    fileNameExt = (char *)malloc(sizeof(char)*(strlen(fileName)+5));
    sprintf( fileNameExt, "%s.pic", fileName ); 

    if ( verbose >= 1 )
        fprintf( outStream, "Looking for texture file: '%s'\n", fileNameExt );

    // try to make conversion of file
    int found_file = ( access( fileNameExt, F_OK ) == 0);
    
    if ( found_file )
    {
        if ( tex_path )
        {
            texNamePath = (char *)malloc(sizeof(char)*(strlen(tex_path) +
                strlen(texName) + 2));

            sprintf( texNamePath, "%s/%s", tex_path, texName );    

            if ( texFile )
                texFile << texNamePath << ": " << fileNameExt << "\n";

            // make sure conversion doesn't already exist    
            if ( (access( texNamePath, F_OK ) != 0)  && !texFile )    
            {
                char *command = (char *)malloc(sizeof(char)*
                    (strlen(fileNameExt) + strlen(texNamePath) + 20));

                sprintf( command, "image-resize -1 %s %s",  
                    fileNameExt, texNamePath );

                if ( verbose >=1 )
                    fprintf( outStream, "executing %s\n", command );
            
                system( command );    

                //free( command );
            }
            else
                if ( verbose >=1 )
                    fprintf( outStream, "%s already exists!\n", texNamePath );
        }
        else
        {
            if ( verbose >= 1 )
            {
                fprintf( outStream, "Warning: No texture path defined" );
                fprintf( outStream, " - No automatic conversion performed\n" );
            }
        }
    }
    else
    {
        fprintf( outStream, "Warning: Couldn't find texture file: %s\n", 
            fileNameExt );
    }

    //free( fileNameExt );

    if (tex_path)
        return( texNamePath );
    else
        return( texName );
  }
  else
  {
    fprintf( outStream, "Warning: ConvertTexture received NULL fileName\n" );
    return( NULL );
  }
}

////////////////////////////////////////////////////////////////////
//     Function: FindClosestTriVert
//       Access: Public
//  Description: Given an egg vertex pool, map each vertex therein to 
//                 a vertex within an array of SAA model vertices of
//                 size numVert. Mapping is done by closest proximity.
////////////////////////////////////////////////////////////////////
int *soft2egg::
FindClosestTriVert( EggVertexPool *vpool, SAA_DVector *vertices, int numVert )
{
    int    *vertMap = NULL; 
    int     vpoolSize = vpool->NumVertices();
    int     i,j;
    float   thisDist;
    float   closestDist;
    int     closest;
    

    vertMap = (int *)malloc(sizeof(int)*vpoolSize);

    // for each vertex in vpool
    for ( i = 0; i < vpoolSize; i++ )
    {
        // find closest model vertex 
        for ( j = 0; j < numVert-1; j++ ) 
        {
            // calculate distance
            thisDist =    sqrtf( 
                powf( vpool->Vertex(i)->position[0] - vertices[j].x , 2 ) + 
                powf( vpool->Vertex(i)->position[1] - vertices[j].y , 2 ) + 
                powf( vpool->Vertex(i)->position[2] - vertices[j].z , 2 ) ); 

            // remember this if its the closest so far
            if ( !j || ( thisDist < closestDist ) )
            {
                closest = j;
                closestDist = thisDist;
            }
        } 
        vertMap[i] = closest;

        if ( verbose >= 2 )
        {
            fprintf( outStream, "mapping v %d of %d:( %f, %f, %f )\n", i, 
                vpoolSize, vpool->Vertex(i)->position[0], 
                vpool->Vertex(i)->position[1],
                vpool->Vertex(i)->position[2] ); 
            fprintf( outStream, "to cv %d of %d:( %f, %f, %f )\tdelta = %f\n",
                closest, numVert-1, vertices[closest].x, vertices[closest].y, 
                vertices[closest].z, closestDist );
        }
		}

   	return( vertMap );
}


////////////////////////////////////////////////////////////////////
//     Function: MakeIndexMap
//       Access: Public
//  Description: Given an array of indices that is a map from one
//                 set of vertices to another, return an array that
//                 performs the reverse mapping of the indices array 
////////////////////////////////////////////////////////////////////
int *soft2egg::
MakeIndexMap( int *indices, int numIndices, int mapSize )
{
    int i, j;

    // allocate map array
    int *map = (int *)malloc(sizeof(int)*mapSize); 

    if ( map != NULL )
    {
        for ( i = 0; i < mapSize; i++ )
        {
            j = 0;
            int found = 0;
            while( j < numIndices )
            {
                if ( indices[j] == i )
                {
                    map[i] = j;
                    if ( verbose >= 2 )
                        fprintf( outStream, "map[%d] = %d\n", i, map[i] );
                    found = 1;
                    break;
                }
                j++;
            }
            if ( !found) 
            {
                if ( verbose >= 2 )
                    fprintf( outStream, "Warning: orphan vertex (%d)\n", i );
                // default to -1 for now
                map[i] = -1;
            }
        }
    }
    else
        fprintf( outStream, "Not enough Memory for index Map...\n");
    

    return( map );
}

    
////////////////////////////////////////////////////////////////////
//     Function: findShapeVert
//       Access: Public
//  Description: given a vertex, find its corresponding shape vertex
//                 and return its index.
////////////////////////////////////////////////////////////////////
int     soft2egg::
findShapeVert( SAA_DVector vertex, SAA_DVector *vertices, int numVert )
{
    int i;
    int found = 0;

    for ( i = 0; i < numVert && !found ; i++ )
    {
        if ( ( vertex.x == vertices[i].x ) && 
             ( vertex.y == vertices[i].y ) && 
             ( vertex.z == vertices[i].z ) )
        {
            found = 1;

            if ( verbose >= 2)
                fprintf( outStream, "found shape vert at index %d\n", i );
        }
             
    } 

    if (!found )
        i = -1;
    else
        i--;

    return( i );
}


////////////////////////////////////////////////////////////////////
//     Function: LoadSoft
//       Access: Public
//  Description: Open the SI database and grab the scene & model info
////////////////////////////////////////////////////////////////////
void soft2egg::
LoadSoft()
{
    int      i;

    if ( (scene_name == NULL && model_name == NULL) || database_name == NULL )
    {
        Usage();
        exit( 1 );
    }

    if ((result = SAA_Init(rsrc_path, FALSE)) != SI_SUCCESS)
    {
        fprintf( outStream, "Error: Couldn't get resource path!\n");
        exit( 1 );
    }

    if ((result = SAA_databaseLoad(database_name, &database)) != SI_SUCCESS)
    {
        fprintf( outStream, "Error: Couldn't load database!\n");
        exit( 1 );
    }

    if ((result = SAA_sceneGetCurrent(&scene)) == SI_SUCCESS)
    {
        // load scene if present
        if ( scene_name != NULL )
        {
            SAA_sceneLoad( &database, scene_name, &scene );

            // if no egg filename specified, make up a name
            if ( eggFileName == NULL )
            {
                eggFileName = (char *)malloc(sizeof(char)*
                    (strlen( scene_name ) + 14 ));
                sprintf( eggFileName, "%s", DepointellizeName(scene_name) );
                if ( make_nurbs )
                    strcat( eggFileName, "-nurb" );
                strcat( eggFileName, "-mod.egg" );
            }

            // open an output file for the geometry if necessary 
            if ( make_poly || make_nurbs )
            {
                unlink( eggFileName );
                eggFile.open( eggFileName, ios::out, 0666 );

                if ( !eggFile )
                {
                    fprintf( outStream, "Couldn't open output file: %s\n", 
                        eggFileName ); 
                    exit( 1 );
                }
            }

            // open an output file for texture list if specified
            if ( tex_filename != NULL )
            {
                unlink( tex_filename );
                texFile.open( tex_filename, ios::out, 0666 );
                
                if ( !texFile )
                {    
                    fprintf( outStream, "Couldn't open output file: %s\n",
                        tex_filename );
                    exit( 1 );
                }
            }

            if ( SAA_updatelistGet( &scene ) == SI_SUCCESS )
            {
                float time;

                fprintf( outStream, "setting Scene to frame %d...\n", pose_frame );
                //SAA_sceneSetPlayCtrlCurrentFrame( &scene, pose_frame );
                SAA_frame2Seconds( &scene, pose_frame, &time );
                SAA_updatelistEvalScene( &scene, time );
                sginap( 100 );
                SAA_updatelistEvalScene( &scene, time );
                if ( make_pose )
                    SAA_sceneFreeze( &scene );
            } 

            int            numModels;
            SAA_Elem    *models;

            SAA_sceneGetNbModels( &scene, &numModels ); 
            fprintf( outStream, "Scene has %d model(s)...\n", numModels );
        
            if ( numModels )
            {
                // allocate array of models
                models = (SAA_Elem *)malloc(sizeof(SAA_Elem)*numModels);

                if ( models != NULL )
                {
                    char *rootName = GetRootName( eggFileName );
                    
                
                    if ( eggGroupName == NULL )    
                        dart = _data.CreateGroup( NULL, rootName );
                    else
                        dart = _data.CreateGroup( NULL, eggGroupName );

                    if (make_dart)
                      dart->flags |= EF_DART;

                    AnimGroup    *rootTable;

                    rootTable = animData.CreateTable( NULL, eggFileName );    

                    if ( eggGroupName == NULL )
                        animRoot = animData.CreateBundle( rootTable, rootName );
                    else
                        animRoot = animData.CreateBundle( rootTable, 
                            eggGroupName );

                    // propagate commet to anim data
                    animData.root_group.children.push_front(
                        new EggComment( _commandLine ) );

                    if ( verbose >= 1 )
                        fprintf( outStream, "made animRoot: %s\n", rootName );

                    SAA_sceneGetModels( &scene, numModels, models );

                    for ( i = 0; i < numModels; i++ )
                    {
                        int level;

                        SAA_elementGetHierarchyLevel( &scene, &models[i], &level );
                        if ( !level )
                        {
                            if ( verbose >= 1 )
                                fprintf( outStream,
                                    "\negging scene model[%d]\n", i );

                            MakeEgg( dart, NULL, NULL,  &scene, &models[i] );
                        }
                    }

            if ( make_poly || make_nurbs )
            {
                // generate soft skinning assignments if desired
                //
                //disabled 1/1/99 to streamline joint assignments.
                // all joint assignments now done here. Hard & Soft.
                //if ( make_soft)
                {
                    char        *name;
                    char        *fullname;
                    SAA_Boolean isSkeleton;

                    // search through models and look for skeleton parts
                    for ( i = 0; i < numModels; i++ )
                    {
                        SAA_modelIsSkeleton( &scene, &models[i], &isSkeleton );

                        // get fullname for splitting files, but
                        // only use it in file if requested
                        fullname = GetFullName( &scene, &models[i] );
                        if ( use_prefix )
                            name = fullname;
                        else
                            name = GetName( &scene, &models[i] );

                        // split
                        if ( strstr( fullname, search_prefix ) != NULL )
                        {
                            // for every skel part: get soft skin info 
                            if ( isSkeleton )
                                MakeSoftSkin( &scene, &models[i], models,
                                    numModels, name );
                        }

                        //free( name );
                    }

                    // make sure all vertices were assigned
                    // via soft skinning - if not hard assign them
                    for ( i = 0; i < numModels; i++ )
                    {
                        // get fullname for splitting files, but
                        // only use it in file if requested
                        fullname = GetFullName( &scene, &models[i] );
                        if ( use_prefix )
                            name = fullname;
                        else
                            name = GetName( &scene, &models[i] );

                        // split
                        if ( strstr( fullname, search_prefix ) != NULL )
                            CleanUpSoftSkin( &scene, &models[i], name );

                        //free( name );
                    }

                }


                // put the skeleton data into the egg data
                dart->StealChildren( *skeleton );

                // make sure all elements have unique names
                _data.UniquifyNames();
    
                // write out the geometry data if requested    
                //if ( make_poly || make_nurbs )
                //{
                    eggFile << _data << "\n";
                    fprintf( outStream, "\nwriting out %s...\n", eggFileName );
                    eggFile.close();
                }

                // close texture list file if opened
                if ( texFile )
                    texFile.close();

                // generate animation data if desired
                if ( make_anim )
                {
                if ( animFileName == NULL )
                {
                    animFileName = (char *)malloc(sizeof(char)*
                        (strlen(scene_name)+ 10 ));
                    sprintf( animFileName, "%s", DepointellizeName(scene_name) );
                    strcat( animFileName, "-chan.egg" );
                }

                unlink( animFileName );
                animFile.open( animFileName, ios::out, 0666 );

                if ( !animFile )
                {
                    fprintf( outStream, "Couldn't open output file: %s\n", 
                        animFileName ); 
                    exit( 1 );
                }

                int frame;
                //int frameStep;
                float time;

                // get all the animation frame info if not specified
                // on the command line
                if (anim_start == -1000) 
                    SAA_sceneGetPlayCtrlStartFrame( &scene, &anim_start );

                if (anim_end == -1000)
                    SAA_sceneGetPlayCtrlEndFrame( &scene, &anim_end );

                //SAA_sceneGetPlayCtrlFrameStep( &scene, &frameStep );

                fprintf( outStream, "\nframeStart = %d\n", anim_start );
                fprintf( outStream, "frameEnd = %d\n", anim_end );
                //fprintf( outStream, "frameStep = %d\n", frameStep );

                // start at first frame and go to last
                for ( frame = anim_start; frame <= anim_end; 
                        frame += 1)
                {
                    SAA_frame2Seconds( &scene, frame, &time );
                    SAA_updatelistEvalScene( &scene, time );
                    sginap( 100 );
                    SAA_updatelistEvalScene( &scene, time );
                    fprintf( outStream, "\n> animating frame %d\n", frame );

                    // for each model
                    for ( i = 0; i < numModels; i++ )
                    {
                        char           *name;
                        char           *fullname;
                        SAA_Boolean     isSkeleton;
                        SAA_ModelType     type;

                        SAA_modelIsSkeleton( &scene, &models[i], &isSkeleton );

                        // get fullname for splitting files, but
                        // only use it in file if requested
                        fullname = GetFullName( &scene, &models[i] );
                        if ( use_prefix )
                            name = fullname;
                        else
                            name = GetName( &scene, &models[i] );

                        // split
                        if ( strstr( fullname, search_prefix ) != NULL )
                        {
                            // make the morph table for this critter
                            if ( make_morph )
                            {
                                MakeMorphTable( &scene, &models[i], models,
                                    numModels, name, time );
                            }
                        }

                        // find out what type of node we're dealing with
                        result = SAA_modelGetType( &scene, &models[i], &type );

                        int size;

                        // check for uv texture animation
                        SAA_elementGetUserDataSize( &scene, &models[i], 
                            "TEX_OFFSETS", &size );

                        // if so, update for this frame if desired
                        if ( ( size != 0 ) && make_duv )    
                            MakeTexAnim( &scene, &models[i], name );

                        // if we have a skeleton or something that acts
                        // like one - build anim tables
                        if ( isSkeleton  ||
                            ( strstr( name, "joint") != NULL ) )
                                MakeAnimTable( &scene, &models[i], name ); 

                        //free( name );
                    }

                    if ( verbose >= 1 )
                        fprintf( outStream, "\n" );
                }

                animFile << animData << "\n";
                fprintf( outStream, "\nwriting out %s...\n", animFileName );
                animFile.close();
                }

                //free( models );

                }
                else
                    fprintf( outStream, "Error: Not enough Memory for models...\n");
            }
        }
        // otherwise try to load a model
        else if ( model_name != NULL )
        {

            if ( eggFileName == NULL )
            {    
                eggFileName = 
                    (char *)malloc(sizeof(char)*(strlen( model_name )+13));
                sprintf( eggFileName, "%s", DepointellizeName( model_name ) );

                if ( make_nurbs )
                    strcat( eggFileName, "-nurb" );
                strcat( eggFileName, "-mod.egg" );
            }

            eggFile.open( eggFileName );    

            if ( !eggFile )
            {
                fprintf( outStream, "Couldn't open output file: %s\n", 
                    eggFileName ); 
                exit( 1 );
            }

            if ((result = 
                SAA_elementLoad(&database, &scene, model_name, &model))
                == SI_SUCCESS)
            {
                fprintf( outStream, "Loading single model...\n");
                MakeEgg( NULL, NULL, NULL,  &scene, &model );
            }

            eggFile << _data << "\n";
        }
    }

}

////////////////////////////////////////////////////////////////////
//     Function: MakeEgg
//       Access: Public
//  Description: Make egg geometry from a given model. This include
//                 textures, tex coords, colors, normals, and joints. 
////////////////////////////////////////////////////////////////////
void soft2egg::
MakeEgg( EggGroup *parent, EggJoint *lastJoint, AnimGroup *lastAnim,
            SAA_Scene *scene, SAA_Elem *model )
{
    char        *name;
    char        *fullname;
    SAA_ModelType type;
    int         id = 0;
    int         numShapes;
    int         numTri;
    int         numVert;
    int         numTexLoc = 0;
    int         numTexGlb = 0;
    int         i, j;
    float        matrix[4][4];
    float        *uScale = NULL; 
    float        *vScale = NULL;
    float        *uOffset = NULL;
    float        *vOffset = NULL;
    SAA_Boolean    uv_swap = FALSE;
    void        *relinfo;
    SAA_SubElem *triangles = NULL;
    SAA_Elem    *materials = NULL;
    SAA_SubElem *cvertices = NULL;
    SAA_DVector *cvertPos = NULL;
    SAA_DVector *vertices = NULL;
    SAA_DVector *normals = NULL;
    int            *indices = NULL;
    int            *indexMap = NULL;
    int            *numTexTri = NULL;
    SAA_Elem    *textures = NULL;
    char        **texNameArray;
    float        *uCoords = NULL;
    float        *vCoords = NULL;
    SAA_GeomType gtype = SAA_GEOM_ORIGINAL;
    SAA_Boolean    visible;

    /////////////////////////////////////////////////
    // find out what type of node we're dealing with
    /////////////////////////////////////////////////
    result = SAA_modelGetType( scene, model, &type );

    if ( verbose >= 1 )
    {
        if ( type == SAA_MNILL )
            fprintf( outStream, "encountered null\n"); 
        else if ( type == SAA_MPTCH )
            fprintf( outStream, "encountered patch\n" );
        else if ( type == SAA_MFACE )
            fprintf( outStream, "encountered face\n" );
        else if ( type == SAA_MSMSH )
            fprintf( outStream, "encountered mesh\n" );
        else if ( type == SAA_MJNT )
            fprintf( outStream, "encountered joint\n" );
        else if ( type == SAA_MSPLN )
            fprintf( outStream, "encountered spline\n" );
        else if ( type == SAA_MMETA )
            fprintf( outStream, "encountered meta element\n" );
        else if ( type == SAA_MBALL )
            fprintf( outStream, "encountered metaball\n" );
        else if ( type == SAA_MNCRV )
            fprintf( outStream, "encountered nurb curve\n" );
        else if ( type == SAA_MNSRF )
            fprintf( outStream, "encountered nurbs surf\n" );
        else 
            fprintf( outStream, "encountered unknown type: %d\n", type );
    }

    /////////////////////////////
    // Get the name of the model
    /////////////////////////////

    // Get the FULL name of the model
    fullname = GetFullName( scene, model );

    if ( use_prefix )
    {
        // Get the FULL name of the trim curve
        name = fullname; 
    }
    else
    {
        // Get the name of the trim curve
        name = GetName( scene, model );
    }

    if ( verbose >= 1 )
        fprintf( outStream, "element name <%s>\n", name );

    fflush( outStream );

    // get the model's matrix    
    SAA_modelGetMatrix( scene, model, SAA_COORDSYS_GLOBAL,  matrix );

    if ( verbose >= 2 )
    {
        fprintf( outStream, "model matrix = %f %f %f %f\n", matrix[0][0],
             matrix[0][1],  matrix[0][2],  matrix[0][3] ); 
        fprintf( outStream, "model matrix = %f %f %f %f\n", matrix[1][0],
             matrix[1][1],  matrix[1][2],  matrix[1][3] ); 
        fprintf( outStream, "model matrix = %f %f %f %f\n", matrix[2][0],
             matrix[2][1],  matrix[2][2],  matrix[2][3] ); 
        fprintf( outStream, "model matrix = %f %f %f %f\n", matrix[3][0],
             matrix[3][1],  matrix[3][2],  matrix[3][3] ); 
    }
    
    ///////////////////////////////////////////////////////////////////////
    // check to see if this is a branch we don't want to descend - this
    // will prevent creating geometry for animation control structures
    ///////////////////////////////////////////////////////////////////////
    if ( (strstr( name, "con-" ) == NULL) && 
         (strstr( name, "con_" ) == NULL) && 
         (strstr( name, "fly_" ) == NULL) && 
         (strstr( name, "fly-" ) == NULL) && 
         (strstr( name, "camRIG" ) == NULL) &&
         (strstr( name, "bars" ) == NULL) && 
         // split
         (strstr( fullname, search_prefix ) != NULL) )
    {

    // if making a pose - get deformed geometry
    if ( make_pose )
        gtype = SAA_GEOM_DEFORMED;
        
    // Get the number of key shapes
    SAA_modelGetNbShapes( scene, model, &numShapes );
    if ( verbose >= 1 )
        fprintf( outStream, "MakeEgg: num shapes: %d\n", numShapes);

    ///////////////////////////////////////////////////////////////////////
    // if multiple key shapes exist create table entries for each
    ///////////////////////////////////////////////////////////////////////
    if ( (numShapes > 0) && make_morph )
    {
        has_morph = 1;

        // make sure root morph table exists
        if ( morphRoot == NULL )
            morphRoot = animData.CreateTable( animRoot, "morph" ); 
    
        char   *tableName;

        // create morph table entry for each key shape
        // (start at second shape - as first is the original geometry)
        for ( i = 1; i < numShapes; i++ )
        {
            tableName = MakeTableName( name, i );
            SAnimTable *table = new SAnimTable( );     
            table->name = tableName;
            table->fps = anim_rate;
            morphRoot->children.push_back( table );
            if ( verbose >= 1 )
                fprintf( outStream, "created table named: '%s'\n", tableName );    
        }

        //free( tableName );
    }

    SAA_modelGetNodeVisibility( scene, model, &visible ); 
    if ( verbose >= 1 )
        fprintf( outStream, "model visibility: %d\n", visible ); 
    
    ///////////////////////////////////////////////////////////////////////
    // Only create egg polygon data if: the node is visible, and its not
    // a NULL or a Joint, and we're outputing polys (or if we are outputing 
    // NURBS and the model is a poly mesh or a face) 
    ///////////////////////////////////////////////////////////////////////
    if ( visible &&  
         (type != SAA_MNILL) &&
         (type != SAA_MJNT) && 
         ((make_poly || 
         (make_nurbs && ((type == SAA_MSMSH) || (type == SAA_MFACE )) )) 
         || (!make_poly && !make_nurbs && make_duv && 
            ((type == SAA_MSMSH) || (type == SAA_MFACE )) ))
       )
    {
      // If the model is a NURBS in soft, set its step before tesselating
      if ( type == SAA_MNSRF )
        SAA_nurbsSurfaceSetStep( scene, model, nurbs_step, nurbs_step );

      // If the model is a PATCH in soft, set its step before tesselating
      else if ( type == SAA_MPTCH )
        SAA_patchSetStep( scene, model, nurbs_step, nurbs_step );
 
      // Get the number of triangles    
      result = SAA_modelGetNbTriangles( scene, model, gtype, id, &numTri);
      if ( verbose >= 1 )
          fprintf( outStream, "triangles: %d\n", numTri);

      if ( result != SI_SUCCESS )
      {
        if ( verbose >= 1 ) {
            fprintf( outStream, 
            "Error: couldn't get number of triangles!\n" );
            fprintf( outStream, "\tbailing on model: '%s'\n", name );
        }
        return;    
      }

      // check to see if surface is also skeleton...
      SAA_Boolean isSkeleton = FALSE;

      SAA_modelIsSkeleton( scene, model, &isSkeleton );

      // check to see if this surface is used as a skeleton
      // or is animated via constraint only ( these nodes are
      // tagged by the animator with the keyword "joint"
      // somewhere in the nodes name)
      if ( isSkeleton || (strstr( name, "joint" ) != NULL) )
      {
          if ( verbose >= 1 )
              fprintf( outStream, "animating Polys as joint!!!\n" );

            MakeJoint( scene, lastJoint, lastAnim, model, name );
      }
    
      // model is not a null and has no triangles!
      if ( !numTri )
      {
        if ( verbose >= 1 )
            fprintf( outStream, "no triangles!\n"); 
      }
      else 
      {
        // allocate array of triangles
        triangles = (SAA_SubElem *)malloc(sizeof(SAA_SubElem)*numTri);
        if ( triangles != NULL )
        {
            // triangulate model and read the triangles into array
            SAA_modelGetTriangles( scene, model, gtype, id, numTri, triangles );
        }
        else
            fprintf( outStream, "Not enough Memory for triangles...\n");

        // allocate array of materials
        materials = (SAA_Elem *)malloc(sizeof(SAA_Elem)*numTri);
        if ( materials != NULL )
        {
            // read each triangle's material into array  
            SAA_triangleGetMaterials( scene, model, numTri, triangles, 
                materials );
        }
        else
            fprintf( outStream, "Not enough Memory for materials...\n");

        // allocate array of textures per triangle
        numTexTri = (int *)malloc(sizeof(int)*numTri);

        // find out how many local textures per triangle
        for ( i = 0; i < numTri; i++ )
        {    
            result = SAA_materialRelationGetT2DLocNbElements( scene, 
							&materials[i], FALSE, &relinfo, &numTexTri[i] );

            // polytex    
            if ( result == SI_SUCCESS )
                numTexLoc += numTexTri[i];
        }

        // don't need this anymore...
        //free( numTexTri ); 

        // get local textures if present
        if ( numTexLoc )
        {
            // ASSUME only one texture per material
            textures = (SAA_Elem *)malloc(sizeof(SAA_Elem)*numTri);

            for ( i = 0; i < numTri; i++ )
            {
                // and read all referenced local textures into array
                SAA_materialRelationGetT2DLocElements( scene, &materials[i],
                    TEX_PER_MAT , &textures[i] ); 
            }

            if ( verbose >= 1 )
                fprintf( outStream, "numTexLoc = %d\n", numTexLoc);    
        }
        // if no local textures, try to get global textures
        else
        {
            SAA_modelRelationGetT2DGlbNbElements( scene, model,
                FALSE, &relinfo, &numTexGlb );

            if ( numTexGlb )
            {
                // ASSUME only one texture per model
                textures = (SAA_Elem *)malloc(sizeof(SAA_Elem));

                // get the referenced texture
                SAA_modelRelationGetT2DGlbElements( scene, model, 
                    TEX_PER_MAT, textures ); 

                if ( verbose >= 1 )
                    fprintf( outStream, "numTexGlb = %d\n", numTexGlb);    
            }
        }

        // allocate array of control vertices 
        cvertices = (SAA_SubElem *)malloc(sizeof(SAA_SubElem)*numTri*3);
        if ( cvertices != NULL )
        {
            // read each triangle's control vertices into array
            SAA_triangleGetCtrlVertices( scene, model, gtype, id,
                numTri, triangles, cvertices );

            if ( verbose >= 2 )
            {
                cvertPos = (SAA_DVector *)malloc(sizeof(SAA_DVector)*numTri*3);
                SAA_ctrlVertexGetPositions(  scene, model, numTri*3, 
                    cvertices, cvertPos);

                for ( i=0; i < numTri*3; i++ )
                {
                    fprintf( outStream, "cvert[%d] = %f %f %f %f\n", i, 
                        cvertPos[i].x, cvertPos[i].y, cvertPos[i].z, 
                        cvertPos[i].w );
                }
            }
        }
        else
            fprintf( outStream, "Not enough Memory for control vertices...\n");

        // allocate array of control vertex indices
        // this array maps from the redundant cvertices array into
        // the unique vertices array (cvertices->vertices)
        indices = (int *)malloc(sizeof(int)*numTri*3);
        if ( indices != NULL )
        {
            for ( i=0; i < numTri*3; i++ )
                indices[i] = 0;

            SAA_ctrlVertexGetIndices( scene, model, numTri*3, 
                cvertices, indices );
       
            if ( verbose >= 2 ) 
                for ( i=0; i < numTri*3; i++ )
                    fprintf( outStream, "indices[%d] = %d\n", i, indices[i] );
        }
        else
            fprintf( outStream, "Not enough Memory for indices...\n");

        // get number of UNIQUE vertices in model
        SAA_modelGetNbTriVertices( scene, model, &numVert );

        if ( verbose >= 2 )
            fprintf( outStream, "num unique verts = %d\n", numVert );

        //allocate array of vertices
        vertices = (SAA_DVector *)malloc(sizeof(SAA_DVector)*numVert);

        // get the UNIQUE vertices of all triangles in model
        SAA_modelGetTriVertices( scene, model, numVert, vertices );

        if ( verbose >= 2 )
        {
            for ( i=0; i < numVert; i++ )
            {
                fprintf( outStream, "vertices[%d] = %f ", i, vertices[i].x );
                fprintf( outStream, "%f %f %f\n", vertices[i].y, 
                vertices[i].z, vertices[i].w );
            }
        }

        // allocate indexMap array
        // we contruct this array to map from the unique vertices
        // array to the redundant cvertices array - it will save
        // us from doing repetitive searches later
        indexMap = MakeIndexMap( indices, numTri*3, numVert ); 

        // allocate array of normals
        normals = (SAA_DVector *)malloc(sizeof(SAA_DVector)*numTri*3);
        if ( normals != NULL )
        {
            // read each control vertex's normals into an array
            SAA_ctrlVertexGetNormals( scene, model, numTri*3,
                cvertices, normals );
        }
        else
            fprintf( outStream, "Not enough Memory for normals...\n");

        if ( verbose >= 2 )
        {
            for ( i=0; i<numTri*3; i++ )
                fprintf( outStream, "normals[%d] = %f %f %f %f\n", i,
                    normals[i].x, normals[i].y, normals[i].z, normals[i].w );
        }

        int uRepeat, vRepeat;

        // make sure we have textures before we get t-coords
        if ( numTexLoc )
        {
            // allocate arrays for u & v coords
            uCoords = (float *)malloc(sizeof(float)*numTri*numTexLoc*3); 
            vCoords = (float *)malloc(sizeof(float)*numTri*numTexLoc*3); 
          
            // read the u & v coords into the arrays
            if ( uCoords != NULL && vCoords != NULL)
            {
              for ( i = 0; i < numTri*numTexLoc*3; i++ )
                uCoords[i] = vCoords[i] = 0.0f;
 
                SAA_ctrlVertexGetUVTxtCoords( scene, model, numTri*3,
                    cvertices, numTexLoc*3, uCoords, vCoords );
            }
            else
                fprintf( outStream, "Not enough Memory for texture coords...\n");

            if ( verbose >= 2 )
            {
                for ( i=0; i<numTexLoc*3; i++ )
                    fprintf( outStream, "texcoords[%d] = ( %f , %f )\n", i, 
                        uCoords[i], vCoords[i] );
            }

            // allocate arrays of texture info
            uScale = ( float *)malloc(sizeof(float)*numTri);
            vScale = ( float *)malloc(sizeof(float)*numTri);
            uOffset = ( float *)malloc(sizeof(float)*numTri);
            vOffset = ( float *)malloc(sizeof(float)*numTri);
            texNameArray = ( char **)malloc(sizeof(char *)*numTri);

            for ( i = 0; i < numTri; i++ )
            {
                // initialize the array value
                texNameArray[i] = NULL;

                SAA_Boolean    valid = FALSE;
                // check to see if texture is present
                result = SAA_elementIsValid( scene, &textures[i], &valid );
               
                if ( result != SI_SUCCESS )
                    fprintf( outStream, "SAA_elementIsValid failed!!!!\n" );
 
                // texture present - get the name and uv info 
                if ( valid )
                {
                    texNameArray[i] = ConvertTexture( scene, &textures[i] );
                   
                    if ( verbose >= 2 ) 
                        fprintf( outStream, " tritex[%d] named: %s\n", i, 
                            texNameArray[i] );

                    SAA_texture2DGetUVSwap( scene, &textures[i], &uv_swap );

                    if ( verbose >= 2 )
                        if ( uv_swap == TRUE )
                            fprintf( outStream, " swapping u and v...\n" );

                    SAA_texture2DGetUScale( scene, &textures[i], &uScale[i] );
                    SAA_texture2DGetVScale( scene, &textures[i], &vScale[i] );
                    SAA_texture2DGetUOffset( scene, &textures[i], &uOffset[i] );
                    SAA_texture2DGetVOffset( scene, &textures[i], &vOffset[i] );

                    if ( verbose >= 2 )
                    {    
                        fprintf(outStream, "tritex[%d] uScale: %f vScale: %f\n",                             i, uScale[i], vScale[i] );
                        fprintf(outStream, " uOffset: %f vOffset: %f\n", 
                            uOffset[i], vOffset[i] );
                    }


                    SAA_texture2DGetRepeats(  scene, &textures[i], &uRepeat,
                        &vRepeat );

                    if ( verbose >= 2 )
                    {
                        fprintf(outStream, "uRepeat = %d, vRepeat = %d\n",
                            uRepeat, vRepeat );
                    }
                }
                else 
                {
                    if ( verbose >= 2 ) 
                    {
                        fprintf( outStream, "Invalid texture...\n");
                        fprintf( outStream, " tritex[%d] named: (null)\n", i );
                    }
                }
            }

            //debug
            //for ( i = 0; i < numTri; i++ )
            //{
                //if ( texNameArray[i] != NULL )
                    //fprintf( outStream, " tritex[%d] named: %s\n", i, 
                        //texNameArray[i] );
                //else
                    //fprintf( outStream, " tritex[%d] named: (null)\n", i );
            //}
        }
        // make sure we have textures before we get t-coords
        else if ( numTexGlb )
        {
            SAA_Boolean    valid;

            // check to see if texture is present
            SAA_elementIsValid( scene, textures, &valid );
                
            // texture present - get the name and uv info 
            if ( valid )
            {
                SAA_texture2DGetUVSwap( scene, textures, &uv_swap );

                if ( verbose >= 1 )
                    if ( uv_swap == TRUE )
                        fprintf( outStream, " swapping u and v...\n" );

                // allocate arrays for u & v coords
                uCoords = (float *)malloc(sizeof(float)*numTri*numTexGlb*3); 
                vCoords = (float *)malloc(sizeof(float)*numTri*numTexGlb*3); 

                for ( i = 0; i < numTri*numTexGlb*3; i++ )
                {
                    uCoords[i] = vCoords[i] = 0.0f;
                }                
                    
                // read the u & v coords into the arrays
                if ( uCoords != NULL && vCoords != NULL)
                {
                    SAA_triCtrlVertexGetGlobalUVTxtCoords( scene, model, 
                        numTri*3, cvertices, numTexGlb, textures, 
                        uCoords, vCoords );
                }
                else
                    fprintf( outStream, "Not enough Memory for texture coords...\n");

                if ( verbose >= 2 )
                  {
                    for ( i=0; i<numTri*numTexGlb*3; i++ )
                      fprintf( outStream, "texcoords[%d] = ( %f , %f )\n", i, 
                               uCoords[i], vCoords[i] );
                  }
                
                texNameArray = ( char **)malloc(sizeof(char *));
                *texNameArray = ConvertTexture( scene, textures );

                if ( verbose >= 1 )    
                    fprintf( outStream, " global tex named: %s\n", 
                        texNameArray );
        
                // allocate arrays of texture info
                uScale = ( float *)malloc(sizeof(float));
                vScale = ( float *)malloc(sizeof(float));
                uOffset = ( float *)malloc(sizeof(float));
                vOffset = ( float *)malloc(sizeof(float));

                SAA_texture2DGetUScale( scene, textures, uScale );
                SAA_texture2DGetVScale( scene, textures, vScale );
                SAA_texture2DGetUOffset( scene, textures, uOffset );
                SAA_texture2DGetVOffset( scene, textures, vOffset );

                if ( verbose >= 1 ) 
                {
                    fprintf( outStream, " global tex uScale: %f vScale: %f\n", 
                        *uScale, *vScale );
                    fprintf( outStream, " uOffset: %f vOffset: %f\n", 
                        *uOffset, *vOffset );
                }

                SAA_texture2DGetRepeats(  scene, textures, &uRepeat,
                    &vRepeat );

                if ( verbose >= 2 )
                {
                    fprintf(outStream, "uRepeat = %d, vRepeat = %d\n",
                        uRepeat, vRepeat );
                }
            }
            else fprintf( outStream, "Invalid texture...\n");
        }

        // make the egg vertex pool 
        EggVertexPool *pool = _data.CreateVertexPool( parent, name );

        for ( i = 0; i < numVert; i++ )
        {
            pfVec3    eggVert; 
            pfVec3    eggNorm; 
            
            //convert to global coords
            SAA_DVector local = vertices[i];
            SAA_DVector global;

            _VCT_X_MAT( global, local, matrix );

            // set vertices array to reflect global coords
            //vertices[i].x = global.x;
            //vertices[i].y = global.y;
            //vertices[i].z = global.z;

            //eggVert.set( vertices[i].x, vertices[i].y, vertices[i].z );

            // we'll preserve original verts for now
            eggVert.set( global.x, global.y, global.z );

            local = normals[indexMap[i]];

            _VCT_X_MAT( global, local, matrix );

            eggNorm.set( global.x, global.y, global.z );
            eggNorm.normalize();

            pool->AddVertex( eggVert, i );
            pool->Vertex(i)->attrib.SetNormal( eggNorm );

            // translate local uv's to global and add to vertex pool
            if ( numTexLoc && (uCoords != NULL && vCoords !=NULL ))
            {
                float u, v;

                if ( ignore_tex_offsets ) {
                  u = uCoords[indexMap[i]];
                  v = 1.0f - vCoords[indexMap[i]];
                } else {
                  u = (uCoords[indexMap[i]] - uOffset[indexMap[i]/3]) /
                    uScale[indexMap[i]/3];
                  
                  v = 1.0f - ((vCoords[indexMap[i]] - vOffset[indexMap[i]/3]) /
                              vScale[indexMap[i]/3]);
                }
                
                if ( isNum(u) && isNum(v) )
                { 
                    if ( uv_swap == TRUE )
                        pool->Vertex(i)->attrib.SetUV( v, u );
                    else
                        pool->Vertex(i)->attrib.SetUV( u, v );
                }
            }
            else if ( numTexGlb && (uCoords != NULL && vCoords !=NULL ) )
            {
                float u, v;
            
                if ( ignore_tex_offsets ) {
                  u = uCoords[indexMap[i]];
                  v = 1.0f - vCoords[indexMap[i]];
                } else {
                  u = (uCoords[indexMap[i]] - *uOffset) / *uScale;
                  v = 1.0f - (( vCoords[indexMap[i]] - *vOffset ) / *vScale);
                }

                if ( isNum(u) && isNum(v) )
                { 
                    if ( uv_swap == TRUE )
                        pool->Vertex(i)->attrib.SetUV( v, u );
                    else
                        pool->Vertex(i)->attrib.SetUV( u, v );
                }
                        
            }
             
            // if we've encountered textures and we desire duv anims
            if (( numTexLoc || numTexGlb ) && make_duv )
            {
                int            numExp;
                SAA_Elem   *tex;

                // grab the current texture
                if ( numTexLoc ) 
                    tex = &textures[0];
                else
                    tex = textures;

                // find how many expressions for this shape
                SAA_elementGetNbExpressions( scene, tex, NULL, FALSE, 
                    &numExp );

                // if it has expressions we'll assume its animated
                if ( numExp )
                {
                    // if animated object make base duv's, animtables
                    // for the duv's and store the original offsets
                    strstream uName, vName;

                    // create duv target names
                    uName << name << ".u" << ends;
                    vName << name << ".v" << ends;

                    // only create tables and store offsets
                    // on a per model basis (not per vertex)
                    if ( !i )
                    {

                        // make sure root morph table exists
                        if ( morphRoot == NULL )
                            morphRoot = animData.CreateTable( animRoot, 
                                "morph" ); 

                        // create morph table entry for each duv
                        SAnimTable *uTable = new SAnimTable( );     
                        uTable->name = uName.str();
                        uTable->fps = anim_rate;
                        morphRoot->children.push_back( uTable );
                        if ( verbose >= 1 )
                            fprintf( outStream, "created duv table named: %s\n",                                uName.str() );    

                        SAnimTable *vTable = new SAnimTable( );     
                        vTable->name = vName.str();
                        vTable->fps = anim_rate;
                        morphRoot->children.push_back( vTable );
                        if ( verbose >= 1 )
                            fprintf( outStream, "created duv table named: %s\n",                                vName.str() );    

                        float    texOffsets[4];

                        if ( numTexGlb )
                        {
                            texOffsets[0] = *uOffset;
                            texOffsets[1] = *vOffset;
                            texOffsets[2] = *uScale;
                            texOffsets[3] = *vScale;
                        }
                        else
                        {
                            texOffsets[0] = uOffset[indexMap[i]/3];
                            texOffsets[1] = vOffset[indexMap[i]/3];
                            texOffsets[2] = uScale[indexMap[i]/3];
                            texOffsets[3] = vScale[indexMap[i]/3];
                        }

                        // remember original texture offsets future reference
                        SAA_elementSetUserData( scene, model, "TEX_OFFSETS", 
                            sizeof( texOffsets ), TRUE, (void  **)&texOffsets );
                    }

                    EggMorphOffset *duvU;
                    EggMorphOffset *duvV;

                    // generate base duv's for this vertex
                    duvU = new EggMorphOffset( uName.str(), 1.0 , 0.0 );    
                    pool->Vertex(i)->attrib.uv_morphs.push_back( *duvU );
            
                    duvV = new EggMorphOffset( vName.str(), 0.0 , 1.0 );    
                    pool->Vertex(i)->attrib.uv_morphs.push_back( *duvV );

                } // if ( numExp )

            } // if ( numTexLoc || numTexGlb )

        } // for ( i = 0; i < numVert; i++ )

        // if model has key shapes, generate vertex offsets
        if ( has_morph && make_morph )
            MakeVertexOffsets( scene, model, type, numShapes, numVert, 
                vertices, matrix,  name );


        // create vertex ref list for all polygons in the model
        EggVertexRef *vref;

        vref = new EggVertexRef( pool);
        for ( i = 0; i < numVert; i++ )
        {
            //add each vert in pool to last joint for hard skinning
            vref->indices.push_back( EggVertexIndex( i ) );
        }

        // hard assign poly geometry if no soft-skinning requested
        //
        //disabled 1/1/99 to streamline joint assignments.
        // all hard-skinning now done in CleanUpSoftSkin.
        //if ( !make_soft )
        //{
            //if ( lastJoint != NULL )
            //{
                //lastJoint->vrefs.AddUniqueNode( *vref );

                //if ( verbose >= 1 )
                    //fprintf( outStream, "hard-skinning %s (%d vertices)\n", 
                        //name, i+1 );
            //}
        //}

        // make an egg group to hold all triangles
        EggGroup *group = _data.CreateGroup( parent, name);

        // make this group the current parent
        parent = group;

        EggPolygon *poly = NULL;
        EggColor *cref = NULL;
        EggTexture *tref = NULL;

        // for each triangle
        for ( i = 0; i < numTri*3; i+=3 )
        {
            float    r,g,b,a;
            pfVec4    color;

            // make egg poly for each traingle and reference
            // the appropriate vertex in the pool
            poly = _data.CreatePolygon( group, pool );
            poly->AddVertex(indices[i]);
            poly->AddVertex(indices[i+1]);
            poly->AddVertex(indices[i+2]);

            // check for back face flag in model note info
            char *modelNoteStr = GetModelNoteInfo( scene, model );

            if ( modelNoteStr != NULL )
            {
                if ( strstr( modelNoteStr, "bface" ) != NULL )
                    poly->flags |= EG_BFACE;
            }
            
            // check to see if material is present
            SAA_Boolean    valid;
            SAA_elementIsValid( scene, &materials[i/3], &valid );

            // material present - get the color 
            if ( valid )
            {
                SAA_materialGetDiffuse( scene, &materials[i/3], &r, &g, &b );
                SAA_materialGetTransparency( scene, &materials[i/3], &a );
                color.set( r, g, b, 1.0f - a );
            }
            // no material - default to white
            else
                color.set( 1.0, 1.0, 1.0, 1.0 );

            cref = _data.CreateColor(color);
            poly->attrib.SetCRef(cref);

            strstream uniqueTexName;

            if (numTexLoc)
            {
                // polytex
                if ( (texNameArray[i/3] != NULL) &&
                        (strcmp(texNameArray[i/3], "NULL") != 0) )
                {
                    // append unique identifier to texname for
                    // this particular object
                    uniqueTexName << name << "-" 
                        << RemovePathName(texNameArray[i/3]);

                    tref = _data.CreateTexture( texNameArray[i/3], 
                        uniqueTexName.str() ); 

                    if ( verbose >= 1 )
                        fprintf( outStream, " tritex[%d] named: %s\n", i/3, 
                            texNameArray[i/3] );
                }
            }
            else if ( numTexGlb )
            {
                if ( texNameArray != NULL )
                {
                    // append unique identifier to texname for
                    // this particular object
                    uniqueTexName << name << "-"
                        << RemovePathName(*texNameArray);

                    tref = _data.CreateTexture( *texNameArray, 
                        uniqueTexName.str() );

                    if ( verbose >= 1 )
                        fprintf( outStream, " tritex named: %s\n",
                             *texNameArray );
                }
            }

            // set the clamp on the texture
            if ( tref != NULL )
            {
                if ( uRepeat > 0 )
                    tref->wrapu = EggTexture::WM_repeat;
                else
                    tref->wrapu = EggTexture::WM_clamp;

                if ( vRepeat > 1 )
                    tref->wrapv = EggTexture::WM_repeat;
                else
                    tref->wrapv = EggTexture::WM_clamp;

                poly->attrib.SetTRef(tref);
            }

        }

        // we're done - trash triangles...
        SAA_modelClearTriangles( scene, model );

        // free molloc'd memory
        //free( triangles );
        //free( materials );
        //free( normals );
        //free( cvertices );
        //free( vertices );
        //free( indices );
        //free( indexMap );

        // free these only if they were malloc'd for textures
        if (numTexLoc || numTexGlb)
        {
            //free( textures );
            //free( uCoords );
            //free( vCoords );
            //free( texNameArray );
            //free( uScale );
            //free( vScale );
            //free( uOffset );
            //free( vOffset );
        }
      }
    }
    else
    {
        ///////////////////////////////////////
        // check to see if its a nurbs surface
        ///////////////////////////////////////
        if ( (type == SAA_MNSRF) && ( visible ) && (( make_nurbs ) 
            || ( !make_nurbs && !make_poly &&  make_duv )) )
        {
            // check to see if NURBS is also skeleton...
            SAA_Boolean isSkeleton = FALSE;

            SAA_modelIsSkeleton( scene, model, &isSkeleton );

            // check to see if this NURBS is used as a skeleton
            // or is animated via constraint only ( these nodes are
            // tagged by the animator with the keyword "joint"
            // somewhere in the nodes name)
            if ( isSkeleton || (strstr( name, "joint" ) != NULL) )
            {
                MakeJoint( scene, lastJoint, lastAnim, model, name );
                geom_as_joint = 1;
                if ( verbose >= 1 )
                    fprintf( outStream, "animating NURBS as joint!!!\n" );
            }

            EggNurbsSurface    *eggNurbsSurf = new EggNurbsSurface( name );
            int uDegree, vDegree;

            // create nurbs representation of surface
            SAA_nurbsSurfaceGetDegree( scene, model, &uDegree, &vDegree );
            eggNurbsSurf->u_order = uDegree + 1;
            eggNurbsSurf->v_order = vDegree + 1;
            if ( verbose >= 1 )
            {
                fprintf( outStream, "nurbs degree: %d u, %d v\n", 
                    uDegree, vDegree );
                fprintf( outStream, "nurbs order: %d u, %d v\n", 
                    uDegree + 1, vDegree + 1 );
            }

            SAA_Boolean    uClosed = FALSE;
            SAA_Boolean    vClosed = FALSE;

            SAA_nurbsSurfaceGetClosed( scene, model, &uClosed, &vClosed);    

            if ( verbose >= 1 )
            {    
                if ( uClosed )
                    fprintf( outStream, "nurbs is closed in u...\n");
                if ( vClosed )
                    fprintf( outStream, "nurbs is closed in v...\n");
            }    
            
            int uRows, vRows;
            SAA_nurbsSurfaceGetNbVertices( scene, model, &uRows, &vRows );
            if ( verbose >= 1 )
                fprintf( outStream, "nurbs vertices: %d u, %d v\n", 
                    uRows, vRows );
            
            int uCurves, vCurves;
            SAA_nurbsSurfaceGetNbCurves( scene, model, &uCurves, &vCurves );
            if ( verbose >= 1 )
                fprintf( outStream, "nurbs curves: %d u, %d v\n", 
                    uCurves, vCurves );

            if ( shift_textures )
            {
            if ( uClosed )
                // shift starting point on NURBS surface for correct textures
                SAA_nurbsSurfaceShiftParameterization( scene, model, -2, 0 );

            if ( vClosed )
                // shift starting point on NURBS surface for correct textures
                SAA_nurbsSurfaceShiftParameterization( scene, model, 0, -2 );
            }

            SAA_nurbsSurfaceSetStep( scene, model, nurbs_step, nurbs_step );

            // check for back face flag in model note info
            char *modelNoteStr = GetModelNoteInfo( scene, model );

            if ( modelNoteStr != NULL )
            {
                if ( strstr( modelNoteStr, "bface" ) != NULL )
                    eggNurbsSurf->flags |= EG_BFACE;
            }
            
            int numKnotsU, numKnotsV;
            
            SAA_nurbsSurfaceGetNbKnots( scene, model, &numKnotsU, &numKnotsV );
            if ( verbose >= 1 )
                fprintf( outStream, "nurbs knots: %d u, %d v\n", 
                    numKnotsU, numKnotsV );

            double *knotsU, *knotsV;    
            knotsU = (double *)malloc(sizeof(double)*numKnotsU);
            knotsV = (double *)malloc(sizeof(double)*numKnotsV);
            SAA_nurbsSurfaceGetKnots( scene, model, gtype, 0, 
                numKnotsU, numKnotsV, knotsU, knotsV );

            if ( verbose >= 2 )
                fprintf( outStream, "u knots:\n" );

            AddKnots( eggNurbsSurf->u_knots, knotsU, numKnotsU, uClosed, uDegree ); 
            if ( verbose >= 2 )
                fprintf( outStream, "v knots:\n" );

            AddKnots( eggNurbsSurf->v_knots, knotsV, numKnotsV, vClosed, vDegree); 

            //free( knotsU );
            //free( knotsV );

            // set sub_div so we can see it in perfly
            eggNurbsSurf->u_subdiv = (uRows-1)*nurbs_step;
            eggNurbsSurf->v_subdiv = (vRows-1)*nurbs_step;

            SAA_modelGetNbVertices( scene, model, &numVert );
        
            if ( verbose >= 2 )    
                fprintf( outStream, "%d CV's\n", numVert ); 

            // get the CV's
            vertices = (SAA_DVector *)malloc(sizeof(SAA_DVector)*numVert);
            SAA_modelGetVertices( scene, model, gtype, 0,
                numVert, vertices );

            // create pool of NURBS vertices 
            EggVertexPool *pool = _data.CreateVertexPool( parent, name );
            eggNurbsSurf->SetVertexPool( pool );

            // create vertex ref list for all cv's in the model
            EggVertexRef *vref;

            vref = new EggVertexRef( pool);

            for ( int k = 0; k<numVert; k++ )
            {
                if ( verbose >= 2 )
                {
                    fprintf( outStream, "original cv[%d] = %f %f %f %f\n", k, 
                        vertices[k].x, vertices[k].y, vertices[k].z, 
                        vertices[k].w );
                }

                pfVec4  eggVert;

                // convert to global coords
                SAA_DVector global;

                _VCT_X_MAT( global, vertices[k], matrix );

                //preserve original weight
                global.w = vertices[k].w;

                // normalize coords to weight
                global.x *= global.w;
                global.y *= global.w;
                global.z *= global.w;

                // this code is commented out because I
                // am no longer sending global data to
                // the other routines (ie makevertexoffset)

                // set vertices array to reflect global coords
                //vertices[k].x = global.x;
                //vertices[k].y = global.y;
                //vertices[k].z = global.z;
                //vertices[k].w = global.w;

                //if ( verbose >= 2 )
                //{
                    //fprintf( outStream, "global cv[%d] = %f %f %f %f\n", k, 
                        //vertices[k].x, vertices[k].y, vertices[k].z, 
                        //vertices[k].w );
                //}

                //eggVert.set( vertices[k].x, vertices[k].y, vertices[k].z, 
                    //vertices[k].w );

                if ( verbose >= 2 )
                {
                    fprintf( outStream, "global cv[%d] = %f %f %f %f\n", k, 
                        global.x, global.y, global.z, 
                        global.w );
                }

                eggVert.set( global.x, global.y, global.z, 
                    global.w );

                // populate vertex pool
                pool->AddVertex( eggVert, k );

                // add vref's to NURBS info 
                eggNurbsSurf->AddVertex( k ); 

                //add each vert in pool to vref for hard skinning
                vref->indices.push_back( EggVertexIndex( k ) );
                
                // check to see if the NURB is closed in u    
                if ( uClosed )
                {
                    // add first uDegree verts to end of row
                    if ( (k % uRows) == ( uRows - 1) )
                    for ( int i = 0; i < uDegree; i++ ) 
                    {
                        // add vref's to NURBS info 
                        eggNurbsSurf->AddVertex( i+((k/uRows)*uRows) ); 

                        //add each vert to vref 
                        vref->indices.push_back( 
                            EggVertexIndex( i+((k/uRows)*uRows) ) );
                    }
                } 
            }

            // if hard skinned or this nurb is also a joint
            //
            //disabled 1/1/99 to streamline joint assignments.
            // all hard skinning now done in CleanUpSoftSkin.
            //if (!make_soft || geom_as_joint)
            //{
                //add the new cv references to the last 
                //joint for hard skinning only 
                //if ( lastJoint != NULL )
                //{
                    //lastJoint->vrefs.AddUniqueNode( *vref );
                    //geom_as_joint = 0;    
                    //if ( verbose >= 1 )    
                        //fprintf( outStream, "Doing NURBS hard skinning...\n");
                //}
            //}

            // check to see if the NURB is closed in v    
            if ( vClosed && !uClosed )
            {
                // add first vDegree rows of verts to end of list
                for ( int i = 0; i < vDegree*uRows; i++ ) 
                    eggNurbsSurf->AddVertex( i ); 
            }
            // check to see if the NURB is closed in u and v    
            else if ( vClosed && uClosed )
            {
                // add the first (degree) v verts and a few
                // extra - for good measure
                for ( i = 0; i < vDegree; i++ ) 
                {
                    // add first vDegree rows of verts to end of list
                    for ( j = 0; j < uRows; j++ )
                        eggNurbsSurf->AddVertex( j+(i*uRows) );

                    // if u is closed to we have added uDegree
                    // verts onto the ends of the rows - add them here too
                    for ( k = 0; k < uDegree; k++ )
                        eggNurbsSurf->AddVertex( k+(i*uRows)+((k/uRows)*uRows) );
                }

            }

            // get the color of the NURBS surface
            int numNurbMats;
            EggColor *nurbCref;
            pfVec4    nurbColor;

            SAA_modelRelationGetMatNbElements( scene, model, FALSE, &relinfo,
                &numNurbMats ); 

            if ( verbose >= 1 )
                fprintf( outStream, "nurbs surf has %d materials\n", 
                    numNurbMats );

            if ( numNurbMats )
            {
                float r,g,b,a;

                materials = (SAA_Elem *)malloc(sizeof(SAA_Elem)*numNurbMats);
                
                SAA_modelRelationGetMatElements( scene, model, relinfo, 
                    numNurbMats, materials ); 

                SAA_materialGetDiffuse( scene, &materials[0], &r, &g, &b );
                SAA_materialGetTransparency( scene, &materials[0], &a );
                nurbColor.set( r, g, b, 1.0f - a );
                //nurbColor.set( r, g, b, 1.0 );

                nurbCref = _data.CreateColor(nurbColor);
                eggNurbsSurf->attrib.SetCRef(nurbCref);
        
                //get the texture of the NURBS surface from the material
                int numNurbTexLoc = 0;
                int numNurbTexGlb = 0;

                // ASSUME only one texture per material
                SAA_Elem nurbTex;

                // find out how many local textures per NURBS surface
                // ASSUME it only has one material
                SAA_materialRelationGetT2DLocNbElements( scene, &materials[0],
                    FALSE, &relinfo, &numNurbTexLoc );

                // if present, get local textures
                if ( numNurbTexLoc )
                {
                    if ( verbose >= 1 )
                        fprintf( outStream, "%s had %d local tex\n", name, 
                            numNurbTexLoc );

                    // get the referenced texture
                    SAA_materialRelationGetT2DLocElements( scene, &materials[0],
                        TEX_PER_MAT, &nurbTex ); 

                }
                // if no locals, try to get globals
                else
                {
                    SAA_modelRelationGetT2DGlbNbElements( scene, model,
                        FALSE, &relinfo, &numNurbTexGlb );

                    if ( numNurbTexGlb )
                    {
                        if ( verbose >= 1 )
                            fprintf( outStream, "%s had %d global tex\n", name, 
                                numNurbTexGlb );

                            // get the referenced texture
                            SAA_modelRelationGetT2DGlbElements( scene, 
                                model, TEX_PER_MAT, &nurbTex ); 
                        }
                }

                // add tex ref's if we found any textures
                if ( numNurbTexLoc || numNurbTexGlb) 
                {
                    char    *texName = NULL;
                    char    *uniqueTexName = NULL;
                    EggTexture *tref;
                    pfMatrix  nurbTexMat;


                    // convert the texture to .rgb and adjust name 
                    texName = ConvertTexture( scene, &nurbTex );

                    // append unique identifier to texname for
                    // this particular object
                    uniqueTexName = (char *)malloc(sizeof(char)*
                        (strlen(name)+strlen(texName)+3) );
                    sprintf( uniqueTexName, "%s-%s", name, 
                        RemovePathName(texName) );

                    if ( verbose >= 1 )
                    {
                        fprintf( outStream, "creating tref %s\n", 
                            uniqueTexName );
                    }

                    tref = _data.CreateTexture( texName, uniqueTexName );

                    uScale = ( float *)malloc(sizeof(float));
                    vScale = ( float *)malloc(sizeof(float));
                    uOffset = ( float *)malloc(sizeof(float));
                    vOffset = ( float *)malloc(sizeof(float));

                    // get texture offset info
                    SAA_texture2DGetUScale( scene, &nurbTex, uScale );
                    SAA_texture2DGetVScale( scene, &nurbTex, vScale );
                    SAA_texture2DGetUOffset( scene, &nurbTex, uOffset );
                    SAA_texture2DGetVOffset( scene, &nurbTex, vOffset );
                    SAA_texture2DGetUVSwap( scene, &nurbTex, &uv_swap );


                    if ( verbose >= 1 )
                    {
                        fprintf( outStream, "nurbTex uScale: %f\n", *uScale );
                        fprintf( outStream, "nurbTex vScale: %f\n", *vScale );
                        fprintf( outStream, "nurbTex uOffset: %f\n", *uOffset );
                        fprintf( outStream, "nurbTex vOffset: %f\n", *vOffset );
                        if ( uv_swap )
                            fprintf( outStream, "nurbTex u & v swapped!\n" );
                        else
                            fprintf( outStream, "nurbTex u & v NOT swapped\n" );
                    }

                    nurbTexMat.makeIdent();

                    if ( !ignore_tex_offsets )
                    {
                        if ( uv_swap )
                        {
                            nurbTexMat[0][0] = 0.0f;
                            nurbTexMat[1][1] = 0.0f;
                            nurbTexMat[0][1] = 1 / *vScale;
                            nurbTexMat[1][0] = 1 / *uScale;
                            nurbTexMat[2][1] = -(*uOffset / *uScale);
                            nurbTexMat[2][0] = -(*vOffset / *vScale);
                        }
                        else
                        {
                            nurbTexMat[0][0] = 1 / *uScale;
                            nurbTexMat[1][1] = 1 / *vScale;
                            nurbTexMat[2][0] = -(*uOffset / *uScale);
                            nurbTexMat[2][1] = -(*vOffset / *vScale);
                        }
                    }


    //call printMat
    if ( verbose >= 2 )
    {    
    fprintf( outStream, "nurb tex matrix = %f %f %f %f\n", nurbTexMat[0][0],
         nurbTexMat[0][1],  nurbTexMat[0][2],  nurbTexMat[0][3] ); 
    fprintf( outStream, "nurb tex matrix = %f %f %f %f\n", nurbTexMat[1][0],
         nurbTexMat[1][1],  nurbTexMat[1][2],  nurbTexMat[1][3] ); 
    fprintf( outStream, "nurb tex matrix = %f %f %f %f\n", nurbTexMat[2][0],
         nurbTexMat[2][1],  nurbTexMat[2][2],  nurbTexMat[2][3] ); 
    fprintf( outStream, "nurb tex matrix = %f %f %f %f\n", nurbTexMat[3][0],
         nurbTexMat[3][1],  nurbTexMat[3][2],  nurbTexMat[3][3] ); 
    }


                    tref->tex_mat = nurbTexMat;
                    tref->flags |= EFT_TRANSFORM;

                    eggNurbsSurf->attrib.SetTRef(tref);

                }

                // if we've encountered textures and we desire duv anims
                if (( numNurbTexLoc || numNurbTexGlb ) && make_duv )
                {
                    int            numExp;

                     // find how many expressions for this shape
                    SAA_elementGetNbExpressions( scene, &nurbTex, NULL, FALSE, 
                        &numExp );

                    // if it has expressions we'll assume its animated
                    if ( numExp )
                    {
                        if ( verbose > 1 )
                            printf( "nurbTex has %d expressions...\n", numExp );

                        // if animated object make base duv's, animtables
                        // for the duv's and store the original offsets
                        strstream uName, vName;

                        // create duv target names
                        uName << name << ".u" << ends;
                        vName << name << ".v" << ends;

                        // make sure root morph table exists
                        if ( morphRoot == NULL )
                            morphRoot = animData.CreateTable( animRoot, 
                                "morph" ); 

                        // create morph table entry for each duv
                        SAnimTable *uTable = new SAnimTable( );     
                        uTable->name = uName.str();
                        uTable->fps = anim_rate;
                        morphRoot->children.push_back( uTable );
                        if ( verbose >= 1 )
                            fprintf( outStream, "created duv table named: %s\n",                                uName.str() );    

                        SAnimTable *vTable = new SAnimTable( );     
                        vTable->name = vName.str();
                        vTable->fps = anim_rate;
                        morphRoot->children.push_back( vTable );
                        if ( verbose >= 1 )
                            fprintf( outStream, "created duv table named: %s\n",                                vName.str() );    

                        float    texOffsets[4];

                        texOffsets[0] = *uOffset;
                        texOffsets[1] = *vOffset;
                        texOffsets[2] = *uScale;
                        texOffsets[3] = *vScale;

                        // remember original texture offsets future reference
                        SAA_elementSetUserData( scene, model, "TEX_OFFSETS", 
                            sizeof( texOffsets ), TRUE, (void  **)&texOffsets );

                        // create UV's and duv's for each vertex 
                        for( i = 0; i < numVert; i++ )
                        {
                            pfVec2            tmpUV;
                            EggMorphOffset *duvU;
                            EggMorphOffset *duvV;

                            //create uv's so we can store duv's
                            eggNurbsSurf->CalcActualUV( i, tmpUV );
                            pool->Vertex(i)->attrib.SetUV( tmpUV[0], tmpUV[1] );
                            
                            // generate base duv's for this vertex
                            duvU = new EggMorphOffset(uName.str(), 1.0 , 0.0);    
                            pool->Vertex(i)->attrib.uv_morphs.push_back(*duvU);
                    
                            duvV = new EggMorphOffset(vName.str(), 0.0 , 1.0);    
                            pool->Vertex(i)->attrib.uv_morphs.push_back(*duvV);
                        }

                  } // if ( numExp )
                } // if ( numTexLoc || numTexGlb )

                //free( uScale );
                //free( vScale );
                //free( uOffset );
                //free( vOffset );

                //free( materials );
            }
            else
            {
                // no material present - default to white 
                nurbColor.set( 1.0, 1.0, 1.0, 1.0 );
            }

            //////////////////////////////////////////
            // check NURBS surface for trim curves
            //////////////////////////////////////////
            int     numTrims;
            bool    isTrim = TRUE;
            SAA_SubElem *trims;
        
            SAA_nurbsSurfaceGetNbTrimCurves( scene, model, SAA_TRIMTYPE_TRIM,
                &numTrims );

            if ( verbose >= 1 )
                fprintf( outStream, "nurbs surf has %d trim curves\n", 
                    numTrims );

            if ( numTrims)
            {
                trims = (SAA_SubElem *)malloc(sizeof(SAA_SubElem)*numTrims);

                if ( trims )
                {
                    SAA_nurbsSurfaceGetTrimCurves( scene, model, 
                        gtype, 0, SAA_TRIMTYPE_TRIM, numTrims, 
                        trims );

                    MakeSurfaceCurve( scene, model, parent, eggNurbsSurf, 
                        numTrims, trims, isTrim );
                }

                //free( trims );
            }

            //////////////////////////////////////////
            // check NURBS surface for surface curves
            //////////////////////////////////////////
            isTrim = FALSE;

            SAA_nurbsSurfaceGetNbTrimCurves( scene, model, 
                SAA_TRIMTYPE_PROJECTION, &numTrims );

            if ( verbose >= 1 )
                fprintf( outStream, "nurbs surf has %d surface curves\n", 
                    numTrims );

            if ( numTrims)
            {
                trims = (SAA_SubElem *)malloc(sizeof(SAA_SubElem)*numTrims);

                if ( trims )
                {
                    SAA_nurbsSurfaceGetTrimCurves( scene, model, 
                        gtype, 0, SAA_TRIMTYPE_PROJECTION, 
                        numTrims, trims );

                    MakeSurfaceCurve( scene, model, parent, eggNurbsSurf, 
                        numTrims, trims, isTrim );
                }

                //free( trims );
            }

            // push the NURBS into the egg data
            parent->children.push_back( eggNurbsSurf );    

            // if model has key shapes, generate vertex offsets
            if ( has_morph && make_morph )
                MakeVertexOffsets( scene, model, type, numShapes, numVert, 
                    vertices, matrix, name );


            //free( vertices );

        }
        /////////////////////////////////////
        // check to see if its a NURBS curve
        /////////////////////////////////////
        else if ( (type == SAA_MNCRV) && ( visible ) && ( make_nurbs ) )
        {
            // ignore for now
            // make the NURBS curve and push it into the egg data
            //parent->children.push_back( MakeNurbsCurve( scene, model, parent, 
                //matrix, name ) );
           }
        else if ( type == SAA_MJNT )
        {
            MakeJoint( scene, lastJoint, lastAnim, model, name );
            if ( verbose >= 1 )
                fprintf( outStream, "encountered IK joint: %s\n", name );
        }
        /////////////////////
        // it must be a NULL
        /////////////////////
        else 
        {
            SAA_AlgorithmType    algo;
        
            SAA_modelGetAlgorithm( scene, model, &algo );
            if ( verbose >= 1 )
                fprintf( outStream, "null algorithm: %d\n", algo );

            if ( algo == SAA_ALG_INV_KIN )
            {
                MakeJoint( scene, lastJoint, lastAnim,  model, name );
                if ( verbose >= 1 )
                    fprintf( outStream, "encountered IK root: %s\n", name );
            }
            else if ( algo == SAA_ALG_INV_KIN_LEAF )
            {
                MakeJoint( scene, lastJoint, lastAnim, model, name );
                if ( verbose >= 1 )
                    fprintf( outStream, "encountered IK leaf: %s\n", name );
            }
            else if ( algo == SAA_ALG_STANDARD )
            {
                SAA_Boolean isSkeleton = FALSE;

                if ( verbose >= 1 )
                    fprintf( outStream, "encountered Standard null: %s\n", name);

                SAA_modelIsSkeleton( scene, model, &isSkeleton );

                // check to see if this NULL is used as a skeleton
                // or is animated via constraint only ( these nodes are
                // tagged by the animator with the keyword "joint"
                // somewhere in the nodes name)
                if ( isSkeleton || (strstr( name, "joint" ) != NULL) )
                {
                    MakeJoint( scene, lastJoint, lastAnim, model, name );
                    if ( verbose >= 1 )
                        fprintf( outStream, "animating Standard null!!!\n" );
                }
                
            }
            else
                if ( verbose >= 1 )
                    fprintf( outStream, "encountered some other NULL: %d\n", 
                        algo );
        }
    }


    // check for children...
    int        numChildren;
    int        thisChild;
    SAA_Elem *children;

    SAA_modelGetNbChildren( scene, model, &numChildren );
    if ( verbose >= 1 )
        fprintf( outStream, "Model children: %d\n", numChildren );

    if ( numChildren )
    {
        children = (SAA_Elem *)malloc(sizeof(SAA_Elem)*numChildren);
        SAA_modelGetChildren( scene, model, numChildren, children );
        if ( children != NULL )
        {
            for ( thisChild = 0; thisChild < numChildren; thisChild++ )
            {
                if ( verbose >= 1 )
                    fprintf( outStream, "\negging child %d...\n", thisChild);
                MakeEgg( parent, lastJoint, lastAnim, scene, 
                    &children[thisChild] );
            }
        }
        else
            fprintf( outStream, "Not enough Memory for children...\n");
        //free( children );
    }
    fflush( outStream );
  }
  else
    if ( verbose >= 1 )
        fprintf( outStream, "Don't descend this branch!\n" );
    
  // we are done for the most part - start cleaning up memory
  //free( name );
}


////////////////////////////////////////////////////////////////////
//     Function: MakeSurfaceCurve
//       Access: Public 
//  Description: Given a scene and lists of u and v samples create a
//                an egg NURBS curve of degree two from the samples 
////////////////////////////////////////////////////////////////////
void  soft2egg::
MakeSurfaceCurve(  SAA_Scene *scene, SAA_Elem *model, EggGroup *parent, 
    EggNurbsSurface *&nurbsSurf, int numTrims, SAA_SubElem *trims,
    bool isTrim )
{
    int      i;
    long      totalSamples = 0;
    long    *numSamples;
    double    *uSamples;
    double    *vSamples;
    SAA_Elem *trimCurves;
    char    *name;

    //get UV coord data
    numSamples = (long *)malloc(sizeof(long)*numTrims);

    SAA_surfaceCurveGetNbLinearSamples( scene, model,  numTrims, trims,
        numSamples );

    for ( i = 0; i < numTrims; i++ )
    {
        totalSamples += numSamples[i];
        if ( verbose >= 2 )
            fprintf( outStream, "numSamples[%d] = %d\n", i, numSamples[i] );
    }

    if ( verbose >= 2 )
        fprintf( outStream, "total samples = %ld\n", totalSamples );

    uSamples = (double *)malloc(sizeof(double)*totalSamples);
    vSamples = (double *)malloc(sizeof(double)*totalSamples);

    SAA_surfaceCurveGetLinearSamples( scene, model, numTrims, trims,
        numSamples, uSamples, vSamples ); 

    if ( verbose >= 2 )
        for ( long li = 0; li < totalSamples; li++ )
            fprintf( outStream, "master list cv[%ld] = %f, %f\n", li, 
                uSamples[li], vSamples[li] ); 

    trimCurves = (SAA_Elem *)malloc(sizeof(SAA_Elem)*numTrims);

    SAA_surfaceCurveExtract( scene, model, numTrims, trims, trimCurves );

    // if it's a trim create a trim to assign trim curves to
    EggNurbsSurface::Trim *eggTrim = new EggNurbsSurface::Trim();

    // for each trim curve, make an egg curve and 
    // add it to the trims of the NURBS surface
    for ( i = 0; i < numTrims; i++ )
    {
        if ( use_prefix )
        {
            // Get the FULL name of the trim curve
            name = GetFullName( scene, &trimCurves[i] );
        }
        else
        {
            // Get the name of the trim curve
            name = GetName( scene, &trimCurves[i] );
        }

        if ( isTrim )
        {
            // add to trim list
            EggNurbsSurface::Loop *eggLoop = new EggNurbsSurface::Loop();
            eggLoop->push_back( MakeUVNurbsCurve( i, numSamples, uSamples, 
                vSamples, parent, name ) );
            eggTrim->push_back( *eggLoop );
        }
        else
            // add to curve list
            nurbsSurf->curves.push_back( MakeUVNurbsCurve( i, numSamples,                         uSamples, vSamples, parent, name ) );
    } 

    if ( isTrim )
        // pus trim list onto trims list
        nurbsSurf->trims.push_back( *eggTrim );

    //free( name );
    //free( trimCurves );
    //free( uSamples );
    //free( vSamples );
}

////////////////////////////////////////////////////////////////////
//     Function: MakeUVNurbsCurve
//       Access: Public 
//  Description: Given a scene and lists of u and v samples create a
//                an egg NURBS curve of degree two from the samples 
////////////////////////////////////////////////////////////////////
EggNurbsCurve  *soft2egg::
MakeUVNurbsCurve( int numCurve, long *numSamples, double *uSamples, 
    double *vSamples, EggGroup *parent, char *name )
{
    EggNurbsCurve    *eggNurbsCurve = new EggNurbsCurve( name );

    eggNurbsCurve->order = 2;


    if ( verbose >= 2 )
        fprintf( outStream, "nurbs UV curve %s:\n", name );

    //set sub_div so we can see it in perfly
    //eggNurbsCurve->subdiv = numSamples[numCurve]/4;
    // perfly chokes on big numbers - keep it reasonable
    eggNurbsCurve->subdiv = 150; 

    //create pool of NURBS vertices 
    EggVertexPool *pool = _data.CreateVertexPool( parent, name );
    eggNurbsCurve->SetVertexPool( pool );

    // calculate offset to this curve's samples
    // in list of all curve samples
    int offset = 0;

    for ( int o = 0; o < numCurve; o++ )
        offset += numSamples[o];
    
    for ( int k = 0; k<numSamples[numCurve]; k++ )
    {
        pfVec3  eggVert;

        // index into the array of samples for this curve
        eggVert.set( uSamples[k+offset], vSamples[k+offset], 1.0f );

        if ( verbose >= 2 )
            fprintf( outStream, "cv[%d] = %f %f %f\n", k, eggVert[0], 
                eggVert[1], eggVert[2] ); 

        //populate vertex pool
        pool->AddVertex( eggVert, k );

        //add vref's to NURBS info 
        eggNurbsCurve->AddVertex( k ); 
    }

    // create numSamples[numCurve]+2 knots
    eggNurbsCurve->knots.push_back( 0 );
    for ( k = 0; k < numSamples[numCurve]; k++ )
        eggNurbsCurve->knots.push_back( k  );
    eggNurbsCurve->knots.push_back( numSamples[numCurve] - 1 );

    //set color to bright green for now
    EggColor *nurbCref;
    pfVec4    nurbColor;

    nurbColor.set( 0.5, 1.0, 0.5, 1.0 );
    nurbCref = _data.CreateColor(nurbColor);
    eggNurbsCurve->attrib.SetCRef(nurbCref);

    return( eggNurbsCurve );
}

////////////////////////////////////////////////////////////////////
//     Function: MakeNurbsCurve
//       Access: Public 
//  Description: Given a scene and a NURBS curve model create the
//                 the appropriate egg structures  
////////////////////////////////////////////////////////////////////
EggNurbsCurve  *soft2egg::
MakeNurbsCurve( SAA_Scene *scene, SAA_Elem *model, EggGroup *parent, 
    float matrix[4][4], char *name )
{
    EggNurbsCurve    *eggNurbsCurve = new EggNurbsCurve( name );
    int degree;

    if ( verbose >= 2 )
        fprintf( outStream, "nurbs curve %s:\n", name );

    //create nurbs representation of surface
    SAA_nurbsCurveGetDegree( scene, model, &degree );
    eggNurbsCurve->order = degree + 1;
    if ( verbose >= 2 )
        fprintf( outStream, "nurbs curve order: %d\n", degree + 1 );
    
    SAA_nurbsCurveSetStep( scene, model, nurbs_step );

    SAA_Boolean    closed = FALSE;

    SAA_nurbsCurveGetClosed( scene, model, &closed );    
    if ( closed )
        if ( verbose >= 2 )
            fprintf( outStream, "nurbs curve is closed...\n");

    int numKnots;
    
    SAA_nurbsCurveGetNbKnots( scene, model, &numKnots );
    if ( verbose >= 2 )
        fprintf( outStream, "nurbs curve knots: %d\n", numKnots );
    double *knots;    
    knots = (double *)malloc(sizeof(double)*numKnots);
    SAA_nurbsCurveGetKnots( scene, model, SAA_GEOM_ORIGINAL, 0, 
        numKnots, knots );

    AddKnots( eggNurbsCurve->knots, knots, numKnots, closed, degree );

    //free( knots );

    int    numCV;

    SAA_modelGetNbVertices( scene, model, &numCV );
    if ( verbose >= 2 )
        fprintf( outStream, "%d CV's (=? %d)\n", numCV, (numKnots-(degree+1)) );

    //set sub_div so we can see it in perfly
    eggNurbsCurve->subdiv = (numCV-1)*nurbs_step;

    // get the CV's
    SAA_DVector *cvArray;
    cvArray = (SAA_DVector *)malloc(sizeof(SAA_DVector)*numCV);
    SAA_modelGetVertices( scene, model, SAA_GEOM_ORIGINAL, 0,
        numCV, cvArray );

    //create pool of NURBS vertices 
    EggVertexPool *pool = _data.CreateVertexPool( parent, name );
    eggNurbsCurve->SetVertexPool( pool );

    for ( int k = 0; k<numCV; k++ )
    {
        if ( verbose >= 2 )
            fprintf( outStream, "cv[%d] = %f %f %f %f\n", k, cvArray[k].x,
                cvArray[k].y, cvArray[k].z, cvArray[k].w );

        pfVec4  eggVert;

        //convert to global coords
        SAA_DVector local = cvArray[k];
        SAA_DVector global;

        _HVCT_X_MAT( global, local, matrix );

        eggVert.set( global.x, global.y, global.z, global.w );

        //populate vertex pool
        pool->AddVertex( eggVert, k );

        //add vref's to NURBS info 
        eggNurbsCurve->AddVertex( k ); 
    }

    if ( closed )
    {
        // need to replicate first (degree) vertices
        for ( k = 0; k < degree; k++ )
        {
            eggNurbsCurve->AddVertex( k );
            if ( verbose >= 2 )
                fprintf( outStream, "adding cv[%d] = %f %f %f %f\n", k, 
                    cvArray[k].x, cvArray[k].y, cvArray[k].z, cvArray[k].w );
        }
    }

    //free( cvArray );

    //set color to bright green for now
    EggColor *nurbCref;
    pfVec4    nurbColor;

    nurbColor.set( 0.5, 1.0, 0.5, 1.0 );
    nurbCref = _data.CreateColor(nurbColor);
    eggNurbsCurve->attrib.SetCRef(nurbCref);

    return( eggNurbsCurve );
}

////////////////////////////////////////////////////////////////////
//     Function: AddKnots
//       Access: Public 
//  Description: Given a parametric surface, and its knots, create
//                 the appropriate egg structure by filling in Soft's
//                 implicit knots and assigning the rest to eggKnots. 
////////////////////////////////////////////////////////////////////
void soft2egg::
AddKnots( perf_vector<double> &eggKnots, double *knots, int numKnots, 
    SAA_Boolean closed, int degree ) 
{
    int k = 0;
    double lastKnot = knots[0];
    double    *newKnots;

    // add initial implicit knot(s)
    if ( closed ) 
    {
        int i = 0;
        newKnots = (double *)malloc(sizeof(double)*degree);

        // need to add (degree) number of knots 
        for ( k = numKnots - 1; k >= numKnots - degree; k-- )
        {
            // we have to know these in order to calculate
            // next knot value so hold them in temp array
            newKnots[i] =  lastKnot - (knots[k] - knots[k-1]);
            lastKnot = newKnots[i];
            i++;
        }
        for ( k = degree - 1; k >= 0; k-- )
        {
            eggKnots.push_back( newKnots[k] );
            if ( verbose >= 2 )
                fprintf( outStream, "knots[%d] = %f\n", k, newKnots[k] ); 
        }

        //free( newKnots );
    }
    else
    {
        eggKnots.push_back( knots[k] );
        if ( verbose >= 2 )
            fprintf( outStream, "knots[%d] = %f\n", k, knots[k] ); 
    }

    // add the regular complement of knots
    for (k = 0; k < numKnots; k++)
    {
        eggKnots.push_back( knots[k] );
        if ( verbose >= 2 )
            fprintf( outStream, "knots[%d] = %f\n", k+1, knots[k] ); 
    }

    lastKnot = knots[numKnots-1];

    // add trailing implicit knots
    if ( closed ) 
    {

        // need to add (degree) number of knots 
        for ( k = 1; k <= degree; k++ )
        {
            eggKnots.push_back( lastKnot + (knots[k] - knots[k-1]) );
            if ( verbose >= 2 )
                fprintf( outStream, "knots[%d] = %f\n", k, 
                    lastKnot + (knots[k] - knots[k-1]) ); 
            lastKnot = lastKnot + (knots[k] - knots[k-1]);
        }
    }
    else
    {
        eggKnots.push_back( knots[k-1] );
        if ( verbose >= 2 )
            fprintf( outStream, "knots[%d] = %f\n", k+1, knots[k-1] ); 
    }
}

////////////////////////////////////////////////////////////////////
//     Function: MakeJoint
//       Access: Public 
//  Description: Given a name, a parent and a model create a new 
//                 a new EggJoint for that model. 
////////////////////////////////////////////////////////////////////
void soft2egg::
MakeJoint( SAA_Scene *scene, EggJoint *&lastJoint, AnimGroup *&lastAnim, 
    SAA_Elem *model, char *name )
{
    float     matrix[4][4];
    pfMatrix  Matrix;
    EggJoint *joint;
    SAA_Boolean    globalFlag = FALSE;
    int    scale_joint = 0;


    // this is a quick fix to make scaled skeletons possible
    // if the parent contains the keyword "scale" make this joint
    // a global root joint instead of a child...
    if (lastJoint != NULL)
    {
        if ( strstr( lastJoint->name.Str(), "scale" ) != NULL )
        {
            scale_joint = 1;    
            if ( verbose >= 1 )
                fprintf( outStream, "scale joint flag set!\n" );
        }
    }

    // if not root, flatten is false, and last joint had no scaling
    // applied to it, then create joint in skeleton tree
    if ( (lastJoint != NULL) && !flatten && !scale_joint )
    {
        if ( verbose >= 1 )
        {
            fprintf( outStream, "lastJoint = %s\n", lastJoint->name.Str() );
            fprintf( outStream, "getting local transform\n" );
        }

        SAA_elementSetUserData( scene, model, "GLOBAL", sizeof( SAA_Boolean ),
            TRUE, (void  **)&globalFlag );

        // get the local matrix
        SAA_modelGetMatrix( scene, model, SAA_COORDSYS_LOCAL,  matrix );

        // make this into a pfMatrix
        Matrix[0][0] = matrix[0][0];
        Matrix[0][1] = matrix[0][1];
        Matrix[0][2] = matrix[0][2];
        Matrix[0][3] = matrix[0][3];
        Matrix[1][0] = matrix[1][0];
        Matrix[1][1] = matrix[1][1];
        Matrix[1][2] = matrix[1][2];
        Matrix[1][3] = matrix[1][3];
        Matrix[2][0] = matrix[2][0];
        Matrix[2][1] = matrix[2][1];
        Matrix[2][2] = matrix[2][2];
        Matrix[2][3] = matrix[2][3];
        Matrix[3][0] = matrix[3][0];
        Matrix[3][1] = matrix[3][1];
        Matrix[3][2] = matrix[3][2];
        Matrix[3][3] = matrix[3][3];

        joint = _data.CreateJoint( lastJoint, name ); 
        joint->transform = Matrix;
    }
    // if we already have a root attach this joint to it
    else if (foundRoot)
    {
        if ( verbose >= 1 )
            fprintf( outStream, "getting global transform\n" );

        globalFlag = TRUE;

        SAA_elementSetUserData( scene, model, "GLOBAL", sizeof( SAA_Boolean ),
            TRUE, (void *)&globalFlag );

        // get the global matrix
        SAA_modelGetMatrix( scene, model, SAA_COORDSYS_GLOBAL,  matrix );

        // make this into a pfMatrix
        Matrix[0][0] = matrix[0][0];
        Matrix[0][1] = matrix[0][1];
        Matrix[0][2] = matrix[0][2];
        Matrix[0][3] = matrix[0][3];
        Matrix[1][0] = matrix[1][0];
        Matrix[1][1] = matrix[1][1];
        Matrix[1][2] = matrix[1][2];
        Matrix[1][3] = matrix[1][3];
        Matrix[2][0] = matrix[2][0];
        Matrix[2][1] = matrix[2][1];
        Matrix[2][2] = matrix[2][2];
        Matrix[2][3] = matrix[2][3];
        Matrix[3][0] = matrix[3][0];
        Matrix[3][1] = matrix[3][1];
        Matrix[3][2] = matrix[3][2];
        Matrix[3][3] = matrix[3][3];

        if ( verbose >= 1 )
            fprintf( outStream, "attaching orphan chain to root\n" );

        joint = _data.CreateJoint( rootJnt, name ); 
        joint->transform = Matrix;
        lastAnim = rootAnim;    
    }
    // if root, make a seperate tree for skeleton and
    // create required Table for the Egg heirarchy
    else 
    {
        if ( verbose >= 1 )
            fprintf( outStream, "getting global transform\n" );

        globalFlag = TRUE;

        SAA_elementSetUserData( scene, model, "GLOBAL", sizeof( SAA_Boolean ),
            TRUE, (void *)&globalFlag );

        // get the global matrix
        SAA_modelGetMatrix( scene, model, SAA_COORDSYS_GLOBAL,  matrix );

        // make this into a pfMatrix
        Matrix[0][0] = matrix[0][0];
        Matrix[0][1] = matrix[0][1];
        Matrix[0][2] = matrix[0][2];
        Matrix[0][3] = matrix[0][3];
        Matrix[1][0] = matrix[1][0];
        Matrix[1][1] = matrix[1][1];
        Matrix[1][2] = matrix[1][2];
        Matrix[1][3] = matrix[1][3];
        Matrix[2][0] = matrix[2][0];
        Matrix[2][1] = matrix[2][1];
        Matrix[2][2] = matrix[2][2];
        Matrix[2][3] = matrix[2][3];
        Matrix[3][0] = matrix[3][0];
        Matrix[3][1] = matrix[3][1];
        Matrix[3][2] = matrix[3][2];
        Matrix[3][3] = matrix[3][3];

        rootJnt = _data.CreateJoint( skeleton, "root" );
        rootJnt->transform.makeIdent();
        if ( verbose >= 1 )
            fprintf( outStream, "setting skeleton root\n" );
        rootJnt->flags |= EF_TRANSFORM;

        joint = _data.CreateJoint( rootJnt, name ); 
        joint->transform = Matrix;
        foundRoot = TRUE;
        if ( verbose >= 1 )
            fprintf( outStream, "found first chain\n" );

        // make skeleton table
        AnimGroup *skeletonTable;
        skeletonTable = animData.CreateTable( animRoot, "<skeleton>" ); 
        rootAnim = animData.CreateTable( skeletonTable, "root" ); 
        XfmSAnimTable *table = new XfmSAnimTable( );     
        table->name = "xform";
        table->fps = anim_rate;
        rootAnim->children.push_back( table );
        lastAnim = rootAnim;    
    }

    joint->flags |= EF_TRANSFORM;

    //if ( make_anim)
    //{
        AnimGroup *anim = animData.CreateTable( lastAnim, name );
        XfmSAnimTable *table = new XfmSAnimTable( );     
        if ( verbose >= 1 )
            fprintf( outStream, "created anim table: %s\n", "xform" );
        table->name = "xform";
        table->fps = anim_rate;
        anim->children.push_back( table );
        lastAnim = anim;
    //}

    // make this joint current parent of chain
    lastJoint = joint;
}


////////////////////////////////////////////////////////////////////
//     Function: MakeSoftSkin
//       Access: Public 
//  Description: Given a skeleton part find its envelopes (if any)
//                 get the vertices associated with the envelopes and
//                 their weights and make vertex ref's for the joint 
////////////////////////////////////////////////////////////////////
void soft2egg::
MakeSoftSkin( SAA_Scene *scene, SAA_Elem *model, SAA_Elem *models,
    int numModels, char *name )
{
    int            numEnv;
    SAA_ModelType   type;
    SAA_Elem    *envelopes;

    if ( verbose >= 1 )
        fprintf( outStream, "\n>found skeleton part( %s )!\n", name );

    SAA_skeletonGetNbEnvelopes( scene, model, &numEnv );
    
    if ( numEnv )
    {
        // it's got envelopes - must be soft skinned
        if ( verbose >= 1 )
            fprintf( outStream, "numEnv = %d\n", numEnv );

        // allocate envelope array
        envelopes = ( SAA_Elem *)malloc( sizeof( SAA_Elem )*numEnv );

        if ( envelopes != NULL )
        {
            int         thisEnv;
            SAA_EnvType envType;
            bool        hasEnvVertices = 0;

            SAA_skeletonGetEnvelopes( scene, model, numEnv, envelopes );

            for ( thisEnv = 0; thisEnv < numEnv; thisEnv++ )
            {
                if ( verbose >= 1 )
                    fprintf( outStream, "env[%d]: ", thisEnv );

                SAA_envelopeGetType( scene, &envelopes[thisEnv], &envType );

                if ( envType == SAA_ENVTYPE_NONE )
                {
                    if ( verbose >= 1 )
                        fprintf( outStream, "envType = none\n" );
                }
                else if ( envType == SAA_ENVTYPE_FLXLCL )
                {
                    if ( verbose >= 1 )
                        fprintf( outStream, "envType = flexible, local\n" );
                    hasEnvVertices = 1;
                }
                else if ( envType == SAA_ENVTYPE_FLXGLB )
                {
                    if ( verbose >= 1 )
                        fprintf( outStream, "envType = flexible, global\n" );
                    hasEnvVertices = 1;
                }
                else if ( envType == SAA_ENVTYPE_RGDGLB )
                {
                    if ( verbose >= 1 )
                        fprintf( outStream, "envType = rigid, global\n" );
                    hasEnvVertices = 1;
                }
                else
                {
                    if ( verbose >= 1 )
                        fprintf( outStream, "envType = unknown\n" );
                }
            }

            if ( hasEnvVertices)
            {
                int            *numEnvVertices;
                SAA_SubElem    *envVertices = NULL;

                numEnvVertices = (int *)malloc(sizeof(int)*numEnv);

                SAA_envelopeGetNbCtrlVertices( scene, model, numEnv,
                    envelopes, numEnvVertices );

                if ( numEnvVertices != NULL )
                {
                    int totalEnvVertices = 0;
                    int    i,j,k;

                    for( i = 0; i < numEnv; i++ )
                    {
                        totalEnvVertices += numEnvVertices[i];
                        if ( verbose >= 1 )
                            fprintf( outStream, "numEnvVertices[%d] = %d\n", 
                                i, numEnvVertices[i] );
                    }


                    if ( verbose >= 1 )
                        fprintf( outStream, "total env verts = %d\n", 
                            totalEnvVertices );    

                    if ( totalEnvVertices )
                    {
                    envVertices = (SAA_SubElem *)malloc(sizeof(SAA_SubElem)*totalEnvVertices);

                    if ( envVertices != NULL )
                    {

                        SAA_envelopeGetCtrlVertices( scene, model,
                            numEnv, envelopes, numEnvVertices, envVertices);
            
                        // loop through for each envelope
                        for ( i = 0; i < numEnv; i++ )
                        {
                            float *weights = NULL;
                            int       vertArrayOffset = 0;

                            if ( verbose >= 2 )
                                fprintf( outStream, "\nenvelope[%d]:\n", i );

                            weights = (float *)malloc(sizeof(float)*numEnvVertices[i]);

                            if ( weights )
                            {
                            char *envName;
                            int *vpoolMap = NULL;

                            for ( j = 0; j < i; j++ )
                                vertArrayOffset += numEnvVertices[j];

                            if ( verbose >= 1 )
                                fprintf( outStream, 
                                    "envVertArray offset = %d\n", 
                                    vertArrayOffset );

                            // get the weights of the envelope vertices
                            SAA_ctrlVertexGetEnvelopeWeights( 
                                    scene, model, &envelopes[i], 
                                    numEnvVertices[i], 
                                    &envVertices[vertArrayOffset], weights ); 

                            // Get the name of the envelope model
                            if ( use_prefix )
                            {
                                // Get the FULL name of the envelope
                                envName = GetFullName( scene, &envelopes[i] );
                            }
                            else
                            {
                                // Get the name of the envelope
                                envName = GetName( scene, &envelopes[i] );
                            }

                            if ( verbose >= 1 )
                                fprintf( outStream, "envelope name %s\n", envName );

                            // find out if envelope geometry is poly or nurb
                            //SAA_modelGetType( scene, 
                                //FindModelByName( envName, scene, 
                                    //models, numModels ), &type );

                            SAA_modelGetType( scene, &envelopes[i], &type );
			
			    if ( verbose >= 1 )
			    {
                                fprintf( outStream, "envelope model type ");
			
				if ( type == SAA_MSMSH )
					fprintf( outStream, "MESH\n" );
				else if ( type == SAA_MNSRF )
					fprintf( outStream, "NURBS\n" );
				else
					fprintf( outStream, "OTHER\n" );
			   }

                            int *envVtxIndices = NULL;
                            envVtxIndices = (int *)malloc(sizeof(int)*numEnvVertices[i]);

                            // Get the envelope vertex indices
                            SAA_ctrlVertexGetIndices( scene, &envelopes[i],                                     numEnvVertices[i], 
                                &envVertices[vertArrayOffset], envVtxIndices );

                            // find out how many vertices the model has
                            int modelNumVert;

                            SAA_modelGetNbVertices( scene, &envelopes[i], &modelNumVert );
                             
                            SAA_DVector *modelVertices = NULL;
                            modelVertices = (SAA_DVector *)malloc(sizeof(SAA_DVector)*modelNumVert);

                            // get the model vertices
                            SAA_modelGetVertices( scene, &envelopes[i],
                                SAA_GEOM_ORIGINAL, 0, modelNumVert, 
                                modelVertices );
                            
                            // create array of global model coords 
                            SAA_DVector *globalModelVertices = NULL;
                            globalModelVertices = (SAA_DVector *)malloc(sizeof(SAA_DVector)*modelNumVert);
                            float        matrix[4][4];

                            // tranform local model vert coords to global

                            // first get the global matrix
                            SAA_modelGetMatrix( scene, &envelopes[i],                                                 SAA_COORDSYS_GLOBAL,  matrix );

                            // populate array of global model verts
                            for ( j = 0; j < modelNumVert; j++ )
                            {
                                _VCT_X_MAT( globalModelVertices[j], 
                                    modelVertices[j], matrix );
                            }

                            // find the egg vertex pool that corresponds
                            // to this envelope model
                            EggVertexPool *envPool = 
                                (EggVertexPool *)(_data.pools.FindName( envName ));
                            // If we are outputting triangles:
                            // create an array that maps from a referenced
                            // vertex in the envelope to a corresponding 
                            // vertex in the egg vertex pool
                            //if ( (type == SAA_MNSRF) && !make_nurbs ) 
                            if ( !make_nurbs || (type == SAA_MSMSH) ) 
                            {
                                vpoolMap = FindClosestTriVert( envPool,
                                    globalModelVertices, modelNumVert );
                            }


                            if ( envPool != NULL )
                            {
    
                            // find the egg joint that corresponds to this model
                            EggJoint *joint = 
                                (EggJoint *)(skeleton->FindDescendent( name ));

														// this doesn't seem to be necessary 4/7/99
                            //EggJoint *parent = (EggJoint *)joint->parent;
														//assert(parent->IsA(NT_EggJoint));

                            // for every envelope vertex 
                            for (j = 0; j < numEnvVertices[i]; j++)
                            {
                                double scaledWeight =  weights[j]/ 100.0f;

                                // make sure its in legal range
                                if (( envVtxIndices[j] < modelNumVert )
                                    && ( envVtxIndices[j] >= 0 ))
                                {
                                  if ( (type == SAA_MNSRF) && make_nurbs ) 
                                  { 
                                    // assign all referenced control vertices
                                    joint->AddVertex( envPool->Vertex(envVtxIndices[j]), scaledWeight );

                                    if ( verbose >= 2 )
                                        fprintf( outStream, 
                                            "%d: adding vref to cv %d with weight %f\n", 
                                            j, envVtxIndices[j], scaledWeight );

                                    envPool->Vertex(envVtxIndices[j])->AddJoint( joint, scaledWeight );
                                    // set flag to show this vertex has
                                    // been assigned
                                    envPool->Vertex(envVtxIndices[j])->multipleJoints = 1;
                                  }
                                  else
                                  {    
                                    //assign all the tri verts associated
                                    // with this control vertex to joint 
                                    for ( k = 0; k < envPool->NumVertices(); k++ )    
                                    {
                                      if ( vpoolMap[k] == envVtxIndices[j] ) 
                                      {

                                        // add each vert in pool to last 
                                        // joint for soft skinning
                                        joint->AddVertex(envPool->Vertex(k), 
                                            scaledWeight);

                                        if ( verbose >= 2 )
                                            fprintf( outStream, 
                                                "%d: adding vref from cv %d to vert %d with weight %f(vpool)\n", 
                                                j, envVtxIndices[j], k, scaledWeight );

                                        envPool->Vertex(k)->AddJoint( joint, scaledWeight );
                                        // set flag to show this vertex has
                                        // been assigned
                                        envPool->Vertex(k)->multipleJoints = 1;
                                      }
                                    }
                                 }
                                }
                                else
                                    if ( verbose >= 2 )
                                        fprintf( outStream, 
                                            "%d: Omitted vref from cv %d with weight %f (out of range 0 to %d )\n",
                                            j, envVtxIndices[j], scaledWeight, modelNumVert );
                                    
                            }

                            }
                            else 
                                if ( verbose >= 2 )
                                    fprintf( outStream, "Couldn't find vpool %s!\n", envName );

                            //free( modelVertices ); 
                            //free( globalModelVertices ); 
                            //free( envVtxIndices );
                            //free( envName );
                            } //if (weights)
                            //free( weights );

                        } // for i

                    } // if (envVertices != NULL)
                    else
                        fprintf( outStream, "Not enough memory for envelope vertices...\n");
                    //free( envVertices );
                    } // if (totalEnvVertices) 
                    else
                        if ( verbose >= 1 )
                            fprintf( outStream, "No envelope vertices present...\n");

                    //free( numEnvVertices );

            } // if (numEnvVertices != NULL)

          } // if (hasEnvVertices)

        } // if (envelopes != NULL)
        else
            fprintf( outStream, "Not enough memory for envelopes...\n" );

        //free( envelopes );

    } //if (numEnv)

    else
        if ( verbose >= 1 )
            fprintf( outStream, "Skeleton member has no envelopes...\n" );
}


////////////////////////////////////////////////////////////////////
//     Function: CleanUpSoftSkin
//       Access: Public 
//  Description: Given a model, make sure all its vertices have been 
//                 soft assigned. If not hard assign to the last
//                 joint we saw.
////////////////////////////////////////////////////////////////////
void soft2egg::
CleanUpSoftSkin( SAA_Scene *scene, SAA_Elem *model, char *name )
{
    static EggJoint *joint;
    SAA_Elem        parent;
    SAA_ModelType     type;
    SAA_Boolean        skel;

    /////////////////////////////////////////////////
    // find out what type of node we're dealing with
    /////////////////////////////////////////////////
    SAA_modelGetType( scene, model, &type );

    char   *parentName;
    int        level;
    SAA_Elem    *searchNode = model;

    if ( verbose >= 1 )
        fprintf( outStream, "\nCleaning up model %s\n", name );

    // this step is weird - I think I want it here but it seems
    // to break some models. Files like props-props_wh_cookietime.3-0 in
    // /ful/rnd/pub/vrml/chip/chips_adventure/char/zone1/rooms/warehouse_final
    // need to do the "if (skel)" bit.

    // am I a skeleton too?
    SAA_modelIsSkeleton( scene, model, &skel );

    // if not look for the last skeleton part
    if ( skel ) 
        parentName = name;
    else do 
    {
        SAA_elementGetHierarchyLevel( scene, searchNode, &level );

        // make sure we don't try to get the root's parent
        if ( level )
        {
            SAA_modelGetParent( scene, searchNode, &parent );

            if ( use_prefix )
            {
                // Get the FULL name of the parent
                parentName = GetFullName( scene, &parent );
            }
            else
            {
                // Get the name of the parent
                parentName = GetName( scene, &parent );
            }

            SAA_modelGetType( scene, &parent, &type );

            SAA_modelIsSkeleton( scene, &parent, &skel );

            if ( verbose >= 1 )
                fprintf( outStream, "model %s, level %d, type %d, skel %d\n",
                    parentName, level, type, skel );

            searchNode = &parent;
        }
        else
        {
            // we reached the root of the tree
            parentName = NULL;
            if ( verbose >= 1 )
                fprintf( outStream, "at root of tree! level %d\n", level );
            break;
        }

    // look until parent is a joint or acts like one
    } while ( !skel && ( strstr( parentName,"joint") == NULL ));

    EggJoint    *thisJoint = NULL;

    if ( parentName != NULL )
    {
        if ( verbose >= 1 )
        {
            fprintf( outStream, "found model parent joint %s\n", parentName);
            fprintf( outStream, "looking for joint %s\n", parentName );
        }
        thisJoint = (EggJoint *)(skeleton->FindDescendent( parentName ));
    }
    else
        if ( verbose >= 1 )
                fprintf( outStream, "Couldn't find parent joint!\n");

    if ( thisJoint != NULL )
    {
        joint = thisJoint;
        if ( verbose >= 1 )
            fprintf( outStream, "setting joint to %s\n", parentName );

        //find the vpool for this model
        EggVertexPool *vPool = 
            (EggVertexPool *)(_data.pools.FindName( name ));

        if (vPool != NULL)
        {
            int i;
            double membership;
            int numVerts = vPool->NumVertices() ;


            if ( verbose >= 1 )
                fprintf( outStream, "found vpool %s w/ %d verts\n", 
                name, numVerts );
        
            for ( i = 0; i < numVerts; i++ )     
            {
                if ( vPool->Vertex(i)->multipleJoints != 1 )
                {
                    if ( verbose >= 1 )
                    {    
                        fprintf( outStream, "vpool %s vert %d", name, i );
                        fprintf( outStream, " not assigned!\n" );
                    }

                    // hard skin this vertex
                    joint->AddVertex( vPool->Vertex(i), 1.0f );
                }
                else
                {    
                    membership = vPool->Vertex(i)->NetMembership();


                    if ( verbose >= 1 )
                    {
                        fprintf( outStream, "vpool %s vert %d", name, 
                            i );
                        fprintf( outStream, " has membership %f\n", 
                            membership );
                    }

                    if ( membership == 0 )
                    {
                        if ( verbose >= 1 )
                            fprintf( outStream, "adding full weight..\n" );

                        // hard skin this vertex
                        joint->AddVertex( vPool->Vertex(i), 1.0f );
                    }
                }
            }
        }
        else
            if ( verbose >= 1 )
                fprintf( outStream, "couldn't find vpool %s\n", name );
    }
    else
    {
        if ( parentName != NULL )
            if ( verbose >= 1 )
                fprintf( outStream, "Couldn't find joint %s\n", parentName );
    }
}

//////////////////////////////////////////////////////////////////////
//     Function: MakeAnimTable
//       Access: Public 
//  Description: Given a scene and a skeleton part ,get all the 
//                   position, rotation, and scale for the skeleton
//                 part for this frame and write them out as Egg
//                 animation tables. 
////////////////////////////////////////////////////////////////////
void soft2egg::
MakeAnimTable( SAA_Scene *scene, SAA_Elem *skeletonPart, char *name )
{    

    if ( skeletonPart != NULL )
    {
        float    i,j,k;
        float    h,p,r;
        float    x,y,z;
        int         size;
        SAA_Boolean globalFlag = FALSE;
        SAA_Boolean bigEndian;

        if ( verbose >= 1 )
            fprintf( outStream, "\n\nanimating child %s\n", name );

        SAA_elementGetUserDataSize( scene, skeletonPart, "GLOBAL", &size );
        
        if ( size != 0 )    
            SAA_elementGetUserData( scene, skeletonPart, "GLOBAL", 
                sizeof( SAA_Boolean), &bigEndian, (void *)&globalFlag );
 
        if ( globalFlag )
        {
            if ( verbose >= 1 )
                fprintf( outStream, " using global matrix\n" );

            //get SAA orientation
            SAA_modelGetRotation( scene, skeletonPart, SAA_COORDSYS_GLOBAL, 
                &p, &h, &r ); 

            //get SAA translation
            SAA_modelGetTranslation( scene, skeletonPart, SAA_COORDSYS_GLOBAL, 
                &x, &y, &z ); 

            //get SAA scaling
            SAA_modelGetScaling( scene, skeletonPart, SAA_COORDSYS_GLOBAL, 
                &i, &j, &k );
        }
        else
        {
            if ( verbose >= 1 )
                fprintf( outStream, "using local matrix\n" );

            //get SAA orientation
            SAA_modelGetRotation( scene, skeletonPart, SAA_COORDSYS_LOCAL, 
                &p, &h, &r ); 

            //get SAA translation
            SAA_modelGetTranslation( scene, skeletonPart, SAA_COORDSYS_LOCAL, 
                &x, &y, &z ); 

            //get SAA scaling
            SAA_modelGetScaling( scene, skeletonPart, SAA_COORDSYS_LOCAL, 
                &i, &j, &k );
        }


        if ( verbose >= 2 )
            fprintf( outStream, "\nanim data: %f %f %f\n\t%f %f %f\n\t%f %f %f\n",
                i, j, k, h, p, r, x, y, z );  

        // find the appropriate anim table for this skeleton part
        AnimGroup     *thisGroup;
        XfmSAnimTable *thisTable;

        //find the anim table associated with this group
        thisGroup = (AnimGroup *)(animRoot->FindDescendent( name ));
        if ( verbose >= 2 )
            fprintf( outStream, "\nlooking for anim group %s\n", name );
        if ( thisGroup != NULL )
        {
            thisTable = (XfmSAnimTable *)(thisGroup->FindDescendent( "xform" ));

            if ( thisTable != NULL )
            {    
                thisTable->sub_tables[0].AddElement( i );
                thisTable->sub_tables[1].AddElement( j ); 
                thisTable->sub_tables[2].AddElement( k ); 
                thisTable->sub_tables[3].AddElement( p ); 
                thisTable->sub_tables[4].AddElement( h ); 
                thisTable->sub_tables[5].AddElement( r ); 
                thisTable->sub_tables[6].AddElement( x ); 
                thisTable->sub_tables[7].AddElement( y ); 
                thisTable->sub_tables[8].AddElement( z ); 
            }
            else
                fprintf( outStream, "Couldn't allocate anim table\n" );
        }
        else
            if ( verbose >= 2 )
                fprintf( outStream, "Couldn't find anim group  %s\n",  name );
    }
    else
    {
        if ( verbose >= 2 )
            fprintf( outStream, "Cannot build anim table - no skeleton\n" );
    }
    
}

////////////////////////////////////////////////////////////////////
//     Function: MakeVertexOffsets
//       Access: Public 
//  Description: Given a scene, a model , the vertices of its original
//                 shape and its name find the difference between the 
//                 geometry of its key shapes and the models original 
//                 geometry and add morph vertices to the egg data to 
//                 reflect these changes.
////////////////////////////////////////////////////////////////////
void soft2egg::
MakeVertexOffsets( SAA_Scene *scene, SAA_Elem *model, SAA_ModelType type,
    int numShapes, int numOrigVert, SAA_DVector *originalVerts, float
    matrix[4][4], char *name )
{
    int i, j;
    int offset;
    int    numCV;
    char *mTableName;
    SAA_DVector *shapeVerts = NULL;
    SAA_DVector *uniqueVerts = NULL;

    if ( (type == SAA_MNSRF) && make_nurbs ) 
        SAA_nurbsSurfaceSetStep( scene, model, nurbs_step, nurbs_step );

    SAA_modelGetNbVertices( scene, model, &numCV );

    // get the shape verts
    uniqueVerts = (SAA_DVector *)malloc(sizeof(SAA_DVector)*numCV);
    SAA_modelGetVertices( scene, model, SAA_GEOM_ORIGINAL, 0,
        numCV, uniqueVerts );

    if ( verbose >= 2 )
        fprintf( outStream, "%d CV's\n", numCV ); 

    if ( verbose >= 2 )
    {    
        for ( i = 0; i < numCV; i++ )
            fprintf( outStream, "uniqueVerts[%d] = %f %f %f %f\n", i, 
                uniqueVerts[i].x, uniqueVerts[i].y, 
                uniqueVerts[i].z,  uniqueVerts[i].w );
    }

    // iterate through for each key shape (except original)
    for ( i = 1; i < numShapes; i++ )
    {
        mTableName = MakeTableName( name, i );

        if ( verbose >= 1 )
        {
            fprintf( outStream, "\nMaking geometry offsets for %s...\n", 
                mTableName );

            if ( (type == SAA_MNSRF) && make_nurbs ) 
                fprintf( outStream, "calculating NURBS morphs...\n" );
            else 
                fprintf( outStream, "calculating triangle morphs...\n" );
        }

        // get the shape verts
        shapeVerts = (SAA_DVector *)malloc(sizeof(SAA_DVector)*numCV);
        SAA_modelGetVertices( scene, model, SAA_GEOM_SHAPE, i+1,
            numCV, shapeVerts );

        if ( verbose >= 2 )
        { 
            for ( j=0; j < numCV; j++ )
            {
                fprintf( outStream, "shapeVerts[%d] = %f %f %f\n", j, 
                    shapeVerts[j].x, shapeVerts[j].y, shapeVerts[j].z );
            }
        }

        // find the appropriate vertex pool
        EggVertexPool *vPool = 
            (EggVertexPool *)(_data.pools.FindName( name ));

        // for every original vertex, compare to the corresponding
        // key shape vertex and see if a vertex offset is needed 
        for ( j=0; j < numOrigVert; j++ )
        {
            double    dx, dy, dz;
        
            if ( (type == SAA_MNSRF) && make_nurbs )
            {
                //dx = shapeVerts[j].x - (originalVerts[j].x/originalVerts[j].w); 
                //dy = shapeVerts[j].y - (originalVerts[j].y/originalVerts[j].w); 
                //dz = shapeVerts[j].z - (originalVerts[j].z/originalVerts[j].w); 
                dx = shapeVerts[j].x - originalVerts[j].x; 
                dy = shapeVerts[j].y - originalVerts[j].y; 
                dz = shapeVerts[j].z - originalVerts[j].z; 
            }
            else 
            {
                // we need to map from original vertices
                // to triangle shape vertices here
                offset = findShapeVert( originalVerts[j], uniqueVerts,
                    numCV ); 

                dx = shapeVerts[offset].x - originalVerts[j].x; 
                dy = shapeVerts[offset].y - originalVerts[j].y; 
                dz = shapeVerts[offset].z - originalVerts[j].z; 
            }

            if ( verbose >= 2 )
            {
                fprintf( outStream, "oVert[%d] = %f %f %f %f\n", j, 
                    originalVerts[j].x, originalVerts[j].y, 
                    originalVerts[j].z,  originalVerts[j].w );

                if ( (type == SAA_MNSRF) && make_nurbs )
                {
                    fprintf( outStream, "global shapeVerts[%d] = %f %f %f %f\n",                        j, shapeVerts[j].x, shapeVerts[j].y, 
                        shapeVerts[j].z, shapeVerts[j].w );
                }
                else
                {
                    fprintf( outStream, 
                        "global shapeVerts[%d] = %f %f %f\n", offset, 
                        shapeVerts[offset].x, 
                        shapeVerts[offset].y, 
                        shapeVerts[offset].z );
                }

                fprintf( outStream, "%d: dx = %f, dy = %f, dz = %f\n", j,
                    dx, dy, dz );
            }

            // if change isn't negligible, make a morph vertex entry 
            double total = fabs(dx)+fabs(dy)+fabs(dz);
            if ( total > 0.00001 )
            {
                if ( vPool != NULL )
                {
                    // create offset
                    EggMorphOffset *dxyz = 
                        new EggMorphOffset( mTableName, dx, dy, dz );

                    EggVertex *eggVert;

                    // get the appropriate egg vertex
                    eggVert = vPool->Vertex(j);    

                    // add the offset to the vertex
                    eggVert->morphs.push_back( *dxyz );
                }
                else
                    fprintf( outStream, "Error: couldn't find vertex pool %s\n", name ); 
                
            } // if total
        } //for j
    } //for i
}


////////////////////////////////////////////////////////////////////
//     Function: MakeMorphTable
//       Access: Public 
//  Description: Given a scene, a model, a name and a frame time,
//                 determine what type of shape interpolation is
//                 used and call the appropriate function to extract
//                 the shape weight info for this frame...
////////////////////////////////////////////////////////////////////
void soft2egg::
MakeMorphTable( SAA_Scene *scene, SAA_Elem *model, SAA_Elem *models,
    int numModels, char *name, float time )
{
    int         numShapes;
    SAA_AnimInterpType    type;

    // Get the number of key shapes
    SAA_modelGetNbShapes( scene, model, &numShapes );

    if ( numShapes > 0 )
    {
        if ( verbose >= 1 )
            fprintf( outStream, "MakeMorphTable: %s: num shapes: %d\n", 
                name, numShapes);

        SAA_modelGetShapeInterpolation( scene, model, &type );

        if ( type == SAA_ANIM_LINEAR || type == SAA_ANIM_CARDINAL )
        {
            MakeLinearMorphTable( scene, model, numShapes, name, time );
        }
        else     // must be weighted...
        {
            // check first for expressions
            MakeExpressionMorphTable( scene, model, models, numModels,
                numShapes, name, time );
        }

    }
    
}


////////////////////////////////////////////////////////////////////
//     Function: MakeLinearMorphTable
//       Access: Public 
//  Description: Given a scene, a model, its name, and the time,
//                 get the shape fcurve for the model and determine
//                 the shape weights for the given time and use them
//                 to populate the morph table.
////////////////////////////////////////////////////////////////////
void soft2egg::
MakeLinearMorphTable( SAA_Scene *scene, SAA_Elem *model, int numShapes,
    char *name, float time )
{    
    int            i;
    SAA_Elem     fcurve;
    float        curveVal;
    SAnimTable *thisTable;
    char          *tableName;

    if ( verbose >= 1 )
        fprintf( outStream, "linear interp, getting fcurve\n" );

    SAA_modelFcurveGetShape( scene, model, &fcurve );

    SAA_fcurveEval( scene, &fcurve, time, &curveVal );    
    
    if ( verbose >= 2 )
        fprintf( outStream, "at time %f, fcurve for %s = %f\n", time,
            name, curveVal );

    float nextVal = 0.0f;

    // populate morph table values for this frame
    for ( i = 1; i < numShapes; i++ )
    {
        // derive table name from the model name
        tableName = MakeTableName( name, i );

        if ( verbose >= 2 )
            fprintf( outStream, "Linear: looking for table '%s'\n", tableName );

        //find the morph table associated with this key shape
        thisTable = (SAnimTable *)(morphRoot->FindDescendent( tableName ));

        if ( thisTable != NULL )
        {    
            if ( i == (int)curveVal )
            {
                if ( curveVal - i == 0 )
                {
                    thisTable->AddElement( 1.0f ); 
                    if ( verbose >= 2 )
                        fprintf( outStream, "adding element 1.0f\n" );
                }
                else
                {
                    thisTable->AddElement( 1.0f - (curveVal - i) );
                    nextVal = curveVal - i;
                    if ( verbose >= 2 )
                        fprintf( outStream, "adding element %f\n",                                                 1.0f - (curveVal - i) );
                }
            }
            else
            {
                if ( nextVal )
                {
                    thisTable->AddElement( nextVal );
                    nextVal = 0.0f;
                    if ( verbose >= 2 )
                        fprintf( outStream, "adding element %f\n", nextVal );
                }
                else
                {
                    thisTable->AddElement( 0.0f );
                    if ( verbose >= 2 )
                        fprintf( outStream, "adding element 0.0f\n" );
                }
            }

            if ( verbose >= 2 )
                fprintf( outStream, " to '%s'\n", tableName );
        }
        else
            fprintf( outStream, "%d: Couldn't find table '%s'\n", 
                i, tableName );
    }    

}

////////////////////////////////////////////////////////////////////
//     Function: MakeWeightedMorphTable
//       Access: Public 
//  Description: Given a scene, a model, a list of all models in the
//                 scene, the number of models in the scece, the number 
//                 of key shapes for this model, the name of the model
//                 and the current time, determine what method of
//                 controlling the shape weights is used and call the
//                 appropriate routine.
////////////////////////////////////////////////////////////////////
void soft2egg::
MakeWeightedMorphTable( SAA_Scene *scene, SAA_Elem *model, SAA_Elem *models, 
    int numModels,  int numShapes, char *name, float time )
{
    SI_Error     result;
    SAA_Elem    *weightCurves;
    float         curveVal;
    SAnimTable *thisTable;
    char          *tableName;

    // allocate array of weight curves (one for each shape)
    weightCurves = ( SAA_Elem *)malloc( sizeof( SAA_Elem ) * numShapes ); 

    result = SAA_modelFcurveGetShapeWeights(
        scene, model, numShapes, weightCurves );

    if ( result == SI_SUCCESS )
    {
        for ( int i = 1; i < numShapes; i++ )
        {
						SAA_fcurveEval( scene, &weightCurves[i], time, &curveVal );    

						// make sure soft gave us a reasonable number
						if (!isNum(curveVal))
							curveVal = 0.0f;

						if ( verbose >= 2 )
								fprintf( outStream, "at time %f, weightCurve[%d] for %s = %f\n",                     time, i, name, curveVal );


            // derive table name from the model name
            tableName = MakeTableName( name, i );

						// find and populate shape table
            if ( verbose >= 2 )
                fprintf( outStream, "Weight: looking for table '%s'\n", 
								tableName );

            //find the morph table associated with this key shape
            thisTable = (SAnimTable *)(morphRoot->FindDescendent( tableName ));

            if ( thisTable != NULL )
            {    
                thisTable->AddElement( curveVal ); 
                if ( verbose >= 2 )
                    fprintf( outStream, "adding element %f\n", curveVal );
            }
            else
                fprintf( outStream, "%d: Couldn't find table '%s'\n", 
                    i, tableName );
        }
    }
}


////////////////////////////////////////////////////////////////////
//     Function: MakeExpressionMorphTable
//       Access: Public 
//  Description: Given a scene, a model and its number of key shapes
//                 generate a morph table describing transitions btwn
//                 the key shapes by evaluating the positions of the
//                 controlling sliders. 
////////////////////////////////////////////////////////////////////
void soft2egg::
MakeExpressionMorphTable( SAA_Scene *scene, SAA_Elem *model, SAA_Elem *models, 
    int numModels,  int numShapes, char *name, float time )
{    
    int            j;
    SAnimTable *thisTable;
    char          *tableName;
    char          *sliderName;
    char        *track;
    int            numExp;
    SAA_Elem   *expressions;
    float        expVal;
    float        sliderVal;

    // populate morph table values for this frame

    // compose track name
    track = NULL;

    // find how many expressions for this shape
    SAA_elementGetNbExpressions( scene, model, track, FALSE, &numExp );

    if ( verbose >= 2 )
        fprintf( outStream, "%s has %d RHS expressions\n", name, numExp );

    if ( numExp )
    {
        // get the expressions for this shape
        expressions = (SAA_Elem *)malloc(sizeof(SAA_Elem)*numExp);    

        if ( verbose >= 1 )
            fprintf( outStream, "getting %d RHS expressions...\n", numExp );

        result = SAA_elementGetExpressions( scene, model, track, FALSE, 
            numExp, expressions );

        if ( !result )
        {
            for ( j = 1; j < numExp; j++ )
            {
                if ( verbose >= 2 )
                {
                // debug see what we got
                int numvars;
        
                SAA_expressionGetNbVars( scene, &expressions[j], &numvars );

                int *varnamelen;
                int *varstrlen;
                int  expstrlen;

                varnamelen = (int *)malloc(sizeof(int)*numvars);
                varstrlen = (int *)malloc(sizeof(int)*numvars);

                SAA_expressionGetStringLengths( scene, &expressions[j],
                    numvars, varnamelen, varstrlen, &expstrlen );    

                int *varnamesizes;    
                int *varstrsizes;

                varnamesizes = (int *)malloc(sizeof(int)*numvars);
                varstrsizes = (int *)malloc(sizeof(int)*numvars);

                for ( int k = 0; k < numvars; k++ )
                {
                    varnamesizes[k] = varnamelen[k] + 1;
                    varstrsizes[k] = varstrlen[k] + 1;
                }
    
                int expstrsize = expstrlen + 1;

                char **varnames;
                char **varstrs;

                varnames = (char **)malloc(sizeof(char *)*numvars);
                varstrs = (char **)malloc(sizeof(char *)*numvars);

                for ( k = 0; k < numvars; k++ )
                {
                    varnames[k] = (char *)malloc(sizeof(char)*
                        varnamesizes[k]);

                    varstrs[k] = (char *)malloc(sizeof(char)*
                        varstrsizes[k]);
                }
        
                char *expstr = (char *)malloc(sizeof(char)* expstrsize );    

                SAA_expressionGetStrings( scene, &expressions[j], numvars,
                    varnamesizes, varstrsizes, expstrsize, varnames,
                    varstrs, expstr );
                
                if ( verbose >= 2 )
                {
                    fprintf( outStream, "expression = '%s'\n", expstr );
                    fprintf( outStream, "has %d variables\n", numvars );
                }
                } //if verbose
                
                if ( verbose >= 2 )
                    fprintf( outStream, "evaling expression...\n" );

                SAA_expressionEval( scene, &expressions[j], time, &expVal ); 

                if ( verbose >= 2 )
                    fprintf( outStream, "time %f: exp val %f\n", 
                        time, expVal );

                // derive table name from the model name
                tableName = MakeTableName( name, j );

                if ( verbose >= 2 )
                    fprintf( outStream, "Exp: looking for table '%s'\n", 
                        tableName );

                //find the morph table associated with this key shape
                thisTable = (SAnimTable *)
                    (morphRoot->FindDescendent( tableName ));

                if ( thisTable != NULL )
                {    
                    thisTable->AddElement( expVal ); 
                    if ( verbose >= 1 )    
                        fprintf( outStream, "%d: adding element %f to %s\n",
                            j, expVal, tableName );
                    fflush( outStream );
                }
                else
                {
                    fprintf( outStream, "%d: Couldn't find table '%s'", j, 
                            tableName ); 

                    fprintf( outStream, " for value %f\n", expVal );
                }
            }
        }
        else
            fprintf( outStream, "couldn't get expressions!!!\n" );
    }
    else 
        // no expression, use weight curves
        MakeWeightedMorphTable( scene, model, models, numModels, 
            numShapes, name, time );

}


////////////////////////////////////////////////////////////////////
//     Function: MakeTexAnim
//       Access: Public 
//  Description: Given a scene, a POLYGON model, and the name
//                 of the that model, get the u and v offsets for
//                 the current frame. 
////////////////////////////////////////////////////////////////////
void soft2egg::
MakeTexAnim( SAA_Scene *scene, SAA_Elem *model, char *modelName )
{
    if ( verbose >= 1 )
        fprintf( outStream, "\n\nmaking texture animation for %s...\n", 
            modelName );

    // get the color of the surface
    int         numMats;
    pfVec4        Color;
    SAA_Elem    *materials;
    void        *relinfo;

    SAA_modelRelationGetMatNbElements( scene, model, FALSE, &relinfo,
        &numMats ); 

    if ( verbose >= 2 )    
        fprintf( outStream, "surface has %d materials\n", numMats );

    if ( numMats )
    {
        float r,g,b,a;

        materials = (SAA_Elem *)malloc(sizeof(SAA_Elem)*numMats);
        
        SAA_modelRelationGetMatElements( scene, model, relinfo, 
            numMats, materials ); 

        SAA_materialGetDiffuse( scene, &materials[0], &r, &g, &b );
        SAA_materialGetTransparency( scene, &materials[0], &a );
        Color.set( r, g, b, 1.0f - a );

        int numTexLoc = 0;
        int numTexGlb = 0;

        // ASSUME only one texture per material
        SAA_Elem tex;

        // find out how many local textures per surface
        // ASSUME it only has one material
        SAA_materialRelationGetT2DLocNbElements( scene, &materials[0],
            FALSE, &relinfo, &numTexLoc );

        // if present, get local textures
        if ( numTexLoc )
        {
            if ( verbose >= 1 )
                fprintf( outStream, "%s had %d local tex\n", modelName, 
                    numTexLoc );

            // get the referenced texture
            SAA_materialRelationGetT2DLocElements( scene, &materials[0],
                TEX_PER_MAT, &tex ); 

        }
        // if no locals, try to get globals
        else
        {
            SAA_modelRelationGetT2DGlbNbElements( scene, model,
                FALSE, &relinfo, &numTexGlb );

            if ( numTexGlb )
            {
                if ( verbose >= 1 )
                    fprintf( outStream, "%s had %d global tex\n", modelName, numTexGlb );

                // get the referenced texture
                SAA_modelRelationGetT2DGlbElements( scene, 
                    model, TEX_PER_MAT, &tex ); 
            }
        }

        // add tex ref's if we found any textures
        if ( numTexLoc || numTexGlb) 
        {
            char    *fullTexName = NULL;
            char    *texName = NULL;
            char    *uniqueTexName = NULL;
            int      texNameLen;

            // get its name 
            SAA_texture2DGetPicNameLength( scene, &tex, &texNameLen);
            fullTexName = (char *)malloc(sizeof(char)*++texNameLen);
            SAA_texture2DGetPicName( scene, &tex, texNameLen, 
                fullTexName );

            // append unique identifier to texname for
            // this particular object
            uniqueTexName = (char *)malloc(sizeof(char)*
                (strlen(modelName)+strlen(texName)+3) );
            sprintf( uniqueTexName, "%s-%s", modelName, texName );
            if ( verbose >= 2 )
                fprintf( outStream, "referencing tref %s\n", 
                    uniqueTexName );

            float uScale;
            float vScale;
            float uOffset;
            float vOffset;
            SAA_Boolean    uv_swap = FALSE;

            // get texture offset info
            SAA_texture2DGetUScale( scene, &tex, &uScale );
            SAA_texture2DGetVScale( scene, &tex, &vScale );
            SAA_texture2DGetUOffset( scene, &tex, &uOffset );
            SAA_texture2DGetVOffset( scene, &tex, &vOffset );
            SAA_texture2DGetUVSwap( scene, &tex, &uv_swap );


            if ( verbose >= 2 )
            {
                fprintf( outStream, "tex uScale: %f\n", uScale );
                fprintf( outStream, "tex vScale: %f\n", vScale );
                fprintf( outStream, "tex uOffset: %f\n", uOffset );
                fprintf( outStream, "tex vOffset: %f\n", vOffset );
                if ( uv_swap )
                    fprintf( outStream, "nurbTex u & v swapped!\n" );
                else
                    fprintf( outStream, "nurbTex u & v NOT swapped\n" );
            }


            // find the vpool for this model
            EggVertexPool *vPool = 
                (EggVertexPool *)(_data.pools.FindName( modelName ));

            // if we found the pool
            if ( vPool != NULL )
            {
                // generate duv's for model
                float oldOffsets[4];
                double u, v, du, dv;    
                int        size;
                SAA_Boolean bigEndian;

                SAA_elementGetUserDataSize( scene, model, "TEX_OFFSETS",                                 &size );

                if ( size != 0 )    
                {
                    // remember original texture offsets future reference
                    SAA_elementGetUserData( scene, model, "TEX_OFFSETS", 
                        size, &bigEndian, (void *)&oldOffsets );

                    // get the original scales and offsets
                    u = oldOffsets[0];
                    v = oldOffsets[1];

                    du = u - uOffset;
                    dv = v - vOffset;

                    if ( verbose >= 1 )
                    {
                        fprintf( outStream, "original u = %f, v = %f\n",
                            u, v );
                        fprintf( outStream, "u = %f, v = %f\n",
                            uOffset, vOffset );
                        fprintf( outStream, "du = %f, dv = %f\n",
                            du, dv );
                    }

                    strstream uName, vName;

                    // create duv target names
                    uName << modelName << ".u" << ends;
                    vName << modelName << ".v" << ends;

                    // find the appropriate table to store the
                    // duv animation info into
                    SAnimTable *thisTable;

                    //find the duv U table associated with this model
                    thisTable = (SAnimTable *)(morphRoot->FindDescendent( 
                        uName.str() ));

                    if ( thisTable != NULL )
                    {    
                        thisTable->AddElement( du ); 
                        if ( verbose >= 1 )
                            fprintf( outStream, "adding element %f to %s\n", 
                                du, uName.str() );
                    }
                    else
                        fprintf( outStream, "Couldn't find uTable %s\n", 
                            uName.str() );

                    //find the duv V table associated with this model
                    thisTable = (SAnimTable *)(morphRoot->FindDescendent( 
                        vName.str() ));

                    if ( thisTable != NULL )
                    {    
                        thisTable->AddElement( dv ); 
                        if ( verbose >= 1 )
                            fprintf( outStream, "adding element %f to %s\n", 
                                dv, uName.str() );
                    }
                    else
                        fprintf( outStream, "Couldn't find vTable %s\n", 
                            uName.str() );
                }
            }
            else
                if ( verbose >= 2 )
                    fprintf( outStream, "Couldn't find vpool %s\n", modelName );
            
        }

        //free( materials );

    }

}
#endif

////////////////////////////////////////////////////////////////////
//     Function: Main
//       Access: Private 
//  Description: Instantiate converter and process a file
////////////////////////////////////////////////////////////////////
EXPCL_MISC SI_Error soft2egg(int argc, char *argv[]) {
  // pass control to the c++ system
  init_soft2egg(argc, argv);
  return SI_SUCCESS;
}
#ifdef __cplusplus
}
#endif
