surfaceBumpManip/surfaceBumpManip.cpp
 
 
 
surfaceBumpManip/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;
}