1use he3d_math::{Quat, Vec3};
2
3use crate::aabb::Aabb;
4use crate::shape::{MassProperties, Shape};
5
6#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
9#[non_exhaustive]
10pub enum MotionType {
11 #[default]
15 Dynamic,
16 Static,
19 Kinematic,
24}
25
26impl MotionType {
27 #[must_use]
30 pub fn is_simulated(self) -> bool {
31 matches!(self, Self::Dynamic)
32 }
33
34 #[must_use]
37 pub fn moves(self) -> bool {
38 !matches!(self, Self::Static)
39 }
40
41 #[must_use]
44 pub fn is_pushable(self) -> bool {
45 matches!(self, Self::Dynamic)
46 }
47}
48
49#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
58pub struct BodyId {
59 index: u32,
60 generation: u32,
61}
62
63impl BodyId {
64 pub(crate) const fn new(index: u32, generation: u32) -> Self {
67 Self { index, generation }
68 }
69
70 #[must_use]
73 pub const fn index(self) -> u32 {
74 self.index
75 }
76
77 #[must_use]
80 pub const fn generation(self) -> u32 {
81 self.generation
82 }
83}
84
85pub const DEFAULT_FRICTION: f32 = 0.5;
94
95pub(crate) const NO_SLEEP_GROUP: u32 = u32::MAX;
98
99#[derive(Clone, Copy, Debug, PartialEq)]
107#[non_exhaustive]
108pub struct BodyDesc {
109 pub shape: Shape,
112 pub motion: MotionType,
115 pub mass: f32,
118 pub position: Vec3,
121 pub orientation: Quat,
124 pub linear_velocity: Vec3,
127 pub angular_velocity: Vec3,
130 pub friction: f32,
133 pub restitution: f32,
136 pub linear_damping: f32,
139 pub gravity_scale: f32,
143 pub angular_damping: f32,
146}
147
148impl BodyDesc {
149 #[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 #[must_use]
173 pub fn dynamic(shape: Shape, mass: f32) -> Self {
174 Self::new(shape, MotionType::Dynamic).with_mass(mass)
175 }
176
177 #[must_use]
180 pub fn stationary(shape: Shape) -> Self {
181 Self::new(shape, MotionType::Static)
182 }
183
184 #[must_use]
187 pub fn kinematic(shape: Shape) -> Self {
188 Self::new(shape, MotionType::Kinematic)
189 }
190
191 #[must_use]
194 pub fn with_mass(mut self, mass: f32) -> Self {
195 self.mass = mass;
196 self
197 }
198
199 #[must_use]
202 pub fn at(mut self, position: Vec3) -> Self {
203 self.position = position;
204 self
205 }
206
207 #[must_use]
210 pub fn oriented(mut self, orientation: Quat) -> Self {
211 self.orientation = orientation;
212 self
213 }
214
215 #[must_use]
218 pub fn with_velocity(mut self, velocity: Vec3) -> Self {
219 self.linear_velocity = velocity;
220 self
221 }
222
223 #[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 #[must_use]
234 pub fn with_friction(mut self, friction: f32) -> Self {
235 self.friction = friction;
236 self
237 }
238
239 #[must_use]
242 pub fn with_restitution(mut self, restitution: f32) -> Self {
243 self.restitution = restitution;
244 self
245 }
246
247 #[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 #[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#[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 pub(crate) pseudo_linear_velocity: Vec3,
301 pub(crate) pseudo_angular_velocity: Vec3,
304 pub(crate) friction: f32,
305 pub(crate) restitution: f32,
306 pub(crate) linear_damping: f32,
307 pub(crate) gravity_scale: f32,
310 pub(crate) angular_damping: f32,
311 pub(crate) sleeping: bool,
313 pub(crate) sleep_timer: f32,
315 pub(crate) was_moving: bool,
325 pub(crate) sleep_group: u32,
333}
334
335impl Body {
336 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 pub(crate) fn aabb(&self) -> Aabb {
374 self.shape.aabb(self.position, self.orientation)
375 }
376
377 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 pub(crate) fn point_velocity(&self, anchor: Vec3) -> Vec3 {
392 self.linear_velocity + self.angular_velocity.cross(anchor)
393 }
394
395 pub(crate) fn pseudo_point_velocity(&self, anchor: Vec3) -> Vec3 {
398 self.pseudo_linear_velocity + self.pseudo_angular_velocity.cross(anchor)
399 }
400
401 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 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 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 pub(crate) fn wake(&mut self) {
426 self.sleeping = false;
427 self.sleep_timer = 0.0;
428 }
429
430 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 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 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}