objExport.cpp

//-
// ==========================================================================
// Copyright (C) 1995 - 2006 Autodesk, Inc. and/or its licensors.  All 
// rights reserved.
//
// The coded instructions, statements, computer programs, and/or related 
// material (collectively the "Data") in these files contain unpublished 
// information proprietary to Autodesk, Inc. ("Autodesk") and/or its 
// licensors, which is protected by U.S. and Canadian federal copyright 
// law and by international treaties.
//
// The Data is provided for use exclusively by You. You have the right 
// to use, modify, and incorporate this Data into other products for 
// purposes authorized by the Autodesk software license agreement, 
// without fee.
//
// The copyright notices in the Software and this entire statement, 
// including the above license grant, this restriction and the 
// following disclaimer, must be included in all copies of the 
// Software, in whole or in part, and all derivative works of 
// the Software, unless such copies or derivative works are solely 
// in the form of machine-executable object code generated by a 
// source language processor.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND. 
// AUTODESK DOES NOT MAKE AND HEREBY DISCLAIMS ANY EXPRESS OR IMPLIED 
// WARRANTIES INCLUDING, BUT NOT LIMITED TO, THE WARRANTIES OF 
// NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR 
// PURPOSE, OR ARISING FROM A COURSE OF DEALING, USAGE, OR 
// TRADE PRACTICE. IN NO EVENT WILL AUTODESK AND/OR ITS LICENSORS 
// BE LIABLE FOR ANY LOST REVENUES, DATA, OR PROFITS, OR SPECIAL, 
// DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES, EVEN IF AUTODESK 
// AND/OR ITS LICENSORS HAS BEEN ADVISED OF THE POSSIBILITY 
// OR PROBABILITY OF SUCH DAMAGES.
//
// ==========================================================================
//+

#include <string.h> 
#include <sys/types.h>
#include <maya/MStatus.h>
#include <maya/MPxCommand.h>
#include <maya/MString.h>
#include <maya/MStringArray.h>
#include <maya/MArgList.h>
#include <maya/MGlobal.h>
#include <maya/MSelectionList.h>
#include <maya/MItSelectionList.h>
#include <maya/MPoint.h>
#include <maya/MPointArray.h>
#include <maya/MDagPath.h>
#include <maya/MDagPathArray.h>
#include <maya/MFnPlugin.h>
#include <maya/MFnMesh.h>
#include <maya/MFnSet.h>
#include <maya/MItMeshPolygon.h>
#include <maya/MItMeshVertex.h>
#include <maya/MItMeshEdge.h>
#include <maya/MFloatVector.h>
#include <maya/MFloatVectorArray.h>
#include <maya/MFloatArray.h>
#include <maya/MObjectArray.h>
#include <maya/MObject.h>
#include <maya/MPlug.h>
#include <maya/MPxFileTranslator.h>
#include <maya/MFnDagNode.h>
#include <maya/MItDag.h>
#include <maya/MDistance.h>
#include <maya/MIntArray.h>
#include <maya/MIOStream.h>

#if defined (_WIN32)
#define strcasecmp stricmp
#elif defined  (OSMac_)
extern "C" int strcasecmp (const char *, const char *);
extern "C" Boolean createMacFile (const char *fileName, FSRef *fsRef, long creator, long type);
#endif

#define NO_SMOOTHING_GROUP      -1
#define INITIALIZE_SMOOTHING    -2
#define INVALID_ID              -1

//
// Edge info structure
//
typedef struct EdgeInfo {
    int                 polyIds[2]; // Id's of polygons that reference edge
    int                 vertId;     // The second vertex of this edge
    struct EdgeInfo *   next;       // Pointer to next edge
    bool                smooth;     // Is this edge smooth
} * EdgeInfoPtr;


class ObjTranslator : public MPxFileTranslator {
public:
                    ObjTranslator () {};
    virtual         ~ObjTranslator () {};
    static void*    creator();

    MStatus         reader ( const MFileObject& file,
                             const MString& optionsString,
                             FileAccessMode mode);

    MStatus         writer ( const MFileObject& file,
                             const MString& optionsString,
                             FileAccessMode mode );
    bool            haveReadMethod () const;
    bool            haveWriteMethod () const;
    MString         defaultExtension () const;
    MFileKind       identifyFile ( const MFileObject& fileName,
                                   const char* buffer,
                                   short size) const;
private:
    void            outputSetsAndGroups    ( MDagPath&, int, bool, int );
    MStatus         OutputPolygons( MDagPath&, MObject& );
    MStatus         exportSelected();
    MStatus         exportAll();
    void            initializeSetsAndLookupTables( bool exportAll );
    void            freeLookupTables();
    bool            lookup( MDagPath&, int, int, bool );
    void            setToLongUnitName( const MDistance::Unit&, MString& );
    void            recFindTransformDAGNodes( MString&, MIntArray& );
    

    // Edge lookup methods
    //
    void            buildEdgeTable( MDagPath& );
    void            addEdgeInfo( int, int, bool );
    EdgeInfoPtr     findEdgeInfo( int, int );
    void            destroyEdgeTable();
    bool            smoothingAlgorithm( int, MFnMesh& );

private:
    // counters
    int v,vt,vn;
    // offsets
    int voff,vtoff,vnoff;
    // options
    bool groups, ptgroups, materials, smoothing, normals;

    FILE *fp;
    
    // Keeps track of all sets.
    //
    int numSets;
    MObjectArray *sets;
    
    // Keeps track of all objects and components.
    // The Tables are used to mark which sets each 
    // component belongs to.
    //
    MStringArray *objectNames;
    
    bool **polygonTablePtr;
    bool **vertexTablePtr;
    bool * polygonTable;
    bool * vertexTable;
    bool **objectGroupsTablePtr;
    
    
    // Used to determine if the last set(s) written out are the same
    // as the current sets to be written. We don't need to write out
    // sets unless they change between components. Same goes for
    // materials.
    //
    MIntArray *lastSets;
    MIntArray *lastMaterials;
    
    // We have to do 2 dag iterations so keep track of the
    // objects found in the first iteration by this index.
    //
    int objectId;
    int objectCount;
    
    // Edge lookup table (by vertex id) and smoothing group info
    //
    EdgeInfoPtr *   edgeTable;
    int *           polySmoothingGroups;
    int             edgeTableSize;
    int             nextSmoothingGroup;
    int             currSmoothingGroup;
    bool            newSmoothingGroup;

