blackout/src/pathfinding.rs

313 lines
11 KiB
Rust
Raw Normal View History

2024-10-06 16:10:46 +00:00
use avian2d::prelude::*;
use bevy::{prelude::*, utils::HashMap};
use leafwing_input_manager::{plugin::InputManagerSystem, prelude::*};
2021-05-13 17:25:45 +00:00
use pathfinding::prelude::*;
use crate::{
core::{GlobalTransformExt, Obstacle},
navigation::{NavigationAction, RotationSpeed, Speed},
2021-05-13 17:25:45 +00:00
};
2023-09-08 21:12:35 +00:00
#[derive(PartialEq, Eq, Clone, Copy, Hash, Debug, Reflect)]
pub struct NegotiatePathAction;
impl Actionlike for NegotiatePathAction {
fn input_control_kind(&self) -> InputControlKind {
InputControlKind::DualAxis
}
}
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)]
2024-12-04 22:51:25 +00:00
pub struct Destination(pub IVec2);
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)]
2024-12-04 22:51:25 +00:00
pub struct Path(pub Vec<IVec2>);
2021-05-13 17:25:45 +00:00
#[derive(Component, Clone, Debug, Default, Reflect, Deref, DerefMut)]
#[reflect(Component)]
2024-12-04 22:51:25 +00:00
pub struct CostMap(pub HashMap<IVec2, f32>);
#[derive(Bundle, Deref, DerefMut)]
pub struct PathfindingControlsBundle(pub ActionState<NegotiatePathAction>);
impl Default for PathfindingControlsBundle {
fn default() -> Self {
let mut input: ActionState<NegotiatePathAction> = Default::default();
input.disable();
Self(input)
}
}
fn calculate_path(
2021-05-13 17:25:45 +00:00
mut commands: Commands,
2024-10-06 16:10:46 +00:00
spatial_query: SpatialQuery,
mut query: Query<
2022-05-06 16:07:59 +00:00
(
Entity,
&Destination,
2024-12-04 22:51:25 +00:00
&GlobalTransform,
2022-05-06 16:07:59 +00:00
&Collider,
Option<&CostMap>,
2024-10-06 16:10:46 +00:00
Option<&mut ActionState<NegotiatePathAction>>,
2022-05-06 16:07:59 +00:00
),
2022-01-10 19:50:52 +00:00
Changed<Destination>,
>,
2025-01-07 01:33:46 +00:00
obstacles: Query<Entity, With<Obstacle>>,
2024-12-10 19:17:55 +00:00
sensors: Query<Entity, With<Sensor>>,
2021-05-13 17:25:45 +00:00
) {
2024-12-04 22:51:25 +00:00
for (entity, destination, transform, collider, cost_map, actions) in &mut query {
2024-10-06 16:10:46 +00:00
trace!("{entity}: destination: {destination:?}");
2025-01-07 01:33:46 +00:00
commands.entity(entity).remove::<Path>().remove::<NoPath>();
2024-12-04 22:51:25 +00:00
if transform.translation().truncate().as_ivec2() == **destination {
2025-01-07 01:33:46 +00:00
commands.entity(entity).remove::<Destination>();
2021-07-21 20:20:02 +00:00
continue;
}
2024-10-06 16:10:46 +00:00
let path = astar(
2024-12-04 22:51:25 +00:00
&transform.translation().truncate().as_ivec2(),
2024-10-06 16:10:46 +00:00
|p| {
2025-01-07 01:33:46 +00:00
let mut start = Vec2::new(p.x as f32, p.y as f32);
if start.x >= 0. {
start.x += 0.5;
} else {
start.x -= 0.5;
}
if start.y >= 0. {
start.y += 0.5;
} else {
start.y -= 0.5;
}
2024-12-04 22:51:25 +00:00
let mut successors: Vec<(IVec2, u32)> = vec![];
let x = p.x;
let y = p.y;
2024-10-06 16:10:46 +00:00
let exits = vec![
2024-12-04 22:51:25 +00:00
(IVec2::new(x - 1, y), 1.),
(IVec2::new(x + 1, y), 1.),
(IVec2::new(x, y - 1), 1.),
(IVec2::new(x, y + 1), 1.),
(IVec2::new(x - 1, y - 1), 1.5),
(IVec2::new(x + 1, y - 1), 1.5),
(IVec2::new(x - 1, y + 1), 1.5),
(IVec2::new(x + 1, y + 1), 1.5),
2024-10-06 16:10:46 +00:00
];
for exit in &exits {
2024-12-10 19:17:55 +00:00
let mut check = exit.0.as_vec2();
if check.x >= 0. {
check.x += 0.5;
} else {
check.x -= 0.5;
}
if check.y >= 0. {
check.y += 0.5;
} else {
check.y -= 0.5;
}
2025-01-07 01:33:46 +00:00
let dir = (check - start).normalize();
let dir = Dir2::new_unchecked(dir);
let delta = (check - start).length();
let hits = spatial_query.cast_shape_predicate(
2024-12-10 19:17:55 +00:00
collider,
2025-01-07 01:33:46 +00:00
start,
2024-12-10 19:17:55 +00:00
transform.yaw().as_radians(),
2025-01-07 01:33:46 +00:00
dir,
&ShapeCastConfig {
max_distance: delta,
ignore_origin_penetration: true,
..default()
},
2025-01-03 19:19:57 +00:00
&SpatialQueryFilter::from_excluded_entities(&sensors),
2025-01-07 01:33:46 +00:00
&|entity| obstacles.contains(entity),
2024-12-10 19:17:55 +00:00
);
2025-01-07 01:33:46 +00:00
if hits.is_none() {
2024-10-06 16:10:46 +00:00
let mut cost = exit.1 * 100.;
if let Some(cost_map) = cost_map {
2024-12-04 22:51:25 +00:00
if let Some(modifier) = cost_map.get(&exit.0) {
2024-10-06 16:10:46 +00:00
cost *= modifier;
}
}
successors.push((exit.0, cost as u32));
}
}
2024-10-06 16:10:46 +00:00
successors
},
2024-12-04 22:51:25 +00:00
|p| (p.distance_squared(**destination) * 100) as u32,
|p| *p == **destination,
2024-10-06 16:10:46 +00:00
);
if let Some(path) = path {
commands.entity(entity).insert(Path(path.0));
} else {
commands.entity(entity).insert(NoPath);
if let Some(mut actions) = actions {
trace!("{entity:?}: Disabling and resetting because no path");
actions.disable();
actions.reset_all();
}
2021-05-13 17:25:45 +00:00
}
}
}
fn remove_destination(mut commands: Commands, mut removed: RemovedComponents<Destination>) {
2024-02-09 20:59:16 +00:00
for entity in removed.read() {
if let Some(mut commands) = commands.get_entity(entity) {
2024-10-06 16:10:46 +00:00
commands.remove::<Path>().remove::<NoPath>();
2022-01-21 00:06:20 +00:00
}
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,
time: Res<Time>,
2024-12-10 19:17:55 +00:00
physics_time: Res<Time<Physics>>,
2024-10-06 16:10:46 +00:00
spatial_query: SpatialQuery,
2021-05-13 17:25:45 +00:00
mut query: Query<(
Entity,
Option<&mut ActionState<NegotiatePathAction>>,
&mut ActionState<NavigationAction>,
2021-05-13 17:25:45 +00:00
&mut Path,
2022-05-06 16:07:59 +00:00
&mut Transform,
2024-12-10 19:17:55 +00:00
&GlobalTransform,
2023-03-28 13:49:17 +00:00
&Collider,
&Speed,
2021-05-13 17:25:45 +00:00
Option<&RotationSpeed>,
)>,
2024-12-10 19:17:55 +00:00
sensors: Query<Entity, With<Sensor>>,
2021-05-13 17:25:45 +00:00
) {
2024-12-10 19:17:55 +00:00
if physics_time.is_paused() {
return;
}
for (
entity,
actions,
mut navigation_actions,
mut path,
mut transform,
2024-12-10 19:17:55 +00:00
global_transform,
collider,
speed,
rotation_speed,
) in &mut query
{
trace!("{entity:?}: negotiating path");
2025-01-07 01:33:46 +00:00
let start = global_transform.translation().truncate().as_ivec2();
2024-12-04 22:51:25 +00:00
if path.len() > 0 && path[0] == start {
trace!("At start, removing");
path.remove(0);
}
2023-03-28 13:49:17 +00:00
if let Some(next) = path.first() {
2024-12-10 19:17:55 +00:00
let start = global_transform.translation().truncate();
2024-12-04 22:51:25 +00:00
let mut next = next.as_vec2();
if next.x >= 0. {
2023-03-28 13:49:17 +00:00
next.x += 0.5;
} else {
next.x -= 0.5;
}
if next.y >= 0. {
2023-03-28 13:49:17 +00:00
next.y += 0.5;
} else {
2023-03-28 13:49:17 +00:00
next.y -= 0.5;
}
2025-01-07 01:33:46 +00:00
let direction = (next - start).normalize();
let delta = time.delta_secs() * **speed;
let dir = Dir2::new_unchecked(direction);
let hit = spatial_query.cast_shape(
2024-12-10 19:17:55 +00:00
collider,
start,
global_transform.yaw().as_radians(),
2025-01-07 01:33:46 +00:00
dir,
2025-01-03 19:19:57 +00:00
&ShapeCastConfig {
2025-01-07 01:33:46 +00:00
max_distance: delta,
2025-01-03 19:19:57 +00:00
ignore_origin_penetration: true,
..default()
},
&SpatialQueryFilter::from_excluded_entities(&sensors),
2023-03-28 13:49:17 +00:00
);
2025-01-07 01:33:46 +00:00
if hit.is_some() {
// println!("{entity} is stuck");
2023-03-28 13:49:17 +00:00
// TODO: Remove when we have an actual character controller.
next.x = next.x.trunc();
next.y = next.y.trunc();
2023-03-28 13:49:17 +00:00
transform.translation = next.extend(0.);
} else {
2025-01-07 01:33:46 +00:00
// println!("Translating: {:?}", direction * delta);
navigation_actions.set_axis_pair(&NavigationAction::Translate, direction * delta);
2023-03-28 13:49:17 +00:00
}
if rotation_speed.is_some() {
let angle = direction.y.atan2(direction.x);
transform.rotation = Quat::from_rotation_z(angle);
}
} else {
2024-10-06 16:10:46 +00:00
trace!("{entity:?}: empty path, cleaning");
if let Some(mut actions) = actions {
trace!("{entity:?}: Disabling pathfind because at destination");
actions.reset_all();
actions.disable();
}
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>();
trace!("{entity:?}: pathfinding: cleaned up");
}
}
}
fn actions(
mut commands: Commands,
mut query: Query<(
Entity,
&ActionState<NegotiatePathAction>,
2023-03-28 13:49:17 +00:00
&mut ActionState<NavigationAction>,
Option<&mut Destination>,
)>,
) {
for (entity, actions, mut navigation_action, destination) in &mut query {
if actions.action_disabled(&NegotiatePathAction) {
if destination.is_some() {
commands.entity(entity).remove::<Destination>();
}
} else {
let pair = actions.axis_pair(&NegotiatePathAction);
trace!("{entity:?}: Negotiating path to {pair:?}");
2024-12-04 22:51:25 +00:00
let dest = Destination(pair.as_ivec2());
if let Some(mut current_dest) = destination {
trace!("Got a destination");
if *current_dest != dest {
2025-01-07 01:33:46 +00:00
trace!("{entity:?}: New destination {dest:?} differs from {current_dest:?}, zeroing velocity");
navigation_action
.set_axis_pair(&NavigationAction::SetLinearVelocity, Vec2::ZERO);
*current_dest = dest;
}
} else {
2024-10-06 16:10:46 +00:00
trace!("{entity:?}: Adding destination, zeroing velocity");
navigation_action.set_axis_pair(&NavigationAction::SetLinearVelocity, Vec2::ZERO);
commands.entity(entity).insert(dest);
}
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_plugins((InputManagerPlugin::<NegotiatePathAction>::default(),))
.register_type::<Destination>()
2022-12-19 20:08:31 +00:00
.register_type::<NoPath>()
.register_type::<Path>()
.register_type::<CostMap>()
.add_systems(
2025-01-07 01:33:46 +00:00
FixedPreUpdate,
(actions, calculate_path, negotiate_path)
2023-03-28 16:57:37 +00:00
.chain()
.after(InputManagerSystem::Tick),
)
.add_systems(PostUpdate, remove_destination);
2021-05-13 17:25:45 +00:00
}
}