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

//
//  squareScaleManip.cpp
//      Creates manipulator node squareScaleManipulator
//      Creates command squareManipCmd
//
//  This example demonstrates how to use the MPxManipulatorNode
//  class along with a command to create a user defined
//  manipulator.  The manipulator created is a simple square
//  with the 4 sides as OpenGL pickable components.  As you
//  move the pickable component, selected transforms have
//  their scale attribute modified.
//  A corresponding command is used to create and delete
//  the manipulator node and to support undo/redo etc.

/*

// To show this example using MEL, run the following:
loadPlugin squareScaleManip.so;
squareManipCmd -create;

// To delete the manipulator using MEL:
squareManipCmd -delete;

*/

#include "squareScaleManip.h"

#include <maya/MIOStream.h>
#include <maya/MMatrix.h>
#include <maya/MVector.h>
#include <maya/MSelectionList.h>
#include <maya/MItSelectionList.h>
#include <maya/MFnTransform.h>
#include <maya/MGlobal.h>
#include <maya/MFnCamera.h>
#include <maya/MTemplateCommand.h>
#include <maya/MDrawRegistry.h>
#include <maya/MDrawContext.h>
#include <maya/MUserData.h>
#include <maya/MStateManager.h>

// Statics
MTypeId squareScaleManipulator::id( 0x81046 );
MString squareScaleManipulator::classification("drawdb/geometry/manip/squareScaleManipulator");
MString squareScaleManipulator::registrantId("SquareScaleManipPlugin");
const MPoint squareScaleManipulator::topLeft(-0.5f, 0.5f, 0.0f);
const MPoint squareScaleManipulator::topRight(0.5f, 0.5f, 0.0f);
const MPoint squareScaleManipulator::bottomLeft(-0.5f, -0.5f, 0.0f);
const MPoint squareScaleManipulator::bottomRight(0.5f, -0.5f, 0.0f);

//
// class implementation
//
squareScaleManipulator::squareScaleManipulator()
: MPxManipulatorNode()
, activeName(0)
, topName(0)
, rightName(0)
, bottomName(0)
, leftName(0)
, mousePointGlName(MPoint())
{
    // Populate initial points
    topLeft.get(tl);
    topRight.get(tr);
    bottomLeft.get(bl);
    bottomRight.get(br);
    // Setup the plane with a point on the
    // plane along with a normal
    MPoint pointOnPlane(topLeft);
    // Normal = cross product of two vectors on the plane
    MVector normalToPlane =
        (MVector(topLeft) - MVector(topRight)) ^
        (MVector(topRight) - MVector(bottomRight));
    // Necessary to normalize
    normalToPlane.normalize();
    // Plane defined by a point and a normal
    plane.setPlane(pointOnPlane, normalToPlane);
}

squareScaleManipulator::~squareScaleManipulator()
{
}

// virtual
void squareScaleManipulator::draw(
    M3dView &view,
    const MDagPath &path,
    M3dView::DisplayStyle style,
    M3dView::DisplayStatus status)
{
    // Are we in the right view
    MDagPath dpath;
    view.getCamera(dpath);
    if (!shouldDraw(dpath))
    {
        return;
    }

    // do the draw using common helper method
    view.beginGL();

    // Get the starting value of the pickable items
    MGLuint glPickableItem;
    glFirstHandle(glPickableItem);

    // Top
    topName = glPickableItem;
    // Place before you draw the manipulator component that can
    // be pickable.
    colorAndName(view, glPickableItem, true, mainColor());
    glBegin(GL_LINES);
        glVertex3fv(tl);
        glVertex3fv(tr);
    glEnd();

    // Right
    glPickableItem++;
    rightName = glPickableItem;
    colorAndName(view, glPickableItem, true, mainColor());
    glBegin(GL_LINES);
        glVertex3fv(tr);
        glVertex3fv(br);
    glEnd();

    // Bottom
    glPickableItem++;
    bottomName = glPickableItem;
    colorAndName(view, glPickableItem, true, mainColor());
    glBegin(GL_LINES);
        glVertex3fv(br);
        glVertex3fv(bl);
    glEnd();

    // Left
    glPickableItem++;
    leftName = glPickableItem;
    colorAndName(view, glPickableItem, true, mainColor());
    glBegin(GL_LINES);
        glVertex3fv(bl);
        glVertex3fv(tl);
    glEnd();

    view.endGL();
}

// virtual
MStatus squareScaleManipulator::doPress(M3dView& view)
{
    // Reset the mousePoint information on a new press
    mousePointGlName = MPoint::origin;
    updateDragInformation();
    return MS::kSuccess;
}

// virtual
MStatus squareScaleManipulator::doDrag(M3dView& view)
{
    updateDragInformation();
    return MS::kSuccess;
}

// virtual
MStatus squareScaleManipulator::doRelease(M3dView& view)
{
    // Scale nodes on the selection list. Implementation
    // is very simple and will not support undo.
    MSelectionList list;
    MGlobal::getActiveSelectionList(list);
    for (MItSelectionList iter(list); !iter.isDone(); iter.next())
    {
        MObject node;
        MStatus status;
        iter.getDependNode(node);
        MFnTransform xform(node, &status);
        if (MS::kSuccess == status)
        {
            double newScale[3];
            newScale[0] = mousePointGlName.x + 1;
            newScale[1] = mousePointGlName.y + 1;
            newScale[2] = mousePointGlName.z + 1;
            xform.setScale(newScale);
        }
    }
    return MS::kSuccess;
}

MStatus squareScaleManipulator::updateDragInformation()
{
    // Find the mouse point in local space
    MPoint localMousePoint;
    MVector localMouseDirection;
    if (MS::kFailure == mouseRay(localMousePoint, localMouseDirection))
        return MS::kFailure;

    // Find the intersection of the mouse point with the
    // manip plane
    MPoint mouseIntersectionWithManipPlane;
    if (!plane.intersect(localMousePoint, localMouseDirection, mouseIntersectionWithManipPlane))
        return MS::kFailure;

    mousePointGlName = mouseIntersectionWithManipPlane;

    if (glActiveName(activeName))
    {
        // Reset draw points
        topLeft.get(tl);
        topRight.get(tr);
        bottomLeft.get(bl);
        bottomRight.get(br);
        float* start = 0;
        float* end = 0;
        if (activeName == topName)
        {
            start = tl;
            end = tr;
        }
        if (activeName == bottomName)
        {
            start = bl;
            end = br;
        }
        if (activeName == rightName)
        {
            start = tr;
            end = br;
        }
        if (activeName == leftName)
        {
            start = tl;
            end = bl;
        }
        if (start && end)
        {
            // Find a vector on the plane
            lineMath line;
            MPoint a(start[0], start[1], start[2]);
            MPoint b(end[0], end[1], end[2]);
            MPoint vab = a - b;
            // Define line with a point and a vector on the plane
            line.setLine(start, vab);
            MPoint cpt;
            // Find the closest point so that we can get the
            // delta change of the mouse in local space
            if (line.closestPoint(mousePointGlName, cpt))
            {
                mousePointGlName.x -= cpt.x;
                mousePointGlName.y -= cpt.y;
                mousePointGlName.z -= cpt.z;
            }
            // Move draw points
            start[0] += (float)mousePointGlName.x;
            start[1] += (float)mousePointGlName.y;
            start[2] +=(float) mousePointGlName.z;
            end[0] += (float)mousePointGlName.x;
            end[1] += (float)mousePointGlName.y;
            end[2] +=(float) mousePointGlName.z;
        }
    }
    return MS::kSuccess;
}

