use glam::Vec2; use rand::random_range; use rapier2d::{dynamics::RigidBodyHandle, parry::bounding_volume::Aabb, pipeline::QueryFilter}; use crate::{ content::materials::{MaterialForm, MaterialId, fire::FireCellView}, sim::{cell::Cell, lib::ray::AwDda, particle_manager::particle::Particle, sim_manager::SimCtx}, }; pub fn apply_explosion( ctx: &mut SimCtx, centre: Vec2, radius: i32, force_offset: Vec2, force_multiplier: f32, ) { let get_vel = |pos: Vec2| { let d = pos.distance(centre); if d < 0.001 || d > radius as f32 { return Vec2::ZERO; } let dir = (pos - centre) / d; let falloff = 1.0 - (d / radius as f32); (force_offset + dir) * force_multiplier * random_range(0.5..1.5) * falloff }; // cell grid for x in -radius..radius { for y in -radius..radius { if x.pow(2) + y.pow(2) < radius.pow(2) { // if there's a cell, convert it to a particle let pos = Vec2::new(centre.x + x as f32, centre.y + y as f32); let ipos = pos.round().as_ivec2(); if let Some(cell) = ctx.cell_manager.get_cell_from_game_position(ipos.x, ipos.y) { let mut fire = Cell::from_material(MaterialId::Fire); fire.set_ticks_lived(300); fire.match_parity(ctx.cell_manager.seqno); if cell.material == MaterialId::Void && random_range(0.0..1.0) > 0.5 { if random_range(0.0..1.0) > 0.1 { ctx.cell_manager .set_cell_from_game_position(ipos.x, ipos.y, fire, false); } else { let vel = get_vel(pos); ctx.particle_manager.particles.push(Particle::new( pos, vel, MaterialId::Fire, 1.0, 0.0, )); } } else if [ MaterialForm::Solid, MaterialForm::Liquid, MaterialForm::Powder, ] .contains(&cell.material.def().form) { ctx.cell_manager .set_cell_from_game_position(ipos.x, ipos.y, fire, false); if random_range(0.0..1.0) > 0.6 { let vel = get_vel(pos); ctx.particle_manager.particles.push(Particle::new( pos, vel, cell.material, 5.0, 0.0, )); } } } } } } // rigidbodies let colliders = ctx .rb_manager .physics_manager .world .intersect_aabb_conservative( Aabb::new(centre - radius as f32, centre + radius as f32), QueryFilter::only_dynamic(), ); let body_handles: Vec> = colliders.map(|(_, c)| c.parent()).collect(); for h in body_handles { if let Some(h) = h && let Some(body) = ctx.rb_manager.physics_manager.world.bodies.get_mut(h) { // magic number for good vibes body.apply_impulse(get_vel(body.translation()) * 3.0, true); } } } // TODO profile packing entity ID into cells? // TODO sink power in a radius around the impact // might improve performance by a lot if we don't have to raycast for hit entities pub fn apply_bullet(ctx: &mut SimCtx, from: Vec2, to: Vec2, power: u32) -> Vec2 { let dda = AwDda::new(from, to); let mut p = power as i32; let dir = (to - from).normalize(); let mut stopped_at = to; for collision in dda { if let Some(cell) = ctx .cell_manager .get_cell_from_game_position(collision.x, collision.y) { let m = cell.material.def(); if ![ MaterialForm::Solid, MaterialForm::Powder, MaterialForm::Liquid, ] .contains(&m.form) { continue; } p -= m.density as i32; if p <= 0 { stopped_at = collision.as_vec2(); break; } ctx.cell_manager.set_cell_from_game_position( collision.x, collision.y, Cell::void(), false, ); if random_range(0.0..1.0) > 0.8 { ctx.particle_manager.particles.push(Particle::new( collision.as_vec2(), (dir + Vec2::new(random_range(-0.15..0.15), random_range(-0.15..0.15))) * 150.0, cell.material, 5.0, 0.2, )); } } } stopped_at }