Skip to content

Instantly share code, notes, and snippets.

@mottosso
Last active February 24, 2021 07:34
Show Gist options
  • Save mottosso/681edd78c71a449c0b520f220a49c1e9 to your computer and use it in GitHub Desktop.
Save mottosso/681edd78c71a449c0b520f220a49c1e9 to your computer and use it in GitHub Desktop.
Editable Initial State - Attempt 11

⬅️ Back

Putting it all together, I'll borrow from the Maya devkit examples testNsolverNode and testNobjectNode for an event loop

Table of contents

Source Code

Roughly 800 lines in total, including comments.

  • main.cpp Single translation unit, single output plug-in
  • rigid.hpp The rigid, separated into its own file for readability, but is #included into main.cpp
  • solver.hpp Also included, contains the solver part.

Architecture

Here's the intended evaluation from start to finish, except for Parallel which doesn't play by the rules.

  1. pCube is about to be rendered
  2. pCube1.translateX is evaluated
  3. rigid.outputTranslateX is pulled
  4. solver.nextState is pulled
  5. rigid.startState is pulled
  6. solver steps simulation, updates the position of all rigids
  7. rigid.outputTranslateX is clean
  8. pCube1.translateX is clean
  9. Finish

On subsequent frames, rigid.currentState is pulled instead, so we can handle initialisation once and only once.

solvergraph


DG

Almost works with only a single rigid, the cruxes are:

  1. Cycle on framestep (but not playback)
  2. Caches previous frames for some reason
  3. Reverts passive values whenever you edit them

DG, unlike Parallel, pulls on currentState and startState reliably and doesn't cycle when pulling pCube1.matrix during evaluation of pCube1.translateX. It does however cycle when you framestep, rather than playback. Unclear as to why.

Notes Test
Fundamentals works well, no cycle dgbasicsworkd
It for some reason pulls on startState whenever manipulation begins, which resets it to the previous position. I expect the passive values to take precedence. dgstartstate
Finally, a viewport bug. Doesn't happen in VP1. I've resorted to calling ogs -reset on the start frame in this example, which is bad. This only happens in DG, not parallel. dgcache

Parallel

Parallel just doesn't do as advertised. Why is startState pulled every frame? It doesn't cycle during playback, nor during frame step. But there is no way to initialise our data once, resulting in the velocity being zeroed out each time.

parallelbad


DG and Multiple Rigids

At this point, everything stops working.

dghierarchy

One of them works, sometimes. Notice how one of the translates snaps back and simulates from where it was?

dgnotalways

This tells me cycles are still happening even if not reported, and that the overall approach is flawed.


Parallel and Multiple Rigids

Parallel also cannot cope.

paralell


Build & Run

From Powershell.

cd c:\
git clone https://gist.github.com/681edd78c71a449c0b520f220a49c1e9.git initialStateNode12
mkdir initialStateNode12/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:\initialState12\build\initialStateNode.mll"
cmds.loadPlugin(fname)

time = "time1"
solver = cmds.createNode("solverNode", parent=cmds.createNode("transform", name="solver"))
cmds.setAttr(solver + ".gravity", 0, -9.81, 0)
cmds.setAttr(solver + ".startTime", cmds.getAttr(time + ".outTime"))
cmds.connectAttr(time + ".outTime", solver + ".currentTime")

parent = None
for i in range(2):
    cube, _ = cmds.polyCube()

    if parent:
        cmds.parent(cube, parent, relative=True)
        cmds.move(3, 0, 0, relative=True)
    else:
        cmds.move(-3, 15, -7)

    rigid = cmds.createNode("rigidNode", parent=cube)

    cmds.connectAttr(time + ".outTime", rigid + ".currentTime")
    cmds.connectAttr(cube + ".parentInverseMatrix", rigid + ".inParentInverseMatrix")
    cmds.connectAttr(cube + ".matrix", rigid + ".inMatrix")
    cmds.connectAttr(rigid + ".outputTranslateX", cube + ".translateX")
    cmds.connectAttr(rigid + ".outputTranslateY", cube + ".translateY")
    cmds.connectAttr(rigid + ".outputTranslateZ", cube + ".translateZ")

    # Connect to scene
    cmds.connectAttr(rigid + ".startState", solver + ".startState[%d]" % i)
    cmds.connectAttr(rigid + ".currentState", solver + ".currentState[%d]" % i)
    cmds.connectAttr(solver + ".nextState[%d]" % i, rigid + ".nextState")

    #parent = cube  # This only makes matters worse


