Skip to content

Instantly share code, notes, and snippets.

@mottosso
Last active February 11, 2021 13:42
Show Gist options
  • Save mottosso/096c402c7d3934ec313b0786ea1ce9ab to your computer and use it in GitHub Desktop.
Save mottosso/096c402c7d3934ec313b0786ea1ce9ab to your computer and use it in GitHub Desktop.
Editable Initial State - Attempt 8

⬅️ Back

From Attempt 3, we got it almost working. The caveat was that we weren't able to get a hold of the latest available pCube1.worldMatrix. So what if we forced its hand even more?

What if we write the final position used during rendering? This must be the final position, because you're looking at it!

initialstate58

As it happens, the problem isn't here. We're still out of order. We are always called before rendering. Which mean rendering for frame 10 will happen, and then frame 11 is called. It's only after our code gets called that we record whatever was rendered. So we still end up with an out-of-date frame.

On the upside, we are now getting the proper, actual pCube1.worldMatrix, regardless of what dirtied the worldMatrix. When there's a pairBlend hooked up to it, and it's moving about with just animation, we'll get it.

But it's really really ugly. And it'll only work if the thing is actually rendered. What about offscreen playblasts? What about exports? What about mayapy? It won't hold water. It's possible we could find an equally brutal callback, like the dirty callback, but it's not a good route.

I think we're forcing this from the wrong direction. We need to look at the problem from a different perspective.


Build & Run

From Powershell.

cd c:\
git clone https://gist.github.com/096c402c7d3934ec313b0786ea1ce9ab.git initialStateNode6
mkdir initialStateNode6/build
cd initialStateNode6/build
$env:DEVKIT_LOCATION="c:\path\to\your\devkit"
cmake .. -G Ninja
ninja

Then from the Script Editor.

import os
from maya import cmds

fname = r"c:\initialState6\build\initialStateNode.mll"
cmds.loadPlugin(fname)

time = "time1"
cube, _ = cmds.polyCube()
cmds.move(-3, 15, -7)
cmds.scale(4, 4, 4)
node = cmds.createNode("initialStateNode")
cmds.setAttr(node + ".startTime", cmds.getAttr(time + ".outTime"))
cmds.setAttr(node + ".gravity", 0, -9.81, 0)
cmds.connectAttr(time + ".outTime", node + ".currentTime")
cmds.connectAttr(cube + ".worldMatrix[0]", node + ".inWorldMatrix")
cmds.connectAttr(node + ".outputTranslateX", cube + ".translateX")
cmds.connectAttr(node + ".outputTranslateY", cube + ".translateY")
cmds.connectAttr(node + ".outputTranslateZ", cube + ".translateZ")

