footPrintNode_GeometryOverride_AnimatedMaterial/footPrintNode_GeometryOverride_AnimatedMaterial.cpp

footPrintNode_GeometryOverride_AnimatedMaterial/footPrintNode_GeometryOverride_AnimatedMaterial.cpp
//-
// ==========================================================================
// 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.
// ==========================================================================
//+
// DESCRIPTION:
//
// This plug-in demonstrates how to draw a simple mesh like foot Print in an efficient way.
//
// This efficient way is supported in Viewport 2.0.
//
// For comparison, you can also reference a Maya Developer Kit sample named footPrintNode.
// In that sample, we draw the footPrint using the MUIDrawManager primitives in footPrintDrawOverride::addUIDrawables()
//
// For comparison, you can also reference another Maya Developer Kit sample named rawfootPrintNode.
// In that sample, we draw the footPrint with OpenGL\DX in method rawFootPrintDrawOverride::draw().
//
//
// This plugin uses two different techniques to optimize the performance of the foot print draw
// in VP2.
//
// Technique 1:
// In order to know when the footPrintGeometryOverrideAnimatedMaterial geometry needs to be updated the
// footPrint node taps into dirty propagation and the evaluation manager to track when
// attributes which affect geometry change. footPrintGeometryOverrideAnimatedMaterial can then query
// footPrint to find out if a geometry update is necessary.
//
// Technique 2:
// In order to know when the footPrintGeometryOverrideAnimatedMaterial render items need to be updated
// the factors affecting how render items are drawn are stored. When updateRenderItems()
// is called the current values can be compared against the previously used values,
// allowing the bulk of the updateRenderItems() work to only occur when necessary.
//
// Evaluation Caching:
// footPrint is fully compatible with Evaluation Caching. Supporting Evaluation Caching
// does add some additional, subtle requirements on the plug-in node. Evaluation Caching
// automatically stores data for two types of attributes: output attributes and dynamic
// attributes, where output attributes are defined as any attribute which is affected by
// another attribute on the node. The affects relationship can be created either by
// calling MPxNode::attributeAffects() or by returning affected attributes from
// MPxNode::setDependentsDirty().
// Note that as of the time of this comment there is a known issue where calling
// MPxNode::attributeAffects() will not correctly flag an attribute for evaluation
// caching. Creating affects relationships through MPxNode::setDependentsDirty()
// does correctly cause the plug to be cached.
//
// Using Evaluation Caching with Evaluation Manager Parallel Update has an additional issue
// to be aware of. When using Evaluation Manager Parallel Update some MPxGeometryOverride
// methods are called after the corresponding DAG node has been evaluated but before the
// full evaluation graph has been evaluated. When Evaluation Caching is enabled only
// cached DG values may be read before the full evaluation graph has been evaluated. Therefore,
// when using Evaluation Manager Parallel Update and Evaluation Caching only cached DG values
// may be read. Attempting to read an uncached value before evaluation finishes results in
// undefined behavior (incorrect data or crashes).
//
// VP2 Custom Caching:
// footPrintGeometryOverrideAnimatedMaterial is fully compatible with VP2 Custom Caching. When using VP2
// custom caching the MPxGeometryOverride may be invoked in the normal context or in a
// background thread using another context. If playback and background evaluation occur
// concurrently Maya guarantees that all of the MPxGeoemtryOverride methods called for a
// context occur atomically without being interleaved with MPxGeometryOverride methods
// for the same DAG object in a different context.
//
// However, Maya does not make any timing guarantee between the call(s) to evaluate
// or restore values to the MPxNode and calls to the MPxGeometryOverride. For example,
// a postEvaluation call in the background evaluation thread may occur at the some time
// the foreground thread is using the MPxGeometryOverride.
//
// This means that any communication which occurs between the MPxNode during evaluation
// and the MPxGeometryOverride must be context aware. The communication channel must
// use different storage for each context. The easiest way to implement this is to use
// internal attributes on the MPxNode which may be accessed by the MPxGeometryOverride.
//
// Internal attributes are used as the communication channel between footPrint and
// footPrintGeometryOverrideAnimatedMaterial as a part of Technique 1.
//
//
// Example usage is to load the plug-in and create the node using
// the createNode command:
//
// loadPlugin footPrintNode_GeometryOverride_AnimatedMaterial;
// createNode footPrint_GeometryOverride_AnimatedMaterial;
//
#include <maya/MPxLocatorNode.h>
#include <maya/MString.h>
#include <maya/MTypeId.h>
#include <maya/MPlug.h>
#include <maya/MPlugArray.h>
#include <maya/MVector.h>
#include <maya/MDataBlock.h>
#include <maya/MDataHandle.h>
#include <maya/MColor.h>
#include <maya/MFnPlugin.h>
#include <maya/MDistance.h>
#include <maya/MFnUnitAttribute.h>
#include <maya/MFnNumericAttribute.h>
#include <maya/MGlobal.h>
#include <maya/MEvaluationNode.h>
#include <maya/MFnDependencyNode.h>
#include <maya/MDagMessage.h>
// Viewport 2.0 includes
#include <maya/MDrawRegistry.h>
#include <maya/MPxGeometryOverride.h>
#include <maya/MUserData.h>
#include <maya/MDrawContext.h>
#include <maya/MShaderManager.h>
#include <maya/MHWGeometry.h>
#include <maya/MHWGeometryUtilities.h>
#include <maya/MPointArray.h>
#include <unordered_map>
namespace
{
// Foot Data
//
float sole[][3] = { { 0.00f, 0.0f, -0.70f },
{ 0.04f, 0.0f, -0.69f },
{ 0.09f, 0.0f, -0.65f },
{ 0.13f, 0.0f, -0.61f },
{ 0.16f, 0.0f, -0.54f },
{ 0.17f, 0.0f, -0.46f },
{ 0.17f, 0.0f, -0.35f },
{ 0.16f, 0.0f, -0.25f },
{ 0.15f, 0.0f, -0.14f },
{ 0.13f, 0.0f, 0.00f },
{ 0.00f, 0.0f, 0.00f },
{ -0.13f, 0.0f, 0.00f },
{ -0.15f, 0.0f, -0.14f },
{ -0.16f, 0.0f, -0.25f },
{ -0.17f, 0.0f, -0.35f },
{ -0.17f, 0.0f, -0.46f },
{ -0.16f, 0.0f, -0.54f },
{ -0.13f, 0.0f, -0.61f },
{ -0.09f, 0.0f, -0.65f },
{ -0.04f, 0.0f, -0.69f },
{ -0.00f, 0.0f, -0.70f } };
float heel[][3] = { { 0.00f, 0.0f, 0.06f },
{ 0.13f, 0.0f, 0.06f },
{ 0.14f, 0.0f, 0.15f },
{ 0.14f, 0.0f, 0.21f },
{ 0.13f, 0.0f, 0.25f },
{ 0.11f, 0.0f, 0.28f },
{ 0.09f, 0.0f, 0.29f },
{ 0.04f, 0.0f, 0.30f },
{ 0.00f, 0.0f, 0.30f },
{ -0.04f, 0.0f, 0.30f },
{ -0.09f, 0.0f, 0.29f },
{ -0.11f, 0.0f, 0.28f },
{ -0.13f, 0.0f, 0.25f },
{ -0.14f, 0.0f, 0.21f },
{ -0.14f, 0.0f, 0.15f },
{ -0.13f, 0.0f, 0.06f },
{ -0.00f, 0.0f, 0.06f } };
int soleCount = 21;
int heelCount = 17;
// Viewport 2.0 specific data
//
const MString colorParameterName_ = "solidColor";
const MString wireframeItemName_ = "footPrintLocatorWires";
const MString shadedItemName_ = "footPrintLocatorTriangles";
// Maintain a mini cache for 3d solid shaders in order to reuse the shader
// instance whenever possible. This can allow Viewport 2.0 optimization e.g.
// the GPU instancing system and the consolidation system to be leveraged.
//
struct MColorHash
{
std::size_t operator()(const MColor& color) const
{
std::size_t seed = 0;
CombineHashCode(seed, color.r);
CombineHashCode(seed, color.g);
CombineHashCode(seed, color.b);
CombineHashCode(seed, color.a);
return seed;
}
void CombineHashCode(std::size_t& seed, float v) const
{
std::hash<float> hasher;
seed ^= hasher(v) + 0x9e3779b9 + (seed << 6) + (seed >> 2);
}
};
std::unordered_map<MColor, MHWRender::MShaderInstance*, MColorHash> the3dSolidShaders;
MHWRender::MShaderInstance* get3dSolidShader(const MColor& color)
{
// Return the shader instance if exists.
auto it = the3dSolidShaders.find(color);
if (it != the3dSolidShaders.end())
{
return it->second;
}
if (renderer)
{
const MHWRender::MShaderManager* shaderMgr = renderer->getShaderManager();
if (shaderMgr)
{
}
}
if (shader)
{
float solidColor[] = { color.r, color.g, color.b, 1.0f };
shader->setParameter(colorParameterName_, solidColor);
the3dSolidShaders[color] = shader;
}
return shader;
}
MStatus releaseShaders()
{
if (renderer)
{
const MHWRender::MShaderManager* shaderMgr = renderer->getShaderManager();
if (shaderMgr)
{
for (auto it = the3dSolidShaders.begin(); it != the3dSolidShaders.end(); it++)
{
shaderMgr->releaseShader(it->second);
}
the3dSolidShaders.clear();
return MS::kSuccess;
}
}
return MS::kFailure;
}
// Technique 2: Store per-instance draw information (such as if a given instance is
// selected). Set up a data structure to hold this information.
//
// VP2 Custom Caching: This information does not need to be stored in context aware storage
// because this information is only used in requiresUpdateRenderItems() and
// updateRenderItems() and those methods are not invoked from the background thread
// during for VP2 Custom Caching.
struct VP2InstanceDrawInfo
{
VP2InstanceDrawInfo() : mDisplayStatus(MHWRender::DisplayStatus::kNoStatus) {}
MHWRender::DisplayStatus mDisplayStatus;
MColor mDisplayColor;
};
// Technique 2: Use a map to store the instance data rather than a vector because the
// MDagPath::instanceNumber() is not necessarily monotonically increasing and starting
// at 0.
typedef std::unordered_map<unsigned int, VP2InstanceDrawInfo*> VP2InstancesDrawInfo;
struct VP2DrawInfo
{
VP2DrawInfo() : mCallbackInitialized(false) {}
VP2InstancesDrawInfo mInstanceInfo;
MCallbackId mInstanceAddedCallbackId;
MCallbackId mInstanceRemovedCallbackId;
bool mCallbackInitialized;
};
} // anonymous namespace
//---------------------------------------------------------------------------
//---------------------------------------------------------------------------
// Node implementation with standard viewport draw
//---------------------------------------------------------------------------
//---------------------------------------------------------------------------
class footPrint : public MPxLocatorNode
{
public:
footPrint();
~footPrint() override;
MStatus compute( const MPlug& plug, MDataBlock& data ) override;
bool isBounded() const override;
MBoundingBox boundingBox() const override;
MStatus setDependentsDirty(const MPlug& plug, MPlugArray& plugArray) override;
MStatus postEvaluation(const MDGContext& context, const MEvaluationNode& evaluationNode, PostEvaluationType evalType) override;
static void * creator();
static MStatus initialize();
/*
Evaluation Caching: For evaluation caching to automatically cache an attribute
that attribute must be affected by another attribute on the node. In this case
size must be cached by evaluation caching because size will be read in
footPrintGeometryOverrideAnimatedMaterial. Therefore, create an inputSize attribute which
affects size.
*/
static MObject inputSize;
static MObject size; // The size of the foot
/*
Animated Material: Create an inputColor and color attribute to control how the
footprint node draws.
*/
static MObject inputColor;
static MObject solidColor;
/*
Technique 1: Use an internal attribute to store if any attribute which affects
the geometry created by footPrintGeometryOverrideAnimatedMaterial has changed since the last time
footPrintGeometryOverrideAnimatedMaterial was executed. Storing this information here is
breaking our abstraction. footPrint has to know about details of how
footPrintGeometryOverrideAnimatedMaterial works that it really shouldn't know.
However, attributes are stored in the MDataBlock and the MDataBlock is context
aware storage, so internal attributes are a good way to communicate between the
MPxNode and the MPxGeometryOverride which is safe to use with VP2 Custom Caching.
*/
static MObject sizeChangedSinceVP2Update;
static MObject colorChangedSinceVP2Update;
void setSizeChangedSinceVP2Update(bool sizeChanged);
bool getSizeChangedSinceVP2Update();
void setColorChangedSinceVP2Update(bool colorChanged);
bool getColorChangedSinceVP2Update();
friend class footPrintGeometryOverrideAnimatedMaterial;
public:
static MTypeId id;
static MString drawDbClassification;
static MString drawRegistrantId;
};
class footPrintGeometryOverrideAnimatedMaterial : public MHWRender::MPxGeometryOverride
{
public:
static MHWRender::MPxGeometryOverride* Creator(const MObject& obj)
{
return new footPrintGeometryOverrideAnimatedMaterial(obj);
}
~footPrintGeometryOverrideAnimatedMaterial() override;
bool hasUIDrawables() const override { return false; }
bool requiresUpdateRenderItems(const MDagPath& path) const override;
bool supportsVP2CustomCaching() const override;
bool requiresGeometryUpdate() const override;
void updateDG() override;
bool isIndexingDirty(const MHWRender::MRenderItem &item) override { return false; }
bool isStreamDirty(const MHWRender::MVertexBufferDescriptor &desc) override { return mFootPrintNode->getSizeChangedSinceVP2Update(); }
void updateRenderItems(const MDagPath &path, MHWRender::MRenderItemList& list) override;
void populateGeometry(const MHWRender::MGeometryRequirements &requirements, const MHWRender::MRenderItemList &renderItems, MHWRender::MGeometry &data) override;
void cleanUp() override {};
/*
Tracing will look something like the following when in shaded mode:
footPrintGeometryOverrideAnimatedMaterial: Geometry override DG update: footPrint1
footPrintGeometryOverrideAnimatedMaterial: Start geometry override render item update: |transform1|footPrint1
footPrintGeometryOverrideAnimatedMaterial: - Call API to update render items
footPrintGeometryOverrideAnimatedMaterial: End geometry override render item update: |transform1|footPrint1
footPrintGeometryOverrideAnimatedMaterial: Start geometry override update stream and indexing data: footPrint1
footPrintGeometryOverrideAnimatedMaterial: - Update render item: soleLocatorTriangles
footPrintGeometryOverrideAnimatedMaterial: - Update render item: heelLocatorTriangles
footPrintGeometryOverrideAnimatedMaterial: End geometry override stream and indexing data: footPrint1
footPrintGeometryOverrideAnimatedMaterial: End geometry override clean up: footPrint1
at creation time.
footPrintGeometryOverrideAnimatedMaterial: Geometry override DG update: footPrint1
footPrintGeometryOverrideAnimatedMaterial: Start geometry override render item update: |transform1|footPrint1
footPrintGeometryOverrideAnimatedMaterial: - Call API to update render items
footPrintGeometryOverrideAnimatedMaterial: End geometry override render item update: |transform1|footPrint1
footPrintGeometryOverrideAnimatedMaterial: End geometry override clean up: footPrint1
on selection change.
footPrintGeometryOverrideAnimatedMaterial: Geometry override DG update: footPrint1
footPrintGeometryOverrideAnimatedMaterial: Start geometry override render item update: |transform1|footPrint1
footPrintGeometryOverrideAnimatedMaterial: - Call API to update render items
footPrintGeometryOverrideAnimatedMaterial: End geometry override render item update: |transform1|footPrint1
footPrintGeometryOverrideAnimatedMaterial: Geometry override dirty stream check: footPrint1
footPrintGeometryOverrideAnimatedMaterial: Start geometry override update stream and indexing data: footPrint1
footPrintGeometryOverrideAnimatedMaterial: End geometry override stream and indexing data: footPrint1
footPrintGeometryOverrideAnimatedMaterial: End geometry override clean up: footPrint1
for footprint size change.
This is based on the existing stream and indexing dirty flags being used
which attempts to minimize the amount of render item, vertex buffer and indexing update.
*/
bool traceCallSequence() const override
{
// Return true if internal tracing is desired.
return false;
}
void handleTraceMessage(const MString &message) const override
{
MGlobal::displayInfo("footPrintGeometryOverrideAnimatedMaterial: " + message);
// Some simple custom message formatting.
fputs("footPrintGeometryOverrideAnimatedMaterial: ", stderr);
fputs(message.asChar(), stderr);
fputs("\n", stderr);
}
static void instancingChangedCallback(MDagPath& child, MDagPath& parent, void* clientData);
private:
footPrintGeometryOverrideAnimatedMaterial(const MObject& obj);
void clearInstanceInfo();
friend class footPrint;
MObject mLocatorNode;
float mMultiplier;
MFloatVector mSolidColor;
// Technique 1: footPrint tracks when any attributes which affect the geometry change.
// This information may be accessed at any time, so store a pointer to the associated
// DAG node.
footPrint* mFootPrintNode;
// Technique 2: storage for last used values to track if render items need to update.
VP2DrawInfo mVP2DrawInfo;
};
MObject footPrint::size;
MObject footPrint::inputSize;
MObject footPrint::inputColor;
MObject footPrint::solidColor;
MObject footPrint::sizeChangedSinceVP2Update;
MObject footPrint::colorChangedSinceVP2Update;
MTypeId footPrint::id( 0x00080033 );
MString footPrint::drawDbClassification("drawdb/geometry/light/footPrint_GeometryOverride_AnimatedMaterial");
static bool sMakeFootPrintDirLight = (getenv("MAYA_FOOTPRINT_GEOMETRY_OVERRIDE_AS_DIRLIGHT") != NULL);
static MString lightClassification("light:drawdb/geometry/light/footPrint_GeometryOverride_AnimatedMaterial:drawdb/light/directionalLight");
MString footPrint::drawRegistrantId("footPrintNode_GeometryOverride_AnimatedMaterialPlugin");
footPrint::footPrint() {}
footPrint::~footPrint() {}
/*
Technique 1: Use setDependentsDirty to tap into Maya's dirty propagation to track when
the size plug changes so that footPrintGeometryOverrideAnimatedMaterial can find out if it needs to
update geometry.
Evaluation Caching: in order for evaluation caching to automatically cache a plug's value
that plug must either be an output attribute or a dynamic attribute. For a plug to be an
output attribute it must be affected by at least one other plug on the node. Create that
affects relationship here. attributeAffects() has a known defect which is not correctly
setting up the affected relationship at this time.
Warning: any time you implement setDependentsDirty you probably also need to implement
something similar in preEvaluation() or postEvaluation() so the code works correctly
with Evaluation Manager enabled.
*/
MStatus footPrint::setDependentsDirty(const MPlug& plug, MPlugArray& plugArray)
{
if (plug.partialName() == "isz")
{
MObject thisNode = thisMObject();
MPlug sizePlug(thisNode, size);
plugArray.append(sizePlug);
setSizeChangedSinceVP2Update(true);
}
MStatus status;
MPlug parentPlug = plug.parent(&status);
if (plug.partialName() == "ic" || (!parentPlug.isNull() && parentPlug.partialName() == "ic"))
{
MObject thisNode = thisMObject();
MPlug colorPlug(thisNode, solidColor);
plugArray.append(colorPlug);
setColorChangedSinceVP2Update(true);
}
}
MStatus footPrint::compute( const MPlug& plug, MDataBlock& data )
{
if (plug.partialName() == "sz")
{
/*
Evaluation Caching: while this is quite convoluted it is necessary for evaluation
caching to work correctly. My two plugs inputSize and size have the exact same
value. The only reason inputSize exist is for it to affect size.
*/
MStatus status;
MDataHandle inputSizeHandle = data.inputValue(inputSize, &status);
if (status != MStatus::kSuccess) return status;
MDataHandle sizeHandle = data.outputValue(size, &status);
if (status != MStatus::kSuccess) return status;
sizeHandle.copy(inputSizeHandle);
}
else if (plug.partialName() == "sc")
{
MStatus status;
MDataHandle inputColorHandle = data.inputValue(inputColor, &status);
if (status != MStatus::kSuccess) return status;
MDataHandle colorHandle = data.outputValue(solidColor, &status);
if (status != MStatus::kSuccess) return status;
colorHandle.copy(inputColorHandle);
}
else
{
return MS::kUnknownParameter;
}
}
bool footPrint::isBounded() const
{
return true;
}
MBoundingBox footPrint::boundingBox() const
{
// Get the size
//
MObject thisNode = thisMObject();
MPlug plug( thisNode, size );
MDistance sizeVal;
plug.getValue( sizeVal );
double multiplier = sizeVal.asCentimeters();
MPoint corner1( -0.17, 0.0, -0.7 );
MPoint corner2( 0.17, 0.0, 0.3 );
corner1 = corner1 * multiplier;
corner2 = corner2 * multiplier;
return MBoundingBox( corner1, corner2 );
}
MSelectionMask footPrint::getShapeSelectionMask() const
{
return MSelectionMask("footPrintSelection");
}
/*
Technique 1: Use postEvaluation to tap into Evaluation Manager dirty information
to track when the size plug changes so that FootPrintNodeGeometryOverride can
find out if it needs to update geometry.
Evaluation Caching: It is critical for Evaluation Caching that the EM dirty information
is accessed from postEvaluation rather than preEvaluation. During Evaluation
Caching restore (or VP2 Custom Caching restore) preEvaluation will not be called,
causing the sizeChangedSinceVP2Update flag to be set incorrectly and preventing VP2
from updating to use the new data restored from the cache.
preEvaluation should be used to prepare for calls to MPxNode::compute().
postEvaluation should be used to clean up calls to MPxNode::compute() AND notify consumers of the data (VP2) that new data is ready.
Warning: any time you implement preEvaluation or postEvaluation and use dirtyPlugExists
you probably also need to implement something similar in setDependentsDirty() so
the code works correctly without Evaluation Manager.
*/
MStatus footPrint::postEvaluation(const MDGContext& context, const MEvaluationNode& evaluationNode, PostEvaluationType evalType)
{
MStatus status;
if (evaluationNode.dirtyPlugExists(size, &status) && status)
{
setSizeChangedSinceVP2Update(true);
}
if (evaluationNode.dirtyPlugExists(solidColor, &status) && status)
{
setColorChangedSinceVP2Update(true);
}
}
void footPrint::setSizeChangedSinceVP2Update(bool sizeChanged)
{
/*
Calling forceCache here should be fast. Possible calling sites are:
- setDependentsDirty() -> the normal context is current.
- preparing the draw in VP2 -> the normal context is current.
- background evaluation postEvaluation() -> datablock for background context already exists.
- background evaluation for VP2 Custom Caching -> datablock for background context already exists.
*/
MDataBlock block = forceCache();
MDataHandle sizeChangedSinceVP2UpdateHandle = block.outputValue(sizeChangedSinceVP2Update);
sizeChangedSinceVP2UpdateHandle.setBool(sizeChanged);
}
void footPrint::setColorChangedSinceVP2Update(bool colorChanged)
{
MDataBlock block = forceCache();
MDataHandle colorChangedSinceVP2UpdateHandle = block.outputValue(colorChangedSinceVP2Update);
colorChangedSinceVP2UpdateHandle.setBool(colorChanged);
}
bool footPrint::getSizeChangedSinceVP2Update()
{
MDataBlock block = forceCache();
MDataHandle sizeChangedSinceVP2UpdateHandle = block.outputValue(sizeChangedSinceVP2Update);
return sizeChangedSinceVP2UpdateHandle.asBool();
}
bool footPrint::getColorChangedSinceVP2Update()
{
MDataBlock block = forceCache();
MDataHandle colorChangedSinceVP2UpdateHandle = block.outputValue(colorChangedSinceVP2Update);
return colorChangedSinceVP2UpdateHandle.asBool();
}
void* footPrint::creator()
{
return new footPrint();
}
//---------------------------------------------------------------------------
//---------------------------------------------------------------------------
// Viewport 2.0 override implementation
//---------------------------------------------------------------------------
//---------------------------------------------------------------------------
footPrintGeometryOverrideAnimatedMaterial::footPrintGeometryOverrideAnimatedMaterial(const MObject& obj)
: MHWRender::MPxGeometryOverride(obj)
, mLocatorNode(obj)
, mMultiplier(0.0f)
, mFootPrintNode(nullptr)
{
MStatus returnStatus;
MFnDependencyNode dependNode(mLocatorNode, &returnStatus);
if (returnStatus != MStatus::kSuccess) return;
MPxNode* footPrintNode = dependNode.userNode(&returnStatus);
if (returnStatus != MStatus::kSuccess) footPrintNode = nullptr;
mFootPrintNode = dynamic_cast<footPrint*>(footPrintNode);
}
footPrintGeometryOverrideAnimatedMaterial::~footPrintGeometryOverrideAnimatedMaterial()
{
// Technique 2: Clean up the memory allocated to store the per instance information.
clearInstanceInfo();
// Technique 2: Remove the callbacks added to track instancing changed messages.
MMessage::removeCallback(mVP2DrawInfo.mInstanceAddedCallbackId);
MMessage::removeCallback(mVP2DrawInfo.mInstanceRemovedCallbackId);
}
void footPrintGeometryOverrideAnimatedMaterial::clearInstanceInfo()
{
VP2InstancesDrawInfo& instanceInfo = mVP2DrawInfo.mInstanceInfo;
for (auto it = instanceInfo.begin(); it != instanceInfo.end(); it++)
{
delete it->second;
}
instanceInfo.clear();
}
MHWRender::DrawAPI footPrintGeometryOverrideAnimatedMaterial::supportedDrawAPIs() const
{
// this plugin supports both GL and DX
}
void footPrintGeometryOverrideAnimatedMaterial::updateDG()
{
// Technique 1: Only update mMultiplier when the footPrint node has signaled that mMultiplier has
// changed. In this trivial example it is possible to get the current value and compare it
// to what is stored in mMultiplier. In a real use case the value may be a poly
// mesh with a million vertices, which would make such a comparison terrible for performance.
if (mFootPrintNode->getSizeChangedSinceVP2Update())
{
MPlug plug(mLocatorNode, footPrint::size);
if (!plug.isNull())
{
MDistance sizeVal;
if (plug.getValue(sizeVal))
{
mMultiplier = (float)sizeVal.asCentimeters();
}
}
}
if (mFootPrintNode->getColorChangedSinceVP2Update())
{
MDataBlock datablock = mFootPrintNode->forceCache();
mSolidColor = datablock.inputValue(footPrint::solidColor).asFloatVector();
}
}
void footPrintGeometryOverrideAnimatedMaterial::instancingChangedCallback(MDagPath& child, MDagPath& parent, void* clientData)
{
footPrintGeometryOverrideAnimatedMaterial* geometryOverride = reinterpret_cast<footPrintGeometryOverrideAnimatedMaterial*>(clientData);
// Technique 2: Understanding the relationship between the list of old instances and the new instances
// is very challenging. Rather than writing some complex code to handle it, destroy all our per-instance
// information. This means updateRenderItems must be expected for all instances which may be slow.
// Typically instancing changes are interactive changes triggered by the user, so this won't impact
// playback performance of the node.
//
// If your plug-in is using a lot of DAG instancing and you need high performance you should consider using
// MPxSubSceneOverride instead of MPxGeometryOverride. MPxSubSceneOverride gives you the flexibility to make
// the workflows which are important to you fast.
geometryOverride->clearInstanceInfo();
}
bool footPrintGeometryOverrideAnimatedMaterial::requiresUpdateRenderItems(const MDagPath& path) const
{
// Technique 2: If the display status and display color have not changed, then don't do any
// render item updates. The first time updateRenderItems() is called the items must be added.
// Initialize mDisplayStatus to kNoStatus to ensure this occurs. The current display status
// can never be kNoStatus.
MStatus returnStatus;
auto instanceDrawInfoIter = mVP2DrawInfo.mInstanceInfo.find(path.instanceNumber(&returnStatus));
if (returnStatus == MStatus::kFailure || (mVP2DrawInfo.mInstanceInfo.end() == instanceDrawInfoIter)) return true;
const VP2InstanceDrawInfo* instanceDrawInfo = instanceDrawInfoIter->second;
// We don't check the wireframeColor itself here deliberately to work around a color management
// defect. Normally we'd want to check the color directly because that would handle things like
// changes to the current color palette. However, this method is called to check if Evaluation
// Manager Parallel Update is supported and at the time of that call color management doesn't
// know how to color correct the wireframe color. This means that the stored color (recoded
// color managed) won't match the queried color (evaluated without color management) and we'll
// never direct update.
if (currentDisplayStatus != instanceDrawInfo->mDisplayStatus) return true;
if (mFootPrintNode->getColorChangedSinceVP2Update()) return true;
return false;
}
bool footPrintGeometryOverrideAnimatedMaterial::supportsEvaluationManagerParallelUpdate() const
{
return true;
}
bool footPrintGeometryOverrideAnimatedMaterial::supportsVP2CustomCaching() const
{
return true;
}
bool footPrintGeometryOverrideAnimatedMaterial::requiresGeometryUpdate() const
{
return mFootPrintNode->getSizeChangedSinceVP2Update();
}
void footPrintGeometryOverrideAnimatedMaterial::updateRenderItems( const MDagPath& path, MHWRender::MRenderItemList& list )
{
// There should always be an instanceDrawInfo for path, because requiresUpdateRenderItems()
// was called immediately before updateRenderItems() and requiresUpdateRenderItems() adds it.
MStatus returnStatus;
VP2InstanceDrawInfo* instanceDrawInfo = mVP2DrawInfo.mInstanceInfo[path.instanceNumber(&returnStatus)];
if (returnStatus == MStatus::kFailure) return;
if (!instanceDrawInfo)
{
instanceDrawInfo = new VP2InstanceDrawInfo;
mVP2DrawInfo.mInstanceInfo[path.instanceNumber(&returnStatus)] = instanceDrawInfo;
if (returnStatus == MStatus::kFailure) return;
// Technique 2: Store information about each instance of the footPrint node. If
// instances are added or removed then update our per-instance information.
if (!mVP2DrawInfo.mCallbackInitialized)
{
mVP2DrawInfo.mCallbackInitialized = true;
MDagPath& nonConstPath = const_cast<MDagPath&>(path);
mVP2DrawInfo.mInstanceAddedCallbackId = MDagMessage::addInstanceAddedDagPathCallback(nonConstPath, &footPrintGeometryOverrideAnimatedMaterial::instancingChangedCallback, this, &returnStatus);
if (returnStatus == MStatus::kFailure) return;
mVP2DrawInfo.mInstanceRemovedCallbackId = MDagMessage::addInstanceRemovedDagPathCallback(nonConstPath, &footPrintGeometryOverrideAnimatedMaterial::instancingChangedCallback, this, &returnStatus);
if (returnStatus == MStatus::kFailure) return;
}
}
// Technique 2: updateRenderItems() is going to be called so record the values that
// affect which render items are drawn to avoid extracting the values from Maya twice.
instanceDrawInfo->mDisplayStatus = displayStatus;
instanceDrawInfo->mDisplayColor = displayColor;
MHWRender::MShaderInstance* wireframeShader = get3dSolidShader(displayColor);
if (!wireframeShader) return;
unsigned int depthPriority;
switch (displayStatus)
{
break;
default:
break;
}
MHWRender::MRenderItem* wireframeItem = NULL;
int index = list.indexOf(wireframeItemName_);
if (index < 0)
{
wireframeItemName_,
list.append(wireframeItem);
}
else
{
wireframeItem = list.itemAt(index);
}
if(wireframeItem)
{
wireframeItem->setShader(wireframeShader);
wireframeItem->depthPriority(depthPriority);
wireframeItem->enable(true);
}
MHWRender::MRenderItem* shadedItem = NULL;
index = list.indexOf(shadedItemName_);
if (index < 0)
{
shadedItemName_,
list.append(shadedItem);
}
else
{
shadedItem = list.itemAt(index);
}
if(shadedItem)
{
// Animated Material: If the shadedItem already has an MShaderInstance then update the color.
// Otherwise, create the MShaderInstance and set the color we want.
MHWRender::MShaderInstance* solidShader = shadedItem->getShader();
if (!solidShader)
{
// Don't use get3dSolidShader! That gets a shader from our cache of pre-existing shaders.
// That cache is shared among all footPrintNode_GeometryOverride_AnimatedMaterial nodes, so
// those shaders cannot change color without potentially changing the color of another
// footPrint.
if (renderer)
{
const MHWRender::MShaderManager* shaderMgr = renderer->getShaderManager();
if (shaderMgr)
{
}
}
if (!solidShader) return;
shadedItem->setShader(solidShader);
}
// If the object is selected draw it in the active color
{
float activeColor[] = { displayColor.r, displayColor.g, displayColor.b, 1.0f };
solidShader->setParameter(colorParameterName_, activeColor);
}
// When the object is dormant, draw it the color in the solidColor attribute
else
{
float solidColor[] = { mSolidColor[0], mSolidColor[1], mSolidColor[2], 1.0f };
solidShader->setParameter(colorParameterName_, solidColor);
}
shadedItem->depthPriority(depthPriority);
shadedItem->enable(true);
}
mFootPrintNode->setColorChangedSinceVP2Update(false);
}
void footPrintGeometryOverrideAnimatedMaterial::populateGeometry(
const MHWRender::MGeometryRequirements& requirements,
const MHWRender::MRenderItemList& renderItems,
{
MHWRender::MVertexBuffer* verticesBuffer = NULL;
float* vertices = NULL;
const MHWRender::MVertexBufferDescriptorList& vertexBufferDescriptorList = requirements.vertexRequirements();
const int numberOfVertexRequirments = vertexBufferDescriptorList.length();
MHWRender::MVertexBufferDescriptor vertexBufferDescriptor;
for (int requirmentNumber = 0; requirmentNumber < numberOfVertexRequirments; ++requirmentNumber)
{
if (!vertexBufferDescriptorList.getDescriptor(requirmentNumber, vertexBufferDescriptor))
{
continue;
}
switch (vertexBufferDescriptor.semantic())
{
{
if (!verticesBuffer)
{
verticesBuffer = data.createVertexBuffer(vertexBufferDescriptor);
if (verticesBuffer)
{
vertices = (float*)verticesBuffer->acquire(soleCount+heelCount, false);
}
}
}
break;
default:
// do nothing for stuff we don't understand
break;
}
}
int verticesPointerOffset = 0;
// We concatenate the heel and sole positions into a single vertex buffer.
// The index buffers will decide which positions will be selected for each render items.
for (int currentVertex = 0 ; currentVertex < soleCount+heelCount; ++currentVertex)
{
if(vertices)
{
if (currentVertex < heelCount)
{
int heelVtx = currentVertex;
vertices[verticesPointerOffset++] = heel[heelVtx][0] * mMultiplier;
vertices[verticesPointerOffset++] = heel[heelVtx][1] * mMultiplier;
vertices[verticesPointerOffset++] = heel[heelVtx][2] * mMultiplier;
}
else
{
int soleVtx = currentVertex - heelCount;
vertices[verticesPointerOffset++] = sole[soleVtx][0] * mMultiplier;
vertices[verticesPointerOffset++] = sole[soleVtx][1] * mMultiplier;
vertices[verticesPointerOffset++] = sole[soleVtx][2] * mMultiplier;
}
}
}
if(verticesBuffer && vertices)
{
verticesBuffer->commit(vertices);
}
for (int i=0; i < renderItems.length(); ++i)
{
const MHWRender::MRenderItem* item = renderItems.itemAt(i);
if (!item)
{
continue;
}
if (item->name() == wireframeItemName_ )
{
int primitiveIndex = 0;
int startIndex = 0;
int numPrimitive = heelCount + soleCount - 2;
int numIndex = numPrimitive * 2;
unsigned int* indices = (unsigned int*)indexBuffer->acquire(numIndex);
for (int i = 0; i < numIndex; )
{
if (i < (heelCount - 1) * 2)
{
startIndex = 0;
primitiveIndex = i / 2;
}
else
{
startIndex = heelCount;
primitiveIndex = i / 2 - heelCount + 1;
}
indices[i++] = startIndex + primitiveIndex;
indices[i++] = startIndex + primitiveIndex + 1;
}
indexBuffer->commit(indices);
}
else if (item->name() == shadedItemName_ )
{
int primitiveIndex = 0;
int startIndex = 0;
int numPrimitive = heelCount + soleCount - 4;
int numIndex = numPrimitive * 3;
unsigned int* indices = (unsigned int*)indexBuffer->acquire(numIndex);
for (int i = 0; i < numIndex; )
{
if (i < (heelCount - 2) * 3)
{
startIndex = 0;
primitiveIndex = i / 3;
}
else
{
startIndex = heelCount;
primitiveIndex = i / 3 - heelCount + 2;
}
indices[i++] = startIndex;
indices[i++] = startIndex + primitiveIndex + 1;
indices[i++] = startIndex + primitiveIndex + 2;
}
indexBuffer->commit(indices);
}
item->associateWithIndexBuffer(indexBuffer);
}
// Technique 1: Now that the current geometry reflects the current value of the size
// attribute, clear the signal flag.
mFootPrintNode->setSizeChangedSinceVP2Update(false);
}
//---------------------------------------------------------------------------
//---------------------------------------------------------------------------
// Plugin Registration
//---------------------------------------------------------------------------
//---------------------------------------------------------------------------
MStatus footPrint::initialize()
{
MStatus stat;
size = unitFn.create( "size", "sz", MFnUnitAttribute::kDistance );
unitFn.setDefault( 1.0 );
/*
Evaluation Caching: marking the attribute as "not writable" implies that it
is an output attribute, but this is not sufficient for Evaluation Caching to
store the attribute's value. The attribute must be affected by another
attribute on the node for Evaluation Caching to store the value.
*/
unitFn.setWritable(false);
stat = addAttribute(size);
if (!stat) {
stat.perror("addAttribute");
return stat;
}
inputSize = unitFn.create("inputSize", "isz", MFnUnitAttribute::kDistance);
unitFn.setDefault(1.0);
stat = addAttribute(inputSize);
if (!stat) {
stat.perror("addAttribute");
return stat;
}
/*
Evaluation Caching: calling attributeAffects() here should cause the size attribute
to be cached by evaluation caching. However, due to a known defect this doesn't
currently work. footPrint works around this issue using the code in setDependentsDirty().
*/
attributeAffects(inputSize, size);
/*
VP2 Custom Caching: When using VP2 custom caching the MPxGeometryOverride associated
with the footPrint node might be invoked in the normal context or in the background
context. If playback and background evaluation occur concurrently Maya guarantees
that all of the MPxGeometryOverride methods called for a context occur atomically
without being interleaved with MPxGeometryOverride methods for the same object in
a different context.
However, Maya does not make any timing guarantee between the call(s) to evaluate the
MPxNode and calls to the MPxGeometryOverride. For example, a postEvaluation call
in the background evaluation thread may occur at the same time that the foreground
thread is using the MPxGeometryOverride.
This means that any communication which occurs between the MPxNode during evaluation
and the MPxGeometryOverride must be context aware. The communication channel must
use different storage for each context. The easiest way to implement this to use
internal attributes on the MPxNode that the MPxGeometryOverride has access to.
footPrint uses this technique here. Don't create any affects relationships because
sizeChangedSinceVP2Update doesn't use any Maya dirty management or evaluation. Only
access sizeChangedSinceVP2Update by getting the MDataHandle directly from the
MDataBlock using outputValue().
*/
sizeChangedSinceVP2Update = attrFn.create("sizeChangedSinceVP2Update", "sd", MFnNumericData::kBoolean, true);
attrFn.setStorable(false);
attrFn.setHidden(true);
attrFn.setConnectable(false);
stat = addAttribute(sizeChangedSinceVP2Update);
if (!stat) {
stat.perror("addAttribute");
return stat;
}
/*
Animated Material: Add a color attribute to the object. Add an input color attribute
so caching knows color is an output attribute.
*/
inputColor = nAttr.createColor("inputColor", "ic");
nAttr.setStorable(true);
nAttr.setKeyable(true);
nAttr.setDefault(0.6f, 0.6f, 0.6f);
stat = addAttribute(inputColor);
if (!stat) {
stat.perror("addAttribute");
return stat;
}
solidColor = nAttr.createColor("solidColor", "sc", &stat);
if (!stat) {
stat.perror("addAttribute");
return stat;
}
nAttr.setStorable(false);
nAttr.setKeyable(false);
nAttr.setReadable(true);
nAttr.setWritable(false);
nAttr.setDefault(0.6f, 0.6f, 0.6f);
stat = addAttribute(solidColor);
if (!stat) {
stat.perror("addAttribute");
return stat;
}
attributeAffects(inputColor, solidColor);
colorChangedSinceVP2Update = attrFn.create("colorChangedSinceVP2Update", "cd", MFnNumericData::kBoolean, true);
attrFn.setStorable(false);
attrFn.setHidden(true);
attrFn.setConnectable(false);
stat = addAttribute(colorChangedSinceVP2Update);
if (!stat) {
stat.perror("addAttribute");
return stat;
}
return MS::kSuccess;
}
MStatus initializePlugin( MObject obj )
{
MStatus status;
MFnPlugin plugin( obj, PLUGIN_COMPANY, "3.0", "Any");
status = plugin.registerNode(
"footPrint_GeometryOverride_AnimatedMaterial",
footPrint::id,
&footPrint::creator,
&footPrint::initialize,
(sMakeFootPrintDirLight ? &lightClassification : &footPrint::drawDbClassification));
if (!status) {
status.perror("registerNode");
return status;
}
footPrint::drawDbClassification,
footPrint::drawRegistrantId,
footPrintGeometryOverrideAnimatedMaterial::Creator);
if (!status) {
status.perror("registerDrawOverrideCreator");
return status;
}
// Register a custom selection mask with priority 2 (same as locators
// by default).
MSelectionMask::registerSelectionType("footPrintSelection", 2);
status = MGlobal::executeCommand("selectType -byName \"footPrintSelection\" 1");
return status;
}
MStatus uninitializePlugin( MObject obj)
{
MStatus status;
MFnPlugin plugin( obj );
footPrint::drawDbClassification,
footPrint::drawRegistrantId);
if (!status) {
status.perror("deregisterDrawOverrideCreator");
return status;
}
status = releaseShaders();
if (!status) {
status.perror("releaseShaders");
return status;
}
status = plugin.deregisterNode( footPrint::id );
if (!status) {
status.perror("deregisterNode");
return status;
}
// Deregister custom selection mask
return status;
}