Skip to content

Instantly share code, notes, and snippets.

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

⬅️ Back

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

Everything works well, except (1) inital state is written from Python and (2) changes to initial state are also written from Python.

The goal is having initial state get picked up from when the rigid is created and for changes to initial state to automatically be picked up.


Architecture

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.

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

Frame 1

  1. pCube is about to be rendered
  2. pCube1.translateX is evaluated
  3. rigid.outputTranslateX is pulled
  4. solver.nextState is pulled
  5. All 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

Frame 2+

  1. Same, except all rigid.currentState is pulled

solvergraph

For multiple rigids, the solver pulls on all start or current states for the rigid before stepping simulation. That way, it can take all rigids into account for e.g. collisions. Only the first rigid triggers a simulation step. The rest merely fetch the last simulated value.

solvergraph2


Result

All is well, with the exception of initial state being manual. Here's an example, where you can see:

  1. Changes to initial state are not preserved
  2. Unless you (1) change and (2) explicitly call set_initial_state_all()

The goal is automating that explicit, manual step.

initialstatemanual


FAQ

Why do you call ogs -reset?

Here's what happens without it. You tell me.

  1. Viewport "remembers" frames when you framestep backwards
  2. It also "remembers" frame sometimes when you step forwards
  3. Sometimes, remembering means not even calling compute()
  4. Other times, it does. It's random. Gets worse over time.

This is only a problem in Viewport 2.0.

ogsrest

ogsdirty


Build & Run

From Powershell.

cd c:\
git clone https://gist.github.com/05f234e784b9b97d7f1fea901e7686c0.git initialStateNode14
mkdir initialStateNode14/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:\initialState14\build\initialStateNode.mll"
cmds.loadPlugin(fname)

# Set to True to test hierarchy
USE_PARENTING = True

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(3):
    cube, _ = cmds.polyCube()

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

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

    cmds.connectAttr(time + ".outTime", rigid + ".currentTime")
    cmds.connectAttr(cube + ".parentInverseMatrix", rigid + ".inParentInverseMatrix")
    
    cmds.setAttr(rigid + ".inWorldMatrix", cmds.getAttr(rigid + ".worldMatrix[0]"), type="matrix")
    
    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")

    if USE_PARENTING:
        parent = cube




#
# The *manual* way of recording the current transforms as initial state
# This is what I'd like to make automatic, whenever any change to the 
# initial state is made by the user.
#

def set_initial_state_all():
    rigids = cmds.ls(type="rigidNode")
    matrices = []
    
    # Read matrices first
    for rigid in rigids:
        matrix =  cmds.getAttr(rigid + ".worldMatrix[0]")
        matrices.append(matrix)
    
    for rigid, matrix in zip(rigids, matrices):
        cmds.setAttr(rigid + ".inWorldMatrix", matrix, type="matrix")


set_initial_state_all()





#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/MPxLocatorNode.h>
#include <maya/MFnNumericAttribute.h>
#include <maya/MFnMatrixAttribute.h>
#include <maya/MFnUnitAttribute.h>
#include <maya/MFnPlugin.h>
#include <maya/MFnDagNode.h>
#include <maya/MFnTransform.h>
#include <maya/MDagPath.h>
#include <maya/MVector.h>
#include <maya/MQuaternion.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>
#include <maya/MMessage.h>
#include <maya/MDGMessage.h>
#include <maya/MDagMessage.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);
static bool debug() {
static bool debugInfoExists { false };
const bool debugInfo = MGlobal::optionVarIntValue("debugInfo", &debugInfoExists);
return debugInfoExists ? debugInfo : false;
}
// Global counter, for a unique UID per rigid
static int gUniqueIdCounter { 1000 };
struct RigidBody {
int uid { -1 };
MString name;
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;
// 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,
MPxNode::kLocatorNode,
&SolverNode::drawDbClassification);
status = plugin.registerNode(RigidNode::name,
RigidNode::id,
RigidNode::creator,
RigidNode::initialize,
MPxNode::kLocatorNode,
&RigidNode::drawDbClassification);
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 MPxLocatorNode {
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&);
int uid() { return _uid; }
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 aCurrentTime;
static MObject aInputWorldMatrix;
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 };
int _lastFrame { -1 };
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::aInputWorldMatrix;
MObject RigidNode::aInputParentInverseMatrix;
MObject RigidNode::aOutputTranslate;
MObject RigidNode::aOutputTranslateX;
MObject RigidNode::aOutputTranslateY;
MObject RigidNode::aOutputTranslateZ;
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);
aInputWorldMatrix = matFn.create("inWorldMatrix", "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(aInputWorldMatrix);
AddAttribute(aInputParentInverseMatrix);
AddAttribute(aOutputTranslate);
// Affects initial state
AttributeAffects(aInputWorldMatrix, aStartState);
AttributeAffects(aInputParentInverseMatrix, aOutputTranslate);
// 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 MPxLocatorNode::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 };
}
/**
* Restore initial state
*
*/
MStatus RigidNode::computeStartState(const MPlug& plug, MDataBlock& datablock) {
Print() << "RigidNode:: Rigid[" << _uid << "].computeStartState() ===============\n";
MStatus status { MS::kSuccess };
RigidBody& rigid = gRigids.at(_uid);
// Restore initial state, do not evaluate anything
MMatrix worldMatrix = InputValue(datablock, aInputWorldMatrix).asMatrix();
rigid.restMatrix = worldMatrix;
rigid.worldMatrix = rigid.restMatrix;
rigid.worldMatrixChanged = true;
rigid.velocity = {0, 0, 0};
MFnDagNode fn { thisMObject() };
rigid.name = fn.name();
OutputValue(datablock, aStartState).set(_uid);
datablock.setClean(plug);
return status;
}
/**
* Read values that change over time
*
* We don't really have any in this example, but for example linearDamping
*
*/
MStatus RigidNode::computeCurrentState(const MPlug& plug, MDataBlock& datablock) {
Print() << "RigidNode:: Rigid[" << _uid << "].computeCurrentState() ===============\n";
MStatus status { MS::kSuccess };
OutputValue(datablock, aCurrentState).set(_uid);
datablock.setClean(plug);
return status;
}
MStatus RigidNode::computeOutputTranslate(const MPlug& plug, MDataBlock& datablock) {
Print() << "RigidNode::computeOutputTranslate() -------------------------------\n";
MStatus status { MS::kSuccess };
RigidBody& rigid = gRigids.at(_uid);
// Pull and let the SolverNode go to work..
InputValue(datablock, aNextState).asInt();
// .
// ..
// ...
// aaaaand we're back.
// At this point, the solver will have pulled on either..
//
// - RigidNode::computeStartState
// - RigidNode::computeCurrentState
//
// ..depending on what time it was. It will also have populated
// the rigid.worldMatrix with the latest up-to-date simulated values
//
// Preserve hierarchy when manipulating an object during standstill
// Try removing this and rotate the parent of a cube,
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: Rigid[" << _uid << "].translate" << translate << " written\n";
return status;
}
MStatus RigidNode::computeUid(const MPlug& plug, MDataBlock& datablock) {
if (debug()) 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 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;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment