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}