use std::marker::PhantomData; use bevy::{ ecs::entity::Entities, prelude::*, tasks::{prelude::*, Task}, }; use bevy_rapier2d::{ na::{Isometry2, Vector2}, prelude::*, rapier::prelude::{ColliderHandle, ColliderSet, QueryPipeline}, }; use futures_lite::future; use pathfinding::prelude::*; use crate::{ commands::RunIfExistsExt, core::{Coordinates, PointLike}, map::{Map, MapObstruction}, navigation::{RotationSpeed, Speed}, }; #[derive(Component, Debug, Deref, DerefMut)] struct Calculating(Task<Option<Path>>); #[derive(Component, Clone, Copy, Debug, Default, Deref, DerefMut, Eq, Hash, PartialEq, Reflect)] #[reflect(Component)] pub struct Destination(pub (i32, i32)); impl_pointlike_for_tuple_component!(Destination); #[derive(Component, Clone, Debug, Default, Reflect)] #[reflect(Component)] pub struct NoPath; #[derive(Component, Clone, Debug, Default, Deref, DerefMut, Reflect)] #[reflect(Component)] pub struct Path(pub Vec<(i32, i32)>); pub fn find_path<D: 'static + Clone + Default + Send + Sync>( start: &dyn PointLike, destination: &dyn PointLike, map: &Map<D>, ) -> Option<(Vec<(i32, i32)>, u32)> { astar( &start.into(), |p| { let mut successors: Vec<((i32, i32), u32)> = vec![]; if map.at(p.0 as usize, p.1 as usize).is_walkable() { for tile in map.get_available_exits(p.0 as usize, p.1 as usize) { successors.push(((tile.0 as i32, tile.1 as i32), (tile.2 * 100.) as u32)); } } successors }, |p| (p.distance_squared(destination) * 100.) as u32, |p| *p == destination.into(), ) } fn find_path_for_shape<D: 'static + Clone + Default + Send + Sync>( initiator: ColliderHandle, start: Coordinates, destination: Destination, map: Map<D>, query_pipeline: QueryPipeline, collider_set: ColliderSet, shape: Collider, ) -> Option<Path> { let path = astar( &start.i32(), |p| { let mut successors: Vec<((i32, i32), u32)> = vec![]; if map.at(p.0 as usize, p.1 as usize).is_walkable() { for tile in map.get_available_exits(p.0 as usize, p.1 as usize) { let mut should_push = true; let shape_pos = Isometry2::new(Vector2::new(tile.0 as f32, tile.1 as f32), 0.); query_pipeline.intersections_with_shape( &collider_set, &shape_pos, &*shape.raw, InteractionGroups::all(), Some(&|v| v != initiator), |_handle| { should_push = false; false }, ); if should_push { successors.push(((tile.0 as i32, tile.1 as i32), (tile.2 * 100.) 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<D: 'static + Clone + Default + Send + Sync>( mut commands: Commands, pool: Res<AsyncComputeTaskPool>, rapier_context: Res<RapierContext>, obstructions: Query<&MapObstruction>, query: Query< ( Entity, &RapierColliderHandle, &Destination, &Coordinates, &Collider, ), Changed<Destination>, >, map: Query<&Map<D>>, ) { for (entity, handle, destination, coordinates, shape) in query.iter() { if coordinates.i32() == **destination { commands .entity(entity) .remove::<Path>() .remove::<NoPath>() .remove::<Calculating>() .remove::<Destination>() .remove::<Speed>(); continue; } if let Ok(map) = map.get_single() { let coordinates_clone = *coordinates; let destination_clone = *destination; let query_pipeline = rapier_context.query_pipeline.clone(); let map_clone = map.clone(); let handle_clone = *handle; let collider_set = rapier_context.colliders.clone(); let shape_clone = (*shape).clone(); let task = pool.spawn(async move { find_path_for_shape( handle_clone.0, coordinates_clone, destination_clone, map_clone, query_pipeline, collider_set, shape_clone, ) }); commands.run_if_exists(entity, |mut entity| { entity.insert(Calculating(task)); entity.remove::<Path>(); entity.remove::<NoPath>(); }); } } } fn poll_tasks(mut commands: Commands, mut query: Query<(Entity, &mut Calculating)>) { for (entity, mut calculating) in query.iter_mut() { if let Some(result) = future::block_on(future::poll_once(&mut **calculating)) { if let Some(path) = result { commands.entity(entity).insert(path); } else { commands.entity(entity).insert(NoPath); } commands.entity(entity).remove::<Calculating>(); } } } fn remove_destination( mut commands: Commands, entities: &Entities, removed: RemovedComponents<Destination>, ) { for entity in removed.iter() { if entities.contains(entity) { commands.entity(entity).remove::<Calculating>(); } } } fn negotiate_path( mut commands: Commands, mut query: Query<( Entity, &mut Path, &mut Transform, &mut Velocity, &Speed, Option<&RotationSpeed>, )>, ) { for (entity, mut path, mut transform, mut velocity, speed, rotation_speed) in query.iter_mut() { let start_i32 = (transform.translation.x, transform.translation.y).i32(); let mut new_path = path .iter() .cloned() .skip_while(|v| *v == start_i32) .collect::<Vec<(i32, i32)>>(); new_path.retain(|v| *v != start_i32); **path = new_path; if let Some(next) = path.first() { let start = Vec2::new(transform.translation.x, transform.translation.y); let next = Vec2::new(next.0 as f32 + 0.5, next.1 as f32 + 0.5); let mut direction = next - start; direction = direction.normalize(); direction *= **speed; velocity.linvel = direction; if rotation_speed.is_some() { let v = next - start; let angle = v.y.atan2(v.x); transform.rotation = Quat::from_rotation_z(angle); } continue; } else { commands .entity(entity) .remove::<Path>() .remove::<NoPath>() .remove::<Destination>() .remove::<Speed>(); velocity.linvel = Vec2::ZERO; } } } pub struct PathfindingPlugin<D: 'static + Clone + Default + Send + Sync>(PhantomData<D>); impl<D: 'static + Clone + Default + Send + Sync> Default for PathfindingPlugin<D> { fn default() -> Self { Self(Default::default()) } } impl<D: 'static + Clone + Default + Send + Sync> Plugin for PathfindingPlugin<D> { fn build(&self, app: &mut App) { app.add_system(calculate_path::<D>) .add_system_to_stage(CoreStage::PostUpdate, remove_destination) .add_system(poll_tasks) .add_system(negotiate_path); } }