Add MediaImagePlane Node
This commit is contained in:
@@ -0,0 +1,190 @@
|
||||
//-
|
||||
// ==========================================================================
|
||||
// Copyright 2019 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.
|
||||
// ==========================================================================
|
||||
//+
|
||||
// Description:
|
||||
// Demonstrates how to create your own custom image plane based on
|
||||
// Maya's internal image plane classes. This allows API users to
|
||||
// override the default Maya image plane behavior. This class works
|
||||
// like typical API nodes in that it can have a compute method and
|
||||
// can contain static attributes added by the API user. This
|
||||
// example class overrides the default image plane behavior and
|
||||
// allows users to add transparency to an image plane using the
|
||||
// transparency attribute on the node. Note, this code also
|
||||
// illustrates how to use MImage to control the floating point
|
||||
// depth buffer. When useDepthMap is set to true, depth is added
|
||||
// is added to the image such that half of the image is at the near
|
||||
// clip plane and the remaining half is at the far clip plane.
|
||||
//
|
||||
// Note, once the image plane node has been created it you must
|
||||
// attached it to the camera shape that is displaying the node.
|
||||
// You need to use the imagePlane command to attach the image plane
|
||||
// you created to the specified camera.
|
||||
//
|
||||
// This example works only with renderers that use node evaluation
|
||||
// as a part of the rendering process, e.g. Maya Software. It does
|
||||
// not work with 3rd party renderers that rely on a scene translation
|
||||
// mechanism.
|
||||
//
|
||||
// For example,
|
||||
// string $imageP = `createNode customImagePlane`;
|
||||
// imagePlane -edit -camera "persp" $imageP
|
||||
//
|
||||
#include <maya/MPxImagePlane.h>
|
||||
#include <maya/MFnPlugin.h>
|
||||
#include <maya/MImage.h>
|
||||
#include <maya/MString.h>
|
||||
#include <maya/MFnNumericAttribute.h>
|
||||
#include <maya/MFnNumericData.h>
|
||||
#include <maya/MDataHandle.h>
|
||||
#include <maya/MPlug.h>
|
||||
#include <maya/MEventMessage.h>
|
||||
#include <maya/MGlobal.h>
|
||||
class customImagePlane : public MPxImagePlane
|
||||
{
|
||||
public:
|
||||
customImagePlane();
|
||||
virtual ~customImagePlane();
|
||||
MStatus loadImageMap( const MString &fileName, int frame, MImage &image ) override;
|
||||
bool getInternalValue( const MPlug&, MDataHandle&) override;
|
||||
bool setInternalValue( const MPlug&, const MDataHandle&) override;
|
||||
static void* creator();
|
||||
static MStatus initialize();
|
||||
static MTypeId id; // The IFF type id
|
||||
static MObject aTransparency;
|
||||
static void timeChangedCallback( void * );
|
||||
private:
|
||||
double fTransparency;
|
||||
MCallbackId fTimeChangedCallbackId;
|
||||
};
|
||||
MObject customImagePlane::aTransparency;
|
||||
customImagePlane::customImagePlane()
|
||||
: MPxImagePlane()
|
||||
, fTransparency( 0.0 )
|
||||
, fTimeChangedCallbackId( 0 )
|
||||
{
|
||||
// Uncomment the following line if you need a workaround which updates the
|
||||
// transparency of the image plane during playback or srubbing with the
|
||||
// Time Slider.
|
||||
//
|
||||
//fTimeChangedCallbackId = MEventMessage::addEventCallback(
|
||||
// "timeChanged", &timeChangedCallback, this);
|
||||
}
|
||||
customImagePlane::~customImagePlane()
|
||||
{
|
||||
if (fTimeChangedCallbackId != 0) {
|
||||
MMessage::removeCallback(fTimeChangedCallbackId);
|
||||
fTimeChangedCallbackId = 0;
|
||||
}
|
||||
}
|
||||
bool
|
||||
customImagePlane::getInternalValue( const MPlug &plug, MDataHandle &handle)
|
||||
{
|
||||
if ( plug == aTransparency ) {
|
||||
handle.set( fTransparency );
|
||||
return true;
|
||||
}
|
||||
return MPxImagePlane::getInternalValue( plug, handle );
|
||||
}
|
||||
bool
|
||||
customImagePlane::setInternalValue( const MPlug &plug, const MDataHandle &handle)
|
||||
{
|
||||
if ( plug == aTransparency ) {
|
||||
fTransparency = handle.asDouble();
|
||||
setImageDirty();
|
||||
return true;
|
||||
}
|
||||
return MPxImagePlane::setInternalValue( plug, handle );
|
||||
}
|
||||
MStatus
|
||||
customImagePlane::loadImageMap( const MString &fileName, int frame, MImage &image )
|
||||
{
|
||||
image.readFromFile(fileName);
|
||||
unsigned int width, height;
|
||||
image.getSize(width, height);
|
||||
unsigned int size = width * height;
|
||||
unsigned char *pixels = image.pixels();
|
||||
unsigned int i;
|
||||
for ( i = 0; i < size; i ++, pixels += 4 ) {
|
||||
pixels[3] = (unsigned char)(pixels[3] * (1.0 - fTransparency));
|
||||
}
|
||||
MPlug depthMap( thisMObject(), useDepthMap );
|
||||
bool value;
|
||||
depthMap.getValue( value );
|
||||
|
||||
if ( value ) {
|
||||
float *buffer = new float[width*height];
|
||||
unsigned int c, j;
|
||||
for ( c = i = 0; i < height; i ++ ) {
|
||||
for ( j = 0; j < width; j ++, c++ ) {
|
||||
if ( i > height/2 ) {
|
||||
buffer[c] = -1.0f;
|
||||
} else {
|
||||
buffer[c] = 0.0f;
|
||||
}
|
||||
}
|
||||
}
|
||||
image.setDepthMap( buffer, width, height );
|
||||
delete [] buffer;
|
||||
}
|
||||
return MStatus::kSuccess;
|
||||
}
|
||||
MTypeId customImagePlane::id( 0x1A19 );
|
||||
void*
|
||||
customImagePlane::creator()
|
||||
{
|
||||
return new customImagePlane;
|
||||
}
|
||||
MStatus
|
||||
customImagePlane::initialize()
|
||||
{
|
||||
MFnNumericAttribute nAttr;
|
||||
aTransparency = nAttr.create( "transparency", "tp",
|
||||
MFnNumericData::kDouble, 0 );
|
||||
nAttr.setStorable(true);
|
||||
nAttr.setInternal(true);
|
||||
nAttr.setMin(0.0);
|
||||
nAttr.setMax(1.0);
|
||||
nAttr.setDefault(0.0);
|
||||
nAttr.setKeyable(true);
|
||||
addAttribute( aTransparency );
|
||||
return MStatus::kSuccess;
|
||||
}
|
||||
// These methods load and unload the plugin, registerNode registers the
|
||||
// new node type with maya
|
||||
//
|
||||
MStatus initializePlugin( MObject obj )
|
||||
{
|
||||
MFnPlugin plugin( obj, PLUGIN_COMPANY, "7.0", "Any");
|
||||
MStatus status = plugin.registerNode( "customImagePlane", customImagePlane::id,
|
||||
customImagePlane::creator,
|
||||
customImagePlane::initialize,
|
||||
MPxNode::kImagePlaneNode );
|
||||
if (!status) {
|
||||
status.perror("registerNode");
|
||||
}
|
||||
return status;
|
||||
}
|
||||
MStatus uninitializePlugin( MObject obj )
|
||||
{
|
||||
MFnPlugin plugin( obj );
|
||||
MStatus status = plugin.deregisterNode( customImagePlane::id );
|
||||
if (!status) {
|
||||
status.perror("deregisterNode");
|
||||
}
|
||||
return status;
|
||||
}
|
||||
void customImagePlane::timeChangedCallback(void *clientData)
|
||||
{
|
||||
customImagePlane* imagePlane = (customImagePlane *) clientData;
|
||||
if (imagePlane) {
|
||||
MString cmd = "getAttr " + imagePlane->name() + ".tp";
|
||||
MGlobal::executeCommand(cmd);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,345 @@
|
||||
//-
|
||||
// ==========================================================================
|
||||
// Copyright 2015 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.
|
||||
// ==========================================================================
|
||||
//+
|
||||
/*
|
||||
transformDrawNode uses MPxDrawOverride to draw texts about transformation of
|
||||
all mesh shapes in the viewport via MPxDrawOverride::addUIDrawables. By setting
|
||||
isAlwaysDirty to false in MPxDrawOverride constructor, the draw override will
|
||||
be updated (via prepareForDraw()) only when the node is marked dirty via DG
|
||||
evaluation or dirty propagation. Additional callbacks are also added to
|
||||
explicitly mark the node as being dirty (via MRenderer::setGeometryDrawDirty())
|
||||
for certain circumstances. Note that the draw callback in MPxDrawOverride
|
||||
constructor is set to NULL in order to achieve better performance.
|
||||
*/
|
||||
#include <maya/MCallbackIdArray.h>
|
||||
#include <maya/MDagMessage.h>
|
||||
#include <maya/MDrawRegistry.h>
|
||||
#include <maya/MFnDagNode.h>
|
||||
#include <maya/MFnEnumAttribute.h>
|
||||
#include <maya/MFnNumericAttribute.h>
|
||||
#include <maya/MFnPlugin.h>
|
||||
#include <maya/MFrameContext.h>
|
||||
#include <maya/MItDag.h>
|
||||
#include <maya/MPxDrawOverride.h>
|
||||
#include <maya/MPxLocatorNode.h>
|
||||
#include <maya/MVectorArray.h>
|
||||
#include <stdio.h>
|
||||
using namespace MHWRender;
|
||||
class transformDrawNode : public MPxLocatorNode
|
||||
{
|
||||
public:
|
||||
enum ETransformType
|
||||
{
|
||||
kTranslate,
|
||||
kRotate,
|
||||
kScale,
|
||||
kShear
|
||||
};
|
||||
static void* creator() { return new transformDrawNode(); }
|
||||
static MStatus initialize();
|
||||
// Registration
|
||||
static constexpr const char className[] = "transformDrawNode";
|
||||
static MTypeId id;
|
||||
static MString drawDbClassification;
|
||||
static MString drawRegistrantId;
|
||||
// Attributes
|
||||
static MObject aTextColor;
|
||||
static MObject aTransformType;
|
||||
private:
|
||||
transformDrawNode();
|
||||
~transformDrawNode() override;
|
||||
static void AllDagChangesCallback(
|
||||
MDagMessage::DagMessage msgType,
|
||||
MDagPath &child,
|
||||
MDagPath &parent,
|
||||
void *clientData);
|
||||
static void WorldMatrixModifiedCallback(
|
||||
MObject &transformNode,
|
||||
MDagMessage::MatrixModifiedFlags &modified,
|
||||
void *clientData);
|
||||
void processDagMessage(bool refreshWorldMatrixCbIds);
|
||||
MCallbackId fAllDagChangesCbId;
|
||||
MCallbackIdArray fWorldMatrixModifiedCbIds;
|
||||
};
|
||||
// Registration
|
||||
constexpr const char transformDrawNode::className[];
|
||||
MTypeId transformDrawNode::id(0x80034);
|
||||
MString transformDrawNode::drawDbClassification("drawdb/geometry/transformDrawNode");
|
||||
MString transformDrawNode::drawRegistrantId(transformDrawNode::className);
|
||||
// Attributes
|
||||
MObject transformDrawNode::aTextColor;
|
||||
MObject transformDrawNode::aTransformType;
|
||||
MStatus transformDrawNode::initialize()
|
||||
{
|
||||
MFnNumericAttribute nAttr;
|
||||
aTextColor = nAttr.create("textColor", "tc", MFnNumericData::k3Float);
|
||||
nAttr.setDefault(1.0f, 1.0f, 1.0f);
|
||||
nAttr.setUsedAsColor(true);
|
||||
MPxNode::addAttribute(aTextColor);
|
||||
MFnEnumAttribute eAttr;
|
||||
aTransformType = eAttr.create("transformType", "tt", kTranslate);
|
||||
eAttr.addField("Translate", kTranslate);
|
||||
eAttr.addField("Rotate", kRotate);
|
||||
eAttr.addField("Scale", kScale);
|
||||
eAttr.addField("Shear", kShear);
|
||||
MPxNode::addAttribute(aTransformType);
|
||||
return MS::kSuccess;
|
||||
}
|
||||
transformDrawNode::transformDrawNode() : MPxLocatorNode()
|
||||
{
|
||||
fAllDagChangesCbId = MDagMessage::addAllDagChangesCallback(
|
||||
AllDagChangesCallback, this);
|
||||
}
|
||||
transformDrawNode::~transformDrawNode()
|
||||
{
|
||||
if (fWorldMatrixModifiedCbIds.length() > 0)
|
||||
{
|
||||
MMessage::removeCallbacks(fWorldMatrixModifiedCbIds);
|
||||
}
|
||||
if (fAllDagChangesCbId != 0)
|
||||
{
|
||||
MMessage::removeCallback(fAllDagChangesCbId);
|
||||
}
|
||||
}
|
||||
void transformDrawNode::processDagMessage(bool refreshWorldMatrixModifiedCbIds)
|
||||
{
|
||||
// Explicitly mark the node as being dirty on certain DAG message callbacks
|
||||
// so that the draw override can be updated.
|
||||
MRenderer::setGeometryDrawDirty(thisMObject());
|
||||
if (refreshWorldMatrixModifiedCbIds)
|
||||
{
|
||||
MStatus status;
|
||||
MItDag dagIt(MItDag::kDepthFirst, MFn::kMesh, &status);
|
||||
if (status)
|
||||
{
|
||||
// Remove existing callbacks
|
||||
if (fWorldMatrixModifiedCbIds.length() > 0)
|
||||
{
|
||||
MMessage::removeCallbacks(fWorldMatrixModifiedCbIds);
|
||||
fWorldMatrixModifiedCbIds.clear();
|
||||
}
|
||||
// Add new callbacks
|
||||
for ( ;!dagIt.isDone(); dagIt.next() )
|
||||
{
|
||||
MDagPath dagPath;
|
||||
status = dagIt.getPath(dagPath);
|
||||
if (status)
|
||||
{
|
||||
MCallbackId id = MDagMessage::addWorldMatrixModifiedCallback(
|
||||
dagPath, WorldMatrixModifiedCallback, this);
|
||||
fWorldMatrixModifiedCbIds.append(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
void transformDrawNode::AllDagChangesCallback(
|
||||
MDagMessage::DagMessage msgType,
|
||||
MDagPath &child,
|
||||
MDagPath &parent,
|
||||
void *clientData)
|
||||
{
|
||||
// We need to refresh the world matrix modified callbacks because the DAG
|
||||
// is just changed.
|
||||
transformDrawNode *node = static_cast<transformDrawNode*>(clientData);
|
||||
if (node) node->processDagMessage(true);
|
||||
}
|
||||
void transformDrawNode::WorldMatrixModifiedCallback(
|
||||
MObject &transformNode,
|
||||
MDagMessage::MatrixModifiedFlags &modified,
|
||||
void *clientData)
|
||||
{
|
||||
// We don't need to refresh the world matrix modified callbacks because the
|
||||
// DAG is not changed.
|
||||
transformDrawNode *node = static_cast<transformDrawNode*>(clientData);
|
||||
if (node) node->processDagMessage(false);
|
||||
}
|
||||
class transformDrawData : public MUserData
|
||||
{
|
||||
public:
|
||||
MColor fTextColor{1.0f, 1.0f, 1.0f, 1.0f};
|
||||
transformDrawNode::ETransformType fTransformType{transformDrawNode::kTranslate};
|
||||
MVectorArray fPositions;
|
||||
MVectorArray fVectors;
|
||||
};
|
||||
class transformDrawOverride : public MPxDrawOverride
|
||||
{
|
||||
public:
|
||||
static MPxDrawOverride* creator(const MObject& obj)
|
||||
{
|
||||
return new transformDrawOverride(obj);
|
||||
}
|
||||
DrawAPI supportedDrawAPIs() const override { return kAllDevices; }
|
||||
bool hasUIDrawables() const override { return true; }
|
||||
MUserData* prepareForDraw(
|
||||
const MDagPath& objPath,
|
||||
const MDagPath& cameraPath,
|
||||
const MFrameContext& frameContext,
|
||||
MUserData* oldData) override;
|
||||
void addUIDrawables(
|
||||
const MDagPath& objPath,
|
||||
MUIDrawManager& drawManager,
|
||||
const MFrameContext& frameContext,
|
||||
const MUserData* data) override;
|
||||
private:
|
||||
// By setting isAlwaysDirty to false in MPxDrawOverride constructor, the
|
||||
// draw override will be updated (via prepareForDraw()) only when the node
|
||||
// is marked dirty via DG evaluation or dirty propagation. Additional
|
||||
// callbacks are also added to explicitly mark the node as being dirty (via
|
||||
// MRenderer::setGeometryDrawDirty()) for certain circumstances. Note that
|
||||
// the draw callback in MPxDrawOverride constructor is set to NULL in order
|
||||
// to achieve better performance.
|
||||
transformDrawOverride(const MObject& obj) : MPxDrawOverride(obj, NULL, false) {}
|
||||
~transformDrawOverride() override {}
|
||||
};
|
||||
MUserData* transformDrawOverride::prepareForDraw(
|
||||
const MDagPath& objPath,
|
||||
const MDagPath& cameraPath,
|
||||
const MFrameContext& frameContext,
|
||||
MUserData* oldData)
|
||||
{
|
||||
MStatus status;
|
||||
MObject obj = objPath.node(&status);
|
||||
if (!status) return NULL;
|
||||
transformDrawData* tdData = dynamic_cast<transformDrawData*>(oldData);
|
||||
if (!tdData)
|
||||
{
|
||||
tdData = new transformDrawData();
|
||||
}
|
||||
// Text color
|
||||
{
|
||||
MPlug plug(obj, transformDrawNode::aTextColor);
|
||||
MObject o = plug.asMObject();
|
||||
MFnNumericData nData(o);
|
||||
nData.getData(tdData->fTextColor.r, tdData->fTextColor.g, tdData->fTextColor.b);
|
||||
}
|
||||
// Transform type
|
||||
{
|
||||
MPlug plug(obj, transformDrawNode::aTransformType);
|
||||
tdData->fTransformType = (transformDrawNode::ETransformType)plug.asInt();
|
||||
}
|
||||
tdData->fPositions.clear();
|
||||
tdData->fVectors.clear();
|
||||
MItDag dagIt(MItDag::kDepthFirst, MFn::kMesh, &status);
|
||||
if (status)
|
||||
{
|
||||
for ( ; !dagIt.isDone(); dagIt.next() )
|
||||
{
|
||||
MDagPath dagPath;
|
||||
status = dagIt.getPath(dagPath);
|
||||
if (!status)
|
||||
{
|
||||
status.perror("MItDag::getPath");
|
||||
continue;
|
||||
}
|
||||
MObject transformNode = dagPath.transform(&status);
|
||||
if (!status)
|
||||
{
|
||||
status.perror("MDagPath::transform");
|
||||
continue;
|
||||
}
|
||||
MFnDagNode transform(transformNode, &status);
|
||||
if (!status)
|
||||
{
|
||||
status.perror("MFnDagNode constructor");
|
||||
continue;
|
||||
}
|
||||
MTransformationMatrix matrix(transform.transformationMatrix());
|
||||
MVector vec = matrix.getTranslation(MSpace::kWorld);
|
||||
tdData->fPositions.append(vec);
|
||||
double tmp[3];
|
||||
MTransformationMatrix::RotationOrder order;
|
||||
switch (tdData->fTransformType)
|
||||
{
|
||||
case transformDrawNode::kRotate:
|
||||
matrix.getRotation(tmp, order);
|
||||
vec = MVector(tmp);
|
||||
break;
|
||||
case transformDrawNode::kScale:
|
||||
matrix.getScale(tmp, MSpace::kWorld);
|
||||
vec = MVector(tmp);
|
||||
break;
|
||||
case transformDrawNode::kShear:
|
||||
matrix.getShear(tmp, MSpace::kWorld);
|
||||
vec = MVector(tmp);
|
||||
break;
|
||||
default:
|
||||
// Don't reset vec so that translation is drawn by default.
|
||||
break;
|
||||
}
|
||||
tdData->fVectors.append(vec);
|
||||
}
|
||||
}
|
||||
return tdData;
|
||||
}
|
||||
void transformDrawOverride::addUIDrawables(
|
||||
const MDagPath& objPath,
|
||||
MUIDrawManager& drawManager,
|
||||
const MFrameContext& frameContext,
|
||||
const MUserData* data)
|
||||
{
|
||||
const transformDrawData* tdData = dynamic_cast<const transformDrawData*>(data);
|
||||
if (!tdData) return;
|
||||
drawManager.beginDrawable();
|
||||
drawManager.setColor(tdData->fTextColor);
|
||||
for (unsigned int i = 0; i < tdData->fVectors.length(); i++)
|
||||
{
|
||||
MPoint pos(tdData->fPositions[i]);
|
||||
MVector vec(tdData->fVectors[i]);
|
||||
char tmpStr[128] = {0};
|
||||
sprintf(tmpStr, "(%.3f, %.3f, %.3f)", vec.x, vec.y, vec.z);
|
||||
MString text(tmpStr);
|
||||
drawManager.text(pos, text, MUIDrawManager::kCenter);
|
||||
}
|
||||
drawManager.endDrawable();
|
||||
}
|
||||
MStatus initializePlugin( MObject obj )
|
||||
{
|
||||
MFnPlugin plugin( obj, PLUGIN_COMPANY, "1.0", "Any" );
|
||||
MStatus status = plugin.registerNode(
|
||||
transformDrawNode::className,
|
||||
transformDrawNode::id,
|
||||
transformDrawNode::creator,
|
||||
transformDrawNode::initialize,
|
||||
MPxNode::kLocatorNode,
|
||||
&transformDrawNode::drawDbClassification);
|
||||
if (!status)
|
||||
{
|
||||
status.perror("registerNode");
|
||||
return status;
|
||||
}
|
||||
status = MDrawRegistry::registerDrawOverrideCreator(
|
||||
transformDrawNode::drawDbClassification,
|
||||
transformDrawNode::drawRegistrantId,
|
||||
transformDrawOverride::creator);
|
||||
if (!status)
|
||||
{
|
||||
status.perror("registerDrawOverrideCreator");
|
||||
return status;
|
||||
}
|
||||
return status;
|
||||
}
|
||||
MStatus uninitializePlugin( MObject obj )
|
||||
{
|
||||
MFnPlugin plugin( obj );
|
||||
MStatus status = MDrawRegistry::deregisterDrawOverrideCreator(
|
||||
transformDrawNode::drawDbClassification,
|
||||
transformDrawNode::drawRegistrantId);
|
||||
if (!status)
|
||||
{
|
||||
status.perror("deregisterDrawOverrideCreator");
|
||||
return status;
|
||||
}
|
||||
status = plugin.deregisterNode(transformDrawNode::id);
|
||||
if (!status)
|
||||
{
|
||||
status.perror("deregisterNode");
|
||||
return status;
|
||||
}
|
||||
return status;
|
||||
}
|
||||
Reference in New Issue
Block a user