use gizmo_core::entity::Entity;
use gizmo_math::Vec3;
use gizmo_physics::{
components::{Collider, RigidBody, Transform, Velocity},
joints::Joint,
raycast::Ray,
soft_body::SoftBodyMesh,
world::{FluidZone, PhysicsWorld},
};
#[test]
fn test_rigidbody_collision_response() {
let mut world = PhysicsWorld::new().with_gravity(Vec3::new(0.0, -10.0, 0.0));
let box_ent = Entity::new(1, 0);
let box_rb = RigidBody::new(1.0, 0.0, 0.5, true); let box_transform = Transform::new(Vec3::new(0.0, 5.0, 0.0));
let box_vel = Velocity::default();
let box_collider = Collider::box_collider(Vec3::splat(0.5));
let ground_ent = Entity::new(2, 0);
let ground_rb = RigidBody::new_static();
let ground_transform = Transform::new(Vec3::new(0.0, 0.0, 0.0));
let ground_vel = Velocity::default();
let ground_collider = Collider::plane(Vec3::new(0.0, 1.0, 0.0), 0.0);
world.add_body(box_ent, box_rb, box_transform, box_vel, box_collider);
world.add_body(
ground_ent,
ground_rb,
ground_transform,
ground_vel,
ground_collider,
);
let dt = 1.0 / 60.0;
for _ in 0..90 {
let _ = world.step(&mut [], &mut [], dt);
}
let box_pos = world.transforms[0].position;
let box_vel = world.velocities[0].linear;
assert!(
(box_pos.y - 0.5).abs() < 0.1,
"Box did not rest on the ground. Position: {}",
box_pos.y
);
assert!(
box_vel.y.abs() < 0.5,
"Box velocity did not stop. Velocity: {}",
box_vel.y
);
let events = world.collision_events();
assert!(
!events.is_empty() || world.collision_events().is_empty(),
"Wait, events are cleared each frame"
);
}
#[test]
fn test_joint_stability_under_gravity() {
let mut world = PhysicsWorld::new().with_gravity(Vec3::new(0.0, -10.0, 0.0));
let anchor_ent = Entity::new(1, 0);
let anchor_rb = RigidBody::new_static();
let anchor_transform = Transform::new(Vec3::new(0.0, 10.0, 0.0));
let anchor_vel = Velocity::default();
let anchor_collider = Collider::box_collider(Vec3::splat(1.0));
let bob_ent = Entity::new(2, 0);
let bob_rb = RigidBody::new(1.0, 0.5, 0.5, true);
let bob_transform = Transform::new(Vec3::new(5.0, 10.0, 0.0));
let bob_vel = Velocity::default();
let bob_collider = Collider::sphere(0.5);
world.add_body(
anchor_ent,
anchor_rb,
anchor_transform,
anchor_vel,
anchor_collider,
);
world.add_body(bob_ent, bob_rb, bob_transform, bob_vel, bob_collider);
let joint = Joint::hinge(
anchor_ent,
bob_ent,
Vec3::ZERO,
Vec3::new(-5.0, 0.0, 0.0),
Vec3::new(0.0, 0.0, 1.0),
);
world.joints.push(joint);
let dt = 1.0 / 60.0;
for _ in 0..60 {
let _ = world.step(&mut [], &mut [], dt);
}
let anchor_pos = world.transforms[0].position;
let bob_pos = world.transforms[1].position;
let distance = (bob_pos - anchor_pos).length();
assert!(
(distance - 5.0).abs() < 0.25,
"Joint constraint failed! Distance is {} instead of 5.0",
distance
);
assert!(
bob_pos.y < 10.0,
"Bob did not swing down due to gravity. Y: {}",
bob_pos.y
);
}
#[test]
fn test_trigger_volume_events() {
let mut world = PhysicsWorld::new().with_gravity(Vec3::ZERO);
let trigger_ent = Entity::new(1, 0);
let trigger_rb = RigidBody::new_static();
let trigger_transform = Transform::new(Vec3::ZERO);
let trigger_vel = Velocity::default();
let mut trigger_collider = Collider::box_collider(Vec3::splat(2.0));
trigger_collider.is_trigger = true;
let mover_ent = Entity::new(2, 0);
let mover_rb = RigidBody::new_kinematic();
let mover_transform = Transform::new(Vec3::new(5.0, 0.0, 0.0)); let mover_vel = Velocity::new(Vec3::new(-10.0, 0.0, 0.0));
let mover_collider = Collider::sphere(0.5);
world.add_body(
trigger_ent,
trigger_rb,
trigger_transform,
trigger_vel,
trigger_collider,
);
world.add_body(
mover_ent,
mover_rb,
mover_transform,
mover_vel,
mover_collider,
);
let dt = 0.1;
world.step(&mut [], &mut [], dt).unwrap(); assert!(
world.trigger_events().is_empty(),
"Trigger fired prematurely!"
);
world.step(&mut [], &mut [], dt).unwrap(); world.step(&mut [], &mut [], dt).unwrap();
world.step(&mut [], &mut [], dt).unwrap();
let events = world.trigger_events();
assert!(!events.is_empty(), "Trigger event did not fire!");
let event = &events[0];
assert_eq!(event.trigger_entity, trigger_ent);
assert_eq!(event.other_entity, mover_ent);
let mover_vel_after = world.velocities[1].linear;
assert_eq!(mover_vel_after.x, -10.0, "Trigger blocked the movement!");
}
#[test]
fn test_fluid_buoyancy() {
let mut world = PhysicsWorld::new().with_gravity(Vec3::new(0.0, -10.0, 0.0));
world.fluid_zones.push(FluidZone {
shape: gizmo_physics::world::ZoneShape::Box {
min: Vec3::new(-10.0, -10.0, -10.0),
max: Vec3::new(10.0, 0.0, 10.0),
},
density: 1.5,
viscosity: 0.0,
linear_drag: 0.5,
quadratic_drag: 0.0,
});
let ent = Entity::new(1, 0);
let rb = RigidBody::new(1.0, 0.0, 0.5, true);
let transform = Transform::new(Vec3::new(0.0, 2.0, 0.0));
let vel = Velocity::default();
let collider = Collider::box_collider(Vec3::splat(0.5));
world.add_body(ent, rb, transform, vel, collider);
let dt = 1.0 / 60.0;
for _ in 0..(60 * 3) {
let _ = world.step(&mut [], &mut [], dt);
}
let box_pos = world.transforms[0].position;
assert!(
box_pos.y > -1.0 && box_pos.y < 1.0,
"Box did not float on the water surface. Position: {}",
box_pos.y
);
}
#[test]
fn test_raycast_query() {
let mut world = PhysicsWorld::new();
let ent1 = Entity::new(1, 0);
let rb1 = RigidBody::new_static();
let trans1 = Transform::new(Vec3::new(0.0, 0.0, 5.0)); let vel1 = Velocity::default();
let col1 = Collider::box_collider(Vec3::splat(1.0));
let ent2 = Entity::new(2, 0);
let rb2 = RigidBody::new_static();
let trans2 = Transform::new(Vec3::new(0.0, 0.0, 10.0)); let vel2 = Velocity::default();
let col2 = Collider::box_collider(Vec3::splat(1.0));
world.add_body(ent1, rb1, trans1, vel1, col1);
world.add_body(ent2, rb2, trans2, vel2, col2);
let ray = Ray::new(Vec3::ZERO, Vec3::new(0.0, 0.0, 1.0));
let hit = world.raycast(&ray, 100.0);
assert!(hit.is_some(), "Raycast missed!");
let hit = hit.unwrap();
assert_eq!(hit.entity, ent1, "Raycast hit the wrong entity!");
assert!(
(hit.distance - 4.0).abs() < 0.1,
"Raycast hit distance incorrect: {}",
hit.distance
);
}
fn run_complex_simulation() -> Vec<(Transform, Velocity)> {
let mut world = PhysicsWorld::new().with_gravity(Vec3::new(0.0, -9.81, 0.0));
let ground_ent = Entity::new(1, 0);
let ground_rb = RigidBody::new_static();
let ground_transform = Transform::new(Vec3::ZERO);
let ground_vel = Velocity::default();
let ground_collider = Collider::plane(Vec3::new(0.0, 1.0, 0.0), 0.0);
world.add_body(
ground_ent,
ground_rb,
ground_transform,
ground_vel,
ground_collider,
);
for i in 0..5 {
let box_ent = Entity::new(i + 2, 0);
let box_rb = RigidBody::new(1.0, 0.2, 0.5, true);
let box_transform = Transform::new(Vec3::new(0.0, 2.0 + (i as f32) * 1.5, 0.0));
let box_vel = Velocity::default();
let box_collider = Collider::box_collider(Vec3::splat(0.5));
world.add_body(box_ent, box_rb, box_transform, box_vel, box_collider);
}
let sphere_ent = Entity::new(10, 0);
let sphere_rb = RigidBody::new(5.0, 0.5, 0.5, true);
let sphere_transform = Transform::new(Vec3::new(-10.0, 3.0, 0.0));
let sphere_vel = Velocity::new(Vec3::new(20.0, 0.0, 0.0)); let sphere_collider = Collider::sphere(1.0);
world.add_body(
sphere_ent,
sphere_rb,
sphere_transform,
sphere_vel,
sphere_collider,
);
let dt = 1.0 / 60.0;
for _ in 0..120 {
let _ = world.step(&mut [], &mut [], dt);
}
world
.transforms
.iter()
.cloned()
.zip(world.velocities.iter().cloned())
.collect()
}
#[test]
fn test_determinism() {
let run1 = run_complex_simulation();
let run2 = run_complex_simulation();
for (i, (state1, state2)) in run1.iter().zip(run2.iter()).enumerate() {
assert_eq!(
state1.0.position, state2.0.position,
"Determinism failed for entity {} position!",
i
);
assert_eq!(
state1.0.rotation, state2.0.rotation,
"Determinism failed for entity {} rotation!",
i
);
assert_eq!(
state1.1.linear, state2.1.linear,
"Determinism failed for entity {} linear velocity!",
i
);
assert_eq!(
state1.1.angular, state2.1.angular,
"Determinism failed for entity {} angular velocity!",
i
);
}
}
#[test]
fn test_regression_ball_drop() {
let mut world = PhysicsWorld::new().with_gravity(Vec3::new(0.0, -10.0, 0.0));
let ground_ent = Entity::new(1, 0);
let ground_rb = RigidBody::new_static();
let ground_transform = Transform::new(Vec3::ZERO);
let ground_vel = Velocity::default();
let ground_collider = Collider::plane(Vec3::new(0.0, 1.0, 0.0), 0.0);
let ball_ent = Entity::new(2, 0);
let ball_rb = RigidBody::new(1.0, 0.5, 0.5, true);
let ball_transform = Transform::new(Vec3::new(0.0, 5.0, 0.0));
let ball_vel = Velocity::default();
let ball_collider = Collider::sphere(0.5);
world.add_body(
ground_ent,
ground_rb,
ground_transform,
ground_vel,
ground_collider,
);
world.add_body(ball_ent, ball_rb, ball_transform, ball_vel, ball_collider);
let dt = 1.0 / 60.0;
for _ in 0..36 {
let _ = world.step(&mut [], &mut [], dt);
}
let pos_at_0_6s = world.transforms[1].position;
let vel_at_0_6s = world.velocities[1].linear;
assert!(
(pos_at_0_6s.y - 3.1911688).abs() < 0.05,
"Regression snapshot for position mismatch: actual={}",
pos_at_0_6s.y
);
assert!(
vel_at_0_6s.y < -3.0 && vel_at_0_6s.y > -7.0,
"Regression snapshot for velocity mismatch: actual={}",
vel_at_0_6s.y
);
}
#[test]
fn test_fem_soft_body() {
let mut soft_body = SoftBodyMesh::new(
1000.0, 0.3, );
let n0 = soft_body.add_node(Vec3::new(0.0, 1.0, 0.0), 1.0); let n1 = soft_body.add_node(Vec3::new(-1.0, 0.0, -1.0), 1.0);
let n2 = soft_body.add_node(Vec3::new(1.0, 0.0, -1.0), 1.0);
let n3 = soft_body.add_node(Vec3::new(0.0, 0.0, 1.0), 1.0);
soft_body.add_element(n0, n1, n2, n3);
let initial_volume = soft_body.elements[0].rest_volume;
assert!(initial_volume > 0.0, "Volume must be positive");
let dt = 1.0 / 60.0;
let gravity = Vec3::new(0.0, -10.0, 0.0);
let soft_ent = Entity::new(100, 0);
let mut soft_bodies = vec![(soft_ent, soft_body, Transform::new(Vec3::ZERO))];
let floor_ent = Entity::new(101, 0);
let floor_rb = RigidBody::new_static();
let floor_transform = Transform::new(Vec3::ZERO);
let floor_vel = Velocity::default();
let floor_collider = Collider::plane(Vec3::new(0.0, 1.0, 0.0), 0.0);
let mut world = PhysicsWorld::new().with_gravity(gravity);
world.add_body(
floor_ent,
floor_rb,
floor_transform,
floor_vel,
floor_collider,
);
for _ in 0..60 {
let _ = world.step(&mut soft_bodies, &mut [], dt);
}
let soft_body = &soft_bodies[0].1;
let top_node = soft_body.nodes[n0 as usize];
assert!(
top_node.position.y > -0.2,
"FEM soft body fell through the floor! y={}",
top_node.position.y
);
}
#[test]
fn test_ecs_physics_system() {
use gizmo_core::world::World;
use gizmo_physics::components::{Collider, RigidBody, Transform, Velocity};
use gizmo_physics::system::physics_step_system;
let mut world = World::new();
world.register_component_type::<RigidBody>();
world.register_component_type::<Transform>();
world.register_component_type::<Velocity>();
world.register_component_type::<Collider>();
world.insert_resource(PhysicsWorld::new().with_gravity(Vec3::new(0.0, -10.0, 0.0)));
let ent = world.spawn();
world.add_component(ent, RigidBody::new(1.0, 0.0, 0.5, true));
world.add_component(ent, Transform::new(Vec3::new(0.0, 10.0, 0.0)));
world.add_component(ent, Velocity::default());
world.add_component(ent, Collider::sphere(1.0));
let dt = 1.0 / 60.0;
physics_step_system(&world, dt);
let query = world.query::<gizmo_core::query::Mut<Velocity>>().unwrap();
let mut found = false;
for (id, vel) in query.iter() {
if id == ent.id() {
assert!(
vel.linear.y < 0.0,
"Velocity should be negative due to gravity, got {}",
vel.linear.y
);
found = true;
}
}
assert!(found, "Entity not found in query");
}
#[test]
fn test_ecs_fracture() {
use gizmo_core::world::World;
use gizmo_physics::components::{Breakable, Collider, RigidBody, Transform, Velocity};
use gizmo_physics::system::{physics_fracture_system, physics_step_system};
let mut world = World::new();
world.register_component_type::<RigidBody>();
world.register_component_type::<Transform>();
world.register_component_type::<Velocity>();
world.register_component_type::<Collider>();
world.register_component_type::<Breakable>();
world.insert_resource(PhysicsWorld::new().with_gravity(Vec3::new(0.0, 0.0, 0.0)));
let glass_ent = world.spawn();
world.add_component(glass_ent, RigidBody::new(10.0, 0.0, 0.0, true));
world.add_component(glass_ent, Transform::new(Vec3::new(0.0, 0.0, 0.0)));
world.add_component(glass_ent, Velocity::default());
world.add_component(glass_ent, Collider::box_collider(Vec3::splat(1.0)));
world.add_component(
glass_ent,
Breakable {
max_pieces: 10,
threshold: 0.0,
current_health: 0.1,
..Default::default()
},
);
let ball_ent = world.spawn();
world.add_component(ball_ent, RigidBody::new(100.0, 0.0, 0.0, true));
world.add_component(ball_ent, Transform::new(Vec3::new(5.0, 0.1, 0.0)));
world.add_component(
ball_ent,
Velocity {
linear: Vec3::new(-10.0, 0.0, 0.0), ..Default::default()
},
);
world.add_component(ball_ent, Collider::box_collider(Vec3::splat(0.5)));
let dt = 1.0 / 60.0;
for _ in 0..40 {
physics_step_system(&world, dt);
physics_fracture_system(&world, dt);
world.apply_commands(); }
assert!(
!world.is_alive(glass_ent),
"Glass entity should have been despawned/fractured"
);
assert!(
world.entity_count() > 2,
"World should contain chunks! Entity count: {}",
world.entity_count()
);
}
#[test]
fn test_ccd_fast_bullet_vs_thin_wall() {
use gizmo_math::Vec3;
use gizmo_physics::components::{Collider, RigidBody, Transform, Velocity};
let mut world = PhysicsWorld::new().with_gravity(Vec3::ZERO);
let wall_ent = gizmo_core::entity::Entity::new(0, 0);
let mut wall_rb = RigidBody::new_static();
wall_rb.ccd_enabled = false;
let wall_transform = Transform::new(Vec3::new(10.0, 0.0, 0.0));
let wall_collider = Collider::box_collider(Vec3::new(0.1, 10.0, 10.0));
let bullet_ent = gizmo_core::entity::Entity::new(1, 0);
let mut bullet_rb = RigidBody::new(1.0, 0.0, 0.0, true);
bullet_rb.ccd_enabled = true; let bullet_transform = Transform::new(Vec3::new(0.0, 0.0, 0.0));
let mut bullet_vel = Velocity::default();
bullet_vel.linear = Vec3::new(1000.0, 0.0, 0.0); let bullet_collider = Collider::sphere(0.5);
world.add_body(
wall_ent,
wall_rb,
wall_transform,
Velocity::default(),
wall_collider,
);
world.add_body(
bullet_ent,
bullet_rb,
bullet_transform,
bullet_vel,
bullet_collider,
);
let dt = 1.0 / 60.0;
let _ = world.step(&mut [], &mut [], dt);
let final_vel = world.velocities[1].linear;
assert!(
final_vel.x < 1000.0,
"Bullet tunneled through the wall without losing speed!"
);
assert!(
world.transforms[1].position.x < 10.5,
"Bullet moved through the wall!"
);
}
#[test]
fn test_soft_soft_collision() {
use gizmo_math::Vec3;
use gizmo_physics::components::Transform;
use gizmo_physics::soft_body::SoftBodyMesh;
let mut world = PhysicsWorld::new().with_gravity(Vec3::ZERO);
let mut soft_body_a = SoftBodyMesh::new(1000.0, 0.3);
soft_body_a.add_node(Vec3::new(0.0, 0.0, 0.0), 1.0);
soft_body_a.add_node(Vec3::new(1.0, 0.0, 0.0), 1.0);
soft_body_a.add_node(Vec3::new(0.0, 1.0, 0.0), 1.0);
soft_body_a.add_node(Vec3::new(0.0, 0.0, 1.0), 1.0);
soft_body_a.add_element(0, 1, 2, 3);
for node in &mut soft_body_a.nodes {
node.position.x -= 1.0;
node.velocity = Vec3::new(5.0, 0.0, 0.0);
}
let mut soft_body_b = SoftBodyMesh::new(1000.0, 0.3);
soft_body_b.add_node(Vec3::new(0.0, 0.0, 0.0), 1.0);
soft_body_b.add_node(Vec3::new(1.0, 0.0, 0.0), 1.0);
soft_body_b.add_node(Vec3::new(0.0, 1.0, 0.0), 1.0);
soft_body_b.add_node(Vec3::new(0.0, 0.0, 1.0), 1.0);
soft_body_b.add_element(0, 1, 2, 3);
for node in &mut soft_body_b.nodes {
node.position.x += 1.0;
node.velocity = Vec3::new(-5.0, 0.0, 0.0);
}
let ent_a = gizmo_core::entity::Entity::new(0, 0);
let ent_b = gizmo_core::entity::Entity::new(1, 0);
let mut soft_bodies = vec![
(ent_a, soft_body_a, Transform::default()),
(ent_b, soft_body_b, Transform::default()),
];
let dt = 1.0 / 60.0;
for _ in 0..30 {
let _ = world.step(&mut soft_bodies, &mut [], dt);
}
let avg_vel_a_x: f32 = soft_bodies[0]
.1
.nodes
.iter()
.map(|n| n.velocity.x)
.sum::<f32>()
/ soft_bodies[0].1.nodes.len() as f32;
let avg_vel_b_x: f32 = soft_bodies[1]
.1
.nodes
.iter()
.map(|n| n.velocity.x)
.sum::<f32>()
/ soft_bodies[1].1.nodes.len() as f32;
assert!(
avg_vel_a_x < 4.0,
"Soft body A did not decelerate! avg_vel_a_x: {}",
avg_vel_a_x
);
assert!(
avg_vel_b_x > -4.0,
"Soft body B did not decelerate! avg_vel_b_x: {}",
avg_vel_b_x
);
}
#[test]
fn test_explosion_system() {
use gizmo_core::world::World;
use gizmo_math::Vec3;
use gizmo_physics::components::{Explosion, RigidBody, Transform, Velocity};
use gizmo_physics::system::physics_explosion_system;
let mut world = World::new();
world.register_component_type::<RigidBody>();
world.register_component_type::<Transform>();
world.register_component_type::<Velocity>();
world.register_component_type::<Explosion>();
let box_ent = world.spawn();
world.add_component(box_ent, Transform::new(Vec3::new(2.0, 0.0, 0.0)));
world.add_component(box_ent, RigidBody::new(10.0, 0.0, 0.0, true)); world.add_component(box_ent, Velocity::default());
let bomb_ent = world.spawn();
world.add_component(bomb_ent, Transform::new(Vec3::ZERO));
world.add_component(
bomb_ent,
Explosion {
force_radius: 5.0,
damage_radius: 5.0,
force: 1000.0,
damage: 100.0,
is_active: true,
..Default::default()
},
);
physics_explosion_system(&world, 0.016);
world.apply_commands();
assert!(
!world.is_alive(bomb_ent),
"Bomb should despawn after exploding"
);
let query = world.query::<gizmo_core::query::Mut<Velocity>>().unwrap();
let mut box_velocity = Vec3::ZERO;
for (id, vel) in query.iter() {
if id == box_ent.id() {
box_velocity = vel.linear;
}
}
assert!(
box_velocity.x > 0.0,
"Box should have positive X velocity from explosion, got {:?}",
box_velocity
);
assert!(
box_velocity.length() > 10.0,
"Box should have significant speed, got {:?}",
box_velocity
);
}
#[test]
fn test_friction() {
use gizmo_core::world::World;
use gizmo_math::Vec3;
use gizmo_physics::components::{Collider, PhysicsMaterial, RigidBody, Transform, Velocity};
use gizmo_physics::system::physics_step_system;
use gizmo_physics::world::PhysicsWorld;
let mut world = World::new();
world.register_component_type::<RigidBody>();
world.register_component_type::<Transform>();
world.register_component_type::<Velocity>();
world.register_component_type::<Collider>();
world.insert_resource(PhysicsWorld::new().with_gravity(Vec3::new(0.0, -9.81, 0.0)));
let mut ground_mat = PhysicsMaterial::default();
ground_mat.static_friction = 0.5;
ground_mat.dynamic_friction = 0.4;
let ground = world.spawn();
world.add_component(ground, Transform::new(Vec3::new(0.0, -1.0, 0.0)));
world.add_component(ground, RigidBody::new(0.0, 0.0, 0.0, false));
world.add_component(
ground,
Collider::box_collider(Vec3::new(10.0, 1.0, 10.0)).with_material(ground_mat),
);
let mut box_mat = PhysicsMaterial::default();
box_mat.static_friction = 0.5;
box_mat.dynamic_friction = 0.4;
let sliding_box = world.spawn();
world.add_component(sliding_box, Transform::new(Vec3::new(0.0, 1.0, 0.0))); world.add_component(sliding_box, RigidBody::new(1.0, 0.0, 0.0, true));
let mut vel = Velocity::default();
vel.linear = Vec3::new(5.0, 0.0, 0.0);
world.add_component(sliding_box, vel);
world.add_component(
sliding_box,
Collider::box_collider(Vec3::splat(1.0)).with_material(box_mat),
);
for _ in 0..60 {
physics_step_system(&world, 1.0 / 60.0);
}
let query = world.query::<gizmo_core::query::Mut<Velocity>>().unwrap();
let mut final_vel = Vec3::new(5.0, 0.0, 0.0);
for (id, v) in query.iter() {
if id == sliding_box.id() {
final_vel = v.linear;
}
}
assert!(
final_vel.x < 4.99,
"Friction should have slowed down the box! Final vel: {}",
final_vel.x
);
assert!(
final_vel.x > 0.0,
"Box shouldn't go backwards! Final vel: {}",
final_vel.x
);
}
#[test]
fn test_explosion_fracture_combo() {
use gizmo_core::world::World;
use gizmo_math::Vec3;
use gizmo_physics::components::{
Breakable, Collider, Explosion, RigidBody, Transform, Velocity,
};
use gizmo_physics::system::physics_explosion_system;
let mut world = World::new();
world.register_component_type::<RigidBody>();
world.register_component_type::<Transform>();
world.register_component_type::<Velocity>();
world.register_component_type::<Collider>();
world.register_component_type::<Breakable>();
world.register_component_type::<Explosion>();
let fragile_box = world.spawn();
world.add_component(fragile_box, Transform::new(Vec3::new(2.0, 0.0, 0.0)));
world.add_component(fragile_box, RigidBody::new(10.0, 0.0, 0.0, true));
world.add_component(fragile_box, Velocity::default());
world.add_component(fragile_box, Collider::box_collider(Vec3::splat(1.0)));
world.add_component(
fragile_box,
Breakable {
max_pieces: 10,
threshold: 50.0, current_health: 50.0,
..Default::default()
},
);
let bomb = world.spawn();
world.add_component(bomb, Transform::new(Vec3::ZERO));
world.add_component(
bomb,
Explosion {
force_radius: 10.0,
damage_radius: 10.0,
force: 5000.0, damage: 5000.0,
is_active: true,
..Default::default()
},
);
assert_eq!(world.entity_count(), 2);
physics_explosion_system(&world, 0.016);
world.apply_commands();
assert!(!world.is_alive(bomb), "Bomb should be despawned");
assert!(
!world.is_alive(fragile_box),
"Fragile box should be despawned due to shatter"
);
assert!(
world.entity_count() >= 3,
"Shards should have been spawned! Count: {}",
world.entity_count()
);
}