Skip to main content

he3d_physics/
contact.rs

1use he3d_math::Vec3;
2
3use crate::body::BodyId;
4use crate::static_geometry::StaticMeshId;
5
6/// The maximum number of points a single pair of Bodies can produce.
7/// 一对 Body 最多能产生的接触点数。
8///
9/// Clipping one convex face against another yields at most one vertex per edge of
10/// the reference face, so a box-on-box manifold never exceeds four; and four
11/// coplanar points are the minimum needed to pin a resting face against rocking
12/// about either axis. The constraint arrays are sized to this, so it is also the
13/// compile-time capacity.
14/// 把一个凸面裁剪到另一个上,最多为参考面的每条棱产生一个顶点,因此盒对盒的流形不会超过
15/// 四个;而四个共面点正是把一个静止面在两个轴上都钉住、不让它摇摆所需的最小数量。约束数组
16/// 按此分配,因此它同时也是编译期容量。
17pub(crate) const MAX_MANIFOLD_POINTS: usize = 4;
18
19/// Whether a Contact is appearing, persisting, or disappearing this step.
20/// 该 Contact 本步是正在出现、持续存在,还是正在消失。
21#[derive(Clone, Copy, Debug, PartialEq, Eq)]
22#[non_exhaustive]
23pub enum ContactPhase {
24   /// The pair touched this step and did not touch last step.
25   /// 本步相碰,上一步未相碰。
26   Begin,
27   /// The pair touched last step and still touches.
28   /// 上一步相碰,本步仍相碰。
29   Persist,
30   /// The pair touched last step and does not touch now.
31   /// 上一步相碰,本步不再相碰。
32   End,
33}
34
35/// What lies on the far side of a Contact's normal.
36/// Contact 法线另一侧的东西。
37///
38/// A Contact is always anchored on a Body; the other side is either a second Body
39/// or a piece of Static geometry, which has no Body of its own.
40/// 一个 Contact 总以某个 Body 为一端;另一端要么是另一个 Body,要么是一块本身没有 Body
41/// 的 Static geometry。
42#[derive(Clone, Copy, Debug, PartialEq, Eq)]
43#[non_exhaustive]
44pub enum ContactPartner {
45   /// Another Body.
46   /// 另一个 Body。
47   Body(BodyId),
48   /// A registered piece of Static geometry.
49   /// 一块已注册的 Static geometry。
50   Static(StaticMeshId),
51}
52
53/// A single point at which two things touch.
54/// 两样东西相互接触的单个点。
55///
56/// The normal points from `a` toward `b`; `depth` is how far the two shapes
57/// overlap along it. A pair that separates still reports an `End` contact built
58/// from the last state in which it overlapped, so an application can always
59/// observe both ends of a contact's lifetime.
60/// 法线由 `a` 指向 `b`;`depth` 是两形状沿该方向的重叠量。已经分离的一对仍会报出一个
61/// 由最后一次重叠状态构造的 `End` 接触,因此应用总能观察到一段接触生命周期的两端。
62#[derive(Clone, Copy, Debug, PartialEq)]
63#[non_exhaustive]
64pub struct Contact {
65   /// The Body the normal points away from.
66   /// 法线背离的那个 Body。
67   pub a: BodyId,
68   /// What the normal points toward.
69   /// 法线指向的东西。
70   pub b: ContactPartner,
71   /// The contact point, in world space.
72   /// 接触点(世界空间)。
73   pub point: Vec3,
74   /// The unit contact normal, pointing from `a` toward `b`.
75   /// 单位接触法线,由 `a` 指向 `b`。
76   pub normal: Vec3,
77   /// Penetration depth along the normal. Always positive for a live Contact.
78   /// 沿法线的穿透深度。存活接触恒为正。
79   pub depth: f32,
80   /// Where this Contact is in its lifetime.
81   /// 该接触在其生命周期中的位置。
82   pub phase: ContactPhase,
83}
84
85impl Contact {
86   /// Whether the Contact has the given Body on either side.
87   /// 该 Contact 的任意一侧是否为给定的 Body。
88   #[must_use]
89   pub fn involves(&self, id: BodyId) -> bool {
90      self.a == id || self.b == ContactPartner::Body(id)
91   }
92}
93
94/// One point of a manifold, before it becomes a solved constraint.
95/// 流形中的一个点,尚未成为已求解的约束。
96#[derive(Clone, Copy, Debug, Default)]
97pub(crate) struct ManifoldPoint {
98   /// World-space contact position.
99   pub position: Vec3,
100   /// Penetration depth along the manifold normal.
101   pub depth: f32,
102   /// A stable identifier for warm-start matching between steps.
103   pub feature: u32,
104}
105
106/// The points at which two shapes overlap, with the normal between them.
107/// 两个形状重叠处的接触点,以及它们之间的法线。
108#[derive(Clone, Copy, Debug)]
109pub(crate) struct Manifold {
110   /// Unit normal pointing from the first shape toward the second.
111   pub normal: Vec3,
112   /// The contact points, in a deterministic order.
113   pub points: [ManifoldPoint; MAX_MANIFOLD_POINTS],
114   /// How many entries of `points` are live.
115   pub count: usize,
116}
117
118impl Default for Manifold {
119   fn default() -> Self {
120      Self {
121         normal: Vec3::Z,
122         points: [ManifoldPoint::default(); MAX_MANIFOLD_POINTS],
123         count: 0,
124      }
125   }
126}
127
128impl Manifold {
129   /// An empty manifold.
130   pub(crate) fn empty() -> Self {
131      Self::default()
132   }
133
134   /// Append a point, ignoring anything past the manifold's capacity.
135   pub(crate) fn push(&mut self, point: ManifoldPoint) {
136      if self.count < MAX_MANIFOLD_POINTS {
137         self.points[self.count] = point;
138         self.count += 1;
139      }
140   }
141}
142
143/// One point of a contact after the solver has claimed it.
144/// 求解器接管后的单个接触点。
145///
146/// `anchor_a` and `anchor_b` are the contact position relative to each Body's
147/// center of mass, captured once so that every solver iteration applies its
148/// impulse at exactly the same point.
149/// `anchor_a` 与 `anchor_b` 是接触点相对于各 Body 质心的位置,只捕获一次,使每次求解
150/// 迭代都在完全相同的点上施加冲量。
151#[derive(Clone, Copy, Debug, Default)]
152pub(crate) struct ConstraintPoint {
153   /// World-space contact position, captured when the constraint was built.
154   pub position: Vec3,
155   /// Contact position relative to Body `a`'s center.
156   pub anchor_a: Vec3,
157   /// Contact position relative to Body `b`'s center.
158   pub anchor_b: Vec3,
159   /// Penetration depth at the start of the step.
160   pub depth: f32,
161   /// Feature id, used to carry impulses across steps.
162   pub feature: u32,
163   /// Accumulated normal impulse.
164   pub normal_impulse: f32,
165   /// Accumulated impulse along each friction tangent.
166   pub tangent_impulse: [f32; 2],
167   /// Effective mass along the normal.
168   pub normal_mass: f32,
169   /// Effective mass along each friction tangent.
170   pub tangent_mass: [f32; 2],
171   /// The angular half of the effective mass: `inv_inertia(anchor × axis)` for
172   /// each of the three solve axes, per side.
173   ///
174   /// The velocity solver applies every impulse along one of these three axes, and
175   /// the inertia tensor is linear, so
176   /// `inv_inertia(anchor × (axis * delta)) == inv_inertia(anchor × axis) * delta`.
177   /// Keeping the product of the first two lets an iteration apply an impulse with
178   /// one scalar multiply where it would otherwise do two quaternion rotations —
179   /// and a four-point constraint does twenty-four of those per iteration otherwise.
180   /// The position solve reuses the normal entry.
181   /// 有效质量的角速度部分:三条求解轴上各自的 `inv_inertia(anchor × axis)`,按侧存放。
182   /// 速度求解器施加的每个冲量都沿这三条轴之一,而惯量张量是线性的,因此
183   /// `inv_inertia(anchor × (axis * delta)) == inv_inertia(anchor × axis) * delta`。把前
184   /// 两者相乘的结果存下来,每次迭代施加冲量就只需一次标量乘法,而不必做两次四元数旋转——否则
185   /// 四点的约束每次迭代要做二十四次。位置求解复用其中的法线项。
186   ///
187   /// Indexed `[side][axis]`, where the axes are the normal and then the two
188   /// tangents, and the second side is zero for Static geometry.
189   /// 索引为 `[side][axis]`:轴依次是法线与两条切线;第二侧为 Static geometry 时为零。
190   pub angular_response: [[Vec3; 3]; 2],
191   /// Velocity bias from restitution, applied to real velocities.
192   pub bias: f32,
193   /// Penetration bias, applied only to pseudo-velocities so that removing
194   /// overlap never leaves kinetic energy behind.
195   pub position_bias: f32,
196   /// Accumulated pseudo-impulse that removes penetration.
197   pub position_impulse: f32,
198}
199
200/// A solved constraint: one normal, two friction tangents, and up to four points.
201/// 一条已求解的约束:一条法线、两条摩擦切线,以及至多四个接触点。
202#[derive(Clone, Copy, Debug)]
203pub(crate) struct ContactConstraint {
204   /// The handle of the first Body, kept so an `End` contact can still name it
205   /// after the Body itself has been removed.
206   /// 第一个 Body 的句柄。保留它是为了在 Body 已被移除之后,`End` 接触仍能指代它。
207   pub id_a: BodyId,
208   /// What lies on the other side.
209   /// 另一侧是什么。
210   pub id_b: ContactPartner,
211   /// Slot index of the first Body.
212   /// 第一个 Body 的槽位索引。
213   pub a: u32,
214   /// Slot index of the other Body, or [`crate::storage::STATIC_PARTNER`] when the
215   /// other side is Static geometry.
216   /// 另一侧 Body 的槽位索引;当另一侧是 Static geometry 时为
217   /// [`crate::storage::STATIC_PARTNER`]。
218   pub b: u32,
219   /// Distinguishes constraints that share a Body pair.
220   ///
221   /// All of a Body's contacts against Static geometry share the same `(a, b)`, so
222   /// this carries the mesh and triangle identity instead. It is zero for a
223   /// Body-versus-Body constraint.
224   /// 区分共享同一对 `(a, b)` 的约束。一个 Body 与 Static geometry 的所有接触共享同一个
225   /// `(a, b)`,因此这里改为携带网格与三角形的身份。Body 对 Body 的约束此值为零。
226   pub partner_key: u64,
227   /// Unit normal from `a` toward `b`.
228   /// 由 `a` 指向 `b` 的单位法线。
229   pub normal: Vec3,
230   /// The two friction tangents, orthogonal to the normal and to each other.
231   /// 两条摩擦切线,与法线正交且彼此正交。
232   pub tangents: [Vec3; 2],
233   /// Combined Coulomb friction coefficient.
234   /// 合并后的库仑摩擦系数。
235   pub friction: f32,
236   /// How many entries of `points` are live.
237   /// `points` 中存活的条目数。
238   pub point_count: u8,
239   /// The contact points.
240   /// 接触点。
241   pub points: [ConstraintPoint; MAX_MANIFOLD_POINTS],
242}
243
244impl Default for ContactConstraint {
245   fn default() -> Self {
246      Self {
247         id_a: BodyId::new(0, 0),
248         id_b: ContactPartner::Body(BodyId::new(0, 0)),
249         a: 0,
250         b: 0,
251         partner_key: 0,
252         normal: Vec3::Z,
253         tangents: [Vec3::X, Vec3::Y],
254         friction: 0.0,
255         point_count: 0,
256         points: [ConstraintPoint::default(); MAX_MANIFOLD_POINTS],
257      }
258   }
259}
260
261impl ContactConstraint {
262   /// The live points.
263   /// 存活的接触点。
264   pub(crate) fn points(&self) -> &[ConstraintPoint] {
265      &self.points[..usize::from(self.point_count)]
266   }
267
268   /// The live points, mutably.
269   /// 存活的接触点(可变)。
270   pub(crate) fn points_mut(&mut self) -> &mut [ConstraintPoint] {
271      let count = usize::from(self.point_count);
272      &mut self.points[..count]
273   }
274
275   /// The key that identifies this constraint across steps.
276   ///
277   /// Both constraint lists are sorted by it, so warm starting can match a pair
278   /// with a binary search.
279   /// 跨步识别这条约束的键。两份约束列表都按它排序,因此 warm starting 可以用二分查找
280   /// 匹配同一对。
281   pub(crate) fn sort_key(&self) -> (u32, u32, u64) {
282      (self.a, self.b, self.partner_key)
283   }
284}
285
286/// Build an orthonormal basis around `normal`, returned as the two tangents.
287/// 围绕 `normal` 构造一组正交基,返回两条切线。
288pub(crate) fn tangent_basis(normal: Vec3) -> [Vec3; 2] {
289   // Pick the axis least aligned with the normal so the cross product is stable.
290   // 选取与法线最不共线的轴,使叉积保持数值稳定。
291   let helper = if normal.x.abs() > normal.y.abs() {
292      Vec3::new(0.0, 1.0, 0.0)
293   } else {
294      Vec3::new(1.0, 0.0, 0.0)
295   };
296   let first = normal.cross(helper).normalize_or_zero();
297   let second = normal.cross(first);
298   [first, second]
299}
300
301#[cfg(test)]
302// Test fixtures turn small loop counters into depths; the values sit far inside
303// the range f32 represents exactly.
304// 测试夹具把小整数循环计数转换为深度;这些值远在 f32 能精确表示的范围内。
305#[allow(clippy::cast_precision_loss)]
306mod tests {
307   use super::{ContactConstraint, Manifold, ManifoldPoint, tangent_basis};
308   use he3d_math::Vec3;
309
310   #[test]
311   fn a_manifold_stops_at_its_capacity() {
312      let mut manifold = Manifold::empty();
313      for index in 0..10 {
314         manifold.push(ManifoldPoint {
315            depth: index as f32,
316            ..ManifoldPoint::default()
317         });
318      }
319      assert_eq!(manifold.count, 4);
320   }
321
322   #[test]
323   fn tangents_are_orthonormal_to_the_normal() {
324      for normal in [
325         Vec3::X,
326         Vec3::Y,
327         Vec3::Z,
328         Vec3::new(1.0, 2.0, 3.0).normalize(),
329      ] {
330         let [t0, t1] = tangent_basis(normal);
331         assert!(normal.dot(t0).abs() < 1e-5, "t0 not orthogonal");
332         assert!(normal.dot(t1).abs() < 1e-5, "t1 not orthogonal");
333         assert!(t0.dot(t1).abs() < 1e-5, "tangents not orthogonal");
334         assert!((t0.length() - 1.0).abs() < 1e-5);
335         assert!((t1.length() - 1.0).abs() < 1e-5);
336      }
337   }
338
339   #[test]
340   fn an_empty_constraint_has_no_points() {
341      let constraint = ContactConstraint::default();
342      assert!(constraint.points().is_empty());
343   }
344}