bool squareScaleManipulator::shouldDraw(const MDagPath& cameraPath) const
{
    MStatus status;
    MFnCamera camera(cameraPath, &status);
    if (!status)
    {
        return false;
    }

    const char* nameBuffer = camera.name().asChar();
    if (0 == nameBuffer)
    {
        return false;
    }

    if ((0 == strstr(nameBuffer,"persp")) &&
        (0 == strstr(nameBuffer,"front")))
    {
        return false;
    }

    return true;
}


//
// Draw override implementation for VP2.0
//
class squareScaleManipulatorData : public MUserData
{
public:
    enum ManipEdge
    {
        kNone,
        kTop,
        kRight,
        kBottom,
        kLeft
    };
    squareScaleManipulatorData()
    : MUserData(false) // don't delete after draw
    , tl(0), tr(0), br(0), bl(0), activeEdge(kNone) {}
    virtual ~squareScaleManipulatorData() {}
    float* tl; float* tr; float* br; float* bl;
    ManipEdge activeEdge;
};

squareScaleManipulatorOverride::squareScaleManipulatorOverride(const MObject& obj)
: MHWRender::MPxDrawOverride(obj, squareScaleManipulatorOverride::draw)
{
}

squareScaleManipulatorOverride::~squareScaleManipulatorOverride()
{
}

MHWRender::DrawAPI squareScaleManipulatorOverride::supportedDrawAPIs() const
{
    return MHWRender::kOpenGL;
}

bool squareScaleManipulatorOverride::isBounded(
    const MDagPath& objPath,
    const MDagPath& cameraPath) const
{
    return true;
}

MBoundingBox squareScaleManipulatorOverride::boundingBox(
    const MDagPath& objPath,
    const MDagPath& cameraPath) const
{
    return MBoundingBox(
        squareScaleManipulator::topLeft,
        squareScaleManipulator::bottomRight);
}

bool squareScaleManipulatorOverride::disableInternalBoundingBoxDraw() const
{
    return true;
}

MUserData* squareScaleManipulatorOverride::prepareForDraw(
    const MDagPath& objPath,
    const MDagPath& cameraPath,
    MUserData* oldData)
{
    // get the node
    MStatus status;
    MFnDependencyNode node(objPath.node(), &status);
    if (!status) return NULL;
    squareScaleManipulator* manipNode =
        dynamic_cast<squareScaleManipulator*>(node.userNode());
    if (!manipNode) return NULL;

    // access/create user data for draw callback
    squareScaleManipulatorData* data =
        dynamic_cast<squareScaleManipulatorData*>(oldData);
    if (!data)
    {
        data = new squareScaleManipulatorData();
    }

    // populate user data from the node
    data->tl = data->tr = data->br = data->bl = 0;
    data->activeEdge = squareScaleManipulatorData::kNone;
    if (manipNode->shouldDraw(cameraPath))
    {
        data->tl = manipNode->tl;
        data->tr = manipNode->tr;
        data->br = manipNode->br;
        data->bl = manipNode->bl;
        if (manipNode->activeName == manipNode->topName)
        {
            data->activeEdge = squareScaleManipulatorData::kTop;
        }
        else if (manipNode->activeName == manipNode->rightName)
        {
            data->activeEdge = squareScaleManipulatorData::kRight;
        }
        else if (manipNode->activeName == manipNode->bottomName)
        {
            data->activeEdge = squareScaleManipulatorData::kBottom;
        }
        else if (manipNode->activeName == manipNode->leftName)
        {
            data->activeEdge = squareScaleManipulatorData::kLeft;
        }
    }

    return data;
}

