Skip to content

Instantly share code, notes, and snippets.

@mottosso
Last active February 24, 2021 09:34
Show Gist options
  • Save mottosso/4a6f0ae4546c6b9c30d0196f77841465 to your computer and use it in GitHub Desktop.
Save mottosso/4a6f0ae4546c6b9c30d0196f77841465 to your computer and use it in GitHub Desktop.
Editable Initial State - Attempt 15

⬅️ Back

In the previous attempts, DG has been working well, but Parallel not so much. I've learnt that where DG starts by pulling pCube1.translateX, Parallel does the exact opposite. It pushes evaluations through from a "root node" all the way to pCube1.translateX.

To accommodate for this, we can no longer rely on RigidNode::startState being evaluated when necessary. We also can't assume rigids have been updated prior to simulating. Instead, we will always be simulating the previous state.


Architecture

Roughly 800 lines in total, including comments.

  • main.cpp Single translation unit, single output plug-in
  • solver.hpp A DAG shape node stepping the simulation for connected rigids
  • rigid.hpp A DAG shape node outputting translation for a connected Maya transform

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

Frame 1

  1. Solver initialises
  2. Rigid pull their plugs
  3. Rigid pushes outputTranslate (which is unchanged)

Frame 2+

  1. Solver steps
  2. Rigid pull their plugs for the next step
  3. Rigid pushes outputTranslate (reflecing the previous step)
  4. (Repeat)

When the solver steps, since it evaluates first, notice that it'll operate on whatever rigids pulled on the previous frame. Once it finishes, the rigid will then display the updated simulation, and immediately pull on its plugs for the next step of the simulation.

Again, this is only relevant to Parallel. DG reverses this relationship.


Result

This works well, with the exception of initial state being manually updated by the user from Python which means we can't expect channel box or keyframes to affect it.

Test Status
DG ✔️
Parallel ✔️
Manipulator ✔️
Parented to each other ✔️
Children preserved ✔️
ChannelBox
Keyframe
Autokeyframe
Hierarchy

Basic Usage

basicuse1

Hierarchy Preserved

basicuse2

Edits to parent preserves children

basicuse3


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


Build & Run

From Powershell.

cd c:\
git clone https://gist.github.com/4a6f0ae4546c6b9c30d0196f77841465.git initialStateNode15
mkdir initialStateNode15/build
cd initialStateNode15/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:\initialState15\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(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")
    
    # Set, rather than connect, the initial state
    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")

    parent = cube


def set_initial_state_all():
    """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.

    """

    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
OpenMayaAnim
OpenMayaUI
Foundation
)
add_compile_definitions("/D_DEBUG /Zi")
build_plugin()
#include <string.h>
#include <unordered_map>
#include <vector>
#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>
#include <maya/MAnimControl.h>
#include <maya/MEvaluationNode.h>
#include <maya/MEvaluationManager.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 Transform {
MMatrix matrix;
MVector velocity;
};
struct RigidBody {
int uid { -1 };
MString name;
MVector velocity { 0, 0, 0 };
float damping { 0.5f };
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 "rigid.hpp"
#include "solver.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 computeStartState(const MPlug&, MDataBlock&);
MStatus computeCurrentState(const MPlug&, MDataBlock&);
MStatus computeOutputTranslate(const MPlug&, MDataBlock&);
MStatus computeOutputWorldMatrix(const MPlug&, MDataBlock&);
static void* creator() { return new RigidNode(); }
static MStatus initialize();
void postConstructor();
void reset(MDataBlock&);
int uid() { return _uid; }
bool getInternalValue(const MPlug&, MDataHandle&) override;
bool isPassiveOutput(const MPlug&) const override;
// Does nothing
MStatus preEvaluation(const MDGContext&, const MEvaluationNode&) override;
MStatus setDependentsDirty(const MPlug&, MPlugArray&) override;
public:
static MString name;
static MTypeId id;
static const MString drawDbClassification;
static const MString drawRegistrantId;
// Attributes
static MObject aUid;
static MObject aDamping;
static MObject aCurrentTime;
static MObject aInputWorldMatrix;
static MObject aInputParentInverseMatrix;
static MObject aOutputWorldMatrix;
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 };
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::aDamping;
MObject RigidNode::aInputWorldMatrix;
MObject RigidNode::aInputParentInverseMatrix;
MObject RigidNode::aOutputWorldMatrix;
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.setInternal(true); // We'll manage this ourselves
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);
aCurrentState = numFn.create("currentState", "cust", MFnNumericData::kInt, -1, &status);
numFn.setStorable(false);
aDamping = numFn.create("damping", "damp", MFnNumericData::kFloat, 0.5f, &status);
numFn.setKeyable(true);
aCurrentTime = uniFn.create("currentTime", "cuti", MFnUnitAttribute::kTime, 0.0, &status);
uniFn.setReadable(false);
uniFn.setStorable(false);
uniFn.setHidden(true);
aInputParentInverseMatrix = matFn.create("inParentInverseMatrix", "ipim");
matFn.setStorable(false);
matFn.setReadable(false);
aInputWorldMatrix = matFn.create("inWorldMatrix", "inma");
matFn.setStorable(false);
matFn.setReadable(false);
aOutputWorldMatrix = matFn.create("outWorldMatrix", "ouma");
matFn.setWritable(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(aDamping);
AddAttribute(aNextState);
AddAttribute(aStartState);
AddAttribute(aCurrentState);
AddAttribute(aCurrentTime);
AddAttribute(aInputWorldMatrix);
AddAttribute(aInputParentInverseMatrix);
AddAttribute(aOutputWorldMatrix);
AddAttribute(aOutputTranslate);
// Affects initial state
AttributeAffects(aInputWorldMatrix, aStartState);
AttributeAffects(aInputWorldMatrix, aOutputWorldMatrix);
AttributeAffects(aInputParentInverseMatrix, aOutputWorldMatrix);
AttributeAffects(aNextState, aOutputWorldMatrix);
AttributeAffects(aCurrentTime, aOutputWorldMatrix);
AttributeAffects(aDamping, aOutputTranslate);
AttributeAffects(aCurrentTime, aOutputTranslate);
AttributeAffects(aOutputWorldMatrix, 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::preEvaluation(const MDGContext& context, const MEvaluationNode& evaluationNode) {
return MS::kSuccess;
}
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 };
}
bool RigidNode::getInternalValue(const MPlug& plug, MDataHandle& datahandle) {
if (plug == aUid) {
datahandle.setInt(_uid);
return true;
}
return false;
}
/**
* 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.damping = InputValue(datablock, aDamping).asFloat();
MFnDagNode fn { thisMObject() };
rigid.name = fn.name();
OutputValue(datablock, aStartState).set(_uid);
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 };
RigidBody& rigid = gRigids.at(_uid);
rigid.damping = InputValue(datablock, aDamping).asFloat();
OutputValue(datablock, aCurrentState).set(_uid);
return status;
}
MStatus RigidNode::computeOutputWorldMatrix(const MPlug& plug, MDataBlock& datablock) {
Print() << "RigidNode::computeOutputWorldMatrix()\n";
MStatus status { MS::kSuccess };
RigidBody& rigid = gRigids.at(_uid);
const int simulatedFrame = InputValue(datablock, aNextState).asInt();
if (simulatedFrame == 0) {
computeStartState(plug, datablock);
}
else {
computeCurrentState(plug, datablock);
}
// Preserve hierarchy when manipulating an object during standstill
// Try removing this and rotate the parent of a cube, you will find that
// children remain stationary as the parent moves around. That's bad.
if (rigid.worldMatrixChanged) {
rigid.parentInverseMatrix = InputValue(datablock, aInputParentInverseMatrix).asMatrix();
rigid.worldMatrixChanged = false;
}
MDataHandle outputHandle = OutputValue(datablock, aOutputWorldMatrix);
outputHandle.setMMatrix(rigid.worldMatrix);
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);
MMatrix worldMatrix = InputValue(datablock, aOutputWorldMatrix).asMatrix();
MMatrix localMatrix = 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);
return status;
}
MStatus RigidNode::compute(const MPlug& plug, MDataBlock& datablock) {
MStatus status { MS::kUnknownParameter };
Print() << "RigidNode::compute() - " << plug.name() << "\n";
// 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 == aOutputWorldMatrix) {
status = computeOutputWorldMatrix(plug, datablock);
}
else if (plug == aOutputTranslate ||
plug == aOutputTranslateX ||
plug == aOutputTranslateY ||
plug == aOutputTranslateZ) {
status = computeOutputTranslate(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);
MStatus restoreSimulation(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() << "-------------------------------------------------------------------\n";
Print() << "(!) onForceUpdate() " << time.value() << "\n";
Print() << "-------------------------------------------------------------------\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;
}
datablock.setClean(plug);
return status;
}
MStatus SolverNode::computeOutputRigids(const MPlug& plug, MDataBlock& datablock) {
if (debug()) Print() << "SolverNode::computeOutputRigids()\n";
MStatus status { MS::kSuccess };
auto noChange = [&]() -> MStatus {
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);
return status;
};
if (_beforeStartTime) status = noChange();
else if (_isStartTime) status = addRigids(plug, datablock);
else status = updateRigids(plug, datablock);
// These should all have succeeded
assert(status == MS::kSuccess);
return status;
}
MStatus SolverNode::addRigids(const MPlug& plug, MDataBlock& datablock) {
MStatus status { MS::kUnknownParameter };
// In DG mode, Maya appears to cache previously-visited frames
// resulting in compute() *sometimes* not being called.
if (!MEvaluationManager::evaluationManagerActive(MDGContext::current())) {
Print() << "ogs -reset\n";
MGlobal::executeCommand("ogs -reset");
}
// Reset physics-related data
for (auto& pair : gRigids) {
RigidBody& rigid = pair.second;
rigid.worldMatrixChanged = true;
rigid.velocity = {0, 0, 0};
}
_simulationFrame = 0;
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;
}
// Pull on *all* startStates
MArrayDataHandle startStateHandle = datablock.inputArrayValue(aStartState, &status);
assert(status == MS::kSuccess);
MArrayDataHandle nextStateHandle = datablock.outputArrayValue(aNextState, &status);
assert(status == MS::kSuccess);
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);
// Tell rigid about which frame we just solved, i.e. the start frame.
MDataHandle outputHandle = nextStateHandle.outputValue(&status);
assert(status == MS::kSuccess);
outputHandle.setInt(_currentFrame);
Print() << "SolverNode: Solver pulled on solver.index[" << index << "] --> rigid.next[" << uid << "]\n";
return status;
}
MStatus SolverNode::updateRigids(const MPlug& plug, MDataBlock& datablock) {
// 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);
}
MDataHandle outputHandle = nextStateHandle.outputValue(&status);
assert(status == MS::kSuccess);
outputHandle.setInt(_currentFrame);
return status;
}
/**
* Apply gravity and update position
*
*/
MStatus SolverNode::stepSimulation(MDataBlock& datablock) {
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 * rigid.damping, MSpace::kTransform);
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