|
/** |
|
* Pull on all rigid at once, and solve them in one big batch. |
|
* |
|
*/ |
|
|
|
class SolverNode : public MPxLocatorNode { |
|
public: |
|
SolverNode() {}; |
|
~SolverNode() override; |
|
|
|
static void* creator() { return new SolverNode(); } |
|
static MStatus initialize(); |
|
MStatus compute(const MPlug& plug, MDataBlock& data) override; |
|
MStatus computeOutputRigids(const MPlug& plug, MDataBlock& data); |
|
MStatus addRigids(const MPlug& plug, MDataBlock& datablock); |
|
MStatus updateRigids(const MPlug& plug, MDataBlock& datablock); |
|
MStatus setDependentsDirty(const MPlug&, MPlugArray&) override; |
|
void postConstructor() override; |
|
|
|
MStatus stepSimulation(MDataBlock& datablock); |
|
|
|
public: |
|
static MString name; |
|
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; |
|
|
|
// Initial state of each rigid, e.g. position and orientation |
|
static MObject aStartState; |
|
|
|
// Current values of rigid plugs, e.g. damping |
|
static MObject aCurrentState; |
|
|
|
// Resulting transforms after simulation |
|
static MObject aNextState; |
|
|
|
private: |
|
bool _isStartTime { false }; |
|
bool _beforeStartTime { false }; |
|
bool _isFirstTime { false }; |
|
MTime _lastTime { }; |
|
MTime _currentTime { }; |
|
double _deltaTime { 0.0 }; |
|
|
|
// 0-based time |
|
int _startFrame { }; |
|
int _currentFrame { }; |
|
int _simulationFrame { 0 }; |
|
MCallbackIdArray _callbackIds; |
|
|
|
}; |
|
|
|
|
|
MString SolverNode::name("solverNode"); |
|
MTypeId SolverNode::id(0x80013); |
|
const MString SolverNode::drawDbClassification("drawdb/geometry/SolverNode"); |
|
const MString SolverNode::drawRegistrantId("SolverNodePlugin"); |
|
MObject SolverNode::aCurrentState; |
|
MObject SolverNode::aStartState; |
|
MObject SolverNode::aNextState; |
|
MObject SolverNode::aStartTime; |
|
MObject SolverNode::aCurrentTime; |
|
MObject SolverNode::aGravity; |
|
MObject SolverNode::aGravityX; |
|
MObject SolverNode::aGravityY; |
|
MObject SolverNode::aGravityZ; |
|
|
|
|
|
/** |
|
* To help separate print statements in the Output Window |
|
* |
|
*/ |
|
static void onForceUpdate(MTime& time, void* clientData) { |
|
SolverNode* node = static_cast<SolverNode*>(clientData); |
|
Print() << "-------------------------------------------------------------------\n"; |
|
Print() << "(!) onForceUpdate() " << time.value() << "\n"; |
|
Print() << "-------------------------------------------------------------------\n"; |
|
} |
|
|
|
|
|
SolverNode::~SolverNode() { |
|
for (unsigned int ii=0; ii<_callbackIds.length(); ii++) |
|
MMessage::removeCallback(_callbackIds[ii]); |
|
|
|
_callbackIds.clear(); |
|
} |
|
|
|
MStatus SolverNode::initialize() { |
|
MStatus status; |
|
MFnNumericAttribute numFn; |
|
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); |
|
|
|
aStartState = numFn.create("startState", "stst", MFnNumericData::kInt, -1, &status); |
|
numFn.setStorable(false); |
|
numFn.setReadable(true); |
|
numFn.setWritable(true); |
|
numFn.setArray(true); |
|
numFn.setDisconnectBehavior(MFnUnitAttribute::kDelete); |
|
|
|
aCurrentState = numFn.create("currentState", "cust", MFnNumericData::kInt, -1, &status); |
|
numFn.setStorable(false); |
|
numFn.setReadable(true); |
|
numFn.setWritable(true); |
|
numFn.setArray(true); |
|
numFn.setDisconnectBehavior(MFnUnitAttribute::kDelete); |
|
|
|
aNextState = numFn.create("nextState", "nest", MFnNumericData::kInt, -1, &status); |
|
numFn.setStorable(false); |
|
numFn.setWritable(false); |
|
numFn.setArray(true); |
|
numFn.setDisconnectBehavior(MFnUnitAttribute::kDelete); |
|
|
|
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(aGravity); |
|
AddAttribute(aCurrentState); |
|
AddAttribute(aStartState); |
|
AddAttribute(aNextState); |
|
|
|
AttributeAffects(aStartTime, aNextState); |
|
AttributeAffects(aCurrentTime, aNextState); |
|
AttributeAffects(aGravity, aNextState); |
|
AttributeAffects(aStartState, aNextState); |
|
AttributeAffects(aCurrentState, aNextState); |
|
|
|
return status; |
|
} |
|
|
|
void SolverNode::postConstructor() { |
|
MStatus status; |
|
|
|
/** |
|
* We aren't actually using this, but it's interesting to see |
|
* in what order this is called relative nextState etc. |
|
* |
|
*/ |
|
_callbackIds.append( |
|
MDGMessage::addForceUpdateCallback(onForceUpdate, this, &status) |
|
); |
|
assert(status == MS::kSuccess); |
|
} |
|
|
|
|
|
MStatus SolverNode::setDependentsDirty(const MPlug& plugBeingDirtied, MPlugArray& affectedPlugs) { |
|
return MPxLocatorNode::setDependentsDirty(plugBeingDirtied, affectedPlugs); |
|
} |
|
|
|
|
|
MStatus SolverNode::compute(const MPlug& plug, MDataBlock& datablock) { |
|
MStatus status { MS::kUnknownParameter }; |
|
|
|
if (plug == aNextState && plug.isElement()){ |
|
const MTime currentTime = InputValue(datablock, aCurrentTime).asTime(); |
|
const MTime startTime = InputValue(datablock, aStartTime).asTime(); |
|
|
|
_currentTime = currentTime; |
|
_startFrame = static_cast<int>(startTime.value()); |
|
_currentFrame = static_cast<int>(currentTime.value()) - _startFrame; |
|
_deltaTime = (_currentTime - _lastTime).as(MTime::kSeconds); |
|
_isFirstTime = _isStartTime && _deltaTime > 0.0; |
|
_isStartTime = currentTime == startTime; |
|
_beforeStartTime = currentTime < startTime; |
|
|
|
status = computeOutputRigids(plug, datablock); |
|
|
|
_lastTime = currentTime; |
|
} |
|
|
|
return status; |
|
} |
|
|
|
|
|
MStatus SolverNode::computeOutputRigids(const MPlug& plug, MDataBlock& datablock) { |
|
if (debug()) Print() << "SolverNode::computeOutputRigids()\n"; |
|
MStatus status { MS::kSuccess }; |
|
|
|
auto noChange = [&]() { |
|
MStatus status; |
|
|
|
const int index = plug.logicalIndex(&status); |
|
assert(status == MS::kSuccess); |
|
|
|
MArrayDataHandle nextStateHandle = datablock.outputArrayValue(aNextState, &status); |
|
assert(status == MS::kSuccess); |
|
|
|
nextStateHandle.jumpToElement(index); |
|
MDataHandle outputHandle = nextStateHandle.outputValue(&status); |
|
assert(status == MS::kSuccess); |
|
|
|
outputHandle.setInt(_currentFrame); |
|
}; |
|
|
|
if (_beforeStartTime) { |
|
noChange(); |
|
} |
|
|
|
else if (_isStartTime) { |
|
status = addRigids(plug, datablock); |
|
_simulationFrame = 0; |
|
|
|
} |
|
|
|
else if (_isFirstTime) { |
|
status = updateRigids(plug, datablock); |
|
} |
|
|
|
else if (_deltaTime > 0.0) { |
|
status = updateRigids(plug, datablock); |
|
} |
|
|
|
else { |
|
noChange(); |
|
} |
|
|
|
assert(status == MS::kSuccess); |
|
|
|
datablock.setClean(plug); |
|
|
|
return status; |
|
} |
|
|
|
|
|
MStatus SolverNode::addRigids(const MPlug& plug, MDataBlock& datablock) { |
|
MStatus status { MS::kUnknownParameter }; |
|
if (debug()) Print() << "SolverNode::addRigids()\n"; |
|
|
|
// In DG mode, evaluation never happens on frames previously |
|
// rendered until this is called. Madness. Why oh why? |
|
Print() << "ogs -reset\n"; |
|
MGlobal::executeCommand("ogs -reset"); |
|
|
|
// Pull on *all* startStates |
|
MArrayDataHandle startStateHandle = datablock.inputArrayValue(aStartState, &status); |
|
assert(status == MS::kSuccess); |
|
|
|
MArrayDataHandle nextStateHandle = datablock.outputArrayValue(aNextState, &status); |
|
assert(status == MS::kSuccess); |
|
|
|
const int index = plug.logicalIndex(&status); |
|
assert(status == MS::kSuccess); |
|
|
|
// Only consider rigids that are still connected |
|
MPlug startStatePlug { thisMObject(), aStartState }; |
|
MPlug elementPlug = startStatePlug.elementByLogicalIndex(index, &status); |
|
assert(status == MS::kSuccess); |
|
|
|
if (!elementPlug.isConnected()) { |
|
Print() << "SolverNode: Disconnected index: " << index << "\n"; |
|
return MS::kFailure; |
|
} |
|
|
|
status = startStateHandle.jumpToElement(index); |
|
assert(status == MS::kSuccess); |
|
status = nextStateHandle.jumpToElement(index); |
|
assert(status == MS::kSuccess); |
|
|
|
// Pull on rigid.outputStartState |
|
int uid = startStateHandle.inputValue(&status).asInt(); |
|
assert(status == MS::kSuccess && uid >= 0); |
|
|
|
Print() << "SolverNode: Solver pulled on solver.index[" << index << "] --> rigid.next[" << uid << "]\n"; |
|
|
|
// Changed |
|
MDataHandle outputHandle = nextStateHandle.outputValue(&status); |
|
assert(status == MS::kSuccess); |
|
outputHandle.setInt(_currentFrame); |
|
|
|
return status; |
|
} |
|
|
|
|
|
MStatus SolverNode::updateRigids(const MPlug& plug, MDataBlock& datablock) { |
|
if (debug()) Print() << "SolverNode::updateRigids()\n"; |
|
MStatus status { MS::kSuccess }; |
|
|
|
const int index = plug.logicalIndex(&status); |
|
assert(status == MS::kSuccess); |
|
|
|
// Pull on *all* currentStates |
|
MArrayDataHandle currentStateHandle = datablock.inputArrayValue(aCurrentState, &status); |
|
assert(status == MS::kSuccess); |
|
|
|
MArrayDataHandle nextStateHandle = datablock.outputArrayValue(aNextState, &status); |
|
assert(status == MS::kSuccess); |
|
|
|
status = currentStateHandle.jumpToElement(index); |
|
assert(status == MS::kSuccess); |
|
status = nextStateHandle.jumpToElement(index); |
|
assert(status == MS::kSuccess); |
|
|
|
MDataHandle inputHandle = currentStateHandle.inputValue(&status); |
|
assert(status == MS::kSuccess); |
|
inputHandle.asInt(); |
|
|
|
if (_currentFrame > _simulationFrame) { |
|
stepSimulation(datablock); |
|
} |
|
|
|
// Output a "solver-changed" message (1) to Rigid |
|
MDataHandle outputHandle = nextStateHandle.outputValue(&status); |
|
assert(status == MS::kSuccess); |
|
outputHandle.setInt(_currentFrame); |
|
|
|
return status; |
|
} |
|
|
|
|
|
/** |
|
* Apply gravity and update position |
|
* |
|
*/ |
|
MStatus SolverNode::stepSimulation(MDataBlock& datablock) { |
|
if (debug()) Print() << "SolverNode::stepSimulation()\n"; |
|
MStatus status { MS::kSuccess }; |
|
MVector gravity = InputValue(datablock, aGravity).asDouble3(); |
|
|
|
for (auto& pair : gRigids) { |
|
RigidBody& rigid = pair.second; |
|
MTransformationMatrix tm { rigid.worldMatrix }; |
|
|
|
rigid.velocity += gravity * _deltaTime; |
|
tm.addTranslation(rigid.velocity, MSpace::kTransform); |
|
Print() << "SolverNode: rigid[" << rigid.uid << "].translate " << tm.translation(MSpace::kTransform) << "\n"; |
|
|
|
rigid.worldMatrix = tm.asMatrix(); |
|
rigid.worldMatrixChanged = true; |
|
} |
|
|
|
_simulationFrame = _currentFrame; |
|
|
|
return status; |
|
} |
|
|