`
xumingrencai
  • 浏览: 1179620 次
文章分类
社区版块
存档分类
最新评论

Box2D v2.1.0用户手册翻译 - 第04章 碰撞模块(Collision Module)

 
阅读更多

转载自:http://blog.csdn.net/complex_ok/article/details/6716676

第04章 碰撞模块(Collision Module)

4.1 关于

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

4.2 形状(Shapes)

形状描述了可相互碰撞的几何对象, 就算不进行物理模拟,也可独立使用。你可以在shape上执行一些操作。

b2Shape是个基类, Box2D的各种形状都实现了这个基类。此基类定义了几个函数:

•判断一个点与形状是否有重叠

•在形状上执行光线投射(ray cast)

•计算形状的AABB

•计算形状的质量

另外, 每个形状都有两成员变量: 类型(type)和半径(radius)。 对于多边形,半径也是有意义的, 下面会进行讨论。

4.3 圆形(Circle Shapes)

圆形有位置和半径。

圆形是实心的,你没有办法使圆形变成空心。但是,你可以使用多边形来创建一系列线段,让这些线段首尾相连,串成一串,就可以模拟出空心的圆形。

b2CircleShape circle;

circle.m_p.Set(1.0f, 2.0f, 3.0f);

circle.m_radius = 0.5f;

4.4 多边形(Polygon Shapes)

Box2D的多边形是实心的凸(Convex)多边形。在多边形内部任意选择两点,作一线段,如果所有的线段跟多边形的边都不相交,这个多边形就是凸多边形。多边形是实心的,不可能空心。但是,你可以使用两个点来创建多边形,这样就退化成线段。


创建多边形时,使用的点必须是逆时针排列(CCW)。我们必须很小心,逆时针是相对于右手坐标系统来说的,这坐标系下,Z轴指向平面外面。有可能相对于你的屏幕,就变成顺时针了,这取决于你自己的坐标系统是怎么规定的。


多边形的成员变量具有public访问权限,但是你也应该使用初始化函数来创建多边形。初始化函数会创建法向量(normal vectors)并检查参数的合法性。

创建多边形时,你可以传递一个包含顶点的数组。数组大小最多是b2_maxPolygonVertices,这数值默认是8。这已足够描述大多数的凸多边形了。

// 按逆时针顺序定义一个三角形

b2Vec2 vertices[3];

vertices[0].Set(0.0f, 0.0f);

vertices[1].Set(1.0f, 0.0f);

vertices[2].Set(0.0f, 1.0f);

int32 count = 3;

b2PolygonShape polygon;

polygon.Set(vertices, count);

多边形有一些定义好的初始化函数来创建箱(box)和边(edge,也就是线段)。

void SetAsBox(float32 hx, float32hy);

void SetAsBox(float32 hx, float32hy, const b2Vec2& center, float32 angle);

void SetAsEdge(const b2Vec2& v1,const b2Vec2& v2);

多边形从b2Shape中继承了半径。通过半径,在多边形的周围创建了一个保护层(skin)。堆叠的情况下,此保护层让多边形之间保持稍微分开。这使得可以在核心多边形上执行连续碰撞。

(译注:这一段我不太明白,原文是
Polygons inherit a radius from b2Shape. The radius creates a skin around the polygon. The skin is used in stacking scenarios to keep polygons slightly separated. This allows continuous collision to work against the core polygon.)

4.5 形状的点测试(Shape Point Test)

你可以测试一个点是否与形状有所重叠。使用这个函数, 需要提供一个形状的变换以及世界坐标上的一个点。

b2Transfrom transform;

transform.SetIdentity();

b2Vec2 point(5.0f, 2.0f);

bool hit =shape->TestPoint(transform, point);

(译注: Box2D中,形状附加在物体之上,它存储的数据是在物体的局部坐标系下定义的,而传进来要测试的点是在世界坐标系下,坐标系不同,就没有办法比较。这个transform用于将形状从局部坐标系转到世界坐标系,之后才可进行比较。而transform的逆转换就是将世界坐标系转到局部坐标系。故要实现这个函数,也可以先求逆转换,将传过来的点转到局部坐标系,这同样可以进行比较,要看哪一个方便。

看Box2D的源码,它实现b2CircleShape::TestPoint时,是将圆心转成世界坐标系,再比较。而b2PolygonShape::TestPoint,是将传进来的点先转成局部坐标再比较。因为转成世界坐标,多边形要同时转换多个点,而圆形就只转换圆心。

使用局部坐标系,不管物体怎么移动,旋转及缩放,改变的只是这个转换矩阵,形状存储的点不用修改,这样就很方便了。下面文档中,你可以看到形状的很多函数,都会传进一个转换,道理是一样的。)

4.6 形状的光线投射(Shape Ray Cast)

你可以用光线射向形状,得到它们之间的交点和法向量。如果在形状内部开始投射,就当成没有交点,返回false。

b2Transfrom transform;

transform.SetIdentity();

b2RayCastInput input;

input.p1.Set(0.0f, 0.0f);

input.p2.Set(1.0f, 0.0f);

input.maxFraction = 1.0f;

b2RayCastOutput output;

bool hit =shape->RayCast(&output, input, transform);

if (hit)

{

b2Vec2 hitPoint = input.p1 +output.fraction * (input.p2 - input.p1);

}

(译注: 这里说的光线指几何中的射线。

看看b2RayCastInput的定义,除指定了两个点p1, p2外,还有个maxFraction。这个maxFraction是什么意思呢? 我们知道,两点决定一个直线,在数学上知道了两点,再定义直线上的其它点,常使用参数方程。也就是定义 P(fraction) = p1 + fraction * (p2 - p1)。当fraction为0时,就代表p1, 当fraction=1时,就代表p2。这样的定义下,两点之间的线段就是参数从0到1之间变化。参数小于0,表示反向的点,大于1就表示正向超出线段的点。maxFraction就表示要测试的点对应的参数是在[0, maxFraction]内。b2RayCastOutput也有个fraction,意思是一样的。

数学上很喜欢将一些变量归结成0到1之间变化,这叫做规范化。处理问题的常用手段是用某个变换(这里说的变换是广义的)将变量归结成0到1之间,再在规范化之下计算, 之后再用个反变换得到原问题的答案。上面说的直线参数化,可以看成规范化的一种。那为什么要规范化呢?因为这样计算起来方便。那为什么会方便呢?我就答不出来了。Box2D中凡是涉及到向量的,那个单词fraction应该都是上面说的意思。)

4.7 对等函数(Bilateral Functions)

碰撞模块含有对等函数,要传递两个形状,计算出结果。包括:

•接触manifolds

•距离

•撞击时间

4.8 接触Manifolds

(译注: 我不知道Manifold应该翻译成什么,我猜Manifold指有相同属性的一堆东西,可以理解成集合,但翻译成集合又不好。故直接保留英文)

Box2D有些函数用来计算重合形状之间的接触点。考虑一下圆与圆,圆与多边形的碰撞,我们只会得到一个接触点和一个向量。多边形与多边形的碰撞,我们可以得到两个接触点。这些接触点都具有相同的法向量,所以Box2D就将它们归成一组,构成manifold结构。接触求解器进一步处理这结构,以改善物体堆叠在一起时,系统的稳定性。


通常你不需要直接计算接触manifold, 但你可能会希望使用这模拟过程中已处理好的结果。

b2Manifold结构含有一个法向量和最多两个的接触点。向量和接触点都是相对于局部坐标系。为方便接触求解器处理,每个接触点都存储了法向冲量和切向(摩擦)冲量。

b2WorldManifold结构可以用来生成世界坐标下的接触向量和点。你需要提供b2Manifold结构和形状的转换及半径。

b2WorldManifold worldManifold;

worldManifold.Initialize(&manifold,transformA, shapeA.m_radius,

transformB,shapeB.m_radius);

for (int32 i = 0; i <manifold.pointCount; ++i)

{

b2Vec2 point =worldManifold.points[i];

}

模拟过程中,形状会移动而manifold可能会改变。接触点有可能会添加或移除。你可以使用b2GetPointStates来检查状态。

b2PointState state1[2], state2[2];

b2GetPointStates(state1, state2,&manifold1, &manifold2);

if (state1[0] == b2_removeState)

{

// process event

}

4.9 距离(Distance)

b2Distance函数可以用来计算两个形状之间的距离。距离函数需要两个形状,转成b2DistanceProxy。There is also some caching used to warm start the distance function for repeated calls.(看不明白,见谅)。详细见b2Distance.h文件。


4.10 撞击时间(Time of Impact)

如果两个形状快速移动,它们可能会在一个时间步内穿过对方。


b2TimeOfImpact函数用于确定两个形状运动时碰撞的时间。这称为撞击时间(time of impact, TOI)。b2TimeOfImpact的主要目地是防止隧穿效应。特别是,它设计来防止运动的物体隧穿过静态的几何形状而出到外面。

这个函数考虑了形状的旋转和平移,但如果旋转足够大,这函数还是会错过碰撞。函数会报告一个非重叠的时间,并捕捉到所有的平移碰撞。

撞击时间函数最开始时定义了一条的分离轴,并确保形状没有超过这条轴。这可能会在结束位置错过一些碰撞。但这方法很快,在防止隧穿方面也已经足够了。



很难去限定旋转角的范围,有些情况下,就算是很小的旋转角也会导致错过碰撞。通常,就算错过了一些碰撞,也不会影响到游戏的好玩性。

这函数需要两个形状(转成b2DistanceProxy)和两个b2Sweep结构。b2Sweep结构定义了形状的开始和结束时的转换。

你可以在固定旋转角的情况下去执行这个计算撞击时间的函数,这样就不会错过任何碰撞。

4.11 动态树(Dynamic Tree)

Box2D使用b2DynamicTree来高效地组织大量的形状。这个类并不知道形状的存在。取而代之,它通过用户数据指针来操作轴对齐包围框(AABB)。

动态树是分层的AABB树。树的每个内部节点都有两个子节点。左边的子节点是用户的AABB。

这树结构支持了高效的光线投射(ray casts)和区域查询(region queries)。比如,场景中有数百个形状,你想对场景执行光线投射,如果采用蛮力,就需要对每个形状都进行投射。这是很低效的,并没有利用到形状的分布信息。替代方法是,你维护一棵动态树,并对树进行光线投射。在遍历树的时候,可以跳过大量的形状。

区域查询使用树来找到跟需查询的AABB有重叠的所有叶节点。这比蛮力算法高效得多,很多形状会被直接跳过。



通常你并不会直接用到动态树。你会通过b2World类来执行光线投射和区域查询。如果你想创建自己的动态树,你可以去看看Box2D是怎么使用动态树的。

4.12 Broad-phase

物理步内的碰撞处理可以分成两个阶段: narrow-phase和broad-phase。narrow-phase时,我们去计算两个形状之间的接触点。假设有N个形状,使用蛮力算法的话,就需要执行 N*N/2次narrow-phase。

Tb2BroadPhase类使用了动态树来减少管理数据方面的开销。这可以大幅度减少narrow-phase的调用次数。

通常你不会直接和broad-phase交互。Box2D自己会在内部创建并管理broad-phase。另外要注意,b2BroadPhase是设计用于Box2D中的物理模拟,它可能不适合处理其它情况。


分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics