Skip to main content

he3d_physics/
body.rs

1use he3d_math::{Quat, Vec3};
2
3use crate::aabb::Aabb;
4use crate::shape::{MassProperties, Shape};
5
6/// How a Body's motion is produced.
7/// Body 的运动是如何产生的。
8#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
9#[non_exhaustive]
10pub enum MotionType {
11   /// Driven by gravity, forces, impulses, and Contacts. It has finite mass, and
12   /// anything may move it.
13   /// 由重力、力、冲量与 Contact 驱动。具有有限质量,任何东西都可以移动它。
14   #[default]
15   Dynamic,
16   /// Never moves, and no Contact can move it.
17   /// 永不移动,且没有任何 Contact 能移动它。
18   Static,
19   /// Driven by a velocity the app sets, and no Contact can change it. It pushes
20   /// Dynamic Bodies as though infinitely massive.
21   /// 由应用设定的速度驱动,没有任何 Contact 能改变它。它以无穷大的质量推动
22   /// Dynamic。
23   Kinematic,
24}
25
26impl MotionType {
27   /// Whether gravity, forces, and impulses accelerate a Body in this mode.
28   /// 该模式下重力、力与冲量是否会加速 Body。
29   #[must_use]
30   pub fn is_simulated(self) -> bool {
31      matches!(self, Self::Dynamic)
32   }
33
34   /// Whether the Body is carried by its own velocity each step.
35   /// 该 Body 是否每步被自身速度带动。
36   #[must_use]
37   pub fn moves(self) -> bool {
38      !matches!(self, Self::Static)
39   }
40
41   /// Whether a Contact can change this Body's velocity.
42   /// Contact 是否能改变该 Body 的速度。
43   #[must_use]
44   pub fn is_pushable(self) -> bool {
45      matches!(self, Self::Dynamic)
46   }
47}
48
49/// A stable generational handle to a Body within a [`crate::World`].
50/// 指向 [`crate::World`] 中某个 Body 的稳定代际句柄。
51///
52/// It becomes permanently invalid once that Body is removed: the slot's
53/// generation advances, so a stale handle is rejected rather than silently
54/// addressing whatever Body reuses the slot.
55/// 该 Body 被移除后此句柄永久失效:槽位的代际会递增,因此过期句柄会被拒绝,而不会静默
56/// 地指向复用该槽位的另一个 Body。
57#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
58pub struct BodyId {
59   index: u32,
60   generation: u32,
61}
62
63impl BodyId {
64   /// Mint a handle. Only a World may do this.
65   /// 铸造句柄。只有 World 可以这样做。
66   pub(crate) const fn new(index: u32, generation: u32) -> Self {
67      Self { index, generation }
68   }
69
70   /// The slot this handle addresses within its World.
71   /// 此句柄在其 World 中寻址的槽位。
72   #[must_use]
73   pub const fn index(self) -> u32 {
74      self.index
75   }
76
77   /// The slot generation this handle was minted against.
78   /// 铸造此句柄时所对应的槽位代际。
79   #[must_use]
80   pub const fn generation(self) -> u32 {
81      self.generation
82   }
83}
84
85/// Default friction coefficient for a new Body.
86/// 新建 Body 的默认摩擦系数。
87///
88/// The middle of the usable dry-sliding range, so a Body built without
89/// `with_friction` neither slides like ice nor refuses to be pushed. A scene that
90/// cares about the value should set it.
91/// 干滑动可用区间的中值,使未调用 `with_friction` 的 Body 既不会像冰面一样滑动,也不会
92/// 推不动。真正在意该值的场景应当显式设置。
93pub const DEFAULT_FRICTION: f32 = 0.5;
94
95/// The sleep-group tag a Body carries when it belongs to no sleeping Island.
96/// Body 不属于任何睡眠 Island 时所携带的睡眠组标记。
97pub(crate) const NO_SLEEP_GROUP: u32 = u32::MAX;
98
99/// Construction parameters for a Body.
100/// Body 的构造参数。
101///
102/// Mass is the input and the inertia tensor is derived from the Shape, so the two
103/// can never disagree. See `docs/physics-spec.md` §3.
104/// 输入的是质量,惯量张量由 Shape 推导,因此二者永不可能不一致。见
105/// `docs/physics-spec.md` §3。
106#[derive(Clone, Copy, Debug, PartialEq)]
107#[non_exhaustive]
108pub struct BodyDesc {
109   /// The Body's collision shape.
110   /// Body 的碰撞形状。
111   pub shape: Shape,
112   /// How the Body's motion is produced.
113   /// Body 的运动如何产生。
114   pub motion: MotionType,
115   /// Mass in kilograms. Ignored when `motion` is not [`MotionType::Dynamic`].
116   /// 质量(千克)。当 `motion` 不是 [`MotionType::Dynamic`] 时忽略。
117   pub mass: f32,
118   /// Initial position.
119   /// 初始位置。
120   pub position: Vec3,
121   /// Initial orientation. Normalized on insertion.
122   /// 初始朝向。插入时会被归一化。
123   pub orientation: Quat,
124   /// Initial linear velocity.
125   /// 初始线速度。
126   pub linear_velocity: Vec3,
127   /// Initial angular velocity.
128   /// 初始角速度。
129   pub angular_velocity: Vec3,
130   /// Coulomb friction coefficient.
131   /// 库仑摩擦系数。
132   pub friction: f32,
133   /// Bounciness, from 0 (no bounce) upward.
134   /// 弹性,0 表示不反弹。
135   pub restitution: f32,
136   /// Velocity decay per second applied to linear motion.
137   /// 每秒施加于线运动的衰减。
138   pub linear_damping: f32,
139   /// How much of the World's gravity reaches this Body. See
140   /// [`BodyDesc::with_gravity_scale`].
141   /// World 的重力有多少作用于此 Body。见 [`BodyDesc::with_gravity_scale`]。
142   pub gravity_scale: f32,
143   /// Velocity decay per second applied to angular motion.
144   /// 每秒施加于角运动的衰减。
145   pub angular_damping: f32,
146}
147
148impl BodyDesc {
149   /// A Body in the given motion mode, with the given shape.
150   /// 给定运动模式与形状的 Body。
151   #[must_use]
152   pub fn new(shape: Shape, motion: MotionType) -> Self {
153      Self {
154         shape,
155         motion,
156         mass: 1.0,
157         position: Vec3::ZERO,
158         orientation: Quat::IDENTITY,
159         linear_velocity: Vec3::ZERO,
160         angular_velocity: Vec3::ZERO,
161         friction: DEFAULT_FRICTION,
162         restitution: 0.0,
163         linear_damping: 0.0,
164         gravity_scale: 1.0,
165         angular_damping: 0.0,
166      }
167   }
168
169   /// A Dynamic Body of the given shape and mass. A non-positive or non-finite
170   /// mass yields a Body nothing can accelerate.
171   /// 给定形状与质量的 Dynamic Body。非正或非有限的质量得到无法被加速的 Body。
172   #[must_use]
173   pub fn dynamic(shape: Shape, mass: f32) -> Self {
174      Self::new(shape, MotionType::Dynamic).with_mass(mass)
175   }
176
177   /// A Static Body: it never moves.
178   /// Static Body:永不移动。
179   #[must_use]
180   pub fn stationary(shape: Shape) -> Self {
181      Self::new(shape, MotionType::Static)
182   }
183
184   /// A Kinematic Body, moved by whatever velocity the app sets on it.
185   /// Kinematic Body,由应用设定的速度驱动。
186   #[must_use]
187   pub fn kinematic(shape: Shape) -> Self {
188      Self::new(shape, MotionType::Kinematic)
189   }
190
191   /// Set the mass.
192   /// 设置质量。
193   #[must_use]
194   pub fn with_mass(mut self, mass: f32) -> Self {
195      self.mass = mass;
196      self
197   }
198
199   /// Set the initial position.
200   /// 设置初始位置。
201   #[must_use]
202   pub fn at(mut self, position: Vec3) -> Self {
203      self.position = position;
204      self
205   }
206
207   /// Set the initial orientation.
208   /// 设置初始朝向。
209   #[must_use]
210   pub fn oriented(mut self, orientation: Quat) -> Self {
211      self.orientation = orientation;
212      self
213   }
214
215   /// Set the initial linear velocity.
216   /// 设置初始线速度。
217   #[must_use]
218   pub fn with_velocity(mut self, velocity: Vec3) -> Self {
219      self.linear_velocity = velocity;
220      self
221   }
222
223   /// Set the initial angular velocity.
224   /// 设置初始角速度。
225   #[must_use]
226   pub fn with_angular_velocity(mut self, angular_velocity: Vec3) -> Self {
227      self.angular_velocity = angular_velocity;
228      self
229   }
230
231   /// Set the friction coefficient.
232   /// 设置摩擦系数。
233   #[must_use]
234   pub fn with_friction(mut self, friction: f32) -> Self {
235      self.friction = friction;
236      self
237   }
238
239   /// Set the restitution.
240   /// 设置弹性。
241   #[must_use]
242   pub fn with_restitution(mut self, restitution: f32) -> Self {
243      self.restitution = restitution;
244      self
245   }
246
247   /// Scale the World's gravity for this Body.
248   /// 按比例缩放作用于此 Body 的 World 重力。
249   ///
250   /// `0.0` means gravity never touches it, which is what a caller wants when
251   /// something else already owns the Body's motion — a flight model, or anything
252   /// kinematic-in-spirit that still needs contacts and friction. `1.0` is the
253   /// default and `2.0` falls twice as fast.
254   /// `0.0` 表示重力永不作用于它,这正是当别的东西已经拥有该 Body 的运动时想要的——例如
255   /// 飞行模型,或任何在精神上近似运动学、却仍需要接触与摩擦的东西。默认为 `1.0`,`2.0`
256   /// 则以两倍速度下坠。
257   ///
258   /// Negative or non-finite values are clamped to zero.
259   /// 负值或非有限值被钳制为零。
260   #[must_use]
261   pub fn with_gravity_scale(mut self, scale: f32) -> Self {
262      self.gravity_scale = if scale.is_finite() && scale > 0.0 {
263         scale
264      } else {
265         0.0
266      };
267      self
268   }
269
270   /// Damping applied to linear and angular velocity.
271   /// 施加于线速度与角速度的阻尼。
272   #[must_use]
273   pub fn with_damping(mut self, linear: f32, angular: f32) -> Self {
274      self.linear_damping = linear;
275      self.angular_damping = angular;
276      self
277   }
278}
279
280/// A Body's simulation state. Owned by a [`crate::World`].
281/// Body 的模拟状态。由 [`crate::World`] 持有。
282#[derive(Clone, Copy, Debug)]
283pub(crate) struct Body {
284   pub(crate) shape: Shape,
285   pub(crate) motion: MotionType,
286   pub(crate) mass: MassProperties,
287   pub(crate) position: Vec3,
288   pub(crate) orientation: Quat,
289   pub(crate) linear_velocity: Vec3,
290   pub(crate) angular_velocity: Vec3,
291   pub(crate) force: Vec3,
292   pub(crate) torque: Vec3,
293   /// Velocity that exists only to push overlap apart.
294   ///
295   /// Positional correction runs against these instead of the real velocities, so
296   /// resolving penetration never leaves kinetic energy behind to jitter a
297   /// resting stack.
298   /// 仅用于推开重叠的速度。位置修正作用于它们而非真实速度,因此消除穿透不会留下让静止
299   /// 堆叠抖动的动能。
300   pub(crate) pseudo_linear_velocity: Vec3,
301   /// Angular counterpart of `pseudo_linear_velocity`.
302   /// `pseudo_linear_velocity` 的角对应量。
303   pub(crate) pseudo_angular_velocity: Vec3,
304   pub(crate) friction: f32,
305   pub(crate) restitution: f32,
306   pub(crate) linear_damping: f32,
307   /// How much of the World's gravity reaches this Body.
308   /// World 的重力有多少作用于此 Body。
309   pub(crate) gravity_scale: f32,
310   pub(crate) angular_damping: f32,
311   /// Whether the Body is currently excluded from simulation.
312   pub(crate) sleeping: bool,
313   /// Seconds spent below the sleep velocity thresholds.
314   pub(crate) sleep_timer: f32,
315   /// Whether the Body was still moving after the previous step's solve.
316   ///
317   /// Waking a neighbour must ask this rather than look at the current velocity:
318   /// every awake Dynamic Body carries a fresh gravity increment early in a step,
319   /// so a raw velocity test calls it "moving" every single step and no stack ever
320   /// stays asleep.
321   /// Body 在上一步求解之后是否仍在运动。唤醒邻居必须问这个,而不是看当前速度:每个清醒的
322   /// Dynamic Body 在一步的早期都带着刚加上的重力增量,因此直接看速度会每步都判定它"在动",
323   /// 任何堆叠都无法保持睡眠。
324   pub(crate) was_moving: bool,
325   /// The Island this Body went to sleep with, or [`NO_SLEEP_GROUP`].
326   ///
327   /// It survives the Body waking up: that is exactly how a disturbance to one
328   /// member is turned into waking the whole Island on the next step.
329   /// Body 与之一起入睡的 Island,或 [`NO_SLEEP_GROUP`]。
330   /// 它在 Body 醒来之后依然保留:这正是把"某个成员被扰动"转化为"下一步唤醒整个 Island"
331   /// 的机制。
332   pub(crate) sleep_group: u32,
333}
334
335impl Body {
336   /// Build the simulation state for a description.
337   pub(crate) fn from_desc(desc: &BodyDesc) -> Self {
338      let mass = if desc.motion.is_simulated() {
339         desc.shape.mass_properties(desc.mass)
340      } else {
341         MassProperties::IMMOVABLE
342      };
343      let orientation = if desc.orientation.length_squared() > 0.0 {
344         desc.orientation.normalize()
345      } else {
346         Quat::IDENTITY
347      };
348      Self {
349         shape: desc.shape,
350         motion: desc.motion,
351         mass,
352         position: desc.position,
353         orientation,
354         linear_velocity: desc.linear_velocity,
355         angular_velocity: desc.angular_velocity,
356         force: Vec3::ZERO,
357         torque: Vec3::ZERO,
358         pseudo_linear_velocity: Vec3::ZERO,
359         pseudo_angular_velocity: Vec3::ZERO,
360         friction: desc.friction.max(0.0),
361         restitution: desc.restitution.max(0.0),
362         linear_damping: desc.linear_damping.max(0.0),
363         gravity_scale: desc.gravity_scale,
364         angular_damping: desc.angular_damping.max(0.0),
365         sleeping: false,
366         sleep_timer: 0.0,
367         was_moving: false,
368         sleep_group: NO_SLEEP_GROUP,
369      }
370   }
371
372   /// The Body's world-space bounding box.
373   pub(crate) fn aabb(&self) -> Aabb {
374      self.shape.aabb(self.position, self.orientation)
375   }
376
377   /// Apply the Body's inverse world inertia tensor to a world-space vector.
378   ///
379   /// The tensor is diagonal in local space, so this rotates into local space,
380   /// scales per axis, and rotates back: `R * (I⁻¹ ⊙ (R⁻¹ v))`.
381   /// 该张量在局部空间是对角的,因此这里先旋入局部空间、各轴缩放、再旋回:
382   /// `R * (I⁻¹ ⊙ (R⁻¹ v))`。
383   pub(crate) fn apply_inv_inertia(&self, value: Vec3) -> Vec3 {
384      let local = self.orientation.inverse() * value;
385      self.orientation * (local * self.mass.inv_inertia)
386   }
387
388   /// Velocity of the Body at a point given relative to its center of mass,
389   /// including the contribution of its spin.
390   /// Body 在相对于质心给定点处的速度,含自转的贡献。
391   pub(crate) fn point_velocity(&self, anchor: Vec3) -> Vec3 {
392      self.linear_velocity + self.angular_velocity.cross(anchor)
393   }
394
395   /// The pseudo-velocity at a point, used only by positional correction.
396   /// 该点处的伪速度,仅用于位置修正。
397   pub(crate) fn pseudo_point_velocity(&self, anchor: Vec3) -> Vec3 {
398      self.pseudo_linear_velocity + self.pseudo_angular_velocity.cross(anchor)
399   }
400
401   /// Drop the positional-correction velocities once they have been applied.
402   /// 在伪速度被应用之后将其清零。
403   pub(crate) fn clear_pseudo_velocities(&mut self) {
404      self.pseudo_linear_velocity = Vec3::ZERO;
405      self.pseudo_angular_velocity = Vec3::ZERO;
406   }
407
408   /// Whether the Body is moving slowly enough to be a sleep candidate.
409   pub(crate) fn is_below_sleep_threshold(&self, linear: f32, angular: f32) -> bool {
410      self.linear_velocity.length_squared() <= linear * linear
411         && self.angular_velocity.length_squared() <= angular * angular
412   }
413
414   /// Stop the Body and reset its sleep timer.
415   pub(crate) fn put_to_sleep(&mut self) {
416      self.linear_velocity = Vec3::ZERO;
417      self.angular_velocity = Vec3::ZERO;
418      self.force = Vec3::ZERO;
419      self.torque = Vec3::ZERO;
420      self.sleeping = true;
421      self.sleep_timer = 0.0;
422   }
423
424   /// Return the Body to simulation.
425   pub(crate) fn wake(&mut self) {
426      self.sleeping = false;
427      self.sleep_timer = 0.0;
428   }
429
430   /// Record whether the Body is moving, for the next step's wake decisions.
431   pub(crate) fn refresh_movement(&mut self, linear: f32, angular: f32) {
432      self.was_moving = !self.is_below_sleep_threshold(linear, angular);
433   }
434}
435
436#[cfg(test)]
437mod tests {
438   use super::{Body, BodyDesc, BodyId, MotionType};
439   use crate::shape::Shape;
440   use he3d_math::{Quat, Vec3};
441
442   #[test]
443   fn motion_mode_reports_its_own_semantics() {
444      assert!(MotionType::Dynamic.is_simulated());
445      assert!(MotionType::Dynamic.moves());
446      assert!(MotionType::Dynamic.is_pushable());
447
448      assert!(!MotionType::Static.is_simulated());
449      assert!(!MotionType::Static.moves());
450      assert!(!MotionType::Static.is_pushable());
451
452      assert!(!MotionType::Kinematic.is_simulated());
453      assert!(MotionType::Kinematic.moves());
454      assert!(!MotionType::Kinematic.is_pushable());
455   }
456
457   #[test]
458   fn generated_bodies_carry_derived_mass() {
459      let dynamic = Body::from_desc(&BodyDesc::dynamic(Shape::sphere(1.0), 2.0));
460      assert!(dynamic.mass.inv_mass > 0.0);
461
462      let stationary = Body::from_desc(&BodyDesc::stationary(Shape::sphere(1.0)));
463      assert!(stationary.mass.is_immovable());
464
465      let kinematic = Body::from_desc(&BodyDesc::kinematic(Shape::sphere(1.0)));
466      assert!(kinematic.mass.is_immovable());
467   }
468
469   #[test]
470   fn a_non_unit_orientation_is_normalized_on_construction() {
471      let desc =
472         BodyDesc::dynamic(Shape::sphere(1.0), 1.0).oriented(Quat::from_xyzw(0.0, 0.0, 0.0, 3.0));
473      let body = Body::from_desc(&desc);
474      assert!((body.orientation.length() - 1.0).abs() < 1e-5);
475   }
476
477   #[test]
478   fn inverse_inertia_acts_along_the_bodys_own_axes() {
479      let desc = BodyDesc::dynamic(Shape::cuboid(Vec3::new(1.0, 2.0, 4.0)), 3.0)
480         .oriented(Quat::from_rotation_y(0.7));
481      let body = Body::from_desc(&desc);
482      // The world direction of the Body's local X axis must come back scaled by
483      // inv_inertia.x and still lie on that same axis.
484      let world_x = body.orientation * Vec3::X;
485      let applied = body.apply_inv_inertia(world_x);
486      assert!((applied.length() - body.mass.inv_inertia.x).abs() < 1e-4);
487      assert!(
488         applied.cross(world_x).length() < 1e-4,
489         "the result left the axis: {applied:?}"
490      );
491   }
492
493   #[test]
494   fn velocity_at_combines_linear_and_angular_motion() {
495      let mut desc = BodyDesc::dynamic(Shape::sphere(0.5), 1.0);
496      desc.linear_velocity = Vec3::new(0.0, 0.0, 1.0);
497      desc.angular_velocity = Vec3::new(0.0, 1.0, 0.0);
498      let body = Body::from_desc(&desc);
499      // omega x r at a point on +Z is +X, so the two contributions add.
500      let at = body.point_velocity(Vec3::new(0.0, 0.0, 1.0));
501      assert!((at.x - 1.0).abs() < 1e-5, "got {at:?}");
502      assert!(at.y.abs() < 1e-5, "got {at:?}");
503      assert!((at.z - 1.0).abs() < 1e-5, "got {at:?}");
504   }
505
506   #[test]
507   fn ids_compare_by_slot_and_generation() {
508      assert_eq!(BodyId::new(1, 0), BodyId::new(1, 0));
509      assert_ne!(BodyId::new(1, 0), BodyId::new(1, 1));
510      assert_ne!(BodyId::new(1, 0), BodyId::new(2, 0));
511      assert_eq!(BodyId::new(7, 3).index(), 7);
512      assert_eq!(BodyId::new(7, 3).generation(), 3);
513   }
514}