Chapter 8 关节

8.1 关于

关节用于把物体约束到世界,或约束到其它物体上。在游戏中,典型例子有木偶,跷跷板和滑轮。用不同的方式将关节结合起来使用,可以创造出有趣的运动。
有些关节提供了限制(limit),使你可以控制运动的范围。有些关节还提供了马达(motor),它可以以指定的速度驱动关节一直运动,直到你指定了更大的力或扭矩来抵消这种运动。
关节马达有许多不同的用途。你可以使用关节来控制位置,只要提供一个与目标之距离成正比例的关节速度即可。你还可以模拟关节摩擦:将关节速度置零,并且提供一个小的、但有效的最大力或扭矩;那么马达就会努力保持关节不动,直到负载变得过大为止。

8.2 关节定义

每种关节类型都有各自的定义(definition),它们都派生自b2JointDef。所有的关节都连接两个不同的物体,其中一个物体有可能是静态的。关节也可以连接两个static或者kinematic类型的物体,但这没有任何实际用途,只会浪费处理器时间。
你可以为任何一种关节类型指定用户数据。你还可以提供一个标记,用于防止用关节相连的物体之间发生碰撞。实际上, 这是默认行为。你也可以通过设置 collideConnected,来允许相连的物体之间发生碰撞。
很多关节定义需要你提供一些几何数据。一个关节常常需要一个锚点(anchor point)来定义,这是固定于相接物体中的点。Box2D要求这些点在局部坐标系中指定,这样,即便当前物体的变化违反了关节约束(joint constraint),关节还是可以被指定——这通常会发生在游戏保存或载入进度时。另外,有些关节定义需要知道物体之间默认的相对角度。这样才能正确地约束旋转。
初始化几何数据可能有些乏味。所以很多关节提供了初始化函数,使用当前的物体的形状,来消除大部分工作。然而,这些初始化函数通常只应用于原型,在产品代码中应该直接地定义几何数据。这能使关节行为更具健壮性。
其余的关节定义数据依赖于关节的类型。下面我们来介绍它们。

8.3 关节工厂

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

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

这里有个例子,展示了旋转关节(revolute joint)的生命周期:

b2RevoluteJointDef jointDef;
jointDef.bodyA = myBodyA;
jointDef.bodyB = myBodyB;
jointDef.anchorPoint = myBodyA->GetCenterPosition();
b2RevoluteJoint* joint = (b2RevoluteJoint*)myWorld->CreateJoint(&jointDef);
... do stuff ...
myWorld->DestroyJoint(joint);
joint = NULL;

一个很好的习惯:当对象摧毁后,就将对应的指针清零。这样的话,当你试图再次使用这个指针时,程序会以一种可控的方式崩溃。

(译注:野指针虽然不一定会使程序崩溃,但它所带来的问题非常难以调试。空指针虽然一定会让程序崩溃,但是调试起来很简单。)

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

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

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

8.4 使用关节

在许多模拟中,关节被创建之后,直到摧毁也不会再被访问。然而,关节中包含着很多有用的数据,使你可以创建出丰富的模拟。
首先,你可以在关节上得到物体、锚点和用户数据。

b2Body* GetBodyA();
b2Body* GetBodyB();
b2Vec2 GetAnchorA();
b2Vec2 GetAnchorB();
void* GetUserData();

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

b2Vec2 GetReactionForce();
float32 GetReactionTorque();

8.5 距离关节

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

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

b2DistanceJointDef jointDef;
jointDef.Initialize(myBodyA, myBodyB, worldAnchorOnBodyA, worldAnchorOnBodyB);
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 旋转关节

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

要指定一个旋转关节,你需要提供两个物体以及世界坐标的一个锚点。初始化函数会假定物体已经在正确的位置了。
在此例中,两个物体被旋转关节连接起来,其中铰接点为第一个物体的质心。

b2RevoluteJointDef jointDef;
jointDef.Initialize(myBodyA, myBodyB, myBodyA->GetWorldCenter());

