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 /// Velocity bias from restitution, applied to real velocities.
172 pub bias: f32,
173 /// Penetration bias, applied only to pseudo-velocities so that removing
174 /// overlap never leaves kinetic energy behind.
175 pub position_bias: f32,
176 /// Accumulated pseudo-impulse that removes penetration.
177 pub position_impulse: f32,
178}
179
180/// A solved constraint: one normal, two friction tangents, and up to four points.
181/// 一条已求解的约束:一条法线、两条摩擦切线,以及至多四个接触点。
182#[derive(Clone, Copy, Debug)]
183pub(crate) struct ContactConstraint {
184 /// The handle of the first Body, kept so an `End` contact can still name it
185 /// after the Body itself has been removed.
186 /// 第一个 Body 的句柄。保留它是为了在 Body 已被移除之后,`End` 接触仍能指代它。
187 pub id_a: BodyId,
188 /// What lies on the other side.
189 /// 另一侧是什么。
190 pub id_b: ContactPartner,
191 /// Slot index of the first Body.
192 /// 第一个 Body 的槽位索引。
193 pub a: u32,
194 /// Slot index of the other Body, or [`crate::storage::STATIC_PARTNER`] when the
195 /// other side is Static geometry.
196 /// 另一侧 Body 的槽位索引;当另一侧是 Static geometry 时为
197 /// [`crate::storage::STATIC_PARTNER`]。
198 pub b: u32,
199 /// Distinguishes constraints that share a Body pair.
200 ///
201 /// All of a Body's contacts against Static geometry share the same `(a, b)`, so
202 /// this carries the mesh and triangle identity instead. It is zero for a
203 /// Body-versus-Body constraint.
204 /// 区分共享同一对 `(a, b)` 的约束。一个 Body 与 Static geometry 的所有接触共享同一个
205 /// `(a, b)`,因此这里改为携带网格与三角形的身份。Body 对 Body 的约束此值为零。
206 pub partner_key: u64,
207 /// Unit normal from `a` toward `b`.
208 /// 由 `a` 指向 `b` 的单位法线。
209 pub normal: Vec3,
210 /// The two friction tangents, orthogonal to the normal and to each other.
211 /// 两条摩擦切线,与法线正交且彼此正交。
212 pub tangents: [Vec3; 2],
213 /// Combined Coulomb friction coefficient.
214 /// 合并后的库仑摩擦系数。
215 pub friction: f32,
216 /// How many entries of `points` are live.
217 /// `points` 中存活的条目数。
218 pub point_count: u8,
219 /// The contact points.
220 /// 接触点。
221 pub points: [ConstraintPoint; MAX_MANIFOLD_POINTS],
222}
223
224impl Default for ContactConstraint {
225 fn default() -> Self {
226 Self {
227 id_a: BodyId::new(0, 0),
228 id_b: ContactPartner::Body(BodyId::new(0, 0)),
229 a: 0,
230 b: 0,
231 partner_key: 0,
232 normal: Vec3::Z,
233 tangents: [Vec3::X, Vec3::Y],
234 friction: 0.0,
235 point_count: 0,
236 points: [ConstraintPoint::default(); MAX_MANIFOLD_POINTS],
237 }
238 }
239}
240
241impl ContactConstraint {
242 /// The live points.
243 /// 存活的接触点。
244 pub(crate) fn points(&self) -> &[ConstraintPoint] {
245 &self.points[..usize::from(self.point_count)]
246 }
247
248 /// The live points, mutably.
249 /// 存活的接触点(可变)。
250 pub(crate) fn points_mut(&mut self) -> &mut [ConstraintPoint] {
251 let count = usize::from(self.point_count);
252 &mut self.points[..count]
253 }
254
255 /// The key that identifies this constraint across steps.
256 ///
257 /// Both constraint lists are sorted by it, so warm starting can match a pair
258 /// with a binary search.
259 /// 跨步识别这条约束的键。两份约束列表都按它排序,因此 warm starting 可以用二分查找
260 /// 匹配同一对。
261 pub(crate) fn sort_key(&self) -> (u32, u32, u64) {
262 (self.a, self.b, self.partner_key)
263 }
264}
265
266/// Build an orthonormal basis around `normal`, returned as the two tangents.
267/// 围绕 `normal` 构造一组正交基,返回两条切线。
268pub(crate) fn tangent_basis(normal: Vec3) -> [Vec3; 2] {
269 // Pick the axis least aligned with the normal so the cross product is stable.
270 // 选取与法线最不共线的轴,使叉积保持数值稳定。
271 let helper = if normal.x.abs() > normal.y.abs() {
272 Vec3::new(0.0, 1.0, 0.0)
273 } else {
274 Vec3::new(1.0, 0.0, 0.0)
275 };
276 let first = normal.cross(helper).normalize_or_zero();
277 let second = normal.cross(first);
278 [first, second]
279}
280
281#[cfg(test)]
282// Test fixtures turn small loop counters into depths; the values sit far inside
283// the range f32 represents exactly.
284// 测试夹具把小整数循环计数转换为深度;这些值远在 f32 能精确表示的范围内。
285#[allow(clippy::cast_precision_loss)]
286mod tests {
287 use super::{ContactConstraint, Manifold, ManifoldPoint, tangent_basis};
288 use he3d_math::Vec3;
289
290 #[test]
291 fn a_manifold_stops_at_its_capacity() {
292 let mut manifold = Manifold::empty();
293 for index in 0..10 {
294 manifold.push(ManifoldPoint {
295 depth: index as f32,
296 ..ManifoldPoint::default()
297 });
298 }
299 assert_eq!(manifold.count, 4);
300 }
301
302 #[test]
303 fn tangents_are_orthonormal_to_the_normal() {
304 for normal in [
305 Vec3::X,
306 Vec3::Y,
307 Vec3::Z,
308 Vec3::new(1.0, 2.0, 3.0).normalize(),
309 ] {
310 let [t0, t1] = tangent_basis(normal);
311 assert!(normal.dot(t0).abs() < 1e-5, "t0 not orthogonal");
312 assert!(normal.dot(t1).abs() < 1e-5, "t1 not orthogonal");
313 assert!(t0.dot(t1).abs() < 1e-5, "tangents not orthogonal");
314 assert!((t0.length() - 1.0).abs() < 1e-5);
315 assert!((t1.length() - 1.0).abs() < 1e-5);
316 }
317 }
318
319 #[test]
320 fn an_empty_constraint_has_no_points() {
321 let constraint = ContactConstraint::default();
322 assert!(constraint.points().is_empty());
323 }
324}