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;
}