meshReorder/meshRemapTool.cpp
 
 
 
meshReorder/meshRemapTool.cpp
//
// Description: Get user selections for mesh vertex/edge remapping 
//
// Author: Bruce Hickey
//

// This is added to prevent multiple definitions of the MApiVersion string.
#define _MApiVersion

#include <maya/MCursor.h>
#include <maya/MFnDependencyNode.h>
#include <maya/MFnMesh.h>
#include <maya/MGlobal.h>
#include <maya/MIOStream.h>
#include <maya/MItMeshPolygon.h>
#include <maya/MItMeshVertex.h>
#include <maya/MMatrix.h>
#include <maya/MPlug.h>
#include <maya/MPointArray.h>
#include <maya/MVector.h>

#include <meshRemapTool.h>
#include <meshMapUtils.h>


// The user Context
const double epsilon = 0.00001;

meshRemapTool::meshRemapTool()
{
        setTitleString ( "Mesh Remap Tool" );
        setCursor( MCursor::editCursor );
        reset();
}

meshRemapTool::~meshRemapTool() {}

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

void meshRemapTool::toolOnSetup ( MEvent & )
{
        reset();
}

void meshRemapTool::reset()
{
        fNumSelectedPoints = 0;

        fSelectedPathSrc.clear();
        fSelectedComponentSrc.clear();
        fSelectVtxSrc.clear();
        fSelectedFaceSrc = -1;

        fSelectedPathDst.clear();
        fSelectedComponentDst.clear();
        fSelectVtxDst.clear();
        fSelectedFaceDst = -1;

        fCurrentHelpString = "Select 1st point on source mesh.";

        helpStateHasChanged();
}

//
// Selects objects within the user defined area, then process them
// 
MStatus meshRemapTool::doRelease( MEvent & event )
{
        char buf[1024];

        // Perform the base actions
        MStatus stat = MPxSelectionContext::doRelease(event);

        // Get the list of selected items 
        MGlobal::getActiveSelectionList( fSelectionList );

        //
        //  Get the selection's details, we must have exactly one component selected
        //
        MObject component;
        MDagPath path;

        MItSelectionList selectionIt (fSelectionList, MFn::kComponent);

        MStringArray    selections;
        selectionIt.getStrings( selections );
        
        // There are valid cases where only the meshes are selected.
        if( selections.length() == 0 )
        {
                // Handling the case where the user's workflow is
                // 1) select src mesh, select vtx1, vtx2, vtx3 (i.e. fNumSelectedPoints == 0)
                // 2) select dst mesh, select vtx1, vtx2, vtx3 (i.e. fNumSelectedPoints == 3)
                if( fNumSelectedPoints == 0 || fNumSelectedPoints == 3 )
                {
                        MItSelectionList selectionDag (fSelectionList, MFn::kDagNode);
                        if (!selectionDag.isDone() && selectionDag.getDagPath(path) == MS::kSuccess)
                        {
                                path.extendToShape();
                                // return true there is exactly one mesh selected
                                if (path.hasFn(MFn::kMesh))
                                {
                                        selectionDag.next();
                                        if (selectionDag.isDone())
                                        {
                                                //      If this is the destination mesh, make sure that
                                                //      it doesn't have history.
                                                if (fNumSelectedPoints == 3)
                                                {
                                                        return checkForHistory(path);
                                                }

                                                return MS::kSuccess;
                                        }
                                }
                        }
                }

                // Handling the case where the user is doing the auto remap, i.e.
                // select src mesh, and then dst mesh when fNumSelectedPoints == 0.
                if( fNumSelectedPoints == 0 )
                {
                        MItSelectionList selectionDag (fSelectionList, MFn::kDagNode);
                        if (!selectionDag.isDone() && selectionDag.getDagPath(path) == MS::kSuccess)
                        {
                                path.extendToShape();
                                // Confirm first selection is a mesh
                                if (path.hasFn(MFn::kMesh))
                                {
                                        selectionDag.next();
                                        if (!selectionDag.isDone() &&
                                            selectionDag.getDagPath(path) == MS::kSuccess)
                                        {
                                                path.extendToShape();
                                                // Confirm second selection is a mesh
                                                if (path.hasFn(MFn::kMesh))
                                                {
                                                        selectionDag.next();
                                                        // Confirm there are exactly 2 selections
                                                        if (selectionDag.isDone())
                                                        {
                                                                //      Make sure that the destination mesh
                                                                //      doesn't have history.
                                                                return checkForHistory(path);
                                                        }
                                                }
                                        }
                                }
                        }
                }
        }

        if( selections.length() != 1 )
        {
                MGlobal::displayError( "Must select exactly one vertex" );
                return MS::kSuccess;
        }

        if (selectionIt.isDone ())
        {
                MGlobal::displayError( "Selected item not a vertex" );
                return MS::kSuccess;
        }

        if( selectionIt.getDagPath (path, component) != MStatus::kSuccess )
        {
                MGlobal::displayError( "Must select a mesh or its vertex" );
                return MS::kSuccess;
        }

        if (!path.node().hasFn(MFn::kMesh) && !(path.node().hasFn(MFn::kTransform) && path.hasFn(MFn::kMesh)))
        {
                MGlobal::displayError( "Must select a mesh or its transform" );
                return MS::kSuccess;
        }

        //      If this is the first vertex of the destination mesh, make sure that
        //      it doesn't have history.
        if ((fNumSelectedPoints == 3) && (checkForHistory(path) != MS::kSuccess))
        {
                return MS::kSuccess;
        }

        MItMeshVertex fIt ( path, component, &stat );
        if( stat != MStatus::kSuccess )
        {
                MGlobal::displayError( "MItMeshVertex failed");
                return MStatus::kFailure;
        }

        if (fIt.count() != 1 )
        {
                sprintf(buf, "Invalid selection '%s'. Vertices must be picked one at a time.", selections[0].asChar() );
                MGlobal::displayError( buf );
                return MS::kSuccess;
        }
        else
        {
                sprintf(buf, "Accepting vertex '%s'", selections[0].asChar() );
                MGlobal::displayInfo(  buf );
        }

        //
        // Now that we know it's valid, process the selection, the first 3 items are the source, the second
        // 3 define the target
        //

        if( fNumSelectedPoints < 3 )
        {
                fSelectedPathSrc.append( path );
                fSelectedComponentSrc.append( component );
        }
        else 
        {
                fSelectedPathDst.append( path );
                fSelectedComponentDst.append( component );
        }

        //
        //  When each of the source/target are defined, process them. An error/invalid selection will restart the selection for
        //  that particular mesh.
        //  
        if( fNumSelectedPoints == 2 )
        {
                if( ( stat = meshMapUtils::validateFaceSelection( fSelectedPathSrc, fSelectedComponentSrc, &fSelectedFaceSrc, &fSelectVtxSrc ) ) != MStatus::kSuccess )
                {
                        MGlobal::displayError("Selected vertices don't define a unique face on source mesh");
                        reset();
                        return stat;
                }
        }

        //
        // Once the target is defined, invoke the command
        //
        if( fNumSelectedPoints == 5 )
        {
                if( ( stat = meshMapUtils::validateFaceSelection( fSelectedPathDst, fSelectedComponentDst, &fSelectedFaceDst, &fSelectVtxDst ) ) != MStatus::kSuccess )
                {
                        MGlobal::displayError("Selected vertices don't define a unique face on destination mesh");
                        reset();
                        return stat;
                }

                executeCmd();
        }
        else
        {
                //
                // We don't have all the details yet, just move to the next item
                //
                fNumSelectedPoints++;   
        }

        helpStateHasChanged();

        return stat;
}

