这一部分的承接前面的关节类型和相关函数(中)
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 the soft_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个,则相应的力矩就会被忽略掉。