|
/** |
|
* Individual rigid body |
|
* |
|
*/ |
|
class RigidNode : public MPxNode { |
|
public: |
|
RigidNode() {}; |
|
~RigidNode() override; |
|
|
|
MStatus compute(const MPlug&, MDataBlock&) override; |
|
MStatus computeUid(const MPlug&, MDataBlock&); |
|
MStatus computeStartState(const MPlug&, MDataBlock&); |
|
MStatus computeCurrentState(const MPlug&, MDataBlock&); |
|
MStatus computeOutputTranslate(const MPlug&, MDataBlock&); |
|
|
|
static void* creator() { return new RigidNode(); } |
|
static MStatus initialize(); |
|
void postConstructor(); |
|
void reset(MDataBlock&); |
|
|
|
MStatus setDependentsDirty(const MPlug&, MPlugArray&) override; |
|
bool isPassiveOutput(const MPlug&) const override; |
|
|
|
public: |
|
static MString name; |
|
static MTypeId id; |
|
static const MString drawDbClassification; |
|
static const MString drawRegistrantId; |
|
|
|
// Attributes |
|
static MObject aUid; |
|
static MObject aStartTime; |
|
static MObject aCurrentTime; |
|
static MObject aInputMatrix; |
|
static MObject aInputParentInverseMatrix; |
|
static MObject aOutputTranslate; |
|
static MObject aOutputTranslateX; |
|
static MObject aOutputTranslateY; |
|
static MObject aOutputTranslateZ; |
|
|
|
// <-- Pull on this to fetch latest goods from the solver |
|
static MObject aNextState; |
|
|
|
// --> Deliver initial state of this rigid to the solver |
|
static MObject aStartState; |
|
|
|
// --> Deliver animated values, like damping, to the solver |
|
static MObject aCurrentState; |
|
|
|
private: |
|
int _uid { -1 }; |
|
bool _firstFrame { false }; |
|
MCallbackIdArray _callbackIds; |
|
}; |
|
|
|
|
|
MString RigidNode::name("rigidNode"); |
|
MTypeId RigidNode::id(0x80012); |
|
const MString RigidNode::drawDbClassification("drawdb/geometry/RigidNode"); |
|
const MString RigidNode::drawRegistrantId("RigidNodePlugin"); |
|
MObject RigidNode::aUid; |
|
MObject RigidNode::aInputMatrix; |
|
MObject RigidNode::aInputParentInverseMatrix; |
|
MObject RigidNode::aOutputTranslate; |
|
MObject RigidNode::aOutputTranslateX; |
|
MObject RigidNode::aOutputTranslateY; |
|
MObject RigidNode::aOutputTranslateZ; |
|
MObject RigidNode::aStartTime; |
|
MObject RigidNode::aCurrentTime; |
|
MObject RigidNode::aNextState; |
|
MObject RigidNode::aStartState; |
|
MObject RigidNode::aCurrentState; |
|
|
|
|
|
|
|
RigidNode::~RigidNode() { |
|
// Any node created will have an entry in this map |
|
gRigids.erase(_uid); |
|
} |
|
|
|
|
|
MStatus RigidNode::initialize() { |
|
MStatus status; |
|
MFnNumericAttribute numFn; |
|
MFnMatrixAttribute matFn; |
|
MFnUnitAttribute uniFn; |
|
|
|
aUid = numFn.create("uid", "uid", MFnNumericData::kInt, -1, &status); |
|
numFn.setWritable(false); |
|
numFn.setStorable(false); |
|
numFn.setKeyable(true); |
|
|
|
aNextState = numFn.create("nextState", "nest", MFnNumericData::kInt, -1, &status); |
|
numFn.setStorable(false); |
|
numFn.setReadable(false); |
|
|
|
aStartState = numFn.create("startState", "stst", MFnNumericData::kInt, -1, &status); |
|
numFn.setStorable(false); |
|
numFn.setWritable(false); |
|
|
|
aCurrentState = numFn.create("currentState", "cust", MFnNumericData::kInt, -1, &status); |
|
numFn.setStorable(false); |
|
numFn.setWritable(false); |
|
|
|
aCurrentTime = uniFn.create("currentTime", "cuti", MFnUnitAttribute::kTime, 0.0, &status); |
|
uniFn.setKeyable(true); |
|
|
|
aInputMatrix = matFn.create("inMatrix", "inma"); |
|
matFn.setStorable(false); |
|
matFn.setReadable(false); |
|
aInputParentInverseMatrix = matFn.create("inParentInverseMatrix", "ipim"); |
|
matFn.setStorable(false); |
|
matFn.setReadable(false); |
|
|
|
aOutputTranslateX = uniFn.create("outputTranslateX", "outx", MFnUnitAttribute::kDistance, 0.0, &status); |
|
uniFn.setWritable(false); |
|
uniFn.setStorable(false); |
|
uniFn.setChannelBox(true); |
|
aOutputTranslateY = uniFn.create("outputTranslateY", "outy", MFnUnitAttribute::kDistance, 0.0, &status); |
|
uniFn.setWritable(false); |
|
uniFn.setStorable(false); |
|
uniFn.setChannelBox(true); |
|
aOutputTranslateZ = uniFn.create("outputTranslateZ", "outz", MFnUnitAttribute::kDistance, 0.0, &status); |
|
uniFn.setWritable(false); |
|
uniFn.setStorable(false); |
|
uniFn.setChannelBox(true); |
|
aOutputTranslate = numFn.create("outputTranslate", "outr", aOutputTranslateX, aOutputTranslateY, aOutputTranslateZ); |
|
|
|
AddAttribute(aUid); |
|
AddAttribute(aNextState); |
|
AddAttribute(aStartState); |
|
AddAttribute(aCurrentState); |
|
AddAttribute(aCurrentTime); |
|
AddAttribute(aInputMatrix); |
|
AddAttribute(aInputParentInverseMatrix); |
|
AddAttribute(aOutputTranslate); |
|
|
|
// Affects initial state |
|
AttributeAffects(aInputMatrix, aStartState); |
|
AttributeAffects(aInputParentInverseMatrix, aStartState); |
|
|
|
// Affects simulation |
|
AttributeAffects(aCurrentTime, aStartState); |
|
AttributeAffects(aCurrentTime, aCurrentState); |
|
AttributeAffects(aCurrentTime, aOutputTranslate); |
|
AttributeAffects(aNextState, aOutputTranslate); |
|
|
|
return status; |
|
} |
|
|
|
|
|
bool RigidNode::isPassiveOutput(const MPlug& plug) const { |
|
/** |
|
* Let anything connected to these still be editable |
|
* |
|
*/ |
|
return plug == aOutputTranslateX || |
|
plug == aOutputTranslateY || |
|
plug == aOutputTranslateZ; |
|
} |
|
|
|
|
|
MStatus RigidNode::setDependentsDirty(const MPlug& plugBeingDirtied, MPlugArray& affectedPlugs) { |
|
return MPxNode::setDependentsDirty(plugBeingDirtied, affectedPlugs); |
|
} |
|
|
|
|
|
void RigidNode::postConstructor() { |
|
MStatus status { MS::kSuccess }; |
|
|
|
// Find ourselves amongst rigids hosted by the solver |
|
_uid = ++gUniqueIdCounter; |
|
|
|
// Initialise our data, in a place accessible to our solver |
|
gRigids[_uid] = { _uid }; |
|
} |
|
|
|
|
|
void RigidNode::reset(MDataBlock& datablock) { |
|
RigidBody& rigid = gRigids.at(_uid); |
|
rigid.restMatrix = InputValue(datablock, aInputMatrix).asMatrix(); |
|
rigid.parentInverseMatrix = InputValue(datablock, aInputParentInverseMatrix).asMatrix(); |
|
rigid.worldMatrix = rigid.restMatrix * rigid.parentInverseMatrix.inverse(); |
|
rigid.velocity = {0, 0, 0}; |
|
|
|
Print() << "RigidNode: Rigid reset @ " << Translate(rigid.restMatrix) << "\n"; |
|
} |
|
|
|
|
|
/** |
|
* Restore rest matrix |
|
* |
|
*/ |
|
MStatus RigidNode::computeStartState(const MPlug& plug, MDataBlock& datablock) { |
|
Print() << "RigidNode::computeStartState() ===============\n"; |
|
MStatus status { MS::kSuccess }; |
|
|
|
RigidBody& rigid = gRigids.at(_uid); |
|
rigid.worldMatrix = rigid.restMatrix * rigid.parentInverseMatrix.inverse(); |
|
rigid.velocity = {0, 0, 0}; |
|
|
|
OutputValue(datablock, aStartState).set(_uid); |
|
|
|
datablock.setClean(plug); |
|
|
|
_firstFrame = true; |
|
|
|
return status; |
|
} |
|
|
|
|
|
/** |
|
* Read values that change over time |
|
* |
|
* We don't really have any, but for example linearDamping |
|
* |
|
*/ |
|
MStatus RigidNode::computeCurrentState(const MPlug& plug, MDataBlock& datablock) { |
|
Print() << "RigidNode::computeCurrentState()\n"; |
|
MStatus status { MS::kSuccess }; |
|
|
|
if (_firstFrame) { |
|
reset(datablock); |
|
_firstFrame = false; |
|
} |
|
|
|
datablock.setClean(plug); |
|
return status; |
|
} |
|
|
|
|
|
/** |
|
* Following the first frame |
|
* |
|
*/ |
|
MStatus RigidNode::computeOutputTranslate(const MPlug& plug, MDataBlock& datablock) { |
|
Print() << "RigidNode::computeOutputTranslate()\n"; |
|
|
|
MStatus status { MS::kSuccess }; |
|
|
|
// Pull and let the SolverNode go to work.. |
|
int changed = InputValue(datablock, aNextState).asInt(); |
|
|
|
// . |
|
// .. |
|
// ... |
|
// aaaaand we're back. |
|
|
|
// E.g. start frame hasn't yet been reached, time was dirty but not different, etc. |
|
if (changed < 0) { |
|
Print() << "RigidNode: Solver unchanged\n"; |
|
return MS::kFailure; |
|
} |
|
|
|
// At this point, the solver will have pulled on either.. |
|
// |
|
// - RigidNode::computeStartState |
|
// - RigidNode::computeCurrentState |
|
// |
|
// ..depending on what time it was. They will in turn have populated |
|
// our translate values with the latest up-to-date simulated values |
|
// |
|
|
|
RigidBody& rigid = gRigids.at(_uid); |
|
|
|
// Preserve hierarchy when manipulating an object during standstill |
|
// NOTE: Only relevant during parallel, because.. |
|
// ..DG doesn't trigger evaluation during manipulation..?? |
|
if (rigid.worldMatrixChanged) { |
|
rigid.parentInverseMatrix = InputValue(datablock, aInputParentInverseMatrix).asMatrix(); |
|
rigid.worldMatrixChanged = false; |
|
} |
|
|
|
MMatrix localMatrix = rigid.worldMatrix * rigid.parentInverseMatrix; |
|
|
|
MTransformationMatrix tm { localMatrix }; |
|
MVector translate = tm.translation(MSpace::kTransform); |
|
MDataHandle tx = OutputValue(datablock, aOutputTranslateX), |
|
ty = OutputValue(datablock, aOutputTranslateY), |
|
tz = OutputValue(datablock, aOutputTranslateZ); |
|
|
|
tx.set(translate.x); |
|
ty.set(translate.y); |
|
tz.set(translate.z); |
|
|
|
datablock.setClean(plug); |
|
|
|
Print() << "RigidNode: " << translate << " written successfully!\n"; |
|
|
|
return status; |
|
} |
|
|
|
|
|
MStatus RigidNode::computeUid(const MPlug& plug, MDataBlock& datablock) { |
|
Print() << "RigidNode::computeUid()\n"; |
|
MStatus status { MS::kSuccess }; |
|
|
|
// We'll use this from the solver mostly to sanity-check |
|
// that we're talking to the rigid we think we're talking to. |
|
MDataHandle outputHandle = OutputValue(datablock, aUid); |
|
outputHandle.setInt(_uid); |
|
|
|
datablock.setClean(plug); |
|
|
|
return status; |
|
} |
|
|
|
|
|
MStatus RigidNode::compute(const MPlug& plug, MDataBlock& datablock) { |
|
MStatus status { MS::kUnknownParameter }; |
|
|
|
// For some reason, we need to pull on time, even though we don't need it |
|
// in order for its dependents to continue to be dirtied when it changes. |
|
// The alternative is having it drawn in the channel box or AE. |
|
InputValue(datablock, aCurrentTime).asTime(); |
|
|
|
if (plug == aStartState) { |
|
status = computeStartState(plug, datablock); |
|
} |
|
|
|
else if (plug == aCurrentState) { |
|
status = computeCurrentState(plug, datablock); |
|
} |
|
|
|
else if (plug == aOutputTranslate || |
|
plug == aOutputTranslateX || |
|
plug == aOutputTranslateY || |
|
plug == aOutputTranslateZ) { |
|
status = computeOutputTranslate(plug, datablock); |
|
} |
|
|
|
else if (plug == aUid) { |
|
status = computeUid(plug, datablock); |
|
} |
|
|
|
return status; |
|
} |