MStatus meshRemapTool::checkForHistory(const MDagPath& mesh)
{
        MFnMesh meshFn(mesh);
        MPlug   historyPlug = meshFn.findPlug("inMesh", true);

        if (historyPlug.isDestination())
        {
                MGlobal::displayError("Destination mesh has history. Its geometry cannot be modified.");
                return MS::kInvalidParameter;
        }

        return MS::kSuccess;
}

MStatus meshRemapTool::resolveMapping()
{
        // Get the list of selected items 
        MGlobal::getActiveSelectionList( fSelectionList );
        if( fSelectionList.length() != 2)
        {
                reset();
                helpStateHasChanged();
                return MStatus::kFailure;
        }

        MDagPath dagPath;
        MObject  component;
        if( fSelectionList.getDagPath(0, dagPath, component) != MStatus::kSuccess)
        {
                MGlobal::displayError(" Invalid source mesh");
                return MStatus::kFailure;
        }
        dagPath.extendToShape();
        // Repeat this 3 times one for each vertex
        fSelectedPathSrc.append(dagPath);
        fSelectedPathSrc.append(dagPath);
        fSelectedPathSrc.append(dagPath);

        if( fSelectionList.getDagPath(1, dagPath, component) != MStatus::kSuccess)
        {
                MGlobal::displayError(" Invalid destination mesh");
                return MStatus::kFailure;
        }
        dagPath.extendToShape();
        // Repeat this 3 times one for each vertex
        fSelectedPathDst.append(dagPath);
        fSelectedPathDst.append(dagPath);
        fSelectedPathDst.append(dagPath);

        // Find the vertices in Object Space of the first polygon
        MPointArray srcPts;
        MIntArray   srcVertIds, dstVertIds;
        MItMeshPolygon faceIterSrc(fSelectedPathSrc[0]);
        unsigned srcFaceId = faceIterSrc.index();
        faceIterSrc.getPoints(srcPts, MSpace::kObject);
        faceIterSrc.getVertices(srcVertIds);

        // Tranfrom the vertices to dst World Space
        unsigned i, j;
        MMatrix m = fSelectedPathDst[0].inclusiveMatrix();
        for(i = 0; i < srcPts.length(); i++)
        {
            srcPts[i] = srcPts[i] * m;
        }

        MItMeshPolygon faceIterDst(fSelectedPathDst[0]);
        while (!faceIterDst.isDone())
        {
                MPointArray dstPts;
                faceIterDst.getPoints(dstPts, MSpace::kWorld);
                if (arePointsOverlap(srcPts, dstPts))
                {
                        // Set face and vertex indices
                        fSelectedFaceSrc = srcFaceId;
                        fSelectedFaceDst = faceIterDst.index();
                        fSelectVtxSrc.append(srcVertIds[0]);
                        fSelectVtxSrc.append(srcVertIds[1]);
                        fSelectVtxSrc.append(srcVertIds[2]);

                        faceIterDst.getVertices(dstVertIds);
                        for (j = 0; j < dstPts.length(); j++)
                        {
                                MVector v = dstPts[j] - srcPts[0];
                                if (v * v < epsilon)
                                {
                                        fSelectVtxDst.append(dstVertIds[j]);
                                        break;
                                }
                        }

                        for (j = 0; j < dstPts.length(); j++)
                        {
                                MVector v = dstPts[j] - srcPts[1];
                                if (v * v < epsilon)
                                {
                                        fSelectVtxDst.append(dstVertIds[j]);
                                        break;
                                }
                        }

                        for (j = 0; j < dstPts.length(); j++)
                        {
                                MVector v = dstPts[j] - srcPts[2];
                                if (v * v < epsilon)
                                {
                                        fSelectVtxDst.append(dstVertIds[j]);
                                        break;
                                }
                        }

                        return MStatus::kSuccess;
                }
                faceIterDst.next();
        }
        return MStatus::kFailure;
}

