Skip to main content

goud_engine/ecs/components/
rigidbody.rs

1//! RigidBody component for physics simulation.
2//!
3//! The [`RigidBody`] component marks an entity as a physics object that participates
4//! in physics simulation. It controls the entity's physics behavior type (dynamic,
5//! kinematic, or static) and stores physics state like velocity and forces.
6//!
7//! # Physics Behavior Types
8//!
9//! - **Dynamic**: Fully simulated, affected by forces and collisions
10//! - **Kinematic**: Moves via velocity but not affected by forces
11//! - **Static**: Immovable, acts as obstacles for other bodies
12//!
13//! # Usage
14//!
15//! ```
16//! use goud_engine::ecs::components::{RigidBody, RigidBodyType};
17//! use goud_engine::core::math::Vec2;
18//!
19//! // Create a dynamic body (player, enemies, etc.)
20//! let player = RigidBody::dynamic()
21//!     .with_velocity(Vec2::new(100.0, 0.0))
22//!     .with_mass(1.0);
23//!
24//! // Create a kinematic body (moving platforms)
25//! let platform = RigidBody::kinematic()
26//!     .with_velocity(Vec2::new(50.0, 0.0));
27//!
28//! // Create a static body (walls, floors)
29//! let wall = RigidBody::static_body();
30//! ```
31//!
32//! # Integration with Physics Systems
33//!
34//! The physics system reads RigidBody components to:
35//! - Apply forces and impulses
36//! - Integrate velocity to update position
37//! - Handle collisions and constraints
38//! - Implement sleeping for optimization
39//!
40//! # Thread Safety
41//!
42//! RigidBody is `Send + Sync` and can be safely used in parallel systems.
43
44use crate::core::math::Vec2;
45use crate::ecs::Component;
46
47// =============================================================================
48// RigidBodyType Enum
49// =============================================================================
50
51/// Defines the physics behavior of a rigid body.
52///
53/// Different body types interact with the physics system in different ways.
54/// This determines whether the body is affected by forces, can be moved by
55/// the simulation, and how it collides with other bodies.
56///
57/// # FFI Safety
58///
59/// `#[repr(u8)]` ensures this enum has a stable ABI for FFI.
60#[repr(u8)]
61#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
62pub enum RigidBodyType {
63    /// Dynamic bodies are fully simulated by the physics engine.
64    ///
65    /// - Affected by gravity and forces
66    /// - Responds to collisions
67    /// - Can be moved by constraints
68    /// - Most expensive to simulate
69    ///
70    /// Use for: players, enemies, projectiles, movable objects
71    Dynamic = 0,
72
73    /// Kinematic bodies move via velocity but are not affected by forces.
74    ///
75    /// - NOT affected by gravity or forces
76    /// - Does NOT respond to collisions (but affects other bodies)
77    /// - Moved by setting velocity or position directly
78    /// - Cheaper than dynamic
79    ///
80    /// Use for: moving platforms, elevators, doors, cutscene objects
81    Kinematic = 1,
82
83    /// Static bodies do not move and are not affected by forces.
84    ///
85    /// - Immovable
86    /// - NOT affected by gravity or forces
87    /// - Acts as obstacles for other bodies
88    /// - Cheapest to simulate (often excluded from updates)
89    ///
90    /// Use for: walls, floors, terrain, static obstacles
91    Static = 2,
92}
93
94impl Default for RigidBodyType {
95    /// Defaults to Dynamic for most common use case.
96    fn default() -> Self {
97        Self::Dynamic
98    }
99}
100
101impl RigidBodyType {
102    /// Returns true if this body type is affected by gravity.
103    #[inline]
104    pub fn is_affected_by_gravity(self) -> bool {
105        matches!(self, RigidBodyType::Dynamic)
106    }
107
108    /// Returns true if this body type is affected by forces and impulses.
109    #[inline]
110    pub fn is_affected_by_forces(self) -> bool {
111        matches!(self, RigidBodyType::Dynamic)
112    }
113
114    /// Returns true if this body type can move.
115    #[inline]
116    pub fn can_move(self) -> bool {
117        !matches!(self, RigidBodyType::Static)
118    }
119
120    /// Returns true if this body type responds to collisions.
121    #[inline]
122    pub fn responds_to_collisions(self) -> bool {
123        matches!(self, RigidBodyType::Dynamic)
124    }
125
126    /// Returns the name of this body type.
127    pub fn name(self) -> &'static str {
128        match self {
129            RigidBodyType::Dynamic => "Dynamic",
130            RigidBodyType::Kinematic => "Kinematic",
131            RigidBodyType::Static => "Static",
132        }
133    }
134}
135
136impl std::fmt::Display for RigidBodyType {
137    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
138        write!(f, "{}", self.name())
139    }
140}
141
142// =============================================================================
143// RigidBody Component
144// =============================================================================
145
146/// A physics body component for 2D physics simulation.
147///
148/// RigidBody stores all the physics state for an entity, including velocity,
149/// forces, mass, and behavior type. It works together with the `Transform2D`
150/// component (for position/rotation) and optionally with `Collider` components
151/// for collision detection.
152///
153/// # Memory Layout
154///
155/// The component is laid out as:
156/// - `body_type`: u8 (1 byte) + 3 bytes padding
157/// - `linear_velocity`: Vec2 (8 bytes)
158/// - `angular_velocity`: f32 (4 bytes)
159/// - `linear_damping`: f32 (4 bytes)
160/// - `angular_damping`: f32 (4 bytes)
161/// - `mass`: f32 (4 bytes)
162/// - `inverse_mass`: f32 (4 bytes)
163/// - `inertia`: f32 (4 bytes)
164/// - `inverse_inertia`: f32 (4 bytes)
165/// - `restitution`: f32 (4 bytes)
166/// - `friction`: f32 (4 bytes)
167/// - `gravity_scale`: f32 (4 bytes)
168/// - `flags`: u8 (1 byte) + 3 bytes padding
169/// - `sleep_time`: f32 (4 bytes)
170/// - Total: 64 bytes (may vary with padding)
171///
172/// # Examples
173///
174/// ```
175/// use goud_engine::ecs::components::{RigidBody, RigidBodyType};
176/// use goud_engine::core::math::Vec2;
177///
178/// // Dynamic body with custom properties
179/// let body = RigidBody::dynamic()
180///     .with_mass(2.0)
181///     .with_velocity(Vec2::new(100.0, 0.0))
182///     .with_restitution(0.8)  // Bouncy
183///     .with_friction(0.5);
184///
185/// // Kinematic platform
186/// let platform = RigidBody::kinematic()
187///     .with_velocity(Vec2::new(50.0, 0.0));
188///
189/// // Static wall
190/// let wall = RigidBody::static_body();
191/// ```
192#[derive(Clone, Copy, Debug, PartialEq)]
193pub struct RigidBody {
194    /// Physics behavior type (Dynamic, Kinematic, or Static).
195    pub body_type: RigidBodyType,
196
197    /// Linear velocity in pixels/second.
198    pub linear_velocity: Vec2,
199
200    /// Angular velocity in radians/second.
201    pub angular_velocity: f32,
202
203    /// Linear damping (air resistance) - 0.0 = no damping, 1.0 = full damping.
204    /// Applied each frame as: velocity *= (1.0 - damping * dt)
205    pub linear_damping: f32,
206
207    /// Angular damping (rotational resistance) - 0.0 = no damping, 1.0 = full damping.
208    pub angular_damping: f32,
209
210    /// Mass in arbitrary units (kg assumed for consistency).
211    /// Must be positive for dynamic bodies.
212    pub mass: f32,
213
214    /// Inverse mass (1.0 / mass). Pre-calculated for performance.
215    /// Zero for static/kinematic bodies (infinite mass).
216    pub inverse_mass: f32,
217
218    /// Rotational inertia (moment of inertia) in kg·m².
219    /// Calculated from mass and collider shape.
220    pub inertia: f32,
221
222    /// Inverse inertia (1.0 / inertia). Pre-calculated for performance.
223    /// Zero for static/kinematic bodies.
224    pub inverse_inertia: f32,
225
226    /// Restitution (bounciness) coefficient.
227    /// - 0.0 = inelastic (no bounce)
228    /// - 1.0 = perfectly elastic (full bounce)
229    /// - >1.0 = super-elastic (gains energy)
230    pub restitution: f32,
231
232    /// Friction coefficient.
233    /// - 0.0 = frictionless (ice)
234    /// - 1.0 = high friction (rubber)
235    pub friction: f32,
236
237    /// Gravity scale multiplier.
238    /// - 0.0 = no gravity
239    /// - 1.0 = normal gravity
240    /// - 2.0 = double gravity
241    pub gravity_scale: f32,
242
243    /// Bit flags for body state (see RigidBodyFlags).
244    flags: u8,
245
246    /// Time the body has been below sleep thresholds (seconds).
247    /// When this exceeds PhysicsWorld::sleep_time_threshold, the body sleeps.
248    sleep_time: f32,
249}
250
251// =============================================================================
252// RigidBodyFlags
253// =============================================================================
254
255/// Bit flags for rigid body state.
256struct RigidBodyFlags;
257
258impl RigidBodyFlags {
259    /// Body is currently sleeping (optimization).
260    const SLEEPING: u8 = 1 << 0;
261    /// Body can sleep when idle.
262    const CAN_SLEEP: u8 = 1 << 1;
263    /// Continuous collision detection enabled.
264    const CONTINUOUS_CD: u8 = 1 << 2;
265    /// Fixed rotation (prevents rotation from collisions).
266    const FIXED_ROTATION: u8 = 1 << 3;
267}
268
269// =============================================================================
270// RigidBody Implementation
271// =============================================================================
272
273impl Default for RigidBody {
274    /// Creates a default dynamic rigid body.
275    fn default() -> Self {
276        Self::dynamic()
277    }
278}
279
280impl RigidBody {
281    // =========================================================================
282    // Constructors
283    // =========================================================================
284
285    /// Creates a new rigid body with custom parameters.
286    ///
287    /// # Arguments
288    ///
289    /// * `body_type` - Physics behavior type
290    ///
291    /// # Examples
292    ///
293    /// ```
294    /// use goud_engine::ecs::components::{RigidBody, RigidBodyType};
295    ///
296    /// let body = RigidBody::new(RigidBodyType::Dynamic);
297    /// ```
298    pub fn new(body_type: RigidBodyType) -> Self {
299        let (mass, inverse_mass) = if body_type == RigidBodyType::Dynamic {
300            (1.0, 1.0)
301        } else {
302            (0.0, 0.0) // Infinite mass for static/kinematic
303        };
304
305        Self {
306            body_type,
307            linear_velocity: Vec2::zero(),
308            angular_velocity: 0.0,
309            linear_damping: 0.01, // Slight air resistance
310            angular_damping: 0.01,
311            mass,
312            inverse_mass,
313            inertia: 1.0,
314            inverse_inertia: if body_type == RigidBodyType::Dynamic {
315                1.0
316            } else {
317                0.0
318            },
319            restitution: 0.0,                 // No bounce by default
320            friction: 0.5,                    // Moderate friction
321            gravity_scale: 1.0,               // Full gravity
322            flags: RigidBodyFlags::CAN_SLEEP, // Can sleep by default
323            sleep_time: 0.0,
324        }
325    }
326
327    /// Creates a dynamic rigid body (fully simulated).
328    ///
329    /// # Examples
330    ///
331    /// ```
332    /// use goud_engine::ecs::components::RigidBody;
333    ///
334    /// let player = RigidBody::dynamic()
335    ///     .with_mass(2.0);
336    /// ```
337    #[inline]
338    pub fn dynamic() -> Self {
339        Self::new(RigidBodyType::Dynamic)
340    }
341
342    /// Creates a kinematic rigid body (controlled velocity).
343    ///
344    /// # Examples
345    ///
346    /// ```
347    /// use goud_engine::ecs::components::RigidBody;
348    /// use goud_engine::core::math::Vec2;
349    ///
350    /// let platform = RigidBody::kinematic()
351    ///     .with_velocity(Vec2::new(50.0, 0.0));
352    /// ```
353    #[inline]
354    pub fn kinematic() -> Self {
355        Self::new(RigidBodyType::Kinematic)
356    }
357
358    /// Creates a static rigid body (immovable).
359    ///
360    /// # Examples
361    ///
362    /// ```
363    /// use goud_engine::ecs::components::RigidBody;
364    ///
365    /// let wall = RigidBody::static_body();
366    /// ```
367    #[inline]
368    pub fn static_body() -> Self {
369        Self::new(RigidBodyType::Static)
370    }
371
372    // =========================================================================
373    // Builder Pattern
374    // =========================================================================
375
376    /// Sets the linear velocity.
377    pub fn with_velocity(mut self, velocity: Vec2) -> Self {
378        self.linear_velocity = velocity;
379        self
380    }
381
382    /// Sets the angular velocity.
383    pub fn with_angular_velocity(mut self, angular_velocity: f32) -> Self {
384        self.angular_velocity = angular_velocity;
385        self
386    }
387
388    /// Sets the mass (dynamic bodies only).
389    ///
390    /// # Panics
391    ///
392    /// Panics if mass is not positive and finite.
393    pub fn with_mass(mut self, mass: f32) -> Self {
394        assert!(
395            mass > 0.0 && mass.is_finite(),
396            "Mass must be positive and finite"
397        );
398        self.mass = mass;
399        self.inverse_mass = 1.0 / mass;
400        self
401    }
402
403    /// Sets the linear damping.
404    pub fn with_linear_damping(mut self, damping: f32) -> Self {
405        self.linear_damping = damping.max(0.0);
406        self
407    }
408
409    /// Sets the angular damping.
410    pub fn with_angular_damping(mut self, damping: f32) -> Self {
411        self.angular_damping = damping.max(0.0);
412        self
413    }
414
415    /// Sets the restitution (bounciness).
416    pub fn with_restitution(mut self, restitution: f32) -> Self {
417        self.restitution = restitution.max(0.0);
418        self
419    }
420
421    /// Sets the friction coefficient.
422    pub fn with_friction(mut self, friction: f32) -> Self {
423        self.friction = friction.max(0.0);
424        self
425    }
426
427    /// Sets the gravity scale.
428    pub fn with_gravity_scale(mut self, scale: f32) -> Self {
429        self.gravity_scale = scale;
430        self
431    }
432
433    /// Sets whether the body can sleep.
434    pub fn with_can_sleep(mut self, can_sleep: bool) -> Self {
435        if can_sleep {
436            self.flags |= RigidBodyFlags::CAN_SLEEP;
437        } else {
438            self.flags &= !RigidBodyFlags::CAN_SLEEP;
439        }
440        self
441    }
442
443    /// Enables continuous collision detection (for fast-moving objects).
444    pub fn with_continuous_cd(mut self, enabled: bool) -> Self {
445        if enabled {
446            self.flags |= RigidBodyFlags::CONTINUOUS_CD;
447        } else {
448            self.flags &= !RigidBodyFlags::CONTINUOUS_CD;
449        }
450        self
451    }
452
453    /// Fixes the rotation (prevents rotation from collisions).
454    pub fn with_fixed_rotation(mut self, fixed: bool) -> Self {
455        if fixed {
456            self.flags |= RigidBodyFlags::FIXED_ROTATION;
457            self.angular_velocity = 0.0;
458            self.inertia = 0.0;
459            self.inverse_inertia = 0.0;
460        } else {
461            self.flags &= !RigidBodyFlags::FIXED_ROTATION;
462        }
463        self
464    }
465
466    // =========================================================================
467    // Accessors
468    // =========================================================================
469
470    /// Returns the body type.
471    #[inline]
472    pub fn body_type(&self) -> RigidBodyType {
473        self.body_type
474    }
475
476    /// Returns true if this is a dynamic body.
477    #[inline]
478    pub fn is_dynamic(&self) -> bool {
479        self.body_type == RigidBodyType::Dynamic
480    }
481
482    /// Returns true if this is a kinematic body.
483    #[inline]
484    pub fn is_kinematic(&self) -> bool {
485        self.body_type == RigidBodyType::Kinematic
486    }
487
488    /// Returns true if this is a static body.
489    #[inline]
490    pub fn is_static(&self) -> bool {
491        self.body_type == RigidBodyType::Static
492    }
493
494    /// Returns true if the body is currently sleeping.
495    #[inline]
496    pub fn is_sleeping(&self) -> bool {
497        self.flags & RigidBodyFlags::SLEEPING != 0
498    }
499
500    /// Returns true if the body can sleep.
501    #[inline]
502    pub fn can_sleep(&self) -> bool {
503        self.flags & RigidBodyFlags::CAN_SLEEP != 0
504    }
505
506    /// Returns true if continuous collision detection is enabled.
507    #[inline]
508    pub fn has_continuous_cd(&self) -> bool {
509        self.flags & RigidBodyFlags::CONTINUOUS_CD != 0
510    }
511
512    /// Returns true if rotation is fixed.
513    #[inline]
514    pub fn has_fixed_rotation(&self) -> bool {
515        self.flags & RigidBodyFlags::FIXED_ROTATION != 0
516    }
517
518    /// Returns the current sleep time.
519    #[inline]
520    pub fn sleep_time(&self) -> f32 {
521        self.sleep_time
522    }
523
524    /// Returns the linear speed (magnitude of velocity).
525    #[inline]
526    pub fn linear_speed(&self) -> f32 {
527        self.linear_velocity.length()
528    }
529
530    /// Returns the linear speed squared (avoids sqrt).
531    #[inline]
532    pub fn linear_speed_squared(&self) -> f32 {
533        self.linear_velocity.length_squared()
534    }
535
536    /// Returns the kinetic energy of the body (0.5 * m * v²).
537    #[inline]
538    pub fn kinetic_energy(&self) -> f32 {
539        0.5 * self.mass * self.linear_speed_squared()
540    }
541
542    // =========================================================================
543    // Mutators
544    // =========================================================================
545
546    /// Sets the linear velocity.
547    pub fn set_velocity(&mut self, velocity: Vec2) {
548        self.linear_velocity = velocity;
549        self.wake();
550    }
551
552    /// Sets the angular velocity.
553    pub fn set_angular_velocity(&mut self, angular_velocity: f32) {
554        self.angular_velocity = angular_velocity;
555        self.wake();
556    }
557
558    /// Sets the mass (dynamic bodies only).
559    ///
560    /// # Panics
561    ///
562    /// Panics if mass is not positive and finite, or if called on non-dynamic body.
563    pub fn set_mass(&mut self, mass: f32) {
564        assert!(self.is_dynamic(), "Cannot set mass on non-dynamic body");
565        assert!(
566            mass > 0.0 && mass.is_finite(),
567            "Mass must be positive and finite"
568        );
569        self.mass = mass;
570        self.inverse_mass = 1.0 / mass;
571    }
572
573    /// Sets the body type, updating mass accordingly.
574    pub fn set_body_type(&mut self, body_type: RigidBodyType) {
575        if self.body_type == body_type {
576            return;
577        }
578
579        self.body_type = body_type;
580
581        // Update mass/inertia based on new type
582        if body_type == RigidBodyType::Dynamic {
583            if self.mass == 0.0 {
584                self.mass = 1.0;
585            }
586            self.inverse_mass = 1.0 / self.mass;
587            if self.inertia == 0.0 {
588                self.inertia = 1.0;
589            }
590            self.inverse_inertia = 1.0 / self.inertia;
591        } else {
592            self.inverse_mass = 0.0;
593            self.inverse_inertia = 0.0;
594        }
595
596        self.wake();
597    }
598
599    // =========================================================================
600    // Physics Operations
601    // =========================================================================
602
603    /// Applies a force to the body (affects acceleration).
604    ///
605    /// Force is accumulated and applied during physics integration.
606    /// Only affects dynamic bodies.
607    ///
608    /// # Arguments
609    ///
610    /// * `_force` - Force vector in Newtons (kg·m/s²)
611    ///
612    /// # Note
613    ///
614    /// This is a placeholder for the public API. Forces will be accumulated
615    /// in a separate `Forces` component or applied directly during physics
616    /// integration in future implementation.
617    pub fn apply_force(&mut self, _force: Vec2) {
618        if !self.is_dynamic() {
619            return;
620        }
621        // Forces will be accumulated in a separate Forces component
622        // or applied directly during integration
623        // This is a placeholder for the public API
624        self.wake();
625    }
626
627    /// Applies an impulse to the body (instant velocity change).
628    ///
629    /// Impulse directly modifies velocity: Δv = impulse / mass
630    /// Only affects dynamic bodies.
631    ///
632    /// # Arguments
633    ///
634    /// * `impulse` - Impulse vector in kg·m/s
635    pub fn apply_impulse(&mut self, impulse: Vec2) {
636        if !self.is_dynamic() {
637            return;
638        }
639        self.linear_velocity = self.linear_velocity + impulse * self.inverse_mass;
640        self.wake();
641    }
642
643    /// Applies an angular impulse to the body.
644    ///
645    /// # Arguments
646    ///
647    /// * `impulse` - Angular impulse in kg·m²/s
648    pub fn apply_angular_impulse(&mut self, impulse: f32) {
649        if !self.is_dynamic() || self.has_fixed_rotation() {
650            return;
651        }
652        self.angular_velocity += impulse * self.inverse_inertia;
653        self.wake();
654    }
655
656    /// Applies damping to velocity (called by physics system).
657    ///
658    /// # Arguments
659    ///
660    /// * `dt` - Delta time in seconds
661    pub fn apply_damping(&mut self, dt: f32) {
662        // Exponential damping: v *= exp(-damping * dt) ≈ v *= (1 - damping * dt)
663        let linear_factor = 1.0 - self.linear_damping * dt;
664        let angular_factor = 1.0 - self.angular_damping * dt;
665
666        self.linear_velocity = self.linear_velocity * linear_factor.max(0.0);
667        self.angular_velocity *= angular_factor.max(0.0);
668    }
669
670    // =========================================================================
671    // Sleep Management
672    // =========================================================================
673
674    /// Puts the body to sleep (optimization).
675    ///
676    /// Sleeping bodies are excluded from physics updates until woken.
677    pub fn sleep(&mut self) {
678        self.flags |= RigidBodyFlags::SLEEPING;
679        self.linear_velocity = Vec2::zero();
680        self.angular_velocity = 0.0;
681        self.sleep_time = 0.0;
682    }
683
684    /// Wakes the body from sleep.
685    pub fn wake(&mut self) {
686        self.flags &= !RigidBodyFlags::SLEEPING;
687        self.sleep_time = 0.0;
688    }
689
690    /// Updates sleep time based on current motion (called by physics system).
691    ///
692    /// # Arguments
693    ///
694    /// * `dt` - Delta time in seconds
695    /// * `linear_threshold` - Linear velocity threshold for sleep (pixels/s)
696    /// * `angular_threshold` - Angular velocity threshold for sleep (radians/s)
697    ///
698    /// # Returns
699    ///
700    /// True if the body should sleep.
701    pub fn update_sleep_time(
702        &mut self,
703        dt: f32,
704        linear_threshold: f32,
705        angular_threshold: f32,
706    ) -> bool {
707        if !self.can_sleep() || !self.is_dynamic() {
708            self.sleep_time = 0.0;
709            return false;
710        }
711
712        // Check if motion is below thresholds
713        let below_threshold = self.linear_speed_squared() < linear_threshold * linear_threshold
714            && self.angular_velocity.abs() < angular_threshold;
715
716        if below_threshold {
717            self.sleep_time += dt;
718            true
719        } else {
720            self.sleep_time = 0.0;
721            false
722        }
723    }
724}
725
726// Implement Component trait for ECS integration
727impl Component for RigidBody {}
728
729// =============================================================================
730// Display Implementation
731// =============================================================================
732
733impl std::fmt::Display for RigidBody {
734    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
735        write!(
736            f,
737            "RigidBody({}, vel: {:?}, mass: {}, sleeping: {})",
738            self.body_type,
739            self.linear_velocity,
740            self.mass,
741            self.is_sleeping()
742        )
743    }
744}
745
746// =============================================================================
747// Tests
748// =============================================================================
749
750#[cfg(test)]
751mod tests {
752    use super::*;
753
754    // =========================================================================
755    // RigidBodyType Tests
756    // =========================================================================
757
758    #[test]
759    fn test_rigidbody_type_default() {
760        assert_eq!(RigidBodyType::default(), RigidBodyType::Dynamic);
761    }
762
763    #[test]
764    fn test_rigidbody_type_is_affected_by_gravity() {
765        assert!(RigidBodyType::Dynamic.is_affected_by_gravity());
766        assert!(!RigidBodyType::Kinematic.is_affected_by_gravity());
767        assert!(!RigidBodyType::Static.is_affected_by_gravity());
768    }
769
770    #[test]
771    fn test_rigidbody_type_is_affected_by_forces() {
772        assert!(RigidBodyType::Dynamic.is_affected_by_forces());
773        assert!(!RigidBodyType::Kinematic.is_affected_by_forces());
774        assert!(!RigidBodyType::Static.is_affected_by_forces());
775    }
776
777    #[test]
778    fn test_rigidbody_type_can_move() {
779        assert!(RigidBodyType::Dynamic.can_move());
780        assert!(RigidBodyType::Kinematic.can_move());
781        assert!(!RigidBodyType::Static.can_move());
782    }
783
784    #[test]
785    fn test_rigidbody_type_responds_to_collisions() {
786        assert!(RigidBodyType::Dynamic.responds_to_collisions());
787        assert!(!RigidBodyType::Kinematic.responds_to_collisions());
788        assert!(!RigidBodyType::Static.responds_to_collisions());
789    }
790
791    #[test]
792    fn test_rigidbody_type_name() {
793        assert_eq!(RigidBodyType::Dynamic.name(), "Dynamic");
794        assert_eq!(RigidBodyType::Kinematic.name(), "Kinematic");
795        assert_eq!(RigidBodyType::Static.name(), "Static");
796    }
797
798    #[test]
799    fn test_rigidbody_type_display() {
800        assert_eq!(format!("{}", RigidBodyType::Dynamic), "Dynamic");
801        assert_eq!(format!("{}", RigidBodyType::Kinematic), "Kinematic");
802        assert_eq!(format!("{}", RigidBodyType::Static), "Static");
803    }
804
805    // =========================================================================
806    // RigidBody Construction Tests
807    // =========================================================================
808
809    #[test]
810    fn test_rigidbody_new_dynamic() {
811        let body = RigidBody::new(RigidBodyType::Dynamic);
812        assert_eq!(body.body_type, RigidBodyType::Dynamic);
813        assert_eq!(body.mass, 1.0);
814        assert_eq!(body.inverse_mass, 1.0);
815        assert!(body.can_sleep());
816        assert!(!body.is_sleeping());
817    }
818
819    #[test]
820    fn test_rigidbody_new_kinematic() {
821        let body = RigidBody::new(RigidBodyType::Kinematic);
822        assert_eq!(body.body_type, RigidBodyType::Kinematic);
823        assert_eq!(body.mass, 0.0);
824        assert_eq!(body.inverse_mass, 0.0);
825    }
826
827    #[test]
828    fn test_rigidbody_new_static() {
829        let body = RigidBody::new(RigidBodyType::Static);
830        assert_eq!(body.body_type, RigidBodyType::Static);
831        assert_eq!(body.mass, 0.0);
832        assert_eq!(body.inverse_mass, 0.0);
833    }
834
835    #[test]
836    fn test_rigidbody_dynamic() {
837        let body = RigidBody::dynamic();
838        assert!(body.is_dynamic());
839        assert!(!body.is_kinematic());
840        assert!(!body.is_static());
841    }
842
843    #[test]
844    fn test_rigidbody_kinematic() {
845        let body = RigidBody::kinematic();
846        assert!(!body.is_dynamic());
847        assert!(body.is_kinematic());
848        assert!(!body.is_static());
849    }
850
851    #[test]
852    fn test_rigidbody_static_body() {
853        let body = RigidBody::static_body();
854        assert!(!body.is_dynamic());
855        assert!(!body.is_kinematic());
856        assert!(body.is_static());
857    }
858
859    #[test]
860    fn test_rigidbody_default() {
861        let body = RigidBody::default();
862        assert!(body.is_dynamic());
863        assert_eq!(body.mass, 1.0);
864    }
865
866    // =========================================================================
867    // Builder Pattern Tests
868    // =========================================================================
869
870    #[test]
871    fn test_rigidbody_with_velocity() {
872        let body = RigidBody::dynamic().with_velocity(Vec2::new(100.0, 50.0));
873        assert_eq!(body.linear_velocity, Vec2::new(100.0, 50.0));
874    }
875
876    #[test]
877    fn test_rigidbody_with_angular_velocity() {
878        let body = RigidBody::dynamic().with_angular_velocity(3.14);
879        assert_eq!(body.angular_velocity, 3.14);
880    }
881
882    #[test]
883    fn test_rigidbody_with_mass() {
884        let body = RigidBody::dynamic().with_mass(2.0);
885        assert_eq!(body.mass, 2.0);
886        assert_eq!(body.inverse_mass, 0.5);
887    }
888
889    #[test]
890    #[should_panic(expected = "Mass must be positive and finite")]
891    fn test_rigidbody_with_mass_zero() {
892        let _ = RigidBody::dynamic().with_mass(0.0);
893    }
894
895    #[test]
896    #[should_panic(expected = "Mass must be positive and finite")]
897    fn test_rigidbody_with_mass_negative() {
898        let _ = RigidBody::dynamic().with_mass(-1.0);
899    }
900
901    #[test]
902    fn test_rigidbody_with_damping() {
903        let body = RigidBody::dynamic()
904            .with_linear_damping(0.5)
905            .with_angular_damping(0.3);
906        assert_eq!(body.linear_damping, 0.5);
907        assert_eq!(body.angular_damping, 0.3);
908    }
909
910    #[test]
911    fn test_rigidbody_with_restitution() {
912        let body = RigidBody::dynamic().with_restitution(0.8);
913        assert_eq!(body.restitution, 0.8);
914    }
915
916    #[test]
917    fn test_rigidbody_with_friction() {
918        let body = RigidBody::dynamic().with_friction(0.7);
919        assert_eq!(body.friction, 0.7);
920    }
921
922    #[test]
923    fn test_rigidbody_with_gravity_scale() {
924        let body = RigidBody::dynamic().with_gravity_scale(2.0);
925        assert_eq!(body.gravity_scale, 2.0);
926    }
927
928    #[test]
929    fn test_rigidbody_with_can_sleep() {
930        let body1 = RigidBody::dynamic().with_can_sleep(true);
931        assert!(body1.can_sleep());
932
933        let body2 = RigidBody::dynamic().with_can_sleep(false);
934        assert!(!body2.can_sleep());
935    }
936
937    #[test]
938    fn test_rigidbody_with_continuous_cd() {
939        let body1 = RigidBody::dynamic().with_continuous_cd(true);
940        assert!(body1.has_continuous_cd());
941
942        let body2 = RigidBody::dynamic().with_continuous_cd(false);
943        assert!(!body2.has_continuous_cd());
944    }
945
946    #[test]
947    fn test_rigidbody_with_fixed_rotation() {
948        let body = RigidBody::dynamic()
949            .with_angular_velocity(5.0)
950            .with_fixed_rotation(true);
951
952        assert!(body.has_fixed_rotation());
953        assert_eq!(body.angular_velocity, 0.0);
954        assert_eq!(body.inertia, 0.0);
955        assert_eq!(body.inverse_inertia, 0.0);
956    }
957
958    #[test]
959    fn test_rigidbody_builder_chaining() {
960        let body = RigidBody::dynamic()
961            .with_velocity(Vec2::new(100.0, 50.0))
962            .with_mass(2.0)
963            .with_restitution(0.8)
964            .with_friction(0.5)
965            .with_gravity_scale(1.5);
966
967        assert_eq!(body.linear_velocity, Vec2::new(100.0, 50.0));
968        assert_eq!(body.mass, 2.0);
969        assert_eq!(body.restitution, 0.8);
970        assert_eq!(body.friction, 0.5);
971        assert_eq!(body.gravity_scale, 1.5);
972    }
973
974    // =========================================================================
975    // Accessor Tests
976    // =========================================================================
977
978    #[test]
979    fn test_rigidbody_linear_speed() {
980        let body = RigidBody::dynamic().with_velocity(Vec2::new(3.0, 4.0));
981        assert_eq!(body.linear_speed(), 5.0);
982    }
983
984    #[test]
985    fn test_rigidbody_linear_speed_squared() {
986        let body = RigidBody::dynamic().with_velocity(Vec2::new(3.0, 4.0));
987        assert_eq!(body.linear_speed_squared(), 25.0);
988    }
989
990    #[test]
991    fn test_rigidbody_kinetic_energy() {
992        let body = RigidBody::dynamic()
993            .with_mass(2.0)
994            .with_velocity(Vec2::new(3.0, 4.0));
995        // KE = 0.5 * m * v² = 0.5 * 2.0 * 25.0 = 25.0
996        assert_eq!(body.kinetic_energy(), 25.0);
997    }
998
999    // =========================================================================
1000    // Mutator Tests
1001    // =========================================================================
1002
1003    #[test]
1004    fn test_rigidbody_set_velocity() {
1005        let mut body = RigidBody::dynamic();
1006        body.set_velocity(Vec2::new(100.0, 50.0));
1007        assert_eq!(body.linear_velocity, Vec2::new(100.0, 50.0));
1008        assert!(!body.is_sleeping()); // Should wake
1009    }
1010
1011    #[test]
1012    fn test_rigidbody_set_angular_velocity() {
1013        let mut body = RigidBody::dynamic();
1014        body.set_angular_velocity(3.14);
1015        assert_eq!(body.angular_velocity, 3.14);
1016        assert!(!body.is_sleeping()); // Should wake
1017    }
1018
1019    #[test]
1020    fn test_rigidbody_set_mass() {
1021        let mut body = RigidBody::dynamic();
1022        body.set_mass(2.0);
1023        assert_eq!(body.mass, 2.0);
1024        assert_eq!(body.inverse_mass, 0.5);
1025    }
1026
1027    #[test]
1028    #[should_panic(expected = "Cannot set mass on non-dynamic body")]
1029    fn test_rigidbody_set_mass_kinematic() {
1030        let mut body = RigidBody::kinematic();
1031        body.set_mass(2.0);
1032    }
1033
1034    #[test]
1035    fn test_rigidbody_set_body_type() {
1036        let mut body = RigidBody::dynamic();
1037        body.set_body_type(RigidBodyType::Kinematic);
1038
1039        assert!(body.is_kinematic());
1040        assert_eq!(body.inverse_mass, 0.0);
1041        assert_eq!(body.inverse_inertia, 0.0);
1042    }
1043
1044    // =========================================================================
1045    // Physics Operations Tests
1046    // =========================================================================
1047
1048    #[test]
1049    fn test_rigidbody_apply_impulse() {
1050        let mut body = RigidBody::dynamic().with_mass(2.0);
1051
1052        body.apply_impulse(Vec2::new(10.0, 0.0));
1053        // Δv = impulse / mass = 10.0 / 2.0 = 5.0
1054        assert_eq!(body.linear_velocity, Vec2::new(5.0, 0.0));
1055    }
1056
1057    #[test]
1058    fn test_rigidbody_apply_impulse_kinematic() {
1059        let mut body = RigidBody::kinematic();
1060        let initial_velocity = body.linear_velocity;
1061
1062        body.apply_impulse(Vec2::new(10.0, 0.0));
1063        // Should not affect kinematic body
1064        assert_eq!(body.linear_velocity, initial_velocity);
1065    }
1066
1067    #[test]
1068    fn test_rigidbody_apply_angular_impulse() {
1069        let mut body = RigidBody::dynamic();
1070        body.apply_angular_impulse(5.0);
1071        // Δω = impulse / inertia = 5.0 / 1.0 = 5.0
1072        assert_eq!(body.angular_velocity, 5.0);
1073    }
1074
1075    #[test]
1076    fn test_rigidbody_apply_angular_impulse_fixed_rotation() {
1077        let mut body = RigidBody::dynamic().with_fixed_rotation(true);
1078
1079        body.apply_angular_impulse(5.0);
1080        // Should not affect body with fixed rotation
1081        assert_eq!(body.angular_velocity, 0.0);
1082    }
1083
1084    #[test]
1085    fn test_rigidbody_apply_damping() {
1086        let mut body = RigidBody::dynamic()
1087            .with_velocity(Vec2::new(100.0, 0.0))
1088            .with_angular_velocity(10.0)
1089            .with_linear_damping(0.1)
1090            .with_angular_damping(0.1);
1091
1092        body.apply_damping(0.1); // 0.1 seconds
1093
1094        // Velocity should decrease
1095        assert!(body.linear_velocity.x < 100.0);
1096        assert!(body.angular_velocity < 10.0);
1097    }
1098
1099    // =========================================================================
1100    // Sleep Management Tests
1101    // =========================================================================
1102
1103    #[test]
1104    fn test_rigidbody_sleep() {
1105        let mut body = RigidBody::dynamic()
1106            .with_velocity(Vec2::new(100.0, 50.0))
1107            .with_angular_velocity(5.0);
1108
1109        body.sleep();
1110
1111        assert!(body.is_sleeping());
1112        assert_eq!(body.linear_velocity, Vec2::zero());
1113        assert_eq!(body.angular_velocity, 0.0);
1114        assert_eq!(body.sleep_time, 0.0);
1115    }
1116
1117    #[test]
1118    fn test_rigidbody_wake() {
1119        let mut body = RigidBody::dynamic();
1120        body.sleep();
1121        assert!(body.is_sleeping());
1122
1123        body.wake();
1124        assert!(!body.is_sleeping());
1125        assert_eq!(body.sleep_time, 0.0);
1126    }
1127
1128    #[test]
1129    fn test_rigidbody_update_sleep_time_below_threshold() {
1130        let mut body = RigidBody::dynamic().with_velocity(Vec2::new(1.0, 1.0));
1131
1132        let should_sleep = body.update_sleep_time(0.1, 5.0, 0.1);
1133        assert!(should_sleep);
1134        assert!(body.sleep_time > 0.0);
1135    }
1136
1137    #[test]
1138    fn test_rigidbody_update_sleep_time_above_threshold() {
1139        let mut body = RigidBody::dynamic().with_velocity(Vec2::new(100.0, 0.0));
1140
1141        let should_sleep = body.update_sleep_time(0.1, 5.0, 0.1);
1142        assert!(!should_sleep);
1143        assert_eq!(body.sleep_time, 0.0);
1144    }
1145
1146    #[test]
1147    fn test_rigidbody_update_sleep_time_cannot_sleep() {
1148        let mut body = RigidBody::dynamic()
1149            .with_can_sleep(false)
1150            .with_velocity(Vec2::new(1.0, 1.0));
1151
1152        let should_sleep = body.update_sleep_time(0.1, 5.0, 0.1);
1153        assert!(!should_sleep);
1154        assert_eq!(body.sleep_time, 0.0);
1155    }
1156
1157    // =========================================================================
1158    // Component and Display Tests
1159    // =========================================================================
1160
1161    #[test]
1162    fn test_rigidbody_is_component() {
1163        fn requires_component<T: Component>() {}
1164        requires_component::<RigidBody>();
1165    }
1166
1167    #[test]
1168    fn test_rigidbody_display() {
1169        let body = RigidBody::dynamic().with_velocity(Vec2::new(100.0, 50.0));
1170
1171        let display = format!("{}", body);
1172        assert!(display.contains("RigidBody"));
1173        assert!(display.contains("Dynamic"));
1174        assert!(display.contains("vel"));
1175        assert!(display.contains("mass"));
1176    }
1177
1178    #[test]
1179    fn test_rigidbody_debug() {
1180        let body = RigidBody::dynamic();
1181        let debug = format!("{:?}", body);
1182        assert!(debug.contains("RigidBody"));
1183    }
1184
1185    #[test]
1186    fn test_rigidbody_clone() {
1187        let body1 = RigidBody::dynamic()
1188            .with_velocity(Vec2::new(100.0, 50.0))
1189            .with_mass(2.0);
1190
1191        let body2 = body1;
1192        assert_eq!(body1.linear_velocity, body2.linear_velocity);
1193        assert_eq!(body1.mass, body2.mass);
1194    }
1195
1196    // =========================================================================
1197    // Thread Safety Tests
1198    // =========================================================================
1199
1200    #[test]
1201    fn test_rigidbody_is_send() {
1202        fn requires_send<T: Send>() {}
1203        requires_send::<RigidBody>();
1204    }
1205
1206    #[test]
1207    fn test_rigidbody_is_sync() {
1208        fn requires_sync<T: Sync>() {}
1209        requires_sync::<RigidBody>();
1210    }
1211}