Skip to content

Instantly share code, notes, and snippets.

@mottosso
Last active February 18, 2021 13:42
Show Gist options
  • Save mottosso/3dd7f9a0889b1a9bbcad8bc688d6550a to your computer and use it in GitHub Desktop.
Save mottosso/3dd7f9a0889b1a9bbcad8bc688d6550a to your computer and use it in GitHub Desktop.
Editable Initial State - Attempt 10

⬅️ Back

In the last attempt we learnt that we can pull .matrix whilst evaluating .translateX, but wasn't able to account for hierarchy.

So the question is; can we also pull on .parentMatrix whilst evaluating .translateX?

Test Status
Manipulator ✔️
Channel Box ✔️
Keyframe ✔️
Auto-keyframe ✔️
Hierarchy ✔️
Parented to each other

initialstatehierarchy

By the looks of it, yes!. This makes sense to me. Maya will natively evaluate nodes in the order they are parented, starting with the top-most parent. By the time it gets to the child, the parent plugs - including its worldMatrix - should already be evaluated and thus clean. So once we ask for it, we're just getting a copy of that, without triggering another evaluation.

The last piece of the puzzle is when rigid bodies are parented to each other.


Build & Run

From Powershell.

cd c:\
git clone https://gist.github.com/3dd7f9a0889b1a9bbcad8bc688d6550a.git initialStateNode10
mkdir initialStateNode10/build
cd initialStateNode10/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:\initialState10\build\initialStateNode.mll"
cmds.loadPlugin(fname)

parent = cmds.spaceLocator(name="parent")
cmds.move(-3, 15, -7)
cmds.rotate(15, 30, 30)

time = "time1"
cube, _ = cmds.polyCube()
cmds.parent(cube, parent, relative=True)
cmds.move(3, 0, 0, relative=True)

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 + ".matrix", node + ".inMatrix")
cmds.connectAttr(cube + ".parentMatrix", node + ".inParentMatrix")
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/MPxNode.h>
#include <maya/MFnNumericAttribute.h>
#include <maya/MFnMatrixAttribute.h>
#include <maya/MFnUnitAttribute.h>
#include <maya/MFnPlugin.h>
#include <maya/MTypeId.h>
#include <maya/MPlug.h>
#include <maya/MString.h>
#include <maya/MVector.h>
#include <maya/MTransformationMatrix.h>
#include <maya/MMatrix.h>
#include <maya/MDataBlock.h>
#include <maya/MDataHandle.h>
#include <maya/MStreamUtils.h>
#include <maya/MNodeMessage.h>
#include <maya/MFnDependencyNode.h>
#include <maya/MFnMatrixData.h>
#include <maya/MCallbackIdArray.h>
#define Print MStreamUtils::stdErrorStream
class initialStateNode : public MPxNode {
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 computeNextState(const MPlug& plug, MDataBlock& data);
MStatus writeOutputTranslate(MDataBlock& datablock);
MStatus stepSimulation(MDataBlock& datablock);
void setMatrix(MMatrix mat) { _restMatrix = mat; }
static void* creator() { return new initialStateNode(); }
static MStatus initialize();
void postConstructor();
bool isPassiveOutput(const MPlug& plug) const override;
public:
static MTypeId id;
// Attributes
static MObject aStartTime;
static MObject aCurrentTime;
static MObject aGravity;
static MObject aGravityX;
static MObject aGravityY;
static MObject aGravityZ;
static MObject aInMatrix;
static MObject aInParentMatrix;
static MObject aOutputTranslate;
static MObject aOutputTranslateX;
static MObject aOutputTranslateY;
static MObject aOutputTranslateZ;
private:
bool _isStartTime { false };
bool _isFirstTime { false };
MTime _lastTime { };
MTime _currentTime { };
double _deltaTime { 0.0 };
MString _name { };
MMatrix _localMatrix {};
MMatrix _worldMatrix {};
MMatrix _restMatrix {};
MVector _velocity { 0, 0, 0 };
MCallbackIdArray _callbackIds;
};
MTypeId initialStateNode::id(0x80012);
MObject initialStateNode::aInMatrix;
MObject initialStateNode::aInParentMatrix;
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;
#define Translate(mat) MTransformationMatrix(mat).translation(MSpace::kTransform);
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);
uniFn.setKeyable(true);
aCurrentTime = uniFn.create("currentTime", "cuti", MFnUnitAttribute::kTime, 0.0, &status);
uniFn.setKeyable(true);
aInMatrix = matFn.create("inMatrix", "inma");
aInParentMatrix = matFn.create("inParentMatrix", "inpm");
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);
numFn.setKeyable(true);
addAttribute(aStartTime);
addAttribute(aCurrentTime);
addAttribute(aInMatrix);
addAttribute(aInParentMatrix);
addAttribute(aOutputTranslate);
addAttribute(aGravity);
attributeAffects(aGravity, aOutputTranslate);
attributeAffects(aCurrentTime, aOutputTranslate);
return MS::kSuccess;
}
/**
* Because we don't pull on the input matrix at the start frame
* we aren't able to store the original values from the input matrix.
* To cope with that, we read those whenever anything connects to it
*
*/
void onAttributeChanged(MNodeMessage::AttributeMessage msg, MPlug& plug, MPlug& otherPlug, void* clientData) {
if (msg & MNodeMessage::kConnectionMade) {
if (plug == initialStateNode::aInMatrix) {
auto* node = static_cast<initialStateNode*>(clientData);
MObject obj = otherPlug.asMObject();
node->setMatrix(MFnMatrixData(obj).matrix());
}
}
}
void initialStateNode::postConstructor() {
MStatus status { MS::kSuccess };
// Bypass warning; below callback takes a reference,
// even though it doesn't care for actually keeping
// it around. Bad design.
MObject tempobj { this->thisMObject() };
_callbackIds.append(
MNodeMessage::addAttributeChangedCallback(
tempobj, onAttributeChanged, this, &status
)
);
}
/**
* Restore rest matrix
*
*/
MStatus initialStateNode::computeStartState(const MPlug& plug, MDataBlock& datablock) {
MFnDependencyNode fn { thisMObject() };
_name = fn.name();
Print() << _name << ".computeStartState()\n";
MStatus status { MS::kSuccess };
_localMatrix = _restMatrix;
_isFirstTime = true;
writeOutputTranslate(datablock);
datablock.setClean(plug);
return status;
}
/**
* Following the start time
*
*/
MStatus initialStateNode::computeFirstState(const MPlug& plug, MDataBlock& datablock) {
Print() << " " << _name << ".computeFirstState()\n";
MStatus status { MS::kUnknownParameter };
MMatrix matrix = datablock.inputValue(aInMatrix).asMatrix();
MMatrix parentMatrix = datablock.inputValue(aInParentMatrix).asMatrix();
_restMatrix = matrix;
_worldMatrix = matrix * parentMatrix;
_velocity = { 0, 0, 0 };
_isFirstTime = false;
stepSimulation(datablock);
writeOutputTranslate(datablock);
datablock.setClean(plug);
return status;
}
/**
* Following the first frame
*
*/
MStatus initialStateNode::computeNextState(const MPlug& plug, MDataBlock& datablock) {
Print() << " " << _name << ".computeCurrentState()\n";
MStatus status { MS::kSuccess };
stepSimulation(datablock);
writeOutputTranslate(datablock);
datablock.setClean(plug);
return status;
}
/**
* Apply gravity and update position
*
*/
MStatus initialStateNode::stepSimulation(MDataBlock& datablock) {
MStatus status { MS::kSuccess };
MTransformationMatrix tm { _worldMatrix };
if (_deltaTime > 0.0) {
MVector gravity = datablock.inputValue(aGravity, &status).asDouble3();
_velocity += gravity * _deltaTime;
tm.addTranslation(_velocity, MSpace::kTransform);
}
_worldMatrix = tm.asMatrix();
MMatrix parentMatrix = datablock.inputValue(aInParentMatrix).asMatrix();
_localMatrix = _worldMatrix * parentMatrix.inverse();
return status;
}
MStatus initialStateNode::writeOutputTranslate(MDataBlock& datablock) {
MStatus status { MS::kSuccess };
MMatrix localMatrix = _localMatrix;
MTransformationMatrix tm { localMatrix };
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;
_deltaTime = (_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 = computeNextState(plug, datablock);
}
_lastTime = currentTime;
return status;
}
MStatus initializePlugin(MObject obj) {
MStatus status;
MFnPlugin plugin(obj, PLUGIN_COMPANY, "3.0", "Any");
status = plugin.registerNode("initialStateNode",
initialStateNode::id,
initialStateNode::creator,
initialStateNode::initialize);
if (!status) {
status.perror("registerNode");
return status;
}
Print() << "==============\n";
Print() << "Initialising\n";
return status;
}
MStatus uninitializePlugin(MObject obj) {
MStatus status;
MFnPlugin plugin(obj);
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