6.1 关节的创建和销毁
dJointID dJointCreateBall (dWorldID, dJointGroupID);
dJointID dJointCreateHinge (dWorldID, dJointGroupID);
dJointID dJointCreateSlider (dWorldID, dJointGroupID);
dJointID dJointCreateContact (dWorldID, dJointGroupID, const dContact *);
dJointID dJointCreateUniversal (dWorldID, dJointGroupID);
dJointID dJointCreateHinge2 (dWorldID, dJointGroupID);
dJointID dJointCreatePR (dWorldID, dJointGroupID);
dJointID dJointCreatePU (dWorldID, dJointGroupID);
dJointID dJointCreatePiston (dWorldID, dJointGroupID);
dJointID dJointCreateFixed (dWorldID, dJointGroupID);
dJointID dJointCreateAMotor (dWorldID, dJointGroupID);
dJointID dJointCreateLMotor (dWorldID, dJointGroupID);
dJointID dJointCreatePlane2D (dWorldID, dJointGroupID);
一个新创建的给定类型的关节,因为它并没有连接到任何一个刚体上,所以就会被初始化为“监狱模式(也就是说对当前的仿真是没有任何作用的)”。一般情况下分配给它的关节组ID为0,如果分配给它的是一个指定关节组的非零ID,那么就需要使用一个给定的dContact结构来初始化连接关节。
void dJointDestroy (dJointID);
如果需要销毁一个关节、断开和它连接的刚体或者是把它从world中移除,就可以使用上面的dJointDestroy函数。需要注意的是,如果销毁的关节式一个关节组的一员的话,这个函数时无能为力的,因为需要销毁的关节它所在的组必须为空或者是已被销毁的。
dJointGroupID dJointGroupCreate (int max_size);
上面的函数用来创建一个关节组,参数max_size在现在的版本中已经不再使用,但为了保证程序的向后兼容性,请将其置为0。
void dJointGroupDestroy (dJointGroupID);
void dJointGroupEmpty (dJointGroupID);
dJointGroupDestroy函数用来销毁一个关节组,包含在该组中的所有关节也同时被销毁掉。dJointGroupEmpty函数则只是销毁掉关节组中的所有关节,并不将关节组销毁掉,也就是说它的作用是清空一个关节组。
6.2 其它一些关节函数
在对关节模型的处理中,通常会需要对关节进行各种各样的操作以完成不同场景的不同需求。下面扼要地介绍一些常用的关节函数,以帮助对关节模型的了解。
void dJointAttach (dJointID, dBodyID body1, dBodyID body2);
有时候我们会需要将一个关节从一个已连接的刚体上分离下来之后连接到另一个刚体上,或者需要给关节连接一个新创建的刚体等等,这样的操作就可以通过上面的函数来实现。在对一个处于连接状态的关节进行此操作时,首先会断开已连接的刚体,然后再将它连接到一个新的刚体上。另外,如果只需要给关节连接一个刚体,就需要将body1或者body2置为0(0在这里代表的是一个静态的环境)。如果将这两个参数都置为0的话,关节就被转变为“监狱模式”,也就是说它不会对当前的仿真产生任何作用,当然在再次启用它的时候就需要做一些必要的设置工作。
注意:像hinge-2这样的关节需要被连接到两个刚体之上时才会起作用。
void dJointEnable (dJointID);
void dJointDisable (dJointID);
int dJointIsEnabled (dJointID);
上面的函数分别用来启用、禁用和获取关节的状态。被禁用的关节在仿真时会被完全忽略掉,但并不会丢失已经计算出的信息(如描点和轴心)。
void dJointSetData (dJointID, void *data);
void *dJointGetData (dJointID);
设置和获取关节的user-data指针。
int dJointGetType (dJointID);
在ODE中,关节被分为不同的类型以适应不同场合的需求。通过的JointGetType函数可以获取到指定关节的类型,返回的关节类型必定为下面的类型场数之一。
dJointTypeBall | A ball-and-socket joint. |
dJointTypeHinge | A hinge joint. |
dJointTypeSlider | A slider joint. |
dJointTypeContact | A contact joint. |
dJointTypeUniversal | A universal joint. |
dJointTypeHinge2 | A hinge-2 joint. |
dJointTypeFixed | A fixed joint. |
dJointTypeAMotor | An angular motor joint. |
dJointTypeLMotor | A linear motor joint. |
dJointTypePlane2D | A Plane 2D joint. |
dJointTypePR | A Prismatic-Rotoide joint. |
dJointTypePU | A Prismatic-Universal joint. |
dJointTypePiston | A Piston joint. |
dBodyID dJointGetBody (dJointID, int index);
返回给定关节所连接到的刚体。index = 0 时返回第一个刚体,和dJointAttach函数的参数body1相一致;如果index = 1 则返回第二个刚体,和dJointAttach函数的参数body2相一致。当这两个值有一个为0时表示该关节只连接了一个刚体,如果两个值均为0则表示该关节处于“监狱模式”,当然不会对仿真产生任何作用。
int dAreConnected (dBodyID, dBodyID);
如果两个刚体被同一个关节连接,使用上面的函数时就会返回1,否则返回0;
int dAreConnectedExcluding (dBodyID, dBodyID, int joint_type);
上面的函数用来判断连接两个刚体的关节类型是不是joint_type,如果是返回0,如果不是则返回1。其中joint_type是一个dJointTypeXXX型的常量。这个函数在判断是否在两个刚体之间添加连接关节时是非常有用的,如果它们已经被非连接关节的关节连接就不适合再添加连接关节,但是却可以给易筋经被连接关节连接的刚体之间添加更多的连接关节。
6.3 关节反馈
void dJointSetFeedback (dJointID, dJointFeedback *);
dJointFeedback *dJointGetFeedback (dJointID);
在一个world时间步长之内,施加在每一个关节上的所有力都会被计算到,这些力会直接添加到其所约束的刚体上,而且通常用户是无法辨别每一个关节到底会施加多大的力给刚体。如果需要获取这些信息的话就需要用户自己去申请一个dJointFeedback结构体并且把它的指针传递给寒素dJointSetFeedback()。反馈信息的结构体定义如下:
typedef struct dJointFeedback {
dVector3 f1; // 施加到body1上的力
dVector3 t1; // 施加到body1上的力矩
dVector3 f2; // 施加到body2上的力
dVector3 t2; // 施加到body2上的力矩
}
在每个时间步长之内,跟每一个关节相关的所有的反馈结构体都会保存相应关节的力和力矩信息。dJointGetFeedback()函数会返回指定关节当前反馈结构体的指针,如果关节未被使用到的话就返回0(系统默认)。可以通过给dJointSetFeedback()函数传递参数0以金庸关节的反馈信息。需要注意的是,一旦设置了关节的反馈结构体,就不再需要使用dJointGetFeedback()函数来获取相应的反馈信息,只需要在每个仿真步结束之后去查看之前定义的结构体信息。获取函数只有在需要多次对特定的关节启用反馈时才是有用的,对这种情况而言,首先得检查关节是否已经设有一个反馈但还没有设定另一个,这时就会禁用设定的第一个反馈。
6.3.1 返回值是有效的吗?
关于反馈结构体中非常奇怪的、不一致的力和力矩的问题已经提交到邮件列表中(例如:July 2010, April 2009,February 2009,September 2008)。
显然只要你的系统带有或多说少的负载性,反馈系统就会产生不适当的值。约束求解只要能产生正确的结果也不会去在意力是否均匀分布了(就是说当它们施加到各自的刚体上时就算是正确的力了)。
6.3.2 API设计的一点注解
需要用户自己去申请这些反馈结构体看起来是很奇怪的,为什么不静态地为每一个关节存储这些数据呢?原因就是并不是说所有的用户都会去使用关节的反馈信息,即使会使用到也不是所有的关节都需要这些数据。去静态地存储这些数据就是白白地浪费内存,特别是这些结构体会在以后的过程中为了存储更多的额外数据而不断增长。
为什么ODE不去自己分配反馈结构体,而需要用户请求时才这样做呢?原因就是如果需要返回反馈信息的话,连接关节(在每一个时间步中都需要创建和销毁)就需要花费更多的时间用于分配内存。之所以让用户来分配就意味着可以提供更好的分配策略,例如给它们简单地分配一个固定的数组。
对这种API的替代方法就是使用一个关节力的回调函数,虽然可以解决的问题,但仍然存在一点问题。首先,回调函数会破坏掉ODE的APIs,并且有时会需要用户自己去检查一些不自然的弯曲以将数据保存在正确的地方。其次,这样会使得ODE在每一个仿真步中的改变显露出来(后果很严重),你将不得不需要一种方法去阻止它或者需要一定的调试去检查它(这会使问题更加复杂化)。
6.4 关节参数设定函数
6.4.1 球窝关节(Ball and Socket)
如图 4所示即为球窝关节示意图:
图 4:球窝关节
void dJointSetBallAnchor (dJointID, dReal x, dReal y, dReal z);
设置关节的锚点位置。关节会努力保持每个刚体都连接在这一点上,函数输入的坐标指定为世界坐标。如果没有刚体连接到关节上,这个函数就不会起任何作用。
void dJointGetBallAnchor (dJointID, dVector3 result);
获取关节的锚点位置(世界坐标)。这个函数返回的是锚点在body1上的位置,当然如果关节的约束完美无缺的话,这个函数所返回的值和下面的函数(获取锚点在body2上的位置)返回的值就是一致的。
void dJointGetBallAnchor2 (dJointID, dVector3 reslut);
可以试想一下,球窝关节的作用就是尽力去保证dJointGetBallAnchor()的值和dJointGetBallAchor2()的值相等。如果关节的约束是理想的,这两个函数的返回值在舍入误差的范围之内必然是相等的。当然同时使用这两个函数就可以知道当前状态下关节之间(球和窝之间)分开的距离。
6.4.2 合页关节(Hinge)
如图 5所示即为合页关节示意图:
图 5:合页关节
默认轴心为:Axis 1:x=1, y=0, z=0
void dJointSetHingeAnchor (dJointID, dReal x, dReal y, dReal z);
设置合页的锚点参数。关节会努力保持每个刚体都连接在这一点上,输入坐标值指定为世界坐标。如果没有刚体连接到关节上,则此函数无效。
void dJointSetHingeAxis (dJointID, dReal x, dReal y, dReal z);
设置合页的轴心参数。如果没有刚体连接到关节上,则此函数无效。
void dJointGetHingeAnchor (dJointID, dVector3 result);
获取指定合页关节的锚点在世界坐标系中的位置。返回锚点在body1上的位置,如果关节的约束是理想的,其和锚点在body2上的位置是相同的。
void dJointGetHingeAnchor2 (dJointID, dVector3 result);
获取指定合页关节的锚点在世界坐标系中的位置。返回锚点在body2上的位置,如果关节的约束是理想的,其和dJointGetHingeAnchor函数的返回值是相同的。如果关节约束不理想,这两个值就会由轻微的差别。可以用来判断铰链连接的两个刚体分离的距离。
void dJointGetHingeAxis(dJointID, dVector3 result);
获取合页的轴心参数。
dReal dJointGetHingeAngle (dJointID);dReal dJointGetHingeAngleRate (djointID);
获取合页的角度和它对时间的导数。角度表示的是两个刚体时间的角度或者刚体和静态环境之间的角度,范围为[-pi,pi]。当合页的锚点或者轴心被设置后,就会检查两个刚体当前的连接角度并且这个角度必然是0。
6.4.3 插销关节(Slider)
如图 6所示即为插销关节示意图:
图 6:插销关节
默认轴心为:Axis:x=1, y=0, z=0
void dJointSetSliderAxis (dJointID, dReal x, dReal y, dReal z);
设置插销关节的轴心参数。
void dJointGetSliderAxis (dJointID, dVector3 result);
获取插销关节的轴心参数。
dReal dJointGetSliderPosition (dJointID);
dReal dJointGetSliderPositionRete (dJointID);
获取插销关节的线性位置(也就是滑动关节“伸展”)和其对时间的导数(伸展率)。如果轴心被设置为从body1指向body0,关节的位置和伸展率将会随着两个刚体之间距离的增长而增长。
6.4.4 万向轮关节(Universal)
如图 7 所示即为万向轮关节示意图:
图 7:万向轮关节
万向轮关节就像是一个球窝关节,只是多了一个额外约束自由旋转的角度。在body1上指定axis1,在body2上指定与axis1正交的axis2,万向轮关节会保障它们的正交性。换句话说,两个刚体的旋转方向的正交性和两根轴的的正交性是相同的。
在上图中,两个刚体被一个十字架连接起来。axis 1连接到body1上,axis2连接到body2上十字架保证了两根轴成90度夹角。所以如果你抓住body1并且扭动它,body2也会同时扭动。
当hinge-2关节的两根轴相互垂直是就等价于一个通用关节,并且在悬架处呈现出完美的刚性连接特性。
万向轮关节出现于汽车工业中,汽车的发动机产生一个沿着它自己的轴旋转的轴(驱动轴),在某种情况下你会需要去改变轴的方向。问题就来了,如果只是弯曲驱动轴,弯曲后的驱动轴是不能在绕着它自己的轴旋转的。所以如果把它从弯曲的位置截断,再插入一个万向轮关节就可以将这种约束施加到另一个轴上使得它能够和驱动轴旋转同样的角度。
这种关节的另一种应用就是用来连接一种简单的虚拟生物的肢体和身体。想象一个人伸直它的手臂,同时能够同时上下、前后移动它,但是不能绕着它自己的轴旋转。
默认轴心: Axis 1: x=1, y=0, z=0
Axis 2: x=0, y=1, z=0
需要特别注意的是,两根轴必须是正交的。如果将第一个轴的值设为第二个轴的值(反之亦然)就会导致在运行时出现错误,即使随后立即改变了第二个轴的值。
下面是一些与万向轮关节相关的函数:
void dJointSetUniversalAnchor (dJointID, dReal x, dReal y, dReal z);
设置万向轮关节的锚点。关节会努力保持每个刚体都连接在这一点上,输入的轴心坐标必须是世界坐标。如果没有刚体连接到关节上,则此函数无效。
void dJointSetUniversalAxis1 (dJointID, dReal x, dReal y, dReal z);
void dJointSetUniversalAxis2 (dJointID, dReal x, dReal y, dReal z);
设置万向轮关节的锚点和轴心参数。axis1和axis2必须相互垂直。
void dJointGetUniversalAnchor (dJointID, dVector3 result);
获取指定铰链关节的锚点在世界坐标系中的位置,返回其在body1上的位置。如果约束是理想的,返回的值将会和锚点在body2上的位置相同。
void dJointGetUniversalAnchor2 (dJOintID, dVector3 result);
获取指定铰链关节的锚点在世界坐标系中的位置,返回其在body2上的位置。
void dJointGetUniversalAxis1 (dJointID, dVector3 result);
void dJointGetUniversalAxis2 (dJointID, dVector3 reslut);
获取万向轮关节的轴心参数。
dReal dJointGetUniversalAngle1 (dJointID);
dReal dJointGetUniversalAngle2 (dJointID);
dReal dJointGetUniversalAngles (dJointID, dReal *angle1, dReal *angle2);
dReal dJointGetUniversalAngle1Rate (dJointID);
dReal dJointGetUniversalAngle2Rate (dJointID);
获取万向轮关节的旋转角度以及其对时间的导数。角度表示一个刚体和十字架之间的角度,或者十字架和静态环境之间的角度,取值范围为[-pi,pi]。当万向轮关节的锚点或者轴心被设定之后,就会检查两个刚体当前的连接角度并且这个角度必然是0。
6.4.5 铰链-2关节(Hinge-2)
铰链-2关节示意图如图 8所示:
图 8:铰链-2关节
铰链-2关节如同两个合页关节以不用的轴心串联连接在一起。如上图所示的小车的转向轮,一个轴允许轮子进行转向,另一个轴则允许它进行旋转。铰链-2关节包含一个锚点和两根轴,axis1与body1相关(如果body1作为底盘的话,axis1将作为转向轴),axis2跟body2相关联(如果body2作为车轮的话,axis2将作为车轮轴)。
axis1可以被关节约束并且可以有一个发动机,而axis2只能有一个发动机。
axis1可以被定义为一个悬挂轴,也就是说它的约束可以沿着轴向压缩。
铰链-2关节的axis1轴和axis2轴是相互垂直的,就如同给万向轮关节添加了一个悬架。
它的默认轴心为:Axis 1:x=1, y=0, z=0; Axis 2:x=0, y=1, z=0
void dJointSetHinge2Anchor (dJointID, dReal x, dReal y, dReal z);
上面的函数用来设置hinge-d关节的锚点参数。锚点用于保持关节连接的各部分成为一个整体,坐标参数必须是世界坐标。如果没有刚体连接到关节上,使用这个函数就是无效的。
void dJointSetHinge2Axis1 (dJointID, dReal x, dReal y, dReal z);
void dJointSetHinge2Axis2 (dJointID, dReal x, dReal y, dReal z);
设置关节的轴心参数,axis1和axis2不能沿着同一条线。
void dJointGetHinge2Anchor (dJointID, dVector3 result);
void dJointGetHinge2Anchor2 (dJointID, dVector3 result);
上面两个函数分别用来获取锚点在body1和body2上的位置,返回的坐标为世界坐标。
void dJointGetHinge2Axis1 (dJointID, dVector3 result);
void dJointGetHinge2Axis2 (dJointID, dVector3 result);
获取hinge-2关节的轴心参数。
dReal dJointGetHinge2Angle1 (dJointID); dReal dJointGetHinge2Angle1Rate (dJointID);
dReal dJointGetHinge2Angle2Rate (dJointID);
获取hinge-2关节的角度(绕axis1和axis2)及其对时间的导数。在设置了关节的的锚点或者轴心之后,系统会自行检查关节连接到刚体上的位置,以保证这两个夹角为0。
注意:当前还没有函数用于获取第二个角的值(即车轮绕其轴的转角)。
6.4.6 PR 关节(Prismatic and Rotoide)
PR关节即插销关节和合页关节的结合体,其示意图如下图所示:
图 9:PR关节
PR关节是插销(Slider)关节和合页关节(Hinge)的结合,它用于增加模拟中的刚体数量。通常你是不能将两个ODE关节直接连接在一起的,它们需要有一个刚体做中间媒介。这种类型的关节的一个应用实例就是可以用它创建一个液压活塞。
你可以通过设置这种关节的锚点来设置body2相对于合页关节的位置(就如同合页关节一般)。在这中关节被创建之后,系统会去计算body1和合页关节之间的偏移距离。通过查看关节的位置就会得到刚体的偏移位置。
第一个轴是插销轴,称为axis R,它的参数通过dParamX标志访问。
第二根轴是合页轴,称为axis P,它的参数通过dParamX2表示访问。
系统中默认的PR关节轴心为:Axis R:x=1, y=0, z=0; Axis P: x=0, y=1, z=0
void dJointSetPRAxis1 (dJointID, dReal x, dReal y, dReal z);
void dJointGetPRAxis1 (dJointID, dVector3 result);
设置/获取插销关节的轴心。
void dJointSetPRAxis2 (dJointID, dReal x, dReal y, dReal z);
void dJointGetPRAxis2 (dJointID, dVector3 result);
设置/获取合页关节的轴心。
void dJointSetPRAnchor (dJointID, dReal x, dReal y, dReal z);
void dJointGetPRAnchor (dJointID, dVector3 result);
设置PR关节的锚点,锚点坐标必须为世界坐标。如果没有body2连接到关节上,则使用这个函数时无效的。
dReal dJointGetPRPosition (dJointID);
获取PR关节的线性位置(也就是插销关节的扩展)。在关节的轴心被设置后,系统就会自行检查body1的当前位置和锚点,以保证该位置为0。它表示从body1到合页关节之间的长度:position = (Prismatic axis) dot_product [(body1 + offset) - (body2 + anchor2)]。需要注意的是axis P和axis R并不是平行的,因为这是一个新的关节。
6.4.7 PU关节(Prismatic-Universal)
PU关节示意图如图 10所示:
图 10:PU关节
PU关节是对插销关节和万向轮关节的结合,它提供一个自由度的平移和两个自由度的旋转。1号轴为万向轮的1号轴,可以通过dParamX1或者dParamX(不包括dParamFMax1)标志来访问它的参数。2号轴万向轮的2号轴,可以通过dParamX2(不包括dParamFMax2)标志来访问它的参数。3号轴是插销关节的轴,可以通过dParamX2标志来访问它的参数。
系统提供默认的轴心参数为:Axis 1:x=0, y=1, z=0; Axis 2:x=0, y=0, z=1; Axis 3:x=1, y=0, z=0
dReal dJointGetPUPosition (dJointID);
该函数用于获取PU关节的线性位置(也就是插销关节的伸展长度)。当锚点被设置后,系统会自行检查body1的当前位置和锚点的位置以确定PU关节的线性位置为0(initial_offset)。
position = {(Prismatic axis) dot_product [body1 - anchor]} - initial_offset
dReal dJointGetPUPositionRate (dJointID);
获取PU关节的线性位置对时间的导数。
void dJointSetPUAnchor (dJointID, dReal x, dReal y, dReal z);
void dJointGetPUAnchor (dJointID, dVector3 result);
设置PU关节的锚点参数,关节会保证body2和万向轮关节之间的距离固定不变。如果关节上没有连接body2则这个函数的使用将是无效的。
void dJointSetPUAnchorDelta (dJointID, dReal x, dReal y, dReal x, dReal dx, dReal dy, dReal dz);
设置PU关节的锚点以及每一个刚体之间的相对位置,假设body1在当前位置position + [dx, dy, dz](dz,dy,dz均为世界坐标系坐标)。就好像是在设置插销关节已经伸展或者压缩的长度。在设置锚点之后,如果调用了dJointGetPUPosition函数就会返回这样一个结果:sqrt(dx*dx + dy*dy + dz*dz) * Normalize[axis3 dot_product (dx,dy,dz)]
void dJointSetPUAxis1 (dJointID, dReal x, dReal y, dReal z);
void dJointGetPUAxis1 (dJointID, dVector3 result);
void dJointSetPUAxis2 (djointID, dReal x, dReal y, dReal z);
void dJointGetPUAxis2 (dJointID, dVector3 result);
设置/获取万向轮关节的轴心参数,axis1和axis2必须是相互垂直的。
void dJointSetPUAxis3 (dJointID, dReal x, dReal y, dReal z);
void dJointGetPUAxis3 (dJointID, dVector3 result);
设置/获取插销关节的轴心参数。
void dJointSetPUAxisP (dJointID, dReal x, dReal y, dReal z);
void dJointgetPUAxisP (dJointID, dVector3 result);
上面这组函数的作用和dJOintSetPUAxis3相同。
void dJointGetPUAngles (dJointID, dReal *angle1, dReal *angle2);
dReal dJointGetPUAngle1 (dJointID);
dReal dJointGetPUAngle2 (dJointID);
dReal dJointGetPUAngle1Rate (dJointID);
dReal dJOintGetPUAngle2Rate (dJointID);
获取万向轮关节的角度和其对时间的导数,角度表示刚体和十字架之间的角度,或者静态环境和十字架时间的角度,取值范围为[-pi,pi]。当万向轮的锚点或者轴心被设置之后,系统会自行检查关节所连接刚体的当前位置,以保证万向轮的角度为0。
6.4.8 活塞关节
活塞关节示意图如图 11所示:
图 11:活塞关节
活塞关节类似于插销关节,只是它可以绕着平移轴旋转而已。
系统默认的轴心参数为:Axis: x=1, y=0, z=0
void dJointSetPistonAnchor (dJointID, dReal x, dReal y, dReal z);
void dJointGetPistonAnchor (dJointID, dVector3 result);
设置活塞关节的锚点参数。关节会尽力保持这一点相对固定在body2上,坐标参数必须是世界坐标。如果关节上没有连接任何刚体,则这个函数不会起任何作用。
void dJointGetPistonAnchor2 (dJointID, dVector3 result);
以世界坐标的形式获取关节的锚点坐标,返回的是锚点在body2上的坐标。如果关节的约束较为理想的话,这个函数返回的值和dJointGetPistonAnchor返回的值相同。否则,两个返回值之间会有轻微的不同,这个差值可以用来确定关节目前分开的多远。
void dJointSetPistonAxis (dJointID, dReal x, dReal y, dReal z);
void dJointGetPistonAxis (dJointID, dVector3 result);
设置/获取关节的轴心参数。在活塞关节的轴心设置之后,系统会自行检查相关联的刚体,并且将旋转角度初始化为0。
void dJointSetPistonAxisDelta (dJointID j, dReal x, dReal y, dReal z, dReal dx, dReal dy, dReal dz);
dReal dJointGetPistonPosition (dJointID);
dReal dJointGetPistonPosition (dJointID);
获取活塞关节的线性位置(也就是棱柱的伸展长度)。在关节的锚点设置之后,系统会自行检查body1的当前position和锚点,并且将position初始化为0(initial_offset)(也就是说调用函数dJointGetPUPosition获取body1相对于锚点的位置将会返回0.0)。position是body1和锚点之间的直线距离,position = {(Prismatic axis) dot_product [body1 - anchor]} - initial_offset。
dReal dJointGetPistonAngle (dJointID);
dReal dJointGetPistonAngleRate (dJointID);
获取关节的旋转角度以及其对时间的导数(旋转率)。角度是两个刚体之间的相对角度,或者是刚体和静态环境之间的相对角度,取值范围为[-pi, pi]。唯一可能的旋转轴就是用函数dJointSetPistonAxis定义的。在活塞关节的锚点或者轴心设置之后,系统会自行检查刚体间的position和angle,并将其初始化为0。
void dJointAddPistonForce (dJointID j, dReal force);
这个函数用于给活塞关节添加一个力并且这个力会作用在两个刚体上,力的作用点位于刚体的质心上,方向和活塞的旋转轴相一致。
6.4.9 固定关节
固定关节用于维持两个刚体或者刚体和静态环境之间保持一定的相对位置和方向。在实际应用中,除了用于调试,这绝不是一个好方法。如果你需要将两个刚体粘结在一起时,还不如将两个刚体合为一个整体。
void dJointSetFixed (dJointID);
在固定关节已经获得了一个需要的相对偏移量和两个刚体之间的相对方位之后才可以使用这个函数。
6.4.10 接触关节
接触关节示意图如图 12 所示:
图 12:接触关节
接触关节用于阻止body1和body2在接触面上相互贯穿,它只允许刚体在接触面的法线方向用于一个向外的速率。接触关节通常会有一个时间步长的生命周期,它的创建和删除完全是为了用于对碰撞检测的响应。
通过在与接触面法线方向正交的两个摩擦力方向上施加特殊的力,就可实现对接触面上摩擦力的仿真。在接触关节被创建之后,必须以供一个结构体dContact,定义如下:
struct dContact {
dSurfaceParameters surface;
dContactGeom geom;
dVector3 fdir1;
};
geom: 由碰撞函数定义的子结构体,在碰撞篇中会有所介绍。
fdir1: 第一摩擦力方向向量,用于定义一个沿着摩擦力方向的方向向量,必须是单位长度且方向正交于接触面法线方向(所以通常情况下它正切于接触面),只有在标志dContactFDir1被设置为surface.mode时它才可以被定义。第二摩擦力方向向量是一个与接触面法线方向和fdir1方向相互正交的方向向量。
surface:由用户自己定义的一个子结构体,它的成员用于定义碰撞面的属性,定义如下:
struct dSurfaceParameters {
int mode;
dReal mu;
dReal mu2;
dReal bounce;
dReal bounce_vel;
dReal soft_erp;
dReal soft_cfm;
dReal motion1, motion2, motionN;
dReal slip1, slip2;
};
mode: 接触标志,必须设置,可以是以下一个或多个标志的结合:
dContactMu2 | If not set, use mu for both friction directions. If set, use mu for friction direction 1, use mu2 for friction direction 2. |
dContactFDir1 | If set, take fdir1 as friction direction 1, otherwise automatically compute friction direction 1 to be perpendicular to the contact normal (in which case its resulting orientation is unpredictable). |
dContactBounce | If set, the contact surface is bouncy, in other words the bodies will bounce off each other. The exact amount of bouncyness is controlled by the bounce parameter. |
dContactSoftERP | If set, the error reduction parameter of the contact normal can be set with the soft_erp parameter. This is useful to make surfaces soft. |
dContactSoftCFM | If set, the constraint force mixing parameter of the contact normal can be set with thesoft_cfm parameter. This is useful to make surfaces soft. |
dContactMotion1 | If set, the contact surface is assumed to be moving independently of the motion of the bodies. This is kind of like a conveyor belt running over the surface. When this flag is set, motion1 defines the surface velocity in friction direction 1. |
dContactMotion2 | The same thing as above, but for friction direction 2. |
dContactMotionN | The same thing as above, but along the contact normal. |
dContactSlip1 | Force-dependent-slip (FDS) in friction direction 1. |
dContactSlip2 | Force-dependent-slip (FDS) in friction direction 2. |
dContactApprox1_1 | Use the friction pyramid approximation for friction direction 1. If this is not specified then the constant-force-limit approximation is used (and mu is a force limit). |
dContactApprox1_2 | Use the friction pyramid approximation for friction direction 2. If this is not specified then the constant-force-limit approximation is used (and mu is a force limit). |
dContactApprox1 | Equivalent to both dContactApprox1_1 and dContactApprox1_2 . |
mu:库仑摩擦系数,必须设置,范围为[0, dInfinity]。0表示无摩擦接触,dInfinity表示从来不会滑动的接触(硬接触)。注意,计算无摩擦接触花费的时间要少于有摩擦的接触,无限摩擦接触又要比有限摩擦接触的花费少。
mu2:作用于摩擦方向2上的,可选的库仑摩擦系数,范围为[0, dInfinity]。只有在相关的标志设为mode时才可以使用。
bounce:反弹系数(0..1),0意味着表面没有一点弹性,1表示最大弹性。只有在相关的标志设为mode时才可以使用。
bounce_vel: 反弹所需要的最小的传入速度,传入速度低于这个值时反弹系数为0。只有在相关的标志设为mode时才可以使用。
soft_erp: 接点法线柔性参数。只有在相关的标志设为mode时才可以使用。
soft_cfm: 接点法线柔性参数。只有在相关的标志设为mode时才可以使用。
motion1,motion2,motionN: 沿着摩擦力1,摩擦力2,接点法线方向的表面速率。只有在相关的标志设为mode时才可以使用。
slip1,slip2: 对摩擦力1和摩擦力2的FDS(Force Dependent Slip)系数。只有在相关的标志设为mode时才可以使用。
FDS表示在物体移动时,因为受到侧向的外力而产生侧向滑动的现象,滑动距离正比于物体移动的速度和外力的大小,因此FDS也可称为侧向力滑动。
试想这样一个接触点,它的摩擦力因子mu无限大。通常情况下,如果一个力f作用于这样的两个接触面上,它们之间是不会有相互滑动的。然而,如果FDS因子被设置为一个正值K,这时两个面之间就可以相互滑动,并且以一个稳定的相互滑动速度K*f滑动。
需要注意的是,这和正常的摩擦效果是不同的:摩擦力不会在接触面上产生一个恒定不变的加速度,它会产生一个短暂的加速度以实现速度的稳定。
这在为一些特殊的情形如轮胎建模是特别有用。例如试想一辆在道路上停止的汽车,沿着车行驶的方向推动会使它开始移动(也就是说轮胎会开始转动),沿着与车的方向正交的方向推动就没有任何效果,因为轮胎不会在那个方向上转动。然而,如果汽车正在以速度v行驶,施加一个方向垂直于汽车方向的力时就会引起轮胎以一个与f*v成正比的速度在公路上滑动(这的确会发生)。
要在ODE中模拟这种情况,需要设置轮胎与路面的接触参数如下:设置摩擦力1的方向为轮胎转动的方向,设置FDS滑动因子为K*v、方向与摩擦力2的方向一致,其中v是轮胎转动的速度,K是一个可以根据实验选择的轮胎系数。
另外,需要注意的是FDS和库伦摩擦力的粘滑现象是完全独立的,两种模式都可以一起用于对单点接触的模拟中。
6.4.11 角电机关节
角电机(AMotor)允许控制两个刚体之间的相对角速度,能够控制的角速度多达三轴,允许给绕这些轴的旋转设置转矩电机和停点。这种关节主要用于连接球关节(角自由度没有任何限制),也可以用于任何需要控制角度的情形。在使用一个角电机和球关节时,只需要将角电机连接到有球关节连接的两个相同的刚体上。
角电机可以用于不用的模式。在dAMotorUser模式下,由用户设置角电机的控制轴;在dAMotorEuler模式下,由电机自己计算对应相对旋转的欧拉角,并且允许设置欧拉角力矩电机和停点。角电机关节的示意图如图 13所示:
图 13:角电机的欧拉角示意图
在上图中,a0 ,a1 和a2是角电机的三个控制轴,其中绿色的轴(a0)被固定到body1上,蓝色的轴(a2)被固定到body2上,要从body1的轴获取body2的轴需要以下几步操作:
- 绕轴a0旋转theta0
- 绕轴a1旋转theta1(a1已被从原始位置旋转)
- 绕轴a2旋转theta2(a2已被从原始位置旋转两次)
在使用欧拉角时,有一个非常重要的限制:theta1的角度不允许超过(-pi/2, pi/2)。如果超出了这个范围,角电机关节就会变得不稳定(在+/- pi/2是有一个奇点),因此必须在轴1上设置适当的停点。
void dJointSetAMotorMode (dJointID, int mode);
int dJointGetAMotorMode (dJointID);
设置(获取)角电机的模式,模式参数必须是以下常量之一:
dAMotorUser | The AMotor axes and joint angle settings are entirely controlled by the user. This is the default mode. |
dAMotorEuler | Euler angles are automatically computed. The axis a1 is also automatically computed. The AMotor axes must be set correctly when in this mode, as described below. When this mode is initially set the current relative orientations of the bodies will correspond to all euler angles at zero. |
void dJointSetAMotorNumAxes (dJointID, int num);
int dJointGetAMotorNumAxes (dJointID);
设置(获取)角电机所控制的旋转轴数量。参数num的范围为0-3,0表示该关节无效。在dAMotorEuler模式下会自动设置为3。
void dJointSetAMotorAxis (dJointID, int anum, int rel, dReal x, dReal y, dReal z);
void dJointGetAMotorAxis (dJointID, int anum, dVector3 result);
int dJOintGetAMotorAxisRel (dJointID, int anum);
设置(获取)角电机的轴。参数anum选择要改变的轴(0,1或2),每一个轴都可以有三种相对方位模式中的一种,通过参数rel选择:
- 0:轴被固定到全局坐标系上
- 1:轴被固定到第一个刚体上
- 2:轴被固定到第二个刚体上
不论rel的值为何,轴向量(x,y,z)总是被定义为全局坐标。有两个GetAMotorAxis函数,一个用来返回轴心,另一个用来返回相对模式。
对于的AMotorEuler模式而言:
- 只有轴0和轴2需要设置。轴1由系统在每一个时间步中自动设定。
- 轴0和轴2必须是相互正交的。
- 轴0必须固定到body1上,轴2必须固定到body2上。
void dJointSetAMotorAngle (dJointID, int anum, dReal angle);
设置AMotor沿着轴anum的当前角度,该函数只有在dAMotorUser模式下才可使用,因为在该模式下,AMotor没有其它方法去获取关节的角度。如果停点沿着轴向的话就需要关节的角度值,但轴电机不需要。
dReal dJointGetAMotoAngle (dJointID, int anum);
返回关节绕轴anum的旋转角度。在dAMotorUser模式下,它就是通过dJointSetAMotorAngle设置的值。在dAMotorEuler模式下,就是相应的欧拉角。
dReal dJointGetAMotorAngleRate (dJointID, int anum);
返回关节绕轴啊怒骂旋转的角速率。在dAMotorUser模式下,该值恒为0;在的AMotorEuler模式下,就是相应的欧拉角速率。
6.4.12 线性电机关节
线性电机(LMotor)允许控制两个刚体之间的线性速度,最多能够控制三个轴向的线速度,并且允许设置沿这些轴向的转矩电机和停点。
void dJointSetLMotorNumAxes (dJointID, int num);
int dJointGetLMotorNumAxes (dJointID);
设置(获取)LMotor能够控制的轴心数量,参数num的范围从0(表示该关节无效)到3。
void dJointSetLMotorAxis (dJointID, int anum, int rel, dReal x, dReal y, dReal z);
void dJointGetLMotorAxis (dJointID, int anum, dVector3 result);
设置(获取)LMotor的轴心。参数anum选择需要改变的轴心(0,1或2),每一个轴都有三种相对方位模式中的一种模式,通过rel选择:
- 0:轴被固定在全局坐标系上
- 1:轴被固定在第一个刚体上
- 2:轴被固定在第二个刚体上
6.4.13 2D 平面关节(Plane 2D)
2D平面关节作用于一个刚体上,约束它的z轴方向恒为0.但是由于数值计算的不精确,仍然会发生一些漂移,所以在每一帧刚体都必须通过以下代码进行重置,以保证零漂移。
const dReal *rot = dBodyGetAngularVel (my_body.id()));
const dReal *quat_ptr;
dReal quat[4], quat_len;
quat_ptr = dBodyGetQuaternion (my_body.id()));
quat[0] = quat_ptr[0];
quat[1] = 0;
quat[2] = 0;
quat[3] = quat_ptr[3];
quat_len = sqrt (quat[0] * quat[0] + quat[3] * quat[3]);
quat[0] /= quat_len;
quat[3] /= quat_len;
dBodySetQuaternion (my_body.id()), quat);
dBodySetAngularVel (my_body.id()), 0, 0, rot[2]);
6.5 综述
关节的几何学参数设置函数只有在关节上连有刚体之后才可使用,并且这些刚体还必须连接在正确的位置,否则关节的初始化就会出问题。如果关节上还没有连接刚体,这些函数是无效的。
对参数获取函数而言,如果系统失准(也就是说存在关节错误)的话,只有相对于body1的锚点/轴心的值是正确的(如果你通过dJointAttach函数定义了body1为0的话则只有body2有效)。
所有关节的锚点默认为(0,0,0),默认轴心为(1,0,0)。在轴心被设置之后,系统会自行将其正规化为单位长度。轴获取函数返回的为校准轴。
在测量关节的角度和位置量时,值0于刚体之间的相互初始位置一致。需要注意的是,在ODE中不提供可直接设定关节角度和位置的函数,相应的你可以通过设定刚体的位置和速度去间接地实现。
6.5 停点和电机参数
当关节第一次被创建时,没有什么可以阻挡它在整个可移动的范围内活动。例如,一个合页关节就可以绕着它的轴心做360o旋转,一个插销关节可以伸展任意长度。
这些关节的移动范围可以通过给关节设置停点来限制,关节角(位置)将会被限制在小于或者大于停点值的范围内。注意一个值为0的关节角(位置)和刚体的初始位置是一致的。
像停点一样,很多类型的关节都可以拥有电机。电机或施加一个力矩(或者力)在关节的自由度上使得它以特定的速度绕着某个轴旋转或者滑动。当然电机能施加的力时有限制的,这就意味着它不能给关节施加超过最大值的力/力矩。
电机有两个参数:一个速度值,一个可以使电机达到该速度的最大的力。这是现实生活中电机、发动机、舵机的一个非常简单的模型。然而,在连接到关节上之前,要使一个模型化的电机(或者发动机、舵机)通过变速箱减速时却是十分有用的。这样的设备通常通过设置一个需要的速度来控制它,并且只能产生一个最大的功率(与关节上可用的一定量的值相一致)来实现这个速度。
电机也可以用来精确地模拟关节上的干摩擦(库伦摩擦)。仅仅需要将电机的目标速度设为0,最大的力设为一个常量,然后所有的关节运动就都会受到该力的约束。
使用关节停点和电机的替代方案就是由你自己施加一定的力去影响刚体。施加电机力是很简单的,通过抑制弹性力也可以轻松地模拟关节停点。然而直接给刚体施加力绝对不是一个好方法,因为如果施加的力掌握不好的话它会引起严重的稳定性问题。
试想通过给刚体施加力来使它能够拥有一个需要的速度,需要根据当前的速度来计算这个力F:F=k(desired speed - current speed)。这样就存在几个问题:第一,需要手动来调整K的值,如果K的值太小的话,刚体就需要花费很长的一段时间来达到预期的速度;如果太大则又会使仿真变得不稳定。第二,即使给刚体选择了一个很恰当的K值,刚体仍然需要一定的时间来达到预期的速度。第三,如果有任何一个外部的力施加到刚体上,所预期的速度可能就永远不会达到(就需要一个更为复杂的力的等式,并且还会有其它额外的参数和新的问题)。
电机关节就可以解决所有这些问题:它可以在一个时间步长中就让刚体达到预期的速度,并且可以提供不超过允许范围的力。电机关节也不需要额外的参数,因为它们实际上是以约束的形式实现的。它们可以有效地计算出需要施加的正确的力,虽然这样比起你自己计算需要的力来会花费较多的时间,但是却拥有更多的强壮性和稳定性,同时只需要花费较少的时间用于设计。这对大型的刚体系统来说无疑是很有效的。
6.5.1 参数函数
下面是一些用来给关节设置停点和电机参数的函数(以及一些其它的参数):
void dJointSetHingeParam (dJointID, int parameter, dReal value);
void dJointSetSliderParam (dJointID, int parameter, dReal value);
void dJointSetHinge2Param (dJointID, int parameter, dReal value);
void dJointSetUniversalParam (dJointID, int parameter, dReal value);
void dJointSetAMotorParam (dJointID, int parameter, dReal value);
void dJointSetLMotorParam (dJointID, int parameter, dReal value);
void dJointSetPRParam (dJointID, int parameter, dReal value);
void dJointSetPUParam (dJointID, int parameter, dReal value);
void dJointSetPistonParam (dJointID, int parameter, dReal value);
dReal dJointGetHingeParam (dJointID, int parameter);
dReal dJointGetSliderParam (dJointID, int parameter);
dReal dJointGetHinge2Param (dJointID, int parameter);
dReal dJointGetUniversalParam (dJointID, int parameter);
dReal dJointGetAMotorParam (dJointID, int parameter);
dReal dJointGetLMotorParam (dJointID, int parameter);
dReal dJointGetPRParam (dJointID, int parameter);
dReal dJointGetPUParam (dJointID, int parameter);
dReal dJointGetPistonParam (dJointID, int parameter);
对每一个关节类型设置/获取 限制/电机参数,参数号如下:
dParamLoStop | Low stop angle or position. Setting this to -dInfinity (the default value) turns off the low stop. For rotational joints, this stop must be greater than - pi to be effective. |
dParamHiStop | High stop angle or position. Setting this to dInfinity (the default value) turns off the high stop. For rotational joints, this stop must be less than pi to be effective. If the high stop is less than the low stop then both stops will be ineffective. |
dParamVel | Desired motor velocity (this will be an angular or linear velocity). |
dParamFMax | The maximum force or torque that the motor will use to achieve the desired velocity. This must always be greater than or equal to zero. Setting this to zero (the default value) turns off the motor. |
dParamFudgeFactor | The current joint stop/motor implementation has a small problem: when the joint is at one stop and the motor is set to move it away from the stop, too much force may be applied for one time step, causing a jumping motion. This fudge factor is used to scale this excess force. It should have a value between zero and one (the default value). If the jumping motion is too visible in a joint, the value can be reduced. Making this value too small can prevent the motor from being able to move the joint away from a stop. |
dParamBounce | The bouncyness of the stops. This is a restitution parameter in the range 0..1. 0 means the stops are not bouncy at all, 1 means maximum bouncyness. |
dParamCFM | The constraint force mixing (CFM) value used when not at a stop. |
dParamStopERP | The error reduction parameter (ERP) used by the stops. |
dParamStopCFM | The constraint force mixing (CFM) value used by the stops. Together with the ERP value this can be used to get spongy or soft stops. Note that this is intended for unpowered joints, it does not really work as expected when a powered joint reaches its limit. |
dParamSuspensionERP | Suspension error reduction parameter (ERP). Currently this is only implemented on the hinge-2 joint. |
dParamSuspensionCFM | Suspension constraint force mixing (CFM) value. Currently this is only implemented on the hinge-2 joint. |
如果对一个给定关节没有实现的参数,设置它是无效的,获取时则会返回0。
重点: 这些参数名后面可以选择性地追加1(也可以是2或3)用于表示第二个或者第三个参数的设置,例如用于表示hinge-2关节的第二个轴,或者一个角电机关节的第三个轴。常量dParamGroup也可被这样定义:dParamXi = dParamX + dParamGroup *(i-1)
以下表格中展示的是对每一种类型关节的的有限参数组:
Joint Type | Number of Param Groups |
Ball And Socket | 0 |
Hinge | 1 |
Slider | 1 |
Contact | 0 |
Universal | 2 |
Fixed | 0 |
Angular Motor | 3 |
Linear Motor | 3 |
Plane2D | 3 |
Rotoide and Prismatic | 0 |
6.6 直接设置关节力矩/力
电机允许直接设置关机的速度。然而,你也可以通过给相应的关节设置一定的力矩或者力来实现。下面的函数就是用来这样做的。需要注意的是,它们不会影响电机的运转,只是通过调用连接在关节的刚体上的dBodyAddForce和dBodyAddTorque函数。
void dJointAddHingeTorque (dJointID, dReal torque);
向合页关节的body1上施加一个大小为torque的力矩,方向和关节的轴向一致,body2同时也受到一个大小为torque,方向相反的力矩。这个函数只是对bBodyAddTorque的一种封装。
void dJointAddUniversalTorques (dJointID, dReal torque1, dReal torque2);
施加torque1到万向轮的轴1上,施加torque2到万向轮的轴2上。
void dJOintAddSliderForce (dJointID, dReal force);
在插销关节的滑动方向施加一个给定大小的力force。也就是,施加一个大小为force、方向沿着滑动轴的力到body1上,同时body2也受到一个大小为force,方向与滑动轴方向相反的力。
void dJointAddHinge2Torques (dJointID, dReal torque1, dReal torque2);
施加力矩torque1到hinge-2关节的轴1上,施加力矩torque2到hinge-2关节的轴2上。
void dJointAddAMotorTorque (dJointID, dReal torque0, dReal torque1, dReal torque2);
施加力矩torque0到角电机的轴0上,施加力矩torque1到角电机的轴1上,施加力矩torque2到角电机的轴2上。如果电机的轴少于3个,则相应的力矩就会被忽略掉。