Skip to main content

dynamis_model/
body.rs

1use crate::collider::ColliderDesc;
2use crate::shape::{Shape, ShapeSourceHandle};
3
4#[derive(Clone, Copy, Debug, PartialEq)]
5pub struct BodyHandle {
6    pub id: u32,
7    pub generation: u32,
8}
9
10#[derive(Clone, Copy, Debug, PartialEq)]
11pub struct BodyState {
12    pub position: [f32; 3],
13    pub prev_position: [f32; 3],
14    pub orientation: [f32; 4],
15    pub velocity: [f32; 3],
16    pub angular_velocity: [f32; 3],
17    pub inverse_mass: f32,
18    pub com: [f32; 3],
19    pub sleeping: bool,
20    pub step: u64,
21}
22
23pub const DEFAULT_COLLISION_GROUP: u32 = 0x0000_0001;
24pub const DEFAULT_COLLISION_MASK: u32 = 0xFFFF_FFFF;
25pub const MAX_COLLIDERS_PER_BODY: usize = 16;
26
27#[derive(Clone, Debug)]
28pub struct BodyDesc {
29    pub colliders: Vec<ColliderDesc>,
30    pub position: [f32; 3],
31    pub orientation: [f32; 4],
32    pub velocity: [f32; 3],
33    pub angular_velocity: [f32; 3],
34    pub mass: f32,
35    pub density: Option<f32>,
36    pub com: Option<[f32; 3]>,
37    pub inertia: Option<[f32; 6]>,
38    pub collision_group: u32,
39    pub collision_mask: u32,
40    pub linear_damping: Option<f32>,
41    pub angular_damping: Option<f32>,
42    pub gravity_scale: f32,
43    pub sleep_velocity: Option<f32>,
44    pub sleep_angular_velocity: Option<f32>,
45    pub kinematic: bool,
46    pub ccd: bool,
47}
48
49impl BodyDesc {
50    pub fn new(collider: ColliderDesc) -> Self {
51        Self {
52            colliders: vec![collider],
53            position: [0.0; 3],
54            orientation: [0.0, 0.0, 0.0, 1.0],
55            velocity: [0.0; 3],
56            angular_velocity: [0.0; 3],
57            mass: 1.0,
58            density: None,
59            com: None,
60            inertia: None,
61            collision_group: DEFAULT_COLLISION_GROUP,
62            collision_mask: DEFAULT_COLLISION_MASK,
63            linear_damping: None,
64            angular_damping: None,
65            gravity_scale: 1.0,
66            sleep_velocity: None,
67            sleep_angular_velocity: None,
68            kinematic: false,
69            ccd: false,
70        }
71    }
72
73    pub fn collider(mut self, collider: ColliderDesc) -> Self {
74        assert!(
75            self.colliders.len() < MAX_COLLIDERS_PER_BODY,
76            "a body supports at most {MAX_COLLIDERS_PER_BODY} colliders"
77        );
78        self.colliders.push(collider);
79        self
80    }
81
82    pub fn sphere(radius: f32) -> Self {
83        Self::new(ColliderDesc::new(Shape::sphere(radius)))
84    }
85
86    pub fn cuboid(half_extents: [f32; 3]) -> Self {
87        Self::new(ColliderDesc::new(Shape::cuboid(half_extents)))
88    }
89
90    pub fn capsule(radius: f32, half_height: f32) -> Self {
91        Self::new(ColliderDesc::new(Shape::capsule(radius, half_height)))
92    }
93
94    pub fn cylinder(radius: f32, half_height: f32) -> Self {
95        Self::new(ColliderDesc::new(Shape::cylinder(radius, half_height)))
96    }
97
98    pub fn static_sphere(radius: f32) -> Self {
99        Self {
100            mass: 0.0,
101            ..Self::sphere(radius)
102        }
103    }
104
105    pub fn compound(handles: &[ShapeSourceHandle]) -> Self {
106        assert!(
107            !handles.is_empty() && handles.len() <= MAX_COLLIDERS_PER_BODY,
108            "a compound body requires between 1 and {MAX_COLLIDERS_PER_BODY} hulls"
109        );
110        let mut members = handles.iter().copied();
111        let first = members.next().expect("compound body hulls are non-empty");
112        let mut body = Self::new(ColliderDesc::new(Shape::hull(first)));
113        for handle in members {
114            body = body.collider(ColliderDesc::new(Shape::hull(handle)));
115        }
116        body
117    }
118
119    pub fn inverse_mass(&self) -> f32 {
120        if self.kinematic || self.mass <= 0.0 {
121            0.0
122        } else {
123            1.0 / self.mass
124        }
125    }
126
127    pub fn position(mut self, position: [f32; 3]) -> Self {
128        self.position = position;
129        self
130    }
131
132    pub fn restitution(mut self, restitution: f32) -> Self {
133        self.colliders[0].restitution = restitution;
134        self
135    }
136
137    pub fn friction(mut self, friction: f32) -> Self {
138        assert!(friction >= 0.0, "friction must be non-negative");
139        self.colliders[0].friction = friction;
140        self
141    }
142
143    pub fn sensor(mut self, sensor: bool) -> Self {
144        assert!(
145            !self.colliders.is_empty(),
146            "a body needs at least one collider"
147        );
148        self.colliders[0].sensor = sensor;
149        self
150    }
151
152    pub fn orientation(mut self, orientation: [f32; 4]) -> Self {
153        assert!(
154            (orientation[0] * orientation[0]
155                + orientation[1] * orientation[1]
156                + orientation[2] * orientation[2]
157                + orientation[3] * orientation[3]
158                - 1.0)
159                .abs()
160                < 1e-4,
161            "orientation must be a unit quaternion"
162        );
163        self.orientation = orientation;
164        self
165    }
166
167    pub fn velocity(mut self, velocity: [f32; 3]) -> Self {
168        self.velocity = velocity;
169        self
170    }
171
172    pub fn angular_velocity(mut self, angular_velocity: [f32; 3]) -> Self {
173        self.angular_velocity = angular_velocity;
174        self
175    }
176
177    pub fn mass(mut self, mass: f32) -> Self {
178        assert!(mass >= 0.0, "mass must be non-negative");
179        self.mass = mass;
180        self.density = None;
181        self
182    }
183
184    pub fn density(mut self, density: f32) -> Self {
185        assert!(density >= 0.0, "density must be non-negative");
186        self.density = Some(density);
187        self
188    }
189
190    pub fn damping(mut self, damping: f32) -> Self {
191        assert!(damping >= 0.0, "damping must be non-negative");
192        self.linear_damping = Some(damping);
193        self
194    }
195
196    pub fn angular_damping(mut self, angular_damping: f32) -> Self {
197        assert!(
198            angular_damping >= 0.0,
199            "angular damping must be non-negative"
200        );
201        self.angular_damping = Some(angular_damping);
202        self
203    }
204
205    pub fn gravity_scale(mut self, gravity_scale: f32) -> Self {
206        self.gravity_scale = gravity_scale;
207        self
208    }
209
210    pub fn sleep_thresholds(mut self, velocity: f32, angular_velocity: f32) -> Self {
211        assert!(velocity >= 0.0, "sleep velocity must be non-negative");
212        assert!(
213            angular_velocity >= 0.0,
214            "sleep angular velocity must be non-negative"
215        );
216        self.sleep_velocity = Some(velocity);
217        self.sleep_angular_velocity = Some(angular_velocity);
218        self
219    }
220
221    pub fn com(mut self, com: [f32; 3]) -> Self {
222        self.com = Some(com);
223        self
224    }
225
226    pub fn inertia(mut self, inertia: [f32; 6]) -> Self {
227        assert!(
228            inertia.iter().all(|value| value.is_finite()),
229            "inertia tensor must be finite"
230        );
231        self.inertia = Some(inertia);
232        self
233    }
234
235    pub fn mass_properties(
236        &self,
237        bounds: impl Fn(&Shape) -> Option<([f32; 3], [f32; 3])>,
238    ) -> crate::mass::MassProperties {
239        if let Some(inertia) = self.inertia {
240            return crate::mass::MassProperties {
241                com: self.com.unwrap_or([0.0; 3]),
242                inverse_inertia: crate::mass::inertia_inverse(inertia),
243            };
244        }
245        let source = match self.density {
246            Some(density) => crate::mass::MassSource::Density(density),
247            None => crate::mass::MassSource::Fixed(self.mass),
248        };
249        crate::mass::compute_mass_properties(&self.colliders, source, self.com, bounds)
250    }
251
252    pub fn effective_mass(&self, bounds: impl Fn(&Shape) -> Option<([f32; 3], [f32; 3])>) -> f32 {
253        match self.density {
254            Some(density) => density * crate::mass::solid_volume_of(&self.colliders, &bounds),
255            None => self.mass,
256        }
257    }
258
259    pub fn collision_group(mut self, group: u32) -> Self {
260        self.collision_group = group;
261        self
262    }
263
264    pub fn collision_mask(mut self, mask: u32) -> Self {
265        self.collision_mask = mask;
266        self
267    }
268
269    pub fn kinematic(mut self, kinematic: bool) -> Self {
270        self.kinematic = kinematic;
271        self
272    }
273
274    pub fn ccd(mut self, ccd: bool) -> Self {
275        self.ccd = ccd;
276        self
277    }
278}