Skip to main content

he3d_physics/
world.rs

1use alloc::vec::Vec;
2
3use he3d_math::{Quat, Vec3};
4
5use crate::aabb::Aabb;
6use crate::body::{Body, BodyDesc, BodyId, MotionType, NO_SLEEP_GROUP};
7use crate::broadphase::{DEFAULT_CELL_SIZE, SpatialGrid};
8use crate::contact::{
9   ConstraintPoint, Contact, ContactConstraint, ContactPartner, ContactPhase, MAX_MANIFOLD_POINTS,
10   tangent_basis,
11};
12use crate::narrowphase;
13use crate::shape::{MassProperties, Shape};
14use crate::solver::{self, SolverSettings};
15use crate::static_geometry::{StaticMesh, StaticMeshId, StaticTopology};
16use crate::storage::{STATIC_PARTNER, Slot};
17
18/// Linear speed, in units per second, below which a Body counts as at rest.
19/// 线速度(单位/秒)低于此值时,Body 视为静止。
20pub const SLEEP_LINEAR_THRESHOLD: f32 = 0.05;
21
22/// Angular speed, in radians per second, below which a Body counts as at rest.
23/// 角速度(弧度/秒)低于此值时,Body 视为静止。
24pub const SLEEP_ANGULAR_THRESHOLD: f32 = 0.05;
25
26/// Seconds a Body must stay below both sleep thresholds before it sleeps.
27/// Body 必须持续低于两个睡眠阈值多少秒后才会入睡。
28pub const SLEEP_TIME: f32 = 0.5;
29
30/// The default gravity, in units per second squared.
31/// 默认重力,单位/秒²。
32pub const DEFAULT_GRAVITY: Vec3 = Vec3::new(0.0, -9.81, 0.0);
33
34/// One slot in the World's Static geometry storage.
35/// World 静态几何存储中的一个槽位。
36#[derive(Clone, Debug)]
37struct StaticSlot {
38   generation: u32,
39   mesh: Option<StaticMesh>,
40}
41
42/// Owns a set of Bodies and advances them through time.
43/// 持有一组 Body 并推进它们的时间演化。
44///
45/// A World knows nothing about rendering. It never sees an `ObjectId`, a `Scene`,
46/// or a `Transform`; a Body's placement is a position and an orientation, and the
47/// application owns the link between a Body and whatever draws it. See
48/// `CONTEXT.md` and `docs/adr/0001`.
49/// World 对渲染一无所知。它永远看不到 `ObjectId`、`Scene` 或 `Transform`;Body 的摆放
50/// 是位置与朝向,而 Body 与"画它的东西"之间的联系由应用拥有。见 `CONTEXT.md` 与
51/// `docs/adr/0001`。
52///
53/// # Examples
54/// 示例
55/// ```
56/// use he3d_math::Vec3;
57/// use he3d_physics::{BodyDesc, Shape, World};
58///
59/// let mut world = World::new();
60/// let ball = world.add_body(BodyDesc::dynamic(Shape::sphere(0.5), 1.0).at(Vec3::new(0.0, 5.0, 0.0)));
61/// for _ in 0..60 {
62///     world.step(1.0 / 60.0);
63/// }
64/// let y = world.position(ball).expect("the ball exists").y;
65/// assert!(y < 5.0, "the ball should have fallen, got y={y}");
66/// ```
67#[derive(Clone, Debug)]
68pub struct World {
69   slots: Vec<Slot>,
70   free: Vec<u32>,
71   gravity: Vec3,
72   settings: SolverSettings,
73   grid: SpatialGrid,
74   /// Bodies taking part in this step, as handles.
75   active: Vec<BodyId>,
76   /// The bounding box of each entry in `active`, in the same order.
77   aabbs: Vec<Aabb>,
78   /// Constraints built by this step.
79   constraints: Vec<ContactConstraint>,
80   /// The previous step's constraints, kept for warm starting and contact phases.
81   previous: Vec<ContactConstraint>,
82   /// Bodies to wake after contact detection.
83   wakers: Vec<usize>,
84   /// The contact list published for the step just taken.
85   contacts: Vec<Contact>,
86   /// Registered Static geometry, by slot.
87   static_meshes: Vec<StaticSlot>,
88   /// Free Static geometry slots.
89   static_free: Vec<u32>,
90   /// The shared topology every Static mesh welds into, so an edge between two
91   /// separate meshes is still recognised as an Internal edge.
92   /// 所有 Static mesh 焊接进的共享拓扑,因此两个独立 mesh 之间的边仍被识别为
93   /// Internal edge。
94   topology: StaticTopology,
95   /// Friction of Static geometry, combined with each Body's own.
96   /// Static geometry 的摩擦系数,与各 Body 自身的系数合并。
97   static_friction: f32,
98   /// The members of each sleeping Island, by group id.
99   /// 各睡眠 Island 的成员,按组 id 索引。
100   sleep_groups: Vec<Vec<u32>>,
101   /// Group ids whose members have all woken and which may be reused.
102   /// 成员已全部醒来、可以复用的组 id。
103   free_sleep_groups: Vec<u32>,
104   /// Groups found with a woken member this step.
105   /// 本步发现有成员已醒来的组。
106   woken_groups: Vec<u32>,
107   /// Union-find parent per slot, rebuilt every step.
108   /// 每个槽位的并查集父指针,每步重建。
109   island_parent: Vec<u32>,
110   /// The Island root of each awake dynamic Body, or [`NO_SLEEP_GROUP`].
111   /// 每个清醒 Dynamic Body 所属 Island 的根,或 [`NO_SLEEP_GROUP`]。
112   island_root: Vec<u32>,
113   /// Whether every dynamic Body under each root is settled.
114   /// 每个根下的所有 Dynamic Body 是否都已稳定。
115   island_settled: Vec<bool>,
116   /// Whether each root has any dynamic Body at all.
117   /// 每个根下是否至少有一个 Dynamic Body。
118   island_has_dynamic: Vec<bool>,
119   /// The sleep group being built for each root this step.
120   /// 本步为每个根构建中的睡眠组。
121   island_group: Vec<u32>,
122}
123
124impl Default for World {
125   fn default() -> Self {
126      Self::new()
127   }
128}
129
130impl World {
131   /// An empty World under [`DEFAULT_GRAVITY`].
132   /// 处于 [`DEFAULT_GRAVITY`] 下的空 World。
133   #[must_use]
134   pub fn new() -> Self {
135      Self {
136         slots: Vec::new(),
137         free: Vec::new(),
138         gravity: DEFAULT_GRAVITY,
139         settings: SolverSettings::default(),
140         grid: SpatialGrid::new(DEFAULT_CELL_SIZE),
141         active: Vec::new(),
142         aabbs: Vec::new(),
143         constraints: Vec::new(),
144         previous: Vec::new(),
145         wakers: Vec::new(),
146         contacts: Vec::new(),
147         static_meshes: Vec::new(),
148         static_free: Vec::new(),
149         topology: StaticTopology::default(),
150         static_friction: crate::body::DEFAULT_FRICTION,
151         sleep_groups: Vec::new(),
152         free_sleep_groups: Vec::new(),
153         woken_groups: Vec::new(),
154         island_parent: Vec::new(),
155         island_root: Vec::new(),
156         island_settled: Vec::new(),
157         island_has_dynamic: Vec::new(),
158         island_group: Vec::new(),
159      }
160   }
161
162   /// The current gravity vector.
163   /// 当前重力向量。
164   #[must_use]
165   pub fn gravity(&self) -> Vec3 {
166      self.gravity
167   }
168
169   /// Override the gravity vector. Non-finite input is ignored.
170   /// 覆盖重力向量。非有限输入会被忽略。
171   pub fn set_gravity(&mut self, gravity: Vec3) {
172      if gravity.is_finite() {
173         self.gravity = gravity;
174      }
175   }
176
177   /// The edge length of a broadphase cell.
178   /// 宽阶段单元格的边长。
179   ///
180   /// It should be near the size of a typical Body.
181   /// 它应接近典型 Body 的尺寸。
182   pub fn set_broadphase_cell_size(&mut self, cell_size: f32) {
183      if cell_size.is_finite() && cell_size > 0.0 {
184         self.grid = SpatialGrid::new(cell_size);
185      }
186   }
187
188   /// Velocity iterations per step.
189   /// 每步的速度迭代次数。
190   pub fn set_velocity_iterations(&mut self, iterations: u32) {
191      self.settings.velocity_iterations = iterations.max(1);
192   }
193
194   /// Friction of Static geometry, combined with each Body's own.
195   /// Static geometry 的摩擦系数,与各 Body 自身的系数合并。
196   ///
197   /// It is not a per-mesh property: terrain is usually one material, and a Body's
198   /// own coefficient already varies per Body.
199   /// 它不是逐网格属性:地形通常只有一种材质,而 Body 自身的系数已经逐 Body 变化。
200   pub fn set_static_friction(&mut self, friction: f32) {
201      if friction.is_finite() && friction >= 0.0 {
202         self.static_friction = friction;
203      }
204   }
205
206   /// Register immovable triangle geometry and return its handle.
207   /// 注册不可移动的三角形几何并返回其句柄。
208   ///
209   /// `positions` is a flat list in which every three consecutive entries form one
210   /// triangle — the layout [`he3d_math::Vec3`] arrays use everywhere else. Every
211   /// registered mesh welds into one shared topology, so an edge that lies between
212   /// two separate meshes still counts as an Internal edge; that is what keeps a
213   /// Body sliding across streamed terrain from catching on the seam between two
214   /// tiles.
215   /// `positions` 是扁平列表,每三个连续条目构成一个三角形——与其他地方使用
216   /// [`he3d_math::Vec3`] 数组的布局一致。每个已注册网格都焊接进同一套共享拓扑,因此
217   /// 位于两个独立网格之间的边仍算作 Internal edge;这正是让划过流式地形的 Body 不会卡在
218   /// 瓦片接缝上的原因。
219   ///
220   /// Returns `None` for malformed input: an empty list, a length that is not a
221   /// multiple of three, or a coordinate that is not finite.
222   /// 畸形输入返回 `None`:空列表、长度不是三的倍数,或存在非有限坐标。
223   pub fn add_static_mesh(&mut self, positions: &[Vec3]) -> Option<StaticMeshId> {
224      if positions.is_empty() || positions.len() % 3 != 0 {
225         return None;
226      }
227      if !positions.iter().all(|vertex| vertex.is_finite()) {
228         return None;
229      }
230
231      let triangle_count = positions.len() / 3;
232      let mut owned = Vec::with_capacity(positions.len());
233      let mut triangle_bounds = Vec::with_capacity(triangle_count);
234      let mut welded = Vec::with_capacity(triangle_count);
235      for triangle_index in 0..triangle_count {
236         let base = triangle_index * 3;
237         let vertices = [positions[base], positions[base + 1], positions[base + 2]];
238         let ids = [
239            self.topology.weld(vertices[0]),
240            self.topology.weld(vertices[1]),
241            self.topology.weld(vertices[2]),
242         ];
243         self.topology.add_triangle(ids);
244         triangle_bounds.push(Aabb::new(
245            vertices[0].min(vertices[1]).min(vertices[2]),
246            vertices[0].max(vertices[1]).max(vertices[2]),
247         ));
248         welded.push(ids);
249         owned.extend(vertices);
250      }
251      let mut bounds = *triangle_bounds.first()?;
252      for triangle_aabb in &triangle_bounds[1..] {
253         bounds = bounds.merged(triangle_aabb);
254      }
255
256      let mesh = StaticMesh {
257         positions: owned,
258         triangle_bounds,
259         welded,
260         bounds,
261      };
262      if let Some(index) = self.static_free.pop() {
263         let slot = &mut self.static_meshes[index as usize];
264         slot.mesh = Some(mesh);
265         Some(StaticMeshId::new(index, slot.generation))
266      } else {
267         let index = u32::try_from(self.static_meshes.len()).ok()?;
268         self.static_meshes.push(StaticSlot {
269            generation: 0,
270            mesh: Some(mesh),
271         });
272         Some(StaticMeshId::new(index, 0))
273      }
274   }
275
276   /// Remove a piece of Static geometry, invalidating its handle.
277   /// 移除一块 Static geometry 并使其句柄失效。
278   ///
279   /// Its triangles release their edges from the shared topology, so a tile that is
280   /// streamed out stops making its neighbour's boundary look internal.
281   /// 它的三角形会从共享拓扑中释放各自的边,因此被流式卸载的瓦片不再让邻居的边界显得像
282   /// 内部边。
283   pub fn remove_static_mesh(&mut self, id: StaticMeshId) -> bool {
284      let Some(slot) = self.static_meshes.get_mut(id.index() as usize) else {
285         return false;
286      };
287      if slot.generation != id.generation() {
288         return false;
289      }
290      let Some(mesh) = slot.mesh.take() else {
291         return false;
292      };
293      let bounds = mesh.bounds;
294      for triangle in &mesh.welded {
295         self.topology.remove_triangle(*triangle);
296      }
297      slot.generation = slot.generation.wrapping_add(1);
298      self.static_free.push(id.index());
299
300      // Anything asleep inside the region this geometry occupied has just lost its
301      // support, so it must be returned to simulation. Without this a Body left
302      // asleep on streamed-out terrain would hang in mid-air.
303      //
304      // The test is geometric rather than "which constraints mentioned this mesh",
305      // because a sleeping Body stops producing constraints at all — by the time it
306      // is asleep there is nothing left to consult.
307      // 处于该几何所占区域内的睡眠 Body 刚刚失去了支撑,因此必须被唤回模拟。若没有这一步,
308      // 睡在被流式卸载地形上的 Body 会悬在半空。
309      // 判据用几何而不是"哪些约束提到过这块网格",因为睡眠中的 Body 根本不再产生约束——
310      // 等它睡着时已经没有可查询的记录了。
311      for body_slot in &mut self.slots {
312         let Some(body) = body_slot.get_mut() else {
313            continue;
314         };
315         if body.sleeping && body.motion.is_simulated() && body.aabb().overlaps(&bounds) {
316            body.wake();
317         }
318      }
319      true
320   }
321
322   /// Whether a handle still addresses a registered Static mesh.
323   /// 句柄是否仍指向一块已注册的 Static mesh。
324   #[must_use]
325   pub fn contains_static_mesh(&self, id: StaticMeshId) -> bool {
326      self
327         .static_meshes
328         .get(id.index() as usize)
329         .is_some_and(|slot| slot.generation == id.generation() && slot.mesh.is_some())
330   }
331
332   /// How many pieces of Static geometry are registered.
333   /// 已注册的 Static geometry 数量。
334   #[must_use]
335   pub fn static_mesh_count(&self) -> usize {
336      self
337         .static_meshes
338         .iter()
339         .filter(|slot| slot.mesh.is_some())
340         .count()
341   }
342
343   /// The Contacts found by the most recent [`World::step`].
344   /// 最近一次 [`World::step`] 发现的 Contact。
345   ///
346   /// The list is rebuilt on every step, so it must be read before the next one.
347   /// 该列表每步重建,因此必须在下一步之前读取。
348   #[must_use]
349   pub fn contacts(&self) -> &[Contact] {
350      &self.contacts
351   }
352
353   /// The number of live Bodies.
354   /// 存活 Body 的数量。
355   #[must_use]
356   pub fn len(&self) -> usize {
357      self
358         .slots
359         .iter()
360         .filter(|slot| slot.get().is_some())
361         .count()
362   }
363
364   /// Whether the World holds no Bodies.
365   /// World 是否不持有任何 Body。
366   #[must_use]
367   pub fn is_empty(&self) -> bool {
368      self.slots.iter().all(|slot| slot.get().is_none())
369   }
370
371   /// Insert a Body and return its handle.
372   /// 插入一个 Body 并返回其句柄。
373   ///
374   /// # Panics
375   /// Panics once the World holds more than `u32::MAX` Bodies. That is unreachable
376   /// in practice, since every Body occupies a slot in a `Vec` that could not grow
377   /// that large on any real machine.
378   /// 当 World 持有的 Body 数超过 `u32::MAX` 时 panic。实践中不可达:每个 Body 占用
379   /// `Vec` 中的一个槽位,没有任何真实机器能让它增长到那么大。
380   pub fn add_body(&mut self, desc: BodyDesc) -> BodyId {
381      let body = Body::from_desc(&desc);
382      if let Some(index) = self.free.pop() {
383         let slot = &mut self.slots[index as usize];
384         slot.body = Some(body);
385         BodyId::new(index, slot.generation)
386      } else {
387         let index = u32::try_from(self.slots.len()).expect("body count fits u32");
388         self.slots.push(Slot {
389            generation: 0,
390            body: Some(body),
391         });
392         BodyId::new(index, 0)
393      }
394   }
395
396   /// Remove a Body, invalidating its handle. Returns whether it existed.
397   /// 移除一个 Body 并使其句柄失效。返回它原先是否存在。
398   pub fn remove_body(&mut self, id: BodyId) -> bool {
399      let Some(slot) = self.slots.get_mut(id.index() as usize) else {
400         return false;
401      };
402      if slot.generation != id.generation() || slot.get().is_none() {
403         return false;
404      }
405      slot.body = None;
406      slot.generation = slot.generation.wrapping_add(1);
407      self.free.push(id.index());
408      true
409   }
410
411   /// Whether a handle still addresses a live Body.
412   /// 句柄是否仍指向一个存活的 Body。
413   #[must_use]
414   pub fn contains(&self, id: BodyId) -> bool {
415      self.body(id).is_some()
416   }
417
418   /// Every live Body's handle, in slot order.
419   /// 所有存活 Body 的句柄,按槽位顺序。
420   ///
421   /// # Panics
422   /// Panics once the World holds more than `u32::MAX` Bodies; see
423   /// [`World::add_body`].
424   /// 当 World 持有的 Body 数超过 `u32::MAX` 时 panic;见 [`World::add_body`]。
425   pub fn ids(&self) -> impl Iterator<Item = BodyId> + '_ {
426      self.slots.iter().enumerate().filter_map(|(index, slot)| {
427         slot.get().map(|_| {
428            BodyId::new(
429               u32::try_from(index).expect("slot count fits u32"),
430               slot.generation,
431            )
432         })
433      })
434   }
435
436   /// The Body's shape.
437   /// Body 的形状。
438   #[must_use]
439   pub fn shape(&self, id: BodyId) -> Option<Shape> {
440      self.body(id).map(|body| body.shape)
441   }
442
443   /// The Body's motion mode.
444   /// Body 的运动模式。
445   #[must_use]
446   pub fn motion(&self, id: BodyId) -> Option<MotionType> {
447      self.body(id).map(|body| body.motion)
448   }
449
450   /// The Body's mass properties.
451   /// Body 的质量属性。
452   #[must_use]
453   pub fn mass_properties(&self, id: BodyId) -> Option<MassProperties> {
454      self.body(id).map(|body| body.mass)
455   }
456
457   /// The Body's position.
458   /// Body 的位置。
459   #[must_use]
460   pub fn position(&self, id: BodyId) -> Option<Vec3> {
461      self.body(id).map(|body| body.position)
462   }
463
464   /// The Body's orientation.
465   /// Body 的朝向。
466   #[must_use]
467   pub fn orientation(&self, id: BodyId) -> Option<Quat> {
468      self.body(id).map(|body| body.orientation)
469   }
470
471   /// The Body's world-space bounding box.
472   /// Body 的世界空间包围盒。
473   #[must_use]
474   pub fn aabb(&self, id: BodyId) -> Option<Aabb> {
475      self.body(id).map(Body::aabb)
476   }
477
478   /// The Body's linear velocity.
479   /// Body 的线速度。
480   #[must_use]
481   pub fn velocity(&self, id: BodyId) -> Option<Vec3> {
482      self.body(id).map(|body| body.linear_velocity)
483   }
484
485   /// The Body's angular velocity.
486   /// Body 的角速度。
487   #[must_use]
488   pub fn angular_velocity(&self, id: BodyId) -> Option<Vec3> {
489      self.body(id).map(|body| body.angular_velocity)
490   }
491
492   /// The Body's friction coefficient.
493   /// Body 的摩擦系数。
494   #[must_use]
495   pub fn friction(&self, id: BodyId) -> Option<f32> {
496      self.body(id).map(|body| body.friction)
497   }
498
499   /// The Body's restitution.
500   /// Body 的弹性。
501   #[must_use]
502   pub fn restitution(&self, id: BodyId) -> Option<f32> {
503      self.body(id).map(|body| body.restitution)
504   }
505
506   /// Whether the Body is currently asleep.
507   /// Body 当前是否处于睡眠。
508   #[must_use]
509   pub fn is_sleeping(&self, id: BodyId) -> Option<bool> {
510      self.body(id).map(|body| body.sleeping)
511   }
512
513   /// Move a Body to a position and orientation, waking it.
514   /// 将 Body 移动到某位置与朝向,并唤醒它。
515   ///
516   /// Returns whether the Body exists and accepts the change. A Static Body is
517   /// repositioned but stays static, and a non-finite or degenerate input is
518   /// rejected.
519   /// 返回 Body 是否存在并接受该改动。Static Body 会被重新摆放但仍保持静止;非有限或
520   /// 退化的输入会被拒绝。
521   pub fn set_pose(&mut self, id: BodyId, position: Vec3, orientation: Quat) -> bool {
522      if !position.is_finite() || !orientation.is_finite() || orientation.length_squared() <= 0.0 {
523         return false;
524      }
525      let Some(body) = self.body_mut(id) else {
526         return false;
527      };
528      body.position = position;
529      body.orientation = orientation.normalize();
530      if body.motion.is_simulated() {
531         body.wake();
532      }
533      true
534   }
535
536   /// Set a Body's linear velocity, waking it.
537   /// 设置 Body 的线速度,并唤醒它。
538   pub fn set_velocity(&mut self, id: BodyId, velocity: Vec3) -> bool {
539      if !velocity.is_finite() {
540         return false;
541      }
542      let Some(body) = self.body_mut(id) else {
543         return false;
544      };
545      if !body.motion.moves() {
546         return false;
547      }
548      body.linear_velocity = velocity;
549      if body.motion.is_simulated() {
550         body.wake();
551      }
552      true
553   }
554
555   /// Set a Body's angular velocity, waking it.
556   /// 设置 Body 的角速度,并唤醒它。
557   pub fn set_angular_velocity(&mut self, id: BodyId, angular_velocity: Vec3) -> bool {
558      if !angular_velocity.is_finite() {
559         return false;
560      }
561      let Some(body) = self.body_mut(id) else {
562         return false;
563      };
564      if !body.motion.moves() {
565         return false;
566      }
567      body.angular_velocity = angular_velocity;
568      if body.motion.is_simulated() {
569         body.wake();
570      }
571      true
572   }
573
574   /// Set a Body's friction coefficient, clamped to be non-negative.
575   /// 设置 Body 的摩擦系数,钳制为非负。
576   pub fn set_friction(&mut self, id: BodyId, friction: f32) -> bool {
577      if !friction.is_finite() {
578         return false;
579      }
580      let Some(body) = self.body_mut(id) else {
581         return false;
582      };
583      body.friction = friction.max(0.0);
584      true
585   }
586
587   /// Set a Body's restitution, clamped to be non-negative.
588   /// 设置 Body 的弹性,钳制为非负。
589   pub fn set_restitution(&mut self, id: BodyId, restitution: f32) -> bool {
590      if !restitution.is_finite() {
591         return false;
592      }
593      let Some(body) = self.body_mut(id) else {
594         return false;
595      };
596      body.restitution = restitution.max(0.0);
597      true
598   }
599
600   /// Set a Body's linear and angular damping, clamped to be non-negative.
601   /// 设置 Body 的线性与角阻尼,钳制为非负。
602   pub fn set_damping(&mut self, id: BodyId, linear: f32, angular: f32) -> bool {
603      if !linear.is_finite() || !angular.is_finite() {
604         return false;
605      }
606      let Some(body) = self.body_mut(id) else {
607         return false;
608      };
609      body.linear_damping = linear.max(0.0);
610      body.angular_damping = angular.max(0.0);
611      true
612   }
613
614   /// Change how much of the World's gravity reaches a Body.
615   /// 改变 World 的重力有多少作用于此 Body。
616   ///
617   /// The live counterpart of [`BodyDesc::with_gravity_scale`], for a Body whose
618   /// owner changes its mind mid-scene — a flight model that hands the aircraft back
619   /// to gravity when it crashes, for instance.
620   /// [`BodyDesc::with_gravity_scale`] 的运行时对应物,用于所有者在中途改变主意的 Body
621   /// ——例如飞行模型在飞机坠毁时把它交还给重力。
622   ///
623   /// Returns `false` for a dead handle. Negative or non-finite values are clamped to
624   /// zero, and a zero scale wakes the Body, since a Body asleep under no gravity is
625   /// not necessarily asleep under the new one.
626   /// 句柄失效时返回 `false`。负值或非有限值被钳制为零;系数为零时会唤醒该 Body,因为在无
627   /// 重力下睡着的 Body 在新重力下未必还该睡。
628   pub fn set_gravity_scale(&mut self, id: BodyId, scale: f32) -> bool {
629      let scale = if scale.is_finite() && scale > 0.0 {
630         scale
631      } else {
632         0.0
633      };
634      let Some(body) = self.body_mut(id) else {
635         return false;
636      };
637      // An exact comparison is the point here: this is a change detector, not a
638      // measurement, and any difference at all is a change worth waking for.
639      // 这里就是要精确比较:它是变更检测而非测量,任何差异都是值得唤醒的变化。
640      #[expect(clippy::float_cmp)]
641      let changed = body.gravity_scale != scale;
642      if changed {
643         body.wake();
644      }
645      body.gravity_scale = scale;
646      true
647   }
648
649   /// Add a force at the Body's center of mass, acting for the next step only.
650   /// 在 Body 质心施加一个只作用于下一步的力。
651   ///
652   /// Only a Dynamic Body accumulates force; Static and Kinematic Bodies ignore
653   /// gravity and forces entirely.
654   /// 只有 Dynamic Body 会累积力;Static 与 Kinematic 完全忽略重力与力。
655   pub fn apply_force(&mut self, id: BodyId, force: Vec3) -> bool {
656      if !force.is_finite() {
657         return false;
658      }
659      let Some(body) = self.body_mut(id) else {
660         return false;
661      };
662      if !body.motion.is_simulated() {
663         return false;
664      }
665      body.force += force;
666      body.wake();
667      true
668   }
669
670   /// Add a torque, acting for the next step only.
671   /// 施加一个只作用于下一步的力矩。
672   pub fn apply_torque(&mut self, id: BodyId, torque: Vec3) -> bool {
673      if !torque.is_finite() {
674         return false;
675      }
676      let Some(body) = self.body_mut(id) else {
677         return false;
678      };
679      if !body.motion.is_simulated() {
680         return false;
681      }
682      body.torque += torque;
683      body.wake();
684      true
685   }
686
687   /// Apply an instantaneous linear impulse at the center of mass.
688   /// 在质心施加一个瞬时线冲量。
689   pub fn apply_impulse(&mut self, id: BodyId, impulse: Vec3) -> bool {
690      if !impulse.is_finite() {
691         return false;
692      }
693      let Some(body) = self.body_mut(id) else {
694         return false;
695      };
696      if !body.motion.is_simulated() {
697         return false;
698      }
699      body.linear_velocity += impulse * body.mass.inv_mass;
700      body.wake();
701      true
702   }
703
704   /// Apply an instantaneous angular impulse.
705   /// 施加一个瞬时角冲量。
706   pub fn apply_angular_impulse(&mut self, id: BodyId, impulse: Vec3) -> bool {
707      if !impulse.is_finite() {
708         return false;
709      }
710      let Some(body) = self.body_mut(id) else {
711         return false;
712      };
713      if !body.motion.is_simulated() {
714         return false;
715      }
716      let delta = body.apply_inv_inertia(impulse);
717      body.angular_velocity += delta;
718      body.wake();
719      true
720   }
721
722   /// Return a sleeping Body to simulation.
723   /// 将处于睡眠的 Body 唤回模拟。
724   pub fn wake(&mut self, id: BodyId) -> bool {
725      let Some(body) = self.body_mut(id) else {
726         return false;
727      };
728      body.wake();
729      true
730   }
731
732   /// Clear the force and torque accumulators of every Body.
733   /// 清空所有 Body 的力与力矩累积。
734   pub fn clear_forces(&mut self) {
735      for slot in &mut self.slots {
736         if let Some(body) = slot.get_mut() {
737            body.force = Vec3::ZERO;
738            body.torque = Vec3::ZERO;
739         }
740      }
741   }
742
743   /// Advance the simulation by one fixed time step.
744   /// 以固定步长推进模拟一步。
745   ///
746   /// Non-finite or non-positive input is ignored, so a caller handing over a
747   /// degenerate frame delta cannot corrupt the World. Forces and torques
748   /// accumulated before the call act for exactly this step and are then cleared.
749   /// 非有限或非正输入会被忽略,因此调用方传入退化的帧间隔也不会破坏 World。调用前累积
750   /// 的力与力矩恰好作用这一步,随后被清除。
751   pub fn step(&mut self, delta_time: f32) {
752      if !delta_time.is_finite() || delta_time <= 0.0 {
753         return;
754      }
755      self.integrate_velocities(delta_time);
756      self.detect_contacts();
757      self.carry_impulses();
758      solver::prepare(&self.slots, &mut self.constraints, delta_time);
759      solver::warm_start(&mut self.slots, &self.constraints);
760      solver::solve_velocities(&mut self.slots, &mut self.constraints, self.settings);
761      solver::solve_positions(&mut self.slots, &mut self.constraints);
762      self.integrate_positions(delta_time);
763      self.publish_contacts();
764      core::mem::swap(&mut self.constraints, &mut self.previous);
765      self.update_sleep(delta_time);
766      self.refresh_movement();
767      self.clear_forces();
768   }
769
770   /// Record which Bodies are still moving, for the next step's wake decisions.
771   /// 记录哪些 Body 仍在运动,供下一步的唤醒决策使用。
772   ///
773   /// This must run after the solve: the point of it is to read a velocity that is
774   /// not the raw post-gravity one.
775   /// 它必须在求解之后运行:其意义正是读取一个不是"刚加上重力"的速度。
776   fn refresh_movement(&mut self) {
777      for slot in &mut self.slots {
778         if let Some(body) = slot.get_mut() {
779            body.refresh_movement(SLEEP_LINEAR_THRESHOLD, SLEEP_ANGULAR_THRESHOLD);
780         }
781      }
782   }
783
784   /// Apply gravity, accumulated force, damping, and accumulated torque.
785   /// 施加重力、累积力、阻尼与累积力矩。
786   fn integrate_velocities(&mut self, delta_time: f32) {
787      let gravity = self.gravity;
788      for slot in &mut self.slots {
789         let Some(body) = slot.get_mut() else {
790            continue;
791         };
792         if !body.motion.is_simulated() || body.sleeping {
793            continue;
794         }
795         // Gravity is an acceleration, but a Body with no mass is immovable by
796         // definition and must not be dragged along by it either, and a Body whose
797         // gravity scale is zero has its motion owned by something else.
798         // 重力是加速度,但质量为零的 Body 按定义无法被移动,因此也不能被重力拖走;而重力
799         // 系数为零的 Body,其运动由别的东西拥有。
800         if body.mass.inv_mass > 0.0 && body.gravity_scale > 0.0 {
801            body.linear_velocity += gravity * (body.gravity_scale * delta_time);
802         }
803         body.linear_velocity += body.force * body.mass.inv_mass * delta_time;
804         body.linear_velocity *= 1.0 / (1.0 + body.linear_damping * delta_time);
805
806         let angular_acceleration = body.apply_inv_inertia(body.torque);
807         body.angular_velocity += angular_acceleration * delta_time;
808         body.angular_velocity *= 1.0 / (1.0 + body.angular_damping * delta_time);
809      }
810   }
811
812   /// Move every Body that moves by its own velocity, plus whatever positional
813   /// correction asked for.
814   /// 按自身速度移动每一个会移动的 Body,并叠加位置修正所要求的那部分。
815   fn integrate_positions(&mut self, delta_time: f32) {
816      for slot in &mut self.slots {
817         let Some(body) = slot.get_mut() else {
818            continue;
819         };
820         if !body.motion.moves() || body.sleeping {
821            continue;
822         }
823         let linear = body.linear_velocity + body.pseudo_linear_velocity;
824         body.position += linear * delta_time;
825
826         // Rotate by the exact angle rather than by a truncated quaternion
827         // derivative, so a spinning Body accumulates neither drift nor shrink.
828         // 按精确角度旋转,而不是截断的四元数导数,因此旋转的 Body 既不累积漂移也不收缩。
829         let spin = body.angular_velocity + body.pseudo_angular_velocity;
830         let speed = spin.length();
831         if speed > 1e-8 {
832            let axis = spin / speed;
833            let step = Quat::from_axis_angle(axis, speed * delta_time);
834            body.orientation = (step * body.orientation).normalize();
835         }
836         body.clear_pseudo_velocities();
837      }
838   }
839
840   /// Narrow-phase every broadphase candidate into a contact constraint.
841   ///
842   /// A sleeping Body is left out of the solver entirely. It is woken only by a
843   /// neighbour that is itself moving, so a stack resting on Static geometry
844   /// stays asleep instead of being woken by its own floor every step.
845   /// 每个宽阶段候选对都经过窄相位生成接触约束。处于睡眠的 Body 完全不进入求解器;只有
846   /// 当邻居自身在运动时才会唤醒它,因此搁在静态几何上的堆叠不会被自己的地板每步唤醒。
847   fn detect_contacts(&mut self) {
848      self.active.clear();
849      self.aabbs.clear();
850      for (index, slot) in self.slots.iter().enumerate() {
851         if let Some(body) = slot.get() {
852            let Ok(index) = u32::try_from(index) else {
853               break;
854            };
855            self.active.push(BodyId::new(index, slot.generation));
856            self.aabbs.push(body.aabb());
857         }
858      }
859
860      let static_friction = self.static_friction;
861      let Self {
862         slots,
863         grid,
864         active,
865         aabbs,
866         constraints,
867         wakers,
868         static_meshes,
869         topology,
870         ..
871      } = self;
872      grid.rebuild(aabbs);
873      constraints.clear();
874      wakers.clear();
875
876      grid.for_each_candidate(aabbs, |first_compact, second_compact| {
877         let first_index = active[first_compact as usize].index() as usize;
878         let second_index = active[second_compact as usize].index() as usize;
879         let (Some(first), Some(second)) = (slots[first_index].get(), slots[second_index].get())
880         else {
881            return;
882         };
883         if !solver::is_solvable(first, second) {
884            return;
885         }
886         match (first.sleeping, second.sleeping) {
887            (true, true) => return,
888            (true, false) => {
889               // Only a neighbour that was still moving after the last solve can
890               // disturb a sleeper. Asking the live velocity would call every
891               // awake Body "moving", because gravity has just been added.
892               // 只有在上一步求解后仍在运动的邻居才能扰动睡眠者。直接看实时速度会把每个
893               // 清醒的 Body 都判为"在动",因为重力刚刚被加上。
894               if second.was_moving {
895                  wakers.push(first_index);
896               } else {
897                  return;
898               }
899            }
900            (false, true) => {
901               if first.was_moving {
902                  wakers.push(second_index);
903               } else {
904                  return;
905               }
906            }
907            (false, false) => {}
908         }
909
910         let Some(manifold) = narrowphase::collide(
911            first.shape,
912            first.position,
913            first.orientation,
914            second.shape,
915            second.position,
916            second.orientation,
917         ) else {
918            return;
919         };
920
921         let mut constraint = ContactConstraint {
922            id_a: active[first_compact as usize],
923            id_b: ContactPartner::Body(active[second_compact as usize]),
924            a: u32::try_from(first_index).unwrap_or(0),
925            b: u32::try_from(second_index).unwrap_or(0),
926            partner_key: 0,
927            normal: manifold.normal,
928            tangents: tangent_basis(manifold.normal),
929            friction: solver::combine_friction(first.friction, second.friction),
930            point_count: 0,
931            points: [ConstraintPoint::default(); MAX_MANIFOLD_POINTS],
932         };
933         for (slot, point) in manifold.points[..manifold.count].iter().enumerate() {
934            constraint.points[slot] = ConstraintPoint {
935               position: point.position,
936               anchor_a: point.position - first.position,
937               anchor_b: point.position - second.position,
938               depth: point.depth,
939               feature: point.feature,
940               ..ConstraintPoint::default()
941            };
942         }
943         constraint.point_count = u8::try_from(manifold.count).unwrap_or(0);
944         constraints.push(constraint);
945      });
946
947      detect_static_contacts(slots, static_friction, static_meshes, topology, constraints);
948      for index in wakers.drain(..) {
949         if let Some(body) = slots[index].get_mut() {
950            body.wake();
951         }
952      }
953   }
954
955   /// Carry accumulated impulses across from the previous step, matching points
956   /// by Body pair and feature id.
957   /// 按 Body 对与特征 id 匹配接触点,从上一步沿用累积冲量。
958   fn carry_impulses(&mut self) {
959      // Both lists are sorted, so the match is a binary search per pair.
960      // 两个列表都已排序,因此每对的匹配是一次二分查找。
961      self
962         .constraints
963         .sort_unstable_by_key(ContactConstraint::sort_key);
964      for current in &mut self.constraints {
965         let key = current.sort_key();
966         let Ok(found) = self
967            .previous
968            .binary_search_by_key(&key, ContactConstraint::sort_key)
969         else {
970            continue;
971         };
972         let old = &self.previous[found];
973         for point in current.points_mut() {
974            if let Some(previous) = old
975               .points()
976               .iter()
977               .find(|entry| entry.feature == point.feature)
978            {
979               point.normal_impulse = previous.normal_impulse;
980               point.tangent_impulse = previous.tangent_impulse;
981            }
982         }
983      }
984   }
985
986   /// Publish this step's Contacts with their lifecycle phase.
987   /// 发布本步的 Contact 及其生命周期阶段。
988   fn publish_contacts(&mut self) {
989      let Self {
990         constraints,
991         previous,
992         contacts,
993         ..
994      } = self;
995      contacts.clear();
996
997      for current in constraints.iter() {
998         let key = current.sort_key();
999         let old = previous
1000            .binary_search_by_key(&key, ContactConstraint::sort_key)
1001            .ok()
1002            .map(|found| &previous[found]);
1003         for point in current.points() {
1004            let existed = old.is_some_and(|entry| {
1005               entry
1006                  .points()
1007                  .iter()
1008                  .any(|candidate| candidate.feature == point.feature)
1009            });
1010            contacts.push(Contact {
1011               a: current.id_a,
1012               b: current.id_b,
1013               point: point.position,
1014               normal: current.normal,
1015               depth: point.depth,
1016               phase: if existed {
1017                  ContactPhase::Persist
1018               } else {
1019                  ContactPhase::Begin
1020               },
1021            });
1022         }
1023      }
1024
1025      // Anything that touched last step and does not now is ending. The Bodies are
1026      // named from the handle captured last step, so an `End` still refers to a
1027      // Body that has since been removed.
1028      // 上一步相碰、本步不再相碰的接触正在结束。Body 用上一步捕获的句柄命名,因此
1029      // `End` 仍能指代一个此后已被移除的 Body。
1030      for old in previous.iter() {
1031         let key = old.sort_key();
1032         let current = constraints
1033            .binary_search_by_key(&key, ContactConstraint::sort_key)
1034            .ok()
1035            .map(|found| &constraints[found]);
1036         for point in old.points() {
1037            let survives = current.is_some_and(|entry| {
1038               entry
1039                  .points()
1040                  .iter()
1041                  .any(|candidate| candidate.feature == point.feature)
1042            });
1043            if !survives {
1044               contacts.push(Contact {
1045                  a: old.id_a,
1046                  b: old.id_b,
1047                  point: point.position,
1048                  normal: old.normal,
1049                  depth: point.depth,
1050                  phase: ContactPhase::End,
1051               });
1052            }
1053         }
1054      }
1055   }
1056
1057   /// Accumulate rest time, then settle and wake whole Islands.
1058   /// 累积静止时间,然后以整个 Island 为单位入睡与唤醒。
1059   ///
1060   /// Sleep is per Island, not per Body. A stack whose members slept independently
1061   /// would leave the ones above a disturbed Body frozen in mid-air; grouping them
1062   /// by the contact graph and moving the whole group together is what prevents
1063   /// that.
1064   /// 睡眠以 Island 而非 Body 为单位。若一叠箱子的成员各自独立入睡,被扰动的 Body 上方的
1065   /// 那些就会僵在半空;按接触图分组并整组一起移动,正是防止这件事的手段。
1066   fn update_sleep(&mut self, delta_time: f32) {
1067      self.dissolve_woken_groups();
1068
1069      for slot in &mut self.slots {
1070         let Some(body) = slot.get_mut() else {
1071            continue;
1072         };
1073         if !body.motion.is_simulated() || body.sleeping {
1074            continue;
1075         }
1076         if body.is_below_sleep_threshold(SLEEP_LINEAR_THRESHOLD, SLEEP_ANGULAR_THRESHOLD) {
1077            body.sleep_timer += delta_time;
1078         } else {
1079            body.sleep_timer = 0.0;
1080         }
1081      }
1082
1083      self.sleep_settled_islands();
1084   }
1085
1086   /// Wake every Island that has at least one woken member, and release its group.
1087   ///
1088   /// A Body that has been woken keeps its group tag precisely so that this pass
1089   /// can find its companions. That single rule covers every way a disturbance can
1090   /// start — an impulse, a velocity, a new contact — without each call site having
1091   /// to know about Islands.
1092   /// 唤醒每个至少有一个成员已醒来的 Island,并释放其组。已被唤醒的 Body 会刻意保留组标记,
1093   /// 正是为了让这一趟能找到它的同伴。这一条规则覆盖了扰动可能开始的每一种方式——冲量、
1094   /// 速度、新接触——而无需每个调用点都了解 Island。
1095   fn dissolve_woken_groups(&mut self) {
1096      self.woken_groups.clear();
1097      for slot in &self.slots {
1098         let Some(body) = slot.get() else {
1099            continue;
1100         };
1101         if !body.sleeping
1102            && body.sleep_group != NO_SLEEP_GROUP
1103            && !self.woken_groups.contains(&body.sleep_group)
1104         {
1105            self.woken_groups.push(body.sleep_group);
1106         }
1107      }
1108      if self.woken_groups.is_empty() {
1109         return;
1110      }
1111      for slot in &mut self.slots {
1112         let Some(body) = slot.get_mut() else {
1113            continue;
1114         };
1115         if self.woken_groups.contains(&body.sleep_group) {
1116            body.wake();
1117            body.sleep_group = NO_SLEEP_GROUP;
1118         }
1119      }
1120      for group in self.woken_groups.drain(..) {
1121         if let Some(members) = self.sleep_groups.get_mut(group as usize) {
1122            members.clear();
1123         }
1124         self.free_sleep_groups.push(group);
1125      }
1126   }
1127
1128   /// Group the awake dynamic Bodies by the contact graph and sleep any group that
1129   /// has come entirely to rest.
1130   /// 按接触图把清醒的 Dynamic Body 分组,并让整体已稳定的组入睡。
1131   fn sleep_settled_islands(&mut self) {
1132      let count = self.slots.len();
1133      let Ok(count_u32) = u32::try_from(count) else {
1134         return;
1135      };
1136      if count == 0 {
1137         return;
1138      }
1139
1140      self.island_parent.clear();
1141      self.island_parent.extend(0..count_u32);
1142      self.island_root.clear();
1143      self.island_root.resize(count, NO_SLEEP_GROUP);
1144      self.island_settled.clear();
1145      self.island_settled.resize(count, true);
1146      self.island_has_dynamic.clear();
1147      self.island_has_dynamic.resize(count, false);
1148      self.island_group.clear();
1149      self.island_group.resize(count, NO_SLEEP_GROUP);
1150
1151      // Union across Body-versus-Body contacts. Static geometry and Kinematic
1152      // Bodies transmit nothing: they never sleep, and a Body resting on a floor
1153      // must be able to sleep without the floor joining its Island.
1154      // 沿 Body 对 Body 的接触做并集。Static geometry 与 Kinematic Body 不传递任何东西:
1155      // 它们从不入睡,而搁在地板上的 Body 必须能在不把地板拉进自己 Island 的前提下入睡。
1156      for constraint in &self.constraints {
1157         if constraint.b == STATIC_PARTNER {
1158            continue;
1159         }
1160         let first = find_root(&mut self.island_parent, constraint.a);
1161         let second = find_root(&mut self.island_parent, constraint.b);
1162         if first != second {
1163            self.island_parent[second as usize] = first;
1164         }
1165      }
1166
1167      for index in 0..count {
1168         let Some(body) = self.slots[index].get() else {
1169            continue;
1170         };
1171         if !body.motion.is_simulated() || body.sleeping {
1172            continue;
1173         }
1174         let root = find_root(&mut self.island_parent, u32::try_from(index).unwrap_or(0));
1175         self.island_root[index] = root;
1176         self.island_has_dynamic[root as usize] = true;
1177         if body.sleep_timer < SLEEP_TIME {
1178            self.island_settled[root as usize] = false;
1179         }
1180      }
1181
1182      for index in 0..count {
1183         let root = self.island_root[index];
1184         if root == NO_SLEEP_GROUP
1185            || !self.island_has_dynamic[root as usize]
1186            || !self.island_settled[root as usize]
1187         {
1188            continue;
1189         }
1190         let mut group = self.island_group[root as usize];
1191         if group == NO_SLEEP_GROUP {
1192            group = self.allocate_sleep_group();
1193            self.island_group[root as usize] = group;
1194         }
1195         if let Some(members) = self.sleep_groups.get_mut(group as usize) {
1196            members.push(u32::try_from(index).unwrap_or(0));
1197         }
1198         if let Some(body) = self.slots[index].get_mut() {
1199            body.put_to_sleep();
1200            body.sleep_group = group;
1201         }
1202      }
1203   }
1204
1205   /// Reserve a sleep group, reusing a released one when possible.
1206   /// 预留一个睡眠组,尽可能复用已释放的组。
1207   fn allocate_sleep_group(&mut self) -> u32 {
1208      if let Some(group) = self.free_sleep_groups.pop() {
1209         return group;
1210      }
1211      let index = u32::try_from(self.sleep_groups.len()).unwrap_or(u32::MAX);
1212      self.sleep_groups.push(Vec::new());
1213      index
1214   }
1215
1216   /// Borrow a live Body.
1217   fn body(&self, id: BodyId) -> Option<&Body> {
1218      let slot = self.slots.get(id.index() as usize)?;
1219      if slot.generation != id.generation() {
1220         return None;
1221      }
1222      slot.get()
1223   }
1224
1225   /// Mutably borrow a live Body.
1226   fn body_mut(&mut self, id: BodyId) -> Option<&mut Body> {
1227      let slot = self.slots.get_mut(id.index() as usize)?;
1228      if slot.generation != id.generation() {
1229         return None;
1230      }
1231      slot.get_mut()
1232   }
1233}
1234
1235/// Narrow-phase every Body against every registered piece of Static geometry.
1236/// 对每个 Body 与每块已注册 Static geometry 做窄相位。
1237///
1238/// Static geometry is not in the broadphase grid: each mesh carries its own bounds
1239/// and each of its triangles carries tighter ones, so a Body only reaches the
1240/// triangles it can actually touch.
1241/// Static geometry 不在宽阶段网格中:每个 mesh 携带自身包围盒,其每个三角形又携带更紧的
1242/// 包围盒,因此 Body 只会触及它真正够得着的三角形。
1243fn detect_static_contacts(
1244   slots: &[Slot],
1245   static_friction: f32,
1246   static_meshes: &[StaticSlot],
1247   topology: &StaticTopology,
1248   constraints: &mut Vec<ContactConstraint>,
1249) {
1250   // Static geometry is not in the grid. Each mesh carries its own bounds, and
1251   // each of its triangles carries tighter ones, so a Body only reaches the
1252   // triangles it can actually touch.
1253   // Static geometry 不在网格中。每个 mesh 携带自身包围盒,其每个三角形又携带更紧的
1254   // 包围盒,因此 Body 只会触及它真正够得着的三角形。
1255   for (mesh_index, slot) in static_meshes.iter().enumerate() {
1256      let Some(mesh) = slot.mesh.as_ref() else {
1257         continue;
1258      };
1259      let Ok(mesh_index) = u32::try_from(mesh_index) else {
1260         break;
1261      };
1262      let mesh_id = StaticMeshId::new(mesh_index, slot.generation);
1263      for (body_index, body_slot) in slots.iter().enumerate() {
1264         let Some(body) = body_slot.get() else {
1265            continue;
1266         };
1267         // A sleeping Body stays asleep on immovable geometry: nothing there can
1268         // move, so nothing there can wake it.
1269         // 睡眠中的 Body 会在不可移动几何上保持睡眠:那里没有任何东西会移动,因此也
1270         // 没有任何东西能唤醒它。
1271         if body.sleeping || !body.motion.moves() {
1272            continue;
1273         }
1274         let body_bounds = body.aabb();
1275         if !body_bounds.overlaps(&mesh.bounds) {
1276            continue;
1277         }
1278         let Ok(body_index) = u32::try_from(body_index) else {
1279            break;
1280         };
1281         for triangle_index in 0..mesh.triangle_count() {
1282            if !body_bounds.overlaps(&mesh.triangle_bounds[triangle_index]) {
1283               continue;
1284            }
1285            let triangle = mesh.triangle(triangle_index);
1286            let hit = match body.shape {
1287               Shape::Box { half_extents } => {
1288                  let frame =
1289                     narrowphase::BoxFrame::new(body.position, body.orientation, half_extents);
1290                  narrowphase::box_triangle(&frame, triangle)
1291               }
1292               Shape::Sphere { radius } => {
1293                  narrowphase::sphere_triangle(body.position, radius, triangle).map(|manifold| {
1294                     narrowphase::TriangleHit {
1295                        manifold,
1296                        source_edge: None,
1297                     }
1298                  })
1299               }
1300            };
1301            let Some(hit) = hit else {
1302               continue;
1303            };
1304
1305            // An Internal edge is where two triangles of the shared topology
1306            // meet, so there is no real corner there for a Body to catch on.
1307            // Discarding the contact is what lets a Body slide across a
1308            // triangulated surface instead of tripping over its seams.
1309            // Internal edge 是共享拓扑中两个三角形相接之处,那里并不存在可供 Body 卡住
1310            // 的真实棱角。丢弃该接触正是让 Body 能在三角化表面上滑行、而不是被其接缝绊住
1311            // 的原因。
1312            if let Some(edge) = hit.source_edge {
1313               let ids = mesh.welded[triangle_index];
1314               if topology.is_internal_edge(ids[edge], ids[(edge + 1) % 3]) {
1315                  continue;
1316               }
1317            }
1318
1319            let key =
1320               (u64::from(mesh_id.index()) << 32) | u64::try_from(triangle_index).unwrap_or(0);
1321            let mut constraint = ContactConstraint {
1322               id_a: BodyId::new(body_index, body_slot.generation),
1323               id_b: ContactPartner::Static(mesh_id),
1324               a: body_index,
1325               b: STATIC_PARTNER,
1326               partner_key: key,
1327               normal: hit.manifold.normal,
1328               tangents: tangent_basis(hit.manifold.normal),
1329               friction: solver::combine_friction(body.friction, static_friction),
1330               point_count: 0,
1331               points: [ConstraintPoint::default(); MAX_MANIFOLD_POINTS],
1332            };
1333            for (slot, point) in hit.manifold.points[..hit.manifold.count].iter().enumerate() {
1334               constraint.points[slot] = ConstraintPoint {
1335                  position: point.position,
1336                  anchor_a: point.position - body.position,
1337                  // Immovable geometry has no center of mass, and the solver
1338                  // never applies an impulse on that side, so the second anchor
1339                  // is only ever read as a zero contribution.
1340                  // 不可移动几何没有质心,且求解器永不在该侧施加冲量,因此第二个锚点
1341                  // 只会被当作零贡献读取。
1342                  anchor_b: Vec3::ZERO,
1343                  depth: point.depth,
1344                  feature: point.feature,
1345                  ..ConstraintPoint::default()
1346               };
1347            }
1348            constraint.point_count = u8::try_from(hit.manifold.count).unwrap_or(0);
1349            constraints.push(constraint);
1350         }
1351      }
1352   }
1353}
1354/// Walk a union-find tree to its root, halving the path on the way.
1355/// 沿并查集树走到根,途中做路径减半。
1356fn find_root(parent: &mut [u32], start: u32) -> u32 {
1357   let mut index = start;
1358   loop {
1359      let Some(&up) = parent.get(index as usize) else {
1360         return index;
1361      };
1362      if up == index {
1363         return index;
1364      }
1365      let grandparent = parent.get(up as usize).copied().unwrap_or(up);
1366      parent[index as usize] = grandparent;
1367      index = grandparent;
1368   }
1369}
1370
1371#[cfg(test)]
1372// Test fixtures convert small loop counters into world coordinates and settle
1373// times into step counts; both sit far inside f32's exact integer range and the
1374// step count is bounded by the test's own duration.
1375// 测试夹具把小整数循环计数转换为世界坐标、把稳定时长换算为步数;两者都远在 f32 的精确
1376// 整数范围内,且步数受测试自身时长的约束。
1377#[allow(
1378   clippy::cast_precision_loss,
1379   clippy::cast_possible_truncation,
1380   clippy::cast_sign_loss
1381)]
1382mod tests {
1383   use super::{DEFAULT_GRAVITY, SLEEP_TIME, World};
1384   use crate::body::{BodyDesc, BodyId};
1385   use crate::contact::{ContactPartner, ContactPhase};
1386   use crate::shape::Shape;
1387   use he3d_math::Vec3;
1388
1389   fn free_fall_world() -> (World, BodyId) {
1390      let mut world = World::new();
1391      let ball =
1392         world.add_body(BodyDesc::dynamic(Shape::sphere(0.5), 1.0).at(Vec3::new(0.0, 10.0, 0.0)));
1393      (world, ball)
1394   }
1395
1396   /// A wide static slab whose top surface sits at `top`.
1397   fn ground(world: &mut World, top: f32) -> BodyId {
1398      let half = Vec3::new(20.0, 0.5, 20.0);
1399      world.add_body(BodyDesc::stationary(Shape::cuboid(half)).at(Vec3::new(0.0, top - 0.5, 0.0)))
1400   }
1401
1402   fn settle(world: &mut World, seconds: f32) {
1403      let dt = 1.0 / 120.0;
1404      let steps = (seconds / dt) as u32;
1405      for _ in 0..steps {
1406         world.step(dt);
1407      }
1408   }
1409
1410   #[test]
1411   fn gravity_accelerates_a_dynamic_body() {
1412      let (mut world, ball) = free_fall_world();
1413      world.step(0.5);
1414      let velocity = world.velocity(ball).expect("ball exists");
1415      assert!((velocity.y + 4.905).abs() < 1e-3, "got {velocity:?}");
1416   }
1417
1418   #[test]
1419   fn a_static_body_never_moves() {
1420      let mut world = World::new();
1421      let slab = world.add_body(BodyDesc::stationary(Shape::cuboid(Vec3::splat(5.0))));
1422      for _ in 0..120 {
1423         world.step(1.0 / 60.0);
1424      }
1425      assert_eq!(world.position(slab), Some(Vec3::ZERO));
1426      assert_eq!(world.velocity(slab), Some(Vec3::ZERO));
1427   }
1428
1429   #[test]
1430   fn a_kinematic_body_ignores_gravity_and_holds_its_velocity() {
1431      let mut world = World::new();
1432      let platform = world.add_body(BodyDesc::kinematic(Shape::cuboid(Vec3::splat(0.5))));
1433      assert!(world.set_velocity(platform, Vec3::new(2.0, 0.0, 0.0)));
1434      for _ in 0..120 {
1435         world.step(1.0 / 60.0);
1436      }
1437      let position = world.position(platform).expect("platform exists");
1438      assert!((position.x - 4.0).abs() < 1e-3, "got {position:?}");
1439      assert!(position.y.abs() < 1e-6, "got {position:?}");
1440   }
1441
1442   #[test]
1443   fn a_kinematic_body_ignores_forces_and_keeps_its_speed() {
1444      let mut world = World::new();
1445      let platform = world.add_body(BodyDesc::kinematic(Shape::cuboid(Vec3::splat(0.5))));
1446      world.set_velocity(platform, Vec3::new(1.0, 0.0, 0.0));
1447      assert!(!world.apply_force(platform, Vec3::new(1000.0, 0.0, 0.0)));
1448      world.step(0.1);
1449      let velocity = world.velocity(platform).expect("platform exists");
1450      assert!((velocity.x - 1.0).abs() < 1e-6, "got {velocity:?}");
1451   }
1452
1453   #[test]
1454   fn damping_decays_velocity_by_the_discrete_factor_each_step() {
1455      let mut world = World::new();
1456      world.set_gravity(Vec3::ZERO);
1457      let dt = 1.0 / 60.0;
1458      let damping = 2.0;
1459      let body = world.add_body(
1460         BodyDesc::dynamic(Shape::sphere(0.5), 1.0)
1461            .with_velocity(Vec3::new(10.0, 0.0, 0.0))
1462            .with_damping(damping, 0.0),
1463      );
1464      let factor = 1.0 / (1.0 + damping * dt);
1465      let mut expected = 10.0;
1466      for _ in 0..120 {
1467         world.step(dt);
1468         expected *= factor;
1469         let speed = world.velocity(body).expect("body exists").x;
1470         assert!(speed > 0.0, "damping must not reverse the direction");
1471         assert!(
1472            (speed - expected).abs() < 1e-3,
1473            "got {speed}, expected {expected}"
1474         );
1475      }
1476   }
1477
1478   #[test]
1479   fn a_zero_mass_dynamic_body_ignores_gravity() {
1480      let mut world = World::new();
1481      let ghost = world.add_body(BodyDesc::dynamic(Shape::sphere(0.5), 0.0).at(Vec3::Y));
1482      world.step(1.0);
1483      assert_eq!(world.position(ghost), Some(Vec3::Y));
1484   }
1485
1486   #[test]
1487   fn removing_a_body_invalidates_its_handle_without_disturbing_others() {
1488      let mut world = World::new();
1489      let first = world.add_body(BodyDesc::dynamic(Shape::sphere(0.5), 1.0));
1490      let second = world.add_body(BodyDesc::dynamic(Shape::sphere(0.5), 1.0));
1491      assert!(world.remove_body(first));
1492      assert!(!world.contains(first));
1493      assert!(world.contains(second));
1494      assert!(!world.remove_body(first), "double removal must fail");
1495
1496      let third = world.add_body(BodyDesc::dynamic(Shape::sphere(0.5), 1.0));
1497      assert_eq!(third.index(), first.index());
1498      assert_ne!(third.generation(), first.generation());
1499      assert!(!world.contains(first));
1500   }
1501
1502   #[test]
1503   fn a_degenerate_step_is_ignored() {
1504      let (mut world, ball) = free_fall_world();
1505      let before = world.position(ball).expect("ball exists");
1506      world.step(0.0);
1507      world.step(-1.0);
1508      world.step(f32::NAN);
1509      world.step(f32::INFINITY);
1510      assert_eq!(world.position(ball), Some(before));
1511   }
1512
1513   #[test]
1514   fn a_force_acts_for_exactly_one_step() {
1515      let mut world = World::new();
1516      world.set_gravity(Vec3::ZERO);
1517      let body = world.add_body(BodyDesc::dynamic(Shape::sphere(0.5), 1.0));
1518      assert!(world.apply_force(body, Vec3::new(0.0, 100.0, 0.0)));
1519      world.step(0.1);
1520      let after_push = world.velocity(body).expect("body exists").y;
1521      assert!((after_push - 10.0).abs() < 1e-3, "got {after_push}");
1522      world.step(0.1);
1523      let after_idle = world.velocity(body).expect("body exists").y;
1524      assert!(
1525         (after_idle - after_push).abs() < 1e-3,
1526         "the force must not persist: {after_idle}"
1527      );
1528   }
1529
1530   #[test]
1531   fn a_spinning_body_keeps_a_unit_quaternion() {
1532      let mut world = World::new();
1533      world.set_gravity(Vec3::ZERO);
1534      let body = world.add_body(
1535         BodyDesc::dynamic(Shape::cuboid(Vec3::splat(0.5)), 1.0)
1536            .with_angular_velocity(Vec3::new(3.0, 1.0, 2.0)),
1537      );
1538      for _ in 0..2000 {
1539         world.step(1.0 / 120.0);
1540         let orientation = world.orientation(body).expect("body exists");
1541         assert!(
1542            (orientation.length() - 1.0).abs() < 1e-4,
1543            "quaternion drifted: {}",
1544            orientation.length()
1545         );
1546      }
1547   }
1548
1549   #[test]
1550   fn ids_reports_every_live_body() {
1551      let mut world = World::new();
1552      let a = world.add_body(BodyDesc::dynamic(Shape::sphere(0.5), 1.0));
1553      let b = world.add_body(BodyDesc::dynamic(Shape::sphere(0.5), 1.0));
1554      let mut ids: alloc::vec::Vec<_> = world.ids().collect();
1555      ids.sort_unstable();
1556      assert_eq!(ids, alloc::vec![a.min(b), a.max(b)]);
1557      assert_eq!(world.len(), 2);
1558      assert!(!world.is_empty());
1559      world.remove_body(a);
1560      assert_eq!(world.len(), 1);
1561   }
1562
1563   // ---------------------------------------------------------------- collision
1564
1565   #[test]
1566   fn a_ball_lands_on_the_ground_and_stops() {
1567      let mut world = World::new();
1568      let slab = ground(&mut world, 0.0);
1569      let ball = world.add_body(
1570         BodyDesc::dynamic(Shape::sphere(0.5), 1.0)
1571            .at(Vec3::new(0.0, 3.0, 0.0))
1572            .with_friction(0.6),
1573      );
1574      settle(&mut world, 3.0);
1575
1576      let position = world.position(ball).expect("ball exists");
1577      assert!(
1578         (position.y - 0.5).abs() < 0.05,
1579         "the ball should rest on the slab, got {position:?}"
1580      );
1581      let speed = world.velocity(ball).expect("ball exists").length();
1582      assert!(speed < 0.1, "the ball should be at rest, got {speed}");
1583      assert_eq!(world.position(slab), Some(Vec3::new(0.0, -0.5, 0.0)));
1584   }
1585
1586   #[test]
1587   fn a_ball_does_not_fall_through_the_ground() {
1588      let mut world = World::new();
1589      ground(&mut world, 0.0);
1590      let ball = world.add_body(
1591         BodyDesc::dynamic(Shape::sphere(0.5), 1.0)
1592            .at(Vec3::new(0.0, 3.0, 0.0))
1593            .with_velocity(Vec3::new(0.0, -20.0, 0.0)),
1594      );
1595      settle(&mut world, 2.0);
1596      let y = world.position(ball).expect("ball exists").y;
1597      assert!(y > -0.5, "the ball tunnelled through, got y={y}");
1598   }
1599
1600   #[test]
1601   fn a_single_box_lands_flat_and_stops() {
1602      let mut world = World::new();
1603      ground(&mut world, 0.0);
1604      let cube = world.add_body(
1605         BodyDesc::dynamic(Shape::cuboid(Vec3::splat(0.5)), 1.0).at(Vec3::new(0.0, 2.0, 0.0)),
1606      );
1607      settle(&mut world, 3.0);
1608      let position = world.position(cube).expect("cube exists");
1609      assert!(
1610         (position.y - 0.5).abs() < 0.05,
1611         "cube should rest at 0.5, got {position:?}"
1612      );
1613      assert!(
1614         world.velocity(cube).expect("cube exists").length() < 0.1,
1615         "cube should be at rest"
1616      );
1617   }
1618
1619   #[test]
1620   fn a_stack_of_boxes_settles_without_sinking() {
1621      let mut world = World::new();
1622      ground(&mut world, 0.0);
1623      let mut cubes = alloc::vec::Vec::new();
1624      for level in 0..5 {
1625         let y = 0.5 + level as f32 * 1.01;
1626         cubes.push(
1627            world.add_body(
1628               BodyDesc::dynamic(Shape::cuboid(Vec3::splat(0.5)), 1.0)
1629                  .at(Vec3::new(0.0, y, 0.0))
1630                  .with_friction(0.9),
1631            ),
1632         );
1633      }
1634      settle(&mut world, 6.0);
1635
1636      for (level, cube) in cubes.iter().enumerate() {
1637         let y = world.position(*cube).expect("cube exists").y;
1638         let expected = 0.5 + level as f32;
1639         assert!(
1640            (y - expected).abs() < 0.15,
1641            "level {level} should rest near {expected}, got {y}"
1642         );
1643      }
1644   }
1645
1646   #[test]
1647   fn a_resting_stack_does_not_jitter() {
1648      let mut world = World::new();
1649      ground(&mut world, 0.0);
1650      let mut cubes = alloc::vec::Vec::new();
1651      for level in 0..4 {
1652         cubes.push(
1653            world.add_body(
1654               BodyDesc::dynamic(Shape::cuboid(Vec3::splat(0.5)), 1.0)
1655                  .at(Vec3::new(0.0, 0.5 + level as f32 * 1.01, 0.0))
1656                  .with_friction(0.9),
1657            ),
1658         );
1659      }
1660      settle(&mut world, 4.0);
1661      let before: alloc::vec::Vec<_> = cubes
1662         .iter()
1663         .map(|cube| world.position(*cube).expect("cube exists"))
1664         .collect();
1665      settle(&mut world, 2.0);
1666      for (index, cube) in cubes.iter().enumerate() {
1667         let after = world.position(*cube).expect("cube exists");
1668         assert!(
1669            (after - before[index]).length() < 0.02,
1670            "level {index} drifted from {:?} to {after:?}",
1671            before[index]
1672         );
1673      }
1674   }
1675
1676   #[test]
1677   fn a_resting_stack_falls_asleep() {
1678      let mut world = World::new();
1679      ground(&mut world, 0.0);
1680      let mut cubes = alloc::vec::Vec::new();
1681      for level in 0..3 {
1682         cubes.push(
1683            world.add_body(
1684               BodyDesc::dynamic(Shape::cuboid(Vec3::splat(0.5)), 1.0)
1685                  .at(Vec3::new(0.0, 0.5 + level as f32 * 1.01, 0.0))
1686                  .with_friction(0.9),
1687            ),
1688         );
1689      }
1690      settle(&mut world, SLEEP_TIME + 6.0);
1691      for cube in &cubes {
1692         assert_eq!(
1693            world.is_sleeping(*cube),
1694            Some(true),
1695            "a settled stack should sleep"
1696         );
1697      }
1698   }
1699
1700   #[test]
1701   fn a_sleeping_stack_wakes_when_a_neighbour_arrives() {
1702      let mut world = World::new();
1703      ground(&mut world, 0.0);
1704      let cube = world.add_body(
1705         BodyDesc::dynamic(Shape::cuboid(Vec3::splat(0.5)), 1.0)
1706            .at(Vec3::new(0.0, 0.5, 0.0))
1707            .with_friction(0.9),
1708      );
1709      settle(&mut world, SLEEP_TIME + 4.0);
1710      assert_eq!(world.is_sleeping(cube), Some(true));
1711
1712      let projectile = world.add_body(
1713         BodyDesc::dynamic(Shape::sphere(0.4), 1.0)
1714            .at(Vec3::new(-3.0, 0.5, 0.0))
1715            .with_velocity(Vec3::new(12.0, 0.0, 0.0)),
1716      );
1717      settle(&mut world, 0.5);
1718      assert_eq!(
1719         world.is_sleeping(cube),
1720         Some(false),
1721         "the impact should have woken the settled cube"
1722      );
1723
1724      // And the impact should have actually moved it.
1725      let moved = world.position(cube).expect("cube exists").x;
1726      assert!(moved.abs() > 1e-3, "the cube should have been pushed");
1727      let _ = projectile;
1728   }
1729
1730   #[test]
1731   fn contacts_report_begin_then_persist_then_end() {
1732      let mut world = World::new();
1733      let slab = ground(&mut world, 0.0);
1734      world.add_body(BodyDesc::dynamic(Shape::sphere(0.5), 1.0).at(Vec3::new(0.0, 1.2, 0.0)));
1735
1736      // Fall until it touches: the first contact must be a Begin.
1737      let mut saw_begin = false;
1738      for _ in 0..240 {
1739         world.step(1.0 / 120.0);
1740         if world
1741            .contacts()
1742            .iter()
1743            .any(|contact| contact.phase == ContactPhase::Begin)
1744         {
1745            saw_begin = true;
1746            break;
1747         }
1748      }
1749      assert!(saw_begin, "landing must report a Begin phase");
1750
1751      // Keep resting: the same contact must now be a Persist, not a new Begin.
1752      settle(&mut world, 0.5);
1753      assert!(
1754         world
1755            .contacts()
1756            .iter()
1757            .any(|contact| contact.phase == ContactPhase::Persist),
1758         "a resting contact must report Persist"
1759      );
1760
1761      // Tear the ground away and the contact must end.
1762      assert!(world.remove_body(slab));
1763      world.step(1.0 / 120.0);
1764      assert!(
1765         world
1766            .contacts()
1767            .iter()
1768            .any(|contact| contact.phase == ContactPhase::End),
1769         "removing the ground must report an End"
1770      );
1771   }
1772
1773   #[test]
1774   fn contacts_are_not_reported_for_bodies_that_are_apart() {
1775      let mut world = World::new();
1776      world.add_body(BodyDesc::dynamic(Shape::sphere(0.5), 1.0).at(Vec3::new(0.0, 10.0, 0.0)));
1777      world.add_body(BodyDesc::dynamic(Shape::sphere(0.5), 1.0).at(Vec3::new(50.0, 10.0, 0.0)));
1778      world.step(1.0 / 120.0);
1779      assert_eq!(world.contacts(), []);
1780   }
1781
1782   #[test]
1783   fn two_static_bodies_are_never_solved_against_each_other() {
1784      let mut world = World::new();
1785      world.add_body(BodyDesc::stationary(Shape::cuboid(Vec3::splat(1.0))));
1786      world.add_body(
1787         BodyDesc::stationary(Shape::cuboid(Vec3::splat(1.0))).at(Vec3::new(0.5, 0.0, 0.0)),
1788      );
1789      world.step(1.0 / 120.0);
1790      assert_eq!(world.contacts(), []);
1791   }
1792
1793   #[test]
1794   fn a_settled_tower_sleeps_and_then_does_not_move_at_all() {
1795      let mut world = World::new();
1796      ground(&mut world, 0.0);
1797      let mut cubes = alloc::vec::Vec::new();
1798      for level in 0..4 {
1799         cubes.push(
1800            world.add_body(
1801               BodyDesc::dynamic(Shape::cuboid(Vec3::splat(0.5)), 1.0)
1802                  .at(Vec3::new(0.0, 0.5 + level as f32 * 1.02, 0.0))
1803                  .with_friction(1.0),
1804            ),
1805         );
1806      }
1807      settle(&mut world, 8.0);
1808      for (index, cube) in cubes.iter().enumerate() {
1809         assert_eq!(
1810            world.is_sleeping(*cube),
1811            Some(true),
1812            "level {index} of a settled tower should sleep"
1813         );
1814         let y = world.position(*cube).expect("cube exists").y;
1815         assert!(
1816            (y - (0.5 + index as f32)).abs() < 0.1,
1817            "level {index} sits at {y}"
1818         );
1819      }
1820
1821      // Once asleep nothing may move at all.
1822      // 入睡之后不应有任何移动。
1823      let resting: alloc::vec::Vec<_> = cubes
1824         .iter()
1825         .map(|cube| world.position(*cube).expect("cube exists"))
1826         .collect();
1827      settle(&mut world, 10.0);
1828      for (index, cube) in cubes.iter().enumerate() {
1829         let after = world.position(*cube).expect("cube exists");
1830         assert!(
1831            (after - resting[index]).length() < 1e-5,
1832            "level {index} moved from {:?} to {after:?} after sleeping",
1833            resting[index]
1834         );
1835      }
1836   }
1837
1838   #[test]
1839   fn a_taller_tower_holds_its_height_without_sinking() {
1840      // Six boxes is the height this solver holds at the default iteration count.
1841      // Ten needs about twenty iterations and twenty is still unstable; see the
1842      // tall-stack limitation in `docs/physics-spec.md`.
1843      // 六层是本求解器在默认迭代次数下能撑住的高度。十层约需二十次迭代,二十层仍不稳定;
1844      // 见 `docs/physics-spec.md` 中的高塔限制。
1845      let mut world = World::new();
1846      ground(&mut world, 0.0);
1847      let mut cubes = alloc::vec::Vec::new();
1848      for level in 0..6 {
1849         cubes.push(
1850            world.add_body(
1851               BodyDesc::dynamic(Shape::cuboid(Vec3::splat(0.5)), 1.0)
1852                  .at(Vec3::new(0.0, 0.5 + level as f32 * 1.02, 0.0))
1853                  .with_friction(1.0),
1854            ),
1855         );
1856      }
1857      settle(&mut world, 12.0);
1858      for (index, cube) in cubes.iter().enumerate() {
1859         let y = world.position(*cube).expect("cube exists").y;
1860         let expected = 0.5 + index as f32;
1861         assert!(
1862            (y - expected).abs() < 0.1,
1863            "level {index} should rest near {expected}, got {y}"
1864         );
1865      }
1866   }
1867
1868   // ------------------------------------------------------- static geometry
1869
1870   /// A flat grid of triangles at `y = 0`, split the same way terrain tiles are:
1871   /// each cell becomes two triangles sharing a diagonal.
1872   /// `y = 0` 处的平坦三角形网格,按与地形瓦片相同的方式切分:每个格子分成两个共享一条
1873   /// 对角线的三角形。
1874   fn flat_terrain_cells(start_x: usize, cells: usize, step: f32) -> alloc::vec::Vec<Vec3> {
1875      let mut positions = alloc::vec::Vec::new();
1876      for z in 0..cells {
1877         for x in start_x..(start_x + cells) {
1878            let x0 = x as f32 * step;
1879            let x1 = x0 + step;
1880            let z0 = z as f32 * step;
1881            let z1 = z0 + step;
1882            positions.extend([
1883               Vec3::new(x0, 0.0, z0),
1884               Vec3::new(x0, 0.0, z1),
1885               Vec3::new(x1, 0.0, z0),
1886               Vec3::new(x1, 0.0, z0),
1887               Vec3::new(x0, 0.0, z1),
1888               Vec3::new(x1, 0.0, z1),
1889            ]);
1890         }
1891      }
1892      positions
1893   }
1894
1895   fn flat_terrain(cells: usize, step: f32) -> alloc::vec::Vec<Vec3> {
1896      flat_terrain_cells(0, cells, step)
1897   }
1898
1899   #[test]
1900   fn a_body_rests_on_static_terrain() {
1901      let mut world = World::new();
1902      world
1903         .add_static_mesh(&flat_terrain(2, 2.0))
1904         .expect("terrain");
1905      let ball =
1906         world.add_body(BodyDesc::dynamic(Shape::sphere(0.5), 1.0).at(Vec3::new(1.0, 3.0, 1.0)));
1907      settle(&mut world, 3.0);
1908      let y = world.position(ball).expect("ball exists").y;
1909      assert!(
1910         (y - 0.5).abs() < 0.1,
1911         "the ball should rest on the terrain, got y={y}"
1912      );
1913   }
1914
1915   #[test]
1916   fn a_contact_with_static_terrain_names_the_mesh() {
1917      let mut world = World::new();
1918      let mesh = world
1919         .add_static_mesh(&flat_terrain(1, 2.0))
1920         .expect("terrain");
1921      world.add_body(BodyDesc::dynamic(Shape::sphere(0.5), 1.0).at(Vec3::new(1.0, 0.4, 1.0)));
1922      settle(&mut world, 0.2);
1923      assert!(
1924         world
1925            .contacts()
1926            .iter()
1927            .any(|contact| contact.b == ContactPartner::Static(mesh)),
1928         "the terrain contact must name the mesh"
1929      );
1930   }
1931
1932   #[test]
1933   fn a_box_slides_across_triangle_seams_without_catching() {
1934      let mut world = World::new();
1935      world
1936         .add_static_mesh(&flat_terrain(4, 2.0))
1937         .expect("terrain");
1938      let cube = world.add_body(
1939         BodyDesc::dynamic(Shape::cuboid(Vec3::splat(0.5)), 1.0)
1940            .at(Vec3::new(0.5, 0.55, 0.5))
1941            .with_friction(0.0)
1942            .with_velocity(Vec3::new(4.0, 0.0, 0.0)),
1943      );
1944      settle(&mut world, 1.0);
1945      let position = world.position(cube).expect("cube exists");
1946      // Frictionless travel would be four units. Without internal-edge filtering
1947      // the diagonals would trip the box and eat most of that.
1948      // 无摩擦滑行应前进四个单位。若没有内部边过滤,对角线会绊住方块并吃掉大部分位移。
1949      assert!(
1950         position.x > 3.5,
1951         "the box caught on a triangle seam, ended at {position:?}"
1952      );
1953      assert!(
1954         (position.y - 0.5).abs() < 0.1,
1955         "the box sank or floated, ended at {position:?}"
1956      );
1957   }
1958
1959   #[test]
1960   fn a_box_slides_across_a_seam_between_two_separate_meshes() {
1961      let mut world = World::new();
1962      // Two tiles that meet exactly, the way streamed terrain does. Neither tile
1963      // contains the shared boundary edge, so only the shared topology can tell
1964      // that it is an Internal edge.
1965      // 两块精确相接的瓦片,正如流式地形。共享的边界边不在任何单块瓦片内部,因此只有共享
1966      // 拓扑才能判断它是 Internal edge。
1967      world
1968         .add_static_mesh(&flat_terrain_cells(0, 2, 2.0))
1969         .expect("left tile");
1970      world
1971         .add_static_mesh(&flat_terrain_cells(2, 2, 2.0))
1972         .expect("right tile");
1973      let cube = world.add_body(
1974         BodyDesc::dynamic(Shape::cuboid(Vec3::splat(0.5)), 1.0)
1975            .at(Vec3::new(0.5, 0.55, 0.5))
1976            .with_friction(0.0)
1977            .with_velocity(Vec3::new(4.0, 0.0, 0.0)),
1978      );
1979      settle(&mut world, 1.5);
1980      let position = world.position(cube).expect("cube exists");
1981      assert!(
1982         position.x > 5.0,
1983         "the box caught on the seam between two meshes, ended at {position:?}"
1984      );
1985      assert!(
1986         (position.y - 0.5).abs() < 0.1,
1987         "the box sank or floated, ended at {position:?}"
1988      );
1989   }
1990
1991   #[test]
1992   fn removing_static_geometry_lets_bodies_fall_through() {
1993      let mut world = World::new();
1994      let mesh = world
1995         .add_static_mesh(&flat_terrain(2, 2.0))
1996         .expect("terrain");
1997      let ball =
1998         world.add_body(BodyDesc::dynamic(Shape::sphere(0.5), 1.0).at(Vec3::new(1.0, 0.5, 1.0)));
1999      settle(&mut world, 1.0);
2000      assert!((world.position(ball).expect("ball").y - 0.5).abs() < 0.1);
2001
2002      assert!(world.remove_static_mesh(mesh));
2003      assert!(!world.contains_static_mesh(mesh));
2004      assert_eq!(world.static_mesh_count(), 0);
2005      assert!(!world.remove_static_mesh(mesh), "double removal must fail");
2006      settle(&mut world, 1.0);
2007      assert!(
2008         world.position(ball).expect("ball").y < -1.0,
2009         "the ball should fall once its terrain is gone"
2010      );
2011   }
2012
2013   #[test]
2014   fn removing_one_mesh_releases_only_its_own_edges() {
2015      let mut world = World::new();
2016      world
2017         .add_static_mesh(&flat_terrain_cells(0, 2, 2.0))
2018         .expect("left tile");
2019      let right = world
2020         .add_static_mesh(&flat_terrain_cells(1, 2, 2.0))
2021         .expect("right tile");
2022      // The two tiles overlap along x = 2, so that boundary is internal while both
2023      // exist: a Body sliding over it must not catch.
2024      // 两块瓦片在 x = 2 处重叠,因此只要两者都存在,该边界就是内部边:滑过它的 Body 不会
2025      // 被绊住。
2026      let cube = world.add_body(
2027         BodyDesc::dynamic(Shape::cuboid(Vec3::splat(0.5)), 1.0)
2028            .at(Vec3::new(0.5, 0.55, 0.5))
2029            .with_friction(0.0)
2030            .with_velocity(Vec3::new(4.0, 0.0, 0.0)),
2031      );
2032      settle(&mut world, 0.6);
2033      assert!(
2034         world.position(cube).expect("cube").x > 2.0,
2035         "the box should have crossed the shared boundary"
2036      );
2037
2038      // Dropping the second tile must not corrupt the first one's bookkeeping.
2039      // 移除第二块瓦片不应破坏第一块的簿记。
2040      assert!(world.remove_static_mesh(right));
2041      settle(&mut world, 0.6);
2042      assert!(world.position(cube).expect("cube").is_finite());
2043   }
2044
2045   #[test]
2046   fn malformed_static_geometry_is_rejected() {
2047      let mut world = World::new();
2048      assert!(world.add_static_mesh(&[]).is_none(), "empty input");
2049      assert!(
2050         world.add_static_mesh(&[Vec3::ZERO, Vec3::X]).is_none(),
2051         "not a whole number of triangles"
2052      );
2053      assert!(
2054         world
2055            .add_static_mesh(&[Vec3::ZERO, Vec3::X, Vec3::new(f32::NAN, 0.0, 0.0)])
2056            .is_none(),
2057         "a non-finite coordinate"
2058      );
2059      assert_eq!(world.static_mesh_count(), 0);
2060   }
2061
2062   // --------------------------------------------------------------- ledges
2063
2064   /// Settle a block on a ledge with the given fraction of its base hanging off,
2065   /// returning its final position, its tilt in degrees, and whether it slept.
2066   /// 让方块以给定比例的底面悬空搁在台沿上,返回其最终位置、倾斜角度(度),以及是否入睡。
2067   fn ledge_block(overhang: f32, platform_half: Vec3, block_half: f32) -> (Vec3, f32, bool) {
2068      let mut world = World::new();
2069      world.add_body(BodyDesc::stationary(Shape::cuboid(platform_half)).at(Vec3::ZERO));
2070      // The base spans `[c - half, c + half]` and the ledge edge is at
2071      // `platform_half.x`, so the fraction hanging off solves to this centre.
2072      // 底面覆盖 `[c - half, c + half]`,台沿位于 `platform_half.x`,因此悬空比例对应的
2073      // 中心位置如下。
2074      let centre_x = platform_half.x - block_half + overhang * block_half * 2.0;
2075      let block = world.add_body(
2076         BodyDesc::dynamic(Shape::cuboid(Vec3::splat(block_half)), 1.0)
2077            .at(Vec3::new(
2078               centre_x,
2079               platform_half.y + block_half + 0.02,
2080               0.0,
2081            ))
2082            .with_friction(0.6),
2083      );
2084      for _ in 0..600 {
2085         world.step(1.0 / 120.0);
2086      }
2087      let position = world.position(block).expect("block exists");
2088      let up = world.orientation(block).expect("block exists") * Vec3::Y;
2089      let tilt = up.dot(Vec3::Y).clamp(-1.0, 1.0).acos().to_degrees();
2090      (position, tilt, world.is_sleeping(block) == Some(true))
2091   }
2092
2093   #[test]
2094   fn a_block_tips_off_once_its_centre_passes_the_edge() {
2095      // A block on a ledge is supported while its centre of mass is over the ledge
2096      // and tips off once it is not. The threshold is exactly the platform edge,
2097      // and the block's centre sitting on that edge is half of its base, so this
2098      // pins both sides of it.
2099      // 搁在台沿上的方块,质心还在台上时受支撑,一旦越过台沿就倾覆。阈值恰好是台沿,而质心
2100      // 落在台沿上正是底面的一半,因此这个测试把两侧都钉住。
2101      let platform_half = Vec3::new(3.0, 0.5, 3.0);
2102      let block_half = 0.5;
2103      let resting_height = platform_half.y + block_half;
2104
2105      let mut worst_tilt = 0.0_f32;
2106      for overhang in [0.1_f32, 0.2, 0.3, 0.4] {
2107         let (position, tilt, sleeping) = ledge_block(overhang, platform_half, block_half);
2108         assert!(
2109            (position.y - resting_height).abs() < 0.02,
2110            "{overhang} overhang should rest on the ledge, got y={}",
2111            position.y
2112         );
2113         assert!(sleeping, "{overhang} overhang should settle and sleep");
2114         worst_tilt = worst_tilt.max(tilt);
2115      }
2116      // Resting on a lip must not mean propped up at an angle by an asymmetric
2117      // contact.
2118      // 搁在台沿上不应意味着被不对称接触顶成斜的。
2119      assert!(
2120         worst_tilt < 0.01,
2121         "a block resting on a ledge must stay level, it tilted {worst_tilt} deg"
2122      );
2123
2124      for overhang in [0.6_f32, 0.7, 0.8, 0.9] {
2125         let (position, _, _) = ledge_block(overhang, platform_half, block_half);
2126         assert!(
2127            position.y < 0.0,
2128            "{overhang} overhang is past the centre of mass and must fall, got y={}",
2129            position.y
2130         );
2131      }
2132   }
2133
2134   /// Tip a resting cube onto the next face with `impulse` about z, and report how
2135   /// many steps it spent pivoting on an edge and how fast it twisted about x while
2136   /// it did.
2137   ///
2138   /// The pivot's contact points sit on the edge itself, so the correct motion is a
2139   /// pure rotation about z and the contact points do not slide along it. Anything
2140   /// the cube picks up about x is the solver's, not the physics'.
2141   /// 用绕 z 的 `impulse` 把一个静止的方块掀到下一个面上,并报告它花了几步在棱上翻转、以及
2142   /// 翻转期间绕 x 转得多快。翻转的接触点就落在那条棱上,因此正确的运动是纯绕 z 旋转、接触点
2143   /// 不沿棱滑动。方块绕 x 拿到的任何东西都来自求解器,而不是物理。
2144   fn edge_pivot_twist(impulse: f32) -> (u32, f32) {
2145      let mut world = World::new();
2146      ground(&mut world, 0.0);
2147      let cube = world.add_body(
2148         BodyDesc::dynamic(Shape::cuboid(Vec3::splat(0.5)), 1.0)
2149            .at(Vec3::new(0.0, 0.5, 0.0))
2150            .with_friction(1.0),
2151      );
2152      settle(&mut world, 2.0);
2153      world.apply_angular_impulse(cube, Vec3::new(0.0, 0.0, impulse));
2154      let mut edge_steps = 0;
2155      let mut worst_twist = 0.0_f32;
2156      for _ in 0..2400 {
2157         world.step(1.0 / 120.0);
2158         let touching = world
2159            .contacts()
2160            .iter()
2161            .filter(|contact| contact.involves(cube))
2162            .count();
2163         if touching == 2 {
2164            edge_steps += 1;
2165            let spin = world.angular_velocity(cube).expect("the cube exists");
2166            worst_twist = worst_twist.max(spin.x.abs());
2167         }
2168      }
2169      (edge_steps, worst_twist)
2170   }
2171
2172   #[test]
2173   fn a_cube_pivoting_on_its_edge_does_not_twist_about_the_edge() {
2174      // Two contact points apart along an axis share a couple about it: solving them
2175      // one after the other leaves them unequal, and their difference is a torque
2176      // nothing applied. A cube rolled onto its edge is exactly this case, and the
2177      // twist it picks up is rotation about the axis it is rolling over.
2178      // 沿某条轴分开的两个接触点共享一个绕该轴的力偶:逐个求解会让它们不等,而它们的差就是一
2179      // 个无人施加的力矩。滚到棱上的方块正是这种情形,它拿到的扭转就是绕滚动轴的旋转。
2180      //
2181      // The two points are solved as one block, so the couple is gone at the default
2182      // iteration count; measured, 4.2e-7 rad/s against 2.0e-4 rad/s without it, and
2183      // better than raising the count to 24, which costs 65% more on a contact-dense
2184      // step. Four-point manifolds cannot be blocked — theirs is singular — so what
2185      // is left after this is the residual of a face landing, which is a separate
2186      // and already documented limit.
2187      // 两点作为一个块求解,因此默认迭代次数下该力偶就不存在了;实测 4.2e-7 rad/s,而没有它时
2188      // 是 2.0e-4 rad/s,并且优于把迭代次数提到 24——后者在接触密集的一步上要多花 65%。四点
2189      // 流形无法成块——它的块是奇异的——因此这里剩下的是面着地时的残差,那是一个独立且已有
2190      // 记录的局限。
2191      let (edge_steps, twist) = edge_pivot_twist(0.6);
2192      assert!(
2193         edge_steps > 10,
2194         "the cube must actually pivot on an edge, it did for {edge_steps} steps"
2195      );
2196      assert!(
2197         twist < 1e-5,
2198         "the cube twisted {twist} rad/s about the edge it was pivoting on"
2199      );
2200   }
2201
2202   #[test]
2203   fn a_block_sliding_towards_a_ledge_leaves_it_instead_of_hanging() {
2204      // The realistic version of the same question: a block that slides off a
2205      // ledge has to actually leave it once its centre goes past the edge, rather
2206      // than being held up by a contact on the lip.
2207      // 同一个问题的现实版本:滑向台沿的方块,质心越过台沿后必须真的离开,而不是被台沿上的
2208      // 接触顶住悬挂。
2209      //
2210      // Both surfaces are nearly frictionless, so the block still carries real
2211      // speed by the time it reaches the lip. With ordinary friction it coasts to a
2212      // halt just short of the edge, and the test would be measuring friction
2213      // rather than the ledge.
2214      // 两个表面都近乎无摩擦,因此方块抵达台沿时仍有可观速度。用普通摩擦系数它会在离边缘
2215      // 一步之遥处停下,那时测的就是摩擦而不是台沿了。
2216      let mut world = World::new();
2217      world.add_body(
2218         BodyDesc::stationary(Shape::cuboid(Vec3::new(3.0, 0.5, 3.0)))
2219            .at(Vec3::ZERO)
2220            .with_friction(0.02),
2221      );
2222      let block = world.add_body(
2223         BodyDesc::dynamic(Shape::cuboid(Vec3::splat(0.5)), 1.0)
2224            .at(Vec3::new(0.0, 1.02, 0.0))
2225            .with_friction(0.02)
2226            .with_velocity(Vec3::new(3.0, 0.0, 0.0)),
2227      );
2228
2229      let mut left_the_ledge = false;
2230      for _ in 0..600 {
2231         world.step(1.0 / 120.0);
2232         let position = world.position(block).expect("block exists");
2233         if position.x > 3.0 && position.y < 0.0 {
2234            left_the_ledge = true;
2235            break;
2236         }
2237      }
2238      assert!(
2239         left_the_ledge,
2240         "the block should have slid off the ledge, ended at {:?}",
2241         world.position(block).expect("block exists")
2242      );
2243   }
2244
2245   /// Settle a plank across a gap between two platforms, returning its final
2246   /// height, its tilt in degrees, and whether it slept.
2247   /// 让一块板横跨两个平台之间的缝隙,返回其最终高度、倾斜角度(度),以及是否入睡。
2248   fn spanning_plank(gap: f32, plank_half: f32, platform_half: f32) -> (f32, f32, bool) {
2249      let mut world = World::new();
2250      let offset = gap / 2.0 + platform_half;
2251      for side in [-1.0_f32, 1.0] {
2252         world.add_body(
2253            BodyDesc::stationary(Shape::cuboid(Vec3::new(platform_half, 0.5, 3.0))).at(Vec3::new(
2254               side * offset,
2255               0.0,
2256               0.0,
2257            )),
2258         );
2259      }
2260      let plank = world.add_body(
2261         BodyDesc::dynamic(Shape::cuboid(Vec3::new(plank_half, 0.25, 0.5)), 1.0)
2262            .at(Vec3::new(0.0, 0.5 + 0.25 + 0.02, 0.0))
2263            .with_friction(0.8),
2264      );
2265      for _ in 0..600 {
2266         world.step(1.0 / 120.0);
2267      }
2268      let y = world.position(plank).expect("plank exists").y;
2269      let up = world.orientation(plank).expect("plank exists") * Vec3::Y;
2270      let tilt = up.dot(Vec3::Y).clamp(-1.0, 1.0).acos().to_degrees();
2271      (y, tilt, world.is_sleeping(plank) == Some(true))
2272   }
2273
2274   #[test]
2275   fn a_plank_spanning_a_gap_rests_level_until_the_gap_outgrows_it() {
2276      // Two supports at once, which is where a solver that mishandles several
2277      // simultaneous contacts shows up: the plank sags or tips instead of sharing
2278      // load. Even with only a sliver of overlap on each side it must stay level.
2279      // 同时有两个支撑,这正是处理不好多接触的求解器会露馅的地方:板会下垂或倾斜,而不是
2280      // 分担载荷。即使每侧只剩一丝重叠,它也必须保持水平。
2281      let plank_half = 0.9;
2282      let platform_half = 1.5;
2283      let resting = 0.5 + 0.25;
2284      for gap in [0.4_f32, 0.8, 1.2, 1.6, 1.7] {
2285         let (y, tilt, sleeping) = spanning_plank(gap, plank_half, platform_half);
2286         assert!(
2287            (y - resting).abs() < 0.02,
2288            "gap {gap} should rest at {resting}, got y={y}"
2289         );
2290         assert!(tilt < 0.01, "gap {gap} tilted the plank by {tilt} deg");
2291         assert!(sleeping, "gap {gap} should settle and sleep");
2292      }
2293      // At twice the half-width there is no overlap left to stand on.
2294      // 缝隙达到半宽的两倍时,已无任何可站立的重叠。
2295      let (y, _, _) = spanning_plank(1.9, plank_half, platform_half);
2296      assert!(
2297         y < 0.0,
2298         "a gap wider than the plank must let it through, got y={y}"
2299      );
2300   }
2301
2302   #[test]
2303   fn a_sphere_rests_on_a_ledge_up_to_the_edge_and_falls_past_it() {
2304      // A sphere touches a flat top at exactly the point below its centre, so the
2305      // same centre-past-the-edge rule applies — through a different narrowphase
2306      // path than a box takes.
2307      // 球与平顶的接触点恰在其球心正下方,因此"质心越过边缘"这条规则同样适用,只是走的
2308      // 窄相位路径与盒不同。
2309      let radius = 0.4;
2310      let resting = 0.5 + radius;
2311      for centre_x in [2.0_f32, 2.6, 2.99] {
2312         let mut world = World::new();
2313         world
2314            .add_body(BodyDesc::stationary(Shape::cuboid(Vec3::new(3.0, 0.5, 3.0))).at(Vec3::ZERO));
2315         let ball = world.add_body(
2316            BodyDesc::dynamic(Shape::sphere(radius), 1.0)
2317               .at(Vec3::new(centre_x, resting + 0.02, 0.0))
2318               .with_friction(0.6),
2319         );
2320         for _ in 0..600 {
2321            world.step(1.0 / 120.0);
2322         }
2323         let y = world.position(ball).expect("ball exists").y;
2324         assert!(
2325            (y - resting).abs() < 0.02,
2326            "centre at {centre_x} is over the ledge and should rest, got y={y}"
2327         );
2328         assert_eq!(world.is_sleeping(ball), Some(true));
2329      }
2330
2331      let mut world = World::new();
2332      world.add_body(BodyDesc::stationary(Shape::cuboid(Vec3::new(3.0, 0.5, 3.0))).at(Vec3::ZERO));
2333      let ball = world.add_body(
2334         BodyDesc::dynamic(Shape::sphere(radius), 1.0)
2335            .at(Vec3::new(3.01, resting + 0.02, 0.0))
2336            .with_friction(0.6),
2337      );
2338      for _ in 0..600 {
2339         world.step(1.0 / 120.0);
2340      }
2341      assert!(
2342         world.position(ball).expect("ball exists").y < 0.0,
2343         "a sphere whose centre is past the edge must fall"
2344      );
2345   }
2346
2347   #[test]
2348   fn a_sphere_rolling_across_mesh_seams_keeps_its_speed() {
2349      // A sphere's contact normal always runs from the closest point of the
2350      // triangle to its centre, so for coplanar triangles it is the plane normal
2351      // whichever feature that closest point lands on. There is therefore no
2352      // spurious normal to filter, and a seam between two separate meshes is as
2353      // smooth as the interior of one triangle — unlike a box, whose manifold can
2354      // be generated from a triangle edge axis.
2355      // 球的接触法线始终由三角形上的最近点指向球心,因此对共面三角形而言,无论最近点落在
2356      // 哪个特征上,法线都是平面法线。因此不存在需要过滤的伪法线,两块独立网格之间的接缝
2357      // 与单个三角形内部一样平滑——这与盒不同,盒的流形可能由三角形棱轴生成。
2358      let mut world = World::new();
2359      world
2360         .add_static_mesh(&flat_terrain_cells(0, 2, 2.0))
2361         .expect("left tile");
2362      world
2363         .add_static_mesh(&flat_terrain_cells(2, 2, 2.0))
2364         .expect("right tile");
2365      let ball = world.add_body(
2366         BodyDesc::dynamic(Shape::sphere(0.4), 1.0)
2367            .at(Vec3::new(0.5, 0.4005, 0.5))
2368            .with_friction(0.0)
2369            .with_velocity(Vec3::new(4.0, 0.0, 0.0)),
2370      );
2371
2372      let mut lowest = f32::MAX;
2373      for _ in 0..180 {
2374         world.step(1.0 / 120.0);
2375         lowest = lowest.min(world.position(ball).expect("ball exists").y);
2376      }
2377      let velocity = world.velocity(ball).expect("ball exists");
2378      assert!(
2379         (velocity.x - 4.0).abs() < 0.05,
2380         "the seam stole horizontal speed, vx={}",
2381         velocity.x
2382      );
2383      assert!(
2384         lowest > 0.39,
2385         "the ball dipped into the seam, lowest y={lowest}"
2386      );
2387   }
2388
2389   /// Slide a box diagonally off the corner of a platform, along `x = z`.
2390   ///
2391   /// The supported region is the intersection of the box's base with the platform,
2392   /// which near the threshold is a tiny square out at the platform's corner.
2393   /// 让方块沿 `x = z` 斜向滑出平台的角。支撑区是底面与平台的交集,在阈值附近它是平台角落
2394   /// 处的一小块方形。
2395   fn corner_slide(centre: f32) -> (Vec3, f32) {
2396      let mut world = World::new();
2397      world.add_body(BodyDesc::stationary(Shape::cuboid(Vec3::new(3.0, 0.5, 3.0))).at(Vec3::ZERO));
2398      let block = world.add_body(
2399         BodyDesc::dynamic(Shape::cuboid(Vec3::new(0.5, 0.25, 0.5)), 1.0)
2400            .at(Vec3::new(centre, 0.5 + 0.25 + 0.02, centre))
2401            .with_friction(0.7),
2402      );
2403      for _ in 0..900 {
2404         world.step(1.0 / 120.0);
2405      }
2406      let position = world.position(block).expect("block exists");
2407      let up = world.orientation(block).expect("block exists") * Vec3::Y;
2408      (
2409         position,
2410         up.dot(Vec3::Y).clamp(-1.0, 1.0).acos().to_degrees(),
2411      )
2412   }
2413
2414   #[test]
2415   fn a_box_leaves_a_platform_corner_once_its_centre_passes_it() {
2416      // The diagonal version of the ledge test. Two differences make it harder: the
2417      // supported region is a rectangle rather than a half-plane, so the tipping
2418      // direction is diagonal in the body's own frame; and just below the threshold
2419      // that region shrinks to a 0.1 x 0.1 patch at the platform's corner.
2420      //
2421      // The rule is the same one: the box stands while its centre is inside the
2422      // supported region and tips once it is not. The corner of the platform is at
2423      // x = z = 3, so the centre crossing that line is exactly the threshold.
2424      // 台沿测试的对角版本。两点让它更难:支撑区是矩形而非半平面,因此倾覆方向在刚体自身
2425      // 坐标系里是对角线;而就在阈值之下,该区域会收缩到平台角落处 0.1 x 0.1 的一小块。
2426      //
2427      // 规则相同:质心位于支撑区内时方块站立,不在时倾覆。平台的角在 x = z = 3,因此质心
2428      // 越过这条线正是阈值。
2429      let resting = 0.5 + 0.25;
2430      for centre in [1.0_f32, 2.0, 2.5, 2.9, 2.99] {
2431         let (position, tilt) = corner_slide(centre);
2432         assert!(
2433            (position.y - resting).abs() < 0.02,
2434            "centre {centre} is over the platform and should rest, got y={}",
2435            position.y
2436         );
2437         assert!(
2438            tilt < 1.0,
2439            "centre {centre} should stay level, tilted {tilt} deg"
2440         );
2441      }
2442
2443      for centre in [3.2_f32, 3.4, 4.0] {
2444         let (position, _) = corner_slide(centre);
2445         assert!(
2446            position.y < 0.0,
2447            "centre {centre} is past the platform corner and must fall, got y={}",
2448            position.y
2449         );
2450      }
2451   }
2452
2453   #[test]
2454   fn a_stack_pushed_over_a_ledge_leaves_no_body_hanging() {
2455      // A contact on the lip has to turn into a torque, not into permanent support.
2456      // The failure this guards against is a Body left hovering beside the ledge.
2457      //
2458      // Hovering means: still at platform height, but with its centre outside the
2459      // platform. A Body could in principle be cantilevered there legitimately, but
2460      // only by being balanced — and a rigid one balanced that far out has already
2461      // broken the tipping rule, so it counts as the same failure.
2462      // 台沿上的接触必须转化为力矩,而不是永久支撑。这里防的失效是某个 Body 悬在台沿旁边。
2463      //
2464      // 悬空指:仍处于平台高度,但质心已在平台之外。原则上 Body 可以被合法地悬挑在那里,
2465      // 但那要求它处于平衡——而刚体在那么外面还平衡得住,本身就已经违反了倾覆规则,因此
2466      // 归为同一种失效。
2467      for friction in [0.05_f32, 0.8] {
2468         let mut world = World::new();
2469         world.add_body(
2470            BodyDesc::stationary(Shape::cuboid(Vec3::new(4.0, 0.5, 3.0)))
2471               .at(Vec3::ZERO)
2472               .with_friction(friction),
2473         );
2474         let mut stack = alloc::vec::Vec::new();
2475         for level in 0..4 {
2476            stack.push(
2477               world.add_body(
2478                  BodyDesc::dynamic(Shape::cuboid(Vec3::splat(0.5)), 1.0)
2479                     .at(Vec3::new(2.0, 0.5 + level as f32 * 1.01, 0.0))
2480                     .with_friction(friction)
2481                     .with_velocity(Vec3::new(2.0, 0.0, 0.0)),
2482               ),
2483            );
2484         }
2485         settle(&mut world, 10.0);
2486
2487         let mut left_the_platform = 0;
2488         for (level, body) in stack.iter().enumerate() {
2489            let position = world.position(*body).expect("body exists");
2490            if position.y < 0.0 {
2491               left_the_platform += 1;
2492            }
2493            assert!(
2494               position.y < 0.0 || position.x.abs() <= 4.0,
2495               "friction {friction}: level {level} is hanging past the ledge at {position:?}"
2496            );
2497         }
2498         assert!(
2499            left_the_platform > 0,
2500            "friction {friction}: the stack never reached the ledge, so nothing was tested"
2501         );
2502      }
2503   }
2504
2505   /// The outcome of dropping a box onto the overhanging half of a dynamic slab.
2506   /// 把一个方块放到动态板悬挑的一半上之后的结果。
2507   struct SlabOutcome {
2508      slab_y: f32,
2509      slab_tilt: f32,
2510      box_y: f32,
2511      box_slept: bool,
2512   }
2513
2514   /// A dynamic slab centred at x = -0.5 rests on a static ledge that ends at
2515   /// x = 0, so it overhangs by 1.5. A box sits at `box_x` on that overhang.
2516   ///
2517   /// Both bodies have finite mass and both can rotate, so whether the assembly
2518   /// stands is decided by their **combined** centre of mass:
2519   /// `(1 * -0.5 + box_mass * box_x) / (1 + box_mass)`.
2520   /// 一块中心在 x = -0.5 的动态板搁在终止于 x = 0 的静态台沿上,因此悬挑 1.5。方块位于
2521   /// 悬挑上的 `box_x` 处。
2522   ///
2523   /// 两个刚体都有有限质量、都能转动,因此整个组合能否站立由其**联合质心**决定:
2524   /// `(1 * -0.5 + box_mass * box_x) / (1 + box_mass)`。
2525   fn loaded_slab(box_x: f32, box_mass: f32, friction: f32) -> SlabOutcome {
2526      let mut world = World::new();
2527      world.add_body(
2528         BodyDesc::stationary(Shape::cuboid(Vec3::new(2.0, 0.5, 3.0)))
2529            .at(Vec3::new(-2.0, -0.5, 0.0))
2530            .with_friction(friction),
2531      );
2532      let slab = world.add_body(
2533         BodyDesc::dynamic(Shape::cuboid(Vec3::new(2.0, 0.1, 1.0)), 1.0)
2534            .at(Vec3::new(-0.5, 0.1 + 0.02, 0.0))
2535            .with_friction(friction),
2536      );
2537      let box_body = world.add_body(
2538         BodyDesc::dynamic(Shape::cuboid(Vec3::splat(0.4)), box_mass)
2539            .at(Vec3::new(box_x, 0.2 + 0.4 + 0.04, 0.0))
2540            .with_friction(friction),
2541      );
2542      settle(&mut world, 10.0);
2543
2544      let slab_position = world.position(slab).expect("slab exists");
2545      let box_position = world.position(box_body).expect("box exists");
2546      SlabOutcome {
2547         slab_y: slab_position.y,
2548         slab_tilt: {
2549            let up = world.orientation(slab).expect("slab exists") * Vec3::Y;
2550            up.dot(Vec3::Y).clamp(-1.0, 1.0).acos().to_degrees()
2551         },
2552         box_y: box_position.y,
2553         box_slept: world.is_sleeping(box_body) == Some(true),
2554      }
2555   }
2556
2557   #[test]
2558   fn a_loaded_dynamic_slab_tips_by_its_combined_centre_of_mass() {
2559      // Dynamic against dynamic, and the hardest of the edge cases: the platform
2560      // itself has finite mass and can rotate, so the box's weight and the slab's
2561      // own weight together decide whether the assembly stands. Getting this right
2562      // needs torque to cross the contact in both directions — the box must be able
2563      // to push the slab over, and the slab must be able to carry the box.
2564      // 动态对动态,也是这些边缘情形里最难的:平台自身有有限质量、可以转动,因此方块的重量
2565      // 与板自身的重量共同决定组合能否站立。要做对这件事,力矩必须能在接触处双向传递——
2566      // 方块要能压翻板,板也要能带住方块。
2567      //
2568      // The static ledge ends at x = 0, so the assembly tips exactly when its
2569      // combined centre of mass passes it.
2570      // 静态台沿终止于 x = 0,因此当联合质心越过它时组合恰好倾覆。
2571      let combined = |box_x: f32, mass: f32| (1.0 * -0.5 + mass * box_x) / (1.0 + mass);
2572
2573      let stable = [
2574         (-0.5_f32, 1.0_f32),
2575         (0.0, 1.0),
2576         (0.4, 1.0),
2577         (0.0, 3.0),
2578         (0.1, 3.0),
2579      ];
2580      for (box_x, mass) in stable {
2581         let centre = combined(box_x, mass);
2582         assert!(centre < 0.0, "the stable case must be inside the ledge");
2583         let outcome = loaded_slab(box_x, mass, 0.6);
2584         assert!(
2585            outcome.slab_y > -0.5,
2586            "combined centre {centre} is over the ledge, so the slab should stand; got slab y={}",
2587            outcome.slab_y
2588         );
2589         assert!(
2590            outcome.box_y > 0.0,
2591            "the box rode a standing slab, so it should still be up; got y={}",
2592            outcome.box_y
2593         );
2594         assert!(
2595            outcome.slab_tilt < 2.0,
2596            "a standing slab should stay level, tilted {} deg",
2597            outcome.slab_tilt
2598         );
2599         // With the load comfortably inside the ledge, the whole assembly settles
2600         // and the box is allowed to sleep.
2601         // 当载荷稳稳位于台沿之内时,整个组合会稳定下来,方块也可以入睡。
2602         if centre < -0.2 {
2603            assert!(
2604               outcome.box_slept,
2605               "a box riding a settled slab should sleep, centre was {centre}"
2606            );
2607         }
2608      }
2609
2610      let tipping = [(0.6_f32, 1.0_f32), (1.0, 1.0), (1.4, 1.0), (0.3, 3.0)];
2611      for (box_x, mass) in tipping {
2612         let centre = combined(box_x, mass);
2613         assert!(centre > 0.0, "the tipping case must be past the ledge");
2614         let outcome = loaded_slab(box_x, mass, 0.6);
2615         assert!(
2616            outcome.slab_y < -1.0,
2617            "combined centre {centre} is past the ledge, so the slab should go over; got slab y={}",
2618            outcome.slab_y
2619         );
2620         assert!(
2621            outcome.slab_tilt > 5.0,
2622            "the slab should have rotated rather than slid off, tilted only {} deg",
2623            outcome.slab_tilt
2624         );
2625         // And it must not be held up anywhere near the ledge.
2626         assert!(
2627            outcome.box_y < 0.0,
2628            "the box should have followed the slab down, got y={}",
2629            outcome.box_y
2630         );
2631      }
2632   }
2633
2634   // -------------------------------------------------------------- gravity
2635
2636   #[test]
2637   fn gravity_scale_decides_how_much_gravity_reaches_a_body() {
2638      // A Body whose motion something else owns opts out with a scale of zero. The
2639      // default stays full gravity, and the scale multiplies rather than replaces.
2640      // 运动由别的东西拥有的 Body 用零系数退出。默认仍是完整重力,而系数是相乘而非替换。
2641      let mut world = World::new();
2642      let normal =
2643         world.add_body(BodyDesc::dynamic(Shape::sphere(0.5), 1.0).at(Vec3::new(0.0, 10.0, 0.0)));
2644      let weightless = world.add_body(
2645         BodyDesc::dynamic(Shape::sphere(0.5), 1.0)
2646            .at(Vec3::new(10.0, 10.0, 0.0))
2647            .with_gravity_scale(0.0),
2648      );
2649      let heavy = world.add_body(
2650         BodyDesc::dynamic(Shape::sphere(0.5), 1.0)
2651            .at(Vec3::new(20.0, 10.0, 0.0))
2652            .with_gravity_scale(2.0),
2653      );
2654
2655      world.step(1.0 / 120.0);
2656      let vertical = |body| world.velocity(body).expect("body exists").y;
2657      let one_step = DEFAULT_GRAVITY.y / 120.0;
2658
2659      assert!(
2660         vertical(weightless).abs() < 1e-6,
2661         "a zero scale must ignore gravity entirely, got {}",
2662         vertical(weightless)
2663      );
2664      assert!(
2665         (vertical(normal) - one_step).abs() < 1e-4,
2666         "the default must be full gravity, got {}",
2667         vertical(normal)
2668      );
2669      assert!(
2670         (vertical(heavy) - 2.0 * one_step).abs() < 1e-4,
2671         "the scale must multiply, got {}",
2672         vertical(heavy)
2673      );
2674   }
2675
2676   #[test]
2677   fn gravity_scale_can_be_changed_on_a_live_body() {
2678      // The case the setter exists for: something owns the Body's motion for a while
2679      // and then hands it back. It also has to wake the Body, because a Body asleep
2680      // under no gravity is not necessarily asleep under the new one.
2681      // 该 setter 存在的理由:某个东西在一段时间内拥有此 Body 的运动,随后交还回去。它还
2682      // 必须唤醒该 Body,因为在无重力下睡着的 Body 在新重力下未必还该睡。
2683      let mut world = World::new();
2684      let body =
2685         world.add_body(BodyDesc::dynamic(Shape::sphere(0.5), 1.0).at(Vec3::new(0.0, 10.0, 0.0)));
2686
2687      assert!(world.set_gravity_scale(body, 0.0));
2688      for _ in 0..600 {
2689         world.step(1.0 / 120.0);
2690      }
2691      let held = world.position(body).expect("body exists").y;
2692      assert!(
2693         (held - 10.0).abs() < 1e-3,
2694         "a zero scale must hover, got y={held}"
2695      );
2696      assert_eq!(world.is_sleeping(body), Some(true), "it should have slept");
2697
2698      assert!(world.set_gravity_scale(body, 1.0));
2699      assert_eq!(
2700         world.is_sleeping(body),
2701         Some(false),
2702         "changing the scale must wake the Body"
2703      );
2704      world.step(1.0 / 120.0);
2705      assert!(
2706         world.velocity(body).expect("body exists").y < 0.0,
2707         "gravity must reach it again"
2708      );
2709
2710      let dead = world.add_body(BodyDesc::dynamic(Shape::sphere(0.5), 1.0));
2711      assert!(world.remove_body(dead));
2712      assert!(
2713         !world.set_gravity_scale(dead, 1.0),
2714         "a dead handle must be rejected"
2715      );
2716   }
2717
2718   #[test]
2719   fn a_negative_or_non_finite_gravity_scale_is_clamped_to_zero() {
2720      // Anti-gravity is not a thing this engine offers, and a NaN scale would put a
2721      // NaN into every velocity it touched.
2722      // 反重力不是本引擎提供的东西,而 NaN 系数会把它碰到的每一个速度都变成 NaN。
2723      let mut world = World::new();
2724      let negative = world.add_body(
2725         BodyDesc::dynamic(Shape::sphere(0.5), 1.0)
2726            .at(Vec3::new(0.0, 10.0, 0.0))
2727            .with_gravity_scale(-1.0),
2728      );
2729      let nan = world.add_body(
2730         BodyDesc::dynamic(Shape::sphere(0.5), 1.0)
2731            .at(Vec3::new(10.0, 10.0, 0.0))
2732            .with_gravity_scale(f32::NAN),
2733      );
2734
2735      world.step(1.0 / 120.0);
2736      for body in [negative, nan] {
2737         let velocity = world.velocity(body).expect("body exists");
2738         assert!(
2739            velocity.is_finite() && velocity.y.abs() < 1e-6,
2740            "expected no gravity, got {velocity:?}"
2741         );
2742      }
2743   }
2744
2745   // ------------------------------------------------------------- islands
2746
2747   #[test]
2748   fn disturbing_one_member_wakes_the_whole_island() {
2749      let mut world = World::new();
2750      ground(&mut world, 0.0);
2751      let mut cubes = alloc::vec::Vec::new();
2752      for level in 0..4 {
2753         cubes.push(
2754            world.add_body(
2755               BodyDesc::dynamic(Shape::cuboid(Vec3::splat(0.5)), 1.0)
2756                  .at(Vec3::new(0.0, 0.5 + level as f32 * 1.01, 0.0))
2757                  .with_friction(0.9),
2758            ),
2759         );
2760      }
2761      settle(&mut world, 8.0);
2762      for cube in &cubes {
2763         assert_eq!(
2764            world.is_sleeping(*cube),
2765            Some(true),
2766            "the stack should be asleep before it is disturbed"
2767         );
2768      }
2769
2770      // Disturb the bottom box only. Sleeping per Body would leave the rest of the
2771      // stack frozen in mid-air; the Island has to come back as a unit.
2772      // 只扰动最下面的方块。若按 Body 独立睡眠,堆叠的其余部分会僵在半空;Island 必须
2773      // 整体苏醒。
2774      assert!(world.apply_impulse(cubes[0], Vec3::new(3.0, 0.0, 0.0)));
2775      world.step(1.0 / 120.0);
2776      for (index, cube) in cubes.iter().enumerate() {
2777         assert_eq!(
2778            world.is_sleeping(*cube),
2779            Some(false),
2780            "level {index} of the Island should have woken"
2781         );
2782      }
2783   }
2784
2785   #[test]
2786   fn an_island_sleeps_as_a_unit() {
2787      let mut world = World::new();
2788      ground(&mut world, 0.0);
2789      let mut cubes = alloc::vec::Vec::new();
2790      for level in 0..5 {
2791         cubes.push(
2792            world.add_body(
2793               BodyDesc::dynamic(Shape::cuboid(Vec3::splat(0.5)), 1.0)
2794                  .at(Vec3::new(0.0, 0.5 + level as f32 * 1.01, 0.0))
2795                  .with_friction(0.9),
2796            ),
2797         );
2798      }
2799      // At no point may part of the stack be asleep while the rest is not.
2800      // 堆叠的任何一部分都不允许在其余部分清醒时先行入睡。
2801      let mut slept = false;
2802      for step in 0..1200 {
2803         world.step(1.0 / 120.0);
2804         let asleep = cubes
2805            .iter()
2806            .filter(|cube| world.is_sleeping(**cube) == Some(true))
2807            .count();
2808         assert!(
2809            asleep == 0 || asleep == cubes.len(),
2810            "the Island must sleep as a unit, {asleep} of {} were asleep at step {step}",
2811            cubes.len()
2812         );
2813         if asleep == cubes.len() {
2814            slept = true;
2815            break;
2816         }
2817      }
2818      assert!(slept, "the stack never slept");
2819   }
2820}