Box2D v2.1.0用户手册翻译 - 第08章 关节(Joints)

来源:互联网 发布:ubuntu怎么读 编辑:程序博客网 时间:2024/05/01 09:32

原文地址:http://blog.csdn.net/complex_ok/article/details/6725365


第08章 关节(Joints)

8.1 关于

关节用于把物体约束到世界,或约束到其它物体上。在游戏中, 典型例子有木偶, 跷跷板和滑轮。用不同的方式将关节结合起来使用, 可以创造出有趣的运动。

有些关节提供了限制(limit), 使你可以控制运动的范围。有些关节还提供了马达(motor), 它可以以指定的速度驱动关节一直运动, 直到你指定了更大的力或扭矩来抵消这种运动。

关节马达有许多不同的用途。你可以使用关节来控制位置,只要提供一个与目标之距离成正比例的关节速度即可。你还可以模拟关节摩擦:将关节速度置零,并且提供一个小的、但有效的最大力或扭矩; 那么马达就会努力保持关节不动, 直到负载变得过大。

8.2 关节定义

每种关节类型都有各自的定义(definition), 但都派生自b2JointDef。所有关节都连接两个不同的物体, 其中一个物体有可能是静态的。关节也可以连接两个static或者kinematic类型的物体,但这没有任何实际用途,只会浪费处理器时间。

你可以为任何一种关节类型指定用户数据。你还可以提供一个标记, 用于防止用关节相连的物体发生碰撞,实际上, 这是默认行为。你也可以设置 collideConnected 布尔值来允许相连的物体发生碰撞。

很多关节定义需要你提供一些几何数据。一个关节常常需要一个锚点(anchor point)来定义,这是固定于相接物体中的点。Box2D要求这些点要在局部坐标系中指定, 这样, 即便当前物体的变化违反了关节约束(joint constraint),关节还是可以被指定 —— 在游戏保存或载入进度时这经常会发生。另外,有些关节定义需要默认的物体之间的相对角度。这样才能正确地约束旋转。

初始化几何数据可能有些乏味。所以很多关节提供了初始化函数,消除了大部分工作。然而,这些初始化函数通常只应用于原型, 在产品代码中应该直接地定义几何数据。这能使关节行为更具健壮性。

其余的关节定义数据依赖于关节的类型。下面我们来介绍它们。

8.3 关节工厂(Joint Factory)

关节使用world的工厂方法来创建和摧毁。这引入一个老问题:

注意:

不要使用new或malloc在栈(stack)或堆(heap)中创建关节。你想创建或摧毁物体和关节,必须使用b2World类中对应的创建或摧毁函数。

这里有个例子,展示了旋转关节(revolute joint)从创建到摧毁的过程:

b2RevoluteJointDef jointDef;

jointDef.body1 = myBody1;

jointDef.body2 = myBody2;

jointDef.anchorPoint =myBody1->GetCenterPosition();

b2RevoluteJoint* joint =myWorld->CreateJoint(&jointDef);

... do stuff ...

myWorld->DestroyJoint(joint);

joint = NULL;

一个很好的习惯: 当对象摧毁后,就将对应的指针清零。不清零的话,当你试图再次使用这个指针,程序在特定时候会崩溃。

关节的生命周期并不简单,要特别留心下面的警告,敲响警钟。

注意

物体被摧毁时,依附其上的关节也会被摧毁。

上面的注意并非时时必要。你可以组织好自己的游戏引擎,保证物体被摧毁前,依附其上的关节已经先被摧毁。这种情况下,你没有必要实现监听类(listener class), 去监听物体被摧毁的事件。更多细节请看隐式摧毁(Implicit Destruction)那小节。

8.4 使用关节

在许多模拟中,关节被创建之后,直到摧毁也不会再被访问。然而,关节中包含着很多有用的数据, 使你可以创建出丰富的模拟。

首先,你可以在关节上得到物体,锚点,以及用户数据。

b2Body* GetBody1();

b2Body* GetBody2();

b2Vec2 GetAnchor1();

b2Vec2 GetAnchor2();

void* GetUserData();

所有的关节都有反作用力和反扭矩,这个反作用力应用于 body2 的锚点之上。你可以用反作用力来折断关节(break joints),或引发其它游戏事件。这些函数可能需要做一些计算,所以没有必要就不要去调用它们。

b2Vec2 GetReactionForce();

float32 GetReactionTorque();

8.5 距离关节(Distance Joint)

距离关节是最简单的关节之一, 它是说, 两个物体上面各自有一点,两点之间的距离必须固定不变。当你指定一个距离关节时, 两个物体必须已在应有的位置上。之后,你指定世界坐标中的两个锚点。第一个锚点连接 到物体1,第二个锚点连接到物体2。这两点隐含了距离约束的长度。


这是一个距离关节定义的例子。这种情况下, 我们允许物体碰撞。

b2DistanceJointDef jointDef;

jointDef.Initialize(myBody1,myBody2, worldAnchorOnBody1, worldAnchorOnBody2);

jointDef.collideConnected = true;

距离关节也可以是软的,就像用橡皮筋来连接。看看testbed中的Web例子,可以知道出它有什么样的行为。

要使关节有弹性,可以调节一下两个参数:频率(frequency)和阻尼率(damping ratio)。将频率想象成谐振子(harmonic oscillator, 比如吉他弦)振动的快慢。频率使用单位赫兹(Hertz)来指定。典型情况下,关节频率要小于一半的时间步(time step)频率。比如每秒执行60次时间步, 距离关节的频率就要小于30赫兹。这样做的理由可以参考Nyquist频率理论。

阻尼率无单位,典型是在0到1之间, 也可以更大。1是阻尼率的临界值, 当阻尼率为1时,没有振动。

jointDef.frequencyHz = 4.0f;

jointDef.dampingRatio = 0.5f;

8.6 旋转关节(Revolute Joint)

旋转关节会强制两个物体共享一个锚点,即所谓铰接点。旋转关节只有一个自由度:两个物体的相对旋转。这称之为关节角。


要指定一个旋转关节,你需要提供两个物体以及世界坐标的一个锚点。初始化函数会假定物体已经在 应有位置了。

在此例中,两个物体被旋转关节连接起来, 铰接点为第一个物体的质心。

b2RevoluteJointDef jointDef;

jointDef.Initialize(myBody1,myBody2, myBody1->GetWorldCenter());

在 body2 逆时针旋转时,关节角为正。像所有 Box2D 中的角度一样,旋转角也是弧度制的。按规 定,使用Initialize() 创建关节时, 无论两个物体当前的角度怎样,旋转关节角都为0。

有时候,你可能需要控制关节角。为此,旋转关节可以随意地模拟关节限制和马达。

关节限制(joint limit)会强制关节角度保持在一定范围内。为此它会应用足够的扭矩。0应该在范围内,否则在开始模拟时关节会有点倾斜。

关节马达允许你指定关节的角速度(角度的时间导数),速度可正可负。马达可以有产生无限大的力,但这通常是没有必要。想想那个经典问题:

"当一个不可抵抗的力作用在一个不可移动的物体上, 会发生什么?"

我可以告诉你这并不有趣。所以你应该为关节马达提供一个最大扭矩。关节马达会维持在指定的速 度,除非其所需的扭矩超出了最大扭矩。当超出最大扭矩时,关节会慢下来,甚至会反向运动。

你还可以使用关节马达来模拟关节摩擦。只要把关节速度设为0,并将最大扭矩设得很小且有效。这样马达会试图阻止关节旋转, 除非有过大的负载。

这里是对上面旋转关节定义的修订; 这次,关节拥有一个限制以及一个马达,后者用于模拟摩擦。

b2RevoluteJointDef jointDef;

