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
23const DEFAULT_COLLISION_GROUP: u32 = 0x0000_0001;
24const 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 let first = handles
107 .first()
108 .expect("compound body requires at least one hull");
109 assert!(
110 handles.len() <= MAX_COLLIDERS_PER_BODY,
111 "a compound body takes at most {MAX_COLLIDERS_PER_BODY} hulls"
112 );
113 let mut body = Self::new(ColliderDesc::new(Shape::hull(*first)));
114 for handle in &handles[1..] {
115 body = body.collider(ColliderDesc::new(Shape::hull(*handle)));
116 }
117 body
118 }
119
120 pub fn inverse_mass(&self) -> f32 {
121 if self.kinematic || self.mass <= 0.0 {
122 0.0
123 } else {
124 1.0 / self.mass
125 }
126 }
127
128 pub fn position(mut self, position: [f32; 3]) -> Self {
129 self.position = position;
130 self
131 }
132
133 pub fn restitution(mut self, restitution: f32) -> Self {
134 self.colliders[0].restitution = restitution;
135 self
136 }
137
138 pub fn friction(mut self, friction: f32) -> Self {
139 assert!(friction >= 0.0, "friction must be non-negative");
140 self.colliders[0].friction = friction;
141 self
142 }
143
144 pub fn sensor(mut self, sensor: bool) -> Self {
145 self.colliders[0].sensor = sensor;
146 self
147 }
148
149 pub fn orientation(mut self, orientation: [f32; 4]) -> Self {
150 assert!(
151 (orientation[0] * orientation[0]
152 + orientation[1] * orientation[1]
153 + orientation[2] * orientation[2]
154 + orientation[3] * orientation[3]
155 - 1.0)
156 .abs()
157 < 1e-4,
158 "orientation must be a unit quaternion"
159 );
160 self.orientation = orientation;
161 self
162 }
163
164 pub fn velocity(mut self, velocity: [f32; 3]) -> Self {
165 self.velocity = velocity;
166 self
167 }
168
169 pub fn angular_velocity(mut self, angular_velocity: [f32; 3]) -> Self {
170 self.angular_velocity = angular_velocity;
171 self
172 }
173
174 pub fn mass(mut self, mass: f32) -> Self {
175 assert!(mass >= 0.0, "mass must be non-negative");
176 self.mass = mass;
177 self.density = None;
178 self
179 }
180
181 pub fn density(mut self, density: f32) -> Self {
182 assert!(density >= 0.0, "density must be non-negative");
183 self.density = Some(density);
184 self
185 }
186
187 pub fn damping(mut self, damping: f32) -> Self {
188 assert!(damping >= 0.0, "damping must be non-negative");
189 self.linear_damping = Some(damping);
190 self
191 }
192
193 pub fn angular_damping(mut self, angular_damping: f32) -> Self {
194 assert!(
195 angular_damping >= 0.0,
196 "angular damping must be non-negative"
197 );
198 self.angular_damping = Some(angular_damping);
199 self
200 }
201
202 pub fn gravity_scale(mut self, gravity_scale: f32) -> Self {
203 self.gravity_scale = gravity_scale;
204 self
205 }
206
207 pub fn sleep_thresholds(mut self, velocity: f32, angular_velocity: f32) -> Self {
208 assert!(velocity >= 0.0, "sleep velocity must be non-negative");
209 assert!(
210 angular_velocity >= 0.0,
211 "sleep angular velocity must be non-negative"
212 );
213 self.sleep_velocity = Some(velocity);
214 self.sleep_angular_velocity = Some(angular_velocity);
215 self
216 }
217
218 pub fn com(mut self, com: [f32; 3]) -> Self {
219 self.com = Some(com);
220 self
221 }
222
223 pub fn inertia(mut self, inertia: [f32; 6]) -> Self {
224 assert!(
225 inertia.iter().all(|value| value.is_finite()),
226 "inertia tensor must be finite"
227 );
228 self.inertia = Some(inertia);
229 self
230 }
231
232 pub fn mass_properties(
233 &self,
234 bounds: impl Fn(&Shape) -> Option<([f32; 3], [f32; 3])>,
235 ) -> crate::mass::MassProperties {
236 if let Some(inertia) = self.inertia {
237 return crate::mass::mass_properties_of_intent(
238 &self.colliders,
239 self.mass,
240 self.com,
241 Some(inertia),
242 bounds,
243 );
244 }
245 match self.density {
246 Some(density) => crate::mass::compute_mass_properties(
247 &self.colliders,
248 crate::mass::MassSource::Density(density),
249 self.com,
250 bounds,
251 ),
252 None => crate::mass::mass_properties_of_intent(
253 &self.colliders,
254 self.mass,
255 self.com,
256 None,
257 bounds,
258 ),
259 }
260 }
261
262 pub fn effective_mass(&self, bounds: impl Fn(&Shape) -> Option<([f32; 3], [f32; 3])>) -> f32 {
263 match self.density {
264 Some(density) => density * crate::mass::solid_volume_of(&self.colliders, &bounds),
265 None => self.mass,
266 }
267 }
268
269 pub fn collision_group(mut self, group: u32) -> Self {
270 self.collision_group = group;
271 self
272 }
273
274 pub fn collision_mask(mut self, mask: u32) -> Self {
275 self.collision_mask = mask;
276 self
277 }
278
279 pub fn kinematic(mut self, kinematic: bool) -> Self {
280 self.kinematic = kinematic;
281 self
282 }
283
284 pub fn ccd(mut self, ccd: bool) -> Self {
285 self.ccd = ccd;
286 self
287 }
288}