Being a plugin itself, the IK system allows the solver to be a separate plugin. The IKSolver
class defines the base class that plugin solver should derive from. The IK solver is a pure mathematical function: it does not hold state and just solves a given, self-contained, mathematical problem. In other words, the plugin solver does not have influence on when IK is invoked and what an IK problem is (what is the goal and what are the joints, etc.), but contributes to IK by answering how to solve. Structurally, it is independent of the SDK and, hence, can be built independently, except for some theoretically independent math library.
Plugin solvers are recognized by the IK system by the Super Class ID. A unique enum, IK_SOLVER_CLASS_ID
, defined in animtbl
.h, is given to it. The SClass_ID
SuperClassID() method should not to be overridden. Since it is a pure mathematical function that does not hold state, an individual plugin solver is identified by its class name which can be obtained using the GetClassName()
method. When class name clashes, a suffix may be appended. The class name will appear in the solver list from that users can pick for or assign to IK chains.
Following methods are meant to be overridden by the plugin solver queried by the IK system.
IsInteractive()
. IK can be used as a controller or as an interactive manipulation tool. In the former, the relationship between the goal and the joints are permanent: joints are completely controlled by the goal. In the latter, the relationship is transient, existing only during interactive manipulation. In the end, IK solutions are registered at each joint, mostly likely as key-frames, and it no longer matters how joints got their joint angles. Only non-interactive, or controller, IK solvers ares used.
IsHistoryDependent()
. At a specific animation time, the history dependent solver will reach solutions not only based the state of goal at the time, but also its previous states (hence they are history dependent). On the contrary, the history independent solver does its job based on the state of the goal just at the time. The procedural implication is that, when the goal is changed at time t, the IK system would have to invalidate joints, at time t for the history independent solver, and at all times that are greater or equal to t for the history dependent solver. Only history dependent solvers are used by the IK system.
The methods UseSlidingJoint()
and UseSwivelAngle()
are used to tell whether the plugin solver intends to use the sliding joint (translational degrees of freedom) or the swivel angle parameter of the IK chain.
When two IK chains overlap, i.e., if there's a joint belonging to both IK chains, some solvers are able to negotiate between the possibly contending goals and some are not. The method DoesOneChainOnly()
is used to determine this. For those that can only solve one chain at a time, the IK system will pass to the solvers one chain at a time in a definitive order. Only solvers that "do one chain only" are used.
The method DoesRootJointLimits()
and DoesJointLimitsButRoot()
deal with the concern of joint limits. If the solver supports joint limits, the IK system will trust it. Otherwise, the IK system will, after calling the solver, clamp the results according the joint limit constraints. The root joint in DoesRootJointLimits()
refers to the Start Joint. It is treated differently from the rest of joints.
The method SolveEERotation()
tells whether the rotation part of the goal node will be used. If it returns false, only the position of the goal node is taken as the IK goal and rotation threshold will be irrelevant.
Solvers can reach solutions with closed formula, analytically, or going through iterations. For an analytic solver, thresholds and maximum iteration numbers are not relevant. Checking whether or not a solver is analytical can be done using the IsAnalytic()
method.
The methods GetPosThreshold()
, GetRotThreshold(), GetMaxIteration(), SetPosThreshold(), SetRotThreshold(), SetMaxIteration() are used to get and set the Position Threshold, Rotation Threshold, and the Maximum number allowed for iterations and are not relevant for all solvers. For an analytic solver, for example, these are not used at all. The IK system, however, may set values to them.
As mentioned earlier, plugin solvers may have their own Zero Plane Map. If so, they must override the IKSys::ZeroPlaneMap
* GetZeroPlaneMap() method. The IK system will need it to perform IK snapping: setting the swivel angle based on the current pose so that the pose is consistent with the swivel angle.
Solve()
is the method which the IK system will call when its time to update the joints according to the IK goal and other parameters. The IK system will compile an IK problem represented in the 3ds Max scene data structure, nodes, controllers, etc., into a pure mathematical representation, IKSys::LinkChain
. This data structure only represents one IK chain. Overlapping IK chains are not dealt with. The ReturnCondition
, returned by the Solve()
method is a bit-set. Its definition is copied from IKHierarchy
.h:
typedefunsignedReturnCondition;
enum ConditionBit
{
bLimitReached = 0x00000001,
bLimitClamped = 0x00000002,
bMaxIterationReached = 0x00000004,
// The first eight bits are reserved for mild condition.
// They are still considered successful.
bGoalTooCloseToEE = 0x00000100,
bInvalidArgument = 0x00000200,
bInvalidInitialValue = 0x00000400
};
The data structure passed to the solver is transient, meaning that it will be discarded once the solution is copied back to the joints. If the return condition indicates failure, (return_condition
> 0xff) then the result will not be copied back to the joint nodes in the 3ds Max scene database.