    // List of names of the mesh shapes that we export from maya
    MStringArray    objectNodeNamesArray;

    // Used to keep track of Maya groups (transform DAG nodes) that
    // contain objects being exported
    MStringArray    transformNodeNameArray;
};


const char *const objOptionScript = "objExportOptions";
const char *const objDefaultOptions =
    "groups=1;"
    "ptgroups=1;"
    "materials=1;"
    "smoothing=1;"
    "normals=1;"
    ;


void* ObjTranslator::creator()
{
    return new ObjTranslator();
}


MStatus ObjTranslator::reader ( const MFileObject& file,
                                const MString& options,
                                FileAccessMode mode)
{
    fprintf(stderr, "ObjTranslator::reader called in error\n");
    return MS::kFailure;
}

#if defined (OSMac_)

// Convert file system representations
// Possible styles: kCFURLHFSPathStyle, kCFURLPOSIXPathStyle
// kCFURLHFSPathStyle = Emerald:aw:Maya:projects:default:scenes:eagle.ma
// kCFURLPOSIXPathStyle = /Volumes/Emerald/aw/Maya/projects/default/scenes/eagle.ma
// The conversion will be done in place, so make sure fileName is big enough
// to hold the result
//
static Boolean
convertFileRepresentation (char *fileName, short inStyle, short outStyle)
{
    if (fileName == NULL) {
        return (false);
    }
    if (inStyle == outStyle) {
        return (true);
    }

    CFStringRef rawPath = CFStringCreateWithCString (NULL, fileName, kCFStringEncodingUTF8);
    if (rawPath == NULL) {
        return (false);
    }
    CFURLRef baseURL = CFURLCreateWithFileSystemPath (NULL, rawPath, (CFURLPathStyle)inStyle, false);
    CFRelease (rawPath);
    if (baseURL == NULL) {
        return (false);
    }
    CFStringRef newURL = CFURLCopyFileSystemPath (baseURL, (CFURLPathStyle)outStyle);
    CFRelease (baseURL);
    if (newURL == NULL) {
        return (false);
    }
    char newPath[MAXPATHLEN];
    CFStringGetCString (newURL, newPath, MAXPATHLEN, kCFStringEncodingUTF8);
    CFRelease (newURL);
    strcpy (fileName, newPath);
    return (true);
}




#endif


MStatus ObjTranslator::writer ( const MFileObject& file,
                                const MString& options,
                                FileAccessMode mode )

{
    MStatus status;
    
    MString mname = file.fullName(), unitName;
    

#if defined (OSMac_)
    char fname[MAXPATHLEN];
    strcpy (fname, file.fullName().asChar());
    FSRef notUsed;
    //Create a file else convertFileRep will fail.
    createMacFile (fname, &notUsed, 0, 0);
    convertFileRepresentation (fname, kCFURLPOSIXPathStyle, kCFURLHFSPathStyle);
    fp = fopen(fname,"wb");//MAYAMACTODO
#else
    const char *fname = mname.asChar();
    fp = fopen(fname,"w");
#endif

    if (fp == NULL)
    {
        cerr << "Error: The file " << fname << " could not be opened for writing." << endl;
        return MS::kFailure;
    }

    // Options
    //
    groups      = true; // write out facet groups
    ptgroups    = true; // write out vertex groups
    materials   = true; // write out shading groups
    smoothing   = true; // write out facet smoothing information
    normals     = true; // write out normal table and facet normals
    
  if (options.length() > 0) {
        int i, length;
        // Start parsing.
        MStringArray optionList;
        MStringArray theOption;
        options.split(';', optionList); // break out all the options.

        length = optionList.length();
        for( i = 0; i < length; ++i ){
            theOption.clear();
            optionList[i].split( '=', theOption );
            if( theOption[0] == MString("groups") &&
                                                    theOption.length() > 1 ) {
                if( theOption[1].asInt() > 0 ){
                    groups = true;
                }else{
                    groups = false;
                }
            }
            if( theOption[0] == MString("materials") &&
                                                    theOption.length() > 1 ) {
                if( theOption[1].asInt() > 0 ){
                    materials = true;
                }else{
                    materials = false;
                }
            }
            if( theOption[0] == MString("ptgroups") &&
                                                    theOption.length() > 1 ) {
                if( theOption[1].asInt() > 0 ){
                    ptgroups = true;
                }else{
                    ptgroups = false;
                }
            }
            if( theOption[0] == MString("normals") &&
                                                    theOption.length() > 1 ) {
                if( theOption[1].asInt() > 0 ){
                    normals = true;
                }else{
                    normals = false;
                }
            }
            if( theOption[0] == MString("smoothing") &&
                                                    theOption.length() > 1 ) {
                if( theOption[1].asInt() > 0 ){
                    smoothing = true;
                }else{
                    smoothing = false;
                }
            }
        }
    }

    /* print current linear units used as a comment in the obj file */
    setToLongUnitName(MDistance::uiUnit(), unitName);
    //fprintf( fp, "# This file uses %s as units for non-parametric coordinates.\n\n", unitName.asChar() ); 
    fprintf( fp, "# The units used in this file are %s.\n", unitName.asChar() );

    if( ( mode == MPxFileTranslator::kExportAccessMode ) ||
        ( mode == MPxFileTranslator::kSaveAccessMode ) )
    {
        exportAll();
    }
    else if( mode == MPxFileTranslator::kExportActiveAccessMode )
    {
        exportSelected();
    }
    fclose(fp);

    return MS::kSuccess;
}

void ObjTranslator::setToLongUnitName(const MDistance::Unit &unit, MString& unitName)
{
    switch( unit ) 
    {
    case MDistance::kInches:
        unitName = "inches";
        break;
    case MDistance::kFeet:
        unitName = "feet";
        break;
    case MDistance::kYards:
        unitName = "yards";
        break;
    case MDistance::kMiles:
        unitName = "miles";
        break;
    case MDistance::kMillimeters:
        unitName = "millimeters";
        break;
    case MDistance::kCentimeters:
        unitName = "centimeters";
        break;
    case MDistance::kKilometers:
        unitName = "kilometers";
        break;
    case MDistance::kMeters:
        unitName = "meters";
        break;
    default:
        break;
    }
}

bool ObjTranslator::haveReadMethod () const
{
    return false;
}

bool ObjTranslator::haveWriteMethod () const
{
    return true;
}

MString ObjTranslator::defaultExtension () const
{
    return "obj";
}

MPxFileTranslator::MFileKind ObjTranslator::identifyFile (
                                        const MFileObject& fileName,
                                        const char* buffer,
                                        short size) const
{
    const char * name = fileName.name().asChar();
    int   nameLength = strlen(name);
    
    if ((nameLength > 4) && !strcasecmp(name+nameLength-4, ".obj"))
        return kCouldBeMyFileType;
    else
        return kNotMyFileType;
}

MStatus initializePlugin( MObject obj )
{
    MFnPlugin plugin( obj, PLUGIN_COMPANY, "3.0", "Any");

    // Register the translator with the system
    return plugin.registerFileTranslator( "OBJexport", "none",
                                          ObjTranslator::creator,
                                          (char *)objOptionScript,
                                          (char *)objDefaultOptions );                                        
}

MStatus uninitializePlugin( MObject obj )
{
        MFnPlugin plugin( obj );
        return plugin.deregisterFileTranslator( "OBJexport" );
}


MStatus ObjTranslator::OutputPolygons( 
        MDagPath& mdagPath,
        MObject&  mComponent
)
{
    MStatus stat = MS::kSuccess;
    MSpace::Space space = MSpace::kWorld;
    int i;

    MFnMesh fnMesh( mdagPath, &stat );
    if ( MS::kSuccess != stat) {
        fprintf(stderr,"Failure in MFnMesh initialization.\n");
        return MS::kFailure;
    }

    MItMeshPolygon polyIter( mdagPath, mComponent, &stat );
    if ( MS::kSuccess != stat) {
        fprintf(stderr,"Failure in MItMeshPolygon initialization.\n");
        return MS::kFailure;
    }
    MItMeshVertex vtxIter( mdagPath, mComponent, &stat );
    if ( MS::kSuccess != stat) {
        fprintf(stderr,"Failure in MItMeshVertex initialization.\n");
        return MS::kFailure;
    }

    int objectIdx = -1, length;
    MString mdagPathNodeName = fnMesh.name();
    // Find i such that objectGroupsTablePtr[i] corresponds to the
    // object node pointed to by mdagPath
    length = objectNodeNamesArray.length();
    for( i=0; i<length; i++ ) {
        if( objectNodeNamesArray[i] == mdagPathNodeName ) {
            objectIdx = i;
            break;
        }
    }

    // Write out the vertex table
    //

    for ( ; !vtxIter.isDone(); vtxIter.next() ) {
        MPoint p = vtxIter.position( space );
        if (ptgroups && groups && (objectIdx >= 0)) {
            int compIdx = vtxIter.index();
            outputSetsAndGroups( mdagPath, compIdx, true, objectIdx );
        }
        // convert from internal units to the current ui units
        p.x = MDistance::internalToUI(p.x);
        p.y = MDistance::internalToUI(p.y);
        p.z = MDistance::internalToUI(p.z);
        fprintf(fp,"v %f %f %f\n",p.x,p.y,p.z);
        v++;
    }

    // Write out the uv table
    //
    MFloatArray uArray, vArray;
    fnMesh.getUVs( uArray, vArray );
    int uvLength = uArray.length();
    for ( int x=0; x<uvLength; x++ ) {
        fprintf(fp,"vt %f %f\n",uArray[x],vArray[x]);
        vt++;
    }

    // Write out the normal table
    //
    if ( normals ) {
        MFloatVectorArray norms;
        fnMesh.getNormals( norms, MSpace::kWorld );
        int normsLength = norms.length();
        for ( int t=0; t<normsLength; t++ ) {
            MFloatVector tmpf = norms[t];
            fprintf(fp,"vn %f %f %f\n",tmpf[0],tmpf[1],tmpf[2]);
            vn++;
        }
    }

    // For each polygon, write out: 
    //    s  smoothing_group
    //    sets/groups the polygon belongs to 
    //    f  vertex_index/uvIndex/normalIndex
    //
    int lastSmoothingGroup = INITIALIZE_SMOOTHING;

    for ( ; !polyIter.isDone(); polyIter.next() )
    {
        // Write out the smoothing group that this polygon belongs to
        // We only write out the smoothing group if it is different
        // from the last polygon.
        //
        if ( smoothing ) {
            int compIdx = polyIter.index();
            int smoothingGroup = polySmoothingGroups[ compIdx ];
            
            if ( lastSmoothingGroup != smoothingGroup ) {
                if ( NO_SMOOTHING_GROUP == smoothingGroup ) {
                    fprintf(fp,"s off\n");
                }
                else {
                    fprintf(fp,"s %d\n", smoothingGroup );
                }
                lastSmoothingGroup = smoothingGroup;
            }
        }
        
        // Write out all the sets that this polygon belongs to
        //
        if ((groups || materials) && (objectIdx >= 0)) {
            int compIdx = polyIter.index();
            outputSetsAndGroups( mdagPath, compIdx, false, objectIdx );
        }
                
        // Write out vertex/uv/normal index information
        //
        fprintf(fp,"f");
        int polyVertexCount = polyIter.polygonVertexCount();
        for ( int vtx=0; vtx<polyVertexCount; vtx++ ) {
            fprintf(fp," %d", polyIter.vertexIndex( vtx ) +1 +voff);

            bool noUV = true;
            if ( fnMesh.numUVs() > 0 ) {
                int uvIndex;
                // If the call to getUVIndex fails then there is no
                // mapping information for this polyon so we don't write
                // anything
                //
                if ( polyIter.getUVIndex(vtx,uvIndex) ) {
                    fprintf(fp,"/%d",uvIndex+1 +vtoff);
                    noUV = false;
                }
            }
            
            if ( (normals) && (fnMesh.numNormals() > 0) ) {
                if ( noUV ) {
                    // If there are no UVs then our polygon is written
                    // in the form vertex//normal
                    //
                    fprintf(fp,"/");
                }
                fprintf(fp,"/%d",polyIter.normalIndex( vtx ) +1 +vnoff);
            }
        }
        fprintf(fp,"\n");
        fflush(fp);
    }
    return stat;
}

void ObjTranslator::outputSetsAndGroups( 
    MDagPath & mdagPath, 
    int cid,
    bool isVertexIterator,
    int objectIdx
    
)
{
    MStatus stat;
    
    int i, length;
    MIntArray * currentSets = new MIntArray;
    MIntArray * currentMaterials = new MIntArray;
    MStringArray gArray, mArray;


    if (groups || materials) {

        for ( i=0; i<numSets; i++ )
        {
            if ( lookup(mdagPath,i,cid,isVertexIterator) ) {
            
                MFnSet fnSet( (*sets)[i] );
                if ( MFnSet::kRenderableOnly == fnSet.restriction(&stat) ) {
                    currentMaterials->append( i );
                    mArray.append( fnSet.name() );
                }
                else {
                    currentSets->append( i );
                    gArray.append( fnSet.name() );
                }
            }
        }

        if( !isVertexIterator ) {
            // export group nodes (transform DAG nodes) in Maya that
            // the current object is a
            // child/grandchild/grandgrandchild/... of
            bool *objectGroupTable = objectGroupsTablePtr[objectIdx];
            length = transformNodeNameArray.length();
            for( i=0; i<length; i++ ) {
                if( objectGroupTable[i] ) {
                    currentSets->append( numSets + i );
                    gArray.append(transformNodeNameArray[i]);
                }
            }
        }

        // prevent grouping incoherence, use tav default group schema.
        //
        if (0 == currentSets->length())
        {
            currentSets->append( 0 );
            gArray.append( "default" );
        }
        
                    
        // Test for equivalent sets
        //
        bool setsEqual = false;
        if ( (lastSets != NULL) && 
              (lastSets->length() == currentSets->length())
        ) {
            setsEqual = true;
            length = lastSets->length();
            for ( i=0; i<length; i++ )
            {
                if ( (*lastSets)[i] != (*currentSets)[i] ) {
                    setsEqual = false;
                    break;
                }
            }   
        }

        if ( !setsEqual ) {
            if ( lastSets != NULL )
                delete lastSets;
        
            lastSets = currentSets;     
        
            if (groups) {
                int gLength = gArray.length();
                if ( gLength > 0  ) {
                    fprintf(fp,"g");
                    for ( i=0; i<gLength; i++ ) {
                        fprintf(fp," %s",gArray[i].asChar());
                    }
                    fprintf(fp,"\n");
                }
            }
        }
        else
        {
            delete currentSets;
        }

        


        // Test for equivalent materials
        //
        bool materialsEqual = false;
        if ( (lastMaterials != NULL) && 
              (lastMaterials->length() == currentMaterials->length())
        ) {
            materialsEqual = true;
            length = lastMaterials->length();
            for ( i=0; i<length; i++ )
            {
                if ( (*lastMaterials)[i] != (*currentMaterials)[i] ) {
                    materialsEqual = false;
                    break;
                }
            }           
        }

        if ( !materialsEqual ) {
            if ( lastMaterials != NULL )
                delete lastMaterials;
    
            lastMaterials = currentMaterials;
    
            if (materials) {


                int mLength = mArray.length();

                if ( mLength > 0  ) {
                    fprintf(fp,"usemtl");
                    for ( i=0; i<mLength; i++ ) {
                        fprintf(fp," %s",mArray[i].asChar());
                    }
                    fprintf(fp,"\n");
                }
            }
        }
        else
        {
            delete currentMaterials;
        }
    }   
}


void ObjTranslator::initializeSetsAndLookupTables( bool exportAll )
//
// Description :
//    Creates a list of all sets in Maya, a list of mesh objects,
//    and polygon/vertex lookup tables that will be used to
//    determine which sets are referenced by the poly components.
//
{
    int i=0,j=0, length;
    MStatus stat;
    
    // Initialize class data.
    // Note: we cannot do this in the constructor as it
    // only gets called upon registry of the plug-in.
    //
    numSets = 0;
    sets = NULL;
    lastSets = NULL;
    lastMaterials = NULL;
    objectId = 0;
    objectCount = 0;
    polygonTable = NULL;
    vertexTable = NULL;
    polygonTablePtr = NULL;
    vertexTablePtr = NULL;
    objectGroupsTablePtr = NULL;
    objectNodeNamesArray.clear();
    transformNodeNameArray.clear();

    //
    // Find all sets in Maya and store the ones we care about in
    // the 'sets' array. Also make note of the number of sets.
    //
    
    // Get all of the sets in maya and put them into
    // a selection list
    // 
    MStringArray result;
    MGlobal::executeCommand( "ls -sets", result );
    MSelectionList * setList = new MSelectionList;
    length = result.length();
    for ( i=0; i<length; i++ )
    {   
        setList->add( result[i] );
    }
    
    // Extract each set as an MObject and add them to the
    // sets array.
    // We may be excluding groups, matierials, or ptGroups
    // in which case we can ignore those sets. 
    //
    MObject mset;
    sets = new MObjectArray();
    length = setList->length();
    for ( i=0; i<length; i++ )
    {
        setList->getDependNode( i, mset );
        
        MFnSet fnSet( mset, &stat );
        if ( stat ) {
            if ( MFnSet::kRenderableOnly == fnSet.restriction(&stat) ) {
                if ( materials ) {
                    sets->append( mset );
                }
            } 
            else {
                if ( groups ) {
                    sets->append( mset );
                }
            }
        }   
    }
    delete setList;
    
    numSets = sets->length();
            
    //
    // Do a dag-iteration and for every mesh found, create facet and
    // vertex look-up tables. These tables will keep track of which
    // sets each component belongs to.
    //
    // If exportAll is false then iterate over the activeSelection 
    // list instead of the entire DAG.
    //
    // These arrays have a corrisponding entry in the name
    // stringArray.
    //
    MIntArray vertexCounts;
    MIntArray polygonCounts;    
            
    if ( exportAll ) {
        MItDag dagIterator( MItDag::kBreadthFirst, MFn::kInvalid, &stat);

        if ( MS::kSuccess != stat) {
            fprintf(stderr,"Failure in DAG iterator setup.\n");
            return;
        }
        
        objectNames = new MStringArray;
        
        for ( ; !dagIterator.isDone(); dagIterator.next() ) 
        {
            MDagPath dagPath;
            stat = dagIterator.getPath( dagPath );

            if ( stat ) 
            {
                // skip over intermediate objects
                //
                MFnDagNode dagNode( dagPath, &stat );
                if (dagNode.isIntermediateObject()) 
                {
                    continue;
                }

                if (( dagPath.hasFn(MFn::kMesh)) &&
                    ( dagPath.hasFn(MFn::kTransform)))
                {
                    // We want only the shape, 
                    // not the transform-extended-to-shape.
                    continue;
                }
                else if ( dagPath.hasFn(MFn::kMesh))
                {
                    // We have a mesh so create a vertex and polygon table
                    // for this object.
                    //
                    MFnMesh fnMesh( dagPath );
                    int vtxCount = fnMesh.numVertices();
                    int polygonCount = fnMesh.numPolygons();
                    // we do not need this call anymore, we have the shape.
                    // dagPath.extendToShape();
                    MString name = dagPath.fullPathName();
                    objectNames->append( name );
                    objectNodeNamesArray.append( fnMesh.name() );

                    vertexCounts.append( vtxCount );
                    polygonCounts.append( polygonCount );

                    objectCount++;
                }
            }
        }   
    }
    else 
    {
        MSelectionList slist;
        MGlobal::getActiveSelectionList( slist );
        MItSelectionList iter( slist );
        MStatus status;

        objectNames = new MStringArray;

        // We will need to interate over a selected node's heirarchy
        // in the case where shapes are grouped, and the group is selected.
        MItDag dagIterator( MItDag::kDepthFirst, MFn::kInvalid, &status);

        for ( ; !iter.isDone(); iter.next() ) 
        {
            MDagPath objectPath;
            stat = iter.getDagPath( objectPath );

            // reset iterator's root node to be the selected node.
            status = dagIterator.reset (objectPath.node(), 
                                        MItDag::kDepthFirst, MFn::kInvalid );

            // DAG iteration beginning at at selected node
            for ( ; !dagIterator.isDone(); dagIterator.next() )
            {
                MDagPath dagPath;
                MObject  component = MObject::kNullObj;
                status = dagIterator.getPath(dagPath);

                if (!status) {
                    fprintf(stderr,"Failure getting DAG path.\n");
                    freeLookupTables();
                    return ;
                }

                // skip over intermediate objects
                //
                MFnDagNode dagNode( dagPath, &stat );
                if (dagNode.isIntermediateObject()) 
                {
                    continue;
                }


                if (( dagPath.hasFn(MFn::kMesh)) &&
                    ( dagPath.hasFn(MFn::kTransform)))
                {
                    // We want only the shape, 
                    // not the transform-extended-to-shape.
                    continue;
                }
                else if ( dagPath.hasFn(MFn::kMesh))
                {
                    // We have a mesh so create a vertex and polygon table
                    // for this object.
                    //
                    MFnMesh fnMesh( dagPath );
                    int vtxCount = fnMesh.numVertices();
                    int polygonCount = fnMesh.numPolygons();

                    // we do not need this call anymore, we have the shape.
                    // dagPath.extendToShape();
                    MString name = dagPath.fullPathName();
                    objectNames->append( name );
                    objectNodeNamesArray.append( fnMesh.name() );
                                    
                    vertexCounts.append( vtxCount );
                    polygonCounts.append( polygonCount );

                    objectCount++;  
                }
            }
        }
    }

    // Now we know how many objects we are dealing with 
    // and we have counts of the vertices/polygons for each
    // object so create the maya group look-up table.
    //
    if( objectCount > 0 ) {

        // To export Maya groups we traverse the hierarchy starting at
        // each objectNodeNamesArray[i] going towards the root collecting transform
        // nodes as we go.
        length = objectNodeNamesArray.length();
        for( i=0; i<length; i++ ) {
            MIntArray transformNodeNameIndicesArray;
            recFindTransformDAGNodes( objectNodeNamesArray[i], transformNodeNameIndicesArray );
        }

        if( transformNodeNameArray.length() > 0 ) {
            objectGroupsTablePtr = (bool**) malloc( sizeof(bool*)*objectCount );
            length = transformNodeNameArray.length();
            for ( i=0; i<objectCount; i++ )
            {
                objectGroupsTablePtr[i] =
                    (bool*)calloc( length, sizeof(bool) );  
                
                if ( objectGroupsTablePtr[i] == NULL ) {
                    cerr << "Error: calloc returned NULL (objectGroupsTablePtr)\n";
                    return;
                }
            }
        }
    }

    // Create the vertex/polygon look-up tables.
    //
    if ( objectCount > 0 ) {
        
        vertexTablePtr = (bool**) malloc( sizeof(bool*)*objectCount );
        polygonTablePtr = (bool**) malloc( sizeof(bool*)*objectCount );
    
        for ( i=0; i<objectCount; i++ )
        {
            vertexTablePtr[i] =
                 (bool*)calloc( vertexCounts[i]*numSets, sizeof(bool) );    

            if ( vertexTablePtr[i] == NULL ) {
                cerr << "Error: calloc returned NULL (vertexTable)\n";
                return;
            }
    
            polygonTablePtr[i] =
                 (bool*)calloc( polygonCounts[i]*numSets, sizeof(bool) );
            if ( polygonTablePtr[i] == NULL ) {
                cerr << "Error: calloc returned NULL (polygonTable)\n";
                return;
            }
        }   
    }

    // If we found no meshes then return
    //  
    if ( objectCount == 0 ) {
        return;
    }
    
    //
    // Go through all of the set members (flattened lists) and mark
    // in the lookup-tables, the sets that each mesh component belongs
    // to.
    //
    //
    bool flattenedList = true;
    MDagPath object;
    MObject component;
    MSelectionList memberList;
    
    
    for ( i=0; i<numSets; i++ )
    {
        MFnSet fnSet( (*sets)[i] );     
        memberList.clear();
        stat = fnSet.getMembers( memberList, flattenedList );

        if (MS::kSuccess != stat) {
            fprintf(stderr,"Error in fnSet.getMembers()!\n");
        }

        int m, numMembers;
        numMembers = memberList.length();
        for ( m=0; m<numMembers; m++ )
        {
            if ( memberList.getDagPath(m,object,component) ) {

                if ( (!component.isNull()) && (object.apiType() == MFn::kMesh) )
                {
                    if (component.apiType() == MFn::kMeshVertComponent) {
                        MItMeshVertex viter( object, component );   
                        for ( ; !viter.isDone(); viter.next() )
                        {
                            int compIdx = viter.index();
                            MString name = object.fullPathName();
                            
                            // Figure out which object vertexTable
                            // to get.
                            //

                            int o, numObjectNames;
                            numObjectNames = objectNames->length();
                            for ( o=0; o<numObjectNames; o++ ) {
                                if ( (*objectNames)[o] == name ) {
                                    // Mark set i as true in the table
                                    //      
                                    vertexTable = vertexTablePtr[o];
                                    *(vertexTable + numSets*compIdx + i) = true;
                                    break;
                                }
                            }
                        }
                    }
                    else if (component.apiType() == MFn::kMeshPolygonComponent) 
                    {
                        MItMeshPolygon piter( object, component );
                        for ( ; !piter.isDone(); piter.next() )
                        {
                            int compIdx = piter.index();
                            MString name = object.fullPathName();
                            
                            // Figure out which object polygonTable
                            // to get.
                            //                          
                            int o, numObjectNames;
                            numObjectNames = objectNames->length();
                            for ( o=0; o<numObjectNames; o++ ) {
                                if ( (*objectNames)[o] == name ) {
                                    
                                    // Mark set i as true in the table
                                    //

// Check for bad components in the set
//                                  
if ( compIdx >= polygonCounts[o] ) {
    cerr << "Error: component in set >= numPolygons, skipping!\n";
    cerr << "  Component index    = " << compIdx << endl;
    cerr << "  Number of polygons = " << polygonCounts[o] << endl;
    break;
}
                                    
                                    polygonTable = polygonTablePtr[o];
                                    *(polygonTable + numSets*compIdx + i) = true;
                                    break;
                                }
                            }   
                        }
                    }                                       
                }
                else { 

                // There are no components, therefore we can mark
                // all polygons as members of the given set.
                //

                if (object.hasFn(MFn::kMesh)) {

                    MFnMesh fnMesh( object, &stat );
                    if ( MS::kSuccess != stat) {
                        fprintf(stderr,"Failure in MFnMesh initialization.\n");
                        return;
                    }

                    // We are going to iterate over all the polygons.
                    //
                    MItMeshPolygon piter( object, MObject::kNullObj, &stat );
                    if ( MS::kSuccess != stat) {
                        fprintf(stderr,
                                "Failure in MItMeshPolygon initialization.\n");
                        return;
                    }
                    for ( ; !piter.isDone(); piter.next() )
                    {
                        int compIdx = piter.index();
                        MString name = object.fullPathName();

                        // Figure out which object polygonTable to get.
                        //
                        int o, numObjectNames;
                        numObjectNames = objectNames->length();
                        for ( o=0; o<numObjectNames; o++ ) {
                            if ( (*objectNames)[o] == name ) {

// Check for bad components in the set
//
if ( compIdx >= polygonCounts[o] ) {
    cerr << "Error: component in set >= numPolygons, skipping!\n";
    cerr << "  Component index    = " << compIdx << endl;
    cerr << "  Number of polygons = " << polygonCounts[o] << endl;
    break;
}
                                // Mark set i as true in the table
                                //
                                polygonTable = polygonTablePtr[o];
                                *(polygonTable + numSets*compIdx + i) = true;
                                break;
                            }
                        }
                    } // end of piter.next() loop
                } // end of condition if (object.hasFn(MFn::kMesh))
                } // end of else condifion if (!component.isNull()) 
            } // end of memberList.getDagPath(m,object,component)
        } // end of memberList loop
    } // end of for-loop for sets

    // Go through all of the group members and mark in the
    // lookup-table, the group that each shape belongs to.
    length = objectNodeNamesArray.length();
    for( i=0; i<length; i++ ) {
        MIntArray groupTableIndicesArray;
        bool *objectGroupTable = objectGroupsTablePtr[i];
        int length2;
        recFindTransformDAGNodes( objectNodeNamesArray[i], groupTableIndicesArray );
        length2 = groupTableIndicesArray.length();
        for( j=0; j<length2; j++ ) {
            int groupIdx = groupTableIndicesArray[j];
            objectGroupTable[groupIdx] = true;
        }
    }
}


void ObjTranslator::freeLookupTables()
//
// Frees up all tables and arrays allocated by this plug-in.
//
{
    for ( int i=0; i<objectCount; i++ ) {
        if ( vertexTablePtr[i] != NULL ) {
            free( vertexTablePtr[i] );
        }
        if ( polygonTablePtr[i] != NULL ) {
            free( polygonTablePtr[i] );
        }
    }   

    if( objectGroupsTablePtr != NULL ) {
        for ( int i=0; i<objectCount; i++ ) {
            if ( objectGroupsTablePtr[i] != NULL ) {
                free( objectGroupsTablePtr[i] );
            }
        }
        free( objectGroupsTablePtr );
        objectGroupsTablePtr = NULL;
    }
    
    if ( vertexTablePtr != NULL ) {
        free( vertexTablePtr );
        vertexTablePtr = NULL;
    }
    if ( polygonTablePtr != NULL ) {
        free( polygonTablePtr );
        polygonTablePtr = NULL;
    }

    if ( lastSets != NULL ) {
        delete lastSets;
        lastSets = NULL;
    }
    
    if ( lastMaterials != NULL ) {
        delete lastMaterials;
        lastMaterials = NULL;
    }
    
    if ( sets != NULL ) {
        delete sets;
        sets = NULL;
    }
    
    if ( objectNames != NULL ) {
        delete objectNames;
        objectNames = NULL;
    }       
}


bool ObjTranslator::lookup( MDagPath& dagPath, 
                            int setIndex,
                            int compIdx,
                            bool isVtxIter )
{

    if (isVtxIter) {
        vertexTable = vertexTablePtr[objectId];
        bool ret = *(vertexTable + numSets*compIdx + setIndex);
        return ret;
    }
    else  {             
        polygonTable = polygonTablePtr[objectId];
        bool ret = *(polygonTable + numSets*compIdx + setIndex);            
        return ret;
    }
}   


void ObjTranslator::buildEdgeTable( MDagPath& mesh )
{
    if ( !smoothing )
        return;
    
    // Create our edge lookup table and initialize all entries to NULL
    //
    MFnMesh fnMesh( mesh );
    edgeTableSize = fnMesh.numVertices();
    edgeTable = (EdgeInfoPtr*) calloc( edgeTableSize, sizeof(EdgeInfoPtr) );

    // Add entries, for each edge, to the lookup table
    //
    MItMeshEdge eIt( mesh );
    for ( ; !eIt.isDone(); eIt.next() )
    {
        bool smooth = eIt.isSmooth();
        addEdgeInfo( eIt.index(0), eIt.index(1), smooth );
    }

    // Fill in referenced polygons
    //
    MItMeshPolygon pIt( mesh );
    for ( ; !pIt.isDone(); pIt.next() )
    {
        int pvc = pIt.polygonVertexCount();
        for ( int v=0; v<pvc; v++ )
        {
            int a = pIt.vertexIndex( v );
            int b = pIt.vertexIndex( v==(pvc-1) ? 0 : v+1 );

            EdgeInfoPtr elem = findEdgeInfo( a, b );
            if ( NULL != elem ) {
                int edgeId = pIt.index();
                
                if ( INVALID_ID == elem->polyIds[0] ) {
                    elem->polyIds[0] = edgeId;
                }
                else {
                    elem->polyIds[1] = edgeId;
                }                
                    
            }
        }
    }

    // Now create a polyId->smoothingGroup table
    //   
    int numPolygons = fnMesh.numPolygons();
    polySmoothingGroups = (int*)malloc( sizeof(int) *  numPolygons );
    for ( int i=0; i< numPolygons; i++ ) {
        polySmoothingGroups[i] = NO_SMOOTHING_GROUP;
    }    
    
    // Now call the smoothingAlgorithm to fill in the polySmoothingGroups
    // table.
    // Note: we have to traverse ALL polygons to handle the case
    // of disjoint polygons.
    //
    nextSmoothingGroup = 1;
    currSmoothingGroup = 1;
    for ( int pid=0; pid<numPolygons; pid++ ) {
        newSmoothingGroup = true;
        // Check polygon has not already been visited
        if ( NO_SMOOTHING_GROUP == polySmoothingGroups[pid] ) {
           if ( !smoothingAlgorithm(pid,fnMesh) ) {
               // No smooth edges for this polygon so we set
               // the smoothing group to NO_SMOOTHING_GROUP (off)
               polySmoothingGroups[pid] = NO_SMOOTHING_GROUP;
           }
        }
    }
}


bool ObjTranslator::smoothingAlgorithm( int polyId, MFnMesh& fnMesh )
{
    MIntArray vertexList;
    fnMesh.getPolygonVertices( polyId, vertexList );
    int vcount = vertexList.length();
    bool smoothEdgeFound = false;
    
    for ( int vid=0; vid<vcount;vid++ ) {
        int a = vertexList[vid];
        int b = vertexList[ vid==(vcount-1) ? 0 : vid+1 ];
        
        EdgeInfoPtr elem = findEdgeInfo( a, b );
        if ( NULL != elem ) {
            // NOTE: We assume there are at most 2 polygons per edge
            //       halfEdge polygons get a smoothing group of
            //       NO_SMOOTHING_GROUP which is equivalent to "s off"
            //
            if ( NO_SMOOTHING_GROUP != elem->polyIds[1] ) { // Edge not a border
                
                // We are starting a new smoothing group
                //                
                if ( newSmoothingGroup ) {
                    currSmoothingGroup = nextSmoothingGroup++;
                    newSmoothingGroup = false;
                    
                    // This is a SEED (starting) polygon and so we always
                    // give it the new smoothing group id.
                    // Even if all edges are hard this must be done so
                    // that we know we have visited the polygon.
                    //
                    polySmoothingGroups[polyId] = currSmoothingGroup;
                }
                
                // If we have a smooth edge then this poly must be a member
                // of the current smoothing group.
                //
                if ( elem->smooth ) {
                    polySmoothingGroups[polyId] = currSmoothingGroup;
                    smoothEdgeFound = true;
                }
                else { // Hard edge so ignore this polygon
                    continue;
                }
                
                // Find the adjacent poly id
                //
                int adjPoly = elem->polyIds[0];
                if ( adjPoly == polyId ) {
                    adjPoly = elem->polyIds[1];
                }                             
                
                // If we are this far then adjacent poly belongs in this
                // smoothing group.
                // If the adjacent polygon's smoothing group is not
                // NO_SMOOTHING_GROUP then it has already been visited
                // so we ignore it.
                //
                if ( NO_SMOOTHING_GROUP == polySmoothingGroups[adjPoly] ) {
                    smoothingAlgorithm( adjPoly, fnMesh );
                }
                else if ( polySmoothingGroups[adjPoly] != currSmoothingGroup ) {
                    cerr << "Warning: smoothing group problem at polyon ";
                    cerr << adjPoly << endl;
                }
            }
        }
    }
    return smoothEdgeFound;
}


void ObjTranslator::addEdgeInfo( int v1, int v2, bool smooth )
//
// Adds a new edge info element to the vertex table.
//
{
    EdgeInfoPtr element = NULL;

    if ( NULL == edgeTable[v1] ) {
        edgeTable[v1] = (EdgeInfoPtr)malloc( sizeof(struct EdgeInfo) );
        element = edgeTable[v1];
    }
    else {
        element = edgeTable[v1];
        while ( NULL != element->next ) {
            element = element->next;
        }
        element->next = (EdgeInfoPtr)malloc( sizeof(struct EdgeInfo) );
        element = element->next;
    }

    // Setup data for new edge
    //
    element->vertId     = v2;
    element->smooth     = smooth;
    element->next       = NULL;
   
    // Initialize array of id's of polygons that reference this edge.
    // There are at most 2 polygons per edge.
    //
    element->polyIds[0] = INVALID_ID;
    element->polyIds[1] = INVALID_ID;
}


EdgeInfoPtr ObjTranslator::findEdgeInfo( int v1, int v2 )
//
// Finds the info for the specified edge.
//
{
    EdgeInfoPtr element = NULL;
    element = edgeTable[v1];

    while ( NULL != element ) {
        if ( v2 == element->vertId ) {
            return element;
        }
        element = element->next;
    }
    
    if ( element == NULL ) {
        element = edgeTable[v2];

        while ( NULL != element ) {
            if ( v1 == element->vertId ) {
                return element;
            }
            element = element->next;
        }
    }

    return NULL;
}


void ObjTranslator::destroyEdgeTable()
//
// Free up all of the memory used by the edgeTable.
//
{
    if ( !smoothing )
        return;
    
    EdgeInfoPtr element = NULL;
    EdgeInfoPtr tmp = NULL;

    for ( int v=0; v<edgeTableSize; v++ )
    {
        element = edgeTable[v];
        while ( NULL != element )
        {
            tmp = element;
            element = element->next;
            free( tmp );
        }
    }

    if ( NULL != edgeTable ) {
        free( edgeTable );
        edgeTable = NULL;
    }
    
    if ( NULL != polySmoothingGroups ) {
        free( polySmoothingGroups );
        polySmoothingGroups = NULL;
    }
}


MStatus ObjTranslator::exportSelected( )
{
    MStatus status;
    MString filename;

    initializeSetsAndLookupTables( false );

    // Create an iterator for the active selection list
    //
    MSelectionList slist;
    MGlobal::getActiveSelectionList( slist );
    MItSelectionList iter( slist );

    if (iter.isDone()) {
        fprintf(stderr,"Error: Nothing is selected.\n");
        return MS::kFailure;
    }

    // We will need to interate over a selected node's heirarchy 
    // in the case where shapes are grouped, and the group is selected.
    MItDag dagIterator( MItDag::kDepthFirst, MFn::kInvalid, &status);

    // reset counters
    v = vt = vn = 0;
    voff = vtoff = vnoff = 0;

    // Selection list loop
    for ( ; !iter.isDone(); iter.next() )
    {    
        MDagPath objectPath;
        // get the selected node
        status = iter.getDagPath( objectPath);

        // reset iterator's root node to be the selected node.
        status = dagIterator.reset (objectPath.node(), 
                                    MItDag::kDepthFirst, MFn::kInvalid );   

        // DAG iteration beginning at at selected node
        for ( ; !dagIterator.isDone(); dagIterator.next() )
        {
            MDagPath dagPath;
            MObject  component = MObject::kNullObj;
            status = dagIterator.getPath(dagPath);

            if (!status) {
                fprintf(stderr,"Failure getting DAG path.\n");
                freeLookupTables();
                return MS::kFailure;
            }

            if (status ) 
            {
                // skip over intermediate objects
                //
                MFnDagNode dagNode( dagPath, &status );
                if (dagNode.isIntermediateObject()) 
                {
                    continue;
                }

                if (dagPath.hasFn(MFn::kNurbsSurface))
                {
                    status = MS::kSuccess;
                    fprintf(stderr,"Warning: skipping Nurbs Surface.\n");
                }
                else if ((  dagPath.hasFn(MFn::kMesh)) &&
                         (  dagPath.hasFn(MFn::kTransform)))
                {
                    // We want only the shape, 
                    // not the transform-extended-to-shape.
                    continue;
                }
                else if (  dagPath.hasFn(MFn::kMesh))
                {
                    // Build a lookup table so we can determine which 
                    // polygons belong to a particular edge as well as
                    // smoothing information
                    //
                    buildEdgeTable( dagPath );
                    
                    status = OutputPolygons(dagPath, component);
                    objectId++;
                    if (status != MS::kSuccess) {
                        fprintf(stderr, "Error: exporting geom failed, check your selection.\n");
                        freeLookupTables();
                        destroyEdgeTable(); // Free up the edge table               
                        return MS::kFailure;
                    }
                    destroyEdgeTable(); // Free up the edge table               
                }
                voff = v;
                vtoff = vt;
                vnoff = vn;
            }
        }
    }
    
    freeLookupTables();
    
    return status;
}


MStatus ObjTranslator::exportAll( )
{
    MStatus status = MS::kSuccess;

    initializeSetsAndLookupTables( true );

    MItDag dagIterator( MItDag::kBreadthFirst, MFn::kInvalid, &status);

    if ( MS::kSuccess != status) {
        fprintf(stderr,"Failure in DAG iterator setup.\n");
        return MS::kFailure;
    }
    // reset counters
    v = vt = vn = 0;
    voff = vtoff = vnoff = 0;

    for ( ; !dagIterator.isDone(); dagIterator.next() )
    {
        MDagPath dagPath;
        MObject  component = MObject::kNullObj;
        status = dagIterator.getPath(dagPath);

        if (!status) {
            fprintf(stderr,"Failure getting DAG path.\n");
            freeLookupTables();
            return MS::kFailure;
        }

        // skip over intermediate objects
        //
        MFnDagNode dagNode( dagPath, &status );
        if (dagNode.isIntermediateObject()) 
        {
            continue;
        }

        if ((  dagPath.hasFn(MFn::kNurbsSurface)) &&
            (  dagPath.hasFn(MFn::kTransform)))
        {
            status = MS::kSuccess;
            fprintf(stderr,"Warning: skipping Nurbs Surface.\n");
        }
        else if ((  dagPath.hasFn(MFn::kMesh)) &&
                 (  dagPath.hasFn(MFn::kTransform)))
        {
            // We want only the shape, 
            // not the transform-extended-to-shape.
            continue;
        }
        else if (  dagPath.hasFn(MFn::kMesh))
        {
            // Build a lookup table so we can determine which 
            // polygons belong to a particular edge as well as
            // smoothing information
            //
            buildEdgeTable( dagPath );
            
            // Now output the polygon information
            //
            status = OutputPolygons(dagPath, component);
            objectId++;
            if (status != MS::kSuccess) {
                fprintf(stderr,"Error: exporting geom failed.\n");
                freeLookupTables();                
                destroyEdgeTable(); // Free up the edge table               
                return MS::kFailure;
            }
            destroyEdgeTable(); // Free up the edge table
        }
        voff = v;
        vtoff = vt;
        vnoff = vn;
    }

    freeLookupTables();

    return status;
}


void ObjTranslator::recFindTransformDAGNodes( MString& nodeName, MIntArray& transformNodeIndicesArray )
{
    // To handle Maya groups we traverse the hierarchy starting at
    // each objectNames[i] going towards the root collecting transform
    // nodes as we go.
    MStringArray result;
    MString cmdStr = "listRelatives -ap " + nodeName;
    MGlobal::executeCommand( cmdStr, result );
    
    if( result.length() == 0 )
        // nodeName must be at the root of the DAG.  Stop recursing
        return;

    for( unsigned int j=0; j<result.length(); j++ ) {
        // check if the node result[i] is of type transform
        MStringArray result2;
        MGlobal::executeCommand( "nodeType " + result[j], result2 );
        
        if( result2.length() == 1 && result2[0] == "transform" ) {
            // check if result[j] is already in result[j]
            bool found=false;
            unsigned int i;
            for( i=0; i<transformNodeNameArray.length(); i++) {
                if( transformNodeNameArray[i] == result[j] ) {
                    found = true;
                    break;
                }
            }

            if( !found ) {
                transformNodeIndicesArray.append(transformNodeNameArray.length());
                transformNodeNameArray.append(result[j]);
            }
            else {
                transformNodeIndicesArray.append(i);
            }
            recFindTransformDAGNodes(result[j], transformNodeIndicesArray);
        }
    }
}