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

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

    /*

        This example demonstrates how to use the PointOnSurface manipulator in the
        API.  This example uses three classes to accomplish this task: First, a
        context command (surfaceBumpContext) is provided to create instances of
        the context.  Next, a custom selection context (SurfaceBumpContext) is
        created to manage the surface manipulator.  Finally, the surface
        manipulator is provided as a custom node class.

        Loading and unloading:
        ----------------------

        The surface bump manipulator context can be created with the following
        mel commands:

            surfaceBumpContext;
            setToolTo surfaceBumpContext1;

        If the preceding commands were used to create the manipulator context,
        the following commands can destroy it:

            deleteUI surfaceBumpContext1;
            deleteUI surfaceBumpManip;

        If the plugin is loaded and unloaded frequently (eg. during testing),
        it is useful to make these command sequences into shelf buttons.

        How to use:
        -----------

        Once the tool button has been created using the script above, select the
        tool button then click on a nurbs surface object.  The PointOnSurface
        manipulator should appear on the object.  Then drag the manipulator over
        the surface of the object and a bump should appear on the CV nearest to
        the manipulator.  If the bump is too small, try adjusting the BUMP_SCALE
        value and recompile.

    */

    #include <maya/MIOStream.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <math.h>

    #include <maya/MFn.h>
    #include <maya/MPxNode.h>
    #include <maya/MPxManipContainer.h>
    #include <maya/MPxSelectionContext.h>
    #include <maya/MPxContextCommand.h>
    #include <maya/MModelMessage.h>
    #include <maya/MFnPlugin.h>
    #include <maya/MGlobal.h>
    #include <maya/MItSelectionList.h>
    #include <maya/MPoint.h>
    #include <maya/MVector.h>
    #include <maya/MDagPath.h>
    #include <maya/MManipData.h>

    #include <maya/MItDependencyNodes.h>
    #include <maya/MItDependencyGraph.h>
    #include <maya/MFnNurbsSurface.h>
    #include <maya/MFnNumericAttribute.h>

    // Manipulators
    #include <maya/MFnPointOnSurfaceManip.h>

    // BUMP_SCALE is the factor indicating how far the bump created by the
    // manipulator should protrude from the object surface.  The value is treated
    // as a distance in object space.  Note that this value is how far the CV is
    // moved away from the surface, not the actual distance that the surface is
    // moved at the point.
    //
    static const double BUMP_SCALE = 0.5;

    // This is a utility method used to find the surface plug that should be used
    // to connect a pointOnSurface manipulator to a surface.  The method takes the
    // input node (which must be a DAG node whose child is the nurbs shape) and
    // finds the nurbs shape node (shape) and the surface plug (plug).
    //
    MStatus findSurfacePlug(const MObject& node, MPlug& plug, MObject& shape) {
        if (!node.hasFn(MFn::kDagNode))
        {
            MGlobal::displayError("Nodes passed to findSurfacePlug must be DAG"
                " nodes");
            return MS::kFailure;
        }

        MFnDagNode nodeFn(node);
        if (nodeFn.childCount() != 1)
        {
            MGlobal::displayError("DAG node must have 1 child");
            return MS::kFailure;
        }

        shape = nodeFn.child(0);
        if (!shape.hasFn(MFn::kNurbsSurface))
        {
            MGlobal::displayError("Child node is not a nurbs surface");
            shape = MObject::kNullObj;
            return MS::kFailure;
        }

        // Find the connection to the create attribute on the shape node.
        //
        MFnDependencyNode shapeNode(shape);
        plug = shapeNode.findPlug("create");

        return MS::kSuccess;
    }

    //
    // surfaceBumpManip
    //
    // This class implements the example pointOnSurface manipulator
    //

    class surfaceBumpManip : public MPxManipContainer
    {
    public:
        surfaceBumpManip();
        virtual ~surfaceBumpManip();

        static void * creator();
        static MStatus initialize();
        virtual MStatus createChildren();
        virtual MStatus connectToDependNode(const MObject &node);

        virtual void draw(M3dView &view,
                          const MDagPath &path,
                          M3dView::DisplayStyle style,
                          M3dView::DisplayStatus status);

        // Callback function
        MManipData surfacePointChangedCallback(unsigned index);

    public:
        static MTypeId id;

    private:
        MDagPath fPointOnSurfaceManip;
        MObject fSurfaceShape;

        // Previous parameters
        int saved_u,saved_v;
        // Previous position of the CV
        MPoint savedPoint;

        unsigned dummyPlugIndex;
    };

    MTypeId surfaceBumpManip::id( 0x80023 );

    surfaceBumpManip::surfaceBumpManip() : saved_u(-1),saved_v(-1)
    {
        // The constructor must not call createChildren for user-defined
        // manipulators.
    }

    surfaceBumpManip::~surfaceBumpManip()
    {
    }

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

    MStatus surfaceBumpManip::initialize()
    {
        return MPxManipContainer::initialize();
    }

    MStatus surfaceBumpManip::createChildren()
    {
        MStatus stat = MStatus::kSuccess;

        // Add the pointOnSurface manip
        //
        fPointOnSurfaceManip = addPointOnSurfaceManip("surfaceBumpManip", "point");
        MFnPointOnSurfaceManip pointOnSurfaceManip(fPointOnSurfaceManip);
        pointOnSurfaceManip.setDrawSurface(true);
        pointOnSurfaceManip.setDrawArrows(true);
        pointOnSurfaceManip.setParameters(0.0,0.0);

        return stat;
    }

    MStatus surfaceBumpManip::connectToDependNode(const MObject &node)
    {
        MStatus stat;

        MFnPointOnSurfaceManip pointOnSurfaceManip(fPointOnSurfaceManip);

        // Connect to the nurbs surface plug.  The surface plug indicates the
        // surface that should be used as the source for the uv manipulation.
        //
        MPlug surfacePlug;
        findSurfacePlug(node, surfacePlug, fSurfaceShape);
        if (!surfacePlug.isNull())
        {
            MGlobal::displayInfo(MString("Using surface plug: ") + surfacePlug.name());
            stat = pointOnSurfaceManip.connectToSurfacePlug(surfacePlug);
            if (stat != MStatus::kSuccess) {
                MGlobal::displayError("Could not connect surface plug");
                return stat;
            }
        }
        else {
            MGlobal::displayError("Error finding surface plug");
            return MS::kFailure;
        }

        // Add a dynamic attribute to the node to be computed by the manipToPlug
        // conversion function.  For this manipulator, the manipToPlug conversion
        // function is used as a callback so this plug is just a placeholder.
        //
        // Note that this plugin leaves the dummy plugs on the nodes it manipulates.
        //
        MFnDependencyNode nodeFn(node);
        MPlug dummyPlug = nodeFn.findPlug("dummyPlug", &stat);
        if (dummyPlug.isNull())
        {
            MFnNumericAttribute attributeFn;
            MObject attr = attributeFn.create("dummyPlug", "dp", MFnNumericData::k3Double);
            nodeFn.addAttribute(attr, MFnDependencyNode::kLocalDynamicAttr);
            dummyPlug = nodeFn.findPlug("dummyPlug", &stat);
            if (dummyPlug.isNull())
            {
                MGlobal::displayError("Could not find dummyPlug on the manipulator.");
                return MS::kFailure;
            }
        }

        // Create a manipToPlug callback for the param plug.  For now, the plug
        // that is computed is irrelevant (we only care about the callback being
        // invoked), so we pass in the dummy plug.
        //
        dummyPlugIndex = addManipToPlugConversionCallback(dummyPlug,
            (manipToPlugConversionCallback)
            &surfaceBumpManip::surfacePointChangedCallback);

        // Retrieve the transformation from the object and set it in the
        // manipulator.  This allows the manipulator to display with the object
        // it is manipulating.
        //
        MFnTransform transform(node);
        MTransformationMatrix matrix = transform.transformation();
        pointOnSurfaceManip.set(matrix);

        finishAddingManips();
        MPxManipContainer::connectToDependNode(node);
        return stat;
    }

    void surfaceBumpManip::draw(M3dView & view,
                         const MDagPath & path,
                         M3dView::DisplayStyle style,
                         M3dView::DisplayStatus status)
    {
        // Uses default manipulator drawing to draw the pointOnSurface manip
        //
        MPxManipContainer::draw(view, path, style, status);
    }

    MManipData surfaceBumpManip::surfacePointChangedCallback(unsigned index) {

        // Always return (0.0,0.0,0.0) to the dummy plug.
        //
        MFnNumericData numericData;
        MObject obj = numericData.create( MFnNumericData::k3Double );
        numericData.setData(0.0,0.0,0.0);

        if (index != dummyPlugIndex)
        {
            MGlobal::displayError("Invalid index in surface point changed callback!");
            return obj;
        }

        MFnNurbsSurface nurbsSurface(fSurfaceShape);

        // Get the parameter values from the node
        //
        double u = 0.0;
        double v = 0.0;
        MFnPointOnSurfaceManip pointOnSurfaceManip(fPointOnSurfaceManip);
        pointOnSurfaceManip.getParameters(u,v);

        // Uncomment to see the parameters that are received by the callback.
    /*
        MGlobal::displayInfo(MString("Num CVs are: ") +
            nurbsSurface.numSpansInU() + "," + nurbsSurface.numSpansInV());
        MGlobal::displayInfo(MString("Parameters are: ") + u + "," + v);
    */

        //
        // Snap the parameters to the nearest CV.  This code assumes that CVs are
        // a unit distance apart in both the U and V directions.
        //

        int u_int = 0;
        int v_int = 0;
        if (nurbsSurface.formInU() == MFnNurbsSurface::kPeriodic)
        {
            u_int = (int)(floor(u+1.5)) % nurbsSurface.numSpansInU();
            if (u_int < 0)
                u_int += nurbsSurface.numSpansInU();
        }
        else {
            u_int = (int)(floor(u+1.5));
        }

        if (nurbsSurface.formInV() == MFnNurbsSurface::kPeriodic)
        {
            v_int = (int)(floor(v+1.5)) % nurbsSurface.numSpansInV();
            if (v_int < 0)
                v_int += nurbsSurface.numSpansInV();
        }
        else {
            v_int = (int)(floor(v+1.5));
        }

        // Uncomment to see the CVs that were determined from the parameters.
    /*
        MGlobal::displayInfo(MString("Int params are: ") + u_int + "," + v_int);
    */

        // Is it the same CV?
        if (u_int == saved_u && v_int == saved_v)
        {
            return MManipData(obj);
        }

        // Is it the first one?  We need a special case to initialize the class
        // variables.
        if (saved_u == -1)
        {
            nurbsSurface.getCV(u_int,v_int,savedPoint,MSpace::kObject);
            saved_u = u_int;
            saved_v = v_int;
        }

        // Set the old CV back
        nurbsSurface.setCV(saved_u,saved_v,savedPoint,MSpace::kObject);

        // Get the position for this CV and store the u and v values
        nurbsSurface.getCV(u_int,v_int,savedPoint,MSpace::kObject);
        saved_u = u_int;
        saved_v = v_int;

        // Move the CV out by the normal
        MPoint perturbedPosition = savedPoint +
            BUMP_SCALE*nurbsSurface.normal((double)u_int,(double)v_int,MSpace::kObject);

        nurbsSurface.setCV(u_int,v_int,perturbedPosition,MSpace::kObject);

        return MManipData(obj);
    }

    //
    // SurfaceBumpContext
    //
    // This class is a simple context for supporting a pointOnSurface manip.
    //

    class SurfaceBumpContext : public MPxSelectionContext
    {
    public:
        SurfaceBumpContext();
        virtual void    toolOnSetup(MEvent &event);
        virtual void    toolOffCleanup();

        // Callback issued when selection list changes
        static void updateManipulators(void * data);

    private:
        MCallbackId id1;
    };

    SurfaceBumpContext::SurfaceBumpContext()
    {
        MString str("Plugin Surface Bump Manipulator");
        setTitleString(str);
    }

    void SurfaceBumpContext::toolOnSetup(MEvent &)
    {
        MString str("Drag the manipulator around the surface");
        setHelpString(str);

        updateManipulators(this);
        MStatus status;
        id1 = MModelMessage::addCallback(MModelMessage::kActiveListModified,
                                         updateManipulators,
                                         this, &status);
        if (!status) {
            MGlobal::displayError("Model addCallback failed");
        }
    }

    void SurfaceBumpContext::toolOffCleanup()
    {
        MStatus status;
        status = MModelMessage::removeCallback(id1);
        if (!status) {
            MGlobal::displayError("Model remove callback failed");
        }
        MPxContext::toolOffCleanup();
    }

    void SurfaceBumpContext::updateManipulators(void * data)
    {
        MStatus stat = MStatus::kSuccess;

        SurfaceBumpContext * ctxPtr = (SurfaceBumpContext *) data;
        ctxPtr->deleteManipulators();

        MSelectionList list;
        stat = MGlobal::getActiveSelectionList(list);
        MItSelectionList iter(list, MFn::kInvalid, &stat);

        if (MS::kSuccess == stat) {
            for (; !iter.isDone(); iter.next()) {

                // Make sure the selection list item is a depend node and has the
                // required plugs before manipulating it.
                //
                MObject dependNode;
                iter.getDependNode(dependNode);
                if (dependNode.isNull() || !dependNode.hasFn(MFn::kDependencyNode))
                {
                    MGlobal::displayWarning("Object in selection list is not "
                        "a depend node.");
                    continue;
                }

                // Add manipulator to the selected object
                //
                MString manipName ("surfaceBumpManip");
                MObject manipObject;
                surfaceBumpManip* manipulator =
                    (surfaceBumpManip *) surfaceBumpManip::newManipulator(
                        manipName,
                        manipObject);

                if (NULL != manipulator) {
                    // Add the manipulator
                    //
                    ctxPtr->addManipulator(manipObject);

                    // Connect the manipulator to the object in the selection list.
                    //
                    if (!manipulator->connectToDependNode(dependNode))
                    {
                        MFnDependencyNode dependNodeFn(dependNode);
                        MGlobal::displayWarning("Error connecting manipulator to"
                            " object: " + dependNodeFn.name());
                    }
                }
            }
        }
    }

    //
    // surfaceBumpContext
    //
    // This is the command that will be used to create instances
    // of our context.
    //

    class surfaceBumpContext : public MPxContextCommand
    {
    public:
        surfaceBumpContext() {};
        virtual MPxContext * makeObj();

    public:
        static void* creator();
    };

    MPxContext *surfaceBumpContext::makeObj()
    {
        return new SurfaceBumpContext();
    }

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

    //
    // The following routines are used to register/unregister
    // the context and manipulator
    //

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

        status = plugin.registerContextCommand("surfaceBumpContext",
                                               &surfaceBumpContext::creator);
        if (!status) {
            MGlobal::displayError("Error registering surfaceBumpContext command");
            return status;
        }

        status = plugin.registerNode("surfaceBumpManip", surfaceBumpManip::id,
                                     &surfaceBumpManip::creator, &surfaceBumpManip::initialize,
                                     MPxNode::kManipContainer);
        if (!status) {
            MGlobal::displayError("Error registering surfaceBumpManip node");
            return status;
        }

        return status;
    }

    MStatus uninitializePlugin(MObject obj)
    {
        MStatus status;
        MFnPlugin plugin(obj);

        status = plugin.deregisterContextCommand("surfaceBumpContext");
        if (!status) {
            MGlobal::displayError("Error deregistering surfaceBumpContext command");
            return status;
        }

        status = plugin.deregisterNode(surfaceBumpManip::id);
        if (!status) {
            MGlobal::displayError("Error deregistering surfaceBumpManip node");
            return status;
        }

        return status;
    }