use bevy::reflect::Reflect;
use crate::flowfields::{
fields::{Field, FieldCell, cost_field::CostField},
route::RouteStep,
utilities::{CompassDir, FIELD_RESOLUTION},
};
pub const INT_BITS_LOS: u32 = 0b0000_0000_0000_0001_0000_0000_0000_0000;
pub const INT_BITS_GOAL: u32 = 0b0000_0000_0000_0010_0000_0000_0000_0000;
pub const INT_BITS_WAVE_BLOCKED: u32 = 0b0000_0000_0000_0100_0000_0000_0000_0000;
pub const INT_BITS_PORTAL: u32 = 0b0000_0000_0000_1000_0000_0000_0000_0000;
pub const INT_BITS_IMPASSABLE: u32 = 0b0000_0010_0000_0000_0000_0000_0000_0000;
pub const INT_BITS_CORNER: u32 = 0b0000_0100_0000_0000_0000_0000_0000_0000;
pub const INT_FILTER_BITS_COST: u32 = 0b0000_0000_0000_0000_1111_1111_1111_1111;
pub const INT_FILTER_BITS_FLAGS: u32 = 0b1111_1111_1111_1111_0000_0000_0000_0000;
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
#[derive(Clone, Reflect)]
pub struct IntegrationField {
#[cfg_attr(feature = "serde", serde(with = "serde_big_array::BigArray"))]
field: [u32; FIELD_RESOLUTION * FIELD_RESOLUTION],
los_corners: Vec<usize>,
}
impl Default for IntegrationField {
fn default() -> Self {
IntegrationField {
field: [u16::MAX as u32; FIELD_RESOLUTION * FIELD_RESOLUTION],
los_corners: Vec::default(),
}
}
}
impl Field<u32> for IntegrationField {
fn get(&self) -> &[u32; FIELD_RESOLUTION * FIELD_RESOLUTION] {
&self.field
}
fn get_field_cell_value(&self, field_cell: FieldCell) -> u32 {
let index = field_cell.as_1d_index();
self.field[index]
}
fn set_field_cell_value(&mut self, value: u32, field_cell: FieldCell) {
let index = field_cell.as_1d_index();
self.field[index] = value;
}
}
impl IntegrationField {
pub fn init(scaled_costfield: &CostField, route_step: &RouteStep) -> Self {
let mut field = IntegrationField::default();
for (i, value) in scaled_costfield.get().iter().enumerate() {
if *value == 255 {
field.field[i] = 65535 + INT_BITS_IMPASSABLE;
}
}
field.set_goal_value(route_step);
if route_step.portal().is_none() {
let wavefront_cost = 1;
propagate_los(
&mut field,
&[route_step.get_goal()],
wavefront_cost,
route_step.get_goal(),
);
}
field
}
fn set_goal_value(&mut self, route_step: &RouteStep) {
if let Some(window) = route_step.portal() {
let indices = window.get_all_window_cells();
for i in indices.iter() {
self.field[*i] = INT_BITS_PORTAL;
self.los_corners.push(*i);
}
} else {
let goal_index = route_step.get_goal();
self.field[goal_index] = INT_BITS_GOAL;
}
}
pub fn build(&mut self, scaled_costfield: &CostField) {
let mut wavefront = vec![];
for goal in self.los_corners.iter() {
wavefront.push((
(*goal),
self.get_field_cell_value(FieldCell::from_index(*goal)),
));
}
propagate_integrated_wavefront(self, scaled_costfield, wavefront);
}
}
fn propagate_los(
field: &mut IntegrationField,
wavefront: &[usize],
mut wavefront_cost: u32,
goal: usize,
) {
let goal_cell = FieldCell::from_index(goal);
let mut next_wavefront = vec![];
for cell_index in wavefront.iter() {
let wave_cell = FieldCell::from_index(*cell_index);
let neighbours = wave_cell.get_orthogonal_neighbours();
for neighbour in neighbours.iter() {
let n_index = neighbour.as_1d_index();
let cost = field.field[n_index];
if cost & INT_BITS_WAVE_BLOCKED == INT_BITS_WAVE_BLOCKED
|| cost & INT_BITS_GOAL == INT_BITS_GOAL
{
} else if cost & INT_BITS_IMPASSABLE == INT_BITS_IMPASSABLE {
let dir = wave_cell.dir_from_this_to_rhs(neighbour);
match dir {
CompassDir::North | CompassDir::South => {
if let Some(wave_west) =
wave_cell.get_in_compass_direction(&CompassDir::West, 1)
{
let wave_west_cost = field.field[wave_west.as_1d_index()];
if wave_west_cost & INT_BITS_IMPASSABLE != INT_BITS_IMPASSABLE {
if let Some(n_west) =
neighbour.get_in_compass_direction(&CompassDir::West, 1)
{
let n_west_cost = field.field[n_west.as_1d_index()];
if n_west_cost & INT_BITS_IMPASSABLE != INT_BITS_IMPASSABLE {
extend_los_corner(
field,
&n_west,
&goal_cell,
wavefront_cost,
);
}
}
}
}
if let Some(wave_east) =
wave_cell.get_in_compass_direction(&CompassDir::East, 1)
{
let wave_east_cost = field.field[wave_east.as_1d_index()];
if wave_east_cost & INT_BITS_IMPASSABLE != INT_BITS_IMPASSABLE {
if let Some(n_east) =
neighbour.get_in_compass_direction(&CompassDir::East, 1)
{
let n_east_cost = field.field[n_east.as_1d_index()];
if n_east_cost & INT_BITS_IMPASSABLE != INT_BITS_IMPASSABLE {
extend_los_corner(
field,
&n_east,
&goal_cell,
wavefront_cost,
);
}
}
}
}
}
CompassDir::East | CompassDir::West => {
if let Some(wave_north) =
wave_cell.get_in_compass_direction(&CompassDir::North, 1)
{
let wave_north_cost = field.field[wave_north.as_1d_index()];
if wave_north_cost & INT_BITS_IMPASSABLE != INT_BITS_IMPASSABLE {
if let Some(n_north) =
neighbour.get_in_compass_direction(&CompassDir::North, 1)
{
let n_north_cost = field.field[n_north.as_1d_index()];
if n_north_cost & INT_BITS_IMPASSABLE != INT_BITS_IMPASSABLE {
extend_los_corner(
field,
&n_north,
&goal_cell,
wavefront_cost,
);
}
}
}
}
if let Some(wave_south) =
wave_cell.get_in_compass_direction(&CompassDir::South, 1)
{
let wave_south_cost = field.field[wave_south.as_1d_index()];
if wave_south_cost & INT_BITS_IMPASSABLE != INT_BITS_IMPASSABLE {
if let Some(n_south) =
neighbour.get_in_compass_direction(&CompassDir::South, 1)
{
let n_south_cost = field.field[n_south.as_1d_index()];
if n_south_cost & INT_BITS_IMPASSABLE != INT_BITS_IMPASSABLE {
extend_los_corner(
field,
&n_south,
&goal_cell,
wavefront_cost,
);
}
}
}
}
}
_ => panic!("Only orthogonal CompassDir should be here, found {}", dir),
}
} else if cost & INT_BITS_LOS != INT_BITS_LOS {
let mut value = wavefront_cost;
value |= INT_BITS_LOS;
field.field[n_index] = value;
next_wavefront.push(n_index);
}
}
}
wavefront_cost += 1;
if !next_wavefront.is_empty() {
propagate_los(field, &next_wavefront, wavefront_cost, goal);
}
}
fn extend_los_corner(
field: &mut IntegrationField,
corner: &FieldCell,
goal: &FieldCell,
wavefront_cost: u32,
) {
let end = check_los_corner_propagation(corner, goal);
let blocked_cells = corner.get_cells_between_points(&end);
for (i, blocked) in blocked_cells.iter().enumerate() {
let value = field.get_field_cell_value(*blocked);
if value & INT_BITS_IMPASSABLE == INT_BITS_IMPASSABLE {
break;
}
if i > 0 {
let previous = &blocked_cells[i - 1];
match CompassDir::cell_to_cell_direction(*blocked, *previous) {
CompassDir::NorthEast => {
if let Some(south) = CompassDir::get_cell_neighbour(*blocked, CompassDir::South)
&& let Some(west) =
CompassDir::get_cell_neighbour(*blocked, CompassDir::West)
{
let s_v = field.get_field_cell_value(south) & INT_BITS_IMPASSABLE;
let w_v = field.get_field_cell_value(west) & INT_BITS_IMPASSABLE;
if s_v == INT_BITS_IMPASSABLE && w_v == INT_BITS_IMPASSABLE {
break;
}
}
}
CompassDir::SouthEast => {
if let Some(north) = CompassDir::get_cell_neighbour(*blocked, CompassDir::North)
&& let Some(west) =
CompassDir::get_cell_neighbour(*blocked, CompassDir::West)
{
let n_v = field.get_field_cell_value(north) & INT_BITS_IMPASSABLE;
let w_v = field.get_field_cell_value(west) & INT_BITS_IMPASSABLE;
if n_v == INT_BITS_IMPASSABLE && w_v == INT_BITS_IMPASSABLE {
break;
}
}
}
CompassDir::SouthWest => {
if let Some(north) = CompassDir::get_cell_neighbour(*blocked, CompassDir::North)
&& let Some(east) =
CompassDir::get_cell_neighbour(*blocked, CompassDir::East)
{
let n_v = field.get_field_cell_value(north) & INT_BITS_IMPASSABLE;
let e_v = field.get_field_cell_value(east) & INT_BITS_IMPASSABLE;
if n_v == INT_BITS_IMPASSABLE && e_v == INT_BITS_IMPASSABLE {
break;
}
}
}
CompassDir::NorthWest => {
if let Some(south) = CompassDir::get_cell_neighbour(*blocked, CompassDir::South)
&& let Some(east) =
CompassDir::get_cell_neighbour(*blocked, CompassDir::East)
{
let s_v = field.get_field_cell_value(south) & INT_BITS_IMPASSABLE;
let e_v = field.get_field_cell_value(east) & INT_BITS_IMPASSABLE;
if s_v == INT_BITS_IMPASSABLE && e_v == INT_BITS_IMPASSABLE {
break;
}
}
}
CompassDir::Zero => panic!("Neighbour not found"),
_ => {}
}
}
if value & INT_BITS_WAVE_BLOCKED != INT_BITS_WAVE_BLOCKED {
field.los_corners.push(blocked.as_1d_index());
field.set_field_cell_value(
wavefront_cost + 1 + i as u32 + INT_BITS_WAVE_BLOCKED + INT_BITS_CORNER,
*blocked,
);
}
}
}
fn check_los_corner_propagation(corner: &FieldCell, goal: &FieldCell) -> FieldCell {
if corner.get_column() == goal.get_column() {
if corner.get_row() > goal.get_row() {
FieldCell::new(corner.get_column(), FIELD_RESOLUTION - 1)
} else {
FieldCell::new(corner.get_column(), 0)
}
} else if corner.get_row() == goal.get_row() {
if corner.get_column() > goal.get_column() {
FieldCell::new(FIELD_RESOLUTION - 1, corner.get_row())
} else {
FieldCell::new(0, corner.get_row())
}
} else {
let delta_column = corner.get_column() as f32 - goal.get_column() as f32;
let delta_row = corner.get_row() as f32 - goal.get_row() as f32;
let gradient = delta_row / delta_column;
let intercept = -gradient * (corner.get_column() as f32) + corner.get_row() as f32;
if corner.get_column() > goal.get_column() {
let d = (FIELD_RESOLUTION - 1)
.checked_sub(corner.get_column())
.unwrap();
for x in 0..=d {
let end_col = corner.get_column() + x;
let end_row = (gradient * (end_col as f32) + intercept).floor();
if end_row > FIELD_RESOLUTION as f32 - 1.0 {
if end_col < FIELD_RESOLUTION {
return FieldCell::new(end_col, FIELD_RESOLUTION - 1);
} else {
return FieldCell::new(FIELD_RESOLUTION - 1, FIELD_RESOLUTION - 1);
}
} else if end_row < 0.0 {
if end_col < FIELD_RESOLUTION {
return FieldCell::new(end_col, 0);
} else {
return FieldCell::new(FIELD_RESOLUTION - 1, 0);
}
} else if end_col == FIELD_RESOLUTION - 1 {
return FieldCell::new(end_col, end_row as usize);
}
}
panic!("LOS corner prop failed to find increment boundary");
} else {
let d = corner.get_column();
for x in 0..=d {
let end_col = corner.get_column().checked_sub(x).unwrap();
let end_row = (gradient * (end_col as f32) + intercept).floor() as usize;
if end_col == 0 {
if end_row > FIELD_RESOLUTION - 1 {
return FieldCell::new(end_col, FIELD_RESOLUTION - 1);
} else {
return FieldCell::new(end_col, end_row);
}
}
if end_row == 0 {
return FieldCell::new(end_col, end_row);
}
if end_row > FIELD_RESOLUTION - 1 {
return FieldCell::new(end_col, FIELD_RESOLUTION - 1);
}
}
panic!("LOS corner prop failed to find decrement boundary");
}
}
}
fn propagate_integrated_wavefront(
int_field: &mut IntegrationField,
costfield: &CostField,
wavefront: Vec<(usize, u32)>,
) {
let mut next_wavefront = vec![];
for (cell_index, prev_int_cost) in wavefront.iter() {
let neighbours = FieldCell::from_index(*cell_index).get_orthogonal_neighbours();
for n in neighbours.iter() {
let n_int = int_field.get_field_cell_value(*n);
if n_int & INT_BITS_IMPASSABLE != INT_BITS_IMPASSABLE
&& n_int & INT_BITS_LOS != INT_BITS_LOS
{
let cell_cost = costfield.get_field_cell_value(*n) as u32;
let int_cost = cell_cost + (prev_int_cost & INT_FILTER_BITS_COST);
if int_cost < (n_int & INT_FILTER_BITS_COST) {
int_field.set_field_cell_value(int_cost, *n);
next_wavefront.push((n.as_1d_index(), int_cost));
}
}
}
}
if !next_wavefront.is_empty() {
propagate_integrated_wavefront(int_field, costfield, next_wavefront);
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::flowfields::{portal::PortalWindow, sectors::SectorID};
#[test]
fn goal_set_final() {
let costfield = CostField::default();
let sector = SectorID::new(1, 1);
let goal = 14;
let portal = None;
let route_step = RouteStep::new(§or, goal, portal);
let int_field = IntegrationField::init(&costfield, &route_step);
assert!(int_field.field[14] & INT_BITS_GOAL == INT_BITS_GOAL);
}
#[test]
fn goal_set_portal() {
let costfield = CostField::default();
let sector = SectorID::new(1, 1);
let goal = 94;
let portal = Some(PortalWindow::new(
FieldCell::new(0, 9),
FieldCell::new(9, 9),
CompassDir::South,
));
let route_step = RouteStep::new(§or, goal, portal);
let int_field = IntegrationField::init(&costfield, &route_step);
assert!(int_field.field[90] & INT_BITS_PORTAL == INT_BITS_PORTAL);
assert!(int_field.field[91] & INT_BITS_PORTAL == INT_BITS_PORTAL);
assert!(int_field.field[92] & INT_BITS_PORTAL == INT_BITS_PORTAL);
assert!(int_field.field[93] & INT_BITS_PORTAL == INT_BITS_PORTAL);
assert!(int_field.field[94] & INT_BITS_PORTAL == INT_BITS_PORTAL);
assert!(int_field.field[95] & INT_BITS_PORTAL == INT_BITS_PORTAL);
assert!(int_field.field[96] & INT_BITS_PORTAL == INT_BITS_PORTAL);
assert!(int_field.field[97] & INT_BITS_PORTAL == INT_BITS_PORTAL);
assert!(int_field.field[98] & INT_BITS_PORTAL == INT_BITS_PORTAL);
assert!(int_field.field[99] & INT_BITS_PORTAL == INT_BITS_PORTAL);
}
#[test]
fn check_los() {
let mut costfield = CostField::default();
costfield.set_field_cell_value(255, FieldCell::new(3, 5));
costfield.set_field_cell_value(255, FieldCell::new(4, 5));
costfield.set_field_cell_value(255, FieldCell::new(5, 5));
costfield.set_field_cell_value(255, FieldCell::new(2, 9));
costfield.set_field_cell_value(255, FieldCell::new(6, 9));
let sector = SectorID::new(1, 1);
let goal = 84;
let portal = None;
let route_step = RouteStep::new(§or, goal, portal);
let int_field = IntegrationField::init(&costfield, &route_step);
let r_c = FieldCell::new(1, 9);
let r = int_field.get_field_cell_value(r_c);
println!(
"{} :: flags: {:#034b}, cost: {:#034b}",
r_c,
r & INT_FILTER_BITS_FLAGS,
r & INT_FILTER_BITS_COST
);
assert!(r & INT_BITS_WAVE_BLOCKED == INT_BITS_WAVE_BLOCKED);
let r_c = FieldCell::new(2, 5);
let r = int_field.get_field_cell_value(r_c);
println!(
"{} :: flags: {:#034b}, cost: {:#034b}",
r_c,
r & INT_FILTER_BITS_FLAGS,
r & INT_FILTER_BITS_COST
);
assert!(r & INT_BITS_WAVE_BLOCKED == INT_BITS_WAVE_BLOCKED);
let r_c = FieldCell::new(1, 4);
let r = int_field.get_field_cell_value(r_c);
println!(
"{} :: flags: {:#034b}, cost: {:#034b}",
r_c,
r & INT_FILTER_BITS_FLAGS,
r & INT_FILTER_BITS_COST
);
assert!(r & INT_BITS_WAVE_BLOCKED == INT_BITS_WAVE_BLOCKED);
let r_c = FieldCell::new(3, 8);
let r = int_field.get_field_cell_value(r_c);
println!(
"{} :: flags: {:#034b}, cost: {:#034b}",
r_c,
r & INT_FILTER_BITS_FLAGS,
r & INT_FILTER_BITS_COST
);
assert!(r & INT_BITS_LOS == INT_BITS_LOS);
let r_c = FieldCell::new(2, 8);
let r = int_field.get_field_cell_value(r_c);
println!(
"{} :: flags: {:#034b}, cost: {:#034b}",
r_c,
r & INT_FILTER_BITS_FLAGS,
r & INT_FILTER_BITS_COST
);
assert!(r & INT_BITS_WAVE_BLOCKED == INT_BITS_WAVE_BLOCKED);
}
}