gizmo_physics_rigid/components/
rigid_body.rs1use gizmo_math::{Mat3, Quat, Vec3};
2use serde::{Deserialize, Serialize};
3
4use super::Velocity;
5use gizmo_physics_core::{Collider, ColliderShape};
6
7#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
8pub enum BodyType {
9 Dynamic, Kinematic, Static, }
13
14#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
15pub struct RigidBody {
16 pub body_type: BodyType,
17 pub mass: f32,
18 pub restitution: f32,
19 pub friction: f32,
20 pub linear_damping: f32,
21 pub angular_damping: f32,
22 pub use_gravity: bool,
23 pub is_sleeping: bool,
24 pub ccd_enabled: bool,
25 pub local_inertia: Vec3,
26 pub lock_rotation_x: bool,
27 pub lock_rotation_y: bool,
28 pub lock_rotation_z: bool,
29 pub lock_translation_x: bool,
30 pub lock_translation_y: bool,
31 pub lock_translation_z: bool,
32 pub sleep_counter: u32, pub center_of_mass: Vec3,
34 pub fracture_threshold: Option<f32>, pub force_accumulator: Vec3,
36 pub torque_accumulator: Vec3,
37}
38
39impl Default for RigidBody {
40 fn default() -> Self {
41 Self {
42 body_type: BodyType::Dynamic,
43 mass: 1.0,
44 restitution: 0.5,
45 friction: 0.5,
46 linear_damping: 0.01,
47 angular_damping: 0.05,
48 use_gravity: true,
49 is_sleeping: false,
50 ccd_enabled: false,
51 local_inertia: Vec3::splat(1.0),
52 lock_rotation_x: false,
53 lock_rotation_y: false,
54 lock_rotation_z: false,
55 lock_translation_x: false,
56 lock_translation_y: false,
57 lock_translation_z: false,
58 sleep_counter: 0,
59 center_of_mass: Vec3::ZERO,
60 fracture_threshold: None,
61 force_accumulator: Vec3::ZERO,
62 torque_accumulator: Vec3::ZERO,
63 }
64 }
65}
66
67impl RigidBody {
68 pub fn new(mass: f32, restitution: f32, friction: f32, use_gravity: bool) -> Self {
69 Self {
70 mass,
71 restitution,
72 friction,
73 use_gravity,
74 ..Default::default()
75 }
76 }
77
78 pub fn new_static() -> Self {
79 Self {
80 body_type: BodyType::Static,
81 mass: 0.0,
82 restitution: 0.0,
83 friction: 1.0,
84 linear_damping: 0.0,
85 angular_damping: 0.0,
86 use_gravity: false,
87 is_sleeping: true,
88 local_inertia: Vec3::ZERO,
89 lock_rotation_x: true,
90 lock_rotation_y: true,
91 lock_rotation_z: true,
92 lock_translation_x: true,
93 lock_translation_y: true,
94 lock_translation_z: true,
95 ..Default::default()
96 }
97 }
98
99 pub fn new_kinematic() -> Self {
100 Self {
101 body_type: BodyType::Kinematic,
102 mass: 0.0,
103 restitution: 0.0,
104 friction: 0.5,
105 linear_damping: 0.0,
106 angular_damping: 0.0,
107 use_gravity: false,
108 ccd_enabled: true,
109 local_inertia: Vec3::ZERO,
110 ..Default::default()
111 }
112 }
113
114 pub fn with_fracture_threshold(mut self, threshold: f32) -> Self {
115 self.fracture_threshold = Some(threshold);
116 self
117 }
118
119 pub fn wake_up(&mut self) {
120 self.is_sleeping = false;
121 self.sleep_counter = 0;
122 }
123
124 pub fn can_sleep(&self, velocity: &Velocity) -> bool {
125 if self.is_kinematic() {
126 return false; }
128 if !self.is_dynamic() {
129 return true; }
131
132 const SLEEP_LINEAR_THRESHOLD: f32 = 0.05;
133 const SLEEP_ANGULAR_THRESHOLD: f32 = 0.05;
134
135 velocity.linear.length_squared() < SLEEP_LINEAR_THRESHOLD * SLEEP_LINEAR_THRESHOLD
136 && velocity.angular.length_squared() < SLEEP_ANGULAR_THRESHOLD * SLEEP_ANGULAR_THRESHOLD
137 }
138
139 pub fn update_sleep_state(&mut self, velocity: &Velocity) {
140 const SLEEP_FRAMES_REQUIRED: u32 = 60; if self.can_sleep(velocity) {
143 self.sleep_counter += 1;
144 if self.sleep_counter >= SLEEP_FRAMES_REQUIRED {
145 self.is_sleeping = true;
146 }
147 } else {
148 self.sleep_counter = 0;
149 self.is_sleeping = false;
150 }
151 }
152
153 #[inline]
154 pub fn is_dynamic(&self) -> bool {
155 matches!(self.body_type, BodyType::Dynamic)
156 }
157
158 #[inline]
159 pub fn is_kinematic(&self) -> bool {
160 matches!(self.body_type, BodyType::Kinematic)
161 }
162
163 #[inline]
164 pub fn is_static(&self) -> bool {
165 matches!(self.body_type, BodyType::Static)
166 }
167
168 #[inline]
169 pub fn enforce_locks(&self, vel: &mut Velocity) {
170 if self.lock_translation_x {
171 vel.linear.x = 0.0;
172 }
173 if self.lock_translation_y {
174 vel.linear.y = 0.0;
175 }
176 if self.lock_translation_z {
177 vel.linear.z = 0.0;
178 }
179 if self.lock_rotation_x {
180 vel.angular.x = 0.0;
181 }
182 if self.lock_rotation_y {
183 vel.angular.y = 0.0;
184 }
185 if self.lock_rotation_z {
186 vel.angular.z = 0.0;
187 }
188 }
189
190 #[inline]
191 pub fn inv_mass(&self) -> f32 {
192 if self.mass == 0.0 || !self.is_dynamic() {
193 0.0
194 } else {
195 1.0 / self.mass
196 }
197 }
198
199 #[inline]
200 pub fn inv_local_inertia(&self) -> Vec3 {
201 if self.mass == 0.0 || !self.is_dynamic() {
202 Vec3::ZERO
203 } else {
204 Vec3::new(
205 if self.local_inertia.x == 0.0 {
206 0.0
207 } else {
208 1.0 / self.local_inertia.x
209 },
210 if self.local_inertia.y == 0.0 {
211 0.0
212 } else {
213 1.0 / self.local_inertia.y
214 },
215 if self.local_inertia.z == 0.0 {
216 0.0
217 } else {
218 1.0 / self.local_inertia.z
219 },
220 )
221 }
222 }
223
224 pub fn inv_world_inertia_tensor_identity(&self) -> Mat3 {
226 Mat3::from_diagonal(self.inv_local_inertia())
227 }
228
229 pub fn world_inertia_tensor(&self, rotation: Quat) -> Mat3 {
231 let rot_mat = Mat3::from_quat(rotation);
232 let local_inertia_mat = Mat3::from_diagonal(self.local_inertia);
233 rot_mat * local_inertia_mat * rot_mat.transpose()
234 }
235
236 pub fn inv_world_inertia_tensor(&self, rotation: Quat) -> Mat3 {
238 if self.mass == 0.0 || !self.is_dynamic() {
239 return Mat3::ZERO;
240 }
241 let rot_mat = Mat3::from_quat(rotation);
242 let inv_local = Mat3::from_diagonal(self.inv_local_inertia());
243 let mut inv_world = rot_mat * inv_local * rot_mat.transpose();
244
245 if self.lock_rotation_x {
247 inv_world.x_axis = Vec3::ZERO;
248 inv_world.y_axis.x = 0.0;
249 inv_world.z_axis.x = 0.0;
250 }
251 if self.lock_rotation_y {
252 inv_world.y_axis = Vec3::ZERO;
253 inv_world.x_axis.y = 0.0;
254 inv_world.z_axis.y = 0.0;
255 }
256 if self.lock_rotation_z {
257 inv_world.z_axis = Vec3::ZERO;
258 inv_world.x_axis.z = 0.0;
259 inv_world.y_axis.z = 0.0;
260 }
261
262 inv_world
263 }
264
265 pub fn clear_forces(&mut self) {
266 self.force_accumulator = Vec3::ZERO;
267 self.torque_accumulator = Vec3::ZERO;
268 }
269
270 pub fn calculate_box_inertia(&mut self, w: f32, h: f32, d: f32) {
271 let m = self.mass;
272 self.local_inertia = Vec3::new(
273 (m / 12.0) * (h * h + d * d),
274 (m / 12.0) * (w * w + d * d),
275 (m / 12.0) * (w * w + h * h),
276 );
277 }
278
279 pub fn calculate_sphere_inertia(&mut self, r: f32) {
280 let i = 0.4 * self.mass * r * r;
281 self.local_inertia = Vec3::splat(i);
282 }
283
284 pub fn calculate_capsule_inertia(&mut self, r: f32, half_h: f32) {
285 let m = self.mass;
286 let h = half_h * 2.0;
287 let vol_cyl = std::f32::consts::PI * r * r * h;
288 let vol_sph = 4.0 / 3.0 * std::f32::consts::PI * r * r * r;
289 let total_vol = vol_cyl + vol_sph;
290
291 let m_cyl = if total_vol > 0.0 {
292 m * vol_cyl / total_vol
293 } else {
294 0.0
295 };
296 let m_sph = if total_vol > 0.0 {
297 m * vol_sph / total_vol
298 } else {
299 0.0
300 };
301
302 let i_y = m_cyl * (r * r) / 2.0 + m_sph * 2.0 * (r * r) / 5.0;
303 let i_cyl_xz = m_cyl * (3.0 * r * r + h * h) / 12.0;
304 let i_sph_xz =
305 m_sph * (0.4 * r * r + half_h * half_h + 0.75 * r * half_h + 0.140625 * r * r);
306 let i_xz = i_cyl_xz + i_sph_xz;
307
308 self.local_inertia = Vec3::new(i_xz, i_y, i_xz);
309 }
310
311 pub fn update_inertia_from_collider(&mut self, collider: &Collider) {
312 match &collider.shape {
313 ColliderShape::Box(b) => {
314 let w = b.half_extents.x * 2.0;
315 let h = b.half_extents.y * 2.0;
316 let d = b.half_extents.z * 2.0;
317 self.calculate_box_inertia(w, h, d);
318 }
319 ColliderShape::Sphere(s) => {
320 self.calculate_sphere_inertia(s.radius);
321 }
322 ColliderShape::Capsule(c) => {
323 self.calculate_capsule_inertia(c.radius, c.half_height);
324 }
325 ColliderShape::Plane(_) => {
326 self.local_inertia = Vec3::splat(f32::INFINITY);
327 }
328 ColliderShape::TriMesh(_) | ColliderShape::ConvexHull(_) => {
329 self.calculate_box_inertia(1.0, 1.0, 1.0);
330 }
331 ColliderShape::Compound(shapes) => {
332 let mut total_vol = 0.0;
333 let mut vols = Vec::with_capacity(shapes.len());
334 for (_, sub_shape) in shapes {
335 let temp_col = Collider {
336 shape: (**sub_shape).clone(),
337 ..Default::default()
338 };
339 let v = temp_col.volume();
340 vols.push(v);
341 total_vol += v;
342 }
343
344 if total_vol > 0.0 {
345 let mut com = Vec3::ZERO;
346 for (i, (local_t, _)) in shapes.iter().enumerate() {
347 let mass_i = (vols[i] / total_vol) * self.mass;
348 com += local_t.position * mass_i;
349 }
350 if self.mass > 0.0 {
351 self.center_of_mass = com / self.mass;
352 }
353
354 let mut inertia = Vec3::ZERO;
355 for (i, (local_t, sub_shape)) in shapes.iter().enumerate() {
356 let mass_i = (vols[i] / total_vol) * self.mass;
357
358 let mut temp_rb = RigidBody {
359 mass: mass_i,
360 ..Default::default()
361 };
362 let temp_col = Collider {
363 shape: (**sub_shape).clone(),
364 ..Default::default()
365 };
366 temp_rb.update_inertia_from_collider(&temp_col);
367
368 let d = local_t.position - self.center_of_mass;
369 let d_sq = d.length_squared();
370
371 inertia.x += temp_rb.local_inertia.x + mass_i * (d_sq - d.x * d.x);
372 inertia.y += temp_rb.local_inertia.y + mass_i * (d_sq - d.y * d.y);
373 inertia.z += temp_rb.local_inertia.z + mass_i * (d_sq - d.z * d.z);
374 }
375 self.local_inertia = inertia;
376 } else {
377 self.calculate_box_inertia(1.0, 1.0, 1.0);
378 }
379 }
380 }
381 }
382}
383
384gizmo_core::impl_component!(RigidBody);