第七章 Hard Constraints(强约束)
在上一章我们研究了弹力作为力生成元和多个对象互相影响的情况。这是我们第一次令物体基于力学地与其他物体互动。
虽然弹簧可以表示许多情况,但是它们也可以表现得很差。当我们想让我替紧紧地联结在一起,我们需要的弹力常数几乎是不可能模拟的。对于那些物体被硬的线条连接起来的情况,使用弹力不是一个可行的选择。
在这一章,我将会讲一下硬约束(hard constraints)。一开始我们将会研究最简单的硬约束—两个物体间的碰撞和接触。一样的数学可以使用在其他类型的硬约束,就像棒条体或者没有拉紧的电缆那种可以把物体连接在一起的物体。
为了在我们的物理引擎中去处理硬约束,我们需要离开舒服的力生成元世界。我们建立的引擎的所有处理硬约束的技术都与力生成元不同。在本书的最后,18章,我们将会看到所有单元的整合。
7.1 简单地碰撞处理
为了去处理硬约束,我们将会增加一个碰撞处理系统在我们的引擎中。在本书的这一部分,一个碰撞意味着两个物体有接触。在标准的英语中,我们可能会把碰撞这个单词理解成激烈地处理两个物体接近时有很大的相对速度的情况。我们的目标也差不多,但是两个物体只要有接触就可以看作碰撞,而且可以没有接近速度。我们处理高速物体碰撞也是使用同样的方法。这是一个有意义但需要调整的设想。为了避免术语替换,我会在本章中使用碰撞和接触交替使用。
当两个物体碰撞时,他们碰撞后的位移可以根据他们碰撞前的状态计算:这就是碰撞的处理。我们确定两个物体碰撞通过确定两个物体有正确的移动会造成碰撞。因为碰撞发生在很短的时间之内,我们直接控制每个物体的运动。
7.1.1 接近速度
控制着碰撞物体的法则由接近速度决定。接近速度就是当两个物体足够靠近的时候的速度。
注意虽然接近速度叫速度,而不是速率,但它是一个标量。速率没有方向,它们只有非负值。速度有方向。如果我们把向量代表速度的话,那么速度的方向就是向量的方向。但是如果我们使用标量,那么速率的方向就是由它的符号决定。例如,两个物体的分离的话,那么它们的接近速度的小于0
我们计算两个物体的接近速度通过它们的分量计算出它们相对的方向的速度。
Vc是一个接近速度(一个标量),Pa和Pb是a,b的位置,头上有个点的是标量乘,头上有个^的表示这个方向的单位向量。方程可以简化为
虽然这是一个惯例,但是去改变这个标量的符号是更加普遍的。使用接近速度,我可以再做一个分离速度。接近速度是一个两个物体间的相对速度,它们的方向指向对方。
在分离的情况下,两个物体的接近速度是负的,然而它们的分离速度是正的。数学上的等式非常容易得到
Vs作为分离速度,会以这种格式使用在本书的剩余部分。你可以继续使用接近速度如果你喜欢:这是一个偏好问题。
7.1.2 恢复系数
就像我们在上一章看到的那样,当两个物体发生碰撞,他们互相压缩,然后他们的表面由于力的作用发生弹性变形,最终弹性变形再使得它们分离。这些所有的东西都发生在一个很简短的时间之内。最终两个物体不再有任何接近速度。虽然这些行为时弹性的,事实上有更多的事情在里面。
什么事情都可能在压缩中发生,物体的材质也对碰撞有着复杂的影响。实际上,这些行为不遵从弹簧的阻尼弹性运动,我们也不能希望完全模拟真实的情况。
特别是弹簧模型假设冲力在碰撞中是守恒的:
Ma是物体a的质量,Pa是物体a碰撞之前的速度,Pa’则是物体a碰撞之后的速度。
幸运的是质量很大的物体发生碰撞时跟弹簧模型非常相似。我们通过假设动能守恒得到相当不错的行为,我们还会使用7.3的公式去构造我们的碰撞模型。
公式7.3告诉我们碰撞前后的所有速度,但是没有告诉我们单个物体的速度。个别物体的速度是使用接近速度连接在一起的,通过等式:
Vs’是一个碰撞之后的分离速度,Vs是一个碰撞前的分离速度,C是称之为“恢复系数”的常量。
恢复系数控制着碰撞之后分离的速度。它由碰撞物体的材质决定。不同材质会有不同的系数。一些物体比如台球或者网球它们碰到墙会反弹。其他物体比如雪球这些就会碰撞之后粘在一起。
如果系数为1,那么分离的速度就跟接近前一样。如果为0,那么物体将会与碰撞物碰撞后一起运动。但是不过恢复系数怎样,公式7.3还是会使得动能守恒。
使用这两个等式,我们可以得到Pa’和Pb’的值。
7.1.3 碰撞检测和连接法线
我们谈了那么多两个物体的碰撞。我们也常想增加一个物体和一个物体的碰撞的支持。这可能是地面,一个墙壁,或者其他固定的物体。我们可以把这些物体的质量表示为无穷大,但是这会浪费宝贵的CPU时间:我们可以定义它们不动。
如果我们一些场景与一些物体发生了碰撞,那么我们不能使用物体的位置计算分离速度:我们只有一个物体。换句话说,我们不能使用公式7.2;我们需要替代它。
这一项让我们得到了分离速度的方向。分离速度是通过点乘相对速度与这一项得到的。如果我们没有两个物体,我们可以查询得到方向。两个物体碰撞的地方就是这个方向,这个方向也经常被称之为碰撞法线或者接触法线。因为这个方向向量的长度总为1。
在两个粒子碰撞的情况下,碰撞法线将会由以下公式得到:
按照惯例,我们依旧从物体a的角度来讲碰撞法线。这里,从a的角度来说,碰撞是b正面到达的,所以我们使用 。从b的角度的得到碰撞法线向量的话,我们只需要乘以-1即可。我们没有特别明白地做出来,但是这个因素在代码里面经常计算b的分离速度。你将会注意到我们稍后实现的代码里面:一个负号会在b的计算中出现。
当一个粒子与地面发生碰撞,我们只能有物体a,没有物体b。这里,对于a来说,碰撞法线将会是
假设地面是水平的。
当我们离开粒子和开始去使用刚体的时候,用显式的碰撞法线是关键的,即使是两个物体间的碰撞。在之前的章节,图7.1展示了我们可能会遇到的情景。在这里,两个物体根据他们的形状碰撞,碰撞法线与我们所设想的方向几乎完全相反的方向,如果我们只考虑他们的位置的话…
正确的碰撞发下,公式7.2可以得到
7.1.4 动量
我们解决碰撞的方案就是只变化速度。迄今为止,我们的物理引擎只能够通过加速度改变速度。如果加速度加了很长时间,那么速度将会变化非常大。这里的变化确实时间短的情况,:速度的值可以看做是马上改变的。
回想一下,添加一个作用力改变物体的加速度。如果我们马上变化这个力,那么加速度也马上改变。我们可以设想一个类似的方法作用在物体上,改变它的速度。与作用力相比,这个称之为冲量:体现在速度上的一瞬间的变化。我们有公式:
对于力,我们有
冲量是g。冲量经常使用字母p来表示;我将会使用g去避免混淆位置p(position)。
这里有一个巨大的不同点,在力与冲量之间。除非有作用力作用在其上,否则一个物体是不会有加速度的:我们可以计算出总加速度,通过合力。另一方面,一个物体会继续有着一个速度,即使没有冲量作用在其上。………..我们可以像合力那样合成冲量,但是合成的是总的速度变化量,而不是总的速度:
Gi…gn是一系列作用在物体上的冲量。严格上来说,我们不会想合成作用力那样积累冲量。我们将会添加在碰撞发生需要的时候。在一个时间内,每一个冲量都通过下面的公式合成一个冲量:
我们碰撞处理的结果将会作为冲量添加到每一个作用的物体上。冲量将会马上改变物体的速度。
7.2 碰撞处理
为了处理碰撞我们将会创建一片新的代码—ContactResolver。它会参与到每一个碰撞的处理。每一个碰撞会提供在一个碰撞数据结构体内,看起来像这样:
结构体保存着每个物体的指针参与到碰撞当中;一个向量表示一个碰撞法线,从第一个物体的角度;用一个数据成员对应碰撞的恢复系数。如果我们处理物体与场景之间的碰撞,那么第二个指针设置为空NULL。
为了解决一个碰撞我们用先前的章节中的公式实现这些代码:
在碰撞中每个物体的速度都是直接变化的。
7.2.1 碰撞检测
碰撞点由碰撞侦查器找到。一个碰撞侦察器是一堆负责找到数个或一个物体碰撞点的代码。
在我们的引擎中碰撞检测的最终算法结果就是一系列的碰撞数据结构体,填充适当的信息。碰撞检测明显需要考虑到这些对象的几何形状:它们的形状和大小。迄今为止在我们的物理引擎中,我们只假设我们处理粒子,完全没有考虑过物体的几何形状。
这是一个很大的区别,我们保持不变甚至使用更复杂的3D物体:物理模拟系统将不需要知道物体形状的细节。碰撞检测系统才是负责计算任何几何属性的,例如两个物体何时何地发生碰撞,还有碰撞时的碰撞法线。
这里有使用范围很广的算法可以计算出碰撞点,我们将会实现有用的碰撞例程在12章。但现在我们假设这是一个黑盒中的神秘操作。
这里有一个例外:我将会在下一章介绍对于粒子这类球星最简单的碰撞检测的方法。这将允许我们用我们正在建设的多种已有引擎的结合体去建立一种新的物理系统。
一些物体移动中和尝试去预测未来可能发生碰撞的算法是可以考虑的。最简单的方法就是检查两个对象是有穿越。
图7.2 穿插的物体
两个物体穿插在一起,如果他们有一部分互相嵌入,想图7.2那样。当我们处理一个部分嵌入的物体,只改变它们的速度是不够的。如果物体使用一个小的恢复系数碰撞在一起,他们的额分离速度可能会接近于零。这种情况下他们将不会分开,玩家将会看到各种离奇的画面。
作为碰撞解决的一部分,我们需要去解决穿插的情况。
7.2.2 穿插的解决
当两个物体发生穿插,我们要使他们分开。我们期待碰撞侦察器告诉我们两个物体穿插了多深,放在碰撞数据结构体的一部分里面。插入深度的计算决定在碰撞物体的几何属性上,就想我们之前看到的那样,这是碰撞检测系统的领域而不是物理模拟器。
我们增加了一个数据成员在碰撞数据结构体里面保存了信息:
图7.3 穿插与真实
要注意到,就像接近速度那样,插入深度有大小和符号。一个负的深度表示两个物体没有穿插。一个0的深度表示两个物体几乎没有接触。
为了解决穿插问题,我们需要检查插入深度。如果它是接近0或者更小,那么我们什么都不用做;否则,我们可以使两个物体分开到他们的插入深度为0。插入深度的方向应该在碰撞法线里面得到。如果我们在碰撞法线的方向上移动两个物体,当距离等于插入深度时,那么两个物体将不会再发生碰撞。我们只有一个物体发生碰撞的情况下也同样适用:插入深度就是碰撞法线的方向。
所以我们知道物体移动的距离和我们移动的方向;我们需要计算出每一个物体怎么移动。
如果我们只有一个物体参加碰撞,那么很简单:物体只要去走完全程就好了。瑞过我们有两个物体,那么我们有一个总的范围的选择。我们可以简单地移动每一个物体通过同样的方法:插入深度的一半。