use glam::{IVec2, Vec2}; use rapier2d::dynamics::RigidBodyBuilder; use crate::{ config::MIN_ENTITY_CELLS, content::materials::MaterialId, sim::{ cell::Cell, entity::{EntityCells, EntityDef, EntityId, EntityUpdateResult}, lib::components::compute_components, particle_manager::particle::Particle, sim_manager::SimManager, }, }; fn write_entity_to_world(sim: &mut SimManager, entity_id: EntityId) -> Vec<(u8, u8, i32, i32)> { let mut cells_written: Vec<(u8, u8, i32, i32)> = Vec::new(); if let Some(entity) = sim.entities.get(&entity_id) && let Some(cells) = &entity.data.cells && let Some((pos, (cos, sin))) = entity.data._transform(&sim.rb_manager) { let (half_size_x, half_size_y) = ((cells.size.x / 2) as f32, (cells.size.y / 2) as f32); // half-extent of the rotated grid's axis-aligned bounding box, plus a cell of margin let (radius_x, radius_y) = ( half_size_x * (cos.abs() + sin.abs()) + 1.0, half_size_y * (cos.abs() + sin.abs()) + 1.0, ); let world_xl = (pos.x - radius_x).floor() as i32; let world_xu = (pos.x + radius_x).ceil() as i32; let world_yl = (pos.y - radius_y).floor() as i32; let world_yu = (pos.y + radius_y).ceil() as i32; for world_x in world_xl..=world_xu { for world_y in world_yl..=world_yu { if let Some(cur_world_cell) = sim .cell_manager .get_cell_from_game_position(world_x, world_y) && cur_world_cell.material == MaterialId::Void { // same as shader let d = (world_x as f32 + 0.5 - pos.x, world_y as f32 + 0.5 - pos.y); let q = (d.0.floor() + 0.5, d.1.floor() + 0.5); let (lx, ly) = ( (q.0 * cos + q.1 * sin + half_size_x).floor() as i32, (-q.0 * sin + q.1 * cos + half_size_y).floor() as i32, ); if lx < 0 || ly < 0 || lx >= cells.size.x || ly >= cells.size.y { continue; } let mut cell = cells.get_cell_at_local_position(IVec2::new(lx, ly)); if cell.material == MaterialId::Void { continue; } cell.match_parity(sim.cell_manager.seqno); // TODO: OPTIMIZE!! sim.cell_manager .set_cell_from_game_position(world_x, world_y, cell, false); cells_written.push((lx as u8, ly as u8, world_x, world_y)); } } } } cells_written } pub struct WrittenEntitiesScope { cells_written_by_entity: Vec<(EntityId, Vec<(u8, u8, i32, i32)>)>, } impl WrittenEntitiesScope { // TODO can use a hashmap to optimize this pub fn get_entity_id_at_position(&self, position: IVec2) -> Option { self.cells_written_by_entity .iter() .find(|(_, v)| { v.iter() .any(|&(_, _, x, y)| x == position.x && y == position.y) }) .map(|e| e.0) } } pub fn write_entities_to_world( sim: &mut SimManager, // (entity_x, entity_y, cell_x, cell_y) ) -> WrittenEntitiesScope { puffin::profile_function!(); // TODO optimize let entity_ids: Vec = sim.entities.keys().copied().collect(); let mut cells_written_by_entity: Vec<(EntityId, Vec<(u8, u8, i32, i32)>)> = Vec::new(); for entity_id in entity_ids { let cells_written = write_entity_to_world(sim, entity_id); cells_written_by_entity.push((entity_id, cells_written)); } WrittenEntitiesScope { cells_written_by_entity, } } // TODO optimize pub fn read_back_entities_from_world( sim: &mut SimManager, written_entities_scope: WrittenEntitiesScope, ) { puffin::profile_function!(); for (entity_id, cells_written) in written_entities_scope.cells_written_by_entity { let mut entity = sim.entities.get_mut(&entity_id); let mut should_update_entity = false; for (lx, ly, x, y) in cells_written { // update the entity // TODO optimize let new_local_cell = sim.cell_manager.get_cell_from_game_position(x, y).unwrap(); if !new_local_cell.entity_integrated() { // this means that the entity changed in some way, so we should recompute its shape // TODO wait N frames to debounce this should_update_entity = true; } if let Some(entity) = &mut entity { let entity_cells = entity.data.cells.as_mut().unwrap(); // we do this unconditionally because it's cheaper than checking if it actually needs to be updated // and because we don't have a good way to track changes to cell state entity_cells .set_cell_at_local_position(IVec2::new(lx as i32, ly as i32), new_local_cell); } // update the world // TODO optimize sim.cell_manager .set_cell_from_game_position(x, y, Cell::void(), false); } if let Some(entity) = &mut entity && should_update_entity { // correctness: there's no way to get here if the entity is an uncelled entity let ec = entity.data.cells.as_mut().unwrap(); // first check if the entity has been partitioned let components = compute_components(&ec.cells, ec.size.x, ec.size.y); if components.len() == 0 { sim.destroy_entity(entity_id); } else if components.len() == 1 { // the entity's cells were changed, but not partitioned puffin::profile_scope!("Recompute collider"); // if the new entity is too small for a collider, we can turn the entity into particles if components[0].cells.len() < MIN_ENTITY_CELLS { sim.atomize_entity(entity_id); } else if let Some(new_collider) = entity.compute_collider() { // don't profile this separately as it's basically free sim.rb_manager .physics_manager .world .remove_collider(entity.data.collider_h.unwrap()); let new_handle = sim .rb_manager .physics_manager .world .insert_collider(new_collider, Some(entity.data.rb_h.unwrap())); entity.data.collider_h = Some(new_handle); } } else { // the entity was partitioned // destroy the original entity puffin::profile_scope!("Partition entity"); let old_rb = sim .rb_manager .physics_manager .world .bodies .get(entity.data.rb_h.unwrap()) // correctness -- not correct! because we don't have pos without rb .unwrap(); let old_pose = old_rb.position().clone(); let old_linvel = old_rb.linvel().clone(); let old_angvel = old_rb.angvel().clone(); sim.destroy_entity(entity_id); // and create a new entity for each component for component in components { // unless it's too small if component.cells.len() < MIN_ENTITY_CELLS { for x in 0..component.size.x { for y in 0..component.size.y { let r = old_pose.transform_vector( Vec2::new(x as f32, y as f32) - (component.size / 2).as_vec2() + component.position, ); sim.particle_manager.particles.push(Particle::new( old_pose.translation + r, old_linvel, component.cells[(x + y * component.size.x) as usize].material, 3.0, 0.1, )) } } } else { let r = old_pose.transform_vector(component.position); let mut pose = old_pose; pose.translation += r; let rb = RigidBodyBuilder::dynamic() .pose(pose) .linvel(old_linvel) .angvel(old_angvel) .build(); let def = EntityDef::from_cells_and_rb( EntityCells { cells: component.cells, size: component.size, }, rb, None, ); sim.create_entity(def); } } } } } } pub fn process_entity_update_result(sim: &mut SimManager, result: EntityUpdateResult) { for d in result.deferred_destructions { sim.destroy_entity(d); } }