use bevy::{math::u8, prelude::*};
use petgraph::{Directed, stable_graph::StableGraph};
use std::collections::BTreeMap;
use crate::flowfields::{
dimensions::Dimensions,
fields::{Field, FieldCell, cost_field::CostField},
sectors::SectorID,
utilities::{CompassDir, FIELD_RESOLUTION},
};
#[cfg_attr(
feature = "serde",
derive(serde::Deserialize, serde::Serialize),
serde(default)
)]
#[derive(Clone, Default, Reflect)]
pub struct SectorCostFields {
baseline: BTreeMap<SectorID, CostField>,
scaled: BTreeMap<SectorID, CostField>,
#[reflect(ignore)] graphs: BTreeMap<SectorID, StableGraph<u8, u8, Directed, u16>>,
}
impl SectorCostFields {
pub fn new(dimensions: &Dimensions) -> Self {
let mut sector_cost_fields = SectorCostFields::default();
let column_count = dimensions.get_sector_column_count();
let row_count = dimensions.get_sector_row_count();
for m in 0..column_count as i32 {
for n in 0..row_count as i32 {
sector_cost_fields
.baseline
.insert(SectorID::new(m, n), CostField::default());
sector_cost_fields
.graphs
.insert(SectorID::new(m, n), StableGraph::default());
}
}
sector_cost_fields.scale_all_costfields(dimensions);
create_all_graphs(&mut sector_cost_fields.graphs, §or_cost_fields.scaled);
sector_cost_fields
}
pub fn new_with_cost(dimensions: &Dimensions, cost: u8) -> Self {
let mut sector_cost_fields = SectorCostFields::default();
let column_count = dimensions.get_sector_column_count();
let row_count = dimensions.get_sector_row_count();
for m in 0..column_count as i32 {
for n in 0..row_count as i32 {
sector_cost_fields
.baseline
.insert(SectorID::new(m, n), CostField::new_with_cost(cost));
sector_cost_fields
.graphs
.insert(SectorID::new(m, n), StableGraph::default());
}
}
sector_cost_fields.scale_all_costfields(dimensions);
create_all_graphs(&mut sector_cost_fields.graphs, §or_cost_fields.scaled);
sector_cost_fields
}
#[cfg(feature = "ron")]
pub fn from_ron(path: String, dimensions: &Dimensions) -> Self {
let file = std::fs::File::open(path).expect("Failed opening CostField file");
let mut fields: SectorCostFields = match ron::de::from_reader(file) {
Ok(fields) => fields,
Err(e) => panic!("Failed deserializing SectorCostFields: {}", e),
};
fields.scale_all_costfields(dimensions);
for key in fields.baseline.keys() {
fields.graphs.insert(*key, StableGraph::default());
}
create_all_graphs(&mut fields.graphs, &fields.scaled);
fields
}
#[cfg(feature = "heightmap")]
pub fn from_heightmap(dimensions: &Dimensions, path: String) -> Self {
use photon_rs::native::open_image;
let img = open_image(&path).expect("Failed to open heightmap");
let img_width = img.get_width();
let img_height = img.get_height();
let hori_sector_count = dimensions.get_sector_column_count();
let required_px_width = hori_sector_count as u32 * FIELD_RESOLUTION as u32;
if img_width != required_px_width {
panic!(
"Heightmap has incorrect width, expected width of {} pixels, found {}",
required_px_width, img_width
);
}
let vert_sector_count = dimensions.get_sector_row_count();
let required_px_height = vert_sector_count as u32 * FIELD_RESOLUTION as u32;
if img_height != required_px_height {
panic!(
"Heightmap has incorrect height, expected hieght of {} pixels, found {}",
required_px_height, img_height
);
}
let mut sector_cost_fields = SectorCostFields::new(dimensions);
let raw_pixels = img.get_raw_pixels();
let len_if_alpha = img_height * img_height * 4;
let chunk_size = {
if len_if_alpha as usize == raw_pixels.len() {
4
} else {
3
}
};
let mut pixels_rgb: Vec<(u8, u8, u8)> = Vec::new();
for rgb in raw_pixels.chunks(chunk_size) {
let mut as_tuple = vec![(rgb[0], rgb[1], rgb[2])];
pixels_rgb.append(&mut as_tuple);
}
for (line_number, rgba_slice) in pixels_rgb.chunks(img_width as usize).enumerate() {
let sector_row = line_number / FIELD_RESOLUTION;
for (sector_column, rgba_slice_slice) in rgba_slice.chunks(FIELD_RESOLUTION).enumerate()
{
let sector_id = SectorID::new(sector_column as i32, sector_row as i32);
let field = sector_cost_fields.baseline.get_mut(§or_id).unwrap();
for (field_column, px) in rgba_slice_slice.iter().enumerate() {
let field_row = line_number - (FIELD_RESOLUTION * sector_row);
let field_cell = FieldCell::new(field_column, field_row);
let colour_avg = (px.0 as f32 + px.1 as f32 + px.2 as f32) / 3.0;
let value = (255 - colour_avg as u8).clamp(1, 255);
field.set_field_cell_value(value, field_cell);
}
}
}
sector_cost_fields.scale_all_costfields(dimensions);
for key in sector_cost_fields.baseline.keys() {
sector_cost_fields
.graphs
.insert(*key, StableGraph::default());
}
create_all_graphs(&mut sector_cost_fields.graphs, §or_cost_fields.scaled);
sector_cost_fields
}
pub fn scale_all_costfields(&mut self, dimensions: &Dimensions) {
let sector_ids: Vec<SectorID> = self.baseline.keys().cloned().collect();
for sector_id in sector_ids.iter() {
self.scaled
.insert(*sector_id, self.baseline.get(sector_id).unwrap().clone());
}
if dimensions.get_actor_scale() == 1 {
return;
}
for sector_id in sector_ids.iter() {
self.scale_costfield(sector_id, dimensions);
}
}
fn scale_costfield(&mut self, sector_id: &SectorID, dimensions: &Dimensions) {
let scale_count = dimensions.get_actor_scale();
let base = self.baseline.get(sector_id).unwrap();
let scaled = &mut self.scaled;
let base_field = base.get();
for (index, value) in base_field.iter().enumerate() {
if *value == 255 {
let cell = FieldCell::from_index(index);
scale_in_compass_direction(
&CompassDir::North,
scale_count,
cell,
sector_id,
scaled,
);
scale_in_compass_direction(&CompassDir::East, scale_count, cell, sector_id, scaled);
scale_in_compass_direction(
&CompassDir::South,
scale_count,
cell,
sector_id,
scaled,
);
scale_in_compass_direction(&CompassDir::West, scale_count, cell, sector_id, scaled);
scale_in_compass_direction(
&CompassDir::NorthEast,
scale_count,
cell,
sector_id,
scaled,
);
scale_in_compass_direction(
&CompassDir::SouthEast,
scale_count,
cell,
sector_id,
scaled,
);
scale_in_compass_direction(
&CompassDir::SouthWest,
scale_count,
cell,
sector_id,
scaled,
);
scale_in_compass_direction(
&CompassDir::NorthWest,
scale_count,
cell,
sector_id,
scaled,
);
}
}
}
pub fn get_scaled_costs(&self) -> &BTreeMap<SectorID, CostField> {
&self.scaled
}
pub fn get_graphs(&self) -> &BTreeMap<SectorID, StableGraph<u8, u8, Directed, u16>> {
&self.graphs
}
pub fn set_field_cost(
&mut self,
sector: &SectorID,
field_cell: &FieldCell,
cost: u8,
dimensions: &Dimensions,
) {
if let Some(field) = self.baseline.get_mut(sector) {
field.set_field_cell_value(cost, *field_cell);
*self.scaled.get_mut(sector).unwrap() = field.clone();
}
let mut adjacent_sectors = vec![];
for adjacent_sector in sector.get_surrounding_sectors() {
if self.baseline.contains_key(&adjacent_sector) {
adjacent_sectors.push(adjacent_sector);
}
}
for adjacent in adjacent_sectors.iter() {
if let Some(base_field) = self.baseline.get(adjacent) {
*self.scaled.get_mut(adjacent).unwrap() = base_field.clone();
}
}
self.scale_costfield(sector, dimensions);
for adjacent in adjacent_sectors.iter() {
self.scale_costfield(adjacent, dimensions);
}
wipe_sector_graph(sector, &mut self.graphs);
if let Some(graph) = self.graphs.get_mut(sector) {
create_graph_for_sector(sector, graph, &self.scaled);
}
for adjacent in adjacent_sectors.iter() {
wipe_sector_graph(adjacent, &mut self.graphs);
if let Some(graph) = self.graphs.get_mut(adjacent) {
create_graph_for_sector(adjacent, graph, &self.scaled);
}
}
}
}
fn scale_in_compass_direction(
compass_dir: &CompassDir,
scale_count: u32,
origin_cell: FieldCell,
origin_sector_id: &SectorID,
scaled: &mut BTreeMap<SectorID, CostField>,
) {
let mut has_hit_wall = false;
let mut fields_to_mark = vec![];
'scale_loop: for n in 1..=scale_count {
let (sector_delta, next_cell) =
compass_dir.step_cell_in_direction(&origin_cell, n as usize);
let next_sector = SectorID::new(
origin_sector_id.column + sector_delta.column,
origin_sector_id.row + sector_delta.row,
);
if let Some(cost_field) = scaled.get(&next_sector) {
let cost = cost_field.get_field_cell_value(next_cell);
if cost == 255 {
has_hit_wall = true;
break 'scale_loop;
} else {
fields_to_mark.push((*origin_sector_id, next_cell));
}
} else {
has_hit_wall = true;
break;
}
}
if has_hit_wall {
for (sector, cell) in fields_to_mark.iter() {
if let Some(field) = scaled.get_mut(sector) {
field.set_field_cell_value(255, *cell);
}
}
}
}
fn create_all_graphs(
graphs: &mut BTreeMap<SectorID, StableGraph<u8, u8, Directed, u16>>,
scaled_costs: &BTreeMap<SectorID, CostField>,
) {
for (sector, graph) in graphs.iter_mut() {
create_graph_for_sector(sector, graph, scaled_costs);
}
}
fn create_graph_for_sector(
sector: &SectorID,
graph: &mut StableGraph<u8, u8, Directed, u16>,
scaled_costs: &BTreeMap<SectorID, CostField>,
) {
for _ in 0..FIELD_RESOLUTION * FIELD_RESOLUTION {
graph.add_node(1);
}
let sector_costs = scaled_costs.get(sector).unwrap();
for n in 0..FIELD_RESOLUTION * FIELD_RESOLUTION {
let origin = FieldCell::from_index(n);
let origin_cost = sector_costs.get_field_cell_value(origin);
if origin_cost == 255 {
continue;
}
let neighbours = origin.get_orthogonal_neighbours();
for n_cell in neighbours.iter() {
let n_cost = sector_costs.get_field_cell_value(*n_cell);
if n_cost == 255 {
continue;
}
let index = n_cell.as_1d_index() as u16;
graph.add_edge((n as u16).into(), index.into(), 1);
}
}
}
fn wipe_sector_graph(
sector: &SectorID,
graphs: &mut BTreeMap<SectorID, StableGraph<u8, u8, Directed, u16>>,
) {
if let Some(graph) = graphs.get_mut(sector) {
graph.clear();
}
}
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
#[derive(Clone, Default, Reflect)]
pub struct CostFieldUpdateItem {
sector: SectorID,
cell: FieldCell,
cost: u8,
}
impl CostFieldUpdateItem {
pub fn new(sector_id: &SectorID, cell: &FieldCell, cost: u8) -> Self {
CostFieldUpdateItem {
sector: *sector_id,
cell: *cell,
cost,
}
}
pub fn sector(&self) -> &SectorID {
&self.sector
}
pub fn cell(&self) -> &FieldCell {
&self.cell
}
pub fn cost(&self) -> u8 {
self.cost
}
}
#[cfg(test)]
mod tests {
use petgraph::graph::NodeIndex;
use super::*;
#[test]
fn scale_one_field() {
let origin = (0.0, 0.0);
let size = (20.0, 20.0);
let world_unit_size = 1.0;
let actor_radius = 1.5;
let dimensions = Dimensions::new(origin, size, world_unit_size, actor_radius);
let mut sector_costs = SectorCostFields::new(&dimensions);
let mutate_sector = SectorID::new(0, 0);
let mutate_cell1 = FieldCell::new(4, 4);
let mutate_cell2 = FieldCell::new(6, 4);
let cost = 255;
sector_costs.set_field_cost(&mutate_sector, &mutate_cell1, cost, &dimensions);
sector_costs.set_field_cost(&mutate_sector, &mutate_cell2, cost, &dimensions);
let scaled_cell = FieldCell::new(5, 4);
let result = sector_costs
.scaled
.get(&mutate_sector)
.unwrap()
.get_field_cell_value(scaled_cell);
assert!(result == 255)
}
#[test]
fn scale_across_fields1() {
let origin = (0.0, 0.0);
let size = (20.0, 20.0);
let world_unit_size = 1.0;
let actor_radius = 1.5;
let dimensions = Dimensions::new(origin, size, world_unit_size, actor_radius);
let mut sector_costs = SectorCostFields::new(&dimensions);
let mutate_sector1 = SectorID::new(0, 0);
let mutate_cell1 = FieldCell::new(9, 3);
let mutate_sector2 = SectorID::new(1, 0);
let mutate_cell2 = FieldCell::new(1, 5);
let cost = 255;
sector_costs.set_field_cost(&mutate_sector1, &mutate_cell1, cost, &dimensions);
sector_costs.set_field_cost(&mutate_sector2, &mutate_cell2, cost, &dimensions);
let scaled_cell = FieldCell::new(0, 4);
let result = sector_costs
.scaled
.get(&mutate_sector2)
.unwrap()
.get_field_cell_value(scaled_cell);
assert!(result == 255)
}
#[test]
fn graph_path_unmodified() {
let origin = (0.0, 0.0);
let size = (20.0, 20.0);
let world_unit_size = 1.0;
let actor_radius = 0.5;
let dimensions = Dimensions::new(origin, size, world_unit_size, actor_radius);
let sector_costs = SectorCostFields::new(&dimensions);
let sector = SectorID::new(0, 0);
let start = 0;
let end = 9;
let graph = sector_costs.graphs.get(§or).unwrap();
let result = petgraph::algo::astar(
graph,
start.into(),
|finish| finish == end.into(),
|edge| *edge.weight(),
|_| 0,
)
.unwrap();
let result_cost = result.0;
let result_path = result.1;
let actual_cost = 9;
let actual_path = vec![
NodeIndex::new(0),
NodeIndex::new(1),
NodeIndex::new(2),
NodeIndex::new(3),
NodeIndex::new(4),
NodeIndex::new(5),
NodeIndex::new(6),
NodeIndex::new(7),
NodeIndex::new(8),
NodeIndex::new(9),
];
assert_eq!(actual_cost, result_cost);
assert_eq!(actual_path, result_path);
}
#[test]
fn graph_path_modified() {
let origin = (0.0, 0.0);
let size = (20.0, 20.0);
let world_unit_size = 1.0;
let actor_radius = 0.5;
let dimensions = Dimensions::new(origin, size, world_unit_size, actor_radius);
let mut sector_costs = SectorCostFields::new(&dimensions);
let sector = SectorID::new(0, 0);
let mutate_cell = FieldCell::new(1, 0);
sector_costs.set_field_cost(§or, &mutate_cell, 255, &dimensions);
let start = 0;
let end = 2;
let graph = sector_costs.graphs.get(§or).unwrap();
let result = petgraph::algo::astar(
graph,
start.into(),
|finish| finish == end.into(),
|edge| *edge.weight(),
|_| 0,
)
.unwrap();
let result_cost = result.0;
let result_path = result.1;
let actual_cost = 4;
let actual_path = vec![
NodeIndex::new(0),
NodeIndex::new(10),
NodeIndex::new(11),
NodeIndex::new(12),
NodeIndex::new(2),
];
assert_eq!(actual_cost, result_cost);
assert_eq!(actual_path, result_path);
}
#[test]
fn graph_path_scaled() {
let origin = (0.0, 0.0);
let size = (20.0, 20.0);
let world_unit_size = 1.0;
let actor_radius = 1.5;
let dimensions = Dimensions::new(origin, size, world_unit_size, actor_radius);
let mut sector_costs = SectorCostFields::new(&dimensions);
let sector = SectorID::new(0, 0);
let mutates = [
FieldCell::new(3, 0),
FieldCell::new(3, 1),
FieldCell::new(3, 2),
FieldCell::new(3, 3),
FieldCell::new(3, 4),
FieldCell::new(3, 6),
FieldCell::new(3, 7),
FieldCell::new(3, 8),
FieldCell::new(3, 9),
];
for cell in mutates.iter() {
sector_costs.set_field_cost(§or, cell, 255, &dimensions);
}
let start = 17;
let end = 11;
let graph = sector_costs.graphs.get(§or).unwrap();
let result = petgraph::algo::astar(
graph,
start.into(),
|finish| finish == end.into(),
|edge| *edge.weight(),
|_| 0,
);
assert!(result.is_none());
}
#[test]
fn graph_scaled_world_boundary() {
let origin = (0.0, 0.0);
let size = (20.0, 20.0);
let world_unit_size = 1.0;
let actor_radius = 1.5;
let dimensions = Dimensions::new(origin, size, world_unit_size, actor_radius);
let mut sector_costs = SectorCostFields::new(&dimensions);
let sector = SectorID::new(0, 0);
let mutate = FieldCell::new(1, 1);
sector_costs.set_field_cost(§or, &mutate, 255, &dimensions);
let blocked_cell1 = FieldCell::new(0, 1);
let blocked_cell2 = FieldCell::new(1, 0);
let blocked_cost1 = sector_costs
.scaled
.get(§or)
.unwrap()
.get_field_cell_value(blocked_cell1);
let blocked_cost2 = sector_costs
.scaled
.get(§or)
.unwrap()
.get_field_cell_value(blocked_cell2);
assert!(blocked_cost1 == 255);
assert!(blocked_cost2 == 255);
}
}