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}