use crate::tui::ecs::world::*;
use freecs::Entity;
use std::collections::{HashMap, HashSet};
pub fn movement_system(world: &mut World) {
let updates: Vec<(Entity, f64, f64)> = world
.query_entities(POSITION | VELOCITY)
.filter_map(|entity| {
world
.get_velocity(entity)
.map(|velocity| (entity, velocity.column, velocity.row))
})
.collect();
for (entity, delta_column, delta_row) in updates {
if let Some(position) = world.get_position_mut(entity) {
position.column += delta_column;
position.row += delta_row;
}
}
}
pub struct Contact {
pub entity_a: Entity,
pub entity_b: Entity,
pub normal_column: f64,
pub normal_row: f64,
pub penetration: f64,
}
const GRID_CELL_SIZE: f64 = 4.0;
struct ColliderAabb {
entity: Entity,
left: f64,
right: f64,
top: f64,
bottom: f64,
layer: u32,
mask: u32,
}
pub fn collision_pairs(world: &World) -> Vec<Contact> {
let entities: Vec<Entity> = world.query_entities(POSITION | COLLIDER).collect();
let aabbs: Vec<ColliderAabb> = entities
.iter()
.filter_map(|&entity| {
let position = world.get_position(entity)?;
let collider = world.get_collider(entity)?;
let left = position.column + collider.offset_column;
let top = position.row + collider.offset_row;
Some(ColliderAabb {
entity,
left,
right: left + collider.width as f64,
top,
bottom: top + collider.height as f64,
layer: collider.layer,
mask: collider.mask,
})
})
.collect();
let mut grid: HashMap<(i32, i32), Vec<usize>> = HashMap::new();
for (index, aabb) in aabbs.iter().enumerate() {
let min_cell_column = (aabb.left / GRID_CELL_SIZE).floor() as i32;
let max_cell_column = (aabb.right / GRID_CELL_SIZE).floor() as i32;
let min_cell_row = (aabb.top / GRID_CELL_SIZE).floor() as i32;
let max_cell_row = (aabb.bottom / GRID_CELL_SIZE).floor() as i32;
for cell_column in min_cell_column..=max_cell_column {
for cell_row in min_cell_row..=max_cell_row {
grid.entry((cell_column, cell_row)).or_default().push(index);
}
}
}
let mut checked: HashSet<(usize, usize)> = HashSet::new();
let mut contacts = Vec::new();
for cell_indices in grid.values() {
for outer in 0..cell_indices.len() {
for inner in (outer + 1)..cell_indices.len() {
let index_a = cell_indices[outer];
let index_b = cell_indices[inner];
let pair = if index_a < index_b {
(index_a, index_b)
} else {
(index_b, index_a)
};
if !checked.insert(pair) {
continue;
}
let a = &aabbs[pair.0];
let b = &aabbs[pair.1];
if (a.layer & b.mask) == 0 || (b.layer & a.mask) == 0 {
continue;
}
let overlap_left = a.right - b.left;
let overlap_right = b.right - a.left;
let overlap_top = a.bottom - b.top;
let overlap_bottom = b.bottom - a.top;
if overlap_left <= 0.0
|| overlap_right <= 0.0
|| overlap_top <= 0.0
|| overlap_bottom <= 0.0
{
continue;
}
let overlap_x = overlap_left.min(overlap_right);
let overlap_y = overlap_top.min(overlap_bottom);
let (normal_column, normal_row, penetration) = if overlap_x < overlap_y {
let direction = if overlap_left < overlap_right {
-1.0
} else {
1.0
};
(direction, 0.0, overlap_x)
} else {
let direction = if overlap_top < overlap_bottom {
-1.0
} else {
1.0
};
(0.0, direction, overlap_y)
};
contacts.push(Contact {
entity_a: a.entity,
entity_b: b.entity,
normal_column,
normal_row,
penetration,
});
}
}
}
contacts
}
pub fn resolve_collision(world: &mut World, contact: &Contact) {
let half_penetration = contact.penetration / 2.0;
let push_column = contact.normal_column * half_penetration;
let push_row = contact.normal_row * half_penetration;
if let Some(position) = world.get_position_mut(contact.entity_a) {
position.column -= push_column;
position.row -= push_row;
}
if let Some(position) = world.get_position_mut(contact.entity_b) {
position.column += push_column;
position.row += push_row;
}
}
pub fn resolve_collision_static(world: &mut World, contact: &Contact, static_entity: Entity) {
let push_column = contact.normal_column * contact.penetration;
let push_row = contact.normal_row * contact.penetration;
if contact.entity_a == static_entity {
if let Some(position) = world.get_position_mut(contact.entity_b) {
position.column += push_column;
position.row += push_row;
}
} else if let Some(position) = world.get_position_mut(contact.entity_a) {
position.column -= push_column;
position.row -= push_row;
}
}
pub fn parent_transform_system(world: &mut World) {
let updates: Vec<(Entity, f64, f64)> = world
.query_entities(PARENT | LOCAL_OFFSET | POSITION)
.filter_map(|entity| {
let parent_component = world.get_parent(entity)?;
let parent_entity = parent_component.0;
let parent_position = world.get_position(parent_entity)?;
let local_offset = world.get_local_offset(entity)?;
let target_column = parent_position.column + local_offset.column;
let target_row = parent_position.row + local_offset.row;
Some((entity, target_column, target_row))
})
.collect();
for (entity, column, row) in updates {
if let Some(position) = world.get_position_mut(entity) {
position.column = column;
position.row = row;
}
}
}
pub fn cascade_despawn(world: &mut World, parent_entity: Entity) {
let children: Vec<Entity> = world
.query_entities(PARENT)
.filter(|&entity| {
world
.get_parent(entity)
.is_some_and(|parent| parent.0 == parent_entity)
})
.collect();
for child in &children {
cascade_despawn(world, *child);
}
if !children.is_empty() {
world.despawn_entities(&children);
}
}