Files
bullet3/Extras/MayaPlugin/dSolverNode.cpp
rponom d5f5ddf2f1 Bullet debug draw added
It is off by default, see attributes of dSolverNode to turn it on
2010-01-30 04:21:12 +00:00

1239 lines
46 KiB
C++

/*
Bullet Continuous Collision Detection and Physics Library Maya Plugin
Copyright (c) 2008 Walt Disney Studios
This software is provided 'as-is', without any express or implied warranty.
In no event will the authors be held liable for any damages arising
from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it freely,
subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must
not claim that you wrote the original software. If you use this
software in a product, an acknowledgment in the product documentation
would be appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must
not be misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
Written by: Nicola Candussi <nicola@fluidinteractive.com>
Modified by Roman Ponomarev <rponom@gmail.com>
01/22/2010 : Constraints reworked
*/
//dSolverNode.cpp
#include <maya/MFnNumericAttribute.h>
#include <maya/MFnEnumAttribute.h>
#include <maya/MFnTypedAttribute.h>
#include <maya/MFnUnitAttribute.h>
#include <maya/MFnMessageAttribute.h>
#include <maya/MFnStringData.h>
#include <maya/MFnStringArrayData.h>
#include <maya/MPlugArray.h>
#include <maya/MFnDagNode.h>
#include <maya/MEulerRotation.h>
#include <maya/MQuaternion.h>
#include <maya/MFnTransform.h>
#include <maya/MVector.h>
#include <maya/MGlobal.h>
#include <maya/MFnField.h>
#include <maya/MVectorArray.h>
#include <maya/MItDag.h>
#include <maya/MConditionMessage.h>
#include <maya/MDGModifier.h>
#include <maya/MDGMessage.h>
#include <maya/MDagPath.h>
#include <maya/MSceneMessage.h>
#include <fstream>
#include <sstream>
#include "mayaUtils.h"
#include "dSolverNode.h"
#include "solver.h"
#include "rigidBodyNode.h"
#include "rigidBodyArrayNode.h"
#include "constraint/nailConstraintNode.h"
#include "constraint/hingeConstraintNode.h"
#include "constraint/sliderConstraintNode.h"
#include "constraint/sixdofConstraintNode.h"
#include "pdbIO.h"
#include "collisionShapeNode.h"
MTypeId dSolverNode::typeId(0x100331);
MString dSolverNode::typeName("dSolver");
MObject dSolverNode::ia_time;
MObject dSolverNode::ia_startTime;
MObject dSolverNode::ia_gravity;
MObject dSolverNode::ia_enabled;
MObject dSolverNode::ia_splitImpulse;
MObject dSolverNode::ia_substeps;
MObject dSolverNode::oa_rigidBodies;
MObject dSolverNode::ssSolverType;
bool dSolverNode::isStartTime;
MObject dSolverNode::ia_DBG_DrawWireframe;
MObject dSolverNode::ia_DBG_DrawAabb;
MObject dSolverNode::ia_DBG_DrawFeaturesText;
MObject dSolverNode::ia_DBG_DrawContactPoints;
MObject dSolverNode::ia_DBG_NoDeactivation;
MObject dSolverNode::ia_DBG_NoHelpText;
MObject dSolverNode::ia_DBG_DrawText;
MObject dSolverNode::ia_DBG_ProfileTimings;
MObject dSolverNode::ia_DBG_EnableSatComparison;
MObject dSolverNode::ia_DBG_DisableBulletLCP;
MObject dSolverNode::ia_DBG_EnableCCD;
MObject dSolverNode::ia_DBG_DrawConstraints;
MObject dSolverNode::ia_DBG_DrawConstraintLimits;
MObject dSolverNode::ia_DBG_FastWireframe;
#define ATTR_POSITION "position"
//#define ATTR_POSITION_TYPE VECTOR_ATTR
#define ATTR_VELOCITY "velocity"
//#define ATTR_VELOCITY_TYPE VECTOR_ATTR
#define ATTR_IN_RAXIS "in_rotvec" /* vector */
//#define ATTR_IN_RAXIS_TYPE VECTOR_ATTR
#define ATTR_IN_RANGLE "in_rotang" /* float */
//#define ATTR_IN_RANGLE_TYPE FLOAT_ATTR
#define ATTR_IN_ROTVEL "in_rotvel" /* float */
//#define ATTR_IN_ROTVEL_TYPE FLOAT_ATTR
#define ATTR_IN_RAXISNEXT "in_rotvec_next" /* vector */
//#define ATTR_IN_RAXISNEXT_TYPE VECTOR_ATTR
#define ATTR_IN_RANGLENEXT "in_rotang_next" /* float */
//#define ATTR_IN_RANGLENEXT_TYPE FLOAT_ATTR
static int getDbgDrawVal(const MObject& thisObj, const MObject& attr, int flag)
{
MPlug plug(thisObj, attr);
bool retVal = false;
plug.getValue(retVal);
return retVal ? (1 << flag) : 0;
}
void dSolverNode::draw( M3dView & view, const MDagPath & path,
M3dView::DisplayStyle style,
M3dView::DisplayStatus status )
{
view.beginGL();
glPushAttrib( GL_ALL_ATTRIB_BITS );
glDisable(GL_LIGHTING);
if( !(status == M3dView::kActive ||
status == M3dView::kLead ||
status == M3dView::kHilite ||
( style != M3dView::kGouraudShaded && style != M3dView::kFlatShaded )) ) {
glColor3f(1.0, 1.0, 0.0);
}
/*
glBegin(GL_LINES);
glColor3f(1.0, 0.5, 0.5);
glVertex3f(0.0, 0.0, 0.0);
glColor3f(0.5, 0.5, 1.0);
glVertex3f(1.f, 1.f, 1.f);
glEnd();
*/
MObject thisObject = thisMObject();
MFnDagNode fnDagNode(thisObject);
MFnTransform fnParentTransform(fnDagNode.parent(0));
fnParentTransform.setTranslation(MVector(0.f, 0.f, 0.f), MSpace::kObject); // lock translation
fnParentTransform.setRotation(MEulerRotation(0., 0., 0.)); // lock rotation
double fixScale[3] = { 1., 1., 1. }; // lock scale
fnParentTransform.setScale(fixScale);
int dbgMode = 0;
dbgMode |= getDbgDrawVal(thisObject,ia_DBG_DrawWireframe, 0);
dbgMode |= getDbgDrawVal(thisObject,ia_DBG_DrawAabb, 1);
dbgMode |= getDbgDrawVal(thisObject,ia_DBG_DrawFeaturesText, 2);
dbgMode |= getDbgDrawVal(thisObject,ia_DBG_DrawContactPoints, 3);
dbgMode |= getDbgDrawVal(thisObject,ia_DBG_NoDeactivation, 4);
dbgMode |= getDbgDrawVal(thisObject,ia_DBG_NoHelpText, 5);
dbgMode |= getDbgDrawVal(thisObject,ia_DBG_DrawText, 6);
dbgMode |= getDbgDrawVal(thisObject,ia_DBG_ProfileTimings, 7);
dbgMode |= getDbgDrawVal(thisObject,ia_DBG_EnableSatComparison, 8);
dbgMode |= getDbgDrawVal(thisObject,ia_DBG_DisableBulletLCP, 9);
dbgMode |= getDbgDrawVal(thisObject,ia_DBG_EnableCCD, 10);
dbgMode |= getDbgDrawVal(thisObject,ia_DBG_DrawConstraints, 11);
dbgMode |= getDbgDrawVal(thisObject,ia_DBG_DrawConstraintLimits, 12);
dbgMode |= getDbgDrawVal(thisObject,ia_DBG_FastWireframe, 13);
solver_t::debug_draw(dbgMode);
glPopAttrib();
view.endGL();
}
static void sceneLoadedCB(void* clientData)
{
dSolverNode::updateAllRigidBodies();
}
#if 0
static void connCB( MPlug & srcPlug, MPlug & destPlug, bool made, void* clientData)
{
static int numConn = 0;
MObject objSrc = srcPlug.node();
MObject objDst = destPlug.node();
MFnDependencyNode fnNodeSrc(objSrc);
MFnDependencyNode fnNodeDst(objDst);
if(fnNodeSrc.typeId() == collisionShapeNode::typeId)
{
if(fnNodeDst.typeId() == rigidBodyNode::typeId)
{
numConn++;
}
}
if(fnNodeDst.typeId() == collisionShapeNode::typeId)
{
if(fnNodeSrc.typeId() == rigidBodyNode::typeId)
{
numConn++;
}
}
}
#endif
static void updateSceneCB(MTime & time, void* clientData)
{
dSolverNode::updateAllRigidBodies();
}
void dSolverNode::updateAllRigidBodies()
{
MStatus stat;
// MItDag dagIterator( MItDag::kBreadthFirst, MFn::kInvalid, &stat);
MItDag dagIterator( MItDag::kDepthFirst, MFn::kInvalid, &stat);
if (stat != MS::kSuccess)
{
std::cout << "Failure in DAG iterator setup" << std::endl;
return;
}
for ( ;!dagIterator.isDone(); dagIterator.next())
{
MDagPath dagPath;
stat = dagIterator.getPath( dagPath );
if(stat != MS::kSuccess)
{
std::cout << "Failure in getting DAG path" << std::endl;
return;
}
// skip over intermediate objects
MFnDagNode dagNode( dagPath, &stat );
if (dagNode.isIntermediateObject())
{
continue;
}
if(!dagPath.hasFn(MFn::kDependencyNode))
{
continue;
}
MObject mObj = dagNode.object(&stat);
if(stat != MS::kSuccess)
{
std::cout << "Failure in getting MObject" << std::endl;
return;
}
MFnDependencyNode fnNode(mObj, &stat);
if(stat != MS::kSuccess)
{
std::cout << "Failure in getting dependency node" << std::endl;
return;
}
if(fnNode.typeId() == rigidBodyNode::typeId)
{
MPlug plgCollisionShape(mObj, rigidBodyNode::ia_collisionShape);
MObject update;
//force evaluation of the shape
plgCollisionShape.getValue(update);
if(plgCollisionShape.isConnected())
{
rigidBodyNode *rbNode = static_cast<rigidBodyNode*>(dagNode.userNode());
rbNode->update();
}
}
if(fnNode.typeId() == nailConstraintNode::typeId)
{
MPlug plgRbA(mObj, nailConstraintNode::ia_rigidBodyA);
MPlug plgRbB(mObj, nailConstraintNode::ia_rigidBodyB);
MObject update;
//force evaluation
plgRbA.getValue(update);
plgRbB.getValue(update);
bool connA = plgRbA.isConnected();
bool connB = plgRbB.isConnected();
if(connA || connB)
{
nailConstraintNode *ncNode = static_cast<nailConstraintNode*>(dagNode.userNode());
ncNode->update();
}
}
if(fnNode.typeId() == hingeConstraintNode::typeId)
{
MPlug plgRbA(mObj, hingeConstraintNode::ia_rigidBodyA);
MPlug plgRbB(mObj, hingeConstraintNode::ia_rigidBodyB);
MObject update;
//force evaluation
plgRbA.getValue(update);
plgRbB.getValue(update);
bool connA = plgRbA.isConnected();
bool connB = plgRbB.isConnected();
if(connA || connB)
{
hingeConstraintNode *hcNode = static_cast<hingeConstraintNode*>(dagNode.userNode());
hcNode->update();
}
}
if(fnNode.typeId() == sliderConstraintNode::typeId)
{
MPlug plgRbA(mObj, hingeConstraintNode::ia_rigidBodyA);
MPlug plgRbB(mObj, hingeConstraintNode::ia_rigidBodyB);
MObject update;
//force evaluation
plgRbA.getValue(update);
plgRbB.getValue(update);
bool connA = plgRbA.isConnected();
bool connB = plgRbB.isConnected();
if(connA || connB)
{
sliderConstraintNode *scNode = static_cast<sliderConstraintNode*>(dagNode.userNode());
scNode->update();
}
}
if(fnNode.typeId() == sixdofConstraintNode::typeId)
{
MPlug plgRbA(mObj, hingeConstraintNode::ia_rigidBodyA);
MPlug plgRbB(mObj, hingeConstraintNode::ia_rigidBodyB);
MObject update;
//force evaluation
plgRbA.getValue(update);
plgRbB.getValue(update);
bool connA = plgRbA.isConnected();
bool connB = plgRbB.isConnected();
if(connA || connB)
{
sixdofConstraintNode *sdNode = static_cast<sixdofConstraintNode*>(dagNode.userNode());
sdNode->update();
}
}
}
}
MStatus dSolverNode::initialize()
{
MStatus status;
MFnEnumAttribute fnEnumAttr;
MFnMessageAttribute fnMsgAttr;
MFnUnitAttribute fnUnitAttr;
MFnNumericAttribute fnNumericAttr;
MCallbackId updateSceneCBId = MDGMessage::addForceUpdateCallback(updateSceneCB, NULL, NULL );
MCallbackId sceneLoadedCBId = MSceneMessage::addCallback(MSceneMessage::kAfterOpen, sceneLoadedCB, NULL, NULL);
// MCallbackId connCBId = MDGMessage::addConnectionCallback(connCB, NULL, NULL );
//
ssSolverType = fnEnumAttr.create( "ssSolverType", "ssst", 0, &status );
MCHECKSTATUS(status, "creating ssSolverType attribute")
fnEnumAttr.addField( "Bullet Physics", 0 );
fnEnumAttr.addField( "Ageia PhysX", 1 );
fnEnumAttr.addField( "Stanford PhysBAM", 2 );
status = addAttribute(ssSolverType);
MCHECKSTATUS(status, "adding ssSolverType attribute")
//
ia_time = fnUnitAttr.create( "inTime", "it", MFnUnitAttribute::kTime, 0.0, &status );
MCHECKSTATUS(status, "creating ia_time attribute")
fnUnitAttr.setHidden(true);
status = addAttribute(ia_time);
MCHECKSTATUS(status, "adding ia_time attribute")
ia_startTime = fnUnitAttr.create( "startTime", "stm", MFnUnitAttribute::kTime, 1.0, &status );
MCHECKSTATUS(status, "creating ia_startTime attribute")
status = addAttribute(ia_startTime);
MCHECKSTATUS(status, "adding ia_startTime attribute")
oa_rigidBodies = fnMsgAttr.create("rigidBodies", "rbds", &status);
MCHECKSTATUS(status, "creating oa_rigidBodies attribute")
status = addAttribute(oa_rigidBodies);
MCHECKSTATUS(status, "adding oa_rigidBodies attribute")
ia_gravity = fnNumericAttr.createPoint("gravity", "grvt", &status);
MCHECKSTATUS(status, "creating gravity attribute")
fnNumericAttr.setDefault(0.0, -9.81, 0.0);
fnNumericAttr.setKeyable(true);
status = addAttribute(ia_gravity);
MCHECKSTATUS(status, "adding ia_gravity attribute")
ia_substeps = fnNumericAttr.create("substeps", "sbs", MFnNumericData::kInt, 1, &status);
MCHECKSTATUS(status, "creating substeps attribute")
fnNumericAttr.setKeyable(true);
status = addAttribute(ia_substeps);
MCHECKSTATUS(status, "adding ia_substeps attribute")
ia_enabled = fnNumericAttr.create("enabled", "enbl", MFnNumericData::kBoolean, true, &status);
MCHECKSTATUS(status, "creating enabled attribute")
status = addAttribute(ia_enabled);
MCHECKSTATUS(status, "adding ia_enabled attribute")
ia_splitImpulse = fnNumericAttr.create("splitImpulse", "spli", MFnNumericData::kBoolean, false, &status);
MCHECKSTATUS(status, "creating splitImpulse attribute")
status = addAttribute(ia_splitImpulse);
MCHECKSTATUS(status, "adding ia_splitImpulse attribute")
ia_DBG_DrawWireframe = fnNumericAttr.create("drawWireframe", "dwfr", MFnNumericData::kBoolean, false, &status);
MCHECKSTATUS(status, "creating ia_DBG_DrawWireframe attribute")
status = addAttribute(ia_DBG_DrawWireframe);
MCHECKSTATUS(status, "adding ia_DBG_DrawWireframe attribute")
ia_DBG_DrawAabb = fnNumericAttr.create("drawAabb", "daabb", MFnNumericData::kBoolean, false, &status);
MCHECKSTATUS(status, "creating ia_DBG_DrawAabb attribute")
status = addAttribute(ia_DBG_DrawAabb);
MCHECKSTATUS(status, "adding ia_DBG_DrawAabb attribute")
ia_DBG_DrawFeaturesText = fnNumericAttr.create("drawFeaturesText", "dft", MFnNumericData::kBoolean, false, &status);
MCHECKSTATUS(status, "creating ia_DBG_DrawFeaturesText attribute")
status = addAttribute(ia_DBG_DrawFeaturesText);
MCHECKSTATUS(status, "adding ia_DBG_DrawFeaturesText attribute")
ia_DBG_DrawContactPoints = fnNumericAttr.create("drawContactPoints", "dcp", MFnNumericData::kBoolean, false, &status);
MCHECKSTATUS(status, "creating ia_DBG_DrawContactPoints attribute")
status = addAttribute(ia_DBG_DrawContactPoints);
MCHECKSTATUS(status, "adding ia_DBG_DrawContactPoints attribute")
ia_DBG_NoDeactivation = fnNumericAttr.create("noDeactivation", "dnda", MFnNumericData::kBoolean, false, &status);
MCHECKSTATUS(status, "creating ia_DBG_NoDeactivation attribute")
status = addAttribute(ia_DBG_NoDeactivation);
MCHECKSTATUS(status, "adding ia_DBG_NoDeactivation attribute")
ia_DBG_NoHelpText = fnNumericAttr.create("noHelpText", "dnht", MFnNumericData::kBoolean, false, &status);
MCHECKSTATUS(status, "creating ia_DBG_NoHelpText attribute")
status = addAttribute(ia_DBG_NoHelpText);
MCHECKSTATUS(status, "adding ia_DBG_NoHelpText attribute")
ia_DBG_DrawText = fnNumericAttr.create("drawText", "dtxt", MFnNumericData::kBoolean, false, &status);
MCHECKSTATUS(status, "creating ia_DBG_DrawText attribute")
status = addAttribute(ia_DBG_DrawText);
MCHECKSTATUS(status, "adding ia_DBG_DrawText attribute")
ia_DBG_ProfileTimings = fnNumericAttr.create("pProfileTimings", "dptm", MFnNumericData::kBoolean, false, &status);
MCHECKSTATUS(status, "creating ia_DBG_ProfileTimings attribute")
status = addAttribute(ia_DBG_ProfileTimings);
MCHECKSTATUS(status, "adding ia_DBG_ProfileTimings attribute")
ia_DBG_EnableSatComparison = fnNumericAttr.create("enableSatComparison", "desc", MFnNumericData::kBoolean, false, &status);
MCHECKSTATUS(status, "creating ia_DBG_EnableSatComparison attribute")
status = addAttribute(ia_DBG_EnableSatComparison);
MCHECKSTATUS(status, "adding ia_DBG_EnableSatComparison attribute")
ia_DBG_DisableBulletLCP = fnNumericAttr.create("disableBulletLCP", "dblcp", MFnNumericData::kBoolean, false, &status);
MCHECKSTATUS(status, "creating ia_DBG_DisableBulletLCP attribute")
status = addAttribute(ia_DBG_DisableBulletLCP);
MCHECKSTATUS(status, "adding ia_DBG_DisableBulletLCP attribute")
ia_DBG_EnableCCD = fnNumericAttr.create("enableCCD", "deccd", MFnNumericData::kBoolean, false, &status);
MCHECKSTATUS(status, "creating ia_DBG_EnableCCD attribute")
status = addAttribute(ia_DBG_EnableCCD);
MCHECKSTATUS(status, "adding ia_DBG_EnableCCD attribute")
ia_DBG_DrawConstraints = fnNumericAttr.create("drawConstraints", "dcnst", MFnNumericData::kBoolean, false, &status);
MCHECKSTATUS(status, "creating ia_DBG_DrawConstraints attribute")
status = addAttribute(ia_DBG_DrawConstraints);
MCHECKSTATUS(status, "adding ia_DBG_DrawConstraints attribute")
ia_DBG_DrawConstraintLimits = fnNumericAttr.create("drawConstraintLimits", "dcsl", MFnNumericData::kBoolean, false, &status);
MCHECKSTATUS(status, "creating ia_DBG_DrawConstraintLimits attribute")
status = addAttribute(ia_DBG_DrawConstraintLimits);
MCHECKSTATUS(status, "adding ia_DBG_DrawConstraintLimits attribute")
ia_DBG_FastWireframe = fnNumericAttr.create("fastWireframe", "dfwf", MFnNumericData::kBoolean, false, &status);
MCHECKSTATUS(status, "creating ia_DBG_FastWireframe attribute")
status = addAttribute(ia_DBG_FastWireframe);
MCHECKSTATUS(status, "adding ia_DBG_FastWireframe attribute")
status = attributeAffects(ia_time, oa_rigidBodies);
MCHECKSTATUS(status, "adding attributeAffects(ia_time, oa_rigidBodies)")
status = attributeAffects(ia_enabled, oa_rigidBodies);
MCHECKSTATUS(status, "adding attributeAffects(ia_enabled, oa_rigidBodies)")
return MS::kSuccess;
}
dSolverNode::dSolverNode()
{
}
dSolverNode::~dSolverNode()
{
}
void dSolverNode::postConstructor()
{
//prevent deletion if all the rigidbodies are deleted
setExistWithoutOutConnections(true);
}
void* dSolverNode::creator()
{
return new dSolverNode();
}
bool dSolverNode::setInternalValueInContext( const MPlug & plug, const MDataHandle & dataHandle, MDGContext & ctx )
{
return false;
}
MStatus dSolverNode::compute(const MPlug& plug, MDataBlock& data)
{
if(plug == oa_rigidBodies) {
computeRigidBodies(plug, data);
} else {
return MStatus::kUnknownParameter;
}
return MStatus::kSuccess;
}
void initRigidBody(MObject &node)
{
MFnDagNode fnDagNode(node);
rigidBodyNode *rbNode = static_cast<rigidBodyNode*>(fnDagNode.userNode());
rigid_body_t::pointer rb = rbNode->rigid_body();
if(fnDagNode.parentCount() == 0) {
std::cout << "No transform found!" << std::endl;
return;
}
MFnTransform fnTransform(fnDagNode.parent(0));
MPlug plgMass(node, rigidBodyNode::ia_mass);
float mass = 0.f;
plgMass.getValue(mass);
if(mass > 0.f)
{
//active rigid body, set the world transform from the initial* attributes
MObject obj;
vec3f pos;
MPlug plgInitialValue(node, rigidBodyNode::ia_initialPosition);
plgInitialValue.child(0).getValue(pos[0]);
plgInitialValue.child(1).getValue(pos[1]);
plgInitialValue.child(2).getValue(pos[2]);
vec3f rot;
plgInitialValue.setAttribute(rigidBodyNode::ia_initialRotation);
plgInitialValue.child(0).getValue(rot[0]);
plgInitialValue.child(1).getValue(rot[1]);
plgInitialValue.child(2).getValue(rot[2]);
vec3f vel;
plgInitialValue.setAttribute(rigidBodyNode::ia_initialVelocity);
plgInitialValue.child(0).getValue(vel[0]);
plgInitialValue.child(1).getValue(vel[1]);
plgInitialValue.child(2).getValue(vel[2]);
vec3f spin;
plgInitialValue.setAttribute(rigidBodyNode::ia_initialSpin);
plgInitialValue.child(0).getValue(spin[0]);
plgInitialValue.child(1).getValue(spin[1]);
plgInitialValue.child(2).getValue(spin[2]);
MEulerRotation meuler(deg2rad(rot[0]), deg2rad(rot[1]), deg2rad(rot[2]));
MQuaternion mquat = meuler.asQuaternion();
rb->set_transform(pos, quatf((float)mquat.w, (float)mquat.x, (float)mquat.y, (float)mquat.z));
rb->set_linear_velocity(vel);
rb->set_angular_velocity(spin);
rb->set_kinematic(false);
fnTransform.setRotation(meuler);
fnTransform.setTranslation(MVector(pos[0], pos[1], pos[2]), MSpace::kTransform);
} else {
//passive rigid body, get the world trasform from from the shape node
MQuaternion mquat;
fnTransform.getRotation(mquat);
MVector mpos(fnTransform.getTranslation(MSpace::kTransform));
rb->set_transform(vec3f((float)mpos.x, (float)mpos.y, (float)mpos.z), quatf((float)mquat.w, (float)mquat.x, (float)mquat.y, (float)mquat.z));
rb->set_interpolation_transform(vec3f((float)mpos.x, (float)mpos.y, (float)mpos.z), quatf((float)mquat.w, (float)mquat.x, (float)mquat.y, (float)mquat.z));
rb->set_kinematic(true);
}
}
void initRigidBodyArray(MObject &node)
{
MFnDagNode fnDagNode(node);
rigidBodyArrayNode *rbNode = static_cast<rigidBodyArrayNode*>(fnDagNode.userNode());
std::vector<rigid_body_t::pointer>& rbs = rbNode->rigid_bodies();
if(fnDagNode.parentCount() == 0) {
std::cout << "No transform found!" << std::endl;
return;
}
MFnTransform fnTransform(fnDagNode.parent(0));
MPlug plgMass(node, rigidBodyArrayNode::ia_mass);
float mass = 0.f;
plgMass.getValue(mass);
bool active = (mass>0.f);
if(active) {
//active rigid body, set the world transform from the initial* attributes
MObject obj;
MPlug plgInitialPosition(node, rigidBodyArrayNode::ia_initialPosition);
MPlug plgInitialRotation(node, rigidBodyArrayNode::ia_initialRotation);
MPlug plgInitialVelocity(node, rigidBodyArrayNode::ia_initialVelocity);
MPlug plgInitialSpin(node, rigidBodyArrayNode::ia_initialSpin);
MPlug plgPosition(node, rigidBodyArrayNode::io_position);
MPlug plgRotation(node, rigidBodyArrayNode::io_rotation);
MPlug plgElement;
for(size_t j = 0; j < rbs.size(); ++j) {
vec3f pos;
plgElement = plgInitialPosition.elementByLogicalIndex(j);
plgElement.child(0).getValue(pos[0]);
plgElement.child(1).getValue(pos[1]);
plgElement.child(2).getValue(pos[2]);
vec3f rot;
plgElement = plgInitialRotation.elementByLogicalIndex(j);
plgElement.child(0).getValue(rot[0]);
plgElement.child(1).getValue(rot[1]);
plgElement.child(2).getValue(rot[2]);
vec3f vel;
plgElement = plgInitialVelocity.elementByLogicalIndex(j);
plgElement.child(0).getValue(vel[0]);
plgElement.child(1).getValue(vel[1]);
plgElement.child(2).getValue(vel[2]);
vec3f spin;
plgElement = plgInitialSpin.elementByLogicalIndex(j);
plgElement.child(0).getValue(spin[0]);
plgElement.child(1).getValue(spin[1]);
plgElement.child(2).getValue(spin[2]);
MEulerRotation meuler(deg2rad(rot[0]), deg2rad(rot[1]), deg2rad(rot[2]));
MQuaternion mquat = meuler.asQuaternion();
rbs[j]->set_transform(pos, quatf((float)mquat.w, (float)mquat.x, (float)mquat.y, (float)mquat.z));
rbs[j]->set_linear_velocity(vel);
rbs[j]->set_angular_velocity(spin);
rbs[j]->set_kinematic(false);
plgElement = plgPosition.elementByLogicalIndex(j);
plgElement.child(0).setValue(pos[0]);
plgElement.child(1).setValue(pos[1]);
plgElement.child(2).setValue(pos[2]);
plgElement = plgRotation.elementByLogicalIndex(j);
plgElement.child(0).setValue(rot[0]);
plgElement.child(1).setValue(rot[1]);
plgElement.child(2).setValue(rot[2]);
}
} else {
//passive rigid body, get the world trasform from from the position/rotation attributes
MPlug plgPosition(node, rigidBodyArrayNode::io_position);
MPlug plgRotation(node, rigidBodyArrayNode::io_rotation);
MPlug plgElement;
for(size_t j = 0; j < rbs.size(); ++j) {
vec3f pos;
plgElement = plgPosition.elementByLogicalIndex(j);
plgElement.child(0).getValue(pos[0]);
plgElement.child(1).getValue(pos[1]);
plgElement.child(2).getValue(pos[2]);
vec3f rot;
plgElement = plgRotation.elementByLogicalIndex(j);
plgElement.child(0).getValue(rot[0]);
plgElement.child(1).getValue(rot[1]);
plgElement.child(2).getValue(rot[2]);
MEulerRotation meuler(deg2rad(rot[0]), deg2rad(rot[1]), deg2rad(rot[2]));
MQuaternion mquat = meuler.asQuaternion();
rbs[j]->set_transform(pos, quatf((float)mquat.w, (float)mquat.x, (float)mquat.y, (float)mquat.z));
rbs[j]->set_kinematic(false);
}
}
}
//init the rigid bodies to it's first frame configuration
void dSolverNode::initRigidBodies(MPlugArray &rbConnections)
{
for(size_t i = 0; i < rbConnections.length(); ++i) {
MObject node = rbConnections[i].node();
MFnDependencyNode fnNode(node);
if(fnNode.typeId() == rigidBodyNode::typeId) {
initRigidBody(node);
updateConstraint(node);
} else if(fnNode.typeId() == rigidBodyArrayNode::typeId) {
initRigidBodyArray(node);
}
}
}
//gather previous and current frame transformations for substep interpolation
void dSolverNode::gatherPassiveTransforms(MPlugArray &rbConnections, std::vector<xforms_t> &xforms)
{
xforms.resize(0);
xforms_t xform;
for(size_t i = 0; i < rbConnections.length(); ++i) {
MObject node = rbConnections[i].node();
MFnDagNode fnDagNode(node);
if(fnDagNode.typeId() == rigidBodyNode::typeId) {
rigidBodyNode *rbNode = static_cast<rigidBodyNode*>(fnDagNode.userNode());
rigid_body_t::pointer rb = rbNode->rigid_body();
if(fnDagNode.parentCount() == 0) {
std::cout << "No transform found!" << std::endl;
continue;
}
MFnTransform fnTransform(fnDagNode.parent(0));
MPlug plgMass(node, rigidBodyNode::ia_mass);
float mass = 0.f;
plgMass.getValue(mass);
bool active = (mass>0.f);
if(!active) {
MQuaternion mquat;
fnTransform.getRotation(mquat);
MVector mpos(fnTransform.getTranslation(MSpace::kTransform));
rb->get_transform(xform.m_x0, xform.m_q0);
xform.m_x1 = vec3f((float)mpos.x, (float)mpos.y, (float)mpos.z);
xform.m_q1 = quatf((float)mquat.w, (float)mquat.x, (float)mquat.y, (float)mquat.z);
xforms.push_back(xform);
}
} else if(fnDagNode.typeId() == rigidBodyArrayNode::typeId) {
rigidBodyArrayNode *rbNode = static_cast<rigidBodyArrayNode*>(fnDagNode.userNode());
std::vector<rigid_body_t::pointer>& rbs = rbNode->rigid_bodies();
if(fnDagNode.parentCount() == 0) {
std::cout << "No transform found!" << std::endl;
return;
}
MPlug plgMass(node, rigidBodyArrayNode::ia_mass);
float mass = 0.f;
plgMass.getValue(mass);
bool active = (mass>0.f);
if(!active) {
MPlug plgPosition(node, rigidBodyArrayNode::io_position);
MPlug plgRotation(node, rigidBodyArrayNode::io_rotation);
MPlug plgElement;
for(size_t j = 0; j < rbs.size(); ++j) {
rbs[j]->get_transform(xform.m_x0, xform.m_q0);
plgElement = plgPosition.elementByLogicalIndex(j);
plgElement.child(0).getValue(xform.m_x1[0]);
plgElement.child(1).getValue(xform.m_x1[1]);
plgElement.child(2).getValue(xform.m_x1[2]);
vec3f rot;
plgElement = plgRotation.elementByLogicalIndex(j);
plgElement.child(0).getValue(rot[0]);
plgElement.child(1).getValue(rot[1]);
plgElement.child(2).getValue(rot[2]);
MEulerRotation meuler(deg2rad(rot[0]), deg2rad(rot[1]), deg2rad(rot[2]));
MQuaternion mquat = meuler.asQuaternion();
xform.m_q1 = quatf((float)mquat.w, (float)mquat.x, (float)mquat.y, (float)mquat.z);
xforms.push_back(xform);
}
}
}
}
}
//update the passive rigid bodies by interpolation of the previous and current frame
void dSolverNode::updatePassiveRigidBodies(MPlugArray &rbConnections, std::vector<xforms_t> &xforms, float t)
{
size_t pb = 0;
for(size_t i = 0; i < rbConnections.length(); ++i) {
MObject node = rbConnections[i].node();
MFnDagNode fnDagNode(node);
if(fnDagNode.typeId() == rigidBodyNode::typeId) {
rigidBodyNode *rbNode = static_cast<rigidBodyNode*>(fnDagNode.userNode());
rigid_body_t::pointer rb = rbNode->rigid_body();
if(fnDagNode.parentCount() == 0) {
std::cout << "No transform found!" << std::endl;
continue;
}
MPlug plgMass(node, rigidBodyNode::ia_mass);
float mass = 0.f;
plgMass.getValue(mass);
bool active = (mass>0.f);
if(!active) {
/* Why do we need that?
Static objects are animated in Maya
So just set transform as is
//do linear interpolation for now
rb->set_transform(xforms[pb].m_x0 + t * (xforms[pb].m_x1 - xforms[pb].m_x0),
normalize(xforms[pb].m_q0 + t * (xforms[pb].m_q1 - xforms[pb].m_q0)));
*/
rb->set_transform(xforms[pb].m_x1, xforms[pb].m_q1);
++pb;
}
} else if(fnDagNode.typeId() == rigidBodyArrayNode::typeId) {
rigidBodyArrayNode *rbNode = static_cast<rigidBodyArrayNode*>(fnDagNode.userNode());
std::vector<rigid_body_t::pointer>& rbs = rbNode->rigid_bodies();
if(fnDagNode.parentCount() == 0) {
std::cout << "No transform found!" << std::endl;
return;
}
MPlug plgMass(node, rigidBodyArrayNode::ia_mass);
float mass = 0.f;
plgMass.getValue(mass);
bool active = (mass>0.f);
if(!active) {
for(size_t j = 0; j < rbs.size(); ++j) {
rbs[j]->set_transform(xforms[pb].m_x0 + t * (xforms[pb].m_x1 - xforms[pb].m_x0),
normalize(xforms[pb].m_q0 + t * (xforms[pb].m_q1 - xforms[pb].m_q0)));
++pb;
}
}
}
}
}
void dSolverNode::updateConstraint(MObject& bodyNode)
{
MFnDagNode fnDagNode(bodyNode);
rigidBodyNode *rbNode = static_cast<rigidBodyNode*>(fnDagNode.userNode());
MPlug plgMessages(bodyNode, rbNode->message);
MPlugArray rbMsgConnections;
plgMessages.connectedTo(rbMsgConnections, false, true);
for(size_t j = 0; j < rbMsgConnections.length(); j++)
{
MObject msgNode = rbMsgConnections[j].node();
MFnDagNode msgDagNode(msgNode);
if(msgDagNode.typeId() == nailConstraintNode::typeId)
{
nailConstraintNode* nailNode = static_cast<nailConstraintNode*>(msgDagNode.userNode());
if(msgDagNode.parentCount() == 0)
{
std::cout << "No transform for nail constraint found!" << std::endl;
continue;
}
MFnTransform msgTransform(msgDagNode.parent(0));
nail_constraint_t::pointer nail = nailNode->constraint();
vec3f constrPos;
nail->get_world(constrPos);
msgTransform.setTranslation(MVector(constrPos[0], constrPos[1], constrPos[2]), MSpace::kTransform);
msgTransform.setRotation(MEulerRotation(0., 0., 0.));
}
if(msgDagNode.typeId() == hingeConstraintNode::typeId)
{
hingeConstraintNode* hingeNode = static_cast<hingeConstraintNode*>(msgDagNode.userNode());
if(msgDagNode.parentCount() == 0)
{
std::cout << "No transform for hinge constraint found!" << std::endl;
continue;
}
MFnTransform msgTransform(msgDagNode.parent(0));
hinge_constraint_t::pointer hinge = hingeNode->constraint();
vec3f constrPos;
quatf constrRot;
hinge->get_world(constrPos, constrRot);
msgTransform.setTranslation(MVector(constrPos[0], constrPos[1], constrPos[2]), MSpace::kTransform);
msgTransform.setRotation(MQuaternion(constrRot[1], constrRot[2], constrRot[3], constrRot[0]));
}
if(msgDagNode.typeId() == sliderConstraintNode::typeId)
{
sliderConstraintNode* sliderNode = static_cast<sliderConstraintNode*>(msgDagNode.userNode());
if(msgDagNode.parentCount() == 0)
{
std::cout << "No transform for slider constraint found!" << std::endl;
continue;
}
MFnTransform msgTransform(msgDagNode.parent(0));
slider_constraint_t::pointer slider = sliderNode->constraint();
vec3f constrPos;
quatf constrRot;
slider->get_world(constrPos, constrRot);
msgTransform.setTranslation(MVector(constrPos[0], constrPos[1], constrPos[2]), MSpace::kTransform);
msgTransform.setRotation(MQuaternion(constrRot[1], constrRot[2], constrRot[3], constrRot[0]));
}
if(msgDagNode.typeId() == sixdofConstraintNode::typeId)
{
sixdofConstraintNode* sixdofNode = static_cast<sixdofConstraintNode*>(msgDagNode.userNode());
if(msgDagNode.parentCount() == 0)
{
std::cout << "No transform for sixdof constraint found!" << std::endl;
continue;
}
MFnTransform msgTransform(msgDagNode.parent(0));
sixdof_constraint_t::pointer sixdof = sixdofNode->constraint();
vec3f constrPos;
quatf constrRot;
sixdof->get_world(constrPos, constrRot);
msgTransform.setTranslation(MVector(constrPos[0], constrPos[1], constrPos[2]), MSpace::kTransform);
msgTransform.setRotation(MQuaternion(constrRot[1], constrRot[2], constrRot[3], constrRot[0]));
}
}
}
//update the scene after a simulation step
void dSolverNode::updateActiveRigidBodies(MPlugArray &rbConnections)
{
//update the active rigid bodies to the new configuration
for(size_t i = 0; i < rbConnections.length(); ++i) {
MObject node = rbConnections[i].node();
MFnDagNode fnDagNode(node);
if(fnDagNode.typeId() == rigidBodyNode::typeId) {
rigidBodyNode *rbNode = static_cast<rigidBodyNode*>(fnDagNode.userNode());
rigid_body_t::pointer rb = rbNode->rigid_body();
if(fnDagNode.parentCount() == 0) {
std::cout << "No transform found!" << std::endl;
continue;
}
MFnTransform fnTransform(fnDagNode.parent(0));
MPlug plgMass(node, rigidBodyNode::ia_mass);
float mass = 0.f;
plgMass.getValue(mass);
bool active = (mass>0.f);
if(active) {
quatf rot;
vec3f pos;
rb->get_transform(pos, rot);
fnTransform.setRotation(MQuaternion(rot[1], rot[2], rot[3], rot[0]));
fnTransform.setTranslation(MVector(pos[0], pos[1], pos[2]), MSpace::kTransform);
}
updateConstraint(node);
} else if(fnDagNode.typeId() == rigidBodyArrayNode::typeId) {
rigidBodyArrayNode *rbNode = static_cast<rigidBodyArrayNode*>(fnDagNode.userNode());
std::vector<rigid_body_t::pointer>& rbs = rbNode->rigid_bodies();
MPlug plgMass(node, rigidBodyArrayNode::ia_mass);
float mass = 0.f;
plgMass.getValue(mass);
bool active = (mass>0.f);
//write the position and rotations
if(active) {
MPlug plgPosition(node, rigidBodyArrayNode::io_position);
MPlug plgRotation(node, rigidBodyArrayNode::io_rotation);
MPlug plgElement;
for(size_t j = 0; j < rbs.size(); ++j) {
vec3f pos;
quatf rot;
rbs[j]->get_transform(pos, rot);
MEulerRotation meuler(MQuaternion(rot[1], rot[2], rot[3], rot[0]).asEulerRotation());
plgElement = plgPosition.elementByLogicalIndex(j);
plgElement.child(0).setValue(pos[0]);
plgElement.child(1).setValue(pos[1]);
plgElement.child(2).setValue(pos[2]);
plgElement = plgRotation.elementByLogicalIndex(j);
plgElement.child(0).setValue(rad2deg(meuler.x));
plgElement.child(1).setValue(rad2deg(meuler.y));
plgElement.child(2).setValue(rad2deg(meuler.z));
}
}
//check if we have to output the rigid bodies to a file
MPlug plgFileIO(node, rigidBodyArrayNode::ia_fileIO);
bool doIO;
plgFileIO.getValue(doIO);
if(doIO) {
dumpRigidBodyArray(node);
}
}
}
}
//apply fields in the scene from the rigid body
void dSolverNode::applyFields(MPlugArray &rbConnections, float dt)
{
MVectorArray position;
MVectorArray velocity;
MDoubleArray mass;
std::vector<rigid_body_t*> rigid_bodies;
//gather active rigid bodies
for(size_t i = 0; i < rbConnections.length(); ++i) {
MObject node = rbConnections[i].node();
MFnDagNode fnDagNode(node);
if(fnDagNode.typeId() == rigidBodyNode::typeId) {
rigidBodyNode *rbNode = static_cast<rigidBodyNode*>(fnDagNode.userNode());
rigid_body_t::pointer rb = rbNode->rigid_body();
MPlug plgMass(node, rigidBodyNode::ia_mass);
float mass = 0.f;
plgMass.getValue(mass);
bool active = (mass>0.f);
if(active) {
rigid_bodies.push_back(rb.get());
}
} else if(fnDagNode.typeId() == rigidBodyArrayNode::typeId) {
rigidBodyArrayNode *rbNode = static_cast<rigidBodyArrayNode*>(fnDagNode.userNode());
std::vector<rigid_body_t::pointer>& rbs = rbNode->rigid_bodies();
MPlug plgMass(node, rigidBodyArrayNode::ia_mass);
float mass = 0.f;
plgMass.getValue(mass);
bool active = (mass>0.f);
if(active) {
for(size_t j = 0; j < rbs.size(); ++j) {
rigid_bodies.push_back(rbs[j].get());
}
}
}
}
//clear forces and get the properties needed for field computation
for(size_t i = 0; i < rigid_bodies.size(); ++i) {
rigid_bodies[i]->clear_forces();
vec3f pos, vel;
quatf rot;
rigid_bodies[i]->get_transform(pos, rot);
rigid_bodies[i]->get_linear_velocity(vel);
position.append(MVector(pos[0], pos[1], pos[2]));
velocity.append(MVector(vel[0], vel[1], vel[2]));
//TODO
mass.append(1.0);
//
}
//apply the fields to the rigid bodies
MVectorArray force;
for(MItDag it(MItDag::kDepthFirst, MFn::kField); !it.isDone(); it.next()) {
MFnField fnField(it.item());
fnField.getForceAtPoint(position, velocity, mass, force, dt);
for(size_t i = 0; i < rigid_bodies.size(); ++i) {
rigid_bodies[i]->apply_central_force(vec3f((float)force[i].x, (float)force[i].y, (float)force[i].z));
}
}
}
void dSolverNode::computeRigidBodies(const MPlug& plug, MDataBlock& data)
{
// std::cout << "dSolverNode::computeRigidBodies" << std::endl;
bool enabled = data.inputValue(ia_enabled).asBool();
if(!enabled) {
data.outputValue(oa_rigidBodies).set(true);
data.setClean(plug);
return;
}
MTime time = data.inputValue(ia_time).asTime();
MTime startTime = data.inputValue(ia_startTime).asTime();
int subSteps = data.inputValue(ia_substeps).asInt();
MObject thisObject = thisMObject();
MPlug plgRigidBodies(thisObject, oa_rigidBodies);
MPlugArray rbConnections;
plgRigidBodies.connectedTo(rbConnections, true, true);
MPlug plgSplitImpulse(thisObject, ia_splitImpulse);
bool splitImpulseEnabled;
plgSplitImpulse.getValue(splitImpulseEnabled);
if(time == startTime) {
//first frame, init the simulation
isStartTime = true;
initRigidBodies(rbConnections);
solver_t::set_split_impulse(splitImpulseEnabled);
m_prevTime = time;
} else {
isStartTime = false;
double delta_frames = (time - m_prevTime).value();
bool playback = MConditionMessage::getConditionState("playingBack");
if(time > m_prevTime &&
((delta_frames <= 1.0) || (playback && delta_frames < 100))) {
//step the simulation forward,
//don't update if we are jumping more than one frame
float dt = (float)(time - m_prevTime).as(MTime::kSeconds);
//gather start and end transform for passive rigid bodies, used for interpolation
std::vector<xforms_t> passiveXForms;
gatherPassiveTransforms(rbConnections, passiveXForms);
//set the gravity in the solver
MPlug plgGravity(thisObject, ia_gravity);
vec3f gravity;
plgGravity.child(0).getValue(gravity[0]);
plgGravity.child(1).getValue(gravity[1]);
plgGravity.child(2).getValue(gravity[2]);
solver_t::set_gravity(gravity);
solver_t::set_split_impulse(splitImpulseEnabled);
for(int i = 1; i <= subSteps; ++i) {
//first update the passive rigid bodies
updatePassiveRigidBodies(rbConnections, passiveXForms, i * dt / subSteps);
//fields
applyFields(rbConnections, dt / subSteps);
//step the simulation
solver_t::step_simulation(dt / subSteps);
}
m_prevTime = time;
updateActiveRigidBodies(rbConnections);
}
}
data.outputValue(oa_rigidBodies).set(true);
data.setClean(plug);
}
void tokenize(const std::string& str,
std::vector<std::string>& tokens,
const std::string& delimiters = " ")
{
// Skip delimiters at beginning.
std::string::size_type lastPos = str.find_first_not_of(delimiters, 0);
// Find first "non-delimiter".
std::string::size_type pos = str.find_first_of(delimiters, lastPos);
while (std::string::npos != pos || std::string::npos != lastPos)
{
// Found a token, add it to the vector.
tokens.push_back(str.substr(lastPos, pos - lastPos));
// Skip delimiters. Note the "not_of"
lastPos = str.find_first_not_of(delimiters, pos);
// Find next "non-delimiter"
pos = str.find_first_of(delimiters, lastPos);
}
}
void replace(std::string &str, std::string const& what, std::string const& with_what)
{
size_t pos;
while((pos = str.find(what)) != std::string::npos) {
str.replace(pos, pos + what.size(), with_what);
}
}
bool dSolverNode::expandFileExpression(std::string const& expr, std::string &base_name, std::string &extension)
{
std::vector<std::string> tokens;
//check for extension
tokenize(expr, tokens, ".");
if(tokens.size() < 2) return false;
if(tokens.back().size() != 3) return false;
extension = tokens.back();
std::copy(expr.begin(), expr.end() - 4, std::back_inserter(base_name));
int time = (int) m_prevTime.value();
std::stringstream ss;
ss << time;
std::string str_time(ss.str());
//replace ${frame}
replace(base_name, "${frame}", str_time);
//replace ${waveFrame}
while(str_time.size() < 4) str_time = "0" + str_time;
replace(base_name, "${waveFrame}", str_time);
return true;
}
void dSolverNode::dumpRigidBodyArray(MObject &node)
{
// std::cout << "dSolverNode::dumpRigidBodyArray" << std::endl;
MFnDagNode fnDagNode(node);
rigidBodyArrayNode *rbaNode = static_cast<rigidBodyArrayNode*>(fnDagNode.userNode());
MPlug plgFiles(node, rigidBodyArrayNode::ia_fioFiles);
// MPlug plgPositionAttribute(node, rigidBodyArrayNode::ia_fioPositionAttribute);
// MPlug plgRotationAttribute(node, rigidBodyArrayNode::ia_fioRotationAttribute);
MString mstr;
plgFiles.getValue(mstr);
std::string expr(mstr.asChar());
std::string base_path, extension;
if(!expandFileExpression(expr, base_path, extension)) {
std::cout << "dSolverNode::dumpRigidBodyArray: syntax error in file expression: " << std::endl <<
expr << std::endl;
return;
}
if(extension != "pdb") {
std::cout << "dSolverNode::dumpRigidBodyArray: only pdb files are supported" << std::endl;
return;
}
std::string file_name = base_path + "." + extension;
std::vector<rigid_body_t::pointer>& rbs = rbaNode->rigid_bodies();
pdb_io_t pdb_io;
pdb_io.m_num_particles = rbs.size();
pdb_io.m_time = (float)m_prevTime.value();
pdb_io.m_attributes.resize(3);
//position
pdb_io.m_attributes[0].m_num_elements = 1;
pdb_io.m_attributes[0].m_name = ATTR_POSITION;
pdb_io.m_attributes[0].m_type = pdb_io_t::kVector;
pdb_io.m_attributes[0].m_vector_data.resize(rbs.size());
//rotation angle (in degrees)
pdb_io.m_attributes[1].m_num_elements = 1;
pdb_io.m_attributes[1].m_name = ATTR_IN_RANGLE;
pdb_io.m_attributes[1].m_type = pdb_io_t::kReal;
pdb_io.m_attributes[1].m_real_data.resize(rbs.size());
//rotation vector
pdb_io.m_attributes[2].m_num_elements = 1;
pdb_io.m_attributes[2].m_name = ATTR_IN_RAXIS;
pdb_io.m_attributes[2].m_type = pdb_io_t::kVector;
pdb_io.m_attributes[2].m_vector_data.resize(rbs.size());
for(size_t i = 0; i < rbs.size(); ++i) {
vec3f pos;
quatf rot;
rbs[i]->get_transform(pos, rot);
pdb_io.m_attributes[0].m_vector_data[i].x = pos[0];
pdb_io.m_attributes[0].m_vector_data[i].y = pos[1];
pdb_io.m_attributes[0].m_vector_data[i].z = pos[2];
//
vec3f axis;
float angle;
q_to_axis_angle(rot, axis, angle);
pdb_io.m_attributes[1].m_real_data[i] = rad2deg(angle);
pdb_io.m_attributes[2].m_vector_data[i].x = axis[0];
pdb_io.m_attributes[2].m_vector_data[i].y = axis[1];
pdb_io.m_attributes[2].m_vector_data[i].z = axis[2];
}
std::ofstream out(file_name.c_str());
pdb_io.write(out);
}