//! The [`RigidBody`] component and its [`RigidBodyKind`]. //! //! A rigid body is the dynamics half of a physics object: it says *how* an //! entity moves under the simulation (or that it does not). The *shape* it //! collides with is a separate [`Collider`](crate::Collider) component, so the //! two concerns compose — a body with no collider is a point mass, a collider //! with no body is static world geometry. //! //! Like every Oxide component these are plain serializable data with //! `#[derive(Reflect)]`, so they are editable from the inspector and from //! scripts with no per-type editor code. The simulation reads them when it //! builds the rapier world; the //! authored fields here are inputs, never live simulation state (velocities and //! poses live on the [`Transform`](oxide_engine::math::Transform) and inside //! rapier). use oxide_engine::reflect::{Reflect, ReflectEnum}; use serde::{Deserialize, Serialize}; /// How a [`RigidBody`] participates in the simulation. /// /// Mirrors rapier's body types, named for engine users: /// - [`Dynamic`](Self::Dynamic) — fully simulated; moved by gravity, forces, /// and contacts. /// - [`Kinematic`](Self::Kinematic) — moved only by the game (its /// [`Transform`](oxide_engine::math::Transform)); pushes dynamic bodies but is /// never pushed back. The basis for the character controller (Stage 9 later /// piece) and for moving platforms. /// - [`Static`](Self::Static) — never moves; immovable world geometry (floors, /// walls). The cheapest body — no integration cost. #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default, Serialize, Deserialize, ReflectEnum)] pub enum RigidBodyKind { /// Fully simulated — gravity, forces, and contacts move it. #[default] Dynamic, /// Moved only by the game; affects dynamics but is not affected by them. Kinematic, /// Immovable world geometry. Static, } impl RigidBodyKind { /// Every kind, for editor menus. pub const ALL: [RigidBodyKind; 3] = [ RigidBodyKind::Dynamic, RigidBodyKind::Kinematic, RigidBodyKind::Static, ]; /// A human-readable label. pub fn label(self) -> &'static str { match self { RigidBodyKind::Dynamic => "Dynamic", RigidBodyKind::Kinematic => "Kinematic", RigidBodyKind::Static => "Static", } } } /// Component: the dynamics of a physics object. /// /// Attach alongside a [`Collider`](crate::Collider) to make an entity take part /// in the simulation. The fields are authoring inputs consumed when the rapier /// body is created; runtime velocity/pose are not stored here (the /// [`Transform`](oxide_engine::math::Transform) is the authoritative pose, which /// the simulation writes back each step). #[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize, Reflect)] pub struct RigidBody { /// How the body participates in the simulation. pub kind: RigidBodyKind, /// Mass in kilograms. Ignored for non-dynamic bodies. `0` means "derive the /// mass from the collider's [`density`](crate::Collider::density)". pub mass: f32, /// Linear velocity damping (drag), `0` = none. Slows translation over time. pub linear_damping: f32, /// Angular velocity damping, `0` = none. Slows rotation over time. pub angular_damping: f32, /// Per-body multiplier on global gravity (`1` = normal, `0` = floats, /// negative = anti-gravity). Lets some bodies ignore gravity individually. pub gravity_scale: f32, /// Enable continuous collision detection — prevents fast bodies tunnelling /// through thin geometry, at extra cost. Off by default. pub ccd: bool, } impl Default for RigidBody { fn default() -> Self { Self { kind: RigidBodyKind::Dynamic, // 0 = derive mass from collider density (rapier's default behaviour). mass: 0.0, linear_damping: 0.0, angular_damping: 0.0, gravity_scale: 1.0, ccd: false, } } } impl RigidBody { /// A static (immovable) body — convenience for world geometry. pub fn static_body() -> Self { Self { kind: RigidBodyKind::Static, ..Self::default() } } /// A kinematic body — moved by the game, not by the simulation. pub fn kinematic() -> Self { Self { kind: RigidBodyKind::Kinematic, ..Self::default() } } } #[cfg(test)] mod tests { use super::*; #[test] fn default_is_a_dynamic_body() { let b = RigidBody::default(); assert_eq!(b.kind, RigidBodyKind::Dynamic); assert_eq!(b.gravity_scale, 1.0); assert!(!b.ccd); } #[test] fn constructors_set_the_kind() { assert_eq!(RigidBody::static_body().kind, RigidBodyKind::Static); assert_eq!(RigidBody::kinematic().kind, RigidBodyKind::Kinematic); } #[test] fn round_trips_through_ron() { let b = RigidBody { kind: RigidBodyKind::Kinematic, mass: 2.5, linear_damping: 0.1, angular_damping: 0.2, gravity_scale: 0.0, ccd: true, }; let ron = ron::to_string(&b).unwrap(); let back: RigidBody = ron::from_str(&ron).unwrap(); assert_eq!(b, back); } #[test] fn kind_variants_are_reflected_for_a_combo() { // The editor lists these in a dropdown via ReflectEnum. assert_eq!( RigidBodyKind::variants(), &["Dynamic", "Kinematic", "Static"] ); } }