前言
记录在测试IKSolver节点开发过程中,由于文档解释不全的原因造成的坑点。
后面打算如果能成功迭代到3.0版,直接甩到github上。现在就是一点一点在摸。
官方案例->非常简单只是实现xy平面的root的旋转
坑点1:MaxIterations 和Tolerance属性设置
官方案例将最大迭代数和阈值的设置方在构造函数中,然后又把这句话注释掉了。测试发现,放在构造函数里行不通,需要放在doSolve()或者其他重写计算方法。也可以通过节点外设置。如果你选择在方法里设置了,node面板的属性值是不能再修改了
在方法里如何获取这两个属性:使用MFnIKSolver
的maxIterations
和tolerance
坑点2:如何获取IK chain的非末端关节的个数
这里想到实现两种方式,并且经过测试都是行的通的。一种是传统的开发一般node的思路,另一种就是用了IKSolver这种特殊节点的特性。
方法1
在自定义IKsolver节点中自定义一个显式接口,在初始化函数中,先定义其接口数据组织类型。然后在Maya面板中手动设置,然后通过MPlug
类来查找到这个plug当前引用的数值。
如果你写法得当,你将会在node的Extra Attributes中找到他。并且输入的数值是可以被接收的
方法2
sao操作:MPxIkSolverNode
的getJointAngles
返回的数组,得到这个数组的长度,然后就能得到IKchain的关节个数。但这个方法似乎记录的是基于世界的旋转?而且存放方式有些诡异,是zyx的顺序进行存放。
下面是两种方式的示例
MObject thisNode = thisMObject();
nodeFn.setObject(thisNode);
//========= get the num of joints in chain===========
MDoubleArray jointAngles;
int joint_num=0;
if (getJointAngles(jointAngles)){
joint_num = (int)jointAngles.length()/3;
}
else {// try to get num from the plug
MPlug thisPlug(thisNode, num);
joint_num = thisPlug.asInt();
}
坑点3:虽然MPxIkSolverNode的基类是MPxNode,但。。。
如果以下这些方法在从MPxIkSolverNode派生的类中被重写,它们将被忽略。
compute
getInternalValue
setInternalValue
legalConnection
legalDisconnection
connectionMade
connectionBroken
坑点4:怎么得到chain中串联的其他关节对象,并得到相对位置和世界位置
关键点:首先得到handle实例,拜托MFnIKHandle工具找到起始关节
MIkHandleGroup* handle_group = handleGroup();
if (NULL == handle_group) {
return MS::kFailure;
}
MObject handle = handle_group->handle(0);
// get handle path
MDagPath handlePath;
MDagPath::getAPathTo(handle, handlePath);
MFnIkHandle fnHandle(handlePath, &stat);
// get the start of the chain
MDagPath root_joint_path;
fnHandle.getStartJoint(root_joint_path);
然后事情就简单了。以root的子关节为例
nodeFn.setObject(root_joint_path);
MGlobal::displayInfo(nodeFn.name());
MObject sec_joint=nodeFn.child(0);
MFnTransform sec_tran(sec_joint);
MVector sec_position = sec_tran.rotatePivot(MSpace::kTransform);
可以查询到相对坐标,但我试了试,世界坐标查询不到。见了鬼了。因为这是替身攻击。啃了n久的文档,orz。下面是可以访问到世界和相对坐标的正确姿势,必须得到子对象的DagPath
MObject node_root = root_joint_path.node();
// find a child's object
nodeFn.setObject(root_joint_path);
MGlobal::displayInfo(nodeFn.name());
MObject sec_joint=nodeFn.child(0);
// get child's dagpath
MDagPath secPath;
MDagPath::getAPathTo(sec_joint, secPath);
MFnTransform start_transform(secPath);
// get pos of the child
MPoint sec_position = start_transform.rotatePivot(MSpace::kWorld);
PrintVector(sec_position.x, sec_position.y, sec_position.z,"The 2nd Joint in world space:");
MVector sec_vec = start_transform.getTranslation(MSpace::kTransform);
PrintVector(sec_vec.x, sec_vec.y, sec_vec.z, "The 2nd Joint in relative space:");
遍历chain,得到世界位置
// get the start of the chain
MDagPath root_joint_path;
fnHandle.getStartJoint(root_joint_path);
MFnTransform start_transform(root_joint_path);
MPoint start_position = start_transform.rotatePivot(MSpace::kWorld);
// store the chain
MPointArray joints_pos_list;
joints_pos_list.setLength(joint_num+1);
nodeFn.setObject(root_joint_path);
MObject p = nodeFn.child(0);
for (int i = 0; i < joint_num+1; ++i)// record the end joint
{
if (i == 0)
{
joints_pos_list[i] = start_position;
PrintVector(joints_pos_list[i].x, joints_pos_list[i].y, joints_pos_list[i].z, nodeFn.name() + "in WS:");
continue;
}
nodeFn.setObject(p);
MDagPath path;
MDagPath::getAPathTo(p, path);
MFnTransform tran(path);
joints_pos_list[i] = tran.rotatePivot(MSpace::kWorld);
PrintVector(joints_pos_list[i].x, joints_pos_list[i].y, joints_pos_list[i].z, nodeFn.name() + "in WS:");
p = nodeFn.child(0);
}
坑点5:此End Effector非彼End Effector
仅针对个人的理解。本彩笔之前在其他平台上实操IK的时候。end effector的位置一直被认为是末端关节移动的期望位置。然而观察节点编辑器可以发现,effector和末端关节是牢牢锁死的。也就是说Handler的位置才是期望位置。effector的位置其实是当前末端关节的位置。
MPoint ancesPos = joints_pos_list[i];
MPoint curEndPos = joints_pos_list[joint_num];//末端关节
MGlobal::displayInfo("done2");
double s = VectorSquaredDistance(curEndPos, end_effector_pos);
std::string str = std::to_string(s);
const char* ch = str.c_str();
MString sh(ch);
MGlobal::displayInfo("Distance between effector and end joint is :" + sh);
坑点6 关于数据更新
Maya不像unity继承monobehavior那样拥有update,也没有可以随时访问的Transform组件掌握数据动向。如果涉及多级相互影响的实例,更加注意更新的问题。尽量就是初始化的时候就要保存相关的DagPath,方便更新和读取。
坑点7 IKSolverNode特有的计算模式
IKSolver这个节点会有两个原本存在的变量,最大迭代数和阈值。当effector和handler的距离小于阈值或者达到最大迭代数,IKSolver节点会停止计算。换句话说,当自定义的IK算法是优化法时,大循环是不写的,maya会自动完成。如果算法是几何分析或者数学建模之类的,最大迭代数限制为1。