#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
main.cpp
)
set(LIBRARIES
OpenMaya
OpenMayaRender
OpenMayaUI
Foundation
)
add_compile_definitions("/D_DEBUG /Zi")
build_plugin()
#include <string.h>
#include <unordered_map>
#include <cassert>
#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>
#include <maya/MGlobal.h>
#define Print MStreamUtils::stdErrorStream
#define Translate(mat) MTransformationMatrix(mat).translation(MSpace::kTransform)
#define AddAttribute(attr) assert(addAttribute(attr) == MS::kSuccess);
#define AttributeAffects(attr, affects) assert(attributeAffects(attr, affects) == MS::kSuccess);
// Global counter, for a unique UID per rigid
static int gUniqueIdCounter { 1000 };
struct RigidBody {
int uid { -1 };
MVector velocity { 0, 0, 0 };
MMatrix parentInverseMatrix {};
MMatrix restMatrix {};
MMatrix worldMatrix {};
bool worldMatrixChanged { true };
};
using RigidMap = std::unordered_map<int, RigidBody>;
// For purposes of demonstration, rather
// than passing data between nodes, we'll
// use a global variable and pass an index
// into this map between nodes instead.
static RigidMap gRigids;
static bool has(int uid) { return gRigids.find(uid) != gRigids.end(); }
// Helper functions
inline MDataHandle InputValue(MDataBlock& datablock, MObject attr) {
MStatus status;
MDataHandle handle = datablock.inputValue(attr, &status);
assert(status == MS::kSuccess);
return handle;
}
inline MDataHandle OutputValue(MDataBlock& datablock, MObject attr) {
MStatus status;
MDataHandle handle = datablock.outputValue(attr, &status);
assert(status == MS::kSuccess);
return handle;
}
#include "solver.hpp"
#include "rigid.hpp"
MStatus initializePlugin(MObject obj) {
MStatus status;
MFnPlugin plugin(obj, PLUGIN_COMPANY, "3.0", "Any");
status = plugin.registerNode(SolverNode::name,
SolverNode::id,
SolverNode::creator,
SolverNode::initialize);
status = plugin.registerNode(RigidNode::name,
RigidNode::id,
RigidNode::creator,
RigidNode::initialize);
Print() << "==============\n";
Print() << "Initialising\n";
return status;
}
MStatus uninitializePlugin(MObject obj) {
MStatus status;
MFnPlugin plugin(obj);
status = plugin.deregisterNode(RigidNode::id);
status = plugin.deregisterNode(SolverNode::id);
Print() << "Deinitialising.\n";
Print() << "==============\n";
return status;
}
/**
* 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;
}
/**
* Pull on all rigid at once, and solve them in one big batch.
*
*/
class SolverNode : public MPxNode {
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;
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 };
};
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;
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.setUsesArrayDataBuilder(true);
aCurrentState = numFn.create("currentState", "cust", MFnNumericData::kInt, -1, &status);
numFn.setStorable(false);
numFn.setReadable(true);
numFn.setWritable(true);
numFn.setArray(true);
numFn.setUsesArrayDataBuilder(true);
aNextState = numFn.create("nextState", "nest", MFnNumericData::kInt, -1, &status);
numFn.setStorable(false);
numFn.setWritable(false);
numFn.setArray(true);
numFn.setUsesArrayDataBuilder(true);
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;
}
MStatus SolverNode::setDependentsDirty(const MPlug& plugBeingDirtied, MPlugArray& affectedPlugs) {
return MPxNode::setDependentsDirty(plugBeingDirtied, affectedPlugs);
}
MStatus SolverNode::compute(const MPlug& plug, MDataBlock& datablock) {
MStatus status { MS::kUnknownParameter };
if (plug == aNextState) {
// Sometimes the compound plug is dirtied, but when and why I do not know.
// Either way, we don't care for it.
if (plug.isElement()) {
const MTime currentTime = InputValue(datablock, aCurrentTime).asTime();
const MTime startTime = InputValue(datablock, aStartTime).asTime();
Print() << "- time: " << currentTime << " -----------------------------------------------\n";
_currentTime = currentTime;
_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) {
Print() << "SolverNode::computeOutputRigids()\n";
MStatus status { MS::kSuccess };
if (_beforeStartTime) {
Print() << "SolverNode: Doing nothing..\n";
}
else if (_isStartTime) {
status = addRigids(plug, datablock);
}
else if (_isFirstTime) {
status = updateRigids(plug, datablock);
}
else if (_deltaTime > 0.0) {
status = updateRigids(plug, datablock);
}
else {
// Unchanged, the rigid will expect a value 0 or
// higher when the solver has actually changed
MArrayDataHandle outputHandle = datablock.outputArrayValue(aNextState, &status);
for (unsigned int i = 0; i < outputHandle.elementCount(); i++) {
outputHandle.jumpToElement(i);
outputHandle.outputValue().setInt(-1);
}
}
assert(status == MS::kSuccess);
// nextState was pulled, let's clean it
MArrayDataHandle outputArrayHandle = datablock.outputArrayValue(aNextState, &status);
assert(status == MS::kSuccess);
outputArrayHandle.setAllClean();
datablock.setClean(plug);
return status;
}
MStatus SolverNode::addRigids(const MPlug& plug, MDataBlock& datablock) {
MStatus status { MS::kUnknownParameter };
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");
MArrayDataHandle outputArrayHandle = datablock.outputArrayValue(aNextState, &status);
assert(status == MS::kSuccess);
MArrayDataHandle inputRigidsHandle = datablock.inputArrayValue(aStartState, &status);
assert(status == MS::kSuccess);
MDataHandle outputHandle = outputArrayHandle.outputValue(&status);
assert(status == MS::kSuccess);
MPlug outputRigidsPlug { thisMObject(), aNextState };
// Iterate over the entire array, since we can't
// know when the *last* rigid is added. We'll treat
// every rigid as a newly added rigid.
outputArrayHandle.jumpToElement(0);
// Since we use indexes into a global array for Rigid data,
// the indexes we use into that array get left behind when
// disconnected. So we need to ensure we only deal with
// indexes that are both valid and *still connected*.
do {
int index = outputArrayHandle.elementIndex(&status);
assert(status == MS::kSuccess);
// Only consider rigids that are still connected
MPlug elementPlug = outputRigidsPlug.elementByLogicalIndex(index, &status);
assert(status == MS::kSuccess);
if (!elementPlug.isConnected()) { Print() << "SolverNode: Disconnected index: " << index << "\n"; return MS::kFailure; }
// Pull on rigid.outputStartState
int uid = inputRigidsHandle.inputValue(&status).asInt();
assert(status == MS::kSuccess || uid >= 0);
Print() << "SolverNode: Solver.output[" << index << "] --> Rigid.next[" << uid << "]\n";
outputHandle.set(index);
} while (outputArrayHandle.next() == MS::kSuccess);
return status;
}
MStatus SolverNode::updateRigids(const MPlug& plug, MDataBlock& datablock) {
Print() << "SolverNode::updateRigids()\n";
MStatus status { MS::kSuccess };
// Pull on RigidNode::aCurrentState
MArrayDataHandle inputHandle = datablock.inputArrayValue(aCurrentState, &status);
assert(status == MS::kSuccess);
for (unsigned int i = 0; i < inputHandle.elementCount(); i++) {
inputHandle.jumpToElement(i);
inputHandle.inputValue().asInt();
}
stepSimulation(datablock);
// Output a "solver-changed" message (1) to Rigid
MArrayDataHandle outputHandle = datablock.outputArrayValue(aNextState, &status);
assert(status == MS::kSuccess);
for (unsigned int i = 0; i < outputHandle.elementCount(); i++) {
outputHandle.jumpToElement(i);
outputHandle.outputValue().setInt(1);
}
return status;
}
/**
* Apply gravity and update position
*
*/
MStatus SolverNode::stepSimulation(MDataBlock& datablock) {
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;
}
return status;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment