Technical solution? IK arm, box end, movable pivots?

Hey guys, I’ve got a technical problem that I need some help trying to find a solution to. Kind of hard to explain so I’ll include pictures :slight_smile:


I have an IK leg like this on a robot with a cubed foot. It needs to be able to roll from the inner edge to laying flat to laying on the outer edge. However it’s not simple, it also needs to be able to pivot on the box corners and side edges. Ideally this would be as automated as possible. I’ve been racking my brain on this for a few days and can’t seem to figure out a perfect solution. Has anyone else ever run into this issue, how did you/would you solve it?

As of right now if you grab the body and move it around, the edges of the cube will clip through the ground plane.

I wrote a node to do that very thing quite recently.

I gave it the two rotation inputs, i then have four position offset vectors ( relative to the pivot point ), and the pivot point matrix. From there, i create a TransformationMatrix based on the input matrix, offset it by the offset vectors ( depending on the rotation inputs being postive/negative etc ), then rotate by those values.

That ends up giving you a control that you can rotate on two axis and it will change where it pivots from on the fly. I’ll see if i can share some of the code and a video tomorrow.

[QUOTE=MikeM;21777]I wrote a node to do that very thing quite recently.

I gave it the two rotation inputs, i then have four position offset vectors ( relative to the pivot point ), and the pivot point matrix. From there, i create a TransformationMatrix based on the input matrix, offset it by the offset vectors ( depending on the rotation inputs being postive/negative etc ), then rotate by those values.

That ends up giving you a control that you can rotate on two axis and it will change where it pivots from on the fly. I’ll see if i can share some of the code and a video tomorrow.[/QUOTE]

Thanks that sounds great. Would it work for the end of an IK leg though? I figure with this setup I’d have to control the pole vector and the ik solver. I can’t control the beginning of the joint chain as it’s constrained to the body.

Mike! I’d love see any details on the solution you wrote about! Sounds pretty neat.

Somewhat related from Chris Lesage:

[QUOTE=Phil;21781]Somewhat related from Chris Lesage:

http://chrislesage.com/character-rigging/using-a-unit-vector-to-make-a-cylindrical-foot-roll/[/QUOTE]

Yes, I did give this a try but it’s more for a foot that can pivot on the end of an IK leg. My situation has no “foot”, it’s more a block stump.

Well I finally came up with something that works. I’d still love to hear what everyone else has to say and if they could also comment on this solution.

Sample File: https://dl.dropboxusercontent.com/u/77176982/drill_leg_test_02.mb (Maya 2012)

Basically I made a duplicate of the IK chain, parented some locators under it, got the lowest locator using an expression and created an offset to the IK solver using that. The next step would be to add a sub-control so that I could allow the animator to set the ground level. Only seems to work in playback and not real-time (better than nothing).

Here is a video of the node implementation I made ( which I think is similar to what you’re after having looked at your maya scene )

Video Link

Below I have copy pasted the main block of code. I have left out the debug drawing OGL stuff, but you can just remove those calls if you wanted to compile and try it. It could be optimised a bit more, but it does the job.

cpp file


//-------------------------------------------
// Attribute Declaration : Inputs
MObject starPivot::inBackAttr;
MObject starPivot::inFrontAttr;
MObject starPivot::inLeftAttr;
MObject starPivot::inRightAttr;

MObject starPivot::inRollAttr;
MObject starPivot::inBankAttr;

MObject starPivot::inRotationFactorAttr;
MObject starPivot::inOffsetPivotAttr;

MObject starPivot::inDebuggingAttr;

//-------------------------------------------
// Attribute Declaration : Outputs
MObject starPivot::outTargetAttr;
MObject starPivot::outRotateAttr;
MObject starPivot::outTranslateAttr;



//--------------------------------------------------------------------------------
// Returns a new instance of this
// node
 void* starPivot::creator()
{
	return new starPivot;
}

 
//--------------------------------------------------------------------------------
// Called on the initialisation of the node
MStatus starPivot::initialize()
{

	// Define the attribute types we need to use
	MFnNumericAttribute  numericAttrFn;
	MFnMatrixAttribute   matrixAttrFn;
	MFnUnitAttribute     unitAttrFn;
	MFnCompoundAttribute compoundAttrFn;

	// Add our input attributes
	starPivot::inBackAttr = numericAttrFn.create("inNegZPivot", "ibp", MFnNumericData::kDouble);
	addAttribute(starPivot::inBackAttr);

	starPivot::inFrontAttr = numericAttrFn.create("inPosZPivot", "ifp", MFnNumericData::kDouble);
	addAttribute(starPivot::inFrontAttr);

	starPivot::inLeftAttr = numericAttrFn.create("inPosXPivot", "ilp", MFnNumericData::kDouble);
	addAttribute(starPivot::inLeftAttr);

	starPivot::inRightAttr = numericAttrFn.create("inNegXPivot", "irp", MFnNumericData::kDouble);
	addAttribute(starPivot::inRightAttr);

	starPivot::inRotationFactorAttr = numericAttrFn.create("inRotationFactor", "irf", MFnNumericData::kDouble);
	addAttribute(starPivot::inRotationFactorAttr);

	starPivot::inOffsetPivotAttr = matrixAttrFn.create("inOffset", "iof");
	addAttribute(starPivot::inOffsetPivotAttr);

	starPivot::inRollAttr = numericAttrFn.create("inXRoll", "ir", MFnNumericData::kDouble);
	numericAttrFn.setSoftMax(1);
	numericAttrFn.setSoftMin(-1);
	numericAttrFn.setKeyable(true);
	addAttribute(starPivot::inRollAttr);

	starPivot::inBankAttr = numericAttrFn.create("inZRoll", "ib", MFnNumericData::kDouble);
	numericAttrFn.setSoftMax(1);
	numericAttrFn.setSoftMin(-1);
	numericAttrFn.setKeyable(true);
	addAttribute(starPivot::inBankAttr);

	starPivot::inDebuggingAttr = numericAttrFn.create("inDebugging", "idb", MFnNumericData::kBoolean );
	addAttribute( starPivot::inDebuggingAttr );

	// Add our output attributes
	starPivot::outTargetAttr = matrixAttrFn.create("outMat4", "om");
	addAttribute(starPivot::outTargetAttr);

	MObject rX = unitAttrFn.create("rotationX", "rx", MFnUnitAttribute::kAngle);
	MObject rY = unitAttrFn.create("rotationY", "ry", MFnUnitAttribute::kAngle);
	MObject rZ = unitAttrFn.create("rotationZ", "rz", MFnUnitAttribute::kAngle);

	starPivot::outRotateAttr = compoundAttrFn.create("outRotation", "ro");
	compoundAttrFn.addChild(rX);
	compoundAttrFn.addChild(rY);
	compoundAttrFn.addChild(rZ);
	addAttribute(starPivot::outRotateAttr);

	starPivot::outTranslateAttr = numericAttrFn.createPoint("outTranslate", "ot");
	addAttribute(starPivot::outTranslateAttr);

	// Define the dependency
	attributeAffects(starPivot::inBackAttr          , starPivot::outTargetAttr);
	attributeAffects(starPivot::inFrontAttr         , starPivot::outTargetAttr);
	attributeAffects(starPivot::inLeftAttr          , starPivot::outTargetAttr);
	attributeAffects(starPivot::inRightAttr         , starPivot::outTargetAttr);
	attributeAffects(starPivot::inBackAttr          , starPivot::outTargetAttr);
	attributeAffects(starPivot::inRollAttr          , starPivot::outTargetAttr);
	attributeAffects(starPivot::inBankAttr          , starPivot::outTargetAttr);
	attributeAffects(starPivot::inRotationFactorAttr, starPivot::outTargetAttr);
	attributeAffects(starPivot::inOffsetPivotAttr   , starPivot::outTargetAttr);

	attributeAffects(starPivot::inBackAttr          , starPivot::outRotateAttr);
	attributeAffects(starPivot::inFrontAttr         , starPivot::outRotateAttr);
	attributeAffects(starPivot::inLeftAttr          , starPivot::outRotateAttr);
	attributeAffects(starPivot::inRightAttr         , starPivot::outRotateAttr);
	attributeAffects(starPivot::inBackAttr          , starPivot::outRotateAttr);
	attributeAffects(starPivot::inRollAttr          , starPivot::outRotateAttr);
	attributeAffects(starPivot::inBankAttr          , starPivot::outRotateAttr);
	attributeAffects(starPivot::inRotationFactorAttr, starPivot::outRotateAttr);
	attributeAffects(starPivot::inOffsetPivotAttr   , starPivot::outRotateAttr);

	attributeAffects(starPivot::inBackAttr          , starPivot::outTranslateAttr);
	attributeAffects(starPivot::inFrontAttr         , starPivot::outTranslateAttr);
	attributeAffects(starPivot::inLeftAttr          , starPivot::outTranslateAttr);
	attributeAffects(starPivot::inRightAttr         , starPivot::outTranslateAttr);
	attributeAffects(starPivot::inBackAttr          , starPivot::outTranslateAttr);
	attributeAffects(starPivot::inRollAttr          , starPivot::outTranslateAttr);
	attributeAffects(starPivot::inBankAttr          , starPivot::outTranslateAttr);
	attributeAffects(starPivot::inRotationFactorAttr, starPivot::outTranslateAttr);
	attributeAffects(starPivot::inOffsetPivotAttr   , starPivot::outTranslateAttr);

	return MS::kSuccess;
}


//--------------------------------------------------------------------------------
// This is the caluclation method
MStatus starPivot::compute(const MPlug& plug, MDataBlock& dataBlock)
{

	// We want to do as little computation as we can
	// get away with, so start by reading out the 
	// roll and bank values
	double  roll   = dataBlock.inputValue(starPivot::inRollAttr          ).asDouble();
	double  bank   = dataBlock.inputValue(starPivot::inBankAttr          ).asDouble();
	double  factor = dataBlock.inputValue(starPivot::inRotationFactorAttr).asDouble();
	MMatrix offset = dataBlock.inputValue(starPivot::inOffsetPivotAttr   ).asMatrix();

	// Create a transform we can pass between 
	// transformations
	MMatrix mat4 = MMatrix();

	MVector translation(0,0,0);
	MEulerRotation eulerRotation(0,0,0);



	//------------------------------------------
	// ROLLING

	// Do we need to add any negativ rolling?
	if ( roll < -1e-99 )
	{
		double f = dataBlock.inputValue(starPivot::inFrontAttr).asDouble();
		translation = MVector(0, 0, f);
		eulerRotation = MEulerRotation( 0.0174532925 * (roll * -factor ), 0, 0);
	}

	// Do we need to add any positive rolling?
	if ( roll > 1e-99 ) 
	{
		double f      = dataBlock.inputValue(starPivot::inBackAttr).asDouble();
		translation   = MVector(0, 0, f);
		eulerRotation = MEulerRotation( 0.0174532925 * (roll * -factor ), 0, 0);
	}

	// Create a transformation class
	MTransformationMatrix rollXform = MTransformationMatrix();
	rollXform.setRotatePivot(translation, MSpace::kObject, false);
	rollXform.rotateBy(eulerRotation, MSpace::kObject);
	mat4 = rollXform.asMatrix() * mat4;

	
	//------------------------------------------
	// BANKING

	// Do we need to do any positive banking?
	if ( bank > -1e-99)
	{
		double f      = dataBlock.inputValue(starPivot::inLeftAttr).asDouble();
		translation   = MVector(f, 0, 0);
		eulerRotation = MEulerRotation( 0, 0, 0.0174532925 * (bank * -factor ));
	}

	// Do we need to do any negative banking?
	if ( bank < -1e-99)
	{
		double f = dataBlock.inputValue(starPivot::inRightAttr).asDouble();
		translation   = MVector(f, 0, 0);
		eulerRotation = MEulerRotation( 0, 0, 0.0174532925 * (bank * -factor ));
	}

	// Create a transformation class
	MTransformationMatrix bankXform = MTransformationMatrix();
	bankXform.setRotatePivot(translation, MSpace::kObject, true);
	bankXform.rotateBy(eulerRotation, MSpace::kObject);
	mat4 = bankXform.asMatrix() * mat4;

	// Apply the offset
	mat4 = offset * mat4;

	// Set the output variables
	dataBlock.outputValue(starPivot::outTargetAttr).set(mat4);

	// Now map the data to translate and 
	// rotate
	MTransformationMatrix outXform(mat4);
	
	MEulerRotation outEulerRotation = outXform.eulerRotation();
	MVector        rotationVector(outEulerRotation.x, outEulerRotation.y, outEulerRotation.z);
	MFloatVector   translationVector(outXform.getTranslation(MSpace::kWorld));

	MGlobal::displayInfo(MString("") + translationVector.x + ", " + translationVector.y + ", " + translationVector.z);

	dataBlock.outputValue(starPivot::outRotateAttr).set(rotationVector);
	dataBlock.outputValue(starPivot::outTranslateAttr).set(translationVector);
	


	return MStatus::kSuccess;
}


