1use gizmo_physics_core::{Collider, Transform};
2use crate::components::{RigidBody, Velocity};use crate::world::PhysicsWorld;
3use gizmo_core::entity::Entity;
4use gizmo_core::query::{Mut, Query};
5use gizmo_core::world::World;
6
7#[tracing::instrument(skip_all, name = "physics_step_system")]
11pub fn physics_step_system(world: &World, dt: f32) {
12 if let Ok(mut profiler) = world.try_get_resource_mut::<gizmo_core::profiler::FrameProfiler>() {
14 profiler.begin_scope("physics_total");
15 }
16
17 let mut physics_world = match world.try_get_resource_mut::<PhysicsWorld>() {
19 Ok(res) => res,
20 Err(e) => {
21 tracing::info!("[Physics] FAILED TO GET PhysicsWorld Resource: {:?}", e);
22 return;
23 }
24 };
25
26 let mut compound_shapes_map = std::collections::HashMap::new();
28 {
29 if let Some(query) = world.query::<(
30 &Collider,
31 &Transform,
32 &RigidBody,
33 gizmo_core::query::Without<gizmo_core::pool::Pooled>,
34 gizmo_core::query::Without<gizmo_core::component::IsDeleted>,
35 )>() {
36 let mut children_query = world.query::<&gizmo_core::component::Children>();
37 let trans_query = world.query::<&Transform>();
38 let col_query = world.query::<&Collider>();
39
40 for (id, (col, transform, _rb, _, _)) in query.iter() {
41 let mut compound_shapes = Vec::new();
42 compound_shapes.push((
43 gizmo_physics_core::Transform::default(),
44 Box::new(col.shape.clone()),
45 ));
46
47 let mut stack = vec![id];
49 while let Some(curr_id) = stack.pop() {
50 if let Some(children_query_ref) = &mut children_query {
51 if let Some(children) = children_query_ref.get(curr_id) {
52 for &child_id in &children.0 {
53 stack.push(child_id);
54 if let (Some(tq), Some(cq)) = (&trans_query, &col_query) {
55 if let (Some(child_trans), Some(child_col)) = (tq.get(child_id), cq.get(child_id)) {
56 let inv_rot = transform.rotation.inverse();
57 let local_pos =
58 inv_rot.mul_vec3(child_trans.position - transform.position);
59 let local_rot = inv_rot * child_trans.rotation;
60
61 let local_t = gizmo_physics_core::Transform::new(local_pos)
62 .with_rotation(local_rot);
63 compound_shapes
64 .push((local_t, Box::new(child_col.shape.clone())));
65 }
66 }
67 }
68 }
69 }
70 }
71
72 let final_collider = if compound_shapes.is_empty() {
74 Collider::default() } else if compound_shapes.len() == 1 {
76 let (_t, s) = compound_shapes.remove(0);
78 Collider {
79 shape: *s,
80 ..Default::default()
81 }
82 } else {
83 Collider {
84 shape: gizmo_physics_core::ColliderShape::Compound(compound_shapes),
85 ..Default::default()
86 }
87 };
88
89 compound_shapes_map.insert(id, final_collider);
90 }
91 }
92 }
93
94 let mut rigid_bodies = Vec::new();
96 if let Some(mut query) = world.query::<(
97 Mut<RigidBody>,
98 Mut<Transform>,
99 Mut<Velocity>,
100 gizmo_core::query::Without<gizmo_core::pool::Pooled>,
101 gizmo_core::query::Without<gizmo_core::component::IsDeleted>,
102 )>() {
103 for (id, (rb, transform, vel, _, _)) in query.iter_mut() {
104 if let Some(final_collider) = compound_shapes_map.remove(&id) {
105 rigid_bodies.push((Entity::new(id, 0), *rb, *transform, *vel, final_collider));
106 }
107 }
108 } else {
109 tracing::info!("[Physics] FAILED TO BORROW RigidBody/Transform/Velocity Mutably!");
110 }
111
112 physics_world.sync_bodies(rigid_bodies.iter());
114
115 physics_world.step(dt).expect("Gizmo Physics Engine encountered a critical numerical error (NaN, Infinity, or Overflow) and halted!");
116
117 for i in 0..physics_world.entities.len() {
119 let entity_id = physics_world.entities[i].id();
120 if let Some((_, rb, trans, vel, _)) =
121 rigid_bodies.iter_mut().find(|(e, ..)| e.id() == entity_id)
122 {
123 *rb = physics_world.rigid_bodies[i];
124 *trans = physics_world.transforms[i];
125 *vel = physics_world.velocities[i];
126 }
127 }
128
129 if !rigid_bodies.is_empty() {
131 if let Some(query) = world.query::<(
132 Mut<RigidBody>,
133 Mut<Transform>,
134 Mut<Velocity>,
135 gizmo_core::query::Without<gizmo_core::pool::Pooled>,
136 )>() {
137 for (entity, rb, transform, vel, _collider) in rigid_bodies {
138 if let Some((mut ecs_rb, mut ecs_trans, mut ecs_vel, _)) = query.get(entity.id()) {
139 *ecs_rb = rb;
140 *ecs_trans = transform;
141 *ecs_vel = vel;
142 }
143 }
144 }
145 }
146
147
148
149 if let Ok(mut trigger_queue) =
151 world.try_get_resource_mut::<gizmo_core::event::Events<gizmo_physics_core::TriggerEvent>>()
152 {
153 for event in &physics_world.trigger_events {
154 trigger_queue.send(event.clone());
155 }
156 }
157
158 if let Ok(mut collision_queue) =
159 world.try_get_resource_mut::<gizmo_core::event::Events<gizmo_physics_core::CollisionEvent>>()
160 {
161 for event in &physics_world.collision_events {
162 collision_queue.send(event.clone());
163 }
164 }
165
166 if physics_world.step_once {
167 physics_world.step_once = false;
168 }
169
170 drop(physics_world); if let Ok(mut profiler) = world.try_get_resource_mut::<gizmo_core::profiler::FrameProfiler>() {
173 profiler.end_scope("physics_total");
174 }
175}
176
177pub fn physics_fracture_system(world: &World, dt: f32) {
179 use crate::components::Breakable;
180 use gizmo_core::commands::Commands;
181 use gizmo_core::system::SystemParam;
182
183 let physics_world = match world.try_get_resource::<PhysicsWorld>() {
184 Ok(res) => res,
185 Err(_) => return,
186 };
187
188 let mut commands = match Commands::fetch(world, dt) {
189 Ok(c) => c,
190 Err(_) => return,
191 };
192
193 let mut shattered = std::collections::HashSet::new();
194
195 let query_opt = Query::<(
196 gizmo_core::query::Mut<Breakable>,
197 &Transform,
198 &Collider,
199 &Velocity,
200 gizmo_core::query::Without<gizmo_core::pool::Pooled>,
201 )>::new(world);
202 let query = match query_opt {
203 Some(q) => q,
204 None => return,
205 };
206
207 for event in &physics_world.collision_events {
208 let mut max_impulse = 0.0;
209 let mut impact_normal = gizmo_math::Vec3::ZERO;
210 let mut impact_point = gizmo_math::Vec3::ZERO;
211
212 for contact in &event.contact_points {
213 if contact.normal_impulse > max_impulse {
214 max_impulse = contact.normal_impulse;
215 impact_normal = contact.normal;
216 impact_point = contact.point;
217 }
218 }
219
220 if max_impulse <= 0.0 && !event.contact_points.is_empty() {
222 let vel_a = physics_world
224 .entity_index_map
225 .get(&event.entity_a.id())
226 .map(|&idx| physics_world.velocities[idx].linear)
227 .unwrap_or(gizmo_math::Vec3::ZERO);
228 let vel_b = physics_world
229 .entity_index_map
230 .get(&event.entity_b.id())
231 .map(|&idx| physics_world.velocities[idx].linear)
232 .unwrap_or(gizmo_math::Vec3::ZERO);
233 let mass_a = physics_world
234 .entity_index_map
235 .get(&event.entity_a.id())
236 .map(|&idx| physics_world.rigid_bodies[idx].mass)
237 .unwrap_or(1.0);
238 let mass_b = physics_world
239 .entity_index_map
240 .get(&event.entity_b.id())
241 .map(|&idx| physics_world.rigid_bodies[idx].mass)
242 .unwrap_or(1.0);
243
244 let rel_speed = (vel_b - vel_a).length();
245 let reduced_mass = if mass_a > 0.0 && mass_b > 0.0 {
246 (mass_a * mass_b) / (mass_a + mass_b)
247 } else {
248 mass_a.max(mass_b)
249 };
250 max_impulse = rel_speed * reduced_mass;
251 if let Some(contact) = event.contact_points.first() {
252 impact_normal = contact.normal;
253 impact_point = contact.point;
254 }
255 }
256
257 if max_impulse <= 0.0 {
258 continue;
259 }
260
261 if !shattered.contains(&event.entity_a.id()) {
263 if let Some((mut breakable, transform, collider, vel, _)) =
264 query.get(event.entity_a.id())
265 {
266 if !breakable.is_broken && max_impulse > breakable.threshold {
267 breakable.current_health -= max_impulse;
268 if breakable.current_health <= 0.0 {
269 breakable.is_broken = true;
270 shattered.insert(event.entity_a.id());
271 shatter_entity(
272 &mut commands,
273 event.entity_a,
274 &breakable,
275 transform,
276 collider,
277 vel,
278 -impact_normal,
279 impact_point,
280 );
281 }
282 }
283 }
284 }
285
286 if !shattered.contains(&event.entity_b.id()) {
288 if let Some((mut breakable, transform, collider, vel, _)) =
289 query.get(event.entity_b.id())
290 {
291 if !breakable.is_broken && max_impulse > breakable.threshold {
292 breakable.current_health -= max_impulse;
293 if breakable.current_health <= 0.0 {
294 breakable.is_broken = true;
295 shattered.insert(event.entity_b.id());
296 shatter_entity(
297 &mut commands,
298 event.entity_b,
299 &breakable,
300 transform,
301 collider,
302 vel,
303 impact_normal,
304 impact_point,
305 );
306 }
307 }
308 }
309 }
310 }
311 drop(query);
312}
313
314fn shatter_entity(
315 commands: &mut gizmo_core::commands::Commands,
316 entity: Entity,
317 breakable: &crate::components::Breakable,
318 transform: &Transform,
319 collider: &Collider,
320 vel: &Velocity,
321 impact_direction: gizmo_math::Vec3,
322 _impact_point: gizmo_math::Vec3,
323) {
324 use crate::fracture::voronoi_shatter;
325
326 let extents = match &collider.shape {
328 gizmo_physics_core::ColliderShape::Box(b) => b.half_extents,
329 _ => return, };
331
332 commands.entity(entity).despawn();
334
335 let chunks = voronoi_shatter(extents, breakable.max_pieces, 42);
337
338 for chunk in chunks {
339 let radius = (chunk.volume * 0.1).powf(1.0 / 3.0).max(0.1);
343
344 let world_offset = transform.rotation * chunk.center_of_mass;
346 let mut new_transform = *transform;
347 new_transform.position += world_offset;
348
349 let mut new_vel = *vel;
351 let outward = chunk.center_of_mass.normalize_or_zero();
352 new_vel.linear += outward * 2.0 + impact_direction * 5.0; let chunk_collider = Collider::sphere(radius).with_material(collider.material);
355 let mut rb = RigidBody::new(chunk.volume * collider.material.density, 0.0, 0.0, true);
356 rb.update_inertia_from_collider(&chunk_collider);
357
358 commands
359 .spawn()
360 .insert(rb)
361 .insert(chunk_collider)
362 .insert(new_transform)
363 .insert(new_vel);
364 }
365}
366
367pub fn physics_explosion_system(world: &World, dt: f32) {
370 use crate::components::{Explosion, ExplosionFalloff};
371 use gizmo_core::commands::Commands;
372 use gizmo_core::system::SystemParam;
373
374 let mut commands = match Commands::fetch(world, dt) {
375 Ok(c) => c,
376 Err(_) => return,
377 };
378
379 let explosion_query_opt = Query::<(
380 &Explosion,
381 &Transform,
382 gizmo_core::query::Without<gizmo_core::pool::Pooled>,
383 )>::new(world);
384 let mut active_explosions = Vec::new();
385
386 if let Some(exp_query) = &explosion_query_opt {
387 for (ent_id, (explosion, transform, _)) in exp_query.iter() {
388 if explosion.is_active {
389 active_explosions.push((
391 Entity::new(ent_id, 0),
392 *explosion,
393 transform.position + explosion.offset,
394 ));
395 }
396 }
397 }
398
399 if active_explosions.is_empty() {
400 return; }
402
403 let mut shattered = std::collections::HashSet::new();
404
405 let calculate_intensity = |dist: f32, radius: f32, falloff: ExplosionFalloff| -> f32 {
407 if dist >= radius {
408 return 0.0;
409 }
410 match falloff {
411 ExplosionFalloff::None => 1.0,
412 ExplosionFalloff::Linear => 1.0 - (dist / radius),
413 ExplosionFalloff::Quadratic => {
414 let ratio = 1.0 - (dist / radius);
415 ratio * ratio
416 }
417 }
418 };
419
420 let breakable_query_opt = Query::<(
422 gizmo_core::query::Mut<crate::components::Breakable>,
423 &Transform,
424 &Collider,
425 &Velocity,
426 gizmo_core::query::Without<gizmo_core::pool::Pooled>,
427 )>::new(world);
428 if let Some(breakable_query) = &breakable_query_opt {
429 for (_exp_entity, explosion, exp_pos) in &active_explosions {
430 for (id, (mut breakable, transform, collider, vel, _)) in breakable_query.iter() {
431 if breakable.is_broken || shattered.contains(&id) {
432 continue;
433 }
434
435 let diff = transform.position - *exp_pos;
436 let dist_sq = diff.length_squared();
437
438 if dist_sq < explosion.force_radius * explosion.force_radius && dist_sq > 0.001 {
439 let dist = dist_sq.sqrt();
440 let intensity =
441 calculate_intensity(dist, explosion.force_radius, explosion.falloff);
442 let impulse_mag = explosion.force * intensity;
443
444 if impulse_mag > breakable.threshold {
445 breakable.current_health -= explosion.damage * intensity;
446 if breakable.current_health <= 0.0 {
447 breakable.is_broken = true;
448 shattered.insert(id);
449 let dir = diff / dist;
450 let mut exp_vel = *vel;
451 exp_vel.linear += dir * impulse_mag * 0.1; shatter_entity(
453 &mut commands,
454 Entity::new(id, 0),
455 &breakable,
456 transform,
457 collider,
458 &exp_vel,
459 dir,
460 transform.position,
461 );
462 }
463 }
464 }
465 }
466 }
467 }
468
469 let rb_query_opt = Query::<(
471 Mut<RigidBody>,
472 &Transform,
473 Mut<Velocity>,
474 gizmo_core::query::Without<gizmo_core::pool::Pooled>,
475 )>::new(world);
476 if let Some(rb_query) = &rb_query_opt {
477 for (_exp_entity, explosion, exp_pos) in &active_explosions {
478 for (id, (rb, transform, mut vel, _)) in rb_query.iter() {
479 if !rb.is_dynamic() || shattered.contains(&id) {
480 continue;
481 }
482
483 let diff = transform.position - *exp_pos;
484 let dist_sq = diff.length_squared();
485
486 if dist_sq < explosion.force_radius * explosion.force_radius && dist_sq > 0.001 {
487 let dist = dist_sq.sqrt();
488 let dir = diff / dist;
489
490 let intensity =
491 calculate_intensity(dist, explosion.force_radius, explosion.falloff);
492 let impulse_mag = explosion.force * intensity;
493
494 vel.linear += dir * impulse_mag * rb.inv_mass();
496 }
497 }
498 }
499 }
500
501 for (exp_entity, _, _) in active_explosions {
504 commands.entity(exp_entity).despawn();
505 }
506}
507
508