在构建2D物理引擎的过程中,理解并实现物体的质量、惯性以及力的正确计算是至关重要的。本文将指导如何根据物体的形状和密度分配质量与惯性,并展示如何在物理引擎中应用力和冲量。为了简化问题,假设所有物体都具有均匀的密度。
在开始之前,需要确保物体的质心位于其局部空间的原点。对于具有均匀密度的形状,质心就是几何中心。
根据对圆形的定义,圆心就是其几何中心。对于圆形物体,不需要进行额外的操作。
然而,多边形的形状可以是任意的。必须找到顶点的质心,并从每个顶点中减去它,使得多边形的顶点相对于质心作为中心。
多边形的质心是顶点的加权平均值,权重由面三角形的面积决定。文章后面将给出计算面三角形面积和多边形面积的公式及其解释。
质心的计算如下:
let centroid = Vec2::ZERO;
for face in polygon {
let face_area = 0.5 * face.vertex_a.cross(face.vertex_b);
centroid += (face.vertex_a + face.vertex_b) * face_area / (3.0 * polygon_area);
}
// 将质心设为原点
for vertex in vertices {
vertex -= centroid;
}
根据质量的定义,质量等于密度乘以体积(在2D中为面积)。作为一个可加量,面积可以通过将给定形状分解成更小的形状来计算。
圆的面积是 \(\pi r^2\),这使得质量定义非常简单:
let mass = density * PI * radius * radius;
任意凸多边形的面积没有固定公式,但可以将多边形细分为以质心为顶点的三角形。多边形的面积就是这些细分三角形面积的总和。
每个细分的三角形有一个顶点在原点(质心),另外两个顶点是多边形的一个面的顶点。
边向量为 \(A\) 和 \(B\) 的三角形的面积由 \(0.5 * abs(A.cross(B))\) 给出。这是一个众所周知的结果,其推导可以很容易地在网上找到。因此,对于顶点 \(A\) 和 \(B\),相应的细分三角形的边向量是 \(A\) 和 \(B\),面积是 \(0.5 * abs(A.cross(B))\)。多边形的面积就是这些面积的总和。
let area = 0;
for face in polygon {
area += 0.5 * abs(face.vertex_a.cross(face.vertex_b));
}
// 现在质量是 density * area
let mass = density * area;
2D中的旋转是关于垂直于2D平面的轴,即Z轴。因此,2D形状的转动惯量是该形状关于通过原点的Z轴的转动惯量。它类似于质量,是衡量物体对扭矩或旋转运动的抵抗力。
圆关于通过其中心的垂直轴的惯性是 \(\frac{1}{2} m r^2\):
let inertia = 0.5 * mass * radius * radius;
任意凸多边形的惯性没有固定公式,但像质量一样,可以通过将多边形细分为以原点为顶点的三角形并计算它们的惯性来计算惯性。
边向量为 \(A\) 和 \(B\) 的三角形关于通过原点的垂直轴的惯性由 \(mass * (A.sqrLength() + B.sqrLength() + A.dot(B)) / 6\) 给出。这个公式的推导并不简单,为了简洁起见,这里不会包含。如果愿意接受挑战(一个漫长的挑战),可以尝试通过将三角形分解成与不包含原点的边平行的薄片来证明这个结果。
let inertia = 0;
for face in polygon {
let A = face.vertex_a, B = face.vertex_b;
let mass_tri = density * 0.5 * abs(A.cross(B));
let inertia_tri = mass_tri * (A.sqrLength() + B.sqrLength() + A.dot(B)) / 6;
inertia += inertia_tri;
}
本节假设已经了解了力和扭矩是什么,它们是如何相互关联的,以及它们与加速度的关系。在之前的文章中简要讨论了力,但为了相关性,这里将再次讨论这些点。
外部代理(包括物理引擎)与物体的主要交互机制将通过力(间接地通过扭矩)进行。然而,与物体的所有交互都应该是基于每帧或每个时间步的。因此,应用于物体的所有力和扭矩应该仅对当前帧有效。这在现实生活中的力也是如此,但并不明显,因为世界是连续的,不是离散的,并且没有“时间步”。
当在物体的某一点施加力时,它在质心产生线性加速度和扭矩。然而,有两个特殊情况需要考虑:
在刚体结构中,由于有独立的力(在质心)和扭矩变量,可以首先实现每种情况,然后使用这些实现在任意点施加力。
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;
}