bool starPivot::drawLast() const
{
	return false;
}


void starPivot::draw(  M3dView& view,
						const MDagPath& path,
						M3dView::DisplayStyle style,
						M3dView::DisplayStatus status)
{
	// Get this node as a class
	MObject self = thisMObject();

	// First thing to do is ensure we need to 
	// draw...
	bool inEnabled = MPlug( self, starPivot::inDebuggingAttr).asBool();
	
	if ( inEnabled == false )
		return;
	
	// Define the used colors
	MFloatVector red(1, 0, 0);
	MFloatVector green(0, 1, 0);
	MFloatVector yellow(1, 1, 0);

	// Now we know we need to draw, so lets do so
	float len_back  = MPlug( self, starPivot::inBackAttr  ).asFloat();
	float len_front = MPlug( self, starPivot::inFrontAttr ).asFloat();
	float len_left  = MPlug( self, starPivot::inLeftAttr  ).asFloat();
	float len_right = MPlug( self, starPivot::inRightAttr ).asFloat();

	// Start drawing the quad
	view.beginGL();

	MMatrix m = MPlug( self, starPivot::outTargetAttr ).asMDataHandle().asMatrix();
	MMatrix identity = MMatrix();;

	drawGridPlane( view, m,  len_left, len_right, len_front, len_back, yellow, 1);
	drawGridPlane( view, identity,  len_left, len_right, len_front, len_back, red, 1);

	// End Drawing the GL
	view.endGL();

}


h file


#include <maya/MPxLocatorNode.h>
#include <maya/MDataBlock.h>
#include <maya/MPlug.h>
#include <maya/MStatus.h>

// Create a node class
class starPivot : public MPxLocatorNode
{
public :

	//-------------------------------------------
	// Returns a new instance of this
	// node
	static void* creator();

	//-------------------------------------------
	// Called on the initialisation of the node
	static MStatus initialize();

	//-------------------------------------------
	// This is the caluclation method
	MStatus compute(const MPlug& plug, MDataBlock& dataBlock);

	virtual bool drawLast() const;

	virtual void draw(  M3dView&               view ,
						const MDagPath&        path ,
						M3dView::DisplayStyle  style,
						M3dView::DisplayStatus status);

	//-------------------------------------------
	// Attribute Declaration : Inputs
	static MObject inBackAttr;
	static MObject inFrontAttr;
	static MObject inLeftAttr;
	static MObject inRightAttr;

	static MObject inRollAttr;
	static MObject inBankAttr;

	static MObject inRotationFactorAttr;
	static MObject inOffsetPivotAttr;

	static MObject inDebuggingAttr;

	//-------------------------------------------
	// Attribute Declaration : Outputs
	static MObject outTargetAttr;
	static MObject outRotateAttr;
	static MObject outTranslateAttr;

	//--------------------------------------------
	// Export Attribute
	static MObject exportableAttr;
};

Thanks for the input Mike. It’s much like the other link posted above, except more of a cube than a cylinder (the end result anyway). I’m still developing an answer to this solution and so far it works quite well. Right now it’s still an expression but I will be converting it to nodes shorty. When I come up with my final product I’ll post my setup.

Thanks Mike!