解读Box2D (1)核心入口 void b2World::Step

最近在转型做游戏,那么阅读Box2D当然是件必要且愉快的事情。


Box2D的源码地址https://github.com/erincatto/Box2D

Box2D的官网 http://www.box2d.org


Paladin的Box2D克隆地址 https://github.com/PaladinDu/Box2D.git

这个克隆目前并没有新增内容,只是增加了一些中文注释。

在后续的学习过程中可能会添加一些demo到Testbed中。


选择从void b2World::Step说起是因为Step是Box2D对一个逻辑针的实现。

我想这应该是一个物理引擎核心的代码了。

了解了Step,你就了解了物理引擎的逻辑结构。

当然Box2D之所以这么优秀,与其背后高超的结构设计,碰撞算法,逻辑优化同样密不可分。

不过本文旨在在流程上了解Box2D。

将只会对Step进行一定的展开。

首先让我们看下step的声明

/*

 *@brief 处理一个逻辑帧

 *@param dt 逻辑帧的时间长度

 *@param velocityIterations 速度计算迭代次数,

 *@note 将一个逻辑帧内的速度计算迭代velocityIterations次,迭代的次数越多,碰撞传导的速度越快

 *@param positionIterations 位移计算迭代次数

 *@note 迭代的次数越多,解决刚体重叠的速度越快

 */

void b2World::Step(float32 dt,int32 velocityIterations,int32 positionIterations);


b2World::Step 的流程:

1 如果有新的刚体(Fixture)就执行一次基础碰撞检测。

这里有两点需要说明:

1.1 Box2D创建了一个AABB碰撞树,来对物体的碰撞进行基础检测。

1.1.1 先说AABB是什么鬼。

AABB的描述:

    (1)AABB是一个旋转永远为0的矩形。

    (2)每一个刚体都会产生一个AABB。

    (3)AABB会将刚体包裹,且比刚体略大(至少是略大)。

1.1.2 再说说AABB碰撞树是什么鬼

AABB碰撞树的描述:
    (1)AABB碰撞树是二叉平衡树。
    (2)每一个叶子节点都是一个刚体的AABB。
    (3)非叶子节点也是一个AABB,但并不是刚体的AABB。
    (4)父节点的AABB总是包涵子节点的AABB。
    (5)新的叶子节点插入是第一优先级是保持树的平衡。
        第二是插入到而两个子节点中(由于两个节点并没有排序,所以请允许我使用第二个来形容它的位置)。
    (6)详细的树处理操作在 b2DynamicTree.cpp中。

1.2.3 使用AABB碰撞树进行初步检测

    使用AABB碰撞树无疑是为了进行初步筛选,在AABB都无法碰撞的情况下刚体肯定无法碰撞。而且矩形碰撞检测极其简单(只需要检测矩形的右上角是否在B的左下角的左边或下边和与B的右上角是否在A的左下角的左边或下边)。而在查询的时候只需要使用刚体的AABB在树中进行遍历(剪掉节点AABB与刚体AABB不碰撞的节点及其子节点)就能初步获取所有可能产生碰撞的刚体。并组成碰撞元素( Contact )。

1.2 并不是说没有新的刚体产生就不进行碰撞处理

    由于老的刚体在上一帧的逻辑处理是就已经进行过初步碰撞检测。所以这里是由于新的刚体没有处理过所以需要进行一次初步碰撞检测。

2 进行准确碰撞检测,并对真实碰撞的碰撞元素进行碰撞初始化

    在进行准确碰撞检测时会排除掉一些不活跃的、重复的、无效的碰撞元素。并根据刚体的类型进行碰撞初始化。详细碰撞实现在

void b2Collide??(

b2Manifold* manifold,

constb2CircleShape* circleA,constb2Transform& xfA,

constb2CircleShape* circleB,constb2Transform& xfB)

    中。当??为一个刚体类型是,是同类型刚体碰撞否则将形如b2Collide{typeA}And{typeB}。

    不同类型的碰撞是有向的不会出现b2CollideTypeAAndTypeBb2CollideTypeBAndTypeA两种碰撞。

    碰撞初始化的内容主要是记录碰撞体的发力点(point),以方便后续物理模拟计算。比如说圆与圆碰撞时point并不是交点二十圆心。

    碰撞完毕后还会更具条件触发开始碰撞或者结束碰撞。

值得注意的是,如果一个刚体被标记为传感器(sensor)的话,他是不会触发碰撞初始化处理的。需要注意的是,他只会出发开始碰撞和结束碰撞,并不会持续发送碰撞信号。如果一个刚体没有被标记为传感器,在发送碰撞时她还会回调

virtual void PreSolve(b2Contact* contact,constb2Manifold* oldManifold)

    这个方法本意是允许用户进行自定义个物理模拟。当然了,你也可以拿来作别的事情。

2.1. 传感器是什么鬼

传感器是一种会发送碰撞信号但不进行系统物理碰撞模拟的刚体(你也可以自己模拟碰撞效果)。
传感器是可以穿过边界的,他真不会进行任何系统的物理碰撞。所以如果一个传感器在高速运动。你需要在有需要的时候销毁它。
当我们要模拟一些技能效果的时候,就可以使用传感器。当然传感器并不只是这一个用途,至于其它用途就要看你的想象力了。

3 在需要的时候进行物理模拟

    Box2D在进行物理模拟(也包括下面的连续物理模拟)时使用到了island减小计算规模。Box2D将大量需要进行模拟的元素分为一个一个的island,然后再进行物理碰撞模拟。

