diff --git a/CMakeLists.txt b/CMakeLists.txt index 25ff7b7..d4af44d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -16,6 +16,7 @@ find_package(Maya REQUIRED) find_package(OpenGL REQUIRED) set(SOURCE_FILES + "src/drawOverride.h" "src/twistSpline.h" "src/twistSpline_maya.h" "src/twistSplineData.h" @@ -26,6 +27,7 @@ set(SOURCE_FILES "src/twistTangentNode.h" "src/pluginMain.cpp" + "src/drawOverride.cpp" "src/twistSplineData.cpp" "src/twistSplineNode.cpp" "src/riderConstraint.cpp" @@ -33,7 +35,7 @@ set(SOURCE_FILES ) add_library(${PROJECT_NAME} SHARED ${SOURCE_FILES}) -target_include_directories(${PROJECT_NAME} +target_include_directories(${PROJECT_NAME} PRIVATE Maya::Maya PUBLIC "${CMAKE_CURRENT_BINARY_DIR}" "${CMAKE_CURRENT_SOURCE_DIR}" ) diff --git a/src/drawOverride.cpp b/src/drawOverride.cpp new file mode 100644 index 0000000..bc6971b --- /dev/null +++ b/src/drawOverride.cpp @@ -0,0 +1,185 @@ +#include "drawOverride.h" + +TwistSplineGeometryOverride::TwistSplineGeometryOverride(const MObject& obj) + : MPxGeometryOverride(obj), mTwistSplineNode(nullptr) { + MStatus status; + MFnDependencyNode dependNode(obj, &status); + if (status != MStatus::kSuccess) return; + + MPxNode* twistSplineNode = dependNode.userNode(&status); + if (status != MStatus::kSuccess) twistSplineNode = nullptr; + + mTwistSplineNode = dynamic_cast(twistSplineNode); +} + +TwistSplineGeometryOverride::~TwistSplineGeometryOverride() {} + +void TwistSplineGeometryOverride::updateDG() { + /* + Pull (evaluate) all attributes in TwistSplineNodeNode node we will be + using. Here is the list of attributes we are pulling : + - geometryChanging: Needed by requiresGeometryUpdate() to check if we + needs to update the vertex and index buffer; + - debugDisplay: Needed by addUIDrawables() to draw debug mode; + - debugScale: Needed by addUIDrawables() to draw debug mode scale; + - outputSpline: Needed by addUIDrawables() to draw the main spline; + + It is very important that all the attributes pulled from this method are + cached (with Technique 1) otherwise Evaluation Cache will not work. In + fact, this method is not needed by EM Evaluation modes. + */ + mTwistSplineNode->updateRenderAttributes(); +} + +bool TwistSplineGeometryOverride::requiresUpdateRenderItems( + const MDagPath& path) const { + /* + Override this function if you need a more complicated animated-material + behavior For example, you will need to change this to `return true` if + object's associated shader is changing at every frame. (This could be + very expensive). + + Note 1: this method have to return 'false' in most case, otherwise + VP2 caching may not work. + + Note 2: for rendering simple animated-material like animated-color or + animated-texture-uv check FootPrintGeometryOverrideAnimatedMaterial + example. + */ + return false; +} + +//--------------------------------------------------------------------------- +//--------------------------------------------------------------------------- +// Geometry update and VP2 cache implementations +//--------------------------------------------------------------------------- +//--------------------------------------------------------------------------- + +/* + Return true when the aOutputSpline changes, which requires us to re-generate + the geometry. + + Note: this method must return the exact same value in a cache-restoration + frame as it was in the corresponding cache-store frame. +*/ +bool TwistSplineGeometryOverride::requiresGeometryUpdate() const { + /* + Checking the "TwistSplineNodeNode::geometryChanging" attribute if any + attribute affecting the geometry is changing, + "TwistSplineNodeNode::geometryChanging" should be affected and dirtied. + Also check TwistSplineNodeNodometry(). Warning: this method may be + called outside of regular { update() : cleanUp() } scope. Thus, we must + invoke node-local evaluation to ensure the correctness. + */ + return mTwistSplineNode->isGeometryChanging(); +} + +// Generate the geometry(vertex / index) from TwistSplineNodeNode's parameter +// data +// [[ensure : requiresGeometryUpdate() == false]] +void TwistSplineGeometryOverride::populateGeometry( + const MGeometryRequirements& requirements, + const MRenderItemList& renderItems, MGeometry& data) { + // This call will ensure the post-condition that requiresGeometryUpdate() is + // false + mTwistSplineNode->updatingGeometry(); +} + +void TwistSplineGeometryOverride::addUIDrawables( + const MDagPath& path, MHWRender::MUIDrawManager& drawManager, + const MHWRender::MFrameContext& frameContext) { + // Retrieve the spline data to draw elements; + auto splineData = mTwistSplineNode->getSplineData(); + auto splinePoints = splineData->getPoints(); + + bool debugDraw; + double debugScale; + mTwistSplineNode->getDebugDraw(debugDraw, debugScale); + + MFnDependencyNode node(path.node()); + bool draw2D = false; // ALWAYS false + + drawManager.beginDrawable(MUIDrawManager::kSelectable); + + // Get correct color based on the state of object, e.g. active or + // dormant. + MHWRender::DisplayStatus displayStatus = + MHWRender::MGeometryUtilities::displayStatus(path); + MColor color = MHWRender::MGeometryUtilities::wireframeColor(path); + drawManager.setColor(color); + drawManager.lineStrip(splinePoints, draw2D); + + if (debugDraw) { + MPointArray tangents; + MVectorArray tans = splineData->getTangents(); + tangents.setLength(tans.length() * 2); + for (size_t i = 0; i < tans.length(); ++i) { + MPoint& spi = splinePoints[i]; + tangents[2 * i] = spi; + tangents[(2 * i) + 1] = debugScale * tans[i] + spi; + } + drawManager.setColor(MColor(.5, 0, 0)); + drawManager.lineList(tangents, draw2D); + + MPointArray normals; + MVectorArray norms = splineData->getNormals(); + normals.setLength(norms.length() * 2); + for (size_t i = 0; i < norms.length(); ++i) { + MPoint& spi = splinePoints[i]; + MVector& nn = norms[i]; + normals[2 * i] = spi; + normals[(2 * i) + 1] = debugScale * nn + spi; + } + drawManager.setColor(MColor(0, .5, 0)); + drawManager.lineList(normals, draw2D); + + MPointArray binormals; + MVectorArray binorms = splineData->getBinormals(); + binormals.setLength(binorms.length() * 2); + for (size_t i = 0; i < binorms.length(); ++i) { + MPoint& spi = splinePoints[i]; + binormals[2 * i] = spi; + binormals[(2 * i) + 1] = debugScale * binorms[i] + spi; + } + drawManager.setColor(MColor(0, 0, .5)); + drawManager.lineList(binormals, draw2D); + } + drawManager.endDrawable(); +} + +//--------------------------------------------------------------------------- +//--------------------------------------------------------------------------- +// Debug functions +//--------------------------------------------------------------------------- +//--------------------------------------------------------------------------- + +// Return true if internal tracing is desired. +bool TwistSplineGeometryOverride::traceCallSequence() const { + /* + Tracing will look something like the following when in shaded mode (on + selection change): + - TwistSplineGeometryOverride: Geometry override DG update: twistSpline1 + - TwistSplineGeometryOverride: Start geometry override render item + update: |transform1|twistSpline1 + - TwistSplineGeometryOverride: - Call API to update render items + - TwistSplineGeometryOverride: End geometry override render item update: + |transform1|twistSpline1 + - TwistSplineGeometryOverride: End geometry override clean up: + twistSpline1 + + 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. + */ + return false; +} + +inline void TwistSplineGeometryOverride::handleTraceMessage( + const MString& message) const { + MGlobal::displayInfo(gPluginNodeMessagePrefix + message); + + // Some simple custom message formatting. + fputs(gPluginNodeMessagePrefix, stderr); + fputs(message.asChar(), stderr); + fputs("\n", stderr); +} diff --git a/src/drawOverride.h b/src/drawOverride.h new file mode 100644 index 0000000..11da9f8 --- /dev/null +++ b/src/drawOverride.h @@ -0,0 +1,142 @@ +#ifndef DRAW_OVERRIDE_H +#define DRAW_OVERRIDE_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "twistSplineNode.h" + +static const MString WireframeItemName = "twistSplineWires"; + +/* + Customized rendering logic of TwistSplineNodeNode + + This class is responsible for generating the geometry (vertex buffer) from + the procedure geometry. Beside rendering, the functions for generating + geometry is also a crucial part in Viewport Caching (in contrast with + Evaluation Caching). +*/ +class TwistSplineGeometryOverride : public MPxGeometryOverride { + public: + static MPxGeometryOverride* Creator(const MObject& obj) { + return new TwistSplineGeometryOverride(obj); + } + ~TwistSplineGeometryOverride() override; + + // Support configuration functions : + MHWRender::DrawAPI supportedDrawAPIs() const override { + return (MHWRender::DrawAPI::kOpenGL | MHWRender::DrawAPI::kDirectX11 | + MHWRender::DrawAPI::kOpenGLCoreProfile); + } + bool supportsEvaluationManagerParallelUpdate() const override { + return true; + } + /* + Supporting Viewport Caching (VP2 Custom Caching) + + Viewport Cache for customize rendering nodes is caching the geometry + data generated by MPxGeometryOverride::populateGeometry(). Like the + graphics API, the geometry data is usually expressed in three different + objects: + - Vertex Buffer : Viewport Cache + - Index Buffer : No cache, must be time-independent + - Uniform Buffer : Evaluation Cache + Currently, *only* Vertex Buffer can be cached in Viewport Cache and all + the data in Uniform Buffer (shader parameters such as Color), must be + stored in Evaluation Cache. + + Viewport Caching is helpful if you are generating the geometry data in + CPU. For GPU generated geometry, the benefit is limited. To make the + Viewport caching works properly the following constraints must be meet: + - requiresUpdateRenderItems(path) always return 'false' in + background-dg-context. + - requiresGeometryUpdate() always return 'true' when restored from + cache (if geometry is animated). + - populateGeometry() should be context-safe + */ + bool supportsVP2CustomCaching() const override { return true; } + + /* + Interaction with TwistSplineNodeNode + - This is the only method where you can call MPlug::getValue() or + Mdatablock::inputValue() + - This method can be empty (in EM modes), if you have setup the node + correctly with Technique 1. + */ + void updateDG() override; + // For the best practice, this method should contain no status data + void cleanUp() override{}; + + // Render item functions, only involved in foreground rendering, should + // not affect VP2 Caching at all + bool requiresUpdateRenderItems(const MDagPath& path) const override; + void updateRenderItems(const MDagPath& path, + MRenderItemList& list) override{}; + bool hasUIDrawables() const override { return true; }; + void addUIDrawables(const MDagPath& path, MUIDrawManager& drawManager, + const MFrameContext& frameContext) override; + + // Geometry update functions, major entry for support VP2 Custom Caching + bool requiresGeometryUpdate() const override; + bool isIndexingDirty(const MRenderItem& item) override { + return false; + } // Viewport Caching does not support index buffer change (animated + // topology) + void populateGeometry(const MGeometryRequirements& requirements, + const MRenderItemList& renderItems, + MGeometry& data) override; + + // Debugging functions + bool traceCallSequence() const override; + void handleTraceMessage(const MString& message) const override; + + private: + TwistSplineGeometryOverride(const MObject& obj); + /* + To ensure the plugin works well with Viewport and Evaluation Caching + we recommend to use a state-less MpxGeometryOverride: + + - Store all time-dependent data on the attributes of corresponding Maya + node, instead of a data member in MpxGeometryOverride. + - updateDG() pulls (evaluates) all the time-dependent data on node by + "MDataBlock::inputValue()", but *not* store them in a data member of + MPxGeometryOverride + - Always access the time-dependent data by "MDataBlock::outputValue()" + in the renderer + + Backgrounds of the recommendation: + + In this plugin example, we have 3 different pipeline stages involved: + 1. Node Evaluation + 2. Geometry Update (MPxGeometryOverride invocation) + 3. Rendering (Shader-pre-draw-callback invocation) + + With (Evaluation or Viewport) Caching, some surprising mixture of + stages happen concurrently: + + - Foreground thread : + MPxGeometryOverride -> Shader Callback -> (Rendering on frame 10) + - Background thread : + Node Evaluation -> MPxGeometryOverride -> (Viewport cache fill on frame 20) + + Accessing the same data directly cross stage boundaries can cause + incorrect result or even crash Maya. Thus we must use different storage + for each data in each DG context (frame). And a depend node's attribute + are always isolated for different context. + + Beside, within the MPxGeometryOverride invocation stage, it is not + always safe to access its data member : During Viewport Cache + Restoration, requiresGeometryUpdate() is the only method get called. + (No updateDG() call) Thus the data must be stored in the node. + */ + TwistSplineNode* mTwistSplineNode; // The node we are rendering +}; + +#endif diff --git a/src/pluginMain.cpp b/src/pluginMain.cpp index 5e272b0..2b09000 100644 --- a/src/pluginMain.cpp +++ b/src/pluginMain.cpp @@ -26,25 +26,26 @@ SOFTWARE. #include "twistSplineData.h" #include "riderConstraint.h" #include "twistTangentNode.h" +#include "drawOverride.h" #include -MStatus initializePlugin( MObject obj ) { +MStatus initializePlugin( MObject obj ) { MStatus status; - MFnPlugin plugin( obj, "BlurStudio", "2018", "Any"); + MFnPlugin plugin( obj, "BlurStudio", "2023", "Any"); status = plugin.registerData("twistSplineData", TwistSplineData::id, TwistSplineData::creator); if (!status) { status.perror("Failed to register twistSplineData"); return status; } - + status = plugin.registerNode( "twistSpline", TwistSplineNode::id, - TwistSplineNode::creator, - TwistSplineNode::initialize, + &TwistSplineNode::creator, + &TwistSplineNode::initialize, MPxNode::kLocatorNode, - sUseLegacyDraw ? NULL : &TwistSplineNode::drawDbClassification + &TwistSplineNode::drawDbClassification ); if (!status) { @@ -76,15 +77,13 @@ MStatus initializePlugin( MObject obj ) { return status; } - if (sUseLegacyDraw) return status; - - status = MHWRender::MDrawRegistry::registerDrawOverrideCreator( + status = MHWRender::MDrawRegistry::registerGeometryOverrideCreator( TwistSplineNode::drawDbClassification, TwistSplineNode::drawRegistrantId, - TwistSplineDrawOverride::Creator + TwistSplineGeometryOverride::Creator ); if (!status) { - status.perror("Faild to register TwistSpline DrawOverrideCreator"); + status.perror("Faild to register TwistSpline GeometryOverrideCreator"); return status; } return status; @@ -106,12 +105,10 @@ MStatus uninitializePlugin(MObject obj) { tangentStat = plugin.deregisterNode(TwistTangentNode::id); if (!tangentStat) nodeStat.perror("Failed to de-register twistTangent Node"); - if (!sUseLegacyDraw) { - drawStat = MHWRender::MDrawRegistry::deregisterDrawOverrideCreator( - TwistSplineNode::drawDbClassification, - TwistSplineNode::drawRegistrantId); - if (!drawStat) drawStat.perror("deregisterDrawOverrideCreator"); - } + drawStat = MDrawRegistry::deregisterGeometryOverrideCreator( + TwistSplineNode::drawDbClassification, + TwistSplineNode::drawRegistrantId); + if (!drawStat) drawStat.perror("deregisterGeometryOverrideCreator"); if (!nodeStat) return nodeStat; if (!dataStat) return dataStat; diff --git a/src/twistSplineNode.cpp b/src/twistSplineNode.cpp index d9d432c..2c0b61a 100644 --- a/src/twistSplineNode.cpp +++ b/src/twistSplineNode.cpp @@ -26,6 +26,7 @@ SOFTWARE. #include #include #include +#include #include #include #include @@ -42,6 +43,7 @@ SOFTWARE. #include #include +#include #include #include #include @@ -73,6 +75,7 @@ MObject TwistSplineNode::aTwistValue; MObject TwistSplineNode::aTwistWeight; MObject TwistSplineNode::aUseOrient; +MObject TwistSplineNode::aGeometryChanging; MObject TwistSplineNode::aDebugDisplay; MObject TwistSplineNode::aDebugScale; MObject TwistSplineNode::aMaxVertices; @@ -97,6 +100,12 @@ MStatus TwistSplineNode::initialize() { nAttr.setWritable(false); addAttribute(aSplineLength); + aGeometryChanging = nAttr.create("geometryChanging", "gch", MFnNumericData::kBoolean, true); + nAttr.setStorable(false); + nAttr.setHidden(true); + nAttr.setConnectable(false); + addAttribute(aGeometryChanging); + //--------------- Input ------------------- aDebugDisplay = nAttr.create("debugDisplay", "dd", MFnNumericData::kBoolean, false); @@ -163,6 +172,17 @@ MStatus TwistSplineNode::initialize() { attributeAffects(aOutTangent, aSplineLength); attributeAffects(aMaxVertices, aSplineLength); + // Geometry changing + attributeAffects(aInTangent, aGeometryChanging); + attributeAffects(aControlVertex, aGeometryChanging); + attributeAffects(aOutTangent, aGeometryChanging); + attributeAffects(aParamValue, aGeometryChanging); + attributeAffects(aParamWeight, aGeometryChanging); + attributeAffects(aTwistWeight, aGeometryChanging); + attributeAffects(aTwistValue, aGeometryChanging); + attributeAffects(aUseOrient, aGeometryChanging); + attributeAffects(aMaxVertices, aGeometryChanging); + return MS::kSuccess; } @@ -216,9 +236,8 @@ void TwistSplineNode::getDebugDraw(bool &oDraw, double &oScale) const { scalePlug.getValue(oScale); } } - -} +} MStatus TwistSplineNode::compute(const MPlug& plug, MDataBlock& data) { if (plug == aOutputSpline) { @@ -352,157 +371,148 @@ MStatus TwistSplineNode::compute(const MPlug& plug, MDataBlock& data) { outHandle.setDouble(spline->getTotalLength()); data.setClean(aSplineLength); } + else if (plug == aGeometryChanging) { + MStatus status; + MDataHandle boolHandle = data.outputValue(aGeometryChanging, &status); + MCHECKERROR(status); + boolHandle.setBool(true); + boolHandle.setClean(); + } else { return MS::kUnknownParameter; } return MS::kSuccess; } -void* TwistSplineNode::creator() { - return new TwistSplineNode(); -} - -void TwistSplineNode::draw(M3dView &view, const MDagPath &path, M3dView::DisplayStyle style, M3dView::DisplayStatus dstat) { - // TODO: build the legacy viewport draw mechanism -} - - - - - -//--------------------------------------------------------------------------- -//--------------------------------------------------------------------------- -// Viewport 2.0 override implementation -//--------------------------------------------------------------------------- -//--------------------------------------------------------------------------- -// 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 -// callback is 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. -TwistSplineDrawOverride::TwistSplineDrawOverride(const MObject& obj) - : MHWRender::MPxDrawOverride(obj, NULL, false) { - fModelEditorChangedCbId = MEventMessage::addEventCallback("modelEditorChanged", OnModelEditorChanged, this); - - MStatus status; - MFnDependencyNode node(obj, &status); - tsn = status ? dynamic_cast(node.userNode()) : NULL; -} - -TwistSplineDrawOverride::~TwistSplineDrawOverride() { - tsn = NULL; - - if (fModelEditorChangedCbId != 0) { - MMessage::removeCallback(fModelEditorChangedCbId); - fModelEditorChangedCbId = 0; +/* + Technique 1: Hack the EM to force evaluate and cache attributes. + To improve performance, Evaluation Manager aggressively skips evaluation of + attributes which are not connected to other nodes. In cases where an external + user of DG data (in this case the renderer) needs to read unconnected values + from a node during or after EM evaluation, we need to take extra steps to + ensure the data is evaluated by the EM (and cached). The most notable rules + used by the EM for skipping evaluation are: + 1. Output attributes without output-connections are not computed in EM + (and are not eligible for caching). + 2. Input attributes are never cached in Evaluation Cache. + + In TwistSplineNode, attribute "geometryChanging" is virtually connected to the + renderer. But EM does not understand these "virtual connection", and skips + evaluation and caching for them. The current workaround are: + 1. To bypass rule 2, we made them a passing-through output attributes : + inputSize->outputSize + 2. To bypass rule 1, repeat the affect relationship in setDependentsDirty(). + [*] + [*] Note, this is a trick that relies on some internal hack to EM. + Maya may provide better API for this in future updates. + When proper force evaluation API is come, you won't need to override + this method. +*/ +MStatus TwistSplineNode::setDependentsDirty(const MPlug& plug, + MPlugArray& plugArray) { + // Repeating the affect relationship we have specified. + // This method just mean to trick EM. + // No need to do this outside of EM graph construction (for the sake of + // performance) + if (MEvaluationManager::graphConstructionActive()) { + if (plug.partialName() == "inTangent" || + plug.partialName() == "outTangent" || + plug.partialName() == "controlVertex" || + plug.partialName() == "paramValue" || + plug.partialName() == "paramWeight" || + plug.partialName() == "twistWeight" || + plug.partialName() == "twistValue" || + plug.partialName() == "useOrient" || + plug.partialName() == "maxVertices") { + MObject thisNode = thisMObject(); + MPlug geometryChangingPlug(thisNode, aGeometryChanging); + plugArray.append(geometryChangingPlug); + } } + // Try not set any data or attribute value in this method + // Because EM's parallel evaluation will not call this method at all + // A widely used *bad* approach is to write "aGeometryChanged=true" when + // some attribute changed. Use Technique 1.1 to avoid this. + return MStatus::kSuccess; } -void TwistSplineDrawOverride::OnModelEditorChanged(void *clientData) { - // Mark the node as being dirty so that it can update on display appearance - // switch among wireframe and shaded. - TwistSplineDrawOverride *ovr = static_cast(clientData); - if (ovr && ovr->tsn) { - MHWRender::MRenderer::setGeometryDrawDirty(ovr->tsn->thisMObject()); +MStatus TwistSplineNode::postEvaluation(const MDGContext& context, + const MEvaluationNode& evaluationNode, + PostEvaluationType evalType) { + // For cache restoration only. + // This method is responsible for fixing the 'geometryChanging' flag in + // cache restore frames Because in cache store phase, PopulateGeometry & + // Viewport-Caching happens before Evaluation-Cache store The value of + // 'geometryChanging' will always be set to 'false' (it is already used by + // render) Thus, we have to fix the geometryChanging attribute to the + // correct value. + MStatus status; + // kEvaluateDirectly indicates we are restoring from cache. + if (evalType == PostEvaluationEnum::kEvaluatedDirectly && + evaluationNode.dirtyPlugExists(aGeometryChanging, &status) && status) { + MDataBlock data = forceCache(); + MDataHandle boolHandle = data.outputValue(aGeometryChanging, &status); + if (status != MStatus::kSuccess) return status; + boolHandle.setBool(true); + boolHandle.setClean(); } + return MPxLocatorNode::postEvaluation(context, evaluationNode, evalType); } -MHWRender::DrawAPI TwistSplineDrawOverride::supportedDrawAPIs() const { - // this plugin supports both GL and DX - return (MHWRender::kOpenGL | MHWRender::kDirectX11 | MHWRender::kOpenGLCoreProfile); +void TwistSplineNode::getCacheSetup(const MEvaluationNode& evalNode, + MNodeCacheDisablingInfo& disablingInfo, + MNodeCacheSetupInfo& cacheSetupInfo, + MObjectArray& monitoredAttributes) const { + MPxLocatorNode::getCacheSetup(evalNode, disablingInfo, cacheSetupInfo, + monitoredAttributes); + assert(!disablingInfo.getCacheDisabled()); + cacheSetupInfo.setPreference(MNodeCacheSetupInfo::kWantToCacheByDefault, + true); } -bool TwistSplineDrawOverride::isBounded(const MDagPath& /*objPath*/, const MDagPath& /*cameraPath*/) const { - return true; +void* TwistSplineNode::creator() { return new TwistSplineNode(); } + +// Must be called after MPxGeometryOverride::updateDG() +// Typically used by MPxGeometryOverride::requiresGeometryUpdate() +bool TwistSplineNode::isGeometryChanging() const { + MDataBlock block = const_cast(this)->forceCache(); + // Use inputValue() to trigger evaluation here + // Because MPxGeometryOverride::requiresGeometryUpdate() can be called + // outside of MPxGeometryOverride::initialize()/updateDG() This evaluation + // is safe because this attribute cannot be connected And thus cannot reach + // other nodes + return block.inputValue(TwistSplineNode::aGeometryChanging).asBool(); } -MBoundingBox TwistSplineDrawOverride::boundingBox( - const MDagPath& objPath, - const MDagPath& cameraPath) const { - return tsn->boundingBox(); +// Workload for MPxGeometryOverride::updateDG() +// Updating all the attributes needed by the renderer +// Ensure these attributes can be accessed by outputValue() safely later +void TwistSplineNode::updateRenderAttributes() { + MDataBlock datablock = forceCache(); + datablock.inputValue(TwistSplineNode::aGeometryChanging); + datablock.inputValue(TwistSplineNode::aDebugDisplay); + datablock.inputValue(TwistSplineNode::aDebugScale); + datablock.inputValue(TwistSplineNode::aOutputSpline); } - -// Called by Maya each time the object needs to be drawn. -MUserData* TwistSplineDrawOverride::prepareForDraw( - const MDagPath& objPath, - const MDagPath& cameraPath, - const MHWRender::MFrameContext& frameContext, - MUserData* oldData) { - MStatus status; - // Any data needed from the Maya dependency graph must be retrieved and cached in this stage. - TwistSplineDrawData* data = dynamic_cast(oldData); - if (!data) data = new TwistSplineDrawData(); - - TwistSplineT* ts = tsn->getSplineData(); - data->splinePoints = ts->getPoints(); - tsn->getDebugDraw(data->debugDraw, data->debugScale); - - if (data->debugDraw){ - double scale = data->debugScale; - - MVectorArray tans = ts->getTangents(); - data->tangents.setLength(tans.length() * 2); - for (size_t i = 0; i < tans.length(); ++i) { - MPoint &spi = data->splinePoints[i]; - data->tangents[2*i] = spi; - data->tangents[(2*i) +1] = scale * tans[i] + spi; - } - - MVectorArray norms = ts->getNormals(); - data->normals.setLength(norms.length() * 2); - for (size_t i = 0; i < norms.length(); ++i) { - MPoint &spi = data->splinePoints[i]; - MVector &nn = norms[i]; - data->normals[2 * i] = spi; - data->normals[(2 * i) + 1] = scale * nn + spi; - } - - MVectorArray binorms = ts->getBinormals(); - data->binormals.setLength(binorms.length() * 2); - for (size_t i = 0; i < binorms.length(); ++i) { - MPoint &spi = data->splinePoints[i]; - data->binormals[2 * i] = spi; - data->binormals[(2 * i) + 1] = scale * binorms[i] + spi; - } - } - - // get correct color based on the state of object, e.g. active or dormant - data->color = MHWRender::MGeometryUtilities::wireframeColor(objPath); - return data; +// Should only be called from MPxGeometryOverride::populateGeometry() +// Returns the parameters required to update geometry +// Set "TwistSplineNode::geometryChanging" to "false" to avoid duplicate update +// [[ensure : isGeometryChanging() == false]] +TwistSplineNode::GeometryParameters TwistSplineNode::updatingGeometry() { + MDataBlock block = const_cast(this)->forceCache(); + + // Reset the geometryChanging attribute to false so that we do not update + // the geometry multiple times + block.outputValue(TwistSplineNode::aGeometryChanging).set(false); + + GeometryParameters param; + param.debugMode = + block.outputValue(TwistSplineNode::aDebugDisplay).asBool(); + param.debugScale = + block.outputValue(TwistSplineNode::aDebugScale).asDouble(); + param.splineData = + block.outputValue(TwistSplineNode::aOutputSpline).asPluginData(); + + return param; } - -// addUIDrawables() provides access to the MUIDrawManager, which can be used -// to queue up operations for drawing simple UI elements such as lines, circles and -// text. To enable addUIDrawables(), override hasUIDrawables() and make it return true. -void TwistSplineDrawOverride::addUIDrawables( - const MDagPath& objPath, - MHWRender::MUIDrawManager& drawManager, - const MHWRender::MFrameContext& frameContext, - const MUserData* userData) { - TwistSplineDrawData* data = (TwistSplineDrawData*)userData; - if (!data) return; - - bool draw2D = false; // ALWAYS false - drawManager.beginDrawable(); - - drawManager.setColor(data->color); - - drawManager.setDepthPriority(MHWRender::MRenderItem::sActiveLineDepthPriority); - drawManager.lineStrip(data->splinePoints, draw2D); - - if (data->debugDraw){ - drawManager.setColor(MColor(.5, 0, 0)); - drawManager.lineList(data->tangents, draw2D); - - drawManager.setColor(MColor(0, 0, .5)); - drawManager.lineList(data->binormals, draw2D); - - drawManager.setColor(MColor(0, .5, 0)); - drawManager.lineList(data->normals, draw2D); - } - drawManager.endDrawable(); -} - diff --git a/src/twistSplineNode.h b/src/twistSplineNode.h index 39a2e64..170e0c7 100644 --- a/src/twistSplineNode.h +++ b/src/twistSplineNode.h @@ -30,7 +30,7 @@ SOFTWARE. #include #include #include -#include +#include #include #include #include @@ -47,20 +47,25 @@ SOFTWARE. #include "twistSpline.h" #include "twistSplineData.h" -static bool sUseLegacyDraw = (getenv("MAYA_ENABLE_VP2_PLUGIN_LOCATOR_LEGACY_DRAW") != NULL); - +static constexpr const char gPluginNodeMessagePrefix[] = "TwistSpline: "; + class TwistSplineNode : public MPxLocatorNode { public: TwistSplineNode(); - virtual ~TwistSplineNode(); + virtual ~TwistSplineNode(); virtual MStatus compute( const MPlug& plug, MDataBlock& data ); - //virtual MStatus preEvaluation(const MDGContext& context, const MEvaluationNode& evaluationNode); - //virtual MStatus setDependentsDirty(const MPlug& plug, MPlugArray& plugArray); - static void* creator(); + MStatus setDependentsDirty(const MPlug& plug, MPlugArray& plugArray) override; + MStatus postEvaluation(const MDGContext& context, + const MEvaluationNode& evaluationNode, + PostEvaluationType evalType) override; + void getCacheSetup(const MEvaluationNode& evalNode, + MNodeCacheDisablingInfo& disablingInfo, + MNodeCacheSetupInfo& cacheSetupInfo, + MObjectArray& monitoredAttributes) const override; + static void* creator(); static MStatus initialize(); - virtual void draw(M3dView &view, const MDagPath &path, M3dView::DisplayStyle style, M3dView::DisplayStatus); virtual bool isBounded() const { return true; } @@ -68,6 +73,21 @@ class TwistSplineNode : public MPxLocatorNode TwistSplineT* getSplineData() const; void getDebugDraw(bool &oDraw, double &oScale) const; + // Data defines the custom shape, which decides the generated geometry's vertex buffer. + // This only contains geometry data, material and appearance data should not + // be contained here E.g. Outer Radius R and inner radius R for a torus shape. + struct GeometryParameters { + bool debugMode; + double debugScale; + double splineLength; + MPxData* splineData; + }; + + // Methods for the renderer to call + bool isGeometryChanging() const; + void updateRenderAttributes(); + GeometryParameters updatingGeometry(); + public: static MObject aOutputSpline; static MObject aSplineLength; @@ -85,75 +105,11 @@ class TwistSplineNode : public MPxLocatorNode static MObject aTwistWeight; static MObject aUseOrient; + static MObject aGeometryChanging; static MObject aDebugDisplay; static MObject aDebugScale; - static MTypeId id; + static MTypeId id; static MString drawDbClassification; static MString drawRegistrantId; }; - - -class TwistSplineDrawData : public MUserData { -public: - TwistSplineDrawData() : MUserData(false) {} // don't delete after draw - virtual ~TwistSplineDrawData() {} - MColor color; - MPointArray splinePoints; - MPointArray tangents; - MPointArray normals; - MPointArray binormals; - bool debugDraw; - double debugScale; -}; - -class TwistSplineDrawOverride : public MHWRender::MPxDrawOverride { -public: - static MHWRender::MPxDrawOverride* Creator(const MObject& obj) { - return new TwistSplineDrawOverride(obj); - } - - virtual ~TwistSplineDrawOverride(); - - virtual MHWRender::DrawAPI supportedDrawAPIs() const; - - virtual bool isBounded( const MDagPath& objPath, const MDagPath& cameraPath) const; - - virtual MBoundingBox boundingBox( const MDagPath& objPath, const MDagPath& cameraPath) const; - void getDebugDraw(const MDagPath& objPath, bool &oDraw, bool &oScale) const; - - virtual MUserData* prepareForDraw( - const MDagPath& objPath, - const MDagPath& cameraPath, - const MHWRender::MFrameContext& frameContext, - MUserData* oldData); - - virtual bool hasUIDrawables() const { return true; } - - virtual void addUIDrawables( - const MDagPath& objPath, - MHWRender::MUIDrawManager& drawManager, - const MHWRender::MFrameContext& frameContext, - const MUserData* data); - - // Return true if internal tracing is desired. - virtual bool traceCallSequence() const { return false; } - - virtual void handleTraceMessage( const MString &message ) const { - MGlobal::displayInfo("twistSplineDrawOverride: " + message); - - // Some simple custom message formatting. - fprintf(stderr, "twistSplineDrawOverride: "); - fprintf(stderr, message.asChar()); - fprintf(stderr, "\n"); - } - -private: - TwistSplineDrawOverride(const MObject& obj); - - static void OnModelEditorChanged(void *clientData); - - TwistSplineNode *tsn; - MCallbackId fModelEditorChangedCbId; -}; -