|
#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; |
|
} |