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