bool meshRemapTool::arePointsOverlap(const MPointArray& srcPts, const MPointArray& dstPts) const
{
        unsigned i, j;
        for(i = 0; i < 3; i++) 
        {
                bool overlap = false;
                const MPoint &pt = srcPts[i];
                for(j = 0; j < dstPts.length(); j++) 
                {
                        MVector v = dstPts[j] - pt;
                        if (v * v < epsilon)
                        {
                                overlap = true;
                                break;
                        }
                }
                if (!overlap) return false;
        }
        return true;
}

void meshRemapTool::completeAction()
{
        if( resolveMapping() != MStatus::kSuccess )
        {
                reset();
                return;
        }
        executeCmd();
}

void meshRemapTool::executeCmd()
{
        char cmdString[200];
        sprintf(cmdString, "meshRemap %s.vtx[%d] %s.vtx[%d] %s.vtx[%d] %s.vtx[%d] %s.vtx[%d] %s.vtx[%d]",
                fSelectedPathSrc[0].partialPathName().asChar(), fSelectVtxSrc[0],
                fSelectedPathSrc[1].partialPathName().asChar(), fSelectVtxSrc[1],
                fSelectedPathSrc[2].partialPathName().asChar(), fSelectVtxSrc[2],
                fSelectedPathDst[0].partialPathName().asChar(), fSelectVtxDst[0],
                fSelectedPathDst[1].partialPathName().asChar(), fSelectVtxDst[1],
                fSelectedPathDst[2].partialPathName().asChar(), fSelectVtxDst[2]);

        MStatus st = MGlobal::executeCommand(cmdString, true, true);
        if (st)
        {
                MGlobal::displayInfo( "Mesh remapping complete" );
        }

        MGlobal::clearSelectionList();
        reset();
}

void meshRemapTool::helpStateHasChanged()
//
// Description:
//              Set up the correct information in the help window based
//              on the current state.  
//
{
        switch (fNumSelectedPoints)
        {
                case 0:
                 fCurrentHelpString = "For auto remap select source mesh and then destination mesh and Press Enter. For manual remap select 1st vertex on source mesh.";
                 break;
                case 1:
                 fCurrentHelpString = "Select 2nd vertex on source mesh.";
                 break;
                case 2:
                 fCurrentHelpString = "Select 3rd vertex on source mesh.";
                 break;
                case 3:
                 fCurrentHelpString = "Select 1st vertex on target mesh.";
                 break;
                case 4:
                 fCurrentHelpString = "Select 2nd vertex on target mesh.";
                 break;
                case 5:
                 fCurrentHelpString = "Select 3rd vertex on target mesh.";
                 break;
                default:
                 // nothing, shouldn't be here
                 break;
         }

        setHelpString( fCurrentHelpString );
}

MPxContext* meshRemapContextCmd::makeObj()
{
        return new meshRemapTool;
}

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

//-
// ==========================================================================
// 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.
// ==========================================================================
//+