jointDef.Initialize(body1, body2,myBody1->GetWorldCenter());

jointDef.lowerAngle = -0.5f * b2_pi;// -90 degrees

jointDef.upperAngle = 0.25f * b2_pi;// 45 degrees

jointDef.enableLimit = true;

jointDef.maxMotorTorque = 10.0f;

jointDef.motorSpeed = 0.0f;

jointDef.enableMotor = true;

你可以访问旋转关节的关节角,速度,马达扭矩。

float32 GetJointAngle() const;

float32 GetJointSpeed() const;

float32 GetMotorTorque() const;

执行step后,你也可以更新马达的参数。

void SetMotorSpeed(float32 speed);

void SetMaxMotorTorque(float32torque);

关节马达有些有趣的功能。你可以在每个时间步中更新关节的速度,使得它像正弦波那样前后摆动,或者指定一个你想要的函数。

... Game Loop Begin ...

myJoint->SetMotorSpeed(cosf(0.5f* time));

... Game Loop End ...

你也可以用关节马达来跟踪你想要的关节角。比如:

... Game Loop Begin ...

float32 angleError =myJoint->GetJointAngle() - angleTarget;

float32 gain = 0.1f;

myJoint->SetMotorSpeed(-gain *angleError);

... Game Loop End ...

通常你的增益参数不能太大,不然关节会变得不稳定。

8.7 移动关节(Prismatic Joint)

移动关节(prismatic joint)允许两个物体沿指定轴相对移动,它会阻止相对旋转。因此,移动关节只有 一个自由度。


移动关节的定义有些类似于旋转关节;只是转动角度换成了平移,扭矩换成了力。以这样的类比,我们来看一个带有关节限制以及马达摩擦的移动关节定义:

b2PrismaticJointDef jointDef;

b2Vec2 worldAxis(1.0f, 0.0f);

jointDef.Initialize(myBody1,myBody2, myBody1->GetWorldCenter(), worldAxis);

jointDef.lowerTranslation = -5.0f;

jointDef.upperTranslation = 2.5f;

jointDef.enableLimit = true;

jointDef.motorForce = 1.0f;

jointDef.motorSpeed = 0.0f;

jointDef.enableMotor = true;

旋转关节隐含着一个从屏幕射出的轴,而移动关节明确地需要一个平行于屏幕的轴。这个轴会固定于 两个物体之上,沿着它们的运动方向。

就像旋转关节一样,当使用 Initialize() 创建移动关节时,移动为0。所以要确保0在你的移动限制范围内。

移动关节的用法跟旋转关节类似。这是相应的函数:

float32 GetJointTranslation() const;

float32 GetJointSpeed() const;

float32 GetMotorForce() const;

void SetMotorSpeed(float32 speed);

void SetMotorForce(float32 force);

8.8 滑轮关节(Pulley Joint)

滑轮关节用于创建理想的滑轮,它将两个物体接地(ground)并彼此连接。这样,当一个物体上升,另一个物体就会下降。滑轮的绳子长度取决于初始配置。

length1 + length2 == constant

你还可以提供一个系数(ratio)来模拟block and tackle,这会使滑轮一侧的运动比另一侧要快。同时,一侧的约 束力也比另一侧要小。你也可以用这个来模拟机械杠杆(mechanical leverage)。

length1 + ratio * length2 == constant

举个例子,如果系数是2,那么 length1 的变化会是 length2 的两倍。另外连接 body1 的绳子的约束力将会是连接 body2 绳子的一半。


滑轮的一侧完全展开时,另一侧的绳子长度为零,这可能会出问题。此时,约束方程将变得奇异(糟糕)。因此,滑轮关节约束了每一侧的最大长度。另外出于游戏原因你可能也希望控制这个最大长度。最大长度能提高稳定性,以及提供更多的控制。

这是一个滑轮定义的例子:

b2Vec2 anchor1 = myBody1->GetWorldCenter();