#cmds.file(new=True, force=True)
#cmds.unloadPlugin(os.path.basename(fname))
cmake_minimum_required(VERSION 2.8)
include($ENV{DEVKIT_LOCATION}/cmake/pluginEntry.cmake)
set(PROJECT_NAME initialStateNode)
set(SOURCE_FILES
initialStateNode.cpp
)
set(LIBRARIES
OpenMaya
OpenMayaRender
OpenMayaUI
Foundation
)
build_plugin()
#include <string.h>
#include <maya/MPxLocatorNode.h>
#include <maya/MFnDependencyNode.h>
#include <maya/MFnNumericAttribute.h>
#include <maya/MFnMatrixAttribute.h>
#include <maya/MFnUnitAttribute.h>
#include <maya/MFnPlugin.h>
#include <maya/MPxDrawOverride.h>
#include <maya/MTypeId.h>
#include <maya/MPlug.h>
#include <maya/MVector.h>
#include <maya/MTransformationMatrix.h>
#include <maya/MMatrix.h>
#include <maya/MDataBlock.h>
#include <maya/MDataHandle.h>
#include <maya/MFnMatrixData.h>
#include <maya/MStreamUtils.h>
#include <maya/MDrawRegistry.h>
#define Print MStreamUtils::stdErrorStream
class initialStateNode : public MPxLocatorNode {
public:
initialStateNode();
~initialStateNode() override;
MStatus compute(const MPlug& plug, MDataBlock& data) override;
MStatus computeStartState(const MPlug& plug, MDataBlock& data);
MStatus computeFirstState(const MPlug& plug, MDataBlock& data);
MStatus computeCurrentState(const MPlug& plug, MDataBlock& data);
MStatus writeOutputTranslate(MDataBlock& datablock);
MStatus stepSimulation(MDataBlock& datablock);
void saveRestMatrix(MMatrix mat);
static void* creator() { return new initialStateNode(); }
static MStatus initialize();
bool isPassiveOutput(const MPlug& plug) const override;
public:
static MTypeId id;
static const MString drawDbClassification;
static const MString drawRegistrantId;
// Attributes
static MObject aStartTime;
static MObject aCurrentTime;
static MObject aGravity;
static MObject aGravityX;
static MObject aGravityY;
static MObject aGravityZ;
static MObject aInWorldMatrix;
static MObject aOutputTranslate;
static MObject aOutputTranslateX;
static MObject aOutputTranslateY;
static MObject aOutputTranslateZ;
private:
bool _isStartTime { false };
bool _isFirstTime { false };
MTime _lastTime { };
double _deltaSeconds { };
MTime _currentTime { };
MMatrix _restMatrix {};
MMatrix _outputMatrix {};
MMatrix _drawWorldMatrix {};
MVector _velocity { 0, 0, 0 };
};
MTypeId initialStateNode::id(0x80012);
const MString initialStateNode::drawDbClassification("drawdb/geometry/initialStateNode");
const MString initialStateNode::drawRegistrantId("initialStateNodePlugin");
MObject initialStateNode::aInWorldMatrix;
MObject initialStateNode::aOutputTranslate;
MObject initialStateNode::aOutputTranslateX;
MObject initialStateNode::aOutputTranslateY;
MObject initialStateNode::aOutputTranslateZ;
MObject initialStateNode::aGravity;
MObject initialStateNode::aGravityX;
MObject initialStateNode::aGravityY;
MObject initialStateNode::aGravityZ;
MObject initialStateNode::aStartTime;
MObject initialStateNode::aCurrentTime;
static bool gTimeDirty { true };
initialStateNode::initialStateNode() {}
initialStateNode::~initialStateNode() {}
bool initialStateNode::isPassiveOutput(const MPlug& plug) const {
if (plug == aOutputTranslate ||
plug == aOutputTranslateX ||
plug == aOutputTranslateY ||
plug == aOutputTranslateZ) {
return true;
}
return false;
}
MStatus initialStateNode::initialize() {
MStatus status;
MFnNumericAttribute numFn;
MFnMatrixAttribute matFn;
MFnUnitAttribute uniFn;
aStartTime = uniFn.create("startTime", "stti", MFnUnitAttribute::kTime, 0.0, &status);
aCurrentTime = uniFn.create("currentTime", "cuti", MFnUnitAttribute::kTime, 0.0, &status);
aInWorldMatrix = matFn.create("inWorldMatrix", "inwm");
aOutputTranslateX = uniFn.create("outputTranslateX", "outx", MFnUnitAttribute::kDistance, 0.0, &status);
aOutputTranslateY = uniFn.create("outputTranslateY", "outy", MFnUnitAttribute::kDistance, 0.0, &status);
aOutputTranslateZ = uniFn.create("outputTranslateZ", "outz", MFnUnitAttribute::kDistance, 0.0, &status);
aOutputTranslate = numFn.create("outputTranslate", "outr", aOutputTranslateX, aOutputTranslateY, aOutputTranslateZ);
uniFn.setWritable(false);
uniFn.setStorable(false);
aGravityX = uniFn.create("gravityX", "grvx", MFnUnitAttribute::kDistance, 0.0, &status);
aGravityY = uniFn.create("gravityY", "grvy", MFnUnitAttribute::kDistance, -981.0, &status);
aGravityZ = uniFn.create("gravityZ", "grvz", MFnUnitAttribute::kDistance, 0.0, &status);
aGravity = numFn.create("gravity", "grav", aGravityX, aGravityY, aGravityZ);
addAttribute(aStartTime);
addAttribute(aCurrentTime);
addAttribute(aInWorldMatrix);
addAttribute(aOutputTranslate);
addAttribute(aGravity);
attributeAffects(aGravity, aOutputTranslate);
attributeAffects(aCurrentTime, aOutputTranslate);
return MS::kSuccess;
}
/**
* Read and store rest matrix
*
*/
MStatus initialStateNode::computeStartState(const MPlug& plug, MDataBlock& datablock) {
Print() << "computeStartState()\n";
MStatus status { MS::kSuccess };
// Distinguish between user input and changes to time
if (_deltaSeconds) {
Print() << " --> Restoring\n";
}
else {
Print() << " <-- Recording\n";
_restMatrix = _drawWorldMatrix;
}
_outputMatrix = _restMatrix;
_isFirstTime = true;
writeOutputTranslate(datablock);
datablock.setClean(plug);
return status;
}
/**
* Following the start time
*
*/
MStatus initialStateNode::computeFirstState(const MPlug& plug, MDataBlock& datablock) {
Print() << "computeFirstState()\n";
MStatus status { MS::kUnknownParameter };
_outputMatrix = _restMatrix;
_velocity = { 0, 0, 0 };
_isFirstTime = false;
stepSimulation(datablock);
writeOutputTranslate(datablock);
datablock.setClean(plug);
return status;
}
/**
* Following the first frame
*
*/
MStatus initialStateNode::computeCurrentState(const MPlug& plug, MDataBlock& datablock) {
Print() << "computeCurrentState()\n";
MStatus status { MS::kSuccess };
stepSimulation(datablock);
writeOutputTranslate(datablock);
datablock.setClean(plug);
return status;
}
void initialStateNode::saveRestMatrix(MMatrix mat) {
_drawWorldMatrix = mat;
}
/**
* Apply gravity and update position
*
*/
MStatus initialStateNode::stepSimulation(MDataBlock& datablock) {
MStatus status { MS::kSuccess };
MTransformationMatrix tm { _outputMatrix };
if (_deltaSeconds > 0.0) {
MVector gravity = datablock.inputValue(aGravity, &status).asDouble3();
_velocity += gravity * _deltaSeconds;
tm.addTranslation(_velocity, MSpace::kTransform);
}
_outputMatrix = tm.asMatrix();
return status;
}
MStatus initialStateNode::writeOutputTranslate(MDataBlock& datablock) {
MStatus status { MS::kSuccess };
MTransformationMatrix tm { _outputMatrix };
MVector translate = tm.translation(MSpace::kTransform);
MDataHandle tx = datablock.outputValue(aOutputTranslateX),
ty = datablock.outputValue(aOutputTranslateY),
tz = datablock.outputValue(aOutputTranslateZ);
tx.set(translate.x);
ty.set(translate.y);
tz.set(translate.z);
return status;
}
MStatus initialStateNode::compute(const MPlug& plug, MDataBlock& datablock) {
MStatus status { MS::kUnknownParameter };
const MTime currentTime = datablock.inputValue(aCurrentTime).asTime();
const MTime startTime = datablock.inputValue(aStartTime).asTime();
_currentTime = currentTime;
_isStartTime = currentTime <= startTime;
_deltaSeconds = (_currentTime - _lastTime).as(MTime::kSeconds);
if (plug == aOutputTranslate ||
plug == aOutputTranslateX ||
plug == aOutputTranslateY ||
plug == aOutputTranslateZ) {
if (_isStartTime) status = computeStartState(plug, datablock);
else if (_isFirstTime) status = computeFirstState(plug, datablock);
else status = computeCurrentState(plug, datablock);
}
_lastTime = currentTime;
_deltaSeconds = 0.0;
return status;
}
/**
* Read from whatever matrix rendering uses, and pass it back into our node.
*
*
*
*/
class initialStateNodeDrawOverride : public MHWRender::MPxDrawOverride {
public:
static MHWRender::MPxDrawOverride* Creator(const MObject& obj) { return new initialStateNodeDrawOverride(obj); }
~initialStateNodeDrawOverride() override { _node = nullptr; }
MUserData* prepareForDraw(const MDagPath&,
const MDagPath&,
const MHWRender::MFrameContext&,
MUserData*) override { return nullptr; }
void addUIDrawables(const MDagPath&,
MHWRender::MUIDrawManager&,
const MHWRender::MFrameContext&,
const MUserData*) override;
// Constant values
MHWRender::DrawAPI supportedDrawAPIs() const override { return MHWRender::kDirectX11 | MHWRender::kOpenGLCoreProfile; }
bool isBounded(const MDagPath&, const MDagPath&) const override { return false; }
bool hasUIDrawables() const override { return true; }
private:
initialStateNodeDrawOverride(const MObject&);
initialStateNode* _node;
};
initialStateNodeDrawOverride::initialStateNodeDrawOverride(const MObject& obj)
: MHWRender::MPxDrawOverride(obj, nullptr, true), _node(nullptr) {
MStatus status;
MFnDependencyNode fn(obj, &status);
// Maintain handle to the parent fn
if (status) {
_node = dynamic_cast<initialStateNode*>(fn.userNode());
}
}
void initialStateNodeDrawOverride::addUIDrawables(const MDagPath& objPath,
MHWRender::MUIDrawManager&,
const MHWRender::MFrameContext&,
const MUserData*) {
MObject obj = objPath.node();
MPlug inWorldMatrixPlug { obj, initialStateNode::aInWorldMatrix };
MObject inWorldMatrixObj = inWorldMatrixPlug.asMObject();
MMatrix inWorldMatrix = MFnMatrixData(inWorldMatrixObj).matrix();
_node->saveRestMatrix(inWorldMatrix);
}
MStatus initializePlugin(MObject obj) {
MStatus status;
MFnPlugin plugin(obj, PLUGIN_COMPANY, "3.0", "Any");
status = plugin.registerNode("initialStateNode",
initialStateNode::id,
initialStateNode::creator,
initialStateNode::initialize,
MPxNode::kLocatorNode,
&initialStateNode::drawDbClassification);
if (!status) {
status.perror("registerNode");
return status;
}
MHWRender::MDrawRegistry::registerDrawOverrideCreator(
initialStateNode::drawDbClassification,
initialStateNode::drawRegistrantId,
initialStateNodeDrawOverride::Creator
);
Print() << "==============\n";
Print() << "Initialising\n";
return status;
}
MStatus uninitializePlugin(MObject obj) {
MStatus status;
MFnPlugin plugin(obj);
MHWRender::MDrawRegistry::deregisterDrawOverrideCreator(
initialStateNode::drawDbClassification,
initialStateNode::drawRegistrantId
);
status = plugin.deregisterNode(initialStateNode::id);
if (!status) {
status.perror("deregisterNode");
return status;
}
Print() << "Deinitialising\n";
Print() << "==============\n";
return status;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment