制作 2D 物理引擎:质量、惯性和力





5.00/5 (2投票s)
计算物体的质量和惯性并对其施加力
制作 2D 物理引擎:系列
这是制作 2D 物理引擎系列中的第四篇文章。如果您还没有阅读完本系列在此之前的文章,我强烈建议您绕道去快速浏览一下。
- 制作 2D 物理引擎:数学部分
- 制作 2D 物理引擎:空间和物体
- 制作 2D 物理引擎:形状、世界和积分
- 制作 2D 物理引擎:质量、惯性和力
引言
每个物理对象都有一定的质量和转动惯量,这取决于它的形状和密度。在本文中,我们将根据我们 Shape
和提供的密度为我们的物体分配质量和惯性,以使交互更加逼真。为了简化,我们假设物体具有均匀密度。
我们还将学习现实生活中力的施加方式,并在我们的物理引擎中模拟它们。
本文内容包含在我的 Rust 2D 物理引擎 (2D physics engine) 的 此提交中,该引擎是与本文系列同步构建的。
不要害怕!碰撞即将来临。
质量和惯性计算
预处理
在开始之前,我们必须确保我们物体的质心在其局部空间中位于原点。对于密度均匀的形状,质心就是其形心。
圆形
根据我们对 Circle
的定义,圆心就是其形心。我们在这里不需要做任何事情。
多边形
然而,Polygon
可以是任意的。我们必须找到多边形的形心,然后从每个顶点中减去它,这样多边形的顶点就是相对于形心作为中心的。
多边形的形心是 多边形顶点加权平均值,权重是边三角形的面积。计算边三角形面积和多边形面积的公式(及其解释)将在文章后面给出。
形心的计算方法如下:
centroid = Vec2::ZERO;
for face in polygon {
face_area = 0.5 * face.vertex_a.cross(face.vertex_b);
centroid += (face.vertex_a + face.vertex_b) * face_area / (3.0 * polygon_area);
}
// Make the centroid the origin
for vertex in vertices {
vertex -= centroid;
}
质量
根据质量的定义,mass = density * volume
。由于体积(在 2D 中是面积)是可加的量,可以通过将给定形状分解成更小的形状来计算。
圆形
圆的面积是 \(\pi r^2\) 或 PI * radius * radius
,这使得我们的质量定义非常简单。
mass = density * PI * radius * radius;
多边形
任意凸多边形的面积没有固定的公式,但我们可以通过将其分解为以形心为顶点的三角形来计算。多边形的面积是这些子三角形面积的总和。
每个细分后的三角形都以原点(形心)为一个顶点,另外两个顶点是多边形某条边的顶点。
具有边向量 A
和 B
的三角形面积由 0.5 * abs(A.cross(B))
给出。这是一个众所周知的结果,其推导可以在网上轻松找到。因此,对于顶点 A
和 B
,相应细分三角形的边向量是 A
和 B
,面积为 0.5 * abs(A.cross(B))
。我们的多边形面积就是这些面积的总和。
area = 0;
for face in polygon {
area += 0.5 * abs(face.vertex_a.cross(face.vertex_b));
}
现在,质量是 density * area
。
惯性
2D 中的旋转是围绕垂直于 2D 平面的轴进行的,即 Z 轴。因此,2D 形状的转动惯量是该形状绕过原点的 Z 轴的转动惯量。它类似于质量,是衡量物体对扭矩或旋转运动的抵抗程度。与质量一样,惯性也是可加的,任何形状的惯性都可以通过将其组成部分的惯性相加来计算。
圆形
圆绕过其中心的垂直轴的惯性是 \(\frac{1}{2} m r^2\)。
inertia = 0.5 * mass * radius * radius;
多边形
任意凸多边形的惯性没有固定的公式,但与质量类似,我们可以通过将多边形分解为以原点为顶点的三角形并计算它们的惯性来计算惯性。
具有以原点为一个顶点和边向量 A
和 B
的三角形绕过原点的垂直轴的惯性由 mass * (A.sqrLength() + B.sqrLength() + A.dot(B)) / 6
给出。此公式的推导并不简单,为简洁起见在此不包含。如果您愿意接受挑战(一项冗长的挑战),可以尝试通过将三角形分解为平行于不包含原点的边(即由 A
和 B
形成的边)的细长矩形来证明此结果。
现在可以按如下方式计算多边形的惯性:
inertia = 0;
for face in polygon {
A = face.vertex_a, B = face.vertex_b;
mass_tri = density * 0.5 * abs(A.cross(B));
inertia_tri = mass_tri * (A.sqrLength() + B.sqrLength() + A.dot(B)) / 6;
inertia += inertia_tri;
}
力的应用
本节假设您对力、力矩是什么,它们之间如何关联,以及它们与加速度的关系有基本了解。上一篇文章中简要讨论了力,但为了相关性,此处将再次讨论其中的要点。
外部代理(包括物理引擎)与物体的主要交互机制将通过力(以及间接的力矩)进行。然而,与物体的任何和所有交互都应该基于每一帧或每个时间步。因此,施加到物体上的所有力和力矩仅对当前帧有效。现实生活中力也是这样工作的,但由于我们的世界是连续的而非离散的,并且没有“时间步长”,因此不明显。
当力作用在某个点的物体上时,它会在质心产生线加速度以及力矩。但是,有两种特殊情况需要考虑:
- 施加的力矩为零:根据力矩的定义,\(\vec{\tau} = \vec{r} \times \vec{F}\)。因此,当力作用在质心时,产生的力矩将为零。
- 施加的力为零:偶力(大小相等但方向相反的力对)用于产生力矩而不产生线加速度。在质心处大小相等但方向相反的线力会相互抵消。
在我们的刚体结构中,由于我们有独立的力和力矩(在质心)变量,我们可以先实现这两种情况,然后使用这些实现将力施加到任意点。
pub fn add_force(&mut self, force: Vec2) {
self.force += force;
}
pub fn add_torque(&mut self, torque: f32) {
self.torque += torque;
}
pub fn add_force_at_pos(&mut self, force: Vec2, pos: Vec2) {
self.add_force(force);
self.add_torque(pos.cross(force));
}
冲量
冲量是动量的有限变化。它们也可以被认为是作用在非常短时间内的巨大力。然而,我们将把它们视为动量变化,并在施加冲量时直接修改线速度和角速度。
pub fn add_impulse_at_pos(&mut self, impulse: Vec2, pos: Vec2) {
self.velocity += impulse * self.inv_mass;
self.angular_vel += pos.cross(impulse) * self.inv_inertia;
}
后续步骤
恭喜!我们已经学习了物理引擎的枯燥基础,现在可以继续学习更令人兴奋(也更困难)的内容:碰撞。我建议您及时复习线性代数,为下一篇文章做准备。相信我,您会需要的。
历史
- 2018 年 1 月 28 日:首次发布