|
#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/MMessage.h> |
|
#include <maya/MTypeId.h> |
|
#include <maya/MPlug.h> |
|
#include <maya/MVector.h> |
|
#include <maya/MTransformationMatrix.h> |
|
#include <maya/MFnMatrixData.h> |
|
#include <maya/MMatrix.h> |
|
#include <maya/MDataBlock.h> |
|
#include <maya/MDataHandle.h> |
|
#include <maya/MStreamUtils.h> |
|
#include <maya/MNodeMessage.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 computeCurrentState(const MPlug& plug, MDataBlock& data); |
|
MStatus writeOutputTranslate(MDataBlock& datablock); |
|
MStatus stepSimulation(MDataBlock& datablock); |
|
|
|
static void* creator() { return new initialStateNode(); } |
|
static MStatus initialize(); |
|
|
|
void postConstructor() override; |
|
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 aInWorldMatrix; |
|
static MObject aOutputTranslate; |
|
static MObject aOutputTranslateX; |
|
static MObject aOutputTranslateY; |
|
static MObject aOutputTranslateZ; |
|
|
|
private: |
|
bool _isStartTime { false }; |
|
MTime _lastTime { }; |
|
MTime _currentTime { }; |
|
MMatrix _restMatrix {}; |
|
MMatrix _outputMatrix {}; |
|
MVector _velocity { 0, 0, 0 }; |
|
MCallbackIdArray _callbackIds; |
|
bool _isFirstTime { false }; |
|
}; |
|
|
|
|
|
MTypeId initialStateNode::id(0x80012); |
|
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; |
|
|
|
initialStateNode::initialStateNode() {} |
|
initialStateNode::~initialStateNode() { |
|
|
|
for (unsigned int ii=0; ii<_callbackIds.length(); ii++) |
|
MMessage::removeCallback(_callbackIds[ii]); |
|
|
|
_callbackIds.clear(); |
|
} |
|
|
|
|
|
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); |
|
numFn.setWritable(false); |
|
numFn.setStorable(false); |
|
|
|
// numFn.setAffectsWorldSpace(true); // ! Causes crash |
|
|
|
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; |
|
} |
|
|
|
|
|
static void onPlugDirty(MObject& node, MPlug& plug, void* clientData) { |
|
if (plug == initialStateNode::aCurrentTime) { |
|
MPlug inWorldMatrix { node, initialStateNode::aInWorldMatrix }; |
|
|
|
// Just pull, results don't matter |
|
MObject obj = inWorldMatrix.asMObject(); |
|
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::addNodeDirtyPlugCallback( |
|
tempobj, onPlugDirty, nullptr, &status |
|
) |
|
); |
|
} |
|
|
|
|
|
/** |
|
* Read and store rest matrix |
|
* |
|
*/ |
|
MStatus initialStateNode::computeStartState(const MPlug& plug, MDataBlock& datablock) { |
|
Print() << "computeStartState()\n"; |
|
MStatus status { MS::kSuccess }; |
|
|
|
_outputMatrix = _restMatrix; |
|
_isFirstTime = true; |
|
|
|
writeOutputTranslate(datablock); |
|
|
|
datablock.setClean(plug); |
|
|
|
return status; |
|
} |
|
|
|
|
|
/** |
|
* Following the start time |
|
* |
|
*/ |
|
MStatus initialStateNode::computeFirstState(const MPlug& plug, MDataBlock& datablock) { |
|
Print() << " FirstState()\n"; |
|
MStatus status { MS::kUnknownParameter }; |
|
|
|
// Now we can trust that the value has definitely |
|
// been computed via the callback (?) |
|
_restMatrix = datablock.outputValue(aInWorldMatrix).asMatrix(); |
|
|
|
_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() << " CurrentState()\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 { _outputMatrix }; |
|
|
|
double deltaTime = (_currentTime - _lastTime).as(MTime::kSeconds); |
|
|
|
if (deltaTime > 0.0) { |
|
MVector gravity = datablock.inputValue(aGravity, &status).asDouble3(); |
|
_velocity += gravity * deltaTime; |
|
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); |
|
|
|
Print() << "<-- Writing: " << translate << "\n"; |
|
|
|
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; |
|
|
|
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; |
|
|
|
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; |
|
} |