meshMapUtils.cpp

//-
// ==========================================================================
// Copyright 1995,2006,2008 Autodesk, Inc. All rights reserved.
//
// Use of this software is subject to the terms of the Autodesk
// license agreement provided at the time of installation or download,
// or which otherwise accompanies this software in either electronic
// or hard copy form.
// ==========================================================================
//+

// 
// meshMapUtils.cpp
// 
// Description:
//    Utility functions for the meshRemap and meshReorder commands
//       moveToolContext
// 

#include <assert.h>

#include "meshMapUtils.h"

#include <maya/MIntArray.h>
#include <maya/MFnMesh.h>
#include <maya/MItMeshEdge.h>
#include <maya/MItMeshPolygon.h>
#include <maya/MGlobal.h>
#include <maya/MFloatPointArray.h>
#include <maya/MDagPath.h>
#include <maya/MDagPathArray.h>
#include <maya/MObjectArray.h>
#include <maya/MItMeshVertex.h>


//
// Modulating index, will return 0..l-1, for any integer value
#define IDX(i, l) ( ( i + l ) % l )

//
// Intersect the current face list with the one provided, keep only the common elements
// 
void meshMapUtils::intersectArrays( MIntArray& selection, MIntArray& moreFaces )
{
    int j = selection.length() - 1;
    while ( j >= 0 )
    {
        bool found = false;;

        // For each item in moreFaces, try find it in fSelectedFaces
        for( unsigned int i = 0 ; i < moreFaces.length(); i++ )
        {
            if( selection[j] == moreFaces[i] )
            {
                found = true;
                break;
            }
        }   

        if( !found )
        {
            selection.remove(j);
        }

        j--;
    }
}