b2Vec2 anchor2 =myBody2->GetWorldCenter();

b2Vec2 groundAnchor1(p1.x, p1.y +10.0f);

b2Vec2 groundAnchor2(p2.x, p2.y +12.0f);

float32 ratio = 1.0f;

b2PulleyJointDef jointDef;

jointDef.Initialize(myBody1,myBody2, groundAnchor1, groundAnchor2, anchor1, anchor2, ratio);

jointDef.maxLength1 = 18.0f;

jointDef.maxLength2 = 20.0f;

滑轮关节提供函数得到当前长度。

float32 GetLength1() const;

float32 GetLength2() const;

8.9 齿轮关节(Gear Joint)

如果你想创建复杂的机械装置,可能需要齿轮。原则上,在 Box2D 中你可以用复杂的形状来模拟轮齿,但这并不十分高效,而且这样的工作可能有些乏味。另外,你还得小心地排列齿轮,保证轮齿能平稳地啮合。Box2D 提供了一个创建齿轮的更简单的方法:齿轮关节。


齿轮关节需要两个被旋转关节或移动关节接地(ground)的物体,你可以任意组合这些关节类型。另 外,创建旋转或移动关节时,Box2D需要地(ground)作为body1。

类似于滑轮的系数,你可以指定一个齿轮系数(ratio),齿轮系数可以为负。另外值得注意的是,当一 个是旋转关节(有角度的)而另一个是移动关节(平移)时,齿轮系数有长度单位,或者是长度单位的倒数。

coordinate1 + ratio * coordinate2 ==constant

这是一个齿轮关节的例子:

b2GearJointDef jointDef;

jointDef.body1 = myBody1;

jointDef.body2 = myBody2;

jointDef.joint1 = myRevoluteJoint;

jointDef.joint2 = myPrismaticJoint;

jointDef.ratio = 2.0f * b2_pi /myLength;

注意,齿轮关节依赖于两个其它关节,这是脆弱的:当其它关节被删除了会发生什么?

注意

齿轮关节总应该先于旋转或移动关节被删除。否则由于齿轮关节中的关节指针无效,你的代码将会因访问这些无效指针而导致崩溃。另外齿轮关节也应该在任何相关物体被删除之前删除。

8.10 鼠标关节(Mouse Joint)

在testbed例子中, 鼠标关节用于通过鼠标来操控物体。它试图将物体拖向当前鼠标光标的位置。而在旋转方面就没有限制。

 

鼠标关节的定义需要一个目标点(target point),最大力(maximum force),频率(frequency),阻尼率(damping ratio)。目标点最开始与物体的锚点重合。最大力用于防止在多个动态物体相互作用时,会有激烈反应。你想将最大力设为多大就多大。频率和阻尼率用于创造一种弹性效果,就跟距离关节类似。

许多用户为了游戏的可玩性,会试图修改鼠标关节。用户常常希望鼠标关节有即时反应,精确的去到某个点。这情况下,鼠标关节表现并不好。你可以考虑一下用kinematic物体来替代。

8.11 线性关节(Line Joint)

线性关节跟移动关节(prismatic joint)类似,但没有旋转方面的约束。线性关节可以用来模拟悬挂着的车轮。更多细节请看b2LineJoint.h。

8.12 焊接关节(Weld Joint)

焊接关节的用途是使两个物体不能相对运动。看看testbed中的Cantilever例子,可以知道焊接关节有怎么样的表现。

用焊接关节来定义一个可分裂物体,这想法很诱人。但是,由于Box2D的迭代求解,关节焊得有点不稳。 导致用焊接关节连接起来的物体会有所摆动。

创建可裂物体的更好方法是使用单个的物体,上面有很多fixture。 当物体分裂时,你可以删掉原物体其中一个fixture,并重新创建一个新的物体,带有那个fixture。参考一下testbed中的Breakable例子。


原创粉丝点击