Skip to main content

goud_engine/ecs/components/rigidbody/
body.rs

1//! [`RigidBody`] component struct, flags, constructors, builder, accessors, and mutators.
2
3use crate::core::math::Vec2;
4
5use super::body_type::RigidBodyType;
6
7// =============================================================================
8// RigidBodyFlags (private)
9// =============================================================================
10
11/// Bit flags for rigid body state.
12pub(super) struct RigidBodyFlags;
13
14impl RigidBodyFlags {
15    /// Body is currently sleeping (optimization).
16    pub(super) const SLEEPING: u8 = 1 << 0;
17    /// Body can sleep when idle.
18    pub(super) const CAN_SLEEP: u8 = 1 << 1;
19    /// Continuous collision detection enabled.
20    pub(super) const CONTINUOUS_CD: u8 = 1 << 2;
21    /// Fixed rotation (prevents rotation from collisions).
22    pub(super) const FIXED_ROTATION: u8 = 1 << 3;
23}
24
25// =============================================================================
26// RigidBody Component
27// =============================================================================
28
29/// A physics body component for 2D physics simulation.
30///
31/// RigidBody stores all the physics state for an entity, including velocity,
32/// forces, mass, and behavior type. It works together with the `Transform2D`
33/// component (for position/rotation) and optionally with `Collider` components
34/// for collision detection.
35///
36/// # Memory Layout
37///
38/// The component is laid out as:
39/// - `body_type`: u8 (1 byte) + 3 bytes padding
40/// - `linear_velocity`: Vec2 (8 bytes)
41/// - `angular_velocity`: f32 (4 bytes)
42/// - `linear_damping`: f32 (4 bytes)
43/// - `angular_damping`: f32 (4 bytes)
44/// - `mass`: f32 (4 bytes)
45/// - `inverse_mass`: f32 (4 bytes)
46/// - `inertia`: f32 (4 bytes)
47/// - `inverse_inertia`: f32 (4 bytes)
48/// - `gravity_scale`: f32 (4 bytes)
49/// - `flags`: u8 (1 byte) + 3 bytes padding
50/// - `sleep_time`: f32 (4 bytes)
51/// - Total: 56 bytes (may vary with padding)
52///
53/// # Examples
54///
55/// ```
56/// use goud_engine::ecs::components::{RigidBody, RigidBodyType};
57/// use goud_engine::core::math::Vec2;
58///
59/// // Dynamic body with custom properties
60/// let body = RigidBody::dynamic()
61///     .with_mass(2.0)
62///     .with_velocity(Vec2::new(100.0, 0.0))
63///     .with_gravity_scale(1.5);
64///
65/// // Kinematic platform
66/// let platform = RigidBody::kinematic()
67///     .with_velocity(Vec2::new(50.0, 0.0));
68///
69/// // Static wall
70/// let wall = RigidBody::static_body();
71/// ```
72///
73/// # Material Properties
74///
75/// Material properties (restitution, friction) live on [`Collider`], not `RigidBody`.
76/// Set them via [`Collider::with_restitution`] and [`Collider::with_friction`].
77///
78/// [`Collider`]: crate::ecs::components::Collider
79/// [`Collider::with_restitution`]: crate::ecs::components::Collider::with_restitution
80/// [`Collider::with_friction`]: crate::ecs::components::Collider::with_friction
81#[derive(Clone, Copy, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
82pub struct RigidBody {
83    /// Physics behavior type (Dynamic, Kinematic, or Static).
84    pub body_type: RigidBodyType,
85
86    /// Linear velocity in pixels/second.
87    pub linear_velocity: Vec2,
88
89    /// Angular velocity in radians/second.
90    pub angular_velocity: f32,
91
92    /// Linear damping (air resistance) - 0.0 = no damping, 1.0 = full damping.
93    /// Applied each frame as: velocity *= (1.0 - damping * dt)
94    pub linear_damping: f32,
95
96    /// Angular damping (rotational resistance) - 0.0 = no damping, 1.0 = full damping.
97    pub angular_damping: f32,
98
99    /// Mass in arbitrary units (kg assumed for consistency).
100    /// Must be positive for dynamic bodies.
101    pub mass: f32,
102
103    /// Inverse mass (1.0 / mass). Pre-calculated for performance.
104    /// Zero for static/kinematic bodies (infinite mass).
105    pub inverse_mass: f32,
106
107    /// Rotational inertia (moment of inertia) in kg·m².
108    /// Calculated from mass and collider shape.
109    pub inertia: f32,
110
111    /// Inverse inertia (1.0 / inertia). Pre-calculated for performance.
112    /// Zero for static/kinematic bodies.
113    pub inverse_inertia: f32,
114
115    /// Gravity scale multiplier.
116    /// - 0.0 = no gravity
117    /// - 1.0 = normal gravity
118    /// - 2.0 = double gravity
119    pub gravity_scale: f32,
120
121    /// Bit flags for body state (see RigidBodyFlags).
122    pub(super) flags: u8,
123
124    /// Time the body has been below sleep thresholds (seconds).
125    /// When this exceeds PhysicsWorld::sleep_time_threshold, the body sleeps.
126    pub(super) sleep_time: f32,
127}
128
129// =============================================================================
130// Constructors
131// =============================================================================
132
133impl Default for RigidBody {
134    /// Creates a default dynamic rigid body.
135    fn default() -> Self {
136        Self::dynamic()
137    }
138}
139
140impl RigidBody {
141    /// Creates a new rigid body with custom parameters.
142    ///
143    /// # Arguments
144    ///
145    /// * `body_type` - Physics behavior type
146    ///
147    /// # Examples
148    ///
149    /// ```
150    /// use goud_engine::ecs::components::{RigidBody, RigidBodyType};
151    ///
152    /// let body = RigidBody::new(RigidBodyType::Dynamic);
153    /// ```
154    pub fn new(body_type: RigidBodyType) -> Self {
155        let (mass, inverse_mass) = if body_type == RigidBodyType::Dynamic {
156            (1.0, 1.0)
157        } else {
158            (0.0, 0.0) // Infinite mass for static/kinematic
159        };
160
161        Self {
162            body_type,
163            linear_velocity: Vec2::zero(),
164            angular_velocity: 0.0,
165            linear_damping: 0.01, // Slight air resistance
166            angular_damping: 0.01,
167            mass,
168            inverse_mass,
169            inertia: 1.0,
170            inverse_inertia: if body_type == RigidBodyType::Dynamic {
171                1.0
172            } else {
173                0.0
174            },
175            gravity_scale: 1.0,               // Full gravity
176            flags: RigidBodyFlags::CAN_SLEEP, // Can sleep by default
177            sleep_time: 0.0,
178        }
179    }
180
181    /// Creates a dynamic rigid body (fully simulated).
182    ///
183    /// # Examples
184    ///
185    /// ```
186    /// use goud_engine::ecs::components::RigidBody;
187    ///
188    /// let player = RigidBody::dynamic()
189    ///     .with_mass(2.0);
190    /// ```
191    #[inline]
192    pub fn dynamic() -> Self {
193        Self::new(RigidBodyType::Dynamic)
194    }
195
196    /// Creates a kinematic rigid body (controlled velocity).
197    ///
198    /// # Examples
199    ///
200    /// ```
201    /// use goud_engine::ecs::components::RigidBody;
202    /// use goud_engine::core::math::Vec2;
203    ///
204    /// let platform = RigidBody::kinematic()
205    ///     .with_velocity(Vec2::new(50.0, 0.0));
206    /// ```
207    #[inline]
208    pub fn kinematic() -> Self {
209        Self::new(RigidBodyType::Kinematic)
210    }
211
212    /// Creates a static rigid body (immovable).
213    ///
214    /// # Examples
215    ///
216    /// ```
217    /// use goud_engine::ecs::components::RigidBody;
218    ///
219    /// let wall = RigidBody::static_body();
220    /// ```
221    #[inline]
222    pub fn static_body() -> Self {
223        Self::new(RigidBodyType::Static)
224    }
225
226    // =========================================================================
227    // Builder Pattern
228    // =========================================================================
229
230    /// Sets the linear velocity.
231    pub fn with_velocity(mut self, velocity: Vec2) -> Self {
232        self.linear_velocity = velocity;
233        self
234    }
235
236    /// Sets the angular velocity.
237    pub fn with_angular_velocity(mut self, angular_velocity: f32) -> Self {
238        self.angular_velocity = angular_velocity;
239        self
240    }
241
242    /// Sets the mass (dynamic bodies only).
243    ///
244    /// # Panics
245    ///
246    /// Panics if mass is not positive and finite.
247    pub fn with_mass(mut self, mass: f32) -> Self {
248        assert!(
249            mass > 0.0 && mass.is_finite(),
250            "Mass must be positive and finite"
251        );
252        self.mass = mass;
253        self.inverse_mass = 1.0 / mass;
254        self
255    }
256
257    /// Sets the linear damping.
258    pub fn with_linear_damping(mut self, damping: f32) -> Self {
259        self.linear_damping = damping.max(0.0);
260        self
261    }
262
263    /// Sets the angular damping.
264    pub fn with_angular_damping(mut self, damping: f32) -> Self {
265        self.angular_damping = damping.max(0.0);
266        self
267    }
268
269    /// Sets the gravity scale.
270    pub fn with_gravity_scale(mut self, scale: f32) -> Self {
271        self.gravity_scale = scale;
272        self
273    }
274
275    /// Sets whether the body can sleep.
276    pub fn with_can_sleep(mut self, can_sleep: bool) -> Self {
277        if can_sleep {
278            self.flags |= RigidBodyFlags::CAN_SLEEP;
279        } else {
280            self.flags &= !RigidBodyFlags::CAN_SLEEP;
281        }
282        self
283    }
284
285    /// Enables continuous collision detection (for fast-moving objects).
286    pub fn with_continuous_cd(mut self, enabled: bool) -> Self {
287        if enabled {
288            self.flags |= RigidBodyFlags::CONTINUOUS_CD;
289        } else {
290            self.flags &= !RigidBodyFlags::CONTINUOUS_CD;
291        }
292        self
293    }
294
295    /// Fixes the rotation (prevents rotation from collisions).
296    pub fn with_fixed_rotation(mut self, fixed: bool) -> Self {
297        if fixed {
298            self.flags |= RigidBodyFlags::FIXED_ROTATION;
299            self.angular_velocity = 0.0;
300            self.inertia = 0.0;
301            self.inverse_inertia = 0.0;
302        } else {
303            self.flags &= !RigidBodyFlags::FIXED_ROTATION;
304        }
305        self
306    }
307
308    // =========================================================================
309    // Accessors
310    // =========================================================================
311
312    /// Returns the body type.
313    #[inline]
314    pub fn body_type(&self) -> RigidBodyType {
315        self.body_type
316    }
317
318    /// Returns true if this is a dynamic body.
319    #[inline]
320    pub fn is_dynamic(&self) -> bool {
321        self.body_type == RigidBodyType::Dynamic
322    }
323
324    /// Returns true if this is a kinematic body.
325    #[inline]
326    pub fn is_kinematic(&self) -> bool {
327        self.body_type == RigidBodyType::Kinematic
328    }
329
330    /// Returns true if this is a static body.
331    #[inline]
332    pub fn is_static(&self) -> bool {
333        self.body_type == RigidBodyType::Static
334    }
335
336    /// Returns true if the body is currently sleeping.
337    #[inline]
338    pub fn is_sleeping(&self) -> bool {
339        self.flags & RigidBodyFlags::SLEEPING != 0
340    }
341
342    /// Returns true if the body can sleep.
343    #[inline]
344    pub fn can_sleep(&self) -> bool {
345        self.flags & RigidBodyFlags::CAN_SLEEP != 0
346    }
347
348    /// Returns true if continuous collision detection is enabled.
349    #[inline]
350    pub fn has_continuous_cd(&self) -> bool {
351        self.flags & RigidBodyFlags::CONTINUOUS_CD != 0
352    }
353
354    /// Returns true if rotation is fixed.
355    #[inline]
356    pub fn has_fixed_rotation(&self) -> bool {
357        self.flags & RigidBodyFlags::FIXED_ROTATION != 0
358    }
359
360    /// Returns the current sleep time.
361    #[inline]
362    pub fn sleep_time(&self) -> f32 {
363        self.sleep_time
364    }
365
366    /// Returns the linear speed (magnitude of velocity).
367    #[inline]
368    pub fn linear_speed(&self) -> f32 {
369        self.linear_velocity.length()
370    }
371
372    /// Returns the linear speed squared (avoids sqrt).
373    #[inline]
374    pub fn linear_speed_squared(&self) -> f32 {
375        self.linear_velocity.length_squared()
376    }
377
378    /// Returns the kinetic energy of the body (0.5 * m * v²).
379    #[inline]
380    pub fn kinetic_energy(&self) -> f32 {
381        0.5 * self.mass * self.linear_speed_squared()
382    }
383
384    // =========================================================================
385    // Mutators
386    // =========================================================================
387
388    /// Sets the linear velocity.
389    pub fn set_velocity(&mut self, velocity: Vec2) {
390        self.linear_velocity = velocity;
391        self.wake();
392    }
393
394    /// Sets the angular velocity.
395    pub fn set_angular_velocity(&mut self, angular_velocity: f32) {
396        self.angular_velocity = angular_velocity;
397        self.wake();
398    }
399
400    /// Sets the mass (dynamic bodies only).
401    ///
402    /// # Panics
403    ///
404    /// Panics if mass is not positive and finite, or if called on non-dynamic body.
405    pub fn set_mass(&mut self, mass: f32) {
406        assert!(self.is_dynamic(), "Cannot set mass on non-dynamic body");
407        assert!(
408            mass > 0.0 && mass.is_finite(),
409            "Mass must be positive and finite"
410        );
411        self.mass = mass;
412        self.inverse_mass = 1.0 / mass;
413    }
414
415    /// Sets the body type, updating mass accordingly.
416    pub fn set_body_type(&mut self, body_type: RigidBodyType) {
417        if self.body_type == body_type {
418            return;
419        }
420
421        self.body_type = body_type;
422
423        // Update mass/inertia based on new type
424        if body_type == RigidBodyType::Dynamic {
425            if self.mass == 0.0 {
426                self.mass = 1.0;
427            }
428            self.inverse_mass = 1.0 / self.mass;
429            if self.inertia == 0.0 {
430                self.inertia = 1.0;
431            }
432            self.inverse_inertia = 1.0 / self.inertia;
433        } else {
434            self.inverse_mass = 0.0;
435            self.inverse_inertia = 0.0;
436        }
437
438        self.wake();
439    }
440
441    // =========================================================================
442    // Internal Helpers (used by physics_ops submodule)
443    // =========================================================================
444
445    /// Sets or clears the sleeping flag without touching velocity.
446    #[inline]
447    pub(super) fn set_sleeping(&mut self, sleeping: bool) {
448        if sleeping {
449            self.flags |= RigidBodyFlags::SLEEPING;
450        } else {
451            self.flags &= !RigidBodyFlags::SLEEPING;
452        }
453    }
454
455    /// Resets accumulated sleep time to zero.
456    #[inline]
457    pub(super) fn reset_sleep_time(&mut self) {
458        self.sleep_time = 0.0;
459    }
460
461    /// Adds `dt` to the accumulated sleep time.
462    #[inline]
463    pub(super) fn increment_sleep_time(&mut self, dt: f32) {
464        self.sleep_time += dt;
465    }
466}