//
//  Recursively traverse a mesh by processing each face, and the neighbouring faces along it's edges.
//  The initial invocation of this routine provides the basis for the new first vertex/edge
//  and faces.  
//
//  The result of this routine is an array of values that map the old CV indices to the new ones. Along
//  the a new list of reindexed CVs is built, along with a list of poly counts and connetions. These
//  can be used to build a new mesh with the reordering specfied by the seed face and vertices.
//
//
//  Inputs: 
//      path                : Path to the object being traversed
//      faceIdx             : Current face being traversed
//      v0, v1              : Veretices that define the direction of travel along the face
//      faceTraversal       : An array booleans to track which faces have been 
//                          : traversed, controls the recursion  
//      origVertices        : The vertices from the original mesh. The could be obtained
//                          : from the path, but are passed in for efficiency
//
//  Outputs:
//      cvMapping           : Mapping of the existing vertices to their new indices
//                          : the fist values in the final array will be the intial v0, v1
//      cvMappingInverse    : The inverse of the cvMapping
//                          : the value of items v0 and v1 will be 0 and 1 respectively
//      newPolygonCounts    : Vertex counts for each of the new faces
//      newPolygonConnects  : Connections, specified in terms of new CV indices
//      newVertices         : The orginal vertices resorted based on the reindexing
//
//
MStatus meshMapUtils::traverseFace( 
    MDagPath&   path,
    int faceIdx, 
    int v0, 
    int v1, 
    MIntArray& faceTraversal,
    MIntArray& cvMapping,
    MIntArray& cvMappingInverse,
    MIntArray& newPolygonCounts,
    MIntArray& newPolygonConnects,
    MFloatPointArray& origVertices,
    MFloatPointArray& newVertices
)
{
    int vtxCnt = -1;
    int dir = 0;
    int dummy;      // For setIndex calls

    MStatus stat = MStatus::kSuccess;

    MFnMesh theMesh( path, &stat );
    MItMeshPolygon polyIt( path );
    MItMeshEdge edgeIt( path );

    if( stat != MStatus::kSuccess )
    {
        MGlobal::displayError( " theMesh.getPoint failed");
        return stat;
    }


    //
    // Skip over any faces already processed, this is not a failure
    // 
    if( faceTraversal[faceIdx] )
    {
        return MStatus::kSuccess;
    }

    //
    // get the vertex/edge information and sort it based on the user seed
    //
    MIntArray vtxOrig;
    MIntArray edgeOrig;

    polyIt.setIndex( faceIdx, dummy );
    polyIt.getEdges( edgeOrig );       
    polyIt.getVertices( vtxOrig );     

    vtxCnt = vtxOrig.length();

    // 
    // the sorted V/E info
    // 
    MIntArray vtxSorted( vtxCnt );
    MIntArray edgeSorted( vtxCnt );

    //
    // Build a new array ordered with v0, then v1, first figure out the 
    // starting point, and direction 
    //
    int v0Idx = -1;
    int i;
    for( i = 0; i < vtxCnt; i++ )
    {
        if( vtxOrig[i] == v0 )
        {
            // We've found v0, now find in what direction we need to travel to find v1
                    
            v0Idx = i;
            
            if( vtxOrig[IDX(i+1, vtxCnt)] == v1 )
            {
                dir = 1;
            }
            else if( vtxOrig[IDX(i-1, vtxCnt)] == v1 )
            {
                dir = -1;
            }
            else
            {
                assert( dir != 0 );
            }   
            break;
        }
    }

    // Now sort the vertex/edge arrays
    for( i = 0; i < vtxCnt; i++ )
    {
        vtxSorted[i] = vtxOrig[IDX( v0Idx + i * dir, vtxCnt )];
        if( dir == 1 )
        {
            edgeSorted[i] = edgeOrig[IDX( v0Idx + i * dir, vtxCnt )];
        }
        else
        {
            edgeSorted[i] = edgeOrig[IDX( v0Idx - 1 + i * dir, vtxCnt )];
        }
    }


    // Add any new CVs to the vertex array being constructed
    for ( i = 0; i < vtxCnt; i++ )
    {
        MPoint pos;
        int index = vtxSorted[i];

        if( cvMapping[index] == -1  )
        {
            if( stat != MStatus::kSuccess )
            {
                MGlobal::displayError( " theMesh.getPoint failed");
                return stat;
            }

            // Added the new CV, and mark it as transferred
            newVertices.append( origVertices[index] );

            // Store the mapping from the old CV indices to the new ones
            cvMapping[index] = newVertices.length()-1;
            cvMappingInverse[newVertices.length()-1] = index;
        }

    }

    //
    //  Add the new face count 
    //
    newPolygonCounts.append( vtxCnt );

    //
    //  Add the new polyConnects
    
    for ( i = 0; i < vtxCnt; i++ )
    {
        newPolygonConnects.append( cvMapping[vtxSorted[i]] );
    }

    // Mark this face as complete 
    faceTraversal[faceIdx] = true;

    //
    //  Now recurse over the edges of this face
    // 
    for( i = 0; i < (int)edgeSorted.length(); i++ )
    {
        int nextEdge = edgeSorted[i];

        int2 nextEdgeVtx;
        stat = theMesh.getEdgeVertices(nextEdge, nextEdgeVtx );

        //
        // Find the vertex, in the sorted array, that starts the next edge
        int baseIdx = -1;
        bool swap = false;
        int j;
        for( j = 0; j < (int)vtxSorted.length(); j++ )
        {
            if( vtxSorted[j] == nextEdgeVtx[0] )
            {
                baseIdx = j;
                break;
            }
        }

        assert( baseIdx != -1 );
    
        // 
        // Now look forward and backward in the vertex array to find the
        // edge's other point, this indicates the edges direction. This
        // is needed to guide the next recursion level, and keep the
        // normals pointed consistenly
        //
        if( vtxSorted[IDX(j+1, vtxCnt)] == nextEdgeVtx[1] )
        {
            // Nothing
        }
        else if ( vtxSorted[IDX(j-1, vtxCnt)] == nextEdgeVtx[1] )
        {
            swap = true;
        }


        MIntArray connectedFaces;
        edgeIt.setIndex( nextEdge, dummy );
        edgeIt.getConnectedFaces( connectedFaces );

        // A single face is simply the current one. Recurse over the others
        if( connectedFaces.length() > 1 )
        {
            int nextFace;
            if( connectedFaces[0] == faceIdx )
            {
                nextFace = connectedFaces[1];
            }
            else
            {
                nextFace = connectedFaces[0];
            }

            int nextVtx0 = -1;
            int nextVtx1 = -1;
            if ( !swap )
            {
                nextVtx0 = nextEdgeVtx[1];
                nextVtx1 = nextEdgeVtx[0];
            }
            else
            {
                nextVtx0 = nextEdgeVtx[0];
                nextVtx1 = nextEdgeVtx[1];
            }

            stat = traverseFace( path, nextFace, nextVtx0, nextVtx1, faceTraversal,
                    cvMapping, cvMappingInverse, 
                    newPolygonCounts, newPolygonConnects, 
                    origVertices, newVertices );

            // Break out of edge loop on error
            if( stat != MStatus::kSuccess )
            {
                break;
            }   
        }
    }

    return stat;
}

//
//  Given a list of 3 paths and compoents find the face they define and store their indices
//
MStatus meshMapUtils::validateFaceSelection( MDagPathArray& paths, MObjectArray& components, int *faceIdx, MIntArray *seedVtx )
{
    MStatus stat = MStatus::kSuccess;
    MIntArray finalFaceList;

    if ( paths.length() != 3 && components.length() != 3 )
    {
        return MStatus::kFailure;
    }

    seedVtx->clear();

    for (unsigned int i = 0; i < 3; i++)
    {
        MItMeshVertex fIt ( paths[i], components[i], &stat );
        if( stat != MStatus::kSuccess || fIt.isDone() )
        {
            MGlobal::displayError( " MItMeshVertex failed");
            return MStatus::kFailure;
        }

        seedVtx->append( fIt.index() ); // The  cv's location

        MIntArray  faceList;
        stat = fIt.getConnectedFaces ( faceList );
        if( stat != MStatus::kSuccess )
        {
            MGlobal::displayError( " getConnectedFaces failed");
            return MStatus::kFailure;
        }

        if( i == 0 )
        {
            finalFaceList = faceList;
        }
        else
        {
            meshMapUtils::intersectArrays( finalFaceList, faceList );
        }
    }

    if( finalFaceList.length() != 1 )
    {
        return MStatus::kFailure;
    }
    else
    {
        *faceIdx = finalFaceList[0];
    }

    MDagPath p0( paths[0] );
    MDagPath p1( paths[1] );
    MDagPath p2( paths[2] );

    if( !(p0 == p1 ) || !(p0 == p2 ))
    {
        return MStatus::kFailure;
    }

    return MStatus::kSuccess;
}