除了旋转矩阵,旋转可以用角度表示,也可以用complex number of the unit circle表示,但它确实是一回事。更重要的是,你需要一个代表T
的rigid body transformations,这样你就可以编写像t1 * t2 * t3
这样的东西来计算第三个链接的位置和方向。
使用atan2
来计算angle between the vectors。
正如以下Python示例所示,这两件事足以构建一个小型IK解算器。
from gameobjects.vector2 import Vector2 as V
from matrix33 import Matrix33 as T
from math import sin, cos, atan2, pi
import random
的gameobjects库没有2D转换,所以你要自己写matrix33
。它的接口就像gameobjects.matrix44
。
定义从一个关节到下一个关节转换的正向运动学函数。我们通过angle
担任联合旋转,接着是一个固定的转型joint
:
def fk_joint(joint, angle): return T.rotation(angle) * joint
工具的转型是tool == fk(joints, q)
其中joints
是固定的转换和q
是关节角度:
def fk(joints, q):
prev = T.identity()
for i, joint in enumerate(joints):
prev = prev * fk_joint(joint, q[i])
return prev
如果手臂的底部有偏移量,则替换T.identity()
变换。
OP正在通过循环坐标下降来解决位置IK问题。这个想法是通过一次调整一个关节变量来将工具靠近目标位置。设q
为联合的角度,并且prev
为联合的基础的变换。接头应通过向工具和目标位置的矢量之间的角度进行旋转:
def ccd_step(q, prev, tool, goal):
a = tool.get_position() - prev.get_position()
b = goal - prev.get_position()
return q + atan2(b.get_y(), b.get_x()) - atan2(a.get_y(), a.get_x())
遍历关节和更新关节值的每一个变化的工具结构:
def ccd_sweep(joints, tool, q, goal):
prev = T.identity()
for i, joint in enumerate(joints):
next = prev * fk_joint(joint, q[i])
q[i] = ccd_step(q[i], prev, tool, goal)
prev = prev * fk_joint(joint, q[i])
tool = prev * next.get_inverse() * tool
return prev
注意对于3D,fk()
和ccd_sweep()
是相同的;你只需要重写fk_joint()
和ccd_step()
。
构造一个臂n
相同的链接和运行CCD扫描的cnt
迭代,从随机臂形态q
开始:
def ccd_demo(n, cnt):
q = [random.uniform(-pi, pi) for i in range(n)]
joints = [T.translation(0, 1)] * n
tool = fk(joints, q)
goal = V(0.9, 0.75) # Some arbitrary goal.
print "i Error"
for i in range(cnt):
tool = ccd_sweep(joints, tool, q, goal)
error = (tool.get_position() - goal).get_length()
print "%d %e" % (i, error)
我们可以尝试求解器和比较收敛的不同数量的速度链接:
>>> ccd_demo(3, 7)
i Error
0 1.671521e-03
1 8.849190e-05
2 4.704854e-06
3 2.500868e-07
4 1.329354e-08
5 7.066271e-10
6 3.756145e-11
>>> ccd_demo(20, 7)
i Error
0 1.504538e-01
1 1.189107e-04
2 8.508951e-08
3 6.089372e-11
4 4.485040e-14
5 2.601336e-15
6 2.504777e-15