- Main Page
- Related Pages
- Modules
- Namespaces
- Classes
- Examples

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