void squareScaleManipulatorOverride::draw(
    const MHWRender::MDrawContext& context,
    const MUserData* data)
{
    MHWRender::MStateManager* stateMgr = context.getStateManager();
    const squareScaleManipulatorData* manipData =
        dynamic_cast<const squareScaleManipulatorData*>(data);
    if (stateMgr && manipData &&
        manipData->tl && manipData->tr && manipData->br && manipData->bl)
    {
        // turn off depth to get manip to draw on top of everything
        const MHWRender::MDepthStencilState* oldDepthState = stateMgr->getDepthStencilState();
        if (oldDepthState)
        {
            static const MHWRender::MDepthStencilState* depthState = NULL;
            if (!depthState)
            {
                MHWRender::MDepthStencilStateDesc desc;
                desc.depthEnable = false;
                desc.depthWriteEnable = false;
                depthState = stateMgr->acquireDepthStencilState(desc);
            }
            if (depthState)
            {
                stateMgr->setDepthStencilState(depthState);
            }
        }

        // draw manip
        static const float dormantColor[4] = { 0.39f, 0.94f, 1.0f, 1.0f };
        static const float activeColor[4] = { 1.0f, 1.0f, 0.0f, 1.0f };
        glColor4fv(manipData->activeEdge == squareScaleManipulatorData::kTop ? activeColor : dormantColor);
        glBegin(GL_LINES);
            glVertex3fv(manipData->tl);
            glVertex3fv(manipData->tr);
        glEnd();
        glColor4fv(manipData->activeEdge == squareScaleManipulatorData::kRight ? activeColor : dormantColor);
        glBegin(GL_LINES);
            glVertex3fv(manipData->tr);
            glVertex3fv(manipData->br);
        glEnd();
        glColor4fv(manipData->activeEdge == squareScaleManipulatorData::kBottom ? activeColor : dormantColor);
        glBegin(GL_LINES);
            glVertex3fv(manipData->br);
            glVertex3fv(manipData->bl);
        glEnd();
        glColor4fv(manipData->activeEdge == squareScaleManipulatorData::kLeft ? activeColor : dormantColor);
        glBegin(GL_LINES);
            glVertex3fv(manipData->bl);
            glVertex3fv(manipData->tl);
        glEnd();

        // restore old depth state
        if (oldDepthState)
        {
            stateMgr->setDepthStencilState(oldDepthState);
        }
    }
}


//
// Template command that creates and deletes the manipulator
//
class squareManipCmd;
char cmdName[] = "squareManipCmd";
char nodeName[] = "squareScaleManipulator";

class squareManipCmd : public MTemplateCreateNodeCommand<squareManipCmd,cmdName,nodeName>
{
public:
    //
    squareManipCmd()
    {}
};

static squareManipCmd _squareManipCmd;

//
// Static methods
//
void* squareScaleManipulator::creator()
{
    return new squareScaleManipulator();
}

MStatus squareScaleManipulator::initialize()
{
    // No-op
    return MS::kSuccess;
}

//
//  Entry points
//

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

    status = plugin.registerNode(
        nodeName,
        squareScaleManipulator::id,
        squareScaleManipulator::creator,
        squareScaleManipulator::initialize,
        MPxNode::kManipulatorNode,
        &squareScaleManipulator::classification);
    if (!status) {
        status.perror("registerNode");
        return status;
    }

    status = _squareManipCmd.registerCommand( obj );
    if (!status) {
        status.perror("registerCommand");
        return status;
    }

    status = MHWRender::MDrawRegistry::registerDrawOverrideCreator(
        squareScaleManipulator::classification,
        squareScaleManipulator::registrantId,
        squareScaleManipulatorOverride::Creator);
    if (!status) {
        status.perror("registerDrawOverrideCreator");
        return status;
    }

    return status;
}

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

    status = plugin.deregisterNode( squareScaleManipulator::id );
    if (!status) {
        status.perror("deregisterNode");
        return status;
    }
    status = _squareManipCmd.deregisterCommand( obj );
    if (!status) {
        status.perror("deregisterCommand");
        return status;
    }

    status = MHWRender::MDrawRegistry::deregisterDrawOverrideCreator(
        squareScaleManipulator::classification,
        squareScaleManipulator::registrantId);
    if (!status) {
        status.perror("deregisterDrawOverrideCreator");
        return status;
    }

    return status;
}