3.1 island是什么鬼  

island的定义:
    (1)island 由刚体,碰撞元素,连接器(joint)组成。

    (2)island 与island 之间互不影响(这是island存在的理由,也使得Box2D对island可以生成一个处理一个)。

    大量的刚体,碰撞事件和连接器被分为多个island后可以有效的降低物理模拟是消耗的计算资源。原因是碰撞的算法复杂度为O(n2)。这也是为什么一个复杂机器我们会将其分为几个小的模块进行模拟,而不是使用单个动力驱动并将其组为一个整体。

3.2 连接器又是什么鬼

    有的时候我们并不会实现整个物理结构的模拟(比如引擎带动车轮旋转),而是选择在更宏观的层面进行模拟(引擎运作时轮子就会转,但实际上两者之间没有进行物理联系),忽略掉这些细节是为了更高速的进行模拟。同样的,连接器也是为了在较为宏观的层面对一些看似简单其实很复杂的物理逻辑进行模拟。
    举个栗子, b2PulleyJoint 滑轮连接器。
    如果我们想要使用原始的物理定力进行模拟我们需要怎么做?(这还是在理想状态进行模拟)
    1.将绳子分割为无数个(很多,比如1个单位分割为10000份,这里有夸张的成分)小线段。
    2.相邻的两个小线段受到大小相同方向相反的拉力(绳子是没有压力的,当然在滑轮中不需要考虑这一点)。
    3.将滑轮设定为一个可以旋转,永远进行静摩擦,且旋转阻力为0的圆。
    4.对其进行物理模拟。绳子一端的开始线段受到一个拉力,并不断的通过下一线段进行传导力,滑轮可以有效的改变力的方向。
    我相信,同样可以完成对滑轮的模拟。但这样做会增加会增加成吨的计算量。
    而我们使用滑轮连接器进行模拟。
    1.力的发力方向为 发力点与圆的切点 指向 发力点 的方向。
    2.力的受力方向为 受力点 指向 受力点与圆的切点 的方向。
    3.发力与受力大小相同。
    完了,毫无疑问,使用模拟的方式更高效,甚至更真实(理想状态内的真实)。如果你有兴趣还可以给滑轮加个阻尼(b2PulleyJoint是有添加这一属性的)。

    综上所诉,使用模拟的方式表现某些物理结构会更高效且真实。然后Box2D将这些模拟进行了一些整合,也就成了现在的连接器了。

3.3 物理碰撞模拟的流程

    1.将碰撞元素,刚体,链接器分为一个个island。
    2.进行island内部物理碰撞模拟。
        2.1 更新整体的线速度,角速度。主要是环境产生的速度影响,如重力,阻尼,浮力。Box2D中对速度是有限制的。这是为了减轻物理冲突事件发生的概率(比如穿越),速度越快发生的可能性就越高。
        2.2 多次迭代 连接器与碰撞元素 对刚体速度产生的影响。迭代次数越多越真实,因为在一个逻辑针的事件 类由于速度在改变实际上可能已经产生很远的连锁反应,需要多次迭代计算才可以将影响传递过去。
        2.3 更具最新的速度更新位移。这里也做了速度限制。
2.4 多次迭代 位置修复,防止物体被挤的连到一起了。迭代次数越多修复效果越好。当然是在没有空间的地方多次迭代会导致资源浪费。
        2.5 出发碰撞元素影响信号。你可以在回调中获取到碰撞中由速度产生的力(Impulses)。举个栗子,你可以根据这个值来计算伤害,而不是简单的使用碰撞给固定伤害。
        2.6 标记不活跃刚体。这样可以减少变化较少的刚体的计算。
    3.更行AABB,以及基础碰撞检测以方便下一针的处理。  

4 在需要的时候进行连续物理模拟

4.1 为什么要进行连续物理模拟

    我们知道现实世界的时间是连续的。但是物理引擎中的时间确实粒子化的,无论几秒中计算多少次都无法模拟连续的时间(更何况由于cpu或gpu算力有限,时间1秒进行的计算次数并不多)。这就产生了一个问题,如果一个物体移动速度够快,在两次物理模拟计算中间穿越了另一个物体,引擎是无法发现的(一般情况下)。而连续物理模拟就是为了缓解这一问题。

4.2. 连续碰撞物理模拟的对象

    连续碰撞只会针对标记为bullet的刚体进行处理。

4.3 连续碰撞物理模拟的流程

    1. 将Bullet 的碰撞元素,连接器分为一个个island。直到再也获取不到island就会直接结束。
    1.1 获取一个bullet。
    1.2 获取bullet最块发生的碰撞元素。
    1.3 获取该碰撞元素的两个刚体小范围内的碰撞元素(不会连续感染,速度太快了还没传递过去?)
    2.进行island内部物理碰撞模拟。
    2.1 基本流程与物理模拟相同,区别的是这里只会更新速度,不会更新位移。不用太担心(在一些极端情况下连续物理模拟还是会失效)在连续碰撞物理模拟前bullet就已经穿越了。实际上连续模拟是提前模拟了下一针的速度影响。
    2.2 返回第1个步骤,寻找下一个最快碰撞的碰撞元素。

到此Step就说完啦。

期待下一次更新!!!
    
    





发布了54 篇原创文章 · 获赞 1 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/u011255131/article/details/72620274
今日推荐