blackout/src/pathfinding.rs

374 lines
12 KiB
Rust
Raw Normal View History

use bevy::{
2022-01-21 00:06:20 +00:00
ecs::entity::Entities,
prelude::*,
tasks::{prelude::*, Task},
utils::HashMap,
};
use bevy_rapier2d::{
2022-05-06 16:07:59 +00:00
na::{Isometry2, Vector2},
prelude::*,
2022-07-12 17:43:43 +00:00
rapier::prelude::{ColliderHandle, ColliderSet, QueryPipeline, RigidBodySet},
};
use futures_lite::future;
use leafwing_input_manager::{axislike::DualAxisData, plugin::InputManagerSystem, prelude::*};
2021-05-13 17:25:45 +00:00
use pathfinding::prelude::*;
use crate::{
commands::RunIfExistsExt,
2022-05-10 18:56:49 +00:00
core::PointLike,
map::{Map, MapObstruction},
navigation::{NavigationAction, RotationSpeed},
2021-05-13 17:25:45 +00:00
};
#[derive(PartialEq, Eq, Clone, Copy, Hash, Debug)]
pub struct NegotiatePathAction;
impl Actionlike for NegotiatePathAction {
2023-02-01 20:08:06 +00:00
fn n_variants() -> usize {
1
}
fn get_at(index: usize) -> Option<Self> {
if index == 0 {
Some(Self)
} else {
None
}
}
fn index(&self) -> usize {
0
}
}
2022-01-10 19:50:52 +00:00
#[derive(Component, Debug, Deref, DerefMut)]
struct Calculating(Task<Option<Path>>);
2022-01-10 19:50:52 +00:00
#[derive(Component, Clone, Copy, Debug, Default, Deref, DerefMut, Eq, Hash, PartialEq, Reflect)]
2021-05-13 17:25:45 +00:00
#[reflect(Component)]
pub struct Destination(pub (i32, i32));
impl_pointlike_for_tuple_component!(Destination);
impl_pointlike_for_tuple_component!(&Destination);
2021-05-13 17:25:45 +00:00
2022-01-10 19:50:52 +00:00
#[derive(Component, Clone, Debug, Default, Reflect)]
2021-07-27 16:17:06 +00:00
#[reflect(Component)]
pub struct NoPath;
2022-01-10 19:50:52 +00:00
#[derive(Component, Clone, Debug, Default, Deref, DerefMut, Reflect)]
2021-05-13 17:25:45 +00:00
#[reflect(Component)]
pub struct Path(pub Vec<(i32, i32)>);
#[derive(Component, Clone, Debug, Default, Reflect, Deref, DerefMut)]
#[reflect(Component)]
pub struct CostMap(pub HashMap<(i32, i32), f32>);
2022-03-15 15:37:28 +00:00
pub fn find_path<D: 'static + Clone + Default + Send + Sync>(
2021-05-13 17:25:45 +00:00
start: &dyn PointLike,
destination: &dyn PointLike,
2022-03-15 15:37:28 +00:00
map: &Map<D>,
cost_map: &Option<CostMap>,
2021-05-13 17:25:45 +00:00
) -> Option<(Vec<(i32, i32)>, u32)> {
astar(
&start.into(),
|p| {
let mut successors: Vec<((i32, i32), u32)> = vec![];
if p.0 >= 0 && p.1 >= 0 {
if let Some(tile) = map.at(p.0 as usize, p.1 as usize) {
if tile.is_walkable() {
for tile in map.get_available_exits(p.0 as usize, p.1 as usize) {
let mut cost = tile.2 * 100.;
if let Some(cost_map) = cost_map {
if let Some(modifier) =
cost_map.get(&(tile.0 as i32, tile.1 as i32))
{
cost *= modifier;
}
}
successors.push(((tile.0 as i32, tile.1 as i32), cost as u32));
}
2022-08-27 14:11:37 +00:00
}
2021-09-16 19:26:45 +00:00
}
2021-05-13 17:25:45 +00:00
}
successors
},
|p| (p.distance_squared(destination) * 100.) as u32,
|p| *p == destination.into(),
)
}
fn find_path_for_shape(
2022-05-06 16:07:59 +00:00
initiator: ColliderHandle,
2022-05-10 18:56:49 +00:00
start: Transform,
destination: Destination,
cost_map: &Option<CostMap>,
2022-05-06 16:07:59 +00:00
query_pipeline: QueryPipeline,
collider_set: ColliderSet,
2022-07-12 17:43:43 +00:00
rigid_body_set: RigidBodySet,
2022-05-06 16:07:59 +00:00
shape: Collider,
) -> Option<Path> {
let path = astar(
&start.i32(),
|p| {
let mut successors: Vec<((i32, i32), u32)> = vec![];
2022-12-19 20:08:31 +00:00
let x = p.0;
let y = p.1;
let exits = vec![
((x - 1, y), 1.),
((x + 1, y), 1.),
((x, y - 1), 1.),
((x, y + 1), 1.),
((x - 1, y - 1), 1.5),
((x + 1, y - 1), 1.5),
((x - 1, y + 1), 1.5),
((x + 1, y + 1), 1.5),
];
for exit in &exits {
let mut should_push = true;
let shape_pos =
Isometry2::new(Vector2::new(exit.0 .0 as f32, exit.0 .1 as f32), 0.);
query_pipeline.intersections_with_shape(
&rigid_body_set,
&collider_set,
&shape_pos,
&*shape.raw,
bevy_rapier2d::rapier::pipeline::QueryFilter::new()
.predicate(&|h, _c| h != initiator),
|_handle| {
should_push = false;
false
},
);
if should_push {
let mut cost = exit.1 * 100.;
if let Some(cost_map) = cost_map {
if let Some(modifier) = cost_map.get(&(exit.0 .0, exit.0 .1)) {
cost *= modifier;
2022-08-27 14:11:37 +00:00
}
2021-09-16 19:26:45 +00:00
}
successors.push((exit.0, cost as u32));
}
}
successors
},
|p| (p.distance_squared(&destination) * 100.) as u32,
|p| *p == destination.i32(),
);
if let Some(path) = path {
Some(Path(path.0))
} else {
None
}
}
fn calculate_path(
2021-05-13 17:25:45 +00:00
mut commands: Commands,
2022-05-06 16:07:59 +00:00
rapier_context: Res<RapierContext>,
obstructions: Query<&RapierColliderHandle, With<MapObstruction>>,
2022-01-10 19:50:52 +00:00
query: Query<
2022-05-06 16:07:59 +00:00
(
Entity,
&RapierColliderHandle,
&Destination,
2022-05-10 18:56:49 +00:00
&Transform,
2022-05-06 16:07:59 +00:00
&Collider,
Option<&CostMap>,
2022-05-06 16:07:59 +00:00
),
2022-01-10 19:50:52 +00:00
Changed<Destination>,
>,
2021-05-13 17:25:45 +00:00
) {
for (entity, handle, destination, coordinates, shape, cost_map) in &query {
2021-07-21 20:20:02 +00:00
if coordinates.i32() == **destination {
2021-07-27 16:17:06 +00:00
commands
.entity(entity)
.remove::<Path>()
2021-07-28 01:17:36 +00:00
.remove::<NoPath>()
.remove::<Calculating>()
.remove::<Destination>();
2021-07-21 20:20:02 +00:00
continue;
}
let coordinates_clone = *coordinates;
let destination_clone = *destination;
let query_pipeline = rapier_context.query_pipeline.clone();
let cost_map_clone = cost_map.cloned();
let handle_clone = *handle;
let mut collider_set = rapier_context.colliders.clone();
let mut to_remove = vec![];
for handle in collider_set.iter() {
if !obstructions.iter().map(|v| v.0).any(|x| x == handle.0) {
to_remove.push(handle.0);
}
}
let mut bodies = rapier_context.bodies.clone();
if !to_remove.is_empty() {
let mut islands = rapier_context.islands.clone();
for handle in to_remove {
collider_set.remove(handle, &mut islands, &mut bodies, false);
}
2022-01-26 20:56:52 +00:00
}
let shape_clone = (*shape).clone();
trace!(
"{:?}: path: calculating from {:?} to {:?}",
entity,
coordinates.i32(),
destination
);
let pool = AsyncComputeTaskPool::get();
let task = pool.spawn(async move {
find_path_for_shape(
handle_clone.0,
coordinates_clone,
destination_clone,
&cost_map_clone,
query_pipeline,
collider_set,
bodies,
shape_clone,
)
});
commands.run_if_exists(entity, |mut entity| {
entity.insert(Calculating(task));
entity.remove::<Path>();
entity.remove::<NoPath>();
});
}
}
2021-07-28 01:26:16 +00:00
fn poll_tasks(mut commands: Commands, mut query: Query<(Entity, &mut Calculating)>) {
for (entity, mut calculating) in query.iter_mut() {
2021-07-28 01:26:16 +00:00
if let Some(result) = future::block_on(future::poll_once(&mut **calculating)) {
if let Some(path) = result {
trace!("{:?}: path: result: {:?}", entity, path);
2021-07-28 01:26:16 +00:00
commands.entity(entity).insert(path);
} else {
trace!("{:?}: path: no path", entity);
2021-07-28 01:26:16 +00:00
commands.entity(entity).insert(NoPath);
}
commands.entity(entity).remove::<Calculating>();
2021-05-13 17:25:45 +00:00
}
}
}
2022-01-21 00:06:20 +00:00
fn remove_destination(
mut commands: Commands,
entities: &Entities,
removed: RemovedComponents<Destination>,
) {
2021-07-28 01:26:16 +00:00
for entity in removed.iter() {
2022-01-21 00:06:20 +00:00
if entities.contains(entity) {
commands.entity(entity).remove::<Calculating>();
}
2021-07-28 01:26:16 +00:00
}
}
2021-06-15 22:48:49 +00:00
fn negotiate_path(
2021-05-13 17:25:45 +00:00
mut commands: Commands,
mut query: Query<(
Entity,
&mut ActionState<NavigationAction>,
2021-05-13 17:25:45 +00:00
&mut Path,
2022-05-06 16:07:59 +00:00
&mut Transform,
2021-05-13 17:25:45 +00:00
Option<&RotationSpeed>,
)>,
) {
for (entity, mut actions, mut path, mut transform, rotation_speed) in &mut query {
2022-05-06 16:07:59 +00:00
let start_i32 = (transform.translation.x, transform.translation.y).i32();
trace!(
"{entity:?}: start pathfinding from {start_i32:?} to {:?}: {:?}",
path.last(),
transform.translation.truncate()
);
let mut cleanup = false;
if let Some(last) = path.last() {
if last == &start_i32 {
trace!("{entity:?}: path: at destination");
cleanup = true;
2021-05-13 17:25:45 +00:00
}
}
if !cleanup {
if let Some(position) = path.iter().position(|v| v == &start_i32) {
trace!("{entity:?}: Path contains start");
let (_, new_path) = path.split_at(position + 1);
**path = new_path.to_vec();
}
if let Some(next) = path.first() {
trace!("{entity:?}: path: moving from {start_i32:?} to {next:?}");
let start = Vec2::new(start_i32.x(), start_i32.y());
let next = Vec2::new(next.0 as f32, next.1 as f32);
let mut direction = next - start;
direction = direction.normalize();
trace!("{entity:?}: path: direction: {direction:?}");
actions.press(NavigationAction::Move);
actions.action_data_mut(NavigationAction::Move).axis_pair =
Some(DualAxisData::from_xy(Vec2::new(-direction.y, direction.x)));
trace!(
"{entity:?}: path: move: {:?}",
Vec2::new(direction.y, -direction.x)
);
if rotation_speed.is_some() {
let angle = direction.y.atan2(direction.x);
transform.rotation = Quat::from_rotation_z(angle);
}
} else {
trace!("{entity:?}: empty path, cleaning");
cleanup = true;
}
}
if cleanup {
2021-07-27 16:17:06 +00:00
commands
.entity(entity)
.remove::<Path>()
2021-09-16 19:26:45 +00:00
.remove::<NoPath>()
.remove::<Destination>();
actions.release(NavigationAction::Move);
trace!("{entity:?}: pathfinding: cleaned up");
}
}
}
fn actions(
mut commands: Commands,
mut query: Query<(
Entity,
&ActionState<NegotiatePathAction>,
Option<&mut Destination>,
)>,
) {
for (entity, actions, destination) in &mut query {
if actions.pressed(NegotiatePathAction) {
if let Some(pair) = actions.axis_pair(NegotiatePathAction) {
let dest = Destination((pair.x() as i32, pair.y() as i32));
if let Some(mut current_dest) = destination {
if *current_dest != dest {
*current_dest = dest;
}
} else {
commands.entity(entity).insert(dest);
}
} else if destination.is_some() {
commands.entity(entity).remove::<Destination>();
}
2021-05-13 17:25:45 +00:00
}
}
2021-06-15 22:48:49 +00:00
}
2021-05-13 17:25:45 +00:00
pub struct PathfindingPlugin;
2022-03-15 15:37:28 +00:00
impl Plugin for PathfindingPlugin {
2022-01-10 19:50:52 +00:00
fn build(&self, app: &mut App) {
app.add_plugin(InputManagerPlugin::<NegotiatePathAction>::default())
.register_type::<Destination>()
2022-12-19 20:08:31 +00:00
.register_type::<NoPath>()
.register_type::<Path>()
.register_type::<CostMap>()
.add_system(calculate_path)
2022-01-10 19:50:52 +00:00
.add_system_to_stage(CoreStage::PostUpdate, remove_destination)
.add_system(poll_tasks)
.add_system_to_stage(
CoreStage::PreUpdate,
negotiate_path.after(InputManagerSystem::Tick),
)
.add_system(actions);
2021-05-13 17:25:45 +00:00
}
}