在 body2 逆时针旋转时,关节角为正。像所有 Box2D 中的角度一样,旋转角也是弧度制的。按规定,使用Initialize() 创建关节时,无论两个物体当前的角度怎样,旋转关节角都为0。
有时候,你可能需要控制关节角。为此,旋转关节可以随意地模拟关节限制和马达。
关节限制(joint limit)会强制关节角度保持在一定范围内,它会应用足够的扭矩来保持这个范围。0应该在范围内,否则在开始模拟时关节会有点倾斜。
关节马达允许你指定关节的角速度(角度的时间导数),速度可正可负。马达可以有产生无限大的力,但这通常是没有必要的。想想那个经典问题:
“当一个不可抵抗的力作用在一个不可移动的物体上,会发生什么?”
我可以告诉你这并不有趣。所以你应该为关节马达提供一个最大扭矩。关节马达会维持在指定的速度,除非其所需的扭矩超出了最大扭矩。当超出最大扭矩时,关节会慢下来,甚至会反向运动。
你还可以使用关节马达来模拟关节摩擦。只要把关节速度设为0,并将最大扭矩设为很小但有效的值。这样马达会试图阻止关节旋转,除非有过大的负载。
这里是对上面旋转关节定义的修订; 这次,关节拥有一个限制以及一个马达,后者用于模拟摩擦。

b2RevoluteJointDef jointDef;
jointDef.Initialize(bodyA, bodyB, myBodyA->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(float32 torque);

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

... 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)允许两个物体沿指定轴相对移动,它会阻止相对旋转。因此,移动关节只有一个自由度。

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

b2PrismaticJointDef jointDef;
b2Vec2 worldAxis(1.0f, 0.0f);
jointDef.Initialize(myBodyA, myBodyB, myBodyA->GetWorldCenter(), worldAxis);
jointDef.lowerTranslation = -5.0f;
jointDef.upperTranslation = 2.5f;
jointDef.enableLimit = true;
jointDef.maxMotorForce = 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 滑轮关节

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

length1 + length2 == constant

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

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);

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

float32 GetLengthA() const;
float32 GetLengthB() const;

8.9 齿轮关节

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

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

coordinate1 + ratio * coordinate2 == constant

这是一个齿轮关节的例子。物体myBodyA和myBodyB来自于两个关节,并且它们不是同一个物体。

b2GearJointDef jointDef;
jointDef.bodyA = myBodyA;
jointDef.bodyB = myBodyB;
jointDef.joint1 = myRevoluteJoint;
jointDef.joint2 = myPrismaticJoint;
jointDef.ratio = 2.0f * b2_pi / myLength;

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

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

8.10 鼠标关节

在testbed例子中,鼠标关节用于通过鼠标来操控物体。它试图将物体拖向当前鼠标光标的位置。而在旋转方面就没有限制。
鼠标关节的定义需要一个目标点(target point),最大力(maximum force),频率(frequency),阻尼率(damping ratio)。目标点最开始与物体的锚点重合。最大力用于防止在多个动态物体相互作用时的激烈反应。你想将最大力设为多大就多大。频率和阻尼率用于创造一种弹性效果,就跟距离关节类似。
许多用户为了游戏的可玩性,会试图修改鼠标关节。用户常常希望鼠标关节有即时反应,精确的去到某个点。这情况下,鼠标关节表现并不好。你可以考虑一下用kinematic物体来替代。

8.11 轮子关节

轮子关节限制bodyB上的一个点到bodyA的一条线上。轮子关节也提供悬置弹簧的效果。细节参见b2WheelJoint.h和Car.h。

8.12 焊接关节

焊接关节的用途是使两个物体不能相对运动。看看testbed中的Cantilever例子,可以知道焊接关节有怎么样的表现。
用焊接关节来定义一个可分裂物体,这想法很诱人。但是,由于Box2D的迭代求解,关节焊得有点不稳。因此用焊接关节连接起来的物体会有所摆动。
创建可裂物体的更好方法是使用单个的物体,上面有很多fixture。 当物体分裂时,你可以删掉原物体的一个fixture,并重新在一个新的物体上创建它。参考一下testbed中的Breakable例子。

8.13 绳子关节

绳子关机限制了两个点之间的最大距离。它能够阻止连接的物体之间的拉伸,即使在很大的负载下。细节参见b2RopeJoint.h和RopeJoint.h。

8.14 摩擦关节

摩擦关节被用于模拟上下摩擦。关节提供2D的传统摩擦和角度摩擦。细节参见b2FrictionJoint.h 和ApplyForce.h。

8.15 马达关节

马达关节通过指定目标位置和旋转偏移来控制物体的移动。你能设置最大的马达力和力矩来到达目标位置和旋转。如果物体被阻塞,它将停下来,接触力与最大的马达力和力矩成正比。细节参见b2MotorJoint和MotorJoint.h。

(转载)BOX2D V2.3.0 用户手册中文版(第8章)-关节相关推荐

  1. (转载)BOX2D V2.3.0 用户手册中文版(第2章)-Hello Box2D

    (转载)BOX2D V2.3.0 用户手册中文版(第2章)-Hello Box2D Chapter 2 Hello Box2D Box2D的发布包中有个Hello World程序.程序创建了一个大大的 ...

  2. (转载)BOX2D V2.3.0 用户手册中文版(第4章)-碰撞模块

    Chapter 4 碰撞模块 4.1 关于 碰撞模块包含了形状和操作形状的函数.该模块还包含了动态树(dynamic tree)和broad-phase,用于加快大型系统的碰撞处理速度. 碰撞模块被设 ...

  3. (转载)BOX2D V2.3.0 用户手册中文版(第11章)-杂项

    Chapter 11 杂项 11.1 用户数据 b2Fixture, b2Body 和 b2Joint 类都允许你通过一个 void 指针来附加用户数据.当你测试Box2D数据结构,并使其跟自己游戏引 ...

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

    内容很多摘自 Aman JIANG(江超宇)翻译的Box2D v2.0.1 用户手册 第08章 关节(Joints) 8.1 关于 关节用于把物体约束到世界,或约束到其它物体上.在游戏中, 典型例子有 ...

  5. Box2D v2.1.0用户手册翻译 - 第06章 夹具(Fixtures)

    原文地址:http://blog.csdn.net/complex_ok/article/details/6719936 [-] 内容很多摘自 Aman JIANG(江超宇)翻译的Box2D v2.0 ...

  6. Box2D v2.1.0用户手册翻译 - 第09章 接触(Contacts)

    内容很多摘自 Aman JIANG(江超宇)翻译的Box2D v2.0.1 用户手册 第09章 接触(Contacts) 9.1 关于 接触(contact)是由 Box2D 创建的用于管理fixtu ...

  7. Box2D v2.1.0用户手册翻译 - 第02章 Hello Box2D

    内容很多摘自 Aman JIANG(江超宇)翻译的Box2D v2.0.1 用户手册 第02章 Hello Box2D Box2D的发布包中有个Hello World程序.程序创建了一个大大的地面盒( ...

  8. Box2D v2.1.0用户手册翻译 - 第10章 世界(World Class)

    内容很多摘自 Aman JIANG(江超宇)翻译的Box2D v2.0.1 用户手册 第10章 世界(World Class) 关于 b2World类包含物体和关节.它管理着模拟的方方面面,并允许异步 ...

  9. Box2D v2.1.0用户手册翻译 - 第12, 13, 14章

    内容很多摘自 Aman JIANG(江超宇)翻译的Box2D v2.0.1 用户手册 第12章 调试绘图(Debug Drawing) 实现 b2DebugDraw 可得到物理世界的细部图,这里是可用 ...

最新文章

  1. 学python人工智能需要多久_人工智能Python开发怎么学才能最快入门
  2. 云原生语境下,如何重新解读微服务?
  3. STM32开发 -- Ublox GPS之设置PUBX
  4. 在Linux上编写并运行Python文件
  5. python 读取txt文件为字典_python将txt文件读取为字典的示例
  6. [深度学习基础] 深度学习基础及数学原理
  7. python自动化pdf报告_[Python] 自动化办公 PDF提取文字、表格、图片
  8. python输入变量输出常量_Python输入input、输出print
  9. iOS NSString追加字符串的方法
  10. 你的名字比我生命更重要
  11. 超市扫码器应该怎么使用
  12. Affinity Propagation
  13. 再谈桌面虚拟化环境中的默认配置文件与输入法
  14. 超全软件下载网站和网页(一网一匠)
  15. SQL server Date函数之DATEADD()函数
  16. 公司官网产品爬取写入ES
  17. 怎样将本地图片转换成网络链接图片
  18. svn tree conflict
  19. Apple ProRes编码
  20. css 实现sticky 吸顶吸底效果

热门文章

  1. 【Java】Java的各个版本和各个版本的历史版本号的关系与解读
  2. arcgis怎么压缩tif文件_微信传文件有大小限制怎么办?教你3秒把100MPPT压缩成10M...
  3. kali的网络设置及三种网络模式
  4. 卡西欧科学计算机玩法,巨好玩:计算器CASIO(卡西欧)新玩法
  5. 统计篇(四)-- 协方差矩阵的理解
  6. 为什么python代码运行不了_为什么我的python代码不能正常运行?
  7. 王学岗——————H265实现低延时投屏,从零实现高清无损投屏(对应第六节课)
  8. 如何在ipone自带邮件上添加网易邮箱
  9. html正则半角,JS正则密码校验之:JS正则匹配半角英文符号
  10. C